tumwater 0.4.0 → 0.4.2
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 +4 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +5 -2
- package/dist/src/agent/backend-traits.js +21 -0
- package/dist/src/agent/claude/claude-backend.js +1 -1
- package/dist/src/agent/claude/claude-stream.js +181 -182
- package/dist/src/agent/claude/{write-guard-hook.js → claude-write-guard-hook.js} +1 -1
- package/dist/src/agent/claude/claude-write-guard.js +5 -4
- package/dist/src/agent/codex/codex-backend.js +1 -1
- package/dist/src/agent/codex/codex-rollout.js +48 -45
- package/dist/src/agent/codex/codex-stream.js +269 -215
- package/dist/src/agent/codex/{write-guard-hook.js → codex-write-guard-hook.js} +2 -1
- package/dist/src/agent/codex/codex-write-guard.js +3 -3
- package/dist/src/agent/image-input.js +18 -87
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +2 -2
- package/dist/src/agent/opencode/opencode-catalog.js +2 -2
- package/dist/src/agent/opencode/opencode-config.js +2 -2
- package/dist/src/agent/opencode/opencode-stream.js +101 -92
- package/dist/src/agent/run-agent.js +76 -382
- package/dist/src/agent/run-settle.js +25 -15
- package/dist/src/agent/run-spawn.js +226 -0
- package/dist/src/agent/run-stream.js +185 -0
- package/dist/src/agent/session-ledger.js +16 -0
- package/dist/src/agent/write-guard.js +5 -3
- package/dist/src/backlog/backlog-archive.js +86 -170
- package/dist/src/backlog/backlog-cli-view.js +89 -70
- package/dist/src/backlog/backlog-eligibility.js +51 -59
- package/dist/src/backlog/backlog-file-bugs.js +133 -0
- package/dist/src/backlog/backlog-index-data.js +159 -0
- package/dist/src/backlog/backlog-index.js +7 -5
- package/dist/src/backlog/backlog-md.js +39 -28
- package/dist/src/backlog/backlog-release.js +21 -36
- package/dist/src/backlog/backlog-render.js +1 -1
- package/dist/src/backlog/backlog-structure.js +14 -145
- package/dist/src/backlog/backlog-write.js +31 -18
- package/dist/src/backlog/backlog.js +14 -4
- package/dist/src/backlog/question-answer.js +33 -15
- package/dist/src/baseline/main-baseline.js +71 -26
- package/dist/src/baseline/main-guardrails.js +76 -15
- package/dist/src/baseline/main-red.js +45 -94
- package/dist/src/baseline/main-watch.js +9 -3
- package/dist/src/brief/brief.js +40 -0
- package/dist/src/budget/budget-fallback-view.js +50 -8
- package/dist/src/budget/budget-wiring.js +15 -11
- package/dist/src/budget/budget.js +3 -2
- package/dist/src/build/build-check-events.js +4 -2
- package/dist/src/build/build-check-report.js +2 -1
- package/dist/src/build/build-check-scoped.js +70 -41
- package/dist/src/change/change-data.js +4 -2
- package/dist/src/cli/cli-args.js +9 -0
- package/dist/src/cli/cli-command-args.js +199 -98
- package/dist/src/cli/cli-flag-specs.js +6 -4
- package/dist/src/cli/cli-marker-commands.js +2 -2
- package/dist/src/cli/cli-query-commands.js +41 -6
- package/dist/src/cli/cli-review-commands.js +2 -1
- package/dist/src/cli/cli-role-command.js +46 -92
- package/dist/src/cli/cli-start.js +123 -47
- package/dist/src/cli/follow-view.js +3 -1
- package/dist/src/cli/help.js +19 -12
- package/dist/src/cli/log-commands.js +128 -107
- package/dist/src/cli/question-commands.js +82 -63
- package/dist/src/cli.js +396 -343
- package/dist/src/collections.js +46 -9
- package/dist/src/config/config-fallbacks.js +0 -4
- package/dist/src/config/config-live.js +179 -147
- package/dist/src/config/config-request.js +176 -51
- package/dist/src/config/config-validation-roles.js +271 -0
- package/dist/src/config/config-validation.js +10 -205
- package/dist/src/config/config-views.js +2 -7
- package/dist/src/config/config-write.js +205 -175
- package/dist/src/config/config.js +2 -1
- package/dist/src/doctor/doctor-backlog-bugs.js +24 -122
- package/dist/src/doctor/doctor-backlog-plans.js +24 -25
- package/dist/src/doctor/doctor-backlog.js +34 -42
- package/dist/src/doctor/doctor-check-names.js +4 -2
- package/dist/src/doctor/doctor-checks.js +16 -4
- package/dist/src/doctor/doctor-claude-checks.js +2 -2
- package/dist/src/doctor/doctor-codex-checks.js +9 -20
- package/dist/src/doctor/doctor-contention-checks.js +52 -19
- package/dist/src/doctor/doctor-holds.js +22 -20
- package/dist/src/doctor/doctor-model-checks.js +8 -184
- package/dist/src/doctor/doctor-notify.js +2 -5
- package/dist/src/doctor/doctor-opencode-checks.js +90 -41
- package/dist/src/doctor/doctor-principles.js +26 -0
- package/dist/src/doctor/doctor-project-checks.js +69 -19
- package/dist/src/doctor/doctor-role-notes.js +33 -0
- package/dist/src/doctor/doctor.js +13 -6
- package/dist/src/doctor/probe.js +1 -1
- package/dist/src/events/event-format.js +307 -162
- package/dist/src/events/event-labels.js +0 -1
- package/dist/src/events/event-legacy.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +15 -2
- package/dist/src/events/notify.js +1 -1
- package/dist/src/failure/failure-cluster.js +4 -3
- package/dist/src/failure/failure-data.js +126 -94
- package/dist/src/failure/failure-render.js +58 -17
- package/dist/src/failure/failure-state-change.js +308 -27
- package/dist/src/failure/time-spend.js +139 -106
- package/dist/src/files/files.js +7 -0
- package/dist/src/files/json-object.js +15 -2
- package/dist/src/gates/budget-gates.js +90 -68
- package/dist/src/gates/gate-polls.js +92 -61
- package/dist/src/gates/role-cap-gates.js +1 -1
- package/dist/src/gates/startup-gate.js +28 -28
- package/dist/src/gates/streak-gate.js +1 -1
- package/dist/src/git/git.js +1 -1
- package/dist/src/git/slot-salvage.js +9 -9
- package/dist/src/git/slots-state.js +12 -3
- package/dist/src/git/worktree-pool-startup.js +127 -0
- package/dist/src/git/worktree-pool.js +1 -120
- package/dist/src/gui/gui-admin-endpoints.js +3 -4
- package/dist/src/gui/gui-args.js +1 -1
- package/dist/src/gui/gui-backlog-commands.js +20 -11
- package/dist/src/gui/gui-backlog-endpoints.js +2 -8
- package/dist/src/gui/gui-config-commands.js +51 -76
- package/dist/src/gui/gui-endpoint-commands.js +4 -2
- package/dist/src/gui/gui-endpoint-common.js +4 -3
- package/dist/src/gui/gui-history-endpoints.js +36 -27
- package/dist/src/gui/gui-operator-commands.js +43 -17
- package/dist/src/gui/gui-prompt-commands.js +27 -21
- package/dist/src/gui/gui-quarantine-commands.js +30 -0
- package/dist/src/gui/gui-report-endpoints.js +13 -9
- package/dist/src/gui/gui-server.js +5 -3
- package/dist/src/gui/gui-team-commands.js +1 -1
- package/dist/src/history/history-data.js +7 -1
- package/dist/src/history/history-render.js +15 -10
- package/dist/src/history/history.js +103 -68
- package/dist/src/inbox/inbox-submit.js +1 -5
- package/dist/src/inbox/pending-prompt.js +3 -2
- package/dist/src/inbox/prompt-commands.js +194 -161
- package/dist/src/init/init.js +104 -25
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +1 -1
- package/dist/src/merge/merge-git.js +5 -5
- package/dist/src/merge/{merge-post-review.js → merge-post-review-drain.js} +1 -1
- package/dist/src/merge/merge-queue.js +2 -1
- package/dist/src/merge/merge-to-main.js +1 -1
- package/dist/src/merge/merge-verify.js +3 -2
- package/dist/src/operator/operator-commands.js +84 -52
- package/dist/src/operator/operator-intent.js +26 -3
- package/dist/src/operator/retire.js +2 -1
- package/dist/src/operator/revert.js +18 -12
- package/dist/src/orchestrator/orchestrator-poll.js +2 -2
- package/dist/src/orchestrator/orchestrator.js +1 -1
- package/dist/src/orchestrator/retention.js +6 -5
- package/dist/src/paths.js +10 -10
- package/dist/src/pi/pi-bin.js +2 -2
- package/dist/src/pi/pi-models.js +9 -0
- package/dist/src/pi/pi-stream.js +63 -41
- package/dist/src/pi/pi-watchdogs.js +84 -49
- package/dist/src/pi-extension/context-shake.js +55 -31
- package/dist/src/pi-extension/full-output.js +2 -3
- package/dist/src/process/process.js +24 -1
- package/dist/src/process/run-marker.js +2 -2
- package/dist/src/process/supervisor.js +24 -5
- package/dist/src/prompt/gate-notes.js +41 -16
- package/dist/src/prompt/notes-backlog.js +22 -29
- package/dist/src/prompt/principles.js +11 -0
- package/dist/src/prompt/prompt-followup.js +4 -12
- package/dist/src/prompt/prompt.js +15 -12
- package/dist/src/redeploy/redeploy-probes.js +11 -1
- package/dist/src/redeploy/redeployer.js +54 -7
- package/dist/src/redeploy/self-reload.js +7 -2
- package/dist/src/report/report-data.js +224 -320
- package/dist/src/report/report-fold.js +23 -231
- package/dist/src/report/report-main-red.js +70 -3
- package/dist/src/report/report-render.js +181 -48
- package/dist/src/report/report-since-data.js +218 -0
- package/dist/src/report/report-usage-fold.js +288 -0
- package/dist/src/report/report.js +2 -1
- package/dist/src/review/known-flakes.js +22 -3
- package/dist/src/review/post-merge-review-accept.js +3 -2
- package/dist/src/review/post-merge-review.js +16 -14
- package/dist/src/review/quarantine-render.js +29 -11
- package/dist/src/review/review-precheck.js +2 -1
- package/dist/src/roles/role-catalog.js +30 -29
- package/dist/src/roles/role-guidance.js +26 -43
- package/dist/src/roles/role-render.js +19 -0
- package/dist/src/roles/role-view.js +19 -5
- package/dist/src/roles/roles.js +2 -22
- package/dist/src/run/ci-parity.js +5 -6
- package/dist/src/run/feedback-filings.js +10 -1
- package/dist/src/run/qa-coverage.js +4 -4
- package/dist/src/run/run-apply.js +14 -2
- package/dist/src/run/run-detail-data.js +50 -10
- package/dist/src/run/run-detail-render.js +32 -1
- package/dist/src/run/run-detail.js +44 -22
- package/dist/src/run/run-dismiss.js +43 -0
- package/dist/src/run/run-finalize.js +75 -46
- package/dist/src/run/run-prompt.js +271 -187
- package/dist/src/run/run-resume.js +32 -31
- package/dist/src/run/run-stage.js +116 -64
- package/dist/src/run/run-timing.js +18 -4
- package/dist/src/run/run-verdict.js +119 -83
- package/dist/src/run/stage-backlog-checks.js +4 -31
- package/dist/src/run/stage-check.js +4 -12
- package/dist/src/run/stage-checks-shared.js +15 -0
- package/dist/src/run/stage-name-checks.js +3 -13
- package/dist/src/run/stage-path-checks.js +26 -29
- package/dist/src/runner/leftover.js +13 -4
- package/dist/src/runner/revision.js +2 -2
- package/dist/src/runner/runner-fallback.js +1 -1
- package/dist/src/runner/runner-outcome.js +0 -5
- package/dist/src/runner/runner-pi.js +72 -49
- package/dist/src/runner/runner-run.js +19 -102
- package/dist/src/runner/runner-state-load.js +0 -10
- package/dist/src/runner/runner.js +1 -32
- package/dist/src/scheduling/claims.js +4 -11
- package/dist/src/status/status-data.js +140 -91
- package/dist/src/status/status-polls.js +41 -5
- package/dist/src/status/status-role-notes.js +53 -0
- package/dist/src/status/status-roles.js +10 -37
- package/dist/src/text/datetime.js +8 -1
- package/dist/src/text/format.js +13 -1
- package/dist/src/text/phrases.js +16 -2
- package/dist/src/text/text.js +12 -0
- package/dist/src/ui/alert-feeds.js +115 -0
- package/dist/src/ui/badges.js +3 -4
- package/dist/src/ui/gui/gui-client-boot.js +78 -57
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -3
- package/dist/src/ui/gui/gui-client-drawer.js +18 -2
- package/dist/src/ui/gui/gui-client-feed.js +3 -8
- package/dist/src/ui/gui/gui-client-history.js +16 -16
- package/dist/src/ui/gui/gui-client-model.js +9 -9
- package/dist/src/ui/gui/gui-client-operator.js +6 -10
- package/dist/src/ui/gui/gui-client-pending.js +4 -4
- package/dist/src/ui/gui/gui-client-report-chart.js +111 -10
- package/dist/src/ui/gui/gui-client-report.js +70 -11
- package/dist/src/ui/gui/gui-client-roles.js +52 -12
- package/dist/src/ui/gui/gui-client-settings.js +31 -26
- package/dist/src/ui/gui/gui-client-team.js +28 -2
- package/dist/src/ui/gui/gui-client.js +33 -4
- package/dist/src/ui/gui/gui-icons.js +7 -1
- package/dist/src/ui/progress-data.js +2 -2
- package/dist/src/ui/run-progress-model.js +2 -2
- package/dist/src/ui/status-model.js +26 -16
- package/dist/src/ui/status-payload.js +149 -326
- package/dist/src/ui/status-render.js +131 -80
- package/dist/src/ui/team-alert-backlog.js +236 -0
- package/dist/src/ui/team-alert-shared.js +92 -0
- package/dist/src/ui/team-alert-status.js +326 -0
- package/dist/src/ui/team-alert-tail.js +238 -0
- package/dist/src/ui/team-alerts.js +16 -984
- package/dist/src/ui/transcript-tail.js +137 -94
- package/dist/src/ui/tui/tui-backlog.js +1 -1
- package/dist/src/ui/tui/tui-editor-answer.js +54 -44
- package/dist/src/ui/tui/tui-editor-attach.js +74 -63
- package/dist/src/ui/tui/tui-editor-backends.js +62 -56
- package/dist/src/ui/tui/tui-editor-brief.js +64 -54
- package/dist/src/ui/tui/tui-editor-budget.js +106 -89
- package/dist/src/ui/tui/tui-editor-config.js +2 -4
- package/dist/src/ui/tui/tui-editor-ctx.js +12 -1
- package/dist/src/ui/tui/tui-editor-entry.js +171 -153
- package/dist/src/ui/tui/tui-editor-instances.js +2 -1
- package/dist/src/ui/tui/tui-editor-instructions.js +50 -107
- package/dist/src/ui/tui/tui-editor-interval.js +39 -112
- package/dist/src/ui/tui/tui-editor-notes.js +28 -84
- package/dist/src/ui/tui/tui-editor-pause.js +210 -183
- package/dist/src/ui/tui/tui-editor-prompt.js +46 -35
- package/dist/src/ui/tui/tui-editor-queued.js +74 -64
- package/dist/src/ui/tui/tui-editor-quiet.js +122 -108
- package/dist/src/ui/tui/tui-editor-release.js +85 -0
- package/dist/src/ui/tui/tui-editor-retire.js +57 -46
- package/dist/src/ui/tui/tui-editor-role-enabled.js +90 -80
- package/dist/src/ui/tui/tui-editor-role-file.js +87 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +50 -42
- package/dist/src/ui/tui/tui-editor-search.js +70 -58
- package/dist/src/ui/tui/tui-editor-session.js +3 -0
- package/dist/src/ui/tui/tui-editors.js +4 -1
- package/dist/src/ui/tui/tui-frame.js +7 -3
- package/dist/src/ui/tui/tui-history.js +12 -10
- package/dist/src/ui/tui/tui-keys.js +6 -1
- package/dist/src/ui/tui/tui-pane-activity.js +62 -50
- package/dist/src/ui/tui/tui-pane-backlog.js +4 -4
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +45 -26
- package/dist/src/ui/tui/tui-pane-history.js +296 -264
- package/dist/src/ui/tui/tui-pane-report.js +74 -57
- package/dist/src/ui/tui/tui-pane-session.js +6 -2
- package/dist/src/ui/tui/tui-pane-transcript.js +158 -131
- package/dist/src/ui/tui/tui-pane.js +201 -174
- package/dist/src/ui/tui/tui-panes.js +211 -148
- package/dist/src/ui/tui/tui.js +4 -4
- package/dist/src/verdict/fix-claim.js +18 -6
- package/dist/src/verdict/refusal.js +32 -7
- package/package.json +1 -1
- package/dist/src/doctor/image-probe.js +0 -257
- /package/dist/src/gui/{http-body.js → gui-http-body.js} +0 -0
- /package/dist/src/merge/{backlog-conflicts.js → merge-backlog-conflicts.js} +0 -0
|
@@ -6,16 +6,12 @@ import { abortSync, ensureWorktree, resetWorktreeToMain } from "../git/worktree.
|
|
|
6
6
|
import { leaseSlot } from "../git/worktree-pool.js";
|
|
7
7
|
import { slotCount } from "../config/config.js";
|
|
8
8
|
import { useWorktree } from "../git/worktree-use.js";
|
|
9
|
-
import { readPiListedModels } from "../pi/pi-listed-models.js";
|
|
10
9
|
import { branchName, worktreePath, mergeRefName, rejectedRefName } from "../paths.js";
|
|
11
10
|
import { logEventBestEffort } from "../events/events.js";
|
|
12
11
|
import { buildConflictHandbackNote, buildSalvageRestoreNote } from "../prompt/gate-prompts.js";
|
|
13
12
|
import { buildConflictDiscardNote, buildMainKnownRedNote, buildRevisionNote, buildRejectedReviewNote, } from "../prompt/gate-notes.js";
|
|
14
|
-
import { modelSelectorField, resolvedBackendConfig, reviewRunConfig, reviewedAfterMerge, scheduleConfigForRole, seamHoldKey,
|
|
13
|
+
import { modelSelectorField, resolvedBackendConfig, reviewRunConfig, reviewedAfterMerge, scheduleConfigForRole, seamHoldKey, } from "../config/config-views.js";
|
|
15
14
|
import { fallbackRoleConfig } from "../config/config-fallbacks.js";
|
|
16
|
-
import { imageReroute, seamImageInput } from "../agent/image-input.js";
|
|
17
|
-
import { piModelsPath, readPiProviders } from "../pi/pi-models.js";
|
|
18
|
-
import { peekPrompt, peekRolePrompt } from "../inbox/inbox.js";
|
|
19
15
|
import { planRunStart, planWithSalvagedWork, takesSalvagedWork } from "../run/run-resume.js";
|
|
20
16
|
import { restoreSalvagedWork } from "../git/slot-salvage.js";
|
|
21
17
|
import { DEFAULT_BACKEND } from "../agent/agent-backend.js";
|
|
@@ -30,7 +26,8 @@ import { applyRevision, applyWithConflicts } from "./revision.js";
|
|
|
30
26
|
import { conflictedFileHunks, hasConflictMarkers, mainCommitsTouching } from "../merge/merge-git.js";
|
|
31
27
|
import { changedFiles } from "../git/git-diff.js";
|
|
32
28
|
import { handlePiResultPhase } from "./runner-outcome.js";
|
|
33
|
-
import {
|
|
29
|
+
import { mainRedHealNote, namedFailures } from "../baseline/main-red.js";
|
|
30
|
+
import { watchedBaseline } from "../baseline/main-baseline.js";
|
|
34
31
|
import { extractCiResult, extractFlow, extractFiled } from "../verdict/reply-contract.js";
|
|
35
32
|
/** The intent the author needs to resolve a handed-back change: the change's own commit message
|
|
36
33
|
* and the main commits that touched its conflicted files since its merge-base. Mirrors what
|
|
@@ -90,60 +87,6 @@ export async function runPhase(ctx) {
|
|
|
90
87
|
let cfg = onFallback && fallback !== null
|
|
91
88
|
? { ...fallback, minRunIntervalSeconds: primaryCfg.minRunIntervalSeconds }
|
|
92
89
|
: primaryCfg;
|
|
93
|
-
// Image-input reroute (plans/image-input.md part 4/5). A pending one-shot marker means
|
|
94
|
-
// THIS run is the blind-read reroute: start directly on the image-capable target the
|
|
95
|
-
// prior run recorded, over the kept worktree, in a fresh session. The marker is consumed
|
|
96
|
-
// (cleared) here so it does not route a later run; imageRerouteActive stays true for the
|
|
97
|
-
// whole run so the verdict's one-hop guard fails a second blind read instead of
|
|
98
|
-
// rerouting again. A preflight (below) sets the same active flag without a marker.
|
|
99
|
-
s.imageRerouteActive = false;
|
|
100
|
-
let preflightBlocked = false;
|
|
101
|
-
if (s.imageReroute) {
|
|
102
|
-
const r = s.imageReroute;
|
|
103
|
-
cfg = withModelOverrides(ctx.config, r.tier, {
|
|
104
|
-
backend: r.backend,
|
|
105
|
-
provider: r.provider,
|
|
106
|
-
model: r.model,
|
|
107
|
-
thinking: r.thinking,
|
|
108
|
-
});
|
|
109
|
-
// The marker is NOT cleared here: planRunStart reads its `from`/`path` when it builds
|
|
110
|
-
// the handoff note (and clears it then), so a run skipped before the plan (an empty
|
|
111
|
-
// inbox) leaves the reroute pending for the next eligible run.
|
|
112
|
-
s.imageRerouteActive = true;
|
|
113
|
-
}
|
|
114
|
-
else {
|
|
115
|
-
// Preflight (plans/image-input.md "Attachments"): a queued prompt carrying an
|
|
116
|
-
// `[image attached: …]` line needs a model that can read images BEFORE any run starts,
|
|
117
|
-
// so a text-only run hops straight to the target instead of spending a failed run
|
|
118
|
-
// learning it; with no target the run errors without spawning pi.
|
|
119
|
-
const nextPrompt = ctx.role === DIRECTOR_ROLE ? peekPrompt(ctx.root) : peekRolePrompt(ctx.root, ctx.role);
|
|
120
|
-
const carriesImages = nextPrompt !== null && /^\[image attached: .+\]$/m.test(nextPrompt);
|
|
121
|
-
if (carriesImages) {
|
|
122
|
-
const providers = readPiProviders(piModelsPath());
|
|
123
|
-
const listed = readPiListedModels(ctx.root);
|
|
124
|
-
if (seamImageInput(cfg, providers, undefined, listed) !== true) {
|
|
125
|
-
const target = imageReroute(ctx.config, ctx.role, ctx.rerouteRegime(now), undefined, null, undefined, null, listed);
|
|
126
|
-
if (target !== null) {
|
|
127
|
-
cfg = target.config;
|
|
128
|
-
s.imageRerouteActive = true;
|
|
129
|
-
logEventBestEffort(ctx.root, {
|
|
130
|
-
source: ctx.role,
|
|
131
|
-
type: "image_reroute",
|
|
132
|
-
run: s.runs + 1,
|
|
133
|
-
from: primaryCfg.model,
|
|
134
|
-
to: target.config.model,
|
|
135
|
-
tier: target.tier,
|
|
136
|
-
preflight: true,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
else {
|
|
140
|
-
preflightBlocked = true;
|
|
141
|
-
s.lastError =
|
|
142
|
-
`an inbox message for ${ctx.role} attaches an image, but no configured tier can read it`;
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
90
|
// Capture what this run runs on (the orchestrator's budget handback matches it against
|
|
148
91
|
// the fallback pair) and clear any stale handback flag: an abort request that lands while
|
|
149
92
|
// the runner is idle must not name a later run's resume.
|
|
@@ -179,19 +122,11 @@ export async function runPhase(ctx) {
|
|
|
179
122
|
source: ctx.role,
|
|
180
123
|
type: "run_start",
|
|
181
124
|
run: runNumber,
|
|
182
|
-
// A run routed onto the image-capable target — a blind-read handoff or an attachment
|
|
183
|
-
// preflight — is stamped so the feed shows the reroute and the model it landed on.
|
|
184
|
-
...(s.imageRerouteActive ? { reroute: "image" } : {}),
|
|
185
125
|
...modelSelectorField(cfg),
|
|
186
126
|
});
|
|
187
127
|
let outcome;
|
|
188
128
|
try {
|
|
189
|
-
|
|
190
|
-
// attachment and no image-capable target has nothing to run, so it errors without
|
|
191
|
-
// spending a model call. Everything else runs the pipeline as usual.
|
|
192
|
-
outcome = preflightBlocked
|
|
193
|
-
? { result: "error" }
|
|
194
|
-
: await ctx.runOnce(cfg, { fallback, probe: probing, primary: primaryCfg });
|
|
129
|
+
outcome = await ctx.runOnce(cfg, { fallback, probe: probing, primary: primaryCfg });
|
|
195
130
|
}
|
|
196
131
|
catch (err) {
|
|
197
132
|
outcome = { result: "error" };
|
|
@@ -257,17 +192,6 @@ export async function runOncePhase(ctx, cfg, fallbackCtx) {
|
|
|
257
192
|
if (plan === null)
|
|
258
193
|
return { result: "skipped" };
|
|
259
194
|
if (ctx.role === DIRECTOR_ROLE) {
|
|
260
|
-
// The image-reroute one-shot marker routes exactly ONE run onto the image-capable
|
|
261
|
-
// target (plans/image-input.md part 4/5, INV-J2): for a role runner, planRunStart consumes
|
|
262
|
-
// it after reading its handoff-note facts. The director never resumes, so planRunStart's
|
|
263
|
-
// image-reroute branch never runs for it and the marker would otherwise linger and
|
|
264
|
-
// re-route every later director run off its own tier. It is cleared here — after this
|
|
265
|
-
// run is confirmed to run (an empty-inbox skip above returns before this, leaving the
|
|
266
|
-
// reroute pending for the next eligible run) — so the rerouted director prompt is
|
|
267
|
-
// delivered exactly once and the director is back on its own tier next.
|
|
268
|
-
// A promptless recovery run delivers no prompt, so it leaves a pending reroute for the next.
|
|
269
|
-
if (plan.prompt !== null)
|
|
270
|
-
s.imageReroute = undefined;
|
|
271
195
|
// The director keeps its dedicated worktree (it runs outside the permit, consumes its
|
|
272
196
|
// config-request file at that exact path, and never resumes). Hold it from before
|
|
273
197
|
// ensureWorktree — creating or resetting it is part of the use — to the run's end
|
|
@@ -391,7 +315,7 @@ leasedDir) {
|
|
|
391
315
|
// recovery merge raced the slot's batches into `merge_blocked`). A salvaged leftover
|
|
392
316
|
// ENDS the run: the role holds one merge ref, and the leftover owns it until its
|
|
393
317
|
// merge resolves. With nothing to salvage the branch holds nothing either, so the reset
|
|
394
|
-
// below leaves pristine main
|
|
318
|
+
// below leaves pristine main.
|
|
395
319
|
// A permanent reviewer config error — held from re-queueing — clears once the resolved
|
|
396
320
|
// reviewer selector changes, so fixing the model config recovers the pin automatically
|
|
397
321
|
// (BUGS.md 2026-10-06).
|
|
@@ -452,33 +376,26 @@ leasedDir) {
|
|
|
452
376
|
// nothing, so it ends here rather than reach the authoring run with a null prompt.
|
|
453
377
|
if (plan.prompt === null)
|
|
454
378
|
return { result: "skipped" };
|
|
455
|
-
//
|
|
456
|
-
// now
|
|
457
|
-
//
|
|
458
|
-
//
|
|
459
|
-
//
|
|
460
|
-
//the same check runs, but a red main appends a <main-red>
|
|
461
|
-
// note to this run's prompt so it reproduces and fixes that failure rather than hunting
|
|
462
|
-
// blind.
|
|
379
|
+
// The `ci` healer's red-main hand-off (src/baseline/main-red.ts): the worktree is pristine
|
|
380
|
+
// main right now, so a red main appends a <main-red> note to this run's prompt and the
|
|
381
|
+
// healer reproduces and fixes that failure rather than hunting blind. Every other role
|
|
382
|
+
// authors on the red main unblocked — the red-main authoring block came off with the
|
|
383
|
+
// pre-merge blocks, parts 3g/3–3j/3 and 3d/3–3f/3.
|
|
463
384
|
if (ctx.baseRole === "ci") {
|
|
464
385
|
const note = await mainRedHealNote(ctx.root, ctx.role, wt);
|
|
465
386
|
if (note)
|
|
466
387
|
prompt = appendPromptNote(prompt, note);
|
|
467
388
|
}
|
|
468
389
|
else {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
return blocked;
|
|
479
|
-
}
|
|
480
|
-
if (knownRed[0])
|
|
481
|
-
prompt = appendPromptNote(prompt, buildMainKnownRedNote(knownRed[0]));
|
|
390
|
+
// Every other role proceeds on the red main, told which tests already fail (the
|
|
391
|
+
// <main-known-red> note, restored 2026-10-25): a pure read of the main watch's persisted
|
|
392
|
+
// verdict (watchedBaseline runs no check), so the block appears only when a verdict names
|
|
393
|
+
// failures and a role's start pays nothing for it. `ci` owns the repair without
|
|
394
|
+
// monopolizing it — a fix from any role lands like any other change.
|
|
395
|
+
const baseline = await watchedBaseline(wt);
|
|
396
|
+
const tests = baseline?.status === "red" ? namedFailures(baseline) : [];
|
|
397
|
+
if (tests.length > 0)
|
|
398
|
+
prompt = appendPromptNote(prompt, buildMainKnownRedNote(tests));
|
|
482
399
|
}
|
|
483
400
|
}
|
|
484
401
|
// A change handed back after its merges kept conflicting — or a persisted hand-back the
|
|
@@ -208,16 +208,6 @@ export function loadRunnerState(root, role) {
|
|
|
208
208
|
v.probeAt = healNumber(v.probeAt, 0);
|
|
209
209
|
v.cooldownMs = healNumber(v.cooldownMs, 0);
|
|
210
210
|
});
|
|
211
|
-
// The image-reroute marker is a one-shot nested record consumed by the next run; a torn
|
|
212
|
-
// scalar is dropped (its consumers read/mutate fields), and the tier is repaired to a
|
|
213
|
-
// known tier name so a corrupt tier cannot route a rerouted run to a phantom seam.
|
|
214
|
-
if (s.imageReroute !== undefined) {
|
|
215
|
-
const healed = healRecord(s.imageReroute, () => { });
|
|
216
|
-
s.imageReroute =
|
|
217
|
-
healed && (healed.tier === "small" || healed.tier === "default" || healed.tier === "strong")
|
|
218
|
-
? healed
|
|
219
|
-
: undefined;
|
|
220
|
-
}
|
|
221
211
|
return s;
|
|
222
212
|
}
|
|
223
213
|
/** Per-role cache of the persisted live claim, keyed by the state file's stat — one stat per
|
|
@@ -6,7 +6,7 @@ import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
|
|
|
6
6
|
import { promptPreview } from "../inbox/inbox.js";
|
|
7
7
|
import { assembleRunPrompt } from "../run/run-prompt.js";
|
|
8
8
|
import { RunnerPi } from "./runner-pi.js";
|
|
9
|
-
import { configForRole, seamHoldKey,
|
|
9
|
+
import { configForRole, seamHoldKey, } from "../config/config-views.js";
|
|
10
10
|
import { fallbackRoleConfig } from "../config/config-fallbacks.js";
|
|
11
11
|
import { PendingPrompt } from "../inbox/pending-prompt.js";
|
|
12
12
|
import { modelFallbackActive } from "./model-fallback.js";
|
|
@@ -73,13 +73,6 @@ export class RoleRunner {
|
|
|
73
73
|
* resolve to, since different roles may run on different pairs.
|
|
74
74
|
* Transient — never persisted — and captured fresh at every run start. */
|
|
75
75
|
runPair;
|
|
76
|
-
/** Whether the budget gate currently has the team on its free fallback pairs (src/gates/
|
|
77
|
-
* budget-gates.ts onFallback, set alongside `config` by the orchestrator's poll): an
|
|
78
|
-
* image reroute may then look only among the free fallback pairs, never a priced primary
|
|
79
|
-
* (plans/image-input.md "Reroute target" "Regime"). Pure scheduler input, never whether
|
|
80
|
-
* the runner itself is mid-fallback; defaults false, so standalone-runner tests stay on the
|
|
81
|
-
* primary regime unless they set it. */
|
|
82
|
-
budgetFallbackActive = false;
|
|
83
76
|
/** The merge failure this run's leftover recovery is retrying, if any (a retriable merger
|
|
84
77
|
* outcome that kept the pin, which recovery re-queued): set in finishRecoveryRun and
|
|
85
78
|
* attached to the returned RunOutcome so applyRunOutcome can feed it into the error streak
|
|
@@ -185,30 +178,7 @@ export class RoleRunner {
|
|
|
185
178
|
* active (a due probe runs the primary), else the role's own resolved config. The hold and
|
|
186
179
|
* start passes read this so a role mid-episode is keyed by the provider it will actually use.
|
|
187
180
|
* Pure read of config + state + the caller's clock. */
|
|
188
|
-
/** Which pair set an image reroute may look among (plans/image-input.md "Reroute target"
|
|
189
|
-
* "Regime"): the free fallback pairs while the budget gate is on fallback OR the role is
|
|
190
|
-
* in a model-fallback episode; otherwise the primary tiers. One home for the rule, shared
|
|
191
|
-
* by the target-resolution sites (the verdict's reroute arm, the attachment preflight). */
|
|
192
|
-
rerouteRegime(now) {
|
|
193
|
-
if (this.budgetFallbackActive)
|
|
194
|
-
return "fallback";
|
|
195
|
-
if (modelFallbackActive(this.state.modelFallback, now))
|
|
196
|
-
return "fallback";
|
|
197
|
-
return "primary";
|
|
198
|
-
}
|
|
199
181
|
runConfig(now) {
|
|
200
|
-
// A pending image-input reroute (plans/image-input.md part 4/5) outranks the role's own
|
|
201
|
-
// tier and its model fallback: the next run starts on the image-capable target the
|
|
202
|
-
// blind-read run recorded, rebuilt from the stored seam over the live config.
|
|
203
|
-
if (this.state.imageReroute) {
|
|
204
|
-
const r = this.state.imageReroute;
|
|
205
|
-
return withModelOverrides(this.config, r.tier, {
|
|
206
|
-
backend: r.backend,
|
|
207
|
-
provider: r.provider,
|
|
208
|
-
model: r.model,
|
|
209
|
-
thinking: r.thinking,
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
182
|
const fallback = fallbackRoleConfig(this.config, this.role);
|
|
213
183
|
if (fallback !== null && modelFallbackActive(this.state.modelFallback, now))
|
|
214
184
|
return fallback;
|
|
@@ -323,7 +293,6 @@ export class RoleRunner {
|
|
|
323
293
|
warn: (message) => this.warn(message),
|
|
324
294
|
runSignal: () => this.runSignal(),
|
|
325
295
|
runPrompt: (backend) => this.runPrompt(backend),
|
|
326
|
-
rerouteRegime: (now) => this.rerouteRegime(now),
|
|
327
296
|
get recoveryFailure() {
|
|
328
297
|
return self.recoveryFailure;
|
|
329
298
|
},
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import { DAY_MS } from "../text/datetime.js";
|
|
10
10
|
import { hasOutstandingWork } from "../runner/runner-state.js";
|
|
11
|
-
import { backlogHolds, eligibleEntries } from "../backlog/backlog-eligibility.js";
|
|
11
|
+
import { backlogHolds, eligibleEntries, roleSection } from "../backlog/backlog-eligibility.js";
|
|
12
12
|
import { entryKey } from "../backlog/backlog-parts.js";
|
|
13
|
-
import { actionableEntryRanges, actionableEntryRangesFor } from "../backlog/backlog-
|
|
13
|
+
import { actionableEntryRanges, actionableEntryRangesFor } from "../backlog/backlog-index-data.js";
|
|
14
14
|
import { readTextOrNull } from "../files/files.js";
|
|
15
15
|
import { changeBaseRev, fileContentAt } from "../git/git.js";
|
|
16
16
|
import { INSTANCE_ROLES, baseRoleOf, configuredInstances } from "../roles/runner-ids.js";
|
|
@@ -27,20 +27,13 @@ export function planBacklogTarget(config) {
|
|
|
27
27
|
return configuredInstances(config, "feature") + 1;
|
|
28
28
|
}
|
|
29
29
|
/** Does PLANS.md already hold the plan role's target (planBacklogTarget eligible plans), with no
|
|
30
|
-
* plan marked needs-review
|
|
30
|
+
* plan marked needs-review? The charter makes splitting or replanning a marked
|
|
31
31
|
* plan outrank the stop, so a marked entry keeps the plan role running; otherwise a plan run can
|
|
32
32
|
* only end in TUMWATER_NOTHING_TO_DO and the scheduler defers it (deferRunReason). */
|
|
33
33
|
export function planBacklogFull(root, config) {
|
|
34
34
|
if (eligibleEntries(root, "feature").length < planBacklogTarget(config))
|
|
35
35
|
return false;
|
|
36
|
-
return !backlogHolds(root).plans.some((h) => h === "needs-review"
|
|
37
|
-
}
|
|
38
|
-
/** The backlog file and section a role's entries live under: PLANS.md's `## Planned` for
|
|
39
|
-
* feature, BUGS.md's `## Open` for bugfix. An instance id resolves through its base role. */
|
|
40
|
-
function roleSection(role) {
|
|
41
|
-
return baseRoleOf(role) === "bugfix"
|
|
42
|
-
? { file: "BUGS.md", section: "Open" }
|
|
43
|
-
: { file: "PLANS.md", section: "Planned" };
|
|
36
|
+
return !backlogHolds(root).plans.some((h) => h === "needs-review");
|
|
44
37
|
}
|
|
45
38
|
/** Every entry key listed under a role's backlog section, held or not: the set that separates a
|
|
46
39
|
* claim whose entry left the section (`left`) from one that is still listed but ineligible
|
|
@@ -9,11 +9,14 @@ import { pausedRoles, pausedRolesUntil, scheduledStopAt, scheduledWakes, standin
|
|
|
9
9
|
import { orchestratorAlive, readOrchestratorInfo, } from "../team/orchestrator-info.js";
|
|
10
10
|
import { bootstrapHoldsRole, bootstrapStatus } from "../gates/bootstrap-gates.js";
|
|
11
11
|
import { readMergeQueue } from "../merge/merge-queue.js";
|
|
12
|
-
import {
|
|
12
|
+
import { collectRoleRows } from "./status-roles.js";
|
|
13
13
|
import { collectInboxes } from "./status-inbox.js";
|
|
14
|
+
import { collectRoleNotesOverCap } from "./status-role-notes.js";
|
|
14
15
|
import { collectBudget } from "./status-budget.js";
|
|
15
16
|
import { collectRootInstallDrift } from "./status-install.js";
|
|
16
17
|
import { detectBuildCheck } from "../build/build-check-detect.js";
|
|
18
|
+
import { briefBlockMissing, briefBlockOverCap } from "../brief/brief.js";
|
|
19
|
+
import { principlesBlockOverCap } from "../prompt/principles.js";
|
|
17
20
|
/** The earlier of the two optional stop deadlines (`run --for`'s and a scheduled stop's). */
|
|
18
21
|
function earliestStop(a, b) {
|
|
19
22
|
return a === undefined ? b : b === undefined ? a : Math.min(a, b);
|
|
@@ -23,6 +26,128 @@ function earliestStop(a, b) {
|
|
|
23
26
|
export function scopeSnapshotToRoles(snap, roles) {
|
|
24
27
|
return { ...snap, roles: snap.roles.filter((s) => roleInScope(s.role, roles)) };
|
|
25
28
|
}
|
|
29
|
+
/** The run-state header fields (running/pid/build) of the snapshot. */
|
|
30
|
+
function snapshotRunFields(running, info) {
|
|
31
|
+
return {
|
|
32
|
+
running,
|
|
33
|
+
pid: info?.pid,
|
|
34
|
+
build: running && info?.build ? info.build : null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** The director and per-role inbox fields and the open question count of the snapshot. The
|
|
38
|
+
* `collectInboxes` read stays in `snapshot`; the
|
|
39
|
+
* builder recomposes the destructured values and reads the open question count from `root`. */
|
|
40
|
+
function snapshotInboxFields(root, inboxes) {
|
|
41
|
+
return {
|
|
42
|
+
inbox: inboxes.inbox,
|
|
43
|
+
inboxPrompts: inboxes.inboxPrompts,
|
|
44
|
+
inboxFiles: inboxes.inboxFiles,
|
|
45
|
+
inboxQueuedAt: inboxes.inboxQueuedAt,
|
|
46
|
+
inboxNotBefore: inboxes.inboxNotBefore,
|
|
47
|
+
roleInbox: inboxes.roleInbox,
|
|
48
|
+
roleInboxPrompts: inboxes.roleInboxPrompts,
|
|
49
|
+
questions: openQuestions(root).length,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/** The spend/billing fields of the snapshot: the budget, the per-backend subscription windows
|
|
53
|
+
* (and their claude-window spelling) and the running-only chain/provider holds. */
|
|
54
|
+
function snapshotBudgetFields(budget, subscriptionWindows, subscriptionOnly, claudeBlock, running, info) {
|
|
55
|
+
return {
|
|
56
|
+
// Unconditional (never null): a disabled team still shows its spend and the badge is
|
|
57
|
+
// the affordance for SETTING a cap. models.json itself is stat-cached inside pi-models.ts,
|
|
58
|
+
// so an unchanged catalog costs one stat per poll, not a re-read plus parse.
|
|
59
|
+
budget,
|
|
60
|
+
// One window block per subscription backend in use, raw for `status --json` and the
|
|
61
|
+
// parts 3b/9–3e/9 displays; omitted when no subscription backend reported a window.
|
|
62
|
+
...(subscriptionWindows.length > 0 ? { subscriptionWindows } : {}),
|
|
63
|
+
subscriptionOnly,
|
|
64
|
+
...(claudeBlock
|
|
65
|
+
? {
|
|
66
|
+
claudeWindow: {
|
|
67
|
+
windows: claudeBlock.windows,
|
|
68
|
+
primary: claudeBlock.primary,
|
|
69
|
+
only: subscriptionOnly,
|
|
70
|
+
heldUntil: claudeBlock.heldUntil,
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
: {}),
|
|
74
|
+
...(running && info?.chain && Object.keys(info.chain).length > 0 ? { chain: info.chain } : {}),
|
|
75
|
+
// Only while running: a stale info file beside a dead pid must not speak for a stopped
|
|
76
|
+
// team, and only while a hold stands (an empty list is the same as none).
|
|
77
|
+
...(running && info?.providerHolds && info.providerHolds.length > 0 ? { providerHolds: info.providerHolds } : {}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/** The pause/quiet-hours fields of the snapshot: the paused flags and their deadlines, the
|
|
81
|
+
* maintenance quota and bootstrap holds, and the per-role quiet windows. */
|
|
82
|
+
function snapshotPauseFields(teamPause, markerRoles, roles, markerSet, capPaused, maintenanceQuota, bootstrapNow, roleRows, budgetPausedRoles, cfg, root, wakes, quiet) {
|
|
83
|
+
return {
|
|
84
|
+
paused: teamPause !== null,
|
|
85
|
+
pausedRoles: markerRoles,
|
|
86
|
+
pausedRunners: roles.filter((id) => runnerPaused(markerSet, id)),
|
|
87
|
+
capPaused,
|
|
88
|
+
maintenanceQuota,
|
|
89
|
+
// The new-project bootstrap hold (plans/work-ratio.md, part 2/2): the same read-only
|
|
90
|
+
// verdict the gate computes (bootstrapStatus), so the dashboards and the scheduler agree.
|
|
91
|
+
// The held set is computed with the gate's own predicate over the same role list.
|
|
92
|
+
bootstrap: bootstrapNow?.active
|
|
93
|
+
? { plansDone: bootstrapNow.plansDone, untilPlansDone: bootstrapNow.untilPlansDone }
|
|
94
|
+
: undefined,
|
|
95
|
+
bootstrapHeld: bootstrapNow?.active
|
|
96
|
+
? roleRows.filter((l) => bootstrapHoldsRole(l.role, openBugs(root).length > 0)).map((l) => l.role)
|
|
97
|
+
: [],
|
|
98
|
+
budgetPausedRoles,
|
|
99
|
+
// The per-role quiet windows' held roles (PLANS.md quietHoursPerRole): keyed role →
|
|
100
|
+
// window, so the dashboards can render the same `quiet until <end>` wording the team
|
|
101
|
+
// badge uses, scoped to the role's own schedule. The window string comes back out of
|
|
102
|
+
// the same config the hold was decided from, so the label cannot name a window that is
|
|
103
|
+
// not holding (roleQuietHold true implies a parseable, non-empty string there).
|
|
104
|
+
roleQuietPaused: Object.fromEntries(roleRows
|
|
105
|
+
.filter((l) => l.role !== DIRECTOR_ROLE && roleQuietHold(cfg.quietHoursPerRole, baseRoleOf(l.role), new Date()))
|
|
106
|
+
.map((l) => [l.role, cfg.quietHoursPerRole?.[baseRoleOf(l.role)] ?? ""])),
|
|
107
|
+
pausedUntil: teamPause?.until,
|
|
108
|
+
pausedRolesUntil: pausedRolesUntil(root),
|
|
109
|
+
scheduledWake: wakes[0],
|
|
110
|
+
scheduledWakes: wakes.length > 0 ? wakes : undefined,
|
|
111
|
+
pauseReason: teamPause?.reason,
|
|
112
|
+
quietHours: quiet.window ?? undefined,
|
|
113
|
+
inQuietHours: quiet.inWindow,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/** The merge-queue and doctor health fields of the snapshot (main-check, ci-parity and the
|
|
117
|
+
* build-check/brief/principles flags, plus the role notes over cap). */
|
|
118
|
+
function snapshotHealthFields(root, cfg, mergeQueue, roleNotesOverCap) {
|
|
119
|
+
return {
|
|
120
|
+
mergeQueue,
|
|
121
|
+
mainCheck: mainCheckForPoll(root, cfg),
|
|
122
|
+
ciParity: ciParityForPoll(root, cfg),
|
|
123
|
+
noDeclaredCheck: detectBuildCheck(root, cfg) === null ? true : undefined,
|
|
124
|
+
noBrief: briefBlockMissing(root) ? true : undefined,
|
|
125
|
+
briefTruncated: briefBlockOverCap(root) ? true : undefined,
|
|
126
|
+
...(roleNotesOverCap.length > 0 ? { roleNotesOverCap } : {}),
|
|
127
|
+
principlesTruncated: principlesBlockOverCap(root) ? true : undefined,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
/** The running-only resource fields of the snapshot (disk floor, install drift, permits and
|
|
131
|
+
* the run stop deadlines). */
|
|
132
|
+
function snapshotResourceFields(root, running, info, rootDrift) {
|
|
133
|
+
return {
|
|
134
|
+
// The disk floor's published state (plans/disk-floor.md, part 4/4): only while the
|
|
135
|
+
// orchestrator runs — the exit removes the info file, so a stale file beside a dead pid
|
|
136
|
+
// must not speak for a stopped team — and only when the last poll could measure the
|
|
137
|
+
// volume. Absent otherwise, so the surfaces render exactly as before.
|
|
138
|
+
disk: running && info?.disk ? info.disk : undefined,
|
|
139
|
+
// Independent of the team running: a stopped team's worktrees still borrow the root install.
|
|
140
|
+
rootInstallDrift: rootDrift.length ? rootDrift : undefined,
|
|
141
|
+
// Only while running: a stale info file beside a dead pid must not speak for a stopped team.
|
|
142
|
+
permits: running && info?.permits ? info.permits : undefined,
|
|
143
|
+
slotPool: running && info?.slotPool ? info.slotPool : undefined,
|
|
144
|
+
// Only while running: a stale info file beside a dead pid must not speak for a stopped team.
|
|
145
|
+
checkPermits: running && info?.checkPermits ? info.checkPermits : undefined,
|
|
146
|
+
// Only while running: a stale info file beside a dead pid must not speak for a stopped team.
|
|
147
|
+
runStopsAt: running ? earliestStop(info?.stopsAt, scheduledStopAt(root)) : undefined,
|
|
148
|
+
scheduledStopAt: running ? scheduledStopAt(root) : undefined,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
26
151
|
/** One fresh team snapshot for observers. `modelsPath` overrides pi's model definitions
|
|
27
152
|
* location (default ~/.pi/agent/models.json) — a test seam, like doctor's pathEnv. `now` pins
|
|
28
153
|
* the poll's clock for the budget figures (the persisted day-stamp check and the burn-rate
|
|
@@ -51,9 +176,9 @@ export function snapshot(root, modelsPath = piModelsPath(), now = Date.now()) {
|
|
|
51
176
|
// `disk` and the rows agree.
|
|
52
177
|
const slots = readSlotsState(root).slots;
|
|
53
178
|
const roleRows = collectRoleRows(root, cfg, now, slots, modelsPath, markerSet);
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
const
|
|
179
|
+
// Every role whose notes file runs past the read bound (status-role-notes.ts): the standing
|
|
180
|
+
// twin of the doctor `role notes` row, raised as the amber role-notes-truncated alert.
|
|
181
|
+
const roleNotesOverCap = collectRoleNotesOverCap(root);
|
|
57
182
|
// One inbox pass per poll (status-inbox.ts) serves all four director fields and the per-role
|
|
58
183
|
// counts/entries from the same queuedRolePromptEntries reads.
|
|
59
184
|
const { inbox, inboxPrompts, inboxFiles, inboxQueuedAt, inboxNotBefore, roleInbox, roleInboxPrompts } = collectInboxes(root, roles, now);
|
|
@@ -104,94 +229,18 @@ export function snapshot(root, modelsPath = piModelsPath(), now = Date.now()) {
|
|
|
104
229
|
// The one-release claude spelling (see StatusSnapshot.claudeWindow), derived from the claude
|
|
105
230
|
// block so `subscriptionWindows` stays the single source of the window data.
|
|
106
231
|
const claudeBlock = subscriptionWindows.find((b) => b.backend === "claude");
|
|
232
|
+
// The maintenance allowance (plans/work-ratio.md, part 1b/4): the same pure verdict the
|
|
233
|
+
// gate folds, over the same event log, so the dashboards show the hold the scheduler
|
|
234
|
+
// enforces. `merges` is the one merge queue snapshot read above, so in-flight maintenance
|
|
235
|
+
// merges count once here and in mergeQueue.
|
|
236
|
+
const maintenanceQuota = maintenanceQuotaForPoll(root, cfg.maintenancePerWorkMerge, roleRows, new Set(merges.map((e) => e.role)), now);
|
|
107
237
|
return {
|
|
108
|
-
running,
|
|
109
|
-
|
|
110
|
-
build: running && info?.build ? info.build : null,
|
|
111
|
-
inbox,
|
|
112
|
-
inboxPrompts,
|
|
113
|
-
inboxFiles,
|
|
114
|
-
inboxQueuedAt,
|
|
115
|
-
inboxNotBefore,
|
|
116
|
-
roleInbox,
|
|
117
|
-
roleInboxPrompts,
|
|
118
|
-
questions: openQuestions(root).length,
|
|
119
|
-
imageProbeFailures,
|
|
238
|
+
...snapshotRunFields(running, info),
|
|
239
|
+
...snapshotInboxFields(root, { inbox, inboxPrompts, inboxFiles, inboxQueuedAt, inboxNotBefore, roleInbox, roleInboxPrompts }),
|
|
120
240
|
roles: roleRows,
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// One window block per subscription backend in use, raw for `status --json` and the
|
|
126
|
-
// parts 3b/9–3e/9 displays; omitted when no subscription backend reported a window.
|
|
127
|
-
...(subscriptionWindows.length > 0 ? { subscriptionWindows } : {}),
|
|
128
|
-
subscriptionOnly,
|
|
129
|
-
...(claudeBlock
|
|
130
|
-
? {
|
|
131
|
-
claudeWindow: {
|
|
132
|
-
windows: claudeBlock.windows,
|
|
133
|
-
primary: claudeBlock.primary,
|
|
134
|
-
only: subscriptionOnly,
|
|
135
|
-
heldUntil: claudeBlock.heldUntil,
|
|
136
|
-
},
|
|
137
|
-
}
|
|
138
|
-
: {}),
|
|
139
|
-
...(running && info?.chain && Object.keys(info.chain).length > 0 ? { chain: info.chain } : {}),
|
|
140
|
-
// Only while running: a stale info file beside a dead pid must not speak for a stopped
|
|
141
|
-
// team, and only while a hold stands (an empty list is the same as none).
|
|
142
|
-
...(running && info?.providerHolds && info.providerHolds.length > 0 ? { providerHolds: info.providerHolds } : {}),
|
|
143
|
-
paused: teamPause !== null,
|
|
144
|
-
pausedRoles: markerRoles,
|
|
145
|
-
pausedRunners: roles.filter((id) => runnerPaused(markerSet, id)),
|
|
146
|
-
capPaused,
|
|
147
|
-
// The maintenance allowance (plans/work-ratio.md, part 1b/4): the same pure verdict the
|
|
148
|
-
// gate folds, over the same event log, so the dashboards show the hold the scheduler
|
|
149
|
-
// enforces. `merges` is the one merge queue snapshot read above, so in-flight maintenance
|
|
150
|
-
// merges count once here and in mergeQueue.
|
|
151
|
-
maintenanceQuota: maintenanceQuotaForPoll(root, cfg.maintenancePerWorkMerge, roleRows, new Set(merges.map((e) => e.role)), now),
|
|
152
|
-
// The new-project bootstrap hold (plans/work-ratio.md, part 2/2): the same read-only
|
|
153
|
-
// verdict the gate computes (bootstrapStatus), so the dashboards and the scheduler agree.
|
|
154
|
-
// The held set is computed with the gate's own predicate over the same role list.
|
|
155
|
-
bootstrap: bootstrapNow?.active
|
|
156
|
-
? { plansDone: bootstrapNow.plansDone, untilPlansDone: bootstrapNow.untilPlansDone }
|
|
157
|
-
: undefined,
|
|
158
|
-
bootstrapHeld: bootstrapNow?.active
|
|
159
|
-
? roleRows.filter((l) => bootstrapHoldsRole(l.role, openBugs(root).length > 0)).map((l) => l.role)
|
|
160
|
-
: [],
|
|
161
|
-
budgetPausedRoles,
|
|
162
|
-
// The per-role quiet windows' held roles (PLANS.md quietHoursPerRole): keyed role →
|
|
163
|
-
// window, so the dashboards can render the same `quiet until <end>` wording the team
|
|
164
|
-
// badge uses, scoped to the role's own schedule. The window string comes back out of
|
|
165
|
-
// the same config the hold was decided from, so the label cannot name a window that is
|
|
166
|
-
// not holding (roleQuietHold true implies a parseable, non-empty string there).
|
|
167
|
-
roleQuietPaused: Object.fromEntries(roleRows
|
|
168
|
-
.filter((l) => l.role !== DIRECTOR_ROLE && roleQuietHold(cfg.quietHoursPerRole, baseRoleOf(l.role), new Date()))
|
|
169
|
-
.map((l) => [l.role, cfg.quietHoursPerRole?.[baseRoleOf(l.role)] ?? ""])),
|
|
170
|
-
pausedUntil: teamPause?.until,
|
|
171
|
-
pausedRolesUntil: pausedRolesUntil(root),
|
|
172
|
-
scheduledWake: wakes[0],
|
|
173
|
-
scheduledWakes: wakes.length > 0 ? wakes : undefined,
|
|
174
|
-
pauseReason: teamPause?.reason,
|
|
175
|
-
quietHours: quiet.window ?? undefined,
|
|
176
|
-
inQuietHours: quiet.inWindow,
|
|
177
|
-
mergeQueue,
|
|
178
|
-
mainCheck: mainCheckForPoll(root, cfg),
|
|
179
|
-
ciParity: ciParityForPoll(root, cfg),
|
|
180
|
-
noDeclaredCheck: detectBuildCheck(root, cfg) === null ? true : undefined,
|
|
181
|
-
// The disk floor's published state (plans/disk-floor.md, part 4/4): only while the
|
|
182
|
-
// orchestrator runs — the exit removes the info file, so a stale file beside a dead pid
|
|
183
|
-
// must not speak for a stopped team — and only when the last poll could measure the
|
|
184
|
-
// volume. Absent otherwise, so the surfaces render exactly as before.
|
|
185
|
-
disk: running && info?.disk ? info.disk : undefined,
|
|
186
|
-
// Independent of the team running: a stopped team's worktrees still borrow the root install.
|
|
187
|
-
rootInstallDrift: rootDrift.length ? rootDrift : undefined,
|
|
188
|
-
// Only while running: a stale info file beside a dead pid must not speak for a stopped team.
|
|
189
|
-
permits: running && info?.permits ? info.permits : undefined,
|
|
190
|
-
slotPool: running && info?.slotPool ? info.slotPool : undefined,
|
|
191
|
-
// Only while running: a stale info file beside a dead pid must not speak for a stopped team.
|
|
192
|
-
checkPermits: running && info?.checkPermits ? info.checkPermits : undefined,
|
|
193
|
-
// Only while running: a stale info file beside a dead pid must not speak for a stopped team.
|
|
194
|
-
runStopsAt: running ? earliestStop(info?.stopsAt, scheduledStopAt(root)) : undefined,
|
|
195
|
-
scheduledStopAt: running ? scheduledStopAt(root) : undefined,
|
|
241
|
+
...snapshotBudgetFields(budget, subscriptionWindows, subscriptionOnly, claudeBlock, running, info),
|
|
242
|
+
...snapshotPauseFields(teamPause, markerRoles, roles, markerSet, capPaused, maintenanceQuota, bootstrapNow, roleRows, budgetPausedRoles, cfg, root, wakes, quiet),
|
|
243
|
+
...snapshotHealthFields(root, cfg, mergeQueue, roleNotesOverCap),
|
|
244
|
+
...snapshotResourceFields(root, running, info, rootDrift),
|
|
196
245
|
};
|
|
197
246
|
}
|