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
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import { MODEL_TIERS, BACKOFF_KEYS, BOOTSTRAP_KEYS, CHECK_KEYS,
|
|
2
|
-
import { DIRECTOR_ROLE, allRoleIds } from "../roles/roles.js";
|
|
3
|
-
import { INSTANCE_ROLES, baseRoleOf } from "../roles/runner-ids.js";
|
|
1
|
+
import { MODEL_TIERS, BACKOFF_KEYS, BOOTSTRAP_KEYS, CHECK_KEYS, REVIEW_KEYS, TOP_LEVEL_KEYS, } from "./config-schema.js";
|
|
4
2
|
import { isHarnessCodexConfigKey } from "../agent/codex/codex-args.js";
|
|
5
3
|
import { isJsonObject } from "../files/json-object.js";
|
|
6
4
|
import { expandHomeTilde } from "../files/files.js";
|
|
@@ -10,11 +8,11 @@ import { isNonBlankString, tooLongMessage } from "../text/text.js";
|
|
|
10
8
|
import { PREFILL_REASON } from "../text/phrases.js";
|
|
11
9
|
import { parseQuietHours } from "../scheduling/quiet-hours.js";
|
|
12
10
|
import { isBackendId } from "./model-selector.js";
|
|
13
|
-
import {
|
|
14
|
-
import { AT_LEAST_ONE, checkBooleanField, checkKnownKeys, checkKnownRoleId, checkModelTripleField, checkNumberField, checkObjectSection, codexConfigKey, checkStringArrayField, checkStringField, NON_NEGATIVE, NON_NEGATIVE_OR_DISABLED, DOLLAR_CAP, MAX_DURATION_SECONDS, POSITIVE, POSITIVE_INTEGER, show, typeName, } from "./config-field-checks.js";
|
|
11
|
+
import { AT_LEAST_ONE, checkBooleanField, checkKnownKeys, checkModelTripleField, checkNumberField, checkObjectSection, codexConfigKey, checkStringArrayField, checkStringField, NON_NEGATIVE, NON_NEGATIVE_OR_DISABLED, DOLLAR_CAP, MAX_DURATION_SECONDS, POSITIVE, POSITIVE_INTEGER, show, typeName, } from "./config-field-checks.js";
|
|
15
12
|
import { checkEntryBackend, checkRetiredSelector, checkSelectorHalves, } from "./config-selector-checks.js";
|
|
16
13
|
import { validateAgentsBlock } from "./config-validation-agents.js";
|
|
17
14
|
import { validateFallbackBlock, validateModelBlock, } from "./config-validation-model.js";
|
|
15
|
+
import { validateRolesAndCrossField } from "./config-validation-roles.js";
|
|
18
16
|
/** Schema validation for tumwater.json: validateConfig, the gate every load and save passes
|
|
19
17
|
* through (config.ts's load/save, config-write.ts's budget editing and
|
|
20
18
|
* config-request.ts's director config requests), judged against the key allow-lists and
|
|
@@ -38,33 +36,6 @@ const DURATION_OR_DISABLED = {
|
|
|
38
36
|
ok: (n) => n >= 0 && n <= MAX_DURATION_SECONDS,
|
|
39
37
|
what: `a number of 0 or more, at most ${MAX_DURATION_SECONDS} (0 disables)`,
|
|
40
38
|
};
|
|
41
|
-
/** A per-role instance count (`roles.feature.instances`): an integer from 1 to 8. Bounded so
|
|
42
|
-
* a typo like `instances: 800` cannot spawn hundreds of runners, and integer so
|
|
43
|
-
* `feature-1.5` — which no role id can name — is refused. */
|
|
44
|
-
const INSTANCE_COUNT = {
|
|
45
|
-
ok: (n) => Number.isInteger(n) && n >= 1 && n <= 8,
|
|
46
|
-
what: "an integer from 1 to 8",
|
|
47
|
-
};
|
|
48
|
-
/** Validate one per-role map section (`maxDailyCostUsdPerRole`, `quietHoursPerRole`): the
|
|
49
|
-
* value must be an object mapping role ids to entries, every key must name a known role
|
|
50
|
-
* (checkKnownRoleId's guard — a typo'd id would silently no-op the section), and each entry
|
|
51
|
-
* goes through `checkValue` with its role id. `expectation` is the value description the
|
|
52
|
-
* type-error message quotes. One home for the section shape the two dotted per-role keys
|
|
53
|
-
* share, so a third per-role map reuses it instead of re-copying the loop. */
|
|
54
|
-
function checkPerRoleMap(r, key, expectation, customNames, problems, checkValue) {
|
|
55
|
-
if (!(key in r))
|
|
56
|
-
return;
|
|
57
|
-
const map = r[key];
|
|
58
|
-
if (!isJsonObject(map)) {
|
|
59
|
-
problems.push(`${key} must be an object mapping role ids to ${expectation} (got ${show(map)})`);
|
|
60
|
-
return;
|
|
61
|
-
}
|
|
62
|
-
for (const [id, value] of Object.entries(map)) {
|
|
63
|
-
if (!checkKnownRoleId(key, id, customNames, problems))
|
|
64
|
-
continue;
|
|
65
|
-
checkValue(id, value);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
39
|
/** Validate tumwater.json values, so a typo fails fast with an actionable message instead
|
|
69
40
|
* of misbehaving at runtime — e.g. a non-numeric runTimeoutSeconds becomes NaN and kills
|
|
70
41
|
* every pi run instantly, a non-numeric logMaxBytes rotates the event log on every write, an
|
|
@@ -319,180 +290,14 @@ export function validateConfig(raw, label = "tumwater.json", rawFile = false) {
|
|
|
319
290
|
tierBackend,
|
|
320
291
|
eachTierBackend,
|
|
321
292
|
});
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
const cl = r.customRoles;
|
|
330
|
-
if (!Array.isArray(cl)) {
|
|
331
|
-
problems.push(`customRoles must be an array of { name, task } entries (got ${show(cl)})`);
|
|
332
|
-
}
|
|
333
|
-
else {
|
|
334
|
-
cl.forEach((entry, i) => {
|
|
335
|
-
const where = `customRoles[${i}]`;
|
|
336
|
-
if (!isJsonObject(entry)) {
|
|
337
|
-
problems.push(`${where} must be an object with keys name and task (got ${show(entry)})`);
|
|
338
|
-
return;
|
|
339
|
-
}
|
|
340
|
-
checkKnownKeys(entry, CUSTOM_ROLE_KEYS, where, problems);
|
|
341
|
-
const name = entry.name;
|
|
342
|
-
if (typeof name !== "string") {
|
|
343
|
-
problems.push(`${where}.name must be a string matching /^[a-z0-9][a-z0-9_-]{0,31}$/ (got ${show(name)})`);
|
|
344
|
-
}
|
|
345
|
-
else if (!CUSTOM_NAME_RE.test(name)) {
|
|
346
|
-
problems.push(`${where}.name "${name}" is not a valid role name: start with a lowercase letter or digit, then at most 31 more of [a-z0-9_-]`);
|
|
347
|
-
}
|
|
348
|
-
else if (name === TUMWATER_SOURCE) {
|
|
349
|
-
problems.push(`${where}.name "${name}" is reserved: events tumwater writes about itself carry that source, so a role's events would be indistinguishable from them — pick another name`);
|
|
350
|
-
}
|
|
351
|
-
else if (allRoleIds().includes(name)) {
|
|
352
|
-
problems.push(`${where}.name "${name}" collides with a built-in role id — pick another name`);
|
|
353
|
-
}
|
|
354
|
-
else if (baseRoleOf(name) !== name) {
|
|
355
|
-
problems.push(`${where}.name "${name}" would shadow the ${baseRoleOf(name)} role's instance ids — pick another name`);
|
|
356
|
-
}
|
|
357
|
-
else if (customNames.has(name)) {
|
|
358
|
-
problems.push(`${where}.name "${name}" is duplicated in customRoles — names must be unique`);
|
|
359
|
-
}
|
|
360
|
-
else {
|
|
361
|
-
customNames.add(name);
|
|
362
|
-
}
|
|
363
|
-
const task = entry.task;
|
|
364
|
-
// A whitespace-only task is as inert as an empty one — it rides into every one of
|
|
365
|
-
// this role's run prefills as blank text, so the role has nothing to do and runs
|
|
366
|
-
// no_change forever. Reject both shapes with the same message; the model-triple
|
|
367
|
-
// checkString rule uses the same trim-based emptiness test.
|
|
368
|
-
if (!isNonBlankString(task)) {
|
|
369
|
-
problems.push(`${where}.task must be a non-empty string (got ${show(task)})`);
|
|
370
|
-
}
|
|
371
|
-
else {
|
|
372
|
-
checkPromptLength(`${where}.task`, task, CUSTOM_TASK_MAX_CHARS);
|
|
373
|
-
}
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if ("roles" in r) {
|
|
378
|
-
const roles = r.roles;
|
|
379
|
-
if (!isJsonObject(roles)) {
|
|
380
|
-
problems.push(`roles must be an object mapping role ids to settings (got ${show(roles)})`);
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
for (const [id, rc] of Object.entries(roles)) {
|
|
384
|
-
// An id outside the catalog and customRoles cannot work: runPrompt has no prompt for
|
|
385
|
-
// it and the role would error every run forever (checkKnownRoleId).
|
|
386
|
-
if (!checkKnownRoleId("roles", id, customNames, problems))
|
|
387
|
-
continue;
|
|
388
|
-
if (!isJsonObject(rc)) {
|
|
389
|
-
problems.push(`roles.${id} must be an object (got ${show(rc)})`);
|
|
390
|
-
continue;
|
|
391
|
-
}
|
|
392
|
-
const o = rc;
|
|
393
|
-
checkKnownKeys(o, ROLE_ENTRY_KEYS, `roles.${id}`, problems);
|
|
394
|
-
// Empty is a deliberate "no extra instructions". A non-empty value rides into every
|
|
395
|
-
// one of this role's run prefills, so it is capped like a custom role's task.
|
|
396
|
-
checkString(o, `roles.${id}.`, "instructions");
|
|
397
|
-
checkPromptLength(`roles.${id}.instructions`, o.instructions, ROLE_INSTRUCTIONS_MAX_CHARS);
|
|
398
|
-
checkModelTripleEntry(o, `roles.${id}.`, `roles.${id}`);
|
|
399
|
-
checkBoolean(o, `roles.${id}.`, "enabled");
|
|
400
|
-
checkNumber(o, `roles.${id}.`, "minRunIntervalSeconds", DURATION_NON_NEGATIVE);
|
|
401
|
-
// Only feature and bugfix may split (src/roles/runner-ids.ts INSTANCE_ROLES); an
|
|
402
|
-
// instances count under any other role would never spawn a runner, so reject it
|
|
403
|
-
// rather than let the operator believe it did something.
|
|
404
|
-
if ("instances" in o && !INSTANCE_ROLES.has(id)) {
|
|
405
|
-
problems.push(`roles.${id}.instances is only valid for ${[...INSTANCE_ROLES].join(" and ")} (got ${show(o.instances)})`);
|
|
406
|
-
}
|
|
407
|
-
else {
|
|
408
|
-
checkNumber(o, `roles.${id}.`, "instances", INSTANCE_COUNT);
|
|
409
|
-
}
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
// review.afterMerge names roles, so a typo'd id would silently keep that role reviewed before
|
|
414
|
-
// merging; the director is refused because its changes always merge on a review first.
|
|
415
|
-
if (isJsonObject(r.review) && Array.isArray(r.review.afterMerge)) {
|
|
416
|
-
r.review.afterMerge.forEach((id, i) => {
|
|
417
|
-
if (typeof id !== "string" || id.trim() === "")
|
|
418
|
-
return; // Already reported by checkStringArray.
|
|
419
|
-
if (id === DIRECTOR_ROLE)
|
|
420
|
-
problems.push(`review.afterMerge[${i}] must not be "${DIRECTOR_ROLE}" — its changes are always reviewed before merging`);
|
|
421
|
-
else
|
|
422
|
-
checkKnownRoleId("review.afterMerge", id, customNames, problems);
|
|
423
|
-
});
|
|
424
|
-
}
|
|
425
|
-
// Per-role daily cost caps (src/gates/role-cap-gates.ts): each key must name a known role —
|
|
426
|
-
// built-in or customRoles — because a typo'd id would silently no-op the cap, the exact
|
|
427
|
-
// silent-ignore class the `roles.<id>` check exists to prevent (the customNames set above
|
|
428
|
-
// is fully collected by this point, so a custom role can be capped). Each value is a
|
|
429
|
-
// spend threshold with the NON_NEGATIVE_OR_DISABLED semantics: 0 disables that role's cap.
|
|
430
|
-
checkPerRoleMap(r, "maxDailyCostUsdPerRole", "USD caps", customNames, problems, (id) => {
|
|
431
|
-
// Same MAX_SAFE_INTEGER bound as maxDailyCostUsd's DOLLAR_CAP rule (BUGS.md
|
|
432
|
-
// 2026-10-02): a finite-but-unrepresentable per-role cap is an effectively
|
|
433
|
-
// uncapped budget for that role — DOLLAR_CAP is that rule, so the predicate
|
|
434
|
-
// and its wording have one home. checkPerRoleMap narrowed the map, so the cast
|
|
435
|
-
// is sound and checkNumberField sees the entry's value typed as before.
|
|
436
|
-
checkNumberField(problems, r.maxDailyCostUsdPerRole, "maxDailyCostUsdPerRole.", id, DOLLAR_CAP);
|
|
437
|
-
});
|
|
438
|
-
// Per-role quiet hours (src/scheduling/quiet-hours.ts): the same known-role gate as the
|
|
439
|
-
// caps — a typo'd role id would silently no-op the window. Each value must parse as a
|
|
440
|
-
// quiet-hours window (an empty string means off for that role, the same disablement the
|
|
441
|
-
// team-wide key takes), and the message names the key and the offending id/value verbatim.
|
|
442
|
-
checkPerRoleMap(r, "quietHoursPerRole", '"HH:MM-HH:MM" windows', customNames, problems, (id, value) => {
|
|
443
|
-
if (typeof value !== "string") {
|
|
444
|
-
problems.push(`quietHoursPerRole.${id} must be a "HH:MM-HH:MM" string like "23:00-07:00" (an empty string means off) (got ${show(value)})`);
|
|
445
|
-
return;
|
|
446
|
-
}
|
|
447
|
-
if (value.trim() === "")
|
|
448
|
-
return; // empty = off for that role
|
|
449
|
-
const parsed = parseQuietHours(value, `quietHoursPerRole.${id}`);
|
|
450
|
-
if (!parsed.ok)
|
|
451
|
-
problems.push(parsed.error);
|
|
293
|
+
validateRolesAndCrossField(r, problems, {
|
|
294
|
+
checkString,
|
|
295
|
+
checkBoolean,
|
|
296
|
+
checkNumber,
|
|
297
|
+
checkPromptLength,
|
|
298
|
+
checkModelTripleEntry,
|
|
299
|
+
checkWindowReserve,
|
|
452
300
|
});
|
|
453
|
-
// Per-provider concurrency caps (plans/provider-concurrency.md §1): a map from provider
|
|
454
|
-
// key to a positive integer. Unlike the per-role maps above, an unknown provider name is
|
|
455
|
-
// NOT a validation error — providers live in pi's models.json, which validation does not
|
|
456
|
-
// read, and a fallback's provider may only come into use later (doctor warns in part 5/5).
|
|
457
|
-
// Each value must be a positive integer under a non-empty key.
|
|
458
|
-
if (r.maxConcurrentPerProvider !== undefined) {
|
|
459
|
-
const map = r.maxConcurrentPerProvider;
|
|
460
|
-
if (!isJsonObject(map)) {
|
|
461
|
-
problems.push(`maxConcurrentPerProvider must be an object mapping provider names to concurrency caps (got ${show(map)})`);
|
|
462
|
-
}
|
|
463
|
-
else {
|
|
464
|
-
for (const [provider, value] of Object.entries(map)) {
|
|
465
|
-
if (provider.trim() === "") {
|
|
466
|
-
problems.push(`maxConcurrentPerProvider.<empty> — a provider key must not be empty`);
|
|
467
|
-
continue;
|
|
468
|
-
}
|
|
469
|
-
if (typeof value !== "number" || !Number.isFinite(value) || !POSITIVE_INTEGER.ok(value)) {
|
|
470
|
-
problems.push(`maxConcurrentPerProvider.${provider} must be ${POSITIVE_INTEGER.what} (got ${show(value)})`);
|
|
471
|
-
}
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
// The subscription-window reserve: a map from window type to a fraction, 0 < f <= 1.
|
|
476
|
-
if (r.claudeWindowReserve !== undefined)
|
|
477
|
-
checkWindowReserve("claudeWindowReserve", r.claudeWindowReserve);
|
|
478
|
-
// The reserve's two spellings are mutually exclusive (plans/codex-backend.md part 2b/9): the
|
|
479
|
-
// nested key is the current one; `claudeWindowReserve` is kept as claude's older spelling. A
|
|
480
|
-
// file holding both is ambiguous about which drives the window, so it is rejected naming both.
|
|
481
|
-
if (r.claudeWindowReserve !== undefined &&
|
|
482
|
-
isJsonObject(r.backends) &&
|
|
483
|
-
isJsonObject(r.backends.claude) &&
|
|
484
|
-
r.backends.claude.windowReserve !== undefined)
|
|
485
|
-
problems.push("claudeWindowReserve and backends.claude.windowReserve set the same value — keep one (backends.claude.windowReserve is the current spelling; claudeWindowReserve is its old name)");
|
|
486
|
-
// Cross-field (judged on the merged config, where both sides are always present; on a raw
|
|
487
|
-
// file only when the file itself names both): scheduleBackoff clamps every idle wait — the
|
|
488
|
-
// first included — to min(initialSeconds, maxSeconds), so a smaller max silently discards
|
|
489
|
-
// the configured initial wait. Name both values so one edit fixes the pair.
|
|
490
|
-
const backoff = r.idleBackoff;
|
|
491
|
-
if (isJsonObject(backoff) &&
|
|
492
|
-
typeof backoff.initialSeconds === "number" &&
|
|
493
|
-
typeof backoff.maxSeconds === "number" &&
|
|
494
|
-
backoff.maxSeconds < backoff.initialSeconds)
|
|
495
|
-
problems.push(`idleBackoff.maxSeconds (${show(backoff.maxSeconds)}) must be ≥ idleBackoff.initialSeconds (${show(backoff.initialSeconds)}) — every idle wait is clamped to the smaller, so the smaller max would silently shorten the configured first wait`);
|
|
496
301
|
if (problems.length > 0) {
|
|
497
302
|
throw new Error(`invalid ${label}:\n - ${problems.join("\n - ")}`);
|
|
498
303
|
}
|
|
@@ -109,12 +109,7 @@ export function tierModel(config, tier) {
|
|
|
109
109
|
* key it overrides would.
|
|
110
110
|
*
|
|
111
111
|
* `tier` is the seam's effective tier, already resolved by the caller from any tier-name
|
|
112
|
-
* override — so `o.model` here is always a selector string, never a tier reference.
|
|
113
|
-
*
|
|
114
|
-
* Exported so imageRerouteTarget (src/agent/image-input.ts) can probe a peer tier's
|
|
115
|
-
* top-level model (`withModelOverrides(config, tier, {})`, the same shape resolverConfig
|
|
116
|
-
* builds strong with) and reshape a tier's fallback pair into a runnable config — the
|
|
117
|
-
* tier-config builder the plan names. */
|
|
112
|
+
* override — so `o.model` here is always a selector string, never a tier reference. */
|
|
118
113
|
export function withModelOverrides(config, tier, o) {
|
|
119
114
|
// The top-level model resolves at the seam's tier: a string form is the default tier's
|
|
120
115
|
// selector; a map form takes the tier's entry, else `default`'s.
|
|
@@ -204,7 +199,7 @@ export function configForRole(config, role) {
|
|
|
204
199
|
}
|
|
205
200
|
/** The config a role's runs SCHEDULE from: configForRole, except that while PLANS.md still
|
|
206
201
|
* has planning room (planBacklogOpen — below the plan role's eligible-plan target, or holding
|
|
207
|
-
* a plan that needs review
|
|
202
|
+
* a plan that needs review) the plan role schedules with a ZERO min gap, so a
|
|
208
203
|
* productive plan run reruns on the next poll and an unproductive one drops to the idle
|
|
209
204
|
* backoff. The plan role's own machine signal is `planBacklogFull` (src/scheduling/claims.ts):
|
|
210
205
|
* a fresh project's empty backlog is not full, so this subsumes the old placeholder-status
|