@kodelyth/codex 2026.5.42 → 2026.6.2

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 (138) hide show
  1. package/package.json +17 -2
  2. package/doctor-contract-api.test.ts +0 -44
  3. package/doctor-contract-api.ts +0 -68
  4. package/harness.ts +0 -72
  5. package/index.test.ts +0 -230
  6. package/index.ts +0 -66
  7. package/media-understanding-provider.test.ts +0 -486
  8. package/media-understanding-provider.ts +0 -521
  9. package/prompt-overlay-runtime-contract.test.ts +0 -48
  10. package/prompt-overlay.ts +0 -21
  11. package/provider-catalog.ts +0 -83
  12. package/provider-discovery.ts +0 -45
  13. package/provider.test.ts +0 -384
  14. package/provider.ts +0 -243
  15. package/src/app-server/app-inventory-cache.test.ts +0 -176
  16. package/src/app-server/app-inventory-cache.ts +0 -324
  17. package/src/app-server/approval-bridge.test.ts +0 -1471
  18. package/src/app-server/approval-bridge.ts +0 -1211
  19. package/src/app-server/auth-bridge.test.ts +0 -1449
  20. package/src/app-server/auth-bridge.ts +0 -614
  21. package/src/app-server/auth-profile-runtime-contract.test.ts +0 -239
  22. package/src/app-server/capabilities.ts +0 -27
  23. package/src/app-server/client-factory.ts +0 -24
  24. package/src/app-server/client.test.ts +0 -563
  25. package/src/app-server/client.ts +0 -715
  26. package/src/app-server/compact.test.ts +0 -710
  27. package/src/app-server/compact.ts +0 -500
  28. package/src/app-server/computer-use.test.ts +0 -788
  29. package/src/app-server/computer-use.ts +0 -683
  30. package/src/app-server/config.test.ts +0 -879
  31. package/src/app-server/config.ts +0 -1038
  32. package/src/app-server/context-engine-projection.test.ts +0 -252
  33. package/src/app-server/context-engine-projection.ts +0 -403
  34. package/src/app-server/delivery-no-reply-runtime-contract.test.ts +0 -80
  35. package/src/app-server/dynamic-tool-diagnostics.ts +0 -73
  36. package/src/app-server/dynamic-tool-profile.ts +0 -69
  37. package/src/app-server/dynamic-tools.test.ts +0 -1302
  38. package/src/app-server/dynamic-tools.ts +0 -623
  39. package/src/app-server/elicitation-bridge.test.ts +0 -1056
  40. package/src/app-server/elicitation-bridge.ts +0 -783
  41. package/src/app-server/event-projector.test.ts +0 -2668
  42. package/src/app-server/event-projector.ts +0 -2057
  43. package/src/app-server/image-payload-sanitizer.test.ts +0 -49
  44. package/src/app-server/image-payload-sanitizer.ts +0 -167
  45. package/src/app-server/klaw-owned-tool-runtime-contract.test.ts +0 -456
  46. package/src/app-server/local-runtime-attribution.ts +0 -39
  47. package/src/app-server/managed-binary.test.ts +0 -139
  48. package/src/app-server/managed-binary.ts +0 -193
  49. package/src/app-server/models.test.ts +0 -246
  50. package/src/app-server/models.ts +0 -172
  51. package/src/app-server/native-hook-relay.test.ts +0 -271
  52. package/src/app-server/native-hook-relay.ts +0 -150
  53. package/src/app-server/native-subagent-task-mirror.test.ts +0 -573
  54. package/src/app-server/native-subagent-task-mirror.ts +0 -497
  55. package/src/app-server/outcome-fallback-runtime-contract.test.ts +0 -404
  56. package/src/app-server/plugin-activation.test.ts +0 -336
  57. package/src/app-server/plugin-activation.ts +0 -283
  58. package/src/app-server/plugin-app-cache-key.ts +0 -74
  59. package/src/app-server/plugin-approval-roundtrip.ts +0 -122
  60. package/src/app-server/plugin-inventory.test.ts +0 -355
  61. package/src/app-server/plugin-inventory.ts +0 -357
  62. package/src/app-server/plugin-thread-config.test.ts +0 -865
  63. package/src/app-server/plugin-thread-config.ts +0 -455
  64. package/src/app-server/protocol-generated/json/DynamicToolCallParams.json +0 -33
  65. package/src/app-server/protocol-generated/json/v2/ErrorNotification.json +0 -199
  66. package/src/app-server/protocol-generated/json/v2/GetAccountResponse.json +0 -102
  67. package/src/app-server/protocol-generated/json/v2/ModelListResponse.json +0 -227
  68. package/src/app-server/protocol-generated/json/v2/ThreadResumeResponse.json +0 -2630
  69. package/src/app-server/protocol-generated/json/v2/ThreadStartResponse.json +0 -2630
  70. package/src/app-server/protocol-generated/json/v2/TurnCompletedNotification.json +0 -1659
  71. package/src/app-server/protocol-generated/json/v2/TurnStartResponse.json +0 -1655
  72. package/src/app-server/protocol-validators.test.ts +0 -75
  73. package/src/app-server/protocol-validators.ts +0 -203
  74. package/src/app-server/protocol.ts +0 -520
  75. package/src/app-server/rate-limit-cache.ts +0 -48
  76. package/src/app-server/rate-limits.test.ts +0 -202
  77. package/src/app-server/rate-limits.ts +0 -583
  78. package/src/app-server/request.ts +0 -73
  79. package/src/app-server/run-attempt.context-engine.test.ts +0 -1004
  80. package/src/app-server/run-attempt.test.ts +0 -9477
  81. package/src/app-server/run-attempt.ts +0 -4683
  82. package/src/app-server/run-attempt.vision-tools.test.ts +0 -35
  83. package/src/app-server/schema-normalization-runtime-contract.test.ts +0 -206
  84. package/src/app-server/session-binding.test.ts +0 -303
  85. package/src/app-server/session-binding.ts +0 -398
  86. package/src/app-server/session-history.ts +0 -44
  87. package/src/app-server/shared-client.test.ts +0 -589
  88. package/src/app-server/shared-client.ts +0 -289
  89. package/src/app-server/side-question.test.ts +0 -1175
  90. package/src/app-server/side-question.ts +0 -1007
  91. package/src/app-server/test-support.ts +0 -48
  92. package/src/app-server/thread-lifecycle.test.ts +0 -447
  93. package/src/app-server/thread-lifecycle.ts +0 -939
  94. package/src/app-server/thread-lifecycle.user-mcp-servers.test.ts +0 -442
  95. package/src/app-server/timeout.ts +0 -9
  96. package/src/app-server/tool-progress-normalization.ts +0 -77
  97. package/src/app-server/trajectory.test.ts +0 -205
  98. package/src/app-server/trajectory.ts +0 -365
  99. package/src/app-server/transcript-mirror.test.ts +0 -524
  100. package/src/app-server/transcript-mirror.ts +0 -208
  101. package/src/app-server/transcript-repair-runtime-contract.test.ts +0 -44
  102. package/src/app-server/transport-stdio.test.ts +0 -171
  103. package/src/app-server/transport-stdio.ts +0 -107
  104. package/src/app-server/transport-websocket.test.ts +0 -69
  105. package/src/app-server/transport-websocket.ts +0 -90
  106. package/src/app-server/transport.ts +0 -117
  107. package/src/app-server/user-input-bridge.test.ts +0 -249
  108. package/src/app-server/user-input-bridge.ts +0 -316
  109. package/src/app-server/version.ts +0 -4
  110. package/src/app-server/vision-tools.ts +0 -12
  111. package/src/command-account.ts +0 -544
  112. package/src/command-formatters.ts +0 -425
  113. package/src/command-handlers.ts +0 -2004
  114. package/src/command-rpc.test.ts +0 -16
  115. package/src/command-rpc.ts +0 -142
  116. package/src/commands.test.ts +0 -3312
  117. package/src/commands.ts +0 -65
  118. package/src/conversation-binding-data.ts +0 -124
  119. package/src/conversation-binding.test.ts +0 -599
  120. package/src/conversation-binding.ts +0 -561
  121. package/src/conversation-control.test.ts +0 -126
  122. package/src/conversation-control.ts +0 -303
  123. package/src/conversation-turn-collector.test.ts +0 -191
  124. package/src/conversation-turn-collector.ts +0 -186
  125. package/src/conversation-turn-input.test.ts +0 -141
  126. package/src/conversation-turn-input.ts +0 -106
  127. package/src/manifest.test.ts +0 -20
  128. package/src/migration/apply.ts +0 -501
  129. package/src/migration/helpers.ts +0 -55
  130. package/src/migration/plan.ts +0 -461
  131. package/src/migration/provider.test.ts +0 -1741
  132. package/src/migration/provider.ts +0 -41
  133. package/src/migration/source.ts +0 -643
  134. package/src/migration/targets.ts +0 -25
  135. package/src/node-cli-sessions.test.ts +0 -180
  136. package/src/node-cli-sessions.ts +0 -711
  137. package/test-api.ts +0 -82
  138. package/tsconfig.json +0 -16
@@ -1,1211 +0,0 @@
1
- import {
2
- type AgentApprovalEventData,
3
- buildAgentHookContextChannelFields,
4
- formatApprovalDisplayPath,
5
- hasNativeHookRelayInvocation,
6
- invokeNativeHookRelay,
7
- type EmbeddedRunAttemptParams,
8
- type NativeHookRelayProcessResponse,
9
- type NativeHookRelayRegistrationHandle,
10
- runBeforeToolCallHook,
11
- } from "klaw/plugin-sdk/agent-harness-runtime";
12
- import { formatCodexDisplayText } from "../command-formatters.js";
13
- import {
14
- approvalRequestExplicitlyUnavailable,
15
- mapExecDecisionToOutcome,
16
- requestPluginApproval,
17
- type AppServerApprovalOutcome,
18
- waitForPluginApprovalDecision,
19
- } from "./plugin-approval-roundtrip.js";
20
- import { isJsonObject, type JsonObject, type JsonValue } from "./protocol.js";
21
-
22
- const PERMISSION_DESCRIPTION_MAX_LENGTH = 700;
23
- const PERMISSION_SAMPLE_LIMIT = 2;
24
- const PERMISSION_VALUE_MAX_LENGTH = 48;
25
- const COMMAND_PREVIEW_WITH_DETAILS_MAX_LENGTH = 80;
26
- const APPROVAL_PREVIEW_SCAN_MAX_LENGTH = 4096;
27
- const APPROVAL_PREVIEW_OMITTED = "[preview truncated or unsafe content omitted]";
28
- const ANSI_OSC_SEQUENCE_RE = new RegExp(
29
- String.raw`(?:\u001b]|\u009d)[^\u001b\u009c\u0007]*(?:\u0007|\u001b\\|\u009c)`,
30
- "g",
31
- );
32
- const ANSI_CONTROL_SEQUENCE_RE = new RegExp(
33
- String.raw`(?:\u001b\[[0-?]*[ -/]*[@-~]|\u009b[0-?]*[ -/]*[@-~]|\u001b[@-Z\\-_])`,
34
- "g",
35
- );
36
- const CONTROL_CHARACTER_RE = new RegExp(String.raw`[\u0000-\u001f\u007f-\u009f]+`, "g");
37
- const INVISIBLE_FORMATTING_CONTROL_RE = new RegExp(
38
- String.raw`[\u00ad\u034f\u061c\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff\ufe00-\ufe0f\u{e0100}-\u{e01ef}]`,
39
- "gu",
40
- );
41
- const DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE = new RegExp(
42
- String.raw`(?:\u001b\][^\u001b\u009c\u0007]*|\u009d[^\u001b\u009c\u0007]*|\u001b\[[0-?]*[ -/]*|\u009b[0-?]*[ -/]*|\u001b)$`,
43
- );
44
-
45
- type ApprovalPreviewSource = {
46
- value: string;
47
- clipped: boolean;
48
- };
49
-
50
- type SanitizedApprovalPreview = {
51
- text?: string;
52
- omitted: boolean;
53
- };
54
-
55
- export async function handleCodexAppServerApprovalRequest(params: {
56
- method: string;
57
- requestParams: JsonValue | undefined;
58
- paramsForRun: EmbeddedRunAttemptParams;
59
- threadId: string;
60
- turnId: string;
61
- nativeHookRelay?: Pick<NativeHookRelayRegistrationHandle, "allowedEvents" | "relayId">;
62
- signal?: AbortSignal;
63
- }): Promise<JsonValue | undefined> {
64
- const requestParams = isJsonObject(params.requestParams) ? params.requestParams : undefined;
65
- if (!matchesCurrentTurn(requestParams, params.threadId, params.turnId)) {
66
- return undefined;
67
- }
68
- if (!isSupportedAppServerApprovalMethod(params.method)) {
69
- return unsupportedApprovalResponse();
70
- }
71
-
72
- const context = buildApprovalContext({
73
- method: params.method,
74
- requestParams,
75
- paramsForRun: params.paramsForRun,
76
- });
77
-
78
- try {
79
- const policyOutcome = await runKlawToolPolicyForApprovalRequest({
80
- method: params.method,
81
- requestParams,
82
- paramsForRun: params.paramsForRun,
83
- context,
84
- nativeHookRelay: params.nativeHookRelay,
85
- signal: params.signal,
86
- });
87
- if (policyOutcome?.outcome === "denied") {
88
- emitApprovalEvent(params.paramsForRun, {
89
- phase: "resolved",
90
- kind: context.kind,
91
- status: "denied",
92
- title: context.title,
93
- ...context.eventDetails,
94
- ...approvalEventScope(params.method, "denied"),
95
- message: policyOutcome.reason,
96
- });
97
- return buildApprovalResponse(params.method, context.requestParams, "denied");
98
- }
99
- const requestResult = await requestPluginApproval({
100
- paramsForRun: params.paramsForRun,
101
- title: context.title,
102
- description: context.description,
103
- severity: context.severity,
104
- toolName: context.toolName,
105
- toolCallId: context.itemId,
106
- });
107
-
108
- const approvalId = requestResult?.id;
109
- if (!approvalId) {
110
- emitApprovalEvent(params.paramsForRun, {
111
- phase: "resolved",
112
- kind: context.kind,
113
- status: "unavailable",
114
- title: context.title,
115
- ...context.eventDetails,
116
- ...approvalEventScope(params.method, "denied"),
117
- message: "Codex app-server approval route unavailable.",
118
- });
119
- return buildApprovalResponse(params.method, context.requestParams, "denied");
120
- }
121
-
122
- emitApprovalEvent(params.paramsForRun, {
123
- phase: "requested",
124
- kind: context.kind,
125
- status: "pending",
126
- title: context.title,
127
- approvalId,
128
- approvalSlug: approvalId,
129
- ...context.eventDetails,
130
- message: "Codex app-server approval requested.",
131
- });
132
-
133
- const decision = approvalRequestExplicitlyUnavailable(requestResult)
134
- ? null
135
- : await waitForPluginApprovalDecision({ approvalId, signal: params.signal });
136
- const outcome = mapExecDecisionToOutcome(decision);
137
-
138
- emitApprovalEvent(params.paramsForRun, {
139
- phase: "resolved",
140
- kind: context.kind,
141
- status:
142
- outcome === "denied"
143
- ? "denied"
144
- : outcome === "unavailable"
145
- ? "unavailable"
146
- : outcome === "cancelled"
147
- ? "failed"
148
- : "approved",
149
- title: context.title,
150
- approvalId,
151
- approvalSlug: approvalId,
152
- ...context.eventDetails,
153
- ...approvalEventScope(params.method, outcome),
154
- message: approvalResolutionMessage(outcome),
155
- });
156
- return buildApprovalResponse(params.method, context.requestParams, outcome);
157
- } catch (error) {
158
- const cancelled = params.signal?.aborted === true;
159
- emitApprovalEvent(params.paramsForRun, {
160
- phase: "resolved",
161
- kind: context.kind,
162
- status: cancelled ? "failed" : "unavailable",
163
- title: context.title,
164
- ...context.eventDetails,
165
- ...approvalEventScope(params.method, cancelled ? "cancelled" : "denied"),
166
- message: cancelled
167
- ? "Codex app-server approval cancelled because the run stopped."
168
- : `Codex app-server approval route failed: ${formatCodexDisplayText(
169
- formatErrorMessage(error),
170
- )}`,
171
- });
172
- return buildApprovalResponse(
173
- params.method,
174
- context.requestParams,
175
- cancelled ? "cancelled" : "denied",
176
- );
177
- }
178
- }
179
-
180
- export function buildApprovalResponse(
181
- method: string,
182
- requestParams: JsonObject | undefined,
183
- outcome: AppServerApprovalOutcome,
184
- ): JsonValue {
185
- if (method === "item/commandExecution/requestApproval") {
186
- return { decision: commandApprovalDecision(requestParams, outcome) };
187
- }
188
- if (method === "item/fileChange/requestApproval") {
189
- return { decision: fileChangeApprovalDecision(outcome) };
190
- }
191
- if (method === "item/permissions/requestApproval") {
192
- if (outcome === "approved-session" || outcome === "approved-once") {
193
- return {
194
- permissions: requestedPermissions(requestParams),
195
- scope: outcome === "approved-session" ? "session" : "turn",
196
- };
197
- }
198
- return { permissions: {}, scope: "turn" };
199
- }
200
- return unsupportedApprovalResponse();
201
- }
202
-
203
- function matchesCurrentTurn(
204
- requestParams: JsonObject | undefined,
205
- threadId: string,
206
- turnId: string,
207
- ): boolean {
208
- if (!requestParams) {
209
- return false;
210
- }
211
- const requestThreadId =
212
- readString(requestParams, "threadId") ?? readString(requestParams, "conversationId");
213
- const requestTurnId = readString(requestParams, "turnId");
214
- return requestThreadId === threadId && requestTurnId === turnId;
215
- }
216
-
217
- function buildApprovalContext(params: {
218
- method: string;
219
- requestParams: JsonObject | undefined;
220
- paramsForRun: EmbeddedRunAttemptParams;
221
- }) {
222
- const itemId =
223
- readString(params.requestParams, "itemId") ??
224
- readString(params.requestParams, "callId") ??
225
- readString(params.requestParams, "approvalId");
226
- const commandDetailLines =
227
- params.method === "item/commandExecution/requestApproval"
228
- ? describeCommandApprovalDetails(params.requestParams)
229
- : [];
230
- const commandPreview = sanitizeApprovalPreview(
231
- readDisplayCommandPreview(params.requestParams),
232
- commandDetailLines.length > 0 ? COMMAND_PREVIEW_WITH_DETAILS_MAX_LENGTH : 180,
233
- );
234
- const reasonPreview = sanitizeApprovalPreview(
235
- readStringPreview(params.requestParams, "reason"),
236
- 180,
237
- );
238
- const command = commandPreview.text;
239
- const reason = reasonPreview.text;
240
- const kind = approvalKindForMethod(params.method);
241
- const permissionLines =
242
- params.method === "item/permissions/requestApproval"
243
- ? describeRequestedPermissions(params.requestParams)
244
- : [];
245
- const title =
246
- kind === "exec"
247
- ? "Codex app-server command approval"
248
- : params.method === "item/permissions/requestApproval"
249
- ? "Codex app-server permission approval"
250
- : kind === "plugin"
251
- ? "Codex app-server file approval"
252
- : "Codex app-server approval";
253
- const subject =
254
- permissionLines[0] ??
255
- (command
256
- ? `Command: ${formatApprovalPreviewSubject(command, commandPreview.omitted)}`
257
- : commandPreview.omitted
258
- ? `Command: ${APPROVAL_PREVIEW_OMITTED}`
259
- : reason
260
- ? `Reason: ${formatApprovalPreviewSubject(reason, reasonPreview.omitted)}`
261
- : reasonPreview.omitted
262
- ? `Reason: ${APPROVAL_PREVIEW_OMITTED}`
263
- : `Request method: ${params.method}`);
264
- const description =
265
- permissionLines.length > 0
266
- ? joinDescriptionLinesWithinLimit(permissionLines, PERMISSION_DESCRIPTION_MAX_LENGTH)
267
- : [
268
- subject,
269
- ...commandDetailLines,
270
- params.paramsForRun.sessionKey && `Session: ${params.paramsForRun.sessionKey}`,
271
- ]
272
- .filter(Boolean)
273
- .join("\n");
274
- return {
275
- kind,
276
- title,
277
- description,
278
- severity: kind === "exec" ? ("warning" as const) : ("info" as const),
279
- toolName:
280
- kind === "exec"
281
- ? "codex_command_approval"
282
- : params.method === "item/permissions/requestApproval"
283
- ? "codex_permission_approval"
284
- : "codex_file_approval",
285
- itemId,
286
- requestParams: params.requestParams,
287
- eventDetails: {
288
- ...(itemId ? { itemId } : {}),
289
- ...(command ? { command } : {}),
290
- ...(commandPreview.omitted ? { commandPreviewOmitted: true } : {}),
291
- ...(reason ? { reason } : {}),
292
- ...(reasonPreview.omitted ? { reasonPreviewOmitted: true } : {}),
293
- },
294
- };
295
- }
296
-
297
- type ApprovalContext = ReturnType<typeof buildApprovalContext>;
298
- type ApprovalPolicyOutcome = { outcome: "denied"; reason: string } | { outcome: "no-decision" };
299
-
300
- async function runKlawToolPolicyForApprovalRequest(params: {
301
- method: string;
302
- requestParams: JsonObject | undefined;
303
- paramsForRun: EmbeddedRunAttemptParams;
304
- context: ApprovalContext;
305
- nativeHookRelay?: Pick<NativeHookRelayRegistrationHandle, "allowedEvents" | "relayId">;
306
- signal?: AbortSignal;
307
- }): Promise<ApprovalPolicyOutcome | undefined> {
308
- const policyRequest = buildKlawToolPolicyRequest(params.method, params.requestParams);
309
- if (!policyRequest) {
310
- return undefined;
311
- }
312
- const cwd = readString(params.requestParams, "cwd") ?? params.paramsForRun.workspaceDir;
313
- const nativeRelayOutcome = await runNativeRelayToolPolicyForApprovalRequest({
314
- method: params.method,
315
- requestParams: params.requestParams,
316
- context: params.context,
317
- policyRequest,
318
- nativeHookRelay: params.nativeHookRelay,
319
- cwd,
320
- });
321
- if (nativeRelayOutcome?.blocked) {
322
- return { outcome: "denied", reason: nativeRelayOutcome.reason };
323
- }
324
- if (nativeRelayOutcome?.handled) {
325
- return { outcome: "no-decision" };
326
- }
327
- const hookChannelId = buildAgentHookContextChannelFields({
328
- sessionKey: params.paramsForRun.sessionKey,
329
- messageChannel: params.paramsForRun.messageChannel,
330
- messageProvider: params.paramsForRun.messageProvider,
331
- currentChannelId: params.paramsForRun.currentChannelId,
332
- messageTo: params.paramsForRun.messageTo,
333
- }).channelId;
334
- const outcome = await runBeforeToolCallHook({
335
- toolName: policyRequest.toolName,
336
- params: policyRequest.params,
337
- ...(params.context.itemId ? { toolCallId: params.context.itemId } : {}),
338
- approvalMode: "report",
339
- signal: params.signal,
340
- ctx: {
341
- ...(params.paramsForRun.agentId ? { agentId: params.paramsForRun.agentId } : {}),
342
- ...(params.paramsForRun.config ? { config: params.paramsForRun.config } : {}),
343
- ...(cwd ? { cwd } : {}),
344
- ...(params.paramsForRun.sessionKey ? { sessionKey: params.paramsForRun.sessionKey } : {}),
345
- ...(params.paramsForRun.sessionId ? { sessionId: params.paramsForRun.sessionId } : {}),
346
- ...(params.paramsForRun.runId ? { runId: params.paramsForRun.runId } : {}),
347
- ...(hookChannelId ? { channelId: hookChannelId } : {}),
348
- },
349
- });
350
- if (outcome.blocked) {
351
- return { outcome: "denied", reason: outcome.reason };
352
- }
353
- if ("params" in outcome && toolPolicyParamsWereRewritten(policyRequest.params, outcome.params)) {
354
- return {
355
- outcome: "denied",
356
- reason:
357
- "Klaw tool policy rewrote Codex app-server approval params; refusing original request.",
358
- };
359
- }
360
- return undefined;
361
- }
362
-
363
- async function runNativeRelayToolPolicyForApprovalRequest(params: {
364
- method: string;
365
- requestParams: JsonObject | undefined;
366
- context: ApprovalContext;
367
- policyRequest: { toolName: string; params: JsonObject };
368
- nativeHookRelay?: Pick<NativeHookRelayRegistrationHandle, "allowedEvents" | "relayId">;
369
- cwd?: string;
370
- }): Promise<
371
- | {
372
- handled: true;
373
- blocked: true;
374
- reason: string;
375
- }
376
- | {
377
- handled: true;
378
- blocked?: false;
379
- }
380
- | undefined
381
- > {
382
- // Only command approvals correspond to Codex PreToolUse execution. File-change
383
- // and permission approvals stay on the app-server approval route below.
384
- if (
385
- params.method !== "item/commandExecution/requestApproval" ||
386
- !params.nativeHookRelay?.allowedEvents.includes("pre_tool_use")
387
- ) {
388
- return undefined;
389
- }
390
- const payload = buildNativeRelayPreToolUsePayload({
391
- requestParams: params.requestParams,
392
- policyRequest: params.policyRequest,
393
- context: params.context,
394
- cwd: params.cwd,
395
- });
396
- if (!payload) {
397
- return undefined;
398
- }
399
- if (
400
- hasNativeHookRelayInvocation({
401
- relayId: params.nativeHookRelay.relayId,
402
- event: "pre_tool_use",
403
- toolUseId: params.context.itemId,
404
- })
405
- ) {
406
- return { handled: true };
407
- }
408
- try {
409
- const response = await invokeNativeHookRelay({
410
- provider: "codex",
411
- relayId: params.nativeHookRelay.relayId,
412
- event: "pre_tool_use",
413
- rawPayload: payload,
414
- });
415
- const decision = readNativeRelayPreToolUseDecision(response);
416
- if (decision.blocked) {
417
- return { handled: true, blocked: true, reason: decision.reason };
418
- }
419
- return { handled: true };
420
- } catch (error) {
421
- return {
422
- handled: true,
423
- blocked: true,
424
- reason: `Klaw native hook relay unavailable for Codex app-server approval: ${formatCodexDisplayText(
425
- formatErrorMessage(error),
426
- )}`,
427
- };
428
- }
429
- }
430
-
431
- function buildNativeRelayPreToolUsePayload(params: {
432
- requestParams: JsonObject | undefined;
433
- policyRequest: { toolName: string; params: JsonObject };
434
- context: ApprovalContext;
435
- cwd?: string;
436
- }): JsonObject | undefined {
437
- const command = readString(params.policyRequest.params, "command");
438
- if (!command) {
439
- return undefined;
440
- }
441
- const turnId = readString(params.requestParams, "turnId");
442
- return {
443
- hook_event_name: "PreToolUse",
444
- klaw_approval_mode: "report",
445
- tool_name: "exec_command",
446
- ...(params.context.itemId ? { tool_use_id: params.context.itemId } : {}),
447
- ...(params.cwd ? { cwd: params.cwd } : {}),
448
- ...(turnId ? { turn_id: turnId } : {}),
449
- tool_input: {
450
- ...params.policyRequest.params,
451
- command,
452
- cmd: command,
453
- },
454
- };
455
- }
456
-
457
- function readNativeRelayPreToolUseDecision(
458
- response: NativeHookRelayProcessResponse | undefined,
459
- ): { blocked: true; reason: string } | { blocked: false } {
460
- if (!response || response.exitCode !== 0) {
461
- return {
462
- blocked: true,
463
- reason:
464
- sanitizeRelayDecisionReason(response?.stderr) ||
465
- sanitizeRelayDecisionReason(response?.stdout) ||
466
- "Klaw native hook relay failed for Codex app-server approval.",
467
- };
468
- }
469
- const stdout = response.stdout?.trim();
470
- if (!stdout) {
471
- return { blocked: false };
472
- }
473
- const parsed = parseRelayJsonResponse(stdout);
474
- const output = isJsonObject(parsed?.hookSpecificOutput) ? parsed.hookSpecificOutput : undefined;
475
- if (output?.permissionDecision === "deny") {
476
- return {
477
- blocked: true,
478
- reason:
479
- readString(output, "permissionDecisionReason") ||
480
- "Klaw native hook policy denied Codex app-server approval.",
481
- };
482
- }
483
- // The app-server bridge invokes the relay in report mode, where the relay
484
- // contract is deny-or-silent. Any other structured decision fails closed.
485
- return {
486
- blocked: true,
487
- reason: output
488
- ? "Klaw native hook relay returned a non-deny Codex app-server approval decision."
489
- : "Klaw native hook relay returned an unreadable Codex app-server approval result.",
490
- };
491
- }
492
-
493
- function parseRelayJsonResponse(text: string): JsonObject | undefined {
494
- try {
495
- const parsed = JSON.parse(text) as JsonValue;
496
- return isJsonObject(parsed) ? parsed : undefined;
497
- } catch {
498
- return undefined;
499
- }
500
- }
501
-
502
- function sanitizeRelayDecisionReason(value: string | undefined): string | undefined {
503
- const preview = sanitizeApprovalPreview(value ? { value, clipped: false } : undefined, 240);
504
- return preview.text;
505
- }
506
-
507
- function buildKlawToolPolicyRequest(
508
- method: string,
509
- requestParams: JsonObject | undefined,
510
- ): { toolName: string; params: JsonObject } | undefined {
511
- if (method === "item/commandExecution/requestApproval") {
512
- const command = readPolicyCommand(requestParams);
513
- return {
514
- toolName: "exec",
515
- params: {
516
- ...(command ? { command } : {}),
517
- ...(readString(requestParams, "cwd") ? { cwd: readString(requestParams, "cwd") } : {}),
518
- approval: requestParams ?? {},
519
- },
520
- };
521
- }
522
- if (method === "item/fileChange/requestApproval") {
523
- return { toolName: "apply_patch", params: requestParams ?? {} };
524
- }
525
- if (method === "item/permissions/requestApproval") {
526
- return { toolName: "codex_permission_approval", params: requestParams ?? {} };
527
- }
528
- return undefined;
529
- }
530
-
531
- function toolPolicyParamsWereRewritten(original: JsonObject, candidate: unknown): boolean {
532
- if (candidate === original) {
533
- return false;
534
- }
535
- const originalText = stableJsonText(original);
536
- const candidateText = stableJsonText(candidate);
537
- return !candidateText || candidateText !== originalText;
538
- }
539
-
540
- function stableJsonText(value: unknown): string | undefined {
541
- if (
542
- value === null ||
543
- typeof value === "string" ||
544
- typeof value === "number" ||
545
- typeof value === "boolean"
546
- ) {
547
- return JSON.stringify(value);
548
- }
549
- if (Array.isArray(value)) {
550
- const items = value.map((item) => stableJsonText(item));
551
- return items.every((item): item is string => item !== undefined)
552
- ? `[${items.join(",")}]`
553
- : undefined;
554
- }
555
- if (isPlainRecord(value)) {
556
- const entries = Object.entries(value)
557
- .toSorted(([left], [right]) => left.localeCompare(right))
558
- .map(([key, item]) => {
559
- const text = stableJsonText(item);
560
- return text === undefined ? undefined : `${JSON.stringify(key)}:${text}`;
561
- });
562
- return entries.every((entry): entry is string => entry !== undefined)
563
- ? `{${entries.join(",")}}`
564
- : undefined;
565
- }
566
- return undefined;
567
- }
568
-
569
- function isPlainRecord(value: unknown): value is Record<string, unknown> {
570
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
571
- }
572
-
573
- function commandApprovalDecision(
574
- requestParams: JsonObject | undefined,
575
- outcome: AppServerApprovalOutcome,
576
- ): JsonValue {
577
- if (outcome === "cancelled") {
578
- return commandRejectionDecision(requestParams, "cancel");
579
- }
580
- if (outcome === "denied" || outcome === "unavailable") {
581
- return commandRejectionDecision(requestParams, "decline");
582
- }
583
- if (outcome === "approved-session") {
584
- if (hasAvailableDecision(requestParams, "acceptForSession")) {
585
- return "acceptForSession";
586
- }
587
- const amendmentDecision = findAvailableCommandAmendmentDecision(requestParams);
588
- if (amendmentDecision) {
589
- return amendmentDecision;
590
- }
591
- }
592
- return hasAvailableDecision(requestParams, "accept")
593
- ? "accept"
594
- : commandRejectionDecision(requestParams, "decline");
595
- }
596
-
597
- function fileChangeApprovalDecision(outcome: AppServerApprovalOutcome): JsonValue {
598
- if (outcome === "cancelled") {
599
- return "cancel";
600
- }
601
- if (outcome === "denied" || outcome === "unavailable") {
602
- return "decline";
603
- }
604
- return outcome === "approved-session" ? "acceptForSession" : "accept";
605
- }
606
-
607
- function requestedPermissions(requestParams: JsonObject | undefined): JsonObject {
608
- const permissions = isJsonObject(requestParams?.permissions) ? requestParams.permissions : {};
609
- const granted: JsonObject = {};
610
- if (isJsonObject(permissions.network)) {
611
- granted.network = permissions.network;
612
- }
613
- if (isJsonObject(permissions.fileSystem)) {
614
- granted.fileSystem = permissions.fileSystem;
615
- }
616
- return granted;
617
- }
618
-
619
- function unsupportedApprovalResponse(): JsonValue {
620
- return {
621
- decision: "decline",
622
- reason: "Klaw codex app-server bridge does not grant native approvals yet.",
623
- };
624
- }
625
-
626
- function describeRequestedPermissions(requestParams: JsonObject | undefined): string[] {
627
- const permissions = requestedPermissions(requestParams);
628
- return describePermissionProfile(permissions, "Permissions");
629
- }
630
-
631
- function describeCommandApprovalDetails(requestParams: JsonObject | undefined): string[] {
632
- const lines: string[] = [];
633
- const additionalPermissions = isJsonObject(requestParams?.additionalPermissions)
634
- ? requestParams.additionalPermissions
635
- : undefined;
636
- if (additionalPermissions) {
637
- lines.push(...describePermissionProfile(additionalPermissions, "Additional permissions"));
638
- }
639
- const execpolicySummary = summarizeStringArray(
640
- requestParams?.proposedExecpolicyAmendment,
641
- "Proposed exec policy",
642
- sanitizePermissionScalar,
643
- );
644
- if (execpolicySummary) {
645
- lines.push(execpolicySummary);
646
- }
647
- const networkAmendmentSummary = summarizeNetworkPolicyAmendments(
648
- requestParams?.proposedNetworkPolicyAmendments,
649
- );
650
- if (networkAmendmentSummary) {
651
- lines.push(networkAmendmentSummary);
652
- }
653
- return lines;
654
- }
655
-
656
- function describePermissionProfile(permissions: JsonObject, label: string): string[] {
657
- const lines: string[] = [];
658
- const kinds: string[] = [];
659
- const risks = new Set<string>();
660
- if (isJsonObject(permissions.network)) {
661
- kinds.push("network");
662
- }
663
- if (isJsonObject(permissions.fileSystem)) {
664
- kinds.push("fileSystem");
665
- }
666
- if (kinds.length > 0) {
667
- lines.push(`${label}: ${kinds.join(", ")}`);
668
- }
669
- let networkSummary: string | undefined;
670
- if (isJsonObject(permissions.network)) {
671
- const summaries = [
672
- summarizeNetworkEnabledPermission(permissions.network, risks),
673
- summarizePermissionRecord(permissions.network, risks, [
674
- {
675
- key: "allowHosts",
676
- label: "allowHosts",
677
- sanitize: sanitizePermissionHostValue,
678
- risksFor: permissionHostRisks,
679
- },
680
- ]),
681
- ].filter((summary): summary is string => Boolean(summary));
682
- networkSummary = summaries.length > 0 ? summaries.join("; ") : undefined;
683
- }
684
- let fileSystemSummary: string | undefined;
685
- if (isJsonObject(permissions.fileSystem)) {
686
- const summaries = [
687
- summarizePermissionRecord(permissions.fileSystem, risks, [
688
- {
689
- key: "read",
690
- label: "read",
691
- sanitize: sanitizePermissionPathValue,
692
- risksFor: permissionPathRisks,
693
- },
694
- {
695
- key: "write",
696
- label: "write",
697
- sanitize: sanitizePermissionPathValue,
698
- risksFor: permissionPathRisks,
699
- },
700
- {
701
- key: "roots",
702
- label: "roots",
703
- sanitize: sanitizePermissionPathValue,
704
- risksFor: permissionPathRisks,
705
- },
706
- {
707
- key: "readPaths",
708
- label: "readPaths",
709
- sanitize: sanitizePermissionPathValue,
710
- risksFor: permissionPathRisks,
711
- },
712
- {
713
- key: "writePaths",
714
- label: "writePaths",
715
- sanitize: sanitizePermissionPathValue,
716
- risksFor: permissionPathRisks,
717
- },
718
- ]),
719
- summarizeFileSystemEntries(permissions.fileSystem, risks),
720
- ].filter((summary): summary is string => Boolean(summary));
721
- fileSystemSummary = summaries.length > 0 ? summaries.join("; ") : undefined;
722
- }
723
- if (risks.size > 0) {
724
- lines.push(`High-risk targets: ${[...risks].join(", ")}`);
725
- }
726
- if (networkSummary) {
727
- lines.push(`Network ${networkSummary}`);
728
- }
729
- if (fileSystemSummary) {
730
- lines.push(`File system ${fileSystemSummary}`);
731
- }
732
- return lines;
733
- }
734
-
735
- type PermissionArrayDescriptor = {
736
- key: string;
737
- label: string;
738
- sanitize: (value: string) => string;
739
- risksFor: (value: string) => readonly string[];
740
- };
741
-
742
- function summarizeNetworkEnabledPermission(
743
- permission: JsonObject,
744
- risks: Set<string>,
745
- ): string | undefined {
746
- const enabled = permission.enabled;
747
- if (typeof enabled !== "boolean") {
748
- return undefined;
749
- }
750
- if (enabled) {
751
- risks.add("network access");
752
- }
753
- return `enabled: ${enabled}`;
754
- }
755
-
756
- function summarizeFileSystemEntries(
757
- permission: JsonObject,
758
- risks: Set<string>,
759
- ): string | undefined {
760
- const entries = permission.entries;
761
- if (!Array.isArray(entries)) {
762
- return undefined;
763
- }
764
- const samples: string[] = [];
765
- let count = 0;
766
- for (const entry of entries) {
767
- const item = isJsonObject(entry) ? entry : undefined;
768
- const path = typeof item?.path === "string" ? item.path.trim() : "";
769
- const access = typeof item?.access === "string" ? item.access.trim() : "";
770
- if (!path || !access) {
771
- continue;
772
- }
773
- count += 1;
774
- if (access !== "none") {
775
- for (const risk of permissionPathRisks(path)) {
776
- risks.add(risk);
777
- }
778
- }
779
- if (samples.length < PERMISSION_SAMPLE_LIMIT) {
780
- samples.push(`${sanitizePermissionScalar(access)} ${sanitizePermissionPathValue(path)}`);
781
- }
782
- }
783
- if (count === 0) {
784
- return undefined;
785
- }
786
- const remaining = count - samples.length;
787
- const remainderSuffix = remaining > 0 ? ` (+${remaining} more)` : "";
788
- return `entries: ${samples.join(", ")}${remainderSuffix}`;
789
- }
790
-
791
- function summarizePermissionRecord(
792
- permission: JsonObject,
793
- risks: Set<string>,
794
- descriptors: readonly PermissionArrayDescriptor[],
795
- ): string | undefined {
796
- const details: string[] = [];
797
- for (const descriptor of descriptors) {
798
- const summary = summarizePermissionArray(permission, descriptor, risks);
799
- if (summary) {
800
- details.push(summary);
801
- }
802
- }
803
- return details.length > 0 ? details.join("; ") : undefined;
804
- }
805
-
806
- function summarizePermissionArray(
807
- record: JsonObject,
808
- descriptor: PermissionArrayDescriptor,
809
- risks: Set<string>,
810
- ): string | undefined {
811
- const values = readStringArray(record, descriptor.key);
812
- if (values.length === 0) {
813
- return undefined;
814
- }
815
- for (const value of values) {
816
- for (const risk of descriptor.risksFor(value)) {
817
- risks.add(risk);
818
- }
819
- }
820
- const sampleValues = values
821
- .slice(0, PERMISSION_SAMPLE_LIMIT)
822
- .map(descriptor.sanitize)
823
- .filter(Boolean);
824
- if (sampleValues.length === 0) {
825
- return `${descriptor.label}: ${values.length}`;
826
- }
827
- const remaining = values.length - sampleValues.length;
828
- const remainderSuffix = remaining > 0 ? ` (+${remaining} more)` : "";
829
- return `${descriptor.label}: ${sampleValues.join(", ")}${remainderSuffix}`;
830
- }
831
-
832
- function summarizeStringArray(
833
- value: JsonValue | undefined,
834
- label: string,
835
- sanitize: (value: string) => string,
836
- ): string | undefined {
837
- if (!Array.isArray(value)) {
838
- return undefined;
839
- }
840
- const values = value
841
- .filter((entry): entry is string => typeof entry === "string")
842
- .map((entry) => sanitize(entry))
843
- .filter(Boolean);
844
- if (values.length === 0) {
845
- return undefined;
846
- }
847
- const samples = values.slice(0, PERMISSION_SAMPLE_LIMIT);
848
- const remaining = values.length - samples.length;
849
- const remainderSuffix = remaining > 0 ? ` (+${remaining} more)` : "";
850
- return `${label}: ${samples.join(", ")}${remainderSuffix}`;
851
- }
852
-
853
- function summarizeNetworkPolicyAmendments(value: JsonValue | undefined): string | undefined {
854
- if (!Array.isArray(value)) {
855
- return undefined;
856
- }
857
- const samples: string[] = [];
858
- let count = 0;
859
- for (const entry of value) {
860
- const amendment = isJsonObject(entry) ? entry : undefined;
861
- const host = typeof amendment?.host === "string" ? amendment.host : "";
862
- const action = typeof amendment?.action === "string" ? amendment.action : "";
863
- if (!host || !action) {
864
- continue;
865
- }
866
- count += 1;
867
- if (samples.length < PERMISSION_SAMPLE_LIMIT) {
868
- samples.push(`${sanitizePermissionScalar(action)} ${sanitizePermissionHostValue(host)}`);
869
- }
870
- }
871
- if (count === 0) {
872
- return undefined;
873
- }
874
- const remaining = count - samples.length;
875
- const remainderSuffix = remaining > 0 ? ` (+${remaining} more)` : "";
876
- return `Proposed network policy: ${samples.join(", ")}${remainderSuffix}`;
877
- }
878
-
879
- function readStringArray(record: JsonObject, key: string): string[] {
880
- const value = record[key];
881
- return Array.isArray(value)
882
- ? value.map((entry) => (typeof entry === "string" ? entry.trim() : "")).filter(Boolean)
883
- : [];
884
- }
885
-
886
- function sanitizePermissionHostValue(value: string): string {
887
- const compact = sanitizePermissionScalar(value).toLowerCase();
888
- const withoutScheme = compact.replace(/^[a-z][a-z0-9+.-]*:\/\//, "");
889
- const authority = withoutScheme.split(/[/?#]/, 1)[0] ?? withoutScheme;
890
- const withoutUserInfo = authority.includes("@")
891
- ? authority.slice(authority.lastIndexOf("@") + 1)
892
- : authority;
893
- return truncate(withoutUserInfo, PERMISSION_VALUE_MAX_LENGTH);
894
- }
895
-
896
- function sanitizePermissionPathValue(value: string): string {
897
- return truncate(
898
- formatApprovalDisplayPath(sanitizePermissionScalar(value)),
899
- PERMISSION_VALUE_MAX_LENGTH,
900
- );
901
- }
902
-
903
- function sanitizePermissionScalar(value: string): string {
904
- return sanitizeVisibleScalar(value);
905
- }
906
-
907
- function permissionHostRisks(value: string): string[] {
908
- const normalized = value.trim().toLowerCase();
909
- const risks: string[] = [];
910
- if (normalized.includes("*")) {
911
- risks.push("wildcard hosts");
912
- if (isPrivateNetworkHostPattern(normalized)) {
913
- risks.push("private-network wildcards");
914
- }
915
- }
916
- return risks;
917
- }
918
-
919
- function permissionPathRisks(value: string): string[] {
920
- const normalized = sanitizePermissionScalar(value);
921
- const risks: string[] = [];
922
- if (normalized === "/" || normalized === "\\" || /^[A-Za-z]:[\\/]*$/.test(normalized)) {
923
- risks.push("filesystem root");
924
- }
925
- return risks;
926
- }
927
-
928
- function isPrivateNetworkHostPattern(value: string): boolean {
929
- const normalized = value.toLowerCase();
930
- const wildcardStripped = normalized.replace(/^\*\./, "");
931
- if (
932
- wildcardStripped === "localhost" ||
933
- wildcardStripped === "local" ||
934
- wildcardStripped === "internal" ||
935
- wildcardStripped === "lan" ||
936
- wildcardStripped === "home" ||
937
- wildcardStripped === "corp" ||
938
- wildcardStripped === "private" ||
939
- wildcardStripped.endsWith(".local") ||
940
- wildcardStripped.endsWith(".internal") ||
941
- wildcardStripped.endsWith(".lan") ||
942
- wildcardStripped.endsWith(".home") ||
943
- wildcardStripped.endsWith(".corp") ||
944
- wildcardStripped.endsWith(".private")
945
- ) {
946
- return true;
947
- }
948
- if (
949
- wildcardStripped.startsWith("10.") ||
950
- wildcardStripped.startsWith("127.") ||
951
- wildcardStripped.startsWith("192.168.") ||
952
- wildcardStripped.startsWith("169.254.")
953
- ) {
954
- return true;
955
- }
956
- return /^172\.(1[6-9]|2\d|3[0-1])\./.test(wildcardStripped);
957
- }
958
-
959
- function hasAvailableDecision(requestParams: JsonObject | undefined, decision: string): boolean {
960
- const available = requestParams?.availableDecisions;
961
- if (!Array.isArray(available)) {
962
- return true;
963
- }
964
- return available.includes(decision);
965
- }
966
-
967
- function findAvailableCommandAmendmentDecision(
968
- requestParams: JsonObject | undefined,
969
- ): JsonValue | undefined {
970
- const available = requestParams?.availableDecisions;
971
- if (!Array.isArray(available)) {
972
- return undefined;
973
- }
974
- return available.find(
975
- (entry): entry is JsonObject =>
976
- isJsonObject(entry) &&
977
- (isJsonObject(entry.acceptWithExecpolicyAmendment) ||
978
- isJsonObject(entry.applyNetworkPolicyAmendment)),
979
- );
980
- }
981
-
982
- function commandRejectionDecision(
983
- requestParams: JsonObject | undefined,
984
- preferred: "decline" | "cancel",
985
- ): JsonValue {
986
- const available = requestParams?.availableDecisions;
987
- if (!Array.isArray(available)) {
988
- return preferred;
989
- }
990
- if (available.includes(preferred)) {
991
- return preferred;
992
- }
993
- const alternate = preferred === "decline" ? "cancel" : "decline";
994
- if (available.includes(alternate)) {
995
- return alternate;
996
- }
997
- return preferred;
998
- }
999
-
1000
- function approvalResolutionMessage(outcome: AppServerApprovalOutcome): string {
1001
- if (outcome === "approved-session") {
1002
- return "Codex app-server approval granted for the session.";
1003
- }
1004
- if (outcome === "approved-once") {
1005
- return "Codex app-server approval granted for this turn.";
1006
- }
1007
- if (outcome === "cancelled") {
1008
- return "Codex app-server approval cancelled.";
1009
- }
1010
- if (outcome === "unavailable") {
1011
- return "Codex app-server approval unavailable.";
1012
- }
1013
- return "Codex app-server approval denied.";
1014
- }
1015
-
1016
- function approvalScopeForOutcome(outcome: AppServerApprovalOutcome): "turn" | "session" {
1017
- return outcome === "approved-session" ? "session" : "turn";
1018
- }
1019
-
1020
- function approvalEventScope(
1021
- method: string,
1022
- outcome: AppServerApprovalOutcome,
1023
- ): Pick<AgentApprovalEventData, "scope"> {
1024
- return method === "item/permissions/requestApproval"
1025
- ? { scope: approvalScopeForOutcome(outcome) }
1026
- : {};
1027
- }
1028
-
1029
- function approvalKindForMethod(method: string): AgentApprovalEventData["kind"] {
1030
- if (method.includes("commandExecution") || method.includes("execCommand")) {
1031
- return "exec";
1032
- }
1033
- if (method.includes("fileChange") || method.includes("Patch") || method.includes("permissions")) {
1034
- return "plugin";
1035
- }
1036
- return "unknown";
1037
- }
1038
-
1039
- function isSupportedAppServerApprovalMethod(method: string): boolean {
1040
- return (
1041
- method === "item/commandExecution/requestApproval" ||
1042
- method === "item/fileChange/requestApproval" ||
1043
- method === "item/permissions/requestApproval"
1044
- );
1045
- }
1046
-
1047
- function emitApprovalEvent(params: EmbeddedRunAttemptParams, data: AgentApprovalEventData): void {
1048
- void params.onAgentEvent?.({
1049
- stream: "approval",
1050
- data: data as unknown as Record<string, unknown>,
1051
- });
1052
- }
1053
-
1054
- function readDisplayCommandPreview(
1055
- record: JsonObject | undefined,
1056
- ): ApprovalPreviewSource | undefined {
1057
- const actionCommand = readCommandActionsPreview(record);
1058
- if (actionCommand) {
1059
- return actionCommand;
1060
- }
1061
- return readCommandPreview(record);
1062
- }
1063
-
1064
- function readPolicyCommand(record: JsonObject | undefined): string | undefined {
1065
- const command = record?.command;
1066
- if (typeof command === "string") {
1067
- return command;
1068
- }
1069
- if (Array.isArray(command) && command.every((part): part is string => typeof part === "string")) {
1070
- return command.join(" ");
1071
- }
1072
- const actionCommands = readCommandActions(record);
1073
- if (actionCommands.length > 0) {
1074
- return actionCommands.join(" && ");
1075
- }
1076
- return undefined;
1077
- }
1078
-
1079
- function readCommandActions(record: JsonObject | undefined): string[] {
1080
- const actions = record?.commandActions;
1081
- if (!Array.isArray(actions)) {
1082
- return [];
1083
- }
1084
- return actions
1085
- .map((action) => (isJsonObject(action) ? readString(action, "command") : undefined))
1086
- .filter((command): command is string => Boolean(command));
1087
- }
1088
-
1089
- function readCommandActionsPreview(
1090
- record: JsonObject | undefined,
1091
- ): ApprovalPreviewSource | undefined {
1092
- let source: ApprovalPreviewSource | undefined;
1093
- for (const command of readCommandActions(record)) {
1094
- source = appendPreviewPart(source, command, " && ");
1095
- if (source.clipped) {
1096
- break;
1097
- }
1098
- }
1099
- return source;
1100
- }
1101
-
1102
- function readCommandPreview(record: JsonObject | undefined): ApprovalPreviewSource | undefined {
1103
- const command = record?.command;
1104
- if (typeof command === "string") {
1105
- return previewSource(command);
1106
- }
1107
- if (!Array.isArray(command)) {
1108
- return undefined;
1109
- }
1110
- let source: ApprovalPreviewSource | undefined;
1111
- for (const part of command) {
1112
- if (typeof part !== "string") {
1113
- return undefined;
1114
- }
1115
- source = appendPreviewPart(source, part, " ");
1116
- if (source.clipped) {
1117
- break;
1118
- }
1119
- }
1120
- return source;
1121
- }
1122
-
1123
- function readStringPreview(
1124
- record: JsonObject | undefined,
1125
- key: string,
1126
- ): ApprovalPreviewSource | undefined {
1127
- const value = readString(record, key);
1128
- return value === undefined ? undefined : previewSource(value);
1129
- }
1130
-
1131
- function readString(record: JsonObject | undefined, key: string): string | undefined {
1132
- const value = record?.[key];
1133
- return typeof value === "string" ? value : undefined;
1134
- }
1135
-
1136
- function truncate(value: string, maxLength: number): string {
1137
- return value.length <= maxLength ? value : `${value.slice(0, Math.max(0, maxLength - 3))}...`;
1138
- }
1139
-
1140
- function previewSource(value: string): ApprovalPreviewSource {
1141
- return {
1142
- value: value.slice(0, APPROVAL_PREVIEW_SCAN_MAX_LENGTH),
1143
- clipped: value.length > APPROVAL_PREVIEW_SCAN_MAX_LENGTH,
1144
- };
1145
- }
1146
-
1147
- function appendPreviewPart(
1148
- source: ApprovalPreviewSource | undefined,
1149
- part: string,
1150
- separator: string,
1151
- ): ApprovalPreviewSource {
1152
- const prefix = source?.value ? `${source.value}${separator}` : "";
1153
- const value = `${prefix}${part}`;
1154
- const clipped = source?.clipped === true || value.length > APPROVAL_PREVIEW_SCAN_MAX_LENGTH;
1155
- return {
1156
- value: value.slice(0, APPROVAL_PREVIEW_SCAN_MAX_LENGTH),
1157
- clipped,
1158
- };
1159
- }
1160
-
1161
- function sanitizeApprovalPreview(
1162
- source: ApprovalPreviewSource | undefined,
1163
- maxLength: number,
1164
- ): SanitizedApprovalPreview {
1165
- if (!source || !source.value) {
1166
- return { omitted: false };
1167
- }
1168
- const rawPreview = source.value.replace(DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE, "");
1169
- const sanitized = sanitizeVisibleScalar(rawPreview);
1170
- if (!sanitized) {
1171
- return { omitted: true };
1172
- }
1173
- return { text: formatCodexDisplayText(truncate(sanitized, maxLength)), omitted: source.clipped };
1174
- }
1175
-
1176
- function sanitizeVisibleScalar(value: string): string {
1177
- return value
1178
- .replace(ANSI_OSC_SEQUENCE_RE, "")
1179
- .replace(ANSI_CONTROL_SEQUENCE_RE, "")
1180
- .replace(INVISIBLE_FORMATTING_CONTROL_RE, " ")
1181
- .replace(CONTROL_CHARACTER_RE, " ")
1182
- .replace(/\s+/g, " ")
1183
- .trim();
1184
- }
1185
-
1186
- function formatApprovalPreviewSubject(text: string, omitted: boolean): string {
1187
- return omitted ? `${text} ${APPROVAL_PREVIEW_OMITTED}` : text;
1188
- }
1189
-
1190
- function joinDescriptionLinesWithinLimit(lines: string[], maxLength: number): string {
1191
- let description = "";
1192
- for (const line of lines) {
1193
- const prefix = description ? "\n" : "";
1194
- const next = `${description}${prefix}${line}`;
1195
- if (next.length <= maxLength) {
1196
- description = next;
1197
- continue;
1198
- }
1199
- const remaining = maxLength - description.length - prefix.length;
1200
- if (remaining < 3) {
1201
- break;
1202
- }
1203
- description += `${prefix}${truncate(line, remaining)}`;
1204
- break;
1205
- }
1206
- return description;
1207
- }
1208
-
1209
- function formatErrorMessage(error: unknown): string {
1210
- return error instanceof Error ? error.message : String(error);
1211
- }