tumwater 0.4.2 → 0.4.3
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 +6 -6
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +6 -6
- package/dist/src/agent/{run-agent.js → agent-run.js} +25 -12
- package/dist/src/agent/{run-settle.js → agent-settle.js} +48 -24
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +21 -12
- package/dist/src/agent/{run-stream.js → agent-stream.js} +4 -4
- package/dist/src/agent/backend-traits.js +2 -2
- package/dist/src/agent/claude/claude-account.js +1 -8
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-env.js +27 -31
- package/dist/src/agent/claude/claude-stream.js +111 -54
- package/dist/src/agent/codex/codex-account.js +9 -0
- package/dist/src/agent/codex/codex-backend.js +2 -2
- package/dist/src/agent/codex/codex-env.js +28 -15
- package/dist/src/agent/codex/codex-hook-trust.js +39 -20
- package/dist/src/agent/codex/codex-rollout.js +93 -56
- package/dist/src/agent/codex/codex-stream.js +1 -1
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +68 -11
- 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 +50 -27
- package/dist/src/agent/opencode/opencode-config.js +32 -14
- package/dist/src/agent/opencode/opencode-env.js +12 -13
- package/dist/src/agent/opencode/opencode-stream.js +47 -25
- package/dist/src/agent/session-ledger.js +1 -1
- package/dist/src/agent/subscription-account.js +3 -3
- package/dist/src/backlog/backlog-cli-view.js +81 -41
- package/dist/src/backlog/backlog-cli.js +57 -26
- package/dist/src/backlog/backlog-eligibility.js +58 -102
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +1 -1
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/fallback-breaker.js +2 -2
- package/dist/src/build/build-check-detect.js +43 -14
- package/dist/src/build/build-check-events.js +2 -2
- package/dist/src/build/build-check-scoped.js +40 -16
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +34 -12
- package/dist/src/cli/cli-query-commands.js +1 -1
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-start.js +91 -58
- package/dist/src/cli/completion.js +82 -46
- package/dist/src/cli/config-commands.js +82 -71
- package/dist/src/cli/help.js +4 -6
- package/dist/src/cli/log-commands.js +43 -23
- package/dist/src/cli/notify-command.js +2 -2
- package/dist/src/cli/question-commands.js +86 -60
- package/dist/src/cli.js +181 -135
- package/dist/src/collections.js +31 -16
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +22 -0
- package/dist/src/concurrency/run-permit.js +49 -0
- package/dist/src/concurrency/semaphore.js +46 -250
- package/dist/src/config/config-editable-keys.js +4 -7
- package/dist/src/config/config-fallbacks.js +81 -101
- package/dist/src/config/config-field-checks.js +66 -37
- package/dist/src/config/config-keys.js +5 -20
- package/dist/src/config/config-live.js +4 -28
- package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
- package/dist/src/config/config-request.js +153 -102
- package/dist/src/config/config-schema.js +9 -11
- package/dist/src/config/config-selector-checks.js +1 -1
- package/dist/src/config/config-validation-agents.js +2 -2
- package/dist/src/config/config-validation-roles.js +4 -52
- package/dist/src/config/config-validation.js +4 -38
- package/dist/src/config/config-views.js +9 -34
- package/dist/src/config/config-write.js +6 -23
- package/dist/src/config/config.js +4 -5
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +24 -24
- package/dist/src/doctor/doctor-backlog.js +7 -5
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +5 -5
- package/dist/src/doctor/doctor-claude-checks.js +44 -28
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +44 -112
- package/dist/src/doctor/doctor-fallback-checks.js +2 -2
- package/dist/src/doctor/doctor-holds.js +6 -45
- package/dist/src/doctor/doctor-install.js +1 -1
- package/dist/src/doctor/doctor-launch-services.js +1 -1
- package/dist/src/doctor/doctor-model-checks.js +178 -94
- package/dist/src/doctor/doctor-notify.js +2 -2
- package/dist/src/doctor/doctor-opencode-checks.js +5 -4
- package/dist/src/doctor/doctor-orphans.js +120 -20
- package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
- package/dist/src/doctor/doctor-pi-checks.js +2 -2
- package/dist/src/doctor/doctor-principles.js +3 -3
- package/dist/src/doctor/doctor-project-checks.js +3 -3
- package/dist/src/doctor/doctor-role-notes.js +1 -1
- package/dist/src/doctor/doctor.js +8 -11
- package/dist/src/events/event-format.js +30 -54
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +1 -3
- package/dist/src/events/{notify.js → event-notify.js} +2 -2
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/failure/failure-cluster.js +6 -6
- package/dist/src/failure/failure-data.js +8 -4
- package/dist/src/failure/failure-render.js +44 -34
- package/dist/src/failure/failure-state-change.js +4 -34
- package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
- package/dist/src/files/files.js +30 -0
- package/dist/src/files/json-object.js +8 -8
- package/dist/src/files/stat-cache.js +12 -8
- package/dist/src/gates/budget-gates.js +85 -84
- package/dist/src/gates/gate-polls.js +27 -68
- package/dist/src/gates/pause-gates.js +16 -2
- package/dist/src/gates/readiness.js +1 -1
- package/dist/src/gates/role-cap-gates.js +44 -35
- package/dist/src/gates/{startup-gate.js → startup-gates.js} +7 -7
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +3 -3
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +61 -41
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +22 -23
- package/dist/src/gui/gui-prompt-commands.js +23 -23
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +51 -33
- package/dist/src/inbox/prompt-commands.js +59 -42
- package/dist/src/init/init.js +88 -40
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-core.js +1 -1
- package/dist/src/merge/merge-drain.js +8 -11
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +10 -13
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-verify.js +8 -18
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +76 -44
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/orchestrator/orchestrator-launch.js +31 -63
- package/dist/src/orchestrator/orchestrator-poll.js +27 -32
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +1 -1
- package/dist/src/orchestrator/scheduling-skip.js +1 -1
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +8 -7
- package/dist/src/pi/pi-args.js +5 -2
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +3 -3
- package/dist/src/pi/pi-event-line.js +1 -1
- package/dist/src/pi/pi-models.js +43 -29
- package/dist/src/pi/pi-settings.js +43 -20
- package/dist/src/pi/pi-stream.js +32 -6
- package/dist/src/pi/pi-watchdogs.js +12 -5
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +32 -20
- package/dist/src/pi-extension/kill-guard.js +69 -0
- package/dist/src/process/process-group.js +49 -5
- package/dist/src/process/process.js +8 -8
- package/dist/src/process/run-marker.js +31 -13
- package/dist/src/process/supervisor.js +40 -28
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +2 -2
- package/dist/src/prompt/notes-backlog.js +66 -37
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt-followup.js +12 -4
- package/dist/src/prompt/prompt.js +88 -38
- package/dist/src/report/report-data.js +53 -37
- package/dist/src/report/report-fold.js +16 -9
- package/dist/src/report/report-render.js +21 -194
- package/dist/src/report/report-since-data.js +4 -3
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +8 -8
- package/dist/src/report/report.js +46 -22
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +1 -1
- package/dist/src/review/review-precheck.js +11 -1
- package/dist/src/review/review.js +40 -153
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/role-catalog.js +11 -3
- package/dist/src/roles/role-guidance.js +16 -12
- package/dist/src/roles/role-render.js +1 -14
- package/dist/src/roles/role-view.js +9 -21
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/run-apply.js +12 -6
- package/dist/src/run/run-detail-data.js +121 -68
- package/dist/src/run/run-detail-render.js +92 -35
- package/dist/src/run/run-detail.js +72 -41
- package/dist/src/run/run-prompt.js +63 -50
- package/dist/src/run/run-resume.js +76 -29
- package/dist/src/run/run-stage.js +135 -84
- package/dist/src/run/run-timing.js +8 -2
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +119 -50
- package/dist/src/run/stage-check.js +49 -32
- package/dist/src/run/stage-checks-shared.js +28 -0
- package/dist/src/run/stage-name-checks.js +111 -92
- package/dist/src/run/stage-path-checks.js +69 -42
- package/dist/src/run/stage-prose-checks.js +55 -40
- package/dist/src/runner/runner-outcome.js +6 -1
- package/dist/src/runner/runner-pi.js +3 -10
- package/dist/src/runner/runner-recovery.js +1 -1
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -1
- package/dist/src/runner/runner-run.js +97 -35
- package/dist/src/runner/runner-state-load.js +12 -9
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-data.js +2 -2
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -17
- package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
- package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
- package/dist/src/team/team-hold.js +98 -65
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +2 -15
- package/dist/src/team/team-polls.js +28 -126
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +20 -15
- package/dist/src/text/text.js +3 -3
- package/dist/src/ui/alert-feeds.js +49 -73
- package/dist/src/ui/badges.js +3 -13
- package/dist/src/ui/gui/gui-client-boot.js +2 -2
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +7 -17
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-history.js +28 -14
- package/dist/src/ui/gui/gui-client-model.js +0 -12
- package/dist/src/ui/gui/gui-client-pending.js +7 -6
- package/dist/src/ui/gui/gui-client-report-chart.js +5 -5
- package/dist/src/ui/gui/gui-client-report.js +1 -1
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -84
- package/dist/src/ui/gui/gui-client-team.js +4 -4
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +53 -40
- package/dist/src/ui/run-progress-model.js +9 -2
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +3 -7
- package/dist/src/ui/status-render.js +5 -9
- package/dist/src/ui/team-alert-backlog.js +6 -6
- package/dist/src/ui/team-alert-status.js +9 -170
- package/dist/src/ui/team-alert-tail.js +19 -215
- package/dist/src/ui/team-alerts.js +7 -8
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-editor-answer.js +12 -10
- package/dist/src/ui/tui/tui-editor-attach.js +4 -3
- package/dist/src/ui/tui/tui-editor-backends.js +4 -3
- package/dist/src/ui/tui/tui-editor-brief.js +6 -9
- package/dist/src/ui/tui/tui-editor-budget.js +4 -3
- package/dist/src/ui/tui/tui-editor-config.js +80 -68
- package/dist/src/ui/tui/tui-editor-entry.js +12 -20
- package/dist/src/ui/tui/tui-editor-pause.js +51 -57
- package/dist/src/ui/tui/tui-editor-prompt.js +9 -13
- package/dist/src/ui/tui/tui-editor-queued.js +11 -14
- package/dist/src/ui/tui/tui-editor-quiet.js +4 -3
- package/dist/src/ui/tui/tui-editor-release.js +4 -3
- package/dist/src/ui/tui/tui-editor-retire.js +7 -7
- package/dist/src/ui/tui/tui-editor-role-enabled.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-file.js +4 -4
- package/dist/src/ui/tui/tui-editor-role-key.js +4 -3
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +4 -3
- package/dist/src/ui/tui/tui-editor-session.js +17 -4
- package/dist/src/ui/tui/tui-editors.js +1 -7
- package/dist/src/ui/tui/tui-frame.js +182 -134
- package/dist/src/ui/tui/tui-guard.js +20 -0
- package/dist/src/ui/tui/tui-input.js +159 -92
- package/dist/src/ui/tui/tui-keymap.js +51 -29
- package/dist/src/ui/tui/tui-keys.js +1 -1
- package/dist/src/ui/tui/tui-pane-activity.js +3 -3
- package/dist/src/ui/tui/tui-pane-backlog.js +74 -36
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +252 -228
- package/dist/src/ui/tui/tui-pane-history.js +61 -70
- package/dist/src/ui/tui/tui-pane-pending.js +284 -247
- package/dist/src/ui/tui/tui-pane-queued.js +25 -27
- package/dist/src/ui/tui/tui-pane-report.js +2 -1
- package/dist/src/ui/tui/tui-pane-session.js +11 -7
- package/dist/src/ui/tui/tui-pane-transcript.js +19 -31
- package/dist/src/ui/tui/tui-pane.js +14 -11
- package/dist/src/ui/tui/tui-panes.js +29 -32
- package/dist/src/ui/tui/tui.js +3 -4
- package/dist/src/verdict/fix-claim.js +76 -54
- package/dist/src/verdict/refusal.js +1 -1
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
- package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
- /package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +0 -0
- /package/dist/src/doctor/{probe.js → doctor-probe.js} +0 -0
- /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
- /package/dist/src/operator/{retire.js → operator-retire.js} +0 -0
- /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
- /package/dist/src/runner/{leftover.js → runner-leftover.js} +0 -0
|
@@ -13,91 +13,78 @@ import { BUGFIX_ROLE, DEFERRABLE_ROLES, DIRECTOR_ROLE, roleTier, } from "../role
|
|
|
13
13
|
import { baseRoleOf, runnerEnabled } from "../roles/runner-ids.js";
|
|
14
14
|
import { HOUR_MS } from "../text/datetime.js";
|
|
15
15
|
import { yieldMultiplierFor } from "./backoff.js";
|
|
16
|
-
/**
|
|
17
|
-
* (
|
|
18
|
-
*
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// An interrupted run (graceful abort or crash) leaves half-finished work in its pi
|
|
37
|
-
// session and worktree: resume it promptly on restart instead of holding it for a full
|
|
38
|
-
// interval — the min gap below throttles scheduled runs, not recovery. nextRunAt still
|
|
39
|
-
// gates cut-off resumes, which deliberately wait one interval from their compacted context.
|
|
40
|
-
// A queued prompt never preempts this gate: a resuming run assembles buildResumePrompt
|
|
41
|
-
// and does not dequeue the per-role queue, so an early run would only resume sooner than
|
|
42
|
-
// its deliberate wait — the prompt is consumed by the fresh run the inbox check below
|
|
43
|
-
// makes due the moment the resume finishes.
|
|
16
|
+
/** Can the runner start at all right now? A role disabled in tumwater.json stops running
|
|
17
|
+
* immediately (live-reload; re-enabling resumes within one poll cycle because the runner and
|
|
18
|
+
* its persisted state survive), and an already-running role is never eligible. Either way the
|
|
19
|
+
* runner holds this poll — the single gate behind isEligible's two early returns. */
|
|
20
|
+
function blockedFromRunning(runner, s) {
|
|
21
|
+
return !runnerEnabled(runner.config, runner.role) || s.running === true;
|
|
22
|
+
}
|
|
23
|
+
/** The director carries the user's own requests: no min-gap, no backoff — a queued prompt
|
|
24
|
+
* runs as soon as the previous one finishes. Reaches either arm and always resolves. */
|
|
25
|
+
function directorGate(s, now, inboxCount, opts) {
|
|
26
|
+
if (inboxCount > 0)
|
|
27
|
+
return { run: true, reason: "inbox" };
|
|
28
|
+
if (opts.leftoverPending)
|
|
29
|
+
return now >= s.nextRunAt ? { run: true, reason: "leftover" } : { run: false };
|
|
30
|
+
return { run: false };
|
|
31
|
+
}
|
|
32
|
+
/** An interrupted run (graceful abort or crash) resumes promptly on restart; nextRunAt still
|
|
33
|
+
* gates cut-off resumes, which deliberately wait one interval. A queued prompt never preempts
|
|
34
|
+
* this gate. Resolves only when a resume is pending, else undefined. */
|
|
35
|
+
function resumeGate(s, now) {
|
|
44
36
|
if (s.resumePending) {
|
|
45
37
|
return now >= s.nextRunAt ? { run: true, reason: "resume" } : { run: false };
|
|
46
38
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
// after the run ends.
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/** A queued per-role prompt is due on its own existence, exactly like the director's inbox: no
|
|
42
|
+
* min-gap, no backoff, no scheduled clock. The queue is the durable record of the demand. */
|
|
43
|
+
function inboxGate(inboxCount) {
|
|
53
44
|
if (inboxCount > 0)
|
|
54
45
|
return { run: true, reason: "inbox" };
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
// multiplied by the multiplier its recent results earned — a search role that keeps
|
|
62
|
-
// finding nothing slows down, and the multiplication gates the "main moved" wake below
|
|
63
|
-
// too, which is where the cost actually comes from (the gap check above runs first, so a
|
|
64
|
-
// scaled role inside its stretched gap neither runs on schedule nor on a main move).
|
|
65
|
-
// One merge in the last ten counted runs restores the plain gap; error-class results
|
|
66
|
-
// never enter the ring (backoff.ts), so a failing backend does not stretch anything;
|
|
67
|
-
// `--once`, an operator wake, and a queued prompt all bypass the gap check entirely.
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
/** The per-role interval (a slow clock) gates scheduled runs and "main moved" early wakes.
|
|
49
|
+
* Yield scaling multiplies the gap; an operator wake overrides it. Resolves with the hold when
|
|
50
|
+
* the runner is inside the (possibly stretched) gap, else undefined. */
|
|
51
|
+
function gapGate(runner, s, now, opts) {
|
|
68
52
|
const mult = yieldMultiplierFor(runner.state);
|
|
69
|
-
// The same planBacklogOpen rule (scheduleConfigForRole) runner-run.ts schedules nextRunAt
|
|
70
|
-
// from: while PLANS.md has planning room, plan runs on a zero gap; once the target is met it
|
|
71
|
-
// drops to its 3600 s default (the plans-full deferral then holds the run). Yield scaling
|
|
72
|
-
// still applies to whatever gap is chosen.
|
|
73
53
|
const minGapSeconds = scheduleConfigForRole(runner.config, runner.role, opts.planBacklogOpen === true).minRunIntervalSeconds;
|
|
74
54
|
const minGap = opts.once ? 0 : minGapSeconds * 1000 * mult;
|
|
75
55
|
const sinceLast = now - (s.lastRunEndedAt ?? 0);
|
|
76
|
-
// An operator wake newer than the gap window's opening run overrides the interval
|
|
77
|
-
// (RunnerState.wokenAt): an explicit "try again now" with an empty queue must not silently
|
|
78
|
-
// wait out the remaining slow clock it ran inside — a queued prompt carries its own
|
|
79
|
-
// due-ness in the inbox check above, so it never depends on this stamp surviving.
|
|
80
56
|
const woken = freshlyWoken(s);
|
|
81
57
|
if (sinceLast < minGap && !woken) {
|
|
82
|
-
// Held inside the stretched gap: the runner is not due this poll (a schedule clock with
|
|
83
|
-
// the plain gap, or a main move under the multiplier, would otherwise admit it). Report
|
|
84
|
-
// the yield-scaled hold so the phase cell reads "waiting out its gap" instead of the
|
|
85
|
-
// bare "due — starts on the next poll" of the no-reason fallback (RunnerState.skipReason).
|
|
86
58
|
return mult > 1 ? { run: false, skip: "yield-gap" } : { run: false };
|
|
87
59
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
/** The scheduled clock, then the "main moved" wake. Once mode overrides the clock when
|
|
63
|
+
* backoffSeconds is 0 (a productive run scheduled it); a raised backoff honors the deadline.
|
|
64
|
+
* Resolves only once a scheduled reason applies, else undefined. */
|
|
65
|
+
function scheduledGate(s, now, mainHead, opts) {
|
|
91
66
|
if (now >= s.nextRunAt || (opts.once && s.backoffSeconds === 0)) {
|
|
92
67
|
return { run: true, reason: s.runs === 0 ? "startup" : "scheduled" };
|
|
93
68
|
}
|
|
94
|
-
// The world changed under a sleeping role: main moved since its last run. The stretched
|
|
95
|
-
// gap above gates this wake too (the gap check runs first), so a role whose recent runs
|
|
96
|
-
// merged nothing does not re-check on every merge — the multiplier's whole point.
|
|
97
69
|
if (s.lastMainHead && mainHead && mainHead !== s.lastMainHead) {
|
|
98
70
|
return { run: true, reason: "main moved" };
|
|
99
71
|
}
|
|
100
|
-
return
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
/** Should this role run now? `inboxCount` is the calling role's own queued-prompt count
|
|
75
|
+
* (the director's queue for the director, the per-role queue for everyone else) — see the
|
|
76
|
+
* inbox check below. Exported for tests. */
|
|
77
|
+
export function isEligible(runner, now, mainHead, inboxCount, opts = {}) {
|
|
78
|
+
const s = runner.state;
|
|
79
|
+
if (blockedFromRunning(runner, s))
|
|
80
|
+
return { run: false };
|
|
81
|
+
if (runner.role === DIRECTOR_ROLE)
|
|
82
|
+
return directorGate(s, now, inboxCount, opts);
|
|
83
|
+
return (resumeGate(s, now) ??
|
|
84
|
+
inboxGate(inboxCount) ??
|
|
85
|
+
gapGate(runner, s, now, opts) ??
|
|
86
|
+
scheduledGate(s, now, mainHead, opts) ??
|
|
87
|
+
{ run: false });
|
|
101
88
|
}
|
|
102
89
|
/** Fair scheduling order for one poll's eligible roles: the director always leads (it runs
|
|
103
90
|
* the user's prompts), then the work tier (feature/bugfix/plan) before maintenance — a long-idle
|
|
@@ -6,7 +6,7 @@ import { configForStatus, queueInFlight, mainCheckForPoll, maintenanceQuotaForPo
|
|
|
6
6
|
import { quietHoursStatus, roleQuietHold } from "../scheduling/quiet-hours.js";
|
|
7
7
|
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
8
8
|
import { pausedRoles, pausedRolesUntil, scheduledStopAt, scheduledWakes, standingTeamPause } from "../team/team-state.js";
|
|
9
|
-
import { orchestratorAlive, readOrchestratorInfo, } from "../team/orchestrator-info.js";
|
|
9
|
+
import { orchestratorAlive, readOrchestratorInfo, } from "../team/team-orchestrator-info.js";
|
|
10
10
|
import { bootstrapHoldsRole, bootstrapStatus } from "../gates/bootstrap-gates.js";
|
|
11
11
|
import { readMergeQueue } from "../merge/merge-queue.js";
|
|
12
12
|
import { collectRoleRows } from "./status-roles.js";
|
|
@@ -175,7 +175,7 @@ export function snapshot(root, modelsPath = piModelsPath(), now = Date.now()) {
|
|
|
175
175
|
// run or verification) or is pinned to — threaded into the roles collector so `running`,
|
|
176
176
|
// `disk` and the rows agree.
|
|
177
177
|
const slots = readSlotsState(root).slots;
|
|
178
|
-
const roleRows = collectRoleRows(root, cfg,
|
|
178
|
+
const roleRows = collectRoleRows(root, cfg, slots, modelsPath, markerSet);
|
|
179
179
|
// Every role whose notes file runs past the read bound (status-role-notes.ts): the standing
|
|
180
180
|
// twin of the doctor `role notes` row, raised as the amber role-notes-truncated alert.
|
|
181
181
|
const roleNotesOverCap = collectRoleNotesOverCap(root);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import { readRegularTextOrNull } from "../files/files.js";
|
|
4
4
|
import { cachedByStat } from "../files/stat-cache.js";
|
|
5
5
|
import { roleNotesPath } from "../paths.js";
|
|
6
6
|
import { ROLE_NOTES_MAX_BYTES } from "../agent/role-notes-write.js";
|
|
@@ -34,7 +34,7 @@ export function readRoleNotesOverCap(root) {
|
|
|
34
34
|
continue;
|
|
35
35
|
const file = path.join(notesDir, name);
|
|
36
36
|
const chars = cachedByStat(noteLengthCache, file, file, () => {
|
|
37
|
-
const text =
|
|
37
|
+
const text = readRegularTextOrNull(file);
|
|
38
38
|
return text === null ? null : text.length;
|
|
39
39
|
}, (n) => n);
|
|
40
40
|
if (chars === null)
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { configForRole, modelSelectorField, roleSeamTier, } from "../config/config-views.js";
|
|
3
|
-
import { modelFallbackView } from "../config/config-fallbacks.js";
|
|
4
3
|
import { isCustomRole } from "../config/config.js";
|
|
5
4
|
import { baseRoleOf, configuredInstances, instanceIndex, runnerIds, runnerPaused } from "../roles/runner-ids.js";
|
|
6
5
|
import { errorStreakAtWarn, QUIET_KILL_RESUME_LIMIT } from "../run/run-apply.js";
|
|
7
6
|
import { queuedMergeRoles } from "../merge/merge-queue.js";
|
|
8
7
|
import { hasOutstandingWork } from "../runner/runner-state.js";
|
|
9
|
-
import { formatModelSelector } from "../config/model-selector.js";
|
|
8
|
+
import { formatModelSelector } from "../config/config-model-selector.js";
|
|
10
9
|
import { piSettingsPath, readPiDefaultModel } from "../pi/pi-settings.js";
|
|
11
10
|
import { isJsonObject } from "../files/json-object.js";
|
|
12
11
|
import { runnerStateForPoll } from "./status-polls.js";
|
|
@@ -14,14 +13,14 @@ import { roleImageInfo } from "../agent/image-input.js";
|
|
|
14
13
|
import { backendDefaultLabel } from "../agent/backend-traits.js";
|
|
15
14
|
import { readPiListedModels } from "../pi/pi-listed-models.js";
|
|
16
15
|
import { seamContextWindowForRole } from "../pi/pi-models.js";
|
|
17
|
-
/** One row per enabled role id (part 5b/7). `configForRole`,
|
|
16
|
+
/** One row per enabled role id (part 5b/7). `configForRole`, the leased /
|
|
18
17
|
* pinned slot and the pi-default seam all resolve against the values `snapshot()` already
|
|
19
18
|
* read once for the poll — the info and slots pins (threaded as `slots`) keep rows, `running`
|
|
20
|
-
* and `disk` on the same shared reads
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
export function collectRoleRows(root, cfg,
|
|
19
|
+
* and `disk` on the same shared reads. One read of pi's own saved default per poll
|
|
20
|
+
* (docs/backends.md) serves the rows whose seam runs pi and resolves no model — its
|
|
21
|
+
* settings.json sits beside the models catalog this snapshot reads (piSettingsPath), so one
|
|
22
|
+
* seam serves both. */
|
|
23
|
+
export function collectRoleRows(root, cfg, slots, modelsPath, paused) {
|
|
25
24
|
// A surplus runner (index above `instances`, from the runner pool of plans/fast-merge.md
|
|
26
25
|
// "Lever 1") with nothing to show gets no row, so the table does not list idle extras.
|
|
27
26
|
// The exceptions are exactly the surplus runners an operator most needs to see and resume
|
|
@@ -59,19 +58,13 @@ export function collectRoleRows(root, cfg, now, slots, modelsPath, paused) {
|
|
|
59
58
|
// only with a tier map declared, where it names the tier a role rides.
|
|
60
59
|
const eff = configForRole(cfg, r);
|
|
61
60
|
// The image-input surface (plans/image-input.md "Surfaces", part 5/5): the role's own
|
|
62
|
-
// capability
|
|
63
|
-
//
|
|
64
|
-
// prompt would run.
|
|
61
|
+
// capability, one shared resolver so the status rows, the role page, and the submit gate
|
|
62
|
+
// cannot disagree about who reads images.
|
|
65
63
|
const image = roleImageInfo(cfg, r, modelsPath, listed);
|
|
66
64
|
// The peak-ctx share denominator (the role's context window, part 3c): resolved once per
|
|
67
65
|
// poll here, beside `eff` (the same seam the role page resolves and names) — the same seam
|
|
68
66
|
// `seamContextWindow` run against, so row, page and drawer cannot disagree.
|
|
69
67
|
const contextWindow = seamContextWindowForRole(cfg, r, root, modelsPath);
|
|
70
|
-
// Model failure fallback, part 2/2: while the role's episode is active its agent runs use
|
|
71
|
-
// the tier-resolved fallback pair, so the row names that pair, the episode's start, and
|
|
72
|
-
// the failure that tripped it. `now` is the poll's own clock (the snapshot seam), so
|
|
73
|
-
// every row agrees on what "active" means.
|
|
74
|
-
const episode = modelFallbackView(base.modelFallback, cfg, r, now);
|
|
75
68
|
// A seam that runs pi and resolves neither a provider nor a model starts on pi's own
|
|
76
69
|
// saved default — name it in the row (modelFromPi) so the operator sees which model the
|
|
77
70
|
// role runs. The config's `thinking` wins over the default's because tumwater passes it
|
|
@@ -106,7 +99,6 @@ export function collectRoleRows(root, cfg, now, slots, modelsPath, paused) {
|
|
|
106
99
|
? {}
|
|
107
100
|
: { model: backendDefaultLabel(eff.agent) }),
|
|
108
101
|
...(isJsonObject(cfg.model) ? { modelTier: roleSeamTier(cfg, r) } : {}),
|
|
109
|
-
...(episode !== null ? { fallback: episode } : {}),
|
|
110
102
|
...(contextWindow !== null ? { contextWindow } : {}),
|
|
111
103
|
...(slot ? { slot: path.basename(slot.dir), slotPinned: pinned !== undefined } : {}),
|
|
112
104
|
...(baseRoleOf(r) === r ? {} : { instanceOf: baseRoleOf(r) }),
|
|
@@ -22,7 +22,7 @@ export const ERROR_STORM_ROLES = 3;
|
|
|
22
22
|
export const ERROR_STORM_QUIET = { key: null, roles: [] };
|
|
23
23
|
/** The config knob a shared cause points at, when one is known. Only the timeout cause is
|
|
24
24
|
* mapped: `timed out after <dur>` is the reducer's pooled rendering of the run-timeout errors
|
|
25
|
-
* (src/agent/run
|
|
25
|
+
* (src/agent/agent-run.ts's two `timed out after ${runTimeoutSeconds}s` shapes, pooled into one
|
|
26
26
|
* key by poolTimeoutKey — src/failure/failure-cluster.ts owns the two shapes and their
|
|
27
27
|
* pooling), and a team-wide
|
|
28
28
|
* run of it means the run budget does not fit the serving model — the one cause with a knob
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* 2026-09-29 connection-storm entry): when many roles each fail a few times on one provider
|
|
3
3
|
* failure kind, no existing bar trips — the per-role streak alarm needs one role DEEP
|
|
4
4
|
* (ERROR_STREAK_WARN consecutive, src/run/run-apply.ts), the error-storm warning needs several
|
|
5
|
-
* roles deep at once (src/team/error-storm.ts), and the team hold needs failures COALESCED
|
|
6
|
-
* a two-minute window (src/team/team-hold.ts). A degraded backend that fails the team
|
|
7
|
-
* shallowly — six runs across as many roles, each failure minutes apart — rides all
|
|
8
|
-
* gaps in silence. This alarm counts raw failures of one kind across roles in a rolling
|
|
5
|
+
* roles deep at once (src/team/team-error-storm.ts), and the team hold needs failures COALESCED
|
|
6
|
+
* within a two-minute window (src/team/team-hold.ts). A degraded backend that fails the team
|
|
7
|
+
* widely and shallowly — six runs across as many roles, each failure minutes apart — rides all
|
|
8
|
+
* three gaps in silence. This alarm counts raw failures of one kind across roles in a rolling
|
|
9
9
|
* window, independent of any role's streak depth: the fourth bar, shaped for the spread the
|
|
10
|
-
* other three cannot see. POLICY, on the shape of its siblings (src/team/error-storm.ts,
|
|
10
|
+
* other three cannot see. POLICY, on the shape of its siblings (src/team/team-error-storm.ts,
|
|
11
11
|
* src/team/team-hold.ts): a pure reducer over the observations the orchestrator collects each
|
|
12
12
|
* poll and the previous state, so the rule is unit-testable without a team and the
|
|
13
13
|
* orchestrator owns only the wiring (the warning event). Like both siblings it has memory —
|
|
@@ -94,88 +94,121 @@ export function standingHolds(holds) {
|
|
|
94
94
|
out.sort((a, b) => (a.provider ?? "").localeCompare(b.provider ?? ""));
|
|
95
95
|
return out;
|
|
96
96
|
}
|
|
97
|
-
/**
|
|
98
|
-
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
}
|
|
127
|
-
// A rejected subscription usage window is the account-level throttle: it holds every seam on
|
|
128
|
-
// its provider until `resetsAt`, whatever the role count — the one observation kind the
|
|
129
|
-
// per-role storm rule below does not gate. Overage is real money and never holds (the run
|
|
130
|
-
// continues into paid overflow); a window with no future `resetsAt` holds nothing.
|
|
97
|
+
/** Decide whether the current hold still stands or the deadline re-opens it: return `prev`
|
|
98
|
+
* itself while open or still held (and not a re-evaluated reserve), else the re-opened
|
|
99
|
+
* skeleton. A reserve hold is re-evaluated every poll: the window may since have been
|
|
100
|
+
* rejected (the director keeps running) or the reserve removed from the config, and either
|
|
101
|
+
* must show now. The deadline passed: re-open, keeping provider/kind/escalation as the next
|
|
102
|
+
* storm's relapse memory, then fall through so a still-rejected subscription window (or a
|
|
103
|
+
* storm whose observations survived) holds again on this very poll rather than leaving one
|
|
104
|
+
* poll open. kind and provider stay because the next storm's relapse test compares against
|
|
105
|
+
* the hold that just ended. */
|
|
106
|
+
function reopenTeamHold(prev, now) {
|
|
107
|
+
if (prev.until === null)
|
|
108
|
+
return prev;
|
|
109
|
+
if (now < prev.until && prev.reserve !== true)
|
|
110
|
+
return prev;
|
|
111
|
+
return {
|
|
112
|
+
provider: prev.provider,
|
|
113
|
+
until: null,
|
|
114
|
+
kind: prev.kind,
|
|
115
|
+
roles: [],
|
|
116
|
+
escalation: prev.escalation,
|
|
117
|
+
reopenedAt: prev.reserve === true ? prev.reopenedAt : now,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
/** The account-level subscription window hold, or undefined when none is rejected. A rejected
|
|
121
|
+
* subscription usage window is the account-level throttle: it holds every seam on its
|
|
122
|
+
* provider until `resetsAt`, whatever the role count — the one observation kind the
|
|
123
|
+
* per-role storm rule below does not gate. Overage is real money and never holds (the run
|
|
124
|
+
* continues into paid overflow); a window with no future `resetsAt` holds nothing. */
|
|
125
|
+
function subscriptionWindowHold(observations, now, reopenedAt) {
|
|
131
126
|
const window = observations.find((o) => o.kind === "subscription-window" && (o.resetsAt ?? 0) > now);
|
|
132
|
-
if (window
|
|
133
|
-
return
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
127
|
+
if (window === undefined)
|
|
128
|
+
return undefined;
|
|
129
|
+
return {
|
|
130
|
+
provider: window.provider,
|
|
131
|
+
until: window.resetsAt,
|
|
132
|
+
kind: "subscription-window",
|
|
133
|
+
roles: [],
|
|
134
|
+
escalation: 0,
|
|
135
|
+
reopenedAt,
|
|
136
|
+
...(window.reserve === true ? { reserve: true } : {}),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
/** The observations that can still count toward the next storm: failures after the last
|
|
140
|
+
* re-open (the storm that hold already answered, and any an in-flight run hit while it
|
|
141
|
+
* held — that run's own retry is already waiting it out) and inside the storm window. */
|
|
142
|
+
function recentStormObservations(prev, observations, now) {
|
|
143
|
+
return observations.filter((o) => (prev.reopenedAt === null || o.at > prev.reopenedAt) && now - o.at <= HOLD_STORM_WINDOW_MS);
|
|
144
|
+
}
|
|
145
|
+
/** The first provider+kind group (in first-seen order, so the pick is deterministic) with
|
|
146
|
+
* HOLD_STORM_ROLES distinct roles behind it, or undefined when none qualifies — the answer
|
|
147
|
+
* to "which storm wins one poll?", since one poll can hold about only one kind. Undefined
|
|
148
|
+
* providers share one key: an unconfigured team is one backend by construction. */
|
|
149
|
+
function firstStormGroup(recent) {
|
|
147
150
|
const byProviderKind = new Map();
|
|
148
151
|
for (const o of recent) {
|
|
149
152
|
const key = `${o.provider ?? ""}\n${o.kind}`;
|
|
150
153
|
getOrCreate(byProviderKind, key, () => ({ provider: o.provider, kind: o.kind, group: [] })).group.push(o);
|
|
151
154
|
}
|
|
152
|
-
let storm = [];
|
|
153
|
-
let kind = null;
|
|
154
|
-
let provider;
|
|
155
155
|
for (const entry of byProviderKind.values()) {
|
|
156
156
|
const roles = new Set(entry.group.map((o) => o.role));
|
|
157
157
|
if (roles.size >= HOLD_STORM_ROLES) {
|
|
158
|
-
|
|
159
|
-
kind = entry.kind;
|
|
160
|
-
provider = entry.provider;
|
|
161
|
-
break;
|
|
158
|
+
return { provider: entry.provider, kind: entry.kind, storm: entry.group };
|
|
162
159
|
}
|
|
163
160
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
/** The hold severity this storm earns: +1 on the previous escalation when it is a relapse of
|
|
164
|
+
* the just-ended hold, else 0. A relapse is per provider AND kind: the same storm re-tripping
|
|
165
|
+
* inside the window escalates, while a different kind — or the same kind at a DIFFERENT
|
|
166
|
+
* provider — after a re-open is a new incident at the base. */
|
|
167
|
+
function holdEscalation(prev, provider, kind, now) {
|
|
170
168
|
const relapse = prev.kind !== null &&
|
|
171
169
|
prev.kind === kind &&
|
|
172
170
|
prev.provider === provider &&
|
|
173
171
|
prev.reopenedAt !== null &&
|
|
174
172
|
now - prev.reopenedAt <= HOLD_RELAPSE_MS;
|
|
175
|
-
|
|
173
|
+
return relapse ? prev.escalation + 1 : 0;
|
|
174
|
+
}
|
|
175
|
+
/** How long this storm's hold lasts: the longest of the escalated base hold and, for a
|
|
176
|
+
* rate-limit storm, the furthest Retry-After deadline among its observations (each measured
|
|
177
|
+
* from its own 429, since that is when the provider said it), capped at HOLD_CAP_MS. */
|
|
178
|
+
function holdDurationMs(escalation, storm, now) {
|
|
176
179
|
const retryAfterMs = Math.max(0, ...storm.map((o) => o.at + (o.retryAfterSeconds ?? 0) * 1000 - now));
|
|
177
|
-
|
|
178
|
-
|
|
180
|
+
return Math.min(HOLD_CAP_MS, Math.max(HOLD_BASE_MS * 2 ** escalation, retryAfterMs));
|
|
181
|
+
}
|
|
182
|
+
/** Step the hold by one orchestrator poll: from `prev` and every role's latest failure
|
|
183
|
+
* observations, decide what holds at `now`. A held team stays held until `until` and then
|
|
184
|
+
* re-opens by itself — nothing can get stuck, the same guarantee the budget gate's stateless
|
|
185
|
+
* re-evaluation gives. An open team trips when HOLD_STORM_ROLES distinct roles each
|
|
186
|
+
* ended a run on the same failure kind within the storm window (and after the last re-open) —
|
|
187
|
+
* the first qualifying kind in observation order wins, since one poll can hold about only one
|
|
188
|
+
* kind — for the longest of the escalated base hold and, for a rate-limit storm, the furthest
|
|
189
|
+
* Retry-After deadline among those observations (each measured from its own 429, since that is
|
|
190
|
+
* when the provider said it), capped at HOLD_CAP_MS. Returns `prev` itself when
|
|
191
|
+
* nothing changed, so a caller can compare `until` across the step to find transitions. */
|
|
192
|
+
export function teamHold(prev, observations, now) {
|
|
193
|
+
const reopened = reopenTeamHold(prev, now);
|
|
194
|
+
if (reopened.until !== null)
|
|
195
|
+
return reopened;
|
|
196
|
+
const window = subscriptionWindowHold(observations, now, reopened.reopenedAt);
|
|
197
|
+
if (window !== undefined)
|
|
198
|
+
return window;
|
|
199
|
+
const group = firstStormGroup(recentStormObservations(reopened, observations, now));
|
|
200
|
+
if (group === undefined)
|
|
201
|
+
return reopened;
|
|
202
|
+
const escalation = holdEscalation(reopened, group.provider, group.kind, now);
|
|
203
|
+
const roles = sortedRoles(new Set(group.storm.map((o) => o.role)));
|
|
204
|
+
return {
|
|
205
|
+
provider: group.provider,
|
|
206
|
+
until: now + holdDurationMs(escalation, group.storm, now),
|
|
207
|
+
kind: group.kind,
|
|
208
|
+
roles,
|
|
209
|
+
escalation,
|
|
210
|
+
reopenedAt: reopened.reopenedAt,
|
|
211
|
+
};
|
|
179
212
|
}
|
|
180
213
|
/** The providers whose standing hold is the account's rejected subscription window (kind
|
|
181
214
|
* "subscription-window", not a reserve crossing — the director is exempt from the reserve): the
|
|
@@ -84,9 +84,7 @@ function parseBuild(v) {
|
|
|
84
84
|
build.restartBlocked = v.restartBlocked;
|
|
85
85
|
return build;
|
|
86
86
|
}
|
|
87
|
-
/** The published permit snapshot, or undefined for any foreign shape.
|
|
88
|
-
* carry its two numbers; `limit` is kept only when it is a number, so a malformed provider
|
|
89
|
-
* map is dropped to `undefined` rather than reaching a render that reads it as a valid cap. */
|
|
87
|
+
/** The published permit snapshot, or undefined for any foreign shape. */
|
|
90
88
|
/** One entry of the published provider-holds list, or undefined for any foreign shape: a
|
|
91
89
|
* well-formed entry is an object with a finite `until` and a string `kind`; a `provider` that
|
|
92
90
|
* is present but not a string drops the entry, so a guarded list keeps its well-formed
|
|
@@ -106,18 +104,7 @@ function parseCheckPermits(v) {
|
|
|
106
104
|
function parsePermits(v) {
|
|
107
105
|
if (!isJsonObject(v) || !isFiniteNumber(v.inUse) || !isFiniteNumber(v.limit) || !isFiniteNumber(v.reserve))
|
|
108
106
|
return undefined;
|
|
109
|
-
|
|
110
|
-
return undefined;
|
|
111
|
-
const byKey = {};
|
|
112
|
-
for (const [key, value] of Object.entries(v.byKey)) {
|
|
113
|
-
if (!isJsonObject(value) || !isFiniteNumber(value.inUse) || !isFiniteNumber(value.waiting))
|
|
114
|
-
continue;
|
|
115
|
-
const entry = { inUse: value.inUse, waiting: value.waiting };
|
|
116
|
-
if (isFiniteNumber(value.limit))
|
|
117
|
-
entry.limit = value.limit;
|
|
118
|
-
byKey[key] = entry;
|
|
119
|
-
}
|
|
120
|
-
return { inUse: v.inUse, limit: v.limit, reserve: v.reserve, byKey };
|
|
107
|
+
return { inUse: v.inUse, limit: v.limit, reserve: v.reserve };
|
|
121
108
|
}
|
|
122
109
|
function parseSlotPool(v) {
|
|
123
110
|
if (!isJsonObject(v) || !isFiniteNumber(v.leased) || !isFiniteNumber(v.limit) || !isFiniteNumber(v.waiting))
|