tumwater 0.1.2 → 0.2.0
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 +74 -108
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-args.js +24 -0
- package/dist/src/agent/agent-backend.js +2 -2
- package/dist/src/agent/backend-traits.js +60 -0
- package/dist/src/agent/claude/claude-account.js +61 -8
- package/dist/src/agent/claude/claude-args.js +12 -15
- package/dist/src/agent/claude/claude-backend.js +51 -41
- package/dist/src/agent/claude/claude-env.js +7 -3
- package/dist/src/agent/claude/claude-stream.js +7 -16
- package/dist/src/agent/image-input.js +107 -0
- package/dist/src/agent/notes-shim.js +41 -0
- package/dist/src/agent/opencode/opencode-args.js +61 -0
- package/dist/src/agent/opencode/opencode-backend.js +255 -0
- package/dist/src/agent/opencode/opencode-catalog.js +184 -0
- package/dist/src/agent/opencode/opencode-config.js +188 -0
- package/dist/src/agent/opencode/opencode-env.js +36 -0
- package/dist/src/agent/opencode/opencode-session-id.js +37 -0
- package/dist/src/agent/opencode/opencode-stream.js +278 -0
- package/dist/src/agent/role-notes-write.js +16 -32
- package/dist/src/agent/run-agent.js +95 -18
- package/dist/src/agent/session-ledger.js +57 -1
- package/dist/src/backlog/backlog-archive.js +296 -0
- package/dist/src/backlog/backlog-cli.js +573 -0
- package/dist/src/backlog/backlog-commit.js +31 -0
- package/dist/src/backlog/backlog-eligibility.js +357 -33
- package/dist/src/backlog/backlog-md.js +98 -18
- package/dist/src/backlog/backlog-render.js +62 -11
- package/dist/src/backlog/backlog-structure.js +261 -58
- package/dist/src/backlog/backlog-write.js +197 -319
- package/dist/src/backlog/backlog.js +113 -16
- package/dist/src/backlog/question-answer.js +339 -0
- package/dist/src/baseline/main-baseline.js +75 -88
- package/dist/src/baseline/main-red.js +38 -30
- package/dist/src/brief/brief-edit.js +43 -0
- package/dist/src/{brief.js → brief/brief.js} +66 -10
- package/dist/src/budget/budget.js +32 -15
- package/dist/src/budget/fallback-breaker.js +8 -7
- package/dist/src/build/build-check-detect.js +8 -7
- package/dist/src/build/build-check-events.js +68 -22
- package/dist/src/build/build-check-report.js +22 -23
- package/dist/src/build/build-check-scoped.js +51 -62
- package/dist/src/build/build-check.js +15 -14
- package/dist/src/build/build-info.js +2 -1
- package/dist/src/build/build-stage.js +23 -22
- package/dist/src/build/dep-install.js +22 -21
- package/dist/src/build/host-sleep.js +1 -1
- package/dist/src/change/change-data.js +25 -14
- package/dist/src/change/change-render.js +11 -10
- package/dist/src/cli/brief-command.js +58 -0
- package/dist/src/cli/cli-args.js +25 -5
- package/dist/src/cli/cli-command-args.js +46 -14
- package/dist/src/cli/cli-editor.js +64 -0
- package/dist/src/cli/cli-flag-specs.js +54 -16
- package/dist/src/cli/cli-marker-commands.js +13 -10
- package/dist/src/cli/cli-output.js +9 -0
- package/dist/src/cli/cli-query-commands.js +458 -49
- package/dist/src/cli/cli-repo-gate.js +1 -1
- package/dist/src/cli/cli-run.js +208 -59
- package/dist/src/cli/completion.js +207 -0
- package/dist/src/cli/config-commands.js +14 -11
- package/dist/src/cli/help.js +305 -110
- package/dist/src/cli/log-commands.js +127 -21
- package/dist/src/cli/notify-command.js +33 -18
- package/dist/src/cli/question-commands.js +29 -224
- package/dist/src/cli.js +121 -40
- package/dist/src/collections.js +66 -16
- package/dist/src/concurrency/check-permit.js +16 -17
- package/dist/src/concurrency/lock.js +6 -5
- package/dist/src/concurrency/provider-lease.js +69 -0
- package/dist/src/concurrency/semaphore.js +258 -36
- package/dist/src/config/config-aliases.js +106 -0
- package/dist/src/config/config-editable-keys.js +3 -3
- package/dist/src/config/config-fallbacks.js +379 -0
- package/dist/src/config/config-field-checks.js +19 -10
- package/dist/src/config/config-keys.js +160 -0
- package/dist/src/config/config-live.js +64 -12
- package/dist/src/config/config-schema.js +34 -22
- package/dist/src/config/config-validation.js +132 -48
- package/dist/src/config/config-views.js +119 -337
- package/dist/src/config/config-write.js +138 -177
- package/dist/src/config/config.js +60 -49
- package/dist/src/config/model-selector.js +28 -2
- package/dist/src/doctor/doctor-backlog.js +546 -49
- package/dist/src/doctor/doctor-checks.js +534 -56
- package/dist/src/doctor/doctor-holds.js +193 -53
- package/dist/src/doctor/doctor-install.js +30 -0
- package/dist/src/doctor/doctor-launch-services.js +2 -2
- package/dist/src/doctor/doctor-model-checks.js +549 -157
- package/dist/src/doctor/doctor-notify.js +27 -0
- package/dist/src/doctor/doctor-orphans.js +10 -9
- package/dist/src/doctor/doctor-render.js +36 -9
- package/dist/src/doctor/doctor.js +80 -22
- package/dist/src/doctor/image-probe.js +244 -0
- package/dist/src/doctor/probe.js +64 -0
- package/dist/src/events/event-format.js +226 -102
- package/dist/src/events/event-labels.js +116 -0
- package/dist/src/events/event-legacy.js +140 -0
- package/dist/src/events/event-read.js +58 -22
- package/dist/src/events/event-window.js +27 -7
- package/dist/src/events/events.js +18 -14
- package/dist/src/events/notify.js +137 -38
- package/dist/src/failure/failure-cluster.js +12 -9
- package/dist/src/failure/failure-data.js +96 -48
- package/dist/src/failure/failure-render.js +69 -58
- package/dist/src/failure/failure-state-change.js +33 -26
- package/dist/src/failure/time-spend.js +79 -59
- package/dist/src/files/file-queue.js +16 -4
- package/dist/src/files/files.js +18 -6
- package/dist/src/files/json-object.js +18 -3
- package/dist/src/files/tail.js +1 -1
- package/dist/src/gates/bootstrap-gates.js +5 -4
- package/dist/src/gates/budget-gates.js +250 -34
- package/dist/src/gates/disk-gate.js +18 -18
- package/dist/src/gates/gate-polls.js +117 -69
- package/dist/src/gates/maintenance-quota.js +29 -29
- package/dist/src/gates/pause-gates.js +11 -11
- package/dist/src/gates/readiness.js +2 -1
- package/dist/src/gates/role-cap-gates.js +16 -21
- package/dist/src/gates/startup-gate.js +75 -31
- package/dist/src/gates/streak-gate.js +18 -16
- package/dist/src/git/commit-message.js +12 -5
- package/dist/src/git/git-diff.js +16 -16
- package/dist/src/git/git-run.js +8 -8
- package/dist/src/git/git.js +86 -55
- package/dist/src/git/slots-state.js +14 -5
- package/dist/src/git/worktree-pool.js +25 -18
- package/dist/src/git/worktree-use.js +15 -14
- package/dist/src/git/worktree.js +18 -18
- package/dist/src/gui/gui-args.js +10 -14
- package/dist/src/gui/gui-backlog-commands.js +273 -0
- package/dist/src/gui/gui-command.js +1 -1
- package/dist/src/gui/gui-config-commands.js +440 -0
- package/dist/src/gui/gui-endpoint-commands.js +16 -669
- package/dist/src/gui/gui-endpoint-common.js +49 -0
- package/dist/src/gui/gui-endpoints.js +116 -56
- package/dist/src/gui/gui-operator-commands.js +145 -0
- package/dist/src/gui/gui-prompt-commands.js +234 -0
- package/dist/src/gui/gui-server.js +28 -8
- package/dist/src/gui/gui-team-commands.js +164 -0
- package/dist/src/gui/http-body.js +17 -1
- package/dist/src/history/history-data.js +194 -98
- package/dist/src/history/history-render.js +76 -0
- package/dist/src/history/history.js +37 -58
- package/dist/src/inbox/inbox-attachments.js +75 -14
- package/dist/src/inbox/inbox-cancel.js +7 -5
- package/dist/src/inbox/inbox-edit.js +1 -1
- package/dist/src/inbox/inbox-submit.js +44 -4
- package/dist/src/inbox/inbox-top.js +83 -0
- package/dist/src/inbox/inbox.js +8 -2
- package/dist/src/inbox/pending-prompt.js +1 -1
- package/dist/src/inbox/prompt-commands.js +170 -80
- package/dist/src/init/init-templates.js +16 -14
- package/dist/src/init/init.js +12 -11
- package/dist/src/loop/leftover.js +28 -27
- package/dist/src/loop/loop-fallback.js +2 -2
- package/dist/src/loop/loop-pi.js +106 -52
- package/dist/src/loop/loop-recovery.js +15 -15
- package/dist/src/loop/loop-state.js +139 -13
- package/dist/src/loop/loop-tick.js +194 -86
- package/dist/src/loop/loop.js +72 -36
- package/dist/src/loop/model-fallback.js +3 -2
- package/dist/src/loop/revision.js +16 -7
- package/dist/src/merge/backlog-conflicts.js +155 -0
- package/dist/src/{landing/landing-batch.js → merge/merge-batch.js} +90 -89
- package/dist/src/{landing/landing-check-failures.js → merge/merge-check-failures.js} +87 -41
- package/dist/src/{landing/landing-core.js → merge/merge-core.js} +119 -95
- package/dist/src/{landing/landing-diff.js → merge/merge-diff.js} +1 -1
- package/dist/src/merge/merge-drain.js +212 -0
- package/dist/src/{landing/landing-git.js → merge/merge-git.js} +94 -21
- package/dist/src/merge/merge-pipeline.js +99 -0
- package/dist/src/{landing/landing-questions.js → merge/merge-questions.js} +9 -6
- package/dist/src/merge/merge-queue-render.js +75 -0
- package/dist/src/{landing/landing-queue.js → merge/merge-queue.js} +95 -36
- package/dist/src/merge/merge-slot.js +340 -0
- package/dist/src/{landing/landing-stack.js → merge/merge-stack.js} +66 -62
- package/dist/src/{landing/landing-merge.js → merge/merge-to-main.js} +72 -62
- package/dist/src/merge/merge-verify.js +212 -0
- package/dist/src/notes-cli.js +97 -0
- package/dist/src/operator/operator-commands.js +154 -47
- package/dist/src/operator/operator-intent.js +87 -49
- package/dist/src/operator/operator-requests.js +52 -52
- package/dist/src/operator/retire.js +62 -27
- package/dist/src/operator/revert.js +71 -0
- package/dist/src/orchestrator/orchestrator-launch.js +74 -14
- package/dist/src/orchestrator/orchestrator-scheduling.js +129 -44
- package/dist/src/orchestrator/orchestrator.js +158 -100
- package/dist/src/orchestrator/retention.js +29 -12
- package/dist/src/orchestrator/startup-migration-steps.js +51 -0
- package/dist/src/orchestrator/startup-migrations.js +40 -0
- package/dist/src/paths.js +128 -37
- package/dist/src/pi/pi-args.js +26 -3
- package/dist/src/pi/pi-backend.js +15 -23
- package/dist/src/pi/pi-bin.js +18 -12
- package/dist/src/pi/pi-event-line.js +17 -1
- package/dist/src/pi/pi-failure.js +87 -0
- package/dist/src/pi/pi-models.js +126 -38
- package/dist/src/pi/pi-run-result.js +1 -1
- package/dist/src/pi/pi-settings.js +86 -0
- package/dist/src/pi/pi-stream.js +131 -107
- package/dist/src/pi/pi-watchdogs.js +47 -43
- package/dist/src/pi-extension/bounded-output.js +20 -5
- package/dist/src/pi-extension/context-budget.js +3 -1
- package/dist/src/pi-extension/context-shake.js +54 -21
- package/dist/src/pi-extension/context-window.js +37 -0
- package/dist/src/pi-extension/full-output.js +2 -2
- package/dist/src/pi-extension/strip-thinking.js +80 -0
- package/dist/src/pi-extension/trim-system-prompt.js +38 -0
- package/dist/src/process/launch-services.js +11 -10
- package/dist/src/process/process-table.js +1 -1
- package/dist/src/process/process.js +7 -7
- package/dist/src/process/run-marker.js +29 -6
- package/dist/src/process/supervisor.js +47 -25
- package/dist/src/prompt/dialect.js +30 -10
- package/dist/src/prompt/gate-prompts.js +170 -67
- package/dist/src/prompt/principles.js +1 -0
- package/dist/src/prompt/prompt-followup.js +35 -20
- package/dist/src/prompt/prompt.js +249 -91
- package/dist/src/redeploy/redeploy-policy.js +10 -10
- package/dist/src/redeploy/redeploy.js +6 -4
- package/dist/src/redeploy/redeployer.js +63 -55
- package/dist/src/redeploy/self-reload.js +1 -1
- package/dist/src/report/report-data.js +264 -255
- package/dist/src/report/report-fold.js +353 -0
- package/dist/src/report/report-render.js +270 -33
- package/dist/src/request-timeouts.js +1 -1
- package/dist/src/review/known-flakes.js +34 -14
- package/dist/src/review/protected-edits.js +33 -0
- package/dist/src/review/review-followup.js +16 -4
- package/dist/src/review/review-precheck.js +14 -13
- package/dist/src/review/review-verdict.js +14 -0
- package/dist/src/review/review.js +81 -39
- package/dist/src/review/secret-leaks.js +127 -0
- package/dist/src/review/suite-rerun.js +4 -3
- package/dist/src/roles/legacy-role-ids.js +21 -0
- package/dist/src/roles/legacy-role-migration.js +62 -0
- package/dist/src/roles/loop-ids.js +19 -2
- package/dist/src/roles/role-catalog.js +125 -135
- package/dist/src/roles/role-guidance.js +64 -31
- package/dist/src/roles/role-labels.js +10 -0
- package/dist/src/roles/role-render.js +194 -18
- package/dist/src/roles/role-view.js +216 -17
- package/dist/src/roles/roles.js +18 -15
- package/dist/src/scheduling/backoff.js +29 -19
- package/dist/src/scheduling/claims.js +25 -9
- package/dist/src/scheduling/once-round.js +11 -11
- package/dist/src/scheduling/quiet-hours.js +25 -32
- package/dist/src/scheduling/scheduling.js +43 -24
- package/dist/src/scheduling/{work-landed-cache.js → work-merged-cache.js} +14 -14
- package/dist/src/status/status-budget.js +115 -0
- package/dist/src/status/status-data.js +77 -184
- package/dist/src/status/status-inbox.js +47 -0
- package/dist/src/status/status-install.js +27 -0
- package/dist/src/status/status-polls.js +68 -33
- package/dist/src/status/status-roles.js +105 -0
- package/dist/src/{fleet → team}/error-storm.js +10 -10
- package/dist/src/{fleet → team}/failure-spread.js +8 -8
- package/dist/src/{fleet → team}/orchestrator-info.js +86 -18
- package/dist/src/{fleet → team}/reclaim.js +5 -4
- package/dist/src/{fleet/fleet-hold.js → team/team-hold.js} +55 -21
- package/dist/src/{fleet/fleet-polls.js → team/team-polls.js} +116 -65
- package/dist/src/{fleet/fleet-state.js → team/team-state.js} +62 -37
- package/dist/src/text/datetime.js +42 -2
- package/dist/src/text/format.js +10 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +35 -34
- package/dist/src/text/suggest.js +3 -2
- package/dist/src/text/text.js +2 -2
- package/dist/src/tick/qa-coverage.js +2 -1
- package/dist/src/tick/stage-check.js +147 -26
- package/dist/src/tick/telemetry-digest.js +10 -9
- package/dist/src/tick/tick-apply.js +101 -72
- package/dist/src/tick/tick-detail-data.js +32 -18
- package/dist/src/tick/tick-detail.js +62 -21
- package/dist/src/tick/tick-finalize.js +29 -22
- package/dist/src/tick/tick-outcome.js +2 -1
- package/dist/src/tick/tick-prompt.js +94 -26
- package/dist/src/tick/tick-resume.js +29 -13
- package/dist/src/tick/tick-stage.js +91 -47
- package/dist/src/tick/tick-timing.js +60 -59
- package/dist/src/tick/tick-usage.js +53 -23
- package/dist/src/tick/tick-verdict.js +76 -10
- package/dist/src/ui/badges.js +118 -67
- package/dist/src/ui/gui/gui-client-boot.js +209 -30
- package/dist/src/ui/gui/gui-client-composer.js +102 -23
- package/dist/src/ui/gui/gui-client-drawer.js +330 -75
- package/dist/src/ui/gui/gui-client-history.js +96 -38
- package/dist/src/ui/gui/gui-client-labels.js +24 -0
- package/dist/src/ui/gui/gui-client-loops.js +94 -52
- package/dist/src/ui/gui/gui-client-markdown.js +1 -0
- package/dist/src/ui/gui/gui-client-model.js +63 -43
- package/dist/src/ui/gui/gui-client-operator.js +129 -39
- package/dist/src/ui/gui/gui-client-pending.js +36 -32
- package/dist/src/ui/gui/gui-client-report.js +339 -46
- package/dist/src/ui/gui/gui-client-settings.js +148 -14
- package/dist/src/ui/gui/gui-client-sound.js +2 -1
- package/dist/src/ui/gui/{gui-client-fleet.js → gui-client-team.js} +196 -72
- package/dist/src/ui/gui/gui-client.js +26 -20
- package/dist/src/ui/gui/gui-page.js +19 -19
- package/dist/src/ui/gui/gui-styles.js +12 -4
- package/dist/src/ui/progress-data.js +5 -3
- package/dist/src/ui/status-model.js +184 -122
- package/dist/src/ui/status-payload.js +119 -32
- package/dist/src/ui/status-render.js +122 -52
- package/dist/src/ui/team-alerts.js +341 -0
- package/dist/src/ui/tick-progress-model.js +14 -14
- package/dist/src/ui/tone.js +11 -8
- package/dist/src/ui/transcript-tail.js +1 -0
- package/dist/src/ui/transcript.js +23 -16
- package/dist/src/ui/tui/tui-app.js +1 -0
- package/dist/src/ui/tui/tui-backlog.js +85 -11
- package/dist/src/ui/tui/tui-editor-answer.js +97 -0
- package/dist/src/ui/tui/tui-editor-attach.js +107 -0
- package/dist/src/ui/tui/tui-editor-brief.js +107 -0
- package/dist/src/ui/tui/tui-editor-budget.js +155 -0
- package/dist/src/ui/tui/tui-editor-ctx.js +1 -0
- package/dist/src/ui/tui/tui-editor-entry.js +230 -0
- package/dist/src/ui/tui/tui-editor-interval.js +122 -0
- package/dist/src/ui/tui/tui-editor-notes.js +114 -0
- package/dist/src/ui/tui/tui-editor-pause.js +266 -0
- package/dist/src/ui/tui/tui-editor-prompt.js +95 -0
- package/dist/src/ui/tui/tui-editor-queued.js +98 -0
- package/dist/src/ui/tui/tui-editor-quiet.js +106 -0
- package/dist/src/ui/tui/tui-editor-retire.js +83 -0
- package/dist/src/ui/tui/tui-editor-search.js +73 -0
- package/dist/src/ui/tui/tui-editor-session.js +97 -0
- package/dist/src/ui/tui/tui-editors.js +152 -0
- package/dist/src/ui/tui/tui-frame.js +67 -55
- package/dist/src/ui/tui/tui-history.js +12 -7
- package/dist/src/ui/tui/tui-input.js +49 -44
- package/dist/src/ui/tui/tui-keymap.js +1 -0
- package/dist/src/ui/tui/tui-keys.js +91 -1704
- package/dist/src/ui/tui/tui-pane-activity.js +99 -0
- package/dist/src/ui/tui/tui-pane-backlog.js +224 -0
- package/dist/src/ui/tui/tui-pane-brief.js +30 -0
- package/dist/src/ui/tui/tui-pane-ctx.js +11 -0
- package/dist/src/ui/tui/tui-pane-health.js +124 -0
- package/dist/src/ui/tui/tui-pane-history.js +316 -0
- package/dist/src/ui/tui/tui-pane-pending.js +74 -0
- package/dist/src/ui/tui/tui-pane-queued.js +143 -0
- package/dist/src/ui/tui/tui-pane-report.js +122 -0
- package/dist/src/ui/tui/tui-pane-session.js +101 -0
- package/dist/src/ui/tui/tui-pane-transcript.js +150 -0
- package/dist/src/ui/tui/tui-pane.js +66 -28
- package/dist/src/ui/tui/tui-panes.js +294 -0
- package/dist/src/ui/tui/tui.js +45 -25
- package/dist/src/verdict/fix-claim.js +141 -38
- package/dist/src/verdict/no-change.js +19 -0
- package/dist/src/verdict/refusal.js +92 -8
- package/dist/src/verdict/reply-contract.js +17 -5
- package/dist/src/version.js +3 -2
- package/package.json +4 -2
- package/dist/src/agent/mcp/role-notes-server.js +0 -160
- package/dist/src/landing/backlog-conflicts.js +0 -116
- package/dist/src/landing/landing-drain.js +0 -204
- package/dist/src/landing/landing-pipeline.js +0 -96
- package/dist/src/landing/landing-queue-render.js +0 -31
- package/dist/src/landing/landing-slot.js +0 -246
- package/dist/src/landing/landing-vetting.js +0 -196
- package/dist/src/pi-extension/role-notes.js +0 -40
- package/dist/src/ui/fleet-alerts.js +0 -174
|
@@ -16,32 +16,47 @@
|
|
|
16
16
|
import fs from "node:fs";
|
|
17
17
|
import os from "node:os";
|
|
18
18
|
import path from "node:path";
|
|
19
|
-
import { fileURLToPath } from "node:url";
|
|
20
19
|
import { randomUUID } from "node:crypto";
|
|
21
20
|
import { resolvedAgentConfig } from "../../config/config-views.js";
|
|
22
21
|
import { MODEL_TIERS } from "../../config/config-schema.js";
|
|
23
22
|
import { routeModel } from "../../config/model-selector.js";
|
|
24
|
-
import { agentBaseEnv } from "../../process/run-marker.js";
|
|
23
|
+
import { agentBaseEnv, withNotesPath } from "../../process/run-marker.js";
|
|
25
24
|
import { resolveBackendBin } from "../../pi/pi-bin.js";
|
|
25
|
+
import { expandHomeTilde } from "../../files/files.js";
|
|
26
26
|
import { claudeArgs } from "./claude-args.js";
|
|
27
27
|
import { claudeEnv, claudeThirdPartyProvider } from "./claude-env.js";
|
|
28
28
|
import { claudeTranslator } from "./claude-stream.js";
|
|
29
29
|
import { resumableSession } from "../session-ledger.js";
|
|
30
|
-
/** claude's per-cwd project directory name under
|
|
31
|
-
* is not a letter or digit becomes `-`. Exported with
|
|
32
|
-
* and the tests create and look for exactly the file
|
|
30
|
+
/** claude's per-cwd project directory name under the claude config dir's `projects/`:
|
|
31
|
+
* every character that is not a letter or digit becomes `-`. Exported with
|
|
32
|
+
* `claudeTranscriptPath` so the PATH fake and the tests create and look for exactly the file
|
|
33
|
+
* the resume check reads. */
|
|
33
34
|
export function claudeProjectSlug(cwd) {
|
|
34
35
|
return cwd.replace(/[^A-Za-z0-9]/g, "-");
|
|
35
36
|
}
|
|
37
|
+
/** claude's config directory: `$CLAUDE_CONFIG_DIR` when non-empty (a leading `~` expands
|
|
38
|
+
* against the home dir), else `~/.claude`. A configured team `configDir` (the expanded
|
|
39
|
+
* `backends.claude.configDir`) wins over any inherited env var — the team's own directory is
|
|
40
|
+
* never overridden by the operator's shell. This mirrors claude's own lookup and pi's
|
|
41
|
+
* `piAgentDir`; exported so doctor reads the same dir the adapter writes transcripts into. */
|
|
42
|
+
export function claudeConfigDir(env = process.env, homeDir = os.homedir(), configured) {
|
|
43
|
+
if (configured)
|
|
44
|
+
return expandHomeTilde(configured, homeDir);
|
|
45
|
+
const dir = env.CLAUDE_CONFIG_DIR;
|
|
46
|
+
if (!dir)
|
|
47
|
+
return path.join(homeDir, ".claude");
|
|
48
|
+
return expandHomeTilde(dir, homeDir);
|
|
49
|
+
}
|
|
36
50
|
/** The transcript file claude writes for a session started in `cwd`: its resume check must find
|
|
37
51
|
* a real file before passing `--resume`. claude's own layout is
|
|
38
|
-
*
|
|
39
|
-
* cannot drift.
|
|
40
|
-
|
|
41
|
-
|
|
52
|
+
* `<config>/projects/<slug>/<sessionId>.jsonl`; exported so the fake and the resume logic
|
|
53
|
+
* cannot drift. `configDir` is the configured team dir, threaded through so the resume check
|
|
54
|
+
* looks where the run's own env will write. */
|
|
55
|
+
export function claudeTranscriptPath(cwd, id, env = process.env, homeDir = os.homedir(), configDir) {
|
|
56
|
+
return path.join(claudeConfigDir(env, homeDir, configDir), "projects", claudeProjectSlug(cwd), `${id}.jsonl`);
|
|
42
57
|
}
|
|
43
58
|
/** The CLI version every gated claude model needs (plans/agent-backends.md finding 9): on
|
|
44
|
-
* 2.1.216 Opus 5.5 is refused outright and Haiku 5.5 is mispriced about 250×, and
|
|
59
|
+
* 2.1.216 Opus 5.5 is refused outright and Haiku 5.5 is mispriced about 250×, and team runs
|
|
45
60
|
* set DISABLE_AUTOUPDATER so they never update themselves. The gated models are the three
|
|
46
61
|
* tier defaults, derived from AGENT_DEFAULT_MODELS through the same routing the config uses
|
|
47
62
|
* rather than listed again, so this table cannot drift from the models a seam resolves. */
|
|
@@ -65,37 +80,27 @@ function classifyClaudeExit(info) {
|
|
|
65
80
|
}
|
|
66
81
|
return {};
|
|
67
82
|
}
|
|
68
|
-
/** The inline `--mcp-config` JSON that runs the bundled role-notes server (part 5/7): the
|
|
69
|
-
* same dist module pi's extension resolves to, spawned by the same node running this harness
|
|
70
|
-
* so it needs no PATH lookup. */
|
|
71
|
-
function roleNotesMcpConfig() {
|
|
72
|
-
const server = fileURLToPath(new URL("../mcp/role-notes-server.js", import.meta.url));
|
|
73
|
-
return JSON.stringify({ mcpServers: { tumwater: { command: process.execPath, args: [server] } } });
|
|
74
|
-
}
|
|
75
83
|
/** The ledger record this run may resume, or null: the newest record of `opts.kind` for
|
|
76
84
|
* claude, at `opts.cwd` when one is given, whose transcript file exists. Shared by
|
|
77
85
|
* `resolveClaudeSession` and the backend's `resumable` hook, so the resume guard and the
|
|
78
86
|
* invocation can never drift on what counts as resumable. */
|
|
79
|
-
function claudeResumableRecord(dir, opts) {
|
|
87
|
+
function claudeResumableRecord(dir, opts, configDir) {
|
|
80
88
|
const record = resumableSession(dir, opts);
|
|
81
89
|
if (record === null)
|
|
82
90
|
return null;
|
|
83
91
|
if (opts.cwd !== undefined && record.cwd !== opts.cwd)
|
|
84
92
|
return null;
|
|
85
|
-
if (!fs.existsSync(claudeTranscriptPath(record.cwd, record.id)))
|
|
93
|
+
if (!fs.existsSync(claudeTranscriptPath(record.cwd, record.id, process.env, os.homedir(), configDir)))
|
|
86
94
|
return null;
|
|
87
95
|
return record;
|
|
88
96
|
}
|
|
89
97
|
/** The session id this run uses: for a continue, the ledger's newest record of this kind when
|
|
90
98
|
* it is claude's, this cwd's, and its transcript file exists; otherwise a fresh UUID. Returns
|
|
91
|
-
* the id and the `--resume` id when resuming.
|
|
92
|
-
|
|
99
|
+
* the id and the `--resume` id when resuming. `configDir` is the configured team dir, so the
|
|
100
|
+
* resume check looks where the run's own env will write transcripts. */
|
|
101
|
+
function resolveClaudeSession(run, configDir) {
|
|
93
102
|
if (run.continueSession) {
|
|
94
|
-
const record = claudeResumableRecord(run.sessionDir, {
|
|
95
|
-
kind: run.kind,
|
|
96
|
-
agent: "claude",
|
|
97
|
-
cwd: run.cwd,
|
|
98
|
-
});
|
|
103
|
+
const record = claudeResumableRecord(run.sessionDir, { kind: run.kind, agent: "claude", cwd: run.cwd }, configDir);
|
|
99
104
|
if (record !== null)
|
|
100
105
|
return { sessionId: record.id, resumeSessionId: record.id };
|
|
101
106
|
}
|
|
@@ -117,35 +122,35 @@ export const claudeBackend = {
|
|
|
117
122
|
if (run.config.model === undefined) {
|
|
118
123
|
throw new Error("claude backend: no model resolved for this run");
|
|
119
124
|
}
|
|
120
|
-
|
|
125
|
+
// The configured team config dir (plans/notes-command.md "Keeping funes out of team runs"):
|
|
126
|
+
// the run's env, its resume lookup, and the account seed all follow it; unset, nothing changes.
|
|
127
|
+
const claudeCfg = resolvedAgentConfig(run.config, "claude");
|
|
128
|
+
const configDir = claudeCfg.configDir;
|
|
129
|
+
const expandedConfigDir = configDir !== undefined ? claudeConfigDir(process.env, os.homedir(), configDir) : undefined;
|
|
130
|
+
const { sessionId, resumeSessionId } = resolveClaudeSession(run, configDir);
|
|
121
131
|
const { args, stdin } = claudeArgs({
|
|
122
132
|
cwd: run.cwd,
|
|
123
133
|
model: run.config.model,
|
|
124
134
|
thinking: run.config.thinking,
|
|
125
135
|
...(resumeSessionId !== undefined ? { resumeSessionId } : { sessionId }),
|
|
126
|
-
// The operator's `
|
|
136
|
+
// The operator's `backends.claude.args` passthrough, appended after the harness flags (the
|
|
127
137
|
// builder rejects any entry that repeats a harness-managed flag). resolvedAgentConfig
|
|
128
138
|
// owns the view so a caller cannot mutate the loaded config through the returned array.
|
|
129
|
-
args:
|
|
139
|
+
args: claudeCfg.args,
|
|
130
140
|
prompt: run.prompt,
|
|
131
141
|
// The caller computes the remaining daily budget for an API-billed claude run and the
|
|
132
142
|
// adapter forwards it; a subscription run or the director passes none (part 7a/7).
|
|
133
143
|
...(run.maxBudgetUsd !== undefined ? { maxBudgetUsd: run.maxBudgetUsd } : {}),
|
|
134
|
-
// claude does not load the bundled pi extensions, so a run carrying a notebook gets the
|
|
135
|
-
// role_notes MCP server instead. Passed on EVERY claude invocation that carries a
|
|
136
|
-
// notebook — fresh or resumed, a transient retry, a SUMMARY follow-up — because claude
|
|
137
|
-
// keeps no flags across --resume, so a resume without this would lose the tool.
|
|
138
|
-
...(run.notesPath !== undefined ? { mcpConfig: roleNotesMcpConfig() } : {}),
|
|
139
144
|
});
|
|
140
145
|
// agentBaseEnv applies the run marker and the macOS preload (shared with the pi adapter);
|
|
141
146
|
// claudeEnv layers the backend's own scrub-and-set on top. The notebook path rides beside
|
|
142
|
-
// the marker the same way pi's adapter carries it (
|
|
143
|
-
//
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
: { ...agentBaseEnv(run.runMarker), TUMWATER_NOTES_PATH: run.notesPath };
|
|
147
|
+
// the marker the same way pi's adapter carries it (withNotesPath); from part 2/4 the run
|
|
148
|
+
// saves its note through the `tumwater-notes` command on PATH, so no MCP server reaches the
|
|
149
|
+
// run and `--strict-mcp-config` (always in claude-args.ts) keeps it that way.
|
|
150
|
+
const base = withNotesPath(agentBaseEnv(run.runMarker, undefined, run.binDir), run.notesPath);
|
|
147
151
|
const env = claudeEnv(base, {
|
|
148
152
|
quietTimeoutSeconds: run.config.quietTimeoutSeconds,
|
|
153
|
+
...(expandedConfigDir !== undefined ? { configDir: expandedConfigDir } : {}),
|
|
149
154
|
});
|
|
150
155
|
return { args, stdin, env, sessionId };
|
|
151
156
|
},
|
|
@@ -155,8 +160,13 @@ export const claudeBackend = {
|
|
|
155
160
|
// seed the account file, so both must classify it the same way.
|
|
156
161
|
return claudeTranslator({ thirdPartyProvider: claudeThirdPartyProvider(process.env) });
|
|
157
162
|
},
|
|
158
|
-
resumable(dir, opts) {
|
|
159
|
-
return claudeResumableRecord(dir, opts) !== null;
|
|
163
|
+
resumable(dir, opts, configDir) {
|
|
164
|
+
return claudeResumableRecord(dir, opts, configDir) !== null;
|
|
165
|
+
},
|
|
166
|
+
// Every Claude model reads images (Claude Code's Read tool returns them as image blocks), so
|
|
167
|
+
// a claude seam is always capable — the doctor probes nothing and never works around it.
|
|
168
|
+
imageInput() {
|
|
169
|
+
return true;
|
|
160
170
|
},
|
|
161
171
|
classifyExit: classifyClaudeExit,
|
|
162
172
|
};
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
/** Building the environment for one claude run (plans/agent-backends.md "The claude
|
|
2
2
|
* invocation", "Findings" 2 and 4) — pure scrub-and-set over a base environment, with no
|
|
3
3
|
* subprocess. The caller layers the run-marker, the macOS LaunchServices preload, and the
|
|
4
|
-
* notebook on first (src/
|
|
5
|
-
* same), then hands the result here.
|
|
4
|
+
* notebook on first (src/process/run-marker.ts's `withNotesPath`), then hands the result here.
|
|
6
5
|
*
|
|
7
6
|
* Inherited Claude Code session variables break auth: spawned from inside a Claude Code
|
|
8
7
|
* session, a child that keeps `CLAUDECODE`, `CLAUDE_CODE_*`, `CLAUDE_AGENT_SDK_*` or the host's
|
|
@@ -13,7 +12,7 @@
|
|
|
13
12
|
*
|
|
14
13
|
* The set half disables auto-memory, backgrounded Bash tasks (finding 4: claude refuses a long
|
|
15
14
|
* foreground command, backgrounds it, and abandons it when `-p` ends, which would report work
|
|
16
|
-
* never verified), the auto-updater (a mid-
|
|
15
|
+
* never verified), the auto-updater (a mid-team self-update must not swap the binary under a
|
|
17
16
|
* run), telemetry, and error reporting; and raises the Bash timeouts so a slow suite runs in
|
|
18
17
|
* the foreground. `BASH_MAX_TIMEOUT_MS` is derived from `quietTimeoutSeconds` so claude's own
|
|
19
18
|
* Bash timeout fires before tumwater's quiet kill and the model sees a tool error rather than a
|
|
@@ -80,5 +79,10 @@ export function claudeEnv(base, opts) {
|
|
|
80
79
|
env.DISABLE_AUTOUPDATER = "1";
|
|
81
80
|
env.DISABLE_TELEMETRY = "1";
|
|
82
81
|
env.DISABLE_ERROR_REPORTING = "1";
|
|
82
|
+
// A team config dir (plans/notes-command.md "Keeping funes out of team runs"): force claude's
|
|
83
|
+
// own config and transcripts under it, so a funes hook on ~/.claude/projects never indexes
|
|
84
|
+
// team runs. Unset leaves any inherited CLAUDE_CONFIG_DIR (or claude's default) untouched.
|
|
85
|
+
if (opts.configDir)
|
|
86
|
+
env.CLAUDE_CONFIG_DIR = opts.configDir;
|
|
83
87
|
return env;
|
|
84
88
|
}
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* on the seam's optional `warnings()` — src/agent/run-agent.ts forwards each as an
|
|
15
15
|
* `agent_unknown_event` warning after the run. `cost_unknown` is recorded the same way when a
|
|
16
16
|
* run produced no authoritative `run_cost`. */
|
|
17
|
-
import { isJsonObject, objectOrEmpty, nonNegativeNumber, parseJsonObject } from "../../files/json-object.js";
|
|
17
|
+
import { canonicalUsageBlock, isJsonObject, objectOrEmpty, nonNegativeNumber, parseJsonObject } from "../../files/json-object.js";
|
|
18
18
|
/** claude's tool names → the canonical pi names the readers already know. Glob/Grep are kept
|
|
19
19
|
* lower-cased; MultiEdit collapses onto edit (pi has one edit tool). Anything else is
|
|
20
20
|
* lower-cased so a future tool still reads as a name rather than as `undefined`. */
|
|
@@ -37,20 +37,10 @@ function str(value) {
|
|
|
37
37
|
}
|
|
38
38
|
/** claude's usage block → pi's canonical usage block, with `cost.total` 0 because the run's
|
|
39
39
|
* authoritative cost rides the final `run_cost` event (a split-block message repeats its usage
|
|
40
|
-
* on every event, so summing per event would double-count — the fold takes it once).
|
|
40
|
+
* on every event, so summing per event would double-count — the fold takes it once). The
|
|
41
|
+
* wire-field reads stay here; the block's assembly is the shared canonicalUsageBlock. */
|
|
41
42
|
function canonicalUsage(usage) {
|
|
42
|
-
|
|
43
|
-
const cacheRead = nonNegativeNumber(usage?.cache_read_input_tokens, 0);
|
|
44
|
-
const cacheWrite = nonNegativeNumber(usage?.cache_creation_input_tokens, 0);
|
|
45
|
-
const output = nonNegativeNumber(usage?.output_tokens, 0);
|
|
46
|
-
return {
|
|
47
|
-
input,
|
|
48
|
-
cacheRead,
|
|
49
|
-
cacheWrite,
|
|
50
|
-
output,
|
|
51
|
-
totalTokens: input + cacheRead + cacheWrite + output,
|
|
52
|
-
cost: { total: 0 },
|
|
53
|
-
};
|
|
43
|
+
return canonicalUsageBlock(nonNegativeNumber(usage?.input_tokens, 0), nonNegativeNumber(usage?.cache_read_input_tokens, 0), nonNegativeNumber(usage?.cache_creation_input_tokens, 0), nonNegativeNumber(usage?.output_tokens, 0), 0);
|
|
54
44
|
}
|
|
55
45
|
/** One claude content block → one pi content block, or undefined for a block pi has no
|
|
56
46
|
* vocabulary for (dropped, like any other unmapped native shape). `tool_use` becomes pi's
|
|
@@ -76,7 +66,7 @@ function canonicalBlock(block) {
|
|
|
76
66
|
return {
|
|
77
67
|
type: "toolCall",
|
|
78
68
|
id: str(block.id) ?? "",
|
|
79
|
-
name: Object.hasOwn(TOOL_NAMES, name) ? TOOL_NAMES[name] : name.toLowerCase(),
|
|
69
|
+
name: Object.hasOwn(TOOL_NAMES, name) ? (TOOL_NAMES[name]) : name.toLowerCase(),
|
|
80
70
|
arguments: args,
|
|
81
71
|
};
|
|
82
72
|
}
|
|
@@ -347,7 +337,8 @@ export function claudeTranslator(opts = {}) {
|
|
|
347
337
|
return out;
|
|
348
338
|
}
|
|
349
339
|
case "tool_progress":
|
|
350
|
-
// A 30 s heartbeat of a running tool call: known, and the open call already tracks
|
|
340
|
+
// A 30 s heartbeat of a running tool call: known, and the open call already tracks
|
|
341
|
+
// liveness.
|
|
351
342
|
return [];
|
|
352
343
|
default:
|
|
353
344
|
record(type ?? "unknown");
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** The seam's answer to "who in this team can read images" (plans/image-input.md
|
|
2
|
+
* "Capability"). One resolver, `seamImageInput`, is the only place that rule lives; the doctor
|
|
3
|
+
* (src/doctor/doctor-model-checks.ts) and the later reroute parts both read it, so the answer
|
|
4
|
+
* is never reached two ways. It lives at runtime level because it needs the backend registry:
|
|
5
|
+
* a claude seam is always capable through the AgentBackend.imageInput hook, a pi seam reads the
|
|
6
|
+
* model's `input` in models.json, and any other backend answers null unless it declares a hook. */
|
|
7
|
+
import { configForRole, roleSeamTier, withModelOverrides } from "../config/config-views.js";
|
|
8
|
+
import { BORROW_ORDER, resolveTierFallbacks } from "../config/config-fallbacks.js";
|
|
9
|
+
import { seamAgent } from "../config/model-selector.js";
|
|
10
|
+
import { modelLabel } from "./backend-traits.js";
|
|
11
|
+
import { piModelImageInput, piPairFree, piModelsPath, readPiProviders } from "../pi/pi-models.js";
|
|
12
|
+
import { agentBackend } from "./agent-backend.js";
|
|
13
|
+
import { imageProbeEntryFresh, imageProbeKey } from "../doctor/image-probe.js";
|
|
14
|
+
/** One seam's image-input capability (plans/image-input.md "Capability"): `true` when the
|
|
15
|
+
* seam's backend declares image support, `false` when it is text-only, `null` when it cannot
|
|
16
|
+
* be answered from what the team knows (an unknown pair, a backend with no hook and no
|
|
17
|
+
* catalog entry — the doctor's `tier models` check already fails such pairs).
|
|
18
|
+
*
|
|
19
|
+
* The backend bookends: a backend with an `imageInput` hook (claude → true) owns the answer;
|
|
20
|
+
* the default pi backend leaves it to models.json's `input` field, where absent means text-only
|
|
21
|
+
* (pi's own default for a custom model). `probes` is the doctor's live-probe cache (part 2/5): a
|
|
22
|
+
* claimed pi seam whose probe FAILED reads false — a claim is only as good as the verified
|
|
23
|
+
* answer — while a cached pass (or an unprobed claim) stands on the catalog. claude seams are
|
|
24
|
+
* never probed, so the hook answer is never overridden. */
|
|
25
|
+
export function seamImageInput(seam, providers, probes) {
|
|
26
|
+
const agent = seamAgent(seam);
|
|
27
|
+
const backend = agentBackend(agent);
|
|
28
|
+
// A backend with an imageInput hook owns the answer outright (claude: true for every model).
|
|
29
|
+
if (backend?.imageInput !== undefined)
|
|
30
|
+
return backend.imageInput(seam.model);
|
|
31
|
+
// An unknown backend without a hook has no catalog to consult — unknown, never assume.
|
|
32
|
+
if (agent !== "pi")
|
|
33
|
+
return null;
|
|
34
|
+
const base = piModelImageInput(providers, seam.provider, seam.model);
|
|
35
|
+
if (base !== true)
|
|
36
|
+
return base;
|
|
37
|
+
// A claimed pi seam whose live probe failed (fresh, claim unchanged) reads false. Only pi is
|
|
38
|
+
// probed, so the probe key carries the pi agent.
|
|
39
|
+
const entry = probes?.[imageProbeKey(agent, seam.provider, seam.model)];
|
|
40
|
+
if (entry?.verdict === "fail" && imageProbeEntryFresh(entry))
|
|
41
|
+
return false;
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
/** The image-capable reroute target for `role`, or null when no peer tier can read images
|
|
45
|
+
* (plans/image-input.md "Reroute target"). Walks the role's seam tier through BORROW_ORDER,
|
|
46
|
+
* skipping the tier itself — small → default → strong, default → strong → small, strong →
|
|
47
|
+
* default (a strong-tier seam never drops to small, the same rule as the budget fallback) —
|
|
48
|
+
* and takes the first tier whose model reads images. `regime` picks the pair set (see
|
|
49
|
+
* RerouteRegime); `modelsPath` and `account` are injectable so tests need no real pi install
|
|
50
|
+
* or account. */
|
|
51
|
+
export function imageReroute(config, role, regime = "primary", modelsPath = piModelsPath(), account = null, probes, providers = null) {
|
|
52
|
+
const tier = roleSeamTier(config, role);
|
|
53
|
+
const candidates = BORROW_ORDER[tier].filter((t) => t !== tier);
|
|
54
|
+
if (candidates.length === 0)
|
|
55
|
+
return null;
|
|
56
|
+
const providerMap = providers ?? readPiProviders(modelsPath);
|
|
57
|
+
const capable = (rc) => seamImageInput(rc, providerMap, probes) === true;
|
|
58
|
+
if (regime === "primary") {
|
|
59
|
+
for (const t of candidates) {
|
|
60
|
+
const rc = withModelOverrides(config, t, {});
|
|
61
|
+
if (capable(rc))
|
|
62
|
+
return { tier: t, config: rc };
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
// Fallback regime: only the tiers' free fallback pairs qualify (resolveTierFallbacks over the
|
|
67
|
+
// price check), never a priced primary model — a reroute must not spend past the cap.
|
|
68
|
+
const resolved = resolveTierFallbacks(config, (p) => piPairFree(providerMap, p.provider, p.model, account));
|
|
69
|
+
for (const t of candidates) {
|
|
70
|
+
const pair = resolved[t].pair;
|
|
71
|
+
if (!pair)
|
|
72
|
+
continue;
|
|
73
|
+
const rc = withModelOverrides(config, t, {
|
|
74
|
+
provider: pair.provider,
|
|
75
|
+
model: pair.model,
|
|
76
|
+
thinking: pair.thinking,
|
|
77
|
+
});
|
|
78
|
+
if (capable(rc))
|
|
79
|
+
return { tier: t, config: rc };
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
/** The config of imageReroute, for callers that want only the target model. */
|
|
84
|
+
export function imageRerouteTarget(config, role, regime = "primary", modelsPath = piModelsPath(), account = null, probes, providers = null) {
|
|
85
|
+
return imageReroute(config, role, regime, modelsPath, account, probes, providers)?.config ?? null;
|
|
86
|
+
}
|
|
87
|
+
/** A seam's model as one readable token, the same label the doctor's `image input` check uses
|
|
88
|
+
* (`claude/<id>` for a claude seam, `provider/model` for pi). The one home of the label, so
|
|
89
|
+
* the surface strings (the images alert, the role page, the status rows) and the doctor report
|
|
90
|
+
* cannot drift, and delegates to backend-traits' modelLabel. */
|
|
91
|
+
export function seamImageLabel(agent, provider, model) {
|
|
92
|
+
return modelLabel(agent, provider, model);
|
|
93
|
+
}
|
|
94
|
+
/** Compute RoleImageInfo for one loop. `modelsPath` and `probes` are injectable; the providers
|
|
95
|
+
* catalog is read (stat-cached) here and also handed to imageReroute so a poll that asks many
|
|
96
|
+
* loops still parses the catalog once. All reads mirror the doctor's `image input` check, so
|
|
97
|
+
* the surface and the check agree about capability. */
|
|
98
|
+
export function roleImageInfo(config, role, modelsPath = piModelsPath(), probes) {
|
|
99
|
+
const providers = readPiProviders(modelsPath);
|
|
100
|
+
const rc = configForRole(config, role);
|
|
101
|
+
const imageInput = seamImageInput(rc, providers, probes);
|
|
102
|
+
const rr = imageReroute(config, role, "primary", modelsPath, null, probes, providers);
|
|
103
|
+
const imageRerouteStr = rr
|
|
104
|
+
? `${rr.tier} (${seamImageLabel(rr.config.agent, rr.config.provider, rr.config.model)})`
|
|
105
|
+
: null;
|
|
106
|
+
return { imageInput, imageReroute: imageRerouteStr };
|
|
107
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
/** Installing the per-team `tumwater-notes` shim every backend's run finds on its PATH
|
|
3
|
+
* (plans/notes-command.md "Reaching it from a run"): run-agent calls `ensureNotesShim(root)`
|
|
4
|
+
* before building each invocation, and the backend prepends the returned dir to the run's
|
|
5
|
+
* PATH (agentBaseEnv's `binDir`, src/process/run-marker.ts). The shim is a two-line `sh`
|
|
6
|
+
* script that execs the compiled notes CLI, so a model's shell — pi's, Claude Code's or
|
|
7
|
+
* OpenCode's — runs the same command on every backend.
|
|
8
|
+
*
|
|
9
|
+
* The `<dist>` in the exec path is the shared distDir (src/build/build-info.ts), derived from
|
|
10
|
+
* this module's own location so a staged build (.tumwater/build/<sha>) points its shim at its
|
|
11
|
+
* own copy rather than the installed package's. The script is written
|
|
12
|
+
* atomically and made executable, only when its content differs, so a redeploy re-points it
|
|
13
|
+
* without churning the file. */
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { distDir } from "../build/build-info.js";
|
|
17
|
+
import { writeTextAtomic } from "../files/files.js";
|
|
18
|
+
import { notesShimDir } from "../paths.js";
|
|
19
|
+
/** The shim's file name — the command name every run sees on its PATH. */
|
|
20
|
+
export const NOTES_SHIM_NAME = "tumwater-notes";
|
|
21
|
+
/** Install the `tumwater-notes` shim under `<root>/.tumwater/bin` and return that directory.
|
|
22
|
+
* The shim is written atomically with mode 0755, only when its content differs from what is
|
|
23
|
+
* already there (a redeploy that changes `<dist>` rewrites it). Never throws: a failure to
|
|
24
|
+
* write is the caller's to absorb — run-agent logs one warning and runs without the shim. */
|
|
25
|
+
export function ensureNotesShim(root) {
|
|
26
|
+
const dir = notesShimDir(root);
|
|
27
|
+
const target = path.join(dir, NOTES_SHIM_NAME);
|
|
28
|
+
const content = `#!/bin/sh\nexec "${process.execPath}" "${path.join(distDir(), "src", "notes-cli.js")}" "$@"\n`;
|
|
29
|
+
let needsWrite = true;
|
|
30
|
+
try {
|
|
31
|
+
needsWrite = fs.readFileSync(target, "utf8") !== content;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Absent or unreadable — write it.
|
|
35
|
+
}
|
|
36
|
+
if (needsWrite) {
|
|
37
|
+
writeTextAtomic(target, content);
|
|
38
|
+
}
|
|
39
|
+
fs.chmodSync(target, 0o755);
|
|
40
|
+
return dir;
|
|
41
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// vocabulary: plain
|
|
2
|
+
/** Building one OpenCode run's command line (plans/opencode-backend.md "The invocation") —
|
|
3
|
+
* pure argv construction plus the prompt-on-stdin pairing, with no subprocess, backend
|
|
4
|
+
* registration, or session database. Part 5/7 wires this into the adapter; this module turns
|
|
5
|
+
* its inputs (the resolved model, the catalog's variant, the session choice, the session
|
|
6
|
+
* name, and the configured passthrough args) into the invocation's argv and stdin.
|
|
7
|
+
*
|
|
8
|
+
* The environment is built separately (opencode-env.ts) so each concern has one module.
|
|
9
|
+
*
|
|
10
|
+
* `.args` passthrough: a passthrough arg equal to a flag the harness itself sets would be
|
|
11
|
+
* appended AFTER the harness's own copy and OpenCode's parser is last-wins, so it would
|
|
12
|
+
* silently override the resolved model, the session choice, or the isolation flags. Each is
|
|
13
|
+
* rejected by opencodeArgs with the offending arg named, matching the same rejection in
|
|
14
|
+
* claudeArgs and the reserved set from plans/opencode-backend.md "Config and routing". */
|
|
15
|
+
/** Flags the harness invocation sets. A passthrough arg equal to one of these is rejected by
|
|
16
|
+
* opencodeArgs with the offending arg named, so an operator's `backends.opencode.args` can
|
|
17
|
+
* never silently override the resolved model, the session choice, or the isolation flags. This
|
|
18
|
+
* is the reserved set from plans/opencode-backend.md "Config and routing"; part 3 adds
|
|
19
|
+
* `--title`, which tumwater now sets (finding 6). */
|
|
20
|
+
import { appendPassthroughArgs } from "../agent-args.js";
|
|
21
|
+
export const HARNESS_OPENCODE_FLAGS = new Set([
|
|
22
|
+
"run",
|
|
23
|
+
"--format",
|
|
24
|
+
"--model",
|
|
25
|
+
"-m",
|
|
26
|
+
"--session",
|
|
27
|
+
"-s",
|
|
28
|
+
"--continue",
|
|
29
|
+
"-c",
|
|
30
|
+
"--fork",
|
|
31
|
+
"--standalone",
|
|
32
|
+
"--server",
|
|
33
|
+
"--attach",
|
|
34
|
+
"--auto",
|
|
35
|
+
"--yolo",
|
|
36
|
+
"--dangerously-skip-permissions",
|
|
37
|
+
"--agent",
|
|
38
|
+
"--thinking",
|
|
39
|
+
"--dir",
|
|
40
|
+
"--title",
|
|
41
|
+
]);
|
|
42
|
+
/** Build one run's opencode argv and stdin. The prompt is never an argv element (OpenCode
|
|
43
|
+
* reads a non-TTY stdin to EOF, and the runner closes it). A model with no `/` names a model
|
|
44
|
+
* on OpenCode's own gateway and gets the `opencode/` provider; anything with a slash is the
|
|
45
|
+
* provider-qualified id and passes as is. Throws on a passthrough arg that repeats a harness
|
|
46
|
+
* flag, since OpenCode's last-wins parser would otherwise silently override what the harness
|
|
47
|
+
* asked for. */
|
|
48
|
+
/** The opencode-namespaced model id: a bare id (no `/`) names a model on OpenCode's own
|
|
49
|
+
* gateway and gets the `opencode/` provider; an id bearing a slash is provider-qualified and
|
|
50
|
+
* passes as-is. The single home of this routing rule — shared by the argv builder
|
|
51
|
+
* (opencodeArgs, here) and the catalog's cache key (opencode-catalog.ts's opencodeModelKey) —
|
|
52
|
+
* so the id a run is invoked with always matches the id a run is priced by. */
|
|
53
|
+
export function opencodeQualifiedModel(model) {
|
|
54
|
+
return model.includes("/") ? model : `opencode/${model}`;
|
|
55
|
+
}
|
|
56
|
+
export function opencodeArgs(opts) {
|
|
57
|
+
const model = opencodeQualifiedModel(opts.model) + (opts.variant !== undefined ? `#${opts.variant}` : "");
|
|
58
|
+
const args = ["run", "--standalone", "--format", "json", "--auto", "--model", model, "--session", opts.sessionId, "--title", opts.title];
|
|
59
|
+
appendPassthroughArgs(args, opts.args, HARNESS_OPENCODE_FLAGS, "opencodeArgs");
|
|
60
|
+
return { args, stdin: opts.prompt };
|
|
61
|
+
}
|