tumwater 0.3.0 → 0.4.1
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 +1 -1
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +5 -2
- package/dist/src/agent/backend-traits.js +20 -0
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-stream.js +170 -166
- package/dist/src/agent/claude/{write-guard-hook.js → claude-write-guard-hook.js} +1 -1
- package/dist/src/agent/claude/claude-write-guard.js +5 -4
- package/dist/src/agent/codex/codex-backend.js +4 -4
- package/dist/src/agent/codex/codex-rollout.js +10 -21
- package/dist/src/agent/codex/codex-stream.js +202 -179
- package/dist/src/agent/codex/{write-guard-hook.js → codex-write-guard-hook.js} +2 -1
- package/dist/src/agent/codex/codex-write-guard.js +3 -3
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +4 -4
- package/dist/src/agent/opencode/opencode-config.js +2 -2
- package/dist/src/agent/opencode/opencode-stream.js +97 -88
- package/dist/src/agent/run-agent.js +66 -381
- package/dist/src/agent/run-settle.js +3 -2
- package/dist/src/agent/run-spawn.js +235 -0
- package/dist/src/agent/run-stream.js +193 -0
- package/dist/src/agent/write-guard.js +5 -3
- package/dist/src/backlog/backlog-archive.js +88 -36
- package/dist/src/backlog/backlog-cli-view.js +92 -98
- package/dist/src/backlog/backlog-cli.js +9 -64
- package/dist/src/backlog/backlog-commit.js +17 -9
- package/dist/src/backlog/backlog-eligibility.js +45 -27
- package/dist/src/backlog/backlog-index-data.js +159 -0
- package/dist/src/backlog/backlog-index.js +7 -5
- package/dist/src/backlog/backlog-md.js +1 -1
- package/dist/src/backlog/backlog-parts.js +23 -13
- package/dist/src/backlog/backlog-release.js +43 -33
- package/dist/src/backlog/backlog-render.js +1 -1
- package/dist/src/backlog/backlog-structure.js +0 -144
- package/dist/src/backlog/backlog-write.js +30 -17
- package/dist/src/backlog/backlog.js +9 -0
- package/dist/src/backlog/question-answer.js +3 -1
- package/dist/src/baseline/main-baseline.js +50 -15
- package/dist/src/baseline/main-guardrails.js +29 -78
- package/dist/src/baseline/main-red.js +64 -103
- package/dist/src/baseline/main-watch.js +89 -35
- package/dist/src/brief/brief-edit.js +3 -1
- package/dist/src/brief/brief.js +40 -0
- package/dist/src/budget/budget-fallback-view.js +330 -0
- package/dist/src/budget/budget-wiring.js +167 -0
- package/dist/src/budget/budget.js +3 -2
- package/dist/src/build/build-check-events.js +4 -2
- package/dist/src/build/build-check-report.js +2 -1
- package/dist/src/build/dep-install.js +64 -1
- package/dist/src/cli/cli-args.js +72 -0
- package/dist/src/cli/cli-flag-specs.js +38 -0
- package/dist/src/cli/cli-marker-commands.js +2 -2
- package/dist/src/cli/cli-query-commands.js +51 -41
- package/dist/src/cli/cli-review-commands.js +113 -0
- package/dist/src/cli/cli-role-command.js +140 -58
- package/dist/src/cli/cli-start-summary.js +10 -3
- package/dist/src/cli/cli-start.js +92 -38
- package/dist/src/cli/completion.js +21 -1
- package/dist/src/cli/config-commands.js +17 -8
- package/dist/src/cli/help.js +84 -16
- package/dist/src/cli/log-commands.js +137 -105
- package/dist/src/cli/question-commands.js +89 -64
- package/dist/src/cli.js +408 -286
- package/dist/src/collections.js +56 -6
- package/dist/src/config/config-editable-keys.js +3 -0
- package/dist/src/config/config-fallbacks.js +3 -2
- package/dist/src/config/config-request.js +176 -51
- package/dist/src/config/config-validation-roles.js +271 -0
- package/dist/src/config/config-validation.js +51 -241
- package/dist/src/config/config-views.js +10 -2
- package/dist/src/config/config-write.js +209 -140
- package/dist/src/config/config.js +9 -3
- package/dist/src/doctor/doctor-backlog-bugs.js +96 -53
- package/dist/src/doctor/doctor-backlog-plans.js +222 -88
- package/dist/src/doctor/doctor-backlog.js +171 -32
- package/dist/src/doctor/doctor-check-names.js +7 -1
- package/dist/src/doctor/doctor-checks.js +26 -19
- package/dist/src/doctor/doctor-claude-checks.js +6 -9
- package/dist/src/doctor/doctor-codex-checks.js +15 -29
- package/dist/src/doctor/doctor-contention-checks.js +293 -134
- package/dist/src/doctor/doctor-fallback-checks.js +130 -0
- package/dist/src/doctor/doctor-holds.js +13 -15
- package/dist/src/doctor/doctor-model-checks.js +45 -200
- package/dist/src/doctor/doctor-notify.js +7 -2
- package/dist/src/doctor/doctor-opencode-checks.js +23 -9
- package/dist/src/doctor/doctor-pi-checks.js +62 -0
- package/dist/src/doctor/doctor-principles.js +26 -0
- package/dist/src/doctor/doctor-project-checks.js +108 -13
- package/dist/src/doctor/doctor-role-notes.js +51 -0
- package/dist/src/doctor/doctor.js +17 -5
- package/dist/src/doctor/probe.js +14 -1
- package/dist/src/events/event-format.js +313 -157
- package/dist/src/events/event-labels.js +0 -1
- package/dist/src/events/event-read.js +13 -0
- package/dist/src/events/event-window.js +20 -2
- package/dist/src/events/notify.js +1 -1
- package/dist/src/failure/failure-cluster.js +4 -3
- package/dist/src/failure/failure-data.js +127 -90
- package/dist/src/failure/failure-render.js +62 -21
- package/dist/src/failure/failure-state-change.js +174 -8
- package/dist/src/failure/time-spend.js +136 -167
- package/dist/src/files/file-queue.js +17 -0
- package/dist/src/files/files.js +7 -0
- package/dist/src/gates/budget-gates.js +116 -324
- package/dist/src/gates/gate-polls.js +123 -184
- package/dist/src/gates/maintenance-quota.js +27 -17
- package/dist/src/gates/role-cap-gates.js +1 -1
- package/dist/src/gates/streak-gate.js +1 -1
- package/dist/src/git/commit-message.js +12 -0
- package/dist/src/git/git.js +18 -0
- package/dist/src/git/slot-salvage.js +4 -5
- package/dist/src/git/slots-state.js +12 -3
- package/dist/src/git/worktree-pool-startup.js +127 -0
- package/dist/src/git/worktree-pool.js +1 -120
- package/dist/src/gui/gui-admin-endpoints.js +1 -1
- package/dist/src/gui/gui-args.js +1 -1
- package/dist/src/gui/gui-backlog-commands.js +21 -12
- package/dist/src/gui/gui-backlog-endpoints.js +6 -5
- package/dist/src/gui/gui-command.js +19 -1
- package/dist/src/gui/gui-config-commands.js +126 -85
- package/dist/src/gui/gui-endpoint-commands.js +5 -3
- package/dist/src/gui/gui-endpoint-common.js +4 -3
- package/dist/src/gui/gui-history-endpoints.js +36 -27
- package/dist/src/gui/gui-operator-commands.js +175 -10
- package/dist/src/gui/gui-prompt-commands.js +16 -7
- package/dist/src/gui/gui-quarantine-commands.js +30 -0
- package/dist/src/gui/gui-report-endpoints.js +14 -4
- package/dist/src/gui/gui-server.js +20 -3
- package/dist/src/gui/gui-team-commands.js +1 -1
- package/dist/src/history/history-data.js +7 -1
- package/dist/src/history/history-queued.js +8 -0
- package/dist/src/history/history-render.js +15 -10
- package/dist/src/history/history.js +3 -6
- package/dist/src/inbox/inbox-top.js +4 -9
- package/dist/src/inbox/pending-prompt.js +3 -2
- package/dist/src/inbox/prompt-commands.js +189 -161
- package/dist/src/init/init.js +43 -17
- package/dist/src/merge/merge-core.js +2 -25
- package/dist/src/merge/merge-drain.js +23 -136
- package/dist/src/merge/merge-git.js +5 -5
- package/dist/src/merge/merge-pipeline.js +1 -1
- package/dist/src/merge/merge-post-review-drain.js +214 -0
- package/dist/src/merge/merge-queue-render.js +11 -12
- package/dist/src/merge/merge-queue.js +24 -2
- package/dist/src/merge/merge-requeue.js +1 -0
- package/dist/src/merge/merge-stack.js +1 -7
- package/dist/src/merge/merge-to-main.js +2 -10
- package/dist/src/merge/merge-top.js +33 -19
- package/dist/src/merge/merge-verify.js +65 -9
- package/dist/src/notes-cli.js +8 -3
- package/dist/src/operator/operator-commands.js +16 -5
- package/dist/src/operator/operator-intent.js +26 -3
- package/dist/src/operator/revert.js +18 -12
- package/dist/src/orchestrator/orchestrator-poll.js +49 -17
- package/dist/src/orchestrator/orchestrator-scheduling.js +4 -2
- package/dist/src/orchestrator/orchestrator.js +6 -5
- package/dist/src/orchestrator/scheduling-scope.js +51 -7
- package/dist/src/orchestrator/scheduling-skip.js +15 -5
- package/dist/src/paths.js +26 -10
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +2 -2
- package/dist/src/pi/pi-models.js +9 -0
- package/dist/src/pi/pi-stream.js +63 -41
- package/dist/src/pi/pi-watchdogs.js +65 -49
- package/dist/src/pi-extension/full-output.js +2 -3
- package/dist/src/process/process.js +1 -1
- package/dist/src/process/run-marker.js +10 -2
- package/dist/src/process/supervisor.js +7 -4
- package/dist/src/prompt/gate-notes.js +35 -10
- package/dist/src/prompt/gate-prompts.js +30 -7
- package/dist/src/prompt/notes-backlog.js +24 -15
- package/dist/src/prompt/principles.js +11 -0
- package/dist/src/prompt/prompt.js +13 -7
- package/dist/src/redeploy/redeploy-probes.js +11 -1
- package/dist/src/redeploy/redeployer.js +54 -7
- package/dist/src/redeploy/self-reload.js +7 -2
- package/dist/src/report/report-data.js +322 -223
- package/dist/src/report/report-fold.js +257 -191
- package/dist/src/report/report-main-red.js +64 -0
- package/dist/src/report/report-render.js +133 -25
- package/dist/src/report/report.js +2 -5
- package/dist/src/review/diff-rules.js +53 -0
- package/dist/src/review/known-flakes.js +32 -1
- package/dist/src/review/post-merge-review-accept.js +51 -0
- package/dist/src/review/post-merge-review-render.js +112 -0
- package/dist/src/review/post-merge-review.js +159 -24
- package/dist/src/review/protected-edits.js +22 -7
- package/dist/src/review/quarantine-render.js +82 -0
- package/dist/src/review/review-precheck.js +2 -1
- package/dist/src/review/review.js +33 -49
- package/dist/src/roles/role-catalog.js +73 -2
- package/dist/src/roles/role-guidance.js +18 -0
- package/dist/src/roles/role-labels.js +6 -3
- package/dist/src/roles/role-render.js +62 -8
- package/dist/src/roles/role-view.js +54 -12
- package/dist/src/roles/roles.js +3 -23
- package/dist/src/run/ci-parity.js +121 -0
- package/dist/src/run/feedback-filings.js +10 -1
- package/dist/src/run/qa-coverage.js +50 -15
- package/dist/src/run/run-apply.js +14 -2
- package/dist/src/run/run-detail-data.js +36 -16
- package/dist/src/run/run-detail-render.js +27 -9
- package/dist/src/run/run-finalize.js +76 -46
- package/dist/src/run/run-prompt.js +231 -177
- package/dist/src/run/run-stage.js +168 -70
- package/dist/src/run/run-verdict.js +127 -13
- package/dist/src/run/stage-backlog-checks.js +2 -2
- package/dist/src/run/stage-check.js +17 -11
- package/dist/src/run/stage-checks-shared.js +80 -0
- package/dist/src/run/stage-name-checks.js +194 -0
- package/dist/src/run/stage-path-checks.js +35 -537
- package/dist/src/run/stage-prose-checks.js +261 -0
- package/dist/src/runner/leftover.js +61 -21
- package/dist/src/runner/revision.js +14 -25
- package/dist/src/runner/runner-fallback.js +1 -1
- package/dist/src/runner/runner-outcome.js +4 -2
- package/dist/src/runner/runner-pi.js +72 -49
- package/dist/src/runner/runner-recovery.js +18 -3
- package/dist/src/runner/runner-run.js +36 -29
- package/dist/src/runner/runner-state.js +4 -3
- package/dist/src/scheduling/claim-entries.js +49 -0
- package/dist/src/scheduling/claims.js +1 -1
- package/dist/src/status/status-data.js +144 -86
- package/dist/src/status/status-polls.js +56 -5
- package/dist/src/status/status-role-notes.js +49 -0
- package/dist/src/status/status-roles.js +19 -2
- package/dist/src/team/reclaim.js +2 -2
- package/dist/src/text/datetime.js +3 -2
- package/dist/src/text/format.js +13 -1
- package/dist/src/text/phrases.js +22 -5
- package/dist/src/text/text.js +11 -0
- package/dist/src/ui/alert-feeds.js +320 -0
- package/dist/src/ui/badges.js +35 -2
- package/dist/src/ui/gui/gui-client-backlog.js +25 -3
- package/dist/src/ui/gui/gui-client-boot.js +102 -74
- package/dist/src/ui/gui/gui-client-drawer-config.js +64 -17
- package/dist/src/ui/gui/gui-client-drawer.js +46 -5
- package/dist/src/ui/gui/gui-client-feed.js +4 -9
- package/dist/src/ui/gui/gui-client-history.js +90 -88
- package/dist/src/ui/gui/gui-client-labels.js +5 -1
- package/dist/src/ui/gui/gui-client-model.js +9 -9
- package/dist/src/ui/gui/gui-client-operator.js +11 -11
- package/dist/src/ui/gui/gui-client-pending.js +189 -12
- package/dist/src/ui/gui/gui-client-report-chart.js +137 -10
- package/dist/src/ui/gui/gui-client-report.js +81 -6
- package/dist/src/ui/gui/gui-client-roles.js +52 -12
- package/dist/src/ui/gui/gui-client-settings.js +138 -4
- package/dist/src/ui/gui/gui-client-team.js +13 -2
- package/dist/src/ui/gui/gui-client.js +44 -5
- package/dist/src/ui/gui/gui-page.js +1 -0
- package/dist/src/ui/gui/gui-styles.js +4 -0
- package/dist/src/ui/progress-data.js +2 -2
- package/dist/src/ui/run-progress-model.js +4 -3
- package/dist/src/ui/status-model.js +24 -14
- package/dist/src/ui/status-payload.js +168 -117
- package/dist/src/ui/status-render.js +140 -81
- package/dist/src/ui/team-alert-backlog-feed.js +181 -0
- package/dist/src/ui/team-alert-backlog.js +267 -0
- package/dist/src/ui/team-alert-merge.js +183 -0
- package/dist/src/ui/team-alert-shared.js +79 -0
- package/dist/src/ui/team-alert-status.js +321 -0
- package/dist/src/ui/team-alert-tail.js +251 -0
- package/dist/src/ui/team-alerts.js +31 -513
- package/dist/src/ui/tone.js +7 -0
- package/dist/src/ui/tui/tui-backlog.js +8 -6
- package/dist/src/ui/tui/tui-editor-attach.js +1 -7
- package/dist/src/ui/tui/tui-editor-backends.js +1 -6
- package/dist/src/ui/tui/tui-editor-brief.js +4 -24
- package/dist/src/ui/tui/tui-editor-budget.js +10 -12
- package/dist/src/ui/tui/tui-editor-check.js +29 -0
- package/dist/src/ui/tui/tui-editor-checks.js +28 -0
- package/dist/src/ui/tui/tui-editor-concurrency.js +1 -0
- package/dist/src/ui/tui/tui-editor-config.js +19 -6
- package/dist/src/ui/tui/tui-editor-ctx.js +12 -1
- package/dist/src/ui/tui/tui-editor-entry.js +3 -17
- package/dist/src/ui/tui/tui-editor-instances.js +9 -0
- package/dist/src/ui/tui/tui-editor-instructions.js +51 -127
- package/dist/src/ui/tui/tui-editor-interval.js +51 -113
- package/dist/src/ui/tui/tui-editor-notes.js +29 -103
- package/dist/src/ui/tui/tui-editor-pause.js +2 -1
- package/dist/src/ui/tui/tui-editor-prompt.js +2 -1
- package/dist/src/ui/tui/tui-editor-quiet.js +4 -13
- package/dist/src/ui/tui/tui-editor-release.js +77 -0
- package/dist/src/ui/tui/tui-editor-retire.js +1 -7
- package/dist/src/ui/tui/tui-editor-role-enabled.js +126 -0
- package/dist/src/ui/tui/tui-editor-role-file.js +75 -0
- package/dist/src/ui/tui/tui-editor-role-key.js +3 -7
- package/dist/src/ui/tui/tui-editor-session.js +44 -0
- package/dist/src/ui/tui/tui-editor-slots.js +1 -0
- package/dist/src/ui/tui/tui-editors.js +13 -1
- package/dist/src/ui/tui/tui-frame.js +23 -7
- package/dist/src/ui/tui/tui-history.js +12 -10
- package/dist/src/ui/tui/tui-keys.js +14 -0
- package/dist/src/ui/tui/tui-pane-activity.js +21 -8
- package/dist/src/ui/tui/tui-pane-backlog.js +36 -9
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +153 -3
- package/dist/src/ui/tui/tui-pane-history.js +308 -228
- package/dist/src/ui/tui/tui-pane-pending.js +285 -56
- package/dist/src/ui/tui/tui-pane-queued.js +7 -2
- package/dist/src/ui/tui/tui-pane-report.js +9 -3
- package/dist/src/ui/tui/tui-pane-session.js +12 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +12 -8
- package/dist/src/ui/tui/tui-pane.js +16 -10
- package/dist/src/ui/tui/tui-panes.js +247 -134
- package/dist/src/ui/tui/tui.js +8 -5
- package/dist/src/verdict/fix-claim.js +47 -1
- package/dist/src/verdict/refusal.js +2 -1
- package/dist/src/verdict/reply-contract.js +35 -0
- package/package.json +1 -1
- package/dist/src/baseline/main-culprit.js +0 -94
- package/dist/src/baseline/main-revert.js +0 -133
- package/dist/src/merge/merge-post-review.js +0 -90
- /package/dist/src/gui/{http-body.js → gui-http-body.js} +0 -0
- /package/dist/src/merge/{backlog-conflicts.js → merge-backlog-conflicts.js} +0 -0
|
@@ -1,62 +1,41 @@
|
|
|
1
1
|
/** The backend-neutral agent runner (plans/agent-backends.md \"The seam\"): everything a run
|
|
2
|
-
* needs regardless of which agent it drives — the
|
|
3
|
-
*
|
|
2
|
+
* needs regardless of which agent it drives — the watchdog wiring, abort, and stderr capture.
|
|
3
|
+
* The backend (src/agent/agent-backend.ts, with
|
|
4
4
|
* pi in src/pi/pi-backend.ts) provides the argv/stdin/env, the native-to-canonical stdout
|
|
5
5
|
* translator, the stderr warning
|
|
6
|
-
* matcher, and the exit classification. This file keeps the
|
|
7
|
-
*
|
|
8
|
-
* the
|
|
6
|
+
* matcher, and the exit classification. This file keeps the coordinator — setup, the watchdog
|
|
7
|
+
* wiring, abort, the child's stream-attachment and the settlement-state assembly — and
|
|
8
|
+
* delegates the build-and-spawn stage (the marker,
|
|
9
|
+
* parser, bin resolution, `prepare`, invocation and spawn) to src/agent/run-spawn.ts, the
|
|
10
|
+
* streaming pipeline (native-to-canonical stdout split, the stderr window, the stream-minted
|
|
11
|
+
* session record and the settle/flush) to src/agent/run-stream.ts, and the post-run settlement
|
|
12
|
+
* (spawn-error result, exit orphan sweep, close-time reconcile and
|
|
9
13
|
* classify) to src/agent/run-settle.ts. `runPi` (src/pi/pi.ts) is the thin alias the existing
|
|
10
14
|
* call sites use, so a caller and its fakes are untouched by this split. */
|
|
11
|
-
import { spawn } from "node:child_process";
|
|
12
15
|
import fs from "node:fs";
|
|
13
16
|
import os from "node:os";
|
|
14
17
|
import path from "node:path";
|
|
15
|
-
import {
|
|
16
|
-
import { ensureDir, ensureParentDir, removeTree, rotateIfLarge } from "../files/files.js";
|
|
18
|
+
import { ensureDir } from "../files/files.js";
|
|
17
19
|
import { runOnAbort, terminateChild } from "../process/process.js";
|
|
18
|
-
import { makeRunMarker } from "../process/run-marker.js";
|
|
19
|
-
import { PiStreamParser } from "../pi/pi-stream.js";
|
|
20
20
|
import { startPiWatchdogs } from "../pi/pi-watchdogs.js";
|
|
21
21
|
import { DEFAULT_BACKEND, backendById, registerAgentBackend } from "./agent-backend.js";
|
|
22
|
-
import { recordSession } from "./session-ledger.js";
|
|
23
22
|
import { errorMessage } from "../text/text.js";
|
|
24
|
-
import { warnEventBestEffort, TUMWATER_SOURCE } from "../events/events.js";
|
|
25
23
|
import { piBackend } from "../pi/pi-backend.js";
|
|
26
24
|
import { claudeBackend } from "./claude/claude-backend.js";
|
|
27
25
|
import { codexBackend } from "./codex/codex-backend.js";
|
|
28
26
|
import { opencodeBackend } from "./opencode/opencode-backend.js";
|
|
29
27
|
import { setupFailureResult, settleClose, settleError, settleExit } from "./run-settle.js";
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
* warning split across two `data` events is still seen. The warnings are short lines, so a few
|
|
36
|
-
* hundred characters always cover a straddle. */
|
|
37
|
-
const STDERR_MATCH_TAIL = 512;
|
|
38
|
-
/** The largest native stdout line the runner will buffer. A complete native event is one JSON
|
|
39
|
-
* object; a line this long is already at the default whole-log cap (`logMaxBytes`, 16 MiB), so
|
|
40
|
-
* continuing to buffer it can only be a malformed stream. Past it `feedCanonical` drops the
|
|
41
|
-
* buffered line and skips input to the next newline, so a backend that streams bytes with no
|
|
42
|
-
* newline — each byte resetting the quiet watchdog — cannot grow the buffer until the run
|
|
43
|
-
* timeout (4 h by default) exhausts the harness's memory. */
|
|
44
|
-
export const MAX_NATIVE_LINE_CHARS = 16 * 1024 * 1024;
|
|
28
|
+
import { openAppendLog, spawnPiRun } from "./run-spawn.js";
|
|
29
|
+
import { createAgentStream } from "./run-stream.js";
|
|
30
|
+
// The streaming pipeline's over-long-native-line cap stays reachable from here, the historical
|
|
31
|
+
// home the test suite still imports it from, even though the value now lives in run-stream.ts.
|
|
32
|
+
export { MAX_NATIVE_LINE_CHARS } from "./run-stream.js";
|
|
45
33
|
// The seam's adapters. Registering here — where the lookup happens — keeps the registration
|
|
46
34
|
// explicit and lets a test register a fake under its own id.
|
|
47
35
|
registerAgentBackend(piBackend);
|
|
48
36
|
registerAgentBackend(claudeBackend);
|
|
49
37
|
registerAgentBackend(opencodeBackend);
|
|
50
38
|
registerAgentBackend(codexBackend);
|
|
51
|
-
function openAppendLog(file, maxBytes) {
|
|
52
|
-
ensureParentDir(file);
|
|
53
|
-
rotateIfLarge(file, maxBytes);
|
|
54
|
-
const log = { stream: fs.createWriteStream(file, { flags: "a" }), broken: false };
|
|
55
|
-
log.stream.on("error", () => {
|
|
56
|
-
log.broken = true;
|
|
57
|
-
});
|
|
58
|
-
return log;
|
|
59
|
-
}
|
|
60
39
|
/** Run the configured agent backend non-interactively in a worktree and distill the result.
|
|
61
40
|
* Never throws: a setup failure (session dir, log file, scratch dir) resolves as a failed run
|
|
62
41
|
* whose errorMessage names it. */
|
|
@@ -95,209 +74,33 @@ export function runAgent(opts) {
|
|
|
95
74
|
resolve(setupFailureResult(backend.id, `could not prepare the agent run: ${errorMessage(err)}`));
|
|
96
75
|
return;
|
|
97
76
|
}
|
|
98
|
-
// The pre-spawn setup below may throw synchronously (a broken marker write, parser
|
|
99
|
-
// construction, backend.resolveBin). An async executor's own rejection is swallowed by the
|
|
100
|
-
// Promise constructor, so a throw here would otherwise leave the run's promise pending
|
|
101
|
-
// forever and emit an unhandled rejection — a synchronous executor used to reject directly.
|
|
102
|
-
// The try/catch below restores that rejection. A known-recoverable failure (the notes
|
|
103
|
-
// shim) warns and goes on inside its own guard, so it is deliberately not covered here.
|
|
104
|
-
let blindRead;
|
|
105
|
-
let terminateRun;
|
|
106
|
-
let parser;
|
|
107
|
-
let runMarker;
|
|
108
|
-
let resolved;
|
|
109
|
-
let binDir;
|
|
110
|
-
let runSpec;
|
|
111
|
-
try {
|
|
112
|
-
// The marker precedes this run's first event in file order — written to the same stream
|
|
113
|
-
// stdout lines flow through, before spawn, so ordering is exact by construction; on a
|
|
114
|
-
// failed spawn finish() still ends the stream and flushes it. Exactly one per run: the
|
|
115
|
-
// kind, agent and start time are always there, the label only when the caller set one
|
|
116
|
-
// (`history <role> <n> --transcript` selects a run's segments by this ts).
|
|
117
|
-
{
|
|
118
|
-
const marker = {
|
|
119
|
-
type: "tumwater_run",
|
|
120
|
-
kind: opts.kind,
|
|
121
|
-
agent: backend.id,
|
|
122
|
-
ts: Date.now(),
|
|
123
|
-
};
|
|
124
|
-
if (opts.label)
|
|
125
|
-
marker.label = opts.label;
|
|
126
|
-
rawLog.stream.write(JSON.stringify(marker) + "\n");
|
|
127
|
-
}
|
|
128
|
-
// The first blind image read an authoring run made (plans/image-input.md "Detecting a
|
|
129
|
-
// blind read"): the path, plus an indirection to the child so the parser's parse-time
|
|
130
|
-
// callback (which fires during feed, after spawn) can kill it. Unset for a gate run,
|
|
131
|
-
// which only records the read. Declared before the parser so its onBlindRead closure
|
|
132
|
-
// (which assigns them at parse time, after spawn) can reference them.
|
|
133
|
-
parser = new PiStreamParser(opts.onToolCallStart, {
|
|
134
|
-
// The seam's effective image input (src/agent/image-input.ts): a text-only seam makes
|
|
135
|
-
// every image read blind even when pi adds no note — a claim its doctor probe proved
|
|
136
|
-
// false — which the parser checks against its own note rule.
|
|
137
|
-
imageInput: seamImageInput(opts.config, readPiProviders(piModelsPath())),
|
|
138
|
-
onBlindRead: (path) => {
|
|
139
|
-
// Kill an authoring run at once, before the agent goes on working from a picture it
|
|
140
|
-
// never saw (terminateChild is what the watchdogs use). Gate runs (reviewer,
|
|
141
|
-
// resolver) only record the read and keep going.
|
|
142
|
-
if (opts.kind === "author") {
|
|
143
|
-
blindRead = path;
|
|
144
|
-
terminateRun?.();
|
|
145
|
-
}
|
|
146
|
-
},
|
|
147
|
-
});
|
|
148
|
-
// The run's cross-group attribution mark, minted before the spawn so the environment can
|
|
149
|
-
// carry it and the exit sweep can name it.
|
|
150
|
-
runMarker = makeRunMarker();
|
|
151
|
-
resolved = backend.resolveBin(opts.config);
|
|
152
|
-
// Install the notes shim under the team root's `.tumwater/bin` so every backend's run
|
|
153
|
-
// finds `tumwater-notes` first on PATH (notes-shim.ts). A failure to write it logs one
|
|
154
|
-
// warning and the run goes on without it — notes would then be unwritable by the command,
|
|
155
|
-
// but the run itself is not blocked.
|
|
156
|
-
try {
|
|
157
|
-
// Install the shim only under a real team root — the nearest ancestor holding a
|
|
158
|
-
// `.tumwater`. A session dir with no such ancestor (a lab script, a smoke run, a
|
|
159
|
-
// prompt-lab replay) must never mint one: nearestTumwaterRoot is undefined then, and
|
|
160
|
-
// the run goes on without the shim, exactly as if the install had failed — writing
|
|
161
|
-
// `<fallback>/.tumwater` from a run created a phantom harness root in a shared
|
|
162
|
-
// directory and broke every other suite on the host (BUGS.md 2026-10-18).
|
|
163
|
-
const root = nearestTumwaterRoot(opts.sessionDir);
|
|
164
|
-
if (root !== undefined)
|
|
165
|
-
binDir = ensureNotesShim(root);
|
|
166
|
-
}
|
|
167
|
-
catch {
|
|
168
|
-
opts.onAgentWarning?.("could not install the tumwater-notes shim; this run will not find tumwater-notes on PATH");
|
|
169
|
-
}
|
|
170
|
-
runSpec = {
|
|
171
|
-
cwd: opts.cwd,
|
|
172
|
-
prompt: opts.prompt,
|
|
173
|
-
config: opts.config,
|
|
174
|
-
sessionDir: opts.sessionDir,
|
|
175
|
-
sessionName: opts.sessionName,
|
|
176
|
-
continueSession: opts.continueSession,
|
|
177
|
-
kind: opts.kind,
|
|
178
|
-
agentBin: resolved.bin,
|
|
179
|
-
runMarker,
|
|
180
|
-
notesPath: opts.notesPath,
|
|
181
|
-
...(binDir !== undefined ? { binDir } : {}),
|
|
182
|
-
...(opts.maxBudgetUsd !== undefined ? { maxBudgetUsd: opts.maxBudgetUsd } : {}),
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
|
-
catch (err) {
|
|
186
|
-
// A synchronous throw in the run's pre-spawn setup (marker, parser construction,
|
|
187
|
-
// resolveBin) rejects the run, restoring the rejection a synchronous executor's throw
|
|
188
|
-
// used to get from the Promise constructor.
|
|
189
|
-
reject(err instanceof Error ? err : new Error(`runAgent: ${String(err)}`));
|
|
190
|
-
return;
|
|
191
|
-
}
|
|
192
|
-
// A backend's asynchronous pre-run work (the codex write guard resolving its trust by
|
|
193
|
-
// spawning `app-server`, plans/codex-write-guard.md "Trust (parts 4–5)"): awaited just
|
|
194
|
-
// before the synchronous invocation, so trust is settled before invocation reads it. It
|
|
195
|
-
// never throws a run away — a throw is a warm-up failure, warned about on the run's own
|
|
196
|
-
// root (skipped with no root) and the run goes on; a codex trust lookup failure already
|
|
197
|
-
// records `off` and warns from prepare itself, so this catch is the guard for a backend
|
|
198
|
-
// whose prepare throws unexpectedly. The call itself is also guarded: a synchronous
|
|
199
|
-
// `prepare` throw would otherwise escape the async executor (leaving the run's promise
|
|
200
|
-
// pending and emitting an unhandled rejection), so it is warned-and-continued like an
|
|
201
|
-
// async rejection.
|
|
202
|
-
const warnRoot = nearestTumwaterRoot(opts.sessionDir);
|
|
203
|
-
let prepared;
|
|
204
77
|
try {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
const
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
const preSpawnSessionId = invocation.sessionId ?? opts.sessionName;
|
|
232
|
-
// Record the run in the session ledger before its spawn, so a later resume can pick the
|
|
233
|
-
// newest session of its own kind instead of whatever file is newest in the dir. The id is
|
|
234
|
-
// the backend's own when it mints one ahead of the run (claude's UUID, or the resumed
|
|
235
|
-
// record's id); pi mints none and keeps the session name.
|
|
236
|
-
recordSession(opts.sessionDir, {
|
|
237
|
-
agent: backend.id,
|
|
238
|
-
id: preSpawnSessionId,
|
|
239
|
-
kind: opts.kind,
|
|
240
|
-
name: opts.sessionName,
|
|
241
|
-
cwd: opts.cwd,
|
|
242
|
-
...(opts.config.model !== undefined ? { model: opts.config.model } : {}),
|
|
243
|
-
at: Date.now(),
|
|
244
|
-
});
|
|
245
|
-
const translator = backend.translator();
|
|
246
|
-
// Decode stdout incrementally instead of per chunk: a raw Buffer.toString("utf8")
|
|
247
|
-
// replaces any multi-byte character whose bytes straddle two 'data' events with U+FFFD,
|
|
248
|
-
// corrupting that line's text (commit subjects, summaries, transcripts). StringDecoder
|
|
249
|
-
// holds back the incomplete trailing bytes until the next chunk completes them.
|
|
250
|
-
const decoder = new StringDecoder("utf8");
|
|
251
|
-
// stderr gets its own incremental decoder for the same reason: the failure message and
|
|
252
|
-
// the stderr-keyed matchers (crash, stream-severed, config) read this buffer, so a
|
|
253
|
-
// character whose bytes straddle two 'data' events must not become U+FFFD.
|
|
254
|
-
const stderrDecoder = new StringDecoder("utf8");
|
|
255
|
-
let stderr = "";
|
|
256
|
-
// The fallback-clone warning arrives at the START of a run, while `stderr` keeps only the
|
|
257
|
-
// last 64 KiB; match each chunk (with a short carried tail for a straddle) so a chatty run
|
|
258
|
-
// that pushes the warning out of that window still surfaces the mispriced id.
|
|
259
|
-
let fallbackClone;
|
|
260
|
-
let stderrTail = "";
|
|
261
|
-
let settled = false;
|
|
262
|
-
// The translator's native line buffer: feed the decoder's text through it and only complete
|
|
263
|
-
// lines reach the translator, whose canonical output feeds the parser (and the raw log).
|
|
264
|
-
// `nativeOverflow` marks a buffered line that passed MAX_NATIVE_LINE_CHARS without a newline:
|
|
265
|
-
// its remainder is skipped to the next newline so it can never be mistaken for an event.
|
|
266
|
-
let nativeTail = "";
|
|
267
|
-
let nativeOverflow = false;
|
|
268
|
-
const child = spawn(resolved.bin, invocation.args, {
|
|
269
|
-
cwd: opts.cwd,
|
|
270
|
-
// A backend that feeds the prompt on stdin (every registered one) gets a pipe the runner
|
|
271
|
-
// writes and ends below; one that returns no stdin keeps stdio[0] "ignore".
|
|
272
|
-
stdio: [invocation.stdin !== undefined ? "pipe" : "ignore", "pipe", "pipe"],
|
|
273
|
-
// pi sets its process title at startup, and so does every npm its tool calls run: on
|
|
274
|
-
// macOS each would register with LaunchServices and leak a launchservicesd port.
|
|
275
|
-
// Detached so pi leads its own process group: terminateChild signals the group, so a
|
|
276
|
-
// run's tool-call grandchildren — killed or finished — die with it instead of leaking
|
|
277
|
-
// to launchd.
|
|
278
|
-
detached: true,
|
|
279
|
-
// The run's mark, inherited by every tool call and its descendants: pi 0.87.1's bash
|
|
280
|
-
// tool spawns each command detached in its OWN group, so the group sweep below cannot
|
|
281
|
-
// reach what a tool call backgrounds (BUGS.md 2026-09-30) — the mark is the only
|
|
282
|
-
// attribution that follows a reparented orphan. Appended to any inherited mark so a
|
|
283
|
-
// nested harness under test keeps the outer run's.
|
|
284
|
-
env: { ...invocation.env, TMPDIR: runTmp },
|
|
285
|
-
});
|
|
286
|
-
if (invocation.stdin !== undefined) {
|
|
287
|
-
// A backend that dies before draining its stdin — an immediately-failing startup such as
|
|
288
|
-
// an unknown flag, an unresumable session, or a not-logged-in exit — leaves this write
|
|
289
|
-
// nothing to feed. Once the prompt exceeds the OS pipe buffer the write then fails EPIPE,
|
|
290
|
-
// and an unhandled 'error' event on the stdin stream is thrown from the event loop and
|
|
291
|
-
// takes the whole process (the whole team) down. The child's own exit status and stderr
|
|
292
|
-
// already report why the run died, so swallowing the failed write adds no silence the
|
|
293
|
-
// caller needs to break; git.ts's patchId carries the same guard for a patch-id that
|
|
294
|
-
// dies before reading its input.
|
|
295
|
-
child.stdin?.on("error", () => { });
|
|
296
|
-
child.stdin?.end(invocation.stdin);
|
|
297
|
-
}
|
|
298
|
-
// Armed now that the child exists, so the parser's parse-time onBlindRead (fires during
|
|
299
|
-
// feed, always after spawn) can reach it.
|
|
300
|
-
terminateRun = () => terminateChild(child);
|
|
78
|
+
// The whole build-and-spawn stage — the marker write, parser construction, run marker,
|
|
79
|
+
// backend-bin resolution, notes shim, run spec, the backend's optional `prepare`, the
|
|
80
|
+
// session-ledger record, translator/decoders, and the child spawn — now lives in
|
|
81
|
+
// spawnPiRun (src/agent/run-spawn.ts) and returns the SpawnedRun bundle. A synchronous
|
|
82
|
+
// throw anywhere in it (a broken marker write, parser construction, backend.resolveBin,
|
|
83
|
+
// claude's invocation rejecting a run with no model) propagates out of the await and is
|
|
84
|
+
// rejected by the catch below, matching what a synchronous executor's throw used to get
|
|
85
|
+
// from the Promise constructor.
|
|
86
|
+
// A backend without a `prepare` step (pi, claude, opencode) builds and spawns synchronously
|
|
87
|
+
// and hands the bundle straight back, so the `instanceof` below takes the sync leg and the
|
|
88
|
+
// watchdogs (a setInterval registration) start in the same synchronous step the logical-clock
|
|
89
|
+
// watchdog tests pin (they advance the fake clock immediately after runPi returns). A backend
|
|
90
|
+
// WITH a `prepare` (codex) returns a promise that resolves after its trust is settled.
|
|
91
|
+
const spawnedResult = spawnPiRun(backend, { raw: rawLog }, runTmp, opts);
|
|
92
|
+
const spawned = spawnedResult instanceof Promise ? await spawnedResult : spawnedResult;
|
|
93
|
+
// Bind the coordinator's objects from the bundle, then hand the stream-side objects (parser,
|
|
94
|
+
// translator, decoders) to createAgentStream below, which owns them with the pipeline.
|
|
95
|
+
const child = spawned.child;
|
|
96
|
+
const resolved = spawned.resolved;
|
|
97
|
+
const runSpec = spawned.runSpec;
|
|
98
|
+
const invocation = spawned.invocation;
|
|
99
|
+
const parser = spawned.parser;
|
|
100
|
+
const translator = spawned.translator;
|
|
101
|
+
const decoder = spawned.decoder;
|
|
102
|
+
const runMarker = spawned.runMarker;
|
|
103
|
+
const blindRead = spawned.blindRead;
|
|
301
104
|
// The run's two watchdog clocks (src/pi/pi-watchdogs.ts): the run deadline and the quiet
|
|
302
105
|
// watchdog (quiet-kill plus the stalled-tool-call warning). The flags their timers set
|
|
303
106
|
// (timedOut/timedOutProgressing/quietKilled/sawOutput) read live off the returned handle.
|
|
@@ -321,148 +124,30 @@ export function runAgent(opts) {
|
|
|
321
124
|
});
|
|
322
125
|
let aborted = false;
|
|
323
126
|
// A blind image read on an authoring run deliberately kills it, like the watchdogs do.
|
|
324
|
-
const wasKilled = () => aborted || wd.timedOut || wd.quietKilled || blindRead !== undefined;
|
|
127
|
+
const wasKilled = () => aborted || wd.timedOut || wd.quietKilled || blindRead() !== undefined;
|
|
325
128
|
const onAbort = () => {
|
|
326
129
|
aborted = true;
|
|
327
130
|
terminateChild(child);
|
|
328
131
|
};
|
|
329
132
|
runOnAbort(opts.signal, onAbort);
|
|
330
|
-
//
|
|
331
|
-
|
|
332
|
-
//
|
|
333
|
-
//
|
|
334
|
-
//
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
streamMintedSession = true;
|
|
346
|
-
recordSession(opts.sessionDir, {
|
|
347
|
-
agent: backend.id,
|
|
348
|
-
id: sid,
|
|
349
|
-
kind: opts.kind,
|
|
350
|
-
name: opts.sessionName,
|
|
351
|
-
cwd: opts.cwd,
|
|
352
|
-
...(opts.config.model !== undefined ? { model: opts.config.model } : {}),
|
|
353
|
-
at: Date.now(),
|
|
354
|
-
});
|
|
355
|
-
}
|
|
356
|
-
};
|
|
357
|
-
// Split native stdout into complete lines, translate each, and feed the canonical lines to
|
|
358
|
-
// the parser (which also hands them to onLine for the raw log). A trailing partial native
|
|
359
|
-
// line stays buffered exactly as the parser's own buffer once held it. On the final flush
|
|
360
|
-
// (`final`), a remaining tail is emitted as one line: a backend may close after flushing a
|
|
361
|
-
// complete JSON event but not its newline terminator, and dropping that tail loses the
|
|
362
|
-
// run's authoritative result (cost, final text). A genuinely truncated fragment parses as
|
|
363
|
-
// no data (parseJsonObject → []), so this can only recover the last event.
|
|
364
|
-
const feedCanonical = (text, final = false) => {
|
|
365
|
-
// A buffered overflow: discard this line's remainder up to (and including) its newline, so
|
|
366
|
-
// the stream resynchronizes on the next real event line instead of emitting a truncated one.
|
|
367
|
-
if (nativeOverflow) {
|
|
368
|
-
const nl = text.indexOf("\n");
|
|
369
|
-
if (nl < 0)
|
|
370
|
-
return;
|
|
371
|
-
nativeOverflow = false;
|
|
372
|
-
text = text.slice(nl + 1);
|
|
373
|
-
}
|
|
374
|
-
nativeTail += text;
|
|
375
|
-
let start = 0;
|
|
376
|
-
for (;;) {
|
|
377
|
-
const nl = nativeTail.indexOf("\n", start);
|
|
378
|
-
if (nl < 0)
|
|
379
|
-
break;
|
|
380
|
-
emitNativeLine(nativeTail.slice(start, nl));
|
|
381
|
-
start = nl + 1;
|
|
382
|
-
}
|
|
383
|
-
if (start > 0)
|
|
384
|
-
nativeTail = nativeTail.slice(start);
|
|
385
|
-
if (nativeTail.length > MAX_NATIVE_LINE_CHARS) {
|
|
386
|
-
// No newline within the cap: drop the line and warn. On the final flush there is no
|
|
387
|
-
// remainder to skip, so only the buffer is cleared.
|
|
388
|
-
nativeTail = "";
|
|
389
|
-
if (!final) {
|
|
390
|
-
nativeOverflow = true;
|
|
391
|
-
opts.onAgentWarning?.(`native output line exceeded ${MAX_NATIVE_LINE_CHARS / (1024 * 1024)} MiB without a newline and was discarded`);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
else if (final && nativeTail !== "") {
|
|
395
|
-
emitNativeLine(nativeTail);
|
|
396
|
-
nativeTail = "";
|
|
397
|
-
}
|
|
398
|
-
};
|
|
399
|
-
child.stdout?.on("data", (chunk) => {
|
|
400
|
-
wd.noteByte();
|
|
401
|
-
feedCanonical(decoder.write(chunk));
|
|
402
|
-
});
|
|
403
|
-
child.stderr?.on("data", (chunk) => {
|
|
404
|
-
// stderr is rare and meaningful (crash traces, warnings): treat it as progress.
|
|
405
|
-
wd.noteStderr();
|
|
406
|
-
const text = stderrDecoder.write(chunk);
|
|
407
|
-
fallbackClone ??= backend.matchStderrWarning?.(text, stderrTail);
|
|
408
|
-
stderrTail = (stderrTail + text).slice(-STDERR_MATCH_TAIL);
|
|
409
|
-
stderr += text;
|
|
410
|
-
if (stderr.length > 64 * 1024)
|
|
411
|
-
stderr = stderr.slice(-64 * 1024);
|
|
133
|
+
// The streaming pipeline — native-to-canonical stdout split with the over-long-line
|
|
134
|
+
// discard, the stderr window and its fallback-clone warning matcher, the stream-minted
|
|
135
|
+
// session's second ledger record, and the settle/flush that resolves the run — now lives
|
|
136
|
+
// in src/agent/run-stream.ts behind the AgentStream interface. run-agent only wires the
|
|
137
|
+
// child's `data` events to it and binds its getters into the settlement state below.
|
|
138
|
+
const stream = createAgentStream({
|
|
139
|
+
opts,
|
|
140
|
+
backend,
|
|
141
|
+
spawned,
|
|
142
|
+
wd,
|
|
143
|
+
onAbort,
|
|
144
|
+
rawLog,
|
|
145
|
+
nativeLog,
|
|
146
|
+
runTmp,
|
|
147
|
+
resolve,
|
|
412
148
|
});
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
return;
|
|
416
|
-
settled = true;
|
|
417
|
-
// A blind image read (plans/image-input.md "Detecting a blind read"): surface it to the
|
|
418
|
-
// caller once, at settle, with the action its kind earned — the authoring run was killed
|
|
419
|
-
// ("fail"), a gate run only recorded it ("logged"). The caller logs the image_blind event
|
|
420
|
-
// with its own role/run context.
|
|
421
|
-
if (parser.imageBlind) {
|
|
422
|
-
opts.onBlindRead?.(parser.imageBlind.path, opts.kind === "author" ? "fail" : "logged");
|
|
423
|
-
}
|
|
424
|
-
// Best-effort: an unremovable scratch dir (read-only subdir, a straggler still writing) must
|
|
425
|
-
// not throw out of the close handler before the run's result resolves.
|
|
426
|
-
try {
|
|
427
|
-
removeTree(runTmp);
|
|
428
|
-
}
|
|
429
|
-
catch {
|
|
430
|
-
/* left for the OS temp sweep */
|
|
431
|
-
}
|
|
432
|
-
wd.stop();
|
|
433
|
-
opts.signal?.removeEventListener("abort", onAbort);
|
|
434
|
-
// Surface the translator's per-run warnings once, after all native lines were fed.
|
|
435
|
-
for (const warning of translator.warnings?.() ?? [])
|
|
436
|
-
opts.onAgentWarning?.(warning);
|
|
437
|
-
// Resolve only once the logs have flushed: writes complete on libuv's threadpool, so
|
|
438
|
-
// resolving before 'finish' can leave the tail of <role>.pi.jsonl (or the native log)
|
|
439
|
-
// unwritten — a run that dies right after runAgent settles (an abort during a restart
|
|
440
|
-
// drain, a supervisor swap) loses its last lines. end()'s callback fires on 'finish';
|
|
441
|
-
// if a stream errors instead it never does, so settle from 'error' too — a broken log
|
|
442
|
-
// degrades to a lost log, never a stuck run.
|
|
443
|
-
let resolved = false;
|
|
444
|
-
const settle = () => {
|
|
445
|
-
if (!resolved) {
|
|
446
|
-
resolved = true;
|
|
447
|
-
resolve(result);
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
let pending = 0;
|
|
451
|
-
const flushed = () => {
|
|
452
|
-
pending -= 1;
|
|
453
|
-
if (pending === 0)
|
|
454
|
-
settle();
|
|
455
|
-
};
|
|
456
|
-
for (const log of [rawLog, nativeLog]) {
|
|
457
|
-
log.stream.on("error", settle);
|
|
458
|
-
if (!log.broken) {
|
|
459
|
-
pending += 1;
|
|
460
|
-
log.stream.end(flushed);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
if (pending === 0)
|
|
464
|
-
settle();
|
|
465
|
-
};
|
|
149
|
+
child.stdout?.on("data", (chunk) => stream.feedStdout(chunk));
|
|
150
|
+
child.stderr?.on("data", (chunk) => stream.feedStderr(chunk));
|
|
466
151
|
// The run state the settlement-half handlers (run-settle.ts) read when their event
|
|
467
152
|
// fires. The getters read through to the run's still-mutating feed state, so a handler
|
|
468
153
|
// sees the current value rather than a snapshot taken when its listener was registered.
|
|
@@ -475,15 +160,15 @@ export function runAgent(opts) {
|
|
|
475
160
|
translator,
|
|
476
161
|
decoder,
|
|
477
162
|
wd,
|
|
478
|
-
finish,
|
|
479
|
-
feedCanonical,
|
|
480
|
-
onLine,
|
|
163
|
+
finish: stream.finish,
|
|
164
|
+
feedCanonical: stream.feedCanonical,
|
|
165
|
+
onLine: stream.onLine,
|
|
481
166
|
runSpec,
|
|
482
167
|
invocation,
|
|
483
|
-
stderr:
|
|
484
|
-
fallbackClone:
|
|
485
|
-
settled:
|
|
486
|
-
blindRead: () => blindRead,
|
|
168
|
+
stderr: stream.stderr,
|
|
169
|
+
fallbackClone: stream.fallbackClone,
|
|
170
|
+
settled: stream.settled,
|
|
171
|
+
blindRead: () => blindRead(),
|
|
487
172
|
aborted: () => aborted,
|
|
488
173
|
wasKilled: () => wasKilled(),
|
|
489
174
|
kind: opts.kind,
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* same seam the runner-run → runner-outcome split used): everything that happens after the
|
|
3
3
|
* run's live feed — the spawn-`error` result, the `exit` orphan sweep, and the `close`-time
|
|
4
4
|
* flush/reconcile/classify that builds the `PiRunResult`. run-agent.ts keeps the lifecycle
|
|
5
|
-
* (
|
|
6
|
-
*
|
|
5
|
+
* (setup, watchdogs, the feed), delegates its build-and-spawn stage to src/agent/run-spawn.ts,
|
|
6
|
+
* its streaming pipeline to src/agent/run-stream.ts and its `error`/`exit`/`close` listeners
|
|
7
|
+
* here, so the streaming half stays readable.
|
|
7
8
|
*
|
|
8
9
|
* Each handler takes the run's live state (`RunSettleState`, defined in run-agent.ts and
|
|
9
10
|
* imported here only as a type) and reads what it closes over at the moment its event fires —
|