@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.
Files changed (133) hide show
  1. package/README.md +31 -45
  2. package/assets/commands/temporal-up.md +1 -1
  3. package/assets/skills/auth-setup/SKILL.md +1 -1
  4. package/assets/skills/local-dev/SKILL.md +17 -7
  5. package/assets/skills/ory-build-agent/SKILL.md +43 -97
  6. package/assets/skills/ory-e2b-sandbox/SKILL.md +18 -18
  7. package/assets/skills/ory-temporal-worker/SKILL.md +38 -42
  8. package/assets/skills/permissions-onboarding/SKILL.md +131 -104
  9. package/dist/adapters.d.ts +93 -30
  10. package/dist/adapters.js +464 -136
  11. package/dist/agent-auth.d.ts +258 -68
  12. package/dist/agent-auth.js +998 -202
  13. package/dist/auth-store.d.ts +37 -2
  14. package/dist/auth-store.js +37 -3
  15. package/dist/auth.d.ts +40 -4
  16. package/dist/auth.js +247 -19
  17. package/dist/bash-parser.d.ts +98 -0
  18. package/dist/bash-parser.js +396 -0
  19. package/dist/branding.d.ts +128 -0
  20. package/dist/branding.js +151 -0
  21. package/dist/build-info.json +4 -4
  22. package/dist/cli-invocation.d.ts +1 -1
  23. package/dist/cli-invocation.js +2 -1
  24. package/dist/cli.d.ts +20 -29
  25. package/dist/cli.js +271 -278
  26. package/dist/client.d.ts +175 -138
  27. package/dist/client.js +672 -391
  28. package/dist/config.d.ts +249 -57
  29. package/dist/config.js +486 -62
  30. package/dist/context.d.ts +10 -0
  31. package/dist/context.js +21 -0
  32. package/dist/contract-suite.d.ts +8 -8
  33. package/dist/contract-suite.js +88 -69
  34. package/dist/denial.d.ts +36 -3
  35. package/dist/denial.js +79 -10
  36. package/dist/event-reporter.d.ts +77 -0
  37. package/dist/event-reporter.js +776 -0
  38. package/dist/external-registrations-main.d.ts +10 -0
  39. package/dist/external-registrations-main.js +38 -0
  40. package/dist/external-registrations.d.ts +79 -0
  41. package/dist/external-registrations.js +188 -0
  42. package/dist/help-cli.d.ts +39 -0
  43. package/dist/help-cli.js +55 -0
  44. package/dist/hook-timeout.d.ts +64 -0
  45. package/dist/hook-timeout.js +88 -0
  46. package/dist/index.d.ts +31 -19
  47. package/dist/index.js +182 -31
  48. package/dist/lifecycle.d.ts +3 -3
  49. package/dist/lifecycle.js +38 -6
  50. package/dist/local/cli.js +11 -6
  51. package/dist/local/configs.d.ts +74 -18
  52. package/dist/local/configs.js +291 -84
  53. package/dist/local/health.d.ts +14 -0
  54. package/dist/local/health.js +50 -4
  55. package/dist/local/index.d.ts +2 -2
  56. package/dist/local/index.js +24 -10
  57. package/dist/local/manager.d.ts +20 -1
  58. package/dist/local/manager.js +160 -39
  59. package/dist/local/ports.d.ts +158 -0
  60. package/dist/local/ports.js +443 -0
  61. package/dist/local/seed.d.ts +22 -25
  62. package/dist/local/seed.js +88 -56
  63. package/dist/logger.d.ts +54 -25
  64. package/dist/logger.js +329 -63
  65. package/dist/mcp.d.ts +2 -2
  66. package/dist/mcp.js +10 -5
  67. package/dist/mirror-bootstrap.d.ts +48 -0
  68. package/dist/mirror-bootstrap.js +254 -0
  69. package/dist/opl.d.ts +289 -0
  70. package/dist/opl.js +446 -0
  71. package/dist/permission-mode.d.ts +87 -0
  72. package/dist/permission-mode.js +307 -0
  73. package/dist/permissions-cli.d.ts +13 -49
  74. package/dist/permissions-cli.js +154 -348
  75. package/dist/permissions.d.ts +148 -38
  76. package/dist/permissions.js +591 -45
  77. package/dist/post-install.d.ts +33 -0
  78. package/dist/post-install.js +127 -0
  79. package/dist/read-credential.d.ts +65 -0
  80. package/dist/read-credential.js +86 -0
  81. package/dist/registry/cli.js +5 -2
  82. package/dist/registry/config.d.ts +0 -17
  83. package/dist/registry/config.js +0 -23
  84. package/dist/registry/index.d.ts +1 -1
  85. package/dist/registry/index.js +2 -2
  86. package/dist/registry/manager.d.ts +4 -21
  87. package/dist/registry/manager.js +83 -55
  88. package/dist/runtime-credential.d.ts +140 -0
  89. package/dist/runtime-credential.js +572 -0
  90. package/dist/runtime.d.ts +408 -0
  91. package/dist/runtime.js +748 -0
  92. package/dist/setup.d.ts +23 -28
  93. package/dist/setup.js +57 -84
  94. package/dist/status-cli.d.ts +29 -13
  95. package/dist/status-cli.js +124 -144
  96. package/dist/status-data.d.ts +195 -0
  97. package/dist/status-data.js +333 -0
  98. package/dist/status-system.d.ts +24 -0
  99. package/dist/status-system.js +56 -0
  100. package/dist/subject.d.ts +126 -20
  101. package/dist/subject.js +215 -30
  102. package/dist/testing.d.ts +74 -38
  103. package/dist/testing.js +185 -68
  104. package/dist/tool-catalog.d.ts +53 -11
  105. package/dist/tool-catalog.js +164 -13
  106. package/dist/tool-metadata.d.ts +7 -6
  107. package/dist/tool-metadata.js +6 -5
  108. package/dist/types.d.ts +11 -1
  109. package/dist/uninstall.d.ts +74 -19
  110. package/dist/uninstall.js +224 -49
  111. package/dist/user-login.d.ts +22 -16
  112. package/dist/user-login.js +67 -96
  113. package/dist/watch-cli.d.ts +6 -0
  114. package/dist/watch-cli.js +217 -0
  115. package/package.json +3 -11
  116. package/dist/dev.d.ts +0 -103
  117. package/dist/dev.js +0 -584
  118. package/dist/interactive-setup.d.ts +0 -165
  119. package/dist/interactive-setup.js +0 -1546
  120. package/dist/local/jaeger-main.d.ts +0 -13
  121. package/dist/local/jaeger-main.js +0 -85
  122. package/dist/local/jaeger.d.ts +0 -50
  123. package/dist/local/jaeger.js +0 -162
  124. package/dist/otel/exporter.d.ts +0 -17
  125. package/dist/otel/exporter.js +0 -12
  126. package/dist/otel/index.d.ts +0 -2
  127. package/dist/otel/index.js +0 -8
  128. package/dist/otel/otlp.d.ts +0 -103
  129. package/dist/otel/otlp.js +0 -385
  130. package/dist/tracer.d.ts +0 -190
  131. package/dist/tracer.js +0 -481
  132. package/dist/watch-sandbox.d.ts +0 -9
  133. package/dist/watch-sandbox.js +0 -81
package/dist/cli.js CHANGED
@@ -38,8 +38,6 @@ exports.printOryConfig = printOryConfig;
38
38
  exports.printEnvironment = printEnvironment;
39
39
  exports.printLogTail = printLogTail;
40
40
  exports.printEnvHelp = printEnvHelp;
41
- exports.printTraceTail = printTraceTail;
42
- exports.runWatchCommand = runWatchCommand;
43
41
  exports.isTtyAvailable = isTtyAvailable;
44
42
  exports.promptOnTty = promptOnTty;
45
43
  exports.promptForProjectUrl = promptForProjectUrl;
@@ -49,34 +47,34 @@ const fs = __importStar(require("node:fs"));
49
47
  const readline = __importStar(require("node:readline"));
50
48
  const config_js_1 = require("./config.js");
51
49
  const agent_auth_js_1 = require("./agent-auth.js");
52
- const tracer_js_1 = require("./tracer.js");
50
+ const logger_js_1 = require("./logger.js");
53
51
  const cli_invocation_js_1 = require("./cli-invocation.js");
54
52
  const client_js_1 = require("./client.js");
55
53
  /**
56
54
  * Shared `configure` command implementation for all plugin CLIs.
57
55
  *
58
- * Parses --project-url, --api-key, and --oauth2-client-id from args.
59
- * If none is provided, shows the current configuration. Otherwise saves
60
- * the provided values.
56
+ * `configure` only ever touches the **security** half of a plugin. The
57
+ * developer-experience half skills, commands, the local Ory stack, the MCP
58
+ * server, local activity logging — is installed unconditionally and has nothing to
59
+ * configure, so it is never mentioned here as an alternative or a fallback.
61
60
  *
62
- * When `--project-url` is provided, requires the OAuth2 client id to be
63
- * resolvable from one of: the `--oauth2-client-id` flag, the
64
- * `ORY_OAUTH2_CLIENT_ID` env var, or an already-persisted value in the
65
- * shared config. The PKCE browser flow that runs when
66
- * `ORY_USER_LOGIN=true` can't self-register that client surfacing the
67
- * requirement here keeps users from a silently-broken setup later.
61
+ * Connecting takes the project URL and canonical broker origin from the Ory
62
+ * Console (Agent Security). The public OAuth2 client id defaults to the reserved
63
+ * Agent Security login client and can be overridden for custom deployments.
64
+ * Existing configurations use the project URL as a legacy broker fallback.
65
+ * `--disconnect` clears all three stored connection values.
66
+ *
67
+ * With no arguments, prints what is currently resolved.
68
68
  */
69
69
  function runConfigureCommand(binName, args) {
70
70
  let projectUrl;
71
+ let agentSecurityUrl;
71
72
  let projectId;
72
- let apiKey;
73
73
  let oauth2ClientId;
74
- let auditOnly = false;
75
- // `undefined` = not specified (leave the persisted value alone);
76
- // true/false = explicit enable/disable of the interactive user login.
77
- let userLogin;
78
- // `undefined` = not specified; a string sets it; empty string ("") clears it
79
- // (via --no-user-subject-namespace) to fall back to direct SubjectID.
74
+ let disconnect = false;
75
+ // `undefined` = not specified; a string overrides the default `User`
76
+ // namespace; empty string ("") clears the override (via
77
+ // --no-user-subject-namespace) so the default `User` namespace applies.
80
78
  let userSubjectNamespace;
81
79
  for (let i = 0; i < args.length; i++) {
82
80
  switch (args[i]) {
@@ -89,20 +87,14 @@ function runConfigureCommand(binName, args) {
89
87
  case "--project-id":
90
88
  projectId = args[++i];
91
89
  break;
92
- case "--api-key":
93
- apiKey = args[++i];
90
+ case "--agent-security-url":
91
+ agentSecurityUrl = args[++i];
94
92
  break;
95
93
  case "--oauth2-client-id":
96
94
  oauth2ClientId = args[++i];
97
95
  break;
98
- case "--audit-only":
99
- auditOnly = true;
100
- break;
101
- case "--user-login":
102
- userLogin = true;
103
- break;
104
- case "--no-user-login":
105
- userLogin = false;
96
+ case "--disconnect":
97
+ disconnect = true;
106
98
  break;
107
99
  case "--user-subject-namespace":
108
100
  userSubjectNamespace = args[++i];
@@ -113,176 +105,175 @@ function runConfigureCommand(binName, args) {
113
105
  }
114
106
  }
115
107
  if (!projectUrl &&
108
+ !agentSecurityUrl &&
116
109
  !projectId &&
117
- !apiKey &&
118
110
  !oauth2ClientId &&
119
- !auditOnly &&
120
- userLogin === undefined &&
111
+ !disconnect &&
121
112
  userSubjectNamespace === undefined) {
122
113
  const resolved = (0, config_js_1.resolveConfig)();
123
114
  const configPath = (0, config_js_1.getConfigPath)();
124
115
  console.log("Ory Plugin Configuration");
125
116
  console.log("========================");
126
117
  console.log("");
127
- console.log(`Config file: ${configPath}`);
118
+ console.log(`Config file: ${configPath}${fs.existsSync(configPath) ? "" : " (not created yet)"}`);
128
119
  console.log(`Project URL: ${resolved.projectUrl ?? "(not set)"} [${resolved.projectUrlSource}]`);
129
- console.log(`Project ID: ${resolved.projectId ?? "(not set)"} [${resolved.projectIdSource}]`);
130
- console.log(`API Key: ${resolved.apiKey ? "(set)" : "(not set)"} [${resolved.apiKeySource}]`);
120
+ console.log(`Agent Security URL: ${resolved.agentSecurityUrl ?? "(not set)"} [${resolved.agentSecurityUrlSource}]`);
131
121
  console.log(`OAuth2 Client ID: ${resolved.oauth2ClientId ?? "(not set)"} [${resolved.oauth2ClientIdSource}]`);
132
- console.log(`Audit Only: ${resolved.auditOnly ? "yes" : "no"}`);
133
- console.log(`User Login: ${resolved.userLogin ? "on" : "off"} [${resolved.userLoginSource}]`);
134
- console.log(`User subject ns: ${resolved.userSubjectNamespace ?? "(direct subject id)"} [${resolved.userSubjectNamespaceSource}]`);
135
- console.log("");
136
- console.log("To configure:");
137
- console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]`);
122
+ console.log(`Agent Security: ${resolved.security.connected ? "connected" : `not connected (${(0, config_js_1.describeSecurityGap)(resolved.security)})`}`);
138
123
  console.log("");
139
- console.log("To enable the interactive user login (PKCE browser flow at session start):");
140
- console.log(` npx ${binName} configure --user-login (disable with --no-user-login)`);
124
+ console.log(`Project ID: ${resolved.projectId ?? "(not set)"} [${resolved.projectIdSource}]`);
125
+ console.log(`User subject ns: ${resolved.userSubjectNamespace ?? "User (default)"} [${resolved.userSubjectNamespaceSource}]`);
141
126
  console.log("");
142
- console.log("To address the user as a SubjectSet in permission checks (must match your tuples):");
143
- console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --user-subject-namespace User (clear with --no-user-subject-namespace)`);
127
+ if (resolved.security.connected) {
128
+ console.log("Sign-in and permission checks are running. Tool grants, explicit blocks,");
129
+ console.log("and the observe/enforce posture are managed in the Ory Console (Agent Security).");
130
+ console.log("");
131
+ console.log("To disconnect (skills, commands, the local stack, and activity logging keep working):");
132
+ console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --disconnect`);
133
+ }
134
+ else {
135
+ console.log("Skills, commands, the local Ory stack, the MCP server, and local activity logging are");
136
+ console.log("installed and working — they don't need a project. Only sign-in and permission");
137
+ console.log("checks are off.");
138
+ console.log("");
139
+ console.log("To turn them on, supply the project and canonical Agent Security origin:");
140
+ console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --project-url <URL> --agent-security-url <URL>`);
141
+ }
144
142
  console.log("");
145
- console.log("To enable audit logging only (no auth or permission checks):");
146
- console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --audit-only`);
143
+ console.log("Users are addressed as SubjectSets (default namespace 'User'). To override the namespace (must match how the project's permissions were written):");
144
+ console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --user-subject-namespace <Namespace> (reset to 'User' with --no-user-subject-namespace)`);
147
145
  console.log("");
148
146
  console.log("Or set environment variables:");
149
147
  console.log(" export ORY_PROJECT_URL=https://your-project.projects.oryapis.com");
148
+ console.log(" export ORY_AGENT_SECURITY_URL=https://agents.console.ory.com");
150
149
  console.log(" # ORY_SDK_URL (the Ory Console 'Get Started' name) is accepted as an alias.");
151
150
  console.log(" # ORY_PROJECT_ID=<project uuid> is stored for admin operations (not an SDK URL).");
152
- console.log(" export ORY_OAUTH2_CLIENT_ID=<public OAuth2 client id>");
153
- console.log(" export ORY_AGENT_API_KEY=ory_pat_...");
154
- console.log(" export ORY_USER_LOGIN=true");
151
+ console.log(` # ORY_OAUTH2_CLIENT_ID defaults to ${config_js_1.DEFAULT_OAUTH2_CLIENT_ID}.`);
155
152
  return;
156
153
  }
157
- if (auditOnly) {
158
- // Audit-only mode: set the kill switch. Persist an explicit user-login
159
- // choice too if one was passed, so it takes effect once audit-only is
160
- // later turned off; audit-only itself disables login regardless.
161
- (0, config_js_1.saveConfig)({ auditOnly: true, ...(userLogin !== undefined ? { userLogin } : {}) });
154
+ if (disconnect) {
155
+ // Clear both required values. Everything else the plugin does is
156
+ // unaffected this turns off sign-in and permission checks, nothing more.
157
+ (0, config_js_1.saveConfig)({ projectUrl: null, agentSecurityUrl: null, oauth2ClientId: null });
162
158
  const configPath = (0, config_js_1.getConfigPath)();
163
159
  console.log(`Configuration saved to ${configPath}`);
164
- console.log(" Mode: audit-only (logging enabled, auth and permission checks disabled)");
165
- if (userLogin !== undefined) {
166
- console.log(` User login: ${userLogin ? "on" : "off"} (takes effect when audit-only is disabled)`);
167
- }
160
+ console.log(" Ory Agent Security: disconnected (no sign-in, no permission checks)");
168
161
  console.log("");
169
- console.log("This configuration is shared across all Ory agent plugins.");
162
+ console.log("Skills, commands, the local Ory stack, the MCP server, and local activity logging");
163
+ console.log("are unaffected and keep working.");
164
+ if (process.env.ORY_PROJECT_URL || process.env.ORY_SDK_URL || process.env.ORY_AGENT_SECURITY_URL || process.env.ORY_OAUTH2_CLIENT_ID) {
165
+ console.log("");
166
+ console.log("Note: ORY_PROJECT_URL / ORY_SDK_URL / ORY_AGENT_SECURITY_URL /");
167
+ console.log("ORY_OAUTH2_CLIENT_ID are set in your");
168
+ console.log("environment and take precedence over the config file — unset them to stay");
169
+ console.log("disconnected.");
170
+ }
170
171
  return;
171
172
  }
172
- // When --project-url / --sdk-url is provided we need a usable OAuth2 client
173
- // id for the user PKCE flow block the configure with a clear error if none
174
- // is reachable from the flag, env, or already-persisted config. A bare
175
- // --project-id sets an admin identifier only (not an SDK URL), so it doesn't
176
- // trigger this gate on its own.
177
- if (projectUrl) {
178
- const persisted = (0, config_js_1.loadConfig)().oauth2ClientId;
179
- const envClientId = process.env.ORY_OAUTH2_CLIENT_ID;
180
- const resolved = oauth2ClientId ?? envClientId ?? persisted;
181
- if (!resolved) {
182
- console.error("Error: --project-url requires an OAuth2 client id for the user PKCE flow.\n" +
173
+ // Agent Security needs the project and broker origin. The login client uses
174
+ // the reserved default unless an explicit flag, environment variable, or
175
+ // persisted override is present. A bare --project-id is an admin identifier
176
+ // only and doesn't connect anything, so it doesn't trigger this.
177
+ if (agentSecurityUrl) {
178
+ try {
179
+ const parsed = new URL(agentSecurityUrl);
180
+ if ((parsed.protocol !== "https:" && parsed.origin !== config_js_1.LOCAL_AGENT_SECURITY_URL) ||
181
+ !parsed.hostname ||
182
+ parsed.username ||
183
+ parsed.password ||
184
+ (parsed.pathname !== "" && parsed.pathname !== "/") ||
185
+ parsed.search ||
186
+ parsed.hash) {
187
+ throw new Error("invalid origin");
188
+ }
189
+ agentSecurityUrl = parsed.origin;
190
+ }
191
+ catch {
192
+ console.error("Error: --agent-security-url must be an HTTPS origin without credentials, path, query, or fragment " +
193
+ `(for example ${config_js_1.DEFAULT_AGENT_SECURITY_URL} or ${config_js_1.LOCAL_AGENT_SECURITY_URL}).`);
194
+ process.exit(1);
195
+ }
196
+ }
197
+ if (projectUrl || agentSecurityUrl || oauth2ClientId) {
198
+ const persisted = (0, config_js_1.loadConfig)();
199
+ const resolvedUrl = projectUrl ?? process.env.ORY_PROJECT_URL ?? process.env.ORY_SDK_URL ?? persisted.projectUrl;
200
+ if (!resolvedUrl) {
201
+ console.error("Error: Ory Agent Security needs --project-url <URL>.\n" +
183
202
  "\n" +
184
- "Provide one with --oauth2-client-id <id>, or set ORY_OAUTH2_CLIENT_ID in your\n" +
185
- "environment. The client must be a public OAuth2 client (no client secret,\n" +
186
- "token_endpoint_auth_method=none) registered in your Ory project with all four\n" +
187
- "loopback redirect URIs:\n" +
188
- " http://127.0.0.1:47823/callback\n" +
189
- " http://127.0.0.1:47824/callback\n" +
190
- " http://127.0.0.1:47825/callback\n" +
191
- " http://127.0.0.1:47826/callback\n" +
203
+ "Copy the project URL from the Ory Console under Agent Security:\n" +
204
+ ` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --project-url <URL>\n` +
192
205
  "\n" +
193
- "Create it with:\n" +
194
- " ory create oauth2-client --project <project-id> \\\n" +
195
- " --name \"ory-agent-plugin\" \\\n" +
196
- " --grant-type authorization_code,refresh_token \\\n" +
197
- " --response-type code \\\n" +
198
- " --scope openid,offline_access \\\n" +
199
- " --token-endpoint-auth-method none \\\n" +
200
- " --redirect-uri http://127.0.0.1:47823/callback \\\n" +
201
- " --redirect-uri http://127.0.0.1:47824/callback \\\n" +
202
- " --redirect-uri http://127.0.0.1:47825/callback \\\n" +
203
- " --redirect-uri http://127.0.0.1:47826/callback\n" +
206
+ "(--agent-security-url and --oauth2-client-id override their production defaults.)\n" +
204
207
  "\n" +
205
- "If you only want audit logging (no auth or permission checks), use:\n" +
206
- ` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --audit-only`);
208
+ "Connecting needs no workspace privilege. The plugin never provisions the project itself.\n" +
209
+ "\n" +
210
+ "Nothing was changed. The plugin's skills, commands, local Ory stack, MCP\n" +
211
+ "server, and local activity logging work without a project and are unaffected.");
207
212
  process.exit(1);
208
213
  }
209
- }
210
- // Enabling user login needs a project URL and a public OAuth2 client id
211
- // to complete the PKCE flow. Warn (don't block) if either is missing —
212
- // the operator may supply them separately or via env before running.
213
- if (userLogin === true) {
214
- // resolveConfig() already folds in the ORY_SDK_URL alias and the config
215
- // file; OR in the URL being set on this same invocation.
216
- const nextProjectUrl = projectUrl ?? (0, config_js_1.resolveConfig)().projectUrl;
217
- const nextClientId = oauth2ClientId ?? process.env.ORY_OAUTH2_CLIENT_ID ?? (0, config_js_1.loadConfig)().oauth2ClientId;
218
- if (!nextProjectUrl || !nextClientId) {
219
- const missing = [
220
- !nextProjectUrl ? "a project URL (--project-url / --sdk-url / ORY_PROJECT_URL)" : null,
221
- !nextClientId ? "an OAuth2 client id (--oauth2-client-id / ORY_OAUTH2_CLIENT_ID)" : null,
222
- ].filter(Boolean);
223
- console.warn(`Warning: user login is enabled but still missing ${missing.join(" and ")}.\n` +
224
- "The PKCE browser flow can't complete until both are configured.");
214
+ if (projectUrl && !agentSecurityUrl && !process.env.ORY_AGENT_SECURITY_URL?.trim() && !persisted.agentSecurityUrl) {
215
+ agentSecurityUrl = config_js_1.DEFAULT_AGENT_SECURITY_URL;
225
216
  }
226
217
  }
227
218
  const update = {};
228
219
  if (projectUrl)
229
220
  update.projectUrl = projectUrl;
221
+ if (agentSecurityUrl)
222
+ update.agentSecurityUrl = agentSecurityUrl;
230
223
  if (projectId)
231
224
  update.projectId = projectId;
232
- if (apiKey)
233
- update.apiKey = apiKey;
234
225
  if (oauth2ClientId)
235
226
  update.oauth2ClientId = oauth2ClientId;
236
- if (userLogin !== undefined)
237
- update.userLogin = userLogin;
238
- // A non-empty namespace sets the SubjectSet namespace; the empty string
239
- // (from --no-user-subject-namespace) clears it (null ⇒ delete the key).
227
+ // A non-empty namespace overrides the default `User` SubjectSet namespace; the
228
+ // empty string (from --no-user-subject-namespace) clears the override (null ⇒
229
+ // delete the key) so the `User` default applies.
240
230
  if (userSubjectNamespace !== undefined) {
241
231
  update.userSubjectNamespace = userSubjectNamespace === "" ? null : userSubjectNamespace;
242
232
  }
243
- // Configuring an actual connection (a slug-based URL, api key, or client id)
244
- // implies leaving audit-only mode. A bare project id is only an admin
245
- // identifier — it doesn't connect anything on its own — so it doesn't flip
246
- // the flag; nor does a standalone user-login toggle.
247
- if (projectUrl || apiKey || oauth2ClientId)
248
- update.auditOnly = false;
249
233
  (0, config_js_1.saveConfig)(update);
250
234
  const configPath = (0, config_js_1.getConfigPath)();
251
235
  console.log(`Configuration saved to ${configPath}`);
252
236
  if (projectUrl)
253
237
  console.log(` Project URL: ${projectUrl}`);
238
+ if (agentSecurityUrl)
239
+ console.log(` Agent Security URL: ${agentSecurityUrl}`);
254
240
  if (projectId)
255
241
  console.log(` Project ID: ${projectId}`);
256
- if (apiKey)
257
- console.log(` API Key: (set)`);
258
242
  if (oauth2ClientId)
259
243
  console.log(` OAuth2 Client ID: ${oauth2ClientId}`);
260
- if (userLogin !== undefined)
261
- console.log(` User Login: ${userLogin ? "on" : "off"}`);
262
244
  if (userSubjectNamespace !== undefined)
263
- console.log(` User subject namespace: ${userSubjectNamespace === "" ? "(cleared direct subject id)" : userSubjectNamespace}`);
245
+ console.log(` User subject namespace: ${userSubjectNamespace === "" ? "(reset to default 'User')" : userSubjectNamespace}`);
246
+ // Report the resulting posture rather than assuming the write connected
247
+ // anything: `--project-id` on its own changes no part of it.
248
+ const security = (0, config_js_1.resolveConfig)().security;
249
+ console.log(` Ory Agent Security: ${security.connected ? "connected — sign-in and permission checks run on the next session" : `not connected (${(0, config_js_1.describeSecurityGap)(security)})`}`);
264
250
  console.log("");
265
251
  console.log("This configuration is shared across all Ory agent plugins.");
266
- console.log("Environment variables (ORY_PROJECT_URL, ORY_AGENT_API_KEY, ORY_OAUTH2_CLIENT_ID, ORY_USER_LOGIN) take precedence when set.");
252
+ console.log("Environment variables (ORY_PROJECT_URL, ORY_AGENT_SECURITY_URL, ORY_OAUTH2_CLIENT_ID) take precedence when set.");
267
253
  }
268
254
  /**
269
- * Print the "Configuration:" block showing Ory project URL and API key status.
255
+ * Print the "Configuration:" block showing the resolved Ory connection.
270
256
  */
271
257
  function printOryConfig() {
272
258
  const resolved = (0, config_js_1.resolveConfig)();
273
259
  const configPath = (0, config_js_1.getConfigPath)();
274
- const namespace = process.env.ORY_PERMISSION_NAMESPACE || "AgentTools";
260
+ const namespace = process.env.ORY_PERMISSION_NAMESPACE || "AgentTool";
275
261
  console.log("Configuration:");
276
262
  console.log(` Config file: ${configPath}`);
277
- console.log(` Mode: ${resolved.auditOnly ? "audit-only" : "full"}`);
263
+ console.log(` Agent Security: ${resolved.security.connected
264
+ ? "connected (sign-in + permission checks + activity logging)"
265
+ : `not connected — ${(0, config_js_1.describeSecurityGap)(resolved.security)} (local activity logging only)`}`);
278
266
  console.log(` Project URL: ${resolved.projectUrl ?? "NOT SET"} [source: ${resolved.projectUrlSource}]`);
279
- console.log(` Project ID: ${resolved.projectId ?? "NOT SET"} [source: ${resolved.projectIdSource}]`);
280
- console.log(` API Key: ${resolved.apiKey ? "(set)" : "NOT SET"} [source: ${resolved.apiKeySource}]`);
267
+ console.log(` Agent Security: ${resolved.agentSecurityUrl ?? "NOT SET"} [source: ${resolved.agentSecurityUrlSource}]`);
281
268
  console.log(` OAuth2 Client: ${resolved.oauth2ClientId ?? "NOT SET"} [source: ${resolved.oauth2ClientIdSource}]`);
282
- console.log(` Permission mode: ${resolved.permissionMode} [source: ${resolved.permissionModeSource}]`);
283
- console.log(` User login: ${resolved.userLogin ? "on" : "off"} [source: ${resolved.userLoginSource}]`);
269
+ console.log(` Project ID: ${resolved.projectId ?? "NOT SET"} [source: ${resolved.projectIdSource}]`);
270
+ // Only meaningful while connected with no checks running there is no deny
271
+ // to have a posture about.
272
+ if (resolved.security.connected) {
273
+ console.log(` Permission mode: ${resolved.permissionMode} [source: ${resolved.permissionModeSource}]`);
274
+ }
284
275
  console.log(` Namespace: ${namespace}`);
285
- console.log(` User subject ns: ${resolved.userSubjectNamespace ?? "(direct subject id)"} [source: ${resolved.userSubjectNamespaceSource}]`);
276
+ console.log(` User subject ns: ${resolved.userSubjectNamespace ?? "User (default)"} [source: ${resolved.userSubjectNamespaceSource}]`);
286
277
  }
287
278
  /**
288
279
  * Print the "Environment:" block showing Ory-related environment variables.
@@ -293,33 +284,20 @@ function printEnvironment() {
293
284
  const vars = [
294
285
  ["ORY_PROJECT_URL", process.env.ORY_PROJECT_URL, false],
295
286
  ["ORY_SDK_URL", process.env.ORY_SDK_URL, false],
287
+ ["ORY_AGENT_SECURITY_URL", process.env.ORY_AGENT_SECURITY_URL, false],
296
288
  ["ORY_PROJECT_ID", process.env.ORY_PROJECT_ID, false],
297
- ["ORY_USER_LOGIN", process.env.ORY_USER_LOGIN, false],
298
- [
299
- "ORY_AGENT_API_KEY",
300
- process.env.ORY_AGENT_API_KEY ? "(set)" : undefined,
301
- false,
302
- ],
303
289
  ["ORY_AGENT_CLIENT_ID", process.env.ORY_AGENT_CLIENT_ID, false],
304
290
  ["ORY_OAUTH2_CLIENT_ID", process.env.ORY_OAUTH2_CLIENT_ID, false],
305
291
  [
306
- "ORY_USER_SESSION_TOKEN",
307
- process.env.ORY_USER_SESSION_TOKEN ? "(set)" : undefined,
292
+ "ORY_USER_OAUTH2_TOKEN",
293
+ process.env.ORY_USER_OAUTH2_TOKEN ? "(set)" : undefined,
308
294
  false,
309
295
  ],
310
296
  ["ORY_USER_SUBJECT_ID", process.env.ORY_USER_SUBJECT_ID, false],
311
297
  ["ORY_AGENT_SUBJECT_ID", process.env.ORY_AGENT_SUBJECT_ID, false],
312
298
  ["ORY_AGENT_DEBUG", process.env.ORY_AGENT_DEBUG, false],
313
299
  ["ORY_AGENT_LOG_FILE", process.env.ORY_AGENT_LOG_FILE, false],
314
- ["ORY_AGENT_TRACE_FILE", process.env.ORY_AGENT_TRACE_FILE, false],
315
300
  ];
316
- if (process.env.ORY_API_KEY && !process.env.ORY_AGENT_API_KEY) {
317
- vars.splice(2, 0, [
318
- "ORY_API_KEY",
319
- "(set, DEPRECATED — use ORY_AGENT_API_KEY)",
320
- false,
321
- ]);
322
- }
323
301
  for (const [name, value, required] of vars) {
324
302
  const display = value
325
303
  ? value
@@ -331,12 +309,11 @@ function printEnvironment() {
331
309
  }
332
310
  }
333
311
  /**
334
- * Print the last 5 debug log entries. When `harness` is given the path is
312
+ * Print the last 10 unified activity/debug log entries. When `harness` is given the path is
335
313
  * resolved the same way the plugin does at runtime — the default per-harness
336
314
  * data-dir location when `ORY_AGENT_LOG_FILE` is unset — so `status` finds the
337
- * log without the reader hand-setting env vars. The debug log only exists when
338
- * a session ran with `ORY_AGENT_DEBUG=true`; when it doesn't, this prints a
339
- * one-line pointer on how to turn it on rather than staying silent.
315
+ * log without the reader hand-setting env vars. Activity is always filed;
316
+ * verbose diagnostic entries are included only when `ORY_AGENT_DEBUG=true`.
340
317
  */
341
318
  function printLogTail(harness) {
342
319
  const logFile = harness
@@ -345,12 +322,11 @@ function printLogTail(harness) {
345
322
  if (logFile && fs.existsSync(logFile)) {
346
323
  const lines = fs.readFileSync(logFile, "utf-8").trim().split("\n");
347
324
  console.log("");
348
- console.log(`Debug log: ${logFile} (${lines.length} events)`);
349
- console.log("Last 5 events:");
350
- for (const line of lines.slice(-5)) {
325
+ console.log(`Activity log: ${logFile} (${lines.length} events)`);
326
+ console.log("Last 10 events:");
327
+ for (const line of lines.slice(-10)) {
351
328
  try {
352
- const entry = JSON.parse(line);
353
- console.log(` ${entry.timestamp} [${entry.level}] ${entry.event}`);
329
+ console.log(" " + (0, logger_js_1.formatLogEntry)(JSON.parse(line)));
354
330
  }
355
331
  catch {
356
332
  console.log(` ${line.slice(0, 120)}`);
@@ -360,8 +336,8 @@ function printLogTail(harness) {
360
336
  else if (harness) {
361
337
  console.log("");
362
338
  console.log(logFile
363
- ? `Debug log: none yet — run a session with ORY_AGENT_DEBUG=true to write ${logFile}`
364
- : "Debug log: disabled (ORY_AGENT_LOG_FILE set empty)");
339
+ ? `Activity log: none yet — events land in ${logFile} once a session runs`
340
+ : "Activity log: disabled (ORY_AGENT_LOG_FILE set empty)");
365
341
  }
366
342
  }
367
343
  /**
@@ -373,79 +349,19 @@ function printEnvHelp(binName) {
373
349
  console.log("");
374
350
  console.log(" Option 1 — Save to config file (persists across sessions):");
375
351
  console.log(` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --project-url https://your-project.projects.oryapis.com \\`);
376
- console.log(" --oauth2-client-id <public OAuth2 client id> \\");
377
- console.log(" --api-key ory_pat_...");
352
+ console.log(" --agent-security-url https://agents.console.ory.com \\");
353
+ console.log(" --oauth2-client-id <public OAuth2 client id>");
378
354
  console.log("");
379
355
  console.log(" Option 2 — Set environment variables:");
380
356
  console.log(" export ORY_PROJECT_URL=https://your-project.projects.oryapis.com");
357
+ console.log(" export ORY_AGENT_SECURITY_URL=https://agents.console.ory.com");
381
358
  console.log(" export ORY_OAUTH2_CLIENT_ID=<public OAuth2 client id>");
382
- console.log(" export ORY_AGENT_API_KEY=ory_pat_...");
359
+ console.log("");
360
+ console.log(" Both values come from the Ory Console (Agent Security), which provisions the");
361
+ console.log(" project's login client, permission model, grants, and enforce posture.");
383
362
  console.log("");
384
363
  console.log("If neither is set when a session starts, the agent will prompt for configuration.");
385
364
  }
386
- /**
387
- * Print the last 5 trace spans. When `harness` is given the path is resolved
388
- * the same way the plugin does at runtime — the default per-harness data-dir
389
- * location when `ORY_AGENT_TRACE_FILE` is unset — so `status` shows recent
390
- * activity out of the box (traces are written on every recorded span). When no
391
- * spans have been recorded yet, prints a one-line pointer at the trace file so
392
- * the reader knows where to look (and how to watch it live).
393
- */
394
- function printTraceTail(harness) {
395
- const traceFile = harness
396
- ? (0, client_js_1.resolveTraceFilePath)(harness)
397
- : process.env.ORY_AGENT_TRACE_FILE;
398
- if (traceFile && fs.existsSync(traceFile)) {
399
- const lines = fs.readFileSync(traceFile, "utf-8").trim().split("\n");
400
- console.log("");
401
- console.log(`Trace file: ${traceFile} (${lines.length} spans)`);
402
- console.log("Last 5 spans:");
403
- for (const line of lines.slice(-5)) {
404
- try {
405
- const span = JSON.parse(line);
406
- console.log(" " + (0, tracer_js_1.formatSpan)(span));
407
- }
408
- catch {
409
- console.log(` ${line.slice(0, 120)}`);
410
- }
411
- }
412
- }
413
- else if (harness) {
414
- console.log("");
415
- console.log(traceFile
416
- ? `Trace file: none yet — spans land in ${traceFile} once a session runs a tool`
417
- : "Trace file: disabled (ORY_AGENT_TRACE_FILE set empty)");
418
- }
419
- }
420
- /**
421
- * Run the live trace watcher. Tails the NDJSON trace file and prints
422
- * formatted spans to stdout as they arrive. Blocks until interrupted.
423
- */
424
- function runWatchCommand(harness, args) {
425
- // Precedence: explicit arg → resolved default (honors ORY_AGENT_TRACE_FILE,
426
- // falling back to the per-harness data-dir path). The resolver returns
427
- // undefined only when tracing is explicitly disabled (ORY_AGENT_TRACE_FILE
428
- // set empty), so that's the one case that dead-ends here.
429
- const traceFile = args[0] || (0, client_js_1.resolveTraceFilePath)(harness);
430
- if (!traceFile) {
431
- console.error("Trace file is disabled (ORY_AGENT_TRACE_FILE is set empty).\n" +
432
- "Unset it, or pass a path: watch <trace-file>\n");
433
- process.exit(1);
434
- }
435
- console.log(`Watching traces: ${traceFile}`);
436
- if (!fs.existsSync(traceFile)) {
437
- console.log("(file doesn't exist yet — waiting for the first span…)");
438
- }
439
- console.log("Press Ctrl+C to stop.\n");
440
- const stop = (0, tracer_js_1.watchTraceFile)(traceFile, (span) => {
441
- console.log((0, tracer_js_1.formatSpan)(span));
442
- });
443
- process.on("SIGINT", () => {
444
- stop();
445
- console.log("\nStopped watching.");
446
- process.exit(0);
447
- });
448
- }
449
365
  /**
450
366
  * Returns true if `/dev/tty` can be opened for reading. Use this before
451
367
  * attempting to prompt the user — the harness has already taken stdin so
@@ -529,8 +445,9 @@ async function promptForProjectUrl(binName) {
529
445
  process.stderr.write(`\nIgnoring invalid project URL "${answer}".\n\n`);
530
446
  return null;
531
447
  }
532
- (0, config_js_1.saveConfig)({ projectUrl: url, auditOnly: false });
533
- process.stderr.write(`\nSaved project URL to ${(0, config_js_1.getConfigPath)()}.\n\n`);
448
+ (0, config_js_1.saveConfig)({ projectUrl: url, agentSecurityUrl: config_js_1.DEFAULT_AGENT_SECURITY_URL });
449
+ process.stderr.write(`\nSaved project URL to ${(0, config_js_1.getConfigPath)()}.\n`);
450
+ process.stderr.write("\n");
534
451
  return url;
535
452
  }
536
453
  /**
@@ -549,21 +466,19 @@ async function interactiveConfigPrompt(binName) {
549
466
  " Ory Agent Plugin — Configuration Required",
550
467
  "--------------------------------------------",
551
468
  "",
552
- "The Ory agent plugin is installed but not yet configured.",
553
- "Run one of the following commands in another terminal:",
554
- "",
555
- " Connect to Ory (enables auth & permission checks):",
556
- ` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --project-url <URL> --oauth2-client-id <CLIENT_ID> [--api-key <KEY>]`,
469
+ "Ory Agent Security is not connected, so no sign-in or permission checks run.",
470
+ "To turn them on, run this in another terminal:",
557
471
  "",
558
- " Or, enable audit logging only (no auth or permission checks):",
559
- ` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --audit-only`,
472
+ ` ${(0, cli_invocation_js_1.oryNpx)(binName)} configure --project-url <URL>`,
473
+ " (pass --agent-security-url to override the production broker)",
560
474
  "",
561
475
  `Config is saved to ${configPath}`,
562
476
  "and shared across all agent plugins.",
563
477
  "",
564
478
  "You can also set environment variables (requires session restart):",
565
479
  " export ORY_PROJECT_URL=https://your-project.projects.oryapis.com",
566
- " export ORY_AGENT_API_KEY=ory_pat_...",
480
+ " export ORY_AGENT_SECURITY_URL=https://agents.console.ory.com",
481
+ " export ORY_OAUTH2_CLIENT_ID=<public OAuth2 client id>",
567
482
  "",
568
483
  ].join("\n");
569
484
  process.stderr.write(message + "\n");
@@ -591,12 +506,12 @@ async function interactiveConfigPrompt(binName) {
591
506
  }
592
507
  // Re-read config from disk (env vars set in another shell won't be visible)
593
508
  const resolved = (0, config_js_1.resolveConfig)();
594
- if (resolved.projectUrl || resolved.auditOnly) {
595
- process.stderr.write("\nConfiguration detected! Continuing session.\n\n");
509
+ if (resolved.security.connected) {
510
+ process.stderr.write("\nOry Agent Security is connected. Continuing session.\n\n");
596
511
  return true;
597
512
  }
598
- process.stderr.write("\nNo configuration detected. Session blocked.\n");
599
- process.stderr.write("Run one of the configure commands above and start a new session.\n\n");
513
+ process.stderr.write(`\nOry Agent Security is still not connected (${(0, config_js_1.describeSecurityGap)(resolved.security)}).\n`);
514
+ process.stderr.write("Continuing without sign-in or permission checks.\n\n");
600
515
  return false;
601
516
  }
602
517
  /**
@@ -607,71 +522,149 @@ async function interactiveConfigPrompt(binName) {
607
522
  * - `unregister` — best-effort RFC 7592 DELETE on the registered
608
523
  * client, then clear the persisted block.
609
524
  *
525
+ * Each installed harness owns its own agent identity, so both subcommands act
526
+ * on `harness`'s registration by default. `--all` widens them to every
527
+ * harness registered against the shared config.
528
+ *
610
529
  * Returns the process exit code (0 on success, non-zero on usage error).
611
530
  */
612
- async function runAgentCommand(binName, args) {
531
+ async function runAgentCommand(binName, harness, args) {
613
532
  const sub = args[0];
614
533
  if (!sub || sub === "--help" || sub === "-h") {
615
- console.log(`Usage: ${binName} agent <subcommand>`);
534
+ console.log(`Usage: ${binName} agent <subcommand> [--all]`);
616
535
  console.log("");
617
536
  console.log("Subcommands:");
618
- console.log(" status Show the registered agent OAuth2 client (if any)");
619
- console.log(" unregister Delete the registered agent client and clear local state");
537
+ console.log(" status Show the registered agent OAuth2 clients (one per session)");
538
+ console.log(" unregister Delete the registered agent clients and clear local state");
539
+ console.log("");
540
+ console.log("Options:");
541
+ console.log(` --all Act on every harness's agent client, not just ${harness}'s`);
620
542
  return sub ? 0 : 1;
621
543
  }
544
+ const all = args.includes("--all");
622
545
  switch (sub) {
623
546
  case "status":
624
- runAgentStatus();
547
+ runAgentStatus(harness, all);
625
548
  return 0;
626
549
  case "unregister":
627
- return await runAgentUnregister();
550
+ return await runAgentUnregister(harness, all);
628
551
  default:
629
552
  console.error(`Unknown agent subcommand: ${sub}`);
630
553
  console.error(`Run "${binName} agent --help" for usage.`);
631
554
  return 1;
632
555
  }
633
556
  }
634
- function runAgentStatus() {
635
- const persisted = (0, agent_auth_js_1.loadAgentDynamicCredentials)();
557
+ /**
558
+ * The agent registrations a command should act on — one per harness install.
559
+ * `--all` widens to every harness. Sorted newest-registration first.
560
+ */
561
+ function selectAgentRegistrations(harness, all) {
562
+ const byHarness = (0, agent_auth_js_1.loadAgentDynamicCredentialsByHarness)();
563
+ const entries = [];
564
+ for (const [key, bySession] of Object.entries(byHarness)) {
565
+ if (!all && key !== harness)
566
+ continue;
567
+ for (const [sessionKey, credentials] of Object.entries(bySession)) {
568
+ entries.push({ harness: key, sessionKey, credentials });
569
+ }
570
+ }
571
+ return entries.sort((a, b) => b.credentials.registeredAt - a.credentials.registeredAt ||
572
+ a.harness.localeCompare(b.harness));
573
+ }
574
+ function runAgentStatus(harness, all) {
575
+ const registrations = selectAgentRegistrations(harness, all);
636
576
  console.log("Agent identity (Dynamic Client Registration)");
637
577
  console.log("=============================================");
638
578
  console.log("");
639
- if (!persisted) {
640
- console.log(" No registered agent client. The harness will register one on its");
641
- console.log(" next session start using the user's bearer (or");
579
+ if (registrations.length === 0) {
580
+ console.log(all
581
+ ? " No registered agent clients for any harness. Each install registers"
582
+ : ` No registered agent client for ${harness}. Each install registers`);
583
+ console.log(" one on its first session start, using the user's bearer (or");
642
584
  console.log(" ORY_AGENT_REGISTRATION_TOKEN) as the initial access token.");
643
585
  return;
644
586
  }
645
- console.log(` client_id: ${persisted.clientId}`);
646
- console.log(` client_secret: ${persisted.clientSecret ? "(set)" : "(not set — public client)"}`);
647
- console.log(` registered_at: ${new Date(persisted.registeredAt * 1000).toISOString()}`);
648
- console.log(` project_url: ${persisted.projectUrl}`);
649
- if (persisted.harness)
650
- console.log(` harness: ${persisted.harness}`);
651
- if (persisted.registrationClientUri) {
652
- console.log(` management_uri: ${persisted.registrationClientUri}`);
653
- }
654
- if (persisted.registrationAccessToken) {
655
- console.log(` registration_token: (set — used to manage/delete this client)`);
587
+ for (const { harness: key, sessionKey, credentials: persisted } of registrations) {
588
+ console.log(` harness: ${key}`);
589
+ console.log(` session: ${sessionKey}`);
590
+ console.log(` client_id: ${persisted.clientId}`);
591
+ console.log(` client_secret: ${persisted.clientSecret ? "(set)" : "(not set — public client)"}`);
592
+ console.log(` registered_at: ${new Date(persisted.registeredAt * 1000).toISOString()}`);
593
+ console.log(` project_url: ${persisted.projectUrl}`);
594
+ if (persisted.registrationClientUri) {
595
+ console.log(` management_uri: ${persisted.registrationClientUri}`);
596
+ }
597
+ if (persisted.registrationAccessToken) {
598
+ console.log(` registration_token: (set — used to manage/delete this client)`);
599
+ }
600
+ console.log("");
656
601
  }
657
602
  }
658
- async function runAgentUnregister() {
659
- const persisted = (0, agent_auth_js_1.loadAgentDynamicCredentials)();
660
- if (!persisted) {
661
- console.log("No registered agent client to unregister. Nothing to do.");
662
- return 0;
603
+ async function runAgentUnregister(harness, all) {
604
+ const targets = [];
605
+ const children = (0, agent_auth_js_1.loadSubAgentDynamicCredentialsByHarness)();
606
+ for (const [key, bySession] of Object.entries(children)) {
607
+ if (!all && key !== harness)
608
+ continue;
609
+ for (const [sessionKey, byType] of Object.entries(bySession)) {
610
+ for (const [type, credentials] of Object.entries(byType)) {
611
+ targets.push({
612
+ label: `${key}/${sessionKey}/${type}`,
613
+ credentials,
614
+ clear: () => (0, agent_auth_js_1.clearSubAgentDynamicCredentials)(key, sessionKey, type),
615
+ });
616
+ }
617
+ }
663
618
  }
664
- const outcome = await (0, agent_auth_js_1.revokeAgentDynamicClient)(persisted);
665
- if (outcome.status === "skipped") {
666
- console.warn("Warning: no registration_access_token was persisted, so we cannot DELETE the server-side client. Clearing local state only.");
619
+ for (const { harness: key, sessionKey, credentials } of selectAgentRegistrations(harness, all)) {
620
+ targets.push({
621
+ label: `${key}/${sessionKey}`,
622
+ credentials,
623
+ clear: () => (0, agent_auth_js_1.clearAgentDynamicCredentials)(key, sessionKey),
624
+ });
625
+ }
626
+ for (const credentials of (0, agent_auth_js_1.loadRetiredCredentials)()) {
627
+ if (!all && credentials.harness !== harness)
628
+ continue;
629
+ targets.push({
630
+ label: `${credentials.harness ?? "unknown"}/retired`,
631
+ credentials,
632
+ clear: () => undefined,
633
+ retired: true,
634
+ });
635
+ }
636
+ if (targets.length === 0) {
637
+ console.log(all
638
+ ? "No registered agent clients to unregister. Nothing to do."
639
+ : `No registered agent client for ${harness} to unregister. Nothing to do.`);
640
+ return 0;
667
641
  }
668
- else if (outcome.status === "failed") {
669
- const detail = typeof outcome.httpStatus === "number"
670
- ? `returned HTTP ${outcome.httpStatus}`
671
- : `failed (${outcome.message})`;
672
- console.warn(`Warning: server-side DELETE ${detail}. Local state will be cleared anyway.`);
642
+ const retiredIds = [];
643
+ const seen = new Set();
644
+ for (const target of targets) {
645
+ const { label, credentials: persisted } = target;
646
+ if (seen.has(persisted.clientId)) {
647
+ target.clear();
648
+ if (target.retired)
649
+ retiredIds.push(persisted.clientId);
650
+ continue;
651
+ }
652
+ seen.add(persisted.clientId);
653
+ const outcome = await (0, agent_auth_js_1.revokeAgentDynamicClient)(persisted);
654
+ if (outcome.status === "skipped") {
655
+ console.warn(`Warning: no registration_access_token was persisted for ${label}, so we cannot DELETE the server-side client. Clearing local state only.`);
656
+ }
657
+ else if (outcome.status === "failed") {
658
+ const detail = typeof outcome.httpStatus === "number"
659
+ ? `returned HTTP ${outcome.httpStatus}`
660
+ : `failed (${outcome.message})`;
661
+ console.warn(`Warning: server-side DELETE for ${label} ${detail}. Local state will be cleared anyway.`);
662
+ }
663
+ target.clear();
664
+ if (target.retired)
665
+ retiredIds.push(persisted.clientId);
666
+ console.log(`Unregistered agent client ${persisted.clientId} (${label}).`);
673
667
  }
674
- (0, agent_auth_js_1.clearAgentDynamicCredentials)();
675
- console.log(`Unregistered agent client ${persisted.clientId}.`);
668
+ (0, agent_auth_js_1.clearRetiredCredentials)(retiredIds);
676
669
  return 0;
677
670
  }