oh-my-codex 0.21.13 → 0.21.15

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 (141) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +17 -0
  4. package/dist/adapt/herdr/__tests__/wiring.test.js +21 -0
  5. package/dist/adapt/herdr/__tests__/wiring.test.js.map +1 -1
  6. package/dist/adapt/herdr/wiring.d.ts.map +1 -1
  7. package/dist/adapt/herdr/wiring.js +5 -0
  8. package/dist/adapt/herdr/wiring.js.map +1 -1
  9. package/dist/auth/__tests__/hotswap.test.js +28 -0
  10. package/dist/auth/__tests__/hotswap.test.js.map +1 -1
  11. package/dist/auth/hotswap.d.ts +1 -0
  12. package/dist/auth/hotswap.d.ts.map +1 -1
  13. package/dist/auth/hotswap.js +4 -1
  14. package/dist/auth/hotswap.js.map +1 -1
  15. package/dist/cli/__tests__/codex-plugin-layout.test.js +196 -0
  16. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  17. package/dist/cli/__tests__/doctor-warning-copy.test.js +2 -1
  18. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  19. package/dist/cli/__tests__/index.test.js +14 -1
  20. package/dist/cli/__tests__/index.test.js.map +1 -1
  21. package/dist/cli/__tests__/session-timeline.test.d.ts +2 -0
  22. package/dist/cli/__tests__/session-timeline.test.d.ts.map +1 -0
  23. package/dist/cli/__tests__/session-timeline.test.js +155 -0
  24. package/dist/cli/__tests__/session-timeline.test.js.map +1 -0
  25. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +1 -0
  26. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
  27. package/dist/cli/doctor.d.ts.map +1 -1
  28. package/dist/cli/doctor.js +1 -0
  29. package/dist/cli/doctor.js.map +1 -1
  30. package/dist/cli/index.d.ts +1 -0
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli/index.js +11 -0
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/session-search.d.ts.map +1 -1
  35. package/dist/cli/session-search.js +7 -0
  36. package/dist/cli/session-search.js.map +1 -1
  37. package/dist/cli/session-timeline.d.ts +9 -0
  38. package/dist/cli/session-timeline.d.ts.map +1 -0
  39. package/dist/cli/session-timeline.js +174 -0
  40. package/dist/cli/session-timeline.js.map +1 -0
  41. package/dist/config/__tests__/codex-hooks.test.js +3 -1
  42. package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
  43. package/dist/config/__tests__/generator-idempotent.test.js +1 -0
  44. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  45. package/dist/config/codex-hooks.d.ts +1 -1
  46. package/dist/config/codex-hooks.d.ts.map +1 -1
  47. package/dist/config/codex-hooks.js +5 -0
  48. package/dist/config/codex-hooks.js.map +1 -1
  49. package/dist/evals/astra-defaults/__tests__/collector.test.js +51 -23
  50. package/dist/evals/astra-defaults/__tests__/collector.test.js.map +1 -1
  51. package/dist/evals/astra-defaults/collector.d.ts +2 -0
  52. package/dist/evals/astra-defaults/collector.d.ts.map +1 -1
  53. package/dist/evals/astra-defaults/collector.js +19 -8
  54. package/dist/evals/astra-defaults/collector.js.map +1 -1
  55. package/dist/hooks/extensibility/__tests__/dispatcher.test.js +143 -0
  56. package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
  57. package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
  58. package/dist/hooks/extensibility/dispatcher.js +120 -28
  59. package/dist/hooks/extensibility/dispatcher.js.map +1 -1
  60. package/dist/hooks/extensibility/runtime.d.ts.map +1 -1
  61. package/dist/hooks/extensibility/runtime.js +1 -0
  62. package/dist/hooks/extensibility/runtime.js.map +1 -1
  63. package/dist/hooks/extensibility/types.d.ts +2 -0
  64. package/dist/hooks/extensibility/types.d.ts.map +1 -1
  65. package/dist/hooks/native/types.d.ts +1 -1
  66. package/dist/hooks/native/types.d.ts.map +1 -1
  67. package/dist/hooks/native/types.js.map +1 -1
  68. package/dist/notifications/__tests__/lifecycle-dedupe.test.js +200 -2
  69. package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
  70. package/dist/notifications/lifecycle-dedupe.d.ts +10 -0
  71. package/dist/notifications/lifecycle-dedupe.d.ts.map +1 -1
  72. package/dist/notifications/lifecycle-dedupe.js +175 -19
  73. package/dist/notifications/lifecycle-dedupe.js.map +1 -1
  74. package/dist/scripts/__tests__/codex-native-hook.test.js +278 -6
  75. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  76. package/dist/scripts/__tests__/native-hook-protocol.test.d.ts +2 -0
  77. package/dist/scripts/__tests__/native-hook-protocol.test.d.ts.map +1 -0
  78. package/dist/scripts/__tests__/native-hook-protocol.test.js +329 -0
  79. package/dist/scripts/__tests__/native-hook-protocol.test.js.map +1 -0
  80. package/dist/scripts/__tests__/smoke-packed-install.test.js +7 -6
  81. package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
  82. package/dist/scripts/codex-execution-surface.d.ts +1 -1
  83. package/dist/scripts/codex-execution-surface.d.ts.map +1 -1
  84. package/dist/scripts/codex-execution-surface.js.map +1 -1
  85. package/dist/scripts/codex-native-hook.d.ts +1 -1
  86. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  87. package/dist/scripts/codex-native-hook.js +124 -13
  88. package/dist/scripts/codex-native-hook.js.map +1 -1
  89. package/dist/scripts/hook-payload-guard.d.ts +1 -1
  90. package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
  91. package/dist/scripts/hook-payload-guard.js +1 -0
  92. package/dist/scripts/hook-payload-guard.js.map +1 -1
  93. package/dist/scripts/smoke-packed-install.d.ts +2 -2
  94. package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
  95. package/dist/scripts/smoke-packed-install.js +12 -1
  96. package/dist/scripts/smoke-packed-install.js.map +1 -1
  97. package/dist/session-history/__tests__/timeline.test.d.ts +2 -0
  98. package/dist/session-history/__tests__/timeline.test.d.ts.map +1 -0
  99. package/dist/session-history/__tests__/timeline.test.js +459 -0
  100. package/dist/session-history/__tests__/timeline.test.js.map +1 -0
  101. package/dist/session-history/export.d.ts +2 -3
  102. package/dist/session-history/export.d.ts.map +1 -1
  103. package/dist/session-history/export.js +5 -66
  104. package/dist/session-history/export.js.map +1 -1
  105. package/dist/session-history/timeline.d.ts +97 -0
  106. package/dist/session-history/timeline.d.ts.map +1 -0
  107. package/dist/session-history/timeline.js +325 -0
  108. package/dist/session-history/timeline.js.map +1 -0
  109. package/dist/session-history/transcript.d.ts +16 -0
  110. package/dist/session-history/transcript.d.ts.map +1 -0
  111. package/dist/session-history/transcript.js +111 -0
  112. package/dist/session-history/transcript.js.map +1 -0
  113. package/dist/subagents/tracker.d.ts +6 -0
  114. package/dist/subagents/tracker.d.ts.map +1 -1
  115. package/dist/subagents/tracker.js +32 -0
  116. package/dist/subagents/tracker.js.map +1 -1
  117. package/dist/team/__tests__/runtime.test.js +12 -3
  118. package/dist/team/__tests__/runtime.test.js.map +1 -1
  119. package/dist/team/__tests__/scaling.test.js +25 -0
  120. package/dist/team/__tests__/scaling.test.js.map +1 -1
  121. package/dist/team/__tests__/tmux-session.test.js +30 -0
  122. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  123. package/dist/team/tmux-session.d.ts.map +1 -1
  124. package/dist/team/tmux-session.js +9 -1
  125. package/dist/team/tmux-session.js.map +1 -1
  126. package/dist/wiki/__tests__/session-hooks.test.js +26 -0
  127. package/dist/wiki/__tests__/session-hooks.test.js.map +1 -1
  128. package/dist/wiki/lifecycle.d.ts.map +1 -1
  129. package/dist/wiki/lifecycle.js +6 -1
  130. package/dist/wiki/lifecycle.js.map +1 -1
  131. package/package.json +1 -1
  132. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  133. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +85 -18
  134. package/plugins/oh-my-codex/hooks/hooks.json +10 -0
  135. package/src/scripts/__tests__/codex-native-hook.test.ts +273 -5
  136. package/src/scripts/__tests__/native-hook-protocol.test.ts +362 -0
  137. package/src/scripts/__tests__/smoke-packed-install.test.ts +7 -6
  138. package/src/scripts/codex-execution-surface.ts +1 -0
  139. package/src/scripts/codex-native-hook.ts +130 -11
  140. package/src/scripts/hook-payload-guard.ts +1 -0
  141. package/src/scripts/smoke-packed-install.ts +12 -1
@@ -26,6 +26,7 @@ import {
26
26
  readSubagentTrackingState,
27
27
  recordNativeSubagentAuthorityObservation,
28
28
  recordSubagentTurnForSession,
29
+ recordSubagentTurnForSessionPartitions,
29
30
  repairPersistedRootIdentity,
30
31
  quarantinePersistedRootAuthority,
31
32
  revokeNativeSubagentAuthorities,
@@ -149,6 +150,7 @@ import {
149
150
  onPreCompact as buildWikiPreCompactContext,
150
151
  onSessionStart as buildWikiSessionStartContext,
151
152
  } from "../wiki/lifecycle.js";
153
+ import { sessionModelInstructionsPath } from "../hooks/agents-overlay.js";
152
154
  import { readAutoresearchCompletionStatus, readAutoresearchModeStateForActiveDecision } from "../autoresearch/skill-validation.js";
153
155
  import { deriveAutopilotChildPhase, normalizeAutopilotPhase } from "../autopilot/fsm.js";
154
156
  import {
@@ -208,6 +210,7 @@ type CodexHookEventName =
208
210
  | "UserPromptSubmit"
209
211
  | "PreCompact"
210
212
  | "PostCompact"
213
+ | "SubagentStop"
211
214
  | "Stop";
212
215
 
213
216
  type CodexHookPayload = Record<string, unknown>;
@@ -715,6 +718,7 @@ function readHookEventName(payload: CodexHookPayload): CodexHookEventName | null
715
718
  || raw === "UserPromptSubmit"
716
719
  || raw === "PreCompact"
717
720
  || raw === "PostCompact"
721
+ || raw === "SubagentStop"
718
722
  || raw === "Stop"
719
723
  ) {
720
724
  return raw;
@@ -746,6 +750,8 @@ export function mapCodexHookEventToOmxEvent(
746
750
  return "pre-compact";
747
751
  case "PostCompact":
748
752
  return "post-compact";
753
+ case "SubagentStop":
754
+ return "subagent-stop";
749
755
  case "Stop":
750
756
  return "stop";
751
757
  default:
@@ -880,9 +886,11 @@ function sanitizePayloadForHookContext(
880
886
  return sanitized;
881
887
  }
882
888
 
883
- if (hookEventName === "Stop") {
889
+ if (hookEventName === "Stop" || hookEventName === "SubagentStop") {
884
890
  delete sanitized.stop_hook_active;
885
891
  delete sanitized.stopHookActive;
892
+ }
893
+ if (hookEventName === "Stop") {
886
894
  delete sanitized.sessionId;
887
895
  sanitized.session_id = canonicalSessionId.trim() || safeString(payload.session_id ?? payload.sessionId).trim();
888
896
  }
@@ -2266,6 +2274,22 @@ async function buildSessionStartContext(
2266
2274
  ): Promise<string | null> {
2267
2275
  const sections: string[] = [];
2268
2276
 
2277
+ let startupInstructions = "";
2278
+ const startupFile = process.env.OMX_STARTUP_MODEL_INSTRUCTIONS_FILE;
2279
+ if (readSessionStartSource(options.payload) === "startup" && startupFile
2280
+ && resolve(startupFile) === sessionModelInstructionsPath(cwd, sessionId)) {
2281
+ try {
2282
+ startupInstructions = await readFile(startupFile, "utf-8");
2283
+ } catch {
2284
+ // Missing launch evidence must preserve hook-delivered memory.
2285
+ }
2286
+ }
2287
+ const startupMemory = startupInstructions.match(/(?:^|\n)\*\*Project Context:\*\*\r?\n([\s\S]*?)(?=\r?\n\r?\n|$)/)?.[1] ?? "";
2288
+ const alreadyInStartupInstructions = (label: string, value: string): boolean =>
2289
+ startupMemory.split(/\r?\n/).some(line => line.trim().replace(/\s+/g, " ") === `- ${label}: ${value}`);
2290
+ const startupPriority = startupInstructions.match(/(?:^|\n)\*\*Priority Notes:\*\*\r?\n([\s\S]*?)(?=\r?\n\r?\n|$)/)?.[1]
2291
+ .replace(/\s+/g, " ").trim();
2292
+
2269
2293
  sections.push(buildExecutionEnvironmentSection(cwd, {
2270
2294
  hookEventName: options.hookEventName,
2271
2295
  payload: options.payload,
@@ -2310,13 +2334,13 @@ async function buildSessionStartContext(
2310
2334
  const summary: string[] = [];
2311
2335
  const relativeMemoryPath = relative(cwd, projectMemoryPath).replace(/\\/g, "/");
2312
2336
  summary.push(`- source: ${relativeMemoryPath === "project-memory.json" ? "project-memory.json" : ".omx/project-memory.json"}`);
2313
- if (techStack) summary.push(`- stack: ${techStack}`);
2314
- if (conventions) summary.push(`- conventions: ${conventions}`);
2315
- if (build) summary.push(`- build: ${build}`);
2337
+ if (techStack && !alreadyInStartupInstructions("Stack", techStack)) summary.push(`- stack: ${techStack}`);
2338
+ if (conventions && !alreadyInStartupInstructions("Conventions", conventions)) summary.push(`- conventions: ${conventions}`);
2339
+ if (build && !alreadyInStartupInstructions("Build", build)) summary.push(`- build: ${build}`);
2316
2340
  if (directives.length > 0) {
2317
2341
  const firstDirective = directives[0] as Record<string, unknown>;
2318
2342
  const directive = safeContextSnippet(firstDirective.directive);
2319
- if (directive) summary.push(`- directive: ${directive}`);
2343
+ if (directive && !alreadyInStartupInstructions("Directive", directive)) summary.push(`- directive: ${directive}`);
2320
2344
  }
2321
2345
  if (notes.length > 0) {
2322
2346
  const firstNote = notes[0] as Record<string, unknown>;
@@ -2344,7 +2368,7 @@ async function buildSessionStartContext(
2344
2368
  .map((line) => line.trim())
2345
2369
  .filter(Boolean)
2346
2370
  .join(" ");
2347
- if (section) {
2371
+ if (section && startupPriority !== section.replace(/\s+/g, " ")) {
2348
2372
  sections.push(`[Priority notes]\n- ${section.slice(0, 220)}`);
2349
2373
  }
2350
2374
  }
@@ -21841,6 +21865,72 @@ export async function dispatchCodexNativeHook(
21841
21865
  const cwd = options.cwd ?? (safeString(payload.cwd).trim() || process.cwd());
21842
21866
  const reconcileAdvisory = options.reconcileRalplanAdvisoryFn ?? reconcileRalplanAdvisory;
21843
21867
  const omxEventName = mapCodexHookEventToOmxEvent(hookEventName);
21868
+ if (hookEventName === "SubagentStop") {
21869
+ // This event names the parent session and child agent separately; it is not a leader Stop.
21870
+ const parentSessionId = readStrictStopPayloadSessionId(payload);
21871
+ const agentId = normalizeSessionId(safeString(payload.agent_id).trim());
21872
+ let sessionId: string | null = null;
21873
+ let pluginDiscoveryCwd: string | undefined;
21874
+ if (parentSessionId && agentId && agentId !== parentSessionId) {
21875
+ const authoritativeTeamWorker = hasRawTeamWorkerDeclaration()
21876
+ ? await resolveAuthoritativeTeamWorkerContext(cwd)
21877
+ : null;
21878
+ if (authoritativeTeamWorker) {
21879
+ // Team workers can own child agents without the shared leader pointer or tracker summary.
21880
+ sessionId = parentSessionId;
21881
+ pluginDiscoveryCwd = authoritativeTeamWorker.canonicalLeaderCwd;
21882
+ } else {
21883
+ const pointer = await readSessionPointer(resolveSessionPointerContext(cwd));
21884
+ if (pointer.status === "usable" && pointer.state && !sessionPointerAliases(pointer.state).has(agentId)) {
21885
+ const candidateSessionId = pointer.state.session_id;
21886
+ const summary = await readSubagentSessionSummary(cwd, candidateSessionId);
21887
+ if (payloadMatchesSessionPointer(parentSessionId, pointer.state)
21888
+ || summary?.allThreadIds.includes(parentSessionId)) {
21889
+ sessionId = candidateSessionId;
21890
+ }
21891
+ }
21892
+ }
21893
+ }
21894
+ if (sessionId && agentId) {
21895
+ try {
21896
+ await recordSubagentTurnForSessionPartitions(cwd, {
21897
+ sessionId,
21898
+ threadId: agentId,
21899
+ turnId: readPayloadTurnId(payload) || undefined,
21900
+ kind: "subagent",
21901
+ mode: safeString(payload.agent_type).trim() || undefined,
21902
+ completed: true,
21903
+ completionSource: "native_stop_event",
21904
+ });
21905
+ } catch (error) {
21906
+ await logNativeHookCliError(cwd, "native_subagent_completion_tracking_error", error, payload, { agent_id: agentId });
21907
+ return { hookEventName, omxEventName, skillState: null, outputJson: null };
21908
+ }
21909
+ if (!shouldSuppressSubagentLifecycleHookDispatch()) {
21910
+ try {
21911
+ await dispatchHookEventRuntime({
21912
+ event: buildNativeHookEvent(omxEventName!, {
21913
+ ...buildBaseContext(cwd, payload, hookEventName, sessionId),
21914
+ native_session_id: agentId,
21915
+ codex_session_id: agentId,
21916
+ omx_session_id: sessionId,
21917
+ }, {
21918
+ session_id: sessionId,
21919
+ thread_id: agentId,
21920
+ turn_id: readPayloadTurnId(payload) || undefined,
21921
+ mode: safeString(payload.agent_type).trim() || undefined,
21922
+ }),
21923
+ cwd,
21924
+ ...(pluginDiscoveryCwd ? { pluginDiscoveryCwd } : {}),
21925
+ allowTeamWorkerSideEffects: false,
21926
+ });
21927
+ } catch (error) {
21928
+ await logNativeHookCliError(cwd, "native_subagent_completion_dispatch_error", error, payload, { agent_id: agentId });
21929
+ }
21930
+ }
21931
+ }
21932
+ return { hookEventName, omxEventName, skillState: null, outputJson: null };
21933
+ }
21844
21934
  // #3497 / C1 boundary: documented-leader PreToolUse hard gate removed.
21845
21935
  if (hookEventName === "PreToolUse" && safeString(payload.tool_name).trim() === "Bash") {
21846
21936
  void payload;
@@ -21992,7 +22082,26 @@ export async function dispatchCodexNativeHook(
21992
22082
  // never promote itself into a root/leader session or emit an independent
21993
22083
  // session-start notification at session/minimal verbosity.
21994
22084
  isSubagentSessionStart = true;
21995
- if (canonicalSessionId) {
22085
+ if (authoritativeTeamWorker && authoritativeWorkerPayloadSessionId === nativeSessionId) {
22086
+ // A Team worker shares the leader pointer but owns its native child tree.
22087
+ // Retain descriptive child records under the transcript's immediate parent
22088
+ // without letting this child inherit the leader's session scope.
22089
+ canonicalSessionId = "";
22090
+ resolvedNativeSessionId = nativeSessionId;
22091
+ skipCanonicalSessionStartContext = true;
22092
+ allowImplicitSessionSideEffects = false;
22093
+ recordedNativeSubagentSessionStart = true;
22094
+ await recordNativeSubagentSessionStart(
22095
+ cwd,
22096
+ subagentSessionStart.parentThreadId,
22097
+ nativeSessionId,
22098
+ subagentSessionStart,
22099
+ transcriptPath,
22100
+ "",
22101
+ "absent",
22102
+ implicatedChildThreadIds,
22103
+ );
22104
+ } else if (canonicalSessionId) {
21996
22105
  const belongsToCanonicalSession = await nativeSubagentSessionStartBelongsToCanonicalSession(
21997
22106
  cwd,
21998
22107
  canonicalSessionId,
@@ -22521,6 +22630,7 @@ export async function dispatchCodexNativeHook(
22521
22630
  && /^[ \t]*omx[ \t]+cancel\b/.test(readPreToolUseCommand(payload));
22522
22631
  if (omxEventName && !bypassGenericPreToolUsePluginDispatch && allowImplicitSessionSideEffects && allowGlobalSideEffects && !skipCanonicalSessionStartContext && !suppressNoisySubagentLifecycleDispatch) {
22523
22632
  const baseContext = buildBaseContext(cwd, payload, hookEventName!, canonicalSessionId);
22633
+ if (isSubagentStop) baseContext.is_subagent_stop = true;
22524
22634
  if (resolvedNativeSessionId) {
22525
22635
  baseContext.native_session_id = resolvedNativeSessionId;
22526
22636
  baseContext.codex_session_id = resolvedNativeSessionId;
@@ -22541,6 +22651,7 @@ export async function dispatchCodexNativeHook(
22541
22651
  await dispatchHookEventRuntime({
22542
22652
  event,
22543
22653
  cwd,
22654
+ pluginDiscoveryCwd: authoritativeTeamWorkerEvidence?.canonicalLeaderCwd,
22544
22655
  allowTeamWorkerSideEffects: false,
22545
22656
  });
22546
22657
  }
@@ -22688,6 +22799,7 @@ export async function dispatchCodexNativeHook(
22688
22799
  await recordSubagentTurnForSession(cwd, {
22689
22800
  sessionId: canonicalSessionId.trim(),
22690
22801
  threadId: threadId.trim(),
22802
+ turnId: readPayloadTurnId(payload) || undefined,
22691
22803
  completed: true,
22692
22804
  completionSource: 'native_stop_event',
22693
22805
  timestamp: new Date().toISOString(),
@@ -22801,8 +22913,12 @@ async function readStdinJson(): Promise<NativeHookCliReadResult> {
22801
22913
  }
22802
22914
  }
22803
22915
 
22804
- function inferHookEventNameFromMalformedInput(raw: string): CodexHookEventName | null {
22805
- const match = raw.match(/(?:\"|['"])?hook[_-]?event[_-]?name(?:\"|['"])?\s*:\s*(?:\"|['"])?(SessionStart|PreToolUse|PostToolUse|UserPromptSubmit|PreCompact|PostCompact|Stop)\b/i);
22916
+ function inferHookEventNameFromMalformedInput(
22917
+ raw: string,
22918
+ rawHookEventName?: CodexHookEventName | null,
22919
+ ): CodexHookEventName | null {
22920
+ if (rawHookEventName) return rawHookEventName;
22921
+ const match = raw.match(/(?:\"|['"])?hook[_-]?event[_-]?name(?:\"|['"])?\s*:\s*(?:\"|['"])?(SessionStart|PreToolUse|PostToolUse|UserPromptSubmit|PreCompact|PostCompact|SubagentStop|Stop)\b/i);
22806
22922
  const value = match?.[1];
22807
22923
  if (!value) return null;
22808
22924
  return readHookEventName({ hook_event_name: value });
@@ -22823,15 +22939,17 @@ function buildMalformedStdinHookOutput(
22823
22939
  parseError: Error,
22824
22940
  rawInput: string,
22825
22941
  cwd = process.cwd(),
22942
+ rawHookEventName?: CodexHookEventName | null,
22826
22943
  ): Record<string, unknown> {
22827
22944
  const reason =
22828
22945
  "OMX native hook received malformed JSON input. Preserve runtime state, inspect the emitting hook payload yourself, and retry with valid JSON.";
22829
22946
  const systemMessage =
22830
22947
  `${reason} stdin JSON parsing failed inside codex-native-hook: ${parseError.message}.`;
22831
- const inferredHookEventName = inferHookEventNameFromMalformedInput(rawInput);
22948
+ const inferredHookEventName = inferHookEventNameFromMalformedInput(rawInput, rawHookEventName);
22832
22949
  if (inferredHookEventName === "PreToolUse") {
22833
22950
  return buildUnparseablePreToolUseDenyOutput(reason, systemMessage);
22834
22951
  }
22952
+ if (inferredHookEventName === "SubagentStop") return {};
22835
22953
  if (inferredHookEventName === "Stop" || (!inferredHookEventName && hasNativeStopRuntimeSurface(cwd))) {
22836
22954
  return {
22837
22955
  decision: "block",
@@ -22880,6 +22998,7 @@ async function buildOversizedStdinHookOutput(
22880
22998
  ): Promise<Record<string, unknown>> {
22881
22999
  const systemMessage =
22882
23000
  `OMX native hook rejected oversized stdin JSON before parsing; maxBytes=${MAX_NATIVE_STDIN_JSON_BYTES}.`;
23001
+ if (rawHookEventName === "SubagentStop") return {};
22883
23002
  if (rawHookEventName === "PreToolUse") {
22884
23003
  return buildUnparseablePreToolUseDenyOutput(systemMessage, systemMessage);
22885
23004
  }
@@ -22993,7 +23112,7 @@ export async function runCodexNativeHookCli(): Promise<void> {
22993
23112
  {},
22994
23113
  buildRawInputLogFields(rawInput),
22995
23114
  );
22996
- writeNativeHookJsonStdout(buildMalformedStdinHookOutput(parseError, rawInput, process.cwd()));
23115
+ writeNativeHookJsonStdout(buildMalformedStdinHookOutput(parseError, rawInput, process.cwd(), rawHookEventName));
22997
23116
  return;
22998
23117
  }
22999
23118
 
@@ -9,6 +9,7 @@ export const CODEX_HOOK_EVENT_NAMES = [
9
9
  "UserPromptSubmit",
10
10
  "PreCompact",
11
11
  "PostCompact",
12
+ "SubagentStop",
12
13
  "Stop",
13
14
  ] as const;
14
15
 
@@ -53,6 +53,7 @@ export const MANAGED_CODEX_HOOK_EVENTS = [
53
53
  'UserPromptSubmit',
54
54
  'PreCompact',
55
55
  'PostCompact',
56
+ 'SubagentStop',
56
57
  'Stop',
57
58
  ] as const;
58
59
 
@@ -2328,6 +2329,16 @@ export function buildNativeHookSmokePayload(
2328
2329
  transcript_path: join(smokeCwd, 'nonexistent-transcript.jsonl'),
2329
2330
  prompt: 'packed install native hook smoke test',
2330
2331
  };
2332
+ case 'SubagentStop':
2333
+ return {
2334
+ ...base,
2335
+ agent_id: 'packed-install-smoke-child',
2336
+ agent_type: 'executor',
2337
+ turn_id: 'packed-install-smoke-child-turn',
2338
+ agent_transcript_path: null,
2339
+ last_assistant_message: null,
2340
+ stop_hook_active: false,
2341
+ };
2331
2342
  case 'PreCompact':
2332
2343
  case 'PostCompact':
2333
2344
  case 'Stop':
@@ -5102,7 +5113,7 @@ export async function smokePackedHookTrustLifecycle(
5102
5113
  const initialHooksContent = readFileSync(hooksPath, 'utf-8');
5103
5114
  assertForeignHookGroupsPreserved(preSetupForeignSnapshot, initialHooksContent, marker);
5104
5115
  if (rawManagedHookCount(initialHooksContent) !== MANAGED_CODEX_HOOK_EVENTS.length) {
5105
- throw new Error('packed setup did not install exactly seven managed native hooks');
5116
+ throw new Error(`packed setup did not install exactly ${MANAGED_CODEX_HOOK_EVENTS.length} managed native hooks`);
5106
5117
  }
5107
5118
  assertManagedHooksAppendAfterForeign(initialHooksContent, marker);
5108
5119
  const generatedTrust = generatedHookTrustState(readFileSync(configPath, 'utf-8'));