mirror of
https://github.com/akitaonrails/ai-memory.git
synced 2026-10-02 03:24:46 +08:00
test(workstream): harden Antigravity managed support
This commit is contained in:
+9
-7
@@ -105,23 +105,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
pages reinforced by several operators without changing existing scores at
|
||||
the default or for pages with zero or one identified reader (#336).
|
||||
|
||||
- `run` and `show` accept `antigravity` (aliases `antigravity-cli`, `agy`) as a
|
||||
- `run` accepts `antigravity` (aliases `antigravity-cli`, `agy`) as a
|
||||
managed harness, so an Antigravity session joins the same workstream as the
|
||||
Claude Code or Codex sessions on the same checkout. `agy` accepts no
|
||||
caller-chosen id for a new conversation, so a fresh launch injects nothing and
|
||||
the id is linked by the hooks or discovered afterwards; a linked resume passes
|
||||
`--conversation <id>`, and `--continue` / `-c` is respected as an explicit user
|
||||
choice. `--yolo` maps to `--dangerously-skip-permissions`, and the utility
|
||||
caller-chosen id for a new conversation, so a fresh launch injects no
|
||||
selector and the id is linked by the hooks or discovered afterwards; a
|
||||
linked resume passes `--conversation <id>`, and `--continue` / `-c` is
|
||||
respected as an explicit user choice. `--yolo` maps to
|
||||
`--dangerously-skip-permissions`, and the utility
|
||||
subcommands (`models`, `plugin`, `update`, …) pass through without a selector.
|
||||
Conversation discovery reads the per-conversation SQLite databases under
|
||||
`~/.gemini/antigravity-cli/conversations/`: the id is the file name and the
|
||||
workspace comes from two self-describing protobuf fields, so only conversations
|
||||
workspace comes from two observed protobuf fields, so only conversations
|
||||
opened on the current directory are offered and a database from another `agy`
|
||||
version is skipped instead of failing the listing. Step payloads are
|
||||
undocumented, unversioned protobuf, so conversation text is deliberately not
|
||||
decoded — the visible-event ledger for this harness comes from lifecycle-hook
|
||||
capture, and transcript export fails with a message saying so. Antigravity is
|
||||
not part of the no-argument auto-detection pool. (#NNN)
|
||||
not part of the no-argument auto-detection pool. The native contract was
|
||||
verified against Antigravity CLI v1.1.7 (#345).
|
||||
|
||||
### Fixed
|
||||
- `run` on Windows now resolves npm-style harness installs through `PATHEXT`
|
||||
|
||||
@@ -193,8 +193,8 @@ priors are at the [bottom](#influences-and-prior-art).
|
||||
the workstream immediately. If a linked native transcript was deleted,
|
||||
ai-memory detects the orphan before launch and starts fresh; `--fresh` forces
|
||||
that recovery for one harness. Managed mode currently covers Claude Code,
|
||||
Codex, OpenCode, Pi, Crush, Kimi Code, OMP, and Grok Build CLI; direct harness
|
||||
launches remain unchanged. See
|
||||
Codex, OpenCode, Pi, Crush, Kimi Code, OMP, Grok Build CLI, and Antigravity
|
||||
CLI; direct harness launches remain unchanged. See
|
||||
[Managed cross-harness workstreams](docs/managed-workstreams.md).
|
||||
- **"Quit at 4 PM, pick up at 9 AM in a different agent."** The
|
||||
classic. SessionStart hook in the next supported hook client prepends a
|
||||
@@ -882,7 +882,7 @@ diagram, crate breakdown, schema notes, and invariants.
|
||||
|---|---|
|
||||
| [`docs/install.md`](docs/install.md) | **Installation cookbook.** Every agent CLI, every alternative (curl, source build, no-docker, no-auth), and the server-on-a-different-machine (homelab/LAN) walkthrough. Read after the Quick start if your setup doesn't match the happy path. |
|
||||
| [`docs/usage.md`](docs/usage.md) | Handoffs, proactive memory queries, slim routing snippet + managed Agent Skills, migration from other memory tools, web UI, raw-wiki inspection, and rules-vs-facts workflow. |
|
||||
| [`docs/managed-workstreams.md`](docs/managed-workstreams.md) | Optional `ai-memory run` continuity across Claude Code, Codex, OpenCode, Pi, Crush, Kimi Code, OMP, and Grok Build CLI: automatic harness selection, native resume, argument forwarding, ledger search, privacy, and recovery. |
|
||||
| [`docs/managed-workstreams.md`](docs/managed-workstreams.md) | Optional `ai-memory run` continuity across Claude Code, Codex, OpenCode, Pi, Crush, Kimi Code, OMP, Grok Build CLI, and Antigravity CLI: automatic harness selection, native resume, argument forwarding, ledger search, privacy, and recovery. |
|
||||
| [`docs/managed-harness-contributions.md`](docs/managed-harness-contributions.md) | Protocol and acceptance bar for contributors adding managed resume, read-only transcript import, and startup context delivery to another harness. |
|
||||
| [`docs/marker-file.md`](docs/marker-file.md) | `.ai-memory.toml` workspace/project routing for multi-client trees, mono-repos, worktrees, and work/personal separation. |
|
||||
| [`docs/auto-scope.md`](docs/auto-scope.md) | `[auto_scope]` modes for shared servers: default single-slot routing, session-aware isolation, and multi-user `per_actor` behavior. |
|
||||
|
||||
@@ -14,6 +14,7 @@ use rusqlite::{Connection, OpenFlags, params};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use sha2::{Digest as _, Sha256};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::ManagedHarness;
|
||||
|
||||
@@ -88,10 +89,10 @@ pub async fn export_transcript(
|
||||
// blob whose step-type enum is unversioned, so message text cannot be
|
||||
// decoded without guessing at a schema that changes between `agy`
|
||||
// releases. Conversation identity and workspace are read (they are
|
||||
// stable, self-describing fields); the visible-event ledger for this
|
||||
// harness comes from lifecycle-hook capture instead.
|
||||
// stable fields observed in current metadata); the visible-event
|
||||
// ledger for this harness comes from lifecycle-hook capture instead.
|
||||
return Err(anyhow!(
|
||||
"antigravity conversations expose no decodable transcript; this session's events come from hook capture"
|
||||
"antigravity conversations expose no decodable transcript; this session's events come from hook capture"
|
||||
));
|
||||
}
|
||||
let path = locate_session_file(harness, home, cwd, session_dir, native_session_id)?
|
||||
@@ -1575,6 +1576,9 @@ fn locate_session_file(
|
||||
let root = session_root(harness, home, session_dir);
|
||||
if harness == ManagedHarness::Antigravity {
|
||||
// The conversation id is the file name, so no scan is ever needed.
|
||||
if Uuid::parse_str(id).is_err() {
|
||||
return Ok(None);
|
||||
}
|
||||
let exact = root.join(format!("{id}.db"));
|
||||
return Ok(exact.is_file().then_some(exact));
|
||||
}
|
||||
@@ -1760,7 +1764,7 @@ fn antigravity_session_header(path: &Path) -> Result<Option<(String, PathBuf)>>
|
||||
let Some(id) = path.file_stem().and_then(|stem| stem.to_str()) else {
|
||||
return Ok(None);
|
||||
};
|
||||
if !valid_native_session_id(id) {
|
||||
if !valid_native_session_id(id) || Uuid::parse_str(id).is_err() {
|
||||
return Ok(None);
|
||||
}
|
||||
let Ok(connection) = Connection::open_with_flags(
|
||||
@@ -1770,7 +1774,7 @@ fn antigravity_session_header(path: &Path) -> Result<Option<(String, PathBuf)>>
|
||||
return Ok(None);
|
||||
};
|
||||
let blob = connection.query_row(
|
||||
"SELECT data FROM trajectory_metadata_blob LIMIT 1",
|
||||
"SELECT data FROM trajectory_metadata_blob WHERE id = 'main' LIMIT 1",
|
||||
[],
|
||||
|row| row.get::<_, Vec<u8>>(0),
|
||||
);
|
||||
@@ -1819,6 +1823,9 @@ fn protobuf_field(message: &[u8], field: u64) -> Option<&[u8]> {
|
||||
fn protobuf_varint(bytes: &[u8]) -> Option<(u64, usize)> {
|
||||
let mut value = 0u64;
|
||||
for (index, byte) in bytes.iter().take(10).enumerate() {
|
||||
if index == 9 && *byte > 1 {
|
||||
return None;
|
||||
}
|
||||
value |= u64::from(byte & 0x7f) << (index * 7);
|
||||
if byte & 0x80 == 0 {
|
||||
return Some((value, index + 1));
|
||||
@@ -3234,15 +3241,38 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// Build the two protobuf layers `agy` writes: an outer message whose
|
||||
/// field 1 holds a nested message whose field 1 is the workspace URI.
|
||||
fn push_protobuf_varint(output: &mut Vec<u8>, mut value: u64) {
|
||||
loop {
|
||||
let byte = (value & 0x7f) as u8;
|
||||
value >>= 7;
|
||||
output.push(if value == 0 { byte } else { byte | 0x80 });
|
||||
if value == 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn push_protobuf_bytes(output: &mut Vec<u8>, field: u64, value: &[u8]) {
|
||||
push_protobuf_varint(output, (field << 3) | 2);
|
||||
push_protobuf_varint(output, u64::try_from(value.len()).unwrap());
|
||||
output.extend_from_slice(value);
|
||||
}
|
||||
|
||||
/// Build the two protobuf layers observed in `agy` v1.1.7 metadata. The
|
||||
/// unrelated fields ensure discovery searches by field number instead of
|
||||
/// assuming the workspace URI is the entire message.
|
||||
fn antigravity_metadata(uri: &str) -> Vec<u8> {
|
||||
let mut nested = vec![0x0a];
|
||||
nested.push(u8::try_from(uri.len()).unwrap());
|
||||
nested.extend_from_slice(uri.as_bytes());
|
||||
let mut outer = vec![0x0a];
|
||||
outer.push(u8::try_from(nested.len()).unwrap());
|
||||
outer.extend_from_slice(&nested);
|
||||
let mut nested = Vec::new();
|
||||
push_protobuf_varint(&mut nested, 2 << 3);
|
||||
push_protobuf_varint(&mut nested, 7);
|
||||
push_protobuf_bytes(&mut nested, 1, uri.as_bytes());
|
||||
push_protobuf_bytes(&mut nested, 4, b"fixture-metadata");
|
||||
|
||||
let mut outer = Vec::new();
|
||||
push_protobuf_bytes(&mut outer, 3, b"unrelated");
|
||||
push_protobuf_bytes(&mut outer, 1, &nested);
|
||||
push_protobuf_varint(&mut outer, 5 << 3);
|
||||
push_protobuf_varint(&mut outer, 1);
|
||||
outer
|
||||
}
|
||||
|
||||
@@ -3276,13 +3306,15 @@ mod tests {
|
||||
}
|
||||
|
||||
/// The conversation id is the file name and the workspace comes from the
|
||||
/// metadata blob, so a checkout only sees its own conversations.
|
||||
/// metadata blob, so a checkout only sees its own conversations. The long
|
||||
/// component forces both protobuf layers to use multi-byte varint lengths.
|
||||
#[tokio::test]
|
||||
async fn antigravity_lists_only_conversations_from_this_workspace() {
|
||||
let temp = tempfile::TempDir::new().unwrap();
|
||||
let root = temp.path().join(".gemini/antigravity-cli/conversations");
|
||||
fs::create_dir_all(&root).unwrap();
|
||||
let cwd = temp.path().join("checkout");
|
||||
fs::write(root.parent().unwrap().join("outside.db"), b"outside").unwrap();
|
||||
let cwd = temp.path().join(format!("checkout-{}", "x".repeat(140)));
|
||||
fs::create_dir_all(&cwd).unwrap();
|
||||
let mine = "a0d5ac62-2501-4780-b783-76d159c56cb3";
|
||||
let theirs = "9576275f-7c4e-4709-b372-22d1ad2a0af8";
|
||||
@@ -3315,6 +3347,16 @@ mod tests {
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
assert!(
|
||||
!native_session_exists(
|
||||
ManagedHarness::Antigravity,
|
||||
temp.path(),
|
||||
&cwd,
|
||||
None,
|
||||
"../outside"
|
||||
)
|
||||
.unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
/// A database whose metadata is shaped differently — an older or newer
|
||||
@@ -3397,5 +3439,7 @@ mod tests {
|
||||
assert_eq!(protobuf_field(&message, 9), None);
|
||||
// Truncated input ends the walk instead of panicking on a slice.
|
||||
assert_eq!(protobuf_field(&[0x22, 0x10, b'x'], 4), None);
|
||||
// A ten-byte varint may carry only one payload bit in its final byte.
|
||||
assert_eq!(protobuf_varint(&[0xff; 10]), None);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,8 +300,9 @@ Top-line rules carved into the codebase:
|
||||
## 15. Managed workstreams use a portable ledger, not native format conversion
|
||||
|
||||
Managed cross-harness continuity is explicitly opt-in through `ai-memory run`.
|
||||
Direct Claude Code, Codex, OpenCode, Pi, Crush, Kimi Code, OMP, and Grok Build
|
||||
CLI launches retain the existing hook and single-use handoff behavior. There is
|
||||
Direct Claude Code, Codex, OpenCode, Pi, Crush, Kimi Code, OMP, Grok Build CLI,
|
||||
and Antigravity CLI launches retain the existing hook and single-use handoff
|
||||
behavior. There is
|
||||
no process-global mode or manual harness switch: the wrapper selects the
|
||||
current repository/worktree workstream and each adapter applies that harness's
|
||||
native create/resume syntax.
|
||||
|
||||
+1
-1
@@ -1257,7 +1257,7 @@ docker run --rm akitaonrails/ai-memory:latest --help # full subcommand tree
|
||||
| Subcommand | Pattern | What it does |
|
||||
|---|---|---|
|
||||
| `serve` | `docker compose up -d` (already done) | Run the HTTP MCP server |
|
||||
| `run [harness] [args...]` | host wrapper or native binary | Opt into one managed cross-harness workstream; omit the harness to resume the newest usable local session, or name Claude Code, Codex, OpenCode, Pi, Crush, Kimi Code, OMP, or Grok Build CLI explicitly; exact `--yolo` and `--fresh` flags are wrapper-owned and other native arguments pass through |
|
||||
| `run [harness] [args...]` | host wrapper or native binary | Opt into one managed cross-harness workstream; omit the harness to resume the newest usable local session, or name Claude Code, Codex, OpenCode, Pi, Crush, Kimi Code, OMP, Grok Build CLI, or Antigravity CLI explicitly; exact `--yolo` and `--fresh` flags are wrapper-owned and other native arguments pass through |
|
||||
| `workstream-search [query]` | managed child or thin HTTP client | Search the complete visible managed-workstream ledger; the managed child receives its workstream id automatically |
|
||||
| `status` | `docker exec` | Counts, paths, derived-index diagnostics, and passive LLM/embedding provider health |
|
||||
| `search "<query>"` | `docker exec` | Wiki FTS5 search + bounded source authority; use MCP `memory_query` for entity/graph/vector RRF |
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
# Adding a managed harness
|
||||
|
||||
Managed-workstream support is narrower than MCP or lifecycle-hook support. This
|
||||
release can manage Claude Code, Codex, OpenCode, Pi, Crush, Kimi Code, OMP, and
|
||||
Grok Build CLI. Gemini CLI, Devin CLI, Cursor, and the other integrations in
|
||||
the README support matrix do not become managed merely because ai-memory can
|
||||
capture their hooks.
|
||||
release can manage Claude Code, Codex, OpenCode, Pi, Crush, Kimi Code, OMP,
|
||||
Grok Build CLI, and Antigravity CLI. Gemini CLI, Devin CLI, Cursor, and the
|
||||
other integrations in the README support matrix do not become managed merely
|
||||
because ai-memory can capture their hooks.
|
||||
|
||||
A managed adapter must preserve a harness's real native session, deliver the
|
||||
portable workstream delta exactly once, and import only visible history without
|
||||
@@ -78,8 +78,9 @@ continue, or fork selector.
|
||||
|
||||
## 4. Discover and export read-only
|
||||
|
||||
Implement candidate discovery and incremental export in
|
||||
`crates/ai-memory-workstream/src/transcript.rs`.
|
||||
Implement candidate discovery in `crates/ai-memory-workstream/src/transcript.rs`.
|
||||
Implement incremental export only when a documented or repeatably observed
|
||||
native format exposes visible conversation records without private state.
|
||||
|
||||
The adapter must:
|
||||
|
||||
@@ -87,17 +88,19 @@ The adapter must:
|
||||
- honor documented store-root environment and command-line overrides;
|
||||
- open SQLite stores read-only and never create, migrate, vacuum, or repair
|
||||
them;
|
||||
- tolerate an incomplete final JSONL record or an in-progress tool call without
|
||||
advancing past it;
|
||||
- emit deterministic source record and event ids;
|
||||
- resume from a persisted source cursor without duplicates;
|
||||
- normalize visible user/assistant messages, completed tool calls/results, and
|
||||
compaction summaries; and
|
||||
- when export is supported, tolerate an incomplete final record or in-progress
|
||||
tool call without advancing past it;
|
||||
- when export is supported, emit deterministic source record and event ids and
|
||||
resume from a persisted source cursor without duplicates;
|
||||
- normalize only visible user/assistant messages, completed tool calls/results,
|
||||
and compaction summaries; and
|
||||
- exclude system/developer prompts, hidden reasoning, binary payloads,
|
||||
credentials, provider metadata, and unsupported records.
|
||||
|
||||
Extraction gaps should become bounded loss annotations. They must not cause the
|
||||
adapter to copy a private record "just in case."
|
||||
Extraction gaps should become bounded loss annotations. If the conversation
|
||||
payload is opaque or undocumented, return such an annotation and rely on
|
||||
sanitized lifecycle-hook capture instead of guessing. Never copy a private
|
||||
record "just in case."
|
||||
|
||||
## 5. Deliver context before acknowledging it
|
||||
|
||||
@@ -147,10 +150,13 @@ A managed-harness PR should include focused coverage for:
|
||||
- deterministic fake-process acceptance in
|
||||
`scripts/managed-workstream-acceptance.sh`; and
|
||||
- a manual real-harness pass that switches into the new harness, records
|
||||
successful delivery of its assigned context delta, persists a new assistant
|
||||
event, and resumes its original native session when revisited. Do not make
|
||||
pass/fail depend on the model quoting packet text: some harnesses externalize
|
||||
large hook results to a file, making recall depend on a model tool-use choice.
|
||||
successful delivery of its assigned context delta, and resumes its original
|
||||
native session when revisited. Require a new assistant event when the adapter
|
||||
has a readable transcript. A deliberately hook-only adapter must instead
|
||||
prove that a correlated lifecycle observation was persisted and document the
|
||||
bounded transcript-loss annotation. Do not make pass/fail depend on the model
|
||||
quoting packet text: some harnesses externalize large hook results to a file,
|
||||
making recall depend on a model tool-use choice.
|
||||
|
||||
The deterministic phase remains credential-free and suitable for frequent
|
||||
local runs. Real model calls stay opt-in and outside CI. Record the tested CLI
|
||||
|
||||
+37
-26
@@ -158,10 +158,12 @@ is labelled completed evidence and must never be replayed as a pending call.
|
||||
| Kimi Code | native default creation | `--session <id>` | `$KIMI_CODE_HOME/sessions/*/*/agents/main/wire.jsonl` |
|
||||
| OMP | native default creation | `--resume=<id>` | `~/.omp/agent/sessions/**/*.jsonl` |
|
||||
| Grok Build CLI | generated `--session-id` | `--resume <id>` | `$GROK_HOME/sessions/*/*/chat_history.jsonl` |
|
||||
| Antigravity CLI | native default creation | `--conversation <id>` | `~/.gemini/antigravity-cli/conversations/<id>.db` metadata plus lifecycle-hook capture |
|
||||
|
||||
An explicit native selector such as Claude's `--resume`, OpenCode's `--session`,
|
||||
or Codex's `resume` wins. ai-memory links the selected native session and resets
|
||||
an unrelated adapter cursor rather than assuming it belongs to the old session.
|
||||
Codex's `resume`, or Antigravity's `--conversation` / `--continue` wins.
|
||||
ai-memory links the selected native session and resets an unrelated adapter
|
||||
cursor rather than assuming it belongs to the old session.
|
||||
Pi and OMP `--session-dir` values and Crush `--data-dir` values are passed
|
||||
through unchanged and used as the read-only import root. Native store
|
||||
environment overrides are also honored:
|
||||
@@ -180,8 +182,9 @@ chooser.
|
||||
the harness's native dangerous mode. The translation is Claude Code
|
||||
`--dangerously-skip-permissions`, Codex
|
||||
`--dangerously-bypass-approvals-and-sandbox`, OpenCode `--auto`, Pi `--approve`,
|
||||
Crush `--yolo`, Kimi Code `--yolo`, and Grok Build CLI `--yolo` (equivalent to
|
||||
its `--always-approve` option). OMP currently needs no added flag. ai-memory
|
||||
Crush `--yolo`, Kimi Code `--yolo`, Grok Build CLI `--yolo` (equivalent to its
|
||||
`--always-approve` option), and Antigravity CLI
|
||||
`--dangerously-skip-permissions`. OMP currently needs no added flag. ai-memory
|
||||
does not add a duplicate when the translated native flag is already present.
|
||||
|
||||
Managed support is intentionally narrower than the general integration matrix.
|
||||
@@ -260,15 +263,16 @@ workspace is the directory `agy` was launched from, not a checkout root, so a
|
||||
conversation started one level up is not offered inside a subdirectory.
|
||||
|
||||
`agy` accepts no caller-chosen id for a new conversation, so a fresh launch
|
||||
injects nothing and the id is linked by the hooks or discovered after exit; a
|
||||
injects no selector and the id is linked by the hooks or discovered after exit; a
|
||||
linked resume passes `--conversation <id>`. `--continue` / `-c` is treated as an
|
||||
explicit user choice and is never overridden. `--yolo` maps to
|
||||
`--dangerously-skip-permissions`. Step payloads are undocumented, unversioned
|
||||
protobuf blobs, so ai-memory does not decode conversation text: the visible-event
|
||||
ledger for this harness comes from lifecycle-hook capture, and transcript export
|
||||
fails with a message saying so. The managed launcher accepts `antigravity`,
|
||||
`antigravity-cli`, and `agy`. Antigravity is not part of the no-argument
|
||||
auto-detection set; name it explicitly or pick it from `ai-memory show`.
|
||||
`antigravity-cli`, and `agy`. The native contract was verified against
|
||||
Antigravity CLI v1.1.7. Antigravity is not part of the no-argument
|
||||
auto-detection set; name it explicitly.
|
||||
|
||||
Crush needs no ai-memory hook installation for managed mode. The launcher reads
|
||||
its one-time context from the server, copies the existing global Crush JSON into
|
||||
@@ -322,10 +326,10 @@ process launch is fatal; ai-memory does not silently start an unmanaged agent.
|
||||
## Privacy and storage boundaries
|
||||
|
||||
ai-memory's managed adapters do not write to Claude, Codex, OpenCode, Pi, Crush,
|
||||
Kimi Code, OMP, or Grok private stores. The launched harness retains normal
|
||||
ownership of its own session writes. Adapters read only documented or observed
|
||||
local session formats. Provider credentials, encrypted content,
|
||||
system/developer prompt records, and hidden reasoning are not copied. The
|
||||
Kimi Code, OMP, Grok, or Antigravity private stores. The launched harness
|
||||
retains normal ownership of its own session writes. Adapters read only
|
||||
documented or observed local session formats. Provider credentials, encrypted
|
||||
content, system/developer prompt records, and hidden reasoning are not copied. The
|
||||
server sanitizer runs before both the SQLite FTS ledger and immutable files under
|
||||
`<data_dir>/raw/workstreams/<workstream-id>/segments/` are written.
|
||||
|
||||
@@ -339,8 +343,9 @@ belong in wiki pages through consolidation or explicit durable writes.
|
||||
project name. Wiki paths are UUID-keyed, so it moves no server directory, source
|
||||
checkout, or native harness session. If the source checkout path itself is
|
||||
renamed, absolute-path session locators used by Claude Code, Codex, OpenCode,
|
||||
Pi, Kimi Code (`state.json`'s `workDir`), and OMP may still reference the old
|
||||
path; Crush's project-local `.crush` database moves with the checkout.
|
||||
Pi, Kimi Code (`state.json`'s `workDir`), OMP, and Antigravity may still
|
||||
reference the old path; Crush's project-local `.crush` database moves with the
|
||||
checkout.
|
||||
|
||||
There is no portable, supported API that rewrites every harness's private
|
||||
project locator. ai-memory therefore does not mutate those stores or silently
|
||||
@@ -369,12 +374,15 @@ OpenCode receive only copied authentication material; OMP receives a temporary
|
||||
agent directory with read-consistent credential/model database backups and
|
||||
copied settings. Crush uses its existing global provider configuration and an
|
||||
isolated project database. Kimi Code runs with an isolated `$KIMI_CODE_HOME`
|
||||
seeded with the operator's provider configuration. The deterministic phase
|
||||
also covers first-run adoption, bare-mode selection and empty-directory
|
||||
failure, wrapper `--yolo`, lease exclusion, Crush context cleanup, a fake-mode
|
||||
Kimi store/resume/import round trip, and the established-workstream guard
|
||||
against obsolete sessions. The fake Kimi round trip also deletes the linked
|
||||
native session and verifies automatic fresh-session recovery and repointing.
|
||||
seeded with the operator's provider configuration. Antigravity runs with an
|
||||
isolated `HOME` seeded only with the operator's OAuth and settings files. The
|
||||
deterministic phase also covers first-run adoption, bare-mode selection and
|
||||
empty-directory failure, wrapper `--yolo`, lease exclusion, Crush context
|
||||
cleanup, a fake-mode Kimi store/resume/import round trip, an Antigravity
|
||||
hook/link/resume round trip, private-trajectory exclusion, and the
|
||||
established-workstream guard against obsolete sessions. The fake Kimi round
|
||||
trip also deletes the linked native session and verifies automatic
|
||||
fresh-session recovery and repointing.
|
||||
Native session creation, read-only extraction, cross-harness injection, and
|
||||
returning resume paths are all exercised. Docker wrapper host execution and
|
||||
remote URL preservation are covered separately by the `ai-memory-cli`
|
||||
@@ -382,19 +390,22 @@ packaging tests.
|
||||
|
||||
The real-harness phase treats the model as the system under transport, not as
|
||||
the test oracle. For each leg it records the prior ledger sequence, then
|
||||
requires a newly imported assistant event from that harness. When a context
|
||||
delta is expected, it first verifies that the prior ledger endpoint is newer
|
||||
requires a newly imported assistant event from harnesses with readable native
|
||||
transcripts. For Antigravity it instead requires the exact native conversation
|
||||
link and a new correlated startup-hook observation, because its private
|
||||
trajectory protobuf is deliberately not decoded. When a context delta is
|
||||
expected, it first verifies that the prior ledger endpoint is newer
|
||||
than that harness's delivery cursor, then requires the latest managed run to
|
||||
report that exact endpoint as `sync_through` with `context_delivered = 1`. It
|
||||
does not require the model to quote a prior sentinel: Claude Code may
|
||||
externalize a large hook result to a file, and whether a model chooses to read
|
||||
that file is not a deterministic continuity signal. The deterministic fake
|
||||
Grok cross-harness fixture exercises the same assertion helper without
|
||||
credentials or model calls.
|
||||
Grok and Antigravity cross-harness fixtures exercise the same assertion helper
|
||||
without credentials or model calls.
|
||||
|
||||
Set
|
||||
`AI_MEMORY_ACCEPTANCE_HARNESSES="kimi-cli codex"` to select a
|
||||
Kimi-to-Codex-to-Kimi round trip (Kimi aliases normalize to the installed
|
||||
`kimi` executable), `AI_MEMORY_ACCEPTANCE_DETERMINISTIC_ONLY=1` to skip model
|
||||
`AI_MEMORY_ACCEPTANCE_HARNESSES="antigravity codex"` to select an
|
||||
Antigravity-to-Codex-to-Antigravity round trip (`agy` and `antigravity-cli` are
|
||||
accepted aliases), `AI_MEMORY_ACCEPTANCE_DETERMINISTIC_ONLY=1` to skip model
|
||||
calls, or
|
||||
`AI_MEMORY_ACCEPTANCE_KEEP=1` to retain all temporary logs and data.
|
||||
|
||||
+4
-4
@@ -6,8 +6,8 @@ agent CLI actually runs.
|
||||
## Rule Of Thumb
|
||||
|
||||
Run `install-mcp` and `install-hooks` from the same environment that
|
||||
launches Claude Code, Codex, Devin CLI, Cursor, Gemini CLI, Kimi Code, or
|
||||
another agent.
|
||||
launches Claude Code, Codex, Devin CLI, Cursor, Gemini CLI, Kimi Code,
|
||||
Antigravity CLI, or another agent.
|
||||
|
||||
- If the agent runs inside WSL2, install ai-memory inside WSL2.
|
||||
- If the agent runs as a native Windows process, install ai-memory from
|
||||
@@ -347,8 +347,8 @@ Windows agent builds.
|
||||
Claude Code invokes hooks as a direct binary call (no shell) by default;
|
||||
`AI_MEMORY_HOOK_PLATFORM=windows-bash` restores the Git Bash `bash -c`
|
||||
path. WSL2 Claude Code uses normal WSL `.sh` paths.
|
||||
- Codex, Devin CLI, OpenCode, Cursor, Gemini CLI, Grok Build CLI, Zero,
|
||||
Kimi Code, and OpenClaw may each choose different
|
||||
- Codex, Devin CLI, OpenCode, Cursor, Gemini CLI, Antigravity CLI, Grok Build
|
||||
CLI, Zero, Kimi Code, and OpenClaw may each choose different
|
||||
Windows config locations or shell execution behavior. ai-memory uses
|
||||
the current best-known defaults, but they need validation on real
|
||||
installations.
|
||||
|
||||
@@ -8,7 +8,7 @@ ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
BIN=${AI_MEMORY_ACCEPTANCE_BIN:-"$ROOT/target/debug/ai-memory"}
|
||||
KEEP=${AI_MEMORY_ACCEPTANCE_KEEP:-0}
|
||||
DETERMINISTIC_ONLY=${AI_MEMORY_ACCEPTANCE_DETERMINISTIC_ONLY:-0}
|
||||
HARNESS_WORDS=${AI_MEMORY_ACCEPTANCE_HARNESSES:-"claude codex opencode pi crush omp kimi grok"}
|
||||
HARNESS_WORDS=${AI_MEMORY_ACCEPTANCE_HARNESSES:-"claude codex opencode pi crush omp kimi grok antigravity"}
|
||||
TMP=$(mktemp -d "${TMPDIR:-/tmp}/ai-memory-workstream-acceptance.XXXXXX")
|
||||
DATA="$TMP/data"
|
||||
REPO="$TMP/repo"
|
||||
@@ -109,9 +109,21 @@ current_delivery_cursor() {
|
||||
), 0);"
|
||||
}
|
||||
|
||||
agent_observation_count() {
|
||||
local agent=$1
|
||||
sqlite3 "$DATA/db/memory.sqlite" \
|
||||
"SELECT COUNT(*)
|
||||
FROM observations AS o
|
||||
JOIN sessions AS s ON s.id = o.session_id
|
||||
WHERE s.agent_kind = '$agent';"
|
||||
}
|
||||
|
||||
# Assert the deterministic boundaries of one real or fake managed leg:
|
||||
# the harness produced a new assistant event, and any assigned context delta
|
||||
# was acknowledged by its hook/launcher delivery path. Model recall is not a
|
||||
# the harness produced new portable evidence, and any assigned context delta
|
||||
# was acknowledged by its hook/launcher delivery path. Adapters with readable
|
||||
# transcripts persist an assistant event. Antigravity deliberately does not
|
||||
# decode private trajectory protobuf, so its startup hook must instead persist
|
||||
# an observation and link the exact native conversation. Model recall is not a
|
||||
# protocol oracle: large hook results may be file-backed, and whether a model
|
||||
# chooses to read that file must not decide acceptance.
|
||||
assert_managed_leg() {
|
||||
@@ -121,21 +133,42 @@ assert_managed_leg() {
|
||||
local before_delivery=$4
|
||||
local expect_context=$5
|
||||
local log=$6
|
||||
local native_id delivery sync_after sync_through context_delivered
|
||||
local before_observations=${7:-0}
|
||||
local native_id delivery sync_after sync_through context_delivered after_observations
|
||||
|
||||
native_id=$(sqlite3 "$DATA/db/memory.sqlite" \
|
||||
"SELECT native_session_id FROM workstream_events
|
||||
WHERE workstream_id = x'$hex'
|
||||
AND sequence > $before_sequence
|
||||
AND agent_kind = '$expected_agent'
|
||||
AND role = 'assistant'
|
||||
ORDER BY sequence DESC LIMIT 1;")
|
||||
[ -n "$native_id" ] || {
|
||||
printf '%s did not persist a new assistant event after ledger sequence %s\n' \
|
||||
"$expected_agent" "$before_sequence" >&2
|
||||
tail -120 "$log" >&2
|
||||
return 1
|
||||
}
|
||||
if [ "$expected_agent" = antigravity-cli ]; then
|
||||
native_id=$(sqlite3 "$DATA/db/memory.sqlite" \
|
||||
"SELECT native_session_id FROM workstream_native_sessions
|
||||
WHERE workstream_id = x'$hex'
|
||||
AND agent_kind = '$expected_agent'
|
||||
AND is_current = 1;")
|
||||
[ -n "$native_id" ] || {
|
||||
printf '%s did not link a native conversation\n' "$expected_agent" >&2
|
||||
tail -120 "$log" >&2
|
||||
return 1
|
||||
}
|
||||
after_observations=$(agent_observation_count "$expected_agent")
|
||||
[ "$after_observations" -gt "$before_observations" ] || {
|
||||
printf '%s did not persist a startup-hook observation (before: %s, after: %s)\n' \
|
||||
"$expected_agent" "$before_observations" "$after_observations" >&2
|
||||
tail -120 "$log" >&2
|
||||
return 1
|
||||
}
|
||||
else
|
||||
native_id=$(sqlite3 "$DATA/db/memory.sqlite" \
|
||||
"SELECT native_session_id FROM workstream_events
|
||||
WHERE workstream_id = x'$hex'
|
||||
AND sequence > $before_sequence
|
||||
AND agent_kind = '$expected_agent'
|
||||
AND role = 'assistant'
|
||||
ORDER BY sequence DESC LIMIT 1;")
|
||||
[ -n "$native_id" ] || {
|
||||
printf '%s did not persist a new assistant event after ledger sequence %s\n' \
|
||||
"$expected_agent" "$before_sequence" >&2
|
||||
tail -120 "$log" >&2
|
||||
return 1
|
||||
}
|
||||
fi
|
||||
|
||||
if [ "$expect_context" = 1 ]; then
|
||||
if [ "$before_sequence" -le "$before_delivery" ]; then
|
||||
@@ -242,6 +275,42 @@ case "${AI_MEMORY_ACCEPTANCE_FAKE_MODE:-argv}" in
|
||||
printf '{"type":"user","content":[{"type":"text","text":"%s"}]}\n' "$sentinel" >>"$chat"
|
||||
printf '{"type":"assistant","content":"%s reply"}\n' "$sentinel" >>"$chat"
|
||||
;;
|
||||
antigravity)
|
||||
printf '%s\n' "$@" >"$AI_MEMORY_ACCEPTANCE_ARGV_LOG"
|
||||
session_id=""
|
||||
previous_arg=""
|
||||
for arg in "$@"; do
|
||||
if [ "$previous_arg" = --conversation ]; then
|
||||
session_id=$arg
|
||||
fi
|
||||
previous_arg=$arg
|
||||
done
|
||||
if [ -z "$session_id" ]; then
|
||||
session_id=${AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_SESSION_ID:?antigravity fake mode requires a fresh session id}
|
||||
fi
|
||||
conversations="$HOME/.gemini/antigravity-cli/conversations"
|
||||
mkdir -p "$conversations"
|
||||
database="$conversations/$session_id.db"
|
||||
if [ ! -f "$database" ]; then
|
||||
uri="file://$PWD"
|
||||
uri_hex=$(printf '%s' "$uri" | od -An -tx1 | tr -d ' \n')
|
||||
printf -v uri_length '%02x' "${#uri}"
|
||||
nested="0a${uri_length}${uri_hex}"
|
||||
printf -v nested_length '%02x' "$((2 + ${#uri}))"
|
||||
metadata="0a${nested_length}${nested}"
|
||||
sqlite3 "$database" \
|
||||
"CREATE TABLE trajectory_metadata_blob (id text DEFAULT 'main', data blob, PRIMARY KEY (id));
|
||||
INSERT INTO trajectory_metadata_blob (id, data) VALUES ('main', x'$metadata');
|
||||
CREATE TABLE trajectory_blob (data blob);
|
||||
INSERT INTO trajectory_blob (data) VALUES (x'414d57532d505249564154452d5452414a4543544f5259');"
|
||||
fi
|
||||
payload=$(jq -nc --arg id "$session_id" --arg cwd "$PWD" \
|
||||
'{invocationNum: 0, conversationId: $id, workspacePaths: [$cwd]}')
|
||||
printf '%s' "$payload" | \
|
||||
AI_MEMORY_HOOK_URL="${AI_MEMORY_SERVER_URL:?}" \
|
||||
"$AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_HOOK" \
|
||||
>"$AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_HOOK_LOG"
|
||||
;;
|
||||
esac
|
||||
EOF
|
||||
chmod +x "$FAKE"
|
||||
@@ -551,6 +620,106 @@ kimi_current_id=$(sqlite3 "$DATA/db/memory.sqlite" \
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Antigravity fake-mode fixture: `agy` owns the conversation id and SQLite
|
||||
# database, while its real PreInvocation hook links that id and accepts any
|
||||
# pending workstream context. The private trajectory table is never decoded or
|
||||
# copied into the ledger.
|
||||
ANTIGRAVITY_FAKE_HOME="$CONFIG/antigravity-fake"
|
||||
ANTIGRAVITY_FAKE_ID="a0d5ac62-2501-4780-b783-76d159c56cb3"
|
||||
ANTIGRAVITY_HOOK="$ROOT/hooks/antigravity-cli/session-start.sh"
|
||||
mkdir -p "$ANTIGRAVITY_FAKE_HOME"
|
||||
antigravity_first_observations=$(agent_observation_count antigravity-cli)
|
||||
(
|
||||
cd "$REPO"
|
||||
HOME="$ANTIGRAVITY_FAKE_HOME" \
|
||||
AI_MEMORY_ACCEPTANCE_FAKE_MODE=antigravity \
|
||||
AI_MEMORY_ACCEPTANCE_ARGV_LOG="$TMP/antigravity-first-argv.log" \
|
||||
AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_SESSION_ID="$ANTIGRAVITY_FAKE_ID" \
|
||||
AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_HOOK="$ANTIGRAVITY_HOOK" \
|
||||
AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_HOOK_LOG="$TMP/antigravity-first-hook.json" \
|
||||
"$BIN" --data-dir "$DATA" run --new edge-antigravity --executable "$FAKE" \
|
||||
--yolo antigravity >"$LOGS/edge-antigravity-first.log" 2>&1
|
||||
)
|
||||
diff -u <(printf '%s\n' --dangerously-skip-permissions) \
|
||||
"$TMP/antigravity-first-argv.log"
|
||||
antigravity_ws_hex=$(workstream_hex edge-antigravity)
|
||||
[ "${#antigravity_ws_hex}" -eq 32 ] || {
|
||||
printf 'could not resolve the edge-antigravity workstream id\n' >&2
|
||||
exit 1
|
||||
}
|
||||
antigravity_native=$(assert_managed_leg "$antigravity_ws_hex" antigravity-cli 0 0 0 \
|
||||
"$LOGS/edge-antigravity-first.log" "$antigravity_first_observations")
|
||||
[ "$antigravity_native" = "$ANTIGRAVITY_FAKE_ID" ] || {
|
||||
printf 'Antigravity linked native conversation %s, expected %s\n' \
|
||||
"$antigravity_native" "$ANTIGRAVITY_FAKE_ID" >&2
|
||||
exit 1
|
||||
}
|
||||
antigravity_ws_id="${antigravity_ws_hex:0:8}-${antigravity_ws_hex:8:4}-${antigravity_ws_hex:12:4}-${antigravity_ws_hex:16:4}-${antigravity_ws_hex:20:12}"
|
||||
private_hits=$("$BIN" --data-dir "$DATA" workstream-search \
|
||||
--workstream-id "$antigravity_ws_id" --limit 100 --json "PRIVATE")
|
||||
jq -e 'length == 0' <<<"$private_hits" >/dev/null || {
|
||||
printf 'Antigravity private trajectory payload leaked into the workstream ledger\n' >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
antigravity_second_before=$(latest_workstream_sequence "$antigravity_ws_hex")
|
||||
antigravity_second_delivery=$(current_delivery_cursor "$antigravity_ws_hex" antigravity-cli)
|
||||
antigravity_second_observations=$(agent_observation_count antigravity-cli)
|
||||
(
|
||||
cd "$REPO"
|
||||
HOME="$ANTIGRAVITY_FAKE_HOME" \
|
||||
AI_MEMORY_ACCEPTANCE_FAKE_MODE=antigravity \
|
||||
AI_MEMORY_ACCEPTANCE_ARGV_LOG="$TMP/antigravity-second-argv.log" \
|
||||
AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_SESSION_ID="$ANTIGRAVITY_FAKE_ID" \
|
||||
AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_HOOK="$ANTIGRAVITY_HOOK" \
|
||||
AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_HOOK_LOG="$TMP/antigravity-second-hook.json" \
|
||||
"$BIN" --data-dir "$DATA" run --workstream edge-antigravity \
|
||||
--executable "$FAKE" agy >"$LOGS/edge-antigravity-second.log" 2>&1
|
||||
)
|
||||
diff -u <(printf '%s\n' --conversation "$ANTIGRAVITY_FAKE_ID") \
|
||||
"$TMP/antigravity-second-argv.log"
|
||||
returned_antigravity=$(assert_managed_leg "$antigravity_ws_hex" antigravity-cli \
|
||||
"$antigravity_second_before" "$antigravity_second_delivery" 0 \
|
||||
"$LOGS/edge-antigravity-second.log" "$antigravity_second_observations")
|
||||
[ "$returned_antigravity" = "$ANTIGRAVITY_FAKE_ID" ] || {
|
||||
printf 'returning Antigravity launch did not resume %s\n' "$ANTIGRAVITY_FAKE_ID" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
# A fresh Antigravity conversation joining Kimi's established workstream must
|
||||
# receive the prior visible ledger through the real hook's injectSteps output.
|
||||
ANTIGRAVITY_CROSS_HOME="$CONFIG/antigravity-cross"
|
||||
ANTIGRAVITY_CROSS_ID="9576275f-7c4e-4709-b372-22d1ad2a0af8"
|
||||
mkdir -p "$ANTIGRAVITY_CROSS_HOME"
|
||||
antigravity_cross_before=$(latest_workstream_sequence "$kimi_ws_hex")
|
||||
antigravity_cross_delivery=$(current_delivery_cursor "$kimi_ws_hex" antigravity-cli)
|
||||
antigravity_cross_observations=$(agent_observation_count antigravity-cli)
|
||||
(
|
||||
cd "$REPO"
|
||||
HOME="$ANTIGRAVITY_CROSS_HOME" \
|
||||
AI_MEMORY_ACCEPTANCE_FAKE_MODE=antigravity \
|
||||
AI_MEMORY_ACCEPTANCE_ARGV_LOG="$TMP/antigravity-cross-argv.log" \
|
||||
AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_SESSION_ID="$ANTIGRAVITY_CROSS_ID" \
|
||||
AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_HOOK="$ANTIGRAVITY_HOOK" \
|
||||
AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_HOOK_LOG="$TMP/antigravity-cross-hook.json" \
|
||||
"$BIN" --data-dir "$DATA" run --workstream edge-kimi --executable "$FAKE" \
|
||||
antigravity-cli >"$LOGS/edge-antigravity-cross.log" 2>&1
|
||||
)
|
||||
if grep -q . "$TMP/antigravity-cross-argv.log"; then
|
||||
printf 'fresh Antigravity launch unexpectedly received a session selector\n' >&2
|
||||
cat "$TMP/antigravity-cross-argv.log" >&2
|
||||
exit 1
|
||||
fi
|
||||
jq -e '.injectSteps[0].ephemeralMessage | contains("AMWS-FAKE-KIMI")' \
|
||||
"$TMP/antigravity-cross-hook.json" >/dev/null || {
|
||||
printf 'Antigravity hook did not receive the prior Kimi workstream history\n' >&2
|
||||
cat "$TMP/antigravity-cross-hook.json" >&2
|
||||
exit 1
|
||||
}
|
||||
assert_managed_leg "$kimi_ws_hex" antigravity-cli "$antigravity_cross_before" \
|
||||
"$antigravity_cross_delivery" 1 "$LOGS/edge-antigravity-cross.log" \
|
||||
"$antigravity_cross_observations" >/dev/null
|
||||
|
||||
# Grok fake-mode fixture: the fake grok honors the wrapper's `--session-id`
|
||||
# (fresh) and `--resume <id>` (returning) selectors, writes the native store
|
||||
# layout ($GROK_HOME/sessions/<bucket>/<id>/summary.json plus
|
||||
@@ -736,9 +905,12 @@ for requested_harness in "${requested_harnesses[@]}"; do
|
||||
case "$requested_harness" in
|
||||
kimi-code | kimi-cli) harness=kimi ;;
|
||||
grok-build) harness=grok ;;
|
||||
antigravity-cli | agy) harness=antigravity ;;
|
||||
*) harness=$requested_harness ;;
|
||||
esac
|
||||
if command -v "$harness" >/dev/null 2>&1; then
|
||||
harness_command=$harness
|
||||
[ "$harness" != antigravity ] || harness_command=agy
|
||||
if command -v "$harness_command" >/dev/null 2>&1; then
|
||||
harnesses+=("$harness")
|
||||
else
|
||||
printf 'skipping unavailable harness: %s\n' "$requested_harness" >&2
|
||||
@@ -762,10 +934,14 @@ OMP_AGENT_DIR="$CONFIG/omp/agent"
|
||||
CRUSH_DATA_DIR="$CONFIG/crush/data"
|
||||
KIMI_ACCEPTANCE_HOME="$CONFIG/kimi-home"
|
||||
GROK_ACCEPTANCE_HOME="$CONFIG/grok-home"
|
||||
ANTIGRAVITY_ACCEPTANCE_HOME="$CONFIG/antigravity-home"
|
||||
ANTIGRAVITY_HOOKS="$ANTIGRAVITY_ACCEPTANCE_HOME/.gemini/config/hooks.json"
|
||||
mkdir -p "$(dirname "$CLAUDE_SETTINGS")" "$(dirname "$CODEX_HOOKS")" \
|
||||
"$(dirname "$OPENCODE_PLUGIN")" "$(dirname "$PI_EXTENSION")" \
|
||||
"$(dirname "$OMP_EXTENSION")" "$OMP_AGENT_DIR" "$OPENCODE_DATA_HOME/opencode" \
|
||||
"$CRUSH_DATA_DIR" "$KIMI_ACCEPTANCE_HOME" "$GROK_ACCEPTANCE_HOME"
|
||||
"$CRUSH_DATA_DIR" "$KIMI_ACCEPTANCE_HOME" "$GROK_ACCEPTANCE_HOME" \
|
||||
"$(dirname "$ANTIGRAVITY_HOOKS")" \
|
||||
"$ANTIGRAVITY_ACCEPTANCE_HOME/.gemini/antigravity-cli"
|
||||
|
||||
# Redirect native transcript stores into the fixture while reusing only the
|
||||
# minimum authentication material required for real model calls.
|
||||
@@ -829,6 +1005,26 @@ for relative in auth.json config.toml; do
|
||||
fi
|
||||
done
|
||||
|
||||
# Antigravity resolves login, settings, hooks, and conversation databases below
|
||||
# ~/.gemini. Copy only the minimum OAuth and settings files into an isolated
|
||||
# HOME; private history, conversation databases, logs, and caches stay out.
|
||||
for relative in google_accounts.json oauth_creds.json installation_id; do
|
||||
if [ -f "$HOME/.gemini/$relative" ]; then
|
||||
cp "$HOME/.gemini/$relative" \
|
||||
"$ANTIGRAVITY_ACCEPTANCE_HOME/.gemini/$relative"
|
||||
fi
|
||||
done
|
||||
for relative in antigravity-oauth-token installation_id jetski_state.pbtxt settings.json; do
|
||||
if [ -f "$HOME/.gemini/antigravity-cli/$relative" ]; then
|
||||
cp "$HOME/.gemini/antigravity-cli/$relative" \
|
||||
"$ANTIGRAVITY_ACCEPTANCE_HOME/.gemini/antigravity-cli/$relative"
|
||||
fi
|
||||
done
|
||||
if [ -f "$HOME/.gemini/config/config.json" ]; then
|
||||
cp "$HOME/.gemini/config/config.json" \
|
||||
"$ANTIGRAVITY_ACCEPTANCE_HOME/.gemini/config/config.json"
|
||||
fi
|
||||
|
||||
install_hook() {
|
||||
local agent=$1
|
||||
local target=$2
|
||||
@@ -838,7 +1034,7 @@ install_hook() {
|
||||
--config-file "$target"
|
||||
)
|
||||
case "$agent" in
|
||||
claude-code | codex | kimi-code)
|
||||
claude-code | codex | kimi-code | antigravity-cli)
|
||||
command+=(--hooks-dir "$ROOT/hooks")
|
||||
;;
|
||||
esac
|
||||
@@ -852,12 +1048,14 @@ install_hook opencode "$OPENCODE_PLUGIN"
|
||||
install_hook pi "$PI_EXTENSION"
|
||||
install_hook omp "$OMP_EXTENSION"
|
||||
install_hook kimi-code "$KIMI_ACCEPTANCE_HOME/config.toml"
|
||||
install_hook antigravity-cli "$ANTIGRAVITY_HOOKS"
|
||||
|
||||
agent_wire_name() {
|
||||
case "$1" in
|
||||
claude) printf 'claude-code\n' ;;
|
||||
opencode) printf 'open-code\n' ;;
|
||||
kimi) printf 'kimi-code\n' ;;
|
||||
antigravity) printf 'antigravity-cli\n' ;;
|
||||
*) printf '%s\n' "$1" ;;
|
||||
esac
|
||||
}
|
||||
@@ -876,9 +1074,11 @@ run_harness() {
|
||||
local expected_agent
|
||||
local before_sequence=0
|
||||
local before_delivery=0
|
||||
local before_observations=0
|
||||
local existing_hex
|
||||
local -a wrapper_args native_args
|
||||
expected_agent=$(agent_wire_name "$harness")
|
||||
before_observations=$(agent_observation_count "$expected_agent")
|
||||
existing_hex=$(workstream_hex "$WORKSTREAM_NAME")
|
||||
if [ -n "$existing_hex" ]; then
|
||||
before_sequence=$(latest_workstream_sequence "$existing_hex")
|
||||
@@ -924,6 +1124,10 @@ run_harness() {
|
||||
native_args=(-p "$prompt")
|
||||
[ -z "${AI_MEMORY_ACCEPTANCE_GROK_MODEL:-}" ] || native_args=(-p -m "$AI_MEMORY_ACCEPTANCE_GROK_MODEL" "$prompt")
|
||||
;;
|
||||
antigravity)
|
||||
native_args=(-p --print-timeout "${AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_TIMEOUT:-5m}" "$prompt")
|
||||
[ -z "${AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_MODEL:-}" ] || native_args=(-p --print-timeout "${AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_TIMEOUT:-5m}" --model "$AI_MEMORY_ACCEPTANCE_ANTIGRAVITY_MODEL" "$prompt")
|
||||
;;
|
||||
*)
|
||||
printf 'unsupported acceptance harness: %s\n' "$harness" >&2
|
||||
return 1
|
||||
@@ -957,6 +1161,10 @@ run_harness() {
|
||||
(cd "$REPO" && GROK_HOME="$GROK_ACCEPTANCE_HOME" \
|
||||
"$BIN" --data-dir "$DATA" run "${wrapper_args[@]}" "$harness" "${native_args[@]}") \
|
||||
>"$log" 2>&1
|
||||
elif [ "$harness" = antigravity ]; then
|
||||
(cd "$REPO" && HOME="$ANTIGRAVITY_ACCEPTANCE_HOME" \
|
||||
"$BIN" --data-dir "$DATA" run "${wrapper_args[@]}" "$harness" "${native_args[@]}") \
|
||||
>"$log" 2>&1
|
||||
else
|
||||
(cd "$REPO" && "$BIN" --data-dir "$DATA" run \
|
||||
"${wrapper_args[@]}" "$harness" "${native_args[@]}") >"$log" 2>&1
|
||||
@@ -971,7 +1179,8 @@ run_harness() {
|
||||
return 1
|
||||
}
|
||||
if ! native_id=$(assert_managed_leg "$hex" "$expected_agent" \
|
||||
"$before_sequence" "$before_delivery" "$expect_context" "$log"); then
|
||||
"$before_sequence" "$before_delivery" "$expect_context" "$log" \
|
||||
"$before_observations"); then
|
||||
return 1
|
||||
fi
|
||||
printf '%s\n' "$native_id"
|
||||
|
||||
Reference in New Issue
Block a user