@sabaiway/agent-workflow-kit 14.22.0 → 14.24.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 +74 -0
- package/README.md +1 -1
- package/SKILL.md +1 -1
- package/bridges/antigravity-cli-bridge/SKILL.md +16 -10
- package/bridges/antigravity-cli-bridge/bin/agy-jev.test.mjs +105 -0
- package/bridges/antigravity-cli-bridge/bin/agy-review.sh +20 -6
- package/bridges/antigravity-cli-bridge/bin/agy.sh +26 -11
- package/bridges/antigravity-cli-bridge/capability.json +3 -3
- package/bridges/antigravity-cli-bridge/references/models-and-flags.md +4 -3
- package/bridges/antigravity-cli-bridge/setup/README.md +2 -1
- package/bridges/codex-cli-bridge/SKILL.md +44 -24
- package/bridges/codex-cli-bridge/bin/codex-exec-jev.test.mjs +385 -0
- package/bridges/codex-cli-bridge/bin/codex-exec.sh +262 -42
- package/bridges/codex-cli-bridge/bin/codex-exec.test.mjs +16 -18
- package/bridges/codex-cli-bridge/bin/codex-model-posture.test.mjs +16 -7
- package/bridges/codex-cli-bridge/bin/codex-review-honesty.test.mjs +1 -0
- package/bridges/codex-cli-bridge/bin/codex-review-jev.test.mjs +301 -0
- package/bridges/codex-cli-bridge/bin/codex-review.sh +240 -26
- package/bridges/codex-cli-bridge/bin/codex-review.test.mjs +9 -9
- package/bridges/codex-cli-bridge/capability.json +10 -10
- package/bridges/codex-cli-bridge/references/driving-codex.md +18 -14
- package/bridges/codex-cli-bridge/references/sandbox-and-flags.md +61 -22
- package/bridges/codex-cli-bridge/setup/README.md +8 -5
- package/capability.json +1 -1
- package/package.json +1 -1
- package/references/modes/recommendations.md +5 -4
- package/references/modes/velocity.md +1 -1
- package/tools/ack-write.mjs +1 -1
- package/tools/bridge-sandbox-recipe.mjs +40 -0
- package/tools/detect-backends.mjs +3 -3
- package/tools/manifest/schema.md +9 -9
- package/tools/manifest/validate.mjs +5 -5
- package/tools/recommendations.mjs +17 -46
- package/tools/velocity-profile.mjs +64 -14
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,80 @@ 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.24.0 — the bridge tier also writes the placed bridges' hosts into the project sandbox, so where the host honours the settings sandbox keys a review the harness keeps sandboxed is not refused them (AD-172)
|
|
8
|
+
|
|
9
|
+
**`velocity-profile --bridge-tier` now merges the declared hosts of every placed bridge (each bundled manifest's
|
|
10
|
+
`networkHosts`) into the project `.claude/settings.json` `sandbox.network.allowedDomains`.** Before, the tier wrote
|
|
11
|
+
only its allow rules (the review wrappers' code mode and, with agy-review placed, the grounding pre-step) and the
|
|
12
|
+
wrappers' `sandbox.excludedCommands` entries, and a harness that kept a wrapper
|
|
13
|
+
sandboxed anyway (Claude Code 2.1.291, observed 2026-10-06) refused its hosts until the user added them by hand.
|
|
14
|
+
|
|
15
|
+
- **What the consent covers.** Where the host honours the settings sandbox keys, every sandboxed command of the
|
|
16
|
+
project can then reach those hosts, not only the wrappers. The printed tier notice, `--help`, the velocity and
|
|
17
|
+
recommendations mode docs and a new `bridge-tier` posture note say so; the `--autonomy` preview keeps reporting the
|
|
18
|
+
hosts as a network weakening. `sandbox.filesystem.allowWrite` stays unseeded: the bridges' writable state dirs stay
|
|
19
|
+
hand-apply.
|
|
20
|
+
- **Same writer rules as the other surfaces.** Dry-run by default; foreign hosts and other `sandbox.network` keys
|
|
21
|
+
kept; a host already present left where it is; a second `--apply` changes no byte. The dry-run prints
|
|
22
|
+
`would add sandbox.network.allowedDomains entries: N`, the hosts and `already present (allowedDomains): M`.
|
|
23
|
+
- **Stops with nothing written.** A non-object `sandbox.network` or a non-array `allowedDomains` in the project file
|
|
24
|
+
ends a `--bridge-tier` run as `VELOCITY_MALFORMED` naming the key; an unreadable or unparsable bundled manifest ends
|
|
25
|
+
it naming the manifest. The flagless, `--kit-tools` and `--autonomy` runs read those keys as before.
|
|
26
|
+
- **The advisor offers the re-run.** The Recommendations `bridge-tier` item now also counts missing declared hosts,
|
|
27
|
+
so a project that ran the tier before this release is offered the one re-run at its next upgrade:
|
|
28
|
+
`bridge-wrappers tier incomplete — {n} entr(ies) missing (allow rules, sandbox exclusions or declared hosts of placed bridges)`.
|
|
29
|
+
- **One reader.** The manifest reader moved unchanged into `tools/bridge-sandbox-recipe.mjs`; the tier and the
|
|
30
|
+
sandbox-lane recipe both read it. The tarball holds 359 files.
|
|
31
|
+
|
|
32
|
+
## 14.23.0 — a delegated codex or agy run uses Jev as your own run does; ships codex-cli-bridge 5.0.0 and antigravity-cli-bridge 7.0.0 (AD-171)
|
|
33
|
+
|
|
34
|
+
**Every bridge wrapper now passes `TYPESAFE_API_KEY` to the run, and on Linux with codex-cli 0.160.0 or newer a
|
|
35
|
+
`codex-exec` or `codex-review` run reaches `api.typesafe.ai` and `docs.typesafe.ai` and no other host.** The other
|
|
36
|
+
`*_API_KEY` values and `OPENAI_BASE_URL` stay unset. Where the codex permissions profile is not proven, the run keeps
|
|
37
|
+
the earlier flags with network off and its banner says why. An agy run gets the key and nothing more: no flag, no
|
|
38
|
+
setting.
|
|
39
|
+
|
|
40
|
+
- **One key rule in every wrapper.** The shared block of `codex-exec.sh`, `codex-review.sh`, `agy.sh` and
|
|
41
|
+
`agy-review.sh` gains `aw_scrub_billing_keys`, called before the first CLI spawn (login status, version, catalog,
|
|
42
|
+
run). `test/settings-reader-parity.test.mjs` finds the wrappers from the bridge manifests and
|
|
43
|
+
`test/jev-every-run.test.mjs` proves that discovery on fixture trees, so a wrapper added later is held to the same
|
|
44
|
+
block without a test edit.
|
|
45
|
+
- **codex: the permissions profile `jev`.** On Linux (WSL2 included) with codex-cli 0.160.0 or newer, fresh runs,
|
|
46
|
+
resumes and reviews carry the profile: network to the two Jev hosts only; `codex-exec` writes the repository, `/tmp`
|
|
47
|
+
and `$TMPDIR`; `codex-review` writes nothing. Not Linux, an older or unreadable codex version, an exec repository
|
|
48
|
+
or its git common dir under `/tmp` or `$TMPDIR`, or a version and profile recorded as refused: the run takes the
|
|
49
|
+
earlier flags with network off.
|
|
50
|
+
- **The banner says what the run reaches.** Both codex banners gain `jev=api.typesafe.ai,docs.typesafe.ai` or
|
|
51
|
+
`jev=unreachable (<reason>)` before `source=`; the exec banner's sandbox field reads `sandbox=jev-profile` or
|
|
52
|
+
`sandbox=workspace-write`. Receipt schemas are unchanged.
|
|
53
|
+
- **A refused profile stops loudly.** When codex rejects the profile before its first turn, or bwrap fails inside a
|
|
54
|
+
failed command, the run exits 72 with no success receipt. A rejected profile key records the codex version and
|
|
55
|
+
profile digest in `${XDG_STATE_HOME:-~/.local/state}/agent-workflow/codex-jev-profile-refused` when that file can
|
|
56
|
+
be written (otherwise one warning names it), and that wrapper's next run on that pair takes the earlier flags
|
|
57
|
+
before any spend; a `bwrap: execvp` failure records nothing.
|
|
58
|
+
- **`codex-review` sees the skill.** Its fence home links `.agents/skills` to your real Codex skill root when it
|
|
59
|
+
exists; the review directives allow reading it and name the two hosts on a profile run only.
|
|
60
|
+
- **Probe passthrough under the profile.** With `CODEX_PROBE=1`, `--add-dir`, `-C`, `--cd`, `--enable` and `--disable`
|
|
61
|
+
are refused before any spend (exit 2) while the profile is on; under the fallback they stay admitted.
|
|
62
|
+
- **agy.** With the key set, `agy-run` prints one line before dispatch: `jev: TYPESAFE_API_KEY passed; a headless agy
|
|
63
|
+
runs the Jev request only where your agy permissions allow commands — the first denied command ends the run`.
|
|
64
|
+
`agy-review` makes no Jev request while its model obeys its prompts, which forbid every tool (an advisory
|
|
65
|
+
prohibition, not a sandbox).
|
|
66
|
+
- **Manifests.** Both bridges' `networkHosts` gain `api.typesafe.ai` and `docs.typesafe.ai`, so the sandbox
|
|
67
|
+
Recommendations item shows once more on a project that acknowledged it. Guardrails and banner notes are rewritten,
|
|
68
|
+
and `tools/detect-backends.mjs`'s role-contract copy follows them.
|
|
69
|
+
- **The sandbox notes no longer promise the tier routes wrappers outside.** On 2026-10-06 Claude Code 2.1.291 ran
|
|
70
|
+
an agent's plain `codex-review` inside its sandbox although the project excluded it; a sandboxed run's Jev request
|
|
71
|
+
gets `Tunnel connection failed: 403` from the harness proxy (observed 2026-10-05). `references/modes/recommendations.md`
|
|
72
|
+
now says the tier routes the wrappers outside only where the harness honours that entry, names the refusal, and
|
|
73
|
+
points to the hand-apply `allowedDomains` shape with the recipe's hosts, which the kit never writes for you;
|
|
74
|
+
`references/modes/velocity.md` and the manifest schema's `networkHosts` note say the same.
|
|
75
|
+
- **Stated residuals.** Both bridge `SKILL.md` paragraphs say the key sits in every run's environment; the codex one
|
|
76
|
+
names codex's server-side `web_search`, which reaches pages outside the profile, the agy one a `web_search` or
|
|
77
|
+
fetch tool agy is permitted to run. A codex release that stops recognizing a profile key
|
|
78
|
+
costs one run per version and profile digest before the recorded fallback takes over.
|
|
79
|
+
- The tarball pin rises 355 → 358 (the mirrored bridge suites `codex-exec-jev`, `codex-review-jev`, `agy-jev`).
|
|
80
|
+
|
|
7
81
|
## 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
82
|
|
|
9
83
|
**Your project's rules now state the story flow the engine's canon (5.12.0) defines: four sessions — spec and plan,
|
package/README.md
CHANGED
|
@@ -246,7 +246,7 @@ file), or run the guarded `/agent-workflow-kit uninstall`.
|
|
|
246
246
|
| `/agent-workflow-kit core-evidence` | any time | **the ONE loop-evidence writer** (strip-the-kit) — every core evidence record lands in a single append-only JSONL store inside the git dir (never committable; versioned schema, latest-per-key supersession, byte-identical duplicates refused, malformed lines fail every reader closed). `red-proof "<file>#<pattern>"` declares an observed-red **BEFORE a bugfix** (N/N red runs + content custody + base + the pre-fix fingerprint; green/mixed/timeout are DISTINGUISHED refusals — nothing written); `degrade --backend --reason` is the ONLY escape for an unavailable review backend (per-tree, never all backends); `summary` renders the whole loop state statelessly (gate result · per-backend verdicts · red-proofs · degrades) — no ledger, no rounds, nothing remembered. Honest residual: records are forgeable — self-discipline, not a security boundary. Never commits, never runs a subscription CLI. |
|
|
247
247
|
| `/agent-workflow-kit coverage-check` | any time | **the final-run checker** (D3(c)+(d)) — **certifies coverage ONLY inside the `--final` run that owns the lcov** (ownership is exclusive by CONVENTION over the fixed path, not enforced — a concurrent writer to it is a stated residual, queued as LCOV-EXCLUSIVE-OWNERSHIP): an artifact on disk proves nothing about the tree it came from, so a standalone run prints its findings and states `attested=no` / `NO VERDICT` rather than a PASS (an lcov that predates an edit would otherwise certify a line the suite never executed). `attested=` claims a verdict was ISSUED, pass or fail — a run over uncovered lines still reads `attested=yes` and still exits 1, and a run whose coverage arm never executed (no lcov) reads `attested=no` even inside `--final`, because nothing was read and nothing is certified. The runner passes a nonce whose one-way commitment over `{nonce, fingerprint, base}` is the `final-start.attempt` it recorded; a context describing another tree, or matching no recorded attempt, is a REFUSAL, never a verdict. Findings are unchanged — reads the lcov the declared `unit-tests` gate produced at the FIXED git-dir path and fails on any uncovered CHANGED executable Node line (listed `file:line`; a changed file absent from the map is a file-level red; out-of-domain/unsupported files are LISTED — the claim narrowed honestly); VERIFIES every current-base red-proof declaration (bound test exists · custody hash unchanged · green N/N now · pre-fix fingerprint differs); prints `lcov-sha256=<hex|none>` of the exact bytes it consumed — the sha the `--final` receipt binds and re-hashes. An absent lcov is a LOUD `skipped-no-lcov`; a symlinked path is a refusal. `--check` is the gate exit code — declare it as the LAST gate (`run-gates --final` refuses otherwise). Read-only. |
|
|
248
248
|
| `/agent-workflow-kit commit-guard` | any time | **the read-only pre-commit guard** (D10) — makes the commit capture the whole current working tree, so «verified» and «about to be committed» are the same bytes (the receipt itself has a stated residual — see the mode doc). FIRST it refuses an **INDEX that lags the verified working tree** (the gates and the fingerprint describe the WORKING tree while `git commit` takes the INDEX alone, and the fingerprint cannot tell them apart — so a lagging index used to ship a strict subset of what was verified): unstaged tracked paths or reviewable untracked-not-ignored paths, named up to a bounded cap with the remainder stated, a dirty tracked **submodule** named separately with its own recovery, and fail-closed on an undecidable probe. This deliberately blocks a partial commit. Then it binds the LATEST completed `run-gates --final` receipt to the EXACT current tree: refuses on a missing/red/stale receipt, fingerprint drift under the run, a dangling later attempt, declaration content drift, evidence-hash or lcov drift, or unsatisfied review obligations (the same review-state decision, recomputed over a sanitized env — forged out-of-repo stores never satisfy). Re-runs NO gate or test. Wire it into `.git/hooks/pre-commit` (the installer writes the RESOLVED invocation). `git commit --no-verify` stays the stated residual. |
|
|
249
|
-
| `/agent-workflow-kit recommendations` | any time (every `upgrade` ends with it) | **read-only deployment advisor** (AD-044) — computes what in THIS deployment is configured sub-optimally (allowlist not seeded, autonomy render drifted, OS sandbox unavailable, gates undeclared, bridge friction, sandbox-mask clutter, an unacknowledged sandbox recipe, a feature-spec layer never adopted) and renders **verdict-first**: one composed verdict line (does anything need attention?), then each item as **{severity · what · one-line benefit · an optional `recipe:` line (the sandbox-lane live recipe, the worktrees-dir hand-apply-first grant advice, the agents hidden-mode reconcile follow-up, the spec-adoption decline preview, or another offer's decline preview) · the exact consent-gated apply one-liner}**. The agent PRESENTS the section in the user's conversational language — every fact and count, nothing added or dropped; commands, paths, hosts and rule strings byte-exact; raw tool block on request — and runs EXACTLY the rendered one-liners only on your yes, surfacing each item's posture note first. Renders **present-even-when-empty** (`no recommendations — flow optimal.`); a failed probe degrades to a stated skip line. Registry strings are fact-true frozen one-line data (posture/risk notes live in the mode doc at the consent moment); the
|
|
249
|
+
| `/agent-workflow-kit recommendations` | any time (every `upgrade` ends with it) | **read-only deployment advisor** (AD-044) — computes what in THIS deployment is configured sub-optimally (allowlist not seeded, autonomy render drifted, OS sandbox unavailable, gates undeclared, bridge friction, sandbox-mask clutter, an unacknowledged sandbox recipe, a feature-spec layer never adopted) and renders **verdict-first**: one composed verdict line (does anything need attention?), then each item as **{severity · what · one-line benefit · an optional `recipe:` line (the sandbox-lane live recipe, the worktrees-dir hand-apply-first grant advice, the agents hidden-mode reconcile follow-up, the spec-adoption decline preview, or another offer's decline preview) · the exact consent-gated apply one-liner}**. The agent PRESENTS the section in the user's conversational language — every fact and count, nothing added or dropped; commands, paths, hosts and rule strings byte-exact; raw tool block on request — and runs EXACTLY the rendered one-liners only on your yes, surfacing each item's posture note first. Renders **present-even-when-empty** (`no recommendations — flow optimal.`); a failed probe degrades to a stated skip line. Registry strings are fact-true frozen one-line data (posture/risk notes live in the mode doc at the consent moment); `velocity --bridge-tier` seeds the placed bridges' declared hosts into `sandbox.network.allowedDomains`, `filesystem.allowWrite` stays **HAND-APPLY** territory, and the sandbox-lane item's convergence is a neutral fingerprint acknowledgement recorded by a consent-gated ack writer into `docs/ai/acks.json` — never a security key (the recipe is documented per bridge in `capability.json` `networkHosts` + `writableDirs`). `--cwd` is required (the target project is explicit); never writes, never commits, never runs a subscription CLI. |
|
|
250
250
|
| `/agent-workflow-kit doc-parity` | any time | **read-only doc-parity lint** (AD-049) — kills the doc-drift class where a mode-contract doc silently lags a code constant (a `--check` doc still reading `300` after the diff cap moved to `400`): a **closed, exported registry** binds each live constant (review caps, schema versions, the ledger's own class/scope vocabulary, and the autonomy-doctor EXIT/status/trusted-dir contract) to the exact token its `references/modes/*.md` contract must carry, and asserts the CURRENT value renders into every bound file — a drifted doc, an unreadable file, or an absent token **fails closed**. The values are sourced from the live imports (never re-typed), so the lint can't itself go stale; adding a binding is adding a checked entry (closed-world, edit-safe). `--check` is the gate exit code a kit maintainer runs: the lint checks the kit's own package. Never writes, never commits, never runs a subscription CLI. |
|
|
251
251
|
| `/agent-workflow-kit dispatch` | any time | **the delegation engine** — makes "how much does delegating a sub-task actually buy?" a measured number instead of a feeling. `check <file>` validates a sub-task brief's contract block and exits 0/1 naming the first violated field — **form only**: whether the task is genuinely bounded, its design decided and its acceptance adequate stays your judgment, and a well-formed absurdity passes here by construction. `advise --step-class <c>` answers the question that comes BEFORE that one — which vehicle should carry this kind of sub-task, whether it is even present on this machine, and what the ledger has already recorded for threads of that class (finished, failed, closed without a fold, and still open — counted separately) — and it decides nothing: it refuses no dispatch, gates no verb, and a choice that diverges from the advice is noted rather than blocked. It reads the filesystem only: nothing is launched to find out what is installed, an absent ledger prints "no recorded history", an unreadable one prints the store's own words, and either way the advice still prints. The same block appears as a footer under a form-valid `check` — never under a refusal, so it can never hide one. `register` pre-registers an acceptance wave (step classes, pairing key, minimum observations per class, the mean and first-pass thresholds) so the bar can never be chosen after the results it judges — immutable per wave. `observe` records ONE hand-written observation (`solo-construction`, the baseline whose ratio is 1 by construction — except over a scope measuring zero bytes, which has no ratio at all and is recorded ineligible by name — or `self-reported`) — delegated numbers are never hand-written, they are derived. The writer verbs are where that derivation happens: `open` puts a delegated thread on the record with every mint-time field copied from the brief's own header, refusing a deadline the wrapper's cap plus its kill grace would not fit inside, and recording whether the tree it started from was clean against the thread's base — HEAD by default, or the tree `--checkpoint <oid>` names, so the second task of a story is measured over the first one's uncommitted work instead of opening dirty (a dirty one makes the result honestly unmeasurable rather than quietly counted), while `--task <brief>` also records which task brief the thread carries out and the files that brief lists, refusing, since the last commit, a task thread whose files overlap by path prefix those of an open task thread or of a folded thread of another brief (`files-overlap`), and a mix of the two kinds — an untasked open beside an open task thread or, for a `code` open of backend `codex`, after a task fold, and a task open beside an open untasked thread (`mixed-open`), refusing at open a Files path the snapshot scope can never hold (`task-files-scope`), and measuring a task thread over its files alone, so `return` and `fold` refuse a changed path that no open or folded task thread on the same base claims (`out-of-files`); `await` waits for that one dispatch to answer and writes nothing — only the finished receipt satisfies it, a run still holding the nonce means keep waiting, and a wait that runs out says so with its own exit code, names whether the deadline or your own timeout ended it, and authorizes nothing: a wait that ended without an answer is a question for you, never permission to dispatch again; `return` absorbs the receipt the wrapper minted — only a finished one, checked against the contract it actually ran, its deadline and its own report, and refused outright when the tree hides changes from git or moves while the return is being computed — and derives the bytes from git rather than from anyone's claim; `fold` is the acceptance, and it refuses if the tree moved since the return — precisely, it re-confirms **equality of the visible canonical payload**, which is a change detector rather than a cryptographic identity of the tree: under the default head base that payload's untracked section is unframed and carries no file mode, so a content or symlink target that imitates the marker opening the next entry can alias two trees, and making an untracked file executable moves nothing (both named as residuals in the mode doc, with the fix queued); under a checkpoint base it is one indexed diff that carries modes. A change set carrying a binary, a non-regular path, a submodule, or a symlink whose target is not valid UTF-8 is **refused** at `return` and `fold` — CONSERVATIVELY, rather than promise what the default head payload may not be able to follow — and the same refusals hold for a checkpoint thread. `degrade` closes a thread that never earned a fold, on the record and with its reason. `aggregate` prints one wave: the registered thresholds, every observation as context, and per step class the finished threads — a folded success with git-provable bytes contributes its ratio, one whose bytes are unprovable is excluded from the mean but still counts in the first-pass rate, and a failed thread, a degrade-closed one, or one folded after failing acceptance each count as a real zero. Below the registered minimum nothing is computed (insufficient); at or above it is computed and labeled PILOT evidence. It REFUSES rather than guessing: no pre-registration, an unfinished thread in scope, a recorded refusal-to-delegate that never opened a thread, or an ambiguous wave each stop the computation by name. `handoff-return --slug <s>` closes the worktree loop after `land --prepare`: it re-attests that main still holds exactly the prepared tree under the same HEAD (both recorded in the handoff at prepare time), prints the satellite handoff's user-owned content byte for byte with its boundaries, byte lengths and the main-owned destinations each part folds into, prints the handoff digest and both OIDs as its proof, states the after-the-fold order (a fold landed after the gates leaves them stale), and records ONE self-reported worktree-stream observation only when the whole prepared change set is measurable — a deletion, a rename, a symlink, a submodule or a mode-only change ends with a named NOT RECORDED instead, never a partial number. Appends only to its own ledger inside the git dir (never committable); it also writes `handoff-return`'s `write-tree` tree object and, for a checkpoint measurement, the leaf's OWN temporary index file and the blobs its forced adds store — never a ref, never the real index; never commits, never runs a subscription CLI. |
|
|
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. |
|
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.24.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: '7.0.0'
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# antigravity-cli-bridge
|
|
@@ -43,9 +43,10 @@ wrapper [`bin/agy.sh`](bin/agy.sh) on `PATH` as `agy-run`.
|
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
Never read, print, copy, commit, or package that token — it is personal and is **never bundled** with
|
|
46
|
-
this skill. The wrapper [`bin/agy.sh`](bin/agy.sh) **unsets every `*_API_KEY
|
|
47
|
-
`GEMINI_API_KEY`, `GOOGLE_API_KEY`, `GOOGLE_GENAI_API_KEY`
|
|
48
|
-
|
|
46
|
+
this skill. The wrapper [`bin/agy.sh`](bin/agy.sh) **unsets `OPENAI_BASE_URL` and every `*_API_KEY` except
|
|
47
|
+
`TYPESAFE_API_KEY`** (`ANTIGRAVITY_API_KEY`, `GEMINI_API_KEY`, `GOOGLE_API_KEY`, `GOOGLE_GENAI_API_KEY`
|
|
48
|
+
included; `TYPESAFE_API_KEY` passes as given) before invoking `agy`, so a stray key can never silently
|
|
49
|
+
switch you to pay-as-you-go billing.
|
|
49
50
|
|
|
50
51
|
**Caveat:** the subscription has a finite quota. Keep probes short (see *How the main agent drives
|
|
51
52
|
agy*); the model every review and kept run uses is the host posture below, never a per-call pick.
|
|
@@ -223,11 +224,15 @@ AGY_MODEL="<an offered display string>" agy-run \
|
|
|
223
224
|
```
|
|
224
225
|
|
|
225
226
|
**The Jev skill in a delegated run.** A headless `agy-run` loads the vendor's Jev skill from the user root the kit's
|
|
226
|
-
`jev-skill` command fills, `~/.gemini/config/skills`
|
|
227
|
-
root was listed,
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
227
|
+
`jev-skill` command fills, `~/.gemini/config/skills` (measured 2026-10-02 on agy 1.2.14: a probe skill under that
|
|
228
|
+
root was listed, none under `~/.gemini/antigravity-cli/skills`), and `TYPESAFE_API_KEY` passes the wrappers as given
|
|
229
|
+
while every other `*_API_KEY` is unset; nothing in agy's permissions is widened, no flag is added and no setting is
|
|
230
|
+
written. With the key set, `agy-run` prints one line before dispatch: a headless agy runs the Jev request only where
|
|
231
|
+
the user's agy permissions allow commands, and the first denied command ends the run. `agy-review` makes no Jev
|
|
232
|
+
request while its model obeys its prompts, which forbid every tool; that prohibition is advisory, not a sandbox. The documented opt-in `-- --dangerously-skip-permissions`
|
|
233
|
+
stays admitted, and such a run carries the key into an agy whose network and file tool are unconfined. Two residuals
|
|
234
|
+
stand: the key now sits in every run's environment, and a `web_search` or fetch tool agy is permitted to run may reach
|
|
235
|
+
hosts the bridge never names.
|
|
231
236
|
|
|
232
237
|
## How the main agent drives `agy` efficiently
|
|
233
238
|
|
|
@@ -250,7 +255,8 @@ checklist, prompt templates, output handling). Essentials:
|
|
|
250
255
|
writes (branch/add/commit/stash/reset/rewrite) with the orchestrator, and tell `agy` in the prompt
|
|
251
256
|
to return findings only. Add `-- --sandbox` when delegating anything that could trigger
|
|
252
257
|
terminal/tool work; opt into `-- --add-dir . --dangerously-skip-permissions` only for a flow that
|
|
253
|
-
genuinely needs writes, then review the diff afterwards
|
|
258
|
+
genuinely needs writes, then review the diff afterwards (such a run also carries `TYPESAFE_API_KEY`,
|
|
259
|
+
when set, into an agy whose network and file tool are unconfined).
|
|
254
260
|
|
|
255
261
|
## Complementary skills (optional, standalone-first)
|
|
256
262
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// agy-jev.test.mjs — spec jev-every-run, the agy group (D5(a)): agy-run and agy-review pass TYPESAFE_API_KEY by the
|
|
2
|
+
// shared key rule and widen nothing; agy-run prints its one limit line, never as agy-review's child; the documented
|
|
3
|
+
// opt-in stays admitted. A recording stub stands in front of the harness fake agy.
|
|
4
|
+
import { describe, it, after } from 'node:test';
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { writeFileSync, readFileSync, existsSync, rmSync } from 'node:fs';
|
|
7
|
+
import { join, dirname } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { spawnSync } from 'node:child_process';
|
|
10
|
+
import { makeSandbox, farmFor, FAKE_AGY } from './agy-review-harness.test.mjs';
|
|
11
|
+
|
|
12
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const REVIEW = join(HERE, 'agy-review.sh');
|
|
14
|
+
const AGY_RUN = join(HERE, 'agy.sh');
|
|
15
|
+
const LIMIT = `jev: TYPESAFE_API_KEY passed; a headless agy runs the Jev request only where your agy permissions allow commands ${String.fromCharCode(0x2014)} the first denied command ends the run`;
|
|
16
|
+
const KEYS = { TYPESAFE_API_KEY: 'jev-key', ANTIGRAVITY_API_KEY: 'a', GEMINI_API_KEY: 'g', GOOGLE_API_KEY: 'o', GOOGLE_GENAI_API_KEY: 'n', FOO_API_KEY: 'foo' };
|
|
17
|
+
const PASSED = 'key=[jev-key] google=unset,unset,unset,unset foo=unset';
|
|
18
|
+
|
|
19
|
+
// Every spawn of agy appends its kind and the key states; a run also says so on stderr before the fake answers.
|
|
20
|
+
const RECORDER = [
|
|
21
|
+
'#!/usr/bin/env bash',
|
|
22
|
+
'st() { if [[ -n "${!1+x}" ]]; then printf "[%s]" "${!1}"; else printf unset; fi; }',
|
|
23
|
+
'case "${1:-}" in --help|-h) kind=help ;; --version) kind=version ;; models) kind=models ;; *) kind=run; echo STUB-DISPATCH >&2 ;; esac',
|
|
24
|
+
'echo "$kind key=$(st TYPESAFE_API_KEY) google=$(st ANTIGRAVITY_API_KEY),$(st GEMINI_API_KEY),$(st GOOGLE_API_KEY),$(st GOOGLE_GENAI_API_KEY) foo=$(st FOO_API_KEY) child=$(st AW_AGY_REVIEW_CHILD)" >>"$AGY_JEV_LOG"',
|
|
25
|
+
'exec "$AGY_JEV_FAKE" "$@"',
|
|
26
|
+
'',
|
|
27
|
+
].join('\n');
|
|
28
|
+
|
|
29
|
+
const made = [];
|
|
30
|
+
after(() => {
|
|
31
|
+
for (const home of made) rmSync(home, { recursive: true, force: true });
|
|
32
|
+
});
|
|
33
|
+
const sandbox = () => {
|
|
34
|
+
const sb = makeSandbox();
|
|
35
|
+
made.push(sb.home);
|
|
36
|
+
writeFileSync(join(sb.home, 'agy-fake'), FAKE_AGY, { mode: 0o755 });
|
|
37
|
+
writeFileSync(join(sb.bin, 'agy'), RECORDER, { mode: 0o755 });
|
|
38
|
+
writeFileSync(join(sb.repo, 'plan.md'), '# a plan under review\n');
|
|
39
|
+
return sb;
|
|
40
|
+
};
|
|
41
|
+
const drive = (sb, wrapper, args, env = {}) => {
|
|
42
|
+
const cap = (name) => join(sb.home, name);
|
|
43
|
+
for (const name of ['jev-log', 'jev-argv']) rmSync(cap(name), { force: true });
|
|
44
|
+
const r = spawnSync('bash', [wrapper, ...args], {
|
|
45
|
+
cwd: sb.repo, input: '', encoding: 'utf8', timeout: 60000,
|
|
46
|
+
env: { HOME: sb.home, PATH: `${sb.bin}:${farmFor(['agy', 'agy-run'])}`, TMPDIR: process.env.TMPDIR ?? '/tmp',
|
|
47
|
+
AGY_JEV_LOG: cap('jev-log'), AGY_JEV_FAKE: cap('agy-fake'), AGY_FAKE_ARGV: cap('jev-argv'), ...env },
|
|
48
|
+
});
|
|
49
|
+
const read = (name) => (existsSync(cap(name)) ? readFileSync(cap(name), 'utf8') : '');
|
|
50
|
+
const spawns = read('jev-log').split('\n').filter(Boolean).map((line) => [line.slice(0, line.indexOf(' ')), line.slice(line.indexOf(' ') + 1)]);
|
|
51
|
+
return { status: r.status, stdout: r.stdout, stderr: r.stderr, spawns, argv: read('jev-argv').split('\n').slice(0, -1) };
|
|
52
|
+
};
|
|
53
|
+
const runDirect = (sb, env, extra = []) => drive(sb, AGY_RUN, ['a probe prompt', ...extra], env);
|
|
54
|
+
const keysOf = (env) => env.split(' child=')[0];
|
|
55
|
+
const runSpawn = (r) => (r.spawns.find(([kind]) => kind === 'run') ?? [])[1] ?? '';
|
|
56
|
+
const limitLines = (r) => r.stderr.split('\n').filter((line) => line.startsWith('jev:'));
|
|
57
|
+
|
|
58
|
+
describe('agy-run and agy-review pass the key and unset the rest — spec:jev-every-run/S4', () => {
|
|
59
|
+
for (const [label, wrapper, args] of [['agy-run', AGY_RUN, ['a probe prompt']], ['agy-review plan', REVIEW, ['plan', 'plan.md']]]) {
|
|
60
|
+
it(`${label}: every spawn of agy sees TYPESAFE_API_KEY with the four Google keys and FOO_API_KEY unset`, () => {
|
|
61
|
+
const r = drive(sandbox(), wrapper, args, KEYS);
|
|
62
|
+
assert.equal(r.status, 0, r.stderr);
|
|
63
|
+
assert.ok(r.spawns.some(([kind]) => kind === 'run'), 'agy ran');
|
|
64
|
+
for (const [kind, env] of r.spawns) assert.equal(keysOf(env), PASSED, `${kind} spawn`);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe('the agy half of S5: an empty key and an unset key on agy-run', () => {
|
|
70
|
+
it('an empty TYPESAFE_API_KEY reaches agy empty, an unset one stays unset, and the argv is the same with the key set', () => {
|
|
71
|
+
const runs = ['jev-key', '', undefined].map((value) => runDirect(sandbox(), { TYPESAFE_API_KEY: value }));
|
|
72
|
+
assert.deepEqual(runs.map((r) => keysOf(runSpawn(r)).split(' ')[0]), ['key=[jev-key]', 'key=[]', 'key=unset']);
|
|
73
|
+
assert.deepEqual([runs[1].argv, runs[2].argv], [runs[0].argv, runs[0].argv]);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
describe('agy-run prints its one limit line — spec:jev-every-run/S20', () => {
|
|
78
|
+
it('a non-empty key: exactly the one limit line, before dispatch; an empty or an unset key: none', () => {
|
|
79
|
+
const r = runDirect(sandbox(), { TYPESAFE_API_KEY: 'jev-key' });
|
|
80
|
+
assert.equal(r.status, 0, r.stderr);
|
|
81
|
+
assert.deepEqual(limitLines(r), [LIMIT]);
|
|
82
|
+
const lines = r.stderr.split('\n');
|
|
83
|
+
assert.ok(lines.indexOf(LIMIT) < lines.indexOf('STUB-DISPATCH'), r.stderr);
|
|
84
|
+
for (const value of ['', undefined]) {
|
|
85
|
+
const quiet = runDirect(sandbox(), { TYPESAFE_API_KEY: value });
|
|
86
|
+
assert.equal(quiet.status, 0, quiet.stderr);
|
|
87
|
+
assert.deepEqual(limitLines(quiet), [], JSON.stringify(value));
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
it('as agy-review\'s child: agy-run runs marked AW_AGY_REVIEW_CHILD=1 with the key, and no limit line is printed', () => {
|
|
91
|
+
const r = drive(sandbox(), REVIEW, ['plan', 'plan.md'], { TYPESAFE_API_KEY: 'jev-key' });
|
|
92
|
+
assert.equal(r.status, 0, r.stderr);
|
|
93
|
+
assert.equal(runSpawn(r), `${PASSED} child=[1]`);
|
|
94
|
+
assert.deepEqual(limitLines(r), []);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe('the documented opt-in stays admitted — spec:jev-every-run/S21', () => {
|
|
99
|
+
it('agy-run -- --dangerously-skip-permissions reaches agy\'s argv, the key beside it', () => {
|
|
100
|
+
const r = runDirect(sandbox(), { TYPESAFE_API_KEY: 'jev-key' }, ['--', '--dangerously-skip-permissions']);
|
|
101
|
+
assert.equal(r.status, 0, r.stderr);
|
|
102
|
+
assert.ok(r.argv.includes('--dangerously-skip-permissions'), r.argv.join(' '));
|
|
103
|
+
assert.equal(keysOf(runSpawn(r)), PASSED);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
@@ -372,11 +372,24 @@ aw_resolve_timeout_bin() {
|
|
|
372
372
|
[[ -f "$bin" && -x "$bin" ]] || { printf ''; return 0; }
|
|
373
373
|
printf '%s' "$bin"
|
|
374
374
|
}
|
|
375
|
+
aw_scrub_billing_keys() {
|
|
376
|
+
# The one key rule (spec jev-every-run): OPENAI_BASE_URL and every *_API_KEY are unset, so a stray key can never
|
|
377
|
+
# switch a run to paid billing; TYPESAFE_API_KEY is neither read nor set, so it reaches the CLI exactly as given.
|
|
378
|
+
local name
|
|
379
|
+
unset OPENAI_BASE_URL 2>/dev/null || true
|
|
380
|
+
for name in $(compgen -v); do
|
|
381
|
+
case "$name" in
|
|
382
|
+
TYPESAFE_API_KEY) ;;
|
|
383
|
+
*_API_KEY) unset "$name" 2>/dev/null || true ;;
|
|
384
|
+
esac
|
|
385
|
+
done
|
|
386
|
+
return 0
|
|
387
|
+
}
|
|
375
388
|
|
|
376
389
|
# Review-receipt identity (AD-038). AW_BRIDGE_VERSION mirrors this bridge's SKILL.md/capability.json
|
|
377
390
|
# version (drift-guarded by agy-review.test.mjs against capability.json).
|
|
378
391
|
AW_RECEIPT_BACKEND="agy"
|
|
379
|
-
AW_BRIDGE_VERSION="
|
|
392
|
+
AW_BRIDGE_VERSION="7.0.0" # aw-version-anchor
|
|
380
393
|
# D5 control-byte screen — IMMEDIATELY after resolution, BEFORE any interpolation can echo raw
|
|
381
394
|
# newline/ESC bytes into stderr/the terminal (round-2 fold).
|
|
382
395
|
if [[ "$AGY_MODEL" == *[$'\x01'-$'\x1f'$'\x7f']* ]]; then
|
|
@@ -563,12 +576,9 @@ read_agy_envelope() { # $1 = captured payload, $2 = response destination, $3 =
|
|
|
563
576
|
node "$AGY_ENVELOPE_READER" "${args[@]}"
|
|
564
577
|
}
|
|
565
578
|
|
|
566
|
-
# --- Subscription invariant
|
|
579
|
+
# --- Subscription invariant: the shared key rule, before the first spawn of agy --------
|
|
567
580
|
export PATH="$HOME/.local/bin:$PATH"
|
|
568
|
-
|
|
569
|
-
while IFS= read -r _api_key_var; do
|
|
570
|
-
unset "$_api_key_var" 2>/dev/null || true
|
|
571
|
-
done < <(compgen -v 2>/dev/null | grep '_API_KEY$' || true)
|
|
581
|
+
aw_scrub_billing_keys
|
|
572
582
|
|
|
573
583
|
if ! command -v agy >/dev/null 2>&1; then
|
|
574
584
|
echo "error: 'agy' (Antigravity CLI) not found on PATH. See this skill's setup/README.md." >&2
|
|
@@ -763,6 +773,10 @@ aw_agy_catalog_check() {
|
|
|
763
773
|
exit 2
|
|
764
774
|
}
|
|
765
775
|
export AW_AGY_CATALOG_READ=1
|
|
776
|
+
# Every child agy-run is this review's (spec jev-every-run): its prompts forbid every tool, so a review whose model
|
|
777
|
+
# obeys them sends no request to api.typesafe.ai (an advisory prohibition, not a sandbox), and the child prints no
|
|
778
|
+
# TYPESAFE_API_KEY limit line.
|
|
779
|
+
export AW_AGY_REVIEW_CHILD=1
|
|
766
780
|
if [[ -n "$AGY_MODEL" ]]; then aw_agy_catalog_check "$aw_review_timeout_bin" "$DEFAULT_AGY_REVIEW_MODEL"; fi
|
|
767
781
|
if [[ "$AGY_MODEL" != "$HOST_AGY_MODEL" && "$AGY_PROBE" != "1" ]]; then
|
|
768
782
|
echo "error: $(aw_agy_shown_model) is a one-off — the host posture is '$HOST_AGY_MODEL', and only a probe (AGY_PROBE=1) runs a one-off; refusing before any run is spent." >&2
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
#
|
|
9
9
|
# Auth: SUBSCRIPTION ONLY. We use the cached OAuth token at
|
|
10
10
|
# ~/.gemini/antigravity-cli/antigravity-oauth-token (Google AI Pro) and the
|
|
11
|
-
# quota that comes with it. To make that guarantee hard, the wrapper unsets
|
|
12
|
-
#
|
|
13
|
-
# pay-as-you-go billing.
|
|
11
|
+
# quota that comes with it. To make that guarantee hard, the wrapper unsets
|
|
12
|
+
# every *_API_KEY except TYPESAFE_API_KEY (which passes as given), so a stray
|
|
13
|
+
# key can never silently switch us to paid pay-as-you-go billing.
|
|
14
14
|
#
|
|
15
15
|
# This is a THIN, FLOW-AGNOSTIC wrapper on purpose: it just runs one headless
|
|
16
16
|
# prompt and prints the text response. It does NOT encode any orchestration
|
|
@@ -65,14 +65,6 @@ esac
|
|
|
65
65
|
# 1. Make `agy` findable even when ~/.bashrc was not sourced.
|
|
66
66
|
export PATH="$HOME/.local/bin:$PATH"
|
|
67
67
|
|
|
68
|
-
# 2. Force the subscription path: never let an API key hijack billing. Unset EVERY *_API_KEY for the
|
|
69
|
-
# agy subprocess — the explicit Google/Antigravity ones first, then any other *_API_KEY that may
|
|
70
|
-
# have been added later (`compgen` is a bash builtin; the shebang guarantees bash).
|
|
71
|
-
unset ANTIGRAVITY_API_KEY GEMINI_API_KEY GOOGLE_API_KEY GOOGLE_GENAI_API_KEY 2>/dev/null || true
|
|
72
|
-
while IFS= read -r _api_key_var; do
|
|
73
|
-
unset "$_api_key_var" 2>/dev/null || true
|
|
74
|
-
done < <(compgen -v 2>/dev/null | grep '_API_KEY$' || true)
|
|
75
|
-
|
|
76
68
|
# This wrapper's applied settings-file subset (see the shared reader block below).
|
|
77
69
|
AW_SETTINGS_APPLIED="AGY_HARD_TIMEOUT"
|
|
78
70
|
DEFAULT_AGY_MODEL="Gemini 3.8 Flash (High)"
|
|
@@ -262,6 +254,23 @@ aw_resolve_timeout_bin() {
|
|
|
262
254
|
[[ -f "$bin" && -x "$bin" ]] || { printf ''; return 0; }
|
|
263
255
|
printf '%s' "$bin"
|
|
264
256
|
}
|
|
257
|
+
aw_scrub_billing_keys() {
|
|
258
|
+
# The one key rule (spec jev-every-run): OPENAI_BASE_URL and every *_API_KEY are unset, so a stray key can never
|
|
259
|
+
# switch a run to paid billing; TYPESAFE_API_KEY is neither read nor set, so it reaches the CLI exactly as given.
|
|
260
|
+
local name
|
|
261
|
+
unset OPENAI_BASE_URL 2>/dev/null || true
|
|
262
|
+
for name in $(compgen -v); do
|
|
263
|
+
case "$name" in
|
|
264
|
+
TYPESAFE_API_KEY) ;;
|
|
265
|
+
*_API_KEY) unset "$name" 2>/dev/null || true ;;
|
|
266
|
+
esac
|
|
267
|
+
done
|
|
268
|
+
return 0
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# 2. Force the subscription path before the first spawn of agy: the shared key rule (the Google and
|
|
272
|
+
# Antigravity keys included) — no API key can hijack billing.
|
|
273
|
+
aw_scrub_billing_keys
|
|
265
274
|
|
|
266
275
|
if ! command -v agy >/dev/null 2>&1; then
|
|
267
276
|
echo "error: 'agy' (Antigravity CLI) not found on PATH. Install it and run 'agy' once to sign in." >&2
|
|
@@ -422,6 +431,12 @@ fi
|
|
|
422
431
|
|
|
423
432
|
agy_cmd=(agy "${model_flag[@]}" --print-timeout "$AGY_TIMEOUT" "${passthrough[@]}" -p "$prompt")
|
|
424
433
|
|
|
434
|
+
# A non-empty TYPESAFE_API_KEY rides into agy and nothing is widened (spec jev-every-run, D5(a)): the limit, once,
|
|
435
|
+
# before dispatch — never in agy-review's child, whose prompts forbid every tool.
|
|
436
|
+
if [[ -n "${TYPESAFE_API_KEY:-}" && "${AW_AGY_REVIEW_CHILD:-}" != "1" ]]; then
|
|
437
|
+
echo "jev: TYPESAFE_API_KEY passed; a headless agy runs the Jev request only where your agy permissions allow commands — the first denied command ends the run" >&2
|
|
438
|
+
fi
|
|
439
|
+
|
|
425
440
|
# Hard wall-clock cap via timeout(1) (GNU `timeout` on Linux, `gtimeout` from coreutils on macOS).
|
|
426
441
|
# This is the real guard — a backgrounded, hung agy survives its own --print-timeout otherwise.
|
|
427
442
|
# aw_resolve_timeout_bin: builtin type -P (an exported function can shadow neither `timeout` nor
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"schema": 1,
|
|
4
4
|
"name": "antigravity-cli-bridge",
|
|
5
5
|
"kind": "execution-backend",
|
|
6
|
-
"version": "
|
|
6
|
+
"version": "7.0.0",
|
|
7
7
|
"provides": ["review", "probe"],
|
|
8
8
|
"posture": { "model": "Gemini 3.8 Flash (High)" },
|
|
9
9
|
"roles": {
|
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
{ "slot": "<extra agy flags...>", "required": false, "description": "raw agy flags after -- (a --model* or --effort* flag refuses pre-spend: the model is the host posture or the AGY_MODEL environment value)" }
|
|
181
181
|
],
|
|
182
182
|
"guardrails": [
|
|
183
|
-
{ "value": "subscription-only — every *_API_KEY env var is unset before the run", "enforcement": "enforced", "source": "bin/agy.sh" },
|
|
183
|
+
{ "value": "subscription-only — every *_API_KEY env var except TYPESAFE_API_KEY is unset before the run; TYPESAFE_API_KEY passes as given and nothing in agy's permissions is widened", "enforcement": "enforced", "source": "bin/agy.sh" },
|
|
184
184
|
{ "value": "the model is the host posture (AGY_MODEL in the bridge settings file, else Gemini 3.8 Flash (High)); AGY_MODEL in the environment sets it for one run", "enforcement": "enforced", "source": "bin/agy.sh" },
|
|
185
185
|
{ "value": "hard wall-clock cap AGY_HARD_TIMEOUT (built-in default 5m)", "enforcement": "enforced", "condition": "only while timeout(1)/gtimeout is on PATH — otherwise the wrapper warns and runs uncapped", "source": "capability.json settings.AGY_HARD_TIMEOUT" },
|
|
186
186
|
{ "value": "the prompt rides a single argv — over AGY_MAX_PROMPT_BYTES (120000) the run refuses", "enforcement": "enforced", "source": "bin/agy.sh" },
|
|
@@ -240,7 +240,7 @@
|
|
|
240
240
|
"effect": "agy-review code: the ceiling on the SUM of all outgoing prompt bytes the chunked feed may send (every feed-turn framing block, every body, the final turn). Checked BEFORE the first turn is spent; past it the fed review refuses. An economy guard, not the correctness guard — correctness is the per-part delivery echo."
|
|
241
241
|
}
|
|
242
242
|
],
|
|
243
|
-
"networkHosts": ["*.googleapis.com", "accounts.google.com", "antigravity-unleash.goog", "lh3.googleusercontent.com"],
|
|
243
|
+
"networkHosts": ["*.googleapis.com", "accounts.google.com", "antigravity-unleash.goog", "lh3.googleusercontent.com", "api.typesafe.ai", "docs.typesafe.ai"],
|
|
244
244
|
"writableDirs": [{ "env": null, "default": "~/.gemini/antigravity-cli" }],
|
|
245
245
|
"detect": {
|
|
246
246
|
"installed": {
|
|
@@ -51,9 +51,10 @@ Environment:
|
|
|
51
51
|
| `AGY_HARD_TIMEOUT` | `= AGY_TIMEOUT` | hard `timeout(1)` wall-clock cap (a duration string) |
|
|
52
52
|
| `AGY_MAX_PROMPT_BYTES` | `120000` | single-argv byte ceiling. `agy` takes the prompt as ONE `-p` argv; past `MAX_ARG_STRLEN` (~131072) `execve` fails with a cryptic `Argument list too long`. The wrapper measures the resolved `-`/`@file` prompt and fails loud over the ceiling. A huge **literal** `agy-run "<huge>"` fails at the wrapper's own `exec`, so route large prompts via `-`/`@file`. |
|
|
53
53
|
|
|
54
|
-
Subscription invariant: the wrapper prepends `$HOME/.local/bin` to `PATH` and clears
|
|
55
|
-
`
|
|
56
|
-
|
|
54
|
+
Subscription invariant: the wrapper prepends `$HOME/.local/bin` to `PATH` and clears `OPENAI_BASE_URL`
|
|
55
|
+
and every `*_API_KEY` except `TYPESAFE_API_KEY` (`ANTIGRAVITY_API_KEY` / `GEMINI_API_KEY` /
|
|
56
|
+
`GOOGLE_API_KEY` / `GOOGLE_GENAI_API_KEY` included) before execution; `TYPESAFE_API_KEY` passes as
|
|
57
|
+
given and widens nothing. Auth comes from the user's cached OAuth token, never from bundled credentials.
|
|
57
58
|
|
|
58
59
|
## `agy-review` contract (review role)
|
|
59
60
|
|
|
@@ -34,7 +34,8 @@ agy
|
|
|
34
34
|
This caches an OAuth token under `~/.gemini/antigravity-cli/` (`antigravity-oauth-token`). That token
|
|
35
35
|
is **personal** — never copy, commit, package, print, or share that directory or token. This skill
|
|
36
36
|
needs no API keys and must not be configured with API-key billing; the wrapper unsets every
|
|
37
|
-
`*_API_KEY`
|
|
37
|
+
`*_API_KEY` except `TYPESAFE_API_KEY` (the one key a run passes, as given) so billing can never
|
|
38
|
+
silently fall back to pay-as-you-go.
|
|
38
39
|
|
|
39
40
|
## 3. Put the wrappers on `PATH` (`agy-run` + `agy-review`)
|
|
40
41
|
|