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
|
@@ -10,11 +10,13 @@
|
|
|
10
10
|
* state (the open tool calls, the turn's usage fallback, the unmapped native types seen), so
|
|
11
11
|
* call it once per run. Unmapped native types, the distinct text of `error` items, and a
|
|
12
12
|
* command abandoned at the end of its turn are recorded once each on the seam's optional
|
|
13
|
-
* `warnings()` — src/agent/run-
|
|
13
|
+
* `warnings()` — src/agent/run-stream.ts forwards each as an `agent_unknown_event` warning
|
|
14
14
|
* after the run. `sessionId()` reports the thread id once `thread.started` has named it.
|
|
15
15
|
*/
|
|
16
16
|
import { nonNegativeNumber, objectOrEmpty, parseJsonObject } from "../../files/json-object.js";
|
|
17
17
|
import { boundText, outputLimits } from "../../pi-extension/bounded-output.js";
|
|
18
|
+
import { secondsUntil } from "../../text/datetime.js";
|
|
19
|
+
import { SERVER_TRANSIENT_STATUS } from "../backend-traits.js";
|
|
18
20
|
/** How codex wraps every shell command — the prefix to strip (with its quoting) so the
|
|
19
21
|
* canonical `bash` call carries the model's own command, which suite-rerun detection matches. */
|
|
20
22
|
const SHELL_WRAPPER = /^\/(?:bin\/)?(?:zsh|bash) -lc '(.*)'$/s;
|
|
@@ -24,9 +26,6 @@ const RECONNECT = /^Reconnecting\.\.\. (\d+)\/(\d+) /;
|
|
|
24
26
|
/** The item types dropped silently, with no canonical event and no `warnings()` entry
|
|
25
27
|
* (design §5's `item.*` row): `todo_list` and `web_search`. */
|
|
26
28
|
const DROPPED_ITEM_TYPES = new Set(["todo_list", "web_search"]);
|
|
27
|
-
/** HTTP status classes that mean a transient backend failure (shared with the claude and
|
|
28
|
-
* codex classifiers' reading of `codex exec`'s JSON error). */
|
|
29
|
-
const SERVER_STATUS = new Set([500, 502, 503, 504, 529]);
|
|
30
29
|
/** Strip codex's `/bin/zsh -lc '<cmd>'` (or `bash -lc`) wrapper and its quoting, returning
|
|
31
30
|
* the model's own command — the argument suite-rerun detection matches. Passes a command the
|
|
32
31
|
* capture did not wrap through unchanged. */
|
|
@@ -76,7 +75,8 @@ function classifyTurnFailed(parsed) {
|
|
|
76
75
|
const minutes = nonNegativeNumber(parsed.rate_limit_reached_type, undefined);
|
|
77
76
|
const named = typeof parsed.rate_limit_reached_type === "string" ? parsed.rate_limit_reached_type : undefined;
|
|
78
77
|
const resetsAt = nonNegativeNumber(parsed.resets_at, undefined);
|
|
79
|
-
|
|
78
|
+
// secondsUntil folds the unix-seconds resets stamp (×1000 to epoch-ms), clamped at 0.
|
|
79
|
+
const retryAfter = resetsAt === undefined ? undefined : secondsUntil(resetsAt * 1000, Date.now());
|
|
80
80
|
kind.kind = "transientRateLimit";
|
|
81
81
|
kind.rateLimit = {
|
|
82
82
|
type: "rate_limit",
|
|
@@ -94,7 +94,7 @@ function classifyTurnFailed(parsed) {
|
|
|
94
94
|
kind.kind = "configError";
|
|
95
95
|
return kind;
|
|
96
96
|
}
|
|
97
|
-
if (status !== undefined &&
|
|
97
|
+
if (status !== undefined && SERVER_TRANSIENT_STATUS.has(status)) {
|
|
98
98
|
kind.kind = "transientBackend";
|
|
99
99
|
kind.backendKind = "server";
|
|
100
100
|
return kind;
|
|
@@ -183,187 +183,210 @@ export function codexTranslator(opts = {}) {
|
|
|
183
183
|
}
|
|
184
184
|
return out;
|
|
185
185
|
};
|
|
186
|
+
/** The `thread.started` arm: announces the session and the agent start, or records the
|
|
187
|
+
* unmapped event once when the thread id is missing. */
|
|
188
|
+
const handleThreadStarted = (event) => {
|
|
189
|
+
const id = typeof event.thread_id === "string" ? event.thread_id : undefined;
|
|
190
|
+
if (id === undefined) {
|
|
191
|
+
recordEvent("thread.started");
|
|
192
|
+
return [];
|
|
193
|
+
}
|
|
194
|
+
sessionIdValue = id;
|
|
195
|
+
const out = [];
|
|
196
|
+
out.push(JSON.stringify({
|
|
197
|
+
type: "session",
|
|
198
|
+
id,
|
|
199
|
+
...(cwd !== undefined ? { cwd } : {}),
|
|
200
|
+
}));
|
|
201
|
+
out.push(JSON.stringify({ type: "agent_start" }));
|
|
202
|
+
return out;
|
|
203
|
+
};
|
|
204
|
+
/** The `turn.completed` arm: folds the usage block and abandons the open calls. */
|
|
205
|
+
const handleTurnCompleted = (event) => {
|
|
206
|
+
sawTurnCompleted = true;
|
|
207
|
+
const usage = objectOrEmpty(event.usage);
|
|
208
|
+
turnUsage =
|
|
209
|
+
usage.input_tokens !== undefined || usage.output_tokens !== undefined
|
|
210
|
+
? {
|
|
211
|
+
input: nonNegativeNumber(usage.input_tokens, 0),
|
|
212
|
+
cacheRead: nonNegativeNumber(usage.cached_input_tokens, 0),
|
|
213
|
+
cacheWrite: nonNegativeNumber(usage.cache_write_input_tokens, 0),
|
|
214
|
+
output: nonNegativeNumber(usage.output_tokens, 0),
|
|
215
|
+
}
|
|
216
|
+
: undefined;
|
|
217
|
+
return abandonOpenCalls();
|
|
218
|
+
};
|
|
219
|
+
/** The `turn.failed` arm: classifies the inner-JSON error and closes the turn. */
|
|
220
|
+
const handleTurnFailed = (event) => {
|
|
221
|
+
sawTurnFailed = true;
|
|
222
|
+
// The error message is itself a JSON string (finding 11); fall back to the held
|
|
223
|
+
// top-level error text when it is not.
|
|
224
|
+
const raw = typeof event.error === "object" && event.error !== null && !Array.isArray(event.error)
|
|
225
|
+
? event.error.message
|
|
226
|
+
: undefined;
|
|
227
|
+
const inner = typeof raw === "string" ? parseJsonObject(raw) : undefined;
|
|
228
|
+
const cls = inner ? classifyTurnFailed(inner) : {};
|
|
229
|
+
const errorMessage = cls.errorMessage ?? (typeof raw === "string" ? raw : pendingFailureText);
|
|
230
|
+
const closes = abandonOpenCalls();
|
|
231
|
+
const message = { role: "assistant", content: [], stopReason: "error" };
|
|
232
|
+
if (errorMessage !== undefined)
|
|
233
|
+
message.errorMessage = errorMessage;
|
|
234
|
+
const end = {
|
|
235
|
+
type: "message_end",
|
|
236
|
+
message,
|
|
237
|
+
...(cls.kind !== undefined ? { tumwaterKind: cls.kind } : {}),
|
|
238
|
+
...(cls.backendKind !== undefined ? { backendKind: cls.backendKind } : {}),
|
|
239
|
+
};
|
|
240
|
+
if (cls.kind === "transientRateLimit" && typeof cls.rateLimit?.retryAfterSeconds === "number")
|
|
241
|
+
end.retryAfterSeconds = cls.rateLimit.retryAfterSeconds;
|
|
242
|
+
return [...(cls.rateLimit !== undefined ? [JSON.stringify(cls.rateLimit)] : []), ...closes, JSON.stringify(end)];
|
|
243
|
+
};
|
|
244
|
+
/** The top-level `error` arm: a reconnect text emits an `auto_retry_start`; any other
|
|
245
|
+
* message is held as the next `turn.failed`'s fallback text. */
|
|
246
|
+
const handleErrorLine = (event) => {
|
|
247
|
+
const message = typeof event.message === "string" ? event.message : undefined;
|
|
248
|
+
const reconnect = message === undefined ? undefined : RECONNECT.exec(message);
|
|
249
|
+
if (reconnect) {
|
|
250
|
+
return [
|
|
251
|
+
JSON.stringify({
|
|
252
|
+
type: "auto_retry_start",
|
|
253
|
+
attempt: Number(reconnect[1]),
|
|
254
|
+
maxAttempts: Number(reconnect[2]),
|
|
255
|
+
errorMessage: message,
|
|
256
|
+
}),
|
|
257
|
+
];
|
|
258
|
+
}
|
|
259
|
+
// Other top-level `error` lines are not fatal (design §5): held as the fallback text
|
|
260
|
+
// for a following `turn.failed`.
|
|
261
|
+
if (message !== undefined)
|
|
262
|
+
pendingFailureText = message;
|
|
263
|
+
return [];
|
|
264
|
+
};
|
|
265
|
+
/** The `item.started`/`item.completed` arm: reads the item and delegates by type to the
|
|
266
|
+
* item handlers, dropping the silent types and warning once on the unknown tail. */
|
|
267
|
+
const handleItem = (event, started) => {
|
|
268
|
+
const item = objectOrEmpty(event.item);
|
|
269
|
+
const itemType = typeof item.type === "string" ? item.type : "unknown";
|
|
270
|
+
if (itemType === "command_execution")
|
|
271
|
+
return handleCommandItem(item, started);
|
|
272
|
+
if (itemType === "file_change")
|
|
273
|
+
return handleFileChangeItem(item, started);
|
|
274
|
+
if (itemType === "error")
|
|
275
|
+
return handleErrorItem(item);
|
|
276
|
+
if (itemType === "agent_message")
|
|
277
|
+
return handleAgentMessageItem(item);
|
|
278
|
+
if (DROPPED_ITEM_TYPES.has(itemType))
|
|
279
|
+
return [];
|
|
280
|
+
// mcp_tool_call / reasoning and any unknown item shape map to nothing canonical; a
|
|
281
|
+
// once-per-type warning only (design §5).
|
|
282
|
+
recordItem(item);
|
|
283
|
+
return [];
|
|
284
|
+
};
|
|
285
|
+
/** The `command_execution` item arm: opens or closes the bash call, capping the result. */
|
|
286
|
+
const handleCommandItem = (item, started) => {
|
|
287
|
+
const command = typeof item.command === "string" ? item.command : "";
|
|
288
|
+
const out = [];
|
|
289
|
+
if (started) {
|
|
290
|
+
const id = typeof item.id === "string" ? item.id : `cmd-${openCalls.size}`;
|
|
291
|
+
openCalls.set(id, "command_execution");
|
|
292
|
+
const inner = innerCommand(command);
|
|
293
|
+
out.push(JSON.stringify({
|
|
294
|
+
type: "message_end",
|
|
295
|
+
message: {
|
|
296
|
+
role: "assistant",
|
|
297
|
+
content: [{ type: "toolCall", id, name: "bash", arguments: { command: inner } }],
|
|
298
|
+
},
|
|
299
|
+
}));
|
|
300
|
+
out.push(JSON.stringify({
|
|
301
|
+
type: "tool_execution_start",
|
|
302
|
+
toolCallId: id,
|
|
303
|
+
toolName: "bash",
|
|
304
|
+
args: { command: inner },
|
|
305
|
+
}));
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
const id = typeof item.id === "string" ? item.id : "";
|
|
309
|
+
openCalls.delete(id);
|
|
310
|
+
const aggregated = typeof item.aggregated_output === "string" ? item.aggregated_output : "";
|
|
311
|
+
const exitCode = nonNegativeNumber(item.exit_code, undefined);
|
|
312
|
+
out.push(JSON.stringify({
|
|
313
|
+
type: "tool_execution_end",
|
|
314
|
+
toolCallId: id,
|
|
315
|
+
isError: exitCode !== undefined ? exitCode !== 0 : item.status !== "completed",
|
|
316
|
+
result: boundText(aggregated, bashLimit),
|
|
317
|
+
}));
|
|
318
|
+
}
|
|
319
|
+
return out;
|
|
320
|
+
};
|
|
321
|
+
/** The `file_change` item arm: mirrors each change as an `edit` tool call. */
|
|
322
|
+
const handleFileChangeItem = (item, started) => {
|
|
323
|
+
const changes = Array.isArray(item.changes) ? item.changes.filter((c) => typeof c === "object" && c !== null) : [];
|
|
324
|
+
const id = typeof item.id === "string" ? item.id : "";
|
|
325
|
+
const out = [];
|
|
326
|
+
if (!started) {
|
|
327
|
+
const isError = typeof item.status === "string" && item.status !== "completed";
|
|
328
|
+
for (let i = 0; i < changes.length; i++) {
|
|
329
|
+
const callId = `${id}-${i}`;
|
|
330
|
+
openCalls.delete(callId);
|
|
331
|
+
out.push(JSON.stringify({ type: "tool_execution_end", toolCallId: callId, isError }));
|
|
332
|
+
}
|
|
333
|
+
return out;
|
|
334
|
+
}
|
|
335
|
+
const content = changes.map((c, i) => ({
|
|
336
|
+
type: "toolCall",
|
|
337
|
+
id: `${id}-${i}`,
|
|
338
|
+
name: "edit",
|
|
339
|
+
arguments: {
|
|
340
|
+
path: typeof c.path === "string" ? c.path : undefined,
|
|
341
|
+
kind: typeof c.kind === "string" ? c.kind : undefined,
|
|
342
|
+
},
|
|
343
|
+
}));
|
|
344
|
+
if (content.length === 0)
|
|
345
|
+
return out; // no changes to mirror, nothing else to emit
|
|
346
|
+
out.push(JSON.stringify({ type: "message_end", message: { role: "assistant", content } }));
|
|
347
|
+
for (let i = 0; i < content.length; i++) {
|
|
348
|
+
const callId = `${id}-${i}`;
|
|
349
|
+
openCalls.set(callId, "file_change");
|
|
350
|
+
out.push(JSON.stringify({
|
|
351
|
+
type: "tool_execution_start",
|
|
352
|
+
toolCallId: callId,
|
|
353
|
+
toolName: "edit",
|
|
354
|
+
args: content[i].arguments,
|
|
355
|
+
}));
|
|
356
|
+
}
|
|
357
|
+
return out;
|
|
358
|
+
};
|
|
359
|
+
/** The `item.type === "error"` arm: a non-fatal notice warned once per distinct message. */
|
|
360
|
+
const handleErrorItem = (item) => {
|
|
361
|
+
const message = typeof item.message === "string" ? item.message : undefined;
|
|
362
|
+
if (message !== undefined)
|
|
363
|
+
recordErrorItem(message);
|
|
364
|
+
return [];
|
|
365
|
+
};
|
|
366
|
+
/** The `agent_message` arm: the model's reply as one canonical `message_end`. */
|
|
367
|
+
const handleAgentMessageItem = (item) => {
|
|
368
|
+
const text = typeof item.text === "string" ? item.text : "";
|
|
369
|
+
return [
|
|
370
|
+
JSON.stringify({
|
|
371
|
+
type: "message_end",
|
|
372
|
+
message: { role: "assistant", content: [{ type: "text", text }] },
|
|
373
|
+
}),
|
|
374
|
+
];
|
|
375
|
+
};
|
|
186
376
|
const translate = (line) => {
|
|
187
377
|
const parsed = parseJsonObject(line);
|
|
188
378
|
if (!parsed)
|
|
189
379
|
return []; // Non-JSON noise on stdout; ignore, as pi's parser does.
|
|
190
380
|
const event = parsed;
|
|
191
381
|
const type = typeof event.type === "string" ? event.type : "unknown";
|
|
192
|
-
const out = [];
|
|
193
382
|
switch (type) {
|
|
194
|
-
case "thread.started":
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
sessionIdValue = id;
|
|
201
|
-
out.push(JSON.stringify({
|
|
202
|
-
type: "session",
|
|
203
|
-
id,
|
|
204
|
-
...(cwd !== undefined ? { cwd } : {}),
|
|
205
|
-
}));
|
|
206
|
-
out.push(JSON.stringify({ type: "agent_start" }));
|
|
207
|
-
return out;
|
|
208
|
-
}
|
|
209
|
-
case "turn.started":
|
|
210
|
-
return [];
|
|
211
|
-
case "turn.completed": {
|
|
212
|
-
sawTurnCompleted = true;
|
|
213
|
-
const usage = objectOrEmpty(event.usage);
|
|
214
|
-
turnUsage =
|
|
215
|
-
usage.input_tokens !== undefined || usage.output_tokens !== undefined
|
|
216
|
-
? {
|
|
217
|
-
input: nonNegativeNumber(usage.input_tokens, 0),
|
|
218
|
-
cacheRead: nonNegativeNumber(usage.cached_input_tokens, 0),
|
|
219
|
-
cacheWrite: nonNegativeNumber(usage.cache_write_input_tokens, 0),
|
|
220
|
-
output: nonNegativeNumber(usage.output_tokens, 0),
|
|
221
|
-
}
|
|
222
|
-
: undefined;
|
|
223
|
-
return abandonOpenCalls();
|
|
224
|
-
}
|
|
225
|
-
case "turn.failed": {
|
|
226
|
-
sawTurnFailed = true;
|
|
227
|
-
// The error message is itself a JSON string (finding 11); fall back to the held
|
|
228
|
-
// top-level error text when it is not.
|
|
229
|
-
const raw = typeof event.error === "object" && event.error !== null && !Array.isArray(event.error)
|
|
230
|
-
? event.error.message
|
|
231
|
-
: undefined;
|
|
232
|
-
const inner = typeof raw === "string" ? parseJsonObject(raw) : undefined;
|
|
233
|
-
const cls = inner ? classifyTurnFailed(inner) : {};
|
|
234
|
-
const errorMessage = cls.errorMessage ?? (typeof raw === "string" ? raw : pendingFailureText);
|
|
235
|
-
const closes = abandonOpenCalls();
|
|
236
|
-
const message = { role: "assistant", content: [], stopReason: "error" };
|
|
237
|
-
if (errorMessage !== undefined)
|
|
238
|
-
message.errorMessage = errorMessage;
|
|
239
|
-
const end = {
|
|
240
|
-
type: "message_end",
|
|
241
|
-
message,
|
|
242
|
-
...(cls.kind !== undefined ? { tumwaterKind: cls.kind } : {}),
|
|
243
|
-
...(cls.backendKind !== undefined ? { backendKind: cls.backendKind } : {}),
|
|
244
|
-
};
|
|
245
|
-
if (cls.kind === "transientRateLimit" && typeof cls.rateLimit?.retryAfterSeconds === "number")
|
|
246
|
-
end.retryAfterSeconds = cls.rateLimit.retryAfterSeconds;
|
|
247
|
-
return [...(cls.rateLimit !== undefined ? [JSON.stringify(cls.rateLimit)] : []), ...closes, JSON.stringify(end)];
|
|
248
|
-
}
|
|
249
|
-
case "error": {
|
|
250
|
-
const message = typeof event.message === "string" ? event.message : undefined;
|
|
251
|
-
const reconnect = message === undefined ? undefined : RECONNECT.exec(message);
|
|
252
|
-
if (reconnect) {
|
|
253
|
-
return [
|
|
254
|
-
JSON.stringify({
|
|
255
|
-
type: "auto_retry_start",
|
|
256
|
-
attempt: Number(reconnect[1]),
|
|
257
|
-
maxAttempts: Number(reconnect[2]),
|
|
258
|
-
errorMessage: message,
|
|
259
|
-
}),
|
|
260
|
-
];
|
|
261
|
-
}
|
|
262
|
-
// Other top-level `error` lines are not fatal (design §5): held as the fallback text
|
|
263
|
-
// for a following `turn.failed`.
|
|
264
|
-
if (message !== undefined)
|
|
265
|
-
pendingFailureText = message;
|
|
266
|
-
return [];
|
|
267
|
-
}
|
|
383
|
+
case "thread.started": return handleThreadStarted(event);
|
|
384
|
+
case "turn.started": return [];
|
|
385
|
+
case "turn.completed": return handleTurnCompleted(event);
|
|
386
|
+
case "turn.failed": return handleTurnFailed(event);
|
|
387
|
+
case "error": return handleErrorLine(event);
|
|
268
388
|
case "item.started":
|
|
269
|
-
case "item.completed":
|
|
270
|
-
const item = objectOrEmpty(event.item);
|
|
271
|
-
const itemType = typeof item.type === "string" ? item.type : "unknown";
|
|
272
|
-
const started = type === "item.started";
|
|
273
|
-
if (itemType === "command_execution") {
|
|
274
|
-
const command = typeof item.command === "string" ? item.command : "";
|
|
275
|
-
if (started) {
|
|
276
|
-
const id = typeof item.id === "string" ? item.id : `cmd-${openCalls.size}`;
|
|
277
|
-
openCalls.set(id, "command_execution");
|
|
278
|
-
const inner = innerCommand(command);
|
|
279
|
-
out.push(JSON.stringify({
|
|
280
|
-
type: "message_end",
|
|
281
|
-
message: {
|
|
282
|
-
role: "assistant",
|
|
283
|
-
content: [{ type: "toolCall", id, name: "bash", arguments: { command: inner } }],
|
|
284
|
-
},
|
|
285
|
-
}));
|
|
286
|
-
out.push(JSON.stringify({
|
|
287
|
-
type: "tool_execution_start",
|
|
288
|
-
toolCallId: id,
|
|
289
|
-
toolName: "bash",
|
|
290
|
-
args: { command: inner },
|
|
291
|
-
}));
|
|
292
|
-
}
|
|
293
|
-
else {
|
|
294
|
-
const id = typeof item.id === "string" ? item.id : "";
|
|
295
|
-
openCalls.delete(id);
|
|
296
|
-
const aggregated = typeof item.aggregated_output === "string" ? item.aggregated_output : "";
|
|
297
|
-
const exitCode = nonNegativeNumber(item.exit_code, undefined);
|
|
298
|
-
out.push(JSON.stringify({
|
|
299
|
-
type: "tool_execution_end",
|
|
300
|
-
toolCallId: id,
|
|
301
|
-
isError: exitCode !== undefined ? exitCode !== 0 : item.status !== "completed",
|
|
302
|
-
result: boundText(aggregated, bashLimit),
|
|
303
|
-
}));
|
|
304
|
-
}
|
|
305
|
-
return out;
|
|
306
|
-
}
|
|
307
|
-
if (itemType === "file_change") {
|
|
308
|
-
const changes = Array.isArray(item.changes) ? item.changes.filter((c) => typeof c === "object" && c !== null) : [];
|
|
309
|
-
const id = typeof item.id === "string" ? item.id : "";
|
|
310
|
-
if (!started) {
|
|
311
|
-
const isError = typeof item.status === "string" && item.status !== "completed";
|
|
312
|
-
for (let i = 0; i < changes.length; i++) {
|
|
313
|
-
const callId = `${id}-${i}`;
|
|
314
|
-
openCalls.delete(callId);
|
|
315
|
-
out.push(JSON.stringify({ type: "tool_execution_end", toolCallId: callId, isError }));
|
|
316
|
-
}
|
|
317
|
-
return out;
|
|
318
|
-
}
|
|
319
|
-
const content = changes.map((c, i) => ({
|
|
320
|
-
type: "toolCall",
|
|
321
|
-
id: `${id}-${i}`,
|
|
322
|
-
name: "edit",
|
|
323
|
-
arguments: {
|
|
324
|
-
path: typeof c.path === "string" ? c.path : undefined,
|
|
325
|
-
kind: typeof c.kind === "string" ? c.kind : undefined,
|
|
326
|
-
},
|
|
327
|
-
}));
|
|
328
|
-
if (content.length === 0)
|
|
329
|
-
return out; // no changes to mirror, nothing else to emit
|
|
330
|
-
out.push(JSON.stringify({ type: "message_end", message: { role: "assistant", content } }));
|
|
331
|
-
for (let i = 0; i < content.length; i++) {
|
|
332
|
-
const callId = `${id}-${i}`;
|
|
333
|
-
openCalls.set(callId, "file_change");
|
|
334
|
-
out.push(JSON.stringify({
|
|
335
|
-
type: "tool_execution_start",
|
|
336
|
-
toolCallId: callId,
|
|
337
|
-
toolName: "edit",
|
|
338
|
-
args: content[i].arguments,
|
|
339
|
-
}));
|
|
340
|
-
}
|
|
341
|
-
return out;
|
|
342
|
-
}
|
|
343
|
-
if (itemType === "error") {
|
|
344
|
-
// An error item is a non-fatal notice (finding 11); warned once per distinct message.
|
|
345
|
-
const message = typeof item.message === "string" ? item.message : undefined;
|
|
346
|
-
if (message !== undefined)
|
|
347
|
-
recordErrorItem(message);
|
|
348
|
-
return [];
|
|
349
|
-
}
|
|
350
|
-
if (itemType === "agent_message") {
|
|
351
|
-
// The model's reply: one canonical `message_end` with one text block.
|
|
352
|
-
const text = typeof item.text === "string" ? item.text : "";
|
|
353
|
-
return [
|
|
354
|
-
JSON.stringify({
|
|
355
|
-
type: "message_end",
|
|
356
|
-
message: { role: "assistant", content: [{ type: "text", text }] },
|
|
357
|
-
}),
|
|
358
|
-
];
|
|
359
|
-
}
|
|
360
|
-
if (DROPPED_ITEM_TYPES.has(itemType))
|
|
361
|
-
return [];
|
|
362
|
-
// mcp_tool_call / reasoning and any unknown item shape map to nothing canonical; a
|
|
363
|
-
// once-per-type warning only (design §5).
|
|
364
|
-
recordItem(item);
|
|
365
|
-
return [];
|
|
366
|
-
}
|
|
389
|
+
case "item.completed": return handleItem(event, type === "item.started");
|
|
367
390
|
default:
|
|
368
391
|
recordEvent(type);
|
|
369
392
|
return [];
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** The PreToolUse hook codex runs before each `apply_patch` in a team run (part 6/9 installs it
|
|
2
|
-
* through `-c`): `node write-guard-hook.js` with codex's hook payload on stdin. To block
|
|
2
|
+
* through `-c`): `node codex-write-guard-hook.js` with codex's hook payload on stdin. To block
|
|
3
|
+
* the patch
|
|
3
4
|
* it prints a `deny` decision with the refusal as its reason, in the shape finding 5 showed codex
|
|
4
5
|
* accepts: the model reads only the reason, so a denial by exit code would not carry it. It prints
|
|
5
6
|
* nothing to let the patch run, and exits 0 either way. Any failure of its own (bad JSON, a throw)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** codex's half of the write guard (src/agent/write-guard.ts holds the rule): a PreToolUse hook on
|
|
2
2
|
* `apply_patch`, installed by part 6/9, refuses an `apply_patch` that writes under the team root
|
|
3
|
-
* but outside the run's worktree. The entry point is write-guard-hook.ts beside this file.
|
|
3
|
+
* but outside the run's worktree. The entry point is codex-write-guard-hook.ts beside this file.
|
|
4
4
|
*
|
|
5
5
|
* Codex's hook takes no argument naming the worktree, unlike claude's: codex trusts a hook by a
|
|
6
6
|
* hash of its whole definition, so a command constant per build needs one trust lookup, while a
|
|
@@ -63,11 +63,11 @@ export function tomlString(s) {
|
|
|
63
63
|
return out + '"';
|
|
64
64
|
}
|
|
65
65
|
/** The codex write guard's definition: the PreToolUse hook on `apply_patch` that runs the
|
|
66
|
-
* compiled write-guard-hook.js with this process's node, falling back to `node` on PATH when
|
|
66
|
+
* compiled codex-write-guard-hook.js with this process's node, falling back to `node` on PATH when
|
|
67
67
|
* that binary has moved (as claude's and the notes shim do). The hook path resolves from this
|
|
68
68
|
* module, so a staged build (.tumwater/build/<sha>) runs its own copy and gets its own hash. */
|
|
69
69
|
export function codexWriteGuardDefinition(nodeBin = process.execPath) {
|
|
70
|
-
const hook = fileURLToPath(new URL("./write-guard-hook.js", import.meta.url));
|
|
70
|
+
const hook = fileURLToPath(new URL("./codex-write-guard-hook.js", import.meta.url));
|
|
71
71
|
const command = `n=${shellQuote(nodeBin)}; [ -x "$n" ] || n=node; exec "$n" ${shellQuote(hook)}`;
|
|
72
72
|
return { matcher: HOOK_MATCHER, command, timeout: HOOK_TIMEOUT_SECONDS };
|
|
73
73
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Installing the per-team `tumwater-notes` shim every backend's run finds on its PATH
|
|
2
|
-
* (plans/notes-command.md "Reaching it from a run"): run-
|
|
2
|
+
* (plans/notes-command.md "Reaching it from a run"): run-spawn calls `ensureNotesShim(root)`
|
|
3
3
|
* before building each invocation, and the backend prepends the returned dir to the run's
|
|
4
4
|
* PATH (backendBaseEnv's `binDir`, src/process/run-marker.ts). The shim is a two-line `sh`
|
|
5
5
|
* script that execs the compiled notes CLI, so a model's shell — pi's, Claude Code's or
|
|
@@ -20,7 +20,7 @@ export const NOTES_SHIM_NAME = "tumwater-notes";
|
|
|
20
20
|
/** Install the `tumwater-notes` shim under `<root>/.tumwater/bin` and return that directory.
|
|
21
21
|
* The shim is written atomically with mode 0755, only when its content differs from what is
|
|
22
22
|
* already there (a redeploy that changes `<dist>` rewrites it). Never throws: a failure to
|
|
23
|
-
* write is the caller's to absorb — run-
|
|
23
|
+
* write is the caller's to absorb — run-spawn logs one warning and runs without the shim. */
|
|
24
24
|
export function ensureNotesShim(root, execPath = process.execPath) {
|
|
25
25
|
const dir = notesShimDir(root);
|
|
26
26
|
const target = path.join(dir, NOTES_SHIM_NAME);
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/** The opencode adapter for the agent seam (plans/opencode-backend.md "The invocation",
|
|
2
2
|
* "Sessions", §6). It shapes one run's argv/stdin/env, translates opencode's `run --format
|
|
3
3
|
* json` into the canonical stream, runs the authoritative session export after the process
|
|
4
|
-
* exits, and classifies the exit's stderr — everything backend-specific. src/agent/run-
|
|
5
|
-
*
|
|
4
|
+
* exits, and classifies the exit's stderr — everything backend-specific. src/agent/run-spawn.ts
|
|
5
|
+
* spawns the run and records this run in the session ledger; opencode mints its own session id
|
|
6
6
|
* (a `ses_…` id, or the resumed record's id), which `invocation` returns so the ledger can
|
|
7
7
|
* carry it.
|
|
8
8
|
*
|
|
@@ -23,7 +23,7 @@ import path from "node:path";
|
|
|
23
23
|
import { ensureParentDir } from "../../files/files.js";
|
|
24
24
|
import { loadConfigSafe } from "../../config/config.js";
|
|
25
25
|
import { resolvedBackendConfig } from "../../config/config-views.js";
|
|
26
|
-
import {
|
|
26
|
+
import { backendRunEnv } from "../../process/run-marker.js";
|
|
27
27
|
import { resolveBinForBackend } from "../../pi/pi-bin.js";
|
|
28
28
|
import { opencodeDbPath, teamRootOf } from "../../paths.js";
|
|
29
29
|
import { resumableSession } from "../session-ledger.js";
|
|
@@ -189,7 +189,7 @@ export const opencodeBackend = {
|
|
|
189
189
|
// The notes path rides beside the run marker, as the pi and claude adapters' envs do
|
|
190
190
|
// (withNotesPath); from part 2/4 the run saves its note through the `tumwater-notes` command
|
|
191
191
|
// on PATH, so OpenCode configures no tumwater MCP server for notes.
|
|
192
|
-
const base =
|
|
192
|
+
const base = backendRunEnv(run);
|
|
193
193
|
const env = opencodeEnv(base, { dbPath, configContent });
|
|
194
194
|
const { sessionId } = resolveOpencodeSession(run, env);
|
|
195
195
|
// The thinking variant (part 6d/7): ride the catalog's effort levels for the resolved
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
import fs from "node:fs";
|
|
19
19
|
import path from "node:path";
|
|
20
20
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
21
|
-
import { readTextOrNull } from "../../files/files.js";
|
|
21
|
+
import { readTextOrNull, xdgConfigHomeDir } from "../../files/files.js";
|
|
22
22
|
import { isJsonObject } from "../../files/json-object.js";
|
|
23
23
|
/** The operator's global opencode config lives in `$XDG_CONFIG_HOME/opencode/` (or
|
|
24
24
|
* `~/.config/opencode/`), in `config.json`, `opencode.json` or `opencode.jsonc`. */
|
|
@@ -123,7 +123,7 @@ function stripJsonc(src) {
|
|
|
123
123
|
* parse; doctor reports a broken one in part 7/7. `homeDir` is the operator's home directory;
|
|
124
124
|
* the process's own `XDG_CONFIG_HOME` env, when set, wins over `~/.config` (the XDG default). */
|
|
125
125
|
export function operatorMcpNames(env, homeDir) {
|
|
126
|
-
const configHome = env
|
|
126
|
+
const configHome = xdgConfigHomeDir(env, homeDir);
|
|
127
127
|
const dir = path.join(configHome, "opencode");
|
|
128
128
|
for (const file of OPERATOR_CONFIG_FILES) {
|
|
129
129
|
const text = readTextOrNull(path.join(dir, file));
|