@ory/antigravity 0.13.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +76 -76
- package/dist/cli/assets.d.ts +12 -1
- package/dist/cli/assets.js +58 -39
- package/dist/cli/main.js +32 -9
- package/dist/cli/setup.js +9 -2
- package/dist/emit.d.ts +1 -3
- package/dist/emit.js +1 -3
- package/dist/handlers.js +28 -189
- package/dist/hook.js +16 -7
- package/package.json +9 -8
package/README.md
CHANGED
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
Security and developer experience for [Google Antigravity](https://antigravity.google), powered by [Ory](https://ory.com).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Antigravity runs real actions on your machine — editing files, running shell commands, calling APIs. This plugin gives those actions an identity, a permission check, and an audit trail.
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
One command installs two independent halves:
|
|
8
|
+
|
|
9
|
+
- **Developer experience** — the Ory skill catalog, `/ory:` slash commands, the local Ory dev stack, an MCP server, and activity logging. No account, no keys, nothing to configure: it works the moment it's installed.
|
|
10
|
+
- **Ory Agent Security** — browser sign-in, brokered permission checks before tool calls, and the delegation trail. Opt-in connection details from the [Ory Console](https://console.ory.sh) switch it on, and it takes nothing away from the half above. See [Connect to Ory Agent Security](#connect-to-ory-agent-security).
|
|
8
11
|
|
|
9
12
|
> **Antigravity is the successor to Gemini CLI.** If you used the Ory Gemini CLI extension, this is its counterpart for the `agy` CLI and the Antigravity IDE. Your existing skills and commands carry over.
|
|
10
13
|
|
|
@@ -17,27 +20,19 @@ Security and developer experience for [Google Antigravity](https://antigravity.g
|
|
|
17
20
|
|
|
18
21
|
## Get started
|
|
19
22
|
|
|
20
|
-
Run one command
|
|
23
|
+
Run one command It installs the plugin, registering it with Antigravity via `agy plugin install`:
|
|
21
24
|
|
|
22
25
|
```bash
|
|
23
26
|
npx -y -p @ory/antigravity ory-agy install
|
|
24
27
|
```
|
|
25
28
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
- **Ory Network** *(default)* — sign in, or create a free account, in your browser. The project, keys, permissions, and login are all set up for you. Nothing to configure by hand.
|
|
29
|
-
- **Local** — run a complete Ory on your laptop with Docker. No account, no signup, no keys. Great for trying it out.
|
|
30
|
-
- **Audit-only** — skip Ory entirely and just log what the agent does.
|
|
31
|
-
|
|
32
|
-
That's it. Confirm everything landed with:
|
|
29
|
+
Confirm everything landed with:
|
|
33
30
|
|
|
34
31
|
```bash
|
|
35
32
|
npx -y -p @ory/antigravity ory-agy status
|
|
36
33
|
```
|
|
37
34
|
|
|
38
|
-
`status` is your one-stop check: what's configured, who's signed in, which tools are covered by permissions, plugin + skill registration, and recent activity.
|
|
39
|
-
|
|
40
|
-
Re-run install with `--reconfigure` to change your connection later, or `--no-configure` to skip the wizard and configure by hand.
|
|
35
|
+
`status` is your one-stop check: what's configured, who's signed in, which tools are covered by permissions, plugin + skill registration, and recent activity. Until you connect Agent Security, the identity and permission rows say so and name what's missing.
|
|
41
36
|
|
|
42
37
|
<details>
|
|
43
38
|
<summary>No <code>agy</code> binary? Install into <code>.agents/</code> directly</summary>
|
|
@@ -51,13 +46,47 @@ npx -y -p @ory/antigravity ory-agy uninstall
|
|
|
51
46
|
|
|
52
47
|
</details>
|
|
53
48
|
|
|
49
|
+
## Skills and commands
|
|
50
|
+
|
|
51
|
+
Installing the plugin drops the full Ory playbook catalog into Antigravity. **Skills** are model-invoked — just say what you want in plain language and the matching one takes over.
|
|
52
|
+
|
|
53
|
+
| Skill | What it does for you |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `ory-auth-setup` | Adds a complete auth system to your app — login, registration, recovery, verification, settings — on [Ory Elements](https://github.com/ory/elements) |
|
|
56
|
+
| `ory-login-flow` | Builds just the pages, wired to Ory's self-service flows |
|
|
57
|
+
| `ory-social-login` | "Sign in with…" for Google, GitHub, Apple, Microsoft, Discord, Slack, GitLab, Facebook |
|
|
58
|
+
| `ory-local-dev` | Develops and tests login/permission flows against a local Ory — no project, no account, offline |
|
|
59
|
+
| `ory-permissions-onboarding` | Walks a fresh install from observe mode to enforced per-tool permissions without getting blocked |
|
|
60
|
+
| `ory-build-agent` | Drops `@ory/argus` into an agent *you* own — Claude Agent SDK, OpenAI Agents, Mastra, Vercel AI, LangGraph/PydanticAI |
|
|
61
|
+
| `ory-build-integration` | Wires Ory into your app: Action webhooks, JWT validation at a gateway, live event streams |
|
|
62
|
+
| `ory-contribute-integration` | Authors and submits an integration to the public `ory/integrates` registry |
|
|
63
|
+
| `ory-e2b-sandbox` | Scaffolds an E2B sandbox template that boots with this plugin preinstalled |
|
|
64
|
+
| `ory-temporal-worker` | Scaffolds a Temporal TypeScript worker where every Activity is authenticated, authorized, and audited |
|
|
65
|
+
|
|
66
|
+
**Slash commands** run the local stack directly:
|
|
67
|
+
|
|
68
|
+
| Command | What it does |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `/ory:local-up` | Starts a local Ory (Identities, OAuth2, Permissions) in Docker and seeds a test user — it prints the email + password to sign in with |
|
|
71
|
+
| `/ory:local-down` | Stops it, keeping your data volumes |
|
|
72
|
+
| `/ory:temporal-up` | Starts a local Temporal dev server for the `ory-temporal-worker` scaffold |
|
|
73
|
+
|
|
74
|
+
The local stack runs entirely on your laptop: Ory APIs at `http://localhost:4000`, a login UI on `:4455` (not `:3000`, to dodge Next.js port clashes), and the Ory Console on `:4100`.
|
|
75
|
+
|
|
76
|
+
A built-in **Ory MCP server** rounds it out — Antigravity can manage identities, projects, and permissions straight from chat.
|
|
77
|
+
|
|
78
|
+
So: ask Antigravity *"add Ory login to this app"* and it scaffolds the pages, starts a local Ory, and wires them together.
|
|
79
|
+
|
|
54
80
|
## What you get
|
|
55
81
|
|
|
56
|
-
|
|
82
|
+
Out of the box, every tool the agent runs produces a privacy-safe structured activity event in the unified local log.
|
|
83
|
+
|
|
84
|
+
Once you connect to Ory Agent Security, two more things happen automatically:
|
|
85
|
+
|
|
86
|
+
- **Who's driving.** You sign in once in your browser (a secure sign-in flow, no tokens to copy around); each agent session gets its own identity, which it registers automatically on first use. The "who acted on whose behalf" trail stays queryable later.
|
|
87
|
+
- **What it's allowed to do.** Before a tool runs, Ory checks whether it's permitted. It starts in **observe mode** — nothing is blocked, you just *see* what would be — so it never gets in your way on day one.
|
|
57
88
|
|
|
58
|
-
|
|
59
|
-
- **What it's allowed to do.** Before a tool runs, Ory checks whether it's permitted. It starts in **watch mode** — nothing is blocked, you just *see* what would be — so it never gets in your way on day one.
|
|
60
|
-
- **A record of everything.** Every decision (allowed, denied, skipped) is logged as a trace you can send to Jaeger, Honeycomb, Grafana, or just a file.
|
|
89
|
+
If Ory is ever unreachable, the plugin gets out of the way and lets the agent keep working — so it can't lock you out.
|
|
61
90
|
|
|
62
91
|
The plugin hooks into these Antigravity lifecycle events: `SessionStart` (check who's signed in), `PreToolUse` (authorize the tool), `PostToolUse` (record it), `Notification`, and `SessionEnd`.
|
|
63
92
|
|
|
@@ -65,91 +94,63 @@ The plugin hooks into these Antigravity lifecycle events: `SessionStart` (check
|
|
|
65
94
|
|
|
66
95
|
### See what's happening
|
|
67
96
|
|
|
68
|
-
Everything the plugin does is observable out of the box
|
|
97
|
+
Everything the plugin does is observable out of the box:
|
|
69
98
|
|
|
70
|
-
- **
|
|
71
|
-
- **Live traces.** Every tool call is recorded as an OpenTelemetry-style span. Watch them stream as the agent works:
|
|
99
|
+
- **Activity log.** Privacy-safe activity is always appended to `~/.config/ory-agent-plugins/antigravity/ory-agent-debug.log`. View events, decisions, and errors live with:
|
|
72
100
|
|
|
73
101
|
```bash
|
|
74
102
|
npx -y -p @ory/antigravity ory-agy watch
|
|
75
103
|
```
|
|
76
104
|
|
|
77
|
-
|
|
78
|
-
- **
|
|
105
|
+
Set `ORY_AGENT_LOG_FILE` to override the path; set it empty to disable file persistence.
|
|
106
|
+
- **Live debug.** Launch Antigravity with `ORY_AGENT_DEBUG=true` to add verbose local diagnostics, including raw shell commands, to the watched log and stderr. Secrets are recursively redacted; pass `watch --json` for NDJSON.
|
|
79
107
|
|
|
80
108
|
### Ready to enforce?
|
|
81
109
|
|
|
82
|
-
|
|
110
|
+
Once connected, the deny posture lives on the Ory project: when the observe-mode activity looks right, an admin promotes it to **enforce** in the **Ory Console** (Agent Security). Every session reads that posture live, so nothing has to be reinstalled.
|
|
83
111
|
|
|
84
112
|
```bash
|
|
85
|
-
npx -y -p @ory/antigravity ory-agy permissions
|
|
113
|
+
npx -y -p @ory/antigravity ory-agy permissions # what the project grants, and the live mode
|
|
86
114
|
```
|
|
87
115
|
|
|
88
|
-
|
|
116
|
+
Then a denied tool is actually blocked and Antigravity shows why (the `deny_reason`).
|
|
89
117
|
|
|
90
|
-
##
|
|
118
|
+
## Connect to Ory Agent Security
|
|
91
119
|
|
|
92
|
-
|
|
120
|
+
Copy the connection details from the [Ory Console](https://console.ory.sh) under **Agent Security**:
|
|
93
121
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
/ory:local-up # start a local Ory instance in Docker
|
|
100
|
-
/ory:local-down # tear it all down
|
|
101
|
-
/ory:temporal-up # start a local Temporal dev server (for ory-temporal-worker)
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
`local-up` brings up the Ory APIs (Identities, OAuth2, Permissions) at `http://localhost:4000`, a login UI on `:4455` (not `:3000`, to dodge Next.js port clashes), and Jaeger (the trace viewer) on `:16686`. A test user is seeded and its credentials printed — learn Ory hands-on, prototype flows, and test end-to-end, all offline.
|
|
105
|
-
|
|
106
|
-
## Configure by hand (CI / advanced)
|
|
107
|
-
|
|
108
|
-
The guided setup covers most people. For scripted or CI setups, or to point at an existing Ory Network project, configure directly. Settings are saved to `~/.config/ory-agent-plugins/config.json` and shared across all your Ory agent plugins; environment variables win when both are set.
|
|
122
|
+
| Value | Flag | Environment variable |
|
|
123
|
+
|---|---|---|
|
|
124
|
+
| Project URL | `--project-url` | `ORY_PROJECT_URL` |
|
|
125
|
+
| Agent Security URL | `--agent-security-url` | `ORY_AGENT_SECURITY_URL` |
|
|
126
|
+
| Sign-in client id override (default `ory-agent-security-login`) | `--oauth2-client-id` | `ORY_OAUTH2_CLIENT_ID` |
|
|
109
127
|
|
|
110
128
|
```bash
|
|
111
129
|
npx -y -p @ory/antigravity ory-agy configure \
|
|
112
130
|
--project-url https://<slug>.projects.oryapis.com \
|
|
113
|
-
--
|
|
114
|
-
--user-login
|
|
131
|
+
--agent-security-url https://agents.console.ory.com
|
|
115
132
|
```
|
|
116
133
|
|
|
117
|
-
|
|
134
|
+
`install` accepts the same flags, so you can register the plugin **and** connect in one shot (`install --project-url <URL> --agent-security-url <URL>`). The login client defaults to `ory-agent-security-login`; custom deployments can override it with `--oauth2-client-id`. Existing configurations fall back to the project URL when the Agent Security URL is unset. To turn sign-in and checks back off later, use `configure --disconnect`.
|
|
118
135
|
|
|
119
|
-
|
|
120
|
-
<summary>Create the sign-in client by hand</summary>
|
|
121
|
-
|
|
122
|
-
The guided setup normally does this. To do it yourself, create a **public** OAuth2 client (no secret) listing all four loopback URLs — the plugin tries each in turn so sign-in survives a busy port:
|
|
123
|
-
|
|
124
|
-
```bash
|
|
125
|
-
ory create oauth2-client --project <project-id> \
|
|
126
|
-
--name "ory-agent-plugin" \
|
|
127
|
-
--grant-type authorization_code,refresh_token \
|
|
128
|
-
--response-type code \
|
|
129
|
-
--scope openid,offline_access \
|
|
130
|
-
--token-endpoint-auth-method none \
|
|
131
|
-
--redirect-uri http://127.0.0.1:47823/callback \
|
|
132
|
-
--redirect-uri http://127.0.0.1:47824/callback \
|
|
133
|
-
--redirect-uri http://127.0.0.1:47825/callback \
|
|
134
|
-
--redirect-uri http://127.0.0.1:47826/callback
|
|
135
|
-
```
|
|
136
|
+
**Ory Network or OEL.** Either works. For an Ory Network project the URL is `https://<slug>.projects.oryapis.com`; for a self-hosted **Ory Enterprise License** deployment, point `--project-url` at that deployment's base URL and use the sign-in client id from its Agent Security configuration. Everything downstream — sign-in, checks, delegation — is identical.
|
|
136
137
|
|
|
137
|
-
|
|
138
|
+
**There is nothing else for you to create.** The sign-in client, the permission model, the per-tool grants and blocks, and the observe/enforce posture are all provisioned in the Console by someone with project access. At runtime the plugin only *reads* permissions — it has no write path into your project, which is why installing it needs no workspace privilege. Each Antigravity session registers its own identity automatically on first use.
|
|
138
139
|
|
|
139
|
-
|
|
140
|
+
Sign-in runs at the start of every session and never blocks — a declined, skipped, or timed-out login simply leaves that session without a user identity.
|
|
140
141
|
|
|
141
|
-
|
|
142
|
+
Settings are saved to `~/.config/ory-agent-plugins/config.json` and shared across all your Ory agent plugins; environment variables win when both are set. Running headless with an OAuth2 access token already? Set `ORY_USER_OAUTH2_TOKEN` and the browser step is skipped entirely.
|
|
142
143
|
|
|
143
144
|
## Commands
|
|
144
145
|
|
|
145
146
|
```
|
|
146
|
-
ory-agy install | uninstall
|
|
147
|
-
ory-agy status
|
|
148
|
-
ory-agy
|
|
149
|
-
ory-agy
|
|
150
|
-
ory-agy
|
|
151
|
-
ory-agy
|
|
152
|
-
ory-agy
|
|
147
|
+
ory-agy install | uninstall Install (add --project-url to also connect Agent Security) / remove
|
|
148
|
+
ory-agy status Show configuration, identities, permission coverage, recent activity
|
|
149
|
+
ory-agy permissions <cmd> status (read-only; grants + posture live in the Ory Console)
|
|
150
|
+
ory-agy configure <flags> Connect a project (--project-url) or --disconnect
|
|
151
|
+
ory-agy agent <status|unregister> Manage the agent's own auto-created identity
|
|
152
|
+
ory-agy local <up|down|status|…> Run / manage a local Ory in Docker
|
|
153
|
+
ory-agy version Print plugin, core, and Node versions (--json for machine-readable)
|
|
153
154
|
ory-agy-setup Install into .agents/ directly (no agy binary)
|
|
154
155
|
```
|
|
155
156
|
|
|
@@ -158,10 +159,9 @@ All prefixed with `npx -y -p @ory/antigravity`.
|
|
|
158
159
|
## Troubleshooting
|
|
159
160
|
|
|
160
161
|
- **`/ory:local-up` fails** — make sure Docker is running and ports `4000`, `4455`, and `16686` are free.
|
|
161
|
-
- **Browser sign-in loops** — reset with `ory-agy agent unregister` and try again.
|
|
162
|
-
- **
|
|
163
|
-
- **A tool was unexpectedly blocked** — because Antigravity blocks by default, first confirm the hook is answering: set `ORY_AGENT_DEBUG=true` and check the logs for hook errors. Then verify the user has `use` on the tool with `permissions
|
|
164
|
-
- **Want to see what's happening** — `npx -y -p @ory/antigravity ory-agy status` for a snapshot, `npx -y -p @ory/antigravity ory-agy watch` for the live trace stream, or set `ORY_AGENT_DEBUG=true` for a verbose log. Traces and logs live under `~/.config/ory-agent-plugins/antigravity/` (see [See what's happening](#see-whats-happening)).
|
|
162
|
+
- **Browser sign-in loops** (after connecting) — reset with `ory-agy agent unregister` and try again.
|
|
163
|
+
- **Running an older CLI than expected** — `npx -p @ory/antigravity` (no version pin) reuses a previously-downloaded copy instead of re-resolving to the latest. The install banner prints the running version; confirm it with `npx -y -p @ory/antigravity ory-agy version`. To force the current release, clear the cache and reinstall: `rm -rf ~/.npm/_npx` then `npx -y -p @ory/antigravity ory-agy install`. Pinning an exact version (`@ory/antigravity@<version>`) also bypasses the cached copy.
|
|
164
|
+
- **A tool was unexpectedly blocked** — because Antigravity blocks by default, first confirm the hook is answering: set `ORY_AGENT_DEBUG=true` and check the logs for hook errors. Then verify the user has `use` on the tool with `permissions`.
|
|
165
165
|
|
|
166
166
|
## Learn more
|
|
167
167
|
|
package/dist/cli/assets.d.ts
CHANGED
|
@@ -17,8 +17,19 @@
|
|
|
17
17
|
* hooks.json shape (verified): top-level rule id → event name → array of
|
|
18
18
|
* `{ matcher, hooks: [{ type, command, timeout }] }`.
|
|
19
19
|
*/
|
|
20
|
+
import { type PreparedRuntime } from "@ory/argus";
|
|
20
21
|
/** Plugin / rule / MCP-server id. */
|
|
21
22
|
export declare const PLUGIN_NAME = "ory";
|
|
23
|
+
/**
|
|
24
|
+
* Resolve this install's runtime and write its shims. Both entry points (the
|
|
25
|
+
* `agy plugin install` path in `main.ts` and the manual `.agents` fallback in
|
|
26
|
+
* `setup.ts`) go through here so they wire the same runtime.
|
|
27
|
+
*
|
|
28
|
+
* Antigravity **fails closed**: a hook that crashes or answers with malformed
|
|
29
|
+
* output blocks the tool. So the shim's runtime-missing path has to emit an
|
|
30
|
+
* explicit allow, matching what the hook itself returns on every error path.
|
|
31
|
+
*/
|
|
32
|
+
export declare function wireAntigravityRuntime(args: readonly string[]): PreparedRuntime;
|
|
22
33
|
/**
|
|
23
34
|
* Write the full Ory plugin bundle under `root`. Used both for the
|
|
24
35
|
* bundle handed to `agy plugin install` and for the manual `.agents`
|
|
@@ -31,7 +42,7 @@ export declare const PLUGIN_NAME = "ory";
|
|
|
31
42
|
* bundle dir the merge is a no-op over a fresh tree, so the same code
|
|
32
43
|
* path is used everywhere.)
|
|
33
44
|
*/
|
|
34
|
-
export declare function installAntigravityOryAssets(root: string): void;
|
|
45
|
+
export declare function installAntigravityOryAssets(root: string, runtime: PreparedRuntime): void;
|
|
35
46
|
/**
|
|
36
47
|
* Remove every Ory skill, command, and bundle entry written under `root`.
|
|
37
48
|
*
|
package/dist/cli/assets.js
CHANGED
|
@@ -53,17 +53,35 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
53
53
|
})();
|
|
54
54
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
55
55
|
exports.PLUGIN_NAME = void 0;
|
|
56
|
+
exports.wireAntigravityRuntime = wireAntigravityRuntime;
|
|
56
57
|
exports.installAntigravityOryAssets = installAntigravityOryAssets;
|
|
57
58
|
exports.uninstallAntigravityOryAssets = uninstallAntigravityOryAssets;
|
|
58
59
|
const fs = __importStar(require("node:fs"));
|
|
59
60
|
const path = __importStar(require("node:path"));
|
|
60
61
|
const argus_1 = require("@ory/argus");
|
|
61
62
|
const PACKAGE_NAME = "@ory/antigravity";
|
|
62
|
-
const HOOK_BIN_NAME = "ory-agy-hook";
|
|
63
|
-
const MCP_PACKAGE_NAME = "@ory/mcp-server";
|
|
64
|
-
const MCP_BIN_NAME = "ory-mcp-server";
|
|
65
63
|
/** Plugin / rule / MCP-server id. */
|
|
66
64
|
exports.PLUGIN_NAME = "ory";
|
|
65
|
+
const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
|
|
66
|
+
/**
|
|
67
|
+
* Resolve this install's runtime and write its shims. Both entry points (the
|
|
68
|
+
* `agy plugin install` path in `main.ts` and the manual `.agents` fallback in
|
|
69
|
+
* `setup.ts`) go through here so they wire the same runtime.
|
|
70
|
+
*
|
|
71
|
+
* Antigravity **fails closed**: a hook that crashes or answers with malformed
|
|
72
|
+
* output blocks the tool. So the shim's runtime-missing path has to emit an
|
|
73
|
+
* explicit allow, matching what the hook itself returns on every error path.
|
|
74
|
+
*/
|
|
75
|
+
function wireAntigravityRuntime(args) {
|
|
76
|
+
return (0, argus_1.wireRuntime)({
|
|
77
|
+
harness: "antigravity",
|
|
78
|
+
packageName: PACKAGE_NAME,
|
|
79
|
+
packageRoot: PACKAGE_ROOT,
|
|
80
|
+
installCommand: "npx -y -p @ory/antigravity ory-agy install",
|
|
81
|
+
args,
|
|
82
|
+
fallbackStdout: JSON.stringify({ allow_tool: true }),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
67
85
|
const RENDER_OPTS = {
|
|
68
86
|
binName: "ory-agy",
|
|
69
87
|
packageName: PACKAGE_NAME,
|
|
@@ -82,23 +100,28 @@ function readPackageVersion() {
|
|
|
82
100
|
return parsed.version;
|
|
83
101
|
}
|
|
84
102
|
/**
|
|
85
|
-
* Render hooks.json. The
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
* tool name on the tool events.
|
|
103
|
+
* Render hooks.json. The command points at the runtime shim resolved at
|
|
104
|
+
* install time, so no package resolution happens per tool call.
|
|
105
|
+
* `matcher: ".*"` matches every tool name on the tool events.
|
|
89
106
|
*/
|
|
90
|
-
function renderHooks(
|
|
91
|
-
const command = `npx -y -p ${PACKAGE_NAME}@${version} ${HOOK_BIN_NAME}`;
|
|
107
|
+
function renderHooks(command) {
|
|
92
108
|
const entry = (matcher, timeout) => [
|
|
93
109
|
{ matcher, hooks: [{ type: "command", command, timeout }] },
|
|
94
110
|
];
|
|
111
|
+
// Antigravity's hook timeout is in SECONDS and it *validates* the value —
|
|
112
|
+
// "timeout must be between 5 and 120 seconds". The previous 10000/5000 were
|
|
113
|
+
// both far outside that range, so the values were never what they looked
|
|
114
|
+
// like. Core clamps to the 120s ceiling and asserts the session-start window
|
|
115
|
+
// still outlasts the login (#220).
|
|
116
|
+
const sessionStartSec = (0, argus_1.sessionStartHookTimeout)("antigravity", "seconds");
|
|
117
|
+
const toolSec = (0, argus_1.toolHookTimeout)("antigravity", "seconds");
|
|
95
118
|
return {
|
|
96
119
|
[exports.PLUGIN_NAME]: {
|
|
97
|
-
SessionStart: entry(".*",
|
|
98
|
-
SessionEnd: entry(".*",
|
|
99
|
-
PreToolUse: entry(".*",
|
|
100
|
-
PostToolUse: entry(".*",
|
|
101
|
-
Notification: entry(".*",
|
|
120
|
+
SessionStart: entry(".*", sessionStartSec),
|
|
121
|
+
SessionEnd: entry(".*", toolSec),
|
|
122
|
+
PreToolUse: entry(".*", toolSec),
|
|
123
|
+
PostToolUse: entry(".*", toolSec),
|
|
124
|
+
Notification: entry(".*", toolSec),
|
|
102
125
|
},
|
|
103
126
|
};
|
|
104
127
|
}
|
|
@@ -107,24 +130,16 @@ function renderManifest(version) {
|
|
|
107
130
|
return {
|
|
108
131
|
name: exports.PLUGIN_NAME,
|
|
109
132
|
version,
|
|
110
|
-
description: "Ory authentication, authorization, and
|
|
133
|
+
description: "Ory authentication, authorization, and activity auditing for Google Antigravity",
|
|
111
134
|
};
|
|
112
135
|
}
|
|
113
136
|
/**
|
|
114
137
|
* Render mcp_config.json. Antigravity uses the standard `mcpServers` map;
|
|
115
|
-
* stdio servers use `command` + `args` (remote servers would use
|
|
116
|
-
*
|
|
117
|
-
* reason as the hook command.
|
|
138
|
+
* stdio servers use `command` + `args` (remote servers would use `serverUrl`).
|
|
139
|
+
* The server is started from the resolved runtime via its shim.
|
|
118
140
|
*/
|
|
119
|
-
function renderMcpConfig(
|
|
120
|
-
return {
|
|
121
|
-
mcpServers: {
|
|
122
|
-
[exports.PLUGIN_NAME]: {
|
|
123
|
-
command: "npx",
|
|
124
|
-
args: ["-y", "-p", `${MCP_PACKAGE_NAME}@${version}`, MCP_BIN_NAME],
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
};
|
|
141
|
+
function renderMcpConfig(server) {
|
|
142
|
+
return { mcpServers: { [exports.PLUGIN_NAME]: server } };
|
|
128
143
|
}
|
|
129
144
|
/**
|
|
130
145
|
* Read a JSON file as a plain object. Returns `undefined` when the file
|
|
@@ -162,7 +177,7 @@ function isOryManifest(manifest) {
|
|
|
162
177
|
* bundle dir the merge is a no-op over a fresh tree, so the same code
|
|
163
178
|
* path is used everywhere.)
|
|
164
179
|
*/
|
|
165
|
-
function installAntigravityOryAssets(root) {
|
|
180
|
+
function installAntigravityOryAssets(root, runtime) {
|
|
166
181
|
const version = readPackageVersion();
|
|
167
182
|
fs.mkdirSync(root, { recursive: true });
|
|
168
183
|
const skillsDir = path.join(root, "skills");
|
|
@@ -178,19 +193,23 @@ function installAntigravityOryAssets(root) {
|
|
|
178
193
|
// any rules the user has defined alongside it.
|
|
179
194
|
const hooksPath = path.join(root, "hooks.json");
|
|
180
195
|
const hooks = readJsonObject(hooksPath) ?? {};
|
|
181
|
-
hooks[exports.PLUGIN_NAME] = renderHooks(
|
|
196
|
+
hooks[exports.PLUGIN_NAME] = renderHooks((0, argus_1.requireHookCommand)(runtime))[exports.PLUGIN_NAME];
|
|
182
197
|
writeJson(hooksPath, hooks);
|
|
183
198
|
// mcp_config.json — replace only the Ory server entry under `mcpServers`,
|
|
184
|
-
// preserving other servers and any unrelated top-level keys.
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
199
|
+
// preserving other servers and any unrelated top-level keys. Skipped when
|
|
200
|
+
// the runtime ships no MCP server: better no registration than one that
|
|
201
|
+
// cannot start.
|
|
202
|
+
if (runtime.mcpServer) {
|
|
203
|
+
const mcpPath = path.join(root, "mcp_config.json");
|
|
204
|
+
const mcp = readJsonObject(mcpPath) ?? {};
|
|
205
|
+
const existingServers = mcp.mcpServers;
|
|
206
|
+
const servers = existingServers && typeof existingServers === "object" && !Array.isArray(existingServers)
|
|
207
|
+
? existingServers
|
|
208
|
+
: {};
|
|
209
|
+
servers[exports.PLUGIN_NAME] = renderMcpConfig(runtime.mcpServer).mcpServers[exports.PLUGIN_NAME];
|
|
210
|
+
mcp.mcpServers = servers;
|
|
211
|
+
writeJson(mcpPath, mcp);
|
|
212
|
+
}
|
|
194
213
|
// plugin.json — only write over a manifest that is Ory's own (or absent).
|
|
195
214
|
const manifestPath = path.join(root, "plugin.json");
|
|
196
215
|
if (!fs.existsSync(manifestPath) || isOryManifest(readJsonObject(manifestPath))) {
|
package/dist/cli/main.js
CHANGED
|
@@ -61,8 +61,18 @@ function agyAvailable() {
|
|
|
61
61
|
}
|
|
62
62
|
function main() {
|
|
63
63
|
const [command, ...args] = process.argv.slice(2);
|
|
64
|
+
// `--help` after a matched subcommand: print usage, do nothing else.
|
|
65
|
+
// Without this the switch below ignores the flag and `install --help`
|
|
66
|
+
// performs a real install (#221).
|
|
67
|
+
if ((0, argus_1.shouldPrintHelp)(command, args))
|
|
68
|
+
return help();
|
|
64
69
|
switch (command) {
|
|
65
70
|
case "install":
|
|
71
|
+
if (args.includes("--print")) {
|
|
72
|
+
process.argv = ["node", "setup.js", ...args];
|
|
73
|
+
require("./setup.js");
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
66
76
|
(0, argus_1.beginDeferNextSteps)();
|
|
67
77
|
install(args);
|
|
68
78
|
(0, argus_1.runPostInstall)("ory-agy", "antigravity", args).then(() => process.exit(0), (err) => {
|
|
@@ -72,7 +82,10 @@ function main() {
|
|
|
72
82
|
break;
|
|
73
83
|
case "uninstall":
|
|
74
84
|
uninstall(args);
|
|
75
|
-
(0, argus_1.clearCredentialsForUninstall)(
|
|
85
|
+
(0, argus_1.clearCredentialsForUninstall)({
|
|
86
|
+
harness: "antigravity",
|
|
87
|
+
purge: args.includes("--purge"),
|
|
88
|
+
}).then(() => process.exit(0), (err) => {
|
|
76
89
|
console.error(err.message ?? err);
|
|
77
90
|
process.exit(1);
|
|
78
91
|
});
|
|
@@ -81,7 +94,7 @@ function main() {
|
|
|
81
94
|
(0, argus_1.runConfigureCommand)("ory-agy", args);
|
|
82
95
|
break;
|
|
83
96
|
case "agent":
|
|
84
|
-
(0, argus_1.runAgentCommand)("ory-agy", args).then((code) => process.exit(code), (err) => {
|
|
97
|
+
(0, argus_1.runAgentCommand)("ory-agy", "antigravity", args).then((code) => process.exit(code), (err) => {
|
|
85
98
|
console.error(err.message ?? err);
|
|
86
99
|
process.exit(1);
|
|
87
100
|
});
|
|
@@ -101,15 +114,15 @@ function main() {
|
|
|
101
114
|
process.exit(1);
|
|
102
115
|
});
|
|
103
116
|
break;
|
|
117
|
+
case "watch":
|
|
118
|
+
(0, argus_1.runWatchCommand)("ory-agy", "antigravity", args).then((code) => process.exit(code), (err) => { console.error(err.message ?? err); process.exit(1); });
|
|
119
|
+
break;
|
|
104
120
|
case "local":
|
|
105
121
|
(0, argus_1.runLocalCommand)("ory-agy", args).catch((err) => {
|
|
106
122
|
console.error(err.message ?? err);
|
|
107
123
|
process.exit(1);
|
|
108
124
|
});
|
|
109
125
|
break;
|
|
110
|
-
case "watch":
|
|
111
|
-
(0, argus_1.runWatchCommand)("antigravity", args);
|
|
112
|
-
break;
|
|
113
126
|
case "version":
|
|
114
127
|
case "--version":
|
|
115
128
|
case "-v":
|
|
@@ -128,10 +141,19 @@ function main() {
|
|
|
128
141
|
}
|
|
129
142
|
}
|
|
130
143
|
function install(args) {
|
|
144
|
+
// Resolve the runtime first: the bundle's hooks.json / mcp_config.json point
|
|
145
|
+
// at the shims it writes.
|
|
146
|
+
const runtime = (0, assets_js_1.wireAntigravityRuntime)(args);
|
|
147
|
+
console.log(runtime.target.kind === "linked"
|
|
148
|
+
? `Runtime: linked to ${runtime.target.packageDir} (dev)`
|
|
149
|
+
: `Runtime: ${runtime.target.packageName}@${runtime.target.version} in ${runtime.target.storeDir}`);
|
|
150
|
+
for (const pruned of runtime.prunedStores) {
|
|
151
|
+
console.log(` Removed stale runtime: ${pruned}`);
|
|
152
|
+
}
|
|
131
153
|
// Materialize the bundle (plugin.json + hooks.json + mcp_config.json +
|
|
132
154
|
// skills + commands) before handing it to `agy plugin install`.
|
|
133
155
|
console.log("Materializing the Ory plugin bundle...");
|
|
134
|
-
(0, assets_js_1.installAntigravityOryAssets)(BUNDLE_DIR);
|
|
156
|
+
(0, assets_js_1.installAntigravityOryAssets)(BUNDLE_DIR, runtime);
|
|
135
157
|
if (agyAvailable()) {
|
|
136
158
|
console.log("Installing Ory plugin into Antigravity...");
|
|
137
159
|
const result = (0, node_child_process_1.spawnSync)(AGY, ["plugin", "install", BUNDLE_DIR], {
|
|
@@ -176,7 +198,7 @@ function printQuickstart() {
|
|
|
176
198
|
console.log("Quickstart:");
|
|
177
199
|
console.log(" 1. Inside Antigravity, run /ory:local-up to start a local Ory stack.");
|
|
178
200
|
console.log(' 2. Ask Antigravity "add Ory auth to this app" to scaffold login/signup.');
|
|
179
|
-
console.log(" 3. Set
|
|
201
|
+
console.log(" 3. Set ORY_OAUTH2_CLIENT_ID so the per-session PKCE browser login can complete.");
|
|
180
202
|
}
|
|
181
203
|
async function status() {
|
|
182
204
|
await (0, argus_1.runStatusCommand)("ory-agy", "antigravity", {
|
|
@@ -202,10 +224,11 @@ Commands:
|
|
|
202
224
|
uninstall Remove the Ory plugin from Antigravity
|
|
203
225
|
configure Set or view Ory project URL and credentials
|
|
204
226
|
agent <cmd> Manage the agent's OAuth2 (DCR) identity (status, unregister)
|
|
205
|
-
permissions
|
|
227
|
+
permissions Show the live permission mode and per-tool coverage
|
|
206
228
|
setup [--project-dir] Write the plugin bundle into .agents directly (fallback)
|
|
207
229
|
status Show plugin status and configuration
|
|
208
|
-
watch [
|
|
230
|
+
watch [--json] [--lines <count>]
|
|
231
|
+
Follow the live activity/debug log
|
|
209
232
|
version Show version and the ory-agent-plugins build commit
|
|
210
233
|
local <cmd> Manage local Ory dev environment (up, down, status, seed, ...)
|
|
211
234
|
|
package/dist/cli/setup.js
CHANGED
|
@@ -74,9 +74,13 @@ function main() {
|
|
|
74
74
|
}
|
|
75
75
|
(0, assets_js_1.uninstallAntigravityOryAssets)(agentsDir);
|
|
76
76
|
console.log(`Removed Ory plugin bundle from ${agentsDir}`);
|
|
77
|
+
(0, argus_1.removeRuntimeWiring)("antigravity");
|
|
78
|
+
for (const pruned of (0, argus_1.pruneRuntimeStores)()) {
|
|
79
|
+
console.log(` Removed runtime: ${pruned}`);
|
|
80
|
+
}
|
|
77
81
|
return;
|
|
78
82
|
}
|
|
79
|
-
(0, assets_js_1.installAntigravityOryAssets)(agentsDir);
|
|
83
|
+
(0, assets_js_1.installAntigravityOryAssets)(agentsDir, (0, assets_js_1.wireAntigravityRuntime)(process.argv.slice(2)));
|
|
80
84
|
console.log(`Ory plugin bundle installed to ${agentsDir}`);
|
|
81
85
|
(0, argus_1.printNextSteps)("Google Antigravity", "npx -y -p @ory/antigravity ory-agy-setup --uninstall", { binName: "ory-agy", harness: "antigravity" });
|
|
82
86
|
}
|
|
@@ -85,7 +89,10 @@ main();
|
|
|
85
89
|
// stored Ory credentials. When required by the plugin's main CLI, that
|
|
86
90
|
// command owns the purge, so the `require.main` guard prevents a double run.
|
|
87
91
|
if (require.main === module && process.argv.includes("--uninstall")) {
|
|
88
|
-
(0, argus_1.clearCredentialsForUninstall)(
|
|
92
|
+
(0, argus_1.clearCredentialsForUninstall)({
|
|
93
|
+
harness: "antigravity",
|
|
94
|
+
purge: process.argv.includes("--purge"),
|
|
95
|
+
}).then(() => process.exit(0), (err) => {
|
|
89
96
|
console.error(err.message ?? err);
|
|
90
97
|
process.exit(1);
|
|
91
98
|
});
|
package/dist/emit.d.ts
CHANGED
|
@@ -2,9 +2,7 @@
|
|
|
2
2
|
* Write-once stdout emitter for the Antigravity hook.
|
|
3
3
|
*
|
|
4
4
|
* Antigravity parses the hook's stdout as a single JSON document and
|
|
5
|
-
* FAILS CLOSED on malformed output. If two code paths both write
|
|
6
|
-
* the main path writes the decision and then `tracer.shutdown()` rejects,
|
|
7
|
-
* landing in the top-level catch which writes FAIL_OPEN again), stdout
|
|
5
|
+
* FAILS CLOSED on malformed output. If two code paths both write, stdout
|
|
8
6
|
* carries two concatenated JSON documents and the harness blocks the
|
|
9
7
|
* tool. This factory guarantees exactly one write ever reaches stdout.
|
|
10
8
|
*/
|
package/dist/emit.js
CHANGED
|
@@ -3,9 +3,7 @@
|
|
|
3
3
|
* Write-once stdout emitter for the Antigravity hook.
|
|
4
4
|
*
|
|
5
5
|
* Antigravity parses the hook's stdout as a single JSON document and
|
|
6
|
-
* FAILS CLOSED on malformed output. If two code paths both write
|
|
7
|
-
* the main path writes the decision and then `tracer.shutdown()` rejects,
|
|
8
|
-
* landing in the top-level catch which writes FAIL_OPEN again), stdout
|
|
6
|
+
* FAILS CLOSED on malformed output. If two code paths both write, stdout
|
|
9
7
|
* carries two concatenated JSON documents and the harness blocks the
|
|
10
8
|
* tool. This factory guarantees exactly one write ever reaches stdout.
|
|
11
9
|
*/
|
package/dist/handlers.js
CHANGED
|
@@ -19,11 +19,7 @@ async function handleHookEvent(input, client, deps = {}) {
|
|
|
19
19
|
sessionId: input.session_id,
|
|
20
20
|
toolName: input.tool_name,
|
|
21
21
|
});
|
|
22
|
-
client.
|
|
23
|
-
traceId: (0, argus_1.deriveTraceId)(input.session_id),
|
|
24
|
-
sessionId: input.session_id,
|
|
25
|
-
});
|
|
26
|
-
try {
|
|
22
|
+
return (0, argus_1.withHookContext)(client, { sessionId: input.session_id }, async () => {
|
|
27
23
|
switch (event) {
|
|
28
24
|
case "SessionStart":
|
|
29
25
|
return await handleSessionStart(input, client, deps);
|
|
@@ -38,15 +34,12 @@ async function handleHookEvent(input, client, deps = {}) {
|
|
|
38
34
|
return handleSessionEnd(input, client);
|
|
39
35
|
default:
|
|
40
36
|
client.logger.debug("hook.passthrough", { event });
|
|
41
|
-
client.
|
|
37
|
+
client.logger.activity("hook.passthrough", "skipped", {
|
|
42
38
|
attributes: { event },
|
|
43
39
|
});
|
|
44
40
|
return ALLOW;
|
|
45
41
|
}
|
|
46
|
-
}
|
|
47
|
-
finally {
|
|
48
|
-
client.tracer.clearContext();
|
|
49
|
-
}
|
|
42
|
+
});
|
|
50
43
|
}
|
|
51
44
|
// ─── SessionStart ───────────────────────────────────────────────────
|
|
52
45
|
async function handleSessionStart(input, client, deps) {
|
|
@@ -55,91 +48,15 @@ async function handleSessionStart(input, client, deps) {
|
|
|
55
48
|
model: input.model,
|
|
56
49
|
source: input.source,
|
|
57
50
|
});
|
|
58
|
-
|
|
59
|
-
attributes: { model: input.model, source: input.source },
|
|
60
|
-
});
|
|
61
|
-
// User login (interactive PKCE / token refresh). Advisory on
|
|
62
|
-
// Antigravity: SessionStart has no verified hard-block channel
|
|
63
|
-
// (`allow_tool` only governs PreToolUse), so allowBlock is false — the
|
|
64
|
-
// gate runs, refreshes tokens, and emits the audit span, but the
|
|
65
|
-
// session always proceeds. Tool-call enforcement is unaffected.
|
|
66
|
-
const userGate = deps.userLogin ?? argus_1.ensureUserAuthenticated;
|
|
67
|
-
const decision = await userGate(client, {
|
|
51
|
+
await (0, argus_1.sessionStart)(client, {
|
|
68
52
|
binName: "ory-agy",
|
|
69
53
|
harness: HARNESS,
|
|
70
|
-
|
|
54
|
+
userLogin: deps.userLogin,
|
|
55
|
+
agentGate: deps.agentGate,
|
|
56
|
+
activityAttributes: { model: input.model, source: input.source },
|
|
71
57
|
});
|
|
72
|
-
// Resolve the agent identity regardless of how user auth went — never
|
|
73
|
-
// blocks; attaches the agent's bearer token to outgoing Ory calls.
|
|
74
|
-
const agentGate = deps.agentGate ?? argus_1.ensureAgentIdentity;
|
|
75
|
-
await agentGate(client, { projectUrl: (0, argus_1.resolveConfig)().projectUrl, harness: HARNESS });
|
|
76
|
-
// Record the user→agent delegation (audit-trail only; fail-open).
|
|
77
|
-
// Written at most once per install.
|
|
78
|
-
await (0, argus_1.writeUserDelegatesAgent)(client);
|
|
79
|
-
if (decision.mode !== "disabled") {
|
|
80
|
-
return {};
|
|
81
|
-
}
|
|
82
|
-
// Legacy fallthrough: honor pre-supplied tokens for backward compat.
|
|
83
|
-
const resolved = (0, argus_1.resolveConfig)();
|
|
84
|
-
if (resolved.auditOnly || !resolved.projectUrl) {
|
|
85
|
-
return {};
|
|
86
|
-
}
|
|
87
|
-
const sessionToken = process.env.ORY_SESSION_TOKEN;
|
|
88
|
-
const oauth2Token = process.env.ORY_OAUTH2_TOKEN;
|
|
89
|
-
if (sessionToken) {
|
|
90
|
-
await verifySessionToken(sessionToken, client);
|
|
91
|
-
}
|
|
92
|
-
else if (oauth2Token) {
|
|
93
|
-
await verifyOAuth2Token(oauth2Token, client);
|
|
94
|
-
}
|
|
95
|
-
else {
|
|
96
|
-
client.logger.warn("session.no_credentials", {
|
|
97
|
-
message: "No user login enabled and neither ORY_SESSION_TOKEN nor " +
|
|
98
|
-
"ORY_OAUTH2_TOKEN is set. Skipping authentication.",
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
58
|
return {};
|
|
102
59
|
}
|
|
103
|
-
async function verifySessionToken(token, client) {
|
|
104
|
-
try {
|
|
105
|
-
const session = await client.verifySession(token);
|
|
106
|
-
if (!session.active) {
|
|
107
|
-
client.logger.warn("session.inactive", {
|
|
108
|
-
message: "Ory session is not active. Re-authenticate to enable auth checks.",
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
catch (err) {
|
|
113
|
-
const oryErr = err;
|
|
114
|
-
client.logger.warn("session.verify_failed", {
|
|
115
|
-
code: oryErr.code,
|
|
116
|
-
message: oryErr.message,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
async function verifyOAuth2Token(token, client) {
|
|
121
|
-
try {
|
|
122
|
-
const tokenInfo = await client.introspectToken(token);
|
|
123
|
-
if (!tokenInfo.active) {
|
|
124
|
-
client.logger.warn("oauth2.token_inactive", {
|
|
125
|
-
message: "Ory OAuth2 token is not active.",
|
|
126
|
-
});
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
client.logger.info("oauth2.session_authenticated", {
|
|
130
|
-
clientId: tokenInfo.clientId,
|
|
131
|
-
subject: tokenInfo.subject,
|
|
132
|
-
scope: tokenInfo.scope,
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
catch (err) {
|
|
136
|
-
const oryErr = err;
|
|
137
|
-
client.logger.warn("oauth2.introspect_failed", {
|
|
138
|
-
code: oryErr.code,
|
|
139
|
-
message: oryErr.message,
|
|
140
|
-
});
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
60
|
// ─── PreToolUse ─────────────────────────────────────────────────────
|
|
144
61
|
async function handlePreToolUse(input, client) {
|
|
145
62
|
const toolName = input.tool_name ?? "unknown";
|
|
@@ -148,61 +65,19 @@ async function handlePreToolUse(input, client) {
|
|
|
148
65
|
toolName,
|
|
149
66
|
toolInput: input.tool_input,
|
|
150
67
|
});
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
}
|
|
159
|
-
const subject = (0, argus_1.resolveUserSubject)(client, `session:${input.session_id}`);
|
|
160
|
-
const subjectId = (0, argus_1.subjectLabel)(subject);
|
|
161
|
-
const namespace = resolveNamespace();
|
|
162
|
-
try {
|
|
163
|
-
const outcome = await (0, argus_1.gateToolCall)(client, {
|
|
164
|
-
harness: HARNESS,
|
|
165
|
-
toolName,
|
|
166
|
-
check: { namespace, object: toolName, relation: "use", ...subject },
|
|
167
|
-
spanAttributes: { toolName },
|
|
168
|
-
});
|
|
169
|
-
// Interactive tools surface UI to the user; gateToolCall already
|
|
170
|
-
// recorded the user.interaction span. Let the harness prompt.
|
|
171
|
-
if (outcome.kind === "interactive") {
|
|
172
|
-
return ALLOW;
|
|
173
|
-
}
|
|
174
|
-
if (outcome.kind === "fail_open") {
|
|
175
|
-
return failOpen(outcome.error, toolName, client);
|
|
176
|
-
}
|
|
177
|
-
const attrs = { toolName, ...inputSummary };
|
|
178
|
-
const decisionAttrs = outcome.spanAttributes;
|
|
179
|
-
if (outcome.kind === "allow") {
|
|
180
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
181
|
-
attributes: { ...attrs, ...decisionAttrs, allowed: true },
|
|
182
|
-
});
|
|
183
|
-
return ALLOW;
|
|
184
|
-
}
|
|
185
|
-
if (outcome.kind === "observe") {
|
|
186
|
-
client.tracer.record("tool.block", "denied", {
|
|
187
|
-
attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
|
|
188
|
-
});
|
|
189
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
190
|
-
attributes: { ...attrs, ...decisionAttrs, allowed: false, observed: true },
|
|
191
|
-
});
|
|
192
|
-
return ALLOW;
|
|
193
|
-
}
|
|
194
|
-
// deny → block the tool (enforce mode).
|
|
195
|
-
client.tracer.record("tool.block", "denied", {
|
|
196
|
-
attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
|
|
197
|
-
});
|
|
68
|
+
const result = await (0, argus_1.gate)(client, {
|
|
69
|
+
harness: HARNESS,
|
|
70
|
+
toolName,
|
|
71
|
+
toolArgs: input.tool_input,
|
|
72
|
+
subjectFallback: `session:${input.session_id}`,
|
|
73
|
+
});
|
|
74
|
+
if (result.blocked) {
|
|
198
75
|
return {
|
|
199
76
|
allow_tool: false,
|
|
200
|
-
deny_reason:
|
|
77
|
+
deny_reason: result.denialMessage,
|
|
201
78
|
};
|
|
202
79
|
}
|
|
203
|
-
|
|
204
|
-
return failOpen(err, toolName, client);
|
|
205
|
-
}
|
|
80
|
+
return ALLOW;
|
|
206
81
|
}
|
|
207
82
|
// ─── PostToolUse ────────────────────────────────────────────────────
|
|
208
83
|
async function handlePostToolUse(input, client) {
|
|
@@ -213,36 +88,31 @@ async function handlePostToolUse(input, client) {
|
|
|
213
88
|
});
|
|
214
89
|
const failed = input.tool_error !== undefined && input.tool_error !== null;
|
|
215
90
|
if (failed) {
|
|
216
|
-
client.
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
: "[object]",
|
|
222
|
-
...(0, argus_1.summarizeToolInput)(toolName, input.tool_input),
|
|
223
|
-
},
|
|
91
|
+
client.logger.debug("lifecycle.post_tool_use.error", { error: input.tool_error });
|
|
92
|
+
(0, argus_1.fail)(client, {
|
|
93
|
+
toolName,
|
|
94
|
+
input: input.tool_input,
|
|
95
|
+
error: input.tool_error,
|
|
224
96
|
});
|
|
225
97
|
return {};
|
|
226
98
|
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
...(0, argus_1.summarizeToolOutput)(toolName, input.tool_response),
|
|
232
|
-
},
|
|
99
|
+
(0, argus_1.complete)(client, {
|
|
100
|
+
toolName,
|
|
101
|
+
input: input.tool_input,
|
|
102
|
+
output: input.tool_response,
|
|
233
103
|
});
|
|
234
104
|
return {};
|
|
235
105
|
}
|
|
236
106
|
// PreInvocation / PostInvocation bracket each agent turn (not user
|
|
237
107
|
// prompts). We don't model them specially — they fall through to the
|
|
238
|
-
// default `hook.passthrough`
|
|
108
|
+
// default `hook.passthrough` activity event in handleHookEvent.
|
|
239
109
|
// ─── Notification ───────────────────────────────────────────────────
|
|
240
110
|
function handleNotification(input, client) {
|
|
241
111
|
client.logger.info("lifecycle.notification", {
|
|
242
112
|
sessionId: input.session_id,
|
|
243
113
|
messageLen: input.message?.length,
|
|
244
114
|
});
|
|
245
|
-
client.
|
|
115
|
+
client.logger.activity("user.interaction", "ok", {
|
|
246
116
|
attributes: { harness: HARNESS, kind: "notification" },
|
|
247
117
|
});
|
|
248
118
|
return {};
|
|
@@ -253,39 +123,8 @@ function handleSessionEnd(input, client) {
|
|
|
253
123
|
sessionId: input.session_id,
|
|
254
124
|
reason: input.reason,
|
|
255
125
|
});
|
|
256
|
-
client.
|
|
126
|
+
client.logger.activity("session.end", "ok", {
|
|
257
127
|
attributes: { reason: input.reason },
|
|
258
128
|
});
|
|
259
129
|
return {};
|
|
260
130
|
}
|
|
261
|
-
// ─── Helpers ────────────────────────────────────────────────────────
|
|
262
|
-
function resolveNamespace() {
|
|
263
|
-
return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
|
|
264
|
-
}
|
|
265
|
-
/**
|
|
266
|
-
* Fail-open on a permission-check error: log and allow the tool. Critical
|
|
267
|
-
* on Antigravity — returning anything other than `allow_tool: true` here
|
|
268
|
-
* would block the user on a transient Ory/network blip.
|
|
269
|
-
*/
|
|
270
|
-
function failOpen(oryErr, toolName, client) {
|
|
271
|
-
if (oryErr.code === "network_error") {
|
|
272
|
-
client.logger.warn("permission.network_error", {
|
|
273
|
-
toolName,
|
|
274
|
-
message: "Ory unreachable, failing open",
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
else if (oryErr.code === "rate_limited") {
|
|
278
|
-
client.logger.warn("permission.rate_limited", {
|
|
279
|
-
toolName,
|
|
280
|
-
message: "Ory rate limited, failing open",
|
|
281
|
-
});
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
client.logger.error("permission.check.error", {
|
|
285
|
-
toolName,
|
|
286
|
-
code: oryErr.code,
|
|
287
|
-
message: oryErr.message,
|
|
288
|
-
});
|
|
289
|
-
}
|
|
290
|
-
return ALLOW;
|
|
291
|
-
}
|
package/dist/hook.js
CHANGED
|
@@ -22,12 +22,21 @@ const emit_js_1 = require("./emit.js");
|
|
|
22
22
|
const handlers_js_1 = require("./handlers.js");
|
|
23
23
|
/** The fail-open response: explicitly allow, so a crash never blocks. */
|
|
24
24
|
const FAIL_OPEN = { allow_tool: true };
|
|
25
|
+
const EVENT_FLUSH_TIMEOUT_MS = 750;
|
|
26
|
+
let client;
|
|
27
|
+
async function flushEvents() {
|
|
28
|
+
try {
|
|
29
|
+
await client?.flushEvents(EVENT_FLUSH_TIMEOUT_MS);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// The hook decision must not depend on activity delivery.
|
|
33
|
+
}
|
|
34
|
+
}
|
|
25
35
|
/**
|
|
26
36
|
* Single write-once gate shared by every exit path (normal, parse
|
|
27
37
|
* failure, and the top-level catch). Because Antigravity fails closed on
|
|
28
38
|
* malformed stdout, a double write (two concatenated JSON documents)
|
|
29
|
-
* would BLOCK the tool
|
|
30
|
-
* `tracer.shutdown()` then rejects into the top-level catch.
|
|
39
|
+
* would BLOCK the tool.
|
|
31
40
|
*/
|
|
32
41
|
const emit = (0, emit_js_1.createEmitOnce)();
|
|
33
42
|
/**
|
|
@@ -60,7 +69,7 @@ function readStdin() {
|
|
|
60
69
|
});
|
|
61
70
|
}
|
|
62
71
|
async function main() {
|
|
63
|
-
|
|
72
|
+
client = argus_1.OryAgentClient.fromEnv("antigravity");
|
|
64
73
|
const raw = await readStdin();
|
|
65
74
|
let input;
|
|
66
75
|
try {
|
|
@@ -70,7 +79,6 @@ async function main() {
|
|
|
70
79
|
client.logger.error("hook.stdin.parse_failed", { raw: raw.slice(0, 200) });
|
|
71
80
|
// Can't tell the event; allow to avoid wedging a PreToolUse call.
|
|
72
81
|
emit(FAIL_OPEN);
|
|
73
|
-
await client.tracer.shutdown();
|
|
74
82
|
process.exit(0);
|
|
75
83
|
}
|
|
76
84
|
let output;
|
|
@@ -85,14 +93,15 @@ async function main() {
|
|
|
85
93
|
output = FAIL_OPEN;
|
|
86
94
|
}
|
|
87
95
|
emit(output);
|
|
88
|
-
await
|
|
96
|
+
await flushEvents();
|
|
89
97
|
process.exit(0);
|
|
90
98
|
}
|
|
91
|
-
main().catch((err) => {
|
|
99
|
+
main().catch(async (err) => {
|
|
92
100
|
// Last-resort guard: allow rather than block, then exit cleanly. The
|
|
93
101
|
// write-once emitter makes this a no-op when the decision was already
|
|
94
|
-
// written before the rejection
|
|
102
|
+
// written before the rejection.
|
|
95
103
|
process.stderr.write(`[ory-agent] fatal: ${err}\n`);
|
|
96
104
|
emit(FAIL_OPEN);
|
|
105
|
+
await flushEvents();
|
|
97
106
|
process.exit(0);
|
|
98
107
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/antigravity",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Ory plugin for Google Antigravity: scaffolding skills, a local Ory instance, and authentication, authorization, and audit for every tool call",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/ory/ory-agent-plugins/tree/main/packages/antigravity",
|
|
@@ -41,8 +41,7 @@
|
|
|
41
41
|
"llm",
|
|
42
42
|
"mcp",
|
|
43
43
|
"mcp-server",
|
|
44
|
-
"
|
|
45
|
-
"distributed-tracing",
|
|
44
|
+
"activity-auditing",
|
|
46
45
|
"observability",
|
|
47
46
|
"kratos",
|
|
48
47
|
"keto",
|
|
@@ -53,6 +52,10 @@
|
|
|
53
52
|
"registry": "https://registry.npmjs.org/",
|
|
54
53
|
"provenance": true
|
|
55
54
|
},
|
|
55
|
+
"reova": {
|
|
56
|
+
"enabled": true,
|
|
57
|
+
"endpoint": "https://telemetry.reo.dev/data"
|
|
58
|
+
},
|
|
56
59
|
"main": "dist/index.js",
|
|
57
60
|
"types": "dist/index.d.ts",
|
|
58
61
|
"exports": {
|
|
@@ -68,12 +71,11 @@
|
|
|
68
71
|
},
|
|
69
72
|
"files": [
|
|
70
73
|
"dist",
|
|
71
|
-
"!dist/dev",
|
|
72
74
|
"!dist/**/*.tsbuildinfo"
|
|
73
75
|
],
|
|
74
76
|
"dependencies": {
|
|
75
|
-
"
|
|
76
|
-
"@ory/argus": "0.
|
|
77
|
+
"reova": "^0.7.0",
|
|
78
|
+
"@ory/argus": "1.0.0"
|
|
77
79
|
},
|
|
78
80
|
"devDependencies": {
|
|
79
81
|
"typescript": "^6.0.2",
|
|
@@ -87,7 +89,6 @@
|
|
|
87
89
|
"clean": "rm -rf dist *.tsbuildinfo",
|
|
88
90
|
"test": "vitest run",
|
|
89
91
|
"test:watch": "vitest",
|
|
90
|
-
"typecheck": "tsc --noEmit"
|
|
91
|
-
"dev": "node dist/dev/launcher.js"
|
|
92
|
+
"typecheck": "tsc --noEmit"
|
|
92
93
|
}
|
|
93
94
|
}
|