@ory/argus 0.8.1 → 0.9.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/assets/skills/ory-build-agent/SKILL.md +2 -2
- package/assets/skills/ory-e2b-sandbox/SKILL.md +1 -1
- package/assets/skills/permissions-onboarding/SKILL.md +4 -2
- package/dist/agent-auth.js +1 -1
- package/dist/cli.d.ts +10 -2
- package/dist/cli.js +70 -11
- package/dist/config.d.ts +12 -0
- package/dist/config.js +5 -1
- package/dist/dev.js +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +12 -1
- package/dist/lifecycle.d.ts +59 -0
- package/dist/lifecycle.js +123 -0
- package/dist/local/manager.js +12 -3
- package/dist/local/seed.d.ts +7 -0
- package/dist/local/seed.js +10 -4
- package/dist/permissions-cli.js +5 -5
- package/dist/permissions.d.ts +51 -0
- package/dist/permissions.js +48 -0
- package/dist/setup.js +8 -3
- package/dist/status-cli.js +2 -2
- package/dist/tool-catalog.d.ts +31 -0
- package/dist/tool-catalog.js +58 -1
- package/dist/tracer.d.ts +1 -1
- package/dist/user-login.d.ts +2 -1
- package/dist/user-login.js +10 -4
- package/package.json +1 -1
|
@@ -86,7 +86,7 @@ import {
|
|
|
86
86
|
const client = OryAgentClient.fromEnv("my-agent");
|
|
87
87
|
const { projectUrl } = resolveConfig();
|
|
88
88
|
|
|
89
|
-
// 1. User gate — interactive PKCE when ORY_USER_LOGIN=
|
|
89
|
+
// 1. User gate — interactive PKCE when ORY_USER_LOGIN=true, no-op otherwise.
|
|
90
90
|
const userDecision = await ensureUserAuthenticated(client, {
|
|
91
91
|
binName: "my-agent",
|
|
92
92
|
harness: "my-agent",
|
|
@@ -409,7 +409,7 @@ PKCE flow, permission tuples, and trace spans are all visible:
|
|
|
409
409
|
1. {{REF_LOCAL_UP}} — brings up Kratos / Keto / Hydra on `localhost:4000`
|
|
410
410
|
and seeds a demo user. The banner prints the email + password.
|
|
411
411
|
2. Export the env vars the launcher writes (`ORY_PROJECT_URL`,
|
|
412
|
-
`ORY_USER_LOGIN=
|
|
412
|
+
`ORY_USER_LOGIN=true`, `ORY_OAUTH2_CLIENT_ID`, optional
|
|
413
413
|
`ORY_AGENT_TRACE_FILE` for an NDJSON span log).
|
|
414
414
|
3. Start your agent. Confirm the browser opens for PKCE login.
|
|
415
415
|
4. Invoke a gated tool and `tail -f $ORY_AGENT_TRACE_FILE | jq .` — you
|
|
@@ -66,7 +66,7 @@ export const template = Template()
|
|
|
66
66
|
// Sandbox runtime defaults. Per-tenant secrets (project URL, tokens, client
|
|
67
67
|
// IDs) MUST be passed at Sandbox.create() time, never baked into the image.
|
|
68
68
|
.setEnvs({
|
|
69
|
-
ORY_USER_LOGIN: "
|
|
69
|
+
ORY_USER_LOGIN: "true",
|
|
70
70
|
ORY_PERMISSION_MODE: "observe",
|
|
71
71
|
ORY_PERMISSION_NAMESPACE: "AgentTools",
|
|
72
72
|
ORY_AGENT_DEBUG: "true",
|
|
@@ -41,9 +41,11 @@ What you want to see:
|
|
|
41
41
|
If any of those are wrong, fix them before continuing:
|
|
42
42
|
|
|
43
43
|
```sh
|
|
44
|
-
{{NPX}} configure --project-url <URL> --api-key <KEY>
|
|
44
|
+
{{NPX}} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
+
`--oauth2-client-id` is the public OAuth2 client registered in your Ory project (required when `ORY_USER_LOGIN=true`; see the *Register the user OAuth2 client* section of the plugin README).
|
|
48
|
+
|
|
47
49
|
## Step 2: Look at the current permission posture
|
|
48
50
|
|
|
49
51
|
```sh
|
|
@@ -94,7 +96,7 @@ Two common failure modes:
|
|
|
94
96
|
1. **No user identity cached.** Bootstrap needs to know which subject
|
|
95
97
|
to grant tuples to. If user login has never run (no PKCE login,
|
|
96
98
|
no `ORY_USER_SUBJECT_ID`), the command refuses. Run the harness once
|
|
97
|
-
with `ORY_USER_LOGIN=
|
|
99
|
+
with `ORY_USER_LOGIN=true` to cache a user token, or set
|
|
98
100
|
`ORY_USER_SUBJECT_ID=<id>` to target a known subject.
|
|
99
101
|
2. **Credentials lack write scope on the permission namespace.** The
|
|
100
102
|
command prints the full tuple list so you can apply them manually
|
package/dist/agent-auth.js
CHANGED
|
@@ -290,7 +290,7 @@ async function resolveAgentCredentials(options = {}) {
|
|
|
290
290
|
}
|
|
291
291
|
return {
|
|
292
292
|
kind: "none",
|
|
293
|
-
reason: "No agent credentials configured. Run with a user session (ORY_USER_LOGIN=
|
|
293
|
+
reason: "No agent credentials configured. Run with a user session (ORY_USER_LOGIN=true) or set ORY_AGENT_API_KEY / ORY_AGENT_CLIENT_ID + ORY_AGENT_CLIENT_SECRET / ORY_AGENT_REGISTRATION_TOKEN.",
|
|
294
294
|
warnings,
|
|
295
295
|
};
|
|
296
296
|
}
|
package/dist/cli.d.ts
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared `configure` command implementation for all plugin CLIs.
|
|
3
3
|
*
|
|
4
|
-
* Parses --project-url
|
|
5
|
-
* shows the current configuration. Otherwise saves
|
|
4
|
+
* Parses --project-url, --api-key, and --oauth2-client-id from args.
|
|
5
|
+
* If none is provided, shows the current configuration. Otherwise saves
|
|
6
|
+
* the provided values.
|
|
7
|
+
*
|
|
8
|
+
* When `--project-url` is provided, requires the OAuth2 client id to be
|
|
9
|
+
* resolvable from one of: the `--oauth2-client-id` flag, the
|
|
10
|
+
* `ORY_OAUTH2_CLIENT_ID` env var, or an already-persisted value in the
|
|
11
|
+
* shared config. The PKCE browser flow that runs when
|
|
12
|
+
* `ORY_USER_LOGIN=true` can't self-register that client — surfacing the
|
|
13
|
+
* requirement here keeps users from a silently-broken setup later.
|
|
6
14
|
*/
|
|
7
15
|
export declare function runConfigureCommand(binName: string, args: string[]): void;
|
|
8
16
|
/**
|
package/dist/cli.js
CHANGED
|
@@ -54,12 +54,21 @@ const tracer_js_1 = require("./tracer.js");
|
|
|
54
54
|
/**
|
|
55
55
|
* Shared `configure` command implementation for all plugin CLIs.
|
|
56
56
|
*
|
|
57
|
-
* Parses --project-url
|
|
58
|
-
* shows the current configuration. Otherwise saves
|
|
57
|
+
* Parses --project-url, --api-key, and --oauth2-client-id from args.
|
|
58
|
+
* If none is provided, shows the current configuration. Otherwise saves
|
|
59
|
+
* the provided values.
|
|
60
|
+
*
|
|
61
|
+
* When `--project-url` is provided, requires the OAuth2 client id to be
|
|
62
|
+
* resolvable from one of: the `--oauth2-client-id` flag, the
|
|
63
|
+
* `ORY_OAUTH2_CLIENT_ID` env var, or an already-persisted value in the
|
|
64
|
+
* shared config. The PKCE browser flow that runs when
|
|
65
|
+
* `ORY_USER_LOGIN=true` can't self-register that client — surfacing the
|
|
66
|
+
* requirement here keeps users from a silently-broken setup later.
|
|
59
67
|
*/
|
|
60
68
|
function runConfigureCommand(binName, args) {
|
|
61
69
|
let projectUrl;
|
|
62
70
|
let apiKey;
|
|
71
|
+
let oauth2ClientId;
|
|
63
72
|
let auditOnly = false;
|
|
64
73
|
for (let i = 0; i < args.length; i++) {
|
|
65
74
|
switch (args[i]) {
|
|
@@ -69,30 +78,35 @@ function runConfigureCommand(binName, args) {
|
|
|
69
78
|
case "--api-key":
|
|
70
79
|
apiKey = args[++i];
|
|
71
80
|
break;
|
|
81
|
+
case "--oauth2-client-id":
|
|
82
|
+
oauth2ClientId = args[++i];
|
|
83
|
+
break;
|
|
72
84
|
case "--audit-only":
|
|
73
85
|
auditOnly = true;
|
|
74
86
|
break;
|
|
75
87
|
}
|
|
76
88
|
}
|
|
77
|
-
if (!projectUrl && !apiKey && !auditOnly) {
|
|
89
|
+
if (!projectUrl && !apiKey && !oauth2ClientId && !auditOnly) {
|
|
78
90
|
const resolved = (0, config_js_1.resolveConfig)();
|
|
79
91
|
const configPath = (0, config_js_1.getConfigPath)();
|
|
80
92
|
console.log("Ory Plugin Configuration");
|
|
81
93
|
console.log("========================");
|
|
82
94
|
console.log("");
|
|
83
|
-
console.log(`Config file:
|
|
84
|
-
console.log(`Project URL:
|
|
85
|
-
console.log(`API Key:
|
|
86
|
-
console.log(`
|
|
95
|
+
console.log(`Config file: ${configPath}`);
|
|
96
|
+
console.log(`Project URL: ${resolved.projectUrl ?? "(not set)"} [${resolved.projectUrlSource}]`);
|
|
97
|
+
console.log(`API Key: ${resolved.apiKey ? "(set)" : "(not set)"} [${resolved.apiKeySource}]`);
|
|
98
|
+
console.log(`OAuth2 Client ID: ${resolved.oauth2ClientId ?? "(not set)"} [${resolved.oauth2ClientIdSource}]`);
|
|
99
|
+
console.log(`Audit Only: ${resolved.auditOnly ? "yes" : "no"}`);
|
|
87
100
|
console.log("");
|
|
88
101
|
console.log("To configure:");
|
|
89
|
-
console.log(` npx ${binName} configure --project-url <URL> --api-key <KEY
|
|
102
|
+
console.log(` npx ${binName} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]`);
|
|
90
103
|
console.log("");
|
|
91
104
|
console.log("To enable audit logging only (no auth or permission checks):");
|
|
92
105
|
console.log(` npx ${binName} configure --audit-only`);
|
|
93
106
|
console.log("");
|
|
94
107
|
console.log("Or set environment variables:");
|
|
95
108
|
console.log(" export ORY_PROJECT_URL=https://your-project.projects.oryapis.com");
|
|
109
|
+
console.log(" export ORY_OAUTH2_CLIENT_ID=<public OAuth2 client id>");
|
|
96
110
|
console.log(" export ORY_AGENT_API_KEY=ory_pat_...");
|
|
97
111
|
return;
|
|
98
112
|
}
|
|
@@ -106,11 +120,49 @@ function runConfigureCommand(binName, args) {
|
|
|
106
120
|
console.log("This configuration is shared across all Ory agent plugins.");
|
|
107
121
|
return;
|
|
108
122
|
}
|
|
123
|
+
// When --project-url is provided we need a usable OAuth2 client id for
|
|
124
|
+
// the user PKCE flow — block the configure with a clear error if none
|
|
125
|
+
// is reachable from the flag, env, or already-persisted config.
|
|
126
|
+
if (projectUrl) {
|
|
127
|
+
const persisted = (0, config_js_1.loadConfig)().oauth2ClientId;
|
|
128
|
+
const envClientId = process.env.ORY_OAUTH2_CLIENT_ID;
|
|
129
|
+
const resolved = oauth2ClientId ?? envClientId ?? persisted;
|
|
130
|
+
if (!resolved) {
|
|
131
|
+
console.error("Error: --project-url requires an OAuth2 client id for the user PKCE flow.\n" +
|
|
132
|
+
"\n" +
|
|
133
|
+
"Provide one with --oauth2-client-id <id>, or set ORY_OAUTH2_CLIENT_ID in your\n" +
|
|
134
|
+
"environment. The client must be a public OAuth2 client (no client secret,\n" +
|
|
135
|
+
"token_endpoint_auth_method=none) registered in your Ory project with all four\n" +
|
|
136
|
+
"loopback redirect URIs:\n" +
|
|
137
|
+
" http://127.0.0.1:47823/callback\n" +
|
|
138
|
+
" http://127.0.0.1:47824/callback\n" +
|
|
139
|
+
" http://127.0.0.1:47825/callback\n" +
|
|
140
|
+
" http://127.0.0.1:47826/callback\n" +
|
|
141
|
+
"\n" +
|
|
142
|
+
"Create it with:\n" +
|
|
143
|
+
" ory create oauth2-client --project <project-id> \\\n" +
|
|
144
|
+
" --name \"ory-agent-plugin\" \\\n" +
|
|
145
|
+
" --grant-type authorization_code,refresh_token \\\n" +
|
|
146
|
+
" --response-type code \\\n" +
|
|
147
|
+
" --scope openid,offline_access \\\n" +
|
|
148
|
+
" --token-endpoint-auth-method none \\\n" +
|
|
149
|
+
" --redirect-uri http://127.0.0.1:47823/callback \\\n" +
|
|
150
|
+
" --redirect-uri http://127.0.0.1:47824/callback \\\n" +
|
|
151
|
+
" --redirect-uri http://127.0.0.1:47825/callback \\\n" +
|
|
152
|
+
" --redirect-uri http://127.0.0.1:47826/callback\n" +
|
|
153
|
+
"\n" +
|
|
154
|
+
"If you only want audit logging (no auth or permission checks), use:\n" +
|
|
155
|
+
` npx ${binName} configure --audit-only`);
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
109
159
|
const update = {};
|
|
110
160
|
if (projectUrl)
|
|
111
161
|
update.projectUrl = projectUrl;
|
|
112
162
|
if (apiKey)
|
|
113
163
|
update.apiKey = apiKey;
|
|
164
|
+
if (oauth2ClientId)
|
|
165
|
+
update.oauth2ClientId = oauth2ClientId;
|
|
114
166
|
// Clear auditOnly when configuring with a project URL
|
|
115
167
|
update.auditOnly = false;
|
|
116
168
|
(0, config_js_1.saveConfig)(update);
|
|
@@ -120,9 +172,11 @@ function runConfigureCommand(binName, args) {
|
|
|
120
172
|
console.log(` Project URL: ${projectUrl}`);
|
|
121
173
|
if (apiKey)
|
|
122
174
|
console.log(` API Key: (set)`);
|
|
175
|
+
if (oauth2ClientId)
|
|
176
|
+
console.log(` OAuth2 Client ID: ${oauth2ClientId}`);
|
|
123
177
|
console.log("");
|
|
124
178
|
console.log("This configuration is shared across all Ory agent plugins.");
|
|
125
|
-
console.log("Environment variables (ORY_PROJECT_URL, ORY_AGENT_API_KEY) take precedence when set.");
|
|
179
|
+
console.log("Environment variables (ORY_PROJECT_URL, ORY_AGENT_API_KEY, ORY_OAUTH2_CLIENT_ID) take precedence when set.");
|
|
126
180
|
}
|
|
127
181
|
/**
|
|
128
182
|
* Print the "Configuration:" block showing Ory project URL and API key status.
|
|
@@ -136,6 +190,7 @@ function printOryConfig() {
|
|
|
136
190
|
console.log(` Mode: ${resolved.auditOnly ? "audit-only" : "full"}`);
|
|
137
191
|
console.log(` Project URL: ${resolved.projectUrl ?? "NOT SET"} [source: ${resolved.projectUrlSource}]`);
|
|
138
192
|
console.log(` API Key: ${resolved.apiKey ? "(set)" : "NOT SET"} [source: ${resolved.apiKeySource}]`);
|
|
193
|
+
console.log(` OAuth2 Client: ${resolved.oauth2ClientId ?? "NOT SET"} [source: ${resolved.oauth2ClientIdSource}]`);
|
|
139
194
|
console.log(` Permission mode: ${resolved.permissionMode} [source: ${resolved.permissionModeSource}]`);
|
|
140
195
|
console.log(` Namespace: ${namespace}`);
|
|
141
196
|
}
|
|
@@ -153,6 +208,7 @@ function printEnvironment() {
|
|
|
153
208
|
false,
|
|
154
209
|
],
|
|
155
210
|
["ORY_AGENT_CLIENT_ID", process.env.ORY_AGENT_CLIENT_ID, false],
|
|
211
|
+
["ORY_OAUTH2_CLIENT_ID", process.env.ORY_OAUTH2_CLIENT_ID, false],
|
|
156
212
|
[
|
|
157
213
|
"ORY_USER_SESSION_TOKEN",
|
|
158
214
|
process.env.ORY_USER_SESSION_TOKEN ? "(set)" : undefined,
|
|
@@ -210,10 +266,13 @@ function printEnvHelp(binName) {
|
|
|
210
266
|
console.log("Configure your Ory credentials (one of these methods):");
|
|
211
267
|
console.log("");
|
|
212
268
|
console.log(" Option 1 — Save to config file (persists across sessions):");
|
|
213
|
-
console.log(` npx ${binName} configure --project-url https://your-project.projects.oryapis.com
|
|
269
|
+
console.log(` npx ${binName} configure --project-url https://your-project.projects.oryapis.com \\`);
|
|
270
|
+
console.log(" --oauth2-client-id <public OAuth2 client id> \\");
|
|
271
|
+
console.log(" --api-key ory_pat_...");
|
|
214
272
|
console.log("");
|
|
215
273
|
console.log(" Option 2 — Set environment variables:");
|
|
216
274
|
console.log(" export ORY_PROJECT_URL=https://your-project.projects.oryapis.com");
|
|
275
|
+
console.log(" export ORY_OAUTH2_CLIENT_ID=<public OAuth2 client id>");
|
|
217
276
|
console.log(" export ORY_AGENT_API_KEY=ory_pat_...");
|
|
218
277
|
console.log("");
|
|
219
278
|
console.log("If neither is set when a session starts, the agent will prompt for configuration.");
|
|
@@ -371,7 +430,7 @@ async function interactiveConfigPrompt(binName) {
|
|
|
371
430
|
"Run one of the following commands in another terminal:",
|
|
372
431
|
"",
|
|
373
432
|
" Connect to Ory (enables auth & permission checks):",
|
|
374
|
-
` npx ${binName} configure --project-url <URL> --api-key <KEY
|
|
433
|
+
` npx ${binName} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]`,
|
|
375
434
|
"",
|
|
376
435
|
" Or, enable audit logging only (no auth or permission checks):",
|
|
377
436
|
` npx ${binName} configure --audit-only`,
|
package/dist/config.d.ts
CHANGED
|
@@ -75,6 +75,16 @@ export type PermissionMode = "observe" | "enforce";
|
|
|
75
75
|
export interface OryPluginConfig {
|
|
76
76
|
projectUrl?: string;
|
|
77
77
|
apiKey?: string;
|
|
78
|
+
/**
|
|
79
|
+
* Public OAuth2 client id used by the user PKCE browser flow when
|
|
80
|
+
* `ORY_USER_LOGIN` is on. The client must be registered ahead of time
|
|
81
|
+
* in the Ory project with all four loopback redirect URIs
|
|
82
|
+
* (`http://127.0.0.1:47823..47826/callback`) and
|
|
83
|
+
* `token_endpoint_auth_method=none`. The local stack provisions one
|
|
84
|
+
* automatically; against a hosted project the operator registers it
|
|
85
|
+
* once and supplies the id here (or via `ORY_OAUTH2_CLIENT_ID`).
|
|
86
|
+
*/
|
|
87
|
+
oauth2ClientId?: string;
|
|
78
88
|
/** When true, only audit logging is enabled — no auth or permission checks. */
|
|
79
89
|
auditOnly?: boolean;
|
|
80
90
|
/**
|
|
@@ -144,11 +154,13 @@ export declare function mutateConfig(mutator: (current: OryPluginConfig) => OryP
|
|
|
144
154
|
export declare function resolveConfig(): {
|
|
145
155
|
projectUrl?: string;
|
|
146
156
|
apiKey?: string;
|
|
157
|
+
oauth2ClientId?: string;
|
|
147
158
|
auditOnly: boolean;
|
|
148
159
|
permissionMode: PermissionMode;
|
|
149
160
|
permissionModeSource: "env" | "config" | "default";
|
|
150
161
|
projectUrlSource: "env" | "config" | "none";
|
|
151
162
|
apiKeySource: "env" | "config" | "none";
|
|
163
|
+
oauth2ClientIdSource: "env" | "config" | "none";
|
|
152
164
|
};
|
|
153
165
|
/**
|
|
154
166
|
* Build the "not configured" prompt message for a given harness CLI bin name.
|
package/dist/config.js
CHANGED
|
@@ -115,6 +115,7 @@ function loadConfig() {
|
|
|
115
115
|
return {
|
|
116
116
|
projectUrl: typeof parsed.projectUrl === "string" ? parsed.projectUrl : undefined,
|
|
117
117
|
apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : undefined,
|
|
118
|
+
oauth2ClientId: typeof parsed.oauth2ClientId === "string" ? parsed.oauth2ClientId : undefined,
|
|
118
119
|
auditOnly: parsed.auditOnly === true ? true : undefined,
|
|
119
120
|
permissionMode: parsePermissionMode(parsed.permissionMode),
|
|
120
121
|
user: parseUserCredentials(parsed),
|
|
@@ -328,6 +329,7 @@ function resolveConfig() {
|
|
|
328
329
|
// alias kept for back-compat (agent-auth emits a warning when only the
|
|
329
330
|
// legacy form is set).
|
|
330
331
|
const envApiKey = process.env.ORY_AGENT_API_KEY ?? process.env.ORY_API_KEY;
|
|
332
|
+
const envClientId = process.env.ORY_OAUTH2_CLIENT_ID;
|
|
331
333
|
const envMode = parsePermissionMode(process.env.ORY_PERMISSION_MODE);
|
|
332
334
|
const permissionMode = envMode ?? file.permissionMode ?? "observe";
|
|
333
335
|
const permissionModeSource = envMode
|
|
@@ -338,11 +340,13 @@ function resolveConfig() {
|
|
|
338
340
|
return {
|
|
339
341
|
projectUrl: envProjectUrl ?? file.projectUrl,
|
|
340
342
|
apiKey: envApiKey ?? file.apiKey,
|
|
343
|
+
oauth2ClientId: envClientId ?? file.oauth2ClientId,
|
|
341
344
|
auditOnly: file.auditOnly === true,
|
|
342
345
|
permissionMode,
|
|
343
346
|
permissionModeSource,
|
|
344
347
|
projectUrlSource: envProjectUrl ? "env" : file.projectUrl ? "config" : "none",
|
|
345
348
|
apiKeySource: envApiKey ? "env" : file.apiKey ? "config" : "none",
|
|
349
|
+
oauth2ClientIdSource: envClientId ? "env" : file.oauth2ClientId ? "config" : "none",
|
|
346
350
|
};
|
|
347
351
|
}
|
|
348
352
|
/**
|
|
@@ -352,7 +356,7 @@ function configPromptMessage(binName) {
|
|
|
352
356
|
return ("The Ory agent plugin is installed but not yet configured.\n" +
|
|
353
357
|
"\n" +
|
|
354
358
|
" Option 1 — Connect to Ory (enables authentication and permission checks):\n" +
|
|
355
|
-
` npx ${binName} configure --project-url <URL> --api-key <KEY
|
|
359
|
+
` npx ${binName} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]\n` +
|
|
356
360
|
"\n" +
|
|
357
361
|
" Option 2 — Continue without authentication (audit logging only):\n" +
|
|
358
362
|
` npx ${binName} configure --audit-only\n` +
|
package/dist/dev.js
CHANGED
|
@@ -396,7 +396,7 @@ function buildLocalOryEnv(gatewayUrl, seed) {
|
|
|
396
396
|
ORY_PERMISSION_NAMESPACE: seed.permissions.namespace,
|
|
397
397
|
// User login — always on in local dev so the launcher demonstrates
|
|
398
398
|
// the interactive PKCE login UX end-to-end every session.
|
|
399
|
-
ORY_USER_LOGIN: "
|
|
399
|
+
ORY_USER_LOGIN: "true",
|
|
400
400
|
// Agent identity — the harness self-registers via DCR on first run
|
|
401
401
|
// using the user's bearer as the initial access token. No static
|
|
402
402
|
// client_credentials are seeded; explicitly clear them so a leaking
|
package/dist/index.d.ts
CHANGED
|
@@ -15,8 +15,9 @@ export { runDevLauncher, type DevLauncherConfig, type InstallContext, } from "./
|
|
|
15
15
|
export { renderOrySkills, renderOryCommands, commandToSkill, commandToToml, commandToFrontmatterMarkdown, commandToPlainMarkdown, toSkillMarkdown, writeSkillTree, removeSkillDirs, ORY_SKILL_NAMES, ORY_COMMAND_SKILL_NAMES, ORY_COMMAND_SLUGS, type RenderedSkill, type RenderedCommand, type RenderProfileOptions, } from "./skills.js";
|
|
16
16
|
export { runLocalCommand, ensureDevJaeger, stopDevJaeger, DEV_JAEGER_CONTAINER, type EnsureDevJaegerResult, type StopDevJaegerResult, } from "./local/index.js";
|
|
17
17
|
export { runRegistryCommand } from "./registry/index.js";
|
|
18
|
-
export { checkAndDecide, applyPermissionMode, type PermissionDecision, type ModeDecision, type DecisionSpanAttributes, type CheckAndDecideOptions, type ApplyPermissionModeContext, } from "./permissions.js";
|
|
19
|
-
export { HARNESS_TOOL_CATALOG, KNOWN_HARNESSES, ALL_TOOLS, getToolCatalog, type KnownHarness, } from "./tool-catalog.js";
|
|
18
|
+
export { checkAndDecide, applyPermissionMode, gateToolCall, type PermissionDecision, type ModeDecision, type DecisionSpanAttributes, type CheckAndDecideOptions, type ApplyPermissionModeContext, type GateToolCallArgs, type ToolGateOutcome, } from "./permissions.js";
|
|
19
|
+
export { HARNESS_TOOL_CATALOG, KNOWN_HARNESSES, ALL_TOOLS, getToolCatalog, INTERACTIVE_TOOL_CATALOG, getInteractiveToolCatalog, isInteractiveTool, type KnownHarness, } from "./tool-catalog.js";
|
|
20
|
+
export { classifyLifecycle, isUserFacingPhase, isToolExecutionPhase, HARNESS_LIFECYCLE_MAP, USER_FACING_PHASES, TOOL_EXECUTION_PHASES, type LifecyclePhase, } from "./lifecycle.js";
|
|
20
21
|
export { parseClaudeCodeMcpTool, parseGeminiMcpTool, parseMcpToolGeneric, checkMcpPermission, type McpToolIdentifier, type McpPermissionCheckOptions, type McpPermissionResult, } from "./mcp.js";
|
|
21
22
|
export { resolveUserSubject, subjectLabel, type UserSubjectRef, } from "./subject.js";
|
|
22
23
|
export { formatDenialMessage, formatDenialSummary, formatAlertMessage, formatAlertSummary, alertAttributes, OryDenialError, type DenialContext, type AlertAttributes, } from "./denial.js";
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.printOryConfig = exports.runAgentCommand = exports.runConfigureCommand = exports.AGENT_TOKEN_EXPIRY_SKEW_SEC = exports.clearSubAgentDynamicCredentials = exports.saveSubAgentDynamicCredentials = exports.loadSubAgentDynamicCredentials = exports.clearAgentDynamicCredentials = exports.saveAgentDynamicCredentials = exports.loadAgentDynamicCredentials = exports.registerAgentClient = exports.fetchClientCredentialsToken = exports.ensureSubAgentIdentity = exports.ensureAgentIdentity = exports.resolveAgentCredentials = exports.ensureAuthenticated = exports.ensureUserAuthenticated = exports.TOKEN_EXPIRY_SKEW_SEC = exports.waitForPeerTokensSync = exports.waitForPeerTokens = exports.clearPkceFlightLock = exports.tryAcquirePkceFlightLock = exports.refreshAndSave = exports.isExpired = exports.clearTokens = exports.saveTokens = exports.loadTokens = exports.DEFAULT_LOGIN_TIMEOUT_MS = exports.LOOPBACK_PORTS = exports.buildAuthorizeUrl = exports.sha256Base64Url = exports.generateCodeVerifier = exports.detectHeadless = exports.refreshAccessToken = exports.pkceLogin = exports.getHarnessDataDir = exports.getDataDir = exports.getConfigPath = exports.mutateConfig = exports.resolveConfig = exports.saveConfig = exports.loadConfig = exports.watchTraceFile = exports.formatSpan = exports.deriveTraceId = exports.ActiveSpan = exports.Tracer = exports.redactLogData = exports.DebugLogger = exports.OryAgentClient = void 0;
|
|
4
4
|
exports.runLocalCommand = exports.ORY_COMMAND_SLUGS = exports.ORY_COMMAND_SKILL_NAMES = exports.ORY_SKILL_NAMES = exports.removeSkillDirs = exports.writeSkillTree = exports.toSkillMarkdown = exports.commandToPlainMarkdown = exports.commandToFrontmatterMarkdown = exports.commandToToml = exports.commandToSkill = exports.renderOryCommands = exports.renderOrySkills = exports.runDevLauncher = exports.unregisterPlugin = exports.registerPlugin = exports.removeMcpServer = exports.mergeMcpServer = exports.mcpServerEntry = exports.resolveMcpServerCommand = exports.printNextSteps = exports.printSetupHelp = exports.removeFlatHooks = exports.mergeFlatHooks = exports.flatHookEntry = exports.removeMatcherHooks = exports.mergeMatcherHooks = exports.matcherHookEntry = exports.resolveHookCommand = exports.isOryHookCommand = exports.writeJsonFile = exports.readJsonFile = exports.parseSetupArgs = exports.printPermissionsSection = exports.printAgentIdentitySection = exports.printUserIdentitySection = exports.runStatusCommand = exports.printPermissionsOnboardingHelp = exports.maybeAutoBootstrap = exports.isUserIdentityCached = exports.runPermissionsCommand = exports.interactiveConfigPrompt = exports.promptForProjectUrl = exports.promptOnTty = exports.isTtyAvailable = exports.runWatchCommand = exports.printTraceTail = exports.printEnvHelp = exports.printLogTail = exports.printEnvironment = void 0;
|
|
5
|
-
exports.parseKeyValueList = exports.otlpExporterFromEnv = exports.OtlpExporter = exports.summarizeToolOutput = exports.summarizeToolInput = exports.OryDenialError = exports.alertAttributes = exports.formatAlertSummary = exports.formatAlertMessage = exports.formatDenialSummary = exports.formatDenialMessage = exports.subjectLabel = exports.resolveUserSubject = exports.checkMcpPermission = exports.parseMcpToolGeneric = exports.parseGeminiMcpTool = exports.parseClaudeCodeMcpTool = exports.getToolCatalog = exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = exports.applyPermissionMode = exports.checkAndDecide = exports.runRegistryCommand = exports.DEV_JAEGER_CONTAINER = exports.stopDevJaeger = exports.ensureDevJaeger = void 0;
|
|
5
|
+
exports.parseKeyValueList = exports.otlpExporterFromEnv = exports.OtlpExporter = exports.summarizeToolOutput = exports.summarizeToolInput = exports.OryDenialError = exports.alertAttributes = exports.formatAlertSummary = exports.formatAlertMessage = exports.formatDenialSummary = exports.formatDenialMessage = exports.subjectLabel = exports.resolveUserSubject = exports.checkMcpPermission = exports.parseMcpToolGeneric = exports.parseGeminiMcpTool = exports.parseClaudeCodeMcpTool = exports.TOOL_EXECUTION_PHASES = exports.USER_FACING_PHASES = exports.HARNESS_LIFECYCLE_MAP = exports.isToolExecutionPhase = exports.isUserFacingPhase = exports.classifyLifecycle = exports.isInteractiveTool = exports.getInteractiveToolCatalog = exports.INTERACTIVE_TOOL_CATALOG = exports.getToolCatalog = exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = exports.gateToolCall = exports.applyPermissionMode = exports.checkAndDecide = exports.runRegistryCommand = exports.DEV_JAEGER_CONTAINER = exports.stopDevJaeger = exports.ensureDevJaeger = void 0;
|
|
6
6
|
var client_js_1 = require("./client.js");
|
|
7
7
|
Object.defineProperty(exports, "OryAgentClient", { enumerable: true, get: function () { return client_js_1.OryAgentClient; } });
|
|
8
8
|
var logger_js_1 = require("./logger.js");
|
|
@@ -126,11 +126,22 @@ Object.defineProperty(exports, "runRegistryCommand", { enumerable: true, get: fu
|
|
|
126
126
|
var permissions_js_1 = require("./permissions.js");
|
|
127
127
|
Object.defineProperty(exports, "checkAndDecide", { enumerable: true, get: function () { return permissions_js_1.checkAndDecide; } });
|
|
128
128
|
Object.defineProperty(exports, "applyPermissionMode", { enumerable: true, get: function () { return permissions_js_1.applyPermissionMode; } });
|
|
129
|
+
Object.defineProperty(exports, "gateToolCall", { enumerable: true, get: function () { return permissions_js_1.gateToolCall; } });
|
|
129
130
|
var tool_catalog_js_1 = require("./tool-catalog.js");
|
|
130
131
|
Object.defineProperty(exports, "HARNESS_TOOL_CATALOG", { enumerable: true, get: function () { return tool_catalog_js_1.HARNESS_TOOL_CATALOG; } });
|
|
131
132
|
Object.defineProperty(exports, "KNOWN_HARNESSES", { enumerable: true, get: function () { return tool_catalog_js_1.KNOWN_HARNESSES; } });
|
|
132
133
|
Object.defineProperty(exports, "ALL_TOOLS", { enumerable: true, get: function () { return tool_catalog_js_1.ALL_TOOLS; } });
|
|
133
134
|
Object.defineProperty(exports, "getToolCatalog", { enumerable: true, get: function () { return tool_catalog_js_1.getToolCatalog; } });
|
|
135
|
+
Object.defineProperty(exports, "INTERACTIVE_TOOL_CATALOG", { enumerable: true, get: function () { return tool_catalog_js_1.INTERACTIVE_TOOL_CATALOG; } });
|
|
136
|
+
Object.defineProperty(exports, "getInteractiveToolCatalog", { enumerable: true, get: function () { return tool_catalog_js_1.getInteractiveToolCatalog; } });
|
|
137
|
+
Object.defineProperty(exports, "isInteractiveTool", { enumerable: true, get: function () { return tool_catalog_js_1.isInteractiveTool; } });
|
|
138
|
+
var lifecycle_js_1 = require("./lifecycle.js");
|
|
139
|
+
Object.defineProperty(exports, "classifyLifecycle", { enumerable: true, get: function () { return lifecycle_js_1.classifyLifecycle; } });
|
|
140
|
+
Object.defineProperty(exports, "isUserFacingPhase", { enumerable: true, get: function () { return lifecycle_js_1.isUserFacingPhase; } });
|
|
141
|
+
Object.defineProperty(exports, "isToolExecutionPhase", { enumerable: true, get: function () { return lifecycle_js_1.isToolExecutionPhase; } });
|
|
142
|
+
Object.defineProperty(exports, "HARNESS_LIFECYCLE_MAP", { enumerable: true, get: function () { return lifecycle_js_1.HARNESS_LIFECYCLE_MAP; } });
|
|
143
|
+
Object.defineProperty(exports, "USER_FACING_PHASES", { enumerable: true, get: function () { return lifecycle_js_1.USER_FACING_PHASES; } });
|
|
144
|
+
Object.defineProperty(exports, "TOOL_EXECUTION_PHASES", { enumerable: true, get: function () { return lifecycle_js_1.TOOL_EXECUTION_PHASES; } });
|
|
134
145
|
var mcp_js_1 = require("./mcp.js");
|
|
135
146
|
Object.defineProperty(exports, "parseClaudeCodeMcpTool", { enumerable: true, get: function () { return mcp_js_1.parseClaudeCodeMcpTool; } });
|
|
136
147
|
Object.defineProperty(exports, "parseGeminiMcpTool", { enumerable: true, get: function () { return mcp_js_1.parseGeminiMcpTool; } });
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical lifecycle vocabulary for harness plugins.
|
|
3
|
+
*
|
|
4
|
+
* Each harness exposes a different set of hook event names (Claude Code's
|
|
5
|
+
* `PreToolUse`, Gemini's `BeforeTool`, OpenCode's `tool.execute.before`,
|
|
6
|
+
* …). Mapping them onto a shared phase set lets the plugin code reason
|
|
7
|
+
* about *what is happening* without first remembering *which harness
|
|
8
|
+
* we're in*. Spans, logs, docs, and tests all reference the same set of
|
|
9
|
+
* phases.
|
|
10
|
+
*
|
|
11
|
+
* Phases split into three buckets:
|
|
12
|
+
*
|
|
13
|
+
* - **Session / agent lifecycle.** `session.start`, `session.stop`,
|
|
14
|
+
* `compaction`. The session begins or ends, or the harness compacts
|
|
15
|
+
* context. Plugins authenticate here (start) and may flush state
|
|
16
|
+
* (stop).
|
|
17
|
+
*
|
|
18
|
+
* - **User-facing interactions.** `user.prompt`, `user.interaction`,
|
|
19
|
+
* `permission.ask`. The harness is communicating with the human:
|
|
20
|
+
* they typed a prompt, the harness is surfacing a confirmation,
|
|
21
|
+
* the harness is asking whether to allow a tool. These are *traced*
|
|
22
|
+
* but never gated by a tool permission check — the user is the
|
|
23
|
+
* final decision-maker. (`permission.ask` does consult Ory to
|
|
24
|
+
* inform the harness's UI, but the call is advisory.)
|
|
25
|
+
*
|
|
26
|
+
* - **Tool execution.** `tool.before`, `tool.after`, `tool.failure`,
|
|
27
|
+
* `subagent.start`, `subagent.stop`. The agent is reaching into an
|
|
28
|
+
* external system (or spawning a child agent that will). These are
|
|
29
|
+
* the only phases that run a true `use` permission gate.
|
|
30
|
+
*
|
|
31
|
+
* `passthrough` is the explicit "nothing to do here" bucket — the
|
|
32
|
+
* harness fired an event we don't model, and we record an audit span
|
|
33
|
+
* and return.
|
|
34
|
+
*/
|
|
35
|
+
export type LifecyclePhase = "session.start" | "session.stop" | "user.prompt" | "user.interaction" | "permission.ask" | "tool.before" | "tool.after" | "tool.failure" | "subagent.start" | "subagent.stop" | "compaction" | "passthrough";
|
|
36
|
+
/**
|
|
37
|
+
* Per-harness map from the harness's native event name to a canonical
|
|
38
|
+
* {@link LifecyclePhase}. Event names that don't appear here resolve to
|
|
39
|
+
* `passthrough` via {@link classifyLifecycle}.
|
|
40
|
+
*/
|
|
41
|
+
export declare const HARNESS_LIFECYCLE_MAP: Record<string, Record<string, LifecyclePhase>>;
|
|
42
|
+
/**
|
|
43
|
+
* Resolve a harness event name to its canonical {@link LifecyclePhase}.
|
|
44
|
+
* Unknown harnesses or unknown event names both yield `passthrough`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function classifyLifecycle(harness: string, event: string): LifecyclePhase;
|
|
47
|
+
/**
|
|
48
|
+
* Phases that surface to the human (prompts, confirmations, notifications,
|
|
49
|
+
* advisory permission asks). Tool-call permission gates must never fire
|
|
50
|
+
* on these — the user is the decision-maker.
|
|
51
|
+
*/
|
|
52
|
+
export declare const USER_FACING_PHASES: ReadonlySet<LifecyclePhase>;
|
|
53
|
+
/**
|
|
54
|
+
* Phases that represent actual tool execution against external systems.
|
|
55
|
+
* Only these run a real `use` permission check.
|
|
56
|
+
*/
|
|
57
|
+
export declare const TOOL_EXECUTION_PHASES: ReadonlySet<LifecyclePhase>;
|
|
58
|
+
export declare function isUserFacingPhase(phase: LifecyclePhase): boolean;
|
|
59
|
+
export declare function isToolExecutionPhase(phase: LifecyclePhase): boolean;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Canonical lifecycle vocabulary for harness plugins.
|
|
4
|
+
*
|
|
5
|
+
* Each harness exposes a different set of hook event names (Claude Code's
|
|
6
|
+
* `PreToolUse`, Gemini's `BeforeTool`, OpenCode's `tool.execute.before`,
|
|
7
|
+
* …). Mapping them onto a shared phase set lets the plugin code reason
|
|
8
|
+
* about *what is happening* without first remembering *which harness
|
|
9
|
+
* we're in*. Spans, logs, docs, and tests all reference the same set of
|
|
10
|
+
* phases.
|
|
11
|
+
*
|
|
12
|
+
* Phases split into three buckets:
|
|
13
|
+
*
|
|
14
|
+
* - **Session / agent lifecycle.** `session.start`, `session.stop`,
|
|
15
|
+
* `compaction`. The session begins or ends, or the harness compacts
|
|
16
|
+
* context. Plugins authenticate here (start) and may flush state
|
|
17
|
+
* (stop).
|
|
18
|
+
*
|
|
19
|
+
* - **User-facing interactions.** `user.prompt`, `user.interaction`,
|
|
20
|
+
* `permission.ask`. The harness is communicating with the human:
|
|
21
|
+
* they typed a prompt, the harness is surfacing a confirmation,
|
|
22
|
+
* the harness is asking whether to allow a tool. These are *traced*
|
|
23
|
+
* but never gated by a tool permission check — the user is the
|
|
24
|
+
* final decision-maker. (`permission.ask` does consult Ory to
|
|
25
|
+
* inform the harness's UI, but the call is advisory.)
|
|
26
|
+
*
|
|
27
|
+
* - **Tool execution.** `tool.before`, `tool.after`, `tool.failure`,
|
|
28
|
+
* `subagent.start`, `subagent.stop`. The agent is reaching into an
|
|
29
|
+
* external system (or spawning a child agent that will). These are
|
|
30
|
+
* the only phases that run a true `use` permission gate.
|
|
31
|
+
*
|
|
32
|
+
* `passthrough` is the explicit "nothing to do here" bucket — the
|
|
33
|
+
* harness fired an event we don't model, and we record an audit span
|
|
34
|
+
* and return.
|
|
35
|
+
*/
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
exports.TOOL_EXECUTION_PHASES = exports.USER_FACING_PHASES = exports.HARNESS_LIFECYCLE_MAP = void 0;
|
|
38
|
+
exports.classifyLifecycle = classifyLifecycle;
|
|
39
|
+
exports.isUserFacingPhase = isUserFacingPhase;
|
|
40
|
+
exports.isToolExecutionPhase = isToolExecutionPhase;
|
|
41
|
+
/**
|
|
42
|
+
* Per-harness map from the harness's native event name to a canonical
|
|
43
|
+
* {@link LifecyclePhase}. Event names that don't appear here resolve to
|
|
44
|
+
* `passthrough` via {@link classifyLifecycle}.
|
|
45
|
+
*/
|
|
46
|
+
exports.HARNESS_LIFECYCLE_MAP = {
|
|
47
|
+
"claude-code": {
|
|
48
|
+
SessionStart: "session.start",
|
|
49
|
+
SessionEnd: "session.stop",
|
|
50
|
+
PreToolUse: "tool.before",
|
|
51
|
+
PostToolUse: "tool.after",
|
|
52
|
+
PostToolUseFailure: "tool.failure",
|
|
53
|
+
PermissionRequest: "permission.ask",
|
|
54
|
+
UserPromptSubmit: "user.prompt",
|
|
55
|
+
SubagentStart: "subagent.start",
|
|
56
|
+
SubagentStop: "subagent.stop",
|
|
57
|
+
},
|
|
58
|
+
codex: {
|
|
59
|
+
SessionStart: "session.start",
|
|
60
|
+
Stop: "session.stop",
|
|
61
|
+
PreToolUse: "tool.before",
|
|
62
|
+
PostToolUse: "tool.after",
|
|
63
|
+
PermissionRequest: "permission.ask",
|
|
64
|
+
UserPromptSubmit: "user.prompt",
|
|
65
|
+
},
|
|
66
|
+
"gemini-cli": {
|
|
67
|
+
SessionStart: "session.start",
|
|
68
|
+
SessionEnd: "session.stop",
|
|
69
|
+
BeforeTool: "tool.before",
|
|
70
|
+
AfterTool: "tool.after",
|
|
71
|
+
BeforeToolSelection: "tool.before",
|
|
72
|
+
Notification: "user.interaction",
|
|
73
|
+
PreCompress: "compaction",
|
|
74
|
+
},
|
|
75
|
+
openclaw: {
|
|
76
|
+
session_start: "session.start",
|
|
77
|
+
before_agent_run: "user.prompt",
|
|
78
|
+
before_tool_call: "tool.before",
|
|
79
|
+
after_tool_call: "tool.after",
|
|
80
|
+
subagent_spawning: "subagent.start",
|
|
81
|
+
subagent_spawned: "subagent.start",
|
|
82
|
+
subagent_ended: "subagent.stop",
|
|
83
|
+
},
|
|
84
|
+
opencode: {
|
|
85
|
+
config: "session.start",
|
|
86
|
+
"chat.message": "user.prompt",
|
|
87
|
+
"permission.ask": "permission.ask",
|
|
88
|
+
"tool.execute.before": "tool.before",
|
|
89
|
+
"tool.execute.after": "tool.after",
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
/**
|
|
93
|
+
* Resolve a harness event name to its canonical {@link LifecyclePhase}.
|
|
94
|
+
* Unknown harnesses or unknown event names both yield `passthrough`.
|
|
95
|
+
*/
|
|
96
|
+
function classifyLifecycle(harness, event) {
|
|
97
|
+
return exports.HARNESS_LIFECYCLE_MAP[harness]?.[event] ?? "passthrough";
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Phases that surface to the human (prompts, confirmations, notifications,
|
|
101
|
+
* advisory permission asks). Tool-call permission gates must never fire
|
|
102
|
+
* on these — the user is the decision-maker.
|
|
103
|
+
*/
|
|
104
|
+
exports.USER_FACING_PHASES = new Set([
|
|
105
|
+
"user.prompt",
|
|
106
|
+
"user.interaction",
|
|
107
|
+
"permission.ask",
|
|
108
|
+
]);
|
|
109
|
+
/**
|
|
110
|
+
* Phases that represent actual tool execution against external systems.
|
|
111
|
+
* Only these run a real `use` permission check.
|
|
112
|
+
*/
|
|
113
|
+
exports.TOOL_EXECUTION_PHASES = new Set([
|
|
114
|
+
"tool.before",
|
|
115
|
+
"tool.after",
|
|
116
|
+
"tool.failure",
|
|
117
|
+
]);
|
|
118
|
+
function isUserFacingPhase(phase) {
|
|
119
|
+
return exports.USER_FACING_PHASES.has(phase);
|
|
120
|
+
}
|
|
121
|
+
function isToolExecutionPhase(phase) {
|
|
122
|
+
return exports.TOOL_EXECUTION_PHASES.has(phase);
|
|
123
|
+
}
|
package/dist/local/manager.js
CHANGED
|
@@ -622,8 +622,17 @@ function localEnv() {
|
|
|
622
622
|
}
|
|
623
623
|
function localConfigure() {
|
|
624
624
|
console.log(`Saving local Ory gateway URL to shared config...`);
|
|
625
|
-
|
|
626
|
-
|
|
625
|
+
// Persist the seeded user PKCE client id alongside the project URL so
|
|
626
|
+
// a subsequent `configure --project-url <hosted>` doesn't trip the
|
|
627
|
+
// require-OAuth2-client check, and the user gate resolves the right
|
|
628
|
+
// client out of the box.
|
|
629
|
+
(0, config_js_1.saveConfig)({
|
|
630
|
+
projectUrl: configs_js_1.GATEWAY_URL,
|
|
631
|
+
oauth2ClientId: seed_js_1.USER_CLIENT_ID,
|
|
632
|
+
auditOnly: false,
|
|
633
|
+
});
|
|
634
|
+
console.log(` Project URL: ${configs_js_1.GATEWAY_URL}`);
|
|
635
|
+
console.log(` OAuth2 Client ID: ${seed_js_1.USER_CLIENT_ID}`);
|
|
627
636
|
console.log("");
|
|
628
637
|
console.log("All Ory agent plugins will now connect to the local environment.");
|
|
629
638
|
console.log("This persists across sessions. Run 'configure --project-url <URL>'");
|
|
@@ -647,7 +656,7 @@ function printSeedResult(result) {
|
|
|
647
656
|
console.log("To use with any Ory agent plugin, set these environment variables:");
|
|
648
657
|
console.log("");
|
|
649
658
|
console.log(` export ORY_PROJECT_URL=${configs_js_1.GATEWAY_URL}`);
|
|
650
|
-
console.log(` export ORY_USER_LOGIN=
|
|
659
|
+
console.log(` export ORY_USER_LOGIN=true`);
|
|
651
660
|
console.log(` export ORY_OAUTH2_CLIENT_ID=${result.user.client.clientId}`);
|
|
652
661
|
console.log(` export ORY_USER_SUBJECT_NAMESPACE=User`);
|
|
653
662
|
console.log(` export ORY_USER_SUBJECT_ID=${result.user.identity.id}`);
|
package/dist/local/seed.d.ts
CHANGED
|
@@ -23,6 +23,13 @@
|
|
|
23
23
|
* Idempotent — re-running the seed reuses existing identities and
|
|
24
24
|
* recreates OAuth2 clients (so the secret is always known).
|
|
25
25
|
*/
|
|
26
|
+
/**
|
|
27
|
+
* Stable client id Hydra issues to the seeded user PKCE client. Stable
|
|
28
|
+
* so `local configure` can persist it to the shared config and downstream
|
|
29
|
+
* runs can rely on `ORY_OAUTH2_CLIENT_ID` resolution without first
|
|
30
|
+
* dragging the value out of the `local up` banner.
|
|
31
|
+
*/
|
|
32
|
+
export declare const USER_CLIENT_ID = "ory-user-local";
|
|
26
33
|
export interface SeededIdentity {
|
|
27
34
|
id: string;
|
|
28
35
|
email: string;
|
package/dist/local/seed.js
CHANGED
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
* recreates OAuth2 clients (so the secret is always known).
|
|
26
26
|
*/
|
|
27
27
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
28
|
-
exports.USER_SUBJECT_NAMESPACE = void 0;
|
|
28
|
+
exports.USER_SUBJECT_NAMESPACE = exports.USER_CLIENT_ID = void 0;
|
|
29
29
|
exports.seedLocalEnvironment = seedLocalEnvironment;
|
|
30
30
|
const auth_js_1 = require("../auth.js");
|
|
31
31
|
const tool_catalog_js_1 = require("../tool-catalog.js");
|
|
@@ -38,7 +38,13 @@ const AGENT_PASSWORD = "ory-agent-local-dev-password!";
|
|
|
38
38
|
const USER_EMAIL = "user@ory-local.dev";
|
|
39
39
|
const USER_PASSWORD = "ory-user-local-dev-password!";
|
|
40
40
|
const USER_CLIENT_NAME = "ory-agent-plugins-local-user";
|
|
41
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Stable client id Hydra issues to the seeded user PKCE client. Stable
|
|
43
|
+
* so `local configure` can persist it to the shared config and downstream
|
|
44
|
+
* runs can rely on `ORY_OAUTH2_CLIENT_ID` resolution without first
|
|
45
|
+
* dragging the value out of the `local up` banner.
|
|
46
|
+
*/
|
|
47
|
+
exports.USER_CLIENT_ID = "ory-user-local";
|
|
42
48
|
/**
|
|
43
49
|
* Tools the dev launcher seeds tuples for. Sourced from the shared
|
|
44
50
|
* per-harness catalog so a single seed grants the local user identity
|
|
@@ -193,11 +199,11 @@ async function seedLocalEnvironment(namespace = "AgentTools") {
|
|
|
193
199
|
// OAuth2 client is intentionally NOT pre-registered; the harness
|
|
194
200
|
// will self-register via DCR on first session start.
|
|
195
201
|
process.stderr.write(" Registering user OAuth2 client (PKCE)...\n");
|
|
196
|
-
let userClient = { clientId: USER_CLIENT_ID };
|
|
202
|
+
let userClient = { clientId: exports.USER_CLIENT_ID };
|
|
197
203
|
try {
|
|
198
204
|
userClient = await ensureOAuth2Client({
|
|
199
205
|
clientName: USER_CLIENT_NAME,
|
|
200
|
-
clientId: USER_CLIENT_ID,
|
|
206
|
+
clientId: exports.USER_CLIENT_ID,
|
|
201
207
|
// Public PKCE client — no secret. Hydra accepts an empty string
|
|
202
208
|
// for `token_endpoint_auth_method=none`.
|
|
203
209
|
grantTypes: ["authorization_code", "refresh_token"],
|
package/dist/permissions-cli.js
CHANGED
|
@@ -129,12 +129,12 @@ async function runPermissionsStatus(binName, harness) {
|
|
|
129
129
|
console.log("");
|
|
130
130
|
if (resolved.auditOnly) {
|
|
131
131
|
console.log("Ory is disabled (audit-only). No permission checks run.");
|
|
132
|
-
console.log(`Re-enable with: ${binName} configure --project-url <URL> --api-key <KEY
|
|
132
|
+
console.log(`Re-enable with: ${binName} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]`);
|
|
133
133
|
return 0;
|
|
134
134
|
}
|
|
135
135
|
if (!resolved.projectUrl) {
|
|
136
136
|
console.log("No project URL configured — cannot probe permission coverage.");
|
|
137
|
-
console.log(`Configure first: ${binName} configure --project-url <URL> --api-key <KEY
|
|
137
|
+
console.log(`Configure first: ${binName} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]`);
|
|
138
138
|
return 0;
|
|
139
139
|
}
|
|
140
140
|
if (catalog.length === 0) {
|
|
@@ -211,12 +211,12 @@ async function runPermissionsBootstrap(binName, harness, args) {
|
|
|
211
211
|
const catalog = (0, tool_catalog_js_1.getToolCatalog)(harness);
|
|
212
212
|
if (resolved.auditOnly) {
|
|
213
213
|
console.error("Ory is in audit-only mode (kill-switch). Nothing to bootstrap.");
|
|
214
|
-
console.error(`Re-enable Ory: ${binName} configure --project-url <URL> --api-key <KEY
|
|
214
|
+
console.error(`Re-enable Ory: ${binName} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]`);
|
|
215
215
|
return 1;
|
|
216
216
|
}
|
|
217
217
|
if (!resolved.projectUrl) {
|
|
218
218
|
console.error("No ORY_PROJECT_URL configured — cannot write permissions.");
|
|
219
|
-
console.error(`Configure first: ${binName} configure --project-url <URL> --api-key <KEY
|
|
219
|
+
console.error(`Configure first: ${binName} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]`);
|
|
220
220
|
return 1;
|
|
221
221
|
}
|
|
222
222
|
if (catalog.length === 0) {
|
|
@@ -235,7 +235,7 @@ async function runPermissionsBootstrap(binName, harness, args) {
|
|
|
235
235
|
console.error("No user identity resolved — refusing to write permissions for an unknown subject.");
|
|
236
236
|
console.error("");
|
|
237
237
|
console.error("Either:");
|
|
238
|
-
console.error(" - run the harness once with ORY_USER_LOGIN=
|
|
238
|
+
console.error(" - run the harness once with ORY_USER_LOGIN=true so a user token is cached, or");
|
|
239
239
|
console.error(" - set ORY_USER_SUBJECT_ID=<id> to target a known subject.");
|
|
240
240
|
return 1;
|
|
241
241
|
}
|
package/dist/permissions.d.ts
CHANGED
|
@@ -117,6 +117,57 @@ export interface ApplyPermissionModeContext {
|
|
|
117
117
|
* proceed despite the deny.
|
|
118
118
|
*/
|
|
119
119
|
export declare function applyPermissionMode(client: OryAgentClient, allowed: boolean, context?: ApplyPermissionModeContext): ModeDecision;
|
|
120
|
+
/**
|
|
121
|
+
* Outcome of {@link gateToolCall}. Either the tool is a user-interaction
|
|
122
|
+
* primitive (`AskUserQuestion`, `ExitPlanMode`, `TodoWrite`, …) and we
|
|
123
|
+
* pass through with a single audit span, or it's a real tool execution
|
|
124
|
+
* and the caller gets the standard {@link PermissionDecision}.
|
|
125
|
+
*/
|
|
126
|
+
export type ToolGateOutcome = {
|
|
127
|
+
kind: "interactive";
|
|
128
|
+
/** Attributes to attach to the caller's pass-through trace, if any. */
|
|
129
|
+
spanAttributes: {
|
|
130
|
+
interactive: true;
|
|
131
|
+
toolName: string;
|
|
132
|
+
};
|
|
133
|
+
} | PermissionDecision;
|
|
134
|
+
export interface GateToolCallArgs {
|
|
135
|
+
/** Harness name (`claude-code`, `codex`, …). Used to look up the interactive-tool catalog. */
|
|
136
|
+
harness: string;
|
|
137
|
+
/** The tool the agent is invoking. */
|
|
138
|
+
toolName: string;
|
|
139
|
+
/** Permission check to run when the tool is a real execution. */
|
|
140
|
+
check: PermissionCheck;
|
|
141
|
+
/**
|
|
142
|
+
* Attributes merged into the permission span (real path) and the
|
|
143
|
+
* `user.interaction` span (interactive path).
|
|
144
|
+
*/
|
|
145
|
+
spanAttributes?: Record<string, unknown>;
|
|
146
|
+
/** Override the resolved {@link PermissionMode}. Tests use this. */
|
|
147
|
+
modeOverride?: PermissionMode;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Single entry point for the pre-tool-use gate. Splits the harness's
|
|
151
|
+
* incoming "tool" into two semantic categories:
|
|
152
|
+
*
|
|
153
|
+
* - **Interactive** — the tool surfaces UI to the user
|
|
154
|
+
* (`AskUserQuestion`, `ExitPlanMode`, `TodoWrite`, plus anything
|
|
155
|
+
* listed in `ORY_INTERACTIVE_TOOLS`). The plugin must not gate these
|
|
156
|
+
* through Ory: the user is the decision-maker, and blocking them in
|
|
157
|
+
* enforce mode (or logging a misleading observe-deny) hides the very
|
|
158
|
+
* prompt the user needs to see. We record one `user.interaction`
|
|
159
|
+
* audit span and return — no permission check, no `tool.invoke`,
|
|
160
|
+
* no `tool.block`.
|
|
161
|
+
*
|
|
162
|
+
* - **Execution** — every other tool. Delegates to
|
|
163
|
+
* {@link checkAndDecide} so observe/enforce/fail-open behavior is
|
|
164
|
+
* identical to the legacy code path.
|
|
165
|
+
*
|
|
166
|
+
* The caller branches on `outcome.kind`. The four execution kinds
|
|
167
|
+
* (`allow`, `deny`, `observe`, `fail_open`) keep their existing
|
|
168
|
+
* semantics; the new `interactive` kind means "do nothing else."
|
|
169
|
+
*/
|
|
170
|
+
export declare function gateToolCall(client: OryAgentClient, args: GateToolCallArgs): Promise<ToolGateOutcome>;
|
|
120
171
|
/**
|
|
121
172
|
* Run a permission check and resolve the configured mode against the
|
|
122
173
|
* result. Never throws — fail-open scenarios are surfaced as a typed
|
package/dist/permissions.js
CHANGED
|
@@ -23,8 +23,10 @@
|
|
|
23
23
|
*/
|
|
24
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
25
25
|
exports.applyPermissionMode = applyPermissionMode;
|
|
26
|
+
exports.gateToolCall = gateToolCall;
|
|
26
27
|
exports.checkAndDecide = checkAndDecide;
|
|
27
28
|
const config_js_1 = require("./config.js");
|
|
29
|
+
const tool_catalog_js_1 = require("./tool-catalog.js");
|
|
28
30
|
function formatSubjectSet(set) {
|
|
29
31
|
if (!set)
|
|
30
32
|
return undefined;
|
|
@@ -75,6 +77,52 @@ function applyPermissionMode(client, allowed, context = {}) {
|
|
|
75
77
|
}
|
|
76
78
|
return { kind: "deny", mode: "enforce", spanAttributes };
|
|
77
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Single entry point for the pre-tool-use gate. Splits the harness's
|
|
82
|
+
* incoming "tool" into two semantic categories:
|
|
83
|
+
*
|
|
84
|
+
* - **Interactive** — the tool surfaces UI to the user
|
|
85
|
+
* (`AskUserQuestion`, `ExitPlanMode`, `TodoWrite`, plus anything
|
|
86
|
+
* listed in `ORY_INTERACTIVE_TOOLS`). The plugin must not gate these
|
|
87
|
+
* through Ory: the user is the decision-maker, and blocking them in
|
|
88
|
+
* enforce mode (or logging a misleading observe-deny) hides the very
|
|
89
|
+
* prompt the user needs to see. We record one `user.interaction`
|
|
90
|
+
* audit span and return — no permission check, no `tool.invoke`,
|
|
91
|
+
* no `tool.block`.
|
|
92
|
+
*
|
|
93
|
+
* - **Execution** — every other tool. Delegates to
|
|
94
|
+
* {@link checkAndDecide} so observe/enforce/fail-open behavior is
|
|
95
|
+
* identical to the legacy code path.
|
|
96
|
+
*
|
|
97
|
+
* The caller branches on `outcome.kind`. The four execution kinds
|
|
98
|
+
* (`allow`, `deny`, `observe`, `fail_open`) keep their existing
|
|
99
|
+
* semantics; the new `interactive` kind means "do nothing else."
|
|
100
|
+
*/
|
|
101
|
+
async function gateToolCall(client, args) {
|
|
102
|
+
if ((0, tool_catalog_js_1.isInteractiveTool)(args.harness, args.toolName)) {
|
|
103
|
+
client.logger.debug("tool.interactive", {
|
|
104
|
+
harness: args.harness,
|
|
105
|
+
toolName: args.toolName,
|
|
106
|
+
note: "user-interaction primitive — skipping permission check",
|
|
107
|
+
});
|
|
108
|
+
client.tracer.record("user.interaction", "ok", {
|
|
109
|
+
attributes: {
|
|
110
|
+
harness: args.harness,
|
|
111
|
+
toolName: args.toolName,
|
|
112
|
+
kind: "tool",
|
|
113
|
+
...args.spanAttributes,
|
|
114
|
+
},
|
|
115
|
+
});
|
|
116
|
+
return {
|
|
117
|
+
kind: "interactive",
|
|
118
|
+
spanAttributes: { interactive: true, toolName: args.toolName },
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return checkAndDecide(client, args.check, {
|
|
122
|
+
spanAttributes: args.spanAttributes,
|
|
123
|
+
modeOverride: args.modeOverride,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
78
126
|
/**
|
|
79
127
|
* Run a permission check and resolve the configured mode against the
|
|
80
128
|
* result. Never throws — fail-open scenarios are surfaced as a typed
|
package/dist/setup.js
CHANGED
|
@@ -374,7 +374,9 @@ Environment variables:
|
|
|
374
374
|
ORY_PROJECT_URL Your Ory project URL (required at runtime)
|
|
375
375
|
ORY_AGENT_API_KEY Agent API key / OAuth2 bearer (preferred name;
|
|
376
376
|
ORY_API_KEY is honored as a deprecated alias)
|
|
377
|
-
|
|
377
|
+
ORY_OAUTH2_CLIENT_ID Public OAuth2 client id for the user PKCE flow
|
|
378
|
+
(required when ORY_USER_LOGIN=true)
|
|
379
|
+
ORY_USER_LOGIN Set to "true" to enable the interactive user login
|
|
378
380
|
ORY_PERMISSION_MODE "observe" (default) or "enforce" — what to do on deny
|
|
379
381
|
ORY_AGENT_DEBUG Set to "true" for debug logging
|
|
380
382
|
ORY_AGENT_LOG_FILE Path to write debug logs
|
|
@@ -392,9 +394,12 @@ function printNextSteps(harnessName, uninstallCmd) {
|
|
|
392
394
|
console.log(" export ORY_AGENT_API_KEY=ory_pat_...");
|
|
393
395
|
console.log("");
|
|
394
396
|
console.log(" 2. Turn on the interactive user login (opt-in):");
|
|
395
|
-
console.log(" export ORY_USER_LOGIN=
|
|
396
|
-
console.log(
|
|
397
|
+
console.log(" export ORY_USER_LOGIN=true");
|
|
398
|
+
console.log(" export ORY_OAUTH2_CLIENT_ID=<public OAuth2 client id>");
|
|
399
|
+
console.log(` Without these, ${harnessName} runs without a human Ory identity attached`);
|
|
397
400
|
console.log(" to the session and permission checks fall back to a session:<id> subject.");
|
|
401
|
+
console.log(" The OAuth2 client id is the public client (no secret) you registered in");
|
|
402
|
+
console.log(" your Ory project with the four loopback redirect URIs.");
|
|
398
403
|
console.log("");
|
|
399
404
|
console.log(" 3. Optionally enable debug logging:");
|
|
400
405
|
console.log(" export ORY_AGENT_DEBUG=true");
|
package/dist/status-cli.js
CHANGED
|
@@ -57,7 +57,7 @@ function printUserIdentitySection() {
|
|
|
57
57
|
const tokens = (0, auth_store_js_1.loadTokens)();
|
|
58
58
|
console.log("");
|
|
59
59
|
console.log("User identity (interactive PKCE login):");
|
|
60
|
-
console.log(` Login: ${loginOn ? "on (ORY_USER_LOGIN)" : "off (set ORY_USER_LOGIN=
|
|
60
|
+
console.log(` Login: ${loginOn ? "on (ORY_USER_LOGIN)" : "off (set ORY_USER_LOGIN=true to enable)"}`);
|
|
61
61
|
if (!tokens) {
|
|
62
62
|
console.log(" Token cache: empty");
|
|
63
63
|
if (loginOn) {
|
|
@@ -209,7 +209,7 @@ async function printPermissionsSection(binName, harness) {
|
|
|
209
209
|
}
|
|
210
210
|
const hasUser = !!process.env.ORY_USER_SUBJECT_ID || isUserTokenUsable();
|
|
211
211
|
if (!hasUser) {
|
|
212
|
-
console.log(` Coverage: n/a (no cached user identity — run with ORY_USER_LOGIN=
|
|
212
|
+
console.log(` Coverage: n/a (no cached user identity — run with ORY_USER_LOGIN=true once,`);
|
|
213
213
|
console.log(` or set ORY_USER_SUBJECT_ID to probe a known subject)`);
|
|
214
214
|
return;
|
|
215
215
|
}
|
package/dist/tool-catalog.d.ts
CHANGED
|
@@ -36,3 +36,34 @@ export declare function getToolCatalog(harness: string): readonly string[];
|
|
|
36
36
|
* broad access so the same launcher can run every harness end-to-end.
|
|
37
37
|
*/
|
|
38
38
|
export declare const ALL_TOOLS: readonly string[];
|
|
39
|
+
/**
|
|
40
|
+
* Per-harness tool names whose semantics are "ask / inform the user"
|
|
41
|
+
* rather than "act on an external system". These reach the pre-tool-use
|
|
42
|
+
* hook the same way real tools do (e.g. Claude Code's `AskUserQuestion`
|
|
43
|
+
* arrives via `PreToolUse`), but they aren't tool *executions* — gating
|
|
44
|
+
* them through Ory would either block the user from being asked or, in
|
|
45
|
+
* observe mode, log a misleading `permission.observe_deny` for an event
|
|
46
|
+
* the user is about to handle directly.
|
|
47
|
+
*
|
|
48
|
+
* Plugins consult this list in their pre-tool gate and short-circuit to
|
|
49
|
+
* a single `user.interaction` audit span when a match is found. See
|
|
50
|
+
* `gateToolCall` in `permissions.ts`.
|
|
51
|
+
*
|
|
52
|
+
* The list is best-effort and additive: operators can extend it at
|
|
53
|
+
* runtime via the `ORY_INTERACTIVE_TOOLS` env var (comma-separated names
|
|
54
|
+
* applied to every harness).
|
|
55
|
+
*/
|
|
56
|
+
export declare const INTERACTIVE_TOOL_CATALOG: Record<string, readonly string[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Names from {@link INTERACTIVE_TOOL_CATALOG} for the given harness,
|
|
59
|
+
* merged with any operator-supplied names from `ORY_INTERACTIVE_TOOLS`.
|
|
60
|
+
* Unknown harnesses still respect the env-var extension.
|
|
61
|
+
*/
|
|
62
|
+
export declare function getInteractiveToolCatalog(harness: string): readonly string[];
|
|
63
|
+
/**
|
|
64
|
+
* Does the named tool, for the given harness, represent a user-facing
|
|
65
|
+
* interaction (ask / inform / confirm) rather than an external-system
|
|
66
|
+
* tool execution? Treats unknown harnesses the same as known ones — the
|
|
67
|
+
* env-var extension still applies.
|
|
68
|
+
*/
|
|
69
|
+
export declare function isInteractiveTool(harness: string, toolName: string): boolean;
|
package/dist/tool-catalog.js
CHANGED
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
* catalog update is safe.
|
|
14
14
|
*/
|
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = void 0;
|
|
16
|
+
exports.INTERACTIVE_TOOL_CATALOG = exports.ALL_TOOLS = exports.KNOWN_HARNESSES = exports.HARNESS_TOOL_CATALOG = void 0;
|
|
17
17
|
exports.getToolCatalog = getToolCatalog;
|
|
18
|
+
exports.getInteractiveToolCatalog = getInteractiveToolCatalog;
|
|
19
|
+
exports.isInteractiveTool = isInteractiveTool;
|
|
18
20
|
/**
|
|
19
21
|
* Built-in tool names known to ship with each supported harness.
|
|
20
22
|
*/
|
|
@@ -67,3 +69,58 @@ function getToolCatalog(harness) {
|
|
|
67
69
|
* broad access so the same launcher can run every harness end-to-end.
|
|
68
70
|
*/
|
|
69
71
|
exports.ALL_TOOLS = Array.from(new Set(Object.values(exports.HARNESS_TOOL_CATALOG).flat()));
|
|
72
|
+
/**
|
|
73
|
+
* Per-harness tool names whose semantics are "ask / inform the user"
|
|
74
|
+
* rather than "act on an external system". These reach the pre-tool-use
|
|
75
|
+
* hook the same way real tools do (e.g. Claude Code's `AskUserQuestion`
|
|
76
|
+
* arrives via `PreToolUse`), but they aren't tool *executions* — gating
|
|
77
|
+
* them through Ory would either block the user from being asked or, in
|
|
78
|
+
* observe mode, log a misleading `permission.observe_deny` for an event
|
|
79
|
+
* the user is about to handle directly.
|
|
80
|
+
*
|
|
81
|
+
* Plugins consult this list in their pre-tool gate and short-circuit to
|
|
82
|
+
* a single `user.interaction` audit span when a match is found. See
|
|
83
|
+
* `gateToolCall` in `permissions.ts`.
|
|
84
|
+
*
|
|
85
|
+
* The list is best-effort and additive: operators can extend it at
|
|
86
|
+
* runtime via the `ORY_INTERACTIVE_TOOLS` env var (comma-separated names
|
|
87
|
+
* applied to every harness).
|
|
88
|
+
*/
|
|
89
|
+
exports.INTERACTIVE_TOOL_CATALOG = {
|
|
90
|
+
"claude-code": ["AskUserQuestion", "ExitPlanMode", "TodoWrite"],
|
|
91
|
+
codex: [],
|
|
92
|
+
"gemini-cli": [],
|
|
93
|
+
openclaw: [],
|
|
94
|
+
opencode: [],
|
|
95
|
+
};
|
|
96
|
+
function parseEnvInteractiveTools() {
|
|
97
|
+
const raw = process.env.ORY_INTERACTIVE_TOOLS;
|
|
98
|
+
if (!raw)
|
|
99
|
+
return [];
|
|
100
|
+
return raw
|
|
101
|
+
.split(",")
|
|
102
|
+
.map((name) => name.trim())
|
|
103
|
+
.filter((name) => name.length > 0);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Names from {@link INTERACTIVE_TOOL_CATALOG} for the given harness,
|
|
107
|
+
* merged with any operator-supplied names from `ORY_INTERACTIVE_TOOLS`.
|
|
108
|
+
* Unknown harnesses still respect the env-var extension.
|
|
109
|
+
*/
|
|
110
|
+
function getInteractiveToolCatalog(harness) {
|
|
111
|
+
const builtIn = exports.INTERACTIVE_TOOL_CATALOG[harness] ?? [];
|
|
112
|
+
const envExtra = parseEnvInteractiveTools();
|
|
113
|
+
if (envExtra.length === 0)
|
|
114
|
+
return builtIn;
|
|
115
|
+
// Stable order: built-ins first, then env additions, deduplicated.
|
|
116
|
+
return Array.from(new Set([...builtIn, ...envExtra]));
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Does the named tool, for the given harness, represent a user-facing
|
|
120
|
+
* interaction (ask / inform / confirm) rather than an external-system
|
|
121
|
+
* tool execution? Treats unknown harnesses the same as known ones — the
|
|
122
|
+
* env-var extension still applies.
|
|
123
|
+
*/
|
|
124
|
+
function isInteractiveTool(harness, toolName) {
|
|
125
|
+
return getInteractiveToolCatalog(harness).includes(toolName);
|
|
126
|
+
}
|
package/dist/tracer.d.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { EventEmitter } from "node:events";
|
|
20
20
|
import type { SpanExporter } from "./otel/exporter.js";
|
|
21
|
-
export type TraceEvent = "session.start" | "session.end" | "session.verify" | "user.auth" | "user.prompt" | "agent.auth" | "oauth2.introspect" | "oauth2.login" | "oauth2.refresh" | "permission.check" | "permission.batch_check" | "permission.observe_deny" | "tool.invoke" | "tool.complete" | "tool.block" | "tool.fail" | "turn.stop" | "subagent.start" | "subagent.stop" | "notification" | "compaction" | "relationship.create" | "relationship.delete" | "relationship.patch" | "hook.receive" | "hook.passthrough" | "config.resolve";
|
|
21
|
+
export type TraceEvent = "session.start" | "session.end" | "session.verify" | "user.auth" | "user.prompt" | "user.interaction" | "agent.auth" | "oauth2.introspect" | "oauth2.login" | "oauth2.refresh" | "permission.check" | "permission.batch_check" | "permission.observe_deny" | "tool.invoke" | "tool.complete" | "tool.block" | "tool.fail" | "turn.stop" | "subagent.start" | "subagent.stop" | "notification" | "compaction" | "relationship.create" | "relationship.delete" | "relationship.patch" | "hook.receive" | "hook.passthrough" | "config.resolve";
|
|
22
22
|
export type SpanStatus = "ok" | "error" | "denied" | "skipped";
|
|
23
23
|
export interface TraceSpan {
|
|
24
24
|
traceId: string;
|
package/dist/user-login.d.ts
CHANGED
|
@@ -18,7 +18,8 @@
|
|
|
18
18
|
* the audit trail is complete regardless of outcome.
|
|
19
19
|
*
|
|
20
20
|
* The whole flow is a no-op (mode `disabled`) unless the
|
|
21
|
-
* `ORY_USER_LOGIN` env var is set to `1
|
|
21
|
+
* `ORY_USER_LOGIN` env var is set to `true` (legacy values `1`, `yes`,
|
|
22
|
+
* `on` are still accepted for back-compat).
|
|
22
23
|
*
|
|
23
24
|
* This authenticates the *user* (the human at the keyboard). The
|
|
24
25
|
* separate agent identity (the AI process making the calls) is resolved
|
package/dist/user-login.js
CHANGED
|
@@ -19,7 +19,8 @@
|
|
|
19
19
|
* the audit trail is complete regardless of outcome.
|
|
20
20
|
*
|
|
21
21
|
* The whole flow is a no-op (mode `disabled`) unless the
|
|
22
|
-
* `ORY_USER_LOGIN` env var is set to `1
|
|
22
|
+
* `ORY_USER_LOGIN` env var is set to `true` (legacy values `1`, `yes`,
|
|
23
|
+
* `on` are still accepted for back-compat).
|
|
23
24
|
*
|
|
24
25
|
* This authenticates the *user* (the human at the keyboard). The
|
|
25
26
|
* separate agent identity (the AI process making the calls) is resolved
|
|
@@ -39,7 +40,10 @@ function isUserLoginEnabled() {
|
|
|
39
40
|
return !!v && ENABLED_VALUES.has(v);
|
|
40
41
|
}
|
|
41
42
|
function clientId() {
|
|
42
|
-
|
|
43
|
+
// resolveConfig already merges env (ORY_OAUTH2_CLIENT_ID) with the
|
|
44
|
+
// persisted value, env winning. Centralizing here keeps the user gate,
|
|
45
|
+
// status CLI, and configure command in lockstep on resolution order.
|
|
46
|
+
return (0, config_js_1.resolveConfig)().oauth2ClientId;
|
|
43
47
|
}
|
|
44
48
|
function recordAuthSpan(client, decision) {
|
|
45
49
|
const status = decision.mode === "ok" || decision.mode === "refreshed" || decision.mode === "env_token"
|
|
@@ -211,7 +215,9 @@ async function runUserLogin(client, options) {
|
|
|
211
215
|
mode: "skipped",
|
|
212
216
|
reason: "ORY_OAUTH2_CLIENT_ID is not set; cannot start browser login (run `npx " +
|
|
213
217
|
options.binName +
|
|
214
|
-
" configure
|
|
218
|
+
" configure --oauth2-client-id <id>` after registering a public OAuth2 client " +
|
|
219
|
+
"in your Ory project with all four loopback redirect URIs: " +
|
|
220
|
+
"http://127.0.0.1:47823..47826/callback)",
|
|
215
221
|
};
|
|
216
222
|
}
|
|
217
223
|
// No TTY check here on purpose: PKCE only needs a browser that can reach
|
|
@@ -221,7 +227,7 @@ async function runUserLogin(client, options) {
|
|
|
221
227
|
// complete sign-in by pasting the URL into any browser that can reach
|
|
222
228
|
// 127.0.0.1. Truly unattended runs (CI=true) are short-circuited by
|
|
223
229
|
// `pkceLogin` itself via `detectHeadless`, and operators can bypass
|
|
224
|
-
// login entirely with `ORY_USER_SESSION_TOKEN` or `ORY_USER_LOGIN=
|
|
230
|
+
// login entirely with `ORY_USER_SESSION_TOKEN` or `ORY_USER_LOGIN=false`.
|
|
225
231
|
// PKCE-flight lock so concurrent processes share a single browser flow.
|
|
226
232
|
const lock = (0, auth_store_js_1.tryAcquirePkceFlightLock)();
|
|
227
233
|
if (!lock) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ory/argus",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"description": "Ory Argus: the core API for building authentication, authorization, and audit into AI agent harness plugins, extensions, and custom integrations",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://ory.com",
|