@ory/claude-code 0.14.0 → 1.0.1

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/dist/handlers.js CHANGED
@@ -2,54 +2,58 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handleHookEvent = handleHookEvent;
4
4
  const argus_1 = require("@ory/argus");
5
- /**
6
- * Route a Claude Code hook event to the appropriate Ory integration.
7
- */
8
- async function handleHookEvent(input, client, deps = {}) {
5
+ const HARNESS = "claude-code";
6
+ function handleHookEvent(input, client, deps = {}) {
9
7
  const event = input.hook_event_name;
10
8
  client.logger.debug("hook.received", {
11
9
  event,
12
10
  sessionId: input.session_id,
13
11
  toolName: input.tool_name,
14
12
  });
15
- // Set trace context so all spans within this hook invocation correlate
16
- client.tracer.setContext({
17
- traceId: (0, argus_1.deriveTraceId)(input.session_id),
18
- sessionId: input.session_id,
19
- });
20
- try {
21
- switch (event) {
22
- case "SessionStart":
23
- return await handleSessionStart(input, client, deps);
24
- case "PreToolUse":
25
- return await handlePreToolUse(input, client, deps);
26
- case "PostToolUse":
27
- return await handlePostToolUse(input, client);
28
- case "PostToolUseFailure":
29
- return await handlePostToolUseFailure(input, client);
30
- case "PermissionRequest":
31
- return await handlePermissionRequest(input, client);
32
- case "UserPromptSubmit":
33
- return await handleUserPromptSubmit(input, client);
34
- case "SubagentStart":
35
- return await handleSubagentStart(input, client, deps);
36
- case "SubagentStop":
37
- return await handleSubagentStop(input, client);
38
- case "SessionEnd":
39
- return await handleSessionEnd(input, client);
40
- default:
41
- client.logger.debug("hook.passthrough", { event });
42
- client.tracer.record("hook.passthrough", "skipped", {
43
- attributes: { event },
44
- });
45
- return { continue: true };
13
+ return (0, argus_1.withHookContext)(client, { sessionId: input.session_id }, async () => {
14
+ const activeType = input.agent_type ?? input.subagent_type;
15
+ let runtimeCredential;
16
+ if (event !== "SubagentStart" && input.agent_id && activeType && (0, argus_1.isSecurityConnected)()) {
17
+ const identity = await (deps.subAgentGate ?? argus_1.ensureSubAgentIdentity)(client, {
18
+ harness: HARNESS,
19
+ sessionId: input.session_id,
20
+ subAgentType: activeType,
21
+ perSpawnId: input.agent_id,
22
+ allowEnrollment: false,
23
+ emitActivity: false,
24
+ });
25
+ if (identity.runtimeCredential) {
26
+ runtimeCredential = identity.runtimeCredential;
27
+ }
46
28
  }
47
- }
48
- finally {
49
- client.tracer.clearContext();
50
- }
29
+ return (0, argus_1.withHookContext)(client, { runtimeCredential }, async () => {
30
+ switch (event) {
31
+ case "SessionStart":
32
+ return handleSessionStart(input, client, deps);
33
+ case "PreToolUse":
34
+ return handlePreToolUse(input, client, deps);
35
+ case "PostToolUse":
36
+ return handlePostToolUse(input, client);
37
+ case "PostToolUseFailure":
38
+ return handlePostToolUseFailure(input, client);
39
+ case "PermissionRequest":
40
+ return handlePermissionRequest(input, client);
41
+ case "UserPromptSubmit":
42
+ return handleUserPromptSubmit(input, client);
43
+ case "SubagentStart":
44
+ return handleSubagentStart(input, client, deps);
45
+ case "SubagentStop":
46
+ return handleSubagentStop(input, client);
47
+ case "SessionEnd":
48
+ return handleSessionEnd(input, client);
49
+ default:
50
+ client.logger.debug("hook.passthrough", { event });
51
+ client.logger.activity("hook.passthrough", "skipped", { attributes: { event } });
52
+ return { continue: true };
53
+ }
54
+ });
55
+ });
51
56
  }
52
- // ─── SessionStart ───────────────────────────────────────────────────
53
57
  async function handleSessionStart(input, client, deps) {
54
58
  client.logger.info("lifecycle.session_start", {
55
59
  sessionId: input.session_id,
@@ -57,148 +61,49 @@ async function handleSessionStart(input, client, deps) {
57
61
  source: input.source,
58
62
  cwd: input.cwd,
59
63
  });
60
- client.tracer.record("session.start", "ok", {
61
- attributes: { model: input.model, source: input.source },
62
- });
63
- // Run the user login (interactive PKCE on first session, refresh
64
- // when needed). This runs every session and never blocks —
65
- // enforcement is governed solely by permissionMode at tool-call time.
66
- const userGate = deps.userLogin ?? argus_1.ensureUserAuthenticated;
67
- await userGate(client, {
64
+ await (0, argus_1.sessionStart)(client, {
65
+ harness: HARNESS,
66
+ sessionId: input.session_id,
68
67
  binName: "ory-claude",
69
- harness: "claude-code",
68
+ userLogin: deps.userLogin,
69
+ agentGate: deps.agentGate,
70
+ activityAttributes: { model: input.model, source: input.source },
70
71
  });
71
- // Resolve the agent identity (machine credentials) regardless of how
72
- // user auth went — it never blocks and attaches the agent's bearer
73
- // token to outgoing Ory API calls so the audit trail records who
74
- // acted on the user's behalf.
75
- const agentGate = deps.agentGate ?? argus_1.ensureAgentIdentity;
76
- await agentGate(client, { projectUrl: (0, argus_1.resolveConfig)().projectUrl, harness: "claude-code" });
77
- // Record the user→agent delegation so the audit trail captures that
78
- // this user authorized this agent for this session. Best-effort and
79
- // written at most once per install: requires both principals to be
80
- // populated, and any failure is logged and swallowed (fail-open —
81
- // delegation tracking is for audit, not enforcement).
82
- await (0, argus_1.writeUserDelegatesAgent)(client);
83
72
  return {};
84
73
  }
85
- // ─── PreToolUse ─────────────────────────────────────────────────────
86
- async function handlePreToolUse(input, client, deps = {}) {
74
+ async function handlePreToolUse(input, client, deps) {
87
75
  const toolName = input.tool_name ?? "unknown";
88
76
  client.logger.info("lifecycle.pre_tool_use", {
89
77
  sessionId: input.session_id,
90
78
  toolName,
91
79
  toolInput: input.tool_input,
92
80
  });
93
- const inputSummary = (0, argus_1.summarizeToolInput)(toolName, input.tool_input);
94
- // In audit-only mode, log the invocation but skip permission checks —
95
- // and skip sub-agent DCR/delegation writes too (Ory is disabled entirely).
96
- if ((0, argus_1.resolveConfig)().auditOnly) {
97
- client.tracer.record("tool.invoke", "ok", {
98
- attributes: { toolName, ...inputSummary },
99
- });
100
- return {};
101
- }
102
- // If the agent is invoking a sub-agent (Claude Code's "Task"/"Agent"
103
- // tool with a `subagent_type`), resolve a distinct OAuth2 identity for
104
- // that sub-agent via DCR and record the agent→sub-agent delegation
105
- // tuple. Best-effort — failures here never block the actual tool call.
106
- await maybeRegisterSubAgent(toolName, input.tool_input, client, deps);
107
- const subject = (0, argus_1.resolveUserSubject)(client, `session:${input.session_id}`);
108
- const subjectId = (0, argus_1.subjectLabel)(subject);
109
- const mcpTool = (0, argus_1.parseClaudeCodeMcpTool)(toolName);
110
- try {
111
- if (mcpTool) {
112
- const mcpResult = await (0, argus_1.checkMcpPermission)(client, mcpTool, {
113
- subject,
114
- spanAttributes: { toolName },
115
- });
116
- const mcpAttrs = {
117
- toolName,
118
- mcpServer: mcpTool.serverName,
119
- mcpTool: mcpTool.toolName,
120
- ...inputSummary,
121
- };
122
- const decision = (0, argus_1.applyPermissionMode)(client, mcpResult.allowed, {
123
- object: mcpTool.serverName,
124
- relation: "use",
125
- subjectId,
126
- ...("subjectSet" in subject ? { subjectSet: subject.subjectSet } : {}),
127
- spanAttributes: mcpAttrs,
128
- });
129
- const decisionAttrs = decision.spanAttributes;
130
- if (decision.kind === "allow") {
131
- client.tracer.record("tool.invoke", "ok", {
132
- attributes: { ...mcpAttrs, ...decisionAttrs },
133
- });
134
- return {};
135
- }
136
- if (decision.kind === "observe") {
137
- client.tracer.record("tool.block", "denied", {
138
- attributes: { ...mcpAttrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
139
- });
140
- client.tracer.record("tool.invoke", "ok", {
141
- attributes: { ...mcpAttrs, ...decisionAttrs, allowed: false, observed: true },
142
- });
143
- return {};
144
- }
145
- client.tracer.record("tool.block", "denied", {
146
- attributes: { ...mcpAttrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
147
- });
148
- return {
149
- decision: "block",
150
- reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }),
151
- };
152
- }
153
- const namespace = resolveNamespace();
154
- const outcome = await (0, argus_1.gateToolCall)(client, {
155
- harness: "claude-code",
156
- toolName,
157
- check: { namespace, object: toolName, relation: "use", ...subject },
158
- spanAttributes: { toolName },
159
- });
160
- // Interactive tools (AskUserQuestion, ExitPlanMode, TodoWrite, …)
161
- // surface UI to the user and aren't external-system tool executions.
162
- // gateToolCall has already recorded the `user.interaction` audit span;
163
- // we pass through with no `tool.invoke`/`tool.block` and let the
164
- // harness deliver the prompt to the user.
165
- if (outcome.kind === "interactive") {
166
- return {};
167
- }
168
- const decision = outcome;
169
- if (decision.kind === "fail_open") {
170
- return handlePermissionError(decision.error, toolName, client);
171
- }
172
- const attrs = { toolName, ...inputSummary };
173
- const decisionAttrs = decision.spanAttributes;
174
- if (decision.kind === "allow") {
175
- client.tracer.record("tool.invoke", "ok", {
176
- attributes: { ...attrs, ...decisionAttrs, allowed: true },
177
- });
178
- return {};
179
- }
180
- if (decision.kind === "observe") {
181
- client.tracer.record("tool.block", "denied", {
182
- attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
183
- });
184
- client.tracer.record("tool.invoke", "ok", {
185
- attributes: { ...attrs, ...decisionAttrs, allowed: false, observed: true },
186
- });
187
- return {};
188
- }
189
- client.tracer.record("tool.block", "denied", {
190
- attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
191
- });
192
- return {
193
- decision: "block",
194
- reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }),
195
- };
196
- }
197
- catch (err) {
198
- return handlePermissionError(err, toolName, client);
199
- }
81
+ const result = await (0, argus_1.gate)(client, {
82
+ harness: HARNESS,
83
+ toolName,
84
+ toolArgs: input.tool_input,
85
+ subjectFallback: `session:${input.session_id}`,
86
+ mcpTool: (0, argus_1.parseClaudeCodeMcpTool)(toolName) ?? undefined,
87
+ principals: actingSubagentPrincipals(input, client),
88
+ });
89
+ return result.blocked
90
+ ? { decision: "block", reason: result.denialMessage }
91
+ : {};
92
+ }
93
+ function actingSubagentPrincipals(input, client) {
94
+ const type = input.agent_type;
95
+ if (!input.agent_id || !type)
96
+ return undefined;
97
+ const subject = client.agentPrincipal.subject;
98
+ if (!subject)
99
+ return undefined;
100
+ return {
101
+ subAgentClientId: subject,
102
+ subAgentType: type,
103
+ perSpawnId: input.agent_id,
104
+ sessionId: input.session_id,
105
+ };
200
106
  }
201
- // ─── PostToolUse ────────────────────────────────────────────────────
202
107
  async function handlePostToolUse(input, client) {
203
108
  const toolName = input.tool_name ?? "unknown";
204
109
  client.logger.info("lifecycle.post_tool_use", {
@@ -206,17 +111,14 @@ async function handlePostToolUse(input, client) {
206
111
  toolName,
207
112
  toolResponse: input.tool_response,
208
113
  });
209
- client.tracer.record("tool.complete", "ok", {
210
- attributes: {
211
- toolName,
212
- toolUseId: input.tool_use_id,
213
- ...(0, argus_1.summarizeToolInput)(toolName, input.tool_input),
214
- ...(0, argus_1.summarizeToolOutput)(toolName, input.tool_response),
215
- },
114
+ (0, argus_1.complete)(client, {
115
+ toolName,
116
+ input: input.tool_input,
117
+ output: input.tool_response,
118
+ extraActivityAttributes: { toolUseId: input.tool_use_id },
216
119
  });
217
120
  return {};
218
121
  }
219
- // ─── PostToolUseFailure ─────────────────────────────────────────────
220
122
  async function handlePostToolUseFailure(input, client) {
221
123
  const toolName = input.tool_name ?? "unknown";
222
124
  client.logger.info("lifecycle.post_tool_use_failure", {
@@ -224,249 +126,121 @@ async function handlePostToolUseFailure(input, client) {
224
126
  toolName,
225
127
  toolUseId: input.tool_use_id,
226
128
  });
227
- client.tracer.record("tool.fail", "error", {
228
- attributes: {
229
- toolName,
230
- toolUseId: input.tool_use_id,
231
- toolError: typeof input.tool_error === "string"
232
- ? input.tool_error.slice(0, 500)
233
- : input.tool_error
234
- ? "[object]"
235
- : undefined,
236
- ...(0, argus_1.summarizeToolInput)(toolName, input.tool_input),
237
- },
129
+ (0, argus_1.fail)(client, {
130
+ toolName,
131
+ input: input.tool_input,
132
+ error: input.tool_error,
133
+ extraActivityAttributes: { toolUseId: input.tool_use_id },
238
134
  });
239
135
  return {};
240
136
  }
241
- // ─── UserPromptSubmit ──────────────────────────────────────────────
137
+ async function handlePermissionRequest(input, client) {
138
+ const toolName = input.tool_name ?? "unknown";
139
+ client.logger.info("lifecycle.permission_request", {
140
+ sessionId: input.session_id,
141
+ toolName,
142
+ toolInput: input.tool_input,
143
+ });
144
+ const mcpTool = (0, argus_1.parseClaudeCodeMcpTool)(toolName);
145
+ const result = await (0, argus_1.decideTool)(client, {
146
+ harness: HARNESS,
147
+ toolName,
148
+ toolArgs: input.tool_input,
149
+ subjectFallback: `session:${input.session_id}`,
150
+ mcpTool: mcpTool ?? undefined,
151
+ });
152
+ if (result.kind === "not_connected") {
153
+ return permissionRequestFallback("Ory Agent Security not connected — deferring to user");
154
+ }
155
+ if (result.kind === "interactive") {
156
+ return permissionRequestFallback("Interactive tool — deferring to user");
157
+ }
158
+ if (result.kind === "fail_open") {
159
+ return permissionRequestFallback(permissionFailureReason(result.decision));
160
+ }
161
+ if (result.kind === "deny") {
162
+ return permissionRequestDecision("deny", result.denialMessage ?? "Ory permission denied");
163
+ }
164
+ return permissionRequestDecision("allow", result.kind === "observe"
165
+ ? "Ory observe mode — denied by policy but allowed through"
166
+ : mcpTool
167
+ ? "Ory MCP server permission granted"
168
+ : "Ory permission granted");
169
+ }
170
+ function permissionRequestDecision(behavior, reason) {
171
+ return {
172
+ hookSpecificOutput: {
173
+ hookEventName: "PermissionRequest",
174
+ decision: { behavior },
175
+ message: reason,
176
+ },
177
+ };
178
+ }
179
+ function permissionRequestFallback(reason) {
180
+ return {
181
+ hookSpecificOutput: { hookEventName: "PermissionRequest", message: reason },
182
+ };
183
+ }
184
+ function permissionFailureReason(decision) {
185
+ if (decision.kind !== "fail_open")
186
+ return "Ory permission check failed";
187
+ const { code, message } = decision.error;
188
+ return code === "network_error" || code === "rate_limited"
189
+ ? `Ory unavailable (${code}), falling back to user prompt`
190
+ : `Ory permission check failed: ${message}`;
191
+ }
242
192
  async function handleUserPromptSubmit(input, client) {
243
193
  client.logger.info("lifecycle.user_prompt_submit", {
244
194
  sessionId: input.session_id,
245
195
  promptLen: input.prompt?.length,
246
196
  });
247
- client.tracer.record("user.prompt", "ok", {
197
+ client.logger.activity("user.prompt", "ok", {
248
198
  attributes: { promptLen: input.prompt?.length },
249
199
  });
250
200
  return {};
251
201
  }
252
- // ─── SubagentStart ─────────────────────────────────────────────────
253
- //
254
- // Dedicated sub-agent invocation event. Replaces the legacy path of
255
- // sniffing the `Task`/`Agent` tool name in PreToolUse — that still runs
256
- // as a fallback for older Claude Code versions, but registerSubAgent
257
- // is idempotent so duplicate registration is harmless.
258
202
  async function handleSubagentStart(input, client, deps) {
259
203
  const subAgentType = input.subagent_type ?? input.agent_type;
260
- const agentId = input.agent_id;
261
204
  client.logger.info("lifecycle.subagent_start", {
262
205
  sessionId: input.session_id,
263
206
  subAgentType,
264
- agentId,
207
+ agentId: input.agent_id,
265
208
  });
266
- client.tracer.record("subagent.start", "ok", {
267
- attributes: { subAgentType, agentId },
209
+ client.logger.activity("subagent.start", "ok", {
210
+ attributes: { subAgentType, agentId: input.agent_id },
268
211
  });
269
- if (subAgentType) {
270
- await registerSubAgent(subAgentType, client, deps);
271
- }
272
- else {
273
- client.logger.debug("subagent_start.no_type", {
274
- message: "SubagentStart event without subagent_type — skipping DCR.",
212
+ if (subAgentType && input.agent_id) {
213
+ await (0, argus_1.registerSubagent)(client, {
214
+ harness: HARNESS,
215
+ sessionId: input.session_id,
216
+ subAgentType,
217
+ perSpawnId: input.agent_id,
218
+ subAgentGate: deps.subAgentGate,
275
219
  });
276
220
  }
277
221
  return {};
278
222
  }
279
- // ─── SubagentStop ──────────────────────────────────────────────────
280
223
  async function handleSubagentStop(input, client) {
224
+ const subAgentType = input.subagent_type ?? input.agent_type;
281
225
  client.logger.info("lifecycle.subagent_stop", {
282
226
  sessionId: input.session_id,
283
- subAgentType: input.subagent_type ?? input.agent_type,
227
+ subAgentType,
284
228
  agentId: input.agent_id,
285
229
  });
286
- client.tracer.record("subagent.stop", "ok", {
230
+ client.logger.activity("subagent.stop", "ok", {
287
231
  attributes: {
288
- subAgentType: input.subagent_type ?? input.agent_type,
232
+ subAgentType,
289
233
  agentId: input.agent_id,
290
234
  responseLen: input.subagent_response?.length,
291
235
  },
292
236
  });
293
237
  return {};
294
238
  }
295
- // ─── SessionEnd ────────────────────────────────────────────────────
296
239
  async function handleSessionEnd(input, client) {
297
240
  client.logger.info("lifecycle.session_end", {
298
241
  sessionId: input.session_id,
299
242
  reason: input.reason,
300
243
  });
301
- client.tracer.record("session.end", "ok", {
302
- attributes: { reason: input.reason },
303
- });
304
- return {};
305
- }
306
- // ─── PermissionRequest ──────────────────────────────────────────────
307
- async function handlePermissionRequest(input, client) {
308
- const toolName = input.tool_name ?? "unknown";
309
- client.logger.info("lifecycle.permission_request", {
310
- sessionId: input.session_id,
311
- toolName,
312
- toolInput: input.tool_input,
313
- });
314
- // Audit-only mode: omit `decision` so Claude falls through to its normal
315
- // user-permission prompt.
316
- if ((0, argus_1.resolveConfig)().auditOnly) {
317
- return permissionRequestFallback("Audit-only mode — deferring to user");
318
- }
319
- // Interactive tools: the harness is about to surface UI to the user
320
- // (AskUserQuestion, ExitPlanMode, TodoWrite, …). Trace and fall through
321
- // — Ory shouldn't pre-answer a prompt that's meant for the human.
322
- if ((0, argus_1.isInteractiveTool)("claude-code", toolName)) {
323
- client.tracer.record("user.interaction", "ok", {
324
- attributes: { harness: "claude-code", toolName, kind: "permission-request" },
325
- });
326
- return permissionRequestFallback("Interactive tool — deferring to user");
327
- }
328
- const subject = (0, argus_1.resolveUserSubject)(client, `session:${input.session_id}`);
329
- const subjectId = (0, argus_1.subjectLabel)(subject);
330
- const mcpTool = (0, argus_1.parseClaudeCodeMcpTool)(toolName);
331
- try {
332
- if (mcpTool) {
333
- const mcpResult = await (0, argus_1.checkMcpPermission)(client, mcpTool, {
334
- subject,
335
- spanAttributes: { toolName },
336
- });
337
- const decision = (0, argus_1.applyPermissionMode)(client, mcpResult.allowed, {
338
- object: mcpTool.serverName,
339
- relation: "use",
340
- subjectId,
341
- ...("subjectSet" in subject ? { subjectSet: subject.subjectSet } : {}),
342
- spanAttributes: { toolName, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName },
343
- });
344
- if (decision.kind === "deny") {
345
- return permissionRequestDecision("deny", (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }));
346
- }
347
- return permissionRequestDecision("allow", decision.kind === "observe"
348
- ? "Ory observe mode — denied by policy but allowed through"
349
- : "Ory MCP server permission granted");
350
- }
351
- const namespace = resolveNamespace();
352
- const outcome = await (0, argus_1.gateToolCall)(client, {
353
- harness: "claude-code",
354
- toolName,
355
- check: { namespace, object: toolName, relation: "use", ...subject },
356
- spanAttributes: { toolName },
357
- });
358
- if (outcome.kind === "interactive") {
359
- return permissionRequestFallback("Interactive tool — deferring to user");
360
- }
361
- const decision = outcome;
362
- if (decision.kind === "fail_open") {
363
- const code = decision.error.code;
364
- const fallbackReason = code === "network_error" || code === "rate_limited"
365
- ? `Ory unavailable (${code}), falling back to user prompt`
366
- : `Ory permission check failed: ${decision.error.message}`;
367
- return permissionRequestFallback(fallbackReason);
368
- }
369
- if (decision.kind === "deny") {
370
- return permissionRequestDecision("deny", (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }));
371
- }
372
- return permissionRequestDecision("allow", decision.kind === "observe"
373
- ? "Ory observe mode — denied by policy but allowed through"
374
- : "Ory permission granted");
375
- }
376
- catch (err) {
377
- const oryErr = err;
378
- const fallbackReason = oryErr.code === "network_error" || oryErr.code === "rate_limited"
379
- ? `Ory unavailable (${oryErr.code}), falling back to user prompt`
380
- : `Ory permission check failed: ${oryErr.message}`;
381
- return permissionRequestFallback(fallbackReason);
382
- }
383
- }
384
- function permissionRequestDecision(behavior, reason) {
385
- return {
386
- hookSpecificOutput: {
387
- hookEventName: "PermissionRequest",
388
- decision: { behavior },
389
- message: reason,
390
- },
391
- };
392
- }
393
- function permissionRequestFallback(reason) {
394
- return {
395
- hookSpecificOutput: {
396
- hookEventName: "PermissionRequest",
397
- message: reason,
398
- },
399
- };
400
- }
401
- // ─── Helpers ────────────────────────────────────────────────────────
402
- function resolveNamespace() {
403
- return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
404
- }
405
- /**
406
- * Register an OAuth2 identity for a sub-agent and write the
407
- * `agent → subagent` delegation tuple. Fail-open and written at most once
408
- * per install, so the multiple call paths (SubagentStart event + Task-tool
409
- * fallback) won't duplicate state.
410
- */
411
- async function registerSubAgent(subAgentType, client, deps) {
412
- const subAgentGate = deps.subAgentGate ?? argus_1.ensureSubAgentIdentity;
413
- let identity;
414
- try {
415
- identity = await subAgentGate(client, {
416
- subAgentType,
417
- projectUrl: (0, argus_1.resolveConfig)().projectUrl,
418
- harness: "claude-code",
419
- });
420
- }
421
- catch (err) {
422
- client.logger.warn("subagent.identity.failed", {
423
- subAgentType,
424
- message: err instanceof Error ? err.message : String(err),
425
- });
426
- return;
427
- }
428
- if (identity.kind !== "dynamic" || !identity.subject)
429
- return;
430
- await (0, argus_1.writeAgentDelegatesSubagent)(client, identity.subject, subAgentType);
431
- }
432
- /**
433
- * Legacy fallback for older Claude Code versions that don't fire
434
- * `SubagentStart`: the sub-agent invocation appears as a `Task`/`Agent`
435
- * tool call in PreToolUse with a `subagent_type` field on tool_input.
436
- * `SubagentStart` is the preferred path; this is a no-op when both fire.
437
- */
438
- async function maybeRegisterSubAgent(toolName, toolInput, client, deps) {
439
- if (toolName !== "Task" && toolName !== "Agent")
440
- return;
441
- const subAgentType = typeof toolInput === "object"
442
- && toolInput !== null
443
- && typeof toolInput.subagent_type === "string"
444
- ? toolInput.subagent_type
445
- : undefined;
446
- if (!subAgentType)
447
- return;
448
- await registerSubAgent(subAgentType, client, deps);
449
- }
450
- function handlePermissionError(oryErr, toolName, client) {
451
- if (oryErr.code === "network_error") {
452
- client.logger.warn("permission.network_error", {
453
- toolName,
454
- message: "Ory unreachable, failing open",
455
- });
456
- return {};
457
- }
458
- if (oryErr.code === "rate_limited") {
459
- client.logger.warn("permission.rate_limited", {
460
- toolName,
461
- message: "Ory rate limited, failing open",
462
- });
463
- return {};
464
- }
465
- // For other errors, also fail open but log prominently
466
- client.logger.error("permission.check.error", {
467
- toolName,
468
- code: oryErr.code,
469
- message: oryErr.message,
470
- });
244
+ client.logger.activity("session.end", "ok", { attributes: { reason: input.reason } });
471
245
  return {};
472
246
  }