tumwater 0.4.2 → 0.4.4
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} +27 -13
- 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 +6 -27
- 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 +8 -0
- package/dist/src/agent/codex/codex-args.js +5 -5
- 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 +26 -24
- package/dist/src/agent/opencode/opencode-catalog.js +85 -36
- 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 +23 -27
- package/dist/src/agent/subscription-account.js +2 -83
- package/dist/src/backlog/backlog-archive.js +3 -4
- 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 +56 -122
- package/dist/src/backlog/backlog-md.js +5 -5
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +1 -1
- package/dist/src/backlog/backlog-release.js +12 -12
- package/dist/src/backlog/backlog-structure.js +114 -61
- package/dist/src/backlog/backlog.js +3 -2
- package/dist/src/baseline/main-baseline.js +148 -84
- package/dist/src/baseline/main-guardrails.js +2 -2
- package/dist/src/baseline/main-watch.js +84 -50
- package/dist/src/brief/brief.js +26 -13
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +29 -65
- package/dist/src/budget/budget.js +4 -5
- 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-report.js +10 -0
- package/dist/src/build/build-check-scoped.js +40 -18
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/build/build-stage.js +6 -4
- package/dist/src/change/change-data.js +1 -4
- package/dist/src/cli/cli-command-args.js +89 -52
- package/dist/src/cli/cli-completion-command.js +75 -0
- package/dist/src/cli/{config-commands.js → cli-config-commands.js} +82 -71
- package/dist/src/cli/cli-doctor-command.js +103 -0
- package/dist/src/cli/cli-flag-specs.js +42 -22
- package/dist/src/cli/cli-init.js +36 -14
- package/dist/src/cli/{log-commands.js → cli-log-commands.js} +43 -23
- package/dist/src/cli/{notify-command.js → cli-notify-command.js} +2 -2
- package/dist/src/cli/cli-query-commands.js +2 -2
- package/dist/src/cli/{question-commands.js → cli-question-commands.js} +86 -60
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-role-command.js +2 -41
- package/dist/src/cli/cli-start-summary.js +4 -3
- package/dist/src/cli/cli-start-supervisor.js +190 -0
- package/dist/src/cli/cli-start.js +94 -244
- package/dist/src/cli/completion.js +85 -49
- package/dist/src/cli/follow-view.js +20 -2
- package/dist/src/cli/help.js +6 -13
- package/dist/src/cli.js +70 -176
- package/dist/src/collections.js +39 -27
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +23 -1
- 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 +66 -40
- 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 +163 -102
- package/dist/src/config/config-schema.js +10 -12
- 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 +8 -72
- package/dist/src/config/config-validation.js +4 -39
- package/dist/src/config/config-views.js +26 -50
- package/dist/src/config/config-write.js +128 -102
- package/dist/src/config/config.js +5 -14
- package/dist/src/doctor/doctor-backlog-bugs.js +7 -6
- package/dist/src/doctor/doctor-backlog-plans.js +53 -120
- package/dist/src/doctor/doctor-backlog.js +16 -14
- package/dist/src/doctor/doctor-check-names.js +3 -6
- package/dist/src/doctor/doctor-checks.js +8 -8
- package/dist/src/doctor/doctor-claude-checks.js +70 -36
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +57 -117
- package/dist/src/doctor/doctor-fallback-checks.js +48 -26
- package/dist/src/doctor/doctor-holds.js +8 -47
- 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 +122 -21
- 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 +24 -11
- 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 +44 -64
- package/dist/src/events/event-labels.js +1 -5
- package/dist/src/events/event-legacy.js +3 -5
- package/dist/src/events/{notify.js → event-notify.js} +6 -6
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +8 -0
- package/dist/src/failure/failure-cluster.js +9 -18
- package/dist/src/failure/failure-data.js +12 -8
- package/dist/src/failure/failure-render.js +46 -36
- package/dist/src/failure/failure-state-change.js +54 -120
- 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 +32 -74
- 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} +9 -8
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git-diff.js +6 -2
- 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-args.js +12 -0
- 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-config-commands.js +2 -64
- package/dist/src/gui/gui-endpoint-commands.js +1 -1
- package/dist/src/gui/gui-endpoint-common.js +19 -1
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +43 -48
- package/dist/src/gui/gui-prompt-commands.js +25 -25
- package/dist/src/gui/gui-report-endpoints.js +1 -1
- package/dist/src/gui/gui-server.js +1 -2
- package/dist/src/gui/gui-team-commands.js +2 -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 +54 -36
- 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-check-failures.js +12 -9
- package/dist/src/merge/merge-core.js +7 -32
- package/dist/src/merge/merge-drain.js +11 -13
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +11 -14
- package/dist/src/merge/merge-queue-render.js +3 -3
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-stack.js +17 -18
- package/dist/src/merge/merge-to-main.js +13 -58
- package/dist/src/merge/merge-verify.js +12 -21
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +80 -47
- 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 +44 -69
- package/dist/src/orchestrator/orchestrator-scheduling.js +5 -9
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +18 -27
- package/dist/src/orchestrator/scheduling-skip.js +10 -14
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +10 -9
- 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 +28 -1
- package/dist/src/pi/pi-listed-models.js +13 -0
- 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 +41 -8
- package/dist/src/pi/pi-watchdogs.js +43 -15
- 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 +51 -26
- package/dist/src/process/process.js +20 -12
- package/dist/src/process/run-marker.js +33 -15
- 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/gate-prompts.js +1 -44
- 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/redeploy/redeployer.js +21 -1
- 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/diff-rules.js +35 -21
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +48 -41
- package/dist/src/review/review-precheck.js +14 -4
- package/dist/src/review/review.js +48 -170
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/legacy-role-migration.js +6 -3
- 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 +7 -46
- package/dist/src/roles/role-view.js +10 -33
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/ci-parity.js +19 -8
- package/dist/src/run/run-apply.js +12 -15
- 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 +141 -90
- 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 +128 -50
- package/dist/src/run/stage-check.js +60 -50
- 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/{leftover.js → runner-leftover.js} +9 -26
- package/dist/src/runner/runner-outcome.js +7 -2
- package/dist/src/runner/runner-pi.js +9 -22
- package/dist/src/runner/runner-recovery.js +5 -13
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -2
- package/dist/src/runner/runner-run.js +103 -50
- package/dist/src/runner/runner-state-load.js +18 -28
- package/dist/src/runner/runner-state.js +0 -14
- package/dist/src/runner/runner.js +41 -25
- package/dist/src/scheduling/backoff.js +8 -3
- package/dist/src/scheduling/claim-entries.js +2 -1
- package/dist/src/scheduling/claims.js +0 -4
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-budget.js +4 -43
- package/dist/src/status/status-data.js +35 -39
- package/dist/src/status/status-role-notes.js +2 -2
- package/dist/src/status/status-roles.js +9 -18
- 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 +105 -70
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +55 -36
- package/dist/src/team/team-polls.js +53 -201
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/datetime.js +16 -7
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +29 -15
- package/dist/src/text/suggest.js +1 -1
- package/dist/src/text/text.js +6 -5
- package/dist/src/ui/alert-feeds.js +46 -111
- package/dist/src/ui/badges.js +17 -88
- package/dist/src/ui/gui/gui-client-boot.js +2 -8
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +8 -33
- package/dist/src/ui/gui/gui-client-drawer.js +1 -1
- package/dist/src/ui/gui/gui-client-feed.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-operator.js +7 -5
- 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 +12 -6
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/progress-data.js +107 -44
- package/dist/src/ui/run-progress-model.js +20 -4
- package/dist/src/ui/status-model.js +2 -24
- package/dist/src/ui/status-payload.js +11 -30
- package/dist/src/ui/status-render.js +13 -24
- package/dist/src/ui/team-alert-backlog.js +14 -204
- package/dist/src/ui/team-alert-shared.js +6 -29
- 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 +8 -15
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/tui/tui-backlog.js +2 -2
- 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 +5 -10
- 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 +167 -98
- 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 +5 -4
- 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 -230
- package/dist/src/ui/tui/tui-pane-history.js +67 -74
- package/dist/src/ui/tui/tui-pane-pending.js +288 -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 +15 -10
- 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 +38 -33
- package/dist/src/ui/tui/tui.js +4 -7
- package/dist/src/verdict/fix-claim.js +107 -56
- package/dist/src/verdict/refusal.js +1 -1
- package/dist/src/verdict/reply-contract.js +3 -4
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/review/review-followup.js +0 -87
- package/dist/src/review/review-run.js +0 -395
- 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/cli/{brief-command.js → cli-brief-command.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
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
* scope. */
|
|
7
7
|
import { increment } from "../collections.js";
|
|
8
8
|
import { DIRECTOR_ROLE, commitTier } from "../roles/roles.js";
|
|
9
|
+
import { HOUR_MS } from "../text/datetime.js";
|
|
9
10
|
import { baseRoleOf, runnerEnabled, runnerPaused } from "../roles/runner-ids.js";
|
|
10
11
|
import { hasOutstandingWork, saveRunnerStateBestEffort } from "../runner/runner-state.js";
|
|
11
12
|
import { queuedMergeRoles } from "../merge/merge-queue.js";
|
|
12
13
|
import { readMergeMarker, mergeChanges } from "../merge/merge-slot.js";
|
|
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";
|
|
@@ -31,7 +31,7 @@ export function noteSkip(root, runner, reason) {
|
|
|
31
31
|
* hasUnmergedPin director pin, the per-base claim-group build (with its claim-release loop,
|
|
32
32
|
* which mutates runner state, persists and logs), and the writing count. */
|
|
33
33
|
export async function buildSchedulingScope(ctx) {
|
|
34
|
-
const { root, runners, mainBranch, merges, once, now, mainHead, userPaused, quietNow, pausedRoles, capPaused, maintenanceQuota, bootstrapHeld, budgetPausedRoles, probeRoles,
|
|
34
|
+
const { root, runners, mainBranch, merges, once, now, mainHead, userPaused, quietNow, pausedRoles, capPaused, maintenanceQuota, bootstrapHeld, budgetPausedRoles, probeRoles, roleQuietHeld, heldProviders, windowHeldProviders, roleProviders, openBugsNow, workBacklogOpen, planBacklogFull, holdForRestart, diskHeld, deferredDue, workMergedSince, } = ctx;
|
|
35
35
|
// Merge-queue interlock data, listed ONCE per poll (was once per runner per poll — each
|
|
36
36
|
// queuedMergeFiles() re-lists the merge-queue directory and re-stats every queued entry): the
|
|
37
37
|
// check below only asks whether the runner's role has a queued entry. The queue changes
|
|
@@ -43,23 +43,22 @@ export async function buildSchedulingScope(ctx) {
|
|
|
43
43
|
// clock, since its only other run is an operator prompt. A queued or in-flight pin is the
|
|
44
44
|
// merge-queue interlock's to hold, so the leftover reason is not offered then.
|
|
45
45
|
const pinnedDirector = !queuedRoles.has(DIRECTOR_ROLE) && (await hasUnmergedPin(root, DIRECTOR_ROLE));
|
|
46
|
-
// Merge-queue interlock relief (parts 1-3/4):
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
// queued role unless it is BOTH post-merge AND absent from interlockedHold.
|
|
46
|
+
// Merge-queue interlock relief (parts 1-3/4): the queued/in-flight interlock is scoped to
|
|
47
|
+
// the stages that actually race the author's branch. `interlockedHold` holds exactly the
|
|
48
|
+
// QUEUED roles in a non-admissible stage: pre-approval (`verifying`, or queued but not yet
|
|
49
|
+
// picked up — absent from the pipeline's approved/merge sets), conflict resolution
|
|
50
|
+
// (`resolve-wait`/`resolving`, read from the live marker), or a branch already ahead of main
|
|
51
|
+
// (a prior relief run's held commit). An approved-and-waiting or in-flight-merge queued
|
|
52
|
+
// change no longer parks the author. The relief applies team-wide — no role waits on a
|
|
53
|
+
// reviewer — so every queued role is held only while it is in interlockedHold;
|
|
54
|
+
// scheduling-skip.ts skips a queued role unless it is absent from that set.
|
|
56
55
|
const interlockedHold = new Set();
|
|
57
56
|
if (queuedRoles.size > 0) {
|
|
58
57
|
const marker = readMergeMarker(root);
|
|
59
58
|
const markerChanges = marker ? mergeChanges(marker) : [];
|
|
60
59
|
for (const runner of runners) {
|
|
61
60
|
const role = runner.role;
|
|
62
|
-
if (!queuedRoles.has(role)
|
|
61
|
+
if (!queuedRoles.has(role))
|
|
63
62
|
continue;
|
|
64
63
|
const inApproved = merges.approved.has(role);
|
|
65
64
|
const inMerge = (merges.merge?.roles ?? []).includes(role);
|
|
@@ -133,7 +132,7 @@ export async function buildSchedulingScope(ctx) {
|
|
|
133
132
|
const reason = released;
|
|
134
133
|
logEventBestEffort(root, { source: r.role, type: "claim", action: "released", title, reason });
|
|
135
134
|
if (reason === "stale") {
|
|
136
|
-
warnEventBestEffort(root, r.role, `released "${title}", which a stopped instance was working on (idle ${Math.round(CLAIM_IDLE_MAX_MS /
|
|
135
|
+
warnEventBestEffort(root, r.role, `released "${title}", which a stopped instance was working on (idle ${Math.round(CLAIM_IDLE_MAX_MS / HOUR_MS)}h)`);
|
|
137
136
|
}
|
|
138
137
|
}
|
|
139
138
|
claimGroups.set(base, {
|
|
@@ -151,15 +150,10 @@ export async function buildSchedulingScope(ctx) {
|
|
|
151
150
|
// The per-role windows (quietHoursPerRole) fold in beside the team one as a fourth
|
|
152
151
|
// disjunct — a role held by either window (or both, held once) starts no new runs;
|
|
153
152
|
// the director stays exempt by the same role check that covers the other disjuncts.
|
|
154
|
-
// The budget hold, per role (part 5c/8): a tier that resolved to pause holds its own
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
|
|
158
|
-
// EXACTLY the probed pair's tier's roles: the earlier draft's pair-blind pierce lifted the
|
|
159
|
-
// pause team-wide on any due probe.
|
|
160
|
-
const budgetHolds = (role) => (budgetPausedRoles.has(role) ||
|
|
161
|
-
(reviewStrongPaused && !afterMergeRoles.has(baseRoleOf(role)))) &&
|
|
162
|
-
!probeRoles.has(role);
|
|
153
|
+
// The budget hold, per role (part 5c/8): a tier that resolved to pause holds its own
|
|
154
|
+
// roles. The probe of a demoted pair pierces the hold for EXACTLY the probed pair's tier's
|
|
155
|
+
// roles: the earlier draft's pair-blind pierce lifted the pause team-wide on any due probe.
|
|
156
|
+
const budgetHolds = (role) => budgetPausedRoles.has(role) && !probeRoles.has(role);
|
|
163
157
|
const operatorPauseBlocks = (role) => (userPaused || quietNow || roleQuietHeld.has(role) || budgetHolds(role)) &&
|
|
164
158
|
role !== DIRECTOR_ROLE;
|
|
165
159
|
// Fresh operator demand: a queued prompt or a wake newer than the role's last run end. The
|
|
@@ -213,12 +207,9 @@ export async function buildSchedulingScope(ctx) {
|
|
|
213
207
|
bootstrapHeld,
|
|
214
208
|
budgetPausedRoles,
|
|
215
209
|
probeRoles,
|
|
216
|
-
reviewStrongPaused,
|
|
217
|
-
afterMergeRoles,
|
|
218
210
|
roleQuietHeld,
|
|
219
211
|
heldProviders,
|
|
220
212
|
windowHeldProviders,
|
|
221
|
-
reviewHeld,
|
|
222
213
|
roleProviders,
|
|
223
214
|
openBugsNow,
|
|
224
215
|
workBacklogOpen,
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { logEventBestEffort } from "../events/events.js";
|
|
2
2
|
import { baseRoleOf, configuredInstances, runnerPaused } from "../roles/runner-ids.js";
|
|
3
3
|
import { BUGFIX_ROLE, DIRECTOR_ROLE, commitTier } from "../roles/roles.js";
|
|
4
|
-
import { isAfterMergeRole } from "../review/post-merge-review.js";
|
|
5
4
|
import { deferRunReason, isEligible } from "../scheduling/scheduling.js";
|
|
6
5
|
import { inboxSize } from "../inbox/inbox.js";
|
|
7
6
|
import { freshlyWoken } from "../runner/runner-state.js";
|
|
8
|
-
import { MERGE_CONFLICT_LIMIT } from "../runner/leftover.js";
|
|
7
|
+
import { MERGE_CONFLICT_LIMIT } from "../runner/runner-leftover.js";
|
|
9
8
|
import { noteSkip } from "./scheduling-scope.js";
|
|
10
9
|
/** Decide whether this runner is due this poll, running every skip gate and the once/deferral
|
|
11
10
|
* bookkeeping against scheduling-scope.ts's scope. Returns skip:true when a gate declined the
|
|
@@ -14,7 +13,7 @@ import { noteSkip } from "./scheduling-scope.js";
|
|
|
14
13
|
* deferredDue, quotaAdmitted, once-mode settlement — and writes skip reasons as its only
|
|
15
14
|
* side effects. */
|
|
16
15
|
export async function skipRunner(scope, runner) {
|
|
17
|
-
const { root, once, now, mainHead, pausedRoles, capPaused, holdForRestart, diskHeld, windowHeldProviders, roleProviders,
|
|
16
|
+
const { root, once, now, mainHead, pausedRoles, capPaused, holdForRestart, diskHeld, windowHeldProviders, roleProviders, heldProviders, openBugsNow, workBacklogOpen, planBacklogFull, deferredDue, workMergedSince, queuedRoles, interlockedHold, pinnedDirector, claimGroups, writing, maintenanceQuota, maintenanceQuotaOverLine, bootstrapHeldNow, operatorPauseBlocks, } = scope;
|
|
18
17
|
// The role's inbox size and operator demand, computed once per runner per poll: every
|
|
19
18
|
// consumer below (the holds, isEligible's inbox count, the maintenance admission) shares
|
|
20
19
|
// this one directory listing + per-file stat read instead of each re-scanning the queue.
|
|
@@ -73,10 +72,8 @@ export async function skipRunner(scope, runner) {
|
|
|
73
72
|
// re-queue every poll until the repeated-failure pause stops it.
|
|
74
73
|
if (runner.role === DIRECTOR_ROLE && windowHeldProviders.has(roleProviders.get(runner.role)))
|
|
75
74
|
return { skip: true };
|
|
76
|
-
if (runner.role !== DIRECTOR_ROLE &&
|
|
77
|
-
|
|
78
|
-
(reviewHeld && !afterMergeRoles.has(baseRoleOf(runner.role)))))
|
|
79
|
-
return { skip: true }; // nor while ITS provider's failure storm holds — or the reviewer's, which a pre-merge review gates (a review.afterMerge role is exempt)
|
|
75
|
+
if (runner.role !== DIRECTOR_ROLE && heldProviders.has(roleProviders.get(runner.role)))
|
|
76
|
+
return { skip: true }; // nor while ITS provider's failure storm holds — the reviewer's hold parks only the post-merge review queue, never a role start
|
|
80
77
|
// The role's OWN queue decides inbox due-ness: the director counts its historical
|
|
81
78
|
// inbox, every other role its per-role queue (tumwater prompt --role <id>) — so a
|
|
82
79
|
// queued prompt makes its role due by itself, no wake marker needed (isEligible).
|
|
@@ -113,14 +110,13 @@ export async function skipRunner(scope, runner) {
|
|
|
113
110
|
// results — but the merge's outcome replaces it before the interlock lets the role back into
|
|
114
111
|
// this pass), and skipping here saves that branch's workMergedSince git-range query for the
|
|
115
112
|
// skipped role.
|
|
116
|
-
// Interlock relief (parts 1-3/4): a
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
// reach the runner before it authors again.
|
|
113
|
+
// Interlock relief (parts 1-3/4): a queued role is held only while it is in a non-admissible
|
|
114
|
+
// stage — the pre-approval/conflict/branch-ahead holds buildSchedulingScope put in
|
|
115
|
+
// `interlockedHold` — and otherwise falls through to author (the merge no longer parks it).
|
|
116
|
+
// A role still in a held stage keeps the full interlock: its rejection must reach the runner
|
|
117
|
+
// before it authors again. The relief now applies team-wide: no role waits on a reviewer.
|
|
122
118
|
if (queuedRoles.has(runner.role)) {
|
|
123
|
-
if (
|
|
119
|
+
if (interlockedHold.has(runner.role)) {
|
|
124
120
|
noteSkip(root, runner, undefined);
|
|
125
121
|
return { skip: true };
|
|
126
122
|
}
|
|
@@ -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
|
}
|
|
@@ -163,8 +164,8 @@ export function roleNotesPath(root, role) {
|
|
|
163
164
|
export function mergeStatePath(root) {
|
|
164
165
|
return path.join(tumwaterDir(root), "state", "merging.json");
|
|
165
166
|
}
|
|
166
|
-
/** Merged commits awaiting their review after merging (src/review/post-merge-review.ts)
|
|
167
|
-
*
|
|
167
|
+
/** Merged commits awaiting their review after merging (src/review/post-merge-review.ts):
|
|
168
|
+
* `{ pending: PostReview[] }`. Runtime state, never committed. */
|
|
168
169
|
export function postReviewPath(root) {
|
|
169
170
|
return path.join(tumwaterDir(root), "state", "post-review.json");
|
|
170
171
|
}
|
|
@@ -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
|
|
@@ -76,6 +76,33 @@ export function messageUpdateHasContent(event) {
|
|
|
76
76
|
return false;
|
|
77
77
|
return isNonBlankString(update.delta);
|
|
78
78
|
}
|
|
79
|
+
/** Fold one pi event into the pending-reply state and return the new state — the single
|
|
80
|
+
* definition both observers apply (see PendingReply). An assistant `message_start` opens a
|
|
81
|
+
* stretch stamped `now`, or counts one more attempt of an open one. A `message_update` whose
|
|
82
|
+
* delta carries content (`hasContent`: each observer decides for itself whether a delta is worth
|
|
83
|
+
* parsing) or an assistant `message_end` that did not fail closes it: the model answered. A failed
|
|
84
|
+
* or aborted end keeps it open, because pi's retry that follows is the same unanswered request. */
|
|
85
|
+
export function foldPendingReply(pending, event, { hasContent = false, now = Date.now() } = {}) {
|
|
86
|
+
const msg = event.message;
|
|
87
|
+
switch (event.type) {
|
|
88
|
+
case "message_start": {
|
|
89
|
+
if (msg?.role !== "assistant")
|
|
90
|
+
return pending;
|
|
91
|
+
if (pending)
|
|
92
|
+
return { ...pending, attempts: pending.attempts + 1 };
|
|
93
|
+
const target = [msg.provider, msg.model].filter(isNonBlankString).join("/");
|
|
94
|
+
return { since: now, target, attempts: 1 };
|
|
95
|
+
}
|
|
96
|
+
case "message_update":
|
|
97
|
+
return hasContent ? undefined : pending;
|
|
98
|
+
case "message_end":
|
|
99
|
+
if (msg?.role !== "assistant")
|
|
100
|
+
return pending;
|
|
101
|
+
return msg.stopReason === "error" || msg.stopReason === "aborted" ? pending : undefined;
|
|
102
|
+
default:
|
|
103
|
+
return pending;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
79
106
|
/** The call's full raw command text from a tool-execution event's args (bash-like tools name
|
|
80
107
|
* it `command`, some `cmd`; other tools yield ""). The two open-call trackers
|
|
81
108
|
* (src/pi/pi-watchdogs.ts's stall warning, src/ui/progress-data.ts's dashboard flag) store this
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readJsonFile, writeJsonAtomic } from "../files/json-files.js";
|
|
2
|
+
import { cachedByStat } from "../files/stat-cache.js";
|
|
2
3
|
import { piListedModelsPath } from "../paths.js";
|
|
3
4
|
/** The whitespace-split rows of the table `pi --list-models` prints, header first. Null unless
|
|
4
5
|
* the first non-blank line is the `provider model …` header, so other text (a "No models
|
|
@@ -33,8 +34,20 @@ export function parsePiModelImages(stdout) {
|
|
|
33
34
|
}
|
|
34
35
|
return images;
|
|
35
36
|
}
|
|
37
|
+
/** Per-poll cache of the parsed listing, keyed by file path: the status snapshot reads it once
|
|
38
|
+
* per poll through collectRoleRows (src/status/status-roles.ts) while the file changes only when
|
|
39
|
+
* doctor runs `pi --list-models` and rewrites it, so an unchanged file costs one stat per poll
|
|
40
|
+
* instead of a re-read plus JSON.parse. Any write invalidates via dev/ino/mtime/size
|
|
41
|
+
* (stat-cache.cachedByStat, same freshness check as the other polled files); a missing or
|
|
42
|
+
* malformed file yields null and is not cached, so a mid-edit broken file recovers on the next
|
|
43
|
+
* poll exactly like before. */
|
|
44
|
+
const listedCache = new Map();
|
|
36
45
|
/** The saved listing, or null when the file is missing, torn, or has no `images` object. */
|
|
37
46
|
export function readPiListedModels(root) {
|
|
47
|
+
return cachedByStat(listedCache, piListedModelsPath(root), // Keyed by path so distinct roots and test files never collide.
|
|
48
|
+
piListedModelsPath(root), () => readPiListedModelsUncached(root), (listed) => ({ ...listed, images: { ...listed.images } }));
|
|
49
|
+
}
|
|
50
|
+
function readPiListedModelsUncached(root) {
|
|
38
51
|
const saved = readJsonFile(piListedModelsPath(root));
|
|
39
52
|
const images = saved?.images;
|
|
40
53
|
if (typeof images !== "object" || images === null || Array.isArray(images))
|
package/dist/src/pi/pi-models.js
CHANGED
|
@@ -3,7 +3,7 @@ import path from "node:path";
|
|
|
3
3
|
import { enabledRoleIds, loadConfigSafe } from "../config/config.js";
|
|
4
4
|
import { configForRole, resolverConfig, resolvedBackendConfig, reviewConfig } from "../config/config-views.js";
|
|
5
5
|
import { fallbackPair } from "../config/config-fallbacks.js";
|
|
6
|
-
import { seamAgent } from "../config/model-selector.js";
|
|
6
|
+
import { seamAgent } from "../config/config-model-selector.js";
|
|
7
7
|
import { cachedByStat } from "../files/stat-cache.js";
|
|
8
8
|
import { isJsonObject } from "../files/json-object.js";
|
|
9
9
|
import { readJsonFile } from "../files/json-files.js";
|
|
@@ -107,40 +107,54 @@ export function seamContextWindow(seam, projectRoot, modelsPath = piModelsPath()
|
|
|
107
107
|
const backend = seamAgent(seam);
|
|
108
108
|
switch (backend) {
|
|
109
109
|
case "pi":
|
|
110
|
-
|
|
111
|
-
if (!provider || !model) {
|
|
112
|
-
const piDefault = readPiDefaultModel(piSettingsPath(modelsPath), projectRoot);
|
|
113
|
-
provider = piDefault?.provider;
|
|
114
|
-
model = piDefault?.model;
|
|
115
|
-
}
|
|
116
|
-
return declaredWindow(resolveModelDef(readPiProviders(modelsPath), provider, model)?.contextWindow);
|
|
110
|
+
return piSeamContextWindow(seam, projectRoot, modelsPath);
|
|
117
111
|
case "opencode":
|
|
118
|
-
|
|
119
|
-
// the window part 6a/7 parsed from `limit.context`, keyed by the gateway-qualified id the
|
|
120
|
-
// run actually invokes (opencodeModelKey) — the same key and entry the free-ness check
|
|
121
|
-
// prices by, so a seam's window never disagrees with the model it runs.
|
|
122
|
-
if (seam.model === undefined)
|
|
123
|
-
return null;
|
|
124
|
-
return declaredWindow(readOpencodeCache(projectRoot).models[opencodeModelKey(seam.model)]?.context);
|
|
112
|
+
return opencodeSeamContextWindow(seam, projectRoot);
|
|
125
113
|
case "codex":
|
|
126
|
-
|
|
127
|
-
// `context_window × effective_context_window_percent / 100`, read from the team's codex
|
|
128
|
-
// home (backends.codex.configDir) at this project root — the same home the runs use. An
|
|
129
|
-
// unknown model, an absent home or a missing catalog reads null (declaredWindow's safe
|
|
130
|
-
// answer for an unknown window).
|
|
131
|
-
if (seam.model === undefined)
|
|
132
|
-
return null;
|
|
133
|
-
{
|
|
134
|
-
const { config } = loadConfigSafe(projectRoot);
|
|
135
|
-
const home = codexHome(resolvedBackendConfig(config ?? {}, "codex"));
|
|
136
|
-
if (home === undefined)
|
|
137
|
-
return null;
|
|
138
|
-
return declaredWindow(readCodexCatalog(home).get(seam.model)?.contextWindow);
|
|
139
|
-
}
|
|
114
|
+
return codexSeamContextWindow(seam, projectRoot);
|
|
140
115
|
default:
|
|
141
116
|
return null;
|
|
142
117
|
}
|
|
143
118
|
}
|
|
119
|
+
/** The window a pi seam declares, or null when it cannot be known: reads the pair's
|
|
120
|
+
* `contextWindow` from pi's models.json, running on pi's saved default
|
|
121
|
+
* (readPiDefaultModel) when the seam names no provider/model — exactly the model pi will
|
|
122
|
+
* start on. An unresolvable pair or a missing/unusable window reads null. */
|
|
123
|
+
function piSeamContextWindow(seam, projectRoot, modelsPath) {
|
|
124
|
+
let { provider, model } = seam;
|
|
125
|
+
if (!provider || !model) {
|
|
126
|
+
const piDefault = readPiDefaultModel(piSettingsPath(modelsPath), projectRoot);
|
|
127
|
+
provider = piDefault?.provider;
|
|
128
|
+
model = piDefault?.model;
|
|
129
|
+
}
|
|
130
|
+
return declaredWindow(resolveModelDef(readPiProviders(modelsPath), provider, model)?.contextWindow);
|
|
131
|
+
}
|
|
132
|
+
/** The window an opencode seam declares, or null when it cannot be known. The models.dev cache
|
|
133
|
+
* (readOpencodeCache, an empty cache when the file is missing) holds the window part 6a/7
|
|
134
|
+
* parsed from `limit.context`, keyed by the gateway-qualified id the run actually invokes
|
|
135
|
+
* (opencodeModelKey) — the same key and entry the free-ness check prices by, so a seam's
|
|
136
|
+
* window never disagrees with the model it runs. A missing cache, an unknown model or an
|
|
137
|
+
* unusable `context` reads null. */
|
|
138
|
+
function opencodeSeamContextWindow(seam, projectRoot) {
|
|
139
|
+
if (seam.model === undefined)
|
|
140
|
+
return null;
|
|
141
|
+
return declaredWindow(readOpencodeCache(projectRoot).models[opencodeModelKey(seam.model)]?.context);
|
|
142
|
+
}
|
|
143
|
+
/** The window a codex seam declares, or null when it cannot be known. The model catalog
|
|
144
|
+
* (codex-catalog.ts) holds the window part 8/9 parsed from
|
|
145
|
+
* `context_window × effective_context_window_percent / 100`, read from the team's codex home
|
|
146
|
+
* (backends.codex.configDir) at this project root — the same home the runs use. An unknown
|
|
147
|
+
* model, an absent home or a missing catalog reads null (declaredWindow's safe answer for an
|
|
148
|
+
* unknown window). */
|
|
149
|
+
function codexSeamContextWindow(seam, projectRoot) {
|
|
150
|
+
if (seam.model === undefined)
|
|
151
|
+
return null;
|
|
152
|
+
const { config } = loadConfigSafe(projectRoot);
|
|
153
|
+
const home = codexHome(resolvedBackendConfig(config ?? {}, "codex"));
|
|
154
|
+
if (home === undefined)
|
|
155
|
+
return null;
|
|
156
|
+
return declaredWindow(readCodexCatalog(home).get(seam.model)?.contextWindow);
|
|
157
|
+
}
|
|
144
158
|
/** The resolved primary seam's declared context window for a role — `configForRole` resolves
|
|
145
159
|
* the role's seam (the view the role page, status table/drawer and run history all print), then
|
|
146
160
|
* `seamContextWindow` names the window that seam declares, or null when it cannot be known. The
|
|
@@ -50,37 +50,60 @@ export function readPiDefaultModel(settingsPath, projectRoot) {
|
|
|
50
50
|
return cachedByStat(settingsCache, settingsPath, // Keyed by path so distinct roots and test files never collide.
|
|
51
51
|
settingsPath, () => readPiDefaultModelUncached(settingsPath, projectRoot), (defaultModel) => ({ ...defaultModel }));
|
|
52
52
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
const doc = readJsonFile(settingsPath);
|
|
57
|
-
if (!doc)
|
|
58
|
-
return null;
|
|
53
|
+
/** The saved default pair, or null when either half is missing or empty — pi uses a saved
|
|
54
|
+
* default only as a pair. */
|
|
55
|
+
function savedDefaultPair(doc) {
|
|
59
56
|
const { defaultProvider, defaultModel } = doc;
|
|
60
57
|
if (typeof defaultProvider !== "string" || defaultProvider === "")
|
|
61
58
|
return null;
|
|
62
59
|
if (typeof defaultModel !== "string" || defaultModel === "")
|
|
63
60
|
return null;
|
|
64
|
-
|
|
65
|
-
|
|
61
|
+
return { provider: defaultProvider, model: defaultModel };
|
|
62
|
+
}
|
|
63
|
+
/** Whether pi starts on the first scoped model instead of the saved default — a pattern
|
|
64
|
+
* tumwater does not resolve, so the reader shows nothing. */
|
|
65
|
+
function startsWithScopedModels(doc) {
|
|
66
|
+
return Array.isArray(doc.enabledModels) && doc.enabledModels.length > 0;
|
|
67
|
+
}
|
|
68
|
+
/** Whether the project's own `.pi/settings.json` names any default key, which makes pi's final
|
|
69
|
+
* choice depend on whether it trusts the project — unknowable to tumwater, so show nothing. */
|
|
70
|
+
function projectOverridesSavedDefault(projectRoot) {
|
|
66
71
|
// A project-local override that names any default key means pi's final choice depends on
|
|
67
72
|
// whether it trusts the project — unknowable to tumwater, so show nothing.
|
|
68
73
|
const proj = readJsonFile(path.join(projectRoot, ".pi", "settings.json"));
|
|
69
|
-
if (proj)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
+
if (!proj)
|
|
75
|
+
return false;
|
|
76
|
+
for (const key of DEFAULT_KEYS) {
|
|
77
|
+
if (proj[key] !== undefined)
|
|
78
|
+
return true;
|
|
74
79
|
}
|
|
75
|
-
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
/** pi's thinking for the pair: `modelThinkingLevels[key]` first, else `defaultThinkingLevel`. */
|
|
83
|
+
function savedThinkingLevel(doc, key) {
|
|
76
84
|
const levels = doc.modelThinkingLevels;
|
|
77
85
|
const level = isJsonObject(levels) ? levels[key] : undefined;
|
|
78
|
-
let thinking;
|
|
79
86
|
if (typeof level === "string")
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
87
|
+
return level;
|
|
88
|
+
if (typeof doc.defaultThinkingLevel === "string")
|
|
89
|
+
return doc.defaultThinkingLevel;
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
function readPiDefaultModelUncached(settingsPath, projectRoot) {
|
|
93
|
+
// readJsonFile is the shared "missing or torn reads as no data" policy for a JSON object
|
|
94
|
+
// file; the nested shapes get the same isJsonObject guard.
|
|
95
|
+
const doc = readJsonFile(settingsPath);
|
|
96
|
+
if (!doc)
|
|
97
|
+
return null;
|
|
98
|
+
const pair = savedDefaultPair(doc);
|
|
99
|
+
if (!pair)
|
|
100
|
+
return null;
|
|
101
|
+
if (startsWithScopedModels(doc))
|
|
102
|
+
return null;
|
|
103
|
+
if (projectOverridesSavedDefault(projectRoot))
|
|
104
|
+
return null;
|
|
105
|
+
const thinking = savedThinkingLevel(doc, `${pair.provider}/${pair.model}`);
|
|
83
106
|
return thinking !== undefined
|
|
84
|
-
? { provider:
|
|
85
|
-
: { provider:
|
|
107
|
+
? { provider: pair.provider, model: pair.model, thinking }
|
|
108
|
+
: { provider: pair.provider, model: pair.model };
|
|
86
109
|
}
|