@ory/continue 0.14.0 → 1.0.1

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 [Continue](https://continue.dev)'s `cn` CLI, powered by [Ory](https://ory.com).
4
4
 
5
- **Security.** Continue 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; Continue 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
+ Continue 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, 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
  This targets the **`cn` CLI** (not the Continue IDE extension), verified against `@continuedev/cli`.
10
13
 
@@ -17,7 +20,7 @@ This targets the **`cn` CLI** (not the Continue IDE extension), verified against
17
20
 
18
21
  ## Get started
19
22
 
20
- Run one command. It installs the plugin and walks you through connecting:
23
+ Run one command:
21
24
 
22
25
  ```bash
23
26
  npx -y -p @ory/continue ory-cn install
@@ -25,31 +28,53 @@ npx -y -p @ory/continue ory-cn install
25
28
 
26
29
  Continue has no native install command, so this writes the Ory hooks into your project settings and drops the Ory skills into your project — Continue turns each skill into a slash command automatically (so the local Ory commands show up as `/ory-local-up`, `/ory-local-down`, and `/ory-temporal-up`).
27
30
 
28
- It opens a guided setup **in your browser** where you pick how to connect — the default takes just a click:
29
-
30
- - **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.
31
- - **Local** — run a complete Ory on your laptop with Docker. No account, no signup, no keys. Great for trying it out.
32
- - **Audit-only** — skip Ory entirely and just log what Continue does.
33
-
34
- > No browser available (CI, SSH, headless)? The same walkthrough runs right in your terminal instead — or force it with `--no-web`.
35
-
36
- That's it. Confirm everything landed with:
31
+ Confirm everything landed with:
37
32
 
38
33
  ```bash
39
34
  npx -y -p @ory/continue ory-cn status
40
35
  ```
41
36
 
42
- `status` is your one-stop check: what's configured, who's signed in, which tools are covered by permissions, and recent activity. Anything not set up yet shows as `(unset)`.
37
+ `status` is your one-stop check: what's configured, who's signed in, which tools are covered by permissions, and recent activity. Until you connect Agent Security, the identity and permission rows say so and name what's missing.
38
+
39
+ ## Skills and commands
40
+
41
+ Installing the plugin drops the full Ory playbook catalog into Continue. **Skills** are model-invoked — say what you want in plain language. Continue also surfaces every skill as a slash command, so `/ory-auth-setup` works too.
42
+
43
+ | Skill | What it does for you |
44
+ |---|---|
45
+ | `ory-auth-setup` | Adds a complete auth system to your app — login, registration, recovery, verification, settings — on [Ory Elements](https://github.com/ory/elements) |
46
+ | `ory-login-flow` | Builds just the pages, wired to Ory's self-service flows |
47
+ | `ory-social-login` | "Sign in with…" for Google, GitHub, Apple, Microsoft, Discord, Slack, GitLab, Facebook |
48
+ | `ory-local-dev` | Develops and tests login/permission flows against a local Ory — no project, no account, offline |
49
+ | `ory-permissions-onboarding` | Walks a fresh install from observe mode to enforced per-tool permissions without getting blocked |
50
+ | `ory-build-agent` | Drops `@ory/argus` into an agent *you* own — Claude Agent SDK, OpenAI Agents, Mastra, Vercel AI, LangGraph/PydanticAI |
51
+ | `ory-build-integration` | Wires Ory into your app: Action webhooks, JWT validation at a gateway, live event streams |
52
+ | `ory-contribute-integration` | Authors and submits an integration to the public `ory/integrates` registry |
53
+ | `ory-e2b-sandbox` | Scaffolds an E2B sandbox template that boots with this plugin preinstalled |
54
+ | `ory-temporal-worker` | Scaffolds a Temporal TypeScript worker where every Activity is authenticated, authorized, and audited |
55
+
56
+ The local-stack playbooks install as skills too, so they show up as slash commands:
57
+
58
+ | Command | What it does |
59
+ |---|---|
60
+ | `/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 |
61
+ | `/ory-local-down` | Stops it, keeping your data volumes |
62
+ | `/ory-temporal-up` | Starts a local Temporal dev server for the `ory-temporal-worker` scaffold |
43
63
 
44
- > Re-run install with `--reconfigure` to change your connection later, or `--no-configure` to skip the wizard and configure by hand.
64
+ 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`.
65
+
66
+ A built-in **Ory MCP server** rounds it out — Continue can manage identities, projects, and permissions straight from chat.
67
+
68
+ So: ask Continue *"add Ory login to this app"* and it scaffolds the pages, starts a local Ory, and wires them together.
45
69
 
46
70
  ## What you get
47
71
 
48
- Once connected, every tool Continue runs is governed by Ory — three things happen automatically:
72
+ Out of the box, every tool Continue runs produces a privacy-safe structured activity event in the unified local log.
73
+
74
+ Once you connect to Ory Agent Security, two more things happen automatically:
49
75
 
50
- - **Who's driving.** You sign in once in your browser (a quick, secure handoff — no passwords or tokens to copy around). Continue itself registers its own identity automatically the first time it runs, and any sub-agents get their own too. The "who acted on whose behalf" trail stays queryable later.
51
- - **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.
52
- - **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.
76
+ - **Who's driving.** You sign in once in your browser; Continue (and any sub-agents it spawns) each get their own identity. No tokens to copy around, and the "who acted on whose behalf" trail stays queryable later.
77
+ - **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.
53
78
 
54
79
  If Ory is ever unreachable, the plugin gets out of the way and lets Continue keep working — so it can't lock you out.
55
80
 
@@ -57,81 +82,63 @@ Under the hood, Continue runs the plugin the same way [Claude Code](https://gith
57
82
 
58
83
  ### See what's happening
59
84
 
60
- Everything the plugin does is observable out of the box — no configuration required:
85
+ Everything the plugin does is observable out of the box:
61
86
 
62
- - **Status at a glance.** `npx -y -p @ory/continue ory-cn status` shows what's configured, who's signed in, how many built-in tools your permissions cover, and the most recent tool-call activity.
63
- - **Live dashboard.** `npx -y -p @ory/continue ory-cn dashboard` opens the same picture in your browser — configuration, identities, permission coverage, Ory service health, and the latest tool-call activity, all refreshing live. From there you can flip **enforcement** on or off, toggle **user login**, and use **Change stack** to reconnect to a different Ory — no CLI required.
64
- - **Live traces.** Every tool call is recorded as an OpenTelemetry-style span. Watch them stream as the agent works:
87
+ - **Activity log.** Privacy-safe activity is always appended to `~/.config/ory-agent-plugins/continue/ory-agent-debug.log`. View events, decisions, and errors live with:
65
88
 
66
89
  ```bash
67
90
  npx -y -p @ory/continue ory-cn watch
68
91
  ```
69
92
 
70
- Spans are also written to `~/.config/ory-agent-plugins/continue/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.
71
- - **Debug log.** For a verbose play-by-play, set `ORY_AGENT_DEBUG=true`; structured logs land in `~/.config/ory-agent-plugins/continue/ory-agent-debug.log`.
93
+ Set `ORY_AGENT_LOG_FILE` to override the path; set it empty to disable file persistence.
94
+ - **Live debug.** Launch Continue 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.
72
95
 
73
96
  ### Ready to enforce?
74
97
 
75
- When the watch-mode logs look right, turn on blocking with one command (setup already granted you the built-in tools):
98
+ 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.
76
99
 
77
100
  ```bash
78
- npx -y -p @ory/continue ory-cn permissions enforce
101
+ npx -y -p @ory/continue ory-cn permissions # what the project grants, and the live mode
79
102
  ```
80
103
 
81
- Now a denied tool is actually blocked. Go back to watch mode anytime with `permissions observe`. Prefer clicking? The dashboard has the same **Enforce** switch — flip it on, or back to watch mode, without touching the CLI. Use `permissions status` to see what's covered and `permissions bootstrap` to (re-)grant the built-in tools.
82
-
83
- ## Also: add login to your own app
84
-
85
- Beyond securing Continue, the plugin helps you build Ory into whatever you're working on. Ask Continue *"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 with `/ory-local-up` (it prints a test email + password to sign in with) and tear it down with `/ory-local-down`.
104
+ Then a denied tool is actually blocked and Continue shows why.
86
105
 
87
- Bundled **skills** (just ask in plain language, and Continue surfaces each as a slash command) 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.
106
+ ## Connect to Ory Agent Security
88
107
 
89
- ## Configure by hand (CI / advanced)
108
+ Copy the connection details from the [Ory Console](https://console.ory.sh) under **Agent Security**:
90
109
 
91
- 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.
110
+ | Value | Flag | Environment variable |
111
+ |---|---|---|
112
+ | Project URL | `--project-url` | `ORY_PROJECT_URL` |
113
+ | Agent Security URL | `--agent-security-url` | `ORY_AGENT_SECURITY_URL` |
114
+ | Sign-in client id override (default `ory-agent-security-login`) | `--oauth2-client-id` | `ORY_OAUTH2_CLIENT_ID` |
92
115
 
93
116
  ```bash
94
117
  npx -y -p @ory/continue ory-cn configure \
95
118
  --project-url https://<slug>.projects.oryapis.com \
96
- --oauth2-client-id <login client id>
119
+ --agent-security-url https://agents.console.ory.com
97
120
  ```
98
121
 
99
- Continue'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 guided setup makes it for you, or see below to do it by hand. For logging-only with no checks, use `--audit-only`.
122
+ `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`.
100
123
 
101
- <details>
102
- <summary>Create the sign-in client by hand</summary>
103
-
104
- 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:
105
-
106
- ```bash
107
- ory create oauth2-client --project <project-id> \
108
- --name "Ory Agent Security · user login (PKCE)" \
109
- --grant-type authorization_code,refresh_token \
110
- --response-type code \
111
- --scope openid,offline_access \
112
- --token-endpoint-auth-method none \
113
- --redirect-uri http://127.0.0.1:47823/callback \
114
- --redirect-uri http://127.0.0.1:47824/callback \
115
- --redirect-uri http://127.0.0.1:47825/callback \
116
- --redirect-uri http://127.0.0.1:47826/callback
117
- ```
124
+ **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.
118
125
 
119
- Pass the resulting id to `configure --oauth2-client-id`. Running headless with a session token already? Set `ORY_USER_SESSION_TOKEN` and skip the browser step entirely.
126
+ **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 Continue session registers its own identity automatically on first use.
120
127
 
121
- </details>
128
+ 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.
122
129
 
123
- With nothing configured, the plugin still loads and runs in **pass-through mode**: skills and their slash commands work, but no checks run and nothing is blocked. Perfectly fine if you only want the app-building features.
130
+ 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.
124
131
 
125
132
  ## Commands
126
133
 
127
134
  ```
128
- ory-cn install | uninstall Install/remove; --reconfigure re-runs setup, --no-web forces the terminal wizard, --no-configure skips it
135
+ ory-cn install | uninstall Install (add --project-url to also connect Agent Security) / remove
129
136
  ory-cn status Show configuration, identities, permission coverage, recent activity
130
- ory-cn dashboard Open the live browser dashboard (status + service health + Change stack)
131
- ory-cn watch Tail the live trace stream (OTel spans)
132
- ory-cn permissions <cmd> status | bootstrap | observe (watch) | enforce (block)
133
- ory-cn configure <flags> Point at a project by hand (--project-url, --oauth2-client-id, --audit-only)
137
+ ory-cn permissions <cmd> status (read-only; grants + posture live in the Ory Console)
138
+ ory-cn configure <flags> Connect a project (--project-url) or --disconnect
134
139
  ory-cn agent <status|unregister> Manage Continue's own auto-created identity
140
+ ory-cn local <up|down|status|…> Run / manage a local Ory in Docker
141
+ ory-cn version Print plugin, core, and Node versions (--json for machine-readable)
135
142
  ```
136
143
 
137
144
  All prefixed with `npx -y -p @ory/continue`.
@@ -139,10 +146,9 @@ All prefixed with `npx -y -p @ory/continue`.
139
146
  ## Troubleshooting
140
147
 
141
148
  - **`/ory-local-up` fails** — make sure Docker is running and ports `4000`, `4100`, `4455`, and `16686` are free.
142
- - **Browser sign-in loops** — reset with `ory-cn agent unregister` and try again.
143
- - **Install ran but never asked how to connect** — you're almost certainly on a stale `npx` cache. `npx -p @ory/continue` (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/continue ory-cn version`. To force the current release, clear the cache and reinstall: `rm -rf ~/.npm/_npx` then `npx -y -p @ory/continue ory-cn install`. Pinning an exact version (`@ory/continue@<version>`) also bypasses the cached copy.
149
+ - **Browser sign-in loops** (after connecting) — reset with `ory-cn agent unregister` and try again.
150
+ - **Running an older CLI than expected** — `npx -p @ory/continue` (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/continue ory-cn version`. To force the current release, clear the cache and reinstall: `rm -rf ~/.npm/_npx` then `npx -y -p @ory/continue ory-cn install`. Pinning an exact version (`@ory/continue@<version>`) also bypasses the cached copy.
144
151
  - **Hooks stuck on an old version** — re-run the installer.
145
- - **Want to see what's happening** — `npx -y -p @ory/continue ory-cn status` for a snapshot, `npx -y -p @ory/continue ory-cn 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/continue/` (see [See what's happening](#see-whats-happening)).
146
152
 
147
153
  ## Learn more
148
154
 
package/dist/cli/main.js CHANGED
@@ -54,6 +54,11 @@ const argus_1 = require("@ory/argus");
54
54
  const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
55
55
  function main() {
56
56
  const [command, ...args] = process.argv.slice(2);
57
+ // `--help` after a matched subcommand: print usage, do nothing else.
58
+ // Without this the switch below ignores the flag and `install --help`
59
+ // performs a real install (#221).
60
+ if ((0, argus_1.shouldPrintHelp)(command, args))
61
+ return help();
57
62
  switch (command) {
58
63
  case "install":
59
64
  (0, argus_1.beginDeferNextSteps)();
@@ -65,7 +70,10 @@ function main() {
65
70
  break;
66
71
  case "uninstall":
67
72
  uninstall(args);
68
- (0, argus_1.clearCredentialsForUninstall)().then(() => process.exit(0), (err) => {
73
+ (0, argus_1.clearCredentialsForUninstall)({
74
+ harness: "continue",
75
+ purge: args.includes("--purge"),
76
+ }).then(() => process.exit(0), (err) => {
69
77
  console.error(err.message ?? err);
70
78
  process.exit(1);
71
79
  });
@@ -74,7 +82,7 @@ function main() {
74
82
  (0, argus_1.runConfigureCommand)("ory-cn", args);
75
83
  break;
76
84
  case "agent":
77
- (0, argus_1.runAgentCommand)("ory-cn", args).then((code) => process.exit(code), (err) => {
85
+ (0, argus_1.runAgentCommand)("ory-cn", "continue", args).then((code) => process.exit(code), (err) => {
78
86
  console.error(err.message ?? err);
79
87
  process.exit(1);
80
88
  });
@@ -94,17 +102,11 @@ function main() {
94
102
  process.exit(1);
95
103
  });
96
104
  break;
97
- case "local":
98
- (0, argus_1.runLocalCommand)("ory-cn", args).catch((err) => {
99
- console.error(err.message ?? err);
100
- process.exit(1);
101
- });
102
- break;
103
105
  case "watch":
104
- (0, argus_1.runWatchCommand)("continue", args);
106
+ (0, argus_1.runWatchCommand)("ory-cn", "continue", args).then((code) => process.exit(code), (err) => { console.error(err.message ?? err); process.exit(1); });
105
107
  break;
106
- case "dashboard":
107
- (0, argus_1.runDashboardCommand)("ory-cn", "continue", args).then((code) => process.exit(code), (err) => {
108
+ case "local":
109
+ (0, argus_1.runLocalCommand)("ory-cn", args).catch((err) => {
108
110
  console.error(err.message ?? err);
109
111
  process.exit(1);
110
112
  });
@@ -176,11 +178,11 @@ Commands:
176
178
  uninstall Remove the Ory hooks and skills
177
179
  configure Set or view Ory project URL and API key
178
180
  agent <cmd> Manage the agent's OAuth2 (DCR) identity (status, unregister)
179
- permissions <cmd> Manage permission mode and tool permissions (status, bootstrap, observe, enforce)
181
+ permissions Show the live permission mode and per-tool coverage
180
182
  setup [--project-dir] Write hooks directly to .continue/settings.json
181
183
  status Show plugin status and configuration
182
- dashboard Open the Ory Agent dashboard (system health + configuration)
183
- watch [trace-file] Tail the trace stream (OTel spans) live
184
+ watch [--json] [--lines <count>]
185
+ Follow the live activity/debug log
184
186
  version Show version and the ory-agent-plugins build commit
185
187
  local <cmd> Manage local Ory dev environment (up, down, status, seed, ...)
186
188
 
package/dist/cli/setup.js CHANGED
@@ -56,11 +56,11 @@ const argus_1 = require("@ory/argus");
56
56
  const assets_js_1 = require("./assets.js");
57
57
  const BIN_NAME = "ory-cn-hook";
58
58
  const PACKAGE_NAME = "@ory/continue";
59
+ const PACKAGE_ROOT = path.resolve(__dirname, "..", "..");
59
60
  function getSettingsPath(args) {
60
61
  return path.join(args.projectDir, ".continue", "settings.json");
61
62
  }
62
- function generateHooksConfig() {
63
- const cmd = (0, argus_1.resolveHookCommand)(PACKAGE_NAME, BIN_NAME);
63
+ function generateHooksConfig(cmd) {
64
64
  return {
65
65
  SessionStart: (0, argus_1.matcherHookEntry)(cmd),
66
66
  PreToolUse: (0, argus_1.matcherHookEntry)(cmd),
@@ -81,9 +81,9 @@ function main() {
81
81
  process.exit(0);
82
82
  }
83
83
  const args = (0, argus_1.parseSetupArgs)();
84
- const hooksConfig = generateHooksConfig();
85
84
  if (args.print) {
86
- console.log(JSON.stringify({ hooks: hooksConfig }, null, 2));
85
+ // Nothing is resolved or written on a --print run.
86
+ console.log(JSON.stringify({ hooks: generateHooksConfig('node "<runtime shim>"') }, null, 2));
87
87
  return;
88
88
  }
89
89
  const settingsPath = getSettingsPath(args);
@@ -101,10 +101,30 @@ function main() {
101
101
  (0, assets_js_1.uninstallContinueOryAssets)(continueDir);
102
102
  console.log(`Removed Ory hooks and MCP server from ${settingsPath}`);
103
103
  console.log(`Removed Ory skills from ${continueDir}`);
104
+ (0, argus_1.removeRuntimeWiring)("continue");
105
+ for (const pruned of (0, argus_1.pruneRuntimeStores)()) {
106
+ console.log(` Removed runtime: ${pruned}`);
107
+ }
104
108
  return;
105
109
  }
110
+ const runtime = (0, argus_1.wireRuntime)({
111
+ harness: "continue",
112
+ packageName: PACKAGE_NAME,
113
+ packageRoot: PACKAGE_ROOT,
114
+ installCommand: "npx -y -p @ory/continue ory-cn install",
115
+ args: process.argv.slice(2),
116
+ });
117
+ console.log(runtime.target.kind === "linked"
118
+ ? `Runtime: linked to ${runtime.target.packageDir} (dev)`
119
+ : `Runtime: ${runtime.target.packageName}@${runtime.target.version} in ${runtime.target.storeDir}`);
120
+ for (const pruned of runtime.prunedStores) {
121
+ console.log(` Removed stale runtime: ${pruned}`);
122
+ }
123
+ const hooksConfig = generateHooksConfig((0, argus_1.requireHookCommand)(runtime));
106
124
  let merged = (0, argus_1.mergeMatcherHooks)(existing, hooksConfig, BIN_NAME);
107
- merged = (0, argus_1.mergeMcpServer)(merged);
125
+ if (runtime.mcpServer) {
126
+ merged = (0, argus_1.mergeMcpServer)(merged, runtime.mcpServer);
127
+ }
108
128
  (0, argus_1.writeJsonFile)(settingsPath, merged);
109
129
  (0, assets_js_1.installContinueOryAssets)(continueDir);
110
130
  console.log(`Ory hooks and MCP server installed to ${settingsPath}`);
@@ -119,7 +139,10 @@ main();
119
139
  // stored Ory credentials. When required by the plugin's main CLI, that
120
140
  // command owns the purge, so the `require.main` guard prevents a double run.
121
141
  if (require.main === module && process.argv.includes("--uninstall")) {
122
- (0, argus_1.clearCredentialsForUninstall)().then(() => process.exit(0), (err) => {
142
+ (0, argus_1.clearCredentialsForUninstall)({
143
+ harness: "continue",
144
+ purge: process.argv.includes("--purge"),
145
+ }).then(() => process.exit(0), (err) => {
123
146
  console.error(err.message ?? err);
124
147
  process.exit(1);
125
148
  });
@@ -1,17 +1,9 @@
1
- import { OryAgentClient, ensureUserAuthenticated, ensureAgentIdentity, ensureSubAgentIdentity } from "@ory/argus";
1
+ import { ensureAgentIdentity, ensureSubAgentIdentity, ensureUserAuthenticated } from "@ory/argus";
2
+ import type { OryAgentClient } from "@ory/argus";
2
3
  import type { ContinueHookInput, ContinueHookOutput } from "./types.js";
3
4
  export interface HandleHookEventDeps {
4
- /** Test injection point for the user login flow. */
5
5
  userLogin?: typeof ensureUserAuthenticated;
6
- /** Test injection point for the agent identity gate. */
7
6
  agentGate?: typeof ensureAgentIdentity;
8
- /** Test injection point for the sub-agent identity resolver. */
9
7
  subAgentGate?: typeof ensureSubAgentIdentity;
10
8
  }
11
- /**
12
- * Route a Continue CLI hook event to the appropriate Ory integration.
13
- *
14
- * Event names are verified against @continuedev/cli. Unknown events pass
15
- * through.
16
- */
17
9
  export declare function handleHookEvent(input: ContinueHookInput, client: OryAgentClient, deps?: HandleHookEventDeps): Promise<ContinueHookOutput>;
package/dist/handlers.js CHANGED
@@ -3,62 +3,44 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handleHookEvent = handleHookEvent;
4
4
  const argus_1 = require("@ory/argus");
5
5
  const HARNESS = "continue";
6
- const BIN_NAME = "ory-cn";
7
- /**
8
- * Route a Continue CLI hook event to the appropriate Ory integration.
9
- *
10
- * Event names are verified against @continuedev/cli. Unknown events pass
11
- * through.
12
- */
13
- async function handleHookEvent(input, client, deps = {}) {
6
+ function handleHookEvent(input, client, deps = {}) {
14
7
  const event = input.hook_event_name;
15
8
  client.logger.debug("hook.received", {
16
9
  event,
17
10
  sessionId: input.session_id,
18
11
  toolName: input.tool_name,
19
12
  });
20
- // Set trace context so all spans within this hook invocation correlate
21
- client.tracer.setContext({
22
- traceId: (0, argus_1.deriveTraceId)(input.session_id),
23
- sessionId: input.session_id,
24
- });
25
- try {
13
+ return (0, argus_1.withHookContext)(client, { sessionId: input.session_id }, async () => {
26
14
  switch (event) {
27
15
  case "SessionStart":
28
- return await handleSessionStart(input, client, deps);
16
+ return handleSessionStart(input, client, deps);
29
17
  case "PreToolUse":
30
- return await handlePreToolUse(input, client, deps);
18
+ return handlePreToolUse(input, client, deps);
31
19
  case "PostToolUse":
32
- return await handlePostToolUse(input, client);
20
+ return handlePostToolUse(input, client);
33
21
  case "PostToolUseFailure":
34
- return await handlePostToolUseFailure(input, client);
22
+ return handlePostToolUseFailure(input, client);
35
23
  case "PermissionRequest":
36
- return await handlePermissionRequest(input, client);
24
+ return handlePermissionRequest(input, client);
37
25
  case "UserPromptSubmit":
38
- return await handleUserPromptSubmit(input, client);
26
+ return handleUserPromptSubmit(input, client);
39
27
  case "SubagentStart":
40
- return await handleSubagentStart(input, client, deps);
28
+ return handleSubagentStart(input, client, deps);
41
29
  case "SubagentStop":
42
- return await handleSubagentStop(input, client);
30
+ return handleSubagentStop(input, client);
43
31
  case "Stop":
44
- return await handleStop(input, client);
32
+ return handleStop(input, client);
45
33
  case "SessionEnd":
46
- return await handleSessionEnd(input, client);
34
+ return handleSessionEnd(input, client);
47
35
  case "PreCompact":
48
- return await handlePreCompact(input, client);
36
+ return handlePreCompact(input, client);
49
37
  default:
50
38
  client.logger.debug("hook.passthrough", { event });
51
- client.tracer.record("hook.passthrough", "skipped", {
52
- attributes: { event },
53
- });
39
+ client.logger.activity("hook.passthrough", "skipped", { attributes: { event } });
54
40
  return {};
55
41
  }
56
- }
57
- finally {
58
- client.tracer.clearContext();
59
- }
42
+ });
60
43
  }
61
- // ─── SessionStart ───────────────────────────────────────────────────
62
44
  async function handleSessionStart(input, client, deps) {
63
45
  client.logger.info("lifecycle.session_start", {
64
46
  sessionId: input.session_id,
@@ -66,107 +48,65 @@ async function handleSessionStart(input, client, deps) {
66
48
  source: input.source,
67
49
  cwd: input.cwd,
68
50
  });
69
- client.tracer.record("session.start", "ok", {
70
- attributes: { model: input.model, source: input.source },
71
- });
72
- // Run the user login (interactive PKCE on first session, refresh when
73
- // needed). This runs on every session and never blocks — enforcement is
74
- // governed solely by permissionMode at tool-call time.
75
- const userGate = deps.userLogin ?? argus_1.ensureUserAuthenticated;
76
- await userGate(client, {
77
- binName: BIN_NAME,
51
+ await (0, argus_1.sessionStart)(client, {
78
52
  harness: HARNESS,
53
+ binName: "ory-cn",
54
+ userLogin: deps.userLogin,
55
+ agentGate: deps.agentGate,
56
+ activityAttributes: { model: input.model, source: input.source },
79
57
  });
80
- // Resolve the agent identity (machine credentials) regardless of how user
81
- // auth went — never blocks, attaches the agent's bearer token to outgoing
82
- // Ory API calls.
83
- const agentGate = deps.agentGate ?? argus_1.ensureAgentIdentity;
84
- await agentGate(client, { projectUrl: (0, argus_1.resolveConfig)().projectUrl, harness: HARNESS });
85
- // Record the user→agent delegation for the audit trail. Best-effort,
86
- // fail-open, and written at most once per install.
87
- await (0, argus_1.writeUserDelegatesAgent)(client);
88
58
  return {};
89
59
  }
90
- // ─── PreToolUse ─────────────────────────────────────────────────────
91
- async function handlePreToolUse(input, client, deps = {}) {
60
+ async function handlePreToolUse(input, client, deps) {
92
61
  const toolName = input.tool_name ?? "unknown";
93
62
  client.logger.info("lifecycle.pre_tool_use", {
94
63
  sessionId: input.session_id,
95
64
  toolName,
96
65
  toolInput: input.tool_input,
97
66
  });
98
- const inputSummary = (0, argus_1.summarizeToolInput)(toolName, input.tool_input);
99
- // If the agent is invoking a sub-agent (a Task/Agent tool call with a
100
- // subagent_type), resolve a distinct OAuth2 identity for it and record the
101
- // delegation. Best-effort — never blocks the actual tool call.
102
- await maybeRegisterSubAgent(toolName, input.tool_input, client, deps);
103
- // In audit-only mode, log the invocation but skip permission checks
104
- if ((0, argus_1.resolveConfig)().auditOnly) {
105
- client.tracer.record("tool.invoke", "ok", {
106
- attributes: { toolName, ...inputSummary },
107
- });
108
- return {};
109
- }
110
- const subject = (0, argus_1.resolveUserSubject)(client, `session:${input.session_id}`);
111
- const subjectId = (0, argus_1.subjectLabel)(subject);
112
- const namespace = resolveNamespace();
113
- try {
114
- // No Continue-specific MCP tool parser exists in core, so gate every tool
115
- // by name. MCP tools (if Continue namespaces them) flow through the same
116
- // path keyed on the tool name.
117
- const outcome = await (0, argus_1.gateToolCall)(client, {
67
+ const subAgentType = subagentType(toolName, input.tool_input);
68
+ if (subAgentType) {
69
+ await (0, argus_1.registerSubagent)(client, {
118
70
  harness: HARNESS,
119
- toolName,
120
- check: { namespace, object: toolName, relation: "use", ...subject },
121
- spanAttributes: { toolName },
122
- });
123
- // Interactive tools surface UI to the user and aren't external-system tool
124
- // executions. gateToolCall has already recorded the `user.interaction`
125
- // audit span; pass through with no tool.invoke/tool.block.
126
- if (outcome.kind === "interactive") {
127
- return {};
128
- }
129
- const decision = outcome;
130
- if (decision.kind === "fail_open") {
131
- return handlePermissionError(decision.error, toolName, client);
132
- }
133
- const attrs = { toolName, ...inputSummary };
134
- const decisionAttrs = decision.spanAttributes;
135
- if (decision.kind === "allow") {
136
- client.tracer.record("tool.invoke", "ok", {
137
- attributes: { ...attrs, ...decisionAttrs, allowed: true },
138
- });
139
- return {};
140
- }
141
- if (decision.kind === "observe") {
142
- client.tracer.record("tool.block", "denied", {
143
- attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
144
- });
145
- client.tracer.record("tool.invoke", "ok", {
146
- attributes: { ...attrs, ...decisionAttrs, allowed: false, observed: true },
147
- });
148
- return {};
149
- }
150
- // deny
151
- client.tracer.record("tool.block", "denied", {
152
- attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
71
+ subAgentType,
72
+ perSpawnId: input.tool_use_id,
73
+ subAgentGate: deps.subAgentGate,
153
74
  });
154
- const reason = (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace });
155
- return {
156
- decision: "block",
157
- reason,
158
- hookSpecificOutput: {
159
- hookEventName: "PreToolUse",
160
- permissionDecision: "deny",
161
- permissionDecisionReason: reason,
162
- },
163
- };
164
- }
165
- catch (err) {
166
- return handlePermissionError(err, toolName, client);
167
75
  }
76
+ const result = await (0, argus_1.gate)(client, {
77
+ harness: HARNESS,
78
+ toolName,
79
+ toolArgs: input.tool_input,
80
+ subjectFallback: `session:${input.session_id}`,
81
+ principals: actingSubagentPrincipals(input),
82
+ });
83
+ if (!result.blocked)
84
+ return {};
85
+ const reason = result.denialMessage ?? "Ory permission denied";
86
+ return {
87
+ decision: "block",
88
+ reason,
89
+ hookSpecificOutput: {
90
+ hookEventName: "PreToolUse",
91
+ permissionDecision: "deny",
92
+ permissionDecisionReason: reason,
93
+ },
94
+ };
95
+ }
96
+ function actingSubagentPrincipals(input) {
97
+ const type = input.agent_type;
98
+ if (!input.agent_id || !type)
99
+ return undefined;
100
+ const credential = (0, argus_1.loadSubAgentDynamicCredentials)(HARNESS, input.session_id, type);
101
+ if (!credential)
102
+ return undefined;
103
+ return {
104
+ subAgentClientId: credential.clientId,
105
+ subAgentType: type,
106
+ perSpawnId: input.agent_id,
107
+ sessionId: input.session_id,
108
+ };
168
109
  }
169
- // ─── PostToolUse ────────────────────────────────────────────────────
170
110
  async function handlePostToolUse(input, client) {
171
111
  const toolName = input.tool_name ?? "unknown";
172
112
  client.logger.info("lifecycle.post_tool_use", {
@@ -174,17 +114,14 @@ async function handlePostToolUse(input, client) {
174
114
  toolName,
175
115
  toolResponse: input.tool_response,
176
116
  });
177
- client.tracer.record("tool.complete", "ok", {
178
- attributes: {
179
- toolName,
180
- toolUseId: input.tool_use_id,
181
- ...(0, argus_1.summarizeToolInput)(toolName, input.tool_input),
182
- ...(0, argus_1.summarizeToolOutput)(toolName, input.tool_response),
183
- },
117
+ (0, argus_1.complete)(client, {
118
+ toolName,
119
+ input: input.tool_input,
120
+ output: input.tool_response,
121
+ extraActivityAttributes: { toolUseId: input.tool_use_id },
184
122
  });
185
123
  return {};
186
124
  }
187
- // ─── PostToolUseFailure ─────────────────────────────────────────────
188
125
  async function handlePostToolUseFailure(input, client) {
189
126
  const toolName = input.tool_name ?? "unknown";
190
127
  client.logger.info("lifecycle.post_tool_use_failure", {
@@ -192,252 +129,142 @@ async function handlePostToolUseFailure(input, client) {
192
129
  toolName,
193
130
  toolUseId: input.tool_use_id,
194
131
  });
195
- client.tracer.record("tool.fail", "error", {
196
- attributes: {
197
- toolName,
198
- toolUseId: input.tool_use_id,
199
- toolError: typeof input.error === "string"
200
- ? input.error.slice(0, 500)
201
- : undefined,
202
- ...(0, argus_1.summarizeToolInput)(toolName, input.tool_input),
203
- },
132
+ (0, argus_1.fail)(client, {
133
+ toolName,
134
+ input: input.tool_input,
135
+ error: input.error,
136
+ extraActivityAttributes: { toolUseId: input.tool_use_id },
204
137
  });
205
138
  return {};
206
139
  }
207
- // ─── UserPromptSubmit ──────────────────────────────────────────────
140
+ async function handlePermissionRequest(input, client) {
141
+ const toolName = input.tool_name ?? "unknown";
142
+ client.logger.info("lifecycle.permission_request", {
143
+ sessionId: input.session_id,
144
+ toolName,
145
+ toolInput: input.tool_input,
146
+ });
147
+ const result = await (0, argus_1.decideTool)(client, {
148
+ harness: HARNESS,
149
+ toolName,
150
+ toolArgs: input.tool_input,
151
+ subjectFallback: `session:${input.session_id}`,
152
+ });
153
+ if (result.kind === "not_connected") {
154
+ return permissionRequestFallback("Ory Agent Security not connected — deferring to user");
155
+ }
156
+ if (result.kind === "interactive") {
157
+ return permissionRequestFallback("Interactive tool — deferring to user");
158
+ }
159
+ if (result.kind === "fail_open") {
160
+ return permissionRequestFallback(permissionFailureReason(result.decision));
161
+ }
162
+ if (result.kind === "deny") {
163
+ return permissionRequestDecision("deny", result.denialMessage ?? "Ory permission denied");
164
+ }
165
+ if (result.kind === "observe") {
166
+ return permissionRequestFallback("Ory observe mode — denied by policy, deferring to user");
167
+ }
168
+ return permissionRequestDecision("allow", "Ory permission granted");
169
+ }
170
+ function permissionRequestDecision(behavior, reason) {
171
+ return {
172
+ hookSpecificOutput: {
173
+ hookEventName: "PermissionRequest",
174
+ decision: { behavior },
175
+ message: reason,
176
+ },
177
+ };
178
+ }
179
+ function permissionRequestFallback(reason) {
180
+ return {
181
+ hookSpecificOutput: { hookEventName: "PermissionRequest", message: reason },
182
+ };
183
+ }
184
+ function permissionFailureReason(decision) {
185
+ if (decision.kind !== "fail_open")
186
+ return "Ory permission check failed";
187
+ const { code, message } = decision.error;
188
+ return code === "network_error" || code === "rate_limited"
189
+ ? `Ory unavailable (${code}), falling back to user prompt`
190
+ : `Ory permission check failed: ${message}`;
191
+ }
208
192
  async function handleUserPromptSubmit(input, client) {
209
193
  client.logger.info("lifecycle.user_prompt_submit", {
210
194
  sessionId: input.session_id,
211
195
  promptLen: input.prompt?.length,
212
196
  });
213
- client.tracer.record("user.prompt", "ok", {
197
+ client.logger.activity("user.prompt", "ok", {
214
198
  attributes: { promptLen: input.prompt?.length },
215
199
  });
216
200
  return {};
217
201
  }
218
- // ─── SubagentStart ─────────────────────────────────────────────────
219
202
  async function handleSubagentStart(input, client, deps) {
220
203
  const subAgentType = input.subagent_type ?? input.agent_type;
221
- const agentId = input.agent_id;
222
204
  client.logger.info("lifecycle.subagent_start", {
223
205
  sessionId: input.session_id,
224
206
  subAgentType,
225
- agentId,
207
+ agentId: input.agent_id,
226
208
  });
227
- client.tracer.record("subagent.start", "ok", {
228
- attributes: { subAgentType, agentId },
209
+ client.logger.activity("subagent.start", "ok", {
210
+ attributes: { subAgentType, agentId: input.agent_id },
229
211
  });
230
212
  if (subAgentType) {
231
- await registerSubAgent(subAgentType, client, deps);
232
- }
233
- else {
234
- client.logger.debug("subagent_start.no_type", {
235
- message: "SubagentStart event without subagent_type — skipping DCR.",
213
+ await (0, argus_1.registerSubagent)(client, {
214
+ harness: HARNESS,
215
+ subAgentType,
216
+ perSpawnId: input.agent_id,
217
+ subAgentGate: deps.subAgentGate,
236
218
  });
237
219
  }
238
220
  return {};
239
221
  }
240
- // ─── SubagentStop ──────────────────────────────────────────────────
241
222
  async function handleSubagentStop(input, client) {
223
+ const subAgentType = input.subagent_type ?? input.agent_type;
242
224
  client.logger.info("lifecycle.subagent_stop", {
243
225
  sessionId: input.session_id,
244
- subAgentType: input.subagent_type ?? input.agent_type,
226
+ subAgentType,
245
227
  agentId: input.agent_id,
246
228
  });
247
- client.tracer.record("subagent.stop", "ok", {
229
+ client.logger.activity("subagent.stop", "ok", {
248
230
  attributes: {
249
- subAgentType: input.subagent_type ?? input.agent_type,
231
+ subAgentType,
250
232
  agentId: input.agent_id,
251
233
  responseLen: input.subagent_response?.length,
252
234
  },
253
235
  });
254
236
  return {};
255
237
  }
256
- // ─── Stop ──────────────────────────────────────────────────────────
257
238
  async function handleStop(input, client) {
258
- client.logger.info("lifecycle.stop", {
259
- sessionId: input.session_id,
260
- });
261
- client.tracer.record("turn.stop", "ok", {
239
+ client.logger.info("lifecycle.stop", { sessionId: input.session_id });
240
+ client.logger.activity("turn.stop", "ok", {
262
241
  attributes: { responseLen: input.last_assistant_message?.length },
263
242
  });
264
243
  return {};
265
244
  }
266
- // ─── SessionEnd ────────────────────────────────────────────────────
267
245
  async function handleSessionEnd(input, client) {
268
246
  client.logger.info("lifecycle.session_end", {
269
247
  sessionId: input.session_id,
270
248
  reason: input.reason,
271
249
  });
272
- client.tracer.record("session.end", "ok", {
273
- attributes: { reason: input.reason },
274
- });
250
+ client.logger.activity("session.end", "ok", { attributes: { reason: input.reason } });
275
251
  return {};
276
252
  }
277
- // ─── PreCompact ────────────────────────────────────────────────────
278
253
  async function handlePreCompact(input, client) {
279
254
  client.logger.info("lifecycle.pre_compact", {
280
255
  sessionId: input.session_id,
281
256
  trigger: input.trigger,
282
257
  });
283
- client.tracer.record("compaction", "ok", {
258
+ client.logger.activity("compaction", "ok", {
284
259
  attributes: { trigger: input.trigger, stage: "pre_compact" },
285
260
  });
286
261
  return {};
287
262
  }
288
- // ─── PermissionRequest ──────────────────────────────────────────────
289
- //
290
- // Continue surfaces a structured permission decision via
291
- // hookSpecificOutput.decision.{behavior}. Omitting `decision` falls through to
292
- // Continue's normal user prompt. This event is trace-only on the auth side: it
293
- // records the audit span and answers allow/deny based on policy, but never
294
- // hard-blocks the session (the answer is advisory to the user prompt).
295
- async function handlePermissionRequest(input, client) {
296
- const toolName = input.tool_name ?? "unknown";
297
- client.logger.info("lifecycle.permission_request", {
298
- sessionId: input.session_id,
299
- toolName,
300
- toolInput: input.tool_input,
301
- });
302
- // Audit-only mode: omit `decision` so Continue falls through to its normal
303
- // user-permission prompt.
304
- if ((0, argus_1.resolveConfig)().auditOnly) {
305
- return permissionRequestFallback("Audit-only mode — deferring to user");
306
- }
307
- // Interactive tools: the harness is about to surface UI to the user. Trace
308
- // and fall through — Ory shouldn't pre-answer a prompt meant for the human.
309
- if ((0, argus_1.isInteractiveTool)(HARNESS, toolName)) {
310
- client.tracer.record("user.interaction", "ok", {
311
- attributes: { harness: HARNESS, toolName, kind: "permission-request" },
312
- });
313
- return permissionRequestFallback("Interactive tool — deferring to user");
314
- }
315
- const subject = (0, argus_1.resolveUserSubject)(client, `session:${input.session_id}`);
316
- const subjectId = (0, argus_1.subjectLabel)(subject);
317
- const namespace = resolveNamespace();
318
- try {
319
- const outcome = await (0, argus_1.gateToolCall)(client, {
320
- harness: HARNESS,
321
- toolName,
322
- check: { namespace, object: toolName, relation: "use", ...subject },
323
- spanAttributes: { toolName },
324
- });
325
- if (outcome.kind === "interactive") {
326
- return permissionRequestFallback("Interactive tool — deferring to user");
327
- }
328
- const decision = outcome;
329
- if (decision.kind === "fail_open") {
330
- const code = decision.error.code;
331
- const fallbackReason = code === "network_error" || code === "rate_limited"
332
- ? `Ory unavailable (${code}), falling back to user prompt`
333
- : `Ory permission check failed: ${decision.error.message}`;
334
- return permissionRequestFallback(fallbackReason);
335
- }
336
- if (decision.kind === "deny") {
337
- return permissionRequestDecision("deny", (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }));
338
- }
339
- if (decision.kind === "observe") {
340
- // Observe-mode deny: the permission.observe_deny audit span is already
341
- // recorded by the gate. Omit `decision` so Continue falls through to
342
- // its native user prompt — observe mode must never escalate a
343
- // policy-denied tool into an auto-approval.
344
- return permissionRequestFallback("Ory observe mode — denied by policy, deferring to user");
345
- }
346
- return permissionRequestDecision("allow", "Ory permission granted");
347
- }
348
- catch (err) {
349
- const oryErr = err;
350
- const fallbackReason = oryErr.code === "network_error" || oryErr.code === "rate_limited"
351
- ? `Ory unavailable (${oryErr.code}), falling back to user prompt`
352
- : `Ory permission check failed: ${oryErr.message}`;
353
- return permissionRequestFallback(fallbackReason);
354
- }
355
- }
356
- function permissionRequestDecision(behavior, reason) {
357
- return {
358
- hookSpecificOutput: {
359
- hookEventName: "PermissionRequest",
360
- decision: { behavior },
361
- message: reason,
362
- },
363
- };
364
- }
365
- function permissionRequestFallback(reason) {
366
- return {
367
- hookSpecificOutput: {
368
- hookEventName: "PermissionRequest",
369
- message: reason,
370
- },
371
- };
372
- }
373
- // ─── Helpers ────────────────────────────────────────────────────────
374
- function resolveNamespace() {
375
- return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
376
- }
377
- /**
378
- * Register an OAuth2 identity for a sub-agent and write the
379
- * `agent → subagent` delegation tuple. Fail-open and written at most once
380
- * per install.
381
- */
382
- async function registerSubAgent(subAgentType, client, deps) {
383
- const subAgentGate = deps.subAgentGate ?? argus_1.ensureSubAgentIdentity;
384
- let identity;
385
- try {
386
- identity = await subAgentGate(client, {
387
- subAgentType,
388
- projectUrl: (0, argus_1.resolveConfig)().projectUrl,
389
- harness: HARNESS,
390
- });
391
- }
392
- catch (err) {
393
- client.logger.warn("subagent.identity.failed", {
394
- subAgentType,
395
- message: err instanceof Error ? err.message : String(err),
396
- });
397
- return;
398
- }
399
- if (identity.kind !== "dynamic" || !identity.subject)
400
- return;
401
- await (0, argus_1.writeAgentDelegatesSubagent)(client, identity.subject, subAgentType);
402
- }
403
- /**
404
- * Fallback for the case where a sub-agent invocation appears as a Task/Agent
405
- * tool call in PreToolUse with a `subagent_type` field on tool_input.
406
- * registerSubAgent is idempotent so running both this and the dedicated
407
- * SubagentStart path is harmless.
408
- */
409
- async function maybeRegisterSubAgent(toolName, toolInput, client, deps) {
263
+ function subagentType(toolName, toolInput) {
410
264
  if (toolName !== "Task" && toolName !== "Agent")
411
- return;
412
- const subAgentType = typeof toolInput === "object"
413
- && toolInput !== null
414
- && typeof toolInput.subagent_type === "string"
415
- ? toolInput.subagent_type
416
- : undefined;
417
- if (!subAgentType)
418
- return;
419
- await registerSubAgent(subAgentType, client, deps);
420
- }
421
- function handlePermissionError(oryErr, toolName, client) {
422
- if (oryErr.code === "network_error") {
423
- client.logger.warn("permission.network_error", {
424
- toolName,
425
- message: "Ory unreachable, failing open",
426
- });
427
- return {};
428
- }
429
- if (oryErr.code === "rate_limited") {
430
- client.logger.warn("permission.rate_limited", {
431
- toolName,
432
- message: "Ory rate limited, failing open",
433
- });
434
- return {};
435
- }
436
- // For other errors, also fail open but log prominently
437
- client.logger.error("permission.check.error", {
438
- toolName,
439
- code: oryErr.code,
440
- message: oryErr.message,
441
- });
442
- return {};
265
+ return undefined;
266
+ if (typeof toolInput !== "object" || toolInput === null)
267
+ return undefined;
268
+ const value = toolInput.subagent_type;
269
+ return typeof value === "string" ? value : undefined;
443
270
  }
package/dist/hook.js CHANGED
@@ -14,6 +14,16 @@
14
14
  Object.defineProperty(exports, "__esModule", { value: true });
15
15
  const argus_1 = require("@ory/argus");
16
16
  const handlers_js_1 = require("./handlers.js");
17
+ const EVENT_FLUSH_TIMEOUT_MS = 750;
18
+ let client;
19
+ async function flushEvents() {
20
+ try {
21
+ await client?.flushEvents(EVENT_FLUSH_TIMEOUT_MS);
22
+ }
23
+ catch {
24
+ // The hook decision must not depend on activity delivery.
25
+ }
26
+ }
17
27
  /**
18
28
  * Read all of stdin as a string.
19
29
  *
@@ -47,7 +57,7 @@ function readStdin() {
47
57
  });
48
58
  }
49
59
  async function main() {
50
- const client = argus_1.OryAgentClient.fromEnv("continue");
60
+ client = argus_1.OryAgentClient.fromEnv("continue");
51
61
  const raw = await readStdin();
52
62
  let input;
53
63
  try {
@@ -55,22 +65,22 @@ async function main() {
55
65
  }
56
66
  catch {
57
67
  client.logger.error("hook.stdin.parse_failed", { raw: raw.slice(0, 200) });
58
- await client.tracer.shutdown();
59
68
  process.exit(0); // Don't block on parse errors
60
69
  }
61
70
  const output = await (0, handlers_js_1.handleHookEvent)(input, client);
62
71
  if (output.decision === "block") {
63
72
  process.stdout.write(JSON.stringify(output));
64
- await client.tracer.shutdown();
73
+ await flushEvents();
65
74
  process.exit(2);
66
75
  }
67
76
  if (output.hookSpecificOutput || output.systemMessage) {
68
77
  process.stdout.write(JSON.stringify(output));
69
78
  }
70
- await client.tracer.shutdown();
79
+ await flushEvents();
71
80
  process.exit(0);
72
81
  }
73
- main().catch((err) => {
82
+ main().catch(async (err) => {
74
83
  process.stderr.write(`[ory-agent] fatal: ${err}\n`);
84
+ await flushEvents();
75
85
  process.exit(0); // Fail open
76
86
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ory/continue",
3
- "version": "0.14.0",
3
+ "version": "1.0.1",
4
4
  "description": "Ory plugin for Continue CLI (cn): 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",
@@ -36,8 +36,7 @@
36
36
  "agent-security",
37
37
  "guardrails",
38
38
  "llm",
39
- "tracing",
40
- "distributed-tracing",
39
+ "activity-auditing",
41
40
  "observability",
42
41
  "kratos",
43
42
  "keto",
@@ -48,6 +47,10 @@
48
47
  "registry": "https://registry.npmjs.org/",
49
48
  "provenance": true
50
49
  },
50
+ "reova": {
51
+ "enabled": true,
52
+ "endpoint": "https://telemetry.reo.dev/data"
53
+ },
51
54
  "main": "dist/index.js",
52
55
  "types": "dist/index.d.ts",
53
56
  "exports": {
@@ -63,12 +66,11 @@
63
66
  },
64
67
  "files": [
65
68
  "dist",
66
- "!dist/dev",
67
69
  "!dist/**/*.tsbuildinfo"
68
70
  ],
69
71
  "dependencies": {
70
- "reo-census": "^1.2.8",
71
- "@ory/argus": "0.14.0"
72
+ "reova": "^0.7.0",
73
+ "@ory/argus": "1.0.1"
72
74
  },
73
75
  "engines": {
74
76
  "node": ">=22"
@@ -78,7 +80,6 @@
78
80
  "clean": "rm -rf dist *.tsbuildinfo",
79
81
  "test": "vitest run",
80
82
  "test:watch": "vitest",
81
- "typecheck": "tsc --noEmit",
82
- "dev": "node dist/dev/launcher.js"
83
+ "typecheck": "tsc --noEmit"
83
84
  }
84
85
  }