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
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import { publishInfoFields } from "../team/orchestrator-info.js";
|
|
1
|
+
import { publishInfoFields } from "../team/team-orchestrator-info.js";
|
|
2
2
|
import { baseRoleOf, runnerEnabled } from "../roles/runner-ids.js";
|
|
3
3
|
import { DIRECTOR_ROLE } from "../roles/roles.js";
|
|
4
4
|
import { reviewHoldKey, pollDeferralInputs, pollRunnerReasons } from "./orchestrator-scheduling.js";
|
|
5
|
-
import { seamConcurrencyKey } from "../config/config-views.js";
|
|
6
5
|
import { launchDueRuns } from "./orchestrator-launch.js";
|
|
7
6
|
import { branchHead, currentBranch } from "../git/git.js";
|
|
8
7
|
import { queuedMergeFiles, queuedMergeRoles } from "../merge/merge-queue.js";
|
|
@@ -15,11 +14,21 @@ import { checkPermitSnapshot } from "../concurrency/check-permit.js";
|
|
|
15
14
|
import { slotPoolSnapshot } from "../git/worktree-pool.js";
|
|
16
15
|
import { redeployTarget, watchVerifiesMain } from "../baseline/main-baseline.js";
|
|
17
16
|
import { p75RunDurationMs } from "../run/run-timing.js";
|
|
17
|
+
import { reapAttributableOrphans } from "../doctor/doctor-orphans.js";
|
|
18
18
|
import { pollTeamGates } from "../gates/gate-polls.js";
|
|
19
19
|
import { heldProviders, windowHeldProviders } from "../team/team-hold.js";
|
|
20
20
|
import { workReserveCount } from "../scheduling/claims.js";
|
|
21
21
|
export function newPollState() {
|
|
22
|
-
return { restart: false, holdForRestart: false, diskHeld: false, warnedBranchDivergence: false, lastReserve: 0 };
|
|
22
|
+
return { restart: false, holdForRestart: false, diskHeld: false, warnedBranchDivergence: false, lastReserve: 0, lastOrphanReapAt: 0 };
|
|
23
|
+
}
|
|
24
|
+
/** How often the orchestrator's poll loop stands down the orphan reap — a once-per-interval
|
|
25
|
+
* throttle so a slow process-table scan never runs on the busy per-poll hot path. */
|
|
26
|
+
export const ORPHAN_REAP_INTERVAL_MS = 15 * 60 * 1000;
|
|
27
|
+
/** Whether the orphan reap is due now, given the last poll it ran: true at and after
|
|
28
|
+
* `now >= last + intervalMs`. Purely a clock comparison so tests pin it from literal
|
|
29
|
+
* numbers. A fresh state's `last` of 0 is always due against a real `now`. */
|
|
30
|
+
export function dueForOrphanReap(last, now, intervalMs = ORPHAN_REAP_INTERVAL_MS) {
|
|
31
|
+
return now - last >= intervalMs;
|
|
23
32
|
}
|
|
24
33
|
/** The start gate every reserved run passes the moment its permit is granted
|
|
25
34
|
* (runTimedRoleRun's `held`): closed while a restart is pending, and after one is decided so
|
|
@@ -52,7 +61,7 @@ export async function pollRequestsAndWatch(ctx, state) {
|
|
|
52
61
|
// src/config/config-live.ts owns the last-known-good retention and the edge-triggered
|
|
53
62
|
// warnings/events around it.
|
|
54
63
|
const liveConfig = ctx.liveReload.poll();
|
|
55
|
-
// The notify command rides the same last-known-good reload (src/events/notify.ts): a live
|
|
64
|
+
// The notify command rides the same last-known-good reload (src/events/event-notify.ts): a live
|
|
56
65
|
// `config set notify` edit takes effect on the next poll, no restart.
|
|
57
66
|
ctx.notifier.update(liveConfig);
|
|
58
67
|
// Live session retention (the last restart-only setting): the edge-triggered
|
|
@@ -60,6 +69,15 @@ export async function pollRequestsAndWatch(ctx, state) {
|
|
|
60
69
|
// src/orchestrator/retention.ts. The live config (last-known-good while the file is broken or
|
|
61
70
|
// missing) drives the check, like the budget gate.
|
|
62
71
|
ctx.retentionPruner.poll(root, liveConfig.sessionRetentionDays);
|
|
72
|
+
// The orphan reap (src/doctor/doctor-orphans.ts): at most once per interval, kill the
|
|
73
|
+
// orphans the doctor row already reports that are old enough to be certain a live run no
|
|
74
|
+
// longer owns them — the automatic counterpart to the human click `doctor
|
|
75
|
+
// --kill-orphans`/dashboard/TUI makes. The time is stamped before the await, so a slow
|
|
76
|
+
// scan cannot overlap the next poll's reap (and this await anyway holds the poll step).
|
|
77
|
+
if (dueForOrphanReap(state.lastOrphanReapAt, Date.now())) {
|
|
78
|
+
state.lastOrphanReapAt = Date.now();
|
|
79
|
+
await reapAttributableOrphans(root);
|
|
80
|
+
}
|
|
63
81
|
// A missing or stale opencode catalog is refetched at most once an hour while opencode is routed.
|
|
64
82
|
ctx.opencodeCatalogRefresh.poll(liveConfig);
|
|
65
83
|
// launchservicesd's Mach-port budget (src/process/launch-services.ts owns it):
|
|
@@ -119,9 +137,8 @@ export function pollGatesAndHolds(ctx, state, { liveConfig, now }) {
|
|
|
119
137
|
state.diskHeld = gates.diskHeld;
|
|
120
138
|
// Publish the permit pool's live state (plans/provider-concurrency.md §8): observers
|
|
121
139
|
// (status, TUI, GUI) read one file instead of coupling to this Semaphore, and the team
|
|
122
|
-
// line shows
|
|
123
|
-
//
|
|
124
|
-
// renders exactly as before. The check-permit pool rides along, so status can show checks
|
|
140
|
+
// line shows the team total. Written only when it changes, like budget and disk. The
|
|
141
|
+
// check-permit pool rides along, so status can show checks
|
|
125
142
|
// running and waiting while merges stall. The workspace pool's live state rides along too:
|
|
126
143
|
// one small slots.json read per poll, and publishInfoFields rewrites the info file only
|
|
127
144
|
// when a value changed.
|
|
@@ -150,7 +167,7 @@ export function pollGatesAndHolds(ctx, state, { liveConfig, now }) {
|
|
|
150
167
|
teamHeldNow,
|
|
151
168
|
reviewHeldNow: () => liveConfig.review.enabled && teamHeldNow().has(reviewSeamKey),
|
|
152
169
|
reviewHeld: liveConfig.review.enabled && teamHeldProviders.has(reviewSeamKey),
|
|
153
|
-
roleProviders: new Map(runners.map((r) => [r.role, r.runProvider(
|
|
170
|
+
roleProviders: new Map(runners.map((r) => [r.role, r.runProvider()])),
|
|
154
171
|
};
|
|
155
172
|
}
|
|
156
173
|
/** Self-redeploy (src/redeploy/redeployer.ts): with main's head in hand, let the state
|
|
@@ -222,8 +239,7 @@ export async function pollMergeDrain(ctx, state, { liveConfig, mainHead }, gates
|
|
|
222
239
|
runners: ctx.runners,
|
|
223
240
|
liveConfig,
|
|
224
241
|
roleConfig: gates.roleConfig,
|
|
225
|
-
directorConfig:
|
|
226
|
-
piDefault: ctx.piDefault,
|
|
242
|
+
directorConfig: liveConfig,
|
|
227
243
|
startHeld: (role) => mergeStartHeld(runStartHeld(state), gates.reviewHeldNow(), afterMergeRoles, role),
|
|
228
244
|
}, ctx.merges);
|
|
229
245
|
}
|
|
@@ -280,9 +296,7 @@ export async function pollSchedule(ctx, state, { liveConfig, mainHead, now }, ga
|
|
|
280
296
|
probePair: inputs.probePair,
|
|
281
297
|
probeRoles: inputs.probeRoles,
|
|
282
298
|
budgetActive: gates.budgetActive,
|
|
283
|
-
windowMovedRoles: gates.windowMovedRoles,
|
|
284
299
|
semaphore: ctx.semaphore,
|
|
285
|
-
piDefault: ctx.piDefault,
|
|
286
300
|
rolePermitHolders: ctx.rolePermitHolders,
|
|
287
301
|
roleInFlight: ctx.roleInFlight,
|
|
288
302
|
directorInFlight: ctx.directorInFlight,
|
|
@@ -296,36 +310,17 @@ export async function pollSchedule(ctx, state, { liveConfig, mainHead, now }, ga
|
|
|
296
310
|
}
|
|
297
311
|
/** Priority headroom, then once mode's exit; returns whether the loop should stop. Writes
|
|
298
312
|
* state.lastReserve and, in once mode, fires internalStop. */
|
|
299
|
-
export function pollReserveAndExit(ctx, state, { liveConfig
|
|
313
|
+
export function pollReserveAndExit(ctx, state, { liveConfig }) {
|
|
300
314
|
const { root, runners, semaphore } = ctx;
|
|
301
315
|
// Priority headroom (plans/parallel-work-instances.md part 7/7): withhold a few permits
|
|
302
316
|
// from maintenance while a work role has work to take, so a due work run never waits out
|
|
303
317
|
// a whole maintenance run. Sized after the launch pass, so a runner launched this poll
|
|
304
318
|
// already reads as running and reserves nothing for itself.
|
|
305
|
-
const poolSnapshot = semaphore.snapshot();
|
|
306
|
-
// The provider-headroom predicate receives the structural ClaimRunner; the config that
|
|
307
|
-
// names the provider lives on the real RoleRunners, so look the runner up by role.
|
|
308
|
-
const runnerByRole = new Map(runners.map((r) => [r.role, r]));
|
|
309
319
|
const reserve = workReserveCount(root, runners, {
|
|
310
320
|
maxConcurrent: liveConfig.maxConcurrent,
|
|
311
321
|
enabled: new Set(runners.filter((r) => runnerEnabled(r.config, r.role)).map((r) => r.role)),
|
|
312
322
|
queuedMergeRoles: queuedMergeRoles(root),
|
|
313
323
|
instancesOf: (base) => liveConfig.roles[base]?.instances ?? 1,
|
|
314
|
-
// Per-provider headroom (plans/provider-concurrency.md §6): an idle work role reserves a
|
|
315
|
-
// permit only when its own provider could use it — capped and full, it would only idle
|
|
316
|
-
// the slot another provider's maintenance run could take. Read from the pool's live
|
|
317
|
-
// snapshot; an uncapped key (or the team-total-only case) always has headroom.
|
|
318
|
-
providerHasHeadroom: (r) => {
|
|
319
|
-
const runner = runnerByRole.get(r.role);
|
|
320
|
-
const cfg = runner && runner.role !== DIRECTOR_ROLE ? runner.runConfig(now) : null;
|
|
321
|
-
const key = cfg ? seamConcurrencyKey(cfg, ctx.piDefault) : undefined;
|
|
322
|
-
if (key === undefined)
|
|
323
|
-
return true;
|
|
324
|
-
const k = poolSnapshot.byKey[key];
|
|
325
|
-
if (k?.limit === undefined)
|
|
326
|
-
return true;
|
|
327
|
-
return k.inUse < k.limit;
|
|
328
|
-
},
|
|
329
324
|
});
|
|
330
325
|
semaphore.setReserve(reserve);
|
|
331
326
|
if ((state.lastReserve === 0) !== (reserve === 0)) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readOrchestratorInfo, writeOrchestratorInfo } from "../team/orchestrator-info.js";
|
|
1
|
+
import { readOrchestratorInfo, writeOrchestratorInfo } from "../team/team-orchestrator-info.js";
|
|
2
2
|
import { runnerIds } from "../roles/runner-ids.js";
|
|
3
3
|
import { newLiveConfigReload } from "../config/config-live.js";
|
|
4
4
|
import { FALLBACK_BREAKER_POLICY, } from "../budget/fallback-breaker.js";
|
|
@@ -10,20 +10,18 @@ import { mergeTasks, newMergePipeline } from "../merge/merge-pipeline.js";
|
|
|
10
10
|
import { bestEffortEventLogger, logEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
11
11
|
import { removeQuiet } from "../files/files.js";
|
|
12
12
|
import { OnceRound } from "../scheduling/once-round.js";
|
|
13
|
-
import { newNotifier } from "../events/notify.js";
|
|
13
|
+
import { newNotifier } from "../events/event-notify.js";
|
|
14
14
|
import { piModelsPath } from "../pi/pi-models.js";
|
|
15
|
-
import { piSettingsPath, readPiDefaultModel } from "../pi/pi-settings.js";
|
|
16
15
|
import { Semaphore } from "../concurrency/semaphore.js";
|
|
17
16
|
import { orchestratorStatePath } from "../paths.js";
|
|
18
17
|
import { createMainWatch } from "../baseline/main-watch.js";
|
|
19
18
|
import { queueCulpritHeal } from "../baseline/main-red.js";
|
|
20
19
|
import { RetentionPruner } from "./retention.js";
|
|
21
|
-
import { newOpencodeCatalogRefresh } from "../gates/startup-
|
|
20
|
+
import { newOpencodeCatalogRefresh } from "../gates/startup-gates.js";
|
|
22
21
|
import { WorkMergedCache } from "../scheduling/work-merged-cache.js";
|
|
23
22
|
import { drainInFlightWork, HANDOFF_MERGE_WINDOW_MS, sleepInterruptible, } from "../run/run-timing.js";
|
|
24
23
|
import { newTeamGateStates } from "../gates/gate-polls.js";
|
|
25
|
-
import {
|
|
26
|
-
import { ReclaimController } from "../team/reclaim.js";
|
|
24
|
+
import { ReclaimController } from "../team/team-reclaim.js";
|
|
27
25
|
import { reapPreviousTeamOrphans } from "../doctor/doctor-orphans.js";
|
|
28
26
|
import { runStartupMigrations } from "./startup-migration-steps.js";
|
|
29
27
|
import { archiveAtTeamStart } from "../backlog/backlog-archive.js";
|
|
@@ -34,9 +32,6 @@ export async function runOrchestrator(opts) {
|
|
|
34
32
|
const { root, config, mainBranch, signal: externalSignal } = opts;
|
|
35
33
|
const pollMs = opts.pollMs ?? POLL_MS;
|
|
36
34
|
const modelsPath = opts.modelsPath ?? piModelsPath();
|
|
37
|
-
// pi's saved default model — the same stat-cached read status-roles.ts makes — used as the
|
|
38
|
-
// fallback provider key for a pi seam that names no model (plans/provider-concurrency.md §2).
|
|
39
|
-
const piDefault = readPiDefaultModel(piSettingsPath(modelsPath), root);
|
|
40
35
|
const breakerPolicy = opts.fallbackBreakerPolicy ?? FALLBACK_BREAKER_POLICY;
|
|
41
36
|
const breakerNow = opts.breakerNow ?? Date.now;
|
|
42
37
|
// Parallel work instances (part 5b/7): the runner list is every runner id the config enables
|
|
@@ -77,10 +72,6 @@ export async function runOrchestrator(opts) {
|
|
|
77
72
|
// resolver — merge-drain.ts) all hold a permit; see its MergePipelineContext for why a
|
|
78
73
|
// merge is not exempt.
|
|
79
74
|
const semaphore = new Semaphore(Math.max(1, config.maxConcurrent));
|
|
80
|
-
// Seed the pool's per-provider caps from the startup config (plans/provider-concurrency.md
|
|
81
|
-
// §7): a role run's lease is bounded by its provider key from the first launch onward, not
|
|
82
|
-
// just after a live edit. A config without the key passes an empty map, the no-change case.
|
|
83
|
-
semaphore.setKeyCaps(config.maxConcurrentPerProvider ?? {});
|
|
84
75
|
const infoFile = orchestratorStatePath(root);
|
|
85
76
|
const info = { pid: process.pid, startedAt: Date.now(), roles: enabled };
|
|
86
77
|
if (redeploy)
|
|
@@ -174,9 +165,6 @@ export async function runOrchestrator(opts) {
|
|
|
174
165
|
// the start gate reads gateStates.teamHold at permit time, so its closures always see
|
|
175
166
|
// the latest poll's hold verdict rather than the poll that created them.
|
|
176
167
|
const gateStates = newTeamGateStates(config);
|
|
177
|
-
// A restart must not re-announce a window that is still open.
|
|
178
|
-
gateStates.claudeWindowWarned = loggedWindowKeys(root, "subscription_window_warning", Date.now());
|
|
179
|
-
gateStates.claudeWindowReserved = loggedWindowKeys(root, "subscription_window_reserved", Date.now());
|
|
180
168
|
// The live config the last successful reload produced (last-known-good while the file is
|
|
181
169
|
// broken or missing). The reload bookkeeping itself lives in src/config/config-live.ts.
|
|
182
170
|
const liveReload = newLiveConfigReload({ root, config, mainBranch, runnerSignal: roleSignal, runners, semaphore, roleFilter: opts.roleFilter });
|
|
@@ -194,10 +182,10 @@ export async function runOrchestrator(opts) {
|
|
|
194
182
|
// logs exactly once — on the transition in, never while merely not-due and never on exit.
|
|
195
183
|
// In-memory only: a restart mid-episode can re-log at most one event.
|
|
196
184
|
const deferredDue = new Map();
|
|
197
|
-
// The operator notify hook (src/events/notify.ts owns the whole concern): one
|
|
198
|
-
// command run on notable events (budget_paused, role_streak_paused,
|
|
199
|
-
// restart_blocked). Subscribed here so it sees every event this
|
|
200
|
-
// disposed beside the orchestrator_stop event below.
|
|
185
|
+
// The operator notify hook (src/events/event-notify.ts owns the whole concern): one
|
|
186
|
+
// configured shell command run on notable events (budget_paused, role_streak_paused,
|
|
187
|
+
// merge_failed, restart_blocked). Subscribed here so it sees every event this
|
|
188
|
+
// process logs from start; disposed beside the orchestrator_stop event below.
|
|
201
189
|
const notifier = newNotifier(root);
|
|
202
190
|
// Best-effort event logging in the poll loop (bestEffortEventLogger, events.ts): this loop
|
|
203
191
|
// and its finally run with no catch, so an unwritable feed (ENOSPC, EACCES, the path replaced
|
|
@@ -221,7 +209,6 @@ export async function runOrchestrator(opts) {
|
|
|
221
209
|
once,
|
|
222
210
|
info,
|
|
223
211
|
modelsPath,
|
|
224
|
-
piDefault,
|
|
225
212
|
redeploy,
|
|
226
213
|
launchServicesWatch,
|
|
227
214
|
reclaim,
|
|
@@ -19,52 +19,73 @@ import { noteSkip } from "./scheduling-scope.js";
|
|
|
19
19
|
* runner's claim/heldElsewhere state, the claim group's held set, and once-mode settlement on
|
|
20
20
|
* the skip paths. */
|
|
21
21
|
export function admitRunner(scope, runner, reason) {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// still runs), skips its scheduled and main-moved runs instead of paying a model run that
|
|
36
|
-
// can only end in
|
|
37
|
-
// nothing-to-do. Startup, inbox and resume runs, a fresh operator wake, and a runner with a
|
|
38
|
-
// claim or outstanding work still run. nextRunAt and backoff stay put, so the first poll
|
|
39
|
-
// after a plan turns eligible starts a run for it.
|
|
40
|
-
const standDownBase = baseRoleOf(runner.role);
|
|
41
|
-
if ((standDownBase === "feature" || (standDownBase === BUGFIX_ROLE && scope.openBugsNow)) &&
|
|
22
|
+
if (standDownForNoWork(scope, runner, reason))
|
|
23
|
+
return "skipped";
|
|
24
|
+
return claimGroupAdmission(scope, runner, reason);
|
|
25
|
+
}
|
|
26
|
+
/** The candidate half of the stand-down test: true when a feature runner, or a bugfix runner
|
|
27
|
+
* whose open bugs are non-empty (no open bugs at all is search mode, which still runs), is due
|
|
28
|
+
* a scheduled or main-moved run, is not freshly woken, and holds no claim or outstanding work —
|
|
29
|
+
* so startup, inbox and resume runs, a fresh operator wake, and a claimed or mid-entry runner
|
|
30
|
+
* all pass through. The caller stands the runner down only when this holds and no entry is
|
|
31
|
+
* eligible. */
|
|
32
|
+
function noWorkScheduledRun(scope, runner, reason) {
|
|
33
|
+
const base = baseRoleOf(runner.role);
|
|
34
|
+
return ((base === "feature" || (base === BUGFIX_ROLE && scope.openBugsNow)) &&
|
|
42
35
|
(reason === "scheduled" || reason === "main moved") &&
|
|
43
36
|
!freshlyWoken(runner.state) &&
|
|
44
37
|
runner.state.claim === undefined &&
|
|
45
|
-
!hasOutstandingWork(runner.state))
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
runner.state.heldElsewhere = undefined;
|
|
56
|
-
if (scope.once.active)
|
|
57
|
-
scope.once.settle(runner.role, "no free entry");
|
|
58
|
-
return "skipped";
|
|
59
|
-
}
|
|
38
|
+
!hasOutstandingWork(runner.state));
|
|
39
|
+
}
|
|
40
|
+
/** The eligible-entries list for a base role, computed once per poll and memoized on the
|
|
41
|
+
* scope so the stand-down and claim-group paths share it. */
|
|
42
|
+
function eligibleForBase(scope, base) {
|
|
43
|
+
let eligible = scope.eligibleMemo.get(base);
|
|
44
|
+
if (eligible === undefined) {
|
|
45
|
+
eligible =
|
|
46
|
+
scope.claimGroups.get(base)?.eligible ?? eligibleEntries(scope.root, base);
|
|
47
|
+
scope.eligibleMemo.set(base, eligible);
|
|
60
48
|
}
|
|
49
|
+
return eligible;
|
|
50
|
+
}
|
|
51
|
+
/** Stand down a runner with no eligible entry at all on its scheduled and main-moved runs: the
|
|
52
|
+
* feature charter says "If PLANS.md is empty or everything is done, there is nothing to do" and
|
|
53
|
+
* the bugfix charter says "when every open entry carries [a Refused note], there is nothing to
|
|
54
|
+
* do", so a feature runner (primary included) with no eligible plan at all, or a bugfix runner
|
|
55
|
+
* whose open bugs are all held (no open bugs at all is search mode, which still runs), skips
|
|
56
|
+
* its scheduled and main-moved runs instead of paying a model run that can only end in
|
|
57
|
+
* nothing-to-do. nextRunAt and backoff stay put, so the first poll after a plan turns eligible
|
|
58
|
+
* starts a run for it. */
|
|
59
|
+
function standDownForNoWork(scope, runner, reason) {
|
|
60
|
+
if (!noWorkScheduledRun(scope, runner, reason))
|
|
61
|
+
return false;
|
|
62
|
+
if (eligibleForBase(scope, baseRoleOf(runner.role)).length > 0)
|
|
63
|
+
return false;
|
|
64
|
+
noteSkip(scope.root, runner, "no-free-entry");
|
|
65
|
+
runner.state.heldElsewhere = undefined;
|
|
66
|
+
if (scope.once.active)
|
|
67
|
+
scope.once.settle(runner.role, "no free entry");
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
/** Parallel work instances (part 4/7): a multi-runner base role's claim assignment, after
|
|
71
|
+
* every gate has admitted this runner. A runner that already holds a claim proceeds — its
|
|
72
|
+
* revision, resume or retry is the same entry. An unclaimed runner with outstanding work
|
|
73
|
+
* (a revision) is the mid-entry runner the group hold waits on: it proceeds unassigned —
|
|
74
|
+
* holding it too would deadlock the whole group, since only its run can settle the hold.
|
|
75
|
+
* Otherwise, when the run is not an inbox/resume run, it takes the first free entry; an
|
|
76
|
+
* extra instance with none free is skipped without touching its backoff, and the primary
|
|
77
|
+
* stands down too while siblings hold every eligible entry, unless an operator wake is fresh
|
|
78
|
+
* (then it runs unassigned). */
|
|
79
|
+
function claimGroupAdmission(scope, runner, reason) {
|
|
61
80
|
const claimGroup = scope.claimGroups.get(baseRoleOf(runner.role));
|
|
62
81
|
if (claimGroup) {
|
|
63
82
|
if (runner.state.claim) {
|
|
64
83
|
// keeps its claim
|
|
84
|
+
return "admitted";
|
|
65
85
|
}
|
|
66
86
|
else if (hasOutstandingWork(runner.state)) {
|
|
67
87
|
// finishes its own unrecorded entry
|
|
88
|
+
return "admitted";
|
|
68
89
|
}
|
|
69
90
|
else if (reason !== "inbox" && reason !== "resume") {
|
|
70
91
|
// The group holds an unclaimed mid-entry sibling: assign nothing this poll, or a
|
|
@@ -74,39 +95,7 @@ export function admitRunner(scope, runner, reason) {
|
|
|
74
95
|
noteSkip(scope.root, runner, "claim-hold");
|
|
75
96
|
return "skipped";
|
|
76
97
|
}
|
|
77
|
-
|
|
78
|
-
const pick = assignNext(free);
|
|
79
|
-
// An unassigned runner is told which entries its siblings hold (cleared when none).
|
|
80
|
-
const heldTitles = claimGroup.eligible
|
|
81
|
-
.filter((e) => claimGroup.held.has(e.key))
|
|
82
|
-
.map((e) => e.title);
|
|
83
|
-
runner.state.heldElsewhere = !pick && heldTitles.length > 0 ? heldTitles : undefined;
|
|
84
|
-
if (pick) {
|
|
85
|
-
runner.state.claim = {
|
|
86
|
-
file: baseRoleOf(runner.role) === BUGFIX_ROLE ? "BUGS.md" : "PLANS.md",
|
|
87
|
-
key: pick.key,
|
|
88
|
-
title: pick.title,
|
|
89
|
-
at: scope.now,
|
|
90
|
-
source: "assigned",
|
|
91
|
-
};
|
|
92
|
-
claimGroup.held.add(pick.key);
|
|
93
|
-
logEventBestEffort(scope.root, {
|
|
94
|
-
source: runner.role,
|
|
95
|
-
type: "claim",
|
|
96
|
-
action: "assigned",
|
|
97
|
-
key: pick.key,
|
|
98
|
-
title: pick.title,
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
else if (instanceIndex(runner.role) >= 2 ||
|
|
102
|
-
(heldTitles.length > 0 && !freshlyWoken(runner.state))) {
|
|
103
|
-
// An idle extra instance, or a primary whose eligible entries siblings all hold
|
|
104
|
-
// (unless an operator wake is fresh): no run, no backoff climb.
|
|
105
|
-
noteSkip(scope.root, runner, "no-free-entry");
|
|
106
|
-
if (scope.once.active)
|
|
107
|
-
scope.once.settle(runner.role, "no free entry");
|
|
108
|
-
return "skipped";
|
|
109
|
-
}
|
|
98
|
+
return assignClaim(scope, runner, claimGroup.eligible, claimGroup.held);
|
|
110
99
|
}
|
|
111
100
|
}
|
|
112
101
|
else {
|
|
@@ -114,3 +103,47 @@ export function admitRunner(scope, runner, reason) {
|
|
|
114
103
|
}
|
|
115
104
|
return "admitted";
|
|
116
105
|
}
|
|
106
|
+
/** Assign the first free eligible entry to an unclaimed admitted runner: record the claim on
|
|
107
|
+
* the runner, add it to the group's held set, and log the assignment. With no free entry, an
|
|
108
|
+
* idle extra instance, or a primary whose eligible entries siblings all hold (unless an
|
|
109
|
+
* operator wake is fresh), stands down rather than run unassigned. */
|
|
110
|
+
function assignClaim(scope, runner, eligible, held) {
|
|
111
|
+
const free = eligible.filter((e) => !held.has(e.key));
|
|
112
|
+
const pick = assignNext(free);
|
|
113
|
+
// An unassigned runner is told which entries its siblings hold (cleared when none).
|
|
114
|
+
const heldTitles = eligible.filter((e) => held.has(e.key)).map((e) => e.title);
|
|
115
|
+
runner.state.heldElsewhere = !pick && heldTitles.length > 0 ? heldTitles : undefined;
|
|
116
|
+
if (pick) {
|
|
117
|
+
recordAssignedClaim(scope, runner, pick, held);
|
|
118
|
+
return "admitted";
|
|
119
|
+
}
|
|
120
|
+
else if (instanceIndex(runner.role) >= 2 ||
|
|
121
|
+
(heldTitles.length > 0 && !freshlyWoken(runner.state))) {
|
|
122
|
+
// An idle extra instance, or a primary whose eligible entries siblings all hold
|
|
123
|
+
// (unless an operator wake is fresh): no run, no backoff climb.
|
|
124
|
+
noteSkip(scope.root, runner, "no-free-entry");
|
|
125
|
+
if (scope.once.active)
|
|
126
|
+
scope.once.settle(runner.role, "no free entry");
|
|
127
|
+
return "skipped";
|
|
128
|
+
}
|
|
129
|
+
return "admitted";
|
|
130
|
+
}
|
|
131
|
+
/** Build the runner's claim record, add the picked entry to the group's held set, and log the
|
|
132
|
+
* assignment to the events feed. */
|
|
133
|
+
function recordAssignedClaim(scope, runner, pick, held) {
|
|
134
|
+
runner.state.claim = {
|
|
135
|
+
file: baseRoleOf(runner.role) === BUGFIX_ROLE ? "BUGS.md" : "PLANS.md",
|
|
136
|
+
key: pick.key,
|
|
137
|
+
title: pick.title,
|
|
138
|
+
at: scope.now,
|
|
139
|
+
source: "assigned",
|
|
140
|
+
};
|
|
141
|
+
held.add(pick.key);
|
|
142
|
+
logEventBestEffort(scope.root, {
|
|
143
|
+
source: runner.role,
|
|
144
|
+
type: "claim",
|
|
145
|
+
action: "assigned",
|
|
146
|
+
key: pick.key,
|
|
147
|
+
title: pick.title,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
@@ -13,7 +13,7 @@ import { readMergeMarker, mergeChanges } from "../merge/merge-slot.js";
|
|
|
13
13
|
import { isAfterMergeRole } from "../review/post-merge-review.js";
|
|
14
14
|
import { aheadOfMainBranch, branchExists } from "../git/git.js";
|
|
15
15
|
import { branchName } from "../paths.js";
|
|
16
|
-
import { hasUnmergedPin } from "../runner/leftover.js";
|
|
16
|
+
import { hasUnmergedPin } from "../runner/runner-leftover.js";
|
|
17
17
|
import { logEventBestEffort, warnEventBestEffort } from "../events/events.js";
|
|
18
18
|
import { eligibleEntries } from "../backlog/backlog-eligibility.js";
|
|
19
19
|
import { CLAIM_IDLE_MAX_MS, claimReleaseReason, heldKeys, listedKeys, } from "../scheduling/claims.js";
|
|
@@ -5,7 +5,7 @@ import { isAfterMergeRole } from "../review/post-merge-review.js";
|
|
|
5
5
|
import { deferRunReason, isEligible } from "../scheduling/scheduling.js";
|
|
6
6
|
import { inboxSize } from "../inbox/inbox.js";
|
|
7
7
|
import { freshlyWoken } from "../runner/runner-state.js";
|
|
8
|
-
import { MERGE_CONFLICT_LIMIT } from "../runner/leftover.js";
|
|
8
|
+
import { MERGE_CONFLICT_LIMIT } from "../runner/runner-leftover.js";
|
|
9
9
|
import { noteSkip } from "./scheduling-scope.js";
|
|
10
10
|
/** Decide whether this runner is due this poll, running every skip gate and the once/deferral
|
|
11
11
|
* bookkeeping against scheduling-scope.ts's scope. Returns skip:true when a gate declined the
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { logEvent, TUMWATER_SOURCE, warnEventBestEffort } from "../events/events.js";
|
|
2
|
-
import { orchestratorAlive } from "../team/orchestrator-info.js";
|
|
2
|
+
import { orchestratorAlive } from "../team/team-orchestrator-info.js";
|
|
3
3
|
import { errorMessage } from "../text/text.js";
|
|
4
4
|
const registry = [];
|
|
5
5
|
/** Append one step to the registry; a duplicate name throws. */
|
package/dist/src/paths.js
CHANGED
|
@@ -36,7 +36,8 @@ export function configLockPath(root) {
|
|
|
36
36
|
}
|
|
37
37
|
/** The cross-process lock serializing writes to the primary checkout's tracked backlog files:
|
|
38
38
|
* `tumwater bug`/`plan` (backlog-write.ts's appendEntry) and `tumwater questions answer`
|
|
39
|
-
* (backlog/question-answer.ts's tryAnswerQuestion) each read a whole backlog file and
|
|
39
|
+
* (src/backlog/backlog-question-answer.ts's tryAnswerQuestion) each read a whole backlog file and
|
|
40
|
+
* rewrite
|
|
40
41
|
* it, while a merge's working-tree fast-forward (merge-git.ts's ffMainTo) updates those
|
|
41
42
|
* same files in
|
|
42
43
|
* the same checkout by `git merge --ff-only`. Without a shared lock the CLI's stale read can
|
|
@@ -119,7 +120,7 @@ export function worktreeUseLockPath(root) {
|
|
|
119
120
|
return path.join(tumwaterDir(root), "state", "worktree-use.lock");
|
|
120
121
|
}
|
|
121
122
|
/** The orchestrator's own info file (its pid, for liveness checks —
|
|
122
|
-
* team/orchestrator-info.ts). */
|
|
123
|
+
* team/team-orchestrator-info.ts). */
|
|
123
124
|
export function orchestratorStatePath(root) {
|
|
124
125
|
return path.join(tumwaterDir(root), "state", "orchestrator.json");
|
|
125
126
|
}
|
|
@@ -286,7 +287,7 @@ export function piLogPath(root, role) {
|
|
|
286
287
|
/** A role's backend-native stdout log (.tumwater/log/<role>.<backend>.native.jsonl), written
|
|
287
288
|
* beside the canonical raw log and kept for debugging only: no reader folds it, and the backend
|
|
288
289
|
* is in the name because one role's runs may switch backends and the two dialects must not
|
|
289
|
-
* mix. Written and rotated like the raw log (src/agent/run
|
|
290
|
+
* mix. Written and rotated like the raw log (src/agent/agent-run.ts). */
|
|
290
291
|
export function nativeLogPath(root, role, backend) {
|
|
291
292
|
return path.join(tumwaterDir(root), "log", `${role}.${backend}.native.jsonl`);
|
|
292
293
|
}
|
|
@@ -307,7 +308,7 @@ export function sessionDir(root, role) {
|
|
|
307
308
|
return path.join(sessionsRootDir(root), role);
|
|
308
309
|
}
|
|
309
310
|
/** `sessionDir`'s real team root — the nearest ancestor holding a `.tumwater` directory — or
|
|
310
|
-
* undefined when none does.
|
|
311
|
+
* undefined when none does. agent-spawn installs the notes shim from THIS (never from teamRootOf,
|
|
311
312
|
* whose no-ancestor fallback can be a shared directory): writing `.tumwater` at the fallback
|
|
312
313
|
* would mint a phantom harness root every other suite on the host then found — BUGS.md
|
|
313
314
|
* 2026-10-18's `$TMPDIR/.tumwater/bin/tumwater-notes`. */
|
|
@@ -341,15 +342,15 @@ export function owningTeamRoot(dir) {
|
|
|
341
342
|
* unreachable in a harness run and used only by OpenCode's harness-only read-back lookups
|
|
342
343
|
* (opencodeDbPath, loadConfigSafe), which always see a real root. Never use this to decide
|
|
343
344
|
* where to WRITE a `.tumwater`: the fallback can be a shared directory, and creating `.tumwater`
|
|
344
|
-
* there mints a phantom root (BUGS.md 2026-10-18);
|
|
345
|
+
* there mints a phantom root (BUGS.md 2026-10-18); agent-spawn's shim install uses
|
|
345
346
|
* nearestTumwaterRoot instead. Moved here from the opencode adapter (plans/notes-command.md) so
|
|
346
|
-
*
|
|
347
|
+
* agent-spawn's shim install — which every backend's run needs — shares the one lookup instead of
|
|
347
348
|
* the opencode adapter keeping it private. */
|
|
348
349
|
export function teamRootOf(sessionDir) {
|
|
349
350
|
return nearestTumwaterRoot(sessionDir) ?? path.dirname(path.dirname(sessionDir));
|
|
350
351
|
}
|
|
351
352
|
/** The directory the notes shim lands in, under a team root: `<root>/.tumwater/bin`.
|
|
352
|
-
*
|
|
353
|
+
* agent-spawn installs the `tumwater-notes` command there (src/agent/notes-shim.ts) and
|
|
353
354
|
* run-marker's backendBaseEnv prepends it to every run's PATH, so a run on any backend finds
|
|
354
355
|
* the command (plans/notes-command.md "Reaching it from a run"). */
|
|
355
356
|
export function notesShimDir(root) {
|
package/dist/src/pi/pi-args.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Building the pi command line for one run — pure argv construction with no subprocess or
|
|
2
2
|
* file I/O. Split out of pi.ts; the child-process integration (runPi) now lives in
|
|
3
|
-
* src/agent/run
|
|
3
|
+
* src/agent/agent-run.ts and the run's result types in src/pi/pi-run-result.ts, while
|
|
4
4
|
* test/pi-args.test.ts pins how config and session choices shape the command line. */
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
@@ -10,7 +10,9 @@ import { fileURLToPath } from "node:url";
|
|
|
10
10
|
* context-budget appends its context-usage note to the (already bounded) result that crosses a
|
|
11
11
|
* threshold, strip-thinking drops the model's replayed thinking from each request on a
|
|
12
12
|
* small-window chat-completions model, trim-system-prompt drops pi's docs section from the
|
|
13
|
-
* system prompt, and write-guard refuses an edit or write outside the run's worktree
|
|
13
|
+
* system prompt, and write-guard refuses an edit or write outside the run's worktree, while
|
|
14
|
+
* kill-guard refuses a bash call that kills processes by pattern (pkill/killall and ps/pgrep fed
|
|
15
|
+
* into kill). Notes ride
|
|
14
16
|
* the `tumwater-notes` command on
|
|
15
17
|
* every backend from part 2/4 on, so the role-notes extension is no longer bundled. */
|
|
16
18
|
export function bundledExtensionPaths() {
|
|
@@ -21,6 +23,7 @@ export function bundledExtensionPaths() {
|
|
|
21
23
|
"strip-thinking.js",
|
|
22
24
|
"trim-system-prompt.js",
|
|
23
25
|
"write-guard.js",
|
|
26
|
+
"kill-guard.js",
|
|
24
27
|
].map((file) => fileURLToPath(new URL(`../pi-extension/${file}`, import.meta.url)));
|
|
25
28
|
}
|
|
26
29
|
/** pi 1.2.0's built-in extensions other than `mcp`, read from pi's own `dist/extensions/index.js`.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** The pi adapter for the agent seam (plans/agent-backends.md "The seam"). Every piece here
|
|
2
2
|
* is today's pi-specific behavior moved behind the `AgentBackend` interface — argv via
|
|
3
3
|
* pi-args, the identity translator (pi's event vocabulary IS the canonical stream), the
|
|
4
|
-
* stderr exit rules, and the model-fallback-clone warning — so src/agent/run
|
|
5
|
-
* backend-neutral. The claude adapter registers alongside it in src/agent/run
|
|
4
|
+
* stderr exit rules, and the model-fallback-clone warning — so src/agent/agent-run.ts stays
|
|
5
|
+
* backend-neutral. The claude adapter registers alongside it in src/agent/agent-run.ts. */
|
|
6
6
|
import { piArgs } from "./pi-args.js";
|
|
7
7
|
import { resolveBinForBackend } from "./pi-bin.js";
|
|
8
8
|
import { backendRunEnv } from "../process/run-marker.js";
|
package/dist/src/pi/pi-bin.js
CHANGED
|
@@ -5,8 +5,8 @@ import { envPath, findOnPath, isExecutableFile } from "../files/files.js";
|
|
|
5
5
|
* resolution `TUMWATER_<ID>_BIN` → `backends.<id>.bin` (pi's legacy `agentBin` alias) → the
|
|
6
6
|
* backends' defaults (plans/agent-backends.md "Config"; plans/portability.md §5/7), the
|
|
7
7
|
* executability rule the preflight askers share, and the missing-binary wording they quote.
|
|
8
|
-
* The spawn (src/agent/
|
|
9
|
-
* gate (gates/startup-
|
|
8
|
+
* The spawn (src/agent/agent-spawn.ts) resolves through each backend's resolveBin; the startup
|
|
9
|
+
* gate (gates/startup-gates.ts) walks every enabled seam's backend through the same resolution and
|
|
10
10
|
* executability rule, and doctor (doctor/doctor-checks.ts) asks them for every backend in use
|
|
11
11
|
* (backendsInUse, the startup gate's rule). Each surface quotes this module's wording, so the
|
|
12
12
|
* executability verdict and the pi text cannot drift between them. Split out of
|
|
@@ -15,7 +15,7 @@ import { envPath, findOnPath, isExecutableFile } from "../files/files.js";
|
|
|
15
15
|
* layer. */
|
|
16
16
|
const PI_MISSING_MESSAGE = "pi not found on PATH — install it (https://github.com/badlogic/pi-mono) or add its bin directory to your PATH";
|
|
17
17
|
/** The operator-facing name of each source, as it appears in failure text and doctor's
|
|
18
|
-
* report. Exported so src/agent/
|
|
18
|
+
* report. Exported so src/agent/agent-settle.ts's spawn errors and doctor's checks quote the same
|
|
19
19
|
* string. `binEnv` is the environment variable a backend reads (pi's TUMWATER_PI_BIN by
|
|
20
20
|
* default; claude's adapter passes TUMWATER_CLAUDE_BIN), and `configKey` is the tumwater.json
|
|
21
21
|
* key the "config" source names (pi's legacy `agentBin`, or `backends.<id>.bin`), so a claude
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Parsing one line of pi's JSONL event stream — pure string functions with no subprocess or file
|
|
2
2
|
* I/O. Split out of pi.ts, whose child-process integration (runPi) now lives in
|
|
3
|
-
* src/agent/run
|
|
3
|
+
* src/agent/agent-run.ts and whose PiStreamParser in pi-stream.ts folds those lines into a run
|
|
4
4
|
* result — because these are shared
|
|
5
5
|
* by every observer that folds raw pi log lines into per-type state (src/ui/progress-data.ts's live
|
|
6
6
|
* tail, transcript.ts's renderer), and those display modules should not import from the subprocess
|