@sabaiway/agent-workflow-kit 14.20.0 → 14.22.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/CHANGELOG.md +87 -0
- package/README.md +2 -2
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +38 -50
- package/bridges/antigravity-cli-bridge/bin/agy-model-posture.test.mjs +257 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review-harness.test.mjs +10 -3
- package/bridges/antigravity-cli-bridge/bin/agy-review-honesty.test.mjs +20 -21
- package/bridges/antigravity-cli-bridge/bin/agy-review-model-screen.test.mjs +4 -4
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +120 -48
- package/bridges/antigravity-cli-bridge/bin/agy-review.test.mjs +22 -62
- package/bridges/antigravity-cli-bridge/bin/agy.sh +109 -16
- package/bridges/antigravity-cli-bridge/bin/agy.test.mjs +5 -5
- package/bridges/antigravity-cli-bridge/capability.json +14 -7
- package/bridges/antigravity-cli-bridge/references/driving-agy.md +16 -20
- package/bridges/antigravity-cli-bridge/references/models-and-flags.md +10 -27
- package/bridges/codex-cli-bridge/SKILL.md +51 -49
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +145 -46
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +28 -55
- package/bridges/codex-cli-bridge/bin/codex-model-posture.test.mjs +371 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +134 -36
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +33 -45
- package/bridges/codex-cli-bridge/capability.json +22 -8
- package/bridges/codex-cli-bridge/references/driving-codex.md +18 -15
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +16 -12
- package/bridges/codex-cli-bridge/setup/README.md +3 -3
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/authoring/TASK_TEMPLATE.md +11 -16
- package/references/modes/agents.md +2 -2
- package/references/modes/bridge-settings.md +10 -7
- package/references/modes/procedures.md +7 -7
- package/references/modes/recipes.md +2 -2
- package/references/modes/review-state.md +1 -1
- package/references/modes/set-recipe.md +1 -1
- package/references/modes/tier.md +1 -1
- package/references/modes/velocity.md +1 -1
- package/references/reference-profile.json +1 -1
- package/references/templates/SPEC_TEMPLATE.md +2 -1
- package/references/templates/agent_rules.md +7 -7
- package/tools/bridge-catalog.mjs +114 -0
- package/tools/bridge-posture.mjs +11 -2
- package/tools/bridge-settings-read.mjs +41 -5
- package/tools/bridge-settings.mjs +111 -23
- package/tools/carriers.mjs +6 -4
- package/tools/cheap-agents.mjs +1 -1
- package/tools/detect-backends.mjs +6 -6
- package/tools/family-registry.mjs +3 -3
- package/tools/held-session.mjs +1 -33
- package/tools/manifest/schema.md +1 -1
- package/tools/manifest/validate.mjs +3 -2
- package/tools/procedures.mjs +86 -116
- package/tools/recipes.mjs +2 -2
- package/tools/review-state.mjs +1 -1
- package/tools/rules-regions.mjs +2 -1
- package/tools/tier-guide-facts.mjs +145 -159
- package/tools/tier-guide.mjs +8 -14
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,93 @@ Semantically versioned ([semver](https://semver.org)), newest first. The `versio
|
|
|
4
4
|
is the current release. `upgrade` mode reads a project's `docs/ai/.workflow-version` and applies
|
|
5
5
|
every `migrations/<version>-<slug>.md` newer than it, in semver order.
|
|
6
6
|
|
|
7
|
+
## 14.22.0 — a story runs in four sessions, each step with one checked result; the advisor renders the new review loop (AD-170)
|
|
8
|
+
|
|
9
|
+
**Your project's rules now state the story flow the engine's canon (5.12.0) defines: four sessions — spec and plan,
|
|
10
|
+
tests, code, then diff review, release and record — with review on the spec with the plan's ledger and on the staged
|
|
11
|
+
diff.** The canon adds the epic's review by the review lens alone and the one commit after the diff review;
|
|
12
|
+
`/agent-workflow-kit procedures` renders both. Both rules templates' §2.6 (the lens) and §2.7
|
|
13
|
+
(Story sessions) carry the new bodies; the outgoing ones are priors, so an upgrade refreshes an unedited region and
|
|
14
|
+
keeps a custom edit, flagged.
|
|
15
|
+
|
|
16
|
+
- **`/agent-workflow-kit procedures` renders the loop the canon states.** One fold ask per member per round, carrying
|
|
17
|
+
all of its findings; round two with the folded artifact, the decided register and the fold diffs; a fold of the
|
|
18
|
+
last round re-read by its raiser; for `plan-execution`, the re-check of a staged tree that moves after a member's
|
|
19
|
+
last ship-class verdict. The round render, the cap triage and the red-proof instrument are gone from the
|
|
20
|
+
plan-authoring and plan-execution renders; `feedback-triage` keeps its round table. plan-authoring's agy review
|
|
21
|
+
reads the spec review file, and the unarmed flow line names no arming command. On a project that armed the flow
|
|
22
|
+
chain itself, `plan-execution` still renders the armed pre-fold line, a stated residual.
|
|
23
|
+
- **The epic by the lens.** `procedures epic` renders the lens review and, whatever `epic.review` resolves to, no
|
|
24
|
+
bridge wrapper, driving contract or grounding pre-step; the slot keys and the Slots line are unchanged.
|
|
25
|
+
`references/modes/agents.md` and the README say the lens is the whole review of an epic and never replaces the
|
|
26
|
+
review recipe of a plan or a diff; the `agents` report and `dispatch advise` say it never replaces a plan or diff
|
|
27
|
+
review recipe.
|
|
28
|
+
- **Cleanup after the commit.** The rules template's §1.3 Task Completion asks before committing first and runs the
|
|
29
|
+
plan's Phase: Cleanup after the commit.
|
|
30
|
+
- **One fold line.** Under a delegated execute slot the fold lane is one line: resume the run's session with
|
|
31
|
+
`codex-exec --resume <session id> --nonce <n> <prompt file>`, the id read from the run's exec receipt. The advisor
|
|
32
|
+
no longer reads the delegation ledger; `tools/held-session.mjs` drops `foldLaneLines`, which nothing else read.
|
|
33
|
+
- **`task.author` dispatches no slice.** `tools/carriers.mjs`: the orchestrator writes each row's prompt, so under
|
|
34
|
+
`subagent` `dispatchForm` prints the slot's sentence alone, without the executor dispatch lines; under `delegated`
|
|
35
|
+
the slot still renders `codex-exec` and its driving contract, which no step reads (display only, recorded debt).
|
|
36
|
+
`task.execute`'s slice is one prompt the orchestrator wrote from one ledger row; `references/modes/{agents,recipes,
|
|
37
|
+
set-recipe,procedures}.md` say the same.
|
|
38
|
+
- `tools/tier-guide.mjs`, `references/modes/tier.md` and the reference profile say four story sessions;
|
|
39
|
+
`references/templates/SPEC_TEMPLATE.md` says a draft exists at the spec review.
|
|
40
|
+
- Recorded debt (`docs/debt.md`): `recipes --active-line` (`→ codex-review`), the orchestration README default
|
|
41
|
+
sentence, the tier offer and `status` still render `epic.review` as a bridge review (`reviewed`), and `recipes
|
|
42
|
+
--active-line`, `procedures task` and the tier offer still name `codex-exec` for a delegated `task.author`; no
|
|
43
|
+
step reads either slot; display only.
|
|
44
|
+
- **The tier guide walks a story on this flow.** `tools/tier-guide.mjs` says a task is one plan row handed out in
|
|
45
|
+
one prompt from the task template, and the story commits once, after its diff review.
|
|
46
|
+
`references/authoring/TASK_TEMPLATE.md` is that prompt's form: Row, Files, Reads, Run and Do not run, seven keys;
|
|
47
|
+
a bridge prompt adds the contract block with a fresh nonce that the engine's *The task* names. The
|
|
48
|
+
guide's story stages are S1-S4: it reads each row's prompt at its canonical name, and a landed story's leftover
|
|
49
|
+
(read the staged diff, check the review state, the one commit, remove the plan) holds every other story and
|
|
50
|
+
every other epic's close until it is done, so the story commits before its epic closes. The guide no longer judges the plan, mints a checkpoint, stamps
|
|
51
|
+
or checks a brief, or commits per task; it prints `plan-shape-cli --check` as a step.
|
|
52
|
+
|
|
53
|
+
## 14.21.0 — the bridge model is one host setting: your choice, else the default, checked against the installed CLI before any run (AD-169)
|
|
54
|
+
|
|
55
|
+
**Each bridge now runs the model you set, and `/agent-workflow-kit bridge-settings` shows and sets it.** The read-out
|
|
56
|
+
names, per bridge, the model (codex: and effort), where it comes from (`default`, `setting` or `environment`) and the
|
|
57
|
+
models the installed CLI offers, in text and `--json`. `--set CODEX_MODEL|CODEX_EFFORT|AGY_MODEL=…` is checked
|
|
58
|
+
against that list before anything is written, on the preview and `--apply` alike; the choice lives in the host file
|
|
59
|
+
`${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf`, holds for every later run on that host and
|
|
60
|
+
survives a kit upgrade. A new vendor model needs no kit release.
|
|
61
|
+
|
|
62
|
+
> ### ⚠ BREAKING — inherited from codex-cli-bridge 4.0.0 and antigravity-cli-bridge 6.0.0
|
|
63
|
+
>
|
|
64
|
+
> `codex-exec`, `codex-review` and `agy-review` refuse before any spend (exit 2) a run off the host posture — an
|
|
65
|
+
> environment `CODEX_MODEL`, `CODEX_EFFORT` or `AGY_MODEL` that differs from your setting, else the default — unless
|
|
66
|
+
> it is a probe (`CODEX_PROBE=1`, `AGY_PROBE=1`), whose review receipt never attests; `codex-exec` also refuses a
|
|
67
|
+
> probe one-off that carries a dispatch nonce. The refusal names the settings lines that make the model your posture.
|
|
68
|
+
> All four wrappers, `agy-run` included, refuse (exit 2) a model the installed CLI's catalog does not offer, the
|
|
69
|
+
> default included; an unreadable catalog never refuses. A model or effort flag after `--` is refused by `codex-exec`
|
|
70
|
+
> and `agy-run`. To keep a model you used to pass through the environment, set it once with
|
|
71
|
+
> `bridge-settings --set … --apply`.
|
|
72
|
+
|
|
73
|
+
- **New codex default.** codex runs `gpt-6.1-sol` at effort `high`; agy keeps `Gemini 3.8 Flash (High)`.
|
|
74
|
+
- **Checked before any run.** Each wrapper checks the effective model (codex: and effort) against the installed CLI's
|
|
75
|
+
catalog (`codex debug models`, `agy models`) with every `*_API_KEY` cleared (codex: `OPENAI_BASE_URL` too); a
|
|
76
|
+
model the CLI does not offer is refused with the offered list and one remedy. An unreadable catalog is stated and
|
|
77
|
+
never refuses.
|
|
78
|
+
- **The banners say where the model came from.** Every receipt still names what was handed to the CLI (codex: model,
|
|
79
|
+
effort and tier; agy: model); the posture banners of `codex-exec`, `codex-review` and `agy-review` now name where
|
|
80
|
+
the model and, for codex, the effort came from.
|
|
81
|
+
- **The command runs no model.** It runs only the two catalog commands, concurrently and bounded as the wrappers are
|
|
82
|
+
(codex 15 s, agy 30 s), through the new leaf `tools/bridge-catalog.mjs`; a missing, failing or hung CLI is one line,
|
|
83
|
+
and a set then proceeds stating the value was not checked. The status line and the recipes renders show a set model
|
|
84
|
+
with its source and read no catalog.
|
|
85
|
+
- **The manifests declare the keys.** A new setting kind `posture` in `tools/manifest/validate.mjs` (a non-empty
|
|
86
|
+
string, no control byte, no edge whitespace); `--reconcile` reports the three keys current.
|
|
87
|
+
- **The text names the default only.** The bridges' skill files, references and wrapper headers name each default and
|
|
88
|
+
point at the command; the agy frontier list, its off-frontier warning, its probe advice and the retired 3.5 Flash
|
|
89
|
+
text are gone. The recipes quota note gives no per-call cheapest-model advice.
|
|
90
|
+
- Recorded debt (`docs/debt.md`, twelve rows): among them the posture kind admits line separators and the settings
|
|
91
|
+
reader misses a NUL, the agy catalog lookup ignores `~/.local/bin`, a catalog read whose detached child holds its
|
|
92
|
+
output never settles, and the posture render folds whitespace.
|
|
93
|
+
|
|
7
94
|
## 14.20.0 — the kit installs the Jev vendor skill: pinned, one verified copy per agent skill root, by one command you run (AD-168)
|
|
8
95
|
|
|
9
96
|
**Once `TYPESAFE_API_KEY` is set, the Recommendations item `jev-skill` offers the vendor's Jev skill for every agent the
|
package/README.md
CHANGED
|
@@ -252,10 +252,10 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
|
|
|
252
252
|
| `/agent-workflow-kit worktrees` | any time | **parallel feature worktrees** — run several features in DIFFERENT agent sessions on one repo, zero interference on working-tree files (the ONE exception is the dependency cache, below): `provision <slug> --plan <file>` creates a sibling git worktree on branch `aw/<slug>` and populates it (registry-derived footprint copy-if-missing — a tracked file is never overwritten; EXACTLY ONE seeded feature plan; the `handoff-<slug>.md` record from minute zero; `node_modules` symlinked where the link stays ignored — a shared MUTABLE dependency cache: writes through it hit MAIN's node_modules; for isolation run the printed isolated-install command (`--install` only PRINTS it; on `--resume` run the printed unlink-first recovery first — printed only for a link whose RAW TARGET is MAIN's `node_modules`, since a foreign or unreadable link is reported as such with no removal advised); absolute root-pinned gate commands rebased on untracked copies only, and only while their bytes equal the MAIN source or its rebased form — user-modified copies stay untouched); `list` is read-only (slug, branch, base, dirty, handoff); `prompt <slug>` is read-only too and re-prints the satellite's cold-start prompt — the same text `provision` ends its report with: where the worktree is, its ONE seeded plan, the handoff as the one channel back, MAIN's orientation (shared series index, landing) and, under its own heading because it is probed on the satellite, that checkout's install posture — every value derived LIVE rather than replayed from the frozen provision record, a recorded value that no longer matches NAMED as a divergence (with the cause its source makes likely) instead of printed as the runnable one, every offered command marked with WHO runs it (`MAIN $ …` for the landing, `HERE $ …` for this checkout's own install, a posture with nothing to run staying prose), and a control character in any rendered value a typed STOP rather than a forged prompt line; `land <slug> --prepare` locks the common git dir, fail-closes on divergence or incomplete satellite state, transfers the complete accepted satellite diff onto a CLEAN main, runs sync plus the declared gates, and reports HEAD/TRANSFER/PREPARED OIDs — the commit ALWAYS stays a dialogue ask; `cleanup <slug>` takes the same lock and removes a LANDED worktree only after live landed-verification against main HEAD, while `--abandon` is the ONE destructive arm (destroys unlanded work; **no preview step** on any writer). The parent dir is the `docs/ai/worktrees.json` `parentDir` setting (default: the repo's sibling parent); an unwritable parent degrades to printed maintainer-pasted commands, and the one-time host consent that makes it promptless surfaces via `recommendations`. Never commits, never pushes, never runs a subscription CLI. |
|
|
253
253
|
| `/agent-workflow-kit uninstall` | opt-in, any time | **guarded teardown** — the inverse of `init` / `setup`. Removes only what's **provably ours** (managed skill dirs + bridge wrappers; in a project, the hidden-mode git-ignore block it added + the pre-commit hook it installed); **never deletes** your `docs/ai` / `AGENTS.md` (prints the exact `rm` to run by hand) or your `.claude/settings.json` (prints an **edit** — remove the attribution key, review any velocity `permissions.*` — never an `rm`). Always `--dry-run` first; preflight-then-mutate; never commits. |
|
|
254
254
|
| `/agent-workflow-kit velocity` | Claude Code · opt-in | **onboarding velocity profile** — seeds a fixed, audited **read-only** allowlist into `.claude/settings.json` so routine read-only commands stop idling on approval prompts while you're away; opt-in `acceptEdits`; plus a **read-only advisory** of likely project gate commands to add by hand. Writes **only** `.claude/settings.json` — **never** allowlists commit/push/publish, never writes `settings.local.json`, never commits. A seeded entry is a **trust posture, not a sandbox** (a runtime residual remains at the settings level — its guard ships as the opt-in `hook` command); a direct commit/push/publish still asks. `--dry-run` first. |
|
|
255
|
-
| `/agent-workflow-kit agents` | Claude Code · opt-in | **subagent vehicles** — places the five bundled subagent definitions into `.claude/agents/`: **four read-only vehicles + one full-tool `executor`**. **No read-only vehicle gets `Bash`**, and that is the load-bearing property: a read-only fan-out on a full-tool subagent shells out for facts it could have read, and every shelled command is an approval prompt you never needed to see. Three of the four ride a **cheap model** (haiku, low effort) for mechanical work — extraction sweeps, changelog fact-skeletons, gate-failure triage; the fourth, **`review-lens`**, is an ADDITIONAL independent read-only review opinion on code your configured backends have already seen (never a replacement for your review recipe, advisory like every review). The fifth, **`executor`** (all tools; model and effort from `docs/ai/vehicles.json`), is the ONE vehicle with a shell and the instrument behind the **Subagent** recipe: dispatched only for a bounded, file-disjoint execution / authoring / routine slice you verify afterwards — never for read-only work, never as a review backend, and it never commits. Its state (`placed` / `customized` / `unusable` / `missing`) is what a `subagent` slot's readiness resolves from. Writing code and running gates stay off the four read-only vehicles; the executor carries a bounded slice you re-verify on your main lane, and every vehicle's output is verified. Preview by default (`--apply` writes); an existing customized read-only vehicle or lens is **preserved, never overwritten**; the executor's body is re-derived from `docs/ai/vehicles.json` (a hand edit is replaced); never touches `settings*.json`, never commits. |
|
|
255
|
+
| `/agent-workflow-kit agents` | Claude Code · opt-in | **subagent vehicles** — places the five bundled subagent definitions into `.claude/agents/`: **four read-only vehicles + one full-tool `executor`**. **No read-only vehicle gets `Bash`**, and that is the load-bearing property: a read-only fan-out on a full-tool subagent shells out for facts it could have read, and every shelled command is an approval prompt you never needed to see. Three of the four ride a **cheap model** (haiku, low effort) for mechanical work — extraction sweeps, changelog fact-skeletons, gate-failure triage; the fourth, **`review-lens`**, is an ADDITIONAL independent read-only review opinion on code your configured backends have already seen (never a replacement for your plan or diff review recipe, advisory like every review; an epic is reviewed by the lens alone, whatever `epic.review` resolves to). The fifth, **`executor`** (all tools; model and effort from `docs/ai/vehicles.json`), is the ONE vehicle with a shell and the instrument behind the **Subagent** recipe: dispatched only for a bounded, file-disjoint execution / authoring / routine slice you verify afterwards — never for read-only work, never as a review backend, and it never commits. Its state (`placed` / `customized` / `unusable` / `missing`) is what a `subagent` slot's readiness resolves from. Writing code and running gates stay off the four read-only vehicles; the executor carries a bounded slice you re-verify on your main lane, and every vehicle's output is verified. Preview by default (`--apply` writes); an existing customized read-only vehicle or lens is **preserved, never overwritten**; the executor's body is re-derived from `docs/ai/vehicles.json` (a hand edit is replaced); never touches `settings*.json`, never commits. |
|
|
256
256
|
| `/agent-workflow-kit hook` | Claude Code · opt-in | **gate-approval hook** — places a self-contained PreToolUse hook (`.claude/hooks/`) and wires it into `.claude/settings.json`: a Bash command **byte-identical** to a gate you declared in `docs/ai/gates.json` (run from the project root) is auto-approved — no prompt, no idle; a seeded read-only command carrying a runtime residual (output redirection, command substitution, `--output` writes) now **asks** even where an allow rule would have silently passed it (proven live — hook `ask` overrides an allow rule). Detection is a conservative string scan, so it over-asks on a byte that is ordinary text — searching for `=>`, or a read wearing `2>/dev/null`, prompts. That is a documented open limit: 4.1.0 built three mechanisms to narrow it and removed all three in review, each counterexample kept as a test (see Mode: hook). Exact matches only, never patterns; never `deny`; a broken `gates.json` only disables auto-approval, never the guard. An opt-in **read-lane** (`--read-lane` → `docs/ai/lanes.json`, a **separate** file from `gates.json`) additionally auto-approves *compounds* of seeded read-only commands carrying no shell metaprogramming; `--apply --read-lane` verifies the placed hook is current first (delete-to-reseed otherwise). Preview by default; never writes `settings.local.json`; never commits. |
|
|
257
257
|
| `/agent-workflow-kit state-block-guard` | Claude Code · opt-in | **closing-block detector** — a `Stop` hook that reads the turn's final assistant message and warns when the closing state block is defective: the «what I need from you» slot answering *nothing* (a turn that ENDS always needs a resume, so the answer is false, not merely unhelpful), or a first-person promise of imminent work in a turn that is over. Judged on the slot's first clause, so a real ask followed by a clause break and "nothing else" passes, and a promise gated on something named passes; matching is word-bounded and Unicode-aware, quoted and fenced examples are stripped, and the last STARTED block decides — an incomplete tail counts as no block rather than falling back to an earlier one. A message carrying no block at all is silent unless you pass `--require-block` — this kit does not mandate the block, and a hook that runs every turn must not warn every turn. The judgement is lexical and the mode doc names every residual it leaves. **Detection, never prevention** — a `Stop` hook cannot un-send the message it judges; what it buys is that a silent recurrence becomes a visible one. Warns via `systemMessage` and **exits 0 on every path**: never `deny`, never blocks the stop, never re-enters the model. Reads nothing but the message, makes no network call, approves nothing. This mode is **read-only and has no writer** — it ships the self-contained hook and a paste-ready wiring block you merge into `.claude/settings.json` yourself. |
|
|
258
|
-
| `/agent-workflow-kit bridge-settings` | opt-in, any time | **host-level bridge settings** — read or change the bridges' `KEY=VALUE` config file (`${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf`), the **one place a knob survives kit upgrades** (it lives outside every kit tree; a refresh never touches it, and now states loudly if it ever overwrote a local edit).
|
|
258
|
+
| `/agent-workflow-kit bridge-settings` | opt-in, any time | **host-level bridge settings** — read or change the bridges' `KEY=VALUE` config file (`${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf`), the **one place a knob survives kit upgrades** (it lives outside every kit tree; a refresh never touches it, and now states loudly if it ever overwrote a local edit). The **model each bridge runs**: `CODEX_MODEL` + `CODEX_EFFORT` (default `gpt-6.1-sol` at `high`) and `AGY_MODEL` (default `Gemini 3.8 Flash (High)`) — the read-out shows the models each installed CLI offers, and a set one is checked against that list before anything is written. Other knobs: the **codex Fast tier** (`CODEX_SERVICE_TIER=priority` — "2x speed, **increased usage**" in the catalog's own words, quality-neutral, default off — a consented spend), the codex/agy hard timeouts, the codex review byte cap, and `AGY_REVIEW_MAX_TOTAL_BYTES` (the ceiling on what an oversized agy code review may feed before it refuses pre-spend). `AGY_REVIEW_ALLOW_ADDDIR` is a **retired** compatibility key: still recognized so an existing line never warns as unknown, but it arms nothing — the writer refuses to set it and `--unset` clears it. Allowed keys + value rules come from the bundled bridge manifests. **Previews by default**; refuses an unknown key, an out-of-range value, a model or effort the catalog does not offer, or a duplicate-carrying file; writes atomically (symlink/TOCTOU-safe). Never commits, never runs a model (only the read-only catalog commands). |
|
|
259
259
|
| `/agent-workflow-kit autonomy-doctor` | opt-in, any time | **guarded sandbox provisioner "doctor"** — can this machine run the Claude sandbox, and (only with your consent) fix it: macOS Seatbelt built-in / Linux+WSL2 needs `bwrap`+`socat` / native Windows → WSL2. Flagless = **FS-only preview** (the diagnosis, the exact absolute-path command it WOULD run, the exact `--apply <pm>:<pkgs>` consent tuple — runs nothing, never claims ready); `--verify` runs the unprivileged proof (a `bwrap` user-namespace smoke + `socat -V`) — the **only** source of a Linux "ready (verified)" claim; `--apply <pm>:<pkgs>` is the consent-gated privileged install (the tuple must equal the previewed plan; everything executed resolves to absolute paths inside `/usr/bin:/bin:/usr/sbin:/sbin`; the exact command is re-printed immediately before execution), then verifies. Honest loud degrades (unknown PM, untrusted location, nested-sandbox INDETERMINATE, root-unproven) and a stated restart step. Sits **outside every velocity auto-approve tier**; never auto-runs, never writes repo files, never commits. |
|
|
260
260
|
|
|
261
261
|
It **never auto-commits** and **never overwrites** an existing `AGENTS.md` without asking.
|
package/SKILL.md
CHANGED
|
@@ -3,7 +3,7 @@ name: agent-workflow-kit
|
|
|
3
3
|
description: Deploy or upgrade a portable AI-agent memory-and-workflow system in any project. Use when the user wants to bootstrap `docs/ai/` + an entry-point `AGENTS.md` (+ `CLAUDE.md` alias) + cap/archive/index enforcement in a new or existing repo, set up the Memory Map and session protocols, install the docs-rotation pre-commit hook, or run `/agent-workflow-kit` / `/agent-workflow-kit upgrade`. Triggers on phrases like "set up the memory system", "deploy the AI workflow here", "bootstrap docs/ai", "upgrade the workflow". New to the epic, story and task tier? Run `/agent-workflow-kit tier`. A typed decision with a confidence from Jev? Run `/agent-workflow-kit jev`.
|
|
4
4
|
disable-model-invocation: true
|
|
5
5
|
metadata:
|
|
6
|
-
version: '14.
|
|
6
|
+
version: '14.22.0'
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# agent-workflow-kit
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: antigravity-cli-bridge
|
|
3
3
|
description: Delegate work to Google's Antigravity CLI (`agy`) — the successor to Gemini CLI — to reach Gemini, Claude, and GPT-OSS models under a Google AI Pro/Ultra subscription from the terminal. Use when the user wants to run a headless `agy` prompt, hand a focused task or second-opinion review to `agy`, install or authenticate Antigravity CLI, check or economise its quota/models, bridge project context into `agy`, set up a second delegated-execution backend beside Codex, or troubleshoot `agy` flags, models, auth, conversations, or its headless behaviour.
|
|
4
4
|
metadata:
|
|
5
|
-
version: '
|
|
5
|
+
version: '6.0.0'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# antigravity-cli-bridge
|
|
@@ -47,34 +47,19 @@ this skill. The wrapper [`bin/agy.sh`](bin/agy.sh) **unsets every `*_API_KEY`**
|
|
|
47
47
|
`GEMINI_API_KEY`, `GOOGLE_API_KEY`, `GOOGLE_GENAI_API_KEY`) before invoking `agy`, so a stray key can
|
|
48
48
|
never silently switch you to pay-as-you-go billing.
|
|
49
49
|
|
|
50
|
-
**Caveat:** the subscription has a finite quota.
|
|
51
|
-
|
|
50
|
+
**Caveat:** the subscription has a finite quota. Keep probes short (see *How the main agent drives
|
|
51
|
+
agy*); the model every review and kept run uses is the host posture below, never a per-call pick.
|
|
52
52
|
|
|
53
53
|
## Models
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
| `Gemini 3.8 Flash (High)` | wrapper + review default; asserted frontier-grade (AD-136) |
|
|
64
|
-
| `Gemini 3.7 Flash (Low)` | previous Flash generation, still served — prefer 3.8 |
|
|
65
|
-
| `Gemini 3.7 Flash (Medium)` | previous Flash generation, still served — prefer 3.8 |
|
|
66
|
-
| `Gemini 3.7 Flash (High)` | previous Flash generation, still frontier-grade for review (fork (a)) |
|
|
67
|
-
| `Gemini 3.6 Flash (Low)` | older Flash generation, still served — prefer 3.8 |
|
|
68
|
-
| `Gemini 3.6 Flash (Medium)` | older Flash generation, still served — prefer 3.8 |
|
|
69
|
-
| `Gemini 3.6 Flash (High)` | older Flash generation, still served — prefer 3.8 |
|
|
70
|
-
| `Gemini 3.5 Flash (Low)` | older Flash generation, still served — prefer 3.8 |
|
|
71
|
-
| `Gemini 3.5 Flash (Medium)` | older Flash generation, still served — prefer 3.8 |
|
|
72
|
-
| `Gemini 3.5 Flash (High)` | older Flash generation, still served — prefer 3.8 |
|
|
73
|
-
| `Gemini 3.1 Pro (Low)` | cheaper Pro pass for medium reasoning |
|
|
74
|
-
| `Gemini 3.1 Pro (High)` | hard reasoning, plan critique, architecture review (slower, deeper) |
|
|
75
|
-
| `Claude Sonnet 4.6 (Thinking)` | a Claude second opinion through the same subscription |
|
|
76
|
-
| `Claude Opus 4.6 (Thinking)` | strongest Claude reasoning available via `agy` |
|
|
77
|
-
| `GPT-OSS 120B (Medium)` | an open-weights cross-check / diversity pass |
|
|
55
|
+
The model is **one host setting**: `AGY_MODEL=<exact display string>` in the bridge settings file
|
|
56
|
+
`${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf` is the host posture, else the
|
|
57
|
+
built-in default `Gemini 3.8 Flash (High)`. An `AGY_MODEL` environment value off the host posture
|
|
58
|
+
(an explicitly empty one included) is a one-off, which `agy-run` (the probe role) runs and
|
|
59
|
+
`agy-review` refuses pre-spend unless `AGY_PROBE=1`; a `--model`/`--effort` flag after `--` is refused. Before any run the wrappers check the
|
|
60
|
+
model against `agy models` (the live list): a model it does not offer is refused with the offered list
|
|
61
|
+
and the settings-file remedy. **`/agent-workflow-kit bridge-settings`** shows the models the installed
|
|
62
|
+
`agy` offers, in its own order, and sets the host posture (checked against that list before it writes).
|
|
78
63
|
|
|
79
64
|
## Usage
|
|
80
65
|
|
|
@@ -92,10 +77,10 @@ Drive `agy` only through the wrapper [`bin/agy.sh`](bin/agy.sh) (installed on `P
|
|
|
92
77
|
agy-run "your prompt" # prompt as an argument
|
|
93
78
|
echo "your prompt" | agy-run - # prompt from stdin
|
|
94
79
|
agy-run @path/to/prompt.md # prompt from a file
|
|
95
|
-
AGY_MODEL="
|
|
80
|
+
AGY_MODEL="<display string>" agy-run "..." # a one-run model (a probe's one-off)
|
|
96
81
|
AGY_TIMEOUT=10m agy-run "..." # agy's soft --print-timeout
|
|
97
82
|
AGY_HARD_TIMEOUT=8m agy-run "..." # hard wall-clock cap via timeout(1)
|
|
98
|
-
agy-run "..." -- --add-dir . # passthrough agy flags (never a permission widener)
|
|
83
|
+
agy-run "..." -- --add-dir . # passthrough agy flags (never a permission widener; --model*/--effort* refuse)
|
|
99
84
|
```
|
|
100
85
|
|
|
101
86
|
`agy` is **headless-only** here (`-p`/`--print`). `agy-run` stays **text**: `--output-format json`
|
|
@@ -112,13 +97,14 @@ after `--`. Full detail: [`references/models-and-flags.md`](references/models-an
|
|
|
112
97
|
`${XDG_CONFIG_HOME:-~/.config}/agent-workflow/bridge-settings.conf` holds `KEY=VALUE` lines,
|
|
113
98
|
**parsed, never sourced** — a file line can never execute code. Precedence: explicit env (even
|
|
114
99
|
empty — `KEY=` disables a knob for one run) > file > built-in default. File-settable keys for this
|
|
115
|
-
bridge: `
|
|
100
|
+
bridge: `AGY_MODEL` (the host posture — see [§ Models](#models)), `AGY_HARD_TIMEOUT` (duration
|
|
101
|
+
string, e.g. `5m`/`30m`) and `AGY_REVIEW_MAX_TOTAL_BYTES`
|
|
116
102
|
(integer bytes, default `240000` — the ceiling on the SUM of all outgoing prompt bytes an oversized
|
|
117
103
|
`code` review may feed). `AGY_REVIEW_ALLOW_ADDDIR` is **RETIRED**: still recognized so an existing
|
|
118
104
|
line never warns as unknown, but it arms nothing — the writer refuses a new `--set` and `--unset`
|
|
119
105
|
clears it — exactly the manifest `settings` block (the single source; the wrapper
|
|
120
|
-
constants and `--help` are drift-guarded against it
|
|
121
|
-
file lives **outside every kit-managed tree**, so a kit refresh/upgrade can never wipe it; edit it
|
|
106
|
+
constants and `--help` are drift-guarded against it, the model key read by the posture reader
|
|
107
|
+
instead). The file lives **outside every kit-managed tree**, so a kit refresh/upgrade can never wipe it; edit it
|
|
122
108
|
by hand or via `/agent-workflow-kit bridge-settings` (preview-first, consent-gated).
|
|
123
109
|
|
|
124
110
|
## Review mode (`agy-review`)
|
|
@@ -168,12 +154,13 @@ billed turn restating the model's own prose (16,585 → 33,446 tokens on a match
|
|
|
168
154
|
included — **exits 4 with NO receipt**: treat it as a *failed review to re-run*, never a fatal
|
|
169
155
|
session error. The verdict vocabulary is closed (SHIP / SHIP WITH NITS / REWORK), and a
|
|
170
156
|
ship-class verdict arriving beside a numbered `### Blocking` finding is a verdict-body
|
|
171
|
-
contradiction: the same exit 4, NO receipt, both halves named. One stderr banner states the actual posture (`review posture: model=… timeout=…`)
|
|
172
|
-
and the receipt records the same `posture {model}`;
|
|
173
|
-
|
|
174
|
-
pre-spend in every mode. The `
|
|
175
|
-
|
|
176
|
-
|
|
157
|
+
contradiction: the same exit 4, NO receipt, both halves named. One stderr banner states the actual posture (`review posture: model=… source=… timeout=…`)
|
|
158
|
+
and the receipt records the same `posture {model}`; a one-off model — an `AGY_MODEL` differing from
|
|
159
|
+
the host posture, an explicitly empty one included — refuses pre-spend unless `AGY_PROBE=1`, and
|
|
160
|
+
control bytes in a model string refuse pre-spend in every mode. The `source=` field (`model:<s>`,
|
|
161
|
+
`default`, `setting` or `environment`) and the `timeout=` field are **banner-only** (`timeout=` is
|
|
162
|
+
exactly the duration `agy-run` hands to `timeout(1)`; without a capping binary `agy-review` fails
|
|
163
|
+
CLOSED pre-spend) — informational, never a receipt field. **Quote the posture banner verbatim** when labeling a
|
|
177
164
|
dispatch.
|
|
178
165
|
|
|
179
166
|
Every successful review receipt carries integer `durationS` and `blocking`; the wrapper prints
|
|
@@ -192,8 +179,9 @@ release). `agy-review … --nonce <n>` is the plain-argument equivalent
|
|
|
192
179
|
(one seam; flag and a non-empty env must agree, a disagreeing pair refuses pre-spend) — the lane
|
|
193
180
|
for hosts whose dispatch policy has no env-prefix form.
|
|
194
181
|
|
|
195
|
-
|
|
196
|
-
|
|
182
|
+
The review runs on the host posture (the `AGY_MODEL` setting, else `Gemini 3.8 Flash (High)`),
|
|
183
|
+
checked once per run against `agy models`; `AGY_PROBE=1` is the one route to a one-off model, and a
|
|
184
|
+
probe review never attests. An oversized `code` review is **DELIVERED, not refused**: the
|
|
197
185
|
change set is cut into under-cap parts, fed over continuation turns and reviewed in a final turn, and
|
|
198
186
|
the answer must reproduce a line the wrapper picked from each part — a missing or wrong echo is a
|
|
199
187
|
FAILED review (`exit 4`, no receipt). `plan`/`diff` still refuse over the cap (their artifact is a
|
|
@@ -225,12 +213,12 @@ being pointed at any file** (auto-discovery), but that is `agy`'s own mechanism,
|
|
|
225
213
|
Claude-style description-dispatch engine; don't promise more than the probe shows in a given repo.
|
|
226
214
|
**A review, though, must never *depend* on this** — `agy` does not read your repo code or a diff
|
|
227
215
|
without an explicit `--add-dir`, so ground a review **self-contained** via `agy-review --facts` (above)
|
|
228
|
-
rather than relying on `agy` to read the change set. Re-runnable from a project root (
|
|
216
|
+
rather than relying on `agy` to read the change set. Re-runnable from a project root (an `agy-run` probe, so a one-run model is fine):
|
|
229
217
|
|
|
230
218
|
```bash
|
|
231
|
-
AGY_MODEL="
|
|
219
|
+
AGY_MODEL="<an offered display string>" agy-run \
|
|
232
220
|
"Read the cwd context file and state the dialogue language plus one Hard Constraint, in two lines."
|
|
233
|
-
AGY_MODEL="
|
|
221
|
+
AGY_MODEL="<an offered display string>" agy-run \
|
|
234
222
|
"Without me pointing you at any file, name a project-specific skill under .agents/skills/ here and cite its path."
|
|
235
223
|
```
|
|
236
224
|
|
|
@@ -246,10 +234,10 @@ host is governed by the user's agy permissions and sandbox settings, not by the
|
|
|
246
234
|
See [`references/driving-agy.md`](references/driving-agy.md) for the full playbook (delegation
|
|
247
235
|
checklist, prompt templates, output handling). Essentials:
|
|
248
236
|
|
|
249
|
-
- **
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
subscription
|
|
237
|
+
- **The model is the host posture.** Reviews and kept runs use the user's `AGY_MODEL` setting (else
|
|
238
|
+
the default), never a per-call pick. An `agy-run` probe may set `AGY_MODEL` for one run — a
|
|
239
|
+
lighter model for reachability, or another engine for a different opinion, all on the same
|
|
240
|
+
subscription (exact display strings: `/agent-workflow-kit bridge-settings` lists what is offered).
|
|
253
241
|
- **Hand `agy` a self-contained prompt.** It cannot see your conversation — embed the goal,
|
|
254
242
|
constraints, relevant excerpts, and the expected output shape; nothing more.
|
|
255
243
|
- **Continue a thread** with `-- --continue` (most recent) or `-- --conversation <id>` (by id) instead
|
|
@@ -291,13 +279,13 @@ checklist, prompt templates, output handling). Essentials:
|
|
|
291
279
|
parses the envelope; only `stream-json` stays deferred there.) And there is **no `agy inspect`**:
|
|
292
280
|
no machine-readable introspection.
|
|
293
281
|
- Model names must match the `agy models` display strings **exactly**.
|
|
294
|
-
- **Quota is finite.** Heavy use of Pro/Claude models can exhaust the subscription
|
|
295
|
-
|
|
282
|
+
- **Quota is finite.** Heavy use of Pro/Claude models can exhaust the subscription — weigh that when
|
|
283
|
+
choosing the `AGY_MODEL` setting.
|
|
296
284
|
- **A run can't hang forever.** The wrapper caps `agy` with `timeout(1)` (`AGY_HARD_TIMEOUT`,
|
|
297
285
|
default = `AGY_TIMEOUT`) because `agy`'s own `--print-timeout` is **not** a reliable wall-clock
|
|
298
286
|
kill (a run was seen surviving 32 min past a 10m `--print-timeout`). A heavy `--add-dir` agentic
|
|
299
|
-
prompt on a slow model (e.g.
|
|
300
|
-
|
|
287
|
+
prompt on a slow model (e.g. a Pro-class one) can run unbounded — prefer a
|
|
288
|
+
**self-contained prompt** (no `--add-dir`); an "exceeded the hard cap" error is the guard firing.
|
|
301
289
|
- `agy` output may be incomplete or out of date — treat it as advisory until the main agent verifies
|
|
302
290
|
it. (Its FORMAT is not one thing: see the transport section for which wrapper prints text, when,
|
|
303
291
|
and what a failing run prints instead.)
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdirSync, writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
4
|
+
import { join, dirname } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { makeSandbox, farmFor, readReceipts, FED_CAP, seedFedChangeSet } from './agy-review-harness.test.mjs';
|
|
8
|
+
|
|
9
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
const BRIDGE = join(HERE, '..');
|
|
11
|
+
const REVIEW = join(HERE, 'agy-review.sh');
|
|
12
|
+
const AGY_RUN = join(HERE, 'agy.sh');
|
|
13
|
+
const MANIFEST = JSON.parse(readFileSync(join(BRIDGE, 'capability.json'), 'utf8'));
|
|
14
|
+
const REAL_TIMEOUT = spawnSync('bash', ['-c', 'type -P timeout'], { encoding: 'utf8' }).stdout.trim();
|
|
15
|
+
const ESC = String.fromCharCode(27);
|
|
16
|
+
const DEFAULT = 'Gemini 3.8 Flash (High)';
|
|
17
|
+
const SET = 'Claude Opus 4.6 (Thinking)';
|
|
18
|
+
const SECOND = 'Gemini 3.7 Flash (Low)';
|
|
19
|
+
const FACTS = ['code', '--facts', 'a verified fact'];
|
|
20
|
+
|
|
21
|
+
const FAKE_TIMEOUT = [
|
|
22
|
+
'#!/usr/bin/env bash',
|
|
23
|
+
'if [[ " $* " == *" agy models "* ]]; then',
|
|
24
|
+
' while [[ $# -gt 0 && "$1" != "agy" ]]; do shift; done',
|
|
25
|
+
` exec "${REAL_TIMEOUT}" 1 "$@"`,
|
|
26
|
+
'fi',
|
|
27
|
+
`exec "${REAL_TIMEOUT}" "$@"`,
|
|
28
|
+
'',
|
|
29
|
+
].join('\n');
|
|
30
|
+
|
|
31
|
+
const catalogOf = (...displays) => displays.map((display, index) => `id-${index}\t${display}\n`).join('');
|
|
32
|
+
|
|
33
|
+
const sandbox = ({ settings, fakeTimeout = false } = {}) => {
|
|
34
|
+
const sb = makeSandbox();
|
|
35
|
+
sb.settingsFile = join(sb.home, '.config', 'agent-workflow', 'bridge-settings.conf');
|
|
36
|
+
if (settings !== undefined) {
|
|
37
|
+
mkdirSync(dirname(sb.settingsFile), { recursive: true });
|
|
38
|
+
writeFileSync(sb.settingsFile, settings);
|
|
39
|
+
}
|
|
40
|
+
if (fakeTimeout) writeFileSync(join(sb.bin, 'timeout'), FAKE_TIMEOUT, { mode: 0o755 });
|
|
41
|
+
writeFileSync(join(sb.repo, 'plan.md'), '# a plan under review\n');
|
|
42
|
+
writeFileSync(join(sb.repo, 'change.diff'), '--- a/x\n+++ b/x\n@@ -1 +1 @@\n-a\n+b\n');
|
|
43
|
+
return sb;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
let sequence = 0;
|
|
47
|
+
const drive = (sb, args, env = {}, wrapper = REVIEW) => {
|
|
48
|
+
const tag = join(sb.home, `posture-${++sequence}`);
|
|
49
|
+
const r = spawnSync('bash', [wrapper, ...args], {
|
|
50
|
+
cwd: sb.repo, input: '', encoding: 'utf8', timeout: 60000,
|
|
51
|
+
env: {
|
|
52
|
+
HOME: sb.home, PATH: `${sb.bin}:${farmFor(['agy', 'agy-run'])}`, TMPDIR: process.env.TMPDIR ?? '/tmp',
|
|
53
|
+
AGY_FAKE_MODELS_LOG: `${tag}-models`, AGY_FAKE_SENTINEL: `${tag}-sentinel`, AGY_FAKE_ARGV: `${tag}-argv`,
|
|
54
|
+
AGY_FAKE_PROMPT: `${tag}-prompt`, AGY_FAKE_TURNS: `${tag}-turns`, ...env,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
const read = (path) => (existsSync(path) ? readFileSync(path, 'utf8') : '');
|
|
58
|
+
const modelsLog = read(`${tag}-models`);
|
|
59
|
+
return {
|
|
60
|
+
status: r.status, stderr: r.stderr, invoked: existsSync(`${tag}-sentinel`), argv: read(`${tag}-argv`),
|
|
61
|
+
turns: Number(read(`${tag}-turns`) || 0), modelsLog, modelsCalls: modelsLog.split('\n').filter(Boolean).length,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
const runDirect = (sb, env = {}, extra = []) => drive(sb, ['a probe prompt', ...extra], env, AGY_RUN);
|
|
65
|
+
|
|
66
|
+
const quote = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
67
|
+
const remedyRe = (sb, model) => (model === ''
|
|
68
|
+
? /remedy: unset AGY_MODEL in the environment/
|
|
69
|
+
: new RegExp(`remedy: set this line in ${quote(sb.settingsFile)}:\\s+AGY_MODEL=${quote(model)}\\s+and unset AGY_MODEL in the environment`));
|
|
70
|
+
const bannerOf = (stderr) => stderr.split('\n').find((line) => line.startsWith('review posture: ')) ?? '';
|
|
71
|
+
const modelArg = (r) => (r.argv.match(/(?:^|\n)--model\n(.*)\n/) ?? [])[1] ?? null;
|
|
72
|
+
const assertRan = (r, model) => {
|
|
73
|
+
assert.equal(r.status, 0, r.stderr);
|
|
74
|
+
assert.equal(r.invoked, true, 'the CLI ran');
|
|
75
|
+
assert.equal(modelArg(r), model, r.argv);
|
|
76
|
+
};
|
|
77
|
+
const assertRefusedUnspent = (r) => {
|
|
78
|
+
assert.equal(r.status, 2, r.stderr);
|
|
79
|
+
assert.equal(r.invoked, false, 'refused before any run is spent');
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const PARENTS = {
|
|
83
|
+
'review.code': FACTS,
|
|
84
|
+
'review.plan': ['plan', 'plan.md'],
|
|
85
|
+
'review.diff': ['diff', 'change.diff'],
|
|
86
|
+
'review.continue': ['--continue'],
|
|
87
|
+
'review.conversation': ['--conversation', 'conv-1'],
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
describe('agy runs the host posture on both wrappers, with no advisory', () => {
|
|
91
|
+
it('agy-review runs the set model without a probe flag; the banner names source setting', () => {
|
|
92
|
+
const r = drive(sandbox({ settings: `AGY_MODEL=${SET}\n` }), FACTS);
|
|
93
|
+
assertRan(r, SET);
|
|
94
|
+
assert.equal(bannerOf(r.stderr), `review posture: model=${SET} source=model:setting timeout=30m`);
|
|
95
|
+
assert.doesNotMatch(r.stderr, /unknown key|frontier/i);
|
|
96
|
+
});
|
|
97
|
+
it('agy-run runs the set model and prints no posture banner', () => {
|
|
98
|
+
const r = runDirect(sandbox({ settings: `AGY_MODEL=${SET}\n` }));
|
|
99
|
+
assertRan(r, SET);
|
|
100
|
+
assert.doesNotMatch(r.stderr, /posture/);
|
|
101
|
+
});
|
|
102
|
+
for (const [label, settings, env, model] of [
|
|
103
|
+
['the setting restated in the env', `AGY_MODEL=${SET}\n`, { AGY_MODEL: SET }, SET],
|
|
104
|
+
['env Gemini 3.8 Flash (High) on a fresh host', undefined, { AGY_MODEL: DEFAULT }, DEFAULT],
|
|
105
|
+
]) {
|
|
106
|
+
it(`${label} is no one-off: agy-review runs it, source environment`, () => {
|
|
107
|
+
const r = drive(sandbox({ settings }), FACTS, env);
|
|
108
|
+
assertRan(r, model);
|
|
109
|
+
assert.equal(bannerOf(r.stderr), `review posture: model=${model} source=model:environment timeout=30m`);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
describe('an agy one-off is refused pre-spend unless AGY_PROBE=1 — spec:bridge-model/S5', () => {
|
|
115
|
+
it('the probe hook names exactly the parents driven here', () => {
|
|
116
|
+
const hook = MANIFEST.modeCatalog.find((mode) => mode.key === 'AGY_PROBE');
|
|
117
|
+
assert.deepEqual([...hook.parents].sort(), Object.keys(PARENTS).sort());
|
|
118
|
+
});
|
|
119
|
+
const forms = [['env Gemini 3.8 Flash (High)', DEFAULT], ['a second offered non-default model', SECOND], ['an explicitly empty AGY_MODEL=', '']];
|
|
120
|
+
for (const [label, model] of forms) {
|
|
121
|
+
for (const [parent, args] of Object.entries(PARENTS)) {
|
|
122
|
+
it(`${label} on ${parent}: refused with the remedy, run under AGY_PROBE=1`, () => {
|
|
123
|
+
const refusedSb = sandbox({ settings: `AGY_MODEL=${SET}\n` });
|
|
124
|
+
const refused = drive(refusedSb, args, { AGY_MODEL: model });
|
|
125
|
+
assertRefusedUnspent(refused);
|
|
126
|
+
assert.match(refused.stderr, /is a one-off/);
|
|
127
|
+
assert.match(refused.stderr, remedyRe(refusedSb, model));
|
|
128
|
+
const probed = drive(sandbox({ settings: `AGY_MODEL=${SET}\n` }), args, { AGY_MODEL: model, AGY_PROBE: '1' });
|
|
129
|
+
assertRan(probed, model === '' ? null : model);
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
it(`${label}: agy-run, the probe role, runs it`, () => {
|
|
133
|
+
assertRan(runDirect(sandbox({ settings: `AGY_MODEL=${SET}\n` }), { AGY_MODEL: model }), model === '' ? null : model);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('agy-run refuses a model or effort flag after -- on the host posture', () => {
|
|
139
|
+
for (const flag of [['--model', SET], [`--model=${SET}`], ['--effort', 'high'], ['--effort=low']]) {
|
|
140
|
+
it(`refuses ${flag[0]}`, () => {
|
|
141
|
+
const r = runDirect(sandbox(), {}, ['--', ...flag]);
|
|
142
|
+
assertRefusedUnspent(r);
|
|
143
|
+
assert.match(r.stderr, /is not allowed/);
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
describe('an agy model the catalog does not offer is refused with the offered list', () => {
|
|
149
|
+
const laterDefault = catalogOf(SECOND, DEFAULT);
|
|
150
|
+
for (const [label, settings, env] of [['the setting', 'AGY_MODEL=Gemini 9 Ultra\n', {}], ['the environment', undefined, { AGY_MODEL: 'Gemini 9 Ultra', AGY_PROBE: '1' }]]) {
|
|
151
|
+
for (const [wrapper, args] of [[REVIEW, FACTS], [AGY_RUN, ['a probe prompt']]]) {
|
|
152
|
+
it(`a model from ${label} on ${wrapper === REVIEW ? 'agy-review' : 'agy-run'}: the default offered on a later line is set`, () => {
|
|
153
|
+
const sb = sandbox({ settings });
|
|
154
|
+
const r = drive(sb, args, { AGY_FAKE_MODELS: laterDefault, ...env }, wrapper);
|
|
155
|
+
assertRefusedUnspent(r);
|
|
156
|
+
assert.match(r.stderr, /AGY_MODEL='Gemini 9 Ultra' .*not a model the installed agy offers/);
|
|
157
|
+
assert.ok(r.stderr.includes(`offered: ${SECOND}, ${DEFAULT}`), r.stderr);
|
|
158
|
+
assert.match(r.stderr, remedyRe(sb, DEFAULT));
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
it('the default itself is refused when not offered; the remedy names the first offered line', () => {
|
|
163
|
+
const sb = sandbox();
|
|
164
|
+
const r = drive(sb, FACTS, { AGY_FAKE_MODELS: `Fetching...\n${catalogOf(SET, SECOND)}` });
|
|
165
|
+
assertRefusedUnspent(r);
|
|
166
|
+
assert.match(r.stderr, new RegExp(`AGY_MODEL='${quote(DEFAULT)}' \\(default\\)`));
|
|
167
|
+
assert.match(r.stderr, remedyRe(sb, SET));
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('an unreadable agy catalog is one stderr line, one read, and the run proceeds', () => {
|
|
172
|
+
const unreadable = [
|
|
173
|
+
['a failing read', {}, { AGY_FAKE_MODELS_STATUS: '1' }],
|
|
174
|
+
['a timed-out read', { fakeTimeout: true }, { AGY_FAKE_MODELS_SLEEP: '5' }],
|
|
175
|
+
['output without a TAB line', {}, { AGY_FAKE_MODELS: 'Fetching available models...\nno model here\n' }],
|
|
176
|
+
['an empty output', {}, { AGY_FAKE_MODELS: '' }],
|
|
177
|
+
];
|
|
178
|
+
for (const [label, options, env] of unreadable) {
|
|
179
|
+
for (const [wrapper, args] of [[REVIEW, FACTS], [AGY_RUN, ['a probe prompt']]]) {
|
|
180
|
+
it(`${label} on ${wrapper === REVIEW ? 'agy-review' : 'agy-run'}`, () => {
|
|
181
|
+
const r = drive(sandbox(options), args, env, wrapper);
|
|
182
|
+
assertRan(r, DEFAULT);
|
|
183
|
+
assert.equal(r.modelsCalls, 1);
|
|
184
|
+
assert.equal(r.stderr.split('\n').filter((line) => /catalog/.test(line)).length, 1, r.stderr);
|
|
185
|
+
assert.match(r.stderr, /^warning: the agy model catalog is unreadable \(.+\) — the run proceeds/m);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
it('agy-review calls agy models without any of the caller\'s *_API_KEY values', () => {
|
|
190
|
+
const r = drive(sandbox(), FACTS, { FOO_API_KEY: 'foo-key', ANTIGRAVITY_API_KEY: 'agy-key' });
|
|
191
|
+
assertRan(r, DEFAULT);
|
|
192
|
+
assert.equal(r.modelsLog, 'models unset unset\n');
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
describe('agy-review reads the catalog once and its children skip the read — spec:bridge-model/S12', () => {
|
|
197
|
+
for (const [label, env] of [['a readable catalog', {}], ['a failing catalog read', { AGY_FAKE_MODELS_STATUS: '1' }]]) {
|
|
198
|
+
it(`a fed review over ${label} makes exactly one agy models call`, () => {
|
|
199
|
+
const sb = sandbox();
|
|
200
|
+
seedFedChangeSet(sb);
|
|
201
|
+
const r = drive(sb, FACTS, { AGY_MAX_PROMPT_BYTES: String(FED_CAP), ...env });
|
|
202
|
+
assert.equal(r.status, 0, r.stderr);
|
|
203
|
+
assert.ok(r.turns > 1, 'the change set was fed over several agy-run turns');
|
|
204
|
+
assert.equal(r.modelsCalls, 1);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
describe('an explicitly empty AGY_MODEL reads no catalog and drops --model — spec:bridge-model/S13', () => {
|
|
210
|
+
it('a probe agy-review runs without --model, records model null, and makes no agy models call', () => {
|
|
211
|
+
const sb = sandbox({ settings: `AGY_MODEL=${SET}\n` });
|
|
212
|
+
const r = drive(sb, FACTS, { AGY_MODEL: '', AGY_PROBE: '1' });
|
|
213
|
+
assertRan(r, null);
|
|
214
|
+
assert.equal(r.modelsCalls, 0);
|
|
215
|
+
assert.equal(bannerOf(r.stderr), 'review posture: model=<agy settings default> source=model:environment timeout=30m');
|
|
216
|
+
assert.deepEqual(readReceipts(sb.repo).at(-1).posture, { model: null });
|
|
217
|
+
});
|
|
218
|
+
it('a direct agy-run runs without --model and makes no agy models call', () => {
|
|
219
|
+
const r = runDirect(sandbox(), { AGY_MODEL: '' });
|
|
220
|
+
assertRan(r, null);
|
|
221
|
+
assert.equal(r.modelsCalls, 0);
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
describe('no frontier list and no off-frontier warning for any offered model — spec:bridge-model/S14', () => {
|
|
226
|
+
it('agy-review.sh carries no frontier list', () => {
|
|
227
|
+
assert.doesNotMatch(readFileSync(REVIEW, 'utf8'), /frontier/i);
|
|
228
|
+
});
|
|
229
|
+
for (const model of [SECOND, SET]) {
|
|
230
|
+
it(`a host posture of ${model} reviews without a warning`, () => {
|
|
231
|
+
const r = drive(sandbox({ settings: `AGY_MODEL=${model}\n` }), FACTS);
|
|
232
|
+
assertRan(r, model);
|
|
233
|
+
assert.doesNotMatch(r.stderr, /warning/i);
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
describe('the shipped agy text names no 3.5 Flash model or family — spec:bridge-model/S15', () => {
|
|
239
|
+
for (const rel of ['SKILL.md', 'references/models-and-flags.md', 'references/driving-agy.md', 'bin/agy.sh']) {
|
|
240
|
+
it(rel, () => {
|
|
241
|
+
assert.doesNotMatch(readFileSync(join(BRIDGE, rel), 'utf8'), /3[ ._-]?5[\s_-]*flash|flash[\s_-]*3[ ._-]?5/i);
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
describe('a settings-file AGY_MODEL with a control byte or an empty value falls back to the default', () => {
|
|
247
|
+
for (const [label, settings] of [['a control byte', `AGY_MODEL=${SET}${ESC}[0m\n`], ['an empty value', 'AGY_MODEL=\n']]) {
|
|
248
|
+
for (const [wrapper, args] of [[REVIEW, FACTS], [AGY_RUN, ['a probe prompt']]]) {
|
|
249
|
+
it(`${label} on ${wrapper === REVIEW ? 'agy-review' : 'agy-run'} warns without the raw byte`, () => {
|
|
250
|
+
const r = drive(sandbox({ settings }), args, {}, wrapper);
|
|
251
|
+
assertRan(r, DEFAULT);
|
|
252
|
+
assert.equal(r.stderr.includes(ESC), false, 'no raw control byte reaches stderr');
|
|
253
|
+
assert.match(r.stderr, /warning: AGY_MODEL in bridge settings file .* built-in default/);
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
});
|
|
@@ -38,6 +38,13 @@ export const FAKE_AGY = [
|
|
|
38
38
|
' if [[ -n "${AGY_FAKE_HELP_EXTRA:-}" ]]; then printf "%s\\n" "$AGY_FAKE_HELP_EXTRA"; fi',
|
|
39
39
|
' exit 0 ;;',
|
|
40
40
|
' --version) printf "%s\\n" "${AGY_FAKE_VERSION:-1.1.13}"; exit 0 ;;',
|
|
41
|
+
' models)',
|
|
42
|
+
' printf "models %s %s\\n" "${FOO_API_KEY:-unset}" "${ANTIGRAVITY_API_KEY:-unset}" >> "${AGY_FAKE_MODELS_LOG:-/dev/null}"',
|
|
43
|
+
' if [[ -n "${AGY_FAKE_MODELS_SLEEP:-}" ]]; then sleep "$AGY_FAKE_MODELS_SLEEP"; fi',
|
|
44
|
+
' if [[ -n "${AGY_FAKE_MODELS+x}" ]]; then printf "%s" "$AGY_FAKE_MODELS"; else',
|
|
45
|
+
' printf "%s\\t%s\\n" gemini-3.8-flash-high "Gemini 3.8 Flash (High)" gemini-3.7-flash-low "Gemini 3.7 Flash (Low)" claude-opus-4-6-thinking "Claude Opus 4.6 (Thinking)"',
|
|
46
|
+
' fi',
|
|
47
|
+
' exit "${AGY_FAKE_MODELS_STATUS:-0}" ;;',
|
|
41
48
|
'esac',
|
|
42
49
|
': "${AGY_FAKE_ARGV:=/dev/null}"',
|
|
43
50
|
': "${AGY_FAKE_ENV:=/dev/null}"',
|
|
@@ -200,7 +207,7 @@ export const run = (sb, { args, env = {}, cwd, wrapper } = {}) => new Promise((s
|
|
|
200
207
|
sentinel: join(home, `${tag}-sentinel`), adddir: join(home, `${tag}-adddir`),
|
|
201
208
|
adddirMode: join(home, `${tag}-adddir-mode`), artifactMode: join(home, `${tag}-artifact-mode`),
|
|
202
209
|
artifactCopy: join(home, `${tag}-artifact-copy`), turns: join(home, `${tag}-turns`),
|
|
203
|
-
dispatchCwd: join(home, `${tag}-dispatch-cwd`),
|
|
210
|
+
dispatchCwd: join(home, `${tag}-dispatch-cwd`), models: join(home, `${tag}-models`),
|
|
204
211
|
};
|
|
205
212
|
const child = execFile('bash', [wrapper || WRAPPER, ...args], {
|
|
206
213
|
cwd: cwd || repo,
|
|
@@ -214,7 +221,7 @@ export const run = (sb, { args, env = {}, cwd, wrapper } = {}) => new Promise((s
|
|
|
214
221
|
AGY_FAKE_ARGV: cap.argv, AGY_FAKE_ENV: cap.env, AGY_FAKE_PROMPT: cap.prompt,
|
|
215
222
|
AGY_FAKE_SENTINEL: cap.sentinel, AGY_FAKE_ADDDIR: cap.adddir, AGY_FAKE_ADDDIR_MODE: cap.adddirMode,
|
|
216
223
|
AGY_FAKE_ARTIFACT_MODE: cap.artifactMode, AGY_FAKE_ARTIFACT_COPY: cap.artifactCopy,
|
|
217
|
-
AGY_FAKE_TURNS: cap.turns, AGY_FAKE_CWD: cap.dispatchCwd,
|
|
224
|
+
AGY_FAKE_TURNS: cap.turns, AGY_FAKE_CWD: cap.dispatchCwd, AGY_FAKE_MODELS_LOG: cap.models,
|
|
218
225
|
...env,
|
|
219
226
|
},
|
|
220
227
|
}, (error, stdout, stderr) => {
|
|
@@ -232,7 +239,7 @@ export const run = (sb, { args, env = {}, cwd, wrapper } = {}) => new Promise((s
|
|
|
232
239
|
argv: readIf(cap.argv), capEnv: readIf(cap.env), prompt: readIf(cap.prompt),
|
|
233
240
|
adddir: readIf(cap.adddir).trim(), adddirMode: readIf(cap.adddirMode).trim(),
|
|
234
241
|
artifactMode: readIf(cap.artifactMode).trim(), artifactCopy: readIf(cap.artifactCopy),
|
|
235
|
-
dispatchCwd: readIf(cap.dispatchCwd).trim(), turns, prompts, argvs,
|
|
242
|
+
dispatchCwd: readIf(cap.dispatchCwd).trim(), turns, prompts, argvs, modelsLog: readIf(cap.models),
|
|
236
243
|
});
|
|
237
244
|
});
|
|
238
245
|
// Only EPIPE is a refusal working — any other stdin write failure must reach the test.
|