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
|
@@ -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 (
|
|
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(
|
|
22805
|
-
|
|
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
|
|
|
@@ -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(
|
|
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'));
|