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.
- package/Cargo.lock +6 -6
- package/Cargo.toml +1 -1
- package/README.md +17 -0
- package/dist/adapt/herdr/__tests__/wiring.test.js +21 -0
- package/dist/adapt/herdr/__tests__/wiring.test.js.map +1 -1
- package/dist/adapt/herdr/wiring.d.ts.map +1 -1
- package/dist/adapt/herdr/wiring.js +5 -0
- package/dist/adapt/herdr/wiring.js.map +1 -1
- package/dist/auth/__tests__/hotswap.test.js +28 -0
- package/dist/auth/__tests__/hotswap.test.js.map +1 -1
- package/dist/auth/hotswap.d.ts +1 -0
- package/dist/auth/hotswap.d.ts.map +1 -1
- package/dist/auth/hotswap.js +4 -1
- package/dist/auth/hotswap.js.map +1 -1
- package/dist/cli/__tests__/codex-plugin-layout.test.js +196 -0
- package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +2 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +14 -1
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/session-timeline.test.d.ts +2 -0
- package/dist/cli/__tests__/session-timeline.test.d.ts.map +1 -0
- package/dist/cli/__tests__/session-timeline.test.js +155 -0
- package/dist/cli/__tests__/session-timeline.test.js.map +1 -0
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +1 -0
- package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +1 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +11 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/session-search.d.ts.map +1 -1
- package/dist/cli/session-search.js +7 -0
- package/dist/cli/session-search.js.map +1 -1
- package/dist/cli/session-timeline.d.ts +9 -0
- package/dist/cli/session-timeline.d.ts.map +1 -0
- package/dist/cli/session-timeline.js +174 -0
- package/dist/cli/session-timeline.js.map +1 -0
- package/dist/config/__tests__/codex-hooks.test.js +3 -1
- package/dist/config/__tests__/codex-hooks.test.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +1 -0
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/codex-hooks.d.ts +1 -1
- package/dist/config/codex-hooks.d.ts.map +1 -1
- package/dist/config/codex-hooks.js +5 -0
- package/dist/config/codex-hooks.js.map +1 -1
- package/dist/evals/astra-defaults/__tests__/collector.test.js +51 -23
- package/dist/evals/astra-defaults/__tests__/collector.test.js.map +1 -1
- package/dist/evals/astra-defaults/collector.d.ts +2 -0
- package/dist/evals/astra-defaults/collector.d.ts.map +1 -1
- package/dist/evals/astra-defaults/collector.js +19 -8
- package/dist/evals/astra-defaults/collector.js.map +1 -1
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js +143 -0
- package/dist/hooks/extensibility/__tests__/dispatcher.test.js.map +1 -1
- package/dist/hooks/extensibility/dispatcher.d.ts.map +1 -1
- package/dist/hooks/extensibility/dispatcher.js +120 -28
- package/dist/hooks/extensibility/dispatcher.js.map +1 -1
- package/dist/hooks/extensibility/runtime.d.ts.map +1 -1
- package/dist/hooks/extensibility/runtime.js +1 -0
- package/dist/hooks/extensibility/runtime.js.map +1 -1
- package/dist/hooks/extensibility/types.d.ts +2 -0
- package/dist/hooks/extensibility/types.d.ts.map +1 -1
- package/dist/hooks/native/types.d.ts +1 -1
- package/dist/hooks/native/types.d.ts.map +1 -1
- package/dist/hooks/native/types.js.map +1 -1
- package/dist/notifications/__tests__/lifecycle-dedupe.test.js +200 -2
- package/dist/notifications/__tests__/lifecycle-dedupe.test.js.map +1 -1
- package/dist/notifications/lifecycle-dedupe.d.ts +10 -0
- package/dist/notifications/lifecycle-dedupe.d.ts.map +1 -1
- package/dist/notifications/lifecycle-dedupe.js +175 -19
- package/dist/notifications/lifecycle-dedupe.js.map +1 -1
- package/dist/scripts/__tests__/codex-native-hook.test.js +278 -6
- package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
- package/dist/scripts/__tests__/native-hook-protocol.test.d.ts +2 -0
- package/dist/scripts/__tests__/native-hook-protocol.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/native-hook-protocol.test.js +329 -0
- package/dist/scripts/__tests__/native-hook-protocol.test.js.map +1 -0
- package/dist/scripts/__tests__/smoke-packed-install.test.js +7 -6
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/codex-execution-surface.d.ts +1 -1
- package/dist/scripts/codex-execution-surface.d.ts.map +1 -1
- package/dist/scripts/codex-execution-surface.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts +1 -1
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +124 -13
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/hook-payload-guard.d.ts +1 -1
- package/dist/scripts/hook-payload-guard.d.ts.map +1 -1
- package/dist/scripts/hook-payload-guard.js +1 -0
- package/dist/scripts/hook-payload-guard.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +2 -2
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +12 -1
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/session-history/__tests__/timeline.test.d.ts +2 -0
- package/dist/session-history/__tests__/timeline.test.d.ts.map +1 -0
- package/dist/session-history/__tests__/timeline.test.js +459 -0
- package/dist/session-history/__tests__/timeline.test.js.map +1 -0
- package/dist/session-history/export.d.ts +2 -3
- package/dist/session-history/export.d.ts.map +1 -1
- package/dist/session-history/export.js +5 -66
- package/dist/session-history/export.js.map +1 -1
- package/dist/session-history/timeline.d.ts +97 -0
- package/dist/session-history/timeline.d.ts.map +1 -0
- package/dist/session-history/timeline.js +325 -0
- package/dist/session-history/timeline.js.map +1 -0
- package/dist/session-history/transcript.d.ts +16 -0
- package/dist/session-history/transcript.d.ts.map +1 -0
- package/dist/session-history/transcript.js +111 -0
- package/dist/session-history/transcript.js.map +1 -0
- package/dist/subagents/tracker.d.ts +6 -0
- package/dist/subagents/tracker.d.ts.map +1 -1
- package/dist/subagents/tracker.js +32 -0
- package/dist/subagents/tracker.js.map +1 -1
- package/dist/team/__tests__/runtime.test.js +12 -3
- package/dist/team/__tests__/runtime.test.js.map +1 -1
- package/dist/team/__tests__/scaling.test.js +25 -0
- package/dist/team/__tests__/scaling.test.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +30 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +9 -1
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/wiki/__tests__/session-hooks.test.js +26 -0
- package/dist/wiki/__tests__/session-hooks.test.js.map +1 -1
- package/dist/wiki/lifecycle.d.ts.map +1 -1
- package/dist/wiki/lifecycle.js +6 -1
- package/dist/wiki/lifecycle.js.map +1 -1
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +85 -18
- package/plugins/oh-my-codex/hooks/hooks.json +10 -0
- package/src/scripts/__tests__/codex-native-hook.test.ts +273 -5
- package/src/scripts/__tests__/native-hook-protocol.test.ts +362 -0
- package/src/scripts/__tests__/smoke-packed-install.test.ts +7 -6
- package/src/scripts/codex-execution-surface.ts +1 -0
- package/src/scripts/codex-native-hook.ts +130 -11
- package/src/scripts/hook-payload-guard.ts +1 -0
- 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:
|
|
490
|
-
worktree_path:
|
|
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:
|
|
501
|
-
worktree_path:
|
|
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 {
|