mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-10-03 00:01:55 +00:00
feat(agents): recognise Empryo and Prime Agent, add Antigravity status hooks (#975)
* feat(agents): recognise Empryo and Prime Agent Prime Agent (PrimeIntellect-ai/prime-agent) is a Pi fork: detected as `prime-agent`, resumes with `--resume <id>`, forks with `--fork <id>`, `--no-session` opts out, and reports status through the shared Pi extension bridge at ~/.prime/agent/extensions/tty7/index.ts. Empryo is detected as `empryo` and resumes with `empryo --session <id>`. No hook installer yet: Empryo filters TTY7* variables out of hook processes, so `tty7-app agent-hook` would stop at the missing marker. * feat(agents): Antigravity status hooks Antigravity CLI (`agy`) now installs a `tty7` hook set in ~/.gemini/config/hooks.json, next to the user's own sets: PreInvocation -> prompt-submit, PostToolUse -> tool-complete, Stop -> stop. Only the first PreInvocation of a turn (invocationNum 0) counts as a new prompt, later ones keep the turn working; a Stop with fullyIdle false is ignored. conversationId and workspacePaths feed the session id and cwd. PreToolUse is left alone because it must answer with a decision. * fix(agents): index Prime Agent and Antigravity in settings, scope conversationId alias - Add settings titles, search keywords (en/ja/zh) and Agents index entries for the two new HookAgents; agent_rows_are_in_the_search_index requires one per HookAgent::ALL. - Read Antigravity's conversationId as the session id only for antigravity: the alias table is last-write-wins, so a global alias could replace another agent's session id. - Drop the stray .empryo/ job records. --------- Co-authored-by: kalpak <you@example.com> Co-authored-by: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com>
This commit is contained in:
co-authored by
kalpak
l0ng-ai
parent
e6009636b5
commit
7a32e7dbbc
@@ -52,7 +52,7 @@ Native builds for macOS, Windows, and Linux on [**Releases**](https://github.com
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Agent-aware** | per-pane detection (23 CLIs) · status dot · notifications · branch + diff · tray icon when input is needed · resume after reboot · tab sidebar grouped by repository, with pinned groups |
|
||||
| **Agent-aware** | per-pane detection (25 CLIs) · status dot · notifications · branch + diff · tray icon when input is needed · resume after reboot · tab sidebar grouped by repository, with pinned groups |
|
||||
| **CLI + Skills** | bundled `tty7` CLI · [agent skill](skills/tty7/SKILL.md) · `run` streams a command and exits with its code · `split` · `send` · `wait --until free` · `capture` |
|
||||
| **Editor-grade input** | ghost suggestions from history · explained tab completion · syntax highlighting · multi-line editing · click places the caret · <kbd>⌃ R</kbd> fuzzy history |
|
||||
| **Window** | tabs & splits · <kbd>⌘ P</kbd> palette · <kbd>⌘ F</kbd> scrollback search · <kbd>⌘ J</kbd> panel with process tree and listening ports · 13 themes, your own YAML, iTerm2 import · IME |
|
||||
@@ -69,7 +69,7 @@ after a reboot. **Fork** needs both — the agent's own fork command, and the ho
|
||||
that tells tty7 which session to fork.
|
||||
|
||||
<details>
|
||||
<summary>The full support matrix, all twenty-three</summary>
|
||||
<summary>The full support matrix, all twenty-five</summary>
|
||||
|
||||
| Agent | Detected | Status · resume | Fork |
|
||||
|---|:-:|:-:|:-:|
|
||||
@@ -79,6 +79,7 @@ that tells tty7 which session to fork.
|
||||
| **Grok** | ✓ | ✓ | ✓ |
|
||||
| **OpenCode** | ✓ | ✓ | ✓ |
|
||||
| **Oh My Pi** | ✓ | ✓ | ✓ |
|
||||
| **Prime Agent** | ✓ | ✓ | ✓ |
|
||||
| **Droid** | ✓ | ✓ | ✓ |
|
||||
| **Qwen Code** | ✓ | ✓ | ✓ |
|
||||
| **Goose** | ✓ | ✓ | ✓ |
|
||||
@@ -89,13 +90,14 @@ that tells tty7 which session to fork.
|
||||
| **Kimi Code** | ✓ | ✓ | |
|
||||
| **Pi** | ✓ | ✓ | |
|
||||
| **Crush** | ✓ | ✓ | |
|
||||
| **Antigravity** | ✓ | ✓ | |
|
||||
| **Cursor** | ✓ | ✓ | |
|
||||
| Aider | ✓ | | |
|
||||
| Amp | ✓ | | |
|
||||
| Auggie | ✓ | | |
|
||||
| Hermes | ✓ | | |
|
||||
| Vibe | ✓ | | |
|
||||
| Antigravity | ✓ | | |
|
||||
| Empryo | ✓ | | |
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
+3
-1
@@ -77,6 +77,7 @@ macOS、Windows、Linux 的原生构建都在 [**Releases**](https://github.com/
|
||||
| **Grok** | ✓ | ✓ | ✓ |
|
||||
| **OpenCode** | ✓ | ✓ | ✓ |
|
||||
| **Oh My Pi** | ✓ | ✓ | ✓ |
|
||||
| **Prime Agent** | ✓ | ✓ | ✓ |
|
||||
| **Droid** | ✓ | ✓ | ✓ |
|
||||
| **Qwen Code** | ✓ | ✓ | ✓ |
|
||||
| **Goose** | ✓ | ✓ | ✓ |
|
||||
@@ -84,13 +85,14 @@ macOS、Windows、Linux 的原生构建都在 [**Releases**](https://github.com/
|
||||
| **Copilot** | ✓ | ✓ | |
|
||||
| **Kimi Code** | ✓ | ✓ | |
|
||||
| **Pi** | ✓ | ✓ | |
|
||||
| **Antigravity** | ✓ | ✓ | |
|
||||
| Aider | ✓ | | |
|
||||
| Amp | ✓ | | |
|
||||
| Cursor | ✓ | | |
|
||||
| Auggie | ✓ | | |
|
||||
| Hermes | ✓ | | |
|
||||
| Vibe | ✓ | | |
|
||||
| Antigravity | ✓ | | |
|
||||
| Empryo | ✓ | | |
|
||||
|
||||
</details>
|
||||
|
||||
|
||||
@@ -43,6 +43,26 @@ fn effective_agent(agent: &str, ran_by_grok: bool) -> &str {
|
||||
}
|
||||
|
||||
fn effective_event<'a>(agent: &str, event: &'a str, stdin_json: &str) -> Option<&'a str> {
|
||||
// Antigravity has no turn-start event, only `PreInvocation` before every
|
||||
// model call. The first call of a turn starts it; later ones are the same
|
||||
// turn still working, and must not count as new prompts. `Stop` with
|
||||
// `fullyIdle: false` ends one execution loop while more are queued.
|
||||
if agent == "antigravity"
|
||||
&& let Ok(payload) = serde_json::from_str::<serde_json::Value>(stdin_json)
|
||||
{
|
||||
if event == "prompt-submit"
|
||||
&& payload
|
||||
.get("invocationNum")
|
||||
.and_then(|n| n.as_u64())
|
||||
.unwrap_or(0)
|
||||
> 0
|
||||
{
|
||||
return Some("tool-complete");
|
||||
}
|
||||
if event == "stop" && payload.get("fullyIdle").and_then(|v| v.as_bool()) == Some(false) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
// Qoder and CodeBuddy also emit SessionStart after compacting the active
|
||||
// turn. Preserve its status until a real turn or session boundary arrives.
|
||||
if matches!(agent, "qodercli" | "codebuddy")
|
||||
@@ -90,11 +110,25 @@ fn build_hook_sequence(agent: &str, event: &str, stdin_json: &str) -> Vec<u8> {
|
||||
body[key] = serde_json::Value::String(v.to_string());
|
||||
}
|
||||
}
|
||||
// Antigravity names the session after the conversation, in camelCase.
|
||||
// Scoped to it: the alias table is last-write-wins, and another agent's
|
||||
// unrelated `conversationId` must not replace its real session id.
|
||||
if agent == "antigravity"
|
||||
&& body.get("session_id").is_none()
|
||||
&& let Some(id) = payload
|
||||
.get("conversationId")
|
||||
.and_then(|v| v.as_str())
|
||||
.filter(|v| !v.is_empty())
|
||||
{
|
||||
body["session_id"] = serde_json::Value::String(id.to_string());
|
||||
}
|
||||
// Cursor puts `cwd` only on its tool events; the rest carry the workspace
|
||||
// roots instead, and the first of those is where the session runs.
|
||||
// Antigravity's `workspacePaths` is the same list under its own name.
|
||||
if body.get("cwd").is_none()
|
||||
&& let Some(root) = payload
|
||||
.get("workspace_roots")
|
||||
.or_else(|| payload.get("workspacePaths"))
|
||||
.and_then(|r| r.as_array())
|
||||
.and_then(|r| r.first())
|
||||
.and_then(|r| r.as_str())
|
||||
@@ -285,10 +319,12 @@ pub enum HookAgent {
|
||||
Crush,
|
||||
CodeBuddy,
|
||||
Cursor,
|
||||
PrimeAgent,
|
||||
Antigravity,
|
||||
}
|
||||
|
||||
impl HookAgent {
|
||||
pub const ALL: [HookAgent; 17] = [
|
||||
pub const ALL: [HookAgent; 19] = [
|
||||
HookAgent::Claude,
|
||||
HookAgent::Codex,
|
||||
HookAgent::TraeCode,
|
||||
@@ -306,6 +342,8 @@ impl HookAgent {
|
||||
HookAgent::Crush,
|
||||
HookAgent::CodeBuddy,
|
||||
HookAgent::Cursor,
|
||||
HookAgent::PrimeAgent,
|
||||
HookAgent::Antigravity,
|
||||
];
|
||||
|
||||
/// The hooks behind a detected agent process, if it has any.
|
||||
@@ -323,6 +361,8 @@ impl HookAgent {
|
||||
CLIAgent::Pi => Some(HookAgent::Pi),
|
||||
CLIAgent::Grok => Some(HookAgent::Grok),
|
||||
CLIAgent::OhMyPi => Some(HookAgent::OhMyPi),
|
||||
CLIAgent::PrimeAgent => Some(HookAgent::PrimeAgent),
|
||||
CLIAgent::Antigravity => Some(HookAgent::Antigravity),
|
||||
CLIAgent::Gemini => Some(HookAgent::Gemini),
|
||||
CLIAgent::Droid => Some(HookAgent::Droid),
|
||||
CLIAgent::Qwen => Some(HookAgent::Qwen),
|
||||
@@ -337,7 +377,7 @@ impl HookAgent {
|
||||
| CLIAgent::Auggie
|
||||
| CLIAgent::Hermes
|
||||
| CLIAgent::Vibe
|
||||
| CLIAgent::Antigravity => None,
|
||||
| CLIAgent::Empryo => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -361,6 +401,8 @@ impl HookAgent {
|
||||
| HookAgent::Pi
|
||||
| HookAgent::Grok
|
||||
| HookAgent::OhMyPi
|
||||
| HookAgent::PrimeAgent
|
||||
| HookAgent::Antigravity
|
||||
| HookAgent::Goose
|
||||
| HookAgent::Kimi => None,
|
||||
}
|
||||
@@ -406,6 +448,8 @@ impl HookAgent {
|
||||
HookAgent::Pi => "pi",
|
||||
HookAgent::Grok => "grok",
|
||||
HookAgent::OhMyPi => "omp",
|
||||
HookAgent::PrimeAgent => "prime-agent",
|
||||
HookAgent::Antigravity => "antigravity",
|
||||
HookAgent::Gemini => "gemini",
|
||||
HookAgent::Droid => "droid",
|
||||
HookAgent::Qwen => "qwen",
|
||||
@@ -428,6 +472,8 @@ impl HookAgent {
|
||||
HookAgent::Pi => "Pi",
|
||||
HookAgent::Grok => "Grok Build",
|
||||
HookAgent::OhMyPi => "Oh My Pi",
|
||||
HookAgent::PrimeAgent => "Prime Agent",
|
||||
HookAgent::Antigravity => "Antigravity",
|
||||
HookAgent::Gemini => "Gemini",
|
||||
HookAgent::Droid => "Droid",
|
||||
HookAgent::Qwen => "Qwen Code",
|
||||
@@ -455,6 +501,10 @@ impl HookAgent {
|
||||
&["opencode", "plugins", OWNED_FILE_STEM_JS],
|
||||
),
|
||||
HookAgent::Pi => target.under_home(&[".pi", "agent", "extensions", "tty7", "index.ts"]),
|
||||
HookAgent::PrimeAgent => {
|
||||
target.under_home(&[".prime", "agent", "extensions", "tty7", "index.ts"])
|
||||
}
|
||||
HookAgent::Antigravity => target.under_home(&[".gemini", "config", "hooks.json"]),
|
||||
HookAgent::Grok => target.under_home(&[".grok", "hooks", OWNED_FILE_STEM_JSON]),
|
||||
HookAgent::OhMyPi => {
|
||||
target.under_home(&[".omp", "agent", "extensions", "tty7", "index.ts"])
|
||||
@@ -683,6 +733,9 @@ pub enum HooksState {
|
||||
|
||||
pub fn hooks_state(target: &HookTarget, agent: HookAgent) -> HooksState {
|
||||
let path = agent.target_path(target);
|
||||
if agent == HookAgent::Antigravity {
|
||||
return named_hook_set_state(target, &path, &antigravity_hook_set(target));
|
||||
}
|
||||
if let Some(events) = agent.toml_hook_events() {
|
||||
return toml_hooks_state(target, &path, agent, events);
|
||||
}
|
||||
@@ -719,6 +772,10 @@ pub enum HookOutcome {
|
||||
|
||||
pub fn install_hooks(target: &HookTarget, agent: HookAgent) -> anyhow::Result<HookOutcome> {
|
||||
let path = agent.target_path(target);
|
||||
if agent == HookAgent::Antigravity {
|
||||
named_hook_set_install(target, &path, antigravity_hook_set(target))?;
|
||||
return Ok(HookOutcome::Installed);
|
||||
}
|
||||
if let Some(events) = agent.toml_hook_events() {
|
||||
toml_hooks_install(target, &path, agent, events)?;
|
||||
return Ok(HookOutcome::Installed);
|
||||
@@ -744,6 +801,9 @@ pub fn install_hooks(target: &HookTarget, agent: HookAgent) -> anyhow::Result<Ho
|
||||
|
||||
pub fn uninstall_hooks(target: &HookTarget, agent: HookAgent) -> anyhow::Result<HookOutcome> {
|
||||
let path = agent.target_path(target);
|
||||
if agent == HookAgent::Antigravity {
|
||||
return named_hook_set_uninstall(target, &path);
|
||||
}
|
||||
if agent.toml_hook_events().is_some() {
|
||||
return toml_hooks_uninstall(target, &path, agent);
|
||||
}
|
||||
@@ -1324,11 +1384,108 @@ fn enable_codex_hooks_feature() -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The key tty7's entry lives under in Antigravity's `hooks.json`.
|
||||
///
|
||||
/// Antigravity names every hook set at the root of the file —
|
||||
/// `{"<name>": {"<Event>": [...]}}` — rather than grouping handlers under a
|
||||
/// shared `hooks` object the way Claude does, so tty7 owns one named set and
|
||||
/// never has to pick its commands out of the user's lists.
|
||||
const ANTIGRAVITY_HOOK_SET: &str = "tty7";
|
||||
|
||||
/// Antigravity CLI's lifecycle events, mapped onto tty7's.
|
||||
///
|
||||
/// It has no session-start or session-end event. `PreInvocation` fires before
|
||||
/// every model call of a turn, so `effective_event` keeps only the first one
|
||||
/// as the turn start; `Stop` fires when the execution loop ends. The
|
||||
/// `PreToolUse` gate is left alone: it must answer with a `decision`, and an
|
||||
/// empty reply from a status hook has no business deciding anything.
|
||||
fn antigravity_hook_set(target: &HookTarget) -> serde_json::Value {
|
||||
let handler = |event: &str| {
|
||||
serde_json::json!({
|
||||
"type": "command",
|
||||
"command": target.hook_command(HookAgent::Antigravity, event),
|
||||
"timeout": 5,
|
||||
})
|
||||
};
|
||||
serde_json::json!({
|
||||
"PreInvocation": [handler("prompt-submit")],
|
||||
"PostToolUse": [{ "matcher": "*", "hooks": [handler("tool-complete")] }],
|
||||
"Stop": [handler("stop")],
|
||||
})
|
||||
}
|
||||
|
||||
fn read_json_object(target: &HookTarget, path: &Path) -> anyhow::Result<Option<serde_json::Value>> {
|
||||
let text = match target.read(path) {
|
||||
Ok(text) => text,
|
||||
Err(e) if e.kind() == io::ErrorKind::NotFound => return Ok(None),
|
||||
Err(e) => return Err(anyhow::anyhow!("read {}: {e}", path.display())),
|
||||
};
|
||||
if text.trim().is_empty() {
|
||||
return Ok(Some(serde_json::json!({})));
|
||||
}
|
||||
let root: serde_json::Value = serde_json::from_str(&text).map_err(|e| {
|
||||
anyhow::anyhow!(
|
||||
"{} is not valid JSON ({e}); not touching it",
|
||||
path.display()
|
||||
)
|
||||
})?;
|
||||
if !root.is_object() {
|
||||
return Err(anyhow::anyhow!(
|
||||
"{} is not a JSON object; not touching it",
|
||||
path.display()
|
||||
));
|
||||
}
|
||||
Ok(Some(root))
|
||||
}
|
||||
|
||||
fn named_hook_set_state(
|
||||
target: &HookTarget,
|
||||
path: &Path,
|
||||
expected: &serde_json::Value,
|
||||
) -> HooksState {
|
||||
match read_json_object(target, path) {
|
||||
Ok(Some(root)) => match root.get(ANTIGRAVITY_HOOK_SET) {
|
||||
None => HooksState::NotInstalled,
|
||||
Some(set) if set == expected => HooksState::Installed,
|
||||
Some(_) => HooksState::Outdated,
|
||||
},
|
||||
_ => HooksState::NotInstalled,
|
||||
}
|
||||
}
|
||||
|
||||
fn named_hook_set_install(
|
||||
target: &HookTarget,
|
||||
path: &Path,
|
||||
set: serde_json::Value,
|
||||
) -> anyhow::Result<()> {
|
||||
let mut root = read_json_object(target, path)?.unwrap_or_else(|| serde_json::json!({}));
|
||||
root.as_object_mut()
|
||||
.unwrap()
|
||||
.insert(ANTIGRAVITY_HOOK_SET.to_string(), set);
|
||||
target.write(path, serde_json::to_string_pretty(&root)?.as_bytes())
|
||||
}
|
||||
|
||||
fn named_hook_set_uninstall(target: &HookTarget, path: &Path) -> anyhow::Result<HookOutcome> {
|
||||
let Some(mut root) = read_json_object(target, path)? else {
|
||||
return Ok(HookOutcome::NothingInstalled);
|
||||
};
|
||||
if root
|
||||
.as_object_mut()
|
||||
.unwrap()
|
||||
.remove(ANTIGRAVITY_HOOK_SET)
|
||||
.is_none()
|
||||
{
|
||||
return Ok(HookOutcome::NoTty7Hooks);
|
||||
}
|
||||
target.write(path, serde_json::to_string_pretty(&root)?.as_bytes())?;
|
||||
Ok(HookOutcome::Removed)
|
||||
}
|
||||
|
||||
fn owned_file_content(target: &HookTarget, agent: HookAgent) -> Option<String> {
|
||||
match agent {
|
||||
HookAgent::Copilot => copilot_hooks_json(target),
|
||||
HookAgent::OpenCode => opencode_plugin_js(target),
|
||||
HookAgent::Pi | HookAgent::OhMyPi => pi_extension_ts(target, agent),
|
||||
HookAgent::Pi | HookAgent::OhMyPi | HookAgent::PrimeAgent => pi_extension_ts(target, agent),
|
||||
HookAgent::Grok => grok_hooks_json(target),
|
||||
HookAgent::Goose => goose_hooks_json(target),
|
||||
HookAgent::Claude
|
||||
@@ -1341,6 +1498,7 @@ fn owned_file_content(target: &HookTarget, agent: HookAgent) -> Option<String> {
|
||||
| HookAgent::Crush
|
||||
| HookAgent::CodeBuddy
|
||||
| HookAgent::Cursor
|
||||
| HookAgent::Antigravity
|
||||
| HookAgent::Kimi => None,
|
||||
}
|
||||
}
|
||||
@@ -1568,6 +1726,7 @@ fn pi_extension_ts(target: &HookTarget, agent: HookAgent) -> Option<String> {
|
||||
let (slug, package) = match agent {
|
||||
HookAgent::Pi => ("pi", "@mariozechner/pi-coding-agent"),
|
||||
HookAgent::OhMyPi => ("omp", "@oh-my-pi/pi-coding-agent"),
|
||||
HookAgent::PrimeAgent => ("prime-agent", "@earendil-works/pi-coding-agent"),
|
||||
_ => return None,
|
||||
};
|
||||
let exe = serde_json::to_string(&target.exe.display().to_string()).ok()?;
|
||||
@@ -1838,6 +1997,10 @@ mod tests {
|
||||
(HookAgent::Crush, "/home/me/.config/crush/crush.json"),
|
||||
(HookAgent::CodeBuddy, "/home/me/.codebuddy/settings.json"),
|
||||
(HookAgent::Cursor, "/home/me/.cursor/hooks.json"),
|
||||
(
|
||||
HookAgent::PrimeAgent,
|
||||
"/home/me/.prime/agent/extensions/tty7/index.ts",
|
||||
),
|
||||
] {
|
||||
assert_eq!(
|
||||
agent.target_path(&t),
|
||||
@@ -2582,6 +2745,127 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn antigravity_owns_a_named_hook_set_beside_the_users() {
|
||||
let dir = std::env::temp_dir().join(format!("tty7-agy-hooks-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
let host = FakeRemote::shared();
|
||||
let target = HookTarget::remote(&*host, dir.clone());
|
||||
let path = HookAgent::Antigravity.target_path(&target);
|
||||
assert_eq!(path, dir.join(".gemini/config/hooks.json"));
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
let theirs = serde_json::json!({
|
||||
"my-linter-hook": {
|
||||
"PostToolUse": [{ "matcher": "run_command", "hooks": [{ "command": "./lint.sh" }] }]
|
||||
}
|
||||
});
|
||||
std::fs::write(&path, theirs.to_string()).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hooks_state(&target, HookAgent::Antigravity),
|
||||
HooksState::NotInstalled
|
||||
);
|
||||
install_hooks(&target, HookAgent::Antigravity).expect("install succeeds");
|
||||
assert_eq!(
|
||||
hooks_state(&target, HookAgent::Antigravity),
|
||||
HooksState::Installed
|
||||
);
|
||||
let root: serde_json::Value =
|
||||
serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
root["my-linter-hook"], theirs["my-linter-hook"],
|
||||
"the user's set survives"
|
||||
);
|
||||
let set = &root[ANTIGRAVITY_HOOK_SET];
|
||||
assert!(
|
||||
set["PreInvocation"][0]["command"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.ends_with("agent-hook antigravity prompt-submit")
|
||||
);
|
||||
assert_eq!(set["PostToolUse"][0]["matcher"], "*");
|
||||
assert!(
|
||||
set["Stop"][0]["command"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.ends_with("agent-hook antigravity stop")
|
||||
);
|
||||
assert!(
|
||||
set.get("PreToolUse").is_none(),
|
||||
"a status hook must not gate tools"
|
||||
);
|
||||
|
||||
root_set_to(&path, serde_json::json!({ "Stop": [] }), &root);
|
||||
assert_eq!(
|
||||
hooks_state(&target, HookAgent::Antigravity),
|
||||
HooksState::Outdated
|
||||
);
|
||||
install_hooks(&target, HookAgent::Antigravity).expect("reinstall succeeds");
|
||||
assert_eq!(
|
||||
hooks_state(&target, HookAgent::Antigravity),
|
||||
HooksState::Installed
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
uninstall_hooks(&target, HookAgent::Antigravity).expect("uninstall succeeds"),
|
||||
HookOutcome::Removed
|
||||
);
|
||||
let root: serde_json::Value =
|
||||
serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
|
||||
assert_eq!(
|
||||
root, theirs,
|
||||
"uninstall leaves exactly what was there before"
|
||||
);
|
||||
assert_eq!(
|
||||
uninstall_hooks(&target, HookAgent::Antigravity).expect("second uninstall"),
|
||||
HookOutcome::NoTty7Hooks
|
||||
);
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
fn root_set_to(path: &Path, set: serde_json::Value, root: &serde_json::Value) {
|
||||
let mut root = root.clone();
|
||||
root[ANTIGRAVITY_HOOK_SET] = set;
|
||||
std::fs::write(path, root.to_string()).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn antigravity_payloads_map_onto_turns() {
|
||||
let first = r#"{"conversationId":"c-1","invocationNum":0,"workspacePaths":["/repo"]}"#;
|
||||
let later = r#"{"conversationId":"c-1","invocationNum":2,"workspacePaths":["/repo"]}"#;
|
||||
assert_eq!(
|
||||
effective_event("antigravity", "prompt-submit", first),
|
||||
Some("prompt-submit")
|
||||
);
|
||||
assert_eq!(
|
||||
effective_event("antigravity", "prompt-submit", later),
|
||||
Some("tool-complete")
|
||||
);
|
||||
assert_eq!(
|
||||
effective_event(
|
||||
"antigravity",
|
||||
"stop",
|
||||
r#"{"conversationId":"c-1","fullyIdle":true}"#
|
||||
),
|
||||
Some("stop")
|
||||
);
|
||||
assert_eq!(
|
||||
effective_event(
|
||||
"antigravity",
|
||||
"stop",
|
||||
r#"{"conversationId":"c-1","fullyIdle":false}"#
|
||||
),
|
||||
None
|
||||
);
|
||||
let ev = round_trip("antigravity", "prompt-submit", first);
|
||||
assert_eq!(ev.session_id.as_deref(), Some("c-1"));
|
||||
assert_eq!(ev.cwd.as_deref(), Some(Path::new("/repo")));
|
||||
assert_eq!(
|
||||
ev.agent,
|
||||
Some(crate::core::cli_agent::CLIAgent::Antigravity)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_remote_install_round_trips_through_the_host() {
|
||||
let dir = std::env::temp_dir().join(format!("tty7-remote-hooks-{}", std::process::id()));
|
||||
@@ -2679,6 +2963,11 @@ mod tests {
|
||||
for (agent, slug, package) in [
|
||||
(HookAgent::Pi, "pi", "@mariozechner/pi-coding-agent"),
|
||||
(HookAgent::OhMyPi, "omp", "@oh-my-pi/pi-coding-agent"),
|
||||
(
|
||||
HookAgent::PrimeAgent,
|
||||
"prime-agent",
|
||||
"@earendil-works/pi-coding-agent",
|
||||
),
|
||||
] {
|
||||
let bridge =
|
||||
pi_extension_ts(&target, agent).unwrap_or_else(|| panic!("{slug} content builds"));
|
||||
|
||||
@@ -29,10 +29,12 @@ pub enum CLIAgent {
|
||||
QoderCLI,
|
||||
Crush,
|
||||
CodeBuddy,
|
||||
Empryo,
|
||||
PrimeAgent,
|
||||
}
|
||||
|
||||
impl CLIAgent {
|
||||
pub const ALL: [CLIAgent; 23] = [
|
||||
pub const ALL: [CLIAgent; 25] = [
|
||||
CLIAgent::Claude,
|
||||
CLIAgent::Codex,
|
||||
CLIAgent::TraeCode,
|
||||
@@ -56,6 +58,8 @@ impl CLIAgent {
|
||||
CLIAgent::QoderCLI,
|
||||
CLIAgent::Crush,
|
||||
CLIAgent::CodeBuddy,
|
||||
CLIAgent::Empryo,
|
||||
CLIAgent::PrimeAgent,
|
||||
];
|
||||
|
||||
fn aliases(self) -> &'static [&'static str] {
|
||||
@@ -106,6 +110,8 @@ impl CLIAgent {
|
||||
// `cbc` also names the COIN-OR solver; a solver run wearing the
|
||||
// avatar until it exits is the cost of catching the short name.
|
||||
CLIAgent::CodeBuddy => &["codebuddy", "codebuddy-code", "cbc"],
|
||||
CLIAgent::Empryo => &["empryo"],
|
||||
CLIAgent::PrimeAgent => &["prime-agent"],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -134,6 +140,8 @@ impl CLIAgent {
|
||||
CLIAgent::QoderCLI => "qodercli",
|
||||
CLIAgent::Crush => "crush",
|
||||
CLIAgent::CodeBuddy => "codebuddy",
|
||||
CLIAgent::Empryo => "empryo",
|
||||
CLIAgent::PrimeAgent => "prime-agent",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,6 +175,8 @@ impl CLIAgent {
|
||||
CLIAgent::QoderCLI => "Qoder CLI",
|
||||
CLIAgent::Crush => "Crush",
|
||||
CLIAgent::CodeBuddy => "CodeBuddy",
|
||||
CLIAgent::Empryo => "Empryo",
|
||||
CLIAgent::PrimeAgent => "Prime Agent",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,13 +214,15 @@ impl CLIAgent {
|
||||
CLIAgent::Kimi => Some(format!("kimi{flags} --session {session_id}")),
|
||||
CLIAgent::Crush => Some(format!("crush{flags} --session {session_id}")),
|
||||
CLIAgent::CodeBuddy => Some(format!("codebuddy{flags} --resume {session_id}")),
|
||||
CLIAgent::Empryo => Some(format!("empryo{flags} --session {session_id}")),
|
||||
CLIAgent::PrimeAgent => Some(format!("prime-agent{flags} --resume {session_id}")),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn opts_out_of_sessions(self, argv: &[String]) -> bool {
|
||||
let ephemeral: &[&str] = match self {
|
||||
CLIAgent::Pi | CLIAgent::OhMyPi => &["--no-session"],
|
||||
CLIAgent::Pi | CLIAgent::OhMyPi | CLIAgent::PrimeAgent => &["--no-session"],
|
||||
// "Do not save conversation history" — nothing is persisted, so
|
||||
// there is no session left to resume from.
|
||||
CLIAgent::Auggie => &["--dont-save-session"],
|
||||
@@ -245,6 +257,7 @@ impl CLIAgent {
|
||||
)),
|
||||
CLIAgent::OpenCode => Some(format!("opencode{flags} --session {session_id} --fork")),
|
||||
CLIAgent::OhMyPi => Some(format!("omp{flags} --fork {session_id}")),
|
||||
CLIAgent::PrimeAgent => Some(format!("prime-agent{flags} --fork {session_id}")),
|
||||
// Droid forks with a standalone flag rather than resume-plus-a-switch.
|
||||
CLIAgent::Droid => Some(format!("droid{flags} --fork {session_id}")),
|
||||
// `fork` is missing from `amp threads --help`, but the subcommand is
|
||||
@@ -267,6 +280,7 @@ impl CLIAgent {
|
||||
| CLIAgent::Grok
|
||||
| CLIAgent::OpenCode
|
||||
| CLIAgent::OhMyPi
|
||||
| CLIAgent::PrimeAgent
|
||||
| CLIAgent::Droid
|
||||
| CLIAgent::Amp
|
||||
| CLIAgent::Qwen
|
||||
@@ -433,6 +447,12 @@ impl CLIAgent {
|
||||
// `--resume`, `-r` and `--session` are three spellings of one flag
|
||||
// in Oh My Pi; `--session-dir` is a different one and survives.
|
||||
CLIAgent::OhMyPi => &["--resume", "-r", "--session", "--fork", "--continue", "-c"],
|
||||
// Prime Agent is a Pi fork with the same session switches;
|
||||
// `--session-dir` is a different option and survives.
|
||||
CLIAgent::PrimeAgent => &["--resume", "-r", "--fork", "--continue", "-c"],
|
||||
// empryo resumes with `--session <id>`; `--save-session` only
|
||||
// applies to headless runs and is not a session selector.
|
||||
CLIAgent::Empryo => &["--session"],
|
||||
// `--resume`/`-r` is Kimi's hidden alias for `--session`/`-S`.
|
||||
// `--agent`/`--agent-file` bind the main agent at session creation
|
||||
// and Kimi rejects either next to `--session` outright; resuming
|
||||
@@ -548,6 +568,8 @@ impl CLIAgent {
|
||||
CLIAgent::QoderCLI => 0xFFFFFF,
|
||||
// The blue-violet field Charm ships the Crush heart on.
|
||||
CLIAgent::Crush => 0x6B50FF,
|
||||
CLIAgent::Empryo => 0xE8663D,
|
||||
CLIAgent::PrimeAgent => 0x111111,
|
||||
// The near-black field CodeBuddy's own app icon sits on.
|
||||
CLIAgent::CodeBuddy => 0x1F1F1F,
|
||||
}
|
||||
@@ -597,7 +619,9 @@ impl CLIAgent {
|
||||
| CLIAgent::Auggie
|
||||
| CLIAgent::Hermes
|
||||
| CLIAgent::Vibe
|
||||
| CLIAgent::Antigravity => "icons/bot.svg",
|
||||
| CLIAgent::Antigravity
|
||||
| CLIAgent::Empryo
|
||||
| CLIAgent::PrimeAgent => "icons/bot.svg",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1239,7 +1263,15 @@ mod tests {
|
||||
.collect();
|
||||
assert_eq!(
|
||||
fallback,
|
||||
["aider", "auggie", "hermes", "vibe", "antigravity"]
|
||||
[
|
||||
"aider",
|
||||
"auggie",
|
||||
"hermes",
|
||||
"vibe",
|
||||
"antigravity",
|
||||
"empryo",
|
||||
"prime-agent"
|
||||
]
|
||||
);
|
||||
assert!(
|
||||
!fallback.contains(&"omp"),
|
||||
@@ -1255,6 +1287,55 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empryo_and_prime_agent_are_detected_and_resume() {
|
||||
assert_eq!(
|
||||
CLIAgent::detect_from_argv(&argv(&[
|
||||
"/home/me/.empryo/bin/empryo",
|
||||
"--session",
|
||||
"750adff9"
|
||||
])),
|
||||
Some(CLIAgent::Empryo)
|
||||
);
|
||||
assert_eq!(
|
||||
CLIAgent::detect_from_argv(&argv(&["prime-agent", "--model", "x"])),
|
||||
Some(CLIAgent::PrimeAgent)
|
||||
);
|
||||
assert_eq!(CLIAgent::from_slug("empryo"), Some(CLIAgent::Empryo));
|
||||
assert_eq!(
|
||||
CLIAgent::from_slug("prime-agent"),
|
||||
Some(CLIAgent::PrimeAgent)
|
||||
);
|
||||
assert_eq!(
|
||||
CLIAgent::Empryo
|
||||
.resume_command(
|
||||
"750adff9-f366",
|
||||
Some(&argv(&["empryo", "--session", "old"]))
|
||||
)
|
||||
.as_deref(),
|
||||
Some("empryo --session 750adff9-f366")
|
||||
);
|
||||
assert_eq!(
|
||||
CLIAgent::PrimeAgent
|
||||
.resume_command(
|
||||
"s-1",
|
||||
Some(&argv(&["prime-agent", "-r", "old", "--model", "m"]))
|
||||
)
|
||||
.as_deref(),
|
||||
Some("prime-agent --model m --resume s-1")
|
||||
);
|
||||
assert_eq!(
|
||||
CLIAgent::PrimeAgent.fork_command("s-1", None).as_deref(),
|
||||
Some("prime-agent --fork s-1")
|
||||
);
|
||||
assert_eq!(
|
||||
CLIAgent::PrimeAgent
|
||||
.resume_command("s-1", Some(&argv(&["prime-agent", "--no-session"]))),
|
||||
None
|
||||
);
|
||||
assert_eq!(CLIAgent::Empryo.fork_command("s-1", None), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_newer_agents_by_command() {
|
||||
for (cmd, agent) in [
|
||||
|
||||
@@ -15,7 +15,7 @@ need, and what changed.
|
||||
|
||||
## Which agents
|
||||
|
||||
Twenty-three CLIs are recognised on sight, by the command running in the pane:
|
||||
Twenty-five CLIs are recognised on sight, by the command running in the pane:
|
||||
|
||||
| Agent | Command |
|
||||
|---|---|
|
||||
@@ -42,6 +42,8 @@ Twenty-three CLIs are recognised on sight, by the command running in the pane:
|
||||
| Antigravity | `agy`, `antigravity` |
|
||||
| Pi | `pi` |
|
||||
| Oh My Pi | `omp` |
|
||||
| Prime Agent | `prime-agent` |
|
||||
| Empryo | `empryo` |
|
||||
|
||||
Detection sees through the usual disguises: a full path, a `.cmd` or `.exe` on
|
||||
Windows, leading environment assignments, and an interpreter in front
|
||||
@@ -64,7 +66,7 @@ If you launch agents through a wrapper script, map its name to an agent in
|
||||
The key is your command's name; the value is one of the slugs above (`claude`,
|
||||
`codex`, `traecli`, `gemini`, `aider`, `amp`, `opencode`, `copilot`, `cursor`, `goose`,
|
||||
`droid`, `pi`, `auggie`, `hermes`, `vibe`, `antigravity`, `grok`, `qwen`,
|
||||
`omp`, `kimi`, `qodercli`, `crush`, `codebuddy`).
|
||||
`omp`, `kimi`, `qodercli`, `crush`, `codebuddy`, `empryo`, `prime-agent`).
|
||||
|
||||
## Quick launch
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ or right-click the **tab or sidebar row** to open the fork in a new tab.
|
||||
| Grok | `grok --resume <id> --fork-session` |
|
||||
| OpenCode | `opencode --session <id> --fork` |
|
||||
| Oh My Pi | `omp --fork <id>` |
|
||||
| Prime Agent | `prime-agent --fork <id>` |
|
||||
|
||||
It is the agent's own fork command, run in a new pane — nothing is copied by
|
||||
tty7 itself.
|
||||
|
||||
@@ -14,8 +14,8 @@ the agent say which one it is.
|
||||
|
||||
| Agent | |
|
||||
|---|---|
|
||||
| Claude Code · Codex · TraeCode · Copilot CLI · OpenCode · Pi · Grok Build · Oh My Pi · Gemini · Droid · Qwen Code · Goose · Kimi Code · Qoder CLI · Crush · CodeBuddy · Cursor CLI | Hooks available |
|
||||
| Aider · Amp · Auggie · Hermes · Vibe · Antigravity | Detected and labelled, but no status channel yet |
|
||||
| Claude Code · Codex · TraeCode · Copilot CLI · OpenCode · Pi · Grok Build · Oh My Pi · Gemini · Droid · Qwen Code · Goose · Kimi Code · Qoder CLI · Crush · CodeBuddy · Cursor CLI · Prime Agent · Antigravity | Hooks available |
|
||||
| Aider · Amp · Auggie · Hermes · Vibe · Empryo | Detected and labelled, but no status channel yet |
|
||||
|
||||
Installing writes into that agent's own configuration directory. Once installed
|
||||
the row grows a second **Uninstall** button beside the first, which itself
|
||||
@@ -28,6 +28,13 @@ ships a newer hook.
|
||||
carries no status dot and `tty7 wait` times out rather than returning.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
Antigravity has no session-start or permission event, so its pane goes
|
||||
straight from no dot to working on the first model call, and a pending
|
||||
permission prompt shows as working rather than amber. Its hooks live in a
|
||||
`tty7` set inside `~/.gemini/config/hooks.json`, beside any sets of your own.
|
||||
</Note>
|
||||
|
||||
<Note>
|
||||
`cursor-agent -p` (print mode) sends neither of Cursor's turn hooks, so a
|
||||
headless run records its session id — Copy Session ID and resume still
|
||||
|
||||
@@ -54,7 +54,7 @@ leave it off if you type accented characters.
|
||||
|
||||
## 4. If you use coding agents, install the hooks
|
||||
|
||||
**Settings → Integrations.** tty7 detects 23 coding CLIs by process name on its own —
|
||||
**Settings → Integrations.** tty7 detects 25 coding CLIs by process name on its own —
|
||||
you get brand avatars and tab labels for free. The *status dots*, the "needs
|
||||
your permission" notifications, and `tty7 wait` all need one more thing: a small
|
||||
hook the agent calls to report what it is doing.
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ something floods the screen.
|
||||
syntax highlighting, click-to-place-caret, real multi-line editing.
|
||||
</Card>
|
||||
<Card title="Agent-aware" icon="robot" href="/agents/overview">
|
||||
23 coding CLIs are recognised on sight. Per-pane status dots, notifications
|
||||
25 coding CLIs are recognised on sight. Per-pane status dots, notifications
|
||||
when one needs you, git context, and session resume after a reboot.
|
||||
</Card>
|
||||
<Card title="Remote work that feels local" icon="server" href="/remote/workspaces">
|
||||
|
||||
@@ -805,6 +805,8 @@ pub fn translate_en(key: L10nKey) -> &'static str {
|
||||
L10nKey::SettingsAgentCrush => "Crush",
|
||||
L10nKey::SettingsAgentCodeBuddy => "CodeBuddy",
|
||||
L10nKey::SettingsAgentCursorCli => "Cursor CLI",
|
||||
L10nKey::SettingsAgentPrimeAgent => "Prime Agent",
|
||||
L10nKey::SettingsAgentAntigravity => "Antigravity",
|
||||
L10nKey::SettingsSearchAboutKeywords => "version license credits build update check github",
|
||||
L10nKey::SettingsSearchAppHttpProxyKeywords => {
|
||||
"proxy http https socks socks5 clash v2ray network download update"
|
||||
@@ -911,6 +913,12 @@ pub fn translate_en(key: L10nKey) -> &'static str {
|
||||
L10nKey::SettingsSearchCursorCliKeywords => {
|
||||
"agent integration hooks install cursor cursor-agent"
|
||||
}
|
||||
L10nKey::SettingsSearchPrimeAgentKeywords => {
|
||||
"agent integration extension install prime prime-agent primeintellect"
|
||||
}
|
||||
L10nKey::SettingsSearchAntigravityKeywords => {
|
||||
"agent integration hooks install antigravity agy google"
|
||||
}
|
||||
L10nKey::SettingsSearchPiKeywords => "agent integration extension install pi",
|
||||
L10nKey::SettingsSearchPortForwardingKeywords => {
|
||||
"ssh tunnel local remote dynamic socks forward rule"
|
||||
|
||||
@@ -815,6 +815,8 @@ pub fn translate_ja(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::SettingsAgentCrush => "Crush",
|
||||
L10nKey::SettingsAgentCodeBuddy => "CodeBuddy",
|
||||
L10nKey::SettingsAgentCursorCli => "Cursor CLI",
|
||||
L10nKey::SettingsAgentPrimeAgent => "Prime Agent",
|
||||
L10nKey::SettingsAgentAntigravity => "Antigravity",
|
||||
L10nKey::SettingsSearchAboutKeywords => {
|
||||
"バージョン ライセンス クレジット ビルド 更新 確認 github about version license credits update check"
|
||||
}
|
||||
@@ -967,6 +969,12 @@ pub fn translate_ja(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::SettingsSearchCursorCliKeywords => {
|
||||
"エージェント 統合 フック インストール cursor cursor-agent agent integration hooks install"
|
||||
}
|
||||
L10nKey::SettingsSearchPrimeAgentKeywords => {
|
||||
"エージェント 統合 拡張 インストール prime prime-agent primeintellect agent integration extension install"
|
||||
}
|
||||
L10nKey::SettingsSearchAntigravityKeywords => {
|
||||
"エージェント 統合 フック インストール antigravity agy google agent integration hooks install"
|
||||
}
|
||||
L10nKey::SettingsSearchPiKeywords => {
|
||||
"エージェント 統合 拡張 インストール pi agent integration extension install"
|
||||
}
|
||||
|
||||
@@ -620,6 +620,8 @@ l10n_keys! {
|
||||
SettingsAgentCrush,
|
||||
SettingsAgentCodeBuddy,
|
||||
SettingsAgentCursorCli,
|
||||
SettingsAgentPrimeAgent,
|
||||
SettingsAgentAntigravity,
|
||||
SettingsSearchAppHttpProxyKeywords,
|
||||
SettingsSearchAboutKeywords,
|
||||
SettingsSearchAutoDownloadKeywords,
|
||||
@@ -679,6 +681,8 @@ l10n_keys! {
|
||||
SettingsSearchCrushKeywords,
|
||||
SettingsSearchCodeBuddyKeywords,
|
||||
SettingsSearchCursorCliKeywords,
|
||||
SettingsSearchPrimeAgentKeywords,
|
||||
SettingsSearchAntigravityKeywords,
|
||||
SettingsSearchRememberWindowSizeKeywords,
|
||||
SettingsSearchReportMouseToAppsKeywords,
|
||||
SettingsSearchRestoreLastLayoutKeywords,
|
||||
@@ -1666,6 +1670,8 @@ mod tests {
|
||||
L10nKey::SettingsAgentCrush,
|
||||
L10nKey::SettingsAgentCodeBuddy,
|
||||
L10nKey::SettingsAgentCursorCli,
|
||||
L10nKey::SettingsAgentPrimeAgent,
|
||||
L10nKey::SettingsAgentAntigravity,
|
||||
// Windows names its backdrop materials, and Japanese Windows keeps
|
||||
// those names in Latin script — so does this list. Chinese does
|
||||
// translate them (云母 / 亚克力), which is what Microsoft's own
|
||||
|
||||
@@ -712,6 +712,8 @@ pub fn translate_zh(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::SettingsAgentCrush => "Crush",
|
||||
L10nKey::SettingsAgentCodeBuddy => "CodeBuddy",
|
||||
L10nKey::SettingsAgentCursorCli => "Cursor CLI",
|
||||
L10nKey::SettingsAgentPrimeAgent => "Prime Agent",
|
||||
L10nKey::SettingsAgentAntigravity => "Antigravity",
|
||||
L10nKey::SettingsSearchAboutKeywords => {
|
||||
"关于 版本 许可证 致谢 构建 更新 检查 github about version license credits update"
|
||||
}
|
||||
@@ -858,6 +860,12 @@ pub fn translate_zh(key: L10nKey) -> Option<&'static str> {
|
||||
L10nKey::SettingsSearchCursorCliKeywords => {
|
||||
"Cursor CLI agent 集成 钩子 安装 cursor cursor-agent"
|
||||
}
|
||||
L10nKey::SettingsSearchPrimeAgentKeywords => {
|
||||
"Prime Agent agent 集成 扩展 安装 prime prime-agent primeintellect"
|
||||
}
|
||||
L10nKey::SettingsSearchAntigravityKeywords => {
|
||||
"Antigravity agent 集成 钩子 安装 antigravity agy google"
|
||||
}
|
||||
L10nKey::SettingsSearchPiKeywords => {
|
||||
"Pi agent 集成 扩展 安装 pi agent integration extension install"
|
||||
}
|
||||
|
||||
@@ -799,6 +799,16 @@ fn settings_search_entries() -> &'static [SearchEntry] {
|
||||
title: SettingsAgentCursorCli,
|
||||
keywords: SettingsSearchCursorCliKeywords,
|
||||
},
|
||||
SearchEntry {
|
||||
section: Agents,
|
||||
title: SettingsAgentPrimeAgent,
|
||||
keywords: SettingsSearchPrimeAgentKeywords,
|
||||
},
|
||||
SearchEntry {
|
||||
section: Agents,
|
||||
title: SettingsAgentAntigravity,
|
||||
keywords: SettingsSearchAntigravityKeywords,
|
||||
},
|
||||
SearchEntry {
|
||||
section: General,
|
||||
title: SettingsStartupWindow,
|
||||
|
||||
Reference in New Issue
Block a user