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,5 +1,6 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
/** The dashboard's boot, browser-side: view routing over #fragment links (Back/Forward and
|
|
2
|
-
* bookmarks work, re-clicking the open tab refetches), #
|
|
3
|
+
* bookmarks work, re-clicking the open tab refetches), #role/<role> opening that loop's
|
|
3
4
|
* drawer with the address bar following it, the global action dispatcher behind the
|
|
4
5
|
* data-act/data-open attributes (alert and tile buttons, loop names anywhere, questions),
|
|
5
6
|
* the keyboard shortcuts (Escape closes the drawer, "/" focuses the composer), the theme
|
|
@@ -7,11 +8,19 @@
|
|
|
7
8
|
* when the serving build moves. Spliced into gui-client.ts's script as its last section,
|
|
8
9
|
* reaching the shared state and the render helpers of every other section through that
|
|
9
10
|
* concatenation. */
|
|
10
|
-
export const GUI_CLIENT_BOOT_JS = String.raw ` //
|
|
11
|
+
export const GUI_CLIENT_BOOT_JS = String.raw ` // One tail for the "…written to the file but the worktree was left dirty" toasts: the
|
|
12
|
+
// cancel/edit/file backlog actions share the same committed===false copy, kept in one place
|
|
13
|
+
// so the operator-facing wording about an uncommitted edit cannot drift between them. The
|
|
14
|
+
// question-answer toast names QUESTIONS.md explicitly, so it stays inline.
|
|
15
|
+
function uncommittedTail(d) {
|
|
16
|
+
return d.committed === false ? ", but NOT committed to main (uncommitted in the checkout)" : "";
|
|
17
|
+
}
|
|
18
|
+
// ---- views ----
|
|
11
19
|
// view-routing:start
|
|
12
|
-
const VIEWS = {
|
|
20
|
+
const VIEWS = { team: "team-view", history: "history", usage: "report", failures: "failures", pending: "pending", settings: "settings-view" }; // vocabulary-ok: view ids
|
|
13
21
|
function switchView(v) {
|
|
14
|
-
if (
|
|
22
|
+
if (v === "fleet") v = "team"; // vocabulary-ok: legacy dashboard hash, still routed
|
|
23
|
+
if (!VIEWS[v]) v = "team";
|
|
15
24
|
activeView = v;
|
|
16
25
|
for (const k of Object.keys(VIEWS)) {
|
|
17
26
|
$(VIEWS[k]).hidden = k !== v;
|
|
@@ -20,15 +29,15 @@ export const GUI_CLIENT_BOOT_JS = String.raw ` // ---- views ----
|
|
|
20
29
|
if (k === v) tab.setAttribute("aria-current", "page");
|
|
21
30
|
else tab.removeAttribute("aria-current");
|
|
22
31
|
}
|
|
23
|
-
if (v === "
|
|
32
|
+
if (v === "team" && lastStatus) renderTeam(lastStatus);
|
|
24
33
|
if (v === "history") fetchHistory();
|
|
25
34
|
if (v === "usage") fetchReport();
|
|
26
35
|
if (v === "failures") fetchFailures();
|
|
27
36
|
if (v === "pending") fetchPending();
|
|
28
|
-
if (v === "settings") { fetchSettings(); fetchDoctor(); fetchBrief(); }
|
|
37
|
+
if (v === "settings") { fetchSettings(); fetchDoctor(); fetchRetirable(); fetchBrief(); }
|
|
29
38
|
}
|
|
30
39
|
// Tabs are plain #fragment links, so Back/Forward and bookmarks work; re-clicking the open
|
|
31
|
-
// tab refetches its data. #
|
|
40
|
+
// tab refetches its data. #role/<role> opens that loop's drawer (over Team on a fresh load),
|
|
32
41
|
// and the address bar follows the open drawer, so a loop's live view can be linked to.
|
|
33
42
|
let routed = false;
|
|
34
43
|
function route() {
|
|
@@ -38,9 +47,9 @@ export const GUI_CLIENT_BOOT_JS = String.raw ` // ---- views ----
|
|
|
38
47
|
// script and the dashboard never polls again — a frozen page from a hand-edited URL.
|
|
39
48
|
let h = location.hash.slice(1);
|
|
40
49
|
try { h = decodeURIComponent(h); } catch { /* malformed escape: route on the raw fragment */ }
|
|
41
|
-
if (h.startsWith("loop/")) {
|
|
42
|
-
if (!routed) switchView("
|
|
43
|
-
if (openLoopRole() !== h.slice(
|
|
50
|
+
if (h.startsWith("role/") || h.startsWith("loop/")) { // vocabulary-ok: legacy dashboard hash, still routed
|
|
51
|
+
if (!routed) switchView("team");
|
|
52
|
+
if (openLoopRole() !== h.slice(h.indexOf("/") + 1)) openLoop(h.slice(h.indexOf("/") + 1));
|
|
44
53
|
} else switchView(h);
|
|
45
54
|
routed = true;
|
|
46
55
|
}
|
|
@@ -75,26 +84,43 @@ export const GUI_CLIENT_BOOT_JS = String.raw ` // ---- views ----
|
|
|
75
84
|
// The Health check card's Re-run button: runs the doctor checks again.
|
|
76
85
|
document.addEventListener("click", (ev) => {
|
|
77
86
|
if (clickClosest(ev, "button[data-action='doctor-rerun']")) { ev.preventDefault(); fetchDoctor(); }
|
|
87
|
+
if (clickClosest(ev, "button[data-action='notify-test']")) { ev.preventDefault(); testNotify(); }
|
|
88
|
+
const retire = clickClosest(ev, "button[data-action='retire']");
|
|
89
|
+
if (retire) { ev.preventDefault(); confirmRetire(retire.dataset.role); }
|
|
90
|
+
const enable = clickClosest(ev, "button[data-action='enable']");
|
|
91
|
+
if (enable) { ev.preventDefault(); enableRole(enable.dataset.role); }
|
|
92
|
+
});
|
|
93
|
+
// The Health card's Problems only checkbox: repaint the cached report with only its warn
|
|
94
|
+
// and fail rows (toggleDoctorProblems, gui-client-settings), without rerunning the checks.
|
|
95
|
+
document.addEventListener("change", (ev) => {
|
|
96
|
+
const box = clickClosest(ev, "input[data-action='doctor-problems']");
|
|
97
|
+
if (box) toggleDoctorProblems(box.checked);
|
|
78
98
|
});
|
|
79
99
|
// ---- global actions: alert and tile buttons, loop names anywhere, questions ----
|
|
80
100
|
// run-act:start
|
|
101
|
+
// The backlog drawer rows resolve a list entry's title from the last polled status by its
|
|
102
|
+
// backlog file and 0-based position (entry-cancel, entry-edit, and the mention action). The
|
|
103
|
+
// call sites keep their arg's "file:index" split because that shape is theirs alone.
|
|
104
|
+
function entryTitleAt(file, index) {
|
|
105
|
+
return ((lastStatus && lastStatus[file]) || [])[Number(index)];
|
|
106
|
+
}
|
|
81
107
|
function runAct(act, arg) {
|
|
82
|
-
if (act === "
|
|
108
|
+
if (act === "role") openLoop(arg);
|
|
83
109
|
else if (act === "close") closeDrawer();
|
|
84
110
|
else if (act === "view") location.hash = arg;
|
|
85
111
|
else if (act === "budget") openBudgetEditor();
|
|
86
|
-
else if (act === "resume")
|
|
112
|
+
else if (act === "resume") setTeamPause(false);
|
|
87
113
|
else if (act === "copy") copyText(arg);
|
|
88
114
|
else if (act === "restart")
|
|
89
115
|
postAction("/api/restart", {}, (d) =>
|
|
90
116
|
d && typeof d.message === "string"
|
|
91
117
|
? d.message
|
|
92
|
-
: "Restart requested —
|
|
118
|
+
: "Restart requested — tumwater applies it within one poll");
|
|
93
119
|
else if (act === "reclaim")
|
|
94
120
|
postAction("/api/reclaim", {}, (d) =>
|
|
95
121
|
d && typeof d.message === "string" ? d.message : "Reclaim requested");
|
|
96
122
|
else if (act === "questions" || act === "queued" || act === "backlog") {
|
|
97
|
-
if (activeView !== "
|
|
123
|
+
if (activeView !== "team") location.hash = "team";
|
|
98
124
|
if (act !== "backlog") setBacklogTab(act);
|
|
99
125
|
$("backlogcard").scrollIntoView({ behavior: "smooth", block: "start" });
|
|
100
126
|
} else if (act === "answer") {
|
|
@@ -114,19 +140,45 @@ export const GUI_CLIENT_BOOT_JS = String.raw ` // ---- views ----
|
|
|
114
140
|
});
|
|
115
141
|
} else if (act === "entry-cancel") {
|
|
116
142
|
const [file, index] = arg.split(":");
|
|
117
|
-
const title = (
|
|
143
|
+
const title = entryTitleAt(file, index);
|
|
118
144
|
if (title === undefined) return;
|
|
119
145
|
if (!entryCancelArmed(arg, title)) { armEntryCancel(arg, title); return; }
|
|
120
146
|
disarmEntryCancel();
|
|
121
147
|
postAction("/api/backlog-cancel", { kind: file === "bugs" ? "bug" : "plan", position: Number(index) + 1, title }, (d) => {
|
|
122
148
|
closeDrawer();
|
|
123
149
|
return d && d.status === "cancelled"
|
|
124
|
-
? "Cancelled “" + splitTitle(title).title + "” from " + (file === "bugs" ? "BUGS.md" : "PLANS.md") + (d
|
|
150
|
+
? "Cancelled “" + splitTitle(title).title + "” from " + (file === "bugs" ? "BUGS.md" : "PLANS.md") + uncommittedTail(d)
|
|
125
151
|
: "That entry was already removed or moved — refreshed";
|
|
126
152
|
});
|
|
153
|
+
} else if (act === "entry-release") {
|
|
154
|
+
const [file, index] = arg.split(":");
|
|
155
|
+
const title = entryTitleAt(file, index);
|
|
156
|
+
if (title === undefined) return;
|
|
157
|
+
postAction("/api/backlog-release", { kind: file === "bugs" ? "bug" : "plan", position: Number(index) + 1, title }, (d) => {
|
|
158
|
+
closeDrawer();
|
|
159
|
+
if (d && d.status === "released") {
|
|
160
|
+
return "Released “" + splitTitle(title).title + "” in " + (file === "bugs" ? "BUGS.md" : "PLANS.md") + " — " + (file === "bugs" ? "bugfix" : "feature") + " will pick it up" + uncommittedTail(d);
|
|
161
|
+
}
|
|
162
|
+
return d && d.status === "not_refused"
|
|
163
|
+
? "That entry carries no Refused or Waiting note — refreshed"
|
|
164
|
+
: "That entry was removed or moved — refreshed";
|
|
165
|
+
});
|
|
166
|
+
} else if (act === "entry-top") {
|
|
167
|
+
const [file, index] = arg.split(":");
|
|
168
|
+
const title = entryTitleAt(file, index);
|
|
169
|
+
if (title === undefined) return;
|
|
170
|
+
postAction("/api/backlog-top", { kind: file === "bugs" ? "bug" : "plan", position: Number(index) + 1, title }, (d) => {
|
|
171
|
+
closeDrawer();
|
|
172
|
+
if (d && d.status === "moved_to_top") {
|
|
173
|
+
return "Moved “" + splitTitle(title).title + "” to the top of " + (file === "bugs" ? "BUGS.md" : "PLANS.md") + " — " + (file === "bugs" ? "bugfix" : "feature") + " takes it next" + uncommittedTail(d);
|
|
174
|
+
}
|
|
175
|
+
return d && d.status === "already_first"
|
|
176
|
+
? "That entry is already first — refreshed"
|
|
177
|
+
: "That entry was removed or moved — refreshed";
|
|
178
|
+
});
|
|
127
179
|
} else if (act === "entry-edit") {
|
|
128
180
|
const [file, index] = arg.split(":");
|
|
129
|
-
const title = (
|
|
181
|
+
const title = entryTitleAt(file, index);
|
|
130
182
|
if (title !== undefined) startEntryEdit(arg, title);
|
|
131
183
|
} else if (act === "entry-edit-discard") {
|
|
132
184
|
stopEntryEdit();
|
|
@@ -145,11 +197,11 @@ export const GUI_CLIENT_BOOT_JS = String.raw ` // ---- views ----
|
|
|
145
197
|
stopEntryEdit();
|
|
146
198
|
closeDrawer();
|
|
147
199
|
return d && d.status === "edited"
|
|
148
|
-
? "Edited “" + splitTitle(d.title).title + "” in " + (file === "bugs" ? "BUGS.md" : "PLANS.md") + (d
|
|
200
|
+
? "Edited “" + splitTitle(d.title).title + "” in " + (file === "bugs" ? "BUGS.md" : "PLANS.md") + uncommittedTail(d)
|
|
149
201
|
: "That entry was removed or moved — refreshed";
|
|
150
202
|
});
|
|
151
203
|
} else if (act === "note-edit") {
|
|
152
|
-
const note = drawerRole && drawerRole.view && typeof drawerRole.view.
|
|
204
|
+
const note = drawerRole && drawerRole.view && typeof drawerRole.view.notes === "string" ? drawerRole.view.notes : "";
|
|
153
205
|
startNoteEdit(arg, note);
|
|
154
206
|
} else if (act === "note-discard") {
|
|
155
207
|
stopNoteEdit();
|
|
@@ -165,11 +217,129 @@ export const GUI_CLIENT_BOOT_JS = String.raw ` // ---- views ----
|
|
|
165
217
|
postAction("/api/role-notes", { role: arg, text }, (d) => {
|
|
166
218
|
noteEditing = null;
|
|
167
219
|
loadDrawerRole(arg);
|
|
168
|
-
return d && typeof d.message === "string" ? d.message : "
|
|
220
|
+
return d && typeof d.message === "string" ? d.message : "Notes updated";
|
|
221
|
+
});
|
|
222
|
+
} else if (act === "cap-edit") {
|
|
223
|
+
startCapEdit(arg, drawerRole?.view?.dailyCap?.capUsd ?? 0);
|
|
224
|
+
} else if (act === "cap-discard") {
|
|
225
|
+
stopCapEdit();
|
|
226
|
+
} else if (act === "cap-save") {
|
|
227
|
+
const input = $("capeditinput");
|
|
228
|
+
if (!input) return;
|
|
229
|
+
// A type=number input reports "" both for a field cleared on purpose (no cap) and for
|
|
230
|
+
// text the browser rejected ("$2"); validity.badInput tells them apart, so a typo is an
|
|
231
|
+
// error instead of silently removing the cap — the team editor's guard, in scope from
|
|
232
|
+
// the operator blob concatenated earlier.
|
|
233
|
+
if (input.validity && input.validity.badInput) {
|
|
234
|
+
showFlash("error: the cap must be a number of 0 or more — leave it empty for no cap");
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
const parsed = parseBudgetField(input.value);
|
|
238
|
+
if (!parsed.ok) {
|
|
239
|
+
showFlash("error: " + parsed.error);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const value = parsed.value;
|
|
243
|
+
postAction("/api/role-cap", { role: arg, maxDailyCostUsd: value }, (d) => {
|
|
244
|
+
capEditing = null;
|
|
245
|
+
loadDrawerRole(arg);
|
|
246
|
+
return value > 0 ? "Daily cap set to " + fmtCap(value) : "Daily cap removed";
|
|
247
|
+
});
|
|
248
|
+
} else if (act === "interval-edit") {
|
|
249
|
+
startIntervalEdit(arg, drawerRole?.view?.minRunIntervalOverride ?? "");
|
|
250
|
+
} else if (act === "interval-discard") {
|
|
251
|
+
stopIntervalEdit();
|
|
252
|
+
} else if (act === "interval-save") {
|
|
253
|
+
const input = $("intervaleditinput");
|
|
254
|
+
if (!input) return;
|
|
255
|
+
// A type=number input reports "" both for a field cleared on purpose (the team
|
|
256
|
+
// default) and for text the browser rejected ("$2"); validity.badInput tells them
|
|
257
|
+
// apart, so a typo is an error instead of silently clearing the override — the cap
|
|
258
|
+
// arm's guard, in scope from the operator blob concatenated earlier.
|
|
259
|
+
if (input.validity && input.validity.badInput) {
|
|
260
|
+
showFlash("error: the interval must be a number of 0 or more — leave it empty for the team default");
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
const raw = input.value.trim();
|
|
264
|
+
const value = raw === "" ? null : Number(raw);
|
|
265
|
+
postAction("/api/role-interval", { role: arg, minRunIntervalSeconds: value }, (d) => {
|
|
266
|
+
intervalEditing = null;
|
|
267
|
+
loadDrawerRole(arg);
|
|
268
|
+
return d && typeof d.message === "string" ? d.message : "Min run interval updated";
|
|
269
|
+
});
|
|
270
|
+
} else if (act === "instances-edit") {
|
|
271
|
+
startInstancesEdit(arg, drawerRole?.view?.instances ?? "");
|
|
272
|
+
} else if (act === "instances-discard") {
|
|
273
|
+
stopInstancesEdit();
|
|
274
|
+
} else if (act === "instances-save") {
|
|
275
|
+
const input = $("instanceseditinput");
|
|
276
|
+
if (!input) return;
|
|
277
|
+
// A type=number input reports "" both for a field cleared on purpose (the default 1)
|
|
278
|
+
// and for text the browser rejected ("$2"); validity.badInput tells them apart, so a
|
|
279
|
+
// typo is an error instead of silently clearing the count — the interval arm's guard,
|
|
280
|
+
// in scope from the operator blob concatenated earlier. (A rejected "2.5" is a real
|
|
281
|
+
// number the browser accepts, so it posts and validateConfig's 1–8 integer rule 400s
|
|
282
|
+
// it, flashed through postAction's error path.)
|
|
283
|
+
if (input.validity && input.validity.badInput) {
|
|
284
|
+
showFlash("error: the instances must be an integer from 1 to 8 — leave it empty for the default 1");
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const raw = input.value.trim();
|
|
288
|
+
const value = raw === "" ? null : Number(raw);
|
|
289
|
+
postAction("/api/role-instances", { role: arg, instances: value }, (d) => {
|
|
290
|
+
instancesEditing = null;
|
|
291
|
+
loadDrawerRole(arg);
|
|
292
|
+
return d && typeof d.message === "string" ? d.message : "Instances updated";
|
|
293
|
+
});
|
|
294
|
+
} else if (act === "instructions-edit") {
|
|
295
|
+
startInstructionsEdit(arg, drawerRole?.view?.instructions ?? "");
|
|
296
|
+
} else if (act === "instructions-discard") {
|
|
297
|
+
stopInstructionsEdit();
|
|
298
|
+
} else if (act === "instructions-save") {
|
|
299
|
+
const box = $("instructionsedittext");
|
|
300
|
+
const text = box ? box.value : "";
|
|
301
|
+
postAction("/api/role-instructions", { role: arg, text }, (d) => {
|
|
302
|
+
instructionsEditing = null;
|
|
303
|
+
loadDrawerRole(arg);
|
|
304
|
+
return d && typeof d.message === "string" ? d.message : "Instructions updated";
|
|
305
|
+
});
|
|
306
|
+
} else if (act === "quiet-edit") {
|
|
307
|
+
startQuietEdit(arg, drawerRole?.view?.quietHours?.find((w) => w && w.scope === "role")?.window ?? "");
|
|
308
|
+
} else if (act === "quiet-discard") {
|
|
309
|
+
stopQuietEdit();
|
|
310
|
+
} else if (act === "quiet-save") {
|
|
311
|
+
const input = $("quieteditinput");
|
|
312
|
+
const window = input ? input.value : "";
|
|
313
|
+
// A text input has no badInput guard; an empty value reaches the server's clear branch,
|
|
314
|
+
// and a malformed window is refused by checkQuietHours, flashed through postAction's
|
|
315
|
+
// error path.
|
|
316
|
+
postAction("/api/role-quiet-hours", { role: arg, window }, (d) => {
|
|
317
|
+
quietEditing = null;
|
|
318
|
+
loadDrawerRole(arg);
|
|
319
|
+
return d && typeof d.message === "string" ? d.message : "Quiet hours updated";
|
|
320
|
+
});
|
|
321
|
+
} else if (act === "model-edit") {
|
|
322
|
+
startModelEdit(arg, { provider: drawerRole?.view?.providerOverride ?? "", model: drawerRole?.view?.modelOverride ?? "", thinking: drawerRole?.view?.thinkingOverride ?? "" });
|
|
323
|
+
} else if (act === "model-discard") {
|
|
324
|
+
stopModelEdit();
|
|
325
|
+
} else if (act === "model-save") {
|
|
326
|
+
// Every field is a text input, so no badInput guard; an empty value reaches the
|
|
327
|
+
// server's clear branch for that field. All three are sent (each trimmed) — the server
|
|
328
|
+
// ignores whatever it does not recognize and requires at least one present.
|
|
329
|
+
const providerBox = $("modeleditprovider");
|
|
330
|
+
const modelBox = $("modeleditmodel");
|
|
331
|
+
const thinkingBox = $("modeleditthinking");
|
|
332
|
+
const provider = providerBox ? providerBox.value.trim() : "";
|
|
333
|
+
const model = modelBox ? modelBox.value.trim() : "";
|
|
334
|
+
const thinking = thinkingBox ? thinkingBox.value.trim() : "";
|
|
335
|
+
postAction("/api/role-model", { role: arg, provider, model, thinking }, (d) => {
|
|
336
|
+
modelEditing = null;
|
|
337
|
+
loadDrawerRole(arg);
|
|
338
|
+
return d && typeof d.message === "string" ? d.message : "Model overrides updated";
|
|
169
339
|
});
|
|
170
340
|
} else if (act === "mention") {
|
|
171
341
|
const [file, index] = arg.split(":");
|
|
172
|
-
const title = (
|
|
342
|
+
const title = entryTitleAt(file, index);
|
|
173
343
|
if (title !== undefined) draftForDirector("About “" + splitTitle(title).title + "”: ");
|
|
174
344
|
}
|
|
175
345
|
}
|
|
@@ -181,7 +351,10 @@ export const GUI_CLIENT_BOOT_JS = String.raw ` // ---- views ----
|
|
|
181
351
|
// A history row's drill-down button sits inside the row's data-open cell area but must not
|
|
182
352
|
// open the drawer — expansion is the button's alone (gui-client-history's own delegated
|
|
183
353
|
// handler toggles the card).
|
|
184
|
-
if (open && !t.closest("#
|
|
354
|
+
if (open && !t.closest("#rolerows") && !t.closest("[data-tickdetail]")) { // vocabulary-ok: element selectors
|
|
355
|
+
ev.preventDefault();
|
|
356
|
+
toggleLoop(open.dataset.open);
|
|
357
|
+
}
|
|
185
358
|
});
|
|
186
359
|
const typing = (el) => el instanceof Element && el.closest("input, textarea, select, [contenteditable]") !== null;
|
|
187
360
|
document.addEventListener("keydown", (ev) => {
|
|
@@ -210,12 +383,12 @@ export const GUI_CLIENT_BOOT_JS = String.raw ` // ---- views ----
|
|
|
210
383
|
// ---- the poll ----
|
|
211
384
|
function rerender() {
|
|
212
385
|
if (!lastStatus) return;
|
|
213
|
-
if (activeView === "
|
|
386
|
+
if (activeView === "team") renderTeam(lastStatus);
|
|
214
387
|
if (activeView === "pending") renderPendingQueue(lastStatus);
|
|
215
|
-
if (drawer && drawer.kind === "loop") renderLoopDrawer(lastStatus);
|
|
388
|
+
if (drawer && drawer.kind === "loop") renderLoopDrawer(lastStatus); // vocabulary-ok: drawer kind
|
|
216
389
|
}
|
|
217
390
|
let lastEventKey = null;
|
|
218
|
-
// New events are what move the numbers the other panels show (today's
|
|
391
|
+
// New events are what move the numbers the other panels show (today's merges, the History
|
|
219
392
|
// rows, the open loop's recent ticks), so they refetch on that signal, each throttled.
|
|
220
393
|
function onNewEvents() {
|
|
221
394
|
refreshToday(false);
|
|
@@ -227,7 +400,7 @@ export const GUI_CLIENT_BOOT_JS = String.raw ` // ---- views ----
|
|
|
227
400
|
let d;
|
|
228
401
|
try {
|
|
229
402
|
// The guarded getJson: the server's 500 sends a JSON {error} body, which must never be
|
|
230
|
-
// taken for
|
|
403
|
+
// taken for team state — a failed poll keeps the last good frame and says so. The
|
|
231
404
|
// bounded wait covers the other failure a bare fetch never reports: a connection the
|
|
232
405
|
// server accepted but never answers (pollSignal).
|
|
233
406
|
d = await getJson("/api/status", pollSignal());
|
|
@@ -251,14 +424,14 @@ export const GUI_CLIENT_BOOT_JS = String.raw ` // ---- views ----
|
|
|
251
424
|
renderSidebar(d);
|
|
252
425
|
renderAlerts(d);
|
|
253
426
|
renderComposer(d);
|
|
254
|
-
if (activeView === "
|
|
427
|
+
if (activeView === "team") renderTeam(d);
|
|
255
428
|
if (activeView === "pending") renderPendingQueue(d);
|
|
256
429
|
if (activeView === "history") syncHistoryRoles(d);
|
|
257
|
-
// Keep the Usage/Failures Loop pickers in step with the
|
|
430
|
+
// Keep the Usage/Failures Loop pickers in step with the team while either is open.
|
|
258
431
|
if (activeView === "usage" || activeView === "failures") syncReportRoles(d);
|
|
259
432
|
const items = d.eventItems || [];
|
|
260
433
|
const last = items[items.length - 1];
|
|
261
|
-
const key = last ? last.ts + ":" + last.type + ":" + last.
|
|
434
|
+
const key = last ? last.ts + ":" + last.type + ":" + last.source : "";
|
|
262
435
|
if (lastEventKey !== null && key !== lastEventKey) onNewEvents();
|
|
263
436
|
lastEventKey = key;
|
|
264
437
|
} catch (err) {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
1
2
|
/** The dashboard's composer, browser-side — the one input box that steers the director or any
|
|
2
3
|
* single loop: the target selector with its per-target unsent drafts, the character counter,
|
|
3
4
|
* the image attachments (dropped or pasted onto the box, rendered as removable chips), and
|
|
4
5
|
* the submit that reads the bytes and queues the prompt through the same endpoints the CLI's
|
|
5
|
-
* "prompt" and "prompt --role" use. Split out of gui-client-
|
|
6
|
-
* read-only status rendering the composer sits on top of.
|
|
6
|
+
* "prompt" and "prompt --role" use. Split out of gui-client-team.ts, which keeps the
|
|
7
|
+
* read-only status rendering the composer sits on top of. renderTeam calls renderComposer
|
|
7
8
|
* each poll; gui-client-boot.ts's question and backlog actions and gui-client-loops.ts's row
|
|
8
9
|
* actions call draftForDirector and focusComposer. Spliced into gui-client.ts's script right
|
|
9
|
-
* after the
|
|
10
|
-
* and the
|
|
10
|
+
* after the team view, reaching its helpers ($, esc, postJson, plural, showFlash, refresh)
|
|
11
|
+
* and the team view's activeView/drawer state through that concatenation. */
|
|
11
12
|
import { DIRECTOR_PROMPT_MAX_CHARS } from "../../inbox/inbox-submit.js";
|
|
12
13
|
import { PROMPT_IMAGE_EXTENSIONS, PROMPT_IMAGE_MAX_BYTES, PROMPT_IMAGES_MAX_COUNT } from "../../inbox/inbox-attachments.js";
|
|
13
14
|
export const GUI_CLIENT_COMPOSER_JS = String.raw ` // ---- composer: one box for the director or any single loop ----
|
|
@@ -17,39 +18,73 @@ export const GUI_CLIENT_COMPOSER_JS = String.raw ` // ---- composer: one box fo
|
|
|
17
18
|
let promptTarget = "director";
|
|
18
19
|
let targetKey = "";
|
|
19
20
|
const drafts = {}; // each target keeps its own unsent draft
|
|
21
|
+
// Recall of recently sent prompts, the composer's mirror of the TUI prompt line's
|
|
22
|
+
// tui-prompt-history.ts: Up steps back through the prompts you have sent, Down steps
|
|
23
|
+
// forward and restores the live draft you were typing. promptHistory holds the text of
|
|
24
|
+
// each successfully sent prompt, oldest first; recallIndex is null while the live draft
|
|
25
|
+
// is shown, an index while browsing history; recallDraft saves the live draft when the
|
|
26
|
+
// first Up leaves it, so Down past the newest entry hands it back untouched.
|
|
27
|
+
const promptHistory = [];
|
|
28
|
+
let recallIndex = null;
|
|
29
|
+
let recallDraft = "";
|
|
30
|
+
const PROMPT_HISTORY_MAX = 50;
|
|
31
|
+
// The last status payload, kept so setTarget can re-render the image note between polls.
|
|
32
|
+
let composerData = null;
|
|
33
|
+
// The image-input notice under the image chips (plans/image-input.md "Surfaces", part 5/5):
|
|
34
|
+
// when the selected non-director loop's model can't read images but reroutes a blind read
|
|
35
|
+
// to another tier, say so — read from the status payload (imageInput/imageReroute), the same
|
|
36
|
+
// facts the images alert and the submit gate use, so the composer never promises a run the
|
|
37
|
+
// gate would refuse.
|
|
38
|
+
function renderPromptImageNote() {
|
|
39
|
+
const loop = (composerData && composerData.roles || []).find((l) => l.role === promptTarget);
|
|
40
|
+
const note = $("promptimagenote");
|
|
41
|
+
const text = loop && loop.imageInput === false && loop.imageReroute
|
|
42
|
+
? promptTarget + "'s model can't read images; image prompts run on " + loop.imageReroute
|
|
43
|
+
: "";
|
|
44
|
+
if (note.textContent !== text) note.textContent = text;
|
|
45
|
+
note.hidden = text === "";
|
|
46
|
+
}
|
|
20
47
|
function renderComposer(d) {
|
|
21
|
-
const roles = (d.
|
|
48
|
+
const roles = (d.roles || []).map((l) => l.role).filter((r) => r !== "director");
|
|
22
49
|
const key = roles.join(",");
|
|
23
50
|
if (key !== targetKey) {
|
|
24
51
|
targetKey = key;
|
|
25
|
-
targetSelect.innerHTML = "<option value='director'>Director</option>" + (roles.length ? "<optgroup label='One
|
|
26
|
-
roles.map((r) => "<option value='" + esc(r) + "'>" + esc(r) + "</option>").join("") + "</optgroup>" : "");
|
|
52
|
+
targetSelect.innerHTML = "<option value='director'>Director</option>" + (roles.length ? "<optgroup label='One role, at its next run'>" +
|
|
53
|
+
roles.map((r) => "<option value='" + esc(r) + "'>" + esc(roleLabel(r)) + "</option>").join("") + "</optgroup>" : "");
|
|
27
54
|
if (promptTarget !== "director" && !roles.includes(promptTarget)) setTarget("director");
|
|
28
55
|
targetSelect.value = promptTarget;
|
|
29
56
|
}
|
|
30
57
|
const n = (d.inbox || 0) + Object.values(d.roleInbox || {}).reduce((a, b) => a + b, 0);
|
|
31
58
|
const link = $("queuelink");
|
|
32
59
|
link.hidden = n === 0;
|
|
33
|
-
link.dataset.act = "queued";
|
|
34
|
-
const text = plural(n, "
|
|
60
|
+
link.dataset.act = "queued"; // vocabulary-ok: data-act routing key the click handler matches
|
|
61
|
+
const text = plural(n, "message") + " in the inbox";
|
|
35
62
|
if (link.textContent !== text) link.textContent = text;
|
|
63
|
+
composerData = d;
|
|
64
|
+
renderPromptImageNote();
|
|
36
65
|
}
|
|
37
66
|
function composerHint() {
|
|
38
67
|
$("prompthint").innerHTML = esc(promptTarget === "director"
|
|
39
|
-
? "The director runs this next, ahead of every
|
|
40
|
-
: "
|
|
68
|
+
? "The director runs this next, ahead of every role."
|
|
69
|
+
: "Goes to the " + roleLabel(promptTarget) + " inbox for its next run; the role wakes right away.") +
|
|
41
70
|
" <kbd>Enter</kbd> sends · <kbd>Shift</kbd>+<kbd>Enter</kbd> adds a line · <kbd>/</kbd> jumps here";
|
|
42
|
-
promptInput.placeholder = promptTarget === "director" ? "Tell the
|
|
71
|
+
promptInput.placeholder = promptTarget === "director" ? "Tell the team what to do next…" : "Tell the " + roleLabel(promptTarget) + " role what to do on its next run…";
|
|
43
72
|
}
|
|
44
73
|
function setTarget(t) {
|
|
45
74
|
if (t === promptTarget) return;
|
|
46
|
-
|
|
75
|
+
// Mid-recall the box holds a recalled entry, not the draft; hand the saved draft to the
|
|
76
|
+
// old target's slot (the TUI's settlePromptRecall swaps the same way) so no unsent draft
|
|
77
|
+
// is lost, then end recall.
|
|
78
|
+
drafts[promptTarget] = recallIndex !== null ? recallDraft : promptInput.value;
|
|
47
79
|
promptTarget = t;
|
|
80
|
+
recallIndex = null;
|
|
81
|
+
recallDraft = "";
|
|
48
82
|
targetSelect.value = t;
|
|
49
83
|
promptInput.value = drafts[t] || "";
|
|
50
84
|
composerHint();
|
|
51
85
|
autosize();
|
|
52
86
|
updateCount();
|
|
87
|
+
renderPromptImageNote();
|
|
53
88
|
}
|
|
54
89
|
function autosize() {
|
|
55
90
|
promptInput.style.height = "auto";
|
|
@@ -65,25 +100,66 @@ export const GUI_CLIENT_COMPOSER_JS = String.raw ` // ---- composer: one box fo
|
|
|
65
100
|
// about a backlog entry), leaving the cursor at the end.
|
|
66
101
|
function draftForDirector(text) {
|
|
67
102
|
setTarget("director");
|
|
103
|
+
recallIndex = null;
|
|
104
|
+
recallDraft = "";
|
|
68
105
|
promptInput.value = text;
|
|
69
106
|
autosize();
|
|
70
107
|
updateCount();
|
|
71
108
|
focusComposer();
|
|
72
109
|
promptInput.setSelectionRange(text.length, text.length);
|
|
73
110
|
}
|
|
74
|
-
// The composer sits on the
|
|
111
|
+
// The composer sits on the Team view; bring it into sight from anywhere and focus it.
|
|
75
112
|
function focusComposer() {
|
|
76
|
-
if (activeView !== "
|
|
113
|
+
if (activeView !== "team") location.hash = "team";
|
|
77
114
|
if (window.innerWidth <= 1180 && drawer) closeDrawer();
|
|
78
115
|
$("promptform").scrollIntoView({ behavior: "smooth", block: "center" });
|
|
79
116
|
promptInput.focus({ preventScroll: true });
|
|
80
117
|
}
|
|
81
118
|
targetSelect.addEventListener("change", () => setTarget(targetSelect.value));
|
|
82
119
|
promptInput.addEventListener("input", () => { autosize(); updateCount(); });
|
|
120
|
+
// Recall one recently sent prompt: step is -1 for Up (older) and 1 for Down (newer).
|
|
121
|
+
// Returns true when the box changed (so the caller can stop the browser's own Up/Down
|
|
122
|
+
// cursor move). Up from the live draft saves it and moves to the newest entry; Up steps
|
|
123
|
+
// one older while it can and holds the oldest end. Down steps newer; past the newest it
|
|
124
|
+
// restores the saved live draft and ends recall. Down while already live is a no-op. On a
|
|
125
|
+
// change the recalled text is set ready to send, sized, counted, and the cursor put at the
|
|
126
|
+
// end. The composer's mirror of the TUI's recallPromptHistory.
|
|
127
|
+
function recallSentPrompt(step) {
|
|
128
|
+
if (step === -1) {
|
|
129
|
+
if (promptHistory.length === 0) return false;
|
|
130
|
+
if (recallIndex === null) {
|
|
131
|
+
recallDraft = promptInput.value;
|
|
132
|
+
recallIndex = promptHistory.length - 1;
|
|
133
|
+
} else if (recallIndex === 0) {
|
|
134
|
+
return false; // already at the oldest end
|
|
135
|
+
} else {
|
|
136
|
+
recallIndex--;
|
|
137
|
+
}
|
|
138
|
+
} else {
|
|
139
|
+
if (recallIndex === null) return false; // already on the live draft
|
|
140
|
+
if (recallIndex >= promptHistory.length - 1) {
|
|
141
|
+
promptInput.value = recallDraft; // past the newest: the live draft returns
|
|
142
|
+
recallDraft = "";
|
|
143
|
+
recallIndex = null;
|
|
144
|
+
autosize();
|
|
145
|
+
updateCount();
|
|
146
|
+
promptInput.setSelectionRange(promptInput.value.length, promptInput.value.length);
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
recallIndex++;
|
|
150
|
+
}
|
|
151
|
+
promptInput.value = promptHistory[recallIndex];
|
|
152
|
+
autosize();
|
|
153
|
+
updateCount();
|
|
154
|
+
promptInput.setSelectionRange(promptInput.value.length, promptInput.value.length);
|
|
155
|
+
return true;
|
|
156
|
+
}
|
|
83
157
|
promptInput.addEventListener("keydown", (ev) => {
|
|
84
158
|
if (ev.key === "Enter" && !ev.shiftKey && !ev.isComposing) {
|
|
85
159
|
ev.preventDefault();
|
|
86
160
|
$("promptform").requestSubmit();
|
|
161
|
+
} else if ((ev.key === "ArrowUp" || ev.key === "ArrowDown") && !ev.isComposing && (recallIndex !== null || !promptInput.value.includes("\n"))) {
|
|
162
|
+
if (recallSentPrompt(ev.key === "ArrowUp" ? -1 : 1)) ev.preventDefault();
|
|
87
163
|
} else if (ev.key === "Escape") promptInput.blur();
|
|
88
164
|
});
|
|
89
165
|
// composer-images:start
|
|
@@ -185,17 +261,15 @@ export const GUI_CLIENT_COMPOSER_JS = String.raw ` // ---- composer: one box fo
|
|
|
185
261
|
// "prompt" and "prompt --role" use. Resolves to the confirmation to show; rejects when the
|
|
186
262
|
// server did not take it (the caller keeps the text so it can be fixed and resent). Pending
|
|
187
263
|
// images ride along as { name, dataBase64 } and are named in the confirmation.
|
|
188
|
-
async function sendPrompt(target, text, images
|
|
264
|
+
async function sendPrompt(target, text, images) {
|
|
189
265
|
const attached = images && images.length ? " with " + plural(images.length, "image") : "";
|
|
190
|
-
const deferred = defer ? " — delivers in " + defer : "";
|
|
191
266
|
const body = images && images.length ? { text, images } : { text };
|
|
192
|
-
if (defer) body.defer = defer;
|
|
193
267
|
if (target === "director") {
|
|
194
268
|
await postJson("/api/prompt", body);
|
|
195
|
-
return "
|
|
269
|
+
return "Added to the director's inbox — it runs next" + attached;
|
|
196
270
|
}
|
|
197
271
|
await postJson("/api/prompt-role", { role: target, ...body });
|
|
198
|
-
return "
|
|
272
|
+
return "Added to the " + roleLabel(target) + " inbox for its next run — it wakes now" + attached;
|
|
199
273
|
}
|
|
200
274
|
// composer-send:end
|
|
201
275
|
let sending = false;
|
|
@@ -203,14 +277,13 @@ export const GUI_CLIENT_COMPOSER_JS = String.raw ` // ---- composer: one box fo
|
|
|
203
277
|
ev.preventDefault();
|
|
204
278
|
const text = promptInput.value.trim();
|
|
205
279
|
if (!text || sending) return;
|
|
206
|
-
const defer = $("promptdefer").value.trim();
|
|
207
280
|
const target = promptTarget;
|
|
208
281
|
sending = true;
|
|
209
282
|
$("promptsend").disabled = true;
|
|
210
283
|
const images = [];
|
|
211
284
|
try {
|
|
212
285
|
for (const img of promptImages) images.push({ name: img.name, dataBase64: await readImageAsBase64(img.file) });
|
|
213
|
-
showFlash(await sendPrompt(target, text, images
|
|
286
|
+
showFlash(await sendPrompt(target, text, images));
|
|
214
287
|
} catch (err) {
|
|
215
288
|
// The prompt was not accepted: keep the text and the images so they can be fixed and resent.
|
|
216
289
|
showFlash("error: " + err.message);
|
|
@@ -222,8 +295,14 @@ export const GUI_CLIENT_COMPOSER_JS = String.raw ` // ---- composer: one box fo
|
|
|
222
295
|
promptImages.length = 0;
|
|
223
296
|
renderPromptImages();
|
|
224
297
|
promptInput.value = "";
|
|
225
|
-
$("promptdefer").value = "";
|
|
226
298
|
drafts[target] = "";
|
|
299
|
+
// Record the sent prompt for recall: consecutive duplicates add one entry (re-sending the
|
|
300
|
+
// same nudge does not fill the history with copies), the oldest drop past the cap, and the
|
|
301
|
+
// recall position returns to the live draft for the next browse.
|
|
302
|
+
if (text && promptHistory[promptHistory.length - 1] !== text) promptHistory.push(text);
|
|
303
|
+
if (promptHistory.length > PROMPT_HISTORY_MAX) promptHistory.shift();
|
|
304
|
+
recallIndex = null;
|
|
305
|
+
recallDraft = "";
|
|
227
306
|
if (target !== "director") setTarget("director");
|
|
228
307
|
autosize();
|
|
229
308
|
updateCount();
|