mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-14 13:22:37 +00:00
Remove the unreleased Antigravity provider plugin (#5781)
* refactor(providers): remove the unreleased Antigravity plugin Remove the provider at the owner’s request because headless command approvals are unsupported. Preserve Muse, shared plugin infrastructure, and earlier editor and terminal integrations. * test(plugins): keep builtin startup coverage generic
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
Paseo is a mobile app for monitoring and controlling your local AI coding agents from anywhere. Your dev environment, in your pocket. Connects directly to your actual development environment — your code stays on your machine.
|
||||
|
||||
**Supported agents:** Claude Code, Codex, GitHub Copilot, OpenCode, Pi, Antigravity, and Muse Code.
|
||||
**Supported agents:** Claude Code, Codex, GitHub Copilot, OpenCode, Pi, and Muse Code.
|
||||
|
||||
## Repository map
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">One interface for Claude Code, Codex, Copilot, OpenCode, Pi, Antigravity, and Muse Code agents.</p>
|
||||
<p align="center">One interface for Claude Code, Codex, Copilot, OpenCode, Pi, and Muse Code agents.</p>
|
||||
|
||||
<p align="center">
|
||||
<img src="https://paseo.sh/hero-mockup.png" alt="Paseo app screenshot" width="100%">
|
||||
@@ -42,7 +42,7 @@
|
||||
Run agents in parallel on your own machines. Ship from your phone or your desk.
|
||||
|
||||
- **Self-hosted:** Agents run on your machine with your full dev environment. Use your tools, your configs, and your skills.
|
||||
- **Multi-provider:** Claude Code, Codex, Copilot, OpenCode, Pi, Antigravity, and Muse Code through the same interface. Pick the right model for each job.
|
||||
- **Multi-provider:** Claude Code, Codex, Copilot, OpenCode, Pi, and Muse Code through the same interface. Pick the right model for each job.
|
||||
- **Voice control:** Dictate tasks or talk through problems in voice mode. Hands-free when you need it.
|
||||
- **Cross-device:** iOS, Android, desktop, web, and CLI. Start work at your desk, check in from your phone, script it from the terminal.
|
||||
- **Privacy-first:** Paseo doesn't have any telemetry, tracking, or forced log-ins.
|
||||
@@ -68,7 +68,6 @@ You need at least one agent CLI installed and configured with your credentials:
|
||||
- [GitHub Copilot](https://github.com/features/copilot/cli/)
|
||||
- [OpenCode](https://github.com/anomalyco/opencode)
|
||||
- [Pi](https://pi.dev)
|
||||
- [Antigravity](https://paseo.sh/docs/supported-providers#antigravity)
|
||||
- [Muse Code](https://paseo.sh/docs/muse-code)
|
||||
|
||||
### Desktop app (recommended)
|
||||
|
||||
+4
-5
@@ -6,7 +6,7 @@ server integrations.
|
||||
## Plugin providers
|
||||
|
||||
Keep a bundled provider in `plugins/<id>/` and register it through
|
||||
`@getpaseo/plugin/server/provider`. Antigravity and Muse Code follow this pattern. Built-in loading and SDK
|
||||
`@getpaseo/plugin/server/provider`. Muse Code follows this pattern. Built-in loading and SDK
|
||||
import rules belong to [plugins.md](plugins.md#built-in-plugins); the
|
||||
[public provider guide](../public-docs/plugins/providers.md) covers the provider contract.
|
||||
|
||||
@@ -15,10 +15,9 @@ executable resolution and applies `agents.providers.<provider-id>.command` and `
|
||||
connecting. Register the provider's icon with the plugin rather than adding it to the app's
|
||||
provider icon map. You do not need a core manifest entry or provider factory.
|
||||
|
||||
| Provider | Transport | Setup and limitations |
|
||||
| ----------- | ------------------------------------------ | ---------------------------------------------------------------- |
|
||||
| Antigravity | Installed `agy` CLI | [Antigravity](../public-docs/supported-providers.md#antigravity) |
|
||||
| Muse Code | MSP over one `muse serve` host per session | [Muse Code](../public-docs/muse-code.md) |
|
||||
| Provider | Transport | Setup and limitations |
|
||||
| --------- | ------------------------------------------ | ---------------------------------------- |
|
||||
| Muse Code | MSP over one `muse serve` host per session | [Muse Code](../public-docs/muse-code.md) |
|
||||
|
||||
## Provider-native session options
|
||||
|
||||
|
||||
@@ -1,11 +1,4 @@
|
||||
import { describe, test, expect } from "vitest";
|
||||
import { mkdtemp, rm } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { findExecutable } from "../../executable-resolution/executable-resolution.js";
|
||||
import { BuiltinPluginLoader } from "../plugins/builtin/index.js";
|
||||
import { createTestPaseoDaemon } from "../test-utils/paseo-daemon.js";
|
||||
import { DaemonClient } from "../test-utils/daemon-client.js";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { createDaemonTestContext } from "../test-utils/index.js";
|
||||
|
||||
@@ -103,54 +96,3 @@ describe("daemon E2E", () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("Antigravity creates an agent and answers a prompt with real agy", async ({ skip }) => {
|
||||
const command = await findExecutable(process.env.AGY_COMMAND ?? "agy");
|
||||
if (command === null) {
|
||||
skip("agy is not resolvable");
|
||||
return;
|
||||
}
|
||||
const cwd = await mkdtemp(path.join(os.tmpdir(), "paseo-antigravity-e2e-"));
|
||||
const daemon = await createTestPaseoDaemon({
|
||||
agentClients: {},
|
||||
mcpEnabled: false,
|
||||
builtinPlugins: new BuiltinPluginLoader(undefined, ["antigravity-provider"]),
|
||||
providerOverrides: { antigravity: { command: [command], paseoTools: { enabled: false } } },
|
||||
});
|
||||
const client = new DaemonClient({
|
||||
url: `ws://127.0.0.1:${daemon.port}/ws`,
|
||||
appVersion: "0.10.0",
|
||||
});
|
||||
try {
|
||||
await client.connect();
|
||||
await client.fetchAgents({ subscribe: {} });
|
||||
await expect
|
||||
.poll(
|
||||
async () => {
|
||||
const snapshot = await client.getProvidersSnapshot({ cwd });
|
||||
return snapshot.entries.find((entry) => entry.provider === "antigravity")?.status;
|
||||
},
|
||||
{ timeout: 30000 },
|
||||
)
|
||||
.toBe("ready");
|
||||
const agent = await client.createAgent({
|
||||
provider: "antigravity",
|
||||
cwd,
|
||||
title: "Antigravity E2E",
|
||||
});
|
||||
await client.sendMessage(agent.id, "Reply with exactly ANTIGRAVITY_E2E_OK. No tools.");
|
||||
const result = await client.waitForFinish(agent.id, 90000);
|
||||
expect(result.status).toBe("idle");
|
||||
const timeline = await client.fetchAgentTimeline(agent.id, { limit: 100 });
|
||||
expect(
|
||||
timeline.entries.some(
|
||||
(entry) =>
|
||||
entry.item.type === "assistant_message" && entry.item.text.includes("ANTIGRAVITY_E2E_OK"),
|
||||
),
|
||||
).toBe(true);
|
||||
} finally {
|
||||
await client.close();
|
||||
await daemon.close();
|
||||
await rm(cwd, { recursive: true, force: true });
|
||||
}
|
||||
}, 120000);
|
||||
|
||||
@@ -3,7 +3,6 @@ import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export const builtinPlugins = [
|
||||
"antigravity-provider",
|
||||
"claude-usage-source",
|
||||
"codex-usage-source",
|
||||
"copilot-usage-source",
|
||||
|
||||
@@ -99,20 +99,3 @@ export function MuseCodeIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
export function AntigravityIcon(props: React.SVGProps<SVGSVGElement>) {
|
||||
return (
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="currentColor"
|
||||
aria-hidden="true"
|
||||
{...props}
|
||||
>
|
||||
<path
|
||||
transform="translate(12 12) scale(0.24) translate(-55.738 -56.945)"
|
||||
d="M89.6992 93.695C94.3659 97.195 101.366 94.8617 94.9492 88.445C75.6992 69.7783 79.7825 18.445 55.8659 18.445C31.9492 18.445 36.0325 69.7783 16.7825 88.445C9.78251 95.445 17.3658 97.195 22.0325 93.695C40.1159 81.445 38.9492 59.8617 55.8659 59.8617C72.7825 59.8617 71.6159 81.445 89.6992 93.695Z"
|
||||
/>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -79,7 +79,6 @@ import {
|
||||
OpenCodeIcon,
|
||||
PiIcon,
|
||||
MuseCodeIcon,
|
||||
AntigravityIcon,
|
||||
} from "~/components/agent-icons";
|
||||
import { DiscordIcon, GitHubIcon, SlackIcon } from "~/components/brand-icons";
|
||||
import { ClaudeIcon, MobileChat, MobileDiff, MobileSidebar, PhoneFrame } from "~/components/mockup";
|
||||
@@ -163,7 +162,6 @@ function Hero({ title, subtitle }: { title: React.ReactNode; subtitle: React.Rea
|
||||
|
||||
const CLAUDE_CODE_BADGE_ICON = <ClaudeCodeIcon className="h-6 w-6" />;
|
||||
const CODEX_BADGE_ICON = <CodexIcon className="h-6 w-6" />;
|
||||
const ANTIGRAVITY_BADGE_ICON = <AntigravityIcon className="h-6 w-6" />;
|
||||
const MUSE_CODE_BADGE_ICON = <MuseCodeIcon className="h-6 w-6" />;
|
||||
const OPENCODE_BADGE_ICON = <OpenCodeIcon className="h-6 w-6" />;
|
||||
const PI_BADGE_ICON = <PiIcon className="h-6 w-6" />;
|
||||
@@ -436,7 +434,6 @@ function MultiProviderSection() {
|
||||
{ name: "Claude Code", icon: <ClaudeIcon className={PROVIDER_ICON_CLASS} /> },
|
||||
{ name: "Codex", icon: <CodexIcon className={PROVIDER_ICON_CLASS} /> },
|
||||
{ name: "OpenCode", icon: <OpenCodeIcon className={PROVIDER_ICON_CLASS} /> },
|
||||
{ name: "Antigravity", icon: <AntigravityIcon className={PROVIDER_ICON_CLASS} /> },
|
||||
{ name: "Muse Code", icon: <MuseCodeIcon className={PROVIDER_ICON_CLASS} /> },
|
||||
{ name: "Pi", icon: <PiIcon className={PROVIDER_ICON_CLASS} /> },
|
||||
{ name: "Cursor", icon: <CursorIcon className={PROVIDER_ICON_CLASS} /> },
|
||||
@@ -906,8 +903,7 @@ const PRINCIPLES: ReadonlyArray<{ icon: LucideIcon; title: string; description:
|
||||
{
|
||||
icon: Merge,
|
||||
title: "Unified",
|
||||
description:
|
||||
"Claude Code, Codex, OpenCode, Pi, Antigravity, Muse Code, and more, all used the same way.",
|
||||
description: "Claude Code, Codex, OpenCode, Pi, Muse Code, and more, all used the same way.",
|
||||
},
|
||||
{
|
||||
icon: Blocks,
|
||||
@@ -969,7 +965,6 @@ function GetStarted() {
|
||||
<AgentBadge name="Claude Code" icon={CLAUDE_CODE_BADGE_ICON} />
|
||||
<AgentBadge name="Codex" icon={CODEX_BADGE_ICON} />
|
||||
<AgentBadge name="OpenCode" icon={OPENCODE_BADGE_ICON} />
|
||||
<AgentBadge name="Antigravity" icon={ANTIGRAVITY_BADGE_ICON} />
|
||||
<AgentBadge name="Muse Code" icon={MUSE_CODE_BADGE_ICON} />
|
||||
<AgentBadge name="Pi" icon={PI_BADGE_ICON} />
|
||||
<AgentBadge name="Cursor" icon={CURSOR_BADGE_ICON} />
|
||||
@@ -1216,8 +1211,7 @@ function FAQ() {
|
||||
</FAQItem>
|
||||
<FAQItem question="What agents does it support?">
|
||||
Paseo supports many providers. It has custom implementations for Claude, Codex, OpenCode,
|
||||
Pi, OMP, Antigravity, and Muse Code, and supports many more via ACP. See the full list
|
||||
here:{" "}
|
||||
Pi, OMP, and Muse Code, and supports many more via ACP. See the full list here:{" "}
|
||||
<a href="/agents" className="underline hover:text-white/80">
|
||||
all supported providers
|
||||
</a>
|
||||
|
||||
@@ -86,12 +86,6 @@ export function SiteFooter({ width = "default" }: SiteFooterProps) {
|
||||
>
|
||||
OpenCode
|
||||
</a>
|
||||
<a
|
||||
href="/antigravity"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
>
|
||||
Antigravity
|
||||
</a>
|
||||
<a
|
||||
href="/muse-code"
|
||||
className="block text-muted-foreground hover:text-foreground transition-colors"
|
||||
|
||||
@@ -31,7 +31,7 @@ Claude Desktop is the Anthropic-controlled host application. Claude Code can run
|
||||
|
||||
Claude Desktop runs Claude Code.
|
||||
|
||||
Paseo runs Claude Code too, plus Codex, OpenCode, Pi, Antigravity, and Muse Code natively, plus 30+ more agents through the in-app catalog including GitHub Copilot, Cursor, Gemini CLI, and Amp. Paseo speaks the [Agent Client Protocol](https://agentclientprotocol.com), so any ACP agent works. Custom providers run any CLI agent. See [all supported providers](/agents).
|
||||
Paseo runs Claude Code too, plus Codex, OpenCode, Pi, and Muse Code natively, plus 30+ more agents through the in-app catalog including GitHub Copilot, Cursor, Gemini CLI, and Amp. Paseo speaks the [Agent Client Protocol](https://agentclientprotocol.com), so any ACP agent works. Custom providers run any CLI agent. See [all supported providers](/agents).
|
||||
|
||||
## Application plugins
|
||||
|
||||
@@ -94,25 +94,25 @@ Claude supports voice in Claude's own mobile and app surfaces. Claude Code itsel
|
||||
|
||||
## Comparison
|
||||
|
||||
| | Paseo | Claude Desktop |
|
||||
| ---------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------ |
|
||||
| License | Open source (Apache-2.0) | Not published as open source |
|
||||
| Desktop platforms | macOS, Linux, Windows | macOS, Linux, Windows |
|
||||
| Mobile coding workflow | Native Paseo workspace on iOS and Android | Dispatch and Cowork in Claude mobile |
|
||||
| Coding agents | Claude Code, Codex, OpenCode, Pi, Antigravity, Muse Code + 30+ via ACP catalog + custom | Claude Code |
|
||||
| Product account required | No | Claude account |
|
||||
| Cloud agent | Cloud waitlist | Claude Cowork and remote sessions |
|
||||
| Required cloud connection | No | Claude account and services |
|
||||
| Machines you control | Laptop, workstation, VM, server, or home lab | Local machine or SSH host |
|
||||
| Git worktrees | Yes | Yes |
|
||||
| Per-worktree dev server URLs | Yes | No |
|
||||
| Split panes and tabs | Yes | Yes |
|
||||
| In-app terminal | Yes | Yes |
|
||||
| In-app browser / preview | Yes | Yes |
|
||||
| GitHub workflow in app | Commit, push, PR, checks, reviews, merge | PR monitoring and merge workflows |
|
||||
| CLI | Run, `--host`, ls, send, schedule, loop | Claude Code CLI |
|
||||
| Remote transport | Direct connection or end-to-end encrypted relay | Anthropic Remote and cloud services |
|
||||
| Application plugins | Server code and native client components | No |
|
||||
| Self-hosted control plane | Daemon, web client, and relay | No |
|
||||
| | Paseo | Claude Desktop |
|
||||
| ---------------------------- | -------------------------------------------------------------------------- | ------------------------------------ |
|
||||
| License | Open source (Apache-2.0) | Not published as open source |
|
||||
| Desktop platforms | macOS, Linux, Windows | macOS, Linux, Windows |
|
||||
| Mobile coding workflow | Native Paseo workspace on iOS and Android | Dispatch and Cowork in Claude mobile |
|
||||
| Coding agents | Claude Code, Codex, OpenCode, Pi, Muse Code + 30+ via ACP catalog + custom | Claude Code |
|
||||
| Product account required | No | Claude account |
|
||||
| Cloud agent | Cloud waitlist | Claude Cowork and remote sessions |
|
||||
| Required cloud connection | No | Claude account and services |
|
||||
| Machines you control | Laptop, workstation, VM, server, or home lab | Local machine or SSH host |
|
||||
| Git worktrees | Yes | Yes |
|
||||
| Per-worktree dev server URLs | Yes | No |
|
||||
| Split panes and tabs | Yes | Yes |
|
||||
| In-app terminal | Yes | Yes |
|
||||
| In-app browser / preview | Yes | Yes |
|
||||
| GitHub workflow in app | Commit, push, PR, checks, reviews, merge | PR monitoring and merge workflows |
|
||||
| CLI | Run, `--host`, ls, send, schedule, loop | Claude Code CLI |
|
||||
| Remote transport | Direct connection or end-to-end encrypted relay | Anthropic Remote and cloud services |
|
||||
| Application plugins | Server code and native client components | No |
|
||||
| Self-hosted control plane | Daemon, web client, and relay | No |
|
||||
|
||||
See also: [Paseo vs Codex App](/alternatives/codex-app), [Paseo vs OpenCode Desktop](/alternatives/opencode-desktop), [Paseo vs Conductor](/alternatives/conductor).
|
||||
|
||||
@@ -31,7 +31,7 @@ OpenAI provides local, worktree, remote, and cloud Codex workflows through its C
|
||||
|
||||
Codex App runs Codex.
|
||||
|
||||
Paseo runs Codex too, plus Claude Code, OpenCode, Pi, Antigravity, and Muse Code natively, plus 30+ more agents through the in-app catalog including GitHub Copilot, Cursor, Gemini CLI, and Amp. Paseo speaks the [Agent Client Protocol](https://agentclientprotocol.com), so any ACP agent works. Custom providers run any CLI agent. See [all supported providers](/agents).
|
||||
Paseo runs Codex too, plus Claude Code, OpenCode, Pi, and Muse Code natively, plus 30+ more agents through the in-app catalog including GitHub Copilot, Cursor, Gemini CLI, and Amp. Paseo speaks the [Agent Client Protocol](https://agentclientprotocol.com), so any ACP agent works. Custom providers run any CLI agent. See [all supported providers](/agents).
|
||||
|
||||
## Application plugins
|
||||
|
||||
|
||||
@@ -27,7 +27,7 @@ Conductor runs local workspaces through its macOS app and cloud workspaces in ma
|
||||
|
||||
## Providers
|
||||
|
||||
Paseo runs Claude Code, Codex, OpenCode, Pi, Antigravity, and Muse Code natively, plus 30+ more agents through the in-app catalog including GitHub Copilot, Cursor, Gemini CLI, and Amp. Paseo speaks the [Agent Client Protocol](https://agentclientprotocol.com), so any ACP agent works. Custom providers run any CLI agent. See [all supported providers](/agents).
|
||||
Paseo runs Claude Code, Codex, OpenCode, Pi, and Muse Code natively, plus 30+ more agents through the in-app catalog including GitHub Copilot, Cursor, Gemini CLI, and Amp. Paseo speaks the [Agent Client Protocol](https://agentclientprotocol.com), so any ACP agent works. Custom providers run any CLI agent. See [all supported providers](/agents).
|
||||
|
||||
Conductor supports Claude Code, Codex, Cursor, and OpenCode.
|
||||
|
||||
@@ -79,21 +79,21 @@ Paseo supports local speech-to-text and text-to-speech. Conductor does not curre
|
||||
|
||||
## Comparison
|
||||
|
||||
| | Paseo | Conductor |
|
||||
| ---------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------ |
|
||||
| License | Open source (Apache-2.0) | Closed source |
|
||||
| Platforms | macOS, Linux, Windows | macOS only |
|
||||
| Native mobile | iOS, Android | Coming soon under Pro |
|
||||
| Providers | Claude Code, Codex, OpenCode, Pi, Antigravity, Muse Code + 30+ via ACP catalog + custom | Claude Code, Codex, Cursor, OpenCode |
|
||||
| Git worktrees | Yes | Yes |
|
||||
| Per-worktree dev server URLs | Yes | — |
|
||||
| Split panes and tabs | Yes | — |
|
||||
| In-app terminal | Yes | Yes |
|
||||
| In-app browser | Yes | — |
|
||||
| GitHub workflow in app | Commit, push, PR, checks, reviews, merge | Yes |
|
||||
| CLI | Run, `--host`, ls, send, schedule, loop | — |
|
||||
| Application plugins | Server code and native client components | No |
|
||||
| Local voice | Yes | Not documented |
|
||||
| Self-hosted daemon | Yes | — |
|
||||
| | Paseo | Conductor |
|
||||
| ---------------------------- | -------------------------------------------------------------------------- | ------------------------------------ |
|
||||
| License | Open source (Apache-2.0) | Closed source |
|
||||
| Platforms | macOS, Linux, Windows | macOS only |
|
||||
| Native mobile | iOS, Android | Coming soon under Pro |
|
||||
| Providers | Claude Code, Codex, OpenCode, Pi, Muse Code + 30+ via ACP catalog + custom | Claude Code, Codex, Cursor, OpenCode |
|
||||
| Git worktrees | Yes | Yes |
|
||||
| Per-worktree dev server URLs | Yes | — |
|
||||
| Split panes and tabs | Yes | — |
|
||||
| In-app terminal | Yes | Yes |
|
||||
| In-app browser | Yes | — |
|
||||
| GitHub workflow in app | Commit, push, PR, checks, reviews, merge | Yes |
|
||||
| CLI | Run, `--host`, ls, send, schedule, loop | — |
|
||||
| Application plugins | Server code and native client components | No |
|
||||
| Local voice | Yes | Not documented |
|
||||
| Self-hosted daemon | Yes | — |
|
||||
|
||||
See also: [Paseo vs Superset](/alternatives/superset), [Paseo vs OpenChamber](/alternatives/openchamber), [Paseo vs Happy Coder](/alternatives/happy-coder).
|
||||
|
||||
@@ -41,7 +41,7 @@ Both tools ship native iOS and Android apps.
|
||||
|
||||
## Providers
|
||||
|
||||
Paseo runs Claude Code, Codex, OpenCode, Pi, Antigravity, and Muse Code natively, plus 30+ more agents through the in-app catalog including GitHub Copilot, Cursor, Gemini CLI, and Amp. Paseo speaks the [Agent Client Protocol](https://agentclientprotocol.com), so any ACP agent works. Custom providers run any CLI agent. See [all supported providers](/agents).
|
||||
Paseo runs Claude Code, Codex, OpenCode, Pi, and Muse Code natively, plus 30+ more agents through the in-app catalog including GitHub Copilot, Cursor, Gemini CLI, and Amp. Paseo speaks the [Agent Client Protocol](https://agentclientprotocol.com), so any ACP agent works. Custom providers run any CLI agent. See [all supported providers](/agents).
|
||||
|
||||
Happy Coder runs Claude Code and Codex.
|
||||
|
||||
@@ -79,21 +79,21 @@ Both products support voice interaction. Paseo can run speech-to-text and text-t
|
||||
|
||||
## Comparison
|
||||
|
||||
| | Paseo | Happy Coder |
|
||||
| ---------------------------- | --------------------------------------------------------------------------------------- | --------------------------- |
|
||||
| License | Open source (Apache-2.0) | Open source (MIT) |
|
||||
| Desktop app | macOS, Linux, Windows | macOS |
|
||||
| Native mobile | iOS, Android | iOS, Android |
|
||||
| Architecture | Daemon owns agent lifecycle | Wraps the agent CLI |
|
||||
| Providers | Claude Code, Codex, OpenCode, Pi, Antigravity, Muse Code + 30+ via ACP catalog + custom | Claude Code, Codex |
|
||||
| Split workspace | Yes | Yes |
|
||||
| In-app terminal | Yes | Yes |
|
||||
| In-app browser / preview | Yes | Yes |
|
||||
| GitHub workflow in app | Commit, push, PR, checks, reviews, merge | — |
|
||||
| Managed Git worktrees | Yes | Existing worktree paths |
|
||||
| Per-worktree dev server URLs | Yes | — |
|
||||
| CLI | Run, `--host`, ls, send, schedule, loop | Launch and control sessions |
|
||||
| Application plugins | Server code and native client components | No |
|
||||
| Voice | Local or configured cloud speech | Yes |
|
||||
| | Paseo | Happy Coder |
|
||||
| ---------------------------- | -------------------------------------------------------------------------- | --------------------------- |
|
||||
| License | Open source (Apache-2.0) | Open source (MIT) |
|
||||
| Desktop app | macOS, Linux, Windows | macOS |
|
||||
| Native mobile | iOS, Android | iOS, Android |
|
||||
| Architecture | Daemon owns agent lifecycle | Wraps the agent CLI |
|
||||
| Providers | Claude Code, Codex, OpenCode, Pi, Muse Code + 30+ via ACP catalog + custom | Claude Code, Codex |
|
||||
| Split workspace | Yes | Yes |
|
||||
| In-app terminal | Yes | Yes |
|
||||
| In-app browser / preview | Yes | Yes |
|
||||
| GitHub workflow in app | Commit, push, PR, checks, reviews, merge | — |
|
||||
| Managed Git worktrees | Yes | Existing worktree paths |
|
||||
| Per-worktree dev server URLs | Yes | — |
|
||||
| CLI | Run, `--host`, ls, send, schedule, loop | Launch and control sessions |
|
||||
| Application plugins | Server code and native client components | No |
|
||||
| Voice | Local or configured cloud speech | Yes |
|
||||
|
||||
See also: [Paseo vs Conductor](/alternatives/conductor), [Paseo vs Superset](/alternatives/superset), [Paseo vs OpenChamber](/alternatives/openchamber).
|
||||
|
||||
@@ -29,7 +29,7 @@ Paseo's daemon launches each provider through its own native harness or through
|
||||
|
||||
OpenChamber uses OpenCode as its agent runtime. OpenCode can connect to many model providers, and OpenChamber also offers integrations for subscriptions such as Claude.
|
||||
|
||||
Paseo is multi-provider at the agent-harness layer. It runs Claude Code, Codex, OpenCode, Pi, Antigravity, and Muse Code natively, plus 30+ agents through its ACP catalog and any custom CLI agent. See [all supported providers](/agents).
|
||||
Paseo is multi-provider at the agent-harness layer. It runs Claude Code, Codex, OpenCode, Pi, and Muse Code natively, plus 30+ agents through its ACP catalog and any custom CLI agent. See [all supported providers](/agents).
|
||||
|
||||
## Application plugins
|
||||
|
||||
@@ -55,22 +55,22 @@ Both tools provide iOS and Android clients and support dictation and spoken repl
|
||||
|
||||
## Comparison
|
||||
|
||||
| | Paseo | OpenChamber |
|
||||
| ---------------------------- | --------------------------------------------------------------------------------------- | -------------------------------- |
|
||||
| License | Open source (Apache-2.0) | Open source (MIT) |
|
||||
| Desktop platforms | macOS, Linux, Windows | macOS, Linux, Windows |
|
||||
| Mobile implementation | React Native | Capacitor WebView |
|
||||
| Agent harnesses | Claude Code, Codex, OpenCode, Pi, Antigravity, Muse Code + 30+ via ACP catalog + custom | OpenCode |
|
||||
| Application plugins | Server code and native client components | No |
|
||||
| Split panes and tabs | Yes | Workspace views |
|
||||
| In-app terminal | Yes | Yes |
|
||||
| In-app browser | Yes | Yes |
|
||||
| GitHub workflow in app | Commit, push, PR, checks, reviews, merge | Issue, PR, checks, review, merge |
|
||||
| Git worktrees | Yes | Yes |
|
||||
| Per-worktree dev server URLs | Yes | Preview and port detection |
|
||||
| Automation | CLI, SDK, MCP | Server CLI, Agent Control Tool |
|
||||
| Schedules | Yes | Yes |
|
||||
| Voice | Local dictation and realtime voice | Dictation and spoken replies |
|
||||
| Self-hosted daemon | Yes | Yes |
|
||||
| | Paseo | OpenChamber |
|
||||
| ---------------------------- | -------------------------------------------------------------------------- | -------------------------------- |
|
||||
| License | Open source (Apache-2.0) | Open source (MIT) |
|
||||
| Desktop platforms | macOS, Linux, Windows | macOS, Linux, Windows |
|
||||
| Mobile implementation | React Native | Capacitor WebView |
|
||||
| Agent harnesses | Claude Code, Codex, OpenCode, Pi, Muse Code + 30+ via ACP catalog + custom | OpenCode |
|
||||
| Application plugins | Server code and native client components | No |
|
||||
| Split panes and tabs | Yes | Workspace views |
|
||||
| In-app terminal | Yes | Yes |
|
||||
| In-app browser | Yes | Yes |
|
||||
| GitHub workflow in app | Commit, push, PR, checks, reviews, merge | Issue, PR, checks, review, merge |
|
||||
| Git worktrees | Yes | Yes |
|
||||
| Per-worktree dev server URLs | Yes | Preview and port detection |
|
||||
| Automation | CLI, SDK, MCP | Server CLI, Agent Control Tool |
|
||||
| Schedules | Yes | Yes |
|
||||
| Voice | Local dictation and realtime voice | Dictation and spoken replies |
|
||||
| Self-hosted daemon | Yes | Yes |
|
||||
|
||||
See also: [Paseo vs Conductor](/alternatives/conductor), [Paseo vs Superset](/alternatives/superset), [Paseo vs Happy Coder](/alternatives/happy-coder).
|
||||
|
||||
@@ -29,7 +29,7 @@ OpenCode Desktop is the desktop app for OpenCode. OpenCode is available as a ter
|
||||
|
||||
OpenCode is a multi-model coding agent. It can connect to many LLM providers through its own provider system, including OpenCode Zen, local models, and API providers.
|
||||
|
||||
Paseo is multi-provider at the agent harness layer. It runs OpenCode, Claude Code, Codex, Pi, Antigravity, and Muse Code natively, plus 30+ more agents through the in-app catalog including GitHub Copilot, Cursor, Gemini CLI, and Amp. Paseo speaks the [Agent Client Protocol](https://agentclientprotocol.com), so any ACP agent works. Custom providers run any CLI agent. See [all supported providers](/agents).
|
||||
Paseo is multi-provider at the agent harness layer. It runs OpenCode, Claude Code, Codex, Pi, and Muse Code natively, plus 30+ more agents through the in-app catalog including GitHub Copilot, Cursor, Gemini CLI, and Amp. Paseo speaks the [Agent Client Protocol](https://agentclientprotocol.com), so any ACP agent works. Custom providers run any CLI agent. See [all supported providers](/agents).
|
||||
|
||||
## Application plugins
|
||||
|
||||
@@ -94,23 +94,23 @@ Paseo supports dictation and realtime voice mode. Speech-to-text and text-to-spe
|
||||
|
||||
## Comparison
|
||||
|
||||
| | Paseo | OpenCode Desktop |
|
||||
| ---------------------------- | --------------------------------------------------------------------------------------- | ------------------------------- |
|
||||
| License | Open source (Apache-2.0) | Open source (MIT) |
|
||||
| Desktop platforms | macOS, Linux, Windows | macOS, Linux, Windows |
|
||||
| Native mobile | iOS, Android | No |
|
||||
| Agent harnesses | Claude Code, Codex, OpenCode, Pi, Antigravity, Muse Code + 30+ via ACP catalog + custom | OpenCode |
|
||||
| Multi-model support | Through supported agent harnesses | Through OpenCode providers |
|
||||
| Git worktrees | Yes | No built-in worktree manager |
|
||||
| Per-worktree dev server URLs | Yes | No |
|
||||
| Split panes and tabs | Yes | Desktop sessions |
|
||||
| In-app terminal | Yes | OpenCode terminal workflow |
|
||||
| In-app browser | Yes | No |
|
||||
| GitHub workflow in app | Commit, push, PR, checks, reviews, merge | GitHub integration |
|
||||
| CLI | Run, `--host`, ls, send, schedule, loop | OpenCode CLI |
|
||||
| MCP server for orchestration | Yes | MCP support inside OpenCode |
|
||||
| Application plugins | Server code and native client components | No |
|
||||
| Local voice | Yes | No |
|
||||
| Self-hosted daemon | Yes | OpenCode server / local runtime |
|
||||
| | Paseo | OpenCode Desktop |
|
||||
| ---------------------------- | -------------------------------------------------------------------------- | ------------------------------- |
|
||||
| License | Open source (Apache-2.0) | Open source (MIT) |
|
||||
| Desktop platforms | macOS, Linux, Windows | macOS, Linux, Windows |
|
||||
| Native mobile | iOS, Android | No |
|
||||
| Agent harnesses | Claude Code, Codex, OpenCode, Pi, Muse Code + 30+ via ACP catalog + custom | OpenCode |
|
||||
| Multi-model support | Through supported agent harnesses | Through OpenCode providers |
|
||||
| Git worktrees | Yes | No built-in worktree manager |
|
||||
| Per-worktree dev server URLs | Yes | No |
|
||||
| Split panes and tabs | Yes | Desktop sessions |
|
||||
| In-app terminal | Yes | OpenCode terminal workflow |
|
||||
| In-app browser | Yes | No |
|
||||
| GitHub workflow in app | Commit, push, PR, checks, reviews, merge | GitHub integration |
|
||||
| CLI | Run, `--host`, ls, send, schedule, loop | OpenCode CLI |
|
||||
| MCP server for orchestration | Yes | MCP support inside OpenCode |
|
||||
| Application plugins | Server code and native client components | No |
|
||||
| Local voice | Yes | No |
|
||||
| Self-hosted daemon | Yes | OpenCode server / local runtime |
|
||||
|
||||
See also: [Paseo vs Codex App](/alternatives/codex-app), [Paseo vs Claude Desktop](/alternatives/claude-desktop), [Paseo vs OpenChamber](/alternatives/openchamber).
|
||||
|
||||
@@ -45,7 +45,7 @@ Orca's main interface is the agent's terminal. Its Chat UI is an experimental vi
|
||||
|
||||
Orca runs any CLI agent in a terminal and lists more than 30 supported agents.
|
||||
|
||||
Paseo runs Claude Code, Codex, OpenCode, Pi, Antigravity, and Muse Code through native structured harnesses, plus 30+ agents through its ACP catalog and any custom CLI agent. See [all supported providers](/agents).
|
||||
Paseo runs Claude Code, Codex, OpenCode, Pi, and Muse Code through native structured harnesses, plus 30+ agents through its ACP catalog and any custom CLI agent. See [all supported providers](/agents).
|
||||
|
||||
## Application plugins
|
||||
|
||||
@@ -89,24 +89,24 @@ Orca does not require an account for local use. Orca Relay and shared artifacts
|
||||
|
||||
## Comparison
|
||||
|
||||
| | Paseo | Orca |
|
||||
| ---------------------------- | --------------------------------------------------------------------------------------- | ------------------------------------- |
|
||||
| License | Open source (Apache-2.0) | Open source (MIT) |
|
||||
| Funding | Independent | Venture-funded (Y Combinator) |
|
||||
| Desktop platforms | macOS, Linux, Windows | macOS, Linux, Windows |
|
||||
| Native mobile | iOS, Android | iOS, Android APK (beta) |
|
||||
| Main interface | Agent chat | Terminal, with experimental Chat UI |
|
||||
| Remote machines | Daemon on any machine, direct, SSH, or relay | SSH worktrees, Remote Orca Server |
|
||||
| Relay | Optional, end-to-end encrypted, no account | Beta, Orca account required |
|
||||
| Agent harnesses | Claude Code, Codex, OpenCode, Pi, Antigravity, Muse Code + 30+ via ACP catalog + custom | Any CLI agent in a terminal |
|
||||
| Application plugins | Server code and native client components on every client | Experimental, desktop only |
|
||||
| In-app terminal | Yes | Yes |
|
||||
| In-app browser | Yes | Yes, with Design Mode |
|
||||
| GitHub workflow in app | Commit, push, PR, checks, reviews, merge | Yes, plus GitLab, Bitbucket, and more |
|
||||
| Git worktrees | Yes | Yes |
|
||||
| Per-worktree dev server URLs | Yes | Port forwarding for SSH worktrees |
|
||||
| Automation | CLI, SDK, MCP | CLI, automations |
|
||||
| Voice | Local dictation and realtime voice | Dictation |
|
||||
| Telemetry | None | Anonymous usage data, opt-out |
|
||||
| | Paseo | Orca |
|
||||
| ---------------------------- | -------------------------------------------------------------------------- | ------------------------------------- |
|
||||
| License | Open source (Apache-2.0) | Open source (MIT) |
|
||||
| Funding | Independent | Venture-funded (Y Combinator) |
|
||||
| Desktop platforms | macOS, Linux, Windows | macOS, Linux, Windows |
|
||||
| Native mobile | iOS, Android | iOS, Android APK (beta) |
|
||||
| Main interface | Agent chat | Terminal, with experimental Chat UI |
|
||||
| Remote machines | Daemon on any machine, direct, SSH, or relay | SSH worktrees, Remote Orca Server |
|
||||
| Relay | Optional, end-to-end encrypted, no account | Beta, Orca account required |
|
||||
| Agent harnesses | Claude Code, Codex, OpenCode, Pi, Muse Code + 30+ via ACP catalog + custom | Any CLI agent in a terminal |
|
||||
| Application plugins | Server code and native client components on every client | Experimental, desktop only |
|
||||
| In-app terminal | Yes | Yes |
|
||||
| In-app browser | Yes | Yes, with Design Mode |
|
||||
| GitHub workflow in app | Commit, push, PR, checks, reviews, merge | Yes, plus GitLab, Bitbucket, and more |
|
||||
| Git worktrees | Yes | Yes |
|
||||
| Per-worktree dev server URLs | Yes | Port forwarding for SSH worktrees |
|
||||
| Automation | CLI, SDK, MCP | CLI, automations |
|
||||
| Voice | Local dictation and realtime voice | Dictation |
|
||||
| Telemetry | None | Anonymous usage data, opt-out |
|
||||
|
||||
See also: [Paseo vs Conductor](/alternatives/conductor), [Paseo vs Superset](/alternatives/superset), [Paseo vs OpenChamber](/alternatives/openchamber).
|
||||
|
||||
@@ -37,7 +37,7 @@ Superset requires a Superset account and GitHub sign-in when opening the app. It
|
||||
|
||||
Superset supports many CLI-based coding agents and lets you add custom terminal agents.
|
||||
|
||||
Paseo runs Claude Code, Codex, OpenCode, Pi, Antigravity, and Muse Code through native structured harnesses, plus 30+ agents through its ACP catalog and any custom CLI agent. See [all supported providers](/agents).
|
||||
Paseo runs Claude Code, Codex, OpenCode, Pi, and Muse Code through native structured harnesses, plus 30+ agents through its ACP catalog and any custom CLI agent. See [all supported providers](/agents).
|
||||
|
||||
## Application plugins
|
||||
|
||||
|
||||
@@ -72,16 +72,6 @@ export const AGENT_PAGES = [
|
||||
metaDescription:
|
||||
"Open source mobile and desktop app for the Pi coding agent. Launch sessions on your machine, monitor progress, merge from anywhere. Self-hosted.",
|
||||
},
|
||||
{
|
||||
slug: "antigravity",
|
||||
name: "Antigravity",
|
||||
title: "Open source app for Antigravity",
|
||||
subtitle:
|
||||
"Run your installed Antigravity CLI on your machine and drive it from your phone or desktop.",
|
||||
metaTitle: "Antigravity Mobile and Desktop App, Open Source",
|
||||
metaDescription:
|
||||
"Run your installed Antigravity CLI through Paseo. Watch agents work and review changes from your phone or desktop. Self-hosted, your code stays on your machine.",
|
||||
},
|
||||
{
|
||||
slug: "muse-code",
|
||||
name: "Muse Code",
|
||||
|
||||
@@ -12,7 +12,7 @@ Paseo is an open source application that lets you run AI coding agents on your o
|
||||
|
||||
A self-hosted daemon manages agent lifecycle, exposes a WebSocket API, and ships with an MCP server so other agents can talk to it. Native apps for iOS, Android, macOS, Windows, Linux, and the web let you launch sessions, watch them work, review diffs, and ship from anywhere. A Docker-style CLI ("paseo run", "paseo ls", "paseo logs", "paseo wait") gives you scripting access. An end-to-end encrypted relay lets the mobile app reach your daemon over the public internet without exposing it.
|
||||
|
||||
Paseo supports every major coding agent: Claude Code, Codex, GitHub Copilot, OpenCode, Antigravity, Muse Code, Cursor, Gemini, Cline, Goose, Amp, Aider, and 30+ others. Each agent runs as its own process; Paseo handles I/O, persistence, git worktree isolation, schedules, and skills.
|
||||
Paseo supports every major coding agent: Claude Code, Codex, GitHub Copilot, OpenCode, Muse Code, Cursor, Gemini, Cline, Goose, Amp, Aider, and 30+ others. Each agent runs as its own process; Paseo handles I/O, persistence, git worktree isolation, schedules, and skills.
|
||||
|
||||
Distribution: native apps for Mac, Windows, Linux, iOS, and Android; web app; Homebrew; npm. Source: Apache-2.0 at https://github.com/getpaseo/paseo. Marketing site: https://paseo.sh.
|
||||
`;
|
||||
|
||||
@@ -55,7 +55,6 @@ import { Route as ChangelogRouteImport } from "./routes/changelog";
|
||||
import { Route as BlogRouteImport } from "./routes/blog";
|
||||
import { Route as AutohandRouteImport } from "./routes/autohand";
|
||||
import { Route as AuggieRouteImport } from "./routes/auggie";
|
||||
import { Route as AntigravityRouteImport } from "./routes/antigravity";
|
||||
import { Route as AmpRouteImport } from "./routes/amp";
|
||||
import { Route as AgoragenticRouteImport } from "./routes/agoragentic";
|
||||
import { Route as AgentsRouteImport } from "./routes/agents";
|
||||
@@ -303,11 +302,6 @@ const AuggieRoute = AuggieRouteImport.update({
|
||||
path: "/auggie",
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any);
|
||||
const AntigravityRoute = AntigravityRouteImport.update({
|
||||
id: "/antigravity",
|
||||
path: "/antigravity",
|
||||
getParentRoute: () => rootRouteImport,
|
||||
} as any);
|
||||
const AmpRoute = AmpRouteImport.update({
|
||||
id: "/amp",
|
||||
path: "/amp",
|
||||
@@ -396,7 +390,6 @@ export interface FileRoutesByFullPath {
|
||||
"/agents": typeof AgentsRoute;
|
||||
"/agoragentic": typeof AgoragenticRoute;
|
||||
"/amp": typeof AmpRoute;
|
||||
"/antigravity": typeof AntigravityRoute;
|
||||
"/auggie": typeof AuggieRoute;
|
||||
"/autohand": typeof AutohandRoute;
|
||||
"/blog": typeof BlogRouteWithChildren;
|
||||
@@ -461,7 +454,6 @@ export interface FileRoutesByTo {
|
||||
"/agents": typeof AgentsRoute;
|
||||
"/agoragentic": typeof AgoragenticRoute;
|
||||
"/amp": typeof AmpRoute;
|
||||
"/antigravity": typeof AntigravityRoute;
|
||||
"/auggie": typeof AuggieRoute;
|
||||
"/autohand": typeof AutohandRoute;
|
||||
"/changelog": typeof ChangelogRoute;
|
||||
@@ -525,7 +517,6 @@ export interface FileRoutesById {
|
||||
"/agents": typeof AgentsRoute;
|
||||
"/agoragentic": typeof AgoragenticRoute;
|
||||
"/amp": typeof AmpRoute;
|
||||
"/antigravity": typeof AntigravityRoute;
|
||||
"/auggie": typeof AuggieRoute;
|
||||
"/autohand": typeof AutohandRoute;
|
||||
"/blog": typeof BlogRouteWithChildren;
|
||||
@@ -592,7 +583,6 @@ export interface FileRouteTypes {
|
||||
| "/agents"
|
||||
| "/agoragentic"
|
||||
| "/amp"
|
||||
| "/antigravity"
|
||||
| "/auggie"
|
||||
| "/autohand"
|
||||
| "/blog"
|
||||
@@ -657,7 +647,6 @@ export interface FileRouteTypes {
|
||||
| "/agents"
|
||||
| "/agoragentic"
|
||||
| "/amp"
|
||||
| "/antigravity"
|
||||
| "/auggie"
|
||||
| "/autohand"
|
||||
| "/changelog"
|
||||
@@ -720,7 +709,6 @@ export interface FileRouteTypes {
|
||||
| "/agents"
|
||||
| "/agoragentic"
|
||||
| "/amp"
|
||||
| "/antigravity"
|
||||
| "/auggie"
|
||||
| "/autohand"
|
||||
| "/blog"
|
||||
@@ -786,7 +774,6 @@ export interface RootRouteChildren {
|
||||
AgentsRoute: typeof AgentsRoute;
|
||||
AgoragenticRoute: typeof AgoragenticRoute;
|
||||
AmpRoute: typeof AmpRoute;
|
||||
AntigravityRoute: typeof AntigravityRoute;
|
||||
AuggieRoute: typeof AuggieRoute;
|
||||
AutohandRoute: typeof AutohandRoute;
|
||||
BlogRoute: typeof BlogRouteWithChildren;
|
||||
@@ -1167,13 +1154,6 @@ declare module "@tanstack/react-router" {
|
||||
preLoaderRoute: typeof AuggieRouteImport;
|
||||
parentRoute: typeof rootRouteImport;
|
||||
};
|
||||
"/antigravity": {
|
||||
id: "/antigravity";
|
||||
path: "/antigravity";
|
||||
fullPath: "/antigravity";
|
||||
preLoaderRoute: typeof AntigravityRouteImport;
|
||||
parentRoute: typeof rootRouteImport;
|
||||
};
|
||||
"/amp": {
|
||||
id: "/amp";
|
||||
path: "/amp";
|
||||
@@ -1318,7 +1298,6 @@ const rootRouteChildren: RootRouteChildren = {
|
||||
AgentsRoute: AgentsRoute,
|
||||
AgoragenticRoute: AgoragenticRoute,
|
||||
AmpRoute: AmpRoute,
|
||||
AntigravityRoute: AntigravityRoute,
|
||||
AuggieRoute: AuggieRoute,
|
||||
AutohandRoute: AutohandRoute,
|
||||
BlogRoute: BlogRouteWithChildren,
|
||||
|
||||
@@ -10,7 +10,7 @@ export const Route = createFileRoute("/agents")({
|
||||
head: () =>
|
||||
pageMeta(
|
||||
"Supported agents – Every coding agent Paseo runs",
|
||||
"Run Claude Code, Codex, Copilot, OpenCode, Antigravity, Muse Code, Cursor CLI, Gemini CLI, and dozens more coding agents from your phone. Self-hosted, your code stays on your machine.",
|
||||
"Run Claude Code, Codex, Copilot, OpenCode, Muse Code, Cursor CLI, Gemini CLI, and dozens more coding agents from your phone. Self-hosted, your code stays on your machine.",
|
||||
"/agents",
|
||||
),
|
||||
component: AgentsPage,
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
import { createFileRoute } from "@tanstack/react-router";
|
||||
import { agentRouteOptions } from "~/components/agent-route";
|
||||
|
||||
export const Route = createFileRoute("/antigravity")(agentRouteOptions("antigravity"));
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path transform="translate(12 12) scale(0.24) translate(-55.738 -56.945)" d="M89.6992 93.695C94.3659 97.195 101.366 94.8617 94.9492 88.445C75.6992 69.7783 79.7825 18.445 55.8659 18.445C31.9492 18.445 36.0325 69.7783 16.7825 88.445C9.78251 95.445 17.3658 97.195 22.0325 93.695C40.1159 81.445 38.9492 59.8617 55.8659 59.8617C72.7825 59.8617 71.6159 81.445 89.6992 93.695Z"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 463 B |
@@ -1,7 +0,0 @@
|
||||
import type { PluginServerContext } from "@getpaseo/plugin/server";
|
||||
import { createAntigravityProvider } from "./server/provider.js";
|
||||
|
||||
export default function contribute(server: PluginServerContext) {
|
||||
server.registerProvider(createAntigravityProvider());
|
||||
return () => {};
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{ "id": "antigravity-provider", "requirements": { "paseo": ">=0.10.0" } }
|
||||
@@ -1,96 +0,0 @@
|
||||
import type {
|
||||
ProviderCatalog,
|
||||
ProviderLaunch,
|
||||
ProviderModel,
|
||||
ProviderMode,
|
||||
ProviderStatus,
|
||||
} from "@getpaseo/plugin/server/provider";
|
||||
import { probe } from "./process.js";
|
||||
import { AntigravityError } from "./wire.js";
|
||||
|
||||
const modes: readonly ProviderMode[] = [
|
||||
{
|
||||
id: "default",
|
||||
icon: "Shield",
|
||||
colorTier: "moderate",
|
||||
label: "Default",
|
||||
description: "Workspace reads and edits are allowed; shell commands are denied.",
|
||||
},
|
||||
{
|
||||
id: "accept-edits",
|
||||
icon: "ShieldPlus",
|
||||
colorTier: "moderate",
|
||||
label: "Accept edits",
|
||||
description:
|
||||
"Native accept-edits mode. Workspace edits are allowed; shell commands are denied.",
|
||||
},
|
||||
{
|
||||
id: "plan",
|
||||
icon: "ShieldEllipsis",
|
||||
colorTier: "planning",
|
||||
label: "Plan",
|
||||
description:
|
||||
"Native plan mode permits workspace edits. Shell commands are denied; this is not read-only.",
|
||||
},
|
||||
{
|
||||
id: "full-access",
|
||||
icon: "ShieldOff",
|
||||
colorTier: "dangerous",
|
||||
label: "Full access",
|
||||
description: "Skip native permission checks, including for shell commands.",
|
||||
isUnattended: true,
|
||||
},
|
||||
];
|
||||
|
||||
export async function getCatalog(launch: ProviderLaunch, cwd?: string): Promise<ProviderCatalog> {
|
||||
const output = await probe({ launch, args: ["models"], cwd });
|
||||
const models: ProviderModel[] = [];
|
||||
for (const line of output.trim().split(/\r?\n/)) {
|
||||
const [id, label, ...extra] = line.split("\t");
|
||||
if (!id || !label || extra.length > 0)
|
||||
throw new AntigravityError("Invalid `agy models` output", "INVALID_CATALOG");
|
||||
if (models.some((model) => model.id === id))
|
||||
throw new AntigravityError(`Duplicate Antigravity model: ${id}`, "INVALID_CATALOG");
|
||||
models.push({ id, label });
|
||||
}
|
||||
return { models, modes, thinkingOptions: [], defaultMode: "default" };
|
||||
}
|
||||
|
||||
export async function getStatus(launch: ProviderLaunch): Promise<ProviderStatus> {
|
||||
try {
|
||||
const version = (await probe({ launch, args: ["--version"] })).trim();
|
||||
const match = /^(\d+)\.(\d+)\.(\d+)$/.exec(version);
|
||||
if (!match)
|
||||
return {
|
||||
available: false,
|
||||
diagnostic: "Could not read the Antigravity version. Update `agy` to 1.1.15 or later.",
|
||||
};
|
||||
const [, major, minor, patch] = match.map(Number);
|
||||
const supported = major > 1 || (major === 1 && (minor > 1 || (minor === 1 && patch >= 15)));
|
||||
if (!supported)
|
||||
return {
|
||||
available: false,
|
||||
diagnostic: `Antigravity ${version} is unsupported. Update \`agy\` to 1.1.15 or later.`,
|
||||
};
|
||||
await getCatalog(launch);
|
||||
return { available: true };
|
||||
} catch (error) {
|
||||
if (!(error instanceof AntigravityError)) throw error;
|
||||
return { available: false, diagnostic: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
export function validateSelection(
|
||||
config: { model?: string; mode?: string; thinkingOption?: string },
|
||||
catalog: ProviderCatalog,
|
||||
): void {
|
||||
if (config.model && !catalog.models.some((model) => model.id === config.model))
|
||||
throw new AntigravityError(`Unknown Antigravity model: ${config.model}`, "INVALID_MODEL");
|
||||
if (config.mode && !catalog.modes.some((mode) => mode.id === config.mode))
|
||||
throw new AntigravityError(`Unknown Antigravity mode: ${config.mode}`, "INVALID_MODE");
|
||||
if (config.thinkingOption)
|
||||
throw new AntigravityError(
|
||||
"Antigravity effort is selected through the model ID",
|
||||
"UNSUPPORTED_THINKING",
|
||||
);
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
||||
import { signalProcess } from "./signals.js";
|
||||
import { createInterface } from "node:readline";
|
||||
import type { ProviderLaunch, ProviderSessionConfig } from "@getpaseo/plugin/server/provider";
|
||||
import {
|
||||
AntigravityError,
|
||||
decodeFrame,
|
||||
diagnostic,
|
||||
encodePrompt,
|
||||
type Frame,
|
||||
type Init,
|
||||
} from "./wire.js";
|
||||
|
||||
interface DriverOptions {
|
||||
launch: ProviderLaunch;
|
||||
config: ProviderSessionConfig;
|
||||
conversationId: string | null;
|
||||
onFrame(frame: Frame): void;
|
||||
onExit(error: AntigravityError): void;
|
||||
}
|
||||
|
||||
export interface Driver {
|
||||
ready: Promise<Init>;
|
||||
selection: string;
|
||||
prompt(text: string): Promise<void>;
|
||||
stop(reason: "interrupt" | "close"): Promise<void>;
|
||||
}
|
||||
|
||||
export function selection(config: ProviderSessionConfig): string {
|
||||
return JSON.stringify([config.model, config.mode]);
|
||||
}
|
||||
|
||||
function driverArgs(options: DriverOptions): string[] {
|
||||
const { config, conversationId } = options;
|
||||
const args = [
|
||||
...options.launch.args,
|
||||
"--input-format",
|
||||
"stream-json",
|
||||
"--output-format",
|
||||
"stream-json",
|
||||
"--add-dir",
|
||||
config.cwd,
|
||||
"--disable-slash-commands",
|
||||
];
|
||||
if (config.model) args.push("--model", config.model);
|
||||
if (config.mode === "accept-edits" || config.mode === "plan") args.push("--mode", config.mode);
|
||||
if (config.mode === "full-access") args.push("--dangerously-skip-permissions");
|
||||
if (conversationId) args.push("--conversation", conversationId);
|
||||
return args;
|
||||
}
|
||||
|
||||
export function startDriver(options: DriverOptions): Driver {
|
||||
const child = spawn(options.launch.command, driverArgs(options), {
|
||||
cwd: options.config.cwd,
|
||||
env: { ...options.launch.env, ...options.config.env },
|
||||
detached: process.platform !== "win32",
|
||||
stdio: "pipe",
|
||||
});
|
||||
const lines = createInterface({ input: child.stdout });
|
||||
let cleanup = Promise.resolve();
|
||||
let stderr = "";
|
||||
let state: "starting" | "ready" | "stopping" | "exited" = "starting";
|
||||
let resolveReady: (init: Init) => void;
|
||||
let rejectReady: (error: Error) => void;
|
||||
const ready = new Promise<Init>((resolve, reject) => {
|
||||
resolveReady = resolve;
|
||||
rejectReady = reject;
|
||||
});
|
||||
let resolveExit: () => void;
|
||||
const exited = new Promise<void>((resolve) => {
|
||||
resolveExit = resolve;
|
||||
});
|
||||
const startupDeadline = setTimeout(
|
||||
() =>
|
||||
fail(
|
||||
new AntigravityError("Antigravity did not initialize within 30 seconds", "STARTUP_TIMEOUT"),
|
||||
),
|
||||
30_000,
|
||||
);
|
||||
child.stderr.on("data", (chunk: Buffer) => {
|
||||
stderr = (stderr + chunk.toString()).slice(-8192);
|
||||
});
|
||||
child.stdin.on("error", (error: Error) =>
|
||||
fail(new AntigravityError(error.message, "STDIN_ERROR")),
|
||||
);
|
||||
child.on("error", (error: Error) => fail(new AntigravityError(error.message, "SPAWN_ERROR")));
|
||||
child.on("close", (code, signal) => {
|
||||
clearTimeout(startupDeadline);
|
||||
lines.close();
|
||||
const error = new AntigravityError(
|
||||
diagnostic(stderr.trim() || `Antigravity exited (${signal || code})`),
|
||||
"PROCESS_EXIT",
|
||||
);
|
||||
if (state === "starting") rejectReady(error);
|
||||
if (state === "ready") options.onExit(error);
|
||||
state = "exited";
|
||||
resolveExit();
|
||||
});
|
||||
lines.on("line", (line) => {
|
||||
if (!line.trim() || state === "exited" || state === "stopping") return;
|
||||
let decoded: Frame;
|
||||
try {
|
||||
decoded = decodeFrame(line);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
fail(new AntigravityError(`Invalid Antigravity output: ${message}`, "INVALID_FRAME"));
|
||||
return;
|
||||
}
|
||||
if (state === "starting") {
|
||||
if (decoded.event === "init") {
|
||||
clearTimeout(startupDeadline);
|
||||
state = "ready";
|
||||
resolveReady(decoded);
|
||||
} else if (decoded.event === "result") {
|
||||
fail(new AntigravityError(diagnostic(decoded.result.error), "STARTUP_ERROR"));
|
||||
}
|
||||
return;
|
||||
}
|
||||
options.onFrame(decoded);
|
||||
});
|
||||
|
||||
function fail(failure: AntigravityError): void {
|
||||
clearTimeout(startupDeadline);
|
||||
if (state === "starting") rejectReady(failure);
|
||||
if (state === "ready") options.onExit(failure);
|
||||
if (state === "exited" || state === "stopping") return;
|
||||
state = "stopping";
|
||||
cleanup = Promise.resolve(signalGroup(child, "SIGKILL"));
|
||||
// The stop operation awaits cleanup and owns reporting its failure.
|
||||
void cleanup.catch(() => undefined);
|
||||
}
|
||||
|
||||
return {
|
||||
ready,
|
||||
selection: selection(options.config),
|
||||
prompt(text) {
|
||||
if (state !== "ready")
|
||||
return Promise.reject(new AntigravityError("Antigravity is not ready"));
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
child.stdin.write(encodePrompt(text), (error) => {
|
||||
if (error) reject(error);
|
||||
else resolve();
|
||||
});
|
||||
});
|
||||
},
|
||||
async stop(reason) {
|
||||
if (process.platform === "win32") await cleanup;
|
||||
if (state === "exited") return;
|
||||
if (state === "starting") rejectReady(new AntigravityError("Antigravity startup canceled"));
|
||||
clearTimeout(startupDeadline);
|
||||
state = "stopping";
|
||||
if (process.platform === "win32") {
|
||||
// Node signal emulation kills only the leader; taskkill must see the live tree.
|
||||
cleanup = Promise.resolve(signalGroup(child, "SIGKILL"));
|
||||
await cleanup;
|
||||
await exited;
|
||||
return;
|
||||
}
|
||||
if (reason === "close") child.stdin.end();
|
||||
else signalGroup(child, "SIGINT");
|
||||
const terminate = setTimeout(() => signalGroup(child, "SIGTERM"), 1000);
|
||||
const kill = setTimeout(() => signalGroup(child, "SIGKILL"), 2000);
|
||||
await exited;
|
||||
clearTimeout(terminate);
|
||||
clearTimeout(kill);
|
||||
// A tool can keep running after its CLI leader exits.
|
||||
signalGroup(child, "SIGKILL");
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function signalGroup(
|
||||
child: ChildProcessWithoutNullStreams,
|
||||
signal: NodeJS.Signals,
|
||||
): void | Promise<void> {
|
||||
if (!child.pid) return;
|
||||
return signalProcess({ platform: process.platform, pid: child.pid, signal });
|
||||
}
|
||||
|
||||
interface ProbeOptions {
|
||||
launch: ProviderLaunch;
|
||||
args: string[];
|
||||
cwd?: string;
|
||||
}
|
||||
export function probe(options: ProbeOptions): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const child = spawn(options.launch.command, [...options.launch.args, ...options.args], {
|
||||
env: options.launch.env,
|
||||
cwd: options.cwd,
|
||||
stdio: "pipe",
|
||||
detached: process.platform !== "win32",
|
||||
});
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
const deadline = setTimeout(() => {
|
||||
const termination = Promise.resolve(signalGroup(child, "SIGKILL"));
|
||||
void termination.then(() => {
|
||||
reject(new AntigravityError("Antigravity discovery timed out", "PROBE_TIMEOUT"));
|
||||
return undefined;
|
||||
}, reject);
|
||||
}, 10_000);
|
||||
child.stdout.on("data", (chunk: Buffer) => {
|
||||
stdout += chunk.toString();
|
||||
});
|
||||
child.stderr.on("data", (chunk: Buffer) => {
|
||||
stderr = (stderr + chunk.toString()).slice(-8192);
|
||||
});
|
||||
child.on("error", (error: Error) => {
|
||||
clearTimeout(deadline);
|
||||
reject(new AntigravityError(error.message, "SPAWN_ERROR"));
|
||||
});
|
||||
child.on("close", (code) => {
|
||||
clearTimeout(deadline);
|
||||
if (code === 0) resolve(stdout);
|
||||
else
|
||||
reject(
|
||||
new AntigravityError(
|
||||
diagnostic(stderr.trim() || `agy exited with code ${code}`),
|
||||
"PROBE_ERROR",
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { chmod, mkdtemp, rm, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { ProviderPrompt } from "@getpaseo/plugin/server/provider";
|
||||
import { AntigravityError } from "./wire.js";
|
||||
|
||||
interface EncodedPrompt {
|
||||
text: string;
|
||||
nativeText: string;
|
||||
}
|
||||
|
||||
export class PromptFiles {
|
||||
private directory: string | null = null;
|
||||
|
||||
async encode(prompt: ProviderPrompt): Promise<EncodedPrompt> {
|
||||
if (
|
||||
prompt.input.type !== "message" ||
|
||||
prompt.delivery === "steer" ||
|
||||
prompt.outputSchema !== undefined
|
||||
)
|
||||
throw new AntigravityError("Antigravity supports messages only");
|
||||
const parts = prompt.input.content.map((part) => {
|
||||
if (part.type === "text") return part;
|
||||
if (part.type === "image") return { ...part, extension: imageExtension(part.mimeType) };
|
||||
throw new AntigravityError("Antigravity supports text and image content only");
|
||||
});
|
||||
const text: string[] = [];
|
||||
const native: string[] = [];
|
||||
for (const part of parts) {
|
||||
if (part.type === "text") {
|
||||
text.push(part.text);
|
||||
native.push(part.text);
|
||||
} else if (part.type === "image") {
|
||||
const extension = part.extension;
|
||||
if (this.directory === null) {
|
||||
this.directory = await mkdtemp(path.join(os.tmpdir(), "paseo-antigravity-images-"));
|
||||
await chmod(this.directory, 0o700);
|
||||
}
|
||||
const file = path.join(this.directory, `${randomUUID()}.${extension}`);
|
||||
await writeFile(file, Buffer.from(part.data, "base64"), { flag: "wx", mode: 0o600 });
|
||||
await chmod(file, 0o600);
|
||||
native.push(`Attached image: ${file}`);
|
||||
}
|
||||
}
|
||||
return { text: text.join("\n"), nativeText: native.join("\n") };
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
if (this.directory !== null) {
|
||||
await rm(this.directory, { recursive: true, force: true });
|
||||
this.directory = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function imageExtension(mimeType: string): string {
|
||||
const match = /^image\/([a-z0-9][a-z0-9.+-]*)$/i.exec(mimeType);
|
||||
if (!match) throw new AntigravityError(`Expected an image MIME type, received ${mimeType}`);
|
||||
const subtype = match[1].toLowerCase();
|
||||
if (subtype === "jpeg") return "jpg";
|
||||
if (subtype === "svg+xml") return "svg";
|
||||
return subtype;
|
||||
}
|
||||
@@ -1,382 +0,0 @@
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { z } from "zod";
|
||||
import type {
|
||||
ProviderCatalog,
|
||||
ProviderConfigChanges,
|
||||
ProviderEvent,
|
||||
ProviderPersistence,
|
||||
ProviderPrompt,
|
||||
ProviderSessionConfig,
|
||||
ProviderLaunch,
|
||||
ProviderTimelineItem,
|
||||
ProviderUsage,
|
||||
} from "@getpaseo/plugin/server/provider";
|
||||
import { startDriver, selection, type Driver } from "./process.js";
|
||||
import { validateSelection } from "./catalog.js";
|
||||
import { PromptFiles } from "./prompt.js";
|
||||
import { toolItem } from "./timeline.js";
|
||||
import { AntigravityError, diagnostic, type Frame, type Step } from "./wire.js";
|
||||
|
||||
const persistenceSchema = z
|
||||
.object({ version: z.literal(1), data: z.object({ conversationId: z.string().min(1) }).strict() })
|
||||
.strict();
|
||||
interface Turn {
|
||||
id: string;
|
||||
text: Map<number, string>;
|
||||
usageSteps: Set<number>;
|
||||
tools: Map<string, Extract<ProviderTimelineItem, { type: "tool_call" }>>;
|
||||
usage: Required<Pick<ProviderUsage, "inputTokens" | "outputTokens" | "cachedInputTokens">>;
|
||||
}
|
||||
type State =
|
||||
| { type: "dormant" }
|
||||
| { type: "starting"; driver: Driver }
|
||||
| { type: "idle"; driver: Driver }
|
||||
| { type: "running"; driver: Driver; turn: Turn }
|
||||
| { type: "stopping"; driver: Driver }
|
||||
| { type: "closed" };
|
||||
interface SessionOptions {
|
||||
id: string;
|
||||
config: ProviderSessionConfig;
|
||||
launch: ProviderLaunch;
|
||||
catalog(): ProviderCatalog;
|
||||
persistence?: ProviderPersistence;
|
||||
emit(event: ProviderEvent): void;
|
||||
}
|
||||
|
||||
export class Session {
|
||||
private state: State = { type: "dormant" };
|
||||
private config: ProviderSessionConfig;
|
||||
private conversationId: string | null;
|
||||
private firstMessage: boolean;
|
||||
private readonly promptFiles = new PromptFiles();
|
||||
private readonly reportedDenials = new Map<string, number>();
|
||||
|
||||
constructor(private readonly options: SessionOptions) {
|
||||
this.config = options.config;
|
||||
this.conversationId = options.persistence
|
||||
? persistenceSchema.parse(options.persistence).data.conversationId
|
||||
: null;
|
||||
this.firstMessage = this.conversationId === null;
|
||||
validateSelection(this.config, options.catalog());
|
||||
}
|
||||
|
||||
async open(requestId: string, capabilities: readonly string[]): Promise<void> {
|
||||
await this.ensureDriver();
|
||||
this.emit({
|
||||
type: "session.opened",
|
||||
requestId,
|
||||
sessionId: this.options.id,
|
||||
capabilities,
|
||||
restoration: "core",
|
||||
persistence: this.config.persist ? this.persistence() : undefined,
|
||||
cwd: this.config.cwd,
|
||||
title: this.config.title,
|
||||
});
|
||||
this.publishConfig();
|
||||
this.emit({ type: "session.commands", sessionId: this.options.id, commands: [] });
|
||||
this.emit({ type: "session.ready", requestId, sessionId: this.options.id });
|
||||
}
|
||||
|
||||
async prompt(prompt: ProviderPrompt): Promise<void> {
|
||||
if (this.state.type === "running")
|
||||
throw new AntigravityError(
|
||||
"Antigravity already has an active turn; wait for it to finish",
|
||||
"TURN_ACTIVE",
|
||||
);
|
||||
const { text, nativeText } = await this.promptFiles.encode(prompt);
|
||||
await this.ensureDriver();
|
||||
if (this.state.type !== "idle") throw new AntigravityError("Antigravity session is not ready");
|
||||
const driver = this.state.driver;
|
||||
const turn: Turn = {
|
||||
id: randomUUID(),
|
||||
text: new Map(),
|
||||
usageSteps: new Set(),
|
||||
tools: new Map(),
|
||||
usage: { inputTokens: 0, outputTokens: 0, cachedInputTokens: 0 },
|
||||
};
|
||||
this.state = { type: "running", driver, turn };
|
||||
this.emit({
|
||||
type: "session.prompt_result",
|
||||
sessionId: this.options.id,
|
||||
clientMessageId: prompt.clientMessageId,
|
||||
result: { type: "turn", turnId: turn.id },
|
||||
});
|
||||
this.emit({
|
||||
type: "session.turn",
|
||||
sessionId: this.options.id,
|
||||
turnId: turn.id,
|
||||
state: "started",
|
||||
});
|
||||
this.item({
|
||||
type: "user_message",
|
||||
id: prompt.clientMessageId,
|
||||
clientMessageId: prompt.clientMessageId,
|
||||
text,
|
||||
});
|
||||
const driverText =
|
||||
this.firstMessage && this.config.systemPrompt
|
||||
? `${this.config.systemPrompt}\n\n${nativeText}`
|
||||
: nativeText;
|
||||
try {
|
||||
await driver.prompt(driverText);
|
||||
this.firstMessage = false;
|
||||
} catch (error) {
|
||||
if (!(error instanceof Error)) throw error;
|
||||
this.failTurn(new AntigravityError(error.message));
|
||||
await driver.stop("interrupt");
|
||||
}
|
||||
}
|
||||
|
||||
configure(changes: ProviderConfigChanges): void {
|
||||
const config = {
|
||||
...this.config,
|
||||
model: changes.model === null ? undefined : (changes.model ?? this.config.model),
|
||||
mode: changes.mode === null ? undefined : (changes.mode ?? this.config.mode),
|
||||
thinkingOption:
|
||||
changes.thinkingOption === null
|
||||
? undefined
|
||||
: (changes.thinkingOption ?? this.config.thinkingOption),
|
||||
};
|
||||
if (changes.settings && Object.keys(changes.settings).length > 0)
|
||||
throw new AntigravityError("Antigravity has no configurable settings");
|
||||
validateSelection(config, this.options.catalog());
|
||||
this.config = config;
|
||||
this.publishConfig();
|
||||
}
|
||||
|
||||
async interrupt(): Promise<void> {
|
||||
if (this.state.type !== "running") return;
|
||||
const { driver, turn } = this.state;
|
||||
this.state = { type: "stopping", driver };
|
||||
await driver.stop("interrupt");
|
||||
this.finishTools(turn, "canceled");
|
||||
this.emit({
|
||||
type: "session.turn",
|
||||
sessionId: this.options.id,
|
||||
turnId: turn.id,
|
||||
state: "canceled",
|
||||
});
|
||||
this.state = { type: "dormant" };
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
const state = this.state;
|
||||
this.state = { type: "closed" };
|
||||
try {
|
||||
if (state.type !== "dormant" && state.type !== "closed") await state.driver.stop("close");
|
||||
} finally {
|
||||
await this.promptFiles.close();
|
||||
}
|
||||
if (state.type === "running") {
|
||||
this.finishTools(state.turn, "canceled");
|
||||
this.emit({
|
||||
type: "session.turn",
|
||||
sessionId: this.options.id,
|
||||
turnId: state.turn.id,
|
||||
state: "canceled",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private async ensureDriver(): Promise<void> {
|
||||
if (this.state.type === "closed") throw new AntigravityError("Antigravity session is closed");
|
||||
if (this.state.type === "stopping") {
|
||||
await this.state.driver.stop("interrupt");
|
||||
this.state = { type: "dormant" };
|
||||
}
|
||||
if (this.state.type === "idle" && this.state.driver.selection !== selection(this.config)) {
|
||||
const driver = this.state.driver;
|
||||
this.state = { type: "stopping", driver };
|
||||
await driver.stop("close");
|
||||
this.state = { type: "dormant" };
|
||||
}
|
||||
if (this.state.type !== "dormant") return;
|
||||
this.reportedDenials.clear();
|
||||
const driver = startDriver({
|
||||
launch: this.options.launch,
|
||||
config: this.config,
|
||||
conversationId: this.conversationId,
|
||||
onFrame: (frame) => this.accept(frame),
|
||||
onExit: (error) => this.failTurn(error),
|
||||
});
|
||||
this.state = { type: "starting", driver };
|
||||
try {
|
||||
const init = await driver.ready;
|
||||
if (this.conversationId !== null && init.conversation_id !== this.conversationId)
|
||||
throw new AntigravityError(
|
||||
"Antigravity restored a different conversation",
|
||||
"INVALID_RESUME",
|
||||
);
|
||||
this.conversationId = init.conversation_id;
|
||||
this.state = { type: "idle", driver };
|
||||
if (this.config.persist)
|
||||
this.emit({
|
||||
type: "session.persistence",
|
||||
sessionId: this.options.id,
|
||||
persistence: this.persistence(),
|
||||
});
|
||||
} catch (error) {
|
||||
this.state = { type: "dormant" };
|
||||
await driver.stop("interrupt");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private accept(frame: Frame): void {
|
||||
if (this.state.type !== "running") return;
|
||||
const { turn, driver } = this.state;
|
||||
if (frame.event === "step_update") {
|
||||
this.acceptStep(frame.step_update, turn);
|
||||
return;
|
||||
}
|
||||
if (frame.event !== "result") return;
|
||||
const result = frame.result;
|
||||
const denied = takeNewDenials(result.denied_actions, this.reportedDenials);
|
||||
if (denied.length > 0) {
|
||||
this.emit({
|
||||
type: "session.notice",
|
||||
sessionId: this.options.id,
|
||||
notice: {
|
||||
id: `${turn.id}:denied`,
|
||||
severity: "warning",
|
||||
title: denialNoticeTitle(denied),
|
||||
description: "Switch to Full access to allow shell commands.",
|
||||
},
|
||||
});
|
||||
}
|
||||
if (result.status === "ERROR") {
|
||||
const canceled = result.error === "interrupted";
|
||||
this.finishTools(turn, canceled ? "canceled" : "failed");
|
||||
this.retire(driver);
|
||||
this.emit({
|
||||
type: "session.turn",
|
||||
sessionId: this.options.id,
|
||||
turnId: turn.id,
|
||||
state: canceled ? "canceled" : "failed",
|
||||
error: { message: diagnostic(result.error) },
|
||||
});
|
||||
} else {
|
||||
this.finishTools(turn, "completed");
|
||||
this.state = { type: "idle", driver };
|
||||
this.emit({
|
||||
type: "session.turn",
|
||||
sessionId: this.options.id,
|
||||
turnId: turn.id,
|
||||
state: "completed",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private acceptStep(step: Step, turn: Turn): void {
|
||||
const id = `${turn.id}:${step.step_index}`;
|
||||
if (step.step_type === "agent_response") {
|
||||
const previous = turn.text.get(step.step_index) || "";
|
||||
const text = previous + step.text_delta;
|
||||
turn.text.set(step.step_index, text);
|
||||
if (step.text_delta) this.item({ type: "assistant_message", id, messageId: id, text });
|
||||
if (step.state === "DONE" && step.usage && !turn.usageSteps.has(step.step_index)) {
|
||||
turn.usageSteps.add(step.step_index);
|
||||
turn.usage.inputTokens += step.usage.input_tokens;
|
||||
turn.usage.outputTokens += step.usage.output_tokens;
|
||||
turn.usage.cachedInputTokens += step.usage.cache_read_tokens;
|
||||
this.emit({
|
||||
type: "session.usage",
|
||||
sessionId: this.options.id,
|
||||
turnId: turn.id,
|
||||
usage: { ...turn.usage },
|
||||
});
|
||||
}
|
||||
} else if (step.step_type === "tool" || step.step_type === "subagent") {
|
||||
const item = toolItem(step, id);
|
||||
turn.tools.set(id, item);
|
||||
this.item(item);
|
||||
}
|
||||
}
|
||||
|
||||
private failTurn(error: AntigravityError): void {
|
||||
const state = this.state;
|
||||
if (state.type !== "running" && state.type !== "idle") return;
|
||||
this.retire(state.driver);
|
||||
if (state.type === "running") {
|
||||
this.finishTools(state.turn, "failed");
|
||||
this.emit({
|
||||
type: "session.turn",
|
||||
sessionId: this.options.id,
|
||||
turnId: state.turn.id,
|
||||
state: "failed",
|
||||
error: { message: error.message, code: error.code },
|
||||
});
|
||||
} else
|
||||
this.emit({
|
||||
type: "session.runtime_failed",
|
||||
sessionId: this.options.id,
|
||||
error: { message: error.message, code: error.code },
|
||||
});
|
||||
}
|
||||
|
||||
private retire(driver: Driver): void {
|
||||
this.state = { type: "stopping", driver };
|
||||
void driver.stop("interrupt").then(() => {
|
||||
if (this.state.type === "stopping" && this.state.driver === driver)
|
||||
this.state = { type: "dormant" };
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private finishTools(turn: Turn, status: "completed" | "failed" | "canceled"): void {
|
||||
for (const item of turn.tools.values()) {
|
||||
if (item.status !== "running") continue;
|
||||
if (status === "failed") this.item({ ...item, status, error: "Antigravity turn failed" });
|
||||
else this.item({ ...item, status, error: null });
|
||||
}
|
||||
}
|
||||
|
||||
private persistence(): ProviderPersistence {
|
||||
if (this.conversationId === null) throw new AntigravityError("Antigravity has no conversation");
|
||||
return { version: 1, data: { conversationId: this.conversationId } };
|
||||
}
|
||||
|
||||
private publishConfig(): void {
|
||||
this.emit({
|
||||
type: "session.config",
|
||||
sessionId: this.options.id,
|
||||
config: {
|
||||
model: this.config.model,
|
||||
mode: this.config.mode || "default",
|
||||
models: this.options.catalog().models,
|
||||
modes: this.options.catalog().modes,
|
||||
thinkingOptions: [],
|
||||
settings: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
private item(item: ProviderTimelineItem): void {
|
||||
this.emit({ type: "timeline.item", sessionId: this.options.id, item });
|
||||
}
|
||||
private emit(event: ProviderEvent): void {
|
||||
this.options.emit(event);
|
||||
}
|
||||
}
|
||||
|
||||
// Native results contain every denial since this driver started, including repeated actions.
|
||||
function takeNewDenials(
|
||||
actions: Extract<Frame, { event: "result" }>["result"]["denied_actions"],
|
||||
reported: Map<string, number>,
|
||||
) {
|
||||
const counts = new Map<string, number>();
|
||||
const fresh = actions.filter((action) => {
|
||||
const key = JSON.stringify([action.action, action.display_name]);
|
||||
const count = (counts.get(key) ?? 0) + 1;
|
||||
counts.set(key, count);
|
||||
return count > (reported.get(key) ?? 0);
|
||||
});
|
||||
for (const [key, count] of counts) reported.set(key, Math.max(count, reported.get(key) ?? 0));
|
||||
return fresh;
|
||||
}
|
||||
|
||||
function denialNoticeTitle(
|
||||
actions: Extract<Frame, { event: "result" }>["result"]["denied_actions"],
|
||||
): string {
|
||||
if (actions.length > 1) return `${actions.length} actions denied`;
|
||||
return actions[0].action === "command" ? "Shell command denied" : "Action denied";
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
interface SignalOptions {
|
||||
platform: NodeJS.Platform;
|
||||
pid: number;
|
||||
signal: NodeJS.Signals;
|
||||
}
|
||||
|
||||
type SignalPlan =
|
||||
| { type: "group"; pid: number; signal: NodeJS.Signals }
|
||||
| { type: "tree"; command: "taskkill"; args: string[] };
|
||||
|
||||
const executeFile = promisify(execFile);
|
||||
|
||||
export function signalPlan(options: SignalOptions): SignalPlan {
|
||||
if (options.platform === "win32") {
|
||||
return { type: "tree", command: "taskkill", args: ["/PID", String(options.pid), "/T", "/F"] };
|
||||
}
|
||||
return { type: "group", pid: -options.pid, signal: options.signal };
|
||||
}
|
||||
|
||||
export function signalProcess(options: SignalOptions): void | Promise<void> {
|
||||
const plan = signalPlan(options);
|
||||
if (plan.type === "tree") return killTree(plan);
|
||||
try {
|
||||
process.kill(plan.pid, plan.signal);
|
||||
} catch (error) {
|
||||
if (!(error instanceof Error && "code" in error && error.code === "ESRCH")) throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function killTree(plan: Extract<SignalPlan, { type: "tree" }>): Promise<void> {
|
||||
try {
|
||||
await executeFile(plan.command, plan.args, { windowsHide: true });
|
||||
} catch (error) {
|
||||
// taskkill reports an already-exited PID with exit code 128.
|
||||
if (!(error instanceof Error && "code" in error && error.code === 128)) throw error;
|
||||
}
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
import type {
|
||||
ProviderTimelineItem,
|
||||
ProviderToolCallDetail,
|
||||
} from "@getpaseo/plugin/server/provider";
|
||||
import type { Step } from "./wire.js";
|
||||
|
||||
export function toolItem(
|
||||
step: Step,
|
||||
id: string,
|
||||
): Extract<ProviderTimelineItem, { type: "tool_call" }> {
|
||||
const detail = toolDetail(step);
|
||||
const name = step.tool_name || "Antigravity tool";
|
||||
const metadata = step.subagent_info ? { subagents: step.subagent_info.subagents } : undefined;
|
||||
const base = { type: "tool_call" as const, id, callId: id, name, detail, metadata };
|
||||
if (step.state === "ERROR") {
|
||||
const error =
|
||||
step.tool_info && step.tool_info.error
|
||||
? step.tool_info.error.message
|
||||
: "Antigravity tool failed";
|
||||
return { ...base, status: "failed", error };
|
||||
}
|
||||
const status = step.state === "ACTIVE" ? "running" : "completed";
|
||||
return { ...base, status, error: null };
|
||||
}
|
||||
|
||||
function toolDetail(step: Step): ProviderToolCallDetail {
|
||||
if (step.subagent_info) {
|
||||
const child = step.subagent_info.subagents[0];
|
||||
if (child)
|
||||
return {
|
||||
type: "sub_agent",
|
||||
subAgentType: child.type_name,
|
||||
description: child.initial_prompt,
|
||||
childSessionId: child.conversation_id === null ? undefined : child.conversation_id,
|
||||
log: child.log_uri,
|
||||
};
|
||||
}
|
||||
const info = step.tool_info;
|
||||
if (!info) return { type: "plain_text", label: step.tool_name, text: "" };
|
||||
const path = info.parameters.AbsolutePath || info.parameters.TargetFile;
|
||||
if (info.name === "run_command" && typeof info.parameters.CommandLine === "string") {
|
||||
return { type: "shell", command: info.parameters.CommandLine, output: info.output };
|
||||
}
|
||||
if (info.name === "view_file" && typeof path === "string")
|
||||
return { type: "read", filePath: path, content: info.output };
|
||||
if (info.name === "write_to_file" && typeof path === "string")
|
||||
return { type: "write", filePath: path };
|
||||
if (
|
||||
(info.name === "replace_file_content" || info.name === "multi_replace_file_content") &&
|
||||
typeof path === "string"
|
||||
)
|
||||
return { type: "edit", filePath: path };
|
||||
// Native frames summarize writes; they do not carry the file contents or diff.
|
||||
return { type: "unknown", input: info.parameters, output: info.error || info.output };
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const usage = z.object({
|
||||
input_tokens: z.number().nonnegative(),
|
||||
output_tokens: z.number().nonnegative(),
|
||||
cache_read_tokens: z.number().nonnegative().default(0),
|
||||
});
|
||||
const tool = z.object({
|
||||
name: z.string(),
|
||||
parameters: z.record(z.string(), z.json()).default({}),
|
||||
output: z.string().default(""),
|
||||
error: z.object({ type: z.string(), message: z.string() }).nullable().default(null),
|
||||
});
|
||||
const child = z.object({
|
||||
type_name: z.string(),
|
||||
role: z.string().default(""),
|
||||
initial_prompt: z.string().default(""),
|
||||
conversation_id: z.string().nullable().default(null),
|
||||
log_uri: z.string().default(""),
|
||||
workspace_uris: z.array(z.string()).default([]),
|
||||
});
|
||||
const frame = z.discriminatedUnion("event", [
|
||||
z.object({
|
||||
event: z.literal("init"),
|
||||
conversation_id: z.string().min(1),
|
||||
init: z.object({ model: z.string().nullable().default(null) }),
|
||||
}),
|
||||
z.object({
|
||||
event: z.literal("step_update"),
|
||||
step_update: z.object({
|
||||
conversation_id: z.string(),
|
||||
step_index: z.number().int().nonnegative(),
|
||||
state: z.enum(["ACTIVE", "DONE", "ERROR"]),
|
||||
step_type: z.string(),
|
||||
text_delta: z.string().default(""),
|
||||
tool_name: z.string().default(""),
|
||||
tool_info: tool.nullable().default(null),
|
||||
subagent_info: z
|
||||
.object({ subagents: z.array(child) })
|
||||
.nullable()
|
||||
.default(null),
|
||||
usage: usage.nullable().default(null),
|
||||
}),
|
||||
}),
|
||||
z.object({
|
||||
event: z.literal("result"),
|
||||
result: z.object({
|
||||
conversation_id: z.string(),
|
||||
status: z.enum(["SUCCESS", "ERROR"]),
|
||||
response: z.string().default(""),
|
||||
error: z.string().default("Antigravity turn failed"),
|
||||
denied_actions: z
|
||||
.array(z.object({ action: z.string(), display_name: z.string() }))
|
||||
.default([]),
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
export type Frame = z.infer<typeof frame>;
|
||||
export type Step = Extract<Frame, { event: "step_update" }>["step_update"];
|
||||
export type Init = Extract<Frame, { event: "init" }>;
|
||||
|
||||
export function decodeFrame(line: string): Frame {
|
||||
return frame.parse(JSON.parse(line));
|
||||
}
|
||||
|
||||
export function encodePrompt(text: string): string {
|
||||
return `${JSON.stringify({ event: "user", message: { content: text } })}\n`;
|
||||
}
|
||||
|
||||
export class AntigravityError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
readonly code = "ANTIGRAVITY_ERROR",
|
||||
) {
|
||||
super(message);
|
||||
this.name = "AntigravityError";
|
||||
}
|
||||
}
|
||||
|
||||
export function diagnostic(message: string): string {
|
||||
if (/auth|sign.in|log.in/i.test(message)) {
|
||||
return "Antigravity authentication failed. Run `agy` and sign in, then retry.";
|
||||
}
|
||||
return message;
|
||||
}
|
||||
@@ -1,717 +0,0 @@
|
||||
import { mkdtemp, readFile, rm, stat } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
PROVIDER_CAPABILITIES,
|
||||
ProviderEventSchema,
|
||||
type ProviderConnection,
|
||||
type ProviderContent,
|
||||
type ProviderEvent,
|
||||
type ProviderInput,
|
||||
type ProviderLaunch,
|
||||
type ProviderPersistence,
|
||||
} from "@getpaseo/plugin/server/provider";
|
||||
import { signalPlan } from "./internal/signals.js";
|
||||
import { createAntigravityProvider } from "./provider.js";
|
||||
|
||||
const temporary: string[] = [];
|
||||
const connections: ProviderConnection[] = [];
|
||||
const fake = fileURLToPath(new URL("../test/agy.mjs", import.meta.url));
|
||||
const provider = createAntigravityProvider();
|
||||
|
||||
it("publishes a monochrome, theme-aware provider icon", async () => {
|
||||
const icon = await readFile(new URL("../icon.svg", import.meta.url), "utf8");
|
||||
expect(icon).toContain('viewBox="0 0 24 24"');
|
||||
expect(icon).toContain('fill="currentColor"');
|
||||
expect(icon).not.toMatch(/#[\da-f]{3,8}\b|rgba?\(|hsla?\(|url\(/i);
|
||||
expect(icon).not.toMatch(/<(?:style|mask|defs|linearGradient|radialGradient)\b|\bid=/i);
|
||||
expect([...icon.matchAll(/\b(?:fill|stroke)="([^"]+)"/g)].map((match) => match[1])).toEqual([
|
||||
"currentColor",
|
||||
]);
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await Promise.all(connections.splice(0).map((connection) => connection.close()));
|
||||
await Promise.all(
|
||||
temporary.splice(0).map((directory) => rm(directory, { recursive: true, force: true })),
|
||||
);
|
||||
});
|
||||
|
||||
async function harness(env: Record<string, string> = {}) {
|
||||
const cwd = await mkdtemp(path.join(os.tmpdir(), "antigravity-test-"));
|
||||
temporary.push(cwd);
|
||||
const log = path.join(cwd, "argv.ndjson");
|
||||
const launch: ProviderLaunch = {
|
||||
command: process.execPath,
|
||||
args: [fake, "--launch-prefix"],
|
||||
env: { AGY_TEST_LOG: log, ...env },
|
||||
};
|
||||
const connection = await provider.connect({
|
||||
versions: [1],
|
||||
capabilities: PROVIDER_CAPABILITIES,
|
||||
launch,
|
||||
});
|
||||
connections.push(connection);
|
||||
const events: ProviderEvent[] = [];
|
||||
connection.onEvent((event) => {
|
||||
ProviderEventSchema.parse(event);
|
||||
events.push(event);
|
||||
});
|
||||
async function wait(predicate: (event: ProviderEvent) => boolean) {
|
||||
await expect.poll(() => events.some(predicate), { timeout: 5000, interval: 10 }).toBe(true);
|
||||
const event = events.find(predicate);
|
||||
if (!event) throw new Error("Expected event missing");
|
||||
return event;
|
||||
}
|
||||
async function request(input: ProviderInput) {
|
||||
await connection.send(input);
|
||||
if ("requestId" in input)
|
||||
await wait(
|
||||
(event) =>
|
||||
"requestId" in event &&
|
||||
event.requestId === input.requestId &&
|
||||
(event.type === "request.completed" ||
|
||||
event.type === "request.failed" ||
|
||||
event.type === "session.ready" ||
|
||||
event.type === "catalog"),
|
||||
);
|
||||
}
|
||||
async function open(restored?: ProviderPersistence, sessionId = "s") {
|
||||
await request({
|
||||
type: "session.open",
|
||||
requestId: `open-${events.length}`,
|
||||
sessionId,
|
||||
persistence: restored,
|
||||
history: "replay",
|
||||
config: {
|
||||
cwd,
|
||||
env: { AGY_SESSION_VALUE: "session-overlay" },
|
||||
settings: {},
|
||||
mcpServers: {},
|
||||
persist: true,
|
||||
systemPrompt: "SYSTEM PREFIX",
|
||||
},
|
||||
});
|
||||
}
|
||||
async function message(content: ProviderContent[], sessionId = "s") {
|
||||
const clientMessageId = `prompt-${events.length}`;
|
||||
await connection.send({
|
||||
type: "session.prompt",
|
||||
sessionId,
|
||||
prompt: {
|
||||
clientMessageId,
|
||||
delivery: "auto",
|
||||
input: { type: "message", content },
|
||||
},
|
||||
});
|
||||
await wait(
|
||||
(event) =>
|
||||
event.type === "session.prompt_result" && event.clientMessageId === clientMessageId,
|
||||
);
|
||||
return clientMessageId;
|
||||
}
|
||||
function prompt(text: string) {
|
||||
return message([{ type: "text", text }]);
|
||||
}
|
||||
async function completed(clientMessageId: string) {
|
||||
const result = events.find(
|
||||
(event) =>
|
||||
event.type === "session.prompt_result" && event.clientMessageId === clientMessageId,
|
||||
);
|
||||
if (!result || result.type !== "session.prompt_result" || result.result.type !== "turn")
|
||||
throw new Error("Prompt was not admitted");
|
||||
const turnId = result.result.turnId;
|
||||
return wait(
|
||||
(event) =>
|
||||
event.type === "session.turn" && event.turnId === turnId && event.state !== "started",
|
||||
);
|
||||
}
|
||||
async function records() {
|
||||
return (await readFile(log, "utf8"))
|
||||
.trim()
|
||||
.split("\n")
|
||||
.map((line) => JSON.parse(line));
|
||||
}
|
||||
function persistence() {
|
||||
const opened = events.find((event) => event.type === "session.opened");
|
||||
if (!opened || opened.type !== "session.opened" || !opened.persistence)
|
||||
throw new Error("Missing persistence");
|
||||
return opened.persistence;
|
||||
}
|
||||
return {
|
||||
connection,
|
||||
events,
|
||||
launch,
|
||||
cwd,
|
||||
wait,
|
||||
request,
|
||||
open,
|
||||
prompt,
|
||||
message,
|
||||
completed,
|
||||
records,
|
||||
persistence,
|
||||
};
|
||||
}
|
||||
|
||||
it("streams complete text snapshots, prefixes the system prompt only once and preserves launch argv/env", async () => {
|
||||
const h = await harness();
|
||||
expect(h.connection.capabilities).toEqual([
|
||||
"prompt.message",
|
||||
"prompt.image",
|
||||
"session.configure",
|
||||
"session.persistence",
|
||||
]);
|
||||
await h.open();
|
||||
await h.completed(await h.prompt("HELLO"));
|
||||
await h.completed(await h.prompt("HELLO AGAIN"));
|
||||
const text = h.events.filter(
|
||||
(event) => event.type === "timeline.item" && event.item.type === "assistant_message",
|
||||
);
|
||||
expect(
|
||||
text.map((event) =>
|
||||
event.type === "timeline.item" && event.item.type === "assistant_message"
|
||||
? event.item.text
|
||||
: "",
|
||||
),
|
||||
).toEqual(["PHASE0_HELLO", "PHASE0_HELLO\n", "PHASE0_HELLO", "PHASE0_HELLO\n"]);
|
||||
const records = await h.records();
|
||||
expect(records.find((entry) => entry.args.includes("--input-format")).args).toEqual([
|
||||
"--launch-prefix",
|
||||
"--input-format",
|
||||
"stream-json",
|
||||
"--output-format",
|
||||
"stream-json",
|
||||
"--add-dir",
|
||||
h.cwd,
|
||||
"--disable-slash-commands",
|
||||
]);
|
||||
expect(records.find((entry) => entry.args.includes("--input-format")).env).toBe(
|
||||
"session-overlay",
|
||||
);
|
||||
expect(
|
||||
records.filter((entry) => entry.input).map((entry) => entry.input.message.content),
|
||||
).toEqual(["SYSTEM PREFIX\n\nHELLO", "HELLO AGAIN"]);
|
||||
});
|
||||
|
||||
it("maps tools and emits one denial notice for a SUCCESS result", async () => {
|
||||
const h = await harness();
|
||||
await h.open();
|
||||
// This capture contains three distinct native results, one for each submitted prompt.
|
||||
await h.completed(await h.prompt("TOOLS"));
|
||||
await h.completed(await h.prompt("TOOLS"));
|
||||
const result = await h.completed(await h.prompt("TOOLS"));
|
||||
expect(result).toMatchObject({ type: "session.turn", state: "completed" });
|
||||
const tools = h.events.filter(
|
||||
(event) => event.type === "timeline.item" && event.item.type === "tool_call",
|
||||
);
|
||||
expect(tools).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
item: expect.objectContaining({
|
||||
name: "view_file",
|
||||
status: "completed",
|
||||
detail: expect.objectContaining({ type: "read" }),
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
item: expect.objectContaining({
|
||||
name: "write_to_file",
|
||||
status: "completed",
|
||||
detail: expect.objectContaining({ type: "write" }),
|
||||
}),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
item: expect.objectContaining({
|
||||
name: "run_command",
|
||||
status: "failed",
|
||||
detail: expect.objectContaining({ type: "shell", command: "printf PHASE0_SHELL" }),
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
const notices = h.events.filter((event) => event.type === "session.notice");
|
||||
expect(notices).toHaveLength(1);
|
||||
expect(notices[0]).toMatchObject({
|
||||
notice: {
|
||||
title: "Shell command denied",
|
||||
description: "Switch to Full access to allow shell commands.",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("reports only new process denials across replayed turns and resets after respawn", async () => {
|
||||
const h = await harness();
|
||||
await h.open();
|
||||
await h.completed(await h.prompt("DENIAL_REPLAY"));
|
||||
expect(h.events.filter((event) => event.type === "session.notice")).toHaveLength(1);
|
||||
const followupStart = h.events.length;
|
||||
await h.completed(await h.prompt("DENIAL_REPLAY"));
|
||||
const followup = h.events.slice(followupStart);
|
||||
expect(followup.some((event) => event.type === "session.notice")).toBe(false);
|
||||
expect(followup).toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: "timeline.item",
|
||||
item: expect.objectContaining({ type: "assistant_message", text: "FOLLOWUP_OK\n" }),
|
||||
}),
|
||||
);
|
||||
await h.request({
|
||||
type: "session.configure",
|
||||
sessionId: "s",
|
||||
requestId: "mode",
|
||||
changes: { mode: "plan" },
|
||||
});
|
||||
await h.completed(await h.prompt("DENIAL_REPLAY"));
|
||||
expect(h.events.filter((event) => event.type === "session.notice")).toHaveLength(2);
|
||||
expect(
|
||||
(await h.records()).filter((entry) => entry.args?.includes("--input-format")),
|
||||
).toHaveLength(2);
|
||||
});
|
||||
|
||||
it("counts repeated native action denials without exposing tool identifiers", async () => {
|
||||
const h = await harness({ AGY_TEST_DENIAL_COUNTS: "2,2,3" });
|
||||
await h.open();
|
||||
await h.completed(await h.prompt("DENIAL_REPLAY"));
|
||||
await h.completed(await h.prompt("DENIAL_REPLAY"));
|
||||
const notices = h.events.filter((event) => event.type === "session.notice");
|
||||
expect(notices).toHaveLength(1);
|
||||
expect(notices[0]).toMatchObject({
|
||||
notice: {
|
||||
title: "2 actions denied",
|
||||
description: "Switch to Full access to allow shell commands.",
|
||||
},
|
||||
});
|
||||
await h.completed(await h.prompt("DENIAL_REPLAY"));
|
||||
expect(h.events.filter((event) => event.type === "session.notice")).toHaveLength(2);
|
||||
expect(h.events.findLast((event) => event.type === "session.notice")).toMatchObject({
|
||||
notice: { title: "Shell command denied" },
|
||||
});
|
||||
});
|
||||
|
||||
it("accounts DONE generation usage once without adding cumulative results, including resume", async () => {
|
||||
const h = await harness();
|
||||
await h.open();
|
||||
await h.completed(await h.prompt("MULTI"));
|
||||
await h.completed(await h.prompt("MULTI"));
|
||||
const usages = h.events.filter((event) => event.type === "session.usage");
|
||||
expect(
|
||||
usages.map((event) => (event.type === "session.usage" ? event.usage.inputTokens : null)),
|
||||
).toEqual([11263, 11604]);
|
||||
const persistence = h.persistence();
|
||||
await h.request({ type: "session.close", sessionId: "s", requestId: "close" });
|
||||
await h.open(persistence);
|
||||
await h.completed(await h.prompt("RESUME"));
|
||||
expect(h.events.filter((event) => event.type === "session.usage")).toHaveLength(3);
|
||||
expect(h.events.findLast((event) => event.type === "session.usage")).toMatchObject({
|
||||
usage: { inputTokens: 11815 },
|
||||
});
|
||||
});
|
||||
|
||||
it("maps subagent steps to a tool carrying the child conversation ID", async () => {
|
||||
const h = await harness();
|
||||
await h.open();
|
||||
await h.completed(await h.prompt("SUBAGENT"));
|
||||
expect(h.events).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: "timeline.item",
|
||||
item: expect.objectContaining({
|
||||
name: "invoke_subagent",
|
||||
status: "completed",
|
||||
detail: expect.objectContaining({
|
||||
type: "sub_agent",
|
||||
childSessionId: "b6f02329-7415-49a7-a9a9-1cd09e898d26",
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it.each(["HANG", "TOOL_HANG"])(
|
||||
"interrupts %s, settles canceled, and respawns the same conversation",
|
||||
async (text) => {
|
||||
const h = await harness();
|
||||
await h.open();
|
||||
const message = await h.prompt(text);
|
||||
await h.wait(
|
||||
(event) =>
|
||||
event.type === "timeline.item" &&
|
||||
(event.item.type === "assistant_message" || event.item.type === "tool_call"),
|
||||
);
|
||||
await h.request({ type: "session.interrupt", requestId: "interrupt", sessionId: "s" });
|
||||
expect(await h.completed(message)).toMatchObject({ state: "canceled" });
|
||||
await h.completed(await h.prompt("HELLO"));
|
||||
const records = await h.records();
|
||||
const launches = records.filter((entry) => entry.args && entry.args.includes("--input-format"));
|
||||
expect(launches).toHaveLength(2);
|
||||
expect(launches[1].args.slice(-2)).toEqual([
|
||||
"--conversation",
|
||||
"e5cf1e2d-c715-4328-8e67-4a85c8dc3cda",
|
||||
]);
|
||||
// Windows taskkill terminates the tree without invoking the CLI's signal handlers.
|
||||
expect(records.filter((entry) => entry.signal)).toEqual(
|
||||
process.platform === "win32" ? [] : [{ signal: "SIGINT" }],
|
||||
);
|
||||
const canceled = h.events.filter(
|
||||
(event) => event.type === "session.turn" && event.state === "canceled",
|
||||
);
|
||||
expect(canceled).toHaveLength(1);
|
||||
},
|
||||
);
|
||||
|
||||
it("stops an unresponsive driver and still respawns the conversation", async () => {
|
||||
const h = await harness({ AGY_TEST_IGNORE_SIGNALS: "yes" });
|
||||
await h.open();
|
||||
const message = await h.prompt("HANG");
|
||||
await h.wait(
|
||||
(event) => event.type === "timeline.item" && event.item.type === "assistant_message",
|
||||
);
|
||||
await h.request({ type: "session.interrupt", requestId: "interrupt-timeout", sessionId: "s" });
|
||||
expect(await h.completed(message)).toMatchObject({ state: "canceled" });
|
||||
expect((await h.records()).filter((entry) => entry.signal)).toEqual(
|
||||
process.platform === "win32" ? [] : [{ signal: "SIGINT" }, { signal: "SIGTERM" }],
|
||||
);
|
||||
await h.completed(await h.prompt("HELLO"));
|
||||
});
|
||||
|
||||
it.each(["accept-edits", "plan"])(
|
||||
"launches native %s mode without claiming read-only access",
|
||||
async (mode) => {
|
||||
const h = await harness();
|
||||
await h.open();
|
||||
await h.request({
|
||||
type: "session.configure",
|
||||
requestId: "mode",
|
||||
sessionId: "s",
|
||||
changes: { mode },
|
||||
});
|
||||
await h.completed(await h.prompt("HELLO"));
|
||||
const launches = (await h.records()).filter(
|
||||
(entry) => entry.args && entry.args.includes("--input-format"),
|
||||
);
|
||||
expect(launches[1].args).toContain("--mode");
|
||||
expect(launches[1].args[launches[1].args.indexOf("--mode") + 1]).toBe(mode);
|
||||
},
|
||||
);
|
||||
|
||||
it("defers model and mode changes until the next prompt, then respawns with validated flags", async () => {
|
||||
const h = await harness();
|
||||
await h.open();
|
||||
await h.request({
|
||||
type: "session.configure",
|
||||
requestId: "change",
|
||||
sessionId: "s",
|
||||
changes: { model: "gemini-3.8-flash-low", mode: "full-access" },
|
||||
});
|
||||
expect(
|
||||
(await h.records()).filter((entry) => entry.args && entry.args.includes("--input-format")),
|
||||
).toHaveLength(1);
|
||||
await h.completed(await h.prompt("HELLO"));
|
||||
const launches = (await h.records()).filter(
|
||||
(entry) => entry.args && entry.args.includes("--input-format"),
|
||||
);
|
||||
expect(launches).toHaveLength(2);
|
||||
expect(launches[1].args.slice(-5)).toEqual([
|
||||
"--model",
|
||||
"gemini-3.8-flash-low",
|
||||
"--dangerously-skip-permissions",
|
||||
"--conversation",
|
||||
"e5cf1e2d-c715-4328-8e67-4a85c8dc3cda",
|
||||
]);
|
||||
await h.request({
|
||||
type: "session.configure",
|
||||
requestId: "invalid",
|
||||
sessionId: "s",
|
||||
changes: { model: "unknown" },
|
||||
});
|
||||
expect(h.events).toContainEqual(
|
||||
expect.objectContaining({
|
||||
type: "request.failed",
|
||||
requestId: "invalid",
|
||||
error: expect.objectContaining({ code: "INVALID_MODEL" }),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("resumes after connection close without replay or another system prefix", async () => {
|
||||
const first = await harness();
|
||||
await first.open();
|
||||
await first.completed(await first.prompt("HELLO"));
|
||||
const persistence = first.persistence();
|
||||
await first.connection.close();
|
||||
const second = await harness();
|
||||
await second.open(persistence);
|
||||
expect(second.events.filter((event) => event.type === "timeline.item")).toEqual([]);
|
||||
await second.completed(await second.prompt("RESUME"));
|
||||
expect(
|
||||
(await second.records())
|
||||
.filter((entry) => entry.input)
|
||||
.map((entry) => entry.input.message.content),
|
||||
).toEqual(["RESUME"]);
|
||||
});
|
||||
|
||||
it.each(["auth", "exit", "stderr"])(
|
||||
"reports startup %s without waiting for init forever",
|
||||
async (startup) => {
|
||||
const h = await harness({ AGY_TEST_STARTUP: startup });
|
||||
await h.open();
|
||||
expect(h.events.filter((event) => event.type === "session.ready")).toEqual([]);
|
||||
const failure = h.events.find((event) => event.type === "request.failed");
|
||||
expect(failure).toMatchObject({
|
||||
type: "request.failed",
|
||||
error: {
|
||||
message: expect.stringMatching(startup === "exit" ? /exited/ : /Run `agy` and sign in/),
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects mid-turn steering and reports a busy message without queueing a second native turn", async () => {
|
||||
const h = await harness();
|
||||
await h.open();
|
||||
await h.prompt("HANG");
|
||||
const second = await h.prompt("SECOND");
|
||||
expect(
|
||||
h.events.find(
|
||||
(event) => event.type === "session.prompt_result" && event.clientMessageId === second,
|
||||
),
|
||||
).toMatchObject({
|
||||
type: "session.prompt_result",
|
||||
result: { type: "failed", error: { code: "TURN_ACTIVE" } },
|
||||
});
|
||||
await expect(
|
||||
h.connection.send({
|
||||
type: "session.prompt",
|
||||
sessionId: "s",
|
||||
prompt: {
|
||||
clientMessageId: "steer",
|
||||
delivery: "steer",
|
||||
input: { type: "message", content: [{ type: "text", text: "x" }] },
|
||||
},
|
||||
}),
|
||||
).rejects.toThrow(/prompt.steer/);
|
||||
});
|
||||
|
||||
describe("discovery", () => {
|
||||
it("reuses one discovered catalog for two opens and configure, and refreshes explicit catalog requests", async () => {
|
||||
const h = await harness();
|
||||
await h.open();
|
||||
await h.open(undefined, "second");
|
||||
await h.request({
|
||||
type: "session.configure",
|
||||
requestId: "configure-cached",
|
||||
sessionId: "second",
|
||||
changes: { model: "gemini-3.8-flash-low" },
|
||||
});
|
||||
expect(
|
||||
(await h.records()).filter((entry) => entry.args && entry.args.includes("models")),
|
||||
).toHaveLength(1);
|
||||
await h.request({ type: "catalog", requestId: "refresh" });
|
||||
expect(
|
||||
(await h.records()).filter((entry) => entry.args && entry.args.includes("models")),
|
||||
).toHaveLength(2);
|
||||
});
|
||||
it.each(["1.1.15", "1.2.12", "1.2.13"])("accepts driver-capable version %s", async (version) => {
|
||||
const h = await harness({ AGY_TEST_VERSION: version });
|
||||
expect(await provider.status?.({ launch: h.launch })).toEqual({ available: true });
|
||||
});
|
||||
|
||||
it("parses tab-separated catalog and exposes native permission choices without thinking options", async () => {
|
||||
const h = await harness();
|
||||
await h.request({ type: "catalog", requestId: "catalog" });
|
||||
const event = h.events[0];
|
||||
expect(event).toMatchObject({
|
||||
type: "catalog",
|
||||
catalog: {
|
||||
models: expect.arrayContaining([
|
||||
{ id: "gemini-3.8-flash-low", label: "Gemini 3.8 Flash (Low)" },
|
||||
]),
|
||||
thinkingOptions: [],
|
||||
defaultMode: "default",
|
||||
},
|
||||
});
|
||||
if (event.type !== "catalog") throw new Error("Missing catalog");
|
||||
expect(event.catalog.models).toHaveLength(11);
|
||||
expect(event.catalog.modes.map((mode) => mode.label)).toEqual([
|
||||
"Default",
|
||||
"Accept edits",
|
||||
"Plan",
|
||||
"Full access",
|
||||
]);
|
||||
expect(event.catalog.modes.map((mode) => mode.id)).toEqual([
|
||||
"default",
|
||||
"accept-edits",
|
||||
"plan",
|
||||
"full-access",
|
||||
]);
|
||||
expect(
|
||||
event.catalog.modes.map(({ id, icon, colorTier, isUnattended }) => ({
|
||||
id,
|
||||
icon,
|
||||
colorTier,
|
||||
isUnattended,
|
||||
})),
|
||||
).toEqual([
|
||||
{ id: "default", icon: "Shield", colorTier: "moderate", isUnattended: undefined },
|
||||
{ id: "accept-edits", icon: "ShieldPlus", colorTier: "moderate", isUnattended: undefined },
|
||||
{ id: "plan", icon: "ShieldEllipsis", colorTier: "planning", isUnattended: undefined },
|
||||
{ id: "full-access", icon: "ShieldOff", colorTier: "dangerous", isUnattended: true },
|
||||
]);
|
||||
});
|
||||
it("reports missing, old, unauthenticated and available launches", async () => {
|
||||
expect(await provider.status?.({})).toMatchObject({
|
||||
available: false,
|
||||
diagnostic: expect.stringContaining("agy not found"),
|
||||
});
|
||||
const missing = await harness();
|
||||
expect(
|
||||
await provider.status?.({
|
||||
launch: { ...missing.launch, command: path.join(missing.cwd, "missing-agy") },
|
||||
}),
|
||||
).toMatchObject({ available: false, diagnostic: expect.stringContaining("ENOENT") });
|
||||
const old = await harness({ AGY_TEST_VERSION: "1.1.14" });
|
||||
expect(await provider.status?.({ launch: old.launch })).toMatchObject({
|
||||
available: false,
|
||||
diagnostic: expect.stringContaining("1.1.15"),
|
||||
});
|
||||
const unauthenticated = await harness({ AGY_TEST_AUTH: "missing" });
|
||||
expect(await provider.status?.({ launch: unauthenticated.launch })).toEqual({
|
||||
available: false,
|
||||
diagnostic: "Antigravity authentication failed. Run `agy` and sign in, then retry.",
|
||||
});
|
||||
const authenticated = await harness();
|
||||
expect(await provider.status?.({ launch: authenticated.launch })).toEqual({ available: true });
|
||||
});
|
||||
it("keys catalog caching by the complete effective launch without exposing env values", async () => {
|
||||
const h = await harness();
|
||||
const key = await provider.getCatalogCacheKey?.({ scope: "global", launch: h.launch });
|
||||
expect(key).toMatch(/^[a-f0-9]{64}$/);
|
||||
expect(
|
||||
await provider.getCatalogCacheKey?.({
|
||||
scope: "workspace",
|
||||
cwd: h.cwd,
|
||||
force: true,
|
||||
launch: h.launch,
|
||||
}),
|
||||
).toBe(key);
|
||||
expect(
|
||||
await provider.getCatalogCacheKey?.({
|
||||
scope: "global",
|
||||
launch: { ...h.launch, args: [...h.launch.args, "changed"] },
|
||||
}),
|
||||
).not.toBe(key);
|
||||
expect(
|
||||
await provider.getCatalogCacheKey?.({
|
||||
scope: "global",
|
||||
launch: { ...h.launch, env: { ...h.launch.env, HOME: "different" } },
|
||||
}),
|
||||
).not.toBe(key);
|
||||
});
|
||||
});
|
||||
|
||||
describe("process signal plans", () => {
|
||||
it.each(["SIGINT", "SIGTERM", "SIGKILL"] as const)("kills the Windows tree for %s", (signal) => {
|
||||
expect(signalPlan({ platform: "win32", pid: 123, signal })).toEqual({
|
||||
type: "tree",
|
||||
command: "taskkill",
|
||||
args: ["/PID", "123", "/T", "/F"],
|
||||
});
|
||||
});
|
||||
it.each(["SIGINT", "SIGTERM", "SIGKILL"] as const)(
|
||||
"preserves POSIX group signal %s",
|
||||
(signal) => {
|
||||
expect(signalPlan({ platform: "linux", pid: 123, signal })).toEqual({
|
||||
type: "group",
|
||||
pid: -123,
|
||||
signal,
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("image prompts", () => {
|
||||
it.each(["session", "connection"])(
|
||||
"encodes mixed content as private session files and cleans up on %s close",
|
||||
async (close) => {
|
||||
const h = await harness();
|
||||
await h.open();
|
||||
const png = await readFile(
|
||||
new URL("../test/fixtures/image-file-external.png", import.meta.url),
|
||||
);
|
||||
const jpeg = Buffer.from("image file bytes");
|
||||
await h.completed(
|
||||
await h.message([
|
||||
{ type: "text", text: "Describe these images." },
|
||||
{ type: "image", data: png.toString("base64"), mimeType: "image/png" },
|
||||
{ type: "text", text: "Compare them." },
|
||||
{ type: "image", data: jpeg.toString("base64"), mimeType: "image/jpeg" },
|
||||
]),
|
||||
);
|
||||
const sent = (await h.records()).find((entry) => entry.input).input.message.content;
|
||||
const lines = sent.split("\n");
|
||||
const pngPath = lines[3].slice("Attached image: ".length);
|
||||
const jpegPath = lines[5].slice("Attached image: ".length);
|
||||
expect(sent).toBe(
|
||||
`SYSTEM PREFIX\n\nDescribe these images.\nAttached image: ${pngPath}\nCompare them.\nAttached image: ${jpegPath}`,
|
||||
);
|
||||
expect(path.isAbsolute(pngPath)).toBe(true);
|
||||
expect(path.extname(pngPath)).toBe(".png");
|
||||
expect(path.extname(jpegPath)).toBe(".jpg");
|
||||
expect(path.dirname(pngPath)).toBe(path.dirname(jpegPath));
|
||||
expect(path.dirname(pngPath)).not.toBe(h.cwd);
|
||||
expect(await readFile(pngPath)).toEqual(png);
|
||||
expect(await readFile(jpegPath)).toEqual(jpeg);
|
||||
if (process.platform !== "win32") {
|
||||
expect((await stat(path.dirname(pngPath))).mode & 0o777).toBe(0o700);
|
||||
expect((await stat(pngPath)).mode & 0o777).toBe(0o600);
|
||||
expect((await stat(jpegPath)).mode & 0o777).toBe(0o600);
|
||||
}
|
||||
const args = (await h.records()).find((entry) => entry.args?.includes("--input-format")).args;
|
||||
expect(args.filter((arg: string) => arg === "--add-dir")).toHaveLength(1);
|
||||
if (close === "connection") await h.connection.close();
|
||||
else await h.request({ type: "session.close", requestId: "close-images", sessionId: "s" });
|
||||
await expect(stat(path.dirname(pngPath))).rejects.toMatchObject({ code: "ENOENT" });
|
||||
},
|
||||
);
|
||||
|
||||
it("keeps image-only prompts in distinct session directories until each session closes", async () => {
|
||||
const h = await harness();
|
||||
await h.open();
|
||||
await h.open(undefined, "other");
|
||||
const content: ProviderContent[] = [
|
||||
{ type: "image", data: "aW1hZ2U=", mimeType: "image/webp" },
|
||||
];
|
||||
await h.completed(await h.message(content));
|
||||
await h.completed(await h.message(content));
|
||||
await h.completed(await h.message(content, "other"));
|
||||
const paths = (await h.records())
|
||||
.filter((entry) => entry.input)
|
||||
.map((entry) => entry.input.message.content.split("Attached image: ")[1]);
|
||||
expect(new Set(paths).size).toBe(3);
|
||||
expect(path.dirname(paths[0])).toBe(path.dirname(paths[1]));
|
||||
expect(path.dirname(paths[0])).not.toBe(path.dirname(paths[2]));
|
||||
expect(paths.every((file: string) => path.extname(file) === ".webp")).toBe(true);
|
||||
await h.request({ type: "session.close", requestId: "close-first", sessionId: "s" });
|
||||
await expect(stat(path.dirname(paths[0]))).rejects.toMatchObject({ code: "ENOENT" });
|
||||
expect(await readFile(paths[2], "utf8")).toBe("image");
|
||||
await h.connection.close();
|
||||
await expect(stat(path.dirname(paths[2]))).rejects.toMatchObject({ code: "ENOENT" });
|
||||
});
|
||||
|
||||
it("rejects a non-image MIME type before writing to native stdin", async () => {
|
||||
const h = await harness();
|
||||
await h.open();
|
||||
const id = await h.message([{ type: "image", data: "dGV4dA==", mimeType: "text/plain" }]);
|
||||
expect(
|
||||
h.events.find(
|
||||
(event) => event.type === "session.prompt_result" && event.clientMessageId === id,
|
||||
),
|
||||
).toMatchObject({
|
||||
result: { type: "failed", error: { message: expect.stringContaining("image MIME type") } },
|
||||
});
|
||||
expect((await h.records()).filter((entry) => entry.input)).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
@@ -1,171 +0,0 @@
|
||||
import { createHash } from "node:crypto";
|
||||
import {
|
||||
negotiateProviderCapabilities,
|
||||
requireProviderCapabilities,
|
||||
type ProviderCatalog,
|
||||
type ProviderConnection,
|
||||
type ProviderEvent,
|
||||
type ProviderInput,
|
||||
type ProviderLaunch,
|
||||
type ProviderRegistration,
|
||||
} from "@getpaseo/plugin/server/provider";
|
||||
import { getCatalog, getStatus } from "./internal/catalog.js";
|
||||
import { Session } from "./internal/session.js";
|
||||
import { AntigravityError } from "./internal/wire.js";
|
||||
|
||||
const capabilities = [
|
||||
"prompt.message",
|
||||
"prompt.image",
|
||||
"session.configure",
|
||||
"session.persistence",
|
||||
] as const;
|
||||
|
||||
export function createAntigravityProvider(): ProviderRegistration {
|
||||
return {
|
||||
id: "antigravity",
|
||||
label: "Antigravity",
|
||||
icon: "icon.svg",
|
||||
command: ["agy"],
|
||||
description: "Antigravity CLI on your host",
|
||||
async status({ launch }) {
|
||||
if (!launch)
|
||||
return {
|
||||
available: false,
|
||||
diagnostic: "agy not found on PATH. Install Antigravity, then run `agy` and sign in.",
|
||||
};
|
||||
return getStatus(launch);
|
||||
},
|
||||
async getCatalogCacheKey({ launch }) {
|
||||
if (!launch) return undefined;
|
||||
const env = Object.entries(launch.env).sort(([left], [right]) => left.localeCompare(right));
|
||||
return createHash("sha256")
|
||||
.update(JSON.stringify([launch.command, launch.args, env]))
|
||||
.digest("hex");
|
||||
},
|
||||
async connect(request) {
|
||||
if (!request.versions.includes(1))
|
||||
throw new AntigravityError("Provider protocol version 1 is required");
|
||||
if (!request.launch)
|
||||
throw new AntigravityError("The daemon must supply the Antigravity launch");
|
||||
const negotiated = negotiateProviderCapabilities(request.capabilities, capabilities);
|
||||
return createConnection(request.launch, negotiated);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createConnection(
|
||||
launch: ProviderLaunch,
|
||||
negotiated: readonly string[],
|
||||
): ProviderConnection {
|
||||
const sessions = new Map<string, Session>();
|
||||
const listeners = new Set<(event: ProviderEvent) => void>();
|
||||
let discovered: ProviderCatalog | null = null;
|
||||
let closed = false;
|
||||
let pending = Promise.resolve();
|
||||
|
||||
function emit(event: ProviderEvent): void {
|
||||
if (closed) return;
|
||||
for (const listener of listeners) listener(event);
|
||||
}
|
||||
function catalog(): ProviderCatalog {
|
||||
if (discovered === null)
|
||||
throw new AntigravityError("Antigravity catalog has not been discovered");
|
||||
return discovered;
|
||||
}
|
||||
function session(id: string): Session {
|
||||
const found = sessions.get(id);
|
||||
if (!found) throw new AntigravityError(`Unknown Antigravity session: ${id}`);
|
||||
return found;
|
||||
}
|
||||
async function dispatch(input: ProviderInput): Promise<void> {
|
||||
switch (input.type) {
|
||||
case "catalog":
|
||||
discovered = await getCatalog(launch, input.cwd);
|
||||
emit({
|
||||
type: "catalog",
|
||||
requestId: input.requestId,
|
||||
catalog: catalog(),
|
||||
});
|
||||
return;
|
||||
case "session.open": {
|
||||
if (sessions.has(input.sessionId))
|
||||
throw new AntigravityError(`Session already exists: ${input.sessionId}`);
|
||||
if (discovered === null) discovered = await getCatalog(launch, input.config.cwd);
|
||||
const opened = new Session({
|
||||
id: input.sessionId,
|
||||
config: input.config,
|
||||
persistence: input.persistence,
|
||||
catalog,
|
||||
launch,
|
||||
emit,
|
||||
});
|
||||
sessions.set(input.sessionId, opened);
|
||||
try {
|
||||
await opened.open(input.requestId, negotiated);
|
||||
} catch (error) {
|
||||
sessions.delete(input.sessionId);
|
||||
await opened.close();
|
||||
throw error;
|
||||
}
|
||||
return;
|
||||
}
|
||||
case "session.prompt":
|
||||
await session(input.sessionId).prompt(input.prompt);
|
||||
return;
|
||||
case "session.configure":
|
||||
session(input.sessionId).configure(input.changes);
|
||||
break;
|
||||
case "session.interrupt":
|
||||
await session(input.sessionId).interrupt();
|
||||
break;
|
||||
case "session.close": {
|
||||
await session(input.sessionId).close();
|
||||
sessions.delete(input.sessionId);
|
||||
emit({ type: "session.closed", sessionId: input.sessionId });
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw new AntigravityError(`Unsupported Antigravity operation: ${input.type}`);
|
||||
}
|
||||
emit({ type: "request.completed", requestId: input.requestId });
|
||||
}
|
||||
function failed(input: ProviderInput, error: unknown): void {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
const code = error instanceof AntigravityError ? error.code : "ANTIGRAVITY_ERROR";
|
||||
if (input.type === "session.prompt")
|
||||
emit({
|
||||
type: "session.prompt_result",
|
||||
sessionId: input.sessionId,
|
||||
clientMessageId: input.prompt.clientMessageId,
|
||||
result: { type: "failed", error: { message, code } },
|
||||
});
|
||||
else if ("requestId" in input)
|
||||
emit({ type: "request.failed", requestId: input.requestId, error: { message, code } });
|
||||
}
|
||||
return {
|
||||
version: 1,
|
||||
capabilities: negotiated,
|
||||
async send(input) {
|
||||
if (closed) throw new AntigravityError("Antigravity connection is closed");
|
||||
requireProviderCapabilities(negotiated, input);
|
||||
pending = pending
|
||||
.then(async () => {
|
||||
if (!closed) await dispatch(input);
|
||||
return undefined;
|
||||
})
|
||||
.catch((error) => failed(input, error));
|
||||
},
|
||||
onEvent(listener) {
|
||||
listeners.add(listener);
|
||||
return () => listeners.delete(listener);
|
||||
},
|
||||
async close() {
|
||||
if (closed) return;
|
||||
closed = true;
|
||||
await pending;
|
||||
await Promise.all([...sessions.values()].map((opened) => opened.close()));
|
||||
sessions.clear();
|
||||
listeners.clear();
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
import { readFileSync, appendFileSync } from "node:fs";
|
||||
import { createInterface } from "node:readline";
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const log = process.env.AGY_TEST_LOG;
|
||||
function record(value) {
|
||||
if (log) appendFileSync(log, `${JSON.stringify(value)}\n`);
|
||||
}
|
||||
record({ args, pid: process.pid, env: process.env.AGY_SESSION_VALUE });
|
||||
function fixture(name, stream = "stdout") {
|
||||
return readFileSync(new URL(`./fixtures/${name}.${stream}.ndjson`, import.meta.url), "utf8")
|
||||
.trim()
|
||||
.split("\n")
|
||||
.filter(Boolean)
|
||||
.map((line) => JSON.parse(line).data);
|
||||
}
|
||||
if (args.includes("--version")) {
|
||||
process.stdout.write(`${process.env.AGY_TEST_VERSION || "1.2.13"}\n`);
|
||||
process.exit(0);
|
||||
}
|
||||
if (args.includes("models")) {
|
||||
if (process.env.AGY_TEST_AUTH === "missing") {
|
||||
process.stderr.write(fixture("models-unauthenticated", "stderr").join("\n"));
|
||||
process.exit(1);
|
||||
}
|
||||
process.stdout.write(fixture("models-api-key").join("\n") + "\n");
|
||||
process.exit(0);
|
||||
}
|
||||
if (process.env.AGY_TEST_STARTUP === "auth") {
|
||||
process.stderr.write(fixture("not-signed-in", "stderr").join("\n"));
|
||||
process.stdout.write(JSON.stringify(fixture("not-signed-in")[0]) + "\n");
|
||||
process.exit(1);
|
||||
}
|
||||
if (process.env.AGY_TEST_STARTUP === "exit") process.exit(3);
|
||||
if (process.env.AGY_TEST_STARTUP === "stderr") {
|
||||
process.stderr.write("Error: authentication required. Run 'agy' to log in, then retry.\n");
|
||||
process.exit(1);
|
||||
}
|
||||
const resumeIndex = args.indexOf("--conversation");
|
||||
const conversationId =
|
||||
resumeIndex >= 0 ? args[resumeIndex + 1] : fixture("text-turn")[0].conversation_id;
|
||||
let denialResults = 0;
|
||||
function emit(frame) {
|
||||
const converted = structuredClone(frame);
|
||||
if (converted.conversation_id !== undefined) converted.conversation_id = conversationId;
|
||||
if (converted.step_update) converted.step_update.conversation_id = conversationId;
|
||||
if (converted.result) {
|
||||
converted.result.conversation_id = conversationId;
|
||||
if (process.env.AGY_TEST_DENIAL_COUNTS && converted.result.denied_actions?.length) {
|
||||
converted.result.denied_actions = Array.from(
|
||||
{ length: Number(process.env.AGY_TEST_DENIAL_COUNTS.split(",")[denialResults++]) },
|
||||
() => converted.result.denied_actions[0],
|
||||
);
|
||||
}
|
||||
}
|
||||
process.stdout.write(JSON.stringify(converted) + "\n");
|
||||
}
|
||||
const init = fixture("text-turn")[0];
|
||||
if (args.includes("--model")) init.init.model = args[args.indexOf("--model") + 1];
|
||||
emit(init);
|
||||
let interrupted = null;
|
||||
const turns = new Map();
|
||||
const lines = createInterface({ input: process.stdin });
|
||||
lines.on("line", (line) => {
|
||||
const input = JSON.parse(line);
|
||||
record({ input });
|
||||
const text = input.message.content;
|
||||
let name = "text-turn";
|
||||
if (text.includes("Attached image: ")) name = "image-file-external";
|
||||
if (text.includes("TOOLS")) name = "tools-default";
|
||||
if (text.includes("DENIAL_REPLAY")) name = "denial-replay";
|
||||
if (text.includes("SUBAGENT")) name = "subagent";
|
||||
if (text.includes("MULTI")) name = "multi-turn-reasoning";
|
||||
if (text.includes("RESUME")) name = "resume";
|
||||
if (text.includes("HANG")) name = "sigint-mid-text";
|
||||
if (text.includes("TOOL_HANG")) name = "sigint-mid-tool";
|
||||
const frames = fixture(name).slice(1);
|
||||
if (name.startsWith("sigint")) {
|
||||
interrupted = frames[frames.length - 1];
|
||||
for (const frame of frames.slice(0, -1)) emit(frame);
|
||||
return;
|
||||
}
|
||||
const count = frames.filter((frame) => frame.event === "result").length;
|
||||
const ordinal = (turns.get(name) || 0) % count;
|
||||
turns.set(name, ordinal + 1);
|
||||
let resultCount = 0;
|
||||
for (const frame of frames) {
|
||||
if (resultCount === ordinal) emit(frame);
|
||||
if (frame.event === "result") resultCount += 1;
|
||||
}
|
||||
});
|
||||
process.on("SIGINT", () => {
|
||||
record({ signal: "SIGINT" });
|
||||
if (process.env.AGY_TEST_IGNORE_SIGNALS === "yes") return;
|
||||
if (interrupted) emit(interrupted);
|
||||
process.exit(1);
|
||||
});
|
||||
process.on("SIGTERM", () => {
|
||||
record({ signal: "SIGTERM" });
|
||||
if (process.env.AGY_TEST_IGNORE_SIGNALS !== "yes") process.exit(1);
|
||||
});
|
||||
lines.on("close", () => process.exit(0));
|
||||
@@ -1,100 +0,0 @@
|
||||
# agy 1.2.13 stream-json captures
|
||||
|
||||
`denial-replay.stdout.ndjson` replays two turns from one real 1.2.13 process: a denied shell
|
||||
command, then a text-only answer whose result retains the first denial. The replay clock is
|
||||
synthetic; frames retain the native cumulative denial payload.
|
||||
|
||||
Captured on Linux x86_64 on 2026-09-29. These are observations from the supplied binary, not guarantees for the Google sign-in route. No product code or capture drivers are included.
|
||||
|
||||
Each row names a capture prefix. Its `.stdin.ndjson`, `.stdout.ndjson`, `.stderr.ndjson`, and `.capture.ndjson` companions keep the streams separate; empty streams are empty files. Every line wraps the exact decoded JSON frame or text line in `{t_ms, stream, data}`. `t_ms` is milliseconds since construction immediately before spawn. Capture annotations record launch arguments, configuration, signals, EOF, timeouts, and exit code; they are not CLI output. Line references refer to the indicated file, starting at 1.
|
||||
|
||||
Commands below run from `<WORKSPACE>`. `<AGY>` expands to `<HOME>/.local/opt/agy/1.2.13/agy` in the real installation home; the child uses a separate isolated `<HOME>` for state. All captures remove inherited credential/provider environment variables, set `HOME`, `XDG_CONFIG_HOME=<HOME>/.config`, `XDG_CACHE_HOME=<HOME>/.cache`, `XDG_DATA_HOME=<HOME>/.local/share`, `BROWSER=/bin/true`, and loopback-only `NO_PROXY`/`no_proxy`. Neither PATH nor shell rc files changed. Resume rows reuse the original isolated HOME.
|
||||
|
||||
For rows marked **API**, the environment also contains `GEMINI_API_KEY=<DUMMY_KEY>` and `GOOGLE_GEMINI_BASE_URL=http://127.0.0.1:<PORT>`, and `<HOME>/.gemini/antigravity-cli/settings.json` contains `{"modelProvider":"gemini"}`. **Env only** sets those two variables without the setting; **None** has neither. The loopback translator reused the prior QA route and forwarded real requests to a paid upstream model. Its credential was read programmatically inside the temporary proxy, with later captures passing it through the proxy environment. It was never passed to agy or recorded. The translator maps all requested Gemini models to one upstream Gemini model: request routing and CLI selection are verified, native model identity/quality is not.
|
||||
|
||||
The translator emits text, function calls, thought parts and usage. Its usage mapping does not populate Gemini thinking/cache counts, so observed zero `thinking_tokens`/`cache_read_tokens` does not prove native zeros. No dedicated reasoning frames appeared, including the matched high-effort probe.
|
||||
|
||||
`<HOME>`, `<WORKSPACE>`, `<CAPTURE>`, `<PORT>`, `<DUMMY_KEY>`, and `<REDACTED_REFERENCE>` are redactions. Unrelated project/model names and help examples are redacted without removing frames. Delta chunk boundaries remain; a path split across deltas can have a different placeholder spelling than the complete result. UUIDs are retained for conversation correlation.
|
||||
|
||||
| Fixture prefix | Exact argv (redacted) | Auth | Demonstrates | agy |
|
||||
| --------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------- |
|
||||
| `agents` | `<AGY> agents` | API | Available agents listing is plain text. | 1.2.13 |
|
||||
| `api-key-env-only` | `<AGY> --input-format stream-json --output-format stream-json` | Env only | Dummy key and custom endpoint alone do not select API-key auth. | 1.2.13 |
|
||||
| `api-key-settings` | `<AGY> --input-format stream-json --output-format stream-json` | API | modelProvider=gemini yields init; missing event input is rejected. | 1.2.13 |
|
||||
| `cwd-without-add-dir` | `<AGY> --input-format stream-json --output-format stream-json --dangerously-skip-permissions` | API | Without add-dir, model-issued pwd returned launch cwd; required tool Cwd is not exposed by this wire frame. | 1.2.13 |
|
||||
| `effort-unsupported-family` | `<AGY> --input-format stream-json --output-format stream-json --model '<REDACTED_REFERENCE>' --effort high` | API | Unsupported non-Gemini family rejects effort; model reference redacted. | 1.2.13 |
|
||||
| `effort-encoded-match` | `<AGY> --input-format stream-json --output-format stream-json --model gemini-3.8-flash-high --effort high` | API | Matching high effort accepted; arithmetic turn; no thought delta observed. | 1.2.13 |
|
||||
| `effort-encoded-mismatch` | `<AGY> --input-format stream-json --output-format stream-json --model gemini-3.8-flash-high --effort low` | API | High-suffixed model conflicts with effort=low; no init. | 1.2.13 |
|
||||
| `effort-mismatch` | `<AGY> --input-format stream-json --output-format stream-json --model gemini-3.1-pro-preview --effort low` | API | Unlisted preview ID does not support effort; startup ERROR. | 1.2.13 |
|
||||
| `help` | `<AGY> --help` | None | Launch flags and subcommands; help is on stderr. | 1.2.13 |
|
||||
| `image-content` | `<AGY> --input-format stream-json --output-format stream-json --disable-slash-commands` | API | Image source block rejected: only text blocks supported. | 1.2.13 |
|
||||
| `mcp-add-http` | `<AGY> mcp add phase0 'http://127.0.0.1:<PORT>/mcp'` | None | Writes .gemini/config/mcp_config.json; file content recorded in capture annotations. | 1.2.13 |
|
||||
| `mcp-add` | `<AGY> mcp add phase0 --url 'http://127.0.0.1:<PORT>/mcp'` | None | Invalid --url syntax rejected; no configuration written. | 1.2.13 |
|
||||
| `mcp-global` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands --dangerously-skip-permissions` | API | Global HTTP server discovered; strict tool call fails because arguments is a string. | 1.2.13 |
|
||||
| `mcp-help` | `<AGY> mcp add --help` | None | MCP add syntax, HTTP transport, headers/env; flags precede name. | 1.2.13 |
|
||||
| `mcp-list` | `<AGY> mcp list` | None | Model-free empty server list, exit 0. | 1.2.13 |
|
||||
| `mcp-project-config` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands --dangerously-skip-permissions` | API | Workspace .gemini/config.json mcpServers not discovered in this probe. | 1.2.13 |
|
||||
| `mcp-project-isolated` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --dangerously-skip-permissions` | API | Project MCP discovery; no global MCP config before/after; strict arguments error. | 1.2.13 |
|
||||
| `mcp-project` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands --dangerously-skip-permissions` | API | Project .agents/mcp_config.json discovers HTTP server; strict call rejects string arguments; later translator 502 retained. | 1.2.13 |
|
||||
| `mcp-second-add-dir` | `<AGY> --input-format stream-json --output-format stream-json --disable-slash-commands --dangerously-skip-permissions --add-dir '<WORKSPACE>' --add-dir '<CAPTURE>/owned'` | API | Discovers project MCP from the owned directory; strict arguments fail. Shell uses owned directory and writes occur in both directories. | 1.2.13 |
|
||||
| `mcp-owned-first-add-dir` | `<AGY> --input-format stream-json --output-format stream-json --disable-slash-commands --dangerously-skip-permissions --add-dir '<CAPTURE>/owned' --add-dir '<WORKSPACE>'` | API | Reversing argv does not restore command working directory; owned directory remains the shell default. | 1.2.13 |
|
||||
| `mcp-owned-cwd` | `<AGY> --input-format stream-json --output-format stream-json --disable-slash-commands --dangerously-skip-permissions --add-dir '<WORKSPACE>'`, launched in `<CAPTURE>/owned` through a symlink | API | Discovers owned project MCP, but shell and initial read resolve against owned cwd; writes occur in both directories. | 1.2.13 |
|
||||
| `mcp-agent-file` | `<AGY> --input-format stream-json --output-format stream-json --disable-slash-commands --dangerously-skip-permissions --add-dir '<WORKSPACE>' --agent '<CAPTURE>/owned/agent.md'` | API | Direct external agent filename falls back to default; no MCP discovery. Shell/read/write use workspace. | 1.2.13 |
|
||||
| `mcp-agent-definition` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<CAPTURE>/owned' --add-dir '<WORKSPACE>' --agent phase2 --dangerously-skip-permissions` | API | Nested Markdown definition with inline mcpServers is not found; default agent, no MCP server. | 1.2.13 |
|
||||
| `mcp-agent-discovered` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<CAPTURE>/owned' --add-dir '<WORKSPACE>' --agent phase2 --dangerously-skip-permissions` | API | Flat Markdown definition plus agents.json manifest is also not found; default agent, no MCP server. | 1.2.13 |
|
||||
| `image-file-external` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands` | API | Default mode reads an external image path with view_file and correctly describes its pixels, without an extra add-dir. | 1.2.13 |
|
||||
| `model-switch-invalid` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --conversation b966b16a-caf4-448a-bb24-0e34956022b0 --model gemini-3.1-flash-lite-preview` | API | Unlisted model ID rejected before init. | 1.2.13 |
|
||||
| `model-switch-network` | `<AGY> --input-format stream-json --output-format stream-json --conversation b966b16a-caf4-448a-bb24-0e34956022b0 --model gemini-3.8-flash-low` | API | Listed model respawn plus actual Gemini request path in capture annotations; COBALT preserved. | 1.2.13 |
|
||||
| `model-switch-resume` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --conversation b966b16a-caf4-448a-bb24-0e34956022b0 --model gemini-3.1-flash-high` | API | Another unlisted model ID rejected before init. | 1.2.13 |
|
||||
| `model-switch-valid` | `<AGY> --input-format stream-json --output-format stream-json --conversation b966b16a-caf4-448a-bb24-0e34956022b0 --model gemini-3.8-flash-low` | API | Listed low-effort model accepted on same conversation; COBALT preserved. | 1.2.13 |
|
||||
| `models-api-key` | `<AGY> models` | API | Authenticated catalog is tab-separated model ID and display label. | 1.2.13 |
|
||||
| `models-unauthenticated` | `<AGY> models` | None | Models requires auth; no catalog on stdout. | 1.2.13 |
|
||||
| `multi-turn-reasoning` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands` | API | Two text turns with arithmetic; per-generation and cumulative usage. No thought delta observed. | 1.2.13 |
|
||||
| `not-signed-in` | `<AGY> --input-format stream-json --output-format stream-json` | None | No credentials: result ERROR, no init, exit 1. | 1.2.13 |
|
||||
| `project-skill` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands` | API | Project skill read via view_file; marker reply. No distinct skill event. | 1.2.13 |
|
||||
| `question-auto-denial` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>'` | API | Requested ask_question becomes unknown DONE step, auto-skipped; no question payload on wire. | 1.2.13 |
|
||||
| `resume-after-interrupt` | `<AGY> --input-format stream-json --output-format stream-json --conversation a2c2132f-0f10-400b-a142-74f82230f449` | API | Same interrupted conversation resumes and accepts another turn. | 1.2.13 |
|
||||
| `resume` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands --conversation b966b16a-caf4-448a-bb24-0e34956022b0` | API | Remembered COBALT after respawn; no old steps replayed. | 1.2.13 |
|
||||
| `schema-prompt` | `<AGY> --input-format stream-json --output-format stream-json` | API | Unknown prompt event ignored with stderr warning; no turn. | 1.2.13 |
|
||||
| `schema-user-message` | `<AGY> --input-format stream-json --output-format stream-json` | API | Decoding a string message fails before event dispatch. | 1.2.13 |
|
||||
| `schema-user` | `<AGY> --input-format stream-json --output-format stream-json` | API | String message rejected; message must be an object. | 1.2.13 |
|
||||
| `sigint-mid-text` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands --dangerously-skip-permissions` | API | SIGINT after first text delta: interrupted result and exit 1. | 1.2.13 |
|
||||
| `sigint-mid-tool` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --dangerously-skip-permissions` | API | SIGINT while run_command ACTIVE: interrupted result and exit 1. | 1.2.13 |
|
||||
| `sigterm` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands --dangerously-skip-permissions` | API | SIGTERM after user_input: interrupted result and exit 1. | 1.2.13 |
|
||||
| `slash-disabled` | `<AGY> --input-format stream-json --output-format stream-json --disable-slash-commands` | API | /help sent literally to model; normal turn with built-in skill reads. | 1.2.13 |
|
||||
| `slash-enabled` | `<AGY> --input-format stream-json --output-format stream-json` | API | /help rejected in driver mode; result ERROR and exit 2. | 1.2.13 |
|
||||
| `stdin-close-mid-turn` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands --dangerously-skip-permissions` | API | EOF after user_input: active turn finishes successfully, exit 0. | 1.2.13 |
|
||||
| `stdin-mid-turn` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands --dangerously-skip-permissions` | API | Second prompt after first user_input queues a separate turn; two results. | 1.2.13 |
|
||||
| `subagent` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --dangerously-skip-permissions` | API | Define/invoke child; subagent_info contains child ID/log URI/workspaces; child reply reaches parent. | 1.2.13 |
|
||||
| `text-block` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>'` | API | Array of text content blocks accepted. | 1.2.13 |
|
||||
| `text-turn` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>'` | API | event=user, message.content string; streaming text and final usage. | 1.2.13 |
|
||||
| `tools-accept-edits` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands --mode accept-edits` | API | Read, create, edit allowed; shell auto-denied. Result SUCCESS includes denied_actions. No permission request. | 1.2.13 |
|
||||
| `tools-default` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands` | API | Read, create, edit allowed; shell auto-denied. Result SUCCESS includes denied_actions. No permission request. | 1.2.13 |
|
||||
| `tools-plan` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands --mode plan` | API | Read, create, edit allowed; shell auto-denied. Result SUCCESS includes denied_actions. No permission request. | 1.2.13 |
|
||||
| `tools-sandbox` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands --sandbox` | API | Read, create, edit allowed; shell auto-denied. Result SUCCESS includes denied_actions. No permission request. | 1.2.13 |
|
||||
| `tools-skip-permissions` | `<AGY> --input-format stream-json --output-format stream-json --add-dir '<WORKSPACE>' --disable-slash-commands --dangerously-skip-permissions` | API | Read, create, edit and shell succeed; permission_mode=always-proceed. | 1.2.13 |
|
||||
| `version` | `<AGY> --version` | None | Installed binary reports 1.2.13. | 1.2.13 |
|
||||
| `mcp-http.requests.ndjson` | `node <CAPTURE>/mcp-server.mjs` alongside the MCP argv above | None | Actual HTTP JSON-RPC requests to a strict SDK server; `params.arguments` is a string. Server implements initialize, tools/list, and echo. | 1.2.13 client |
|
||||
| `state-inspection.ndjson` | `HOME=<HOME> strace -f -e trace=file -o <CAPTURE>/files.trace <AGY> --input-format stream-json --output-format stream-json`; `python3 <CAPTURE>/finalize.py`; `firecrawl scrape <OFFICIAL_MANIFEST_URL> --format markdown` | None | Binary checksum, isolated state accesses, read-only SQLite listings, written file contents, and manifest retrieval caveat. Inspection data, not agy stdout. | 1.2.13 |
|
||||
|
||||
`project-skill` used `<WORKSPACE>/.agents/skills/phase0-marker/SKILL.md` with name `phase0-marker`, description “Return the Phase 0 marker when requested,” and instructions “Respond with PHASE0_SKILL_MARKER. No tools are needed after reading this skill.”
|
||||
|
||||
Each tools capture begins with `input.txt` containing `PHASE0_READ_MARKER`, then requests create→replace from `PHASE0_WRITE` to `PHASE0_EDIT`, then `printf PHASE0_SHELL`. `state-inspection.ndjson` records the resulting files. These successful writes disprove an enforced read-only boundary for default/plan on this credential route. `--sandbox` alone still denies the shell at permission policy, so OS sandbox enforcement is unverified.
|
||||
|
||||
## MCP discovery
|
||||
|
||||
Phase 0 project setup (`mcp-project` and `mcp-project-isolated`) places `{"mcpServers":{"phase0":{"serverUrl":"http://127.0.0.1:<PORT>/mcp"}}}` in `<WORKSPACE>/.agents/mcp_config.json`. `mcp-project-config` instead uses `<WORKSPACE>/.gemini/config.json`; that file did not expose the server. HTTP calls send string arguments and fail strict validation.
|
||||
|
||||
Phase 2 places the same HTTP configuration, named `phase2`, in a plugin-owned `<CAPTURE>/owned/.agents/mcp_config.json`. Both add-dir orders discover it, as does launching in the owned directory and adding the real workspace. All three turns execute shell `pwd` in the owned directory. The file turns read the workspace marker but also write `output.txt` into the owned directory. These routes fail the requirement that tools resolve against the user's workspace. Reordering arguments did not change the observed shell cwd; the precise directory selection rule is unverified.
|
||||
|
||||
The external `--agent` file, nested agent definition, and flat definition with an `agents.json` manifest contain inline `mcpServers`. Native logs record “not found, falling back to default” for each. These experiments do not establish that every agent definition format is unsupported. `parity-inspection.ndjson` records their definitions and log evidence. Searching the tested binary's strings found no `AGY_MCP`, `MCP_CONFIG`, `AGY_CONFIG`, or `ANTIGRAVITY_CONFIG` override; no environment discovery route was verified.
|
||||
|
||||
No tested MCP route satisfies both discovery and workspace ownership. MCP implementation stopped at this gate: no relay or MCP capability change was added. Images and the daemon e2e continued independently. No global MCP configuration exists in the isolated homes, and no configuration was written into the user repository.
|
||||
|
||||
## Image file fallback
|
||||
|
||||
`image-file-external.png` contains a red triangle on the left and a blue circle on the lower right, against white. The prompt supplies only its path and asks for visible shapes and colors. `image-file-external.stdout.ndjson:4–5` records a successful `view_file`; the result describes the pixels correctly. The native function response supplies bytes at `functionResponse.parts[].inlineData`. The temporary translator was corrected to forward these nested parts to the model; forwarding top-level parts alone dropped the image. `parity-inspection.ndjson` records the image hash and response-part location.
|
||||
|
||||
This verifies one default-mode CLI turn reading a file outside the workspace without an extra add-dir. A later isolated-daemon run attached the same image through the provider and returned the same shapes and colors after a successful `view_file`. The provider advertises `prompt.image`; MCP remains outside the parity implementation.
|
||||
|
||||
Unverified: Google login behaviour; native reasoning/cached-token accounting; filesystem/network isolation under sandbox plus approved commands; exhaustive tool policy; other agent definition formats or environment MCP overrides; child transcript streaming beyond parent metadata; a stable public conversation-list command. No fresh login was created. The fetched official manifest returned 1.2.12 while this binary reports 1.2.13; the checksum identifies the tested binary, but that response does not establish its release provenance.
|
||||
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "agents"], "cwd": "<WORKSPACE>", "home": "<HOME>", "settings": {"modelProvider": "gemini"}, "env": {"GEMINI_API_KEY": "<DUMMY_KEY>", "GOOGLE_GEMINI_BASE_URL": "http://127.0.0.1:<PORT>"}}}
|
||||
{"t_ms": 309, "stream": "capture", "data": {"exit_code": 0}}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "--input-format", "stream-json", "--output-format", "stream-json"], "cwd": "<WORKSPACE>", "home": "<HOME>", "settings": null, "env": {"GEMINI_API_KEY": "<DUMMY_KEY>", "GOOGLE_GEMINI_BASE_URL": "http://127.0.0.1:<PORT>"}}}
|
||||
{"t_ms": 452, "stream": "capture", "data": {"exit_code": 1}}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 288, "stream": "stderr", "data": "Error: authentication required. Run 'agy' to log in, then retry."}
|
||||
{"t_ms": 288, "stream": "stderr", "data": "error: authentication failed or timed out"}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 288, "stream": "stdout", "data": {"event": "result", "result": {"conversation_id": "", "status": "ERROR", "response": "", "error": "authentication failed or timed out", "duration_seconds": 0, "num_turns": 0, "usage": {"input_tokens": 0, "output_tokens": 0, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 0}}}}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "--input-format", "stream-json", "--output-format", "stream-json"], "cwd": "<WORKSPACE>", "home": "<HOME>", "settings": {"modelProvider": "gemini"}, "env": {"GEMINI_API_KEY": "<DUMMY_KEY>", "GOOGLE_GEMINI_BASE_URL": "http://127.0.0.1:<PORT>"}}}
|
||||
{"t_ms": 525, "stream": "capture", "data": {"exit_code": 1}}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 374, "stream": "stderr", "data": "error: stream input message is missing the \"event\" field"}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 373, "stream": "stdin", "data": {"type": "user", "message": {"role": "user", "content": "Reply with PHASE0_HELLO. No tools."}}}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 373, "stream": "stdout", "data": {"event": "init", "conversation_id": "e629b41a-decf-4212-a9fb-3809e8f21122", "init": {"cwd": "<WORKSPACE>", "tools": ["ask_custom_permission", "ask_permission", "ask_question", "browser_click_element", "browser_drag_pixel_to_pixel", "browser_get_dom", "browser_get_network_request", "browser_input", "browser_list_network_requests", "browser_mouse_down", "browser_mouse_up", "browser_move_mouse", "browser_press_key", "browser_refresh_page", "browser_resize_window", "browser_scroll", "browser_scroll_dom", "browser_select_option", "browser_subagent", "call_mcp_tool", "capture_browser_console_logs", "capture_browser_screenshot", "click_browser_pixel", "command_status", "define_subagent", "delete_knowledge", "execute_browser_javascript", "find_by_name", "finish", "generate_image", "grep_search", "invoke_subagent", "list_browser_pages", "list_dir", "list_permissions", "list_resources", "manage_inbox", "manage_subagents", "manage_task", "multi_replace_file_content", "notebook_edit", "notebook_execution", "open_browser_url", "read_browser_page", "read_resource", "read_url_content", "replace_file_content", "run_command", "schedule", "search_web", "sed_file", "send_command_input", "send_message", "view_file", "wait", "wait_5_seconds", "write_to_file"], "permission_mode": "request-review"}}}
|
||||
{"t_ms": 374, "stream": "stdout", "data": {"event": "result", "result": {"conversation_id": "e629b41a-decf-4212-a9fb-3809e8f21122", "status": "ERROR", "response": "", "error": "stream input message is missing the \"event\" field", "duration_seconds": 0, "num_turns": 0, "usage": {"input_tokens": 0, "output_tokens": 0, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 0}}}}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "--input-format", "stream-json", "--output-format", "stream-json", "--dangerously-skip-permissions"], "cwd": "<WORKSPACE>", "home": "<HOME>", "settings": {"modelProvider": "gemini"}, "env": {"GEMINI_API_KEY": "<DUMMY_KEY>", "GOOGLE_GEMINI_BASE_URL": "http://127.0.0.1:<PORT>"}}}
|
||||
{"t_ms": 5003, "stream": "capture", "data": {"exit_code": 0}}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 318, "stream": "stdin", "data": {"event": "user", "message": {"content": "Use run_command to run pwd, WaitMsBeforeAsync 1000. Report the output."}}}
|
||||
@@ -1,11 +0,0 @@
|
||||
{"t_ms": 318, "stream": "stdout", "data": {"event": "init", "conversation_id": "3cea6151-badf-4b4a-ad58-bf3c0b02db25", "init": {"cwd": "<WORKSPACE>", "tools": ["ask_custom_permission", "ask_permission", "ask_question", "browser_click_element", "browser_drag_pixel_to_pixel", "browser_get_dom", "browser_get_network_request", "browser_input", "browser_list_network_requests", "browser_mouse_down", "browser_mouse_up", "browser_move_mouse", "browser_press_key", "browser_refresh_page", "browser_resize_window", "browser_scroll", "browser_scroll_dom", "browser_select_option", "browser_subagent", "call_mcp_tool", "capture_browser_console_logs", "capture_browser_screenshot", "click_browser_pixel", "command_status", "define_subagent", "delete_knowledge", "execute_browser_javascript", "find_by_name", "finish", "generate_image", "grep_search", "invoke_subagent", "list_browser_pages", "list_dir", "list_permissions", "list_resources", "manage_inbox", "manage_subagents", "manage_task", "multi_replace_file_content", "notebook_edit", "notebook_execution", "open_browser_url", "read_browser_page", "read_resource", "read_url_content", "replace_file_content", "run_command", "schedule", "search_web", "sed_file", "send_command_input", "send_message", "view_file", "wait", "wait_5_seconds", "write_to_file"], "permission_mode": "always-proceed"}}}
|
||||
{"t_ms": 347, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "3cea6151-badf-4b4a-ad58-bf3c0b02db25", "step_index": 0, "state": "DONE", "step_type": "user_input"}}}
|
||||
{"t_ms": 3066, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "3cea6151-badf-4b4a-ad58-bf3c0b02db25", "step_index": 1, "state": "DONE", "step_type": "agent_response", "duration_seconds": 2.698205516, "usage": {"input_tokens": 11255, "output_tokens": 138, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 11393}}}}
|
||||
{"t_ms": 3066, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "3cea6151-badf-4b4a-ad58-bf3c0b02db25", "step_index": 2, "state": "ACTIVE", "step_type": "tool", "tool_name": "run_command", "tool_info": {"name": "run_command", "parameters": {"CommandLine": "pwd"}}}}}
|
||||
{"t_ms": 3066, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "3cea6151-badf-4b4a-ad58-bf3c0b02db25", "step_index": 2, "state": "DONE", "step_type": "tool", "tool_name": "run_command", "duration_seconds": 0.006078385, "tool_info": {"name": "run_command", "parameters": {"CommandLine": "pwd"}, "output": "<WORKSPACE>\r\n"}}}}
|
||||
{"t_ms": 4626, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "3cea6151-badf-4b4a-ad58-bf3c0b02db25", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "The output"}}}
|
||||
{"t_ms": 4678, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "3cea6151-badf-4b4a-ad58-bf3c0b02db25", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": " of running "}}}
|
||||
{"t_ms": 4704, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "3cea6151-badf-4b4a-ad58-bf3c0b02db25", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "`pwd` is:\n\n```text\n<CAPTURE>/works"}}}
|
||||
{"t_ms": 4729, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "3cea6151-badf-4b4a-ad58-bf3c0b02db25", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "pace\n```"}}}
|
||||
{"t_ms": 4881, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "3cea6151-badf-4b4a-ad58-bf3c0b02db25", "step_index": 3, "state": "DONE", "step_type": "agent_response", "text_delta": "\n", "duration_seconds": 1.824928838, "usage": {"input_tokens": 11382, "output_tokens": 24, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 11406}}}}
|
||||
{"t_ms": 4881, "stream": "stdout", "data": {"event": "result", "result": {"conversation_id": "3cea6151-badf-4b4a-ad58-bf3c0b02db25", "status": "SUCCESS", "response": "The output of running `pwd` is:\n\n```text\n<WORKSPACE>\n```\n", "duration_seconds": 4.555206291, "num_turns": 1, "usage": {"input_tokens": 22637, "output_tokens": 162, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 22799}}}}
|
||||
@@ -1,11 +0,0 @@
|
||||
{"t_ms":0,"stream":"stdout","data":{"event":"init","conversation_id":"e8243164-64d5-4419-9dbb-c0733300b557","init":{"cwd":"/tmp/agy-p3/workspace","tools":["ask_custom_permission","ask_permission","ask_question","browser_click_element","browser_drag_pixel_to_pixel","browser_get_dom","browser_get_network_request","browser_input","browser_list_network_requests","browser_mouse_down","browser_mouse_up","browser_move_mouse","browser_press_key","browser_refresh_page","browser_resize_window","browser_scroll","browser_scroll_dom","browser_select_option","browser_subagent","call_mcp_tool","capture_browser_console_logs","capture_browser_screenshot","click_browser_pixel","command_status","define_subagent","delete_knowledge","execute_browser_javascript","find_by_name","finish","generate_image","grep_search","invoke_subagent","list_browser_pages","list_dir","list_permissions","list_resources","manage_inbox","manage_subagents","manage_task","multi_replace_file_content","notebook_edit","notebook_execution","open_browser_url","read_browser_page","read_resource","read_url_content","replace_file_content","run_command","schedule","search_web","sed_file","send_command_input","send_message","view_file","wait","wait_5_seconds","write_to_file"],"permission_mode":"request-review"}}}
|
||||
{"t_ms":1,"stream":"stdout","data":{"event":"step_update","step_update":{"conversation_id":"e8243164-64d5-4419-9dbb-c0733300b557","step_index":0,"state":"DONE","step_type":"user_input"}}}
|
||||
{"t_ms":2,"stream":"stdout","data":{"event":"step_update","step_update":{"conversation_id":"e8243164-64d5-4419-9dbb-c0733300b557","step_index":1,"state":"DONE","step_type":"agent_response","duration_seconds":2.86246236,"usage":{"input_tokens":11252,"output_tokens":163,"thinking_tokens":0,"cache_read_tokens":0,"total_tokens":11415}}}}
|
||||
{"t_ms":3,"stream":"stdout","data":{"event":"step_update","step_update":{"conversation_id":"e8243164-64d5-4419-9dbb-c0733300b557","step_index":2,"state":"ACTIVE","step_type":"tool","tool_name":"run_command","tool_info":{"name":"run_command","parameters":{"CommandLine":"printf AGY_DENIAL_PROBE"}}}}}
|
||||
{"t_ms":4,"stream":"stdout","data":{"event":"step_update","step_update":{"conversation_id":"e8243164-64d5-4419-9dbb-c0733300b557","step_index":2,"state":"ERROR","step_type":"tool","tool_name":"run_command","duration_seconds":0.002264915,"tool_info":{"name":"run_command","parameters":{"CommandLine":"printf AGY_DENIAL_PROBE"},"error":{"type":"TOOL_ERROR","message":"permission check failed for command \"printf AGY_DENIAL_PROBE\": user denied permission to run command:\nprintf AGY_DENIAL_PROBE\nDo not attempt to circumvent this denial by rephrasing the command, using alternative tools/scripts (e.g. python, sh, curl), or accessing the same target resource. Proceed without performing this action."}}}}}
|
||||
{"t_ms":5,"stream":"stdout","data":{"event":"result","result":{"conversation_id":"e8243164-64d5-4419-9dbb-c0733300b557","status":"SUCCESS","response":"","duration_seconds":2.890969963,"num_turns":1,"usage":{"input_tokens":11252,"output_tokens":163,"thinking_tokens":0,"cache_read_tokens":0,"total_tokens":11415},"denied_actions":[{"action":"command","display_name":"RunCommand"}]}}}
|
||||
{"t_ms":6,"stream":"stdout","data":{"event":"step_update","step_update":{"conversation_id":"e8243164-64d5-4419-9dbb-c0733300b557","step_index":3,"state":"DONE","step_type":"user_input"}}}
|
||||
{"t_ms":7,"stream":"stdout","data":{"event":"step_update","step_update":{"conversation_id":"e8243164-64d5-4419-9dbb-c0733300b557","step_index":4,"state":"ACTIVE","step_type":"agent_response","text_delta":"FOLLOWUP"}}}
|
||||
{"t_ms":8,"stream":"stdout","data":{"event":"step_update","step_update":{"conversation_id":"e8243164-64d5-4419-9dbb-c0733300b557","step_index":4,"state":"ACTIVE","step_type":"agent_response","text_delta":"_OK"}}}
|
||||
{"t_ms":9,"stream":"stdout","data":{"event":"step_update","step_update":{"conversation_id":"e8243164-64d5-4419-9dbb-c0733300b557","step_index":4,"state":"DONE","step_type":"agent_response","text_delta":"\n","duration_seconds":2.11565723,"usage":{"input_tokens":11533,"output_tokens":4,"thinking_tokens":0,"cache_read_tokens":0,"total_tokens":11537}}}}
|
||||
{"t_ms":10,"stream":"stdout","data":{"event":"result","result":{"conversation_id":"e8243164-64d5-4419-9dbb-c0733300b557","status":"SUCCESS","response":"FOLLOWUP_OK\n","duration_seconds":5.028454184,"num_turns":2,"usage":{"input_tokens":22785,"output_tokens":167,"thinking_tokens":0,"cache_read_tokens":0,"total_tokens":22952},"denied_actions":[{"action":"command","display_name":"RunCommand"}]}}}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "--input-format", "stream-json", "--output-format", "stream-json", "--model", "gemini-3.8-flash-high", "--effort", "high"], "cwd": "<WORKSPACE>", "home": "<HOME>", "settings": {"modelProvider": "gemini"}, "env": {"GEMINI_API_KEY": "<DUMMY_KEY>", "GOOGLE_GEMINI_BASE_URL": "http://127.0.0.1:<PORT>"}}}
|
||||
{"t_ms": 4787, "stream": "capture", "data": {"exit_code": 0}}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 296, "stream": "stdin", "data": {"event": "user", "message": {"content": "Solve 143*127. Think carefully before answering. No tools."}}}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
{"t_ms": 295, "stream": "stdout", "data": {"event": "init", "conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "init": {"model": "gemini-3.8-flash-high", "cwd": "<WORKSPACE>", "tools": ["ask_custom_permission", "ask_permission", "ask_question", "browser_click_element", "browser_drag_pixel_to_pixel", "browser_get_dom", "browser_get_network_request", "browser_input", "browser_list_network_requests", "browser_mouse_down", "browser_mouse_up", "browser_move_mouse", "browser_press_key", "browser_refresh_page", "browser_resize_window", "browser_scroll", "browser_scroll_dom", "browser_select_option", "browser_subagent", "call_mcp_tool", "capture_browser_console_logs", "capture_browser_screenshot", "click_browser_pixel", "command_status", "define_subagent", "delete_knowledge", "execute_browser_javascript", "find_by_name", "finish", "generate_image", "grep_search", "invoke_subagent", "list_browser_pages", "list_dir", "list_permissions", "list_resources", "manage_inbox", "manage_subagents", "manage_task", "multi_replace_file_content", "notebook_edit", "notebook_execution", "open_browser_url", "read_browser_page", "read_resource", "read_url_content", "replace_file_content", "run_command", "schedule", "search_web", "sed_file", "send_command_input", "send_message", "view_file", "wait", "wait_5_seconds", "write_to_file"], "permission_mode": "request-review"}}}
|
||||
{"t_ms": 323, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 0, "state": "DONE", "step_type": "user_input"}}}
|
||||
{"t_ms": 3877, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "**18,161**\n\n### Step-by-"}}}
|
||||
{"t_ms": 3903, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "Step Breakdo"}}}
|
||||
{"t_ms": 3928, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "wn:\nUsing the difference"}}}
|
||||
{"t_ms": 3953, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": " of squares "}}}
|
||||
{"t_ms": 3979, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "formula $("}}}
|
||||
{"t_ms": 4055, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "a + b)(a - b) = a^2 - b^2$:\n- $143 ="}}}
|
||||
{"t_ms": 4081, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": " 135"}}}
|
||||
{"t_ms": 4132, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": " + 8$\n- $127"}}}
|
||||
{"t_ms": 4157, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": " = 135 - 8$\n\n$$143"}}}
|
||||
{"t_ms": 4259, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": " \\times 127 = 135^2 - 8^"}}}
|
||||
{"t_ms": 4284, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "2$$\n$$135"}}}
|
||||
{"t_ms": 4385, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "^2 = 18,225$$\n$$8^2 = 64"}}}
|
||||
{"t_ms": 4411, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "$$\n$$18,22"}}}
|
||||
{"t_ms": 4462, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "5 - 64 = \\ma"}}}
|
||||
{"t_ms": 4487, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "thbf{18,161}$$"}}}
|
||||
{"t_ms": 4640, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "step_index": 1, "state": "DONE", "step_type": "agent_response", "text_delta": "\n", "duration_seconds": 4.300859666, "usage": {"input_tokens": 10982, "output_tokens": 553, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 11535}}}}
|
||||
{"t_ms": 4640, "stream": "stdout", "data": {"event": "result", "result": {"conversation_id": "a090b266-2ea9-418c-bb02-c3267268b67b", "status": "SUCCESS", "response": "**18,161**\n\n### Step-by-Step Breakdown:\nUsing the difference of squares formula $(a + b)(a - b) = a^2 - b^2$:\n- $143 = 135 + 8$\n- $127 = 135 - 8$\n\n$$143 \\times 127 = 135^2 - 8^2$$\n$$135^2 = 18,225$$\n$$8^2 = 64$$\n$$18,225 - 64 = \\mathbf{18,161}$$\n", "duration_seconds": 4.326461265, "num_turns": 1, "usage": {"input_tokens": 10982, "output_tokens": 553, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 11535}}}}
|
||||
-3
@@ -1,3 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "--input-format", "stream-json", "--output-format", "stream-json", "--model", "gemini-3.8-flash-high", "--effort", "low"], "cwd": "<WORKSPACE>", "home": "<HOME>", "settings": {"modelProvider": "gemini"}, "env": {"GEMINI_API_KEY": "<DUMMY_KEY>", "GOOGLE_GEMINI_BASE_URL": "http://127.0.0.1:<PORT>"}}}
|
||||
{"t_ms": 5006, "stream": "capture", "data": {"timeout_waiting_for": "init", "seconds": 5}}
|
||||
{"t_ms": 5006, "stream": "capture", "data": {"exit_code": 1}}
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"t_ms": 296, "stream": "stderr", "data": "error: invalid model selection (--model \"gemini-3.8-flash-high\" --effort \"low\"): --model gemini-3.8-flash-high conflicts with --effort=low"}
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"t_ms": 296, "stream": "stdout", "data": {"event": "result", "result": {"conversation_id": "", "status": "ERROR", "response": "", "error": "invalid model selection (--model \"gemini-3.8-flash-high\" --effort \"low\"): --model gemini-3.8-flash-high conflicts with --effort=low", "duration_seconds": 0, "num_turns": 0, "usage": {"input_tokens": 0, "output_tokens": 0, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 0}}}}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "--input-format", "stream-json", "--output-format", "stream-json", "--model", "gemini-3.1-pro-preview", "--effort", "low"], "cwd": "<WORKSPACE>", "home": "<HOME>", "settings": {"modelProvider": "gemini"}, "env": {"GEMINI_API_KEY": "<DUMMY_KEY>", "GOOGLE_GEMINI_BASE_URL": "http://127.0.0.1:<PORT>"}}}
|
||||
{"t_ms": 415, "stream": "capture", "data": {"exit_code": 1}}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 298, "stream": "stderr", "data": "error: invalid model selection (--model \"gemini-3.1-pro-preview\" --effort \"low\"): --effort is not supported for model \"gemini-3.1-pro-preview\""}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 298, "stream": "stdout", "data": {"event": "result", "result": {"conversation_id": "", "status": "ERROR", "response": "", "error": "invalid model selection (--model \"gemini-3.1-pro-preview\" --effort \"low\"): --effort is not supported for model \"gemini-3.1-pro-preview\"", "duration_seconds": 0, "num_turns": 0, "usage": {"input_tokens": 0, "output_tokens": 0, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 0}}}}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "--input-format", "stream-json", "--output-format", "stream-json", "--model", "<REDACTED_REFERENCE>", "--effort", "high"], "cwd": "<WORKSPACE>", "home": "<HOME>", "settings": {"modelProvider": "gemini"}, "env": {"GEMINI_API_KEY": "<DUMMY_KEY>", "GOOGLE_GEMINI_BASE_URL": "http://127.0.0.1:<PORT>"}}}
|
||||
{"t_ms": 358, "stream": "capture", "data": {"exit_code": 1}}
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"t_ms": 287, "stream": "stderr", "data": "error: invalid model selection (--model \"<REDACTED_REFERENCE>\" --effort \"high\"): --effort is not supported for model \"<REDACTED_REFERENCE>\""}
|
||||
-1
@@ -1 +0,0 @@
|
||||
{"t_ms": 287, "stream": "stdout", "data": {"event": "result", "result": {"conversation_id": "", "status": "ERROR", "response": "", "error": "invalid model selection (--model \"<REDACTED_REFERENCE>\" --effort \"high\"): --effort is not supported for model \"<REDACTED_REFERENCE>\"", "duration_seconds": 0, "num_turns": 0, "usage": {"input_tokens": 0, "output_tokens": 0, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 0}}}}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "--help"], "cwd": "<WORKSPACE>", "home": "<HOME>", "settings": null, "env": {}}}
|
||||
{"t_ms": 209, "stream": "capture", "data": {"exit_code": 0}}
|
||||
@@ -1,39 +0,0 @@
|
||||
{"t_ms": 195, "stream": "stderr", "data": "Usage of agy:"}
|
||||
{"t_ms": 195, "stream": "stderr", "data": " --add-dir Add a directory to the workspace (repeatable) (default [])"}
|
||||
{"t_ms": 195, "stream": "stderr", "data": " --agent Agent for the current CLI session"}
|
||||
{"t_ms": 195, "stream": "stderr", "data": " -c Short alias for --continue"}
|
||||
{"t_ms": 195, "stream": "stderr", "data": " --continue Continue the most recent conversation"}
|
||||
{"t_ms": 195, "stream": "stderr", "data": " --conversation Resume a previous conversation by ID"}
|
||||
{"t_ms": 195, "stream": "stderr", "data": " --dangerously-skip-permissions Auto-approve all tool permission requests without prompting"}
|
||||
{"t_ms": 195, "stream": "stderr", "data": " --disable-slash-commands Disable slash command and skill expansion in print mode"}
|
||||
{"t_ms": 195, "stream": "stderr", "data": " --effort Reasoning effort for the current CLI session (low|medium|high|max)"}
|
||||
{"t_ms": 195, "stream": "stderr", "data": " -i Short alias for --prompt-interactive"}
|
||||
{"t_ms": 195, "stream": "stderr", "data": " --input-format Input format for print mode (text, stream-json). stream-json reads one NDJSON message per line from stdin and runs a turn for each; it requires --output-format stream-json (default text)"}
|
||||
{"t_ms": 195, "stream": "stderr", "data": " --json-schema Optional JSON schema string or path to a schema file to enforce structured output (for stream-json, only applicable to the final result)"}
|
||||
{"t_ms": 195, "stream": "stderr", "data": " --log-file Override CLI log file path"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " --mode Set the agent execution mode for this session (accept-edits, plan)"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " --model Model for the current CLI session"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " --new-project Create a new project for this session"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " --output-format Output format for print mode (text, json, stream-json) (default text)"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " -p Short alias for --print"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " --print Run a single prompt non-interactively and print the response"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " --print-timeout Optional time limit for print mode; 0 waits until the turn completes (default 0s)"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " --project Project ID or project name for the current CLI session"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " --prompt Alias for --print"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " --prompt-interactive Run an initial prompt interactively and continue the session"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " --remote-control Create a remote connection for the CLI session on start up"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " --sandbox Run in a sandbox with terminal restrictions enabled"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": ""}
|
||||
{"t_ms": 196, "stream": "stderr", "data": "Available subcommands:"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " agent List available agents"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " agents List available agents"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " changelog Show changelog and release notes"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " help Show help for subcommands"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " install Configure environment paths and shell settings"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " mcp Manage MCP servers (add, remove, list, enable, disable)"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " mic-serve Serve this machine's microphone to a CLI on another host"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " models List available models"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " plugin Manage plugins (install, uninstall, list, enable, disable)"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " plugins Alias for plugin"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " remote-control Manage the remote-control background daemon (start, status, stop)"}
|
||||
{"t_ms": 196, "stream": "stderr", "data": " update Update CLI"}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "--input-format", "stream-json", "--output-format", "stream-json", "--disable-slash-commands"], "cwd": "<WORKSPACE>", "home": "<HOME>", "settings": {"modelProvider": "gemini"}, "env": {"GEMINI_API_KEY": "<DUMMY_KEY>", "GOOGLE_GEMINI_BASE_URL": "http://127.0.0.1:<PORT>"}}}
|
||||
{"t_ms": 383, "stream": "capture", "data": {"exit_code": 1}}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 294, "stream": "stderr", "data": "error: stream input content block type \"image\" is not supported (only \"text\")"}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 294, "stream": "stdin", "data": {"event": "user", "message": {"content": [{"type": "text", "text": "Describe this image."}, {"type": "image", "source": {"type": "base64", "media_type": "image/png", "data": "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aN1sAAAAASUVORK5CYII="}}]}}}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 294, "stream": "stdout", "data": {"event": "init", "conversation_id": "71e19e79-63e3-4a7b-b628-822e69618edc", "init": {"cwd": "<WORKSPACE>", "tools": ["ask_custom_permission", "ask_permission", "ask_question", "browser_click_element", "browser_drag_pixel_to_pixel", "browser_get_dom", "browser_get_network_request", "browser_input", "browser_list_network_requests", "browser_mouse_down", "browser_mouse_up", "browser_move_mouse", "browser_press_key", "browser_refresh_page", "browser_resize_window", "browser_scroll", "browser_scroll_dom", "browser_select_option", "browser_subagent", "call_mcp_tool", "capture_browser_console_logs", "capture_browser_screenshot", "click_browser_pixel", "command_status", "define_subagent", "delete_knowledge", "execute_browser_javascript", "find_by_name", "finish", "generate_image", "grep_search", "invoke_subagent", "list_browser_pages", "list_dir", "list_permissions", "list_resources", "manage_inbox", "manage_subagents", "manage_task", "multi_replace_file_content", "notebook_edit", "notebook_execution", "open_browser_url", "read_browser_page", "read_resource", "read_url_content", "replace_file_content", "run_command", "schedule", "search_web", "sed_file", "send_command_input", "send_message", "view_file", "wait", "wait_5_seconds", "write_to_file"], "permission_mode": "request-review"}}}
|
||||
{"t_ms": 294, "stream": "stdout", "data": {"event": "result", "result": {"conversation_id": "71e19e79-63e3-4a7b-b628-822e69618edc", "status": "ERROR", "response": "", "error": "stream input content block type \"image\" is not supported (only \"text\")", "duration_seconds": 0, "num_turns": 0, "usage": {"input_tokens": 0, "output_tokens": 0, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 0}}}}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "--input-format", "stream-json", "--output-format", "stream-json", "--add-dir", "<WORKSPACE>", "--disable-slash-commands"], "cwd": "<WORKSPACE>", "home": "<CAPTURE>/image-file-external-home", "settings": {"modelProvider": "gemini"}, "env": {"GEMINI_API_KEY": "<DUMMY_KEY>", "GOOGLE_GEMINI_BASE_URL": "http://127.0.0.1:<PORT>"}}}
|
||||
{"t_ms": 7877, "stream": "capture", "data": {"exit_code": 0}}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 910 B |
@@ -1 +0,0 @@
|
||||
{"t_ms": 320, "stream": "stdin", "data": {"event": "user", "message": {"content": "Use view_file to open the attached image at <CAPTURE>/owned/attachment.png. Describe the shapes and colors visible. Do not use shell or code to infer the pixels."}}}
|
||||
@@ -1,14 +0,0 @@
|
||||
{"t_ms": 320, "stream": "stdout", "data": {"event": "init", "conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "init": {"cwd": "<WORKSPACE>", "tools": ["ask_custom_permission", "ask_permission", "ask_question", "browser_click_element", "browser_drag_pixel_to_pixel", "browser_get_dom", "browser_get_network_request", "browser_input", "browser_list_network_requests", "browser_mouse_down", "browser_mouse_up", "browser_move_mouse", "browser_press_key", "browser_refresh_page", "browser_resize_window", "browser_scroll", "browser_scroll_dom", "browser_select_option", "browser_subagent", "call_mcp_tool", "capture_browser_console_logs", "capture_browser_screenshot", "click_browser_pixel", "command_status", "define_subagent", "delete_knowledge", "execute_browser_javascript", "find_by_name", "finish", "generate_image", "grep_search", "invoke_subagent", "list_browser_pages", "list_dir", "list_permissions", "list_resources", "manage_inbox", "manage_subagents", "manage_task", "multi_replace_file_content", "notebook_edit", "notebook_execution", "open_browser_url", "read_browser_page", "read_resource", "read_url_content", "replace_file_content", "run_command", "schedule", "search_web", "sed_file", "send_command_input", "send_message", "view_file", "wait", "wait_5_seconds", "write_to_file"], "permission_mode": "request-review"}}}
|
||||
{"t_ms": 347, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "step_index": 0, "state": "DONE", "step_type": "user_input"}}}
|
||||
{"t_ms": 4057, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "step_index": 1, "state": "DONE", "step_type": "agent_response", "duration_seconds": 3.704260379, "usage": {"input_tokens": 11275, "output_tokens": 384, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 11659}}}}
|
||||
{"t_ms": 4057, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "step_index": 2, "state": "ACTIVE", "step_type": "tool", "tool_name": "view_file", "tool_info": {"name": "view_file", "parameters": {"AbsolutePath": "<CAPTURE>/owned/attachment.png"}}}}}
|
||||
{"t_ms": 4057, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "step_index": 2, "state": "DONE", "step_type": "tool", "tool_name": "view_file", "duration_seconds": 0.003012462, "tool_info": {"name": "view_file", "parameters": {"AbsolutePath": "<CAPTURE>/owned/attachment.png"}}}}}
|
||||
{"t_ms": 7530, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "Based on the image at [/"}}}
|
||||
{"t_ms": 7555, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "tmp/agy-p2/o"}}}
|
||||
{"t_ms": 7581, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "wned/attachment.png](fil"}}}
|
||||
{"t_ms": 7606, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "e:///tmp/agy"}}}
|
||||
{"t_ms": 7631, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "-p2/owned/attachment.png"}}}
|
||||
{"t_ms": 7657, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "), here are "}}}
|
||||
{"t_ms": 7682, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "the visible shapes and colors:\n\n- **Red Triangl"}}}
|
||||
{"t_ms": 7708, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "step_index": 3, "state": "DONE", "step_type": "agent_response", "text_delta": "e**: A solid red triangle positioned on the left side, pointing upwards.\n- **Blue Circle**: A solid blue circle positioned on the lower right side.\n- **Background**: Solid white.\n", "duration_seconds": 3.629267516, "usage": {"input_tokens": 13749, "output_tokens": 170, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 13919}}}}
|
||||
{"t_ms": 7708, "stream": "stdout", "data": {"event": "result", "result": {"conversation_id": "0c498203-4202-446f-8df1-dfa8f16cb19d", "status": "SUCCESS", "response": "Based on the image at [<CAPTURE>/owned/attachment.png](file://<CAPTURE>/owned/attachment.png), here are the visible shapes and colors:\n\n- **Red Triangle**: A solid red triangle positioned on the left side, pointing upwards.\n- **Blue Circle**: A solid blue circle positioned on the lower right side.\n- **Background**: Solid white.\n", "duration_seconds": 7.367115828, "num_turns": 1, "usage": {"input_tokens": 25024, "output_tokens": 554, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 25578}}}}
|
||||
@@ -1,3 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "mcp", "add", "phase0", "http://127.0.0.1:<PORT>/mcp"], "cwd": "<WORKSPACE>", "home": "<HOME>", "settings": null, "env": {}}}
|
||||
{"t_ms": 68, "stream": "capture", "data": {"written_file": "<HOME>/.gemini/config/mcp_config.json", "content": {"mcpServers": {"phase0": {"disabled": false, "serverUrl": "http://127.0.0.1:<PORT>/mcp"}}}}}
|
||||
{"t_ms": 69, "stream": "capture", "data": {"exit_code": 0}}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 63, "stream": "stdout", "data": "Added MCP server \"phase0\" (http)"}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "mcp", "add", "phase0", "--url", "http://127.0.0.1:<PORT>/mcp"], "cwd": "<WORKSPACE>", "home": "<HOME>", "settings": null, "env": {}}}
|
||||
{"t_ms": 82, "stream": "capture", "data": {"exit_code": 1}}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 76, "stream": "stderr", "data": "Error: invalid command \"--url\": flags must come before the server name (usage: mcp add [flags] <name> <commandOrUrl> [args...])"}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "--input-format", "stream-json", "--output-format", "stream-json", "--add-dir", "<CAPTURE>/owned", "--add-dir", "<WORKSPACE>", "--agent", "phase2", "--dangerously-skip-permissions"], "cwd": "<WORKSPACE>", "home": "<CAPTURE>/mcp-agent-definition-home", "settings": {"modelProvider": "gemini"}, "env": {"GEMINI_API_KEY": "<DUMMY_KEY>", "GOOGLE_GEMINI_BASE_URL": "http://127.0.0.1:<PORT>"}}}
|
||||
{"t_ms": 6091, "stream": "capture", "data": {"exit_code": 0}}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 311, "stream": "stdin", "data": {"event": "user", "message": {"content": "Call the phase2 MCP echo tool with text PHASE2_DISCOVERY. Then use run_command pwd and report the workspace directory. Do not choose a different directory."}}}
|
||||
-15
@@ -1,15 +0,0 @@
|
||||
{"t_ms": 311, "stream": "stdout", "data": {"event": "init", "conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "init": {"cwd": "<WORKSPACE>", "agent": "phase2", "tools": ["ask_custom_permission", "ask_permission", "ask_question", "browser_click_element", "browser_drag_pixel_to_pixel", "browser_get_dom", "browser_get_network_request", "browser_input", "browser_list_network_requests", "browser_mouse_down", "browser_mouse_up", "browser_move_mouse", "browser_press_key", "browser_refresh_page", "browser_resize_window", "browser_scroll", "browser_scroll_dom", "browser_select_option", "browser_subagent", "call_mcp_tool", "capture_browser_console_logs", "capture_browser_screenshot", "click_browser_pixel", "command_status", "define_subagent", "delete_knowledge", "execute_browser_javascript", "find_by_name", "finish", "generate_image", "grep_search", "invoke_subagent", "list_browser_pages", "list_dir", "list_permissions", "list_resources", "manage_inbox", "manage_subagents", "manage_task", "multi_replace_file_content", "notebook_edit", "notebook_execution", "open_browser_url", "read_browser_page", "read_resource", "read_url_content", "replace_file_content", "run_command", "schedule", "search_web", "sed_file", "send_command_input", "send_message", "view_file", "wait", "wait_5_seconds", "write_to_file"], "permission_mode": "always-proceed"}}}
|
||||
{"t_ms": 340, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "step_index": 0, "state": "DONE", "step_type": "user_input"}}}
|
||||
{"t_ms": 4006, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "step_index": 1, "state": "DONE", "step_type": "agent_response", "duration_seconds": 3.658926744, "usage": {"input_tokens": 11286, "output_tokens": 361, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 11647}}}}
|
||||
{"t_ms": 4006, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "step_index": 2, "state": "ACTIVE", "step_type": "tool", "tool_name": "run_command", "tool_info": {"name": "run_command", "parameters": {"CommandLine": "pwd"}}}}}
|
||||
{"t_ms": 4006, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "step_index": 2, "state": "DONE", "step_type": "tool", "tool_name": "run_command", "duration_seconds": 0.008819631, "tool_info": {"name": "run_command", "parameters": {"CommandLine": "pwd"}, "output": "<CAPTURE>/owned\r\n"}}}}
|
||||
{"t_ms": 5473, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "The "}}}
|
||||
{"t_ms": 5603, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "`phase2 MCP echo tool` is not availa"}}}
|
||||
{"t_ms": 5626, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "ble in the tool definiti"}}}
|
||||
{"t_ms": 5652, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "ons provided"}}}
|
||||
{"t_ms": 5678, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": " in this"}}}
|
||||
{"t_ms": 5856, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": " environment.\n\nRunning `"}}}
|
||||
{"t_ms": 5881, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "pwd` in the workspace re"}}}
|
||||
{"t_ms": 5907, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "ports the directory:\n`/t"}}}
|
||||
{"t_ms": 5932, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "step_index": 3, "state": "DONE", "step_type": "agent_response", "text_delta": "mp/agy-p2/owned`\n", "duration_seconds": 1.928095705, "usage": {"input_tokens": 11417, "output_tokens": 43, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 11460}}}}
|
||||
{"t_ms": 5932, "stream": "stdout", "data": {"event": "result", "result": {"conversation_id": "7240f716-f2bd-4641-b210-a4a282a756f7", "status": "SUCCESS", "response": "The `phase2 MCP echo tool` is not available in the tool definitions provided in this environment.\n\nRunning `pwd` in the workspace reports the directory:\n`<CAPTURE>/owned`\n", "duration_seconds": 5.61440038, "num_turns": 1, "usage": {"input_tokens": 22703, "output_tokens": 404, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 23107}}}}
|
||||
-2
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "--input-format", "stream-json", "--output-format", "stream-json", "--add-dir", "<CAPTURE>/owned", "--add-dir", "<WORKSPACE>", "--agent", "phase2", "--dangerously-skip-permissions"], "cwd": "<WORKSPACE>", "home": "<CAPTURE>/mcp-agent-discovered-home", "settings": {"modelProvider": "gemini"}, "env": {"GEMINI_API_KEY": "<DUMMY_KEY>", "GOOGLE_GEMINI_BASE_URL": "http://127.0.0.1:<PORT>"}}}
|
||||
{"t_ms": 5524, "stream": "capture", "data": {"exit_code": 0}}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 324, "stream": "stdin", "data": {"event": "user", "message": {"content": "Call the phase2 MCP echo tool with text PHASE2_DISCOVERY. Then use run_command pwd and report the workspace directory. Do not choose a different directory."}}}
|
||||
-19
@@ -1,19 +0,0 @@
|
||||
{"t_ms": 324, "stream": "stdout", "data": {"event": "init", "conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "init": {"cwd": "<WORKSPACE>", "agent": "phase2", "tools": ["ask_custom_permission", "ask_permission", "ask_question", "browser_click_element", "browser_drag_pixel_to_pixel", "browser_get_dom", "browser_get_network_request", "browser_input", "browser_list_network_requests", "browser_mouse_down", "browser_mouse_up", "browser_move_mouse", "browser_press_key", "browser_refresh_page", "browser_resize_window", "browser_scroll", "browser_scroll_dom", "browser_select_option", "browser_subagent", "call_mcp_tool", "capture_browser_console_logs", "capture_browser_screenshot", "click_browser_pixel", "command_status", "define_subagent", "delete_knowledge", "execute_browser_javascript", "find_by_name", "finish", "generate_image", "grep_search", "invoke_subagent", "list_browser_pages", "list_dir", "list_permissions", "list_resources", "manage_inbox", "manage_subagents", "manage_task", "multi_replace_file_content", "notebook_edit", "notebook_execution", "open_browser_url", "read_browser_page", "read_resource", "read_url_content", "replace_file_content", "run_command", "schedule", "search_web", "sed_file", "send_command_input", "send_message", "view_file", "wait", "wait_5_seconds", "write_to_file"], "permission_mode": "always-proceed"}}}
|
||||
{"t_ms": 356, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 0, "state": "DONE", "step_type": "user_input"}}}
|
||||
{"t_ms": 3842, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 1, "state": "DONE", "step_type": "agent_response", "duration_seconds": 3.487178538, "usage": {"input_tokens": 11279, "output_tokens": 433, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 11712}}}}
|
||||
{"t_ms": 3842, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 2, "state": "ACTIVE", "step_type": "tool", "tool_name": "run_command", "tool_info": {"name": "run_command", "parameters": {"CommandLine": "pwd"}}}}}
|
||||
{"t_ms": 3867, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 2, "state": "DONE", "step_type": "tool", "tool_name": "run_command", "duration_seconds": 0.008305961, "tool_info": {"name": "run_command", "parameters": {"CommandLine": "pwd"}, "output": "<CAPTURE>/owned\r\n"}}}}
|
||||
{"t_ms": 5050, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "1."}}}
|
||||
{"t_ms": 5177, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": " **MCP Echo "}}}
|
||||
{"t_ms": 5203, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "Tool**: There is no MCP "}}}
|
||||
{"t_ms": 5228, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "`echo` tool "}}}
|
||||
{"t_ms": 5254, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "registered or available "}}}
|
||||
{"t_ms": 5279, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "in the current environme"}}}
|
||||
{"t_ms": 5304, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "nt/tool definit"}}}
|
||||
{"t_ms": 5330, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "ions.\n2. **Workspace Dir"}}}
|
||||
{"t_ms": 5355, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "ectory**: Ra"}}}
|
||||
{"t_ms": 5381, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "n `pwd` in the current w"}}}
|
||||
{"t_ms": 5406, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "orkspace directory and o"}}}
|
||||
{"t_ms": 5432, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 3, "state": "ACTIVE", "step_type": "agent_response", "text_delta": "btained:\n ```\n /tmp/"}}}
|
||||
{"t_ms": 5457, "stream": "stdout", "data": {"event": "step_update", "step_update": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "step_index": 3, "state": "DONE", "step_type": "agent_response", "text_delta": "agy-p2/owned\n ```\n", "duration_seconds": 1.60141087, "usage": {"input_tokens": 11407, "output_tokens": 62, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 11469}}}}
|
||||
{"t_ms": 5457, "stream": "stdout", "data": {"event": "result", "result": {"conversation_id": "b4d0a25b-bb0a-4495-84af-247f689ba6ad", "status": "SUCCESS", "response": "1. **MCP Echo Tool**: There is no MCP `echo` tool registered or available in the current environment/tool definitions.\n2. **Workspace Directory**: Ran `pwd` in the current workspace directory and obtained:\n ```\n <CAPTURE>/owned\n ```\n", "duration_seconds": 5.118791831, "num_turns": 1, "usage": {"input_tokens": 22686, "output_tokens": 495, "thinking_tokens": 0, "cache_read_tokens": 0, "total_tokens": 23181}}}}
|
||||
@@ -1,2 +0,0 @@
|
||||
{"t_ms": 0, "stream": "capture", "data": {"argv": ["<HOME>/.local/opt/agy/1.2.13/agy", "--input-format", "stream-json", "--output-format", "stream-json", "--disable-slash-commands", "--dangerously-skip-permissions", "--add-dir", "<WORKSPACE>", "--agent", "<CAPTURE>/owned/agent.md"], "cwd": "<WORKSPACE>", "home": "<CAPTURE>/mcp-agent-file-home", "settings": {"modelProvider": "gemini"}, "env": {"GEMINI_API_KEY": "<DUMMY_KEY>", "GOOGLE_GEMINI_BASE_URL": "http://127.0.0.1:<PORT>"}}}
|
||||
{"t_ms": 12172, "stream": "capture", "data": {"exit_code": 0}}
|
||||
@@ -1 +0,0 @@
|
||||
{"t_ms": 305, "stream": "stdin", "data": {"event": "user", "message": {"content": "First call the phase2 MCP echo tool with text PHASE2_DISCOVERY. If unavailable say MCP_UNAVAILABLE. Then use shell pwd, read input.txt and write output.txt containing PHASE2_WRITE. Use the workspace default directory, without me supplying absolute paths. Report the exact paths used."}}}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user