@makaio/client-codex 1.0.0-dev-1779051654000

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 (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/descriptor.json +21 -0
  4. package/dist/codex-client-session-service-DD2LkxP1.mjs +1551 -0
  5. package/dist/definition.d.ts +90 -0
  6. package/dist/definition.d.ts.map +1 -0
  7. package/dist/index.d.ts +8 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.mjs +4 -0
  10. package/dist/package.d.ts +18 -0
  11. package/dist/package.d.ts.map +1 -0
  12. package/dist/runtime/client-settings.d.ts +149 -0
  13. package/dist/runtime/client-settings.d.ts.map +1 -0
  14. package/dist/runtime/codex-client-session-service.d.ts +148 -0
  15. package/dist/runtime/codex-client-session-service.d.ts.map +1 -0
  16. package/dist/runtime/config-prime-handler.d.ts +37 -0
  17. package/dist/runtime/config-prime-handler.d.ts.map +1 -0
  18. package/dist/runtime/hook-normalizer.d.ts +79 -0
  19. package/dist/runtime/hook-normalizer.d.ts.map +1 -0
  20. package/dist/runtime/namespace.d.ts +193 -0
  21. package/dist/runtime/namespace.d.ts.map +1 -0
  22. package/dist/runtime/package.d.ts +22 -0
  23. package/dist/runtime/package.d.ts.map +1 -0
  24. package/dist/runtime/package.mjs +33 -0
  25. package/dist/runtime/schemas.d.ts +26 -0
  26. package/dist/runtime/schemas.d.ts.map +1 -0
  27. package/dist/runtime/session-config-handler.d.ts +40 -0
  28. package/dist/runtime/session-config-handler.d.ts.map +1 -0
  29. package/dist/runtime/settings-paths.d.ts +43 -0
  30. package/dist/runtime/settings-paths.d.ts.map +1 -0
  31. package/dist/runtime/wiring.d.ts +88 -0
  32. package/dist/runtime/wiring.d.ts.map +1 -0
  33. package/dist/schemas/config.d.ts +306 -0
  34. package/dist/schemas/config.d.ts.map +1 -0
  35. package/dist/schemas/index.d.ts +9 -0
  36. package/dist/schemas/index.d.ts.map +1 -0
  37. package/dist/schemas/wiring.d.ts +108 -0
  38. package/dist/schemas/wiring.d.ts.map +1 -0
  39. package/dist/server.d.ts +3 -0
  40. package/dist/server.d.ts.map +1 -0
  41. package/dist/server.mjs +8 -0
  42. package/dist/src-DkIyzWF3.mjs +19 -0
  43. package/package.json +49 -0
@@ -0,0 +1,1551 @@
1
+ import { codexCapabilityMap, createClientDefinition } from "@makaio/framework/contracts";
2
+ import { z } from "zod";
3
+ import { AbsolutePathSchema, BinaryNotFoundError, ClientSubjects, ClientWiringApplyResponseSchema, ClientWiringListResponseSchema, ClientWiringRemoveResponseSchema, assertAbsoluteProjectDir, atomicModifyFile, buildHookCommand, createClientNamespace, deriveSessionEventDescriptors, pickNonEmptyString } from "@makaio/framework/clients";
4
+ import { MakaioBus, RequestError } from "@makaio/framework/bus";
5
+ import { BaseService } from "@makaio/framework/service-base";
6
+ import * as fs$1 from "node:fs/promises";
7
+ import fs from "node:fs/promises";
8
+ import * as path$1 from "node:path";
9
+ import path from "node:path";
10
+ import os from "node:os";
11
+ import { randomUUID } from "node:crypto";
12
+ import { ClientConfigPrimeSchema, SessionConfigSetupRequestSchema, SessionConfigSetupResponseSchema } from "@makaio/framework/contracts/client";
13
+
14
+ //#region src/definition.ts
15
+ /**
16
+ * Client definition for the OpenAI Codex CLI.
17
+ *
18
+ * Codex is a first-party agentic coding assistant binary (`codex`) that
19
+ * Makaio harnesses via the codex-app-server adapter. Capability annotations
20
+ * are derived from `codexCapabilityMap` in `@makaio/contracts` to keep
21
+ * capability taxonomy in a single canonical location.
22
+ * @packageDocumentation
23
+ */
24
+ /**
25
+ * Static client definition for `@makaio/client-codex`.
26
+ *
27
+ * Declares the two native tools the `codex` binary exposes (`bash` and
28
+ * `patch`) and the recommended default approval policy for new harnesses
29
+ * targeting this client.
30
+ */
31
+ const clientDefinition = createClientDefinition({
32
+ id: "codex",
33
+ name: "Codex",
34
+ version: "0.1.0",
35
+ description: "OpenAI Codex CLI — an agentic coding assistant",
36
+ binary: {
37
+ name: "codex",
38
+ supportedVersions: "0.130.0"
39
+ },
40
+ managedInstall: {
41
+ type: "npm",
42
+ package: "@openai/codex",
43
+ version: "0.130.0"
44
+ },
45
+ versionCommand: {
46
+ executable: {
47
+ default: "node_modules/.bin/codex",
48
+ win32: "node_modules/.bin/codex.cmd"
49
+ },
50
+ args: ["--version"]
51
+ },
52
+ configIsolation: {
53
+ envVar: "CODEX_HOME",
54
+ defaultPath: "~/.codex"
55
+ },
56
+ nativeTools: [{
57
+ name: "bash",
58
+ friendlyName: "Terminal",
59
+ description: "Execute shell commands in the Codex sandbox",
60
+ category: "System",
61
+ capabilities: (codexCapabilityMap.bash ?? []).map((tag) => ({ tag }))
62
+ }, {
63
+ name: "patch",
64
+ friendlyName: "Patch File",
65
+ description: "Apply unified diff patches to files",
66
+ category: "Files",
67
+ capabilities: (codexCapabilityMap.patch ?? []).map((tag) => ({ tag }))
68
+ }],
69
+ defaultApprovalPolicy: "full-access",
70
+ defaultProviderId: "openai-codex",
71
+ runtimeCapabilities: {
72
+ supportsHooks: true,
73
+ supportsStatusline: false,
74
+ supportsSupervisorLaunch: true,
75
+ supportsManagedBinary: true,
76
+ hookEvents: [
77
+ {
78
+ name: "SessionStart",
79
+ frameworkSubject: "client.session.started"
80
+ },
81
+ {
82
+ name: "UserPromptSubmit",
83
+ frameworkSubject: "client.session.userPrompt.submitted"
84
+ },
85
+ {
86
+ name: "PreToolUse",
87
+ frameworkSubject: "client.session.tool.pre"
88
+ },
89
+ {
90
+ name: "PostToolUse",
91
+ frameworkSubject: "client.session.tool.post"
92
+ },
93
+ {
94
+ name: "Stop",
95
+ frameworkSubject: "client.session.turn.completed"
96
+ }
97
+ ]
98
+ }
99
+ });
100
+
101
+ //#endregion
102
+ //#region src/schemas/config.ts
103
+ /**
104
+ * Codex config management schemas.
105
+ *
106
+ * Defines request/response schema pairs for the `config.hooks.*` subjects in
107
+ * the Codex client namespace. Public request and response payloads expose a
108
+ * flat command-hook view, while `CodexNativeHooksFileSchema` models Codex's
109
+ * nested on-disk `hooks.json` structure for lossless runtime I/O.
110
+ *
111
+ * **Subjects:**
112
+ * - `config.hooks.list` — list effective hooks for a project directory
113
+ * - `config.hooks.add` — add a new hook entry to a scope's config
114
+ * - `config.hooks.remove` — remove hook entries matching a command pattern
115
+ * @packageDocumentation
116
+ */
117
+ /**
118
+ * Scope enum for Codex hook configuration.
119
+ *
120
+ * - `global` — applies to all Codex sessions regardless of project directory
121
+ * - `project` — scoped to a specific project directory
122
+ */
123
+ const CodexScopeSchema = z.enum(["global", "project"]);
124
+ /**
125
+ * A single command hook entry in the public config-management API.
126
+ *
127
+ * Codex stores command hooks under `hooks[event][].hooks[]` on disk. The bus
128
+ * API flattens that native shape into `{ event, matcher?, command, timeout? }`
129
+ * so callers can manage hooks without duplicating file-format details.
130
+ */
131
+ const CodexHookEntrySchema = z.object({
132
+ /**
133
+ * The hook event name that triggers this hook (e.g. `SessionStart`,
134
+ * `PreToolUse`).
135
+ */
136
+ event: z.string(),
137
+ /**
138
+ * Optional glob pattern to restrict which tool names trigger the hook.
139
+ * Absent means the hook applies to all tools for the given event.
140
+ */
141
+ matcher: z.string().optional(),
142
+ /**
143
+ * Shell command to execute when the hook fires.
144
+ */
145
+ command: z.string(),
146
+ /**
147
+ * Optional timeout in seconds for the hook command.
148
+ * Absent means no timeout override (the Codex CLI default applies).
149
+ */
150
+ timeout: z.number().optional()
151
+ });
152
+ /**
153
+ * Native Codex command hook handler as stored inside a matcher group.
154
+ */
155
+ const CodexNativeCommandHookSchema = z.object({
156
+ /** Codex hook handler type. */
157
+ type: z.literal("command"),
158
+ /** Shell command to execute when the hook fires. */
159
+ command: z.string(),
160
+ /** Optional UI status message shown while the hook runs. */
161
+ statusMessage: z.string().optional(),
162
+ /** Optional timeout in seconds. */
163
+ timeout: z.number().optional(),
164
+ /** Alternate timeout spelling accepted by Codex. */
165
+ timeoutSec: z.number().optional()
166
+ }).passthrough();
167
+ /**
168
+ * Native Codex matcher group as stored under a hook event key.
169
+ */
170
+ const CodexNativeHookMatcherGroupSchema = z.object({
171
+ /**
172
+ * Optional regex matcher. `undefined`, `""`, and `"*"` are all handled by
173
+ * Codex as broad matches depending on event support.
174
+ */
175
+ matcher: z.string().optional(),
176
+ /** Hook handlers to run when the matcher group applies. */
177
+ hooks: z.array(z.unknown())
178
+ }).passthrough();
179
+ /**
180
+ * Native Codex `hooks.json` file shape.
181
+ *
182
+ * Top-level and nested objects are passthrough so runtime writes preserve
183
+ * fields introduced by newer Codex versions instead of stripping them.
184
+ */
185
+ const CodexNativeHooksFileSchema = z.object({ hooks: z.record(z.string(), z.array(CodexNativeHookMatcherGroupSchema)).optional() }).passthrough();
186
+ /**
187
+ * Per-scope hook configuration record returned by `config.hooks.list`.
188
+ *
189
+ * Describes all hooks registered at a single config scope together with
190
+ * the path to the backing config file and whether it can be written.
191
+ */
192
+ const CodexScopeHookRecordSchema = z.object({
193
+ /**
194
+ * Config scope this record belongs to.
195
+ */
196
+ scope: CodexScopeSchema,
197
+ /**
198
+ * Absolute path to the config file backing this scope.
199
+ */
200
+ path: z.string(),
201
+ /**
202
+ * Whether the backing config file can be written by the current process.
203
+ */
204
+ writable: z.boolean(),
205
+ /**
206
+ * All hooks registered at this scope.
207
+ */
208
+ hooks: z.array(CodexHookEntrySchema)
209
+ });
210
+ /**
211
+ * Request schema for `config.hooks.list`.
212
+ *
213
+ * Returns the effective hook configuration for the given project directory,
214
+ * optionally filtered to a specific event name.
215
+ */
216
+ const CodexConfigHooksListRequestSchema = z.object({
217
+ /**
218
+ * Optional absolute path to the project directory.
219
+ * When absent, only the global scope is included — project-scoped hooks
220
+ * are omitted from both `effective` and `perScope`.
221
+ */
222
+ projectDir: AbsolutePathSchema.optional(),
223
+ /**
224
+ * Optional event name to filter hooks by (e.g. `PreToolUse`).
225
+ * When absent all event hooks are returned.
226
+ */
227
+ eventName: z.string().optional()
228
+ });
229
+ /**
230
+ * Response schema for `config.hooks.list`.
231
+ *
232
+ * Returns the merged effective hook list alongside the per-scope breakdown so
233
+ * consumers can determine which config file contributes each hook.
234
+ */
235
+ const CodexConfigHooksListResponseSchema = z.object({
236
+ /**
237
+ * Effective merged hook list after applying scope precedence rules.
238
+ * This is the set of hooks that actually fire for a Codex session
239
+ * targeting the given project directory.
240
+ */
241
+ effective: z.array(CodexHookEntrySchema),
242
+ /**
243
+ * Per-scope breakdown of hook configuration, ordered from lowest precedence
244
+ * (global) to highest (project). Each entry includes the backing file path
245
+ * and writability flag.
246
+ */
247
+ perScope: z.array(CodexScopeHookRecordSchema)
248
+ });
249
+ /**
250
+ * Request schema for `config.hooks.add`.
251
+ *
252
+ * Adds a new hook entry to the specified scope's configuration.
253
+ * The hook entry fields (`event`, `matcher`, `command`, `timeout`) are
254
+ * composed via {@link CodexHookEntrySchema} so any change to the hook shape
255
+ * propagates here automatically.
256
+ */
257
+ const CodexConfigHooksAddRequestSchema = z.object({
258
+ /**
259
+ * Optional absolute path to the project directory.
260
+ * Required when `scope` is `project` so the correct project config file
261
+ * can be located.
262
+ */
263
+ projectDir: AbsolutePathSchema.optional(),
264
+ /**
265
+ * Config scope to write the hook into.
266
+ */
267
+ scope: CodexScopeSchema
268
+ }).merge(CodexHookEntrySchema).refine((data) => data.scope === "global" || data.projectDir !== void 0, {
269
+ message: "projectDir is required when scope is project",
270
+ path: ["projectDir"]
271
+ });
272
+ /**
273
+ * Response schema for `config.hooks.add`.
274
+ */
275
+ const CodexConfigHooksAddResponseSchema = z.object({
276
+ /**
277
+ * Whether the hook entry was successfully written to the config file.
278
+ */
279
+ added: z.boolean() });
280
+ /**
281
+ * Request schema for `config.hooks.remove`.
282
+ *
283
+ * Removes hook entries from the specified scope's configuration that match
284
+ * both the `event` and the command substring filter.
285
+ */
286
+ const CodexConfigHooksRemoveRequestSchema = z.object({
287
+ /**
288
+ * Optional absolute path to the project directory.
289
+ * Required when `scope` is `project`.
290
+ */
291
+ projectDir: AbsolutePathSchema.optional(),
292
+ /**
293
+ * Config scope from which to remove matching hooks.
294
+ */
295
+ scope: CodexScopeSchema,
296
+ /**
297
+ * Event name to match against when selecting hooks for removal.
298
+ */
299
+ event: z.string(),
300
+ /**
301
+ * Filter criteria selecting which hooks to remove.
302
+ * All hooks whose `command` field contains {@link commandContains} as a
303
+ * substring are removed.
304
+ */
305
+ match: z.object({
306
+ /**
307
+ * Substring that must appear in the hook command for removal.
308
+ */
309
+ commandContains: z.string() })
310
+ }).refine((data) => data.scope === "global" || data.projectDir !== void 0, {
311
+ message: "projectDir is required when scope is project",
312
+ path: ["projectDir"]
313
+ });
314
+ /**
315
+ * Response schema for `config.hooks.remove`.
316
+ */
317
+ const CodexConfigHooksRemoveResponseSchema = z.object({
318
+ /**
319
+ * Number of hook entries removed from the config file.
320
+ */
321
+ removed: z.number() });
322
+ /**
323
+ * Schema record for Codex config management subjects.
324
+ *
325
+ * Pass this record as the `additionalSchemas` argument to
326
+ * {@link createClientNamespace} to register these request/response subjects
327
+ * in the `client:codex.*` namespace.
328
+ *
329
+ * Keys use dotted notation matching the bus subject naming convention:
330
+ * - `config.hooks.list`
331
+ * - `config.hooks.add`
332
+ * - `config.hooks.remove`
333
+ * @example
334
+ * ```typescript
335
+ * import { createClientNamespace } from '@makaio/clients-core';
336
+ * import { CodexConfigSchemas } from './schemas/index.js';
337
+ *
338
+ * const { subjects } = createClientNamespace('codex', CodexConfigSchemas);
339
+ * // subjects.config.hooks.list → 'client:codex.config.hooks.list'
340
+ * ```
341
+ */
342
+ const CodexConfigSchemas = {
343
+ /**
344
+ * List effective hook configuration for a project directory.
345
+ *
346
+ * Merges global and project-scoped hooks and returns both the effective list
347
+ * and the per-scope breakdown.
348
+ */
349
+ "config.hooks.list": {
350
+ request: CodexConfigHooksListRequestSchema,
351
+ response: CodexConfigHooksListResponseSchema
352
+ },
353
+ /**
354
+ * Add a new hook entry to a config scope.
355
+ */
356
+ "config.hooks.add": {
357
+ request: CodexConfigHooksAddRequestSchema,
358
+ response: CodexConfigHooksAddResponseSchema
359
+ },
360
+ /**
361
+ * Remove hook entries matching an event and command substring from a scope.
362
+ */
363
+ "config.hooks.remove": {
364
+ request: CodexConfigHooksRemoveRequestSchema,
365
+ response: CodexConfigHooksRemoveResponseSchema
366
+ }
367
+ };
368
+
369
+ //#endregion
370
+ //#region src/schemas/wiring.ts
371
+ /**
372
+ * Schema record for Codex wiring management subjects.
373
+ *
374
+ * Pass this record (spread) as part of the `additionalSchemas` argument to
375
+ * {@link createClientNamespace} to register these request/response subjects
376
+ * in the `client:codex.*` namespace.
377
+ *
378
+ * Keys use dotted notation matching the bus subject naming convention:
379
+ * - `wiring.list`
380
+ * - `wiring.apply`
381
+ * - `wiring.remove`
382
+ * @example
383
+ * ```typescript
384
+ * import { createClientNamespace } from '@makaio/clients-core';
385
+ * import { CodexConfigSchemas } from './schemas/config.js';
386
+ * import { CodexWiringSchemas } from './schemas/wiring.js';
387
+ *
388
+ * const { subjects } = createClientNamespace('codex', {
389
+ * ...CodexConfigSchemas,
390
+ * ...CodexWiringSchemas,
391
+ * });
392
+ * // subjects.wiring.list → 'client:codex.wiring.list'
393
+ * ```
394
+ */
395
+ const CodexWiringSchemas = {
396
+ /**
397
+ * List all known wiring entries for the target scope, indicating which are
398
+ * currently installed in the Codex native config.
399
+ *
400
+ * When `projectDir` is absent, only the `global` scope entries are reported.
401
+ * Callers that need project-scope entries must supply the absolute path to
402
+ * the project root.
403
+ */
404
+ "wiring.list": {
405
+ request: z.object({
406
+ /**
407
+ * Absolute path of the project directory used to locate project-scope
408
+ * config files. When absent, only the global scope is consulted.
409
+ */
410
+ projectDir: AbsolutePathSchema.optional(),
411
+ /**
412
+ * Makaio shell command to use when building the expected command strings
413
+ * in the response entries.
414
+ */
415
+ makaioCommand: z.string().min(1)
416
+ }),
417
+ response: ClientWiringListResponseSchema
418
+ },
419
+ /**
420
+ * Install all wiring entries into the specified scope.
421
+ *
422
+ * Entries already present are skipped (idempotent). The `makaioCommand`
423
+ * string is written verbatim as the shell command for hook entries.
424
+ */
425
+ "wiring.apply": {
426
+ request: z.object({
427
+ /** Scope at which to install the wiring entries. */
428
+ scope: CodexScopeSchema,
429
+ /**
430
+ * Absolute path of the project directory. Required when `scope` is
431
+ * `'project'`; ignored for `'global'`.
432
+ */
433
+ projectDir: AbsolutePathSchema.optional(),
434
+ /**
435
+ * Makaio shell command to write into the native config. Must be
436
+ * non-empty.
437
+ */
438
+ makaioCommand: z.string().min(1)
439
+ }).refine((data) => data.scope === "global" || data.projectDir !== void 0, {
440
+ message: "projectDir is required when scope is project",
441
+ path: ["projectDir"]
442
+ }),
443
+ response: ClientWiringApplyResponseSchema
444
+ },
445
+ /**
446
+ * Uninstall all wiring entries from the specified scope.
447
+ *
448
+ * Entries that are not present are silently ignored (idempotent). The
449
+ * `removed` count in the response reflects only entries that were actually
450
+ * deleted from the config file.
451
+ */
452
+ "wiring.remove": {
453
+ request: z.object({
454
+ /** Scope from which to remove wiring entries. */
455
+ scope: CodexScopeSchema,
456
+ /**
457
+ * Absolute path of the project directory. Required when `scope` is
458
+ * `'project'`; ignored for `'global'`.
459
+ */
460
+ projectDir: AbsolutePathSchema.optional()
461
+ }).refine((data) => data.scope === "global" || data.projectDir !== void 0, {
462
+ message: "projectDir is required when scope is project",
463
+ path: ["projectDir"]
464
+ }),
465
+ response: ClientWiringRemoveResponseSchema
466
+ }
467
+ };
468
+
469
+ //#endregion
470
+ //#region src/runtime/settings-paths.ts
471
+ /**
472
+ * Codex client settings path resolution.
473
+ *
474
+ * Provides a pure utility for computing the filesystem paths where Codex
475
+ * stores its `hooks.json` configuration files — one global path under the
476
+ * resolved Codex config directory and an optional project-scoped path.
477
+ *
478
+ * No filesystem I/O is performed; callers are responsible for reading,
479
+ * writing, or watching the returned paths.
480
+ * @packageDocumentation
481
+ */
482
+ /** Resolved home directory, captured once at module load time. */
483
+ const HOME_DIR = os.homedir();
484
+ /**
485
+ * Resolve the filesystem paths for Codex `hooks.json` configuration files.
486
+ *
487
+ * This is a pure function: it performs no I/O and has no side effects.
488
+ * The returned paths may or may not exist on disk.
489
+ * @param projectDir - Absolute path to the project root directory. When
490
+ * provided, {@link CodexSettingsPaths.projectHooks} is set to
491
+ * `{projectDir}/.codex/hooks.json`. When omitted, `projectHooks` is `null`.
492
+ * @param configDir - Optional Codex config root. When omitted, the global
493
+ * hooks path falls back to `~/.codex/hooks.json`.
494
+ * @returns Resolved paths for the global and optional project-scoped Codex
495
+ * hooks configuration files.
496
+ */
497
+ function resolveCodexSettingsPaths(projectDir, configDir) {
498
+ const globalRoot = configDir ?? path.join(HOME_DIR, ".codex");
499
+ return {
500
+ globalHooks: path.join(globalRoot, "hooks.json"),
501
+ projectHooks: projectDir !== void 0 ? path.join(projectDir, ".codex", "hooks.json") : null
502
+ };
503
+ }
504
+
505
+ //#endregion
506
+ //#region src/runtime/client-settings.ts
507
+ /**
508
+ * Codex client settings I/O.
509
+ *
510
+ * Provides filesystem read/write operations for Codex `hooks.json`
511
+ * configuration files. Handles both global and project-scoped config files
512
+ * with atomic writes and per-path write serialization to prevent concurrent
513
+ * modification.
514
+ * @packageDocumentation
515
+ */
516
+ /** Empty native hooks file used as the baseline for missing files. */
517
+ const EMPTY_HOOKS_FILE = { hooks: {} };
518
+ /**
519
+ * Global per-path write mutex shared across all {@link CodexClientSettings}
520
+ * instances. Module-scoped so that concurrent requests routed to different
521
+ * service-handler instances still serialize writes to the same file.
522
+ */
523
+ const writeMutex = /* @__PURE__ */ new Map();
524
+ /**
525
+ * Handles reading and writing Codex `hooks.json` configuration files.
526
+ *
527
+ * This is a plain composed component — not a `BaseService` — intended to be
528
+ * instantiated inside `CodexClientSessionService` or a similar host.
529
+ *
530
+ * ## Atomic writes
531
+ * All writes go through a temp-file + `fs.rename()` sequence so that readers
532
+ * never see a partially written file.
533
+ *
534
+ * ## Write serialization
535
+ * A module-scoped per-path mutex ensures that concurrent callers modifying
536
+ * the same file are serialized rather than racing, even across instances.
537
+ */
538
+ var CodexClientSettings = class {
539
+ /**
540
+ * Optional path override used in tests. When `undefined`, paths are
541
+ * resolved dynamically via {@link resolveCodexSettingsPaths}.
542
+ */
543
+ pathsOverride;
544
+ /** Optional managed Codex config root for global-scope hooks. */
545
+ configDir;
546
+ /**
547
+ * Creates a new `CodexClientSettings` instance.
548
+ * @param options - Optional path override or config-root options. Passing
549
+ * `{ globalHooks, projectHooks }` remains supported for existing tests.
550
+ */
551
+ constructor(options) {
552
+ if (options !== void 0 && "globalHooks" in options) {
553
+ this.pathsOverride = options;
554
+ this.configDir = void 0;
555
+ return;
556
+ }
557
+ this.pathsOverride = options?.pathsOverride;
558
+ this.configDir = options?.configDir;
559
+ }
560
+ /**
561
+ * List the effective hook configuration for a project directory.
562
+ *
563
+ * Reads both the global and (when available) project-scoped config files,
564
+ * concatenates their hooks into an effective list, and returns the per-scope
565
+ * breakdown alongside it.
566
+ * @param req - Request options. `projectDir` is the optional absolute path to
567
+ * the project root (when omitted only the global scope is read).
568
+ * `eventName` is an optional event name filter; when omitted all hooks are
569
+ * returned.
570
+ * @returns Effective merged hook list and per-scope breakdown.
571
+ */
572
+ async listHooks(req) {
573
+ const paths = this.resolvePaths(req.projectDir);
574
+ const globalPromise = Promise.all([this.readHooksFile(paths.globalHooks), this.isWritable(paths.globalHooks)]);
575
+ const projectPath = paths.projectHooks;
576
+ const [globalHooks, globalWritable] = await globalPromise;
577
+ const perScope = [{
578
+ scope: "global",
579
+ path: paths.globalHooks,
580
+ writable: globalWritable,
581
+ hooks: globalHooks
582
+ }];
583
+ if (projectPath !== null) {
584
+ const [projectHooks, projectWritable] = await Promise.all([this.readHooksFile(projectPath), this.isWritable(projectPath)]);
585
+ perScope.push({
586
+ scope: "project",
587
+ path: projectPath,
588
+ writable: projectWritable,
589
+ hooks: projectHooks
590
+ });
591
+ }
592
+ let effective = perScope.flatMap((record) => record.hooks);
593
+ if (req.eventName !== void 0) effective = effective.filter((entry) => entry.event === req.eventName);
594
+ return {
595
+ effective,
596
+ perScope
597
+ };
598
+ }
599
+ /**
600
+ * Add a new hook entry to the specified config scope.
601
+ *
602
+ * The operation is idempotent: if a hook with the same `event`, `command`,
603
+ * and `matcher` already exists in the target file, the file is left
604
+ * unchanged and `{ added: false }` is returned.
605
+ * @param req - Hook entry and targeting options. `scope` selects the config
606
+ * file; `projectDir` is required when `scope` is `'project'`. `event`,
607
+ * `command`, and optional `matcher` / `timeout` form the hook entry.
608
+ * @returns `{ added: true }` when the hook was appended, `{ added: false }`
609
+ * when an identical hook already exists.
610
+ */
611
+ async addHook(req) {
612
+ const filePath = this.resolvePathForScope(req.scope, req.projectDir);
613
+ const { result } = await this.modifyHooksFile(filePath, (hooks) => {
614
+ const eventGroups = hooks.hooks?.[req.event] ?? [];
615
+ if (eventGroups.some((group) => {
616
+ if (group.matcher !== req.matcher) return false;
617
+ return group.hooks.some((handler) => {
618
+ const parseResult = CodexNativeCommandHookSchema.safeParse(handler);
619
+ return parseResult.success && parseResult.data.command === req.command;
620
+ });
621
+ })) return {
622
+ hooks,
623
+ result: { added: false },
624
+ changed: false
625
+ };
626
+ const entry = {
627
+ type: "command",
628
+ command: req.command,
629
+ ...req.timeout !== void 0 ? { timeout: req.timeout } : {}
630
+ };
631
+ const matchingGroupIndex = eventGroups.findIndex((group) => group.matcher === req.matcher);
632
+ const updatedGroups = matchingGroupIndex === -1 ? [...eventGroups, {
633
+ ...req.matcher !== void 0 ? { matcher: req.matcher } : {},
634
+ hooks: [entry]
635
+ }] : eventGroups.map((group, index) => index === matchingGroupIndex ? {
636
+ ...group,
637
+ hooks: [...group.hooks, entry]
638
+ } : group);
639
+ return {
640
+ hooks: {
641
+ ...hooks,
642
+ hooks: {
643
+ ...hooks.hooks ?? {},
644
+ [req.event]: updatedGroups
645
+ }
646
+ },
647
+ result: { added: true },
648
+ changed: true
649
+ };
650
+ });
651
+ return result;
652
+ }
653
+ /**
654
+ * Remove hook entries from the specified config scope.
655
+ *
656
+ * Removes all hooks where `entry.event === req.event` and
657
+ * `entry.command.includes(req.match.commandContains)`.
658
+ * @param req - Removal criteria and targeting options. `scope` selects the
659
+ * config file; `projectDir` is required when `scope` is `'project'`.
660
+ * `event` is the event name to match. `match.commandContains` is the
661
+ * command substring filter — any hook whose command contains this string
662
+ * is removed.
663
+ * @returns `{ removed: n }` where `n` is the count of removed hooks.
664
+ */
665
+ async removeHook(req) {
666
+ const filePath = this.resolvePathForScope(req.scope, req.projectDir);
667
+ const { result } = await this.modifyHooksFile(filePath, (hooks) => {
668
+ const eventGroups = hooks.hooks?.[req.event] ?? [];
669
+ let removed = 0;
670
+ const updatedGroups = eventGroups.map((group) => {
671
+ const remainingHandlers = group.hooks.filter((handler) => {
672
+ const parseResult = CodexNativeCommandHookSchema.safeParse(handler);
673
+ const shouldRemove = parseResult.success && parseResult.data.command.includes(req.match.commandContains);
674
+ if (shouldRemove) removed += 1;
675
+ return !shouldRemove;
676
+ });
677
+ return {
678
+ ...group,
679
+ hooks: remainingHandlers
680
+ };
681
+ }).filter((group) => group.hooks.length > 0);
682
+ if (removed === 0) return {
683
+ hooks,
684
+ result: { removed },
685
+ changed: false
686
+ };
687
+ const updatedHooksByEvent = { ...hooks.hooks ?? {} };
688
+ if (updatedGroups.length === 0) delete updatedHooksByEvent[req.event];
689
+ else updatedHooksByEvent[req.event] = updatedGroups;
690
+ return {
691
+ hooks: {
692
+ ...hooks,
693
+ hooks: updatedHooksByEvent
694
+ },
695
+ result: { removed },
696
+ changed: true
697
+ };
698
+ });
699
+ return result;
700
+ }
701
+ /**
702
+ * Resolve config file path for a given scope.
703
+ *
704
+ * For `'project'` scope, `projectDir` must be provided. For `'global'`
705
+ * scope, `projectDir` is ignored.
706
+ * @param scope - Target config scope.
707
+ * @param projectDir - Optional absolute path to the project root.
708
+ * @returns Absolute path to the `hooks.json` file for the given scope.
709
+ * @throws When `scope === 'project'` and `projectDir` is absent.
710
+ */
711
+ resolvePathForScope(scope, projectDir) {
712
+ const paths = this.resolvePaths(projectDir);
713
+ if (scope === "project") {
714
+ if (!paths.projectHooks) throw new Error("Cannot access project scope: projectDir is required");
715
+ return paths.projectHooks;
716
+ }
717
+ return paths.globalHooks;
718
+ }
719
+ /**
720
+ * Resolve settings paths, applying the optional test override when present.
721
+ * @param projectDir - Optional project root passed through to
722
+ * {@link resolveCodexSettingsPaths} when no override is active.
723
+ * @returns Resolved settings paths.
724
+ */
725
+ resolvePaths(projectDir) {
726
+ if (this.pathsOverride !== void 0) return this.pathsOverride;
727
+ return resolveCodexSettingsPaths(projectDir, this.configDir);
728
+ }
729
+ /**
730
+ * Check whether the current process can write to a path.
731
+ *
732
+ * First tests the file itself (when it exists). If that check fails or the
733
+ * file does not yet exist, walks up from the parent directory until an
734
+ * existing ancestor is found and checks write permission there. This handles
735
+ * the fresh-install case where neither the file nor its `.codex/` parent
736
+ * exist yet — the write helper creates the tree via `mkdir({ recursive })`,
737
+ * so writability depends on the nearest existing ancestor.
738
+ * @param filePath - Absolute path to the file to test.
739
+ * @returns `true` when the process has write access, `false` otherwise.
740
+ */
741
+ async isWritable(filePath) {
742
+ try {
743
+ await fs.access(filePath, fs.constants.W_OK);
744
+ return true;
745
+ } catch {
746
+ let candidate = path.dirname(filePath);
747
+ for (;;) try {
748
+ await fs.access(candidate, fs.constants.W_OK);
749
+ return true;
750
+ } catch (error) {
751
+ if (error.code !== "ENOENT") return false;
752
+ const parent = path.dirname(candidate);
753
+ if (parent === candidate) return false;
754
+ candidate = parent;
755
+ }
756
+ }
757
+ }
758
+ /**
759
+ * Read and parse a Codex `hooks.json` file.
760
+ *
761
+ * Returns an empty array when the file does not exist (`ENOENT`) or when no
762
+ * command hooks are configured. Re-throws `SyntaxError` on corrupt JSON and
763
+ * permission errors without swallowing. Throws a descriptive `Error` when the
764
+ * native `hooks` tree fails schema validation.
765
+ * @param filePath - Absolute path to the `hooks.json` file.
766
+ * @returns Flattened command hook entries for public bus responses.
767
+ * @throws When a `hooks` field is present but does not conform to Codex's
768
+ * native grouped hook schema.
769
+ */
770
+ async readHooksFile(filePath) {
771
+ return this.flattenHooksFile(await this.readHooksDocument(filePath));
772
+ }
773
+ /**
774
+ * Read and parse a native Codex `hooks.json` file.
775
+ *
776
+ * Missing files resolve to an empty native document. Unknown top-level and
777
+ * nested fields are preserved by the schema so read-modify-write operations
778
+ * can update a target hook without dropping newer Codex fields.
779
+ * @param filePath - Absolute path to the `hooks.json` file.
780
+ * @returns Parsed native hooks document.
781
+ * @throws When JSON parsing fails or the `hooks` field is not a native Codex
782
+ * event-to-matcher-group map.
783
+ */
784
+ async readHooksDocument(filePath) {
785
+ let content;
786
+ try {
787
+ content = await fs.readFile(filePath, "utf-8");
788
+ } catch (error) {
789
+ if (error.code === "ENOENT") return { ...EMPTY_HOOKS_FILE };
790
+ throw error;
791
+ }
792
+ const parsed = JSON.parse(content);
793
+ const parseResult = CodexNativeHooksFileSchema.safeParse(parsed);
794
+ if (!parseResult.success) throw new Error(`Invalid hooks file ${filePath}: ${parseResult.error.message}`);
795
+ return parseResult.data;
796
+ }
797
+ /**
798
+ * Flatten a native Codex hooks document into the public command-hook view.
799
+ * @param document - Native Codex hooks document.
800
+ * @returns Flat command-hook entries ordered by event, matcher group, then
801
+ * handler order.
802
+ */
803
+ flattenHooksFile(document) {
804
+ const entries = [];
805
+ for (const [event, groups] of Object.entries(document.hooks ?? {})) for (const group of groups) for (const rawHandler of group.hooks) {
806
+ const parseResult = CodexNativeCommandHookSchema.safeParse(rawHandler);
807
+ if (!parseResult.success) continue;
808
+ const handler = parseResult.data;
809
+ entries.push({
810
+ event,
811
+ ...group.matcher !== void 0 ? { matcher: group.matcher } : {},
812
+ command: handler.command,
813
+ ...handler.timeout !== void 0 ? { timeout: handler.timeout } : handler.timeoutSec !== void 0 ? { timeout: handler.timeoutSec } : {}
814
+ });
815
+ }
816
+ return entries;
817
+ }
818
+ /**
819
+ * Read, modify, and atomically write a Codex `hooks.json` file.
820
+ *
821
+ * Delegates serialization and atomic I/O to {@link atomicModifyFile}.
822
+ * The parent directory is created automatically when absent. Write errors
823
+ * surface to the current caller; the mutex queue continues regardless.
824
+ * @param filePath - Absolute path to the `hooks.json` file to modify.
825
+ * @param modifier - Pure function that receives the current native hooks
826
+ * document and returns the updated document, whether it changed, and a
827
+ * caller-defined result value.
828
+ * @returns The `result` value produced by the modifier.
829
+ */
830
+ async modifyHooksFile(filePath, modifier) {
831
+ return { result: await atomicModifyFile(filePath, { ...EMPTY_HOOKS_FILE }, writeMutex, (rawContent) => {
832
+ const parseResult = CodexNativeHooksFileSchema.safeParse(rawContent);
833
+ if (!parseResult.success) throw new Error(`Invalid hooks file ${filePath}: ${parseResult.error.message}`);
834
+ return parseResult.data;
835
+ }, (hooks) => {
836
+ const { hooks: updated, result: innerResult, changed } = modifier(hooks);
837
+ return {
838
+ content: updated,
839
+ changed,
840
+ result: innerResult
841
+ };
842
+ }) };
843
+ }
844
+ };
845
+
846
+ //#endregion
847
+ //#region src/runtime/config-prime-handler.ts
848
+ /**
849
+ * Codex config-prime handler.
850
+ *
851
+ * Handles the `client:codex.config.prime` delegation subject fired by the
852
+ * framework at three lifecycle phases: `managed-install`, `profile-create`,
853
+ * and `session-create`.
854
+ *
855
+ * The handler ensures that `check_for_update_on_startup = false` is present
856
+ * in the Codex `config.toml` file inside the target directory so that managed
857
+ * Codex processes never attempt to auto-update during a Makaio-controlled
858
+ * session. All other existing config keys are preserved; the key is replaced
859
+ * when it already exists with any value, and appended when absent.
860
+ *
861
+ * Writes are atomic (tmp-file + rename) to prevent readers from observing a
862
+ * partially written file. The operation is idempotent: when the file already
863
+ * contains the correct value the write is skipped entirely.
864
+ * @packageDocumentation
865
+ */
866
+ /**
867
+ * Prime the Codex `config.toml` in the target config directory.
868
+ *
869
+ * Ensures that `check_for_update_on_startup = false` is set, preserving all
870
+ * other existing key-value pairs. Existing occurrences of the key (with any
871
+ * value) are replaced; the key is appended when absent. Empty lines are
872
+ * stripped to keep the file compact.
873
+ *
874
+ * The write is atomic via a temporary file + `fs.rename()` to prevent readers
875
+ * from observing a partially written file. The operation is idempotent: when
876
+ * the file already contains the correct value the disk is not touched.
877
+ * @param payload - Config prime request containing `clientId`, `configDir`,
878
+ * and `phase`. Additional optional fields (`binaryVersion`, `adapterName`,
879
+ * `projectDir`) are accepted but not used by the Codex prime handler.
880
+ * @returns `{ primed: true }` on success.
881
+ */
882
+ async function handleCodexConfigPrime(payload) {
883
+ const configPath = path$1.join(payload.configDir, "config.toml");
884
+ await fs$1.mkdir(payload.configDir, { recursive: true });
885
+ let current = "";
886
+ try {
887
+ current = await fs$1.readFile(configPath, "utf-8");
888
+ } catch (error) {
889
+ if (error.code !== "ENOENT") throw error;
890
+ }
891
+ const lines = current.split(/\r?\n/).filter((line) => !line.trimStart().startsWith("check_for_update_on_startup")).filter((line) => line.length > 0);
892
+ const tableIndex = lines.findIndex((line) => line.trimStart().startsWith("["));
893
+ if (tableIndex === -1) lines.push("check_for_update_on_startup = false");
894
+ else lines.splice(tableIndex, 0, "check_for_update_on_startup = false");
895
+ const updated = `${lines.join("\n")}\n`;
896
+ if (updated === current) return { primed: true };
897
+ const tmpPath = path$1.join(payload.configDir, `config.toml.${randomUUID()}.tmp`);
898
+ await fs$1.writeFile(tmpPath, updated, "utf-8");
899
+ await fs$1.rename(tmpPath, configPath);
900
+ return { primed: true };
901
+ }
902
+
903
+ //#endregion
904
+ //#region src/runtime/hook-normalizer.ts
905
+ /**
906
+ * Pure normalizer for Codex CLI hook events.
907
+ *
908
+ * Maps the Codex-native hook event names emitted on
909
+ * `client:codex.hook.received` to their corresponding
910
+ * `client.session.*` observed-semantics subjects.
911
+ *
912
+ * **Mapping table** (Codex event → global subject):
913
+ *
914
+ * | Codex event name | Global subject |
915
+ * |--------------------------|------------------------------------------|
916
+ * | `SessionStart` | `client.session.started` |
917
+ * | `UserPromptSubmit` | `client.session.userPrompt.submitted` |
918
+ * | `Stop` | `client.session.turn.completed` |
919
+ * | `PreToolUse` | `client.session.tool.pre` |
920
+ * | `PostToolUse` | `client.session.tool.post` |
921
+ *
922
+ * All other event names are returned as `null` — they are kept raw only and
923
+ * are never emitted into the global `client.*` namespace.
924
+ *
925
+ * **Source notes:** The Codex CLI hook event names above reflect the OpenAI
926
+ * Codex CLI hook system as documented at the time of authoring. If the binary
927
+ * changes its hook names, update {@link CODEX_EVENT_MAP} accordingly.
928
+ * @packageDocumentation
929
+ */
930
+ /**
931
+ * Static map from Codex-native hook event name to the matching global subject.
932
+ *
933
+ * Update this map when the Codex CLI exposes new hook names that correspond
934
+ * to global session lifecycle events.
935
+ */
936
+ const CODEX_EVENT_MAP = new Map([
937
+ ["SessionStart", ClientSubjects.session.started],
938
+ ["UserPromptSubmit", ClientSubjects.session.userPrompt.submitted],
939
+ ["Stop", ClientSubjects.session.turn.completed],
940
+ ["PreToolUse", ClientSubjects.session.tool.pre],
941
+ ["PostToolUse", ClientSubjects.session.tool.post]
942
+ ]);
943
+ /**
944
+ * Extract optional session identifier from a raw Codex hook payload.
945
+ *
946
+ * Codex may report the session ID under `session_id` or `thread_id`.
947
+ * Both are checked because early events may use `thread_id` before a
948
+ * canonical session is established.
949
+ * @param payload - Raw hook payload object forwarded by the ingress bridge
950
+ * @returns Resolved adapter session ID string, or `undefined` when absent
951
+ */
952
+ function extractAdapterSessionId(payload) {
953
+ return pickNonEmptyString(payload, "session_id") ?? pickNonEmptyString(payload, "thread_id");
954
+ }
955
+ /**
956
+ * Extract optional tool name from a raw Codex hook payload.
957
+ *
958
+ * Codex reports the tool name under `tool_name` for pre/post tool calls.
959
+ * @param payload - Raw hook payload object
960
+ * @returns Tool name string, or `undefined` when absent
961
+ */
962
+ function extractToolName(payload) {
963
+ return pickNonEmptyString(payload, "tool_name");
964
+ }
965
+ /**
966
+ * Extract optional tool call correlation ID from a raw Codex hook payload.
967
+ * @param payload - Raw hook payload object
968
+ * @returns Tool call ID string, or `undefined` when absent
969
+ */
970
+ function extractToolCallId(payload) {
971
+ return pickNonEmptyString(payload, "call_id");
972
+ }
973
+ /**
974
+ * Extract optional tool execution outcome from a raw Codex post-tool payload.
975
+ *
976
+ * Codex reports whether the tool call succeeded under `success`.
977
+ * @param payload - Raw hook payload object
978
+ * @returns Boolean success flag, or `undefined` when absent
979
+ */
980
+ function extractSuccess(payload) {
981
+ return typeof payload["success"] === "boolean" ? payload["success"] : void 0;
982
+ }
983
+ /**
984
+ * Extract optional prompt text from a raw Codex user-prompt payload.
985
+ * @param payload - Raw hook payload object
986
+ * @returns Non-empty prompt string, or `undefined` when absent
987
+ */
988
+ function extractPrompt(payload) {
989
+ return pickNonEmptyString(payload, "prompt");
990
+ }
991
+ /**
992
+ * Normalize a raw Codex hook payload into a `client.session.*` event.
993
+ *
994
+ * Returns `null` for unknown or not-yet-modeled event names so the caller
995
+ * skips global emission and keeps the event raw-only in `client:codex.*`.
996
+ * @param raw - Raw hook payload delivered on `client:codex.hook.received`
997
+ * @returns Normalized event with subject and typed payload, or `null` when
998
+ * the event name is unknown
999
+ */
1000
+ function normalizeCodexHook(raw) {
1001
+ const subject = CODEX_EVENT_MAP.get(raw.eventName);
1002
+ if (subject === void 0) return null;
1003
+ const base = {
1004
+ clientId: "codex",
1005
+ source: "native-hook",
1006
+ observedAt: raw.receivedAt,
1007
+ adapterSessionId: extractAdapterSessionId(raw.payload),
1008
+ metadata: raw.metadata
1009
+ };
1010
+ switch (subject) {
1011
+ case ClientSubjects.session.started: return {
1012
+ subject,
1013
+ payload: { ...base }
1014
+ };
1015
+ case ClientSubjects.session.userPrompt.submitted: return {
1016
+ subject,
1017
+ payload: {
1018
+ ...base,
1019
+ prompt: extractPrompt(raw.payload)
1020
+ }
1021
+ };
1022
+ case ClientSubjects.session.turn.completed: return {
1023
+ subject,
1024
+ payload: { ...base }
1025
+ };
1026
+ case ClientSubjects.session.tool.pre: return {
1027
+ subject,
1028
+ payload: {
1029
+ ...base,
1030
+ toolName: extractToolName(raw.payload),
1031
+ toolCallId: extractToolCallId(raw.payload)
1032
+ }
1033
+ };
1034
+ case ClientSubjects.session.tool.post: return {
1035
+ subject,
1036
+ payload: {
1037
+ ...base,
1038
+ toolName: extractToolName(raw.payload),
1039
+ toolCallId: extractToolCallId(raw.payload),
1040
+ success: extractSuccess(raw.payload)
1041
+ }
1042
+ };
1043
+ default: return null;
1044
+ }
1045
+ }
1046
+
1047
+ //#endregion
1048
+ //#region src/runtime/namespace.ts
1049
+ /**
1050
+ * Codex client namespace registration.
1051
+ *
1052
+ * Registers `client:codex` on the singleton bus using the shared
1053
+ * {@link createClientNamespace} factory, which pre-registers the raw catch-all
1054
+ * hook ingress subject `hook.received` with the canonical
1055
+ * {@link RawClientHookPayloadSchema}.
1056
+ *
1057
+ * Additional subjects registered here:
1058
+ * - `config.hooks.list` — list effective hook configuration
1059
+ * - `config.hooks.add` — add a hook entry to a config scope
1060
+ * - `config.hooks.remove` — remove hook entries matching a pattern
1061
+ * - `config.prime` — blocking config-prime lifecycle hook
1062
+ * - `wiring.list` — list all wiring entries with installation status
1063
+ * - `wiring.apply` — install wiring entries into the target scope
1064
+ * - `wiring.remove` — uninstall wiring entries from the target scope
1065
+ * - `sessionConfig.setup` — seed an isolated session config directory
1066
+ *
1067
+ * **Subject conventions:**
1068
+ * - Raw Codex-native events flow in the `client:codex.*` namespace only.
1069
+ * - Normalized lifecycle observations flow in the global `client.session.*`
1070
+ * namespace after the {@link CodexHookNormalizer} maps them.
1071
+ * @packageDocumentation
1072
+ */
1073
+ const { subjects, namespaceDomain } = createClientNamespace("codex", {
1074
+ ...CodexConfigSchemas,
1075
+ ...CodexWiringSchemas,
1076
+ "config.prime": ClientConfigPrimeSchema,
1077
+ "sessionConfig.setup": {
1078
+ request: SessionConfigSetupRequestSchema,
1079
+ response: SessionConfigSetupResponseSchema
1080
+ }
1081
+ });
1082
+ /**
1083
+ * Typed bus subjects for the Codex client namespace (`client:codex.*`).
1084
+ *
1085
+ * Exposes the raw hook ingress subject, config management subjects, wiring
1086
+ * management subjects, the config-prime lifecycle hook, and the session config
1087
+ * setup subject:
1088
+ * - `CodexClientSubjects.hook.received` → `client:codex.hook.received`
1089
+ * - `CodexClientSubjects.config.hooks.list` → `client:codex.config.hooks.list`
1090
+ * - `CodexClientSubjects.config.hooks.add` → `client:codex.config.hooks.add`
1091
+ * - `CodexClientSubjects.config.hooks.remove` → `client:codex.config.hooks.remove`
1092
+ * - `CodexClientSubjects.config.prime` → `client:codex.config.prime`
1093
+ * - `CodexClientSubjects.wiring.list` → `client:codex.wiring.list`
1094
+ * - `CodexClientSubjects.wiring.apply` → `client:codex.wiring.apply`
1095
+ * - `CodexClientSubjects.wiring.remove` → `client:codex.wiring.remove`
1096
+ * - `CodexClientSubjects.sessionConfig.setup` → `client:codex.sessionConfig.setup`
1097
+ */
1098
+ const CodexClientSubjects = subjects;
1099
+ /**
1100
+ * Fully-qualified namespace domain string for the Codex client.
1101
+ *
1102
+ * Value: `'client:codex'`
1103
+ */
1104
+ const CODEX_CLIENT_NAMESPACE = namespaceDomain;
1105
+
1106
+ //#endregion
1107
+ //#region src/runtime/session-config-handler.ts
1108
+ /**
1109
+ * Codex session config setup handler.
1110
+ *
1111
+ * Implements the `client:codex.sessionConfig.setup` delegation request by
1112
+ * materializing the Codex config files needed for an isolated session
1113
+ * directory.
1114
+ *
1115
+ * The handler copies `config.toml` and `auth.json` from the base config
1116
+ * directory (when present) into the session-scoped directory, then primes the
1117
+ * session directory to ensure `check_for_update_on_startup = false` is set.
1118
+ *
1119
+ * When `sessionDir` and `baseConfigDir` resolve to the same path (the
1120
+ * framework passes `sessionDir` as `baseConfigDir` when no profile is
1121
+ * configured) the copy step is skipped and only the prime step runs.
1122
+ *
1123
+ * Returns `{ env: { CODEX_HOME: sessionDir } }` so the spawned Codex process
1124
+ * inherits the isolated session directory as its configuration root.
1125
+ * @packageDocumentation
1126
+ */
1127
+ /**
1128
+ * Copy a file if it exists at the source path; silently skip when absent.
1129
+ * @param src - Source file path.
1130
+ * @param dst - Destination file path.
1131
+ */
1132
+ async function copyIfPresent(src, dst) {
1133
+ try {
1134
+ await fs$1.copyFile(src, dst);
1135
+ } catch (error) {
1136
+ if (error.code !== "ENOENT") throw error;
1137
+ }
1138
+ }
1139
+ /**
1140
+ * Handle `client:codex.sessionConfig.setup` by seeding the session-scoped
1141
+ * directory with the appropriate Codex config files.
1142
+ *
1143
+ * Steps:
1144
+ * 1. Create `sessionDir` (recursive, no-op when it already exists).
1145
+ * 2. When `sessionDir` and `baseConfigDir` are distinct paths, copy
1146
+ * `config.toml` and `auth.json` from `baseConfigDir` into `sessionDir`
1147
+ * (each copy is skipped when the source file does not exist).
1148
+ * 3. Prime `sessionDir` via {@link handleCodexConfigPrime} to ensure
1149
+ * `check_for_update_on_startup = false` is set.
1150
+ * @param payload - Session config setup delegation payload. Only `sessionDir`,
1151
+ * `baseConfigDir`, and `projectDir` are used; `platform` and
1152
+ * `configInheritance` are accepted for interface compatibility but are not
1153
+ * required for Codex's simpler config model.
1154
+ * @returns Environment variables for the spawned Codex process: `CODEX_HOME`
1155
+ * pointing to `sessionDir`.
1156
+ */
1157
+ async function handleCodexSessionConfigSetup(payload) {
1158
+ const { sessionDir, baseConfigDir, projectDir } = payload;
1159
+ await fs$1.mkdir(sessionDir, { recursive: true });
1160
+ if (path$1.resolve(sessionDir) !== path$1.resolve(baseConfigDir)) {
1161
+ await copyIfPresent(path$1.join(baseConfigDir, "config.toml"), path$1.join(sessionDir, "config.toml"));
1162
+ await copyIfPresent(path$1.join(baseConfigDir, "auth.json"), path$1.join(sessionDir, "auth.json"));
1163
+ }
1164
+ await handleCodexConfigPrime({
1165
+ clientId: "codex",
1166
+ configDir: sessionDir,
1167
+ phase: "session-create",
1168
+ ...projectDir !== void 0 ? { projectDir } : {}
1169
+ });
1170
+ return { env: { CODEX_HOME: sessionDir } };
1171
+ }
1172
+
1173
+ //#endregion
1174
+ //#region src/runtime/wiring.ts
1175
+ /**
1176
+ * Substring used to identify Makaio-managed Codex hook commands.
1177
+ *
1178
+ * The full command for a given event takes the form:
1179
+ * `<makaioCommand> hook received codex <EventName>`
1180
+ *
1181
+ * This sentinel is written verbatim by {@link applyCodexWiring} and used as
1182
+ * the `commandContains` filter in {@link removeCodexWiring}.
1183
+ */
1184
+ const CODEX_HOOK_COMMAND_SENTINEL = "hook received codex";
1185
+ /**
1186
+ * Descriptors for all session-events hooks derived from the client definition.
1187
+ *
1188
+ * Only events with a defined `frameworkSubject` are included — events without
1189
+ * one are Codex-internal and do not need framework wiring.
1190
+ */
1191
+ const SESSION_EVENTS = deriveSessionEventDescriptors(clientDefinition);
1192
+ /**
1193
+ * Build the wiring entry list, annotated with installation status, by
1194
+ * comparing the expected entries against the currently installed Codex hooks.
1195
+ *
1196
+ * Reads hooks from the global scope only when `projectDir` is absent; includes
1197
+ * project-scope hooks when `projectDir` is provided.
1198
+ * @param settings - {@link CodexClientSettings} instance for hook I/O.
1199
+ * @param makaioCommand - Base makaio shell command written into the config.
1200
+ * @param projectDir - Optional absolute project directory. When absent only
1201
+ * the global scope is checked.
1202
+ * @returns Object containing all wiring entries with `installed` flags.
1203
+ */
1204
+ async function buildCodexWiringList(settings, makaioCommand, projectDir) {
1205
+ const { effective } = await settings.listHooks(projectDir !== void 0 ? { projectDir } : {});
1206
+ return { entries: SESSION_EVENTS.map(({ eventName }) => {
1207
+ const sentinel = `${CODEX_HOOK_COMMAND_SENTINEL} ${eventName}`;
1208
+ const command = buildHookCommand(makaioCommand, CODEX_HOOK_COMMAND_SENTINEL, eventName);
1209
+ return {
1210
+ group: "session-events",
1211
+ name: eventName,
1212
+ installed: effective.some((entry) => entry.event === eventName && entry.command.includes(sentinel)),
1213
+ command
1214
+ };
1215
+ }) };
1216
+ }
1217
+ /**
1218
+ * Install all session-event wiring entries into the specified scope.
1219
+ *
1220
+ * The operation uses replace semantics when `makaioCommand` changes: if a hook
1221
+ * for an event already contains the sentinel but with a different command
1222
+ * prefix, the old hook is removed before the new one is added. When the
1223
+ * identical command is already present the entry is skipped unchanged.
1224
+ * @param settings - {@link CodexClientSettings} instance for hook I/O.
1225
+ * @param scope - Target scope (`'global'` or `'project'`).
1226
+ * @param makaioCommand - Base makaio shell command written into each hook.
1227
+ * @param projectDir - Absolute project directory. Required when `scope` is
1228
+ * `'project'`; ignored for `'global'`.
1229
+ * @returns Counts of applied (newly written or replaced) and skipped (already
1230
+ * present) entries.
1231
+ */
1232
+ async function applyCodexWiring(settings, scope, makaioCommand, projectDir) {
1233
+ let applied = 0;
1234
+ let skipped = 0;
1235
+ const { perScope } = await settings.listHooks(projectDir !== void 0 ? { projectDir } : {});
1236
+ const scopeHooks = perScope.find((s) => s.scope === scope)?.hooks ?? [];
1237
+ for (const { eventName } of SESSION_EVENTS) {
1238
+ const sentinel = `${CODEX_HOOK_COMMAND_SENTINEL} ${eventName}`;
1239
+ const command = buildHookCommand(makaioCommand, CODEX_HOOK_COMMAND_SENTINEL, eventName);
1240
+ const existingEntry = scopeHooks.find((entry) => entry.event === eventName && entry.command.includes(sentinel));
1241
+ if (existingEntry !== void 0) {
1242
+ if (existingEntry.command === command) {
1243
+ skipped += 1;
1244
+ continue;
1245
+ }
1246
+ await settings.removeHook({
1247
+ scope,
1248
+ event: eventName,
1249
+ match: { commandContains: sentinel },
1250
+ ...projectDir !== void 0 ? { projectDir } : {}
1251
+ });
1252
+ }
1253
+ if ((await settings.addHook({
1254
+ scope,
1255
+ event: eventName,
1256
+ command,
1257
+ ...projectDir !== void 0 ? { projectDir } : {}
1258
+ })).added) applied += 1;
1259
+ else skipped += 1;
1260
+ }
1261
+ return {
1262
+ applied,
1263
+ skipped
1264
+ };
1265
+ }
1266
+ /**
1267
+ * Remove all Makaio-managed session-event wiring entries from the specified
1268
+ * scope.
1269
+ *
1270
+ * Entries that are not present are silently ignored — the operation is
1271
+ * idempotent.
1272
+ * @param settings - {@link CodexClientSettings} instance for hook I/O.
1273
+ * @param scope - Target scope (`'global'` or `'project'`).
1274
+ * @param projectDir - Absolute project directory. Required when `scope` is
1275
+ * `'project'`; ignored for `'global'`.
1276
+ * @returns Count of entries actually removed from the config file.
1277
+ */
1278
+ async function removeCodexWiring(settings, scope, projectDir) {
1279
+ let removed = 0;
1280
+ for (const { eventName } of SESSION_EVENTS) {
1281
+ const result = await settings.removeHook({
1282
+ scope,
1283
+ event: eventName,
1284
+ match: { commandContains: `${CODEX_HOOK_COMMAND_SENTINEL} ${eventName}` },
1285
+ ...projectDir !== void 0 ? { projectDir } : {}
1286
+ });
1287
+ removed += result.removed;
1288
+ }
1289
+ return { removed };
1290
+ }
1291
+
1292
+ //#endregion
1293
+ //#region src/runtime/codex-client-session-service.ts
1294
+ /** Stable client ID for Codex — used to filter `client.runtime.started` events. */
1295
+ const CLIENT_ID = "codex";
1296
+ /**
1297
+ * Maximum number of adapter-managed session IDs retained in
1298
+ * {@link CodexClientSessionService.managedAdapterSessionIds}.
1299
+ *
1300
+ * Concurrent active Codex sessions are typically single-digit, so this
1301
+ * cap is a safety net against unbounded growth in long-lived processes. When
1302
+ * the cap is reached, the oldest recorded ID is evicted (FIFO) before the new
1303
+ * one is inserted.
1304
+ */
1305
+ const MANAGED_SESSION_CAP = 1e4;
1306
+ /**
1307
+ * Service that normalizes raw Codex hook events into global
1308
+ * `client.session.*` observed-semantics events and handles Codex config
1309
+ * management requests on `client:codex.config.hooks.*`.
1310
+ *
1311
+ * Lifecycle:
1312
+ * 1. `init()` — subscribes to `client:codex.hook.received`, subscribes to
1313
+ * `client.runtime.started` for the adapter-managed session gate, and
1314
+ * registers request handlers for `config.hooks.list`, `config.hooks.add`,
1315
+ * `config.hooks.remove`, `config.prime`, `wiring.list`, `wiring.apply`,
1316
+ * `wiring.remove`, and `sessionConfig.setup`.
1317
+ * 2. On each incoming raw event, calls {@link normalizeCodexHook}.
1318
+ * 3. Emits the normalized subject when the event is recognized; silently
1319
+ * ignores unknown events. Normalized `client.session.*` events are
1320
+ * suppressed when the `adapterSessionId` is already in the adapter-managed
1321
+ * set.
1322
+ * 4. `destroy()` — unsubscribes all handlers automatically via `BaseService`.
1323
+ */
1324
+ var CodexClientSessionService = class extends BaseService {
1325
+ /** Optional injected settings I/O delegate for tests. */
1326
+ settingsOverride;
1327
+ /** Cached active config-dir resolution; reset when the active Codex version changes. */
1328
+ cachedConfigDir;
1329
+ /**
1330
+ * Set of `adapterSessionId` values known to be owned by an adapter-managed
1331
+ * Codex runtime. Populated by {@link handleRuntimeStarted} when a
1332
+ * `client.runtime.started` event arrives with `clientId === CLIENT_ID` and
1333
+ * `source.layer === 'adapter'`.
1334
+ *
1335
+ * Bounded at {@link MANAGED_SESSION_CAP} entries — the oldest ID is evicted
1336
+ * (FIFO) when the cap is reached.
1337
+ *
1338
+ * Used by {@link handleHookReceived} to gate duplicate `client.session.*`
1339
+ * emissions for sessions that the adapter path already covers.
1340
+ */
1341
+ managedAdapterSessionIds = /* @__PURE__ */ new Set();
1342
+ /**
1343
+ * Creates a new Codex client session service.
1344
+ * @param bus - Bus instance used for subscribing and emitting events
1345
+ * @param settings - Optional {@link CodexClientSettings} instance for tests
1346
+ * that need exact filesystem paths. Production callers should omit it so
1347
+ * the service can resolve the active managed config dir via the bus.
1348
+ */
1349
+ constructor(bus = MakaioBus, settings) {
1350
+ super(bus);
1351
+ this.settingsOverride = settings;
1352
+ }
1353
+ /**
1354
+ * Register the raw hook ingress handler, config management request handlers,
1355
+ * wiring management request handlers, the config-prime lifecycle handler,
1356
+ * and the session config setup handler on the bus.
1357
+ *
1358
+ * Also subscribes to `client.runtime.started` to track adapter-managed
1359
+ * sessions for the {@link handleHookReceived} suppression gate.
1360
+ */
1361
+ onInit() {
1362
+ this.registerHandler(ClientSubjects.runtime.started, ({ payload }) => {
1363
+ this.handleRuntimeStarted(payload);
1364
+ });
1365
+ this.registerHandler(ClientSubjects.version.changed, ({ payload }) => {
1366
+ if (payload.clientId === CLIENT_ID) this.cachedConfigDir = void 0;
1367
+ });
1368
+ this.registerHandler(CodexClientSubjects.hook.received, async ({ payload }) => {
1369
+ await this.handleHookReceived(payload);
1370
+ });
1371
+ this.registerHandler(CodexClientSubjects.config.hooks.list, async (ctx) => {
1372
+ ctx.setResult(await (await this.createSettings()).listHooks(ctx.payload));
1373
+ });
1374
+ this.registerHandler(CodexClientSubjects.config.hooks.add, async (ctx) => {
1375
+ ctx.setResult(await (await this.createSettings()).addHook(ctx.payload));
1376
+ });
1377
+ this.registerHandler(CodexClientSubjects.config.hooks.remove, async (ctx) => {
1378
+ ctx.setResult(await (await this.createSettings()).removeHook(ctx.payload));
1379
+ });
1380
+ this.registerHandler(CodexClientSubjects.wiring.list, async (ctx) => {
1381
+ assertAbsoluteProjectDir(ctx.payload.projectDir);
1382
+ ctx.setResult(await buildCodexWiringList(await this.createSettings(), ctx.payload.makaioCommand, ctx.payload.projectDir));
1383
+ });
1384
+ this.registerHandler(CodexClientSubjects.wiring.apply, async (ctx) => {
1385
+ assertAbsoluteProjectDir(ctx.payload.projectDir);
1386
+ if (ctx.payload.scope === "project" && !ctx.payload.projectDir) throw new Error("projectDir is required when scope is 'project'");
1387
+ ctx.setResult(await applyCodexWiring(await this.createSettings(), ctx.payload.scope, ctx.payload.makaioCommand, ctx.payload.projectDir));
1388
+ });
1389
+ this.registerHandler(CodexClientSubjects.wiring.remove, async (ctx) => {
1390
+ assertAbsoluteProjectDir(ctx.payload.projectDir);
1391
+ if (ctx.payload.scope === "project" && !ctx.payload.projectDir) throw new Error("projectDir is required when scope is 'project'");
1392
+ ctx.setResult(await removeCodexWiring(await this.createSettings(), ctx.payload.scope, ctx.payload.projectDir));
1393
+ });
1394
+ this.registerHandler(CodexClientSubjects.config.prime, async (ctx) => {
1395
+ ctx.setResult(await handleCodexConfigPrime(ctx.payload));
1396
+ });
1397
+ this.registerHandler(CodexClientSubjects.sessionConfig.setup, async (ctx) => {
1398
+ ctx.setResult(await handleCodexSessionConfigSetup(ctx.payload));
1399
+ });
1400
+ }
1401
+ /**
1402
+ * Clear the adapter-managed session ID set on teardown.
1403
+ */
1404
+ onDestroy() {
1405
+ this.managedAdapterSessionIds.clear();
1406
+ this.cachedConfigDir = void 0;
1407
+ }
1408
+ /**
1409
+ * Create a settings delegate for the active Codex config root.
1410
+ * @returns Settings instance bound to the managed config dir when available.
1411
+ */
1412
+ async createSettings() {
1413
+ if (this.settingsOverride !== void 0) return this.settingsOverride;
1414
+ const configDir = await this.resolveConfigDir();
1415
+ return new CodexClientSettings(configDir !== void 0 ? { configDir } : void 0);
1416
+ }
1417
+ /**
1418
+ * Return the cached config directory promise, resolving it on first access.
1419
+ *
1420
+ * Missing binary resolution is a graceful fallback so config reads/writes can
1421
+ * still target native Codex config paths in framework-only or global-only
1422
+ * setups.
1423
+ * @returns Absolute managed config dir, or `undefined` to use native paths.
1424
+ */
1425
+ resolveConfigDir() {
1426
+ if (this.cachedConfigDir === void 0) {
1427
+ const pendingConfigDir = this.doResolveConfigDir().then((configDir) => {
1428
+ if (configDir === void 0 && this.cachedConfigDir === pendingConfigDir) this.cachedConfigDir = void 0;
1429
+ return configDir;
1430
+ }, (error) => {
1431
+ if (this.cachedConfigDir === pendingConfigDir) this.cachedConfigDir = void 0;
1432
+ throw error;
1433
+ });
1434
+ this.cachedConfigDir = pendingConfigDir;
1435
+ }
1436
+ return this.cachedConfigDir;
1437
+ }
1438
+ /**
1439
+ * Resolve the active Codex config dir via `client.resolveBinary`.
1440
+ * @returns Config directory returned by the binary resolver, or `undefined`
1441
+ * when no resolver/global binary is available.
1442
+ */
1443
+ async doResolveConfigDir() {
1444
+ let result;
1445
+ try {
1446
+ result = await this.bus.requestOptional(ClientSubjects.resolveBinary, { clientId: CLIENT_ID });
1447
+ } catch (error) {
1448
+ if (isResolveBinaryMissingGlobalBinary(error)) return;
1449
+ throw error;
1450
+ }
1451
+ if (!result.handled) return;
1452
+ return result.data.configDir ?? void 0;
1453
+ }
1454
+ /**
1455
+ * Record a runtime as adapter-managed when the evidence source is an adapter.
1456
+ *
1457
+ * Called for every `client.runtime.started` event. Only events whose
1458
+ * `clientId` equals `'codex'`, whose `source.layer` is `'adapter'`, and
1459
+ * that carry a non-empty `adapterSessionId` update the managed-sessions gate.
1460
+ * Events from other clients (e.g. `'claude-code'`, `'gemini'`) are ignored
1461
+ * unconditionally — their adapter sessions must not suppress Codex hook
1462
+ * emissions. Non-adapter sources (e.g. `'supervisor'`, `'statusline'`) are
1463
+ * also ignored to prevent accidental suppression of native hook paths.
1464
+ *
1465
+ * When the set reaches {@link MANAGED_SESSION_CAP}, the oldest entry is
1466
+ * evicted before the new ID is inserted.
1467
+ * @param payload - `client.runtime.started` payload
1468
+ */
1469
+ handleRuntimeStarted(payload) {
1470
+ if (payload.clientId !== CLIENT_ID) return;
1471
+ if (payload.source.layer === "adapter" && payload.adapterSessionId) {
1472
+ if (this.managedAdapterSessionIds.has(payload.adapterSessionId)) return;
1473
+ if (this.managedAdapterSessionIds.size >= 1e4) {
1474
+ const oldest = this.managedAdapterSessionIds.values().next().value;
1475
+ if (oldest !== void 0) this.managedAdapterSessionIds.delete(oldest);
1476
+ }
1477
+ this.managedAdapterSessionIds.add(payload.adapterSessionId);
1478
+ }
1479
+ }
1480
+ /**
1481
+ * Translate a raw Codex hook event into a normalized `client.session.*` emission.
1482
+ *
1483
+ * Unknown / Codex-specific events produce no emission and are silently
1484
+ * ignored. The raw event remains observable on `client:codex.*` for
1485
+ * consumers that need Codex-native detail.
1486
+ * @param raw - Raw hook payload delivered on `client:codex.hook.received`
1487
+ */
1488
+ async handleHookReceived(raw) {
1489
+ const normalized = normalizeCodexHook(raw);
1490
+ if (normalized === null) return;
1491
+ if (this.isAdapterManagedSession(normalized.payload.adapterSessionId)) return;
1492
+ switch (normalized.subject) {
1493
+ case ClientSubjects.session.started:
1494
+ await this.bus.emit(ClientSubjects.session.started, normalized.payload);
1495
+ break;
1496
+ case ClientSubjects.session.userPrompt.submitted:
1497
+ await this.bus.emit(ClientSubjects.session.userPrompt.submitted, normalized.payload);
1498
+ break;
1499
+ case ClientSubjects.session.turn.completed:
1500
+ await this.bus.emit(ClientSubjects.session.turn.completed, normalized.payload);
1501
+ break;
1502
+ case ClientSubjects.session.tool.pre:
1503
+ await this.bus.emit(ClientSubjects.session.tool.pre, normalized.payload);
1504
+ break;
1505
+ case ClientSubjects.session.tool.post:
1506
+ await this.bus.emit(ClientSubjects.session.tool.post, normalized.payload);
1507
+ break;
1508
+ default: throwUnhandledNormalizedEvent(normalized);
1509
+ }
1510
+ }
1511
+ /**
1512
+ * Determine whether a normalized native hook belongs to an adapter-managed
1513
+ * session whose global observed-semantics events are already emitted by the
1514
+ * adapter layer.
1515
+ * @param adapterSessionId - Adapter/session identifier from the normalized hook
1516
+ * @returns True when the native hook should remain raw-only
1517
+ */
1518
+ isAdapterManagedSession(adapterSessionId) {
1519
+ return adapterSessionId !== void 0 && this.managedAdapterSessionIds.has(adapterSessionId);
1520
+ }
1521
+ };
1522
+ /**
1523
+ * Fail fast when the normalizer grows a new subject but service emission has
1524
+ * not been updated to preserve the normalized-event contract.
1525
+ *
1526
+ * The broad parameter type is intentional — the switch operates on
1527
+ * `SubjectDefinition` subject strings rather than a discriminated union, so
1528
+ * TypeScript cannot narrow `normalized` to `never` in the default branch.
1529
+ * Compile-time exhaustiveness is enforced by the normalizer's return type
1530
+ * and the matching set of case branches above.
1531
+ * @param event - Normalized event whose subject is not emitted above
1532
+ */
1533
+ function throwUnhandledNormalizedEvent(event) {
1534
+ const subject = event.subject.subject;
1535
+ throw new Error(`Unhandled normalized Codex hook subject: ${subject}`);
1536
+ }
1537
+ /**
1538
+ * Return true when `client.resolveBinary` only failed because no Codex
1539
+ * executable was found. Config and wiring requests can still use Codex's
1540
+ * default native settings path in that case; other resolution failures should
1541
+ * propagate.
1542
+ * @param error - Error thrown by the bus request.
1543
+ * @returns True when the error is the global-binary fallback miss.
1544
+ */
1545
+ function isResolveBinaryMissingGlobalBinary(error) {
1546
+ if (!(error instanceof RequestError)) return false;
1547
+ return error.subject?.endsWith("resolveBinary") === true && error.cause instanceof BinaryNotFoundError;
1548
+ }
1549
+
1550
+ //#endregion
1551
+ export { CodexNativeHooksFileSchema as _, CodexWiringSchemas as a, clientDefinition as b, CodexConfigHooksAddResponseSchema as c, CodexConfigHooksRemoveRequestSchema as d, CodexConfigHooksRemoveResponseSchema as f, CodexNativeHookMatcherGroupSchema as g, CodexNativeCommandHookSchema as h, normalizeCodexHook as i, CodexConfigHooksListRequestSchema as l, CodexHookEntrySchema as m, CODEX_CLIENT_NAMESPACE as n, AbsolutePathSchema as o, CodexConfigSchemas as p, CodexClientSubjects as r, CodexConfigHooksAddRequestSchema as s, CodexClientSessionService as t, CodexConfigHooksListResponseSchema as u, CodexScopeHookRecordSchema as v, CodexScopeSchema as y };