@ory/pi 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 CHANGED
@@ -2,9 +2,12 @@
2
2
 
3
3
  Security and developer experience for [Pi](https://pi.dev), powered by [Ory](https://ory.com).
4
4
 
5
- **Security.** Pi runs real actions on your machine — editing files, running shell commands, calling APIs. The plugin gives every session a verifiable identity (you sign in once; Pi and any sub-agents it spawns each get their own), checks every tool call against permissions you control, and records each decision as an audit trace you can ship to your observability stack. It starts in watch mode so nothing is blocked on day one, and if Ory is ever unreachable it steps aside rather than locking you out.
5
+ Pi 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
- **Developer experience.** A single command installs the plugin and walks you through connecting — choose Ory Network, a local Docker stack, or audit-only, and it wires up the project, sign-in client, login, and permissions for you. It also helps you build Ory into your own app: ask in plain language to scaffold login, registration, and recovery pages, run a local Ory, or manage identities and permissions through the bundled MCP server.
7
+ One command installs two independent halves:
8
+
9
+ - **Developer experience** — the Ory skill catalog, the local Ory dev stack, 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
  ## What you'll need
10
13
 
@@ -21,27 +24,21 @@ npm install @ory/pi
21
24
 
22
25
  ## Get started
23
26
 
24
- Then run one command. It installs the plugin and walks you through connecting:
27
+ Then run one command. It installs the plugin.:
25
28
 
26
29
  ```bash
27
30
  npx -y -p @ory/pi ory-pi install
28
31
  ```
29
32
 
30
- The installer drops a tiny loader at `<project>/.pi/extensions/ory.js` so Pi discovers the plugin, adds the Ory skills under `<project>/.pi/skills`, and then asks how you want to connect — **press Enter for the default**:
31
-
32
- - **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.
33
- - **Local** — run a complete Ory on your laptop with Docker. No account, no signup, no keys. Great for trying it out.
34
- - **Audit-only** — skip Ory entirely and just log what Pi does.
33
+ The installer drops a tiny loader at `<project>/.pi/extensions/ory.js` so Pi discovers the plugin and adds the Ory skills under `<project>/.pi/skills`. Add `--global` to install the loader for all projects (at `~/.pi/agent/extensions/ory.js`) instead of just this one.
35
34
 
36
- That's it. Confirm everything landed with:
35
+ Confirm everything landed with:
37
36
 
38
37
  ```bash
39
38
  npx -y -p @ory/pi ory-pi status
40
39
  ```
41
40
 
42
- `status` is your one-stop check: what's configured, who's signed in, which tools are covered by permissions, whether the loader is in place, and recent activity. Anything not set up yet shows as `(unset)`.
43
-
44
- Re-run install with `--reconfigure` to change your connection later, or `--no-configure` to skip the wizard. Add `--global` to install the loader for all projects (at `~/.pi/agent/extensions/ory.js`) instead of just this one.
41
+ `status` is your one-stop check: what's configured, who's signed in, which tools are covered by permissions, whether the loader is in place, and recent activity. Until you connect Agent Security, the identity and permission rows say so and name what's missing.
45
42
 
46
43
  <details>
47
44
  <summary>Prefer to let Pi manage the plugin?</summary>
@@ -52,17 +49,47 @@ Point Pi's own package installer at the npm package:
52
49
  pi install npm:@ory/pi
53
50
  ```
54
51
 
55
- This registers the extension through Pi's package manifest, but skips the guided setup — run `npx -y -p @ory/pi ory-pi install` (or `configure`) in a terminal afterwards to connect.
52
+ This registers the extension through Pi's package manifest. Run `npx -y -p @ory/pi ory-pi install` in a terminal afterwards to materialize the skills (and, later, `configure` if you want to connect).
56
53
 
57
54
  </details>
58
55
 
56
+ ## Skills and commands
57
+
58
+ Installing the plugin drops the full Ory playbook catalog into Pi. **Skills** are model-invoked — just say what you want in plain language and the matching one takes over.
59
+
60
+ | Skill | What it does for you |
61
+ |---|---|
62
+ | `ory-auth-setup` | Adds a complete auth system to your app — login, registration, recovery, verification, settings — on [Ory Elements](https://github.com/ory/elements) |
63
+ | `ory-login-flow` | Builds just the pages, wired to Ory's self-service flows |
64
+ | `ory-social-login` | "Sign in with…" for Google, GitHub, Apple, Microsoft, Discord, Slack, GitLab, Facebook |
65
+ | `ory-local-dev` | Develops and tests login/permission flows against a local Ory — no project, no account, offline |
66
+ | `ory-permissions-onboarding` | Walks a fresh install from observe mode to enforced per-tool permissions without getting blocked |
67
+ | `ory-build-agent` | Drops `@ory/argus` into an agent *you* own — Claude Agent SDK, OpenAI Agents, Mastra, Vercel AI, LangGraph/PydanticAI |
68
+ | `ory-build-integration` | Wires Ory into your app: Action webhooks, JWT validation at a gateway, live event streams |
69
+ | `ory-contribute-integration` | Authors and submits an integration to the public `ory/integrates` registry |
70
+ | `ory-e2b-sandbox` | Scaffolds an E2B sandbox template that boots with this plugin preinstalled |
71
+ | `ory-temporal-worker` | Scaffolds a Temporal TypeScript worker where every Activity is authenticated, authorized, and audited |
72
+
73
+ The local stack has its own playbooks — ask for them by name:
74
+
75
+ | Command | What it does |
76
+ |---|---|
77
+ | `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 |
78
+ | `ory-local-down` | Stops it, keeping your data volumes |
79
+ | `ory-temporal-up` | Starts a local Temporal dev server for the `ory-temporal-worker` scaffold |
80
+
81
+ 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`.
82
+
83
+ So: ask Pi *"add Ory login to this app"* and it scaffolds the pages, starts a local Ory, and wires them together.
84
+
59
85
  ## What you get
60
86
 
61
- Once connected, every tool Pi runs is governed by Ory — three things happen automatically:
87
+ Out of the box, every tool Pi runs produces a privacy-safe structured activity event in the unified local log.
62
88
 
63
- - **Who's driving.** You sign in once in your browser (a secure redirect flow — no passwords touch the plugin), and Pi gets its own identity that it registers automatically on first run. No tokens to copy around, and the "who did what" trail stays queryable later. (Pi's startup can't hard-block, so sign-in there is advisory — it still signs you in and logs it, but never stops the session.)
64
- - **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.
65
- - **A record of everything.** Every decision (allowed, denied, skipped) is logged as a trace you can send to a viewer like Jaeger, or just a file.
89
+ Once you connect to Ory Agent Security, two more things happen automatically:
90
+
91
+ - **Who's driving.** You sign in once in your browser (a secure redirect flow — no passwords touch the plugin), and each Pi session gets its own identity that it registers automatically on first use. No tokens to copy around, and the "who did what" trail stays queryable later. (Pi's startup can't hard-block, so sign-in there is advisory — it still signs you in and logs it, but never stops the session.)
92
+ - **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.
66
93
 
67
94
  If Ory is ever unreachable, the plugin gets out of the way and lets Pi keep working — so it can't lock you out.
68
95
 
@@ -70,58 +97,63 @@ If Ory is ever unreachable, the plugin gets out of the way and lets Pi keep work
70
97
 
71
98
  ### See what's happening
72
99
 
73
- Everything the plugin does is observable out of the box — no configuration required:
100
+ Everything the plugin does is observable out of the box:
74
101
 
75
- - **Status at a glance.** `npx -y -p @ory/pi ory-pi status` shows what's configured, who's signed in, how many built-in tools your permissions cover, and the most recent tool-call activity.
76
- - **Live traces.** Every tool call is recorded as an OpenTelemetry-style span. Watch them stream as the agent works:
102
+ - **Activity log.** Privacy-safe activity is always appended to `~/.config/ory-agent-plugins/pi/ory-agent-debug.log`. View events, decisions, and errors live with:
77
103
 
78
104
  ```bash
79
105
  npx -y -p @ory/pi ory-pi watch
80
106
  ```
81
107
 
82
- Spans are also written to `~/.config/ory-agent-plugins/pi/ory-agent-trace.ndjson` (NDJSON, one span per line) — tail that file, or point `OTEL_EXPORTER_OTLP_ENDPOINT` at a collector to ship them straight to Jaeger, Honeycomb, or Grafana.
83
- - **Debug log.** For a verbose play-by-play, set `ORY_AGENT_DEBUG=true`; structured logs land in `~/.config/ory-agent-plugins/pi/ory-agent-debug.log`.
108
+ Set `ORY_AGENT_LOG_FILE` to override the path; set it empty to disable file persistence.
109
+ - **Live debug.** Launch Pi 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.
84
110
 
85
111
  ### Ready to enforce?
86
112
 
87
- When the watch-mode logs look right, turn on blocking with one command (setup already granted you the built-in tools):
113
+ 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.
88
114
 
89
115
  ```bash
90
- npx -y -p @ory/pi ory-pi permissions enforce
116
+ npx -y -p @ory/pi ory-pi permissions # what the project grants, and the live mode
91
117
  ```
92
118
 
93
- Now a denied tool is actually blocked and Pi shows why (the plugin returns `{ block: true, reason }` on Pi's `tool_call` event). Go back to watch mode anytime with `permissions observe`. Use `permissions status` to see what's covered and `permissions bootstrap` to (re-)grant the built-in tools.
94
-
95
- ## Also: add login to your own app
119
+ Then a denied tool is actually blocked and Pi shows why (the plugin returns `{ block: true, reason }` on Pi's `tool_call` event).
96
120
 
97
- Beyond securing Pi, the plugin helps you build Ory into whatever you're working on. Ask Pi *"add Ory login to this app"* and it scaffolds the login, registration, recovery, and settings pages (using [Ory Elements](https://github.com/ory/elements)) wired to a local Ory — no signup or keys needed. Start that local Ory by asking Pi *"start the local Ory stack"* (the `ory-local-up` skill) — it prints a test email + password to sign in with — and tear it down with `ory-local-down`.
121
+ ## Connect to Ory Agent Security
98
122
 
99
- Bundled **skills** (just ask in plain language) cover more: `ory-auth-setup`, `ory-login-flow`, `ory-social-login` (Google, GitHub, Apple…), `ory-permissions-onboarding`, and playbooks for wiring Ory into your own agents, E2B sandboxes, or Temporal workers.
123
+ Copy the connection details from the [Ory Console](https://console.ory.sh) under **Agent Security**:
100
124
 
101
- ## Configure by hand (CI / advanced)
102
-
103
- 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.
125
+ | Value | Flag | Environment variable |
126
+ |---|---|---|
127
+ | Project URL | `--project-url` | `ORY_PROJECT_URL` |
128
+ | Agent Security URL | `--agent-security-url` | `ORY_AGENT_SECURITY_URL` |
129
+ | Sign-in client id override (default `ory-agent-security-login`) | `--oauth2-client-id` | `ORY_OAUTH2_CLIENT_ID` |
104
130
 
105
131
  ```bash
106
132
  npx -y -p @ory/pi ory-pi configure \
107
133
  --project-url https://<slug>.projects.oryapis.com \
108
- --oauth2-client-id <sign-in client id> \
109
- --user-login
134
+ --agent-security-url https://agents.console.ory.com
110
135
  ```
111
136
 
112
- Pi's own identity registers itself automatically on first run — nothing to create. The `--oauth2-client-id` is the one piece browser sign-in needs — the public sign-in client, registered with the four loopback URLs `http://127.0.0.1:47823..47826/callback`; the guided setup makes it for you. For logging-only with no checks, use `--audit-only`. The equivalent env vars are `ORY_PROJECT_URL`, `ORY_OAUTH2_CLIENT_ID`, and `ORY_USER_LOGIN`.
137
+ `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`.
138
+
139
+ **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.
140
+
141
+ **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 Pi session registers its own identity automatically on first use.
142
+
143
+ 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.
113
144
 
114
- With nothing configured, the plugin still loads and runs in **pass-through mode**: skills and logging work, but no checks run and nothing is blocked. Perfectly fine if you only want the app-building features.
145
+ 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.
115
146
 
116
147
  ## Commands
117
148
 
118
149
  ```
119
- ory-pi install | uninstall Install/remove; --reconfigure re-runs setup, --no-configure skips it, --global for all projects
150
+ ory-pi install | uninstall Install (--global for all projects; add --project-url to also connect Agent Security) / remove
120
151
  ory-pi status Show configuration, identities, permission coverage, recent activity
121
- ory-pi watch Tail the live trace stream (OTel spans)
122
- ory-pi permissions <cmd> status | bootstrap | observe (watch) | enforce (block)
123
- ory-pi configure <flags> Point at a project by hand (--project-url, --oauth2-client-id, --user-login, --audit-only)
152
+ ory-pi permissions <cmd> status (read-only; grants + posture live in the Ory Console)
153
+ ory-pi configure <flags> Connect a project (--project-url) or --disconnect
124
154
  ory-pi agent <status|unregister> Manage Pi's own auto-created identity
155
+ ory-pi local <up|down|status|…> Run / manage a local Ory in Docker
156
+ ory-pi version Print plugin, core, and Node versions (--json for machine-readable)
125
157
  ```
126
158
 
127
159
  All prefixed with `npx -y -p @ory/pi`.
@@ -129,16 +161,14 @@ All prefixed with `npx -y -p @ory/pi`.
129
161
  ## Troubleshooting
130
162
 
131
163
  - **Local Ory won't start** — make sure Docker is running and ports `4000`, `4100`, `4455`, and `16686` are free.
132
- - **Browser sign-in loops** — reset with `ory-pi agent unregister` and try again.
133
- - **Install ran but never asked how to connect** — you're almost certainly on a stale `npx` cache. `npx -p @ory/pi` (no version pin) reuses a previously-downloaded copy instead of re-resolving to the latest, so an older CLI whose install predates the setup wizard can run while you believe you're on the current release. The install banner prints the running version; confirm it with `npx -y -p @ory/pi ory-pi version`. To force the current release, clear the cache and reinstall: `rm -rf ~/.npm/_npx` then `npx -y -p @ory/pi ory-pi install`. Pinning an exact version (`@ory/pi@<version>`) also bypasses the cached copy.
164
+ - **Browser sign-in loops** (after connecting) — reset with `ory-pi agent unregister` and try again.
165
+ - **Running an older CLI than expected** — `npx -p @ory/pi` (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/pi ory-pi version`. To force the current release, clear the cache and reinstall: `rm -rf ~/.npm/_npx` then `npx -y -p @ory/pi ory-pi install`. Pinning an exact version (`@ory/pi@<version>`) also bypasses the cached copy.
134
166
  - **Pi can't find the plugin** — make sure `@ory/pi` is installed in the project (`npm install @ory/pi`) and the loader exists at `.pi/extensions/ory.js`; re-run the installer if not.
135
- - **Want to see what's happening** — `npx -y -p @ory/pi ory-pi status` for a snapshot, `npx -y -p @ory/pi ory-pi 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/pi/` (see [See what's happening](#see-whats-happening)).
136
167
 
137
168
  ## Learn more
138
169
 
139
170
  - [Ory documentation](https://www.ory.com/docs/) · [Ory Console](https://console.ory.sh) · [Ory Elements](https://github.com/ory/elements)
140
171
  - [Pi repository](https://github.com/badlogic/pi-mono)
141
- - [Repo README](../../README.md) and [AGENTS.md](../../AGENTS.md) — full env-var and permission-mode reference
142
172
 
143
173
  ## License
144
174
 
@@ -6,16 +6,23 @@
6
6
  * placed there is loaded via jiti and its default export is invoked as the
7
7
  * extension factory. A bare module id in `settings.json`'s `extensions` array is
8
8
  * NOT loaded — those entries are resolved as filesystem paths. So we drop a
9
- * tiny loader file that re-exports the installed `@ory/pi` package's default
10
- * factory; Pi discovers the file, jiti resolves `@ory/pi` from `node_modules`,
11
- * and the factory runs. No `settings.json` entry is required.
9
+ * tiny loader file that re-exports the runtime's default factory by absolute
10
+ * path; Pi discovers the file and jiti loads that path directly. Re-exporting
11
+ * the bare module id would instead depend on `@ory/pi` being resolvable from
12
+ * wherever Pi happens to run, which a project-local `node_modules` does not
13
+ * guarantee. No `settings.json` entry is required.
12
14
  *
13
15
  * Verified against @earendil-works/pi-coding-agent v0.80.2 (the extension
14
16
  * loader's `discoverAndLoadExtensions` / `discoverExtensionsInDir`).
15
17
  */
16
18
  export declare const EXTENSION_MODULE = "@ory/pi";
17
19
  export declare const LOADER_FILENAME = "ory.js";
18
- export declare const LOADER_CONTENTS = "// Ory extension loader for Pi. Re-exports @ory/pi's default factory so Pi's\n// extension auto-discovery (which scans .pi/extensions/ for *.js files) can\n// load it. Generated by 'ory-pi install' / 'ory-pi-setup' \u2014 safe to delete.\nexport { default } from \"@ory/pi\";\n";
20
+ /**
21
+ * Loader body: re-export the resolved runtime's default factory by absolute
22
+ * path, so Pi's auto-discovery (which scans `.pi/extensions/` for `*.js`)
23
+ * loads exactly the copy this install resolved.
24
+ */
25
+ export declare function loaderContents(entryPath: string): string;
19
26
  /** Resolve the `.pi/extensions/` dir for the requested scope. */
20
27
  export declare function getExtensionsDir(opts: {
21
28
  global?: boolean;
@@ -30,6 +37,8 @@ export declare function getLoaderPath(opts: {
30
37
  export declare function writeOryLoader(opts: {
31
38
  global?: boolean;
32
39
  projectDir: string;
40
+ /** Absolute path to the runtime module the loader re-exports. */
41
+ entryPath: string;
33
42
  }): string;
34
43
  /**
35
44
  * Remove the loader file (and the `.pi/extensions/` dir if it's left empty).
@@ -7,9 +7,11 @@
7
7
  * placed there is loaded via jiti and its default export is invoked as the
8
8
  * extension factory. A bare module id in `settings.json`'s `extensions` array is
9
9
  * NOT loaded — those entries are resolved as filesystem paths. So we drop a
10
- * tiny loader file that re-exports the installed `@ory/pi` package's default
11
- * factory; Pi discovers the file, jiti resolves `@ory/pi` from `node_modules`,
12
- * and the factory runs. No `settings.json` entry is required.
10
+ * tiny loader file that re-exports the runtime's default factory by absolute
11
+ * path; Pi discovers the file and jiti loads that path directly. Re-exporting
12
+ * the bare module id would instead depend on `@ory/pi` being resolvable from
13
+ * wherever Pi happens to run, which a project-local `node_modules` does not
14
+ * guarantee. No `settings.json` entry is required.
13
15
  *
14
16
  * Verified against @earendil-works/pi-coding-agent v0.80.2 (the extension
15
17
  * loader's `discoverAndLoadExtensions` / `discoverExtensionsInDir`).
@@ -48,7 +50,8 @@ var __importStar = (this && this.__importStar) || (function () {
48
50
  };
49
51
  })();
50
52
  Object.defineProperty(exports, "__esModule", { value: true });
51
- exports.LOADER_CONTENTS = exports.LOADER_FILENAME = exports.EXTENSION_MODULE = void 0;
53
+ exports.LOADER_FILENAME = exports.EXTENSION_MODULE = void 0;
54
+ exports.loaderContents = loaderContents;
52
55
  exports.getExtensionsDir = getExtensionsDir;
53
56
  exports.getLoaderPath = getLoaderPath;
54
57
  exports.writeOryLoader = writeOryLoader;
@@ -58,11 +61,19 @@ const os = __importStar(require("node:os"));
58
61
  const path = __importStar(require("node:path"));
59
62
  exports.EXTENSION_MODULE = "@ory/pi";
60
63
  exports.LOADER_FILENAME = "ory.js";
61
- exports.LOADER_CONTENTS = `// Ory extension loader for Pi. Re-exports @ory/pi's default factory so Pi's
62
- // extension auto-discovery (which scans .pi/extensions/ for *.js files) can
63
- // load it. Generated by 'ory-pi install' / 'ory-pi-setup' — safe to delete.
64
- export { default } from "@ory/pi";
64
+ /**
65
+ * Loader body: re-export the resolved runtime's default factory by absolute
66
+ * path, so Pi's auto-discovery (which scans `.pi/extensions/` for `*.js`)
67
+ * loads exactly the copy this install resolved.
68
+ */
69
+ function loaderContents(entryPath) {
70
+ return `// Ory extension loader for Pi. Re-exports the Ory runtime's default factory
71
+ // so Pi's extension auto-discovery (which scans .pi/extensions/ for *.js
72
+ // files) can load it. Generated by 'ory-pi install' / 'ory-pi-setup' — safe to
73
+ // delete. Regenerate by re-running install.
74
+ export { default } from ${JSON.stringify(entryPath)};
65
75
  `;
76
+ }
66
77
  /** Resolve the `.pi/extensions/` dir for the requested scope. */
67
78
  function getExtensionsDir(opts) {
68
79
  return opts.global
@@ -78,7 +89,7 @@ function writeOryLoader(opts) {
78
89
  const dir = getExtensionsDir(opts);
79
90
  const loaderPath = path.join(dir, exports.LOADER_FILENAME);
80
91
  fs.mkdirSync(dir, { recursive: true });
81
- fs.writeFileSync(loaderPath, exports.LOADER_CONTENTS);
92
+ fs.writeFileSync(loaderPath, loaderContents(opts.entryPath));
82
93
  return loaderPath;
83
94
  }
84
95
  /**
package/dist/cli/main.js CHANGED
@@ -51,8 +51,18 @@ const loader_js_1 = require("./loader.js");
51
51
  const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
52
52
  function main() {
53
53
  const [command, ...args] = process.argv.slice(2);
54
+ // `--help` after a matched subcommand: print usage, do nothing else.
55
+ // Without this the switch below ignores the flag and `install --help`
56
+ // performs a real install (#221).
57
+ if ((0, argus_1.shouldPrintHelp)(command, args))
58
+ return help();
54
59
  switch (command) {
55
60
  case "install":
61
+ if (args.includes("--print")) {
62
+ process.argv = ["node", "setup.js", ...args];
63
+ require("./setup.js");
64
+ break;
65
+ }
56
66
  (0, argus_1.beginDeferNextSteps)();
57
67
  install(args);
58
68
  (0, argus_1.runPostInstall)("ory-pi", "pi", args).then(() => process.exit(0), (err) => {
@@ -62,7 +72,10 @@ function main() {
62
72
  break;
63
73
  case "uninstall":
64
74
  uninstall(args);
65
- (0, argus_1.clearCredentialsForUninstall)().then(() => process.exit(0), (err) => {
75
+ (0, argus_1.clearCredentialsForUninstall)({
76
+ harness: "pi",
77
+ purge: args.includes("--purge"),
78
+ }).then(() => process.exit(0), (err) => {
66
79
  console.error(err.message ?? err);
67
80
  process.exit(1);
68
81
  });
@@ -71,7 +84,7 @@ function main() {
71
84
  (0, argus_1.runConfigureCommand)("ory-pi", args);
72
85
  break;
73
86
  case "agent":
74
- (0, argus_1.runAgentCommand)("ory-pi", args).then((code) => process.exit(code), (err) => {
87
+ (0, argus_1.runAgentCommand)("ory-pi", "pi", args).then((code) => process.exit(code), (err) => {
75
88
  console.error(err.message ?? err);
76
89
  process.exit(1);
77
90
  });
@@ -88,15 +101,15 @@ function main() {
88
101
  process.exit(1);
89
102
  });
90
103
  break;
104
+ case "watch":
105
+ (0, argus_1.runWatchCommand)("ory-pi", "pi", args).then((code) => process.exit(code), (err) => { console.error(err.message ?? err); process.exit(1); });
106
+ break;
91
107
  case "local":
92
108
  (0, argus_1.runLocalCommand)("ory-pi", args).catch((err) => {
93
109
  console.error(err.message ?? err);
94
110
  process.exit(1);
95
111
  });
96
112
  break;
97
- case "watch":
98
- (0, argus_1.runWatchCommand)("pi", args);
99
- break;
100
113
  case "version":
101
114
  case "--version":
102
115
  case "-v":
@@ -126,23 +139,48 @@ function isGlobal(args) {
126
139
  // Pi auto-discovers extensions from `<cwd>/.pi/extensions/` (project) and
127
140
  // `~/.pi/agent/extensions/` (global): each `*.js`/`*.ts` file there is loaded
128
141
  // via jiti and its default export is the factory. We drop a loader file that
129
- // re-exports @ory/pi's default factory. No settings.json entry is needed.
142
+ // re-exports the resolved runtime's default factory by absolute path. No
143
+ // settings.json entry is needed.
130
144
  // Verified against @earendil-works/pi-coding-agent v0.80.2. See ./loader.ts.
131
145
  function loaderOpts(args) {
132
146
  return { global: isGlobal(args), projectDir: parseProjectDir(args) };
133
147
  }
134
148
  function install(args) {
135
149
  const projectDir = parseProjectDir(args);
136
- const loaderPath = (0, loader_js_1.writeOryLoader)(loaderOpts(args));
150
+ // Pi loads the extension in-process, so there is no hook subprocess and no
151
+ // hook shim: the loader re-exports the runtime's main module directly. Pi
152
+ // also excludes MCP by design, so no MCP server is registered.
153
+ const runtime = (0, argus_1.wireRuntime)({
154
+ harness: "pi",
155
+ packageName: loader_js_1.EXTENSION_MODULE,
156
+ packageRoot: PACKAGE_ROOT,
157
+ installCommand: "npx -y -p @ory/pi ory-pi install",
158
+ args,
159
+ entryRelPath: "dist/index.js",
160
+ inProcess: true,
161
+ includeMcp: false,
162
+ });
163
+ console.log(runtime.target.kind === "linked"
164
+ ? `Runtime: linked to ${runtime.target.packageDir} (dev)`
165
+ : `Runtime: ${runtime.target.packageName}@${runtime.target.version} in ${runtime.target.storeDir}`);
166
+ for (const pruned of runtime.prunedStores) {
167
+ console.log(` Removed stale runtime: ${pruned}`);
168
+ }
169
+ const entryPath = path.join(runtime.target.packageDir, "dist", "index.js");
170
+ const loaderPath = (0, loader_js_1.writeOryLoader)({ ...loaderOpts(args), entryPath });
137
171
  (0, assets_js_1.installPiOryAssets)(projectDir);
138
172
  console.log(`Ory extension loader installed at ${loaderPath}`);
139
- console.log(` Re-exports the default factory from ${loader_js_1.EXTENSION_MODULE}`);
173
+ console.log(` Re-exports the default factory from ${entryPath}`);
140
174
  console.log(` Skills + commands installed under ${path.join(projectDir, ".pi", "skills")}`);
141
175
  (0, argus_1.printEnvHelp)("ory-pi");
142
176
  }
143
177
  function uninstall(args) {
144
178
  const projectDir = parseProjectDir(args);
145
179
  const removed = (0, loader_js_1.removeOryLoader)(loaderOpts(args));
180
+ (0, argus_1.removeRuntimeWiring)("pi");
181
+ for (const pruned of (0, argus_1.pruneRuntimeStores)()) {
182
+ console.log(` Removed runtime: ${pruned}`);
183
+ }
146
184
  console.log(removed
147
185
  ? "Ory extension loader removed."
148
186
  : "No Ory extension loader found. Nothing to uninstall.");
@@ -177,23 +215,22 @@ Commands:
177
215
  uninstall [--project-dir <path>] [--global] Remove the Ory extension loader
178
216
  configure Set or view Ory project URL and API key
179
217
  agent <status|unregister> Manage the agent's OAuth2 (DCR) identity
180
- permissions <cmd> Manage permission mode and tool permissions
181
- (status, bootstrap, observe, enforce)
218
+ permissions Show the live permission mode and per-tool coverage
182
219
  local <cmd> Manage local Ory dev environment
183
220
  (up, down, status, seed, logs, env, configure, reset)
184
221
  status [--project-dir <path>] Show plugin configuration and status
185
- watch [trace-file] Watch live trace output
222
+ watch [--json] [--lines <count>] Follow the live activity/debug log
186
223
  version Show version and the ory-agent-plugins build commit
187
224
 
188
225
  After installing, the plugin hooks into these Pi lifecycle events:
189
226
  session-start (factory body) Verify session / resolve agent identity via Ory
190
227
  tool_call Check tool permissions via Ory Permissions (can block)
191
- tool_result Record tool completion trace
228
+ tool_result Record tool completion activity
192
229
 
193
230
  Examples:
194
231
  npx -y -p @ory/pi ory-pi install
195
- npx -y -p @ory/pi ory-pi configure --project-url https://<slug>.projects.oryapis.com --api-key ory_pat_...
196
- npx -y -p @ory/pi ory-pi permissions bootstrap
232
+ npx -y -p @ory/pi ory-pi configure --project-url https://<slug>.projects.oryapis.com --agent-security-url https://agents.console.ory.com
233
+ npx -y -p @ory/pi ory-pi permissions
197
234
  npx -y -p @ory/pi ory-pi local up
198
235
  npx -y -p @ory/pi ory-pi status
199
236
  `);
package/dist/cli/setup.js CHANGED
@@ -77,11 +77,16 @@ function main() {
77
77
  }
78
78
  const args = (0, argus_1.parseSetupArgs)({ supportsGlobal: true });
79
79
  if (args.print) {
80
- console.log(loader_js_1.LOADER_CONTENTS);
80
+ // Nothing is resolved or written on a --print run.
81
+ console.log((0, loader_js_1.loaderContents)("<resolved runtime>/dist/index.js"));
81
82
  return;
82
83
  }
83
84
  if (args.uninstall) {
84
85
  const removed = (0, loader_js_1.removeOryLoader)(args);
86
+ (0, argus_1.removeRuntimeWiring)("pi");
87
+ for (const pruned of (0, argus_1.pruneRuntimeStores)()) {
88
+ console.log(` Removed runtime: ${pruned}`);
89
+ }
85
90
  console.log(removed
86
91
  ? "Removed Ory extension loader."
87
92
  : "No Ory extension loader found. Nothing to uninstall.");
@@ -89,7 +94,20 @@ function main() {
89
94
  console.log(`Removed Ory skills from ${path.join(args.projectDir, ".pi", "skills")}`);
90
95
  return;
91
96
  }
92
- const loaderPath = (0, loader_js_1.writeOryLoader)(args);
97
+ const runtime = (0, argus_1.wireRuntime)({
98
+ harness: "pi",
99
+ packageName: "@ory/pi",
100
+ packageRoot: path.resolve(__dirname, "..", ".."),
101
+ installCommand: "npx -y -p @ory/pi ory-pi install",
102
+ args: process.argv.slice(2),
103
+ entryRelPath: "dist/index.js",
104
+ inProcess: true,
105
+ includeMcp: false,
106
+ });
107
+ const loaderPath = (0, loader_js_1.writeOryLoader)({
108
+ ...args,
109
+ entryPath: path.join(runtime.target.packageDir, "dist", "index.js"),
110
+ });
93
111
  (0, assets_js_1.installPiOryAssets)(args.projectDir);
94
112
  console.log(`Ory extension loader installed at ${loaderPath}`);
95
113
  console.log(`Ory skills installed to ${path.join(args.projectDir, ".pi", "skills")}`);
@@ -103,7 +121,10 @@ main();
103
121
  // stored Ory credentials. When required by the plugin's main CLI, that
104
122
  // command owns the purge, so the `require.main` guard prevents a double run.
105
123
  if (require.main === module && process.argv.includes("--uninstall")) {
106
- (0, argus_1.clearCredentialsForUninstall)().then(() => process.exit(0), (err) => {
124
+ (0, argus_1.clearCredentialsForUninstall)({
125
+ harness: "pi",
126
+ purge: process.argv.includes("--purge"),
127
+ }).then(() => process.exit(0), (err) => {
107
128
  console.error(err.message ?? err);
108
129
  process.exit(1);
109
130
  });
package/dist/plugin.d.ts CHANGED
@@ -1,42 +1,11 @@
1
- /**
2
- * Pi in-process extension (auth + blocking permission gate + tracing).
3
- *
4
- * Pi loads a default-exported factory `(pi) => void | Promise<void>` from a
5
- * TypeScript extension module (via jiti) and awaits it before startup
6
- * continues. We treat the factory body as the session-start hook: it runs the
7
- * user gate (advisory — there is no block channel for the session itself), the
8
- * agent gate, and writes the user→agent delegation tuple.
9
- *
10
- * The blocking permission decision lives in the `tool_call` hook, which CAN
11
- * block by returning `{ block: true, reason }` (like OpenClaw's
12
- * `before_tool_call`). The `tool_result` hook records the `tool.complete`
13
- * audit span after a tool runs.
14
- *
15
- * Pi excludes MCP and sub-agents by design, so there is NO MCP tool parsing
16
- * and NO sub-agent identity path.
17
- *
18
- * Contract verified against @earendil-works/pi-coding-agent v0.80.2 (dist
19
- * core/extensions/types.d.ts and the extension loader): the `tool_call`
20
- * handler returns `ToolCallEventResult { block?: boolean; reason? }`,
21
- * `tool_result` carries `isError`/`content`, and Pi awaits the async factory
22
- * default export.
23
- */
24
- import { OryAgentClient, ensureUserAuthenticated, ensureAgentIdentity } from "@ory/argus";
1
+ import { OryAgentClient, ensureAgentIdentity, ensureUserAuthenticated } from "@ory/argus";
25
2
  import type { PiExtension } from "./types.js";
26
3
  export interface CreateOryPluginDeps {
27
- /** Test injection point for the user login flow. */
28
4
  userLogin?: typeof ensureUserAuthenticated;
29
- /** Test injection point for the agent identity gate. */
30
5
  agentGate?: typeof ensureAgentIdentity;
6
+ /** Pin the per-process id for deterministic tests. Production mints one. */
7
+ sessionId?: string;
31
8
  }
32
- /**
33
- * Create the Ory extension factory for Pi.
34
- *
35
- * Returns a function matching Pi's `PiExtension` contract: it receives the
36
- * ExtensionAPI, runs the session-start auth gates in the body, and registers
37
- * the `tool_call` (blocking) and `tool_result` (tracing) hooks.
38
- */
39
9
  export declare function createOryPlugin(clientOrConfig?: OryAgentClient | {
40
10
  projectUrl: string;
41
- apiKey?: string;
42
11
  }, deps?: CreateOryPluginDeps): PiExtension;
package/dist/plugin.js CHANGED
@@ -1,286 +1,64 @@
1
1
  "use strict";
2
- /**
3
- * Pi in-process extension (auth + blocking permission gate + tracing).
4
- *
5
- * Pi loads a default-exported factory `(pi) => void | Promise<void>` from a
6
- * TypeScript extension module (via jiti) and awaits it before startup
7
- * continues. We treat the factory body as the session-start hook: it runs the
8
- * user gate (advisory — there is no block channel for the session itself), the
9
- * agent gate, and writes the user→agent delegation tuple.
10
- *
11
- * The blocking permission decision lives in the `tool_call` hook, which CAN
12
- * block by returning `{ block: true, reason }` (like OpenClaw's
13
- * `before_tool_call`). The `tool_result` hook records the `tool.complete`
14
- * audit span after a tool runs.
15
- *
16
- * Pi excludes MCP and sub-agents by design, so there is NO MCP tool parsing
17
- * and NO sub-agent identity path.
18
- *
19
- * Contract verified against @earendil-works/pi-coding-agent v0.80.2 (dist
20
- * core/extensions/types.d.ts and the extension loader): the `tool_call`
21
- * handler returns `ToolCallEventResult { block?: boolean; reason? }`,
22
- * `tool_result` carries `isError`/`content`, and Pi awaits the async factory
23
- * default export.
24
- */
25
2
  Object.defineProperty(exports, "__esModule", { value: true });
26
3
  exports.createOryPlugin = createOryPlugin;
4
+ const node_crypto_1 = require("node:crypto");
27
5
  const argus_1 = require("@ory/argus");
28
- /**
29
- * Create the Ory extension factory for Pi.
30
- *
31
- * Returns a function matching Pi's `PiExtension` contract: it receives the
32
- * ExtensionAPI, runs the session-start auth gates in the body, and registers
33
- * the `tool_call` (blocking) and `tool_result` (tracing) hooks.
34
- */
6
+ const HARNESS = "pi";
35
7
  function createOryPlugin(clientOrConfig, deps = {}) {
36
8
  return async (pi) => {
37
9
  const client = clientOrConfig instanceof argus_1.OryAgentClient
38
10
  ? clientOrConfig
39
11
  : clientOrConfig
40
- ? new argus_1.OryAgentClient({ ...clientOrConfig, harness: "pi" })
41
- : argus_1.OryAgentClient.fromEnv("pi");
42
- // The factory body IS the session-start hook: Pi awaits async extension
43
- // factories before startup continues (verified against
44
- // @earendil-works/pi-coding-agent v0.80.2). Pi also exposes a dedicated
45
- // `session_start` event, but the factory body is sufficient and runs
46
- // exactly once per process.
47
- await runSessionStart(client, deps);
48
- pi.on("tool_call", createToolCallHandler(client));
49
- pi.on("tool_result", createToolResultHandler(client));
50
- };
51
- }
52
- // ─── session start (factory body) ──────────────────────────────────
53
- async function runSessionStart(client, deps) {
54
- client.logger.info("lifecycle.session_start", { harness: "pi" });
55
- client.tracer.setContext({ traceId: (0, argus_1.deriveTraceId)("pi"), sessionId: "pi" });
56
- client.tracer.record("session.start", "ok", { attributes: {} });
57
- // Run the user login. Pi's factory body has no return-value channel to
58
- // block the session, so allowBlock is false — the flow emits the user.auth
59
- // audit span, refreshes tokens, and may prompt when interactive, but never
60
- // prevents the session from starting. When ORY_USER_LOGIN is unset it is a
61
- // no-op (mode === "disabled") and we fall through to legacy verification.
62
- const userGate = deps.userLogin ?? argus_1.ensureUserAuthenticated;
63
- const decision = await userGate(client, {
64
- binName: "ory-pi",
65
- harness: "pi",
66
- allowBlock: false,
67
- });
68
- // Resolve the agent identity (machine credentials). Never blocks; attaches
69
- // the agent's bearer token to outgoing Ory API calls.
70
- const agentGate = deps.agentGate ?? argus_1.ensureAgentIdentity;
71
- await agentGate(client, { projectUrl: (0, argus_1.resolveConfig)().projectUrl, harness: "pi" });
72
- // Once both principals are populated, write the user→agent delegation tuple.
73
- // Written at most once per install + fail-open: audit-trail data only.
74
- await (0, argus_1.writeUserDelegatesAgent)(client);
75
- if (decision.mode !== "disabled") {
76
- return;
77
- }
78
- const resolved = (0, argus_1.resolveConfig)();
79
- if (resolved.auditOnly) {
80
- client.logger.info("config.audit_only", {
81
- message: "Audit-only mode enabled. Auth and permission checks are disabled.",
82
- });
83
- return;
84
- }
85
- if (!resolved.projectUrl) {
86
- client.logger.warn("config.not_configured", {
87
- message: "Ory plugin is not configured. Auth and permission checks are disabled. " +
88
- "Run 'npx ory-pi configure' to connect to an Ory project.",
89
- });
90
- return;
91
- }
92
- const sessionToken = process.env.ORY_SESSION_TOKEN;
93
- const oauth2Token = process.env.ORY_OAUTH2_TOKEN;
94
- if (sessionToken) {
95
- await verifySessionToken(client, sessionToken);
96
- return;
97
- }
98
- if (oauth2Token) {
99
- await verifyOAuth2Token(client, oauth2Token);
100
- return;
101
- }
102
- client.logger.warn("session.no_credentials", {
103
- message: "Neither ORY_SESSION_TOKEN nor ORY_OAUTH2_TOKEN is set. " +
104
- "Skipping authentication.",
105
- });
106
- }
107
- async function verifySessionToken(client, token) {
108
- try {
109
- const session = await client.verifySession(token);
110
- if (!session.active) {
111
- client.logger.warn("session.inactive", {
112
- message: "Ory session is not active. Re-authenticate to enable auth checks.",
12
+ ? new argus_1.OryAgentClient({ ...clientOrConfig, harness: HARNESS })
13
+ : argus_1.OryAgentClient.fromEnv(HARNESS);
14
+ // Pi events have no session id, so one process UUID identifies the run.
15
+ const sessionId = deps.sessionId ?? (0, node_crypto_1.randomUUID)();
16
+ await (0, argus_1.withHookContext)(client, { sessionId }, async () => {
17
+ client.logger.info("lifecycle.session_start", { harness: HARNESS });
18
+ await (0, argus_1.sessionStart)(client, {
19
+ harness: HARNESS,
20
+ binName: "ory-pi",
21
+ userLogin: deps.userLogin,
22
+ agentGate: deps.agentGate,
113
23
  });
114
- }
115
- }
116
- catch (err) {
117
- client.logger.warn("session.verify_failed", {
118
- code: isOryError(err) ? err.code : "unknown",
119
- message: err instanceof Error ? err.message : String(err),
120
24
  });
121
- }
25
+ pi.on("tool_call", createToolCallHandler(client, sessionId));
26
+ pi.on("tool_result", createToolResultHandler(client, sessionId));
27
+ };
122
28
  }
123
- async function verifyOAuth2Token(client, token) {
124
- try {
125
- const tokenInfo = await client.introspectToken(token);
126
- if (!tokenInfo.active) {
127
- client.logger.warn("oauth2.token_inactive", {
128
- message: "Ory OAuth2 token is not active. Obtain a new token to enable auth checks.",
129
- });
130
- return;
131
- }
132
- client.logger.info("oauth2.session_authenticated", {
133
- clientId: tokenInfo.clientId,
134
- subject: tokenInfo.subject,
29
+ function createToolCallHandler(client, sessionId) {
30
+ return (event) => (0, argus_1.withHookContext)(client, { sessionId }, async () => {
31
+ const toolName = event.toolName ?? "unknown";
32
+ client.logger.info("lifecycle.tool_call", {
33
+ toolName,
34
+ toolCallId: event.toolCallId,
135
35
  });
136
- }
137
- catch (err) {
138
- client.logger.warn("oauth2.introspect_failed", {
139
- code: isOryError(err) ? err.code : "unknown",
140
- message: err instanceof Error ? err.message : String(err),
36
+ const result = await (0, argus_1.gate)(client, {
37
+ harness: HARNESS,
38
+ toolName,
39
+ toolArgs: event.input,
40
+ subjectFallback: "agent:pi",
41
+ canBlock: true,
141
42
  });
142
- }
143
- }
144
- // ─── tool_call (pre-execution, blocking) ───────────────────────────
145
- function createToolCallHandler(client) {
146
- return async (event) => {
147
- const toolName = event.toolName ?? "unknown";
148
- // Pi does not put a session id on the tool_call event; the per-process
149
- // trace context set at session start ("pi") carries correlation. We tag
150
- // the span with the toolCallId so a tool_call lines up with its later
151
- // tool_result.
152
- client.logger.info("lifecycle.tool_call", { toolName, toolCallId: event.toolCallId });
153
- client.tracer.setContext({ traceId: (0, argus_1.deriveTraceId)("pi"), sessionId: "pi" });
154
- const inputSummary = (0, argus_1.summarizeToolInput)(toolName, event.input);
155
- // In audit-only mode, log the invocation but skip permission checks.
156
- if ((0, argus_1.resolveConfig)().auditOnly) {
157
- client.tracer.record("tool.invoke", "ok", {
158
- attributes: { toolName, ...inputSummary },
159
- });
160
- return;
161
- }
162
- // If Ory is not configured, pass through.
163
- if (!(0, argus_1.resolveConfig)().projectUrl) {
164
- client.tracer.record("tool.invoke", "skipped", {
165
- attributes: { toolName, reason: "not_configured", ...inputSummary },
166
- });
167
- return;
168
- }
169
- const subject = (0, argus_1.resolveUserSubject)(client, "agent:pi");
170
- const subjectId = (0, argus_1.subjectLabel)(subject);
171
- const namespace = resolveNamespace();
172
- try {
173
- const outcome = await (0, argus_1.gateToolCall)(client, {
174
- harness: "pi",
175
- toolName,
176
- check: { namespace, object: toolName, relation: "use", ...subject },
177
- spanAttributes: { toolName },
178
- });
179
- // Interactive tools (operator-extensible via ORY_INTERACTIVE_TOOLS):
180
- // the user.interaction span is already recorded; pass through.
181
- if (outcome.kind === "interactive") {
182
- return;
183
- }
184
- const decision = outcome;
185
- if (decision.kind === "fail_open") {
186
- handlePermissionError(decision.error, toolName, client);
187
- return;
188
- }
189
- const attrs = { toolName, ...inputSummary };
190
- const decisionAttrs = decision.spanAttributes;
191
- if (decision.kind === "allow") {
192
- client.tracer.record("tool.invoke", "ok", {
193
- attributes: { ...attrs, ...decisionAttrs, allowed: true },
194
- });
195
- return;
196
- }
197
- if (decision.kind === "observe") {
198
- client.tracer.record("tool.block", "denied", {
199
- attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
200
- });
201
- client.tracer.record("tool.invoke", "ok", {
202
- attributes: { ...attrs, ...decisionAttrs, allowed: false, observed: true },
203
- });
204
- return;
205
- }
206
- // decision.kind === "deny" — enforce: block the tool call.
207
- client.tracer.record("tool.block", "denied", {
208
- attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
209
- });
210
- const reason = (0, argus_1.formatDenialMessage)({
211
- tool: toolName,
212
- subjectId,
213
- namespace,
214
- });
215
- client.logger.warn("tool.denied", {
216
- toolName,
217
- subjectId,
218
- message: reason,
219
- });
220
- return { block: true, reason };
221
- }
222
- catch (err) {
223
- handlePermissionError(err, toolName, client);
224
- // Fail open — allow the tool call.
225
- }
226
- };
43
+ if (result.blocked)
44
+ return { block: true, reason: result.denialMessage };
45
+ });
227
46
  }
228
- // ─── tool_result (post-execution, tracing) ─────────────────────────
229
- function createToolResultHandler(client) {
230
- return async (event) => {
47
+ function createToolResultHandler(client, sessionId) {
48
+ return (event) => (0, argus_1.withHookContext)(client, { sessionId }, async () => {
231
49
  const toolName = event.toolName ?? "unknown";
232
- client.tracer.setContext({ traceId: (0, argus_1.deriveTraceId)("pi"), sessionId: "pi" });
233
- // Pi reports failure via the `isError` boolean and the result payload via
234
- // `content` (verified shape), not `error`/`result`.
235
50
  const isError = event.isError === true;
236
51
  client.logger.info("lifecycle.tool_result", {
237
52
  toolName,
238
53
  toolCallId: event.toolCallId,
239
54
  isError,
240
55
  });
241
- client.tracer.record("tool.complete", isError ? "error" : "ok", {
242
- attributes: {
243
- toolName,
244
- ...(0, argus_1.summarizeToolInput)(toolName, event.input),
245
- ...(0, argus_1.summarizeToolOutput)(toolName, event.content),
246
- ...(isError ? { error: true } : {}),
247
- },
248
- });
249
- };
250
- }
251
- // ─── Helpers ───────────────────────────────────────────────────────
252
- function resolveNamespace() {
253
- return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
254
- }
255
- function isOryError(err) {
256
- return (typeof err === "object" &&
257
- err !== null &&
258
- "code" in err &&
259
- "message" in err);
260
- }
261
- function handlePermissionError(err, toolName, client) {
262
- if (isOryError(err)) {
263
- if (err.code === "network_error" || err.code === "rate_limited") {
264
- client.logger.warn("permission.fallback", {
265
- toolName,
266
- code: err.code,
267
- message: "Failing open",
268
- });
269
- return;
270
- }
271
- client.logger.error("permission.check.error", {
272
- toolName,
273
- code: err.code,
274
- message: err.message,
275
- });
276
- }
277
- else {
278
- client.logger.error("permission.check.error", {
56
+ (0, argus_1.complete)(client, {
279
57
  toolName,
280
- error: err instanceof Error ? err.message : String(err),
58
+ input: event.input,
59
+ output: event.content,
60
+ status: isError ? "error" : "ok",
61
+ extraActivityAttributes: isError ? { error: true } : undefined,
281
62
  });
282
- }
283
- // Fail-open is log-and-allow: no tool.invoke span, matching the core
284
- // gate() vocabulary. The errored permission.check span carries the audit
285
- // trail for the failed check itself.
63
+ });
286
64
  }
package/dist/types.d.ts CHANGED
@@ -10,7 +10,7 @@
10
10
  * Tool hooks are registered with `pi.on(event, handler)`. The pre-execution
11
11
  * `tool_call` hook BLOCKS a tool by returning `{ block: true, reason }`;
12
12
  * returning nothing/undefined allows. A best-effort `tool_result` hook fires
13
- * after a tool completes and is used for audit tracing.
13
+ * after a tool completes and is used for activity auditing.
14
14
  *
15
15
  * Pi deliberately excludes MCP and sub-agents, so there is no MCP tool
16
16
  * parsing and no sub-agent identity path.
package/dist/types.js CHANGED
@@ -11,7 +11,7 @@
11
11
  * Tool hooks are registered with `pi.on(event, handler)`. The pre-execution
12
12
  * `tool_call` hook BLOCKS a tool by returning `{ block: true, reason }`;
13
13
  * returning nothing/undefined allows. A best-effort `tool_result` hook fires
14
- * after a tool completes and is used for audit tracing.
14
+ * after a tool completes and is used for activity auditing.
15
15
  *
16
16
  * Pi deliberately excludes MCP and sub-agents, so there is no MCP tool
17
17
  * parsing and no sub-agent identity path.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/pi",
3
- "version": "0.13.9",
3
+ "version": "1.0.0",
4
4
  "description": "Ory plugin for Pi (the minimal coding agent): 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://ory.com",
@@ -37,8 +37,7 @@
37
37
  "agent-security",
38
38
  "guardrails",
39
39
  "llm",
40
- "tracing",
41
- "distributed-tracing",
40
+ "activity-auditing",
42
41
  "observability",
43
42
  "kratos",
44
43
  "keto",
@@ -49,6 +48,10 @@
49
48
  "registry": "https://registry.npmjs.org/",
50
49
  "provenance": true
51
50
  },
51
+ "reova": {
52
+ "enabled": true,
53
+ "endpoint": "https://telemetry.reo.dev/data"
54
+ },
52
55
  "main": "dist/index.js",
53
56
  "types": "dist/index.d.ts",
54
57
  "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
- "reo-census": "^1.2.8",
76
- "@ory/argus": "0.13.9"
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
  }