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
@@ -66,9 +66,10 @@ import {
66
66
  readUltragoalPlan,
67
67
  } from "../../ultragoal/artifacts.js";
68
68
  import { getBaseStateDir } from "../../state/paths.js";
69
- import { recordNativeSubagentAuthorityObservation } from "../../subagents/tracker.js";
69
+ import { readSubagentTrackingState, recordNativeSubagentAuthorityObservation } from "../../subagents/tracker.js";
70
70
  import { maybeNudgeLeaderForAllowedWorkerStop } from "../notify-hook/team-worker-stop.js";
71
71
  import { MAX_NATIVE_STDIN_JSON_BYTES } from "../hook-payload-guard.js";
72
+ import { generateOverlay, writeSessionModelInstructionsFile } from "../../hooks/agents-overlay.js";
72
73
  import {
73
74
  injectExecFollowup,
74
75
  markExecFollowupsDelivered,
@@ -466,6 +467,7 @@ async function configureAuthoritativeTeamWorker(
466
467
  cwd: string,
467
468
  teamName: string,
468
469
  workerPaneId = "%10",
470
+ workerCwd = cwd,
469
471
  ): Promise<void> {
470
472
  const stateRoot = join(cwd, ".omx", "state");
471
473
  await initTeamState(teamName, "session pointer ownership regression", "executor", 1, cwd, undefined, {
@@ -486,8 +488,8 @@ async function configureAuthoritativeTeamWorker(
486
488
  state.leader_cwd = cwd;
487
489
  state.workers = (state.workers ?? []).map((worker) => ({
488
490
  ...worker,
489
- working_dir: cwd,
490
- worktree_path: cwd,
491
+ working_dir: workerCwd,
492
+ worktree_path: workerCwd,
491
493
  team_state_root: stateRoot,
492
494
  }));
493
495
  await writeJson(filePath, state);
@@ -497,8 +499,8 @@ async function configureAuthoritativeTeamWorker(
497
499
  index: 1,
498
500
  role: "executor",
499
501
  pane_id: workerPaneId,
500
- working_dir: cwd,
501
- worktree_path: cwd,
502
+ working_dir: workerCwd,
503
+ worktree_path: workerCwd,
502
504
  team_state_root: stateRoot,
503
505
  });
504
506
  process.env.TMUX = "1";
@@ -841,6 +843,7 @@ describe("codex native hook config", () => {
841
843
  "UserPromptSubmit",
842
844
  "PreCompact",
843
845
  "PostCompact",
846
+ "SubagentStop",
844
847
  "Stop",
845
848
  ]);
846
849
 
@@ -5197,6 +5200,213 @@ describe("codex native hook dispatch", { concurrency: false }, () => {
5197
5200
  }
5198
5201
  });
5199
5202
 
5203
+ it("discovers leader plugins for authoritative Team worker events in an isolated worktree", async () => {
5204
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-worker-subagent-stop-"));
5205
+ const workerCwd = realpathSync(await mkdtemp(join(tmpdir(), "omx-native-hook-team-worker-worktree-")));
5206
+ const originalCodexHome = process.env.CODEX_HOME;
5207
+ try {
5208
+ await writeSessionStart(cwd, "leader-session", {
5209
+ nativeSessionId: "leader-native",
5210
+ pid: process.pid,
5211
+ });
5212
+ await configureAuthoritativeTeamWorker(cwd, "team-worker-subagent-stop", "%10", workerCwd);
5213
+ const stateDir = join(cwd, ".omx", "state");
5214
+ const leaderPointerPath = join(stateDir, "session.json");
5215
+ const leaderPointerBefore = await readFile(leaderPointerPath, "utf-8");
5216
+ const codexHome = join(cwd, "codex-home");
5217
+ process.env.CODEX_HOME = codexHome;
5218
+ await mkdir(codexHome, { recursive: true });
5219
+ await writeJson(join(codexHome, ".omx-config.json"), {
5220
+ notifications: { enabled: true, includeChildAgents: true },
5221
+ });
5222
+ const eventsPath = join(cwd, "child-events.jsonl");
5223
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
5224
+ await writeFile(join(cwd, ".omx", "hooks", "capture.mjs"), `import { appendFileSync } from 'node:fs';
5225
+ export function onHookEvent(event) {
5226
+ appendFileSync(${JSON.stringify(eventsPath)}, JSON.stringify({ event, pluginCwd: process.cwd(), teamStateRoot: process.env.OMX_TEAM_STATE_ROOT }) + '\\n');
5227
+ }
5228
+ `);
5229
+
5230
+ const parentSessionId = "team-worker-native-session";
5231
+ const childThreadId = "team-worker-native-child";
5232
+ const turnId = "team-worker-child-turn";
5233
+ await dispatchCodexNativeHook({
5234
+ hook_event_name: "SubagentStop",
5235
+ cwd: workerCwd,
5236
+ session_id: parentSessionId,
5237
+ agent_id: childThreadId,
5238
+ agent_type: "executor",
5239
+ turn_id: turnId,
5240
+ }, { cwd: workerCwd });
5241
+
5242
+ const tracking = await readSubagentTrackingState(workerCwd);
5243
+ const child = tracking.sessions[parentSessionId]?.threads[childThreadId];
5244
+ assert.equal(child?.completion_source, "native_stop_event");
5245
+ assert.equal(child?.last_completed_turn_id, turnId);
5246
+ assert.equal(child?.status, "closed");
5247
+ const events = (await readFile(eventsPath, "utf-8")).trim().split("\n").map((line) => JSON.parse(line));
5248
+ assert.deepEqual(events.map(({ event, pluginCwd, teamStateRoot }) => ({
5249
+ event: [event.event, event.session_id, event.thread_id, event.turn_id],
5250
+ pluginCwd,
5251
+ teamStateRoot,
5252
+ })), [
5253
+ {
5254
+ event: ["subagent-stop", parentSessionId, childThreadId, turnId],
5255
+ pluginCwd: workerCwd,
5256
+ teamStateRoot: stateDir,
5257
+ },
5258
+ ]);
5259
+ await dispatchCodexNativeHook({
5260
+ hook_event_name: "SessionStart",
5261
+ cwd: workerCwd,
5262
+ session_id: parentSessionId,
5263
+ }, { cwd: workerCwd, sessionOwnerPid: process.pid });
5264
+ const workerEvents = (await readFile(eventsPath, "utf-8")).trim().split("\n").map((line) => JSON.parse(line)).slice(1);
5265
+ assert.deepEqual(workerEvents.map(({ event, pluginCwd, teamStateRoot }) => ({
5266
+ event: event.event,
5267
+ pluginCwd,
5268
+ teamStateRoot,
5269
+ })), [{
5270
+ event: "session-start",
5271
+ pluginCwd: workerCwd,
5272
+ teamStateRoot: stateDir,
5273
+ }]);
5274
+ assert.equal(await readFile(leaderPointerPath, "utf-8"), leaderPointerBefore);
5275
+ } finally {
5276
+ if (originalCodexHome === undefined) delete process.env.CODEX_HOME;
5277
+ else process.env.CODEX_HOME = originalCodexHome;
5278
+ await rm(workerCwd, { recursive: true, force: true });
5279
+ await rm(cwd, { recursive: true, force: true });
5280
+ }
5281
+ });
5282
+
5283
+ it("tracks native child SessionStart events under verified Team worker parents", async () => {
5284
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-worker-child-start-"));
5285
+ const workerCwd = realpathSync(await mkdtemp(join(tmpdir(), "omx-native-hook-team-worker-child-worktree-")));
5286
+ const originalCodexHome = process.env.CODEX_HOME;
5287
+ try {
5288
+ await writeSessionStart(cwd, "leader-session", {
5289
+ nativeSessionId: "leader-native",
5290
+ pid: process.pid,
5291
+ });
5292
+ await configureAuthoritativeTeamWorker(cwd, "team-worker-child-start", "%10", workerCwd);
5293
+ const stateDir = join(cwd, ".omx", "state");
5294
+ const pointerPath = join(stateDir, "session.json");
5295
+ const pointerBefore = await readFile(pointerPath, "utf-8");
5296
+ const codexHome = join(cwd, "codex-home");
5297
+ process.env.CODEX_HOME = codexHome;
5298
+ await writeJson(join(codexHome, ".omx-config.json"), {
5299
+ notifications: { enabled: true, includeChildAgents: true },
5300
+ });
5301
+ const eventsPath = join(cwd, "team-worker-child-events.jsonl");
5302
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
5303
+ await writeFile(join(cwd, ".omx", "hooks", "capture.mjs"), `import { appendFileSync } from 'node:fs';
5304
+ export function onHookEvent(event) {
5305
+ appendFileSync(${JSON.stringify(eventsPath)}, JSON.stringify({ event: event.event, session_id: event.session_id, omx_session_id: event.omx_session_id }) + '\\n');
5306
+ }
5307
+ `);
5308
+
5309
+ const workerSessionId = "team-worker-native-session";
5310
+ const childSessionId = "team-worker-child-session";
5311
+ const grandchildSessionId = "team-worker-grandchild-session";
5312
+ for (const [sessionId, parentThreadId] of [
5313
+ [childSessionId, workerSessionId],
5314
+ [grandchildSessionId, childSessionId],
5315
+ ]) {
5316
+ const transcriptPath = join(workerCwd, `${sessionId}.jsonl`);
5317
+ await writeFile(transcriptPath, `${JSON.stringify({
5318
+ type: "session_meta",
5319
+ payload: {
5320
+ id: sessionId,
5321
+ source: { subagent: { thread_spawn: { parent_thread_id: parentThreadId } } },
5322
+ },
5323
+ })}\n`);
5324
+ await dispatchCodexNativeHook({
5325
+ hook_event_name: "SessionStart",
5326
+ cwd: workerCwd,
5327
+ session_id: sessionId,
5328
+ transcript_path: transcriptPath,
5329
+ }, { cwd: workerCwd, sessionOwnerPid: process.pid });
5330
+ }
5331
+
5332
+ assert.equal(existsSync(eventsPath), false, "Team worker child starts must not dispatch in the leader session scope");
5333
+ const trackingAfterStarts = await readSubagentTrackingState(workerCwd);
5334
+ assert.equal(trackingAfterStarts.sessions[workerSessionId]?.threads[childSessionId]?.kind, "subagent");
5335
+ assert.equal(trackingAfterStarts.sessions[childSessionId]?.threads[grandchildSessionId]?.kind, "subagent");
5336
+
5337
+ const leafSessionId = "team-worker-leaf-session";
5338
+ const turnId = "team-worker-leaf-turn";
5339
+ await dispatchCodexNativeHook({
5340
+ hook_event_name: "SubagentStop",
5341
+ cwd: workerCwd,
5342
+ session_id: grandchildSessionId,
5343
+ agent_id: leafSessionId,
5344
+ turn_id: turnId,
5345
+ }, { cwd: workerCwd });
5346
+ const trackingAfterStop = await readSubagentTrackingState(workerCwd);
5347
+ const leaf = trackingAfterStop.sessions[grandchildSessionId]?.threads[leafSessionId];
5348
+ assert.equal(leaf?.completion_source, "native_stop_event");
5349
+ assert.equal(leaf?.last_completed_turn_id, turnId);
5350
+ assert.equal(leaf?.status, "closed");
5351
+ assert.equal(await readFile(pointerPath, "utf-8"), pointerBefore);
5352
+ } finally {
5353
+ if (originalCodexHome === undefined) delete process.env.CODEX_HOME;
5354
+ else process.env.CODEX_HOME = originalCodexHome;
5355
+ await rm(workerCwd, { recursive: true, force: true });
5356
+ await rm(cwd, { recursive: true, force: true });
5357
+ }
5358
+ });
5359
+
5360
+ it("keeps native SubagentStop best-effort when opted-in hook dispatch fails", async () => {
5361
+ const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-subagent-dispatch-failure-"));
5362
+ const originalCodexHome = process.env.CODEX_HOME;
5363
+ try {
5364
+ const codexHome = join(cwd, "codex-home");
5365
+ process.env.CODEX_HOME = codexHome;
5366
+ await writeJson(join(codexHome, ".omx-config.json"), {
5367
+ notifications: { enabled: true, includeChildAgents: true },
5368
+ });
5369
+ const parentSessionId = "dispatch-failure-parent";
5370
+ const childSessionId = "dispatch-failure-child";
5371
+ await writeSessionStart(cwd, "dispatch-failure-session", {
5372
+ nativeSessionId: parentSessionId,
5373
+ pid: process.pid,
5374
+ });
5375
+ const eventsPath = join(cwd, "subagent-events.jsonl");
5376
+ await mkdir(join(cwd, ".omx", "hooks"), { recursive: true });
5377
+ await writeFile(join(cwd, ".omx", "hooks", "capture.mjs"), `import { appendFileSync } from 'node:fs';
5378
+ export function onHookEvent(event) {
5379
+ appendFileSync(${JSON.stringify(eventsPath)}, JSON.stringify({ event: event.event, thread_id: event.thread_id }) + '\\n');
5380
+ }
5381
+ `);
5382
+ const logsPath = join(cwd, ".omx", "logs");
5383
+ await rm(logsPath, { recursive: true, force: true });
5384
+ await writeFile(logsPath, "not a directory");
5385
+ const payload = {
5386
+ hook_event_name: "SubagentStop",
5387
+ cwd,
5388
+ session_id: parentSessionId,
5389
+ agent_id: childSessionId,
5390
+ turn_id: "dispatch-failure-turn",
5391
+ };
5392
+
5393
+ for (let attempt = 0; attempt < 2; attempt += 1) {
5394
+ const result = runNativeHookCliResult(payload, { cwd, env: process.env });
5395
+ assert.equal(result.status, 0, result.stderr || result.stdout);
5396
+ assert.deepEqual(parseSingleJsonStdout(result.stdout), {});
5397
+ }
5398
+
5399
+ const events = (await readFile(eventsPath, "utf-8")).trim().split("\n").map((line) => JSON.parse(line));
5400
+ assert.deepEqual(events, [{ event: "subagent-stop", thread_id: childSessionId }]);
5401
+ const tracking = await readSubagentTrackingState(cwd);
5402
+ assert.equal(tracking.sessions["dispatch-failure-session"]?.threads[childSessionId]?.completion_source, "native_stop_event");
5403
+ } finally {
5404
+ if (originalCodexHome === undefined) delete process.env.CODEX_HOME;
5405
+ else process.env.CODEX_HOME = originalCodexHome;
5406
+ await rm(cwd, { recursive: true, force: true });
5407
+ }
5408
+ });
5409
+
5200
5410
  it("rejects leader aliases from foreign-cwd pointer evidence in a worker worktree", async () => {
5201
5411
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-team-worker-foreign-alias-"));
5202
5412
  try {
@@ -6339,6 +6549,64 @@ describe("codex native hook dispatch", { concurrency: false }, () => {
6339
6549
  }
6340
6550
  });
6341
6551
 
6552
+ for (const scenario of ["startup", "direct-codex", "resume", "unknown-source", "missing-file", "foreign-session", "omitted-sections", "changed-memory", "shortened-memory"] as const) {
6553
+ it(`preserves startup memory delivery for ${scenario}`, async () => {
6554
+ const cwd = await mkdtemp(join(tmpdir(), "omx-startup-context-"));
6555
+ const previous = process.env.OMX_STARTUP_MODEL_INSTRUCTIONS_FILE;
6556
+ const sessionId = "sess-startup-context";
6557
+ const stack = "UNIQUE_STACK_MARKER TypeScript";
6558
+ const priority = "UNIQUE_PRIORITY_MARKER Save a checkpoint. Keep review evidence.";
6559
+ try {
6560
+ await writeJson(join(cwd, ".omx", "project-memory.json"), {
6561
+ techStack: stack,
6562
+ build: "UNIQUE_BUILD_MARKER npm test",
6563
+ conventions: "UNIQUE_CONVENTIONS_MARKER small diffs",
6564
+ directives: [{ directive: "UNIQUE_DIRECTIVE_MARKER preserve evidence", priority: "high" }],
6565
+ notes: [{ content: "HOOK_ONLY_MEMORY_NOTE" }],
6566
+ });
6567
+ await writeFile(join(cwd, ".omx", "notepad.md"), `## PRIORITY\n${priority.replace(" Keep", "\nKeep")}\n`);
6568
+ const overlay = scenario === "omitted-sections" ? "Runtime instructions without memory." : await generateOverlay(cwd, sessionId);
6569
+ const instructionsPath = await writeSessionModelInstructionsFile(cwd, sessionId, overlay);
6570
+ process.env.OMX_STARTUP_MODEL_INSTRUCTIONS_FILE = scenario === "foreign-session"
6571
+ ? await writeSessionModelInstructionsFile(cwd, "another-session", overlay)
6572
+ : instructionsPath;
6573
+ if (scenario === "direct-codex") delete process.env.OMX_STARTUP_MODEL_INSTRUCTIONS_FILE;
6574
+ if (scenario === "missing-file") await rm(instructionsPath);
6575
+ if (scenario === "changed-memory") {
6576
+ await writeJson(join(cwd, ".omx", "project-memory.json"), { techStack: "UPDATED_STACK_MARKER" });
6577
+ await writeFile(join(cwd, ".omx", "notepad.md"), "## PRIORITY\nUPDATED_PRIORITY_MARKER\n");
6578
+ }
6579
+ if (scenario === "shortened-memory") {
6580
+ await writeJson(join(cwd, ".omx", "project-memory.json"), { techStack: "UNIQUE_STACK_MARKER" });
6581
+ await writeFile(join(cwd, ".omx", "notepad.md"), "## PRIORITY\nUNIQUE_PRIORITY_MARKER\n");
6582
+ }
6583
+ const result = await dispatchCodexNativeHook({
6584
+ hook_event_name: "SessionStart", cwd, session_id: sessionId,
6585
+ ...(scenario === "unknown-source" ? {} : { source: scenario === "resume" ? "resume" : "startup" }),
6586
+ }, { cwd, sessionOwnerPid: process.pid });
6587
+ const context = JSON.stringify(result.outputJson);
6588
+ if (scenario === "changed-memory") {
6589
+ assert.match(context, /UPDATED_STACK_MARKER/);
6590
+ assert.match(context, /UPDATED_PRIORITY_MARKER/);
6591
+ } else if (scenario === "shortened-memory") {
6592
+ assert.match(context, /UNIQUE_STACK_MARKER/);
6593
+ assert.match(context, /UNIQUE_PRIORITY_MARKER/);
6594
+ } else {
6595
+ assert.equal(context.includes(stack), scenario !== "startup");
6596
+ assert.equal(context.includes(priority), scenario !== "startup");
6597
+ assert.equal(context.includes("UNIQUE_BUILD_MARKER"), scenario !== "startup");
6598
+ assert.equal(context.includes("UNIQUE_CONVENTIONS_MARKER"), scenario !== "startup");
6599
+ assert.equal(context.includes("UNIQUE_DIRECTIVE_MARKER"), scenario !== "startup");
6600
+ assert.match(context, /HOOK_ONLY_MEMORY_NOTE/);
6601
+ }
6602
+ } finally {
6603
+ if (previous === undefined) delete process.env.OMX_STARTUP_MODEL_INSTRUCTIONS_FILE;
6604
+ else process.env.OMX_STARTUP_MODEL_INSTRUCTIONS_FILE = previous;
6605
+ await rm(cwd, { recursive: true, force: true });
6606
+ }
6607
+ });
6608
+ }
6609
+
6342
6610
  it("includes persisted project-memory summary in SessionStart context", async () => {
6343
6611
  const cwd = await mkdtemp(join(tmpdir(), "omx-native-hook-session-memory-"));
6344
6612
  try {