@ory/argus 1.2.4 → 1.3.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.
Files changed (49) hide show
  1. package/README.md +25 -0
  2. package/assets/skills/ory-build-agent/SKILL.md +1 -1
  3. package/assets/skills/ory-deploy-agent/SKILL.md +315 -0
  4. package/assets/skills/ory-deploy-openai-e2b/SKILL.md +289 -0
  5. package/assets/skills/ory-e2b-sandbox/SKILL.md +10 -6
  6. package/assets/skills/ory-temporal-worker/SKILL.md +4 -4
  7. package/dist/adapters.d.ts +11 -3
  8. package/dist/adapters.js +55 -13
  9. package/dist/agent-auth.d.ts +18 -7
  10. package/dist/agent-auth.js +120 -10
  11. package/dist/auth-store.d.ts +3 -3
  12. package/dist/auth-store.js +56 -12
  13. package/dist/auth.d.ts +1 -1
  14. package/dist/auth.js +1 -1
  15. package/dist/build-info.json +4 -4
  16. package/dist/cli.js +5 -0
  17. package/dist/client.d.ts +15 -0
  18. package/dist/client.js +24 -7
  19. package/dist/config.d.ts +23 -3
  20. package/dist/config.js +202 -18
  21. package/dist/contract-suite.d.ts +0 -8
  22. package/dist/contract-suite.js +0 -1
  23. package/dist/event-reporter.d.ts +1 -0
  24. package/dist/event-reporter.js +19 -2
  25. package/dist/hook-timeout.d.ts +2 -2
  26. package/dist/hook-timeout.js +2 -2
  27. package/dist/index.d.ts +4 -4
  28. package/dist/index.js +13 -6
  29. package/dist/lifecycle.js +2 -1
  30. package/dist/local/configs.d.ts +2 -4
  31. package/dist/local/configs.js +2 -4
  32. package/dist/local/manager.js +4 -4
  33. package/dist/local/seed.d.ts +4 -4
  34. package/dist/local/seed.js +8 -10
  35. package/dist/runtime-credential.d.ts +35 -0
  36. package/dist/runtime-credential.js +252 -1
  37. package/dist/runtime.d.ts +19 -12
  38. package/dist/runtime.js +69 -21
  39. package/dist/setup.js +3 -3
  40. package/dist/skills.d.ts +3 -5
  41. package/dist/skills.js +14 -5
  42. package/dist/subject.d.ts +2 -3
  43. package/dist/subject.js +10 -11
  44. package/dist/testing.d.ts +17 -0
  45. package/dist/testing.js +53 -0
  46. package/dist/tool-catalog.d.ts +12 -12
  47. package/dist/tool-catalog.js +206 -50
  48. package/dist/user-login.js +1 -1
  49. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: ory-temporal-worker
3
- description: Scaffold a [Temporal](https://temporal.io) TypeScript worker where every Activity execution is gated by Ory — the user is authenticated, the worker's agent identity is resolved via DCR, each Activity invocation runs an Ory Permission check, and the full lifecycle emits structured activity events. Use when the user asks to "add Ory to my Temporal worker", "wire Ory permissions into Temporal activities", "create a Temporal worker with Ory auth", "build a Temporal TypeScript project with the Ory agent client", or any close variant. The skill scaffolds the project in the user's repo following <https://docs.temporal.io/develop/typescript/set-up-your-local-typescript> — it does not run the worker.
3
+ description: Scaffold a [Temporal](https://temporal.io) TypeScript worker where every Activity execution is gated by Ory — the user is authenticated, the worker resolves an injected or Talos runtime identity, each Activity invocation runs an Ory Permission check, and the full lifecycle emits structured activity events. Use when the user asks to "add Ory to my Temporal worker", "wire Ory permissions into Temporal activities", "create a Temporal worker with Ory auth", "build a Temporal TypeScript project with the Ory agent client", or any close variant. The skill scaffolds the project in the user's repo following <https://docs.temporal.io/develop/typescript/set-up-your-local-typescript> — it does not run the worker.
4
4
  ---
5
5
 
6
6
  # Ory-authed Temporal TypeScript worker
@@ -262,9 +262,9 @@ Temporal Web UI.
262
262
 
263
263
  This skill stops at the local dev server. For production:
264
264
 
265
- - Pin a static agent identity with `ORY_AGENT_CLIENT_ID +
266
- ORY_AGENT_CLIENT_SECRET` (client_credentials) so the worker doesn't
267
- re-register on every cold start.
265
+ - Inject a runtime request authenticator from the workload platform, or provide
266
+ `ORY_AGENT_API_KEY` through the deployment secret manager. Do not bake a
267
+ runtime credential into the worker image.
268
268
  - Set retention and access controls for the worker's unified activity log.
269
269
  Ory Agent Security owns service telemetry; the worker does not export traces.
270
270
  - Use Temporal Cloud or a self-hosted Temporal cluster instead of
@@ -21,7 +21,7 @@
21
21
  * Every primitive is best-effort and fail-open.
22
22
  */
23
23
  import type { OryAgentClient } from "./client.js";
24
- import { ensureAgentIdentity, ensureSubAgentIdentity } from "./agent-auth.js";
24
+ import { ensureAgentIdentity, ensureSubAgentIdentity, type SubAgentIdentity } from "./agent-auth.js";
25
25
  import { ensureUserAuthenticated } from "./user-login.js";
26
26
  import { type ModeDecision, type PermissionDecision, type PrincipalBlockSubjects } from "./permissions.js";
27
27
  import { type DenialContext } from "./denial.js";
@@ -42,12 +42,18 @@ export interface SessionStartResult {
42
42
  }
43
43
  export interface SessionStartOptions {
44
44
  harness: string;
45
+ /** Root agent type supplied by the adapter/application. Defaults to `harness`. */
46
+ agentType?: string;
45
47
  sessionId?: string;
46
48
  projectUrl?: string;
47
49
  binName?: string;
50
+ /** Environment override used by headless deployment packages and tests. */
51
+ env?: NodeJS.ProcessEnv;
48
52
  /** Injectable gates (tests). */
49
53
  userLogin?: typeof ensureUserAuthenticated;
50
54
  agentGate?: typeof ensureAgentIdentity;
55
+ /** Injectable runtime-context transport (tests and custom runtimes). */
56
+ runtimeContextFetch?: typeof fetch;
51
57
  /** Small, privacy-safe session metadata supported by the activity API. */
52
58
  activityAttributes?: Record<string, unknown>;
53
59
  }
@@ -77,7 +83,7 @@ export declare function createSessionStarter(client: OryAgentClient, opts: Sessi
77
83
  * logged and swallowed. No-op when Agent Security isn't connected (there is no
78
84
  * broker to record against) and when no user identity resolves.
79
85
  */
80
- export declare function writeUserDelegatesAgent(client: OryAgentClient, harness?: string, sessionId?: string): Promise<void>;
86
+ export declare function writeUserDelegatesAgent(client: OryAgentClient, harness?: string, sessionId?: string, env?: NodeJS.ProcessEnv): Promise<void>;
81
87
  export interface RegisterSubagentOptions {
82
88
  harness: string;
83
89
  subAgentType: string;
@@ -92,8 +98,9 @@ export interface RegisterSubagentOptions {
92
98
  perSpawnId?: string;
93
99
  sessionId?: string;
94
100
  signal?: AbortSignal;
101
+ env?: NodeJS.ProcessEnv;
95
102
  }
96
- export declare function registerSubagent(client: OryAgentClient, opts: RegisterSubagentOptions): Promise<void>;
103
+ export declare function registerSubagent(client: OryAgentClient, opts: RegisterSubagentOptions): Promise<SubAgentIdentity | undefined>;
97
104
  /**
98
105
  * Record the `agent → subagent` delegation edge through the agent-security
99
106
  * broker. Exported so harness plugins that resolve the sub-agent identity
@@ -115,6 +122,7 @@ export declare function writeAgentDelegatesSubagent(client: OryAgentClient, subA
115
122
  agentToken?: string;
116
123
  runtimeCredential?: RuntimeCredential;
117
124
  signal?: AbortSignal;
125
+ env?: NodeJS.ProcessEnv;
118
126
  }): Promise<void>;
119
127
  export interface GateResult {
120
128
  /** False only when the tool was hard-denied in enforce mode. */
package/dist/adapters.js CHANGED
@@ -104,19 +104,51 @@ async function sessionStart(client, opts) {
104
104
  const projectUrl = opts.projectUrl ?? (0, config_js_1.resolveConfig)().projectUrl;
105
105
  const userLogin = opts.userLogin ?? user_login_js_1.ensureUserAuthenticated;
106
106
  const agentGate = opts.agentGate ?? agent_auth_js_1.ensureAgentIdentity;
107
+ const env = opts.env ?? process.env;
108
+ const packageWarnings = [];
109
+ let rootDelegation = (0, agent_auth_js_1.resolvePreprovisionedRootDelegation)(env, packageWarnings, client.runtimeCredential);
110
+ for (const message of packageWarnings) {
111
+ client.logger.warn("agent.package.invalid", { message });
112
+ }
113
+ if (!rootDelegation) {
114
+ try {
115
+ const context = await (0, agent_auth_js_1.resolvePreprovisionedRuntimeContext)(client, {
116
+ projectUrl,
117
+ expectedAgentType: opts.agentType ?? opts.harness,
118
+ env,
119
+ fetchImpl: opts.runtimeContextFetch,
120
+ });
121
+ rootDelegation = context?.rootDelegation;
122
+ }
123
+ catch (err) {
124
+ client.logger.warn("agent.runtime_context.failed", describeThrown(err));
125
+ }
126
+ }
107
127
  let decision = {
108
128
  proceed: true,
109
129
  mode: "skipped",
110
130
  reason: "User authentication failed open",
111
131
  };
112
- try {
113
- decision = await userLogin(client, {
114
- binName: opts.binName ?? `ory-${opts.harness}`,
115
- harness: opts.harness,
132
+ if (rootDelegation) {
133
+ decision = {
134
+ proceed: true,
135
+ mode: "skipped",
136
+ reason: "Administrator-preprovisioned agent package does not require user login",
137
+ };
138
+ client.logger.activity("user.auth", "skipped", {
139
+ attributes: { mode: decision.mode, reason: decision.reason, proceed: true },
116
140
  });
117
141
  }
118
- catch (err) {
119
- client.logger.warn("session_start.user_auth.failed", describeThrown(err));
142
+ else {
143
+ try {
144
+ decision = await userLogin(client, {
145
+ binName: opts.binName ?? `ory-${opts.harness}`,
146
+ harness: opts.harness,
147
+ });
148
+ }
149
+ catch (err) {
150
+ client.logger.warn("session_start.user_auth.failed", describeThrown(err));
151
+ }
120
152
  }
121
153
  let creds = {
122
154
  kind: "none",
@@ -128,12 +160,13 @@ async function sessionStart(client, opts) {
128
160
  projectUrl,
129
161
  harness: opts.harness,
130
162
  sessionId: opts.sessionId ?? client.sessionId,
163
+ env,
131
164
  });
132
165
  }
133
166
  catch (err) {
134
167
  client.logger.warn("session_start.agent_auth.failed", describeThrown(err));
135
168
  }
136
- await writeUserDelegatesAgent(client, opts.harness, opts.sessionId);
169
+ await writeUserDelegatesAgent(client, opts.harness, opts.sessionId, env);
137
170
  // Warm the permission-mode cache from the server once per session, so the
138
171
  // synchronous `resolveConfig().permissionMode` read (the MCP
139
172
  // `applyPermissionMode` path, display surfaces) reflects the current project
@@ -183,8 +216,7 @@ function unqualify(subject, ns) {
183
216
  }
184
217
  /**
185
218
  * 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
219
+ * falling back to the persisted non-secret delegation anchor. This lets the edge be
188
220
  * recorded from an authoritative source rather than from nothing when the
189
221
  * principal isn't populated for the session. A mismatch between the two is
190
222
  * logged for reconciliation visibility; the live principal wins.
@@ -211,9 +243,12 @@ function reconcileEdgeEnd(client, principal, anchor, role) {
211
243
  * logged and swallowed. No-op when Agent Security isn't connected (there is no
212
244
  * broker to record against) and when no user identity resolves.
213
245
  */
214
- async function writeUserDelegatesAgent(client, harness, sessionId) {
246
+ async function writeUserDelegatesAgent(client, harness, sessionId, env = process.env) {
215
247
  if (!(0, config_js_1.isSecurityConnected)())
216
248
  return;
249
+ if (client.runtimeRootDelegation ||
250
+ (0, agent_auth_js_1.resolvePreprovisionedRootDelegation)(env, undefined, client.runtimeCredential))
251
+ return;
217
252
  // Resolve the delegating user (the anchor of the edge). Prefer the live
218
253
  // principal; fall back to the delegator persisted in the agent client's
219
254
  // delegation anchor so the edge can still be recorded in a session where the
@@ -264,6 +299,7 @@ async function registerSubagent(client, opts) {
264
299
  sessionId: opts.sessionId ?? client.sessionId,
265
300
  perSpawnId: opts.perSpawnId,
266
301
  signal: opts.signal,
302
+ env: opts.env,
267
303
  });
268
304
  }
269
305
  catch (err) {
@@ -271,10 +307,12 @@ async function registerSubagent(client, opts) {
271
307
  subAgentType: opts.subAgentType,
272
308
  ...describeThrown(err),
273
309
  });
274
- return;
310
+ return undefined;
275
311
  }
276
312
  if (identity.kind !== "runtime" && identity.kind !== "dynamic")
277
- return;
313
+ return identity;
314
+ if (identity.runtimeCredential)
315
+ client.rememberRuntimeCredential(identity.runtimeCredential);
278
316
  await writeAgentDelegatesSubagent(client, identity.subject ?? "", opts.subAgentType, {
279
317
  harness: opts.harness,
280
318
  sessionId: opts.sessionId,
@@ -282,7 +320,9 @@ async function registerSubagent(client, opts) {
282
320
  signal: opts.signal,
283
321
  runtimeCredential: identity.runtimeCredential,
284
322
  agentToken: identity.token,
323
+ env: opts.env,
285
324
  });
325
+ return identity;
286
326
  }
287
327
  /**
288
328
  * Record the `agent → subagent` delegation edge through the agent-security
@@ -307,7 +347,8 @@ async function writeAgentDelegatesSubagent(client, subAgentSubject, subAgentType
307
347
  // session*: the parent of this sub-agent is this run's session credential, so
308
348
  // the harness alone would not identify it.
309
349
  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);
350
+ const preprovisionedRoot = client.runtimeRootDelegation ?? (0, agent_auth_js_1.resolvePreprovisionedRootDelegation)(opts.env, undefined, client.runtimeCredential);
351
+ const delegatedBy = client.runtimeRootDelegation ?? (0, agent_auth_js_1.loadDelegationNodeId)(opts.harness ?? client.harness, sessionKey, opts.env, client.runtimeCredential);
311
352
  if (!delegatedBy) {
312
353
  client.logger.debug("delegation.skip", {
313
354
  reason: "no persisted agent delegation node (user→agent edge not recorded yet)",
@@ -327,6 +368,7 @@ async function writeAgentDelegatesSubagent(client, subAgentSubject, subAgentType
327
368
  signal: opts.signal,
328
369
  agentToken: opts.agentToken,
329
370
  runtimeCredential: opts.runtimeCredential,
371
+ preprovisionedRoot: preprovisionedRoot !== undefined,
330
372
  activityAttributes: {
331
373
  delegation: "agent-to-subagent",
332
374
  subAgentType,
@@ -5,15 +5,26 @@
5
5
  * user. Unlike the user, the agent never authenticates interactively.
6
6
  * The normal path resolves a noninteractive runtime credential: an injected
7
7
  * request authenticator, `ORY_AGENT_API_KEY`, or a Talos key enrolled with the
8
- * already-authenticated user. Enrolled keys remain process-local.
8
+ * already-authenticated user and persisted in the native OS credential store.
9
9
  *
10
10
  * Legacy DCR and client-credentials helpers remain exported temporarily so the
11
11
  * monorepo and existing consumers compile, but the normal gates do not select
12
12
  * them.
13
13
  */
14
14
  import { type OryAgentDynamicCredentials, type OryDelegationRecord } from "./config.js";
15
- import { OryAgentClient } from "./client.js";
15
+ import { OryAgentClient, type RuntimeContext } from "./client.js";
16
16
  import { enrollTalosChildRuntimeCredential, enrollTalosRuntimeCredential, type RuntimeCredential, type RuntimeRequestAuthenticator, type TalosCredentialStore } from "./runtime-credential.js";
17
+ /** Resolve the complete administrator-provisioned headless package, if present. */
18
+ export declare function resolvePreprovisionedRootDelegation(env?: NodeJS.ProcessEnv, warnings?: string[], runtimeCredential?: RuntimeCredential): string | undefined;
19
+ export interface ResolvePreprovisionedRuntimeContextOptions {
20
+ projectUrl?: string;
21
+ expectedAgentType: string;
22
+ env?: NodeJS.ProcessEnv;
23
+ fetchImpl?: typeof fetch;
24
+ signal?: AbortSignal;
25
+ }
26
+ /** Resolve missing headless deployment metadata using the existing runtime proof. */
27
+ export declare function resolvePreprovisionedRuntimeContext(client: OryAgentClient, options: ResolvePreprovisionedRuntimeContextOptions): Promise<RuntimeContext | undefined>;
17
28
  /**
18
29
  * Delegation context handed to {@link registerAgentClient} so the freshly
19
30
  * minted OAuth2 client is stamped with its delegator at the identity layer.
@@ -174,7 +185,7 @@ export declare function countAgentSessions(harness: string): number;
174
185
  * sessions but the node is not, so a sub-agent must hang off *its own* run's
175
186
  * agent node.
176
187
  */
177
- export declare function loadDelegationNodeId(harness: string, sessionKey: string): string | undefined;
188
+ export declare function loadDelegationNodeId(harness: string, sessionKey: string, env?: NodeJS.ProcessEnv, runtimeCredential?: RuntimeCredential): string | undefined;
178
189
  /**
179
190
  * Record the broker-assigned delegation node for `(harness, session)`, trimming
180
191
  * the map to the most recent {@link DELEGATION_NODE_HISTORY} entries.
@@ -317,7 +328,7 @@ export interface EnsureAgentIdentityOptions {
317
328
  /** Retained for source compatibility and activity attribution. */
318
329
  harness?: string;
319
330
  /**
320
- * Session this identity acts in. Selects the persisted DCR slot and defaults
331
+ * Session this identity acts in. Selects the OS-store descriptor and defaults
321
332
  * to the client's ambient session.
322
333
  */
323
334
  sessionId?: string;
@@ -417,9 +428,9 @@ export interface EnsureSubAgentIdentityOptions {
417
428
  allowEnrollment?: boolean;
418
429
  }
419
430
  /**
420
- * Resolve a sub-agent against the process runtime credential. The broker's
421
- * `type_name` supplies sub-agent attribution; no separate OAuth identity is
422
- * registered, loaded, minted, or persisted.
431
+ * Resolve a sub-agent to a child Talos credential. Stored credentials are
432
+ * loaded by spawn descriptor; missing credentials may be enrolled through the
433
+ * authenticated parent runtime.
423
434
  */
424
435
  export declare function ensureSubAgentIdentity(client: OryAgentClient, options: EnsureSubAgentIdentityOptions): Promise<SubAgentIdentity>;
425
436
  export {};
@@ -6,7 +6,7 @@
6
6
  * user. Unlike the user, the agent never authenticates interactively.
7
7
  * The normal path resolves a noninteractive runtime credential: an injected
8
8
  * request authenticator, `ORY_AGENT_API_KEY`, or a Talos key enrolled with the
9
- * already-authenticated user. Enrolled keys remain process-local.
9
+ * already-authenticated user and persisted in the native OS credential store.
10
10
  *
11
11
  * Legacy DCR and client-credentials helpers remain exported temporarily so the
12
12
  * monorepo and existing consumers compile, but the normal gates do not select
@@ -14,6 +14,8 @@
14
14
  */
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
16
  exports.DEFAULT_SESSION_RETENTION = exports.AGENT_TOKEN_EXPIRY_SKEW_SEC = void 0;
17
+ exports.resolvePreprovisionedRootDelegation = resolvePreprovisionedRootDelegation;
18
+ exports.resolvePreprovisionedRuntimeContext = resolvePreprovisionedRuntimeContext;
17
19
  exports.resolveRuntimeAgentCredentials = resolveRuntimeAgentCredentials;
18
20
  exports._resetAgentCredentialsCache = _resetAgentCredentialsCache;
19
21
  exports.invalidateAgentAccessToken = invalidateAgentAccessToken;
@@ -55,6 +57,102 @@ const subject_js_1 = require("./subject.js");
55
57
  const runtime_credential_js_1 = require("./runtime-credential.js");
56
58
  const ENROLLMENT_LOCK_STALE_MS = 120_000;
57
59
  const ENROLLMENT_LOCK_WAIT_MS = 35_000;
60
+ const validatedRuntimeSubjects = new WeakSet();
61
+ /** Resolve the complete administrator-provisioned headless package, if present. */
62
+ function resolvePreprovisionedRootDelegation(env = process.env, warnings, runtimeCredential) {
63
+ const root = (0, config_js_1.readAgentRootDelegation)(env, warnings);
64
+ if (!root)
65
+ return undefined;
66
+ if (!env.ORY_AGENT_API_KEY?.trim() && runtimeCredential?.kind !== "injected") {
67
+ warnings?.push("ORY_AGENT_ROOT_DELEGATION requires ORY_AGENT_API_KEY or an injected runtime authenticator; ignoring it.");
68
+ return undefined;
69
+ }
70
+ if (!(0, subject_js_1.readAgentSubjectOverride)(env, warnings)) {
71
+ warnings?.push("ORY_AGENT_ROOT_DELEGATION requires a valid ORY_AGENT_SUBJECT_ID; ignoring it.");
72
+ return undefined;
73
+ }
74
+ return root;
75
+ }
76
+ const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
77
+ const RUNTIME_CONTEXT_KEYS = [
78
+ "agent_type",
79
+ "authenticated",
80
+ "deployment_id",
81
+ "root_delegation",
82
+ "subject_id",
83
+ ];
84
+ function parseRuntimeContext(value) {
85
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
86
+ throw new Error("Runtime context response is malformed");
87
+ }
88
+ const raw = value;
89
+ if (Object.keys(raw).sort().join("\0") !== RUNTIME_CONTEXT_KEYS.join("\0")) {
90
+ throw new Error("Runtime context response is malformed");
91
+ }
92
+ const subjectId = raw.subject_id;
93
+ const rootDelegation = raw.root_delegation;
94
+ const deploymentId = raw.deployment_id;
95
+ const agentType = raw.agent_type;
96
+ if (raw.authenticated !== true ||
97
+ typeof subjectId !== "string" ||
98
+ !subjectId.startsWith("agent:") ||
99
+ !UUID_PATTERN.test(subjectId.slice("agent:".length)) ||
100
+ typeof rootDelegation !== "string" ||
101
+ !rootDelegation.trim() ||
102
+ typeof deploymentId !== "string" ||
103
+ !UUID_PATTERN.test(deploymentId) ||
104
+ typeof agentType !== "string" ||
105
+ !agentType.trim()) {
106
+ throw new Error("Runtime context response is malformed");
107
+ }
108
+ return { authenticated: true, subjectId, rootDelegation, deploymentId, agentType };
109
+ }
110
+ /** Resolve missing headless deployment metadata using the existing runtime proof. */
111
+ async function resolvePreprovisionedRuntimeContext(client, options) {
112
+ const env = options.env ?? process.env;
113
+ const credential = client.runtimeCredential ?? (0, runtime_credential_js_1.resolveRuntimeCredential)({
114
+ env,
115
+ fetch: options.fetchImpl,
116
+ });
117
+ if (!credential)
118
+ return undefined;
119
+ let response;
120
+ try {
121
+ response = await (0, runtime_credential_js_1.authenticatedRuntimeRequest)(credential, {
122
+ url: `${client.agentSecurityUrl.replace(/\/+$/, "")}/agent-security/v1/runtime:context`,
123
+ init: {
124
+ method: "POST",
125
+ headers: { "content-type": "application/json" },
126
+ body: JSON.stringify({ project_url: options.projectUrl ?? (0, config_js_1.resolveConfig)().projectUrl }),
127
+ signal: options.signal ?? AbortSignal.timeout(30_000),
128
+ },
129
+ authorizationHeader: "authorization",
130
+ fetch: options.fetchImpl,
131
+ });
132
+ }
133
+ catch {
134
+ throw new Error("Runtime context request failed");
135
+ }
136
+ if (response.status !== 200) {
137
+ throw new Error(`Runtime context request failed (HTTP ${response.status})`);
138
+ }
139
+ let payload;
140
+ try {
141
+ payload = await response.json();
142
+ }
143
+ catch {
144
+ throw new Error("Runtime context response is malformed");
145
+ }
146
+ const context = parseRuntimeContext(payload);
147
+ if (context.agentType !== options.expectedAgentType) {
148
+ throw new Error("Runtime context agent type does not match the adapter type");
149
+ }
150
+ const contextualCredential = { ...credential, subject: context.subjectId };
151
+ validatedRuntimeSubjects.add(contextualCredential);
152
+ client.setRuntimeCredential(contextualCredential);
153
+ client.setRuntimeContext(context);
154
+ return context;
155
+ }
58
156
  async function withEnrollmentLock(identity, work) {
59
157
  const lockRoot = (0, node_path_1.join)((0, config_js_1.getDataDir)(), "runtime-credential-locks");
60
158
  const lockPath = (0, node_path_1.join)(lockRoot, `${(0, runtime_credential_js_1.credentialAccount)(identity)}.lock`);
@@ -107,7 +205,17 @@ async function resolveRuntimeAgentCredentials(options = {}) {
107
205
  subject,
108
206
  fetch: options.fetchImpl,
109
207
  });
110
- const configured = (0, runtime_credential_js_1.resolveRuntimeCredential)({ env, subject, fetch: options.fetchImpl });
208
+ let configured = (0, runtime_credential_js_1.resolveRuntimeCredential)({ env, subject, fetch: options.fetchImpl });
209
+ const current = options.runtimeCredential;
210
+ if (configured?.kind === "talos" &&
211
+ current?.kind === "talos" &&
212
+ current.subject &&
213
+ validatedRuntimeSubjects.has(current) &&
214
+ current.apiKeyFingerprint !== undefined &&
215
+ current.apiKeyFingerprint === configured.apiKeyFingerprint) {
216
+ configured = { ...configured, subject: current.subject };
217
+ validatedRuntimeSubjects.add(configured);
218
+ }
111
219
  const existing = injected ?? configured
112
220
  ?? (options.runtimeCredential?.kind === "talos" ? options.runtimeCredential : undefined);
113
221
  if (existing) {
@@ -342,7 +450,10 @@ function pruneAgentBlock(current, mutate) {
342
450
  * sessions but the node is not, so a sub-agent must hang off *its own* run's
343
451
  * agent node.
344
452
  */
345
- function loadDelegationNodeId(harness, sessionKey) {
453
+ function loadDelegationNodeId(harness, sessionKey, env = process.env, runtimeCredential) {
454
+ const provisioned = resolvePreprovisionedRootDelegation(env, undefined, runtimeCredential);
455
+ if (provisioned)
456
+ return provisioned;
346
457
  const config = (0, config_js_1.loadConfig)();
347
458
  return config.delegationAnchors?.[harness]?.[sessionKey]
348
459
  // Read the old location during the cutover; every subsequent save writes
@@ -996,8 +1107,8 @@ async function fetchClientCredentialsToken(args) {
996
1107
  */
997
1108
  async function ensureAgentIdentity(client, options = {}) {
998
1109
  const sessionId = options.sessionId ?? client.sessionId;
999
- // Agent Security isn't connected — skip credential resolution entirely
1000
- // (no DCR, no token grant) and record the no-op, shaped like the user
1110
+ // Agent Security isn't connected: skip runtime credential resolution and
1111
+ // record the no-op, shaped like the user
1001
1112
  // gate's not_connected result.
1002
1113
  const resolvedConfig = (0, config_js_1.resolveConfig)();
1003
1114
  const security = resolvedConfig.security;
@@ -1193,13 +1304,12 @@ async function drainRetiredCredentials(client, opts) {
1193
1304
  });
1194
1305
  }
1195
1306
  /**
1196
- * Resolve a sub-agent against the process runtime credential. The broker's
1197
- * `type_name` supplies sub-agent attribution; no separate OAuth identity is
1198
- * registered, loaded, minted, or persisted.
1307
+ * Resolve a sub-agent to a child Talos credential. Stored credentials are
1308
+ * loaded by spawn descriptor; missing credentials may be enrolled through the
1309
+ * authenticated parent runtime.
1199
1310
  */
1200
1311
  async function ensureSubAgentIdentity(client, options) {
1201
- // Same no-op as ensureAgentIdentity — no sub-agent DCR while Agent
1202
- // Security is not connected.
1312
+ // Same no-op as ensureAgentIdentity while Agent Security is disconnected.
1203
1313
  const resolvedConfig = (0, config_js_1.resolveConfig)();
1204
1314
  const subAgentSecurity = resolvedConfig.security;
1205
1315
  if (!subAgentSecurity.connected) {
@@ -33,9 +33,9 @@ export interface EnvUserCredential {
33
33
  *
34
34
  * **`ORY_USER_OAUTH2_TOKEN` is the only accepted variable**, because an OAuth2
35
35
  * access token is the only thing the credential is ever used as. All three
36
- * consumers of `userPrincipal.token` demand one: broker delegation
37
- * authentication and the agent + sub-agent DCR bootstraps (which send it as a
38
- * bearer to `/oauth2/register`). Nothing calls `verifySession`, so a Kratos
36
+ * consumers of `userPrincipal.token` demand one: broker delegation ownership
37
+ * and root Talos enrollment (which sends it as the user bearer). Nothing calls
38
+ * `verifySession`, so a Kratos
39
39
  * session token is never consumed as a session token.
40
40
  *
41
41
  * Three variables used to be accepted alongside it and have been removed:
@@ -100,9 +100,9 @@ function clearTokens() {
100
100
  *
101
101
  * **`ORY_USER_OAUTH2_TOKEN` is the only accepted variable**, because an OAuth2
102
102
  * access token is the only thing the credential is ever used as. All three
103
- * consumers of `userPrincipal.token` demand one: broker delegation
104
- * authentication and the agent + sub-agent DCR bootstraps (which send it as a
105
- * bearer to `/oauth2/register`). Nothing calls `verifySession`, so a Kratos
103
+ * consumers of `userPrincipal.token` demand one: broker delegation ownership
104
+ * and root Talos enrollment (which sends it as the user bearer). Nothing calls
105
+ * `verifySession`, so a Kratos
106
106
  * session token is never consumed as a session token.
107
107
  *
108
108
  * Three variables used to be accepted alongside it and have been removed:
@@ -142,16 +142,60 @@ function isExpired(tokens, now = Date.now()) {
142
142
  * or if the refresh call fails.
143
143
  */
144
144
  async function refreshAndSave(args) {
145
- if (!args.current.refreshToken) {
146
- throw new Error("No refresh_token available; a fresh login is required.");
147
- }
148
- const next = await (0, auth_js_1.refreshAccessToken)({
149
- projectUrl: args.projectUrl,
150
- clientId: args.clientId,
151
- refreshToken: args.current.refreshToken,
145
+ return (0, config_js_1.mutateConfigAsync)(async (config) => {
146
+ const persistedProjectOrigin = canonicalProjectOrigin(config.projectUrl);
147
+ const requestedProjectOrigin = canonicalProjectOrigin(args.projectUrl);
148
+ if (persistedProjectOrigin !== requestedProjectOrigin) {
149
+ throw new Error("Persisted user OAuth2 tokens are bound to the current config projectUrl; sign in again for the selected project.");
150
+ }
151
+ const persisted = config.user?.oauth2;
152
+ const persistedClientId = persisted?.clientId?.trim() ||
153
+ config.oauth2ClientId?.trim() ||
154
+ config_js_1.DEFAULT_OAUTH2_CLIENT_ID;
155
+ if (persistedClientId !== args.clientId.trim()) {
156
+ throw new Error("Persisted user OAuth2 tokens are bound to a different OAuth2 client ID; sign in again with the selected client.");
157
+ }
158
+ if (persisted && !isExpired(persisted)) {
159
+ return { value: persisted };
160
+ }
161
+ const current = persisted ?? args.current;
162
+ if (!current.refreshToken) {
163
+ throw new Error("No refresh_token available; a fresh login is required.");
164
+ }
165
+ const next = await (0, auth_js_1.refreshAccessToken)({
166
+ projectUrl: args.projectUrl,
167
+ clientId: args.clientId,
168
+ refreshToken: current.refreshToken,
169
+ });
170
+ return {
171
+ config: {
172
+ ...config,
173
+ user: { ...(config.user ?? {}), oauth2: next },
174
+ },
175
+ value: next,
176
+ };
152
177
  });
153
- saveTokens(next);
154
- return next;
178
+ }
179
+ function canonicalProjectOrigin(raw) {
180
+ if (!raw?.trim()) {
181
+ throw new Error("Persisted user OAuth2 tokens require projectUrl in the current config.");
182
+ }
183
+ let parsed;
184
+ try {
185
+ parsed = new URL(raw.trim());
186
+ }
187
+ catch {
188
+ throw new Error("Persisted user OAuth2 tokens require a valid projectUrl origin.");
189
+ }
190
+ if ((parsed.protocol !== "https:" && parsed.protocol !== "http:") ||
191
+ parsed.username ||
192
+ parsed.password ||
193
+ (parsed.pathname !== "/" && parsed.pathname !== "") ||
194
+ parsed.search ||
195
+ parsed.hash) {
196
+ throw new Error("Persisted user OAuth2 tokens require a valid projectUrl origin.");
197
+ }
198
+ return parsed.origin;
155
199
  }
156
200
  /**
157
201
  * Try to acquire the pkce-flight lock. Returns the lock handle if this
package/dist/auth.d.ts CHANGED
@@ -26,7 +26,7 @@ export declare const LOOPBACK_PORTS: readonly [47823, 47824, 47825, 47826];
26
26
  *
27
27
  * The binding constraint is Antigravity, which validates its hook timeout to
28
28
  * `5..120` seconds — so 120s is the ceiling, and this leaves headroom for the
29
- * agent DCR registration and delegation record that run after the login inside
29
+ * Talos enrollment and delegation recording that run after login inside
30
30
  * the same hook invocation. See `hook-timeout.ts`, which derives every
31
31
  * harness's declared window and asserts it outlasts this value.
32
32
  */
package/dist/auth.js CHANGED
@@ -75,7 +75,7 @@ exports.LOOPBACK_PORTS = [47823, 47824, 47825, 47826];
75
75
  *
76
76
  * The binding constraint is Antigravity, which validates its hook timeout to
77
77
  * `5..120` seconds — so 120s is the ceiling, and this leaves headroom for the
78
- * agent DCR registration and delegation record that run after the login inside
78
+ * Talos enrollment and delegation recording that run after login inside
79
79
  * the same hook invocation. See `hook-timeout.ts`, which derives every
80
80
  * harness's declared window and asserts it outlasts this value.
81
81
  */
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "repo": "ory-agent-plugins",
3
- "commit": "8e70fe38343bc8599c65c5e1bd4af54861f633be",
4
- "commitShort": "8e70fe3",
3
+ "commit": "7f6431efd4116e683b164cf2c7300cb44d0c4dcb",
4
+ "commitShort": "7f6431e",
5
5
  "branch": "main",
6
- "commitDate": "2026-09-14T08:17:01-07:00",
6
+ "commitDate": "2026-09-28T11:06:31-07:00",
7
7
  "dirty": false,
8
- "builtAt": "2026-09-14T15:20:26.275Z"
8
+ "builtAt": "2026-09-28T18:10:11.171Z"
9
9
  }
package/dist/cli.js CHANGED
@@ -300,6 +300,11 @@ function printEnvironment() {
300
300
  ],
301
301
  ["ORY_USER_SUBJECT_ID", process.env.ORY_USER_SUBJECT_ID, false],
302
302
  ["ORY_AGENT_SUBJECT_ID", process.env.ORY_AGENT_SUBJECT_ID, false],
303
+ [
304
+ "ORY_AGENT_ROOT_DELEGATION",
305
+ process.env.ORY_AGENT_ROOT_DELEGATION ? "(set)" : undefined,
306
+ false,
307
+ ],
303
308
  ["ORY_AGENT_DEBUG", process.env.ORY_AGENT_DEBUG, false],
304
309
  ["ORY_AGENT_LOG_FILE", process.env.ORY_AGENT_LOG_FILE, false],
305
310
  ];
package/dist/client.d.ts CHANGED
@@ -93,6 +93,14 @@ export interface RecordDelegationResult {
93
93
  /** Root-first chain, e.g. `["User:<id>", "agent:<join>"]`. */
94
94
  delegationChain: string[];
95
95
  }
96
+ /** Administrator-provisioned runtime metadata resolved from Agent Security. */
97
+ export interface RuntimeContext {
98
+ authenticated: true;
99
+ subjectId: string;
100
+ rootDelegation: string;
101
+ deploymentId: string;
102
+ agentType: string;
103
+ }
96
104
  /**
97
105
  * A principal known to the client. Either the human user (interactive
98
106
  * PKCE login) or the agent (machine credentials).
@@ -111,6 +119,7 @@ export declare class OryAgentClient {
111
119
  readonly logger: DebugLogger;
112
120
  private readonly eventReporter?;
113
121
  private _runtimeCredential?;
122
+ private _runtimeContext?;
114
123
  private readonly _runtimeCredentialsBySession;
115
124
  private readonly _runtimeCredentialsByPrincipalKey;
116
125
  private sessionCache;
@@ -146,6 +155,10 @@ export declare class OryAgentClient {
146
155
  /** Active noninteractive broker credential. It is process memory only. */
147
156
  get runtimeCredential(): RuntimeCredential | undefined;
148
157
  get hasRuntimeCredential(): boolean;
158
+ /** Process-local deployment context. It is never persisted or copied to the environment. */
159
+ get runtimeContext(): RuntimeContext | undefined;
160
+ get runtimeRootDelegation(): string | undefined;
161
+ setRuntimeContext(context: RuntimeContext): void;
149
162
  setRuntimeCredential(credential: RuntimeCredential | undefined): void;
150
163
  /** Retain an actor-scoped credential for deferred event delivery without activating it globally. */
151
164
  rememberRuntimeCredential(credential: RuntimeCredential): void;
@@ -251,6 +264,8 @@ export declare class OryAgentClient {
251
264
  agentToken?: string;
252
265
  runtimeCredential?: RuntimeCredential;
253
266
  signal?: AbortSignal;
267
+ /** Authenticate without a user bearer for a preprovisioned root lineage. */
268
+ preprovisionedRoot?: boolean;
254
269
  }): Promise<RecordDelegationResult>;
255
270
  /**
256
271
  * Activity attributes describing which principals are attached to the