tumwater 0.4.1 → 0.4.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -5
- package/dist/build-info.json +2 -2
- package/dist/src/agent/agent-backend.js +6 -6
- package/dist/src/agent/{run-agent.js → agent-run.js} +35 -13
- package/dist/src/agent/{run-settle.js → agent-settle.js} +70 -37
- package/dist/src/agent/{run-spawn.js → agent-spawn.js} +24 -24
- package/dist/src/agent/{run-stream.js → agent-stream.js} +6 -14
- package/dist/src/agent/backend-traits.js +16 -2
- package/dist/src/agent/claude/claude-account.js +1 -8
- package/dist/src/agent/claude/claude-backend.js +3 -3
- package/dist/src/agent/claude/claude-env.js +27 -31
- package/dist/src/agent/claude/claude-stream.js +131 -78
- package/dist/src/agent/codex/codex-account.js +9 -0
- package/dist/src/agent/codex/codex-backend.js +2 -2
- package/dist/src/agent/codex/codex-env.js +28 -15
- package/dist/src/agent/codex/codex-hook-trust.js +39 -20
- package/dist/src/agent/codex/codex-rollout.js +120 -69
- package/dist/src/agent/codex/codex-stream.js +72 -39
- package/dist/src/agent/env-scrub.js +18 -0
- package/dist/src/agent/image-input.js +21 -33
- package/dist/src/agent/notes-shim.js +2 -2
- package/dist/src/agent/opencode/opencode-backend.js +2 -2
- package/dist/src/agent/opencode/opencode-catalog.js +52 -29
- package/dist/src/agent/opencode/opencode-config.js +32 -14
- package/dist/src/agent/opencode/opencode-env.js +12 -13
- package/dist/src/agent/opencode/opencode-stream.js +51 -29
- package/dist/src/agent/session-ledger.js +16 -0
- package/dist/src/agent/subscription-account.js +3 -3
- package/dist/src/backlog/backlog-archive.js +64 -165
- package/dist/src/backlog/backlog-cli-view.js +81 -41
- package/dist/src/backlog/backlog-cli.js +57 -26
- package/dist/src/backlog/backlog-eligibility.js +84 -136
- package/dist/src/backlog/backlog-file-bugs.js +133 -0
- package/dist/src/backlog/backlog-index-data.js +2 -2
- package/dist/src/backlog/backlog-md.js +42 -31
- package/dist/src/backlog/backlog-parts.js +65 -27
- package/dist/src/backlog/{question-answer.js → backlog-question-answer.js} +30 -14
- package/dist/src/backlog/backlog-release.js +15 -23
- package/dist/src/backlog/backlog-structure.js +128 -62
- package/dist/src/backlog/backlog-write.js +1 -1
- package/dist/src/backlog/backlog.js +5 -4
- package/dist/src/baseline/main-baseline.js +178 -104
- package/dist/src/baseline/main-guardrails.js +54 -17
- package/dist/src/baseline/main-watch.js +1 -1
- package/dist/src/budget/budget-fallback-view.js +100 -243
- package/dist/src/budget/budget-wiring.js +24 -65
- package/dist/src/budget/budget.js +2 -1
- package/dist/src/budget/fallback-breaker.js +2 -2
- package/dist/src/build/build-check-detect.js +43 -14
- package/dist/src/build/build-check-events.js +2 -2
- package/dist/src/build/build-check-scoped.js +103 -50
- package/dist/src/build/build-check.js +13 -5
- package/dist/src/change/change-data.js +4 -2
- package/dist/src/cli/cli-command-args.js +275 -137
- package/dist/src/cli/cli-flag-specs.js +43 -26
- package/dist/src/cli/cli-init.js +34 -12
- package/dist/src/cli/cli-query-commands.js +1 -1
- package/dist/src/cli/cli-repo-gate.js +2 -2
- package/dist/src/cli/cli-review-commands.js +2 -1
- package/dist/src/cli/cli-role-command.js +27 -50
- package/dist/src/cli/cli-start.js +150 -67
- package/dist/src/cli/completion.js +82 -46
- package/dist/src/cli/config-commands.js +82 -71
- package/dist/src/cli/follow-view.js +3 -1
- package/dist/src/cli/help.js +6 -9
- package/dist/src/cli/log-commands.js +46 -24
- package/dist/src/cli/notify-command.js +2 -2
- package/dist/src/cli/question-commands.js +86 -60
- package/dist/src/cli.js +183 -141
- package/dist/src/collections.js +52 -23
- package/dist/src/concurrency/check-slots.js +70 -0
- package/dist/src/concurrency/lock.js +22 -0
- package/dist/src/concurrency/run-permit.js +49 -0
- package/dist/src/concurrency/semaphore.js +46 -250
- package/dist/src/config/config-editable-keys.js +4 -7
- package/dist/src/config/config-fallbacks.js +79 -103
- package/dist/src/config/config-field-checks.js +66 -37
- package/dist/src/config/config-keys.js +5 -20
- package/dist/src/config/config-live.js +161 -153
- package/dist/src/config/{model-selector.js → config-model-selector.js} +4 -4
- package/dist/src/config/config-request.js +153 -102
- package/dist/src/config/config-schema.js +9 -11
- package/dist/src/config/config-selector-checks.js +1 -1
- package/dist/src/config/config-validation-agents.js +2 -2
- package/dist/src/config/config-validation-roles.js +4 -52
- package/dist/src/config/config-validation.js +4 -38
- package/dist/src/config/config-views.js +4 -34
- package/dist/src/config/config-write.js +22 -46
- package/dist/src/config/config.js +4 -5
- package/dist/src/doctor/doctor-backlog-bugs.js +24 -121
- package/dist/src/doctor/doctor-backlog-plans.js +27 -27
- package/dist/src/doctor/doctor-backlog.js +29 -41
- package/dist/src/doctor/doctor-check-names.js +4 -7
- package/dist/src/doctor/doctor-checks.js +5 -5
- package/dist/src/doctor/doctor-claude-checks.js +46 -30
- package/dist/src/doctor/doctor-codex-checks.js +6 -6
- package/dist/src/doctor/doctor-contention-checks.js +44 -112
- package/dist/src/doctor/doctor-fallback-checks.js +2 -2
- package/dist/src/doctor/doctor-holds.js +19 -56
- package/dist/src/doctor/doctor-install.js +1 -1
- package/dist/src/doctor/doctor-launch-services.js +1 -1
- package/dist/src/doctor/doctor-model-checks.js +181 -273
- package/dist/src/doctor/doctor-notify.js +4 -7
- package/dist/src/doctor/doctor-opencode-checks.js +90 -52
- package/dist/src/doctor/doctor-orphans.js +120 -20
- package/dist/src/doctor/{outcome.js → doctor-outcome.js} +7 -0
- package/dist/src/doctor/doctor-pi-checks.js +2 -2
- package/dist/src/doctor/doctor-principles.js +3 -3
- package/dist/src/doctor/{probe.js → doctor-probe.js} +1 -1
- package/dist/src/doctor/doctor-project-checks.js +69 -20
- package/dist/src/doctor/doctor-role-notes.js +7 -25
- package/dist/src/doctor/doctor.js +12 -15
- package/dist/src/events/event-format.js +19 -53
- package/dist/src/events/event-labels.js +0 -5
- package/dist/src/events/event-legacy.js +0 -2
- package/dist/src/events/{notify.js → event-notify.js} +2 -2
- package/dist/src/events/event-read.js +1 -1
- package/dist/src/events/event-window.js +1 -1
- package/dist/src/events/events.js +15 -2
- package/dist/src/failure/failure-cluster.js +6 -6
- package/dist/src/failure/failure-data.js +8 -4
- package/dist/src/failure/failure-render.js +44 -34
- package/dist/src/failure/failure-state-change.js +177 -92
- package/dist/src/failure/{time-spend.js → failure-time-spend.js} +117 -84
- package/dist/src/files/files.js +30 -0
- package/dist/src/files/json-object.js +21 -8
- package/dist/src/files/stat-cache.js +12 -8
- package/dist/src/gates/budget-gates.js +85 -84
- package/dist/src/gates/gate-polls.js +27 -68
- package/dist/src/gates/pause-gates.js +16 -2
- package/dist/src/gates/readiness.js +1 -1
- package/dist/src/gates/role-cap-gates.js +44 -35
- package/dist/src/gates/{startup-gate.js → startup-gates.js} +34 -34
- package/dist/src/gates/{streak-gate.js → streak-gates.js} +75 -52
- package/dist/src/git/git.js +75 -45
- package/dist/src/git/slot-salvage.js +5 -4
- package/dist/src/git/worktree-pool.js +3 -6
- package/dist/src/gui/gui-admin-endpoints.js +5 -6
- package/dist/src/gui/gui-backlog-commands.js +4 -4
- package/dist/src/gui/gui-backlog-endpoints.js +62 -48
- package/dist/src/gui/gui-command.js +43 -24
- package/dist/src/gui/gui-endpoint-commands.js +1 -1
- package/dist/src/gui/gui-history-endpoints.js +100 -52
- package/dist/src/gui/gui-operator-commands.js +22 -35
- package/dist/src/gui/gui-prompt-commands.js +48 -42
- package/dist/src/gui/gui-report-endpoints.js +3 -2
- package/dist/src/gui/gui-server.js +1 -2
- package/dist/src/history/history-data.js +71 -44
- package/dist/src/history/history-queued.js +49 -27
- package/dist/src/history/history.js +138 -85
- package/dist/src/inbox/inbox-submit.js +1 -5
- package/dist/src/inbox/prompt-commands.js +82 -60
- package/dist/src/init/init.js +156 -55
- package/dist/src/merge/merge-backlog-conflicts.js +1 -1
- package/dist/src/merge/merge-core.js +2 -2
- package/dist/src/merge/merge-drain.js +8 -11
- package/dist/src/merge/merge-pipeline.js +6 -7
- package/dist/src/merge/merge-post-review-drain.js +11 -14
- package/dist/src/merge/merge-requeue.js +1 -1
- package/dist/src/merge/merge-slot.js +0 -8
- package/dist/src/merge/merge-verify.js +8 -18
- package/dist/src/notes-cli.js +52 -42
- package/dist/src/operator/operator-commands.js +143 -90
- package/dist/src/operator/operator-intent.js +3 -3
- package/dist/src/operator/{retire.js → operator-retire.js} +2 -1
- package/dist/src/orchestrator/orchestrator-launch.js +31 -63
- package/dist/src/orchestrator/orchestrator-poll.js +27 -32
- package/dist/src/orchestrator/orchestrator.js +8 -21
- package/dist/src/orchestrator/retention.js +6 -5
- package/dist/src/orchestrator/scheduling-admit.js +101 -68
- package/dist/src/orchestrator/scheduling-scope.js +1 -1
- package/dist/src/orchestrator/scheduling-skip.js +1 -1
- package/dist/src/orchestrator/startup-migrations.js +1 -1
- package/dist/src/paths.js +8 -13
- package/dist/src/pi/pi-args.js +5 -2
- package/dist/src/pi/pi-backend.js +2 -2
- package/dist/src/pi/pi-bin.js +3 -3
- package/dist/src/pi/pi-event-line.js +1 -1
- package/dist/src/pi/pi-models.js +43 -29
- package/dist/src/pi/pi-settings.js +43 -20
- package/dist/src/pi/pi-stream.js +32 -6
- package/dist/src/pi/pi-watchdogs.js +29 -3
- package/dist/src/pi/pi.js +2 -2
- package/dist/src/pi-extension/context-shake.js +87 -51
- package/dist/src/pi-extension/kill-guard.js +69 -0
- package/dist/src/process/process-group.js +49 -5
- package/dist/src/process/process.js +31 -8
- package/dist/src/process/run-marker.js +31 -13
- package/dist/src/process/supervisor.js +57 -29
- package/dist/src/prompt/dialect.js +1 -1
- package/dist/src/prompt/gate-notes.js +11 -11
- package/dist/src/prompt/notes-backlog.js +73 -57
- package/dist/src/prompt/principles.js +6 -4
- package/dist/src/prompt/prompt.js +92 -43
- package/dist/src/report/report-data.js +61 -260
- package/dist/src/report/report-fold.js +34 -302
- package/dist/src/report/report-main-red.js +45 -42
- package/dist/src/report/report-render.js +37 -183
- package/dist/src/report/report-since-data.js +219 -0
- package/dist/src/report/report-since-render.js +173 -0
- package/dist/src/report/report-usage-fold.js +288 -0
- package/dist/src/report/report.js +48 -23
- package/dist/src/review/known-flakes.js +3 -3
- package/dist/src/review/post-merge-review-accept.js +3 -2
- package/dist/src/review/post-merge-review-render.js +9 -3
- package/dist/src/review/post-merge-review.js +10 -10
- package/dist/src/review/quarantine-render.js +2 -1
- package/dist/src/review/review-precheck.js +11 -1
- package/dist/src/review/review.js +40 -153
- package/dist/src/review/secret-leaks.js +26 -16
- package/dist/src/roles/role-catalog.js +28 -32
- package/dist/src/roles/role-guidance.js +25 -46
- package/dist/src/roles/role-render.js +1 -14
- package/dist/src/roles/role-view.js +10 -23
- package/dist/src/run/acceptance-greps.js +88 -45
- package/dist/src/run/ci-parity.js +5 -6
- package/dist/src/run/qa-coverage.js +4 -4
- package/dist/src/run/run-apply.js +12 -6
- package/dist/src/run/run-detail-data.js +137 -68
- package/dist/src/run/run-detail-render.js +97 -34
- package/dist/src/run/run-detail.js +106 -53
- package/dist/src/run/run-dismiss.js +43 -0
- package/dist/src/run/run-prompt.js +150 -93
- package/dist/src/run/run-resume.js +85 -37
- package/dist/src/run/run-stage.js +139 -105
- package/dist/src/run/run-timing.js +26 -6
- package/dist/src/run/run-usage.js +31 -1
- package/dist/src/run/run-verdict.js +71 -67
- package/dist/src/run/stage-backlog-checks.js +2 -29
- package/dist/src/run/stage-check.js +52 -43
- package/dist/src/run/stage-checks-shared.js +28 -0
- package/dist/src/run/stage-name-checks.js +111 -92
- package/dist/src/run/stage-path-checks.js +69 -42
- package/dist/src/run/stage-prose-checks.js +55 -40
- package/dist/src/runner/runner-outcome.js +2 -2
- package/dist/src/runner/runner-pi.js +3 -10
- package/dist/src/runner/runner-recovery.js +1 -1
- package/dist/src/runner/{revision.js → runner-revision.js} +1 -1
- package/dist/src/runner/runner-run.js +23 -38
- package/dist/src/runner/runner-state-load.js +2 -9
- package/dist/src/runner/runner.js +21 -36
- package/dist/src/scheduling/claims.js +3 -14
- package/dist/src/scheduling/scheduling.js +53 -66
- package/dist/src/status/status-data.js +7 -11
- package/dist/src/status/status-role-notes.js +20 -16
- package/dist/src/status/status-roles.js +11 -52
- package/dist/src/team/{error-storm.js → team-error-storm.js} +1 -1
- package/dist/src/team/{failure-spread.js → team-failure-spread.js} +5 -5
- package/dist/src/team/team-hold.js +98 -65
- package/dist/src/team/{orchestrator-info.js → team-orchestrator-info.js} +2 -15
- package/dist/src/team/team-polls.js +28 -126
- package/dist/src/team/{reclaim.js → team-reclaim.js} +61 -31
- package/dist/src/team/team-state.js +4 -13
- package/dist/src/text/datetime.js +8 -1
- package/dist/src/text/markdown.js +1 -1
- package/dist/src/text/phrases.js +20 -15
- package/dist/src/text/text.js +15 -3
- package/dist/src/ui/alert-feeds.js +52 -281
- package/dist/src/ui/badges.js +3 -14
- package/dist/src/ui/gui/gui-client-boot.js +21 -6
- package/dist/src/ui/gui/gui-client-composer.js +1 -1
- package/dist/src/ui/gui/gui-client-drawer-config.js +7 -17
- package/dist/src/ui/gui/gui-client-drawer.js +2 -2
- package/dist/src/ui/gui/gui-client-history.js +28 -14
- package/dist/src/ui/gui/gui-client-model.js +0 -12
- package/dist/src/ui/gui/gui-client-pending.js +7 -6
- package/dist/src/ui/gui/gui-client-report-chart.js +12 -10
- package/dist/src/ui/gui/gui-client-report.js +1 -2
- package/dist/src/ui/gui/gui-client-roles.js +0 -1
- package/dist/src/ui/gui/gui-client-settings.js +43 -107
- package/dist/src/ui/gui/gui-client-team.js +30 -4
- package/dist/src/ui/gui/gui-client.js +7 -1
- package/dist/src/ui/gui/gui-icons.js +7 -1
- package/dist/src/ui/progress-data.js +53 -40
- package/dist/src/ui/run-progress-model.js +9 -2
- package/dist/src/ui/status-model.js +4 -26
- package/dist/src/ui/status-payload.js +4 -8
- package/dist/src/ui/status-render.js +6 -10
- package/dist/src/ui/team-alert-backlog.js +29 -60
- package/dist/src/ui/team-alert-shared.js +23 -10
- package/dist/src/ui/team-alert-status.js +17 -173
- package/dist/src/ui/team-alert-tail.js +19 -228
- package/dist/src/ui/team-alerts.js +12 -24
- package/dist/src/ui/transcript-run.js +27 -16
- package/dist/src/ui/transcript-tail.js +137 -94
- package/dist/src/ui/tui/tui-editor-answer.js +66 -54
- package/dist/src/ui/tui/tui-editor-attach.js +77 -65
- package/dist/src/ui/tui/tui-editor-backends.js +65 -58
- package/dist/src/ui/tui/tui-editor-brief.js +64 -57
- package/dist/src/ui/tui/tui-editor-budget.js +109 -96
- package/dist/src/ui/tui/tui-editor-config.js +81 -71
- package/dist/src/ui/tui/tui-editor-entry.js +167 -157
- package/dist/src/ui/tui/tui-editor-instances.js +2 -1
- package/dist/src/ui/tui/tui-editor-pause.js +208 -188
- package/dist/src/ui/tui/tui-editor-prompt.js +50 -44
- package/dist/src/ui/tui/tui-editor-queued.js +74 -67
- package/dist/src/ui/tui/tui-editor-quiet.js +124 -110
- package/dist/src/ui/tui/tui-editor-release.js +53 -44
- package/dist/src/ui/tui/tui-editor-retire.js +60 -49
- package/dist/src/ui/tui/tui-editor-role-enabled.js +93 -82
- package/dist/src/ui/tui/tui-editor-role-file.js +61 -49
- package/dist/src/ui/tui/tui-editor-role-key.js +52 -44
- package/dist/src/ui/tui/tui-editor-role-model.js +97 -15
- package/dist/src/ui/tui/tui-editor-search.js +73 -60
- package/dist/src/ui/tui/tui-editor-session.js +17 -4
- package/dist/src/ui/tui/tui-editors.js +1 -7
- package/dist/src/ui/tui/tui-frame.js +182 -134
- package/dist/src/ui/tui/tui-guard.js +20 -0
- package/dist/src/ui/tui/tui-input.js +159 -92
- package/dist/src/ui/tui/tui-keymap.js +51 -29
- package/dist/src/ui/tui/tui-keys.js +2 -2
- package/dist/src/ui/tui/tui-pane-activity.js +65 -53
- package/dist/src/ui/tui/tui-pane-backlog.js +78 -40
- package/dist/src/ui/tui/tui-pane-ctx.js +1 -1
- package/dist/src/ui/tui/tui-pane-health.js +254 -251
- package/dist/src/ui/tui/tui-pane-history.js +81 -94
- package/dist/src/ui/tui/tui-pane-pending.js +284 -247
- package/dist/src/ui/tui/tui-pane-queued.js +25 -27
- package/dist/src/ui/tui/tui-pane-report.js +76 -58
- package/dist/src/ui/tui/tui-pane-session.js +14 -9
- package/dist/src/ui/tui/tui-pane-transcript.js +152 -141
- package/dist/src/ui/tui/tui-pane.js +205 -175
- package/dist/src/ui/tui/tui-panes.js +31 -35
- package/dist/src/ui/tui/tui.js +4 -5
- package/dist/src/verdict/fix-claim.js +87 -53
- package/dist/src/verdict/refusal.js +31 -7
- package/package.json +5 -5
- package/dist/src/concurrency/provider-lease.js +0 -105
- package/dist/src/doctor/image-probe.js +0 -257
- package/dist/src/runner/model-fallback.js +0 -76
- package/dist/src/runner/runner-fallback.js +0 -66
- package/dist/src/ui/team-alert-backlog-feed.js +0 -181
- package/dist/src/ui/team-alert-merge.js +0 -183
- package/dist/src/ui/tui/tui-editor-claude-reserve.js +0 -24
- package/dist/src/ui/tui/tui-editor-provider-caps.js +0 -27
- /package/dist/src/gates/{disk-gate.js → disk-gates.js} +0 -0
- /package/dist/src/operator/{revert.js → operator-revert.js} +0 -0
- /package/dist/src/runner/{leftover.js → runner-leftover.js} +0 -0
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
* Pure translation: no spawn, no backend registration, no config. The factory keeps per-run
|
|
12
12
|
* state (the pending split-block assistant message, the unmapped native types seen, whether a
|
|
13
13
|
* `run_cost` arrived), so call it once per run. Unmapped native types are recorded once each
|
|
14
|
-
* on the seam's optional `warnings()` — src/agent/
|
|
14
|
+
* on the seam's optional `warnings()` — src/agent/agent-stream.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
17
|
import { joinParts } from "../../text/text.js";
|
|
18
|
-
import {
|
|
19
|
-
import { canonicalUsageBlock, isJsonObject, objectOrEmpty,
|
|
20
|
-
import { SERVER_TRANSIENT_STATUS } from "../backend-traits.js";
|
|
18
|
+
import { secondsUntilUnixStamp } from "../../text/datetime.js";
|
|
19
|
+
import { canonicalUsageBlock, isJsonObject, nonNegativeNumber, objectOrEmpty, optionalString, parseJsonObject } from "../../files/json-object.js";
|
|
20
|
+
import { SERVER_TRANSIENT_STATUS, transientProseKind } from "../backend-traits.js";
|
|
21
21
|
/** claude's tool names → the canonical pi names the readers already know. Glob/Grep are kept
|
|
22
22
|
* lower-cased; MultiEdit collapses onto edit (pi has one edit tool). Anything else is
|
|
23
23
|
* lower-cased so a future tool still reads as a name rather than as `undefined`. */
|
|
@@ -32,9 +32,6 @@ const TOOL_NAMES = {
|
|
|
32
32
|
};
|
|
33
33
|
/** claude's HTTP status classes that mean a permanent configuration error. */
|
|
34
34
|
const CONFIG_STATUS = new Set([400, 401, 403, 404]);
|
|
35
|
-
function str(value) {
|
|
36
|
-
return typeof value === "string" ? value : undefined;
|
|
37
|
-
}
|
|
38
35
|
/** claude's usage block → pi's canonical usage block, with `cost.total` 0 because the run's
|
|
39
36
|
* authoritative cost rides the final `run_cost` event (a split-block message repeats its usage
|
|
40
37
|
* on every event, so summing per event would double-count — the fold takes it once). The
|
|
@@ -49,15 +46,15 @@ function canonicalUsage(usage) {
|
|
|
49
46
|
function canonicalBlock(block) {
|
|
50
47
|
switch (block.type) {
|
|
51
48
|
case "text":
|
|
52
|
-
return { type: "text", text:
|
|
49
|
+
return { type: "text", text: optionalString(block.text) ?? "" };
|
|
53
50
|
case "thinking":
|
|
54
|
-
return { type: "thinking", thinking:
|
|
51
|
+
return { type: "thinking", thinking: optionalString(block.thinking) ?? optionalString(block.text) ?? "" };
|
|
55
52
|
case "tool_use": {
|
|
56
53
|
const input = objectOrEmpty(block.input);
|
|
57
54
|
const args = { ...input };
|
|
58
55
|
if (typeof input.file_path === "string")
|
|
59
56
|
args.path = input.file_path;
|
|
60
|
-
const name =
|
|
57
|
+
const name = optionalString(block.name) ?? "";
|
|
61
58
|
// Own-property test, not a bare index: a tool name like `constructor`, `toString`, or
|
|
62
59
|
// `__proto__` would otherwise inherit a function (or Object.prototype) from the table's
|
|
63
60
|
// prototype chain and bypass the lower-cased fallback — JSON.stringify then drops the
|
|
@@ -65,7 +62,7 @@ function canonicalBlock(block) {
|
|
|
65
62
|
// applies to EFFORT_BY_THINKING.
|
|
66
63
|
return {
|
|
67
64
|
type: "toolCall",
|
|
68
|
-
id:
|
|
65
|
+
id: optionalString(block.id) ?? "",
|
|
69
66
|
name: Object.hasOwn(TOOL_NAMES, name) ? (TOOL_NAMES[name]) : name.toLowerCase(),
|
|
70
67
|
arguments: args,
|
|
71
68
|
};
|
|
@@ -74,48 +71,80 @@ function canonicalBlock(block) {
|
|
|
74
71
|
return undefined;
|
|
75
72
|
}
|
|
76
73
|
}
|
|
77
|
-
/**
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
|
|
81
|
-
* provider classification applies. `status` is `api_error_status`, which is null on the
|
|
82
|
-
* not-logged-in result, so the text checks carry that case. */
|
|
83
|
-
function classifyResult(result) {
|
|
84
|
-
const terminal = str(result.terminal_reason);
|
|
74
|
+
/** `terminal === "aborted_tools"` is deliberate: the harness killed the run, so no provider
|
|
75
|
+
* classification applies — the chain stops there. `error_max_budget_usd`, as a terminal
|
|
76
|
+
* reason or in the text, caps the budget. */
|
|
77
|
+
function terminalAndBudgetClassification(terminal, text) {
|
|
85
78
|
if (terminal === "aborted_tools")
|
|
86
79
|
return {};
|
|
87
80
|
if (terminal === "error_max_budget_usd")
|
|
88
81
|
return { kind: "budgetCapped" };
|
|
89
|
-
const text = joinParts([str(result.result), str(result.error)], " ");
|
|
90
82
|
if (/error_max_budget_usd/i.test(text))
|
|
91
83
|
return { kind: "budgetCapped" };
|
|
92
|
-
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
function statusClassification(status) {
|
|
93
87
|
if (status === 429)
|
|
94
88
|
return { kind: "transientRateLimit" };
|
|
95
89
|
if (status !== undefined && SERVER_TRANSIENT_STATUS.has(status))
|
|
96
90
|
return { kind: "transientBackend", backendKind: "server" };
|
|
97
|
-
|
|
98
|
-
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
/** The context-overflow text wins over a configuration status: claude reports an oversized
|
|
94
|
+
* prompt as HTTP 400 ("prompt is too long"), whose status alone would read as configError. */
|
|
95
|
+
function contextOverflowClassification(text) {
|
|
99
96
|
if (/prompt is too long/i.test(text))
|
|
100
97
|
return { kind: "contextExceeded" };
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
function configStatusClassification(status) {
|
|
101
101
|
if (status !== undefined && CONFIG_STATUS.has(status))
|
|
102
102
|
return { kind: "configError" };
|
|
103
|
-
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
function resumeMissingClassification(text) {
|
|
104
106
|
if (/No conversation found with session ID/i.test(text))
|
|
105
107
|
return { kind: "resumeMissing" };
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
function configTextClassification(text) {
|
|
106
111
|
if (/Not logged in|issue with the selected model|does not support this model/i.test(text))
|
|
107
112
|
return { kind: "configError" };
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
function transientTextClassification(text) {
|
|
108
116
|
if (/\b429\b|rate limit/i.test(text))
|
|
109
117
|
return { kind: "transientRateLimit" };
|
|
110
118
|
if (/overloaded|internal server error|bad gateway|service unavailable|gateway timeout|\b5\d\d\b/i.test(text))
|
|
111
119
|
return { kind: "transientBackend", backendKind: "server" };
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
120
|
+
const prose = transientProseKind(text);
|
|
121
|
+
return prose;
|
|
122
|
+
}
|
|
123
|
+
/** Text fallbacks, for the statuses claude leaves null or omits. */
|
|
124
|
+
function httpStatusTextClassification(text) {
|
|
116
125
|
if (/\b(400|401|403|404)\b/i.test(text))
|
|
117
126
|
return { kind: "configError" };
|
|
118
|
-
return
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
/** How a `result` event's error signals classify into the canonical `tumwaterKind` (and, for
|
|
130
|
+
* a transient backend, its kind), trusted by `PiStreamParser` before its text regexes. Empty
|
|
131
|
+
* when the result is not an error or carries no signal the vocabulary names — a SIGTERM
|
|
132
|
+
* (`terminal_reason: "aborted_tools"`) is deliberate: the harness killed the run, so no
|
|
133
|
+
* provider classification applies. `status` is `api_error_status`, which is null on the
|
|
134
|
+
* not-logged-in result, so the text checks carry that case. */
|
|
135
|
+
function classifyResult(result) {
|
|
136
|
+
const terminal = optionalString(result.terminal_reason);
|
|
137
|
+
const text = joinParts([optionalString(result.result), optionalString(result.error)], " ");
|
|
138
|
+
const status = nonNegativeNumber(result.api_error_status, undefined);
|
|
139
|
+
return (terminalAndBudgetClassification(terminal, text) ??
|
|
140
|
+
statusClassification(status) ??
|
|
141
|
+
contextOverflowClassification(text) ??
|
|
142
|
+
configStatusClassification(status) ??
|
|
143
|
+
resumeMissingClassification(text) ??
|
|
144
|
+
configTextClassification(text) ??
|
|
145
|
+
transientTextClassification(text) ??
|
|
146
|
+
httpStatusTextClassification(text) ??
|
|
147
|
+
{});
|
|
119
148
|
}
|
|
120
149
|
/** Build a fresh per-run claude translator. */
|
|
121
150
|
export function claudeTranslator(opts = {}) {
|
|
@@ -169,14 +198,14 @@ export function claudeTranslator(opts = {}) {
|
|
|
169
198
|
warnings.add(kind);
|
|
170
199
|
};
|
|
171
200
|
const handleSystem = (event) => {
|
|
172
|
-
const subtype =
|
|
201
|
+
const subtype = optionalString(event.subtype);
|
|
173
202
|
if (subtype === "init") {
|
|
174
203
|
const out = [];
|
|
175
|
-
const cwd =
|
|
176
|
-
const id =
|
|
204
|
+
const cwd = optionalString(event.cwd);
|
|
205
|
+
const id = optionalString(event.session_id);
|
|
177
206
|
out.push(JSON.stringify({ type: "session", ...(cwd !== undefined ? { cwd } : {}), ...(id !== undefined ? { id } : {}) }));
|
|
178
207
|
out.push(JSON.stringify({ type: "agent_start" }));
|
|
179
|
-
const billing = billingOf(
|
|
208
|
+
const billing = billingOf(optionalString(event.apiKeySource), opts.thirdPartyProvider === true);
|
|
180
209
|
if (billing)
|
|
181
210
|
out.push(JSON.stringify({ type: "billing", billing }));
|
|
182
211
|
return out;
|
|
@@ -185,7 +214,7 @@ export function claudeTranslator(opts = {}) {
|
|
|
185
214
|
const attempt = nonNegativeNumber(event.attempt, undefined);
|
|
186
215
|
const maxAttempts = nonNegativeNumber(event.max_retries, undefined);
|
|
187
216
|
const status = nonNegativeNumber(event.error_status, undefined);
|
|
188
|
-
const error =
|
|
217
|
+
const error = optionalString(event.error) ?? "";
|
|
189
218
|
const errorMessage = joinParts([status !== undefined ? `status ${status}` : "", error], ": ");
|
|
190
219
|
return [
|
|
191
220
|
JSON.stringify({
|
|
@@ -207,7 +236,7 @@ export function claudeTranslator(opts = {}) {
|
|
|
207
236
|
const handleAssistant = (event) => {
|
|
208
237
|
sawActivity = true;
|
|
209
238
|
const message = objectOrEmpty(event.message);
|
|
210
|
-
const id =
|
|
239
|
+
const id = optionalString(message.id) ?? `anon-${anonymous++}`;
|
|
211
240
|
// A new id flushes the open message; a continuing one flushes nothing. Either way the
|
|
212
241
|
// fresh or open pending then folds this event's content, usage, and stop reason the
|
|
213
242
|
// same way — that shared fold is why both branches live on one path.
|
|
@@ -230,7 +259,7 @@ export function claudeTranslator(opts = {}) {
|
|
|
230
259
|
// Usage repeats on every event of the same message; the last read wins and is counted
|
|
231
260
|
// once because the fold emits one message_end per id.
|
|
232
261
|
pending.usage = canonicalUsage(isJsonObject(message.usage) ? message.usage : undefined);
|
|
233
|
-
const stopReason =
|
|
262
|
+
const stopReason = optionalString(message.stop_reason);
|
|
234
263
|
if (stopReason !== undefined)
|
|
235
264
|
pending.stopReason = stopReason;
|
|
236
265
|
return out;
|
|
@@ -244,7 +273,7 @@ export function claudeTranslator(opts = {}) {
|
|
|
244
273
|
continue;
|
|
245
274
|
out.push(JSON.stringify({
|
|
246
275
|
type: "tool_execution_end",
|
|
247
|
-
toolCallId:
|
|
276
|
+
toolCallId: optionalString(raw.tool_use_id) ?? "",
|
|
248
277
|
isError: raw.is_error === true,
|
|
249
278
|
}));
|
|
250
279
|
}
|
|
@@ -252,45 +281,12 @@ export function claudeTranslator(opts = {}) {
|
|
|
252
281
|
};
|
|
253
282
|
const handleRateLimitEvent = (event) => {
|
|
254
283
|
const info = objectOrEmpty(event.rate_limit_info);
|
|
255
|
-
const rateLimitType =
|
|
256
|
-
const status =
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
if (type === rateLimitType || !isJsonObject(raw))
|
|
262
|
-
continue;
|
|
263
|
-
const reading = { type: "rate_limit", rateLimitType: type };
|
|
264
|
-
const windowResets = nonNegativeNumber(raw.resetsAt, undefined);
|
|
265
|
-
if (windowResets !== undefined)
|
|
266
|
-
reading.resetsAt = windowResets;
|
|
267
|
-
const windowUtil = nonNegativeNumber(raw.utilization, undefined);
|
|
268
|
-
if (windowUtil !== undefined)
|
|
269
|
-
reading.utilization = windowUtil;
|
|
270
|
-
windows.push(JSON.stringify(reading));
|
|
271
|
-
}
|
|
272
|
-
const own = rateLimitType === undefined ? undefined : objectOrEmpty(objectOrEmpty(info.unifiedWindows)[rateLimitType]);
|
|
273
|
-
const resetsAt = nonNegativeNumber(info.resetsAt, undefined) ?? nonNegativeNumber(own?.resetsAt, undefined);
|
|
274
|
-
const out = { type: "rate_limit" };
|
|
275
|
-
if (rateLimitType !== undefined)
|
|
276
|
-
out.rateLimitType = rateLimitType;
|
|
277
|
-
if (status !== undefined)
|
|
278
|
-
out.status = status;
|
|
279
|
-
if (resetsAt !== undefined)
|
|
280
|
-
out.resetsAt = resetsAt;
|
|
281
|
-
const utilization = nonNegativeNumber(info.utilization, undefined) ?? nonNegativeNumber(own?.utilization, undefined);
|
|
282
|
-
if (utilization !== undefined)
|
|
283
|
-
out.utilization = utilization;
|
|
284
|
-
const overageStatus = str(info.overageStatus);
|
|
285
|
-
if (overageStatus !== undefined)
|
|
286
|
-
out.overageStatus = overageStatus;
|
|
287
|
-
if (info.isUsingOverage === true)
|
|
288
|
-
out.isUsingOverage = true;
|
|
289
|
-
if (status === "rejected" && resetsAt !== undefined) {
|
|
290
|
-
// resetsAt is a unix-seconds stamp; secondsUntil folds it (×1000) to whole seconds.
|
|
291
|
-
out.retryAfterSeconds = secondsUntil(resetsAt * 1000, Date.now());
|
|
292
|
-
}
|
|
293
|
-
return [JSON.stringify(out), ...windows];
|
|
284
|
+
const rateLimitType = optionalString(info.rateLimitType);
|
|
285
|
+
const status = optionalString(info.status);
|
|
286
|
+
return [
|
|
287
|
+
JSON.stringify(claudeRateLimitReading(info, rateLimitType, status)),
|
|
288
|
+
...claudeRateLimitWindowReadings(info, rateLimitType),
|
|
289
|
+
];
|
|
294
290
|
};
|
|
295
291
|
const handleResult = (event) => {
|
|
296
292
|
sawActivity = true;
|
|
@@ -315,7 +311,7 @@ export function claudeTranslator(opts = {}) {
|
|
|
315
311
|
content: [],
|
|
316
312
|
stopReason: "error",
|
|
317
313
|
};
|
|
318
|
-
const errorMessage =
|
|
314
|
+
const errorMessage = optionalString(event.result) ?? optionalString(event.error);
|
|
319
315
|
if (errorMessage !== undefined)
|
|
320
316
|
message.errorMessage = errorMessage;
|
|
321
317
|
// tumwaterKind/backendKind ride the canonical EVENT (not its message): the parser
|
|
@@ -334,7 +330,7 @@ export function claudeTranslator(opts = {}) {
|
|
|
334
330
|
if (!parsed)
|
|
335
331
|
return []; // Non-JSON noise on stdout; ignore, as pi's parser does.
|
|
336
332
|
const event = parsed;
|
|
337
|
-
const type =
|
|
333
|
+
const type = optionalString(event.type);
|
|
338
334
|
switch (type) {
|
|
339
335
|
case "system": return handleSystem(event);
|
|
340
336
|
case "assistant": return handleAssistant(event);
|
|
@@ -367,6 +363,63 @@ export function claudeTranslator(opts = {}) {
|
|
|
367
363
|
},
|
|
368
364
|
};
|
|
369
365
|
}
|
|
366
|
+
/** One reading per sibling `unifiedWindows` window other than the binding window. */
|
|
367
|
+
function claudeRateLimitWindowReadings(info, rateLimitType) {
|
|
368
|
+
// claude 2.1.295 reports every window's utilization and reset under `unifiedWindows`,
|
|
369
|
+
// keyed by window type, and only the binding window's status at the top level.
|
|
370
|
+
const windows = [];
|
|
371
|
+
for (const [type, raw] of Object.entries(objectOrEmpty(info.unifiedWindows))) {
|
|
372
|
+
if (type === rateLimitType || !isJsonObject(raw))
|
|
373
|
+
continue;
|
|
374
|
+
const reading = { type: "rate_limit", rateLimitType: type };
|
|
375
|
+
const windowResets = nonNegativeNumber(raw.resetsAt, undefined);
|
|
376
|
+
if (windowResets !== undefined)
|
|
377
|
+
reading.resetsAt = windowResets;
|
|
378
|
+
const windowUtil = nonNegativeNumber(raw.utilization, undefined);
|
|
379
|
+
if (windowUtil !== undefined)
|
|
380
|
+
reading.utilization = windowUtil;
|
|
381
|
+
windows.push(JSON.stringify(reading));
|
|
382
|
+
}
|
|
383
|
+
return windows;
|
|
384
|
+
}
|
|
385
|
+
/** The binding window's own `unifiedWindows` record, if a rate-limit type was named. */
|
|
386
|
+
function claudeRateLimitOwnWindow(info, rateLimitType) {
|
|
387
|
+
return rateLimitType === undefined ? undefined : objectOrEmpty(objectOrEmpty(info.unifiedWindows)[rateLimitType]);
|
|
388
|
+
}
|
|
389
|
+
/** The binding window's reset time: the top-level `resetsAt` falling back to its own record. */
|
|
390
|
+
function claudeRateLimitResetsAt(info, own) {
|
|
391
|
+
return nonNegativeNumber(info.resetsAt, undefined) ?? nonNegativeNumber(own?.resetsAt, undefined);
|
|
392
|
+
}
|
|
393
|
+
/** The binding window's utilization: the top-level `utilization` falling back to its own record. */
|
|
394
|
+
function claudeRateLimitUtilization(info, own) {
|
|
395
|
+
return nonNegativeNumber(info.utilization, undefined) ?? nonNegativeNumber(own?.utilization, undefined);
|
|
396
|
+
}
|
|
397
|
+
/** The binding window's canonical reading: its optional fields, plus a rejected window's
|
|
398
|
+
* retry-after. */
|
|
399
|
+
function claudeRateLimitReading(info, rateLimitType, status) {
|
|
400
|
+
const own = claudeRateLimitOwnWindow(info, rateLimitType);
|
|
401
|
+
const resetsAt = claudeRateLimitResetsAt(info, own);
|
|
402
|
+
const out = { type: "rate_limit" };
|
|
403
|
+
if (rateLimitType !== undefined)
|
|
404
|
+
out.rateLimitType = rateLimitType;
|
|
405
|
+
if (status !== undefined)
|
|
406
|
+
out.status = status;
|
|
407
|
+
if (resetsAt !== undefined)
|
|
408
|
+
out.resetsAt = resetsAt;
|
|
409
|
+
const utilization = claudeRateLimitUtilization(info, own);
|
|
410
|
+
if (utilization !== undefined)
|
|
411
|
+
out.utilization = utilization;
|
|
412
|
+
const overageStatus = optionalString(info.overageStatus);
|
|
413
|
+
if (overageStatus !== undefined)
|
|
414
|
+
out.overageStatus = overageStatus;
|
|
415
|
+
if (info.isUsingOverage === true)
|
|
416
|
+
out.isUsingOverage = true;
|
|
417
|
+
if (status === "rejected" && resetsAt !== undefined) {
|
|
418
|
+
// resetsAt is a unix-seconds stamp; secondsUntilUnixStamp folds it to whole seconds.
|
|
419
|
+
out.retryAfterSeconds = secondsUntilUnixStamp(resetsAt, Date.now());
|
|
420
|
+
}
|
|
421
|
+
return out;
|
|
422
|
+
}
|
|
370
423
|
/** The run's billing, from `system/init.apiKeySource`: claude.ai OAuth reports `"none"`, an
|
|
371
424
|
* environment key reports `ANTHROPIC_API_KEY`, a helper reports `apiKeyHelper`. A
|
|
372
425
|
* third-party provider overrides both — it is API billing even when the init omitted a
|
|
@@ -84,6 +84,15 @@ export function parseCodexLoginStatus(stdout) {
|
|
|
84
84
|
return "api";
|
|
85
85
|
return null;
|
|
86
86
|
}
|
|
87
|
+
/** The refusal message when a codex seam is API-key logged in (decision 5): Codex reports no
|
|
88
|
+
* cost, so per-token billing would spend money the budget cap cannot see. The one home of the
|
|
89
|
+
* remedy text the startup gate, the live config editor, and the doctor all print, so the remedy
|
|
90
|
+
* command and its wording cannot drift between them. `home` is the codex home named in the
|
|
91
|
+
* remedy: the literal `<home>` placeholder where the call site does not resolve the home, else
|
|
92
|
+
* the resolved `CODEX_HOME` path. */
|
|
93
|
+
export function codexApiKeyRefusalMessage(home) {
|
|
94
|
+
return `Codex is logged in with an API key; tumwater runs Codex on a ChatGPT subscription only — run \`CODEX_HOME=${home} codex login --device-auth\``;
|
|
95
|
+
}
|
|
87
96
|
/** The billing the login probe names, or null when it cannot be decided. */
|
|
88
97
|
function seedBilling(config) {
|
|
89
98
|
const status = probeLoginStatus(config);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** The codex adapter for the agent seam (plans/codex-backend.md §4, §5, §6): it shapes one
|
|
2
2
|
* run's argv/stdin/env, translates codex's `exec --json` stream, reads the authoritative
|
|
3
3
|
* rollout after the process exits, and classifies the exit's stderr — everything
|
|
4
|
-
* backend-specific. src/agent/
|
|
4
|
+
* backend-specific. src/agent/agent-spawn.ts spawns the run and records this run in the session
|
|
5
5
|
* ledger; codex mints its own session id (a thread id, or the resumed record's id) in the
|
|
6
6
|
* stream's `thread.started`, which the runner's stream-mint pass appends to the ledger and
|
|
7
7
|
* `invocation` returns so a later `exec resume` can reach it.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* thread and the existing `handoff` path takes over (design §4). Together with the `resumable`
|
|
13
13
|
* hook, that is every resume point's one rule.
|
|
14
14
|
*
|
|
15
|
-
* Registration lives in src/agent/run
|
|
15
|
+
* Registration lives in src/agent/agent-run.ts beside the pi, claude and opencode adapters; a
|
|
16
16
|
* test selects codex by setting `config.agent = "codex"` on a run. A run carrying a note gets
|
|
17
17
|
* the notes path in its environment (the `tumwater-notes` command on PATH reads it), as the
|
|
18
18
|
* pi and claude adapters do. */
|
|
@@ -1,18 +1,31 @@
|
|
|
1
|
+
/** Building the environment for one codex run (plans/codex-backend.md "The invocation" §3,
|
|
2
|
+
* findings 10, 13) — pure scrub-and-set over a base environment, with no subprocess. The caller
|
|
3
|
+
* layers the run-marker, the macOS LaunchServices preload and the notes shim on first, then hands
|
|
4
|
+
* the result here.
|
|
5
|
+
*
|
|
6
|
+
* Inherited state from a host Codex or Claude Code session breaks the team run: a Codex child
|
|
7
|
+
* keeps every `CODEX_*` it was given (finding 13), and `--ignore-user-config` still reads a host
|
|
8
|
+
* home whose `config.toml` a run writes into (finding 10). So every `CODEX_*`, the host Claude
|
|
9
|
+
* session's `CLAUDECODE` / `CLAUDE_CODE_*`, and both API keys an operator might carry
|
|
10
|
+
* (`OPENAI_API_KEY`, `CODEX_API_KEY`) are scrubbed — pi's OpenAI key must never switch Codex to
|
|
11
|
+
* per-token billing behind tumwater's back (decision 5). Everything else survives, including the
|
|
12
|
+
* run marker and the PATH that carries the notes shim.
|
|
13
|
+
*
|
|
14
|
+
* The set half points Codex at a home of the team's own (`CODEX_HOME`) when one is configured, so
|
|
15
|
+
* team sessions and trust entries stay out of the operator's `~/.codex` (finding 10). Unset, runs
|
|
16
|
+
* keep the operator's home. */
|
|
17
|
+
import { scrubEnv } from "../env-scrub.js";
|
|
1
18
|
/** Build the codex process environment from `base`. Never mutates `base`. */
|
|
2
19
|
export function codexEnv(base, opts = {}) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
}
|
|
15
|
-
if (opts.home)
|
|
16
|
-
env.CODEX_HOME = opts.home;
|
|
17
|
-
return env;
|
|
20
|
+
return scrubEnv(base, {
|
|
21
|
+
keep: (key) => key !== "CLAUDECODE" &&
|
|
22
|
+
!key.startsWith("CODEX_") &&
|
|
23
|
+
!key.startsWith("CLAUDE_CODE_") &&
|
|
24
|
+
key !== "OPENAI_API_KEY" &&
|
|
25
|
+
key !== "CODEX_API_KEY",
|
|
26
|
+
then: (env) => {
|
|
27
|
+
if (opts.home)
|
|
28
|
+
env.CODEX_HOME = opts.home;
|
|
29
|
+
},
|
|
30
|
+
});
|
|
18
31
|
}
|
|
@@ -37,29 +37,48 @@ function hookListMessages(cwd) {
|
|
|
37
37
|
* `eventName: "preToolUse"` and `command` exactly the guard definition's (finding 3), or null
|
|
38
38
|
* when none matches. The key and hash are returned as codex reports them. */
|
|
39
39
|
function findTrust(result, definition) {
|
|
40
|
+
for (const group of hookGroups(result)) {
|
|
41
|
+
for (const hook of groupHooks(group)) {
|
|
42
|
+
const trust = matchHookTrust(hook, definition);
|
|
43
|
+
if (trust !== null)
|
|
44
|
+
return trust;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/** The top-level group list from `hooks/list`'s `result.data`, or `[]` when the result is not
|
|
50
|
+
* an object or has no array `data`. */
|
|
51
|
+
function hookGroups(result) {
|
|
40
52
|
if (typeof result !== "object" || result === null)
|
|
41
|
-
return
|
|
53
|
+
return [];
|
|
42
54
|
const data = result.data;
|
|
43
|
-
if (
|
|
55
|
+
if (Array.isArray(data))
|
|
56
|
+
return data;
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
/** One group's hook list from its `hooks` field, or `[]` when the group is not an object or
|
|
60
|
+
* has no array `hooks`. */
|
|
61
|
+
function groupHooks(group) {
|
|
62
|
+
if (typeof group !== "object" || group === null)
|
|
63
|
+
return [];
|
|
64
|
+
const hooks = group.hooks;
|
|
65
|
+
if (Array.isArray(hooks))
|
|
66
|
+
return hooks;
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
/** The trust of one hook when it is the guard's — `source` `sessionFlags`, `eventName`
|
|
70
|
+
* `preToolUse`, `command` exactly the definition's, with string `key` and `currentHash` — or
|
|
71
|
+
* null. */
|
|
72
|
+
function matchHookTrust(hook, definition) {
|
|
73
|
+
if (typeof hook !== "object" || hook === null)
|
|
44
74
|
return null;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (typeof hook !== "object" || hook === null)
|
|
53
|
-
continue;
|
|
54
|
-
const h = hook;
|
|
55
|
-
if (h.source !== "sessionFlags" || h.eventName !== "preToolUse")
|
|
56
|
-
continue;
|
|
57
|
-
if (h.command !== definition.command)
|
|
58
|
-
continue;
|
|
59
|
-
if (typeof h.key === "string" && typeof h.currentHash === "string") {
|
|
60
|
-
return { key: h.key, hash: h.currentHash };
|
|
61
|
-
}
|
|
62
|
-
}
|
|
75
|
+
const h = hook;
|
|
76
|
+
if (h.source !== "sessionFlags" || h.eventName !== "preToolUse")
|
|
77
|
+
return null;
|
|
78
|
+
if (h.command !== definition.command)
|
|
79
|
+
return null;
|
|
80
|
+
if (typeof h.key === "string" && typeof h.currentHash === "string") {
|
|
81
|
+
return { key: h.key, hash: h.currentHash };
|
|
63
82
|
}
|
|
64
83
|
return null;
|
|
65
84
|
}
|