@ory/argus 0.14.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -46
- package/assets/commands/temporal-up.md +1 -1
- package/assets/skills/auth-setup/SKILL.md +1 -1
- package/assets/skills/local-dev/SKILL.md +17 -7
- package/assets/skills/ory-build-agent/SKILL.md +39 -91
- package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -17
- package/assets/skills/ory-temporal-worker/SKILL.md +31 -33
- package/assets/skills/permissions-onboarding/SKILL.md +131 -104
- package/dist/adapters.d.ts +88 -32
- package/dist/adapters.js +443 -164
- package/dist/agent-auth.d.ts +226 -67
- package/dist/agent-auth.js +951 -205
- package/dist/auth-store.d.ts +37 -2
- package/dist/auth-store.js +37 -3
- package/dist/auth.d.ts +33 -4
- package/dist/auth.js +163 -20
- package/dist/bash-parser.d.ts +98 -0
- package/dist/bash-parser.js +396 -0
- package/dist/branding.d.ts +77 -16
- package/dist/branding.js +93 -23
- package/dist/build-info.json +4 -4
- package/dist/cli-invocation.d.ts +1 -1
- package/dist/cli-invocation.js +2 -1
- package/dist/cli.d.ts +20 -29
- package/dist/cli.js +271 -239
- package/dist/client.d.ts +175 -138
- package/dist/client.js +672 -391
- package/dist/config.d.ts +217 -54
- package/dist/config.js +461 -42
- package/dist/context.d.ts +10 -0
- package/dist/context.js +21 -0
- package/dist/contract-suite.d.ts +3 -5
- package/dist/contract-suite.js +75 -47
- package/dist/denial.d.ts +36 -3
- package/dist/denial.js +79 -10
- package/dist/event-reporter.d.ts +77 -0
- package/dist/event-reporter.js +776 -0
- package/dist/external-registrations-main.d.ts +10 -0
- package/dist/external-registrations-main.js +38 -0
- package/dist/external-registrations.d.ts +79 -0
- package/dist/external-registrations.js +188 -0
- package/dist/help-cli.d.ts +39 -0
- package/dist/help-cli.js +55 -0
- package/dist/hook-timeout.d.ts +64 -0
- package/dist/hook-timeout.js +88 -0
- package/dist/index.d.ts +28 -23
- package/dist/index.js +173 -58
- package/dist/lifecycle.d.ts +3 -3
- package/dist/lifecycle.js +38 -6
- package/dist/local/cli.js +11 -6
- package/dist/local/configs.d.ts +74 -18
- package/dist/local/configs.js +291 -84
- package/dist/local/health.js +7 -7
- package/dist/local/index.d.ts +2 -2
- package/dist/local/index.js +24 -10
- package/dist/local/manager.d.ts +20 -1
- package/dist/local/manager.js +159 -36
- package/dist/local/ports.d.ts +158 -0
- package/dist/local/ports.js +443 -0
- package/dist/local/seed.d.ts +13 -5
- package/dist/local/seed.js +62 -38
- package/dist/logger.d.ts +54 -25
- package/dist/logger.js +329 -63
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +10 -5
- package/dist/mirror-bootstrap.d.ts +48 -0
- package/dist/mirror-bootstrap.js +254 -0
- package/dist/opl.d.ts +289 -0
- package/dist/opl.js +446 -0
- package/dist/permission-mode.d.ts +87 -0
- package/dist/permission-mode.js +307 -0
- package/dist/permissions-cli.d.ts +13 -49
- package/dist/permissions-cli.js +154 -348
- package/dist/permissions.d.ts +148 -38
- package/dist/permissions.js +591 -45
- package/dist/post-install.d.ts +33 -0
- package/dist/post-install.js +127 -0
- package/dist/read-credential.d.ts +65 -0
- package/dist/read-credential.js +86 -0
- package/dist/registry/cli.js +5 -2
- package/dist/registry/config.d.ts +0 -17
- package/dist/registry/config.js +0 -23
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/index.js +2 -2
- package/dist/registry/manager.d.ts +4 -21
- package/dist/registry/manager.js +21 -37
- package/dist/runtime-credential.d.ts +140 -0
- package/dist/runtime-credential.js +572 -0
- package/dist/runtime.d.ts +408 -0
- package/dist/runtime.js +748 -0
- package/dist/setup.d.ts +23 -47
- package/dist/setup.js +59 -116
- package/dist/skills.js +0 -7
- package/dist/status-cli.d.ts +27 -11
- package/dist/status-cli.js +121 -115
- package/dist/status-data.d.ts +116 -17
- package/dist/status-data.js +121 -38
- package/dist/subject.d.ts +126 -20
- package/dist/subject.js +215 -30
- package/dist/testing.d.ts +74 -38
- package/dist/testing.js +185 -68
- package/dist/tool-catalog.d.ts +53 -11
- package/dist/tool-catalog.js +164 -13
- package/dist/tool-metadata.d.ts +7 -6
- package/dist/tool-metadata.js +6 -5
- package/dist/types.d.ts +11 -1
- package/dist/uninstall.d.ts +71 -34
- package/dist/uninstall.js +217 -85
- package/dist/user-login.d.ts +9 -10
- package/dist/user-login.js +56 -75
- package/dist/watch-cli.d.ts +6 -0
- package/dist/watch-cli.js +217 -0
- package/package.json +4 -27
- package/assets/commands/dashboard.md +0 -34
- package/dist/dashboard-cli.d.ts +0 -8
- package/dist/dashboard-cli.js +0 -70
- package/dist/dev.d.ts +0 -103
- package/dist/dev.js +0 -583
- package/dist/interactive-setup.d.ts +0 -286
- package/dist/interactive-setup.js +0 -1734
- package/dist/local/jaeger-main.d.ts +0 -13
- package/dist/local/jaeger-main.js +0 -85
- package/dist/local/jaeger.d.ts +0 -50
- package/dist/local/jaeger.js +0 -162
- package/dist/otel/exporter.d.ts +0 -17
- package/dist/otel/exporter.js +0 -12
- package/dist/otel/index.d.ts +0 -2
- package/dist/otel/index.js +0 -8
- package/dist/otel/otlp.d.ts +0 -103
- package/dist/otel/otlp.js +0 -385
- package/dist/project-api-key.d.ts +0 -69
- package/dist/project-api-key.js +0 -147
- package/dist/setup-actions.d.ts +0 -232
- package/dist/setup-actions.js +0 -507
- package/dist/tracer.d.ts +0 -190
- package/dist/tracer.js +0 -481
- package/dist/watch-sandbox.d.ts +0 -9
- package/dist/watch-sandbox.js +0 -81
- package/dist/web/api.d.ts +0 -33
- package/dist/web/api.js +0 -294
- package/dist/web/launch.d.ts +0 -11
- package/dist/web/launch.js +0 -96
- package/dist/web/server.d.ts +0 -20
- package/dist/web/server.js +0 -233
- package/dist/web/types.d.ts +0 -65
- package/dist/web/types.js +0 -2
- package/dist/webapp/assets/index-Wucl4SZs.css +0 -1
- package/dist/webapp/assets/index-m-GtEdq0.js +0 -49
- package/dist/webapp/favicon.ico +0 -0
- package/dist/webapp/index.html +0 -15
package/dist/adapters.js
CHANGED
|
@@ -3,15 +3,18 @@
|
|
|
3
3
|
* Shared adapter primitives for Agent SDK integrations (and harness plugins).
|
|
4
4
|
*
|
|
5
5
|
* These extract the session-start sequencing and the `tool.invoke` / `tool.block` /
|
|
6
|
-
* `tool.complete`
|
|
6
|
+
* `tool.complete` activity boilerplate that each integration would otherwise re-implement, so
|
|
7
7
|
* an SDK binding is a thin translation between the SDK's hook signature and these calls.
|
|
8
8
|
* They are the TypeScript twin of `ory_argus.adapters` in the Python core.
|
|
9
9
|
*
|
|
10
|
+
* - {@link withHookContext} — scope session state to one lifecycle invocation.
|
|
10
11
|
* - {@link sessionStart} — run the user + agent auth gates and write the user→agent
|
|
11
12
|
* delegation tuple; returns whether the session may proceed.
|
|
12
|
-
* - {@link
|
|
13
|
+
* - {@link createSessionStarter} — coalesce concurrent lazy SDK session starts.
|
|
14
|
+
* - {@link decideTool} — normalize native/MCP policy without execution activity.
|
|
15
|
+
* - {@link gate} — resolve the subject, run {@link gateToolCall}, record activity, and
|
|
13
16
|
* return a normalized {@link GateResult}.
|
|
14
|
-
* - {@link complete} — record
|
|
17
|
+
* - {@link complete} / {@link fail} — record safe terminal tool activity.
|
|
15
18
|
* - {@link registerSubagent} — resolve a sub-agent identity + agent→subagent tuple.
|
|
16
19
|
* - {@link wrapTool} — wrap a plain `execute(args)` with gate+complete, for SDKs whose
|
|
17
20
|
* only veto point is the tool boundary (Vercel AI SDK, etc.).
|
|
@@ -20,26 +23,80 @@
|
|
|
20
23
|
*/
|
|
21
24
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
25
|
exports.resolveNamespace = resolveNamespace;
|
|
26
|
+
exports.withHookContext = withHookContext;
|
|
23
27
|
exports.sessionStart = sessionStart;
|
|
28
|
+
exports.createSessionStarter = createSessionStarter;
|
|
24
29
|
exports.writeUserDelegatesAgent = writeUserDelegatesAgent;
|
|
25
30
|
exports.registerSubagent = registerSubagent;
|
|
26
31
|
exports.writeAgentDelegatesSubagent = writeAgentDelegatesSubagent;
|
|
32
|
+
exports.decideTool = decideTool;
|
|
27
33
|
exports.gate = gate;
|
|
28
34
|
exports.complete = complete;
|
|
35
|
+
exports.fail = fail;
|
|
29
36
|
exports.wrapTool = wrapTool;
|
|
37
|
+
const node_os_1 = require("node:os");
|
|
30
38
|
const config_js_1 = require("./config.js");
|
|
31
39
|
const agent_auth_js_1 = require("./agent-auth.js");
|
|
32
40
|
const user_login_js_1 = require("./user-login.js");
|
|
33
41
|
const permissions_js_1 = require("./permissions.js");
|
|
42
|
+
const permission_mode_js_1 = require("./permission-mode.js");
|
|
43
|
+
const tool_catalog_js_1 = require("./tool-catalog.js");
|
|
34
44
|
const subject_js_1 = require("./subject.js");
|
|
35
45
|
const denial_js_1 = require("./denial.js");
|
|
46
|
+
const mcp_js_1 = require("./mcp.js");
|
|
47
|
+
const tool_metadata_js_1 = require("./tool-metadata.js");
|
|
36
48
|
function resolveNamespace() {
|
|
37
|
-
return process.env.ORY_PERMISSION_NAMESPACE ?? "
|
|
49
|
+
return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTool";
|
|
50
|
+
}
|
|
51
|
+
/** Scope ambient session state to one lifecycle invocation. */
|
|
52
|
+
function withHookContext(client, opts, work) {
|
|
53
|
+
return client.withRuntimeContext(opts, work);
|
|
54
|
+
}
|
|
55
|
+
/** True when a thrown value is a classified {@link OryError} rather than an `Error`. */
|
|
56
|
+
function isOryError(err) {
|
|
57
|
+
return (typeof err === "object" &&
|
|
58
|
+
err !== null &&
|
|
59
|
+
typeof err.code === "string" &&
|
|
60
|
+
typeof err.message === "string");
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Describe a thrown value as structured log fields.
|
|
64
|
+
*
|
|
65
|
+
* The best-effort paths below swallow their failures, so the debug log is the
|
|
66
|
+
* only place the cause survives — it has to actually say something. Two shapes
|
|
67
|
+
* reach them: a real `Error`, and the **plain object** `classifyError` produces
|
|
68
|
+
* (`{ code, status, message, requestId }` — not an `Error` instance). A bare
|
|
69
|
+
* `String(err)` renders that second shape as `"[object Object]"`, which is how a
|
|
70
|
+
* broker returning HTTP 500 showed up as a log line naming no cause at all.
|
|
71
|
+
*
|
|
72
|
+
* Fields are returned separately rather than concatenated because
|
|
73
|
+
* {@link DebugLogger} emits JSON — `code` and `status` are worth grepping for.
|
|
74
|
+
*/
|
|
75
|
+
function describeThrown(err) {
|
|
76
|
+
if (isOryError(err)) {
|
|
77
|
+
return {
|
|
78
|
+
message: err.message,
|
|
79
|
+
code: err.code,
|
|
80
|
+
...(err.status !== undefined ? { status: err.status } : {}),
|
|
81
|
+
...(err.requestId ? { requestId: err.requestId } : {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (err instanceof Error)
|
|
85
|
+
return { message: err.message };
|
|
86
|
+
if (typeof err === "string")
|
|
87
|
+
return { message: err };
|
|
88
|
+
// Anything else: serialize rather than let it stringify to "[object Object]".
|
|
89
|
+
try {
|
|
90
|
+
return { message: JSON.stringify(err) ?? String(err) };
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return { message: String(err) };
|
|
94
|
+
}
|
|
38
95
|
}
|
|
39
96
|
/**
|
|
40
97
|
* Run both auth gates and write the user→agent delegation tuple. The user gate
|
|
41
98
|
* runs every session to establish/refresh the user identity, records the
|
|
42
|
-
|
|
99
|
+
* `user.auth` activity event, and never blocks — enforcement is governed by
|
|
43
100
|
* `permissionMode` at tool-call time. `SessionStartResult.proceed` is therefore
|
|
44
101
|
* always `true`; it is retained only for a stable adapter shape.
|
|
45
102
|
*/
|
|
@@ -47,15 +104,54 @@ async function sessionStart(client, opts) {
|
|
|
47
104
|
const projectUrl = opts.projectUrl ?? (0, config_js_1.resolveConfig)().projectUrl;
|
|
48
105
|
const userLogin = opts.userLogin ?? user_login_js_1.ensureUserAuthenticated;
|
|
49
106
|
const agentGate = opts.agentGate ?? agent_auth_js_1.ensureAgentIdentity;
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
107
|
+
let decision = {
|
|
108
|
+
proceed: true,
|
|
109
|
+
mode: "skipped",
|
|
110
|
+
reason: "User authentication failed open",
|
|
111
|
+
};
|
|
112
|
+
try {
|
|
113
|
+
decision = await userLogin(client, {
|
|
114
|
+
binName: opts.binName ?? `ory-${opts.harness}`,
|
|
115
|
+
harness: opts.harness,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
client.logger.warn("session_start.user_auth.failed", describeThrown(err));
|
|
120
|
+
}
|
|
121
|
+
let creds = {
|
|
122
|
+
kind: "none",
|
|
123
|
+
reason: "Agent authentication failed open",
|
|
124
|
+
warnings: [],
|
|
125
|
+
};
|
|
126
|
+
try {
|
|
127
|
+
creds = await agentGate(client, {
|
|
128
|
+
projectUrl,
|
|
129
|
+
harness: opts.harness,
|
|
130
|
+
sessionId: opts.sessionId ?? client.sessionId,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
catch (err) {
|
|
134
|
+
client.logger.warn("session_start.agent_auth.failed", describeThrown(err));
|
|
135
|
+
}
|
|
136
|
+
await writeUserDelegatesAgent(client, opts.harness, opts.sessionId);
|
|
137
|
+
// Warm the permission-mode cache from the server once per session, so the
|
|
138
|
+
// synchronous `resolveConfig().permissionMode` read (the MCP
|
|
139
|
+
// `applyPermissionMode` path, display surfaces) reflects the current project
|
|
140
|
+
// posture even before the first `gateToolCall`. Best-effort; never blocks —
|
|
141
|
+
// a no-op when Agent Security isn't connected (no checks) and on any Keto error.
|
|
142
|
+
if ((0, config_js_1.isSecurityConnected)()) {
|
|
143
|
+
const subject = (0, subject_js_1.resolveUserSubject)(client);
|
|
144
|
+
const agentSubject = (0, subject_js_1.resolveAgentSubject)(client);
|
|
145
|
+
await (0, permission_mode_js_1.warmPermissionModeCache)(client, subject, {
|
|
146
|
+
additionalSubjects: agentSubject ? [{ scope: "agent", subject: agentSubject }] : [],
|
|
147
|
+
}).catch(() => {
|
|
148
|
+
/* best-effort cache warm; gate-time resolution retries */
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
client.logger.activity("session.start", "ok", {
|
|
152
|
+
attributes: { harness: opts.harness, ...(opts.activityAttributes ?? {}) },
|
|
153
|
+
sessionId: opts.sessionId,
|
|
57
154
|
});
|
|
58
|
-
await writeUserDelegatesAgent(client);
|
|
59
155
|
return {
|
|
60
156
|
proceed: decision.proceed,
|
|
61
157
|
userMode: decision.mode,
|
|
@@ -63,34 +159,22 @@ async function sessionStart(client, opts) {
|
|
|
63
159
|
agentKind: creds.kind,
|
|
64
160
|
};
|
|
65
161
|
}
|
|
66
|
-
/**
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const res = await client.ensureRelationship(check, {
|
|
79
|
-
spanAttributes: options.spanAttributes,
|
|
162
|
+
/** Build a concurrency-safe, retryable lazy session-start trigger. */
|
|
163
|
+
function createSessionStarter(client, opts) {
|
|
164
|
+
const pending = new Map();
|
|
165
|
+
return () => {
|
|
166
|
+
const sessionId = opts.sessionId ?? client.sessionId;
|
|
167
|
+
const key = sessionId?.trim() || "sessionless";
|
|
168
|
+
const existing = pending.get(key);
|
|
169
|
+
if (existing)
|
|
170
|
+
return existing;
|
|
171
|
+
const started = sessionStart(client, { ...opts, sessionId }).then(() => undefined, (err) => {
|
|
172
|
+
pending.delete(key);
|
|
173
|
+
client.logger.warn("session_start.failed", describeThrown(err));
|
|
80
174
|
});
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
message: res.probeError.message,
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
catch (err) {
|
|
89
|
-
client.logger.warn(options.failedEvent, {
|
|
90
|
-
...options.failedContext,
|
|
91
|
-
message: err instanceof Error ? err.message : String(err),
|
|
92
|
-
});
|
|
93
|
-
}
|
|
175
|
+
pending.set(key, started);
|
|
176
|
+
return started;
|
|
177
|
+
};
|
|
94
178
|
}
|
|
95
179
|
/** Strip a `<ns>:` prefix from a namespaced subject, if present. */
|
|
96
180
|
function unqualify(subject, ns) {
|
|
@@ -100,8 +184,8 @@ function unqualify(subject, ns) {
|
|
|
100
184
|
/**
|
|
101
185
|
* Resolve one end of a delegation edge, preferring the live principal but
|
|
102
186
|
* falling back to the persisted delegation anchor (`delegation.delegatedBy`
|
|
103
|
-
* on the DCR credentials in local config). This is what lets the
|
|
104
|
-
*
|
|
187
|
+
* on the DCR credentials in local config). This is what lets the edge be
|
|
188
|
+
* recorded from an authoritative source rather than from nothing when the
|
|
105
189
|
* principal isn't populated for the session. A mismatch between the two is
|
|
106
190
|
* logged for reconciliation visibility; the live principal wins.
|
|
107
191
|
*/
|
|
@@ -112,54 +196,61 @@ function reconcileEdgeEnd(client, principal, anchor, role) {
|
|
|
112
196
|
return principal ?? anchor;
|
|
113
197
|
}
|
|
114
198
|
/**
|
|
115
|
-
*
|
|
116
|
-
* (see {@link
|
|
117
|
-
* their own session-start sequence (rather than {@link sessionStart})
|
|
118
|
-
* the
|
|
119
|
-
* session.
|
|
199
|
+
* Record the `user → agent` delegation edge through the agent-security broker
|
|
200
|
+
* (see {@link OryAgentClient.recordDelegation}). Exported so harness plugins
|
|
201
|
+
* that run their own session-start sequence (rather than {@link sessionStart})
|
|
202
|
+
* record the edge the same way.
|
|
120
203
|
*
|
|
121
|
-
* The
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
204
|
+
* The plugin supplies only the delegating user identity (resolved from the live
|
|
205
|
+
* principal, else the persisted delegation anchor) plus the harness and host;
|
|
206
|
+
* the broker owns the Keto tuple and its join-key encoding. The node id the
|
|
207
|
+
* broker returns (`agent:<join>`) is persisted so a later sub-agent
|
|
208
|
+
* registration — which may run in a separate subprocess with no live principal
|
|
209
|
+
* — can reference it verbatim as `delegated_by`. Best-effort and fail-open:
|
|
210
|
+
* any failure (including a 404 against the local stack, which has no broker) is
|
|
211
|
+
* logged and swallowed. No-op when Agent Security isn't connected (there is no
|
|
212
|
+
* broker to record against) and when no user identity resolves.
|
|
126
213
|
*/
|
|
127
|
-
async function writeUserDelegatesAgent(client) {
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
214
|
+
async function writeUserDelegatesAgent(client, harness, sessionId) {
|
|
215
|
+
if (!(0, config_js_1.isSecurityConnected)())
|
|
216
|
+
return;
|
|
217
|
+
// Resolve the delegating user (the anchor of the edge). Prefer the live
|
|
218
|
+
// principal; fall back to the delegator persisted in the agent client's
|
|
219
|
+
// delegation anchor so the edge can still be recorded in a session where the
|
|
220
|
+
// user principal wasn't populated. The anchor is per-harness — reading a
|
|
221
|
+
// different harness's anchor would attribute this harness's delegation to
|
|
222
|
+
// another agent's client.
|
|
223
|
+
const harnessKey = harness ?? client.harness;
|
|
224
|
+
const sessionKey = (sessionId ?? client.sessionId)?.trim() || config_js_1.SESSIONLESS_KEY;
|
|
225
|
+
const anchorCreds = (0, agent_auth_js_1.loadAgentDynamicCredentials)(harnessKey, sessionKey);
|
|
134
226
|
const anchor = anchorCreds?.delegation;
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
// resolveClientCredentials). The anchor rebuild must apply the *same*
|
|
139
|
-
// precedence, or a no-principal session would write `agent:<clientId>` while
|
|
140
|
-
// a live session wrote `agent:<override>` — two divergent tuples for one edge.
|
|
141
|
-
const anchorAgent = hasUserAgentAnchor
|
|
142
|
-
? (process.env.ORY_AGENT_SUBJECT_ID || anchorCreds.clientId)
|
|
143
|
-
: undefined;
|
|
144
|
-
const user = reconcileEdgeEnd(client, client.userPrincipal.subject, anchorUser, "user");
|
|
145
|
-
const agent = reconcileEdgeEnd(client, client.agentPrincipal.subject, anchorAgent, "agent");
|
|
146
|
-
if (!user || !agent) {
|
|
227
|
+
const anchorUser = anchor?.delegationType === "user-to-agent" ? unqualify(anchor.delegatedBy, "user") : undefined;
|
|
228
|
+
const identityId = reconcileEdgeEnd(client, client.userPrincipalForSession(sessionKey).subject, anchorUser, "user");
|
|
229
|
+
if (!identityId) {
|
|
147
230
|
client.logger.debug("delegation.skip", {
|
|
148
|
-
reason: "
|
|
149
|
-
hasUser: !!user,
|
|
150
|
-
hasAgent: !!agent,
|
|
231
|
+
reason: "no user delegation edge (no principal and no anchor)",
|
|
151
232
|
});
|
|
152
233
|
return;
|
|
153
234
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
235
|
+
// The broker derives the agent node from (identity, harness, host, session)
|
|
236
|
+
// and writes `AgentTool:agent:<join>#delegate@(User:<id>)`. Persist the
|
|
237
|
+
// returned node id under *this session* so the sub-agent edge can reference it
|
|
238
|
+
// without re-deriving the join key.
|
|
239
|
+
try {
|
|
240
|
+
const res = await client.recordDelegation({
|
|
241
|
+
kind: "agent",
|
|
242
|
+
identityId,
|
|
243
|
+
host: (0, node_os_1.hostname)(),
|
|
244
|
+
...(sessionKey !== config_js_1.SESSIONLESS_KEY ? { sessionId: sessionKey } : {}),
|
|
245
|
+
}, {
|
|
246
|
+
activityAttributes: { delegation: "user-to-agent" },
|
|
247
|
+
});
|
|
248
|
+
if (res.nodeId)
|
|
249
|
+
(0, agent_auth_js_1.saveDelegationNodeId)(harnessKey, sessionKey, res.nodeId);
|
|
250
|
+
}
|
|
251
|
+
catch (err) {
|
|
252
|
+
client.logger.warn("delegation.user_to_agent.failed", describeThrown(err));
|
|
253
|
+
}
|
|
163
254
|
}
|
|
164
255
|
async function registerSubagent(client, opts) {
|
|
165
256
|
const projectUrl = opts.projectUrl ?? (0, config_js_1.resolveConfig)().projectUrl;
|
|
@@ -170,128 +261,306 @@ async function registerSubagent(client, opts) {
|
|
|
170
261
|
subAgentType: opts.subAgentType,
|
|
171
262
|
projectUrl,
|
|
172
263
|
harness: opts.harness,
|
|
264
|
+
sessionId: opts.sessionId ?? client.sessionId,
|
|
265
|
+
perSpawnId: opts.perSpawnId,
|
|
266
|
+
signal: opts.signal,
|
|
173
267
|
});
|
|
174
268
|
}
|
|
175
269
|
catch (err) {
|
|
176
270
|
client.logger.warn("subagent.identity.failed", {
|
|
177
271
|
subAgentType: opts.subAgentType,
|
|
178
|
-
|
|
272
|
+
...describeThrown(err),
|
|
179
273
|
});
|
|
180
274
|
return;
|
|
181
275
|
}
|
|
182
|
-
if (identity.kind !== "
|
|
276
|
+
if (identity.kind !== "runtime" && identity.kind !== "dynamic")
|
|
183
277
|
return;
|
|
184
|
-
await writeAgentDelegatesSubagent(client, identity.subject, opts.subAgentType
|
|
278
|
+
await writeAgentDelegatesSubagent(client, identity.subject ?? "", opts.subAgentType, {
|
|
279
|
+
harness: opts.harness,
|
|
280
|
+
sessionId: opts.sessionId,
|
|
281
|
+
perSpawnId: opts.perSpawnId,
|
|
282
|
+
signal: opts.signal,
|
|
283
|
+
runtimeCredential: identity.runtimeCredential,
|
|
284
|
+
agentToken: identity.token,
|
|
285
|
+
});
|
|
185
286
|
}
|
|
186
287
|
/**
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
288
|
+
* Record the `agent → subagent` delegation edge through the agent-security
|
|
289
|
+
* broker. Exported so harness plugins that resolve the sub-agent identity
|
|
290
|
+
* through their own event plumbing (rather than {@link registerSubagent})
|
|
291
|
+
* record the edge the same way.
|
|
292
|
+
*
|
|
293
|
+
* The broker builds the sub-agent node from the parent agent's delegation node
|
|
294
|
+
* (`delegated_by`) and the sub-agent type — so the plugin passes the node id
|
|
295
|
+
* the broker assigned to the `user → agent` edge (persisted at session start),
|
|
296
|
+
* echoed verbatim, and never reconstructs the join-key encoding itself.
|
|
297
|
+
* Best-effort and fail-open. No-op when Agent Security isn't connected and until the
|
|
298
|
+
* `user → agent` edge has been
|
|
299
|
+
* recorded (so the parent node id is available).
|
|
192
300
|
*/
|
|
193
|
-
async function writeAgentDelegatesSubagent(client, subAgentSubject, subAgentType) {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
//
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
301
|
+
async function writeAgentDelegatesSubagent(client, subAgentSubject, subAgentType, opts = {}) {
|
|
302
|
+
if (!(0, config_js_1.isSecurityConnected)())
|
|
303
|
+
return;
|
|
304
|
+
// The parent agent's delegation node (`agent:<join>`) was assigned by the
|
|
305
|
+
// broker when the user→agent edge was recorded and persisted at session start.
|
|
306
|
+
// Echo it verbatim as `delegated_by`. It is read for *this harness and this
|
|
307
|
+
// session*: the parent of this sub-agent is this run's session credential, so
|
|
308
|
+
// the harness alone would not identify it.
|
|
309
|
+
const sessionKey = (opts.sessionId ?? client.sessionId)?.trim() || config_js_1.SESSIONLESS_KEY;
|
|
310
|
+
const delegatedBy = (0, agent_auth_js_1.loadDelegationNodeId)(opts.harness ?? client.harness, sessionKey);
|
|
311
|
+
if (!delegatedBy) {
|
|
203
312
|
client.logger.debug("delegation.skip", {
|
|
204
|
-
reason: "no agent delegation
|
|
313
|
+
reason: "no persisted agent delegation node (user→agent edge not recorded yet)",
|
|
205
314
|
subAgentType,
|
|
315
|
+
sessionKey,
|
|
206
316
|
});
|
|
207
317
|
return;
|
|
208
318
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
319
|
+
try {
|
|
320
|
+
await client.recordDelegation({
|
|
321
|
+
kind: "subagent",
|
|
322
|
+
subAgentType,
|
|
323
|
+
delegatedBy,
|
|
324
|
+
...(sessionKey !== config_js_1.SESSIONLESS_KEY ? { sessionId: sessionKey } : {}),
|
|
325
|
+
...(opts.perSpawnId ? { perSpawnId: opts.perSpawnId } : {}),
|
|
326
|
+
}, {
|
|
327
|
+
signal: opts.signal,
|
|
328
|
+
agentToken: opts.agentToken,
|
|
329
|
+
runtimeCredential: opts.runtimeCredential,
|
|
330
|
+
activityAttributes: {
|
|
331
|
+
delegation: "agent-to-subagent",
|
|
332
|
+
subAgentType,
|
|
333
|
+
...(subAgentSubject ? { subAgentSubject } : {}),
|
|
334
|
+
...(opts.perSpawnId ? { perSpawnId: opts.perSpawnId } : {}),
|
|
335
|
+
},
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
client.logger.warn("delegation.agent_to_subagent.failed", {
|
|
340
|
+
subAgentType,
|
|
341
|
+
...describeThrown(err),
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
function gateActivityAttributes(opts) {
|
|
346
|
+
return {
|
|
347
|
+
toolName: opts.toolName,
|
|
348
|
+
...(0, tool_metadata_js_1.summarizeToolInput)(opts.toolName, opts.toolArgs),
|
|
349
|
+
...(opts.mcpTool
|
|
350
|
+
? {
|
|
351
|
+
mcpServer: opts.mcpTool.serverName,
|
|
352
|
+
...(opts.mcpTool.toolName ? { mcpTool: opts.mcpTool.toolName } : {}),
|
|
353
|
+
}
|
|
354
|
+
: {}),
|
|
355
|
+
...(opts.extraActivityAttributes ?? {}),
|
|
356
|
+
};
|
|
219
357
|
}
|
|
220
358
|
/**
|
|
221
|
-
* Authorize a tool call
|
|
222
|
-
*
|
|
223
|
-
*
|
|
359
|
+
* Authorize a tool call and return a normalized {@link GateResult} without
|
|
360
|
+
* recording execution activity. Permission-ask hooks use this when the harness
|
|
361
|
+
* separately reports actual execution; {@link gate} adds the standard events.
|
|
224
362
|
*/
|
|
225
|
-
async function
|
|
363
|
+
async function decideTool(client, opts) {
|
|
226
364
|
const namespace = resolveNamespace();
|
|
227
365
|
const subjectRef = (0, subject_js_1.resolveUserSubject)(client, opts.subjectFallback);
|
|
228
366
|
const label = (0, subject_js_1.subjectLabel)(subjectRef);
|
|
229
367
|
const canBlock = opts.canBlock ?? true;
|
|
230
|
-
const
|
|
231
|
-
const check = { namespace, object: opts.toolName, relation: "
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
368
|
+
const activityAttrs = gateActivityAttributes(opts);
|
|
369
|
+
const check = { namespace, object: opts.toolName, relation: "users", ...subjectRef };
|
|
370
|
+
if (!(0, config_js_1.isSecurityConnected)()) {
|
|
371
|
+
const outcome = {
|
|
372
|
+
kind: "not_connected",
|
|
373
|
+
activityAttributes: { securityConnected: false, toolName: opts.toolName },
|
|
374
|
+
};
|
|
375
|
+
return {
|
|
376
|
+
proceed: true,
|
|
377
|
+
blocked: false,
|
|
378
|
+
kind: outcome.kind,
|
|
379
|
+
decision: outcome,
|
|
380
|
+
subject: label,
|
|
381
|
+
namespace,
|
|
243
382
|
};
|
|
244
|
-
return { proceed: true, blocked: false, kind: "allow", decision, subject: label, namespace };
|
|
245
383
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
384
|
+
let outcome;
|
|
385
|
+
try {
|
|
386
|
+
if (opts.mcpTool) {
|
|
387
|
+
const mcpResult = await (0, mcp_js_1.checkMcpPermission)(client, opts.mcpTool, {
|
|
388
|
+
subject: subjectRef,
|
|
389
|
+
activityAttributes: activityAttrs,
|
|
390
|
+
});
|
|
391
|
+
const agentSubject = (0, subject_js_1.resolveAgentSubject)(client);
|
|
392
|
+
const subagentSubject = (0, subject_js_1.resolveSubAgentSubject)(opts.principals?.subAgentClientId);
|
|
393
|
+
const mode = await (0, permission_mode_js_1.warmPermissionModeCache)(client, subjectRef, {
|
|
394
|
+
additionalSubjects: [
|
|
395
|
+
...(agentSubject ? [{ scope: "agent", subject: agentSubject }] : []),
|
|
396
|
+
...(subagentSubject ? [{ scope: "subagent", subject: subagentSubject }] : []),
|
|
397
|
+
],
|
|
398
|
+
});
|
|
399
|
+
outcome = (0, permissions_js_1.applyPermissionMode)(client, mcpResult.allowed, {
|
|
400
|
+
namespace: process.env.ORY_MCP_SERVER_NAMESPACE ?? "mcp_servers",
|
|
401
|
+
object: opts.mcpTool.serverName,
|
|
402
|
+
relation: "users",
|
|
403
|
+
...subjectRef,
|
|
404
|
+
activityAttributes: {
|
|
405
|
+
...activityAttrs,
|
|
406
|
+
mcpServer: opts.mcpTool.serverName,
|
|
407
|
+
...(opts.mcpTool.toolName ? { mcpTool: opts.mcpTool.toolName } : {}),
|
|
408
|
+
},
|
|
409
|
+
modeOverride: mode.mode,
|
|
410
|
+
});
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
outcome = await (0, permissions_js_1.gateToolCall)(client, {
|
|
414
|
+
harness: opts.harness,
|
|
415
|
+
toolName: opts.toolName,
|
|
416
|
+
check,
|
|
417
|
+
activityAttributes: activityAttrs,
|
|
418
|
+
shellCommand: (0, tool_catalog_js_1.extractShellCommand)(opts.harness, opts.toolName, opts.toolArgs),
|
|
419
|
+
principals: opts.principals,
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
catch (err) {
|
|
424
|
+
const error = client.classifyError(err);
|
|
425
|
+
client.logger.warn("permission.fail_open", { tool: opts.toolName, ...describeThrown(error) });
|
|
426
|
+
const outcome = {
|
|
427
|
+
kind: "fail_open",
|
|
428
|
+
error,
|
|
429
|
+
mode: (0, config_js_1.resolveConfig)().permissionMode,
|
|
430
|
+
activityAttributes: { permissionMode: (0, config_js_1.resolveConfig)().permissionMode },
|
|
431
|
+
};
|
|
432
|
+
return {
|
|
433
|
+
proceed: true,
|
|
434
|
+
blocked: false,
|
|
435
|
+
kind: outcome.kind,
|
|
436
|
+
decision: outcome,
|
|
437
|
+
subject: label,
|
|
438
|
+
namespace,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
if (outcome.kind === "not_connected") {
|
|
442
|
+
return {
|
|
443
|
+
proceed: true,
|
|
444
|
+
blocked: false,
|
|
445
|
+
kind: "not_connected",
|
|
446
|
+
decision: outcome,
|
|
447
|
+
subject: label,
|
|
448
|
+
namespace,
|
|
449
|
+
};
|
|
258
450
|
}
|
|
259
451
|
if (outcome.kind === "interactive") {
|
|
260
452
|
return { proceed: true, blocked: false, kind: "interactive", decision: outcome, subject: label, namespace };
|
|
261
453
|
}
|
|
262
|
-
const decisionAttrs = outcome.
|
|
454
|
+
const decisionAttrs = outcome.activityAttributes;
|
|
263
455
|
if (outcome.kind === "fail_open") {
|
|
264
456
|
client.logger.warn("permission.fail_open", { tool: opts.toolName, code: outcome.error.code });
|
|
265
457
|
return { proceed: true, blocked: false, kind: "fail_open", decision: outcome, subject: label, namespace };
|
|
266
458
|
}
|
|
267
459
|
if (outcome.kind === "allow") {
|
|
268
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
269
|
-
attributes: { ...spanAttrs, ...decisionAttrs, allowed: true },
|
|
270
|
-
});
|
|
271
460
|
return { proceed: true, blocked: false, kind: "allow", decision: outcome, subject: label, namespace };
|
|
272
461
|
}
|
|
273
462
|
if (outcome.kind === "observe") {
|
|
274
|
-
client.tracer.record("tool.block", "denied", {
|
|
275
|
-
attributes: { ...spanAttrs, ...decisionAttrs, allowed: false, ...(0, denial_js_1.alertAttributes)(false) },
|
|
276
|
-
});
|
|
277
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
278
|
-
attributes: { ...spanAttrs, ...decisionAttrs, allowed: false, observed: true },
|
|
279
|
-
});
|
|
280
463
|
return { proceed: true, blocked: false, kind: "observe", decision: outcome, subject: label, namespace };
|
|
281
464
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
465
|
+
const denialContext = {
|
|
466
|
+
tool: opts.toolName,
|
|
467
|
+
subjectId: label,
|
|
468
|
+
namespace,
|
|
469
|
+
...(opts.mcpTool ? { mcp: opts.mcpTool } : {}),
|
|
470
|
+
...(0, denial_js_1.shellDenialFields)(outcome),
|
|
471
|
+
};
|
|
472
|
+
const denialMessage = (0, denial_js_1.formatDenialMessage)(denialContext);
|
|
287
473
|
client.logger.warn("tool.denied", { tool: opts.toolName, subjectId: label });
|
|
288
|
-
return {
|
|
474
|
+
return {
|
|
475
|
+
proceed: false,
|
|
476
|
+
blocked: canBlock,
|
|
477
|
+
kind: "deny",
|
|
478
|
+
decision: outcome,
|
|
479
|
+
subject: label,
|
|
480
|
+
namespace,
|
|
481
|
+
denialMessage,
|
|
482
|
+
denialContext,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
async function gate(client, opts) {
|
|
486
|
+
const result = await decideTool(client, opts);
|
|
487
|
+
const activityAttrs = gateActivityAttributes(opts);
|
|
488
|
+
const decisionAttrs = result.decision.activityAttributes;
|
|
489
|
+
if (result.kind === "not_connected") {
|
|
490
|
+
client.logger.activity("tool.invoke", "ok", {
|
|
491
|
+
attributes: { ...activityAttrs, securityConnected: false, decision: result.kind },
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
else if (result.kind === "allow") {
|
|
495
|
+
client.logger.activity("tool.invoke", "ok", {
|
|
496
|
+
attributes: { ...activityAttrs, ...decisionAttrs, allowed: true, decision: result.kind },
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
else if (result.kind === "observe") {
|
|
500
|
+
client.logger.activity("tool.block", "denied", {
|
|
501
|
+
attributes: {
|
|
502
|
+
...activityAttrs,
|
|
503
|
+
...decisionAttrs,
|
|
504
|
+
allowed: false,
|
|
505
|
+
observed: true,
|
|
506
|
+
decision: result.kind,
|
|
507
|
+
...(0, denial_js_1.alertAttributes)(false),
|
|
508
|
+
},
|
|
509
|
+
});
|
|
510
|
+
client.logger.activity("tool.invoke", "ok", {
|
|
511
|
+
attributes: {
|
|
512
|
+
...activityAttrs,
|
|
513
|
+
...decisionAttrs,
|
|
514
|
+
allowed: false,
|
|
515
|
+
observed: true,
|
|
516
|
+
decision: result.kind,
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
else if (result.kind === "deny") {
|
|
521
|
+
client.logger.activity("tool.block", "denied", {
|
|
522
|
+
attributes: {
|
|
523
|
+
...activityAttrs,
|
|
524
|
+
...decisionAttrs,
|
|
525
|
+
allowed: false,
|
|
526
|
+
decision: result.kind,
|
|
527
|
+
...(0, denial_js_1.alertAttributes)(opts.canBlock ?? true),
|
|
528
|
+
},
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
else if (result.kind === "interactive" || result.kind === "fail_open") {
|
|
532
|
+
client.logger.activity("tool.invoke", "ok", {
|
|
533
|
+
attributes: { ...activityAttrs, ...decisionAttrs, decision: result.kind },
|
|
534
|
+
});
|
|
535
|
+
}
|
|
536
|
+
return result;
|
|
289
537
|
}
|
|
290
538
|
function complete(client, opts) {
|
|
291
|
-
const attributes = {
|
|
539
|
+
const attributes = {
|
|
540
|
+
toolName: opts.toolName,
|
|
541
|
+
...(0, tool_metadata_js_1.summarizeToolInput)(opts.toolName, opts.input),
|
|
542
|
+
...(0, tool_metadata_js_1.summarizeToolOutput)(opts.toolName, opts.output),
|
|
543
|
+
...(opts.extraActivityAttributes ?? {}),
|
|
544
|
+
};
|
|
292
545
|
if (opts.output !== undefined)
|
|
293
546
|
attributes.hasOutput = true;
|
|
294
|
-
client.
|
|
547
|
+
client.logger.activity("tool.complete", opts.status ?? "ok", { attributes });
|
|
548
|
+
}
|
|
549
|
+
function fail(client, opts) {
|
|
550
|
+
if (opts.error !== undefined) {
|
|
551
|
+
client.logger.debug("tool.failure.detail", {
|
|
552
|
+
toolName: opts.toolName,
|
|
553
|
+
...describeThrown(opts.error),
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
client.logger.activity("tool.fail", "error", {
|
|
557
|
+
attributes: {
|
|
558
|
+
toolName: opts.toolName,
|
|
559
|
+
...(0, tool_metadata_js_1.summarizeToolInput)(opts.toolName, opts.input),
|
|
560
|
+
...(opts.error !== undefined ? { hasError: true } : {}),
|
|
561
|
+
...(opts.extraActivityAttributes ?? {}),
|
|
562
|
+
},
|
|
563
|
+
});
|
|
295
564
|
}
|
|
296
565
|
/**
|
|
297
566
|
* Wrap a plain `execute(args)` callable with gate + complete. On a hard deny it throws
|
|
@@ -307,10 +576,20 @@ function wrapTool(client, opts, execute) {
|
|
|
307
576
|
canBlock: opts.canBlock ?? true,
|
|
308
577
|
});
|
|
309
578
|
if (result.blocked) {
|
|
310
|
-
throw new denial_js_1.OryDenialError(
|
|
579
|
+
throw new denial_js_1.OryDenialError(result.denialContext ?? {
|
|
580
|
+
tool: opts.toolName,
|
|
581
|
+
subjectId: result.subject,
|
|
582
|
+
namespace: result.namespace,
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
try {
|
|
586
|
+
const output = await execute(args);
|
|
587
|
+
complete(client, { toolName: opts.toolName, input: args, output });
|
|
588
|
+
return output;
|
|
589
|
+
}
|
|
590
|
+
catch (error) {
|
|
591
|
+
fail(client, { toolName: opts.toolName, input: args, error });
|
|
592
|
+
throw error;
|
|
311
593
|
}
|
|
312
|
-
const output = await execute(args);
|
|
313
|
-
complete(client, { toolName: opts.toolName, output });
|
|
314
|
-
return output;
|
|
315
594
|
};
|
|
316
595
|
}
|