@ory/codex 0.13.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/handlers.js CHANGED
@@ -3,14 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.handleHookEvent = handleHookEvent;
4
4
  exports.shouldEmitOutput = shouldEmitOutput;
5
5
  const argus_1 = require("@ory/argus");
6
- /**
7
- * Route a Codex hook event to the appropriate Ory integration.
8
- *
9
- * Codex events: SessionStart, PreToolUse, PostToolUse, PermissionRequest,
10
- * UserPromptSubmit, Stop. Hooks are declared by the plugin (`hooks` in
11
- * plugin.json → hooks.json) and must be trusted by the user before they run.
12
- */
13
- async function handleHookEvent(input, client, deps = {}) {
6
+ const session_state_js_1 = require("./session-state.js");
7
+ const HARNESS = "codex";
8
+ const SPAWN_PARENT_FLUSH_TIMEOUT_MS = 500;
9
+ const SPAWN_IDENTITY_TIMEOUT_MS = 10_000;
10
+ function handleHookEvent(input, client, deps = {}) {
14
11
  const event = input.hook_event_name;
15
12
  client.logger.debug("hook.received", {
16
13
  event,
@@ -18,145 +15,54 @@ async function handleHookEvent(input, client, deps = {}) {
18
15
  toolName: input.tool_name,
19
16
  turnId: input.turn_id,
20
17
  });
21
- // Set trace context so all spans within this hook invocation correlate
22
- client.tracer.setContext({
23
- traceId: (0, argus_1.deriveTraceId)(input.session_id),
24
- sessionId: input.session_id,
25
- });
26
- try {
18
+ return (0, argus_1.withHookContext)(client, { sessionId: input.session_id }, async () => {
27
19
  switch (event) {
28
20
  case "SessionStart":
29
- return await handleSessionStart(input, client, deps);
21
+ return handleSessionStart(input, client, deps);
30
22
  case "PreToolUse":
31
- return await handlePreToolUse(input, client);
23
+ return handlePreToolUse(input, client);
32
24
  case "PostToolUse":
33
- return await handlePostToolUse(input, client);
25
+ return handlePostToolUse(input, client, deps);
34
26
  case "PermissionRequest":
35
- return await handlePermissionRequest(input, client);
27
+ return handlePermissionRequest(input, client);
36
28
  case "UserPromptSubmit":
37
- return await handleUserPromptSubmit(input, client);
29
+ return handleUserPromptSubmit(input, client, deps);
38
30
  case "Stop":
39
- return await handleStop(input, client);
31
+ return handleStop(input, client);
40
32
  default:
41
33
  client.logger.debug("hook.passthrough", { event });
42
- client.tracer.record("hook.passthrough", "skipped", {
43
- attributes: { event },
44
- });
34
+ client.logger.activity("hook.passthrough", "skipped", { attributes: { event } });
45
35
  return {};
46
36
  }
47
- }
48
- finally {
49
- client.tracer.clearContext();
50
- }
37
+ });
51
38
  }
52
- // ─── SessionStart ───────────────────────────────────────────────────
53
39
  async function handleSessionStart(input, client, deps) {
54
40
  client.logger.info("lifecycle.session_start", {
55
41
  sessionId: input.session_id,
56
42
  model: input.model,
57
43
  cwd: input.cwd,
58
44
  });
59
- client.tracer.record("session.start", "ok", {
60
- attributes: { model: input.model },
61
- });
62
- const userGate = deps.userLogin ?? argus_1.ensureUserAuthenticated;
63
- const decision = await userGate(client, {
64
- binName: "ory-codex",
65
- harness: "codex",
66
- allowBlock: true,
67
- });
68
- // Resolve the agent identity (machine credentials). Never blocks;
69
- // attaches the agent's bearer token to outgoing Ory API calls.
70
- const agentGate = deps.agentGate ?? argus_1.ensureAgentIdentity;
71
- await agentGate(client, { projectUrl: (0, argus_1.resolveConfig)().projectUrl, harness: "codex" });
72
- // Record the user→agent delegation so the audit trail captures that
73
- // this user authorized this agent for this session. Written at most once
74
- // per install. Best-effort: requires both principals to be populated, and
75
- // any failure is logged and swallowed (fail-open — delegation tracking is
76
- // for audit, not enforcement).
77
- await (0, argus_1.writeUserDelegatesAgent)(client);
78
- if (!decision.proceed) {
79
- return { decision: "block", reason: decision.reason };
80
- }
81
- if (decision.mode !== "disabled") {
82
- return {};
83
- }
84
- const resolved = (0, argus_1.resolveConfig)();
85
- if (resolved.auditOnly) {
86
- client.logger.info("config.audit_only", {
87
- message: "Audit-only mode enabled. Auth and permission checks are disabled.",
88
- });
89
- return {};
90
- }
91
- if (!resolved.projectUrl) {
92
- client.logger.warn("config.not_configured", {
93
- message: "Ory plugin is not configured. Auth and permission checks are disabled. " +
94
- "Run 'npx ory-codex configure' to connect to an Ory project.",
95
- });
96
- return {};
97
- }
98
- // Try session token first, then OAuth2 token
99
- const sessionToken = process.env.ORY_SESSION_TOKEN;
100
- const oauth2Token = process.env.ORY_OAUTH2_TOKEN;
101
- if (sessionToken) {
102
- await verifySessionToken(sessionToken, client);
103
- return {};
104
- }
105
- if (oauth2Token) {
106
- await verifyOAuth2Token(oauth2Token, client);
107
- return {};
108
- }
109
- client.logger.warn("session.no_credentials", {
110
- message: "Neither ORY_SESSION_TOKEN nor ORY_OAUTH2_TOKEN is set. " +
111
- "Skipping authentication.",
112
- });
45
+ await startSession(input, client, deps);
113
46
  return {};
114
47
  }
115
- async function verifySessionToken(token, client) {
116
- try {
117
- const session = await client.verifySession(token);
118
- if (!session.active) {
119
- client.logger.warn("session.inactive", {
120
- message: "Ory session is not active. Re-authenticate to enable auth checks.",
121
- });
122
- }
123
- }
124
- catch (err) {
125
- const oryErr = err;
126
- client.logger.warn("session.verify_failed", {
127
- code: oryErr.code,
128
- message: oryErr.message,
129
- });
130
- }
131
- }
132
- async function verifyOAuth2Token(token, client) {
48
+ async function startSession(input, client, deps) {
49
+ await (0, argus_1.sessionStart)(client, {
50
+ harness: HARNESS,
51
+ binName: "ory-codex",
52
+ userLogin: deps.userLogin,
53
+ agentGate: deps.agentGate,
54
+ activityAttributes: { model: input.model },
55
+ });
133
56
  try {
134
- const tokenInfo = await client.introspectToken(token);
135
- if (!tokenInfo.active) {
136
- client.logger.warn("oauth2.token_inactive", {
137
- message: "Ory OAuth2 token is not active. Obtain a new token to enable auth checks.",
138
- });
139
- return;
140
- }
141
- client.logger.info("oauth2.session_authenticated", {
142
- clientId: tokenInfo.clientId,
143
- subject: tokenInfo.subject,
144
- scope: tokenInfo.scope,
145
- });
57
+ (0, session_state_js_1.markCodexSessionStarted)(input.session_id);
146
58
  }
147
59
  catch (err) {
148
- const oryErr = err;
149
- client.logger.warn("oauth2.introspect_failed", {
150
- code: oryErr.code,
151
- message: oryErr.message,
60
+ client.logger.warn("session_start.marker_write_failed", {
61
+ message: err instanceof Error ? err.message : String(err),
152
62
  });
153
63
  }
154
64
  }
155
- // ─── PreToolUse ─────────────────────────────────────────────────────
156
65
  async function handlePreToolUse(input, client) {
157
- // No default. Codex always sets `tool_name` to the canonical value
158
- // (`Bash`, `apply_patch`, or `mcp__<server>__<tool>`). Defaulting to
159
- // `"Bash"` here would silently mislabel apply_patch and MCP calls.
160
66
  const toolName = input.tool_name;
161
67
  if (!toolName) {
162
68
  client.logger.warn("pre_tool_use.missing_tool_name", {
@@ -170,112 +76,21 @@ async function handlePreToolUse(input, client) {
170
76
  toolName,
171
77
  toolInput: input.tool_input,
172
78
  });
173
- const inputSummary = (0, argus_1.summarizeToolInput)(toolName, input.tool_input);
174
- // In audit-only mode, log the invocation but skip permission checks
175
- if ((0, argus_1.resolveConfig)().auditOnly) {
176
- client.tracer.record("tool.invoke", "ok", {
177
- attributes: { toolName, ...inputSummary },
178
- });
179
- return {};
180
- }
181
- const subject = (0, argus_1.resolveUserSubject)(client, `session:${input.session_id}`);
182
- const subjectId = (0, argus_1.subjectLabel)(subject);
183
- const mcpTool = (0, argus_1.parseMcpToolGeneric)(toolName);
184
- try {
185
- if (mcpTool) {
186
- const mcpResult = await (0, argus_1.checkMcpPermission)(client, mcpTool, {
187
- subject,
188
- spanAttributes: { toolName },
189
- });
190
- const mcpAttrs = {
191
- toolName,
192
- mcpServer: mcpTool.serverName,
193
- mcpTool: mcpTool.toolName,
194
- ...inputSummary,
195
- };
196
- const decision = (0, argus_1.applyPermissionMode)(client, mcpResult.allowed, {
197
- object: mcpTool.serverName,
198
- relation: "use",
199
- subjectId,
200
- ...("subjectSet" in subject ? { subjectSet: subject.subjectSet } : {}),
201
- spanAttributes: mcpAttrs,
202
- });
203
- const decisionAttrs = decision.spanAttributes;
204
- if (decision.kind === "allow") {
205
- client.tracer.record("tool.invoke", "ok", {
206
- attributes: { ...mcpAttrs, ...decisionAttrs },
207
- });
208
- return {};
209
- }
210
- if (decision.kind === "observe") {
211
- client.tracer.record("tool.block", "denied", {
212
- attributes: { ...mcpAttrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
213
- });
214
- client.tracer.record("tool.invoke", "ok", {
215
- attributes: { ...mcpAttrs, ...decisionAttrs, allowed: false, observed: true },
216
- });
217
- return {};
218
- }
219
- client.tracer.record("tool.block", "denied", {
220
- attributes: { ...mcpAttrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
221
- });
222
- return {
223
- decision: "block",
224
- reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }),
225
- };
226
- }
227
- const namespace = resolveNamespace();
228
- const outcome = await (0, argus_1.gateToolCall)(client, {
229
- harness: "codex",
230
- toolName,
231
- check: { namespace, object: toolName, relation: "use", ...subject },
232
- spanAttributes: { toolName },
233
- });
234
- // Interactive tools (operator-extensible via ORY_INTERACTIVE_TOOLS):
235
- // pass through with the user.interaction span already recorded.
236
- if (outcome.kind === "interactive") {
237
- return {};
238
- }
239
- const decision = outcome;
240
- if (decision.kind === "fail_open") {
241
- return handlePermissionError(decision.error, toolName, client);
242
- }
243
- const attrs = { toolName, ...inputSummary };
244
- const decisionAttrs = decision.spanAttributes;
245
- if (decision.kind === "allow") {
246
- client.tracer.record("tool.invoke", "ok", {
247
- attributes: { ...attrs, ...decisionAttrs, allowed: true },
248
- });
249
- return {};
250
- }
251
- if (decision.kind === "observe") {
252
- client.tracer.record("tool.block", "denied", {
253
- attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(false) },
254
- });
255
- client.tracer.record("tool.invoke", "ok", {
256
- attributes: { ...attrs, ...decisionAttrs, allowed: false, observed: true },
257
- });
258
- return {};
259
- }
260
- client.tracer.record("tool.block", "denied", {
261
- attributes: { ...attrs, ...decisionAttrs, allowed: false, ...(0, argus_1.alertAttributes)(true) },
262
- });
263
- return {
264
- decision: "block",
265
- reason: (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }),
266
- };
267
- }
268
- catch (err) {
269
- return handlePermissionError(err, toolName, client);
270
- }
79
+ const result = await (0, argus_1.gate)(client, {
80
+ harness: HARNESS,
81
+ toolName,
82
+ toolArgs: input.tool_input,
83
+ subjectFallback: `session:${input.session_id}`,
84
+ mcpTool: (0, argus_1.parseMcpToolGeneric)(toolName) ?? undefined,
85
+ });
86
+ return result.blocked
87
+ ? { decision: "block", reason: result.denialMessage }
88
+ : {};
271
89
  }
272
- // ─── PostToolUse ────────────────────────────────────────────────────
273
- async function handlePostToolUse(input, client) {
90
+ async function handlePostToolUse(input, client, deps) {
274
91
  const toolName = input.tool_name;
275
92
  if (!toolName) {
276
- client.logger.warn("post_tool_use.missing_tool_name", {
277
- sessionId: input.session_id,
278
- });
93
+ client.logger.warn("post_tool_use.missing_tool_name", { sessionId: input.session_id });
279
94
  return {};
280
95
  }
281
96
  client.logger.info("lifecycle.post_tool_use", {
@@ -284,24 +99,85 @@ async function handlePostToolUse(input, client) {
284
99
  turnId: input.turn_id,
285
100
  toolUseId: input.tool_use_id,
286
101
  });
287
- client.tracer.record("tool.complete", "ok", {
288
- attributes: {
289
- toolName,
290
- turnId: input.turn_id,
291
- toolUseId: input.tool_use_id,
292
- ...(0, argus_1.summarizeToolInput)(toolName, input.tool_input),
293
- ...(0, argus_1.summarizeToolOutput)(toolName, input.tool_response),
294
- },
102
+ const collaborationSpawn = isCollaborationSpawn(toolName);
103
+ const spawnSignal = collaborationSpawn
104
+ ? AbortSignal.timeout(SPAWN_IDENTITY_TIMEOUT_MS)
105
+ : undefined;
106
+ if (collaborationSpawn) {
107
+ await (0, argus_1.ensureReadCredential)(client, {
108
+ harness: HARNESS,
109
+ agentGate: deps.agentGate,
110
+ signal: spawnSignal,
111
+ });
112
+ }
113
+ (0, argus_1.complete)(client, {
114
+ toolName,
115
+ input: input.tool_input,
116
+ output: input.tool_response,
117
+ extraActivityAttributes: { turnId: input.turn_id, toolUseId: input.tool_use_id },
295
118
  });
119
+ if (collaborationSpawn) {
120
+ // Keep the spawn tool's events attributed to the parent before switching
121
+ // the reporter to the newly authenticated Sub-Agent.
122
+ await client.flushEvents(SPAWN_PARENT_FLUSH_TIMEOUT_MS);
123
+ const subAgentType = "collaboration";
124
+ let subAgentKind;
125
+ let subAgentReason;
126
+ let subAgentSubject;
127
+ let subAgentToken;
128
+ const subAgentGate = deps.subAgentGate ?? argus_1.ensureSubAgentIdentity;
129
+ await (0, argus_1.registerSubagent)(client, {
130
+ harness: HARNESS,
131
+ subAgentType,
132
+ perSpawnId: input.tool_use_id,
133
+ signal: spawnSignal,
134
+ subAgentGate: async (gateClient, options) => {
135
+ const identity = await subAgentGate(gateClient, {
136
+ ...options,
137
+ emitActivity: false,
138
+ });
139
+ subAgentKind = identity.kind;
140
+ subAgentReason = identity.reason;
141
+ subAgentSubject = identity.subject;
142
+ subAgentToken = identity.token;
143
+ return identity;
144
+ },
145
+ });
146
+ if (subAgentKind === "runtime" || (subAgentSubject && subAgentToken)) {
147
+ if (subAgentSubject && subAgentToken) {
148
+ client.setAgentPrincipal({ subject: subAgentSubject, token: subAgentToken });
149
+ }
150
+ client.logger.activity("agent.auth", "ok", {
151
+ attributes: {
152
+ kind: subAgentKind === "runtime" ? "subagent_runtime" : "subagent_dynamic",
153
+ subAgentType,
154
+ ...(subAgentSubject ? { subject: subAgentSubject } : {}),
155
+ },
156
+ });
157
+ client.logger.activity("subagent.start", "ok", {
158
+ attributes: { subAgentType, agentId: input.tool_use_id },
159
+ });
160
+ }
161
+ else {
162
+ client.logger.activity("agent.auth", "skipped", {
163
+ attributes: {
164
+ kind: subAgentKind === "dynamic" ? "subagent_dynamic" : "subagent_none",
165
+ subAgentType,
166
+ reason: subAgentReason,
167
+ },
168
+ });
169
+ }
170
+ }
296
171
  return {};
297
172
  }
298
- // ─── PermissionRequest ──────────────────────────────────────────────
173
+ function isCollaborationSpawn(toolName) {
174
+ const normalized = toolName.toLowerCase().replace(/[^a-z0-9]/g, "");
175
+ return normalized === "collaborationspawnagent" || normalized === "spawnagent";
176
+ }
299
177
  async function handlePermissionRequest(input, client) {
300
178
  const toolName = input.tool_name;
301
179
  if (!toolName) {
302
- client.logger.warn("permission_request.missing_tool_name", {
303
- sessionId: input.session_id,
304
- });
180
+ client.logger.warn("permission_request.missing_tool_name", { sessionId: input.session_id });
305
181
  return {};
306
182
  }
307
183
  client.logger.info("lifecycle.permission_request", {
@@ -309,71 +185,31 @@ async function handlePermissionRequest(input, client) {
309
185
  toolName,
310
186
  turnId: input.turn_id,
311
187
  });
312
- if ((0, argus_1.resolveConfig)().auditOnly) {
313
- return permissionRequestFallback("Audit-only mode — deferring to user");
188
+ const mcpTool = (0, argus_1.parseMcpToolGeneric)(toolName);
189
+ const result = await (0, argus_1.decideTool)(client, {
190
+ harness: HARNESS,
191
+ toolName,
192
+ toolArgs: input.tool_input,
193
+ subjectFallback: `session:${input.session_id}`,
194
+ mcpTool: mcpTool ?? undefined,
195
+ });
196
+ if (result.kind === "not_connected") {
197
+ return permissionRequestFallback("Ory Agent Security not connected — deferring to user");
314
198
  }
315
- // Interactive tools: defer to the user instead of pre-answering.
316
- if ((0, argus_1.isInteractiveTool)("codex", toolName)) {
317
- client.tracer.record("user.interaction", "ok", {
318
- attributes: { harness: "codex", toolName, kind: "permission-request" },
319
- });
199
+ if (result.kind === "interactive") {
320
200
  return permissionRequestFallback("Interactive tool — deferring to user");
321
201
  }
322
- const subject = (0, argus_1.resolveUserSubject)(client, `session:${input.session_id}`);
323
- const subjectId = (0, argus_1.subjectLabel)(subject);
324
- const mcpTool = (0, argus_1.parseMcpToolGeneric)(toolName);
325
- try {
326
- if (mcpTool) {
327
- const mcpResult = await (0, argus_1.checkMcpPermission)(client, mcpTool, {
328
- subject,
329
- spanAttributes: { toolName },
330
- });
331
- const decision = (0, argus_1.applyPermissionMode)(client, mcpResult.allowed, {
332
- object: mcpTool.serverName,
333
- relation: "use",
334
- subjectId,
335
- ...("subjectSet" in subject ? { subjectSet: subject.subjectSet } : {}),
336
- spanAttributes: { toolName, mcpServer: mcpTool.serverName, mcpTool: mcpTool.toolName },
337
- });
338
- if (decision.kind === "deny") {
339
- return permissionRequestDecision("deny", (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, mcp: mcpTool }));
340
- }
341
- return permissionRequestDecision("allow", decision.kind === "observe"
342
- ? "Ory observe mode — denied by policy but allowed through"
343
- : "Ory MCP server permission granted");
344
- }
345
- const namespace = resolveNamespace();
346
- const outcome = await (0, argus_1.gateToolCall)(client, {
347
- harness: "codex",
348
- toolName,
349
- check: { namespace, object: toolName, relation: "use", ...subject },
350
- spanAttributes: { toolName },
351
- });
352
- if (outcome.kind === "interactive") {
353
- return permissionRequestFallback("Interactive tool — deferring to user");
354
- }
355
- const decision = outcome;
356
- if (decision.kind === "fail_open") {
357
- const code = decision.error.code;
358
- const reason = code === "network_error" || code === "rate_limited"
359
- ? `Ory unavailable (${code}), falling back to user prompt`
360
- : `Ory permission check failed: ${decision.error.message}`;
361
- return permissionRequestFallback(reason);
362
- }
363
- if (decision.kind === "deny") {
364
- return permissionRequestDecision("deny", (0, argus_1.formatDenialMessage)({ tool: toolName, subjectId, namespace }));
365
- }
366
- return permissionRequestDecision("allow", decision.kind === "observe"
367
- ? "Ory observe mode — denied by policy but allowed through"
368
- : "Ory permission granted");
202
+ if (result.kind === "fail_open") {
203
+ return permissionRequestFallback(permissionFailureReason(result.decision));
369
204
  }
370
- catch (err) {
371
- const oryErr = err;
372
- const reason = oryErr.code === "network_error" || oryErr.code === "rate_limited"
373
- ? `Ory unavailable (${oryErr.code}), falling back to user prompt`
374
- : `Ory permission check failed: ${oryErr.message}`;
375
- return permissionRequestFallback(reason);
205
+ if (result.kind === "deny") {
206
+ return permissionRequestDecision("deny", result.denialMessage ?? "Ory permission denied");
376
207
  }
208
+ return permissionRequestDecision("allow", result.kind === "observe"
209
+ ? "Ory observe mode — denied by policy but allowed through"
210
+ : mcpTool
211
+ ? "Ory MCP server permission granted"
212
+ : "Ory permission granted");
377
213
  }
378
214
  function permissionRequestDecision(behavior, message) {
379
215
  return {
@@ -384,36 +220,48 @@ function permissionRequestDecision(behavior, message) {
384
220
  };
385
221
  }
386
222
  function permissionRequestFallback(message) {
387
- return {
388
- hookSpecificOutput: {
389
- hookEventName: "PermissionRequest",
390
- message,
391
- },
392
- };
223
+ return { hookSpecificOutput: { hookEventName: "PermissionRequest", message } };
224
+ }
225
+ function permissionFailureReason(decision) {
226
+ if (decision.kind !== "fail_open")
227
+ return "Ory permission check failed";
228
+ const { code, message } = decision.error;
229
+ return code === "network_error" || code === "rate_limited"
230
+ ? `Ory unavailable (${code}), falling back to user prompt`
231
+ : `Ory permission check failed: ${message}`;
393
232
  }
394
- // ─── UserPromptSubmit ───────────────────────────────────────────────
395
- async function handleUserPromptSubmit(input, client) {
233
+ async function handleUserPromptSubmit(input, client, deps) {
234
+ let sessionStarted = false;
235
+ try {
236
+ sessionStarted = (0, session_state_js_1.hasCodexSessionStarted)(input.session_id);
237
+ }
238
+ catch (err) {
239
+ client.logger.warn("session_start.marker_read_failed", {
240
+ message: err instanceof Error ? err.message : String(err),
241
+ });
242
+ }
243
+ if (!sessionStarted) {
244
+ // Codex consumes SessionStart only once. If the plugin was installed or
245
+ // trusted afterward, recover before processing the next prompt.
246
+ await startSession(input, client, deps);
247
+ }
396
248
  client.logger.info("lifecycle.user_prompt_submit", {
397
249
  sessionId: input.session_id,
398
250
  turnId: input.turn_id,
399
251
  promptLen: input.prompt?.length,
400
252
  });
401
- client.tracer.record("user.prompt", "ok", {
402
- attributes: {
403
- turnId: input.turn_id,
404
- promptLen: input.prompt?.length,
405
- },
253
+ client.logger.activity("user.prompt", "ok", {
254
+ attributes: { turnId: input.turn_id, promptLen: input.prompt?.length },
406
255
  });
407
256
  return {};
408
257
  }
409
- // ─── Stop ───────────────────────────────────────────────────────────
410
258
  async function handleStop(input, client) {
411
259
  client.logger.info("lifecycle.stop", {
412
260
  sessionId: input.session_id,
413
261
  turnId: input.turn_id,
414
262
  stopHookActive: input.stop_hook_active,
415
263
  });
416
- client.tracer.record("turn.stop", "ok", {
264
+ client.logger.activity("turn.stop", "ok", {
417
265
  attributes: {
418
266
  turnId: input.turn_id,
419
267
  stopHookActive: input.stop_hook_active,
@@ -422,42 +270,6 @@ async function handleStop(input, client) {
422
270
  });
423
271
  return {};
424
272
  }
425
- // ─── Helpers ────────────────────────────────────────────────────────
426
- /**
427
- * Whether a hook output carries anything Codex needs to see on stdout.
428
- *
429
- * The hook entry point handles `decision === "block"` (exit code 2 + stdout)
430
- * before consulting this. Everything else with at least one populated field
431
- * must be written to stdout — most importantly `hookSpecificOutput`, which
432
- * carries the PermissionRequest allow/deny decision. Empty outputs stay
433
- * silent to keep stdout clean for the harness.
434
- */
435
273
  function shouldEmitOutput(output) {
436
274
  return Object.values(output).some((value) => value !== undefined);
437
275
  }
438
- function resolveNamespace() {
439
- return process.env.ORY_PERMISSION_NAMESPACE ?? "AgentTools";
440
- }
441
- function handlePermissionError(oryErr, toolName, client) {
442
- if (oryErr.code === "network_error") {
443
- client.logger.warn("permission.network_error", {
444
- toolName,
445
- message: "Ory unreachable, failing open",
446
- });
447
- return {};
448
- }
449
- if (oryErr.code === "rate_limited") {
450
- client.logger.warn("permission.rate_limited", {
451
- toolName,
452
- message: "Ory rate limited, failing open",
453
- });
454
- return {};
455
- }
456
- // For other errors, also fail open but log prominently
457
- client.logger.error("permission.check.error", {
458
- toolName,
459
- code: oryErr.code,
460
- message: oryErr.message,
461
- });
462
- return {};
463
- }