@ory/argus 0.13.9 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -45
- 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 +43 -97
- package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -18
- package/assets/skills/ory-temporal-worker/SKILL.md +38 -42
- package/assets/skills/permissions-onboarding/SKILL.md +131 -104
- package/dist/adapters.d.ts +93 -30
- package/dist/adapters.js +464 -136
- package/dist/agent-auth.d.ts +258 -68
- package/dist/agent-auth.js +998 -202
- package/dist/auth-store.d.ts +37 -2
- package/dist/auth-store.js +37 -3
- package/dist/auth.d.ts +40 -4
- package/dist/auth.js +247 -19
- package/dist/bash-parser.d.ts +98 -0
- package/dist/bash-parser.js +396 -0
- package/dist/branding.d.ts +128 -0
- package/dist/branding.js +151 -0
- 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 -278
- package/dist/client.d.ts +175 -138
- package/dist/client.js +672 -391
- package/dist/config.d.ts +249 -57
- package/dist/config.js +486 -62
- package/dist/context.d.ts +10 -0
- package/dist/context.js +21 -0
- package/dist/contract-suite.d.ts +8 -8
- package/dist/contract-suite.js +88 -69
- 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 +31 -19
- package/dist/index.js +182 -31
- 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.d.ts +14 -0
- package/dist/local/health.js +50 -4
- 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 +160 -39
- package/dist/local/ports.d.ts +158 -0
- package/dist/local/ports.js +443 -0
- package/dist/local/seed.d.ts +22 -25
- package/dist/local/seed.js +88 -56
- 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 +83 -55
- 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 -28
- package/dist/setup.js +57 -84
- package/dist/status-cli.d.ts +29 -13
- package/dist/status-cli.js +124 -144
- package/dist/status-data.d.ts +195 -0
- package/dist/status-data.js +333 -0
- package/dist/status-system.d.ts +24 -0
- package/dist/status-system.js +56 -0
- 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 +74 -19
- package/dist/uninstall.js +224 -49
- package/dist/user-login.d.ts +22 -16
- package/dist/user-login.js +67 -96
- package/dist/watch-cli.d.ts +6 -0
- package/dist/watch-cli.js +217 -0
- package/package.json +3 -11
- package/dist/dev.d.ts +0 -103
- package/dist/dev.js +0 -584
- package/dist/interactive-setup.d.ts +0 -165
- package/dist/interactive-setup.js +0 -1546
- 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/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/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,41 +23,135 @@
|
|
|
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");
|
|
38
61
|
}
|
|
39
62
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
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
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Run both auth gates and write the user→agent delegation tuple. The user gate
|
|
98
|
+
* runs every session to establish/refresh the user identity, records the
|
|
99
|
+
* `user.auth` activity event, and never blocks — enforcement is governed by
|
|
100
|
+
* `permissionMode` at tool-call time. `SessionStartResult.proceed` is therefore
|
|
101
|
+
* always `true`; it is retained only for a stable adapter shape.
|
|
43
102
|
*/
|
|
44
103
|
async function sessionStart(client, opts) {
|
|
45
104
|
const projectUrl = opts.projectUrl ?? (0, config_js_1.resolveConfig)().projectUrl;
|
|
46
105
|
const userLogin = opts.userLogin ?? user_login_js_1.ensureUserAuthenticated;
|
|
47
106
|
const agentGate = opts.agentGate ?? agent_auth_js_1.ensureAgentIdentity;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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,
|
|
56
154
|
});
|
|
57
|
-
await writeUserDelegatesAgent(client);
|
|
58
155
|
return {
|
|
59
156
|
proceed: decision.proceed,
|
|
60
157
|
userMode: decision.mode,
|
|
@@ -62,62 +159,98 @@ async function sessionStart(client, opts) {
|
|
|
62
159
|
agentKind: creds.kind,
|
|
63
160
|
};
|
|
64
161
|
}
|
|
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));
|
|
174
|
+
});
|
|
175
|
+
pending.set(key, started);
|
|
176
|
+
return started;
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
/** Strip a `<ns>:` prefix from a namespaced subject, if present. */
|
|
180
|
+
function unqualify(subject, ns) {
|
|
181
|
+
const prefix = `${ns}:`;
|
|
182
|
+
return subject.startsWith(prefix) ? subject.slice(prefix.length) : undefined;
|
|
183
|
+
}
|
|
65
184
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
* tuples are audit-only, so a missed write is harmless and retried next
|
|
73
|
-
* session). Best-effort: any failure is logged and swallowed.
|
|
185
|
+
* Resolve one end of a delegation edge, preferring the live principal but
|
|
186
|
+
* falling back to the persisted delegation anchor (`delegation.delegatedBy`
|
|
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
|
|
189
|
+
* principal isn't populated for the session. A mismatch between the two is
|
|
190
|
+
* logged for reconciliation visibility; the live principal wins.
|
|
74
191
|
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
spanAttributes: options.spanAttributes,
|
|
79
|
-
});
|
|
80
|
-
if (res.probeError) {
|
|
81
|
-
client.logger.debug("delegation.probe_failed", {
|
|
82
|
-
...options.failedContext,
|
|
83
|
-
message: res.probeError.message,
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
catch (err) {
|
|
88
|
-
client.logger.warn(options.failedEvent, {
|
|
89
|
-
...options.failedContext,
|
|
90
|
-
message: err instanceof Error ? err.message : String(err),
|
|
91
|
-
});
|
|
192
|
+
function reconcileEdgeEnd(client, principal, anchor, role) {
|
|
193
|
+
if (principal && anchor && principal !== anchor) {
|
|
194
|
+
client.logger.debug("delegation.anchor_mismatch", { role, principal, anchor });
|
|
92
195
|
}
|
|
196
|
+
return principal ?? anchor;
|
|
93
197
|
}
|
|
94
198
|
/**
|
|
95
|
-
*
|
|
96
|
-
* (see {@link
|
|
97
|
-
* their own session-start sequence (rather than {@link sessionStart})
|
|
98
|
-
* the
|
|
99
|
-
*
|
|
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.
|
|
203
|
+
*
|
|
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.
|
|
100
213
|
*/
|
|
101
|
-
async function writeUserDelegatesAgent(client) {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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);
|
|
226
|
+
const anchor = anchorCreds?.delegation;
|
|
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) {
|
|
105
230
|
client.logger.debug("delegation.skip", {
|
|
106
|
-
reason: "
|
|
107
|
-
hasUser: !!user,
|
|
108
|
-
hasAgent: !!agent,
|
|
231
|
+
reason: "no user delegation edge (no principal and no anchor)",
|
|
109
232
|
});
|
|
110
233
|
return;
|
|
111
234
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
+
}
|
|
121
254
|
}
|
|
122
255
|
async function registerSubagent(client, opts) {
|
|
123
256
|
const projectUrl = opts.projectUrl ?? (0, config_js_1.resolveConfig)().projectUrl;
|
|
@@ -128,121 +261,306 @@ async function registerSubagent(client, opts) {
|
|
|
128
261
|
subAgentType: opts.subAgentType,
|
|
129
262
|
projectUrl,
|
|
130
263
|
harness: opts.harness,
|
|
264
|
+
sessionId: opts.sessionId ?? client.sessionId,
|
|
265
|
+
perSpawnId: opts.perSpawnId,
|
|
266
|
+
signal: opts.signal,
|
|
131
267
|
});
|
|
132
268
|
}
|
|
133
269
|
catch (err) {
|
|
134
270
|
client.logger.warn("subagent.identity.failed", {
|
|
135
271
|
subAgentType: opts.subAgentType,
|
|
136
|
-
|
|
272
|
+
...describeThrown(err),
|
|
137
273
|
});
|
|
138
274
|
return;
|
|
139
275
|
}
|
|
140
|
-
if (identity.kind !== "
|
|
276
|
+
if (identity.kind !== "runtime" && identity.kind !== "dynamic")
|
|
141
277
|
return;
|
|
142
|
-
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
|
+
});
|
|
143
286
|
}
|
|
144
287
|
/**
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
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).
|
|
150
300
|
*/
|
|
151
|
-
async function writeAgentDelegatesSubagent(client, subAgentSubject, subAgentType) {
|
|
152
|
-
|
|
153
|
-
|
|
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) {
|
|
154
312
|
client.logger.debug("delegation.skip", {
|
|
155
|
-
reason: "agent
|
|
313
|
+
reason: "no persisted agent delegation node (user→agent edge not recorded yet)",
|
|
156
314
|
subAgentType,
|
|
315
|
+
sessionKey,
|
|
157
316
|
});
|
|
158
317
|
return;
|
|
159
318
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
+
};
|
|
170
357
|
}
|
|
171
358
|
/**
|
|
172
|
-
* Authorize a tool call
|
|
173
|
-
*
|
|
174
|
-
*
|
|
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.
|
|
175
362
|
*/
|
|
176
|
-
async function
|
|
363
|
+
async function decideTool(client, opts) {
|
|
177
364
|
const namespace = resolveNamespace();
|
|
178
365
|
const subjectRef = (0, subject_js_1.resolveUserSubject)(client, opts.subjectFallback);
|
|
179
366
|
const label = (0, subject_js_1.subjectLabel)(subjectRef);
|
|
180
367
|
const canBlock = opts.canBlock ?? true;
|
|
181
|
-
const
|
|
182
|
-
const check = { namespace, object: opts.toolName, relation: "
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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,
|
|
194
382
|
};
|
|
195
|
-
return { proceed: true, blocked: false, kind: "allow", decision, subject: label, namespace };
|
|
196
383
|
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
+
};
|
|
209
450
|
}
|
|
210
451
|
if (outcome.kind === "interactive") {
|
|
211
452
|
return { proceed: true, blocked: false, kind: "interactive", decision: outcome, subject: label, namespace };
|
|
212
453
|
}
|
|
213
|
-
const decisionAttrs = outcome.
|
|
454
|
+
const decisionAttrs = outcome.activityAttributes;
|
|
214
455
|
if (outcome.kind === "fail_open") {
|
|
215
456
|
client.logger.warn("permission.fail_open", { tool: opts.toolName, code: outcome.error.code });
|
|
216
457
|
return { proceed: true, blocked: false, kind: "fail_open", decision: outcome, subject: label, namespace };
|
|
217
458
|
}
|
|
218
459
|
if (outcome.kind === "allow") {
|
|
219
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
220
|
-
attributes: { ...spanAttrs, ...decisionAttrs, allowed: true },
|
|
221
|
-
});
|
|
222
460
|
return { proceed: true, blocked: false, kind: "allow", decision: outcome, subject: label, namespace };
|
|
223
461
|
}
|
|
224
462
|
if (outcome.kind === "observe") {
|
|
225
|
-
client.tracer.record("tool.block", "denied", {
|
|
226
|
-
attributes: { ...spanAttrs, ...decisionAttrs, allowed: false, ...(0, denial_js_1.alertAttributes)(false) },
|
|
227
|
-
});
|
|
228
|
-
client.tracer.record("tool.invoke", "ok", {
|
|
229
|
-
attributes: { ...spanAttrs, ...decisionAttrs, allowed: false, observed: true },
|
|
230
|
-
});
|
|
231
463
|
return { proceed: true, blocked: false, kind: "observe", decision: outcome, subject: label, namespace };
|
|
232
464
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
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);
|
|
238
473
|
client.logger.warn("tool.denied", { tool: opts.toolName, subjectId: label });
|
|
239
|
-
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;
|
|
240
537
|
}
|
|
241
538
|
function complete(client, opts) {
|
|
242
|
-
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
|
+
};
|
|
243
545
|
if (opts.output !== undefined)
|
|
244
546
|
attributes.hasOutput = true;
|
|
245
|
-
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
|
+
});
|
|
246
564
|
}
|
|
247
565
|
/**
|
|
248
566
|
* Wrap a plain `execute(args)` callable with gate + complete. On a hard deny it throws
|
|
@@ -258,10 +576,20 @@ function wrapTool(client, opts, execute) {
|
|
|
258
576
|
canBlock: opts.canBlock ?? true,
|
|
259
577
|
});
|
|
260
578
|
if (result.blocked) {
|
|
261
|
-
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;
|
|
262
593
|
}
|
|
263
|
-
const output = await execute(args);
|
|
264
|
-
complete(client, { toolName: opts.toolName, output });
|
|
265
|
-
return output;
|
|
266
594
|
};
|
|
267
595
|
}
|