@makaio/client-codex 1.0.0-dev-1783973359164 → 1.0.0-dev-1784806252593

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,13 +41,31 @@ is preserved without overwriting a concurrently changed canonical credential.
41
41
 
42
42
  ### Hook Events
43
43
 
44
- | Hook Name | Framework Subject |
45
- |-----------|------------------|
46
- | `SessionStart` | `client.session.started` |
47
- | `UserPromptSubmit` | `client.session.userPrompt.submitted` |
48
- | `PreToolUse` | `client.session.tool.pre` |
49
- | `PostToolUse` | `client.session.tool.post` |
50
- | `Stop` | `client.session.turn.completed` |
44
+ | Hook Name | Framework Subject | Response Capabilities |
45
+ |-----------|------------------|----------------------|
46
+ | `SessionStart` | `client.session.started` | `context.append`, `openai.codex-hook-response.block` |
47
+ | `UserPromptSubmit` | `client.session.userPrompt.submitted` | `context.append`, `openai.codex-hook-response.block` |
48
+ | `PreToolUse` | `client.session.tool.pre` | `context.append`, `openai.codex-hook-response.block`, `openai.codex-hook-response.permission.deny`, `openai.codex-hook-response.input.update` |
49
+ | `PostToolUse` | `client.session.tool.post` | `context.append`, `openai.codex-hook-response.block` |
50
+ | `Stop` | `client.session.turn.completed` | `openai.codex-hook-response.block` |
51
+
52
+ All five events synchronously consume JSON output in the pinned upstream source tag `rust-v0.144.1`. Live CLI probes remain pending; the contract only exposes source-accepted fields.
53
+
54
+ ### Hook Response Contract (`openai.codex-hook-response@1`)
55
+
56
+ The `./runtime` entrypoint registers a `ProviderContractCatalogEntry` that defines how contributions are validated for Codex:
57
+
58
+ | Field | Value |
59
+ |-------|-------|
60
+ | `contractId` | `openai.codex-hook-response` |
61
+ | `version` | `1.1.0` |
62
+ | `supportedInteractions` | `SessionStart`, `UserPromptSubmit`, `PreToolUse`, `PostToolUse`, `Stop` |
63
+
64
+ **Blockability:** All five events support blocking responses. SessionStart uses `continue: false` with `stopReason`; the other events use their event-specific block form.
65
+
66
+ **Composition:** Contributions are collected deterministically. Context appends render as `hookSpecificOutput.additionalContext`; blocks use `decision: "block"` with a non-empty reason; PreToolUse additionally supports `permissionDecision: "deny"` and an allowed `updatedInput` rewrite.
67
+
68
+ See [Client Hook Response Pipeline](../../docs/architecture/client-hook-responses.md) for the full architecture.
51
69
 
52
70
  ## Native Tools
53
71
 
@@ -106,6 +124,8 @@ Capabilities are sourced from `codexCapabilityMap` in `@makaio/contracts`, keepi
106
124
  | `CodexClientSubjects` | namespace subjects | Typed bus subjects for `client:codex.*` |
107
125
  | `CODEX_CLIENT_NAMESPACE` | `string` | Fully-qualified namespace domain (`'client:codex'`) |
108
126
  | `normalizeCodexHook` | function | Normalizes a raw Codex hook payload into a `CodexNormalizedEvent` |
127
+ | `codexProviderContractCatalog` | `ProviderContractCatalogEntry` | Provider contract catalog entry for the `openai.codex-hook-response` contract |
128
+ | `composeCodexHookResponse` | function | Collects and renders source-verified synchronous hook responses |
109
129
 
110
130
  ### Server entrypoint (`./server`)
111
131
 
@@ -1,6 +1,6 @@
1
1
  import { codexCapabilityMap, createClientDefinition } from "@makaio/framework/contracts";
2
2
  import { z } from "zod";
3
- import { AbsolutePathSchema, BinaryNotFoundError, ClientSubjects, ClientWiringApplyResponseSchema, ClientWiringListResponseSchema, ClientWiringRemoveResponseSchema, assertAbsoluteProjectDir, atomicModifyFile, buildHookCommand, createClientNamespace, deriveSessionEventDescriptors, pickNonEmptyString } from "@makaio/framework/clients";
3
+ import { AbsolutePathSchema, BinaryNotFoundError, ClientSubjects, ClientWiringApplyResponseSchema, ClientWiringListResponseSchema, ClientWiringRemoveResponseSchema, DEFAULT_HOOK_HANDLE_TIMEOUT_MS, NOOP_HOOK_HANDLE_RESPONSE, assertAbsoluteProjectDir, atomicModifyFile, buildClientCommand, buildHookCommand, collectContributions, createClientNamespace, deriveSessionEventDescriptors, pickNonEmptyString } from "@makaio/framework/clients";
4
4
  import { MakaioBus, RequestError } from "@makaio/framework/bus";
5
5
  import { BaseService } from "@makaio/framework/service-base";
6
6
  import * as fs$1 from "node:fs/promises";
@@ -10,6 +10,7 @@ import path from "node:path";
10
10
  import * as os$1 from "node:os";
11
11
  import os from "node:os";
12
12
  import { createHash, randomUUID } from "node:crypto";
13
+ import { isDeepStrictEqual } from "node:util";
13
14
  import { ClientConfigPrimeSchema, SessionConfigSetupRequestSchema, SessionConfigSetupResponseSchema, SessionConfigTeardownRequestSchema, SessionConfigTeardownResponseSchema } from "@makaio/framework/contracts/client";
14
15
  import { parse } from "smol-toml";
15
16
  import { lock } from "proper-lockfile";
@@ -24,6 +25,12 @@ import { lock } from "proper-lockfile";
24
25
  * capability taxonomy in a single canonical location.
25
26
  * @packageDocumentation
26
27
  */
28
+ /** Namespaced Codex hook-response capabilities exposed to contributors. */
29
+ const CODEX_HOOK_RESPONSE_CAPABILITIES = Object.freeze({
30
+ block: "openai.codex-hook-response.block",
31
+ permissionDeny: "openai.codex-hook-response.permission.deny",
32
+ inputUpdate: "openai.codex-hook-response.input.update"
33
+ });
27
34
  /**
28
35
  * Static client definition for `@makaio/client-codex`.
29
36
  *
@@ -101,23 +108,33 @@ const clientDefinition = createClientDefinition({
101
108
  hookEvents: [
102
109
  {
103
110
  name: "SessionStart",
104
- frameworkSubject: "client.session.started"
111
+ frameworkSubject: "client.session.started",
112
+ responseCapabilities: ["context.append", CODEX_HOOK_RESPONSE_CAPABILITIES.block]
105
113
  },
106
114
  {
107
115
  name: "UserPromptSubmit",
108
- frameworkSubject: "client.session.userPrompt.submitted"
116
+ frameworkSubject: "client.session.userPrompt.submitted",
117
+ responseCapabilities: ["context.append", CODEX_HOOK_RESPONSE_CAPABILITIES.block]
109
118
  },
110
119
  {
111
120
  name: "PreToolUse",
112
- frameworkSubject: "client.session.tool.pre"
121
+ frameworkSubject: "client.session.tool.pre",
122
+ responseCapabilities: [
123
+ "context.append",
124
+ CODEX_HOOK_RESPONSE_CAPABILITIES.block,
125
+ CODEX_HOOK_RESPONSE_CAPABILITIES.permissionDeny,
126
+ CODEX_HOOK_RESPONSE_CAPABILITIES.inputUpdate
127
+ ]
113
128
  },
114
129
  {
115
130
  name: "PostToolUse",
116
- frameworkSubject: "client.session.tool.post"
131
+ frameworkSubject: "client.session.tool.post",
132
+ responseCapabilities: ["context.append", CODEX_HOOK_RESPONSE_CAPABILITIES.block]
117
133
  },
118
134
  {
119
135
  name: "Stop",
120
- frameworkSubject: "client.session.turn.completed"
136
+ frameworkSubject: "client.session.turn.completed",
137
+ responseCapabilities: [CODEX_HOOK_RESPONSE_CAPABILITIES.block]
121
138
  }
122
139
  ]
123
140
  }
@@ -993,17 +1010,7 @@ function extractToolName(payload) {
993
1010
  * @returns Tool call ID string, or `undefined` when absent
994
1011
  */
995
1012
  function extractToolCallId(payload) {
996
- return pickNonEmptyString(payload, "call_id");
997
- }
998
- /**
999
- * Extract optional tool execution outcome from a raw Codex post-tool payload.
1000
- *
1001
- * Codex reports whether the tool call succeeded under `success`.
1002
- * @param payload - Raw hook payload object
1003
- * @returns Boolean success flag, or `undefined` when absent
1004
- */
1005
- function extractSuccess(payload) {
1006
- return typeof payload["success"] === "boolean" ? payload["success"] : void 0;
1013
+ return pickNonEmptyString(payload, "tool_use_id");
1007
1014
  }
1008
1015
  /**
1009
1016
  * Extract optional prompt text from a raw Codex user-prompt payload.
@@ -1068,14 +1075,410 @@ function normalizeCodexHook(raw, machineId) {
1068
1075
  payload: {
1069
1076
  ...base,
1070
1077
  toolName: extractToolName(raw.payload),
1071
- toolCallId: extractToolCallId(raw.payload),
1072
- success: extractSuccess(raw.payload)
1078
+ toolCallId: extractToolCallId(raw.payload)
1073
1079
  }
1074
1080
  };
1075
1081
  default: return null;
1076
1082
  }
1077
1083
  }
1078
1084
 
1085
+ //#endregion
1086
+ //#region src/runtime/schemas.ts
1087
+ /**
1088
+ * Hook events emitted by Codex that map to the v1 observed-semantics set.
1089
+ *
1090
+ * These are the events the normalizer translates into `client.session.*` bus
1091
+ * emissions. Any event NOT listed here is left as raw `client:codex`
1092
+ * namespace data only.
1093
+ */
1094
+ const CODEX_HOOK_SESSION_START = "SessionStart";
1095
+ const CODEX_HOOK_USER_PROMPT_SUBMIT = "UserPromptSubmit";
1096
+ const CODEX_HOOK_PRE_TOOL_USE = "PreToolUse";
1097
+ const CODEX_HOOK_POST_TOOL_USE = "PostToolUse";
1098
+ const CODEX_HOOK_STOP = "Stop";
1099
+
1100
+ //#endregion
1101
+ //#region src/runtime/hook-response-contracts.ts
1102
+ const CODEX_CLIENT_ID = "codex";
1103
+ const CODEX_CONTRACT_ID = "openai.codex-hook-response";
1104
+ const CODEX_CONTRACT_VERSION = "1.1.0";
1105
+ /**
1106
+ * Build a frozen Codex provider envelope.
1107
+ * @param effects - Provider-native effect record.
1108
+ * @returns A frozen Codex contribution envelope.
1109
+ */
1110
+ function envelope(effects) {
1111
+ return Object.freeze({
1112
+ clientId: CODEX_CLIENT_ID,
1113
+ contractId: CODEX_CONTRACT_ID,
1114
+ effects: Object.freeze(effects)
1115
+ });
1116
+ }
1117
+ /**
1118
+ * Builds a SessionStart context response.
1119
+ * @param value - Context appended when the session starts.
1120
+ * @returns A Codex SessionStart context envelope.
1121
+ */
1122
+ function createCodexSessionStartContextEffect(value) {
1123
+ return envelope({ additionalContext: value });
1124
+ }
1125
+ /**
1126
+ * Builds a SessionStart stop response.
1127
+ * @param reason - Reason for stopping session startup.
1128
+ * @returns A Codex SessionStart block envelope.
1129
+ */
1130
+ function createCodexSessionStartBlockEffect(reason) {
1131
+ return envelope({
1132
+ decision: "block",
1133
+ reason
1134
+ });
1135
+ }
1136
+ /**
1137
+ * Builds a UserPromptSubmit context response.
1138
+ * @param value - Context appended to the submitted prompt.
1139
+ * @returns A Codex UserPromptSubmit context envelope.
1140
+ */
1141
+ function createCodexUserPromptSubmitContextEffect(value) {
1142
+ return envelope({ additionalContext: value });
1143
+ }
1144
+ /**
1145
+ * Builds a UserPromptSubmit block response.
1146
+ * @param reason - Reason for blocking the submitted prompt.
1147
+ * @returns A Codex UserPromptSubmit block envelope.
1148
+ */
1149
+ function createCodexUserPromptSubmitBlockEffect(reason) {
1150
+ return envelope({
1151
+ decision: "block",
1152
+ reason
1153
+ });
1154
+ }
1155
+ /**
1156
+ * Builds a PreToolUse block response.
1157
+ * @param reason - Reason for blocking the tool invocation.
1158
+ * @returns A Codex PreToolUse block envelope.
1159
+ */
1160
+ function createCodexPreToolUseBlockEffect(reason) {
1161
+ return envelope({
1162
+ decision: "block",
1163
+ reason
1164
+ });
1165
+ }
1166
+ /**
1167
+ * Builds a PreToolUse deny-permission response.
1168
+ * @param reason - Reason for denying the tool invocation.
1169
+ * @returns A Codex PreToolUse permission-denial envelope.
1170
+ */
1171
+ function createCodexPreToolUseDenyEffect(reason) {
1172
+ return envelope({
1173
+ permissionDecision: "deny",
1174
+ permissionDecisionReason: reason
1175
+ });
1176
+ }
1177
+ /**
1178
+ * Builds additional model context for PreToolUse.
1179
+ * @param value - Context appended before the tool invocation.
1180
+ * @returns A Codex PreToolUse context envelope.
1181
+ */
1182
+ function createCodexPreToolUseContextEffect(value) {
1183
+ return envelope({ additionalContext: value });
1184
+ }
1185
+ /**
1186
+ * Builds a PreToolUse allowed input update.
1187
+ * @param updatedInput - JSON value that replaces the native tool input.
1188
+ * @returns A Codex PreToolUse input-update envelope.
1189
+ */
1190
+ function createCodexPreToolUseUpdateEffect(updatedInput) {
1191
+ return envelope({
1192
+ permissionDecision: "allow",
1193
+ updatedInput
1194
+ });
1195
+ }
1196
+ /**
1197
+ * Builds a PostToolUse context response.
1198
+ * @param value - Context appended after the tool invocation.
1199
+ * @returns A Codex PostToolUse context envelope.
1200
+ */
1201
+ function createCodexPostToolUseContextEffect(value) {
1202
+ return envelope({ additionalContext: value });
1203
+ }
1204
+ /**
1205
+ * Builds a PostToolUse block response.
1206
+ * @param reason - Reason for blocking after the tool invocation.
1207
+ * @returns A Codex PostToolUse block envelope.
1208
+ */
1209
+ function createCodexPostToolUseBlockEffect(reason) {
1210
+ return envelope({
1211
+ decision: "block",
1212
+ reason
1213
+ });
1214
+ }
1215
+ /**
1216
+ * Builds a Stop block response.
1217
+ * @param reason - Reason for requesting another turn.
1218
+ * @returns A Codex Stop block envelope.
1219
+ */
1220
+ function createCodexStopBlockEffect(reason) {
1221
+ return envelope({
1222
+ decision: "block",
1223
+ reason
1224
+ });
1225
+ }
1226
+ const CODEX_RESPONSE_CAPABILITIES = Object.freeze([
1227
+ "context.append",
1228
+ CODEX_HOOK_RESPONSE_CAPABILITIES.block,
1229
+ CODEX_HOOK_RESPONSE_CAPABILITIES.permissionDeny,
1230
+ CODEX_HOOK_RESPONSE_CAPABILITIES.inputUpdate
1231
+ ]);
1232
+ const CODEX_SUPPORTED_INTERACTIONS = Object.freeze([
1233
+ CODEX_HOOK_SESSION_START,
1234
+ CODEX_HOOK_USER_PROMPT_SUBMIT,
1235
+ CODEX_HOOK_PRE_TOOL_USE,
1236
+ CODEX_HOOK_POST_TOOL_USE,
1237
+ CODEX_HOOK_STOP,
1238
+ ...CODEX_RESPONSE_CAPABILITIES
1239
+ ]);
1240
+ const CODEX_INTERACTION_BLOCKABILITY = Object.freeze(CODEX_SUPPORTED_INTERACTIONS.map((interaction) => Object.freeze({
1241
+ interaction,
1242
+ blockable: interaction === CODEX_HOOK_RESPONSE_CAPABILITIES.block || interaction === CODEX_HOOK_RESPONSE_CAPABILITIES.permissionDeny || interaction === CODEX_HOOK_RESPONSE_CAPABILITIES.inputUpdate || interaction === "SessionStart" || interaction === "UserPromptSubmit" || interaction === "PreToolUse" || interaction === "PostToolUse" || interaction === "Stop"
1243
+ })));
1244
+ /**
1245
+ * Determine whether a runtime value is losslessly representable as JSON.
1246
+ * @param value - Runtime value to inspect.
1247
+ * @param ancestors - Objects already visited on the current recursion path.
1248
+ * @returns Whether the value is an acyclic JSON value.
1249
+ */
1250
+ function isJsonValue(value, ancestors = /* @__PURE__ */ new Set()) {
1251
+ if (value === null || typeof value === "string" || typeof value === "boolean") return true;
1252
+ if (typeof value === "number") return Number.isFinite(value);
1253
+ if (typeof value !== "object") return false;
1254
+ if (ancestors.has(value)) return false;
1255
+ const nextAncestors = new Set(ancestors).add(value);
1256
+ if (Array.isArray(value)) return value.every((entry) => isJsonValue(entry, nextAncestors));
1257
+ if (Object.getPrototypeOf(value) !== Object.prototype && Object.getPrototypeOf(value) !== null) return false;
1258
+ return Object.values(value).every((entry) => isJsonValue(entry, nextAncestors));
1259
+ }
1260
+ const EVENT_EFFECTS = Object.freeze({
1261
+ [CODEX_HOOK_SESSION_START]: new Set(["context", "block"]),
1262
+ [CODEX_HOOK_USER_PROMPT_SUBMIT]: new Set(["context", "block"]),
1263
+ [CODEX_HOOK_PRE_TOOL_USE]: new Set([
1264
+ "context",
1265
+ "block",
1266
+ "deny",
1267
+ "update"
1268
+ ]),
1269
+ [CODEX_HOOK_POST_TOOL_USE]: new Set(["context", "block"]),
1270
+ [CODEX_HOOK_STOP]: new Set(["block"])
1271
+ });
1272
+ /**
1273
+ * Classify an exact provider-native Codex effects record.
1274
+ * @param effects - Provider-native effects to classify.
1275
+ * @returns The recognized effect kind, or `undefined` for an invalid shape.
1276
+ */
1277
+ function classifyEffects(effects) {
1278
+ const keys = Object.keys(effects);
1279
+ if (keys.length === 1 && typeof effects.additionalContext === "string") return "context";
1280
+ if (keys.length !== 2) return void 0;
1281
+ if (effects.decision === "block" && typeof effects.reason === "string" && effects.reason.trim() !== "") return "block";
1282
+ if (effects.permissionDecision === "deny" && typeof effects.permissionDecisionReason === "string" && effects.permissionDecisionReason.trim() !== "") return "deny";
1283
+ if (effects.permissionDecision === "allow" && "updatedInput" in effects && effects.updatedInput !== null && isJsonValue(effects.updatedInput)) return "update";
1284
+ }
1285
+ /**
1286
+ * Extract and validate the provider-native effects record.
1287
+ * @param output - Contributor response to inspect.
1288
+ * @returns The effects record, `undefined` for an empty response, or a diagnostic string.
1289
+ */
1290
+ function extractEffects(output) {
1291
+ if (output === void 0 || output === null) return void 0;
1292
+ if (typeof output !== "object" || Array.isArray(output)) return "Contributor response must be an object or undefined";
1293
+ const response = output;
1294
+ if (Object.keys(response).some((key) => key !== "providerEnvelope")) return "Codex provider responses may contain only providerEnvelope";
1295
+ if (response.providerEnvelope === void 0) return void 0;
1296
+ if (typeof response.providerEnvelope !== "object" || response.providerEnvelope === null || Array.isArray(response.providerEnvelope)) return "providerEnvelope must be an object";
1297
+ const envelope = response.providerEnvelope;
1298
+ const unsupportedField = Object.keys(envelope).find((key) => key !== "clientId" && key !== "contractId" && key !== "effects");
1299
+ if (unsupportedField !== void 0) return `Unsupported Codex providerEnvelope field '${unsupportedField}'`;
1300
+ if (envelope.clientId !== "codex" || envelope.contractId !== "openai.codex-hook-response") return "providerEnvelope clientId and contractId must target the Codex contract";
1301
+ if (typeof envelope.effects !== "object" || envelope.effects === null || Array.isArray(envelope.effects)) return "providerEnvelope.effects must be an object";
1302
+ return envelope.effects;
1303
+ }
1304
+ /**
1305
+ * Validate a provider response against the event-specific Codex parser surface.
1306
+ * @param output - Contributor response to validate.
1307
+ * @param ctx - Current provider validation context.
1308
+ * @returns `true` when valid, otherwise a diagnostic string.
1309
+ */
1310
+ function validateCodexContractOutput(output, ctx) {
1311
+ const effects = extractEffects(output);
1312
+ if (effects === void 0) return true;
1313
+ if (typeof effects === "string") return effects;
1314
+ const effectKind = classifyEffects(effects);
1315
+ if (effectKind !== void 0 && EVENT_EFFECTS[String(ctx.eventName)]?.has(effectKind)) return true;
1316
+ return `Unsupported Codex response effects for '${String(ctx.eventName)}'`;
1317
+ }
1318
+ const codexProviderContractCatalog = Object.freeze({
1319
+ clientId: CODEX_CLIENT_ID,
1320
+ contractId: CODEX_CONTRACT_ID,
1321
+ version: CODEX_CONTRACT_VERSION,
1322
+ supportedInteractions: CODEX_SUPPORTED_INTERACTIONS,
1323
+ blockability: CODEX_INTERACTION_BLOCKABILITY,
1324
+ validate: validateCodexContractOutput
1325
+ });
1326
+
1327
+ //#endregion
1328
+ //#region src/runtime/hook-response-composer.ts
1329
+ /** Deterministic Codex 0.144.1 hook-response composition. @packageDocumentation */
1330
+ /**
1331
+ * Resolve the declared response capabilities for one Codex event.
1332
+ * @param eventName - Native Codex hook event name.
1333
+ * @returns Declared capabilities for the event.
1334
+ */
1335
+ function capabilities(eventName) {
1336
+ return clientDefinition.runtimeCapabilities.hookEvents.find((event) => event.name === eventName)?.responseCapabilities ?? [];
1337
+ }
1338
+ const FIRST_BLOCK_EVENTS = new Set([
1339
+ CODEX_HOOK_SESSION_START,
1340
+ CODEX_HOOK_USER_PROMPT_SUBMIT,
1341
+ CODEX_HOOK_PRE_TOOL_USE
1342
+ ]);
1343
+ /**
1344
+ * Collect one provider-native effect into its composition bucket.
1345
+ * @param collected - Mutable composition buckets for this request.
1346
+ * @param effect - Provider contribution envelope to inspect.
1347
+ * @param supportsContext - Whether the current event accepts additional context.
1348
+ */
1349
+ function collectProviderEffect(collected, effect, supportsContext) {
1350
+ if (effect.clientId !== "codex" || effect.contractId !== "openai.codex-hook-response") return;
1351
+ const value = effect.effects;
1352
+ if (supportsContext && typeof value.additionalContext === "string") collected.contexts.push(value.additionalContext);
1353
+ if (value.decision === "block" && typeof value.reason === "string") collected.blocks.push(value.reason);
1354
+ if (value.permissionDecision === "deny" && typeof value.permissionDecisionReason === "string") collected.denyReasons.push(value.permissionDecisionReason);
1355
+ if (value.permissionDecision === "allow" && "updatedInput" in value) collected.updates.push(value.updatedInput);
1356
+ }
1357
+ /**
1358
+ * Collect canonical and provider-native effects for one event.
1359
+ * @param eventName - Native Codex hook event name.
1360
+ * @param effects - Deterministically ordered effects to collect.
1361
+ * @returns Effects grouped by native output behavior.
1362
+ */
1363
+ function collectEffects(eventName, effects) {
1364
+ const collected = {
1365
+ contexts: [],
1366
+ blocks: [],
1367
+ denyReasons: [],
1368
+ updates: []
1369
+ };
1370
+ const supportsContext = capabilities(eventName).includes("context.append");
1371
+ for (const effect of effects) if ("kind" in effect) {
1372
+ if (supportsContext && effect.kind === "context.append") collected.contexts.push(effect.value);
1373
+ } else collectProviderEffect(collected, effect, supportsContext);
1374
+ return collected;
1375
+ }
1376
+ /**
1377
+ * Select the native block reason according to the pinned event rule.
1378
+ * @param eventName - Native Codex hook event name.
1379
+ * @param blocks - Ordered block reasons.
1380
+ * @returns The selected reason, or `undefined` when no block was contributed.
1381
+ */
1382
+ function selectBlockReason(eventName, blocks) {
1383
+ if (blocks.length === 0) return void 0;
1384
+ return FIRST_BLOCK_EVENTS.has(eventName) ? blocks[0] : blocks.join("\n\n");
1385
+ }
1386
+ /**
1387
+ * Serialize a native Codex response.
1388
+ * @param body - Native JSON response body.
1389
+ * @returns Hook handle response with the serialized body on stdout.
1390
+ */
1391
+ function serialize(body) {
1392
+ return {
1393
+ exitCode: 0,
1394
+ stdout: JSON.stringify(body),
1395
+ stderr: ""
1396
+ };
1397
+ }
1398
+ /**
1399
+ * Render a blocking native response outside PreToolUse.
1400
+ * @param eventName - Native Codex hook event name.
1401
+ * @param reason - Selected block reason.
1402
+ * @param hookSpecificOutput - Event-specific context output.
1403
+ * @param hasContext - Whether context was contributed.
1404
+ * @returns Blocking native response.
1405
+ */
1406
+ function renderBlock(eventName, reason, hookSpecificOutput, hasContext) {
1407
+ const context = hasContext ? { hookSpecificOutput } : {};
1408
+ if (eventName === "SessionStart") return serialize({
1409
+ continue: false,
1410
+ stopReason: reason,
1411
+ ...context
1412
+ });
1413
+ return serialize({
1414
+ decision: "block",
1415
+ reason,
1416
+ ...context
1417
+ });
1418
+ }
1419
+ /**
1420
+ * Render the precedence-sensitive PreToolUse response.
1421
+ * @param collected - Effects grouped by native behavior.
1422
+ * @param blockReason - Selected block reason, if any.
1423
+ * @param hookSpecificOutput - Mutable native event-specific output.
1424
+ * @returns A terminal response, or `undefined` when only context remains to render.
1425
+ */
1426
+ function renderPreToolUse(collected, blockReason, hookSpecificOutput) {
1427
+ if (blockReason !== void 0) return serialize({
1428
+ decision: "block",
1429
+ reason: blockReason,
1430
+ ...collected.contexts.length ? { hookSpecificOutput } : {}
1431
+ });
1432
+ if (collected.denyReasons.length > 0) {
1433
+ hookSpecificOutput.permissionDecision = "deny";
1434
+ hookSpecificOutput.permissionDecisionReason = collected.denyReasons.join("\n");
1435
+ } else if (collected.updates.length > 0) {
1436
+ hookSpecificOutput.permissionDecision = "allow";
1437
+ hookSpecificOutput.updatedInput = collected.updates[0];
1438
+ }
1439
+ }
1440
+ /**
1441
+ * Reduce ordered effects into one provider-valid Codex response.
1442
+ * @param eventName - Native Codex hook event name.
1443
+ * @param effects - Deterministically ordered effects to reduce.
1444
+ * @returns The terminal native hook response.
1445
+ */
1446
+ function output(eventName, effects) {
1447
+ const collected = collectEffects(eventName, effects);
1448
+ if (collected.updates.length > 1 && collected.updates.some((candidate) => !isDeepStrictEqual(candidate, collected.updates[0]))) throw new Error("Conflicting Codex PreToolUse input.update effects");
1449
+ const hookSpecificOutput = { hookEventName: eventName };
1450
+ if (collected.contexts.length) hookSpecificOutput.additionalContext = collected.contexts.join("\n");
1451
+ const blockReason = selectBlockReason(eventName, collected.blocks);
1452
+ if (eventName === "PreToolUse") {
1453
+ const response = renderPreToolUse(collected, blockReason, hookSpecificOutput);
1454
+ if (response !== void 0) return response;
1455
+ } else if (blockReason !== void 0) return renderBlock(eventName, blockReason, hookSpecificOutput, collected.contexts.length > 0);
1456
+ if (!collected.contexts.length && collected.denyReasons.length === 0 && collected.updates.length === 0) return NOOP_HOOK_HANDLE_RESPONSE;
1457
+ return serialize({ hookSpecificOutput });
1458
+ }
1459
+ /**
1460
+ * Compose one terminal Codex native hook response.
1461
+ * @param registry - Active response contributor registry.
1462
+ * @param payload - Normalized native hook payload.
1463
+ * @param options - Request deadline, cancellation, and diagnostics hooks.
1464
+ * @returns The composed native response envelope.
1465
+ */
1466
+ async function composeCodexHookResponse(registry, payload, options) {
1467
+ const snapshot = registry.snapshot(CODEX_CLIENT_ID, CODEX_CONTRACT_ID, payload.eventName, capabilities(payload.eventName));
1468
+ if (!snapshot.length) return NOOP_HOOK_HANDLE_RESPONSE;
1469
+ const result = await collectContributions(snapshot, CODEX_CLIENT_ID, options?.deadline, options?.signal, payload.eventName, payload.payload, codexProviderContractCatalog);
1470
+ if (result.diagnostics.length) options?.onDiagnostics?.(result.diagnostics);
1471
+ if (result.closedFailure) return output(payload.eventName, [{
1472
+ clientId: CODEX_CLIENT_ID,
1473
+ contractId: CODEX_CONTRACT_ID,
1474
+ effects: {
1475
+ decision: "block",
1476
+ reason: result.closedFailure.detail
1477
+ }
1478
+ }]);
1479
+ return output(payload.eventName, result.outcomes.flatMap((outcome) => outcome.effects ?? []));
1480
+ }
1481
+
1079
1482
  //#endregion
1080
1483
  //#region src/runtime/namespace.ts
1081
1484
  /**
@@ -2167,6 +2570,8 @@ function mergeOperationErrors(existing, next, message) {
2167
2570
  * the `commandContains` filter in {@link removeCodexWiring}.
2168
2571
  */
2169
2572
  const CODEX_HOOK_COMMAND_SENTINEL = "hook received codex";
2573
+ /** Sentinel for synchronous Codex hook responses. */
2574
+ const CODEX_HOOK_HANDLE_COMMAND_SENTINEL = "hook handle codex";
2170
2575
  /**
2171
2576
  * Descriptors for all session-events hooks derived from the client definition.
2172
2577
  *
@@ -2188,8 +2593,8 @@ const SESSION_EVENTS = deriveSessionEventDescriptors(clientDefinition);
2188
2593
  */
2189
2594
  async function buildCodexWiringList(settings, makaioCommand, projectDir) {
2190
2595
  const { effective } = await settings.listHooks(projectDir !== void 0 ? { projectDir } : {});
2191
- return { entries: SESSION_EVENTS.map(({ eventName }) => {
2192
- const command = buildHookCommand(makaioCommand, CODEX_HOOK_COMMAND_SENTINEL, eventName, void 0, ["--debounce-failure"]);
2596
+ return { entries: SESSION_EVENTS.map(({ eventName, mode }) => {
2597
+ const command = buildModeCommand(makaioCommand, eventName, mode);
2193
2598
  return {
2194
2599
  group: "session-events",
2195
2600
  name: eventName,
@@ -2218,9 +2623,9 @@ async function applyCodexWiring(settings, scope, makaioCommand, projectDir) {
2218
2623
  let skipped = 0;
2219
2624
  const { perScope } = await settings.listHooks(projectDir !== void 0 ? { projectDir } : {});
2220
2625
  const scopeHooks = perScope.find((s) => s.scope === scope)?.hooks ?? [];
2221
- for (const { eventName } of SESSION_EVENTS) {
2222
- const sentinel = `${CODEX_HOOK_COMMAND_SENTINEL} ${eventName}`;
2223
- const command = buildHookCommand(makaioCommand, CODEX_HOOK_COMMAND_SENTINEL, eventName, void 0, ["--debounce-failure"]);
2626
+ for (const { eventName, mode } of SESSION_EVENTS) {
2627
+ const sentinel = `${mode === "request" ? CODEX_HOOK_HANDLE_COMMAND_SENTINEL : CODEX_HOOK_COMMAND_SENTINEL} ${eventName}`;
2628
+ const command = buildModeCommand(makaioCommand, eventName, mode);
2224
2629
  const existingEntry = scopeHooks.find((entry) => entry.event === eventName && entry.command.includes(sentinel));
2225
2630
  if (existingEntry !== void 0) {
2226
2631
  if (existingEntry.command === command) {
@@ -2261,17 +2666,33 @@ async function applyCodexWiring(settings, scope, makaioCommand, projectDir) {
2261
2666
  */
2262
2667
  async function removeCodexWiring(settings, scope, projectDir) {
2263
2668
  let removed = 0;
2264
- for (const { eventName } of SESSION_EVENTS) {
2669
+ for (const { eventName } of SESSION_EVENTS) for (const sentinel of [CODEX_HOOK_COMMAND_SENTINEL, CODEX_HOOK_HANDLE_COMMAND_SENTINEL]) {
2265
2670
  const result = await settings.removeHook({
2266
2671
  scope,
2267
2672
  event: eventName,
2268
- match: { commandContains: `${CODEX_HOOK_COMMAND_SENTINEL} ${eventName}` },
2673
+ match: { commandContains: `${sentinel} ${eventName}` },
2269
2674
  ...projectDir !== void 0 ? { projectDir } : {}
2270
2675
  });
2271
2676
  removed += result.removed;
2272
2677
  }
2273
2678
  return { removed };
2274
2679
  }
2680
+ /**
2681
+ * Build the managed command for one capability-derived hook mode.
2682
+ * @param makaioCommand - Makaio CLI executable.
2683
+ * @param eventName - Native Codex event name.
2684
+ * @param mode - Capability-derived transport mode.
2685
+ * @returns Shell-safe managed hook command.
2686
+ */
2687
+ function buildModeCommand(makaioCommand, eventName, mode) {
2688
+ return mode === "request" ? buildClientCommand(makaioCommand, [
2689
+ "--no-launch",
2690
+ ...CODEX_HOOK_HANDLE_COMMAND_SENTINEL.split(" "),
2691
+ eventName,
2692
+ "--timeout",
2693
+ String(DEFAULT_HOOK_HANDLE_TIMEOUT_MS)
2694
+ ]) : buildHookCommand(makaioCommand, CODEX_HOOK_COMMAND_SENTINEL, eventName, void 0, ["--debounce-failure"]);
2695
+ }
2275
2696
 
2276
2697
  //#endregion
2277
2698
  //#region src/runtime/codex-client-session-service.ts
@@ -2333,6 +2754,16 @@ var CodexClientSessionService = class extends BaseService {
2333
2754
  */
2334
2755
  managedAdapterSessionIds = /* @__PURE__ */ new Set();
2335
2756
  /**
2757
+ * Optional provider contract registry for registering and unregistering
2758
+ * the Codex hook response contract during the service lifecycle.
2759
+ *
2760
+ * When `undefined`, contract registration is skipped — this allows
2761
+ * lightweight test construction without requiring a full clients-core
2762
+ * dependency.
2763
+ */
2764
+ providerContractRegistry;
2765
+ hookResponseRegistry;
2766
+ /**
2336
2767
  * Creates a new Codex client session service.
2337
2768
  * @param bus - Bus instance used for subscribing and emitting events
2338
2769
  * @param settings - Optional {@link CodexClientSettings} instance for tests
@@ -2342,22 +2773,37 @@ var CodexClientSessionService = class extends BaseService {
2342
2773
  * caller-supplied from the extension context. Omit in tests or when the
2343
2774
  * identity is unavailable.
2344
2775
  * @param sessionConfigHandler - Optional native auth lease handler override.
2776
+ * @param providerContractRegistry - Optional provider contract registry
2777
+ * from clients-core for registering the Codex hook response contract.
2778
+ * Omit in tests that do not exercise the response pipeline.
2779
+ * @param hookResponseRegistry - Optional contributor registry used by the terminal composer.
2345
2780
  */
2346
- constructor(bus = MakaioBus, settings, machineId, sessionConfigHandler = new CodexSessionConfigHandler()) {
2781
+ constructor(bus = MakaioBus, settings, machineId, sessionConfigHandler = new CodexSessionConfigHandler(), providerContractRegistry, hookResponseRegistry) {
2347
2782
  super(bus);
2348
2783
  this.settingsOverride = settings;
2349
2784
  this.machineId = machineId;
2350
2785
  this.sessionConfigHandler = sessionConfigHandler;
2786
+ this.providerContractRegistry = providerContractRegistry;
2787
+ this.hookResponseRegistry = hookResponseRegistry;
2351
2788
  }
2352
2789
  /**
2353
- * Register the raw hook ingress handler, config management request handlers,
2354
- * wiring management request handlers, the config-prime lifecycle handler,
2355
- * and the session config setup/teardown handlers on the bus.
2790
+ * Register the raw hook ingress handler, the `hook.handle` request handler,
2791
+ * config management request handlers, wiring management request handlers,
2792
+ * the config-prime lifecycle handler, and the session config
2793
+ * setup/teardown handlers on the bus.
2356
2794
  *
2357
2795
  * Also subscribes to `client.runtime.started` to track adapter-managed
2358
2796
  * sessions for the {@link handleHookReceived} suppression gate.
2797
+ *
2798
+ * When a {@link ClientHookProviderContractRegistry} was supplied at
2799
+ * construction time, the Codex provider contract is registered so that
2800
+ * contributors can be validated against it during extension activation.
2359
2801
  */
2360
2802
  onInit() {
2803
+ if (this.providerContractRegistry !== void 0) {
2804
+ this.providerContractRegistry.registerProviderContract("codex.runtime", codexProviderContractCatalog);
2805
+ this.addCleanup(() => this.providerContractRegistry?.unregisterProviderContract("codex.runtime", codexProviderContractCatalog.clientId, codexProviderContractCatalog.contractId));
2806
+ }
2361
2807
  this.registerHandler(ClientSubjects.runtime.started, ({ payload }) => {
2362
2808
  this.handleRuntimeStarted(payload);
2363
2809
  });
@@ -2367,6 +2813,7 @@ var CodexClientSessionService = class extends BaseService {
2367
2813
  this.registerHandler(CodexClientSubjects.hook.received, async ({ payload }) => {
2368
2814
  await this.handleHookReceived(payload);
2369
2815
  });
2816
+ this.registerHookHandleHandler();
2370
2817
  this.registerHandler(CodexClientSubjects.config.hooks.list, async (ctx) => {
2371
2818
  ctx.setResult(await (await this.createSettings()).listHooks(ctx.payload));
2372
2819
  });
@@ -2401,13 +2848,44 @@ var CodexClientSessionService = class extends BaseService {
2401
2848
  });
2402
2849
  }
2403
2850
  /**
2404
- * Clear the adapter-managed session ID set on teardown.
2851
+ * Clear the adapter-managed session ID set and config cache on teardown.
2852
+ *
2853
+ * Provider contract unregistration is handled via {@link addCleanup} in
2854
+ * {@link onInit}, so it runs automatically during the base-class destroy
2855
+ * sequence alongside handler unsubscription.
2405
2856
  */
2406
2857
  onDestroy() {
2407
2858
  this.managedAdapterSessionIds.clear();
2408
2859
  this.cachedConfigDir = void 0;
2409
2860
  }
2410
2861
  /**
2862
+ * Register the `hook.handle` request handler.
2863
+ *
2864
+ * The handler receives request-mode hook payloads and returns a
2865
+ * {@link ClientHookHandleResponse} composed by
2866
+ * {@link composeCodexHookResponse}. The pinned Codex `0.144.1` source
2867
+ * parses synchronous responses for all five declared events.
2868
+ *
2869
+ * Extracted from {@link onInit} to keep the init method concise.
2870
+ */
2871
+ registerHookHandleHandler() {
2872
+ this.registerHandler(CodexClientSubjects.hook.handle, (ctx) => {
2873
+ if (!this.hookResponseRegistry) {
2874
+ ctx.setResult({
2875
+ exitCode: 0,
2876
+ stdout: "",
2877
+ stderr: ""
2878
+ });
2879
+ return;
2880
+ }
2881
+ return composeCodexHookResponse(this.hookResponseRegistry, ctx.payload, {
2882
+ deadline: ctx.deadline,
2883
+ signal: ctx.signal,
2884
+ onDiagnostics: (diagnostics) => diagnostics.forEach((diagnostic) => console.warn(`[CodexClientSessionService] Hook contributor '${diagnostic.contributorId}': ${diagnostic.message}`))
2885
+ }).then((response) => ctx.setResult(response));
2886
+ });
2887
+ }
2888
+ /**
2411
2889
  * Create a settings delegate for the active Codex config root.
2412
2890
  * @returns Settings instance bound to the managed config dir when available.
2413
2891
  */
@@ -2550,4 +3028,4 @@ function isResolveBinaryMissingGlobalBinary(error) {
2550
3028
  }
2551
3029
 
2552
3030
  //#endregion
2553
- export { clientDefinition as C, CodexScopeSchema as S, CodexHookEntrySchema as _, CODEX_CLIENT_NAMESPACE as a, CodexNativeHooksFileSchema as b, CodexWiringSchemas as c, CodexConfigHooksAddResponseSchema as d, CodexConfigHooksListRequestSchema as f, CodexConfigSchemas as g, CodexConfigHooksRemoveResponseSchema as h, withCodexNativeAuthSourceLock as i, AbsolutePathSchema as l, CodexConfigHooksRemoveRequestSchema as m, buildCodexNativeAuthSourceLockPath as n, CodexClientSubjects as o, CodexConfigHooksListResponseSchema as p, executeCodexNativeAuthSourceLock as r, normalizeCodexHook as s, CodexClientSessionService as t, CodexConfigHooksAddRequestSchema as u, CodexNativeCommandHookSchema as v, CodexScopeHookRecordSchema as x, CodexNativeHookMatcherGroupSchema as y };
3031
+ export { CodexConfigHooksAddResponseSchema as A, CodexScopeHookRecordSchema as B, createCodexStopBlockEffect as C, CodexWiringSchemas as D, normalizeCodexHook as E, CodexConfigSchemas as F, CODEX_HOOK_RESPONSE_CAPABILITIES as H, CodexHookEntrySchema as I, CodexNativeCommandHookSchema as L, CodexConfigHooksListResponseSchema as M, CodexConfigHooksRemoveRequestSchema as N, AbsolutePathSchema as O, CodexConfigHooksRemoveResponseSchema as P, CodexNativeHookMatcherGroupSchema as R, createCodexSessionStartContextEffect as S, createCodexUserPromptSubmitContextEffect as T, clientDefinition as U, CodexScopeSchema as V, createCodexPreToolUseBlockEffect as _, CODEX_CLIENT_NAMESPACE as a, createCodexPreToolUseUpdateEffect as b, CODEX_CLIENT_ID as c, CODEX_INTERACTION_BLOCKABILITY as d, CODEX_RESPONSE_CAPABILITIES as f, createCodexPostToolUseContextEffect as g, createCodexPostToolUseBlockEffect as h, withCodexNativeAuthSourceLock as i, CodexConfigHooksListRequestSchema as j, CodexConfigHooksAddRequestSchema as k, CODEX_CONTRACT_ID as l, codexProviderContractCatalog as m, buildCodexNativeAuthSourceLockPath as n, CodexClientSubjects as o, CODEX_SUPPORTED_INTERACTIONS as p, executeCodexNativeAuthSourceLock as r, composeCodexHookResponse as s, CodexClientSessionService as t, CODEX_CONTRACT_VERSION as u, createCodexPreToolUseContextEffect as v, createCodexUserPromptSubmitBlockEffect as w, createCodexSessionStartBlockEffect as x, createCodexPreToolUseDenyEffect as y, CodexNativeHooksFileSchema as z };
@@ -7,6 +7,12 @@
7
7
  * capability taxonomy in a single canonical location.
8
8
  * @packageDocumentation
9
9
  */
10
+ /** Namespaced Codex hook-response capabilities exposed to contributors. */
11
+ export declare const CODEX_HOOK_RESPONSE_CAPABILITIES: Readonly<{
12
+ readonly block: 'openai.codex-hook-response.block';
13
+ readonly permissionDeny: 'openai.codex-hook-response.permission.deny';
14
+ readonly inputUpdate: 'openai.codex-hook-response.input.update';
15
+ }>;
10
16
  /**
11
17
  * Static client definition for `@makaio/client-codex`.
12
18
  *
@@ -79,7 +85,7 @@ export declare const clientDefinition: {
79
85
  hookEvents: {
80
86
  name: string;
81
87
  frameworkSubject?: string | undefined;
82
- mode: "event" | "request";
88
+ responseCapabilities: readonly string[];
83
89
  }[];
84
90
  };
85
91
  managedInstall?: {
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { C as clientDefinition, S as CodexScopeSchema, _ as CodexHookEntrySchema, b as CodexNativeHooksFileSchema, c as CodexWiringSchemas, d as CodexConfigHooksAddResponseSchema, f as CodexConfigHooksListRequestSchema, g as CodexConfigSchemas, h as CodexConfigHooksRemoveResponseSchema, l as AbsolutePathSchema, m as CodexConfigHooksRemoveRequestSchema, o as CodexClientSubjects, p as CodexConfigHooksListResponseSchema, t as CodexClientSessionService, u as CodexConfigHooksAddRequestSchema, v as CodexNativeCommandHookSchema, x as CodexScopeHookRecordSchema, y as CodexNativeHookMatcherGroupSchema } from "./codex-client-session-service-B-E98xgY.mjs";
2
- import { t as codexPackage } from "./src-CXxq1vvn.mjs";
1
+ import { A as CodexConfigHooksAddResponseSchema, B as CodexScopeHookRecordSchema, D as CodexWiringSchemas, F as CodexConfigSchemas, I as CodexHookEntrySchema, L as CodexNativeCommandHookSchema, M as CodexConfigHooksListResponseSchema, N as CodexConfigHooksRemoveRequestSchema, O as AbsolutePathSchema, P as CodexConfigHooksRemoveResponseSchema, R as CodexNativeHookMatcherGroupSchema, U as clientDefinition, V as CodexScopeSchema, j as CodexConfigHooksListRequestSchema, k as CodexConfigHooksAddRequestSchema, o as CodexClientSubjects, t as CodexClientSessionService, z as CodexNativeHooksFileSchema } from "./codex-client-session-service-CTJ576Q4.mjs";
2
+ import { t as codexPackage } from "./src-DXsdjc9S.mjs";
3
3
 
4
4
  export { AbsolutePathSchema, CodexClientSessionService, CodexClientSubjects, CodexConfigHooksAddRequestSchema, CodexConfigHooksAddResponseSchema, CodexConfigHooksListRequestSchema, CodexConfigHooksListResponseSchema, CodexConfigHooksRemoveRequestSchema, CodexConfigHooksRemoveResponseSchema, CodexConfigSchemas, CodexHookEntrySchema, CodexNativeCommandHookSchema, CodexNativeHookMatcherGroupSchema, CodexNativeHooksFileSchema, CodexScopeHookRecordSchema, CodexScopeSchema, CodexWiringSchemas, clientDefinition, codexPackage };
@@ -39,6 +39,7 @@
39
39
  * @packageDocumentation
40
40
  */
41
41
  import type { IMakaioBus } from '@makaio/framework/bus';
42
+ import type { ClientHookProviderContractRegistry, ClientHookResponseRegistry } from '@makaio/framework/clients';
42
43
  import { BaseService } from '@makaio/framework/service-base';
43
44
  import { CodexClientSettings } from './client-settings.js';
44
45
  import { CodexSessionConfigHandler } from './session-config-handler.js';
@@ -97,6 +98,16 @@ export declare class CodexClientSessionService extends BaseService {
97
98
  * emissions for sessions that the adapter path already covers.
98
99
  */
99
100
  private readonly managedAdapterSessionIds;
101
+ /**
102
+ * Optional provider contract registry for registering and unregistering
103
+ * the Codex hook response contract during the service lifecycle.
104
+ *
105
+ * When `undefined`, contract registration is skipped — this allows
106
+ * lightweight test construction without requiring a full clients-core
107
+ * dependency.
108
+ */
109
+ private readonly providerContractRegistry;
110
+ private readonly hookResponseRegistry;
100
111
  /**
101
112
  * Creates a new Codex client session service.
102
113
  * @param bus - Bus instance used for subscribing and emitting events
@@ -107,21 +118,45 @@ export declare class CodexClientSessionService extends BaseService {
107
118
  * caller-supplied from the extension context. Omit in tests or when the
108
119
  * identity is unavailable.
109
120
  * @param sessionConfigHandler - Optional native auth lease handler override.
121
+ * @param providerContractRegistry - Optional provider contract registry
122
+ * from clients-core for registering the Codex hook response contract.
123
+ * Omit in tests that do not exercise the response pipeline.
124
+ * @param hookResponseRegistry - Optional contributor registry used by the terminal composer.
110
125
  */
111
- constructor(bus?: IMakaioBus, settings?: CodexClientSettings, machineId?: string, sessionConfigHandler?: CodexSessionConfigHandler);
126
+ constructor(bus?: IMakaioBus, settings?: CodexClientSettings, machineId?: string, sessionConfigHandler?: CodexSessionConfigHandler, providerContractRegistry?: ClientHookProviderContractRegistry, hookResponseRegistry?: ClientHookResponseRegistry);
112
127
  /**
113
- * Register the raw hook ingress handler, config management request handlers,
114
- * wiring management request handlers, the config-prime lifecycle handler,
115
- * and the session config setup/teardown handlers on the bus.
128
+ * Register the raw hook ingress handler, the `hook.handle` request handler,
129
+ * config management request handlers, wiring management request handlers,
130
+ * the config-prime lifecycle handler, and the session config
131
+ * setup/teardown handlers on the bus.
116
132
  *
117
133
  * Also subscribes to `client.runtime.started` to track adapter-managed
118
134
  * sessions for the {@link handleHookReceived} suppression gate.
135
+ *
136
+ * When a {@link ClientHookProviderContractRegistry} was supplied at
137
+ * construction time, the Codex provider contract is registered so that
138
+ * contributors can be validated against it during extension activation.
119
139
  */
120
140
  protected onInit(): void;
121
141
  /**
122
- * Clear the adapter-managed session ID set on teardown.
142
+ * Clear the adapter-managed session ID set and config cache on teardown.
143
+ *
144
+ * Provider contract unregistration is handled via {@link addCleanup} in
145
+ * {@link onInit}, so it runs automatically during the base-class destroy
146
+ * sequence alongside handler unsubscription.
123
147
  */
124
148
  protected onDestroy(): void;
149
+ /**
150
+ * Register the `hook.handle` request handler.
151
+ *
152
+ * The handler receives request-mode hook payloads and returns a
153
+ * {@link ClientHookHandleResponse} composed by
154
+ * {@link composeCodexHookResponse}. The pinned Codex `0.144.1` source
155
+ * parses synchronous responses for all five declared events.
156
+ *
157
+ * Extracted from {@link onInit} to keep the init method concise.
158
+ */
159
+ private registerHookHandleHandler;
125
160
  private createSettings;
126
161
  /**
127
162
  * Return the cached config directory promise, resolving it on first access.
@@ -0,0 +1,15 @@
1
+ import { type ClientHookHandleResponse, type ClientHookResponseRegistry, type CollectionDiagnostic } from '@makaio/framework/clients';
2
+ import { type RawClientHookPayload } from './schemas.js';
3
+ export interface ComposeCodexHookResponseOptions {
4
+ readonly deadline?: number;
5
+ readonly signal?: AbortSignal;
6
+ readonly onDiagnostics?: (diagnostics: readonly CollectionDiagnostic[]) => void;
7
+ }
8
+ /**
9
+ * Compose one terminal Codex native hook response.
10
+ * @param registry - Active response contributor registry.
11
+ * @param payload - Normalized native hook payload.
12
+ * @param options - Request deadline, cancellation, and diagnostics hooks.
13
+ * @returns The composed native response envelope.
14
+ */
15
+ export declare function composeCodexHookResponse(registry: ClientHookResponseRegistry, payload: RawClientHookPayload, options?: ComposeCodexHookResponseOptions): Promise<ClientHookHandleResponse>;
@@ -0,0 +1,103 @@
1
+ /** Codex 0.144.1 synchronous hook-response contract. @packageDocumentation */
2
+ import type { InteractionBlockability, ProviderContractCatalogEntry, ProviderContributionEnvelope } from '@makaio/framework/contracts/client';
3
+ export declare const CODEX_CLIENT_ID = "codex";
4
+ export declare const CODEX_CONTRACT_ID = "openai.codex-hook-response";
5
+ export declare const CODEX_CONTRACT_VERSION = "1.1.0";
6
+ export type CodexBlockEffects = Readonly<{
7
+ decision: 'block';
8
+ reason: string;
9
+ }> & Record<string, unknown>;
10
+ export type CodexContextEffects = Readonly<{
11
+ additionalContext: string;
12
+ }> & Record<string, unknown>;
13
+ export type CodexPermissionDenyEffects = Readonly<{
14
+ permissionDecision: 'deny';
15
+ permissionDecisionReason: string;
16
+ }> & Record<string, unknown>;
17
+ /** JSON value accepted by Codex's `serde_json::Value` hook parser. */
18
+ export type CodexJsonValue = string | number | boolean | null | readonly CodexJsonValue[] | {
19
+ readonly [key: string]: CodexJsonValue;
20
+ };
21
+ /**
22
+ * JSON input accepted as a Codex `updated_input` replacement.
23
+ *
24
+ * The pinned Rust hook contract represents the field as `Option<Value>`:
25
+ * a top-level JSON `null` therefore deserializes as an absent value, while
26
+ * nested null values remain valid JSON within a present replacement.
27
+ */
28
+ export type CodexUpdatedInput = Exclude<CodexJsonValue, null>;
29
+ export type CodexInputUpdateEffects = Readonly<{
30
+ permissionDecision: 'allow';
31
+ updatedInput: CodexUpdatedInput;
32
+ }> & Record<string, unknown>;
33
+ export type CodexEffects = CodexBlockEffects | CodexContextEffects | CodexPermissionDenyEffects | CodexInputUpdateEffects;
34
+ /**
35
+ * Builds a SessionStart context response.
36
+ * @param value - Context appended when the session starts.
37
+ * @returns A Codex SessionStart context envelope.
38
+ */
39
+ export declare function createCodexSessionStartContextEffect(value: string): ProviderContributionEnvelope<CodexContextEffects>;
40
+ /**
41
+ * Builds a SessionStart stop response.
42
+ * @param reason - Reason for stopping session startup.
43
+ * @returns A Codex SessionStart block envelope.
44
+ */
45
+ export declare function createCodexSessionStartBlockEffect(reason: string): ProviderContributionEnvelope<CodexBlockEffects>;
46
+ /**
47
+ * Builds a UserPromptSubmit context response.
48
+ * @param value - Context appended to the submitted prompt.
49
+ * @returns A Codex UserPromptSubmit context envelope.
50
+ */
51
+ export declare function createCodexUserPromptSubmitContextEffect(value: string): ProviderContributionEnvelope<CodexContextEffects>;
52
+ /**
53
+ * Builds a UserPromptSubmit block response.
54
+ * @param reason - Reason for blocking the submitted prompt.
55
+ * @returns A Codex UserPromptSubmit block envelope.
56
+ */
57
+ export declare function createCodexUserPromptSubmitBlockEffect(reason: string): ProviderContributionEnvelope<CodexBlockEffects>;
58
+ /**
59
+ * Builds a PreToolUse block response.
60
+ * @param reason - Reason for blocking the tool invocation.
61
+ * @returns A Codex PreToolUse block envelope.
62
+ */
63
+ export declare function createCodexPreToolUseBlockEffect(reason: string): ProviderContributionEnvelope<CodexBlockEffects>;
64
+ /**
65
+ * Builds a PreToolUse deny-permission response.
66
+ * @param reason - Reason for denying the tool invocation.
67
+ * @returns A Codex PreToolUse permission-denial envelope.
68
+ */
69
+ export declare function createCodexPreToolUseDenyEffect(reason: string): ProviderContributionEnvelope<CodexPermissionDenyEffects>;
70
+ /**
71
+ * Builds additional model context for PreToolUse.
72
+ * @param value - Context appended before the tool invocation.
73
+ * @returns A Codex PreToolUse context envelope.
74
+ */
75
+ export declare function createCodexPreToolUseContextEffect(value: string): ProviderContributionEnvelope<CodexContextEffects>;
76
+ /**
77
+ * Builds a PreToolUse allowed input update.
78
+ * @param updatedInput - JSON value that replaces the native tool input.
79
+ * @returns A Codex PreToolUse input-update envelope.
80
+ */
81
+ export declare function createCodexPreToolUseUpdateEffect(updatedInput: CodexUpdatedInput): ProviderContributionEnvelope<CodexInputUpdateEffects>;
82
+ /**
83
+ * Builds a PostToolUse context response.
84
+ * @param value - Context appended after the tool invocation.
85
+ * @returns A Codex PostToolUse context envelope.
86
+ */
87
+ export declare function createCodexPostToolUseContextEffect(value: string): ProviderContributionEnvelope<CodexContextEffects>;
88
+ /**
89
+ * Builds a PostToolUse block response.
90
+ * @param reason - Reason for blocking after the tool invocation.
91
+ * @returns A Codex PostToolUse block envelope.
92
+ */
93
+ export declare function createCodexPostToolUseBlockEffect(reason: string): ProviderContributionEnvelope<CodexBlockEffects>;
94
+ /**
95
+ * Builds a Stop block response.
96
+ * @param reason - Reason for requesting another turn.
97
+ * @returns A Codex Stop block envelope.
98
+ */
99
+ export declare function createCodexStopBlockEffect(reason: string): ProviderContributionEnvelope<CodexBlockEffects>;
100
+ export declare const CODEX_RESPONSE_CAPABILITIES: readonly string[];
101
+ export declare const CODEX_SUPPORTED_INTERACTIONS: readonly string[];
102
+ export declare const CODEX_INTERACTION_BLOCKABILITY: readonly InteractionBlockability[];
103
+ export declare const codexProviderContractCatalog: ProviderContractCatalogEntry;
@@ -47,7 +47,7 @@ export declare const CodexClientSubjects: import("@makaio/framework/core").BusSu
47
47
  payload: import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>;
48
48
  metadata: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>;
49
49
  }, import("zod/v4/core").$strip>;
50
- 'hook.handle': {
50
+ 'hook.handle': import("@makaio/framework/core").HostLocalRequestSubjectSchema<{
51
51
  request: import("zod").ZodObject<{
52
52
  eventName: import("zod").ZodString;
53
53
  receivedAt: import("zod").ZodNumber;
@@ -59,7 +59,7 @@ export declare const CodexClientSubjects: import("@makaio/framework/core").BusSu
59
59
  stdout: import("zod").ZodDefault<import("zod").ZodString>;
60
60
  stderr: import("zod").ZodDefault<import("zod").ZodString>;
61
61
  }, import("zod/v4/core").$strip>;
62
- };
62
+ }>;
63
63
  } & {
64
64
  'config.hooks.list': {
65
65
  request: import("zod").ZodObject<{
@@ -3,6 +3,12 @@
3
3
  *
4
4
  * Exports the {@link MakaioExtension} manifest for the Codex client runtime and
5
5
  * re-exports the namespace subjects for client-native integrations.
6
+ *
7
+ * The `create()` factory resolves the `clients-core` service via
8
+ * {@link ClientsCoreToken} and passes its
9
+ * {@link ClientHookProviderContractRegistry} to the
10
+ * {@link CodexClientSessionService} so the Codex provider contract is
11
+ * registered and unregistered alongside the service lifecycle.
6
12
  * @packageDocumentation
7
13
  */
8
14
  import type { IMakaioBus } from '@makaio/framework/bus';
@@ -13,11 +19,19 @@ export { normalizeCodexHook } from './hook-normalizer.js';
13
19
  export type { CodexNormalizedEvent, CodexNormalizedSubject } from './hook-normalizer.js';
14
20
  export { buildCodexNativeAuthSourceLockPath, executeCodexNativeAuthSourceLock, withCodexNativeAuthSourceLock, } from './native-auth-source-lock.js';
15
21
  export type { CodexNativeAuthSourceLockExecution } from './native-auth-source-lock.js';
22
+ export { CODEX_HOOK_RESPONSE_CAPABILITIES } from '../definition.js';
23
+ export { codexProviderContractCatalog, CODEX_CLIENT_ID, CODEX_CONTRACT_ID, CODEX_CONTRACT_VERSION, CODEX_SUPPORTED_INTERACTIONS, CODEX_RESPONSE_CAPABILITIES, CODEX_INTERACTION_BLOCKABILITY, createCodexSessionStartContextEffect, createCodexSessionStartBlockEffect, createCodexUserPromptSubmitContextEffect, createCodexUserPromptSubmitBlockEffect, createCodexPreToolUseBlockEffect, createCodexPreToolUseContextEffect, createCodexPreToolUseDenyEffect, createCodexPreToolUseUpdateEffect, createCodexPostToolUseContextEffect, createCodexPostToolUseBlockEffect, createCodexStopBlockEffect, } from './hook-response-contracts.js';
24
+ export type { CodexEffects, CodexJsonValue } from './hook-response-contracts.js';
25
+ export { composeCodexHookResponse } from './hook-response-composer.js';
16
26
  /**
17
27
  * MakaioExtension manifest for the Codex client session normalization service.
18
28
  *
19
29
  * Creates a {@link CodexClientSessionService} that bridges raw
20
30
  * `client:codex.hook.received` events into normalized `client.session.*`
21
31
  * observations when the Codex descriptor server entry activates.
32
+ *
33
+ * The `create()` factory requires the `clients-core` service so provider
34
+ * contract registration and terminal hook composition cannot silently start
35
+ * in a non-functional state.
22
36
  */
23
37
  export declare const codexClientRuntimePackage: MakaioNodeExtension<IMakaioBus>;
@@ -1,5 +1,6 @@
1
- import { a as CODEX_CLIENT_NAMESPACE, i as withCodexNativeAuthSourceLock, n as buildCodexNativeAuthSourceLockPath, o as CodexClientSubjects, r as executeCodexNativeAuthSourceLock, s as normalizeCodexHook, t as CodexClientSessionService } from "../codex-client-session-service-B-E98xgY.mjs";
1
+ import { C as createCodexStopBlockEffect, E as normalizeCodexHook, H as CODEX_HOOK_RESPONSE_CAPABILITIES, S as createCodexSessionStartContextEffect, T as createCodexUserPromptSubmitContextEffect, _ as createCodexPreToolUseBlockEffect, a as CODEX_CLIENT_NAMESPACE, b as createCodexPreToolUseUpdateEffect, c as CODEX_CLIENT_ID, d as CODEX_INTERACTION_BLOCKABILITY, f as CODEX_RESPONSE_CAPABILITIES, g as createCodexPostToolUseContextEffect, h as createCodexPostToolUseBlockEffect, i as withCodexNativeAuthSourceLock, l as CODEX_CONTRACT_ID, m as codexProviderContractCatalog, n as buildCodexNativeAuthSourceLockPath, o as CodexClientSubjects, p as CODEX_SUPPORTED_INTERACTIONS, r as executeCodexNativeAuthSourceLock, s as composeCodexHookResponse, t as CodexClientSessionService, u as CODEX_CONTRACT_VERSION, v as createCodexPreToolUseContextEffect, w as createCodexUserPromptSubmitBlockEffect, x as createCodexSessionStartBlockEffect, y as createCodexPreToolUseDenyEffect } from "../codex-client-session-service-CTJ576Q4.mjs";
2
2
  import { dep } from "@makaio/framework/contracts";
3
+ import { ClientsCoreToken } from "@makaio/framework/clients";
3
4
 
4
5
  //#region src/runtime/package.ts
5
6
  /**
@@ -7,6 +8,12 @@ import { dep } from "@makaio/framework/contracts";
7
8
  *
8
9
  * Exports the {@link MakaioExtension} manifest for the Codex client runtime and
9
10
  * re-exports the namespace subjects for client-native integrations.
11
+ *
12
+ * The `create()` factory resolves the `clients-core` service via
13
+ * {@link ClientsCoreToken} and passes its
14
+ * {@link ClientHookProviderContractRegistry} to the
15
+ * {@link CodexClientSessionService} so the Codex provider contract is
16
+ * registered and unregistered alongside the service lifecycle.
10
17
  * @packageDocumentation
11
18
  */
12
19
  /**
@@ -15,6 +22,10 @@ import { dep } from "@makaio/framework/contracts";
15
22
  * Creates a {@link CodexClientSessionService} that bridges raw
16
23
  * `client:codex.hook.received` events into normalized `client.session.*`
17
24
  * observations when the Codex descriptor server entry activates.
25
+ *
26
+ * The `create()` factory requires the `clients-core` service so provider
27
+ * contract registration and terminal hook composition cannot silently start
28
+ * in a non-functional state.
18
29
  */
19
30
  const codexClientRuntimePackage = {
20
31
  name: "codex.runtime",
@@ -23,11 +34,19 @@ const codexClientRuntimePackage = {
23
34
  dependencies: [dep("makaio.clients-core")],
24
35
  /**
25
36
  * Create the Codex client session service bound to the runtime bus.
37
+ *
38
+ * Resolves the clients-core service via {@link ClientsCoreToken} to
39
+ * obtain the {@link ClientHookProviderContractRegistry} for provider
40
+ * contract registration during the service lifecycle.
26
41
  * @param ctx - Runtime package context
27
42
  * @returns Uninitialized Codex client session service
28
43
  */
29
- create: (ctx) => new CodexClientSessionService(ctx.bus, void 0, ctx.machineId)
44
+ create: (ctx) => {
45
+ const clientsCore = ctx.getService(ClientsCoreToken);
46
+ if (!clientsCore) throw new Error("codex.runtime requires makaio.clients-core");
47
+ return new CodexClientSessionService(ctx.bus, void 0, ctx.machineId, void 0, clientsCore.providerContractRegistry, clientsCore.hookResponseRegistry);
48
+ }
30
49
  };
31
50
 
32
51
  //#endregion
33
- export { CODEX_CLIENT_NAMESPACE, CodexClientSessionService, CodexClientSubjects, buildCodexNativeAuthSourceLockPath, codexClientRuntimePackage, executeCodexNativeAuthSourceLock, normalizeCodexHook, withCodexNativeAuthSourceLock };
52
+ export { CODEX_CLIENT_ID, CODEX_CLIENT_NAMESPACE, CODEX_CONTRACT_ID, CODEX_CONTRACT_VERSION, CODEX_HOOK_RESPONSE_CAPABILITIES, CODEX_INTERACTION_BLOCKABILITY, CODEX_RESPONSE_CAPABILITIES, CODEX_SUPPORTED_INTERACTIONS, CodexClientSessionService, CodexClientSubjects, buildCodexNativeAuthSourceLockPath, codexClientRuntimePackage, codexProviderContractCatalog, composeCodexHookResponse, createCodexPostToolUseBlockEffect, createCodexPostToolUseContextEffect, createCodexPreToolUseBlockEffect, createCodexPreToolUseContextEffect, createCodexPreToolUseDenyEffect, createCodexPreToolUseUpdateEffect, createCodexSessionStartBlockEffect, createCodexSessionStartContextEffect, createCodexStopBlockEffect, createCodexUserPromptSubmitBlockEffect, createCodexUserPromptSubmitContextEffect, executeCodexNativeAuthSourceLock, normalizeCodexHook, withCodexNativeAuthSourceLock };
@@ -41,6 +41,8 @@ export interface CodexWiringSettings {
41
41
  * the `commandContains` filter in {@link removeCodexWiring}.
42
42
  */
43
43
  export declare const CODEX_HOOK_COMMAND_SENTINEL = "hook received codex";
44
+ /** Sentinel for synchronous Codex hook responses. */
45
+ export declare const CODEX_HOOK_HANDLE_COMMAND_SENTINEL = "hook handle codex";
44
46
  /**
45
47
  * Build the wiring entry list, annotated with installation status, by
46
48
  * comparing the expected entries against the currently installed Codex hooks.
package/dist/server.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as codexPackage } from "./src-CXxq1vvn.mjs";
1
+ import { t as codexPackage } from "./src-DXsdjc9S.mjs";
2
2
  import { codexClientRuntimePackage } from "./runtime/package.mjs";
3
3
 
4
4
  //#region src/server.ts
@@ -1,4 +1,4 @@
1
- import { C as clientDefinition } from "./codex-client-session-service-B-E98xgY.mjs";
1
+ import { U as clientDefinition } from "./codex-client-session-service-CTJ576Q4.mjs";
2
2
 
3
3
  //#region src/package.ts
4
4
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@makaio/client-codex",
3
- "version": "1.0.0-dev-1783973359164",
3
+ "version": "1.0.0-dev-1784806252593",
4
4
  "type": "module",
5
5
  "main": "dist/index.mjs",
6
6
  "exports": {
@@ -27,7 +27,7 @@
27
27
  "dependencies": {
28
28
  "@napi-rs/keyring": "^1.2.0",
29
29
  "proper-lockfile": "^4.1.2",
30
- "smol-toml": "^1.3.0",
30
+ "smol-toml": "^1.7.0",
31
31
  "zod": "4.4.3"
32
32
  },
33
33
  "files": [