oh-my-codex 0.14.2 → 0.14.4

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 (144) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.md +1 -1
  4. package/crates/omx-explore/src/main.rs +2 -2
  5. package/dist/agents/__tests__/definitions.test.js +1 -1
  6. package/dist/agents/__tests__/definitions.test.js.map +1 -1
  7. package/dist/agents/__tests__/native-config.test.js +4 -4
  8. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  9. package/dist/agents/definitions.js +1 -1
  10. package/dist/agents/definitions.js.map +1 -1
  11. package/dist/cli/__tests__/agents.test.js +5 -5
  12. package/dist/cli/__tests__/cleanup.test.js +27 -0
  13. package/dist/cli/__tests__/cleanup.test.js.map +1 -1
  14. package/dist/cli/__tests__/doctor-invalid-config.test.js +1 -1
  15. package/dist/cli/__tests__/explore.test.js +62 -5
  16. package/dist/cli/__tests__/explore.test.js.map +1 -1
  17. package/dist/cli/__tests__/index.test.js +63 -0
  18. package/dist/cli/__tests__/index.test.js.map +1 -1
  19. package/dist/cli/__tests__/question.test.js +146 -1
  20. package/dist/cli/__tests__/question.test.js.map +1 -1
  21. package/dist/cli/__tests__/setup-agents-overwrite.test.js +1 -1
  22. package/dist/cli/__tests__/setup-agents-overwrite.test.js.map +1 -1
  23. package/dist/cli/__tests__/setup-refresh.test.js +6 -6
  24. package/dist/cli/__tests__/uninstall.test.js +8 -8
  25. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  26. package/dist/cli/agents.js +1 -1
  27. package/dist/cli/cleanup.d.ts.map +1 -1
  28. package/dist/cli/cleanup.js +18 -2
  29. package/dist/cli/cleanup.js.map +1 -1
  30. package/dist/cli/codex-home.d.ts +8 -0
  31. package/dist/cli/codex-home.d.ts.map +1 -0
  32. package/dist/cli/codex-home.js +54 -0
  33. package/dist/cli/codex-home.js.map +1 -0
  34. package/dist/cli/explore.d.ts +1 -0
  35. package/dist/cli/explore.d.ts.map +1 -1
  36. package/dist/cli/explore.js +15 -6
  37. package/dist/cli/explore.js.map +1 -1
  38. package/dist/cli/index.d.ts +1 -6
  39. package/dist/cli/index.d.ts.map +1 -1
  40. package/dist/cli/index.js +2 -49
  41. package/dist/cli/index.js.map +1 -1
  42. package/dist/config/__tests__/generator-idempotent.test.js +56 -12
  43. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  44. package/dist/config/__tests__/generator-notify.test.js +12 -12
  45. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  46. package/dist/config/generator.d.ts.map +1 -1
  47. package/dist/config/generator.js +60 -22
  48. package/dist/config/generator.js.map +1 -1
  49. package/dist/config/models.d.ts +1 -1
  50. package/dist/config/models.js +1 -1
  51. package/dist/hooks/__tests__/clawhip-event-contract.test.js +7 -0
  52. package/dist/hooks/__tests__/clawhip-event-contract.test.js.map +1 -1
  53. package/dist/hooks/__tests__/deep-interview-contract.test.js +17 -0
  54. package/dist/hooks/__tests__/deep-interview-contract.test.js.map +1 -1
  55. package/dist/hooks/__tests__/keyword-detector.test.js +87 -0
  56. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  57. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +1 -1
  58. package/dist/hooks/__tests__/skill-guidance-contract.test.js +8 -1
  59. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  60. package/dist/hooks/__tests__/tmux-hook-engine.test.js +2 -2
  61. package/dist/hooks/extensibility/__tests__/events.test.js +6 -0
  62. package/dist/hooks/extensibility/__tests__/events.test.js.map +1 -1
  63. package/dist/hooks/extensibility/types.d.ts +1 -1
  64. package/dist/hooks/extensibility/types.d.ts.map +1 -1
  65. package/dist/hooks/keyword-detector.d.ts +3 -0
  66. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  67. package/dist/hooks/keyword-detector.js +11 -4
  68. package/dist/hooks/keyword-detector.js.map +1 -1
  69. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  70. package/dist/hooks/prompt-guidance-contract.js +31 -15
  71. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  72. package/dist/hud/__tests__/reconcile.test.js +22 -0
  73. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  74. package/dist/hud/reconcile.d.ts +2 -1
  75. package/dist/hud/reconcile.d.ts.map +1 -1
  76. package/dist/hud/reconcile.js +3 -2
  77. package/dist/hud/reconcile.js.map +1 -1
  78. package/dist/hud/tmux.d.ts +3 -2
  79. package/dist/hud/tmux.d.ts.map +1 -1
  80. package/dist/hud/tmux.js +11 -4
  81. package/dist/hud/tmux.js.map +1 -1
  82. package/dist/question/__tests__/deep-interview.test.js +58 -1
  83. package/dist/question/__tests__/deep-interview.test.js.map +1 -1
  84. package/dist/question/__tests__/renderer.test.js +282 -24
  85. package/dist/question/__tests__/renderer.test.js.map +1 -1
  86. package/dist/question/__tests__/state.test.js +27 -0
  87. package/dist/question/__tests__/state.test.js.map +1 -1
  88. package/dist/question/__tests__/ui.test.js +129 -0
  89. package/dist/question/__tests__/ui.test.js.map +1 -1
  90. package/dist/question/deep-interview.d.ts +1 -0
  91. package/dist/question/deep-interview.d.ts.map +1 -1
  92. package/dist/question/deep-interview.js +80 -2
  93. package/dist/question/deep-interview.js.map +1 -1
  94. package/dist/question/renderer.d.ts +4 -1
  95. package/dist/question/renderer.d.ts.map +1 -1
  96. package/dist/question/renderer.js +101 -4
  97. package/dist/question/renderer.js.map +1 -1
  98. package/dist/question/state.js +1 -1
  99. package/dist/question/state.js.map +1 -1
  100. package/dist/question/ui.d.ts +3 -1
  101. package/dist/question/ui.d.ts.map +1 -1
  102. package/dist/question/ui.js +14 -6
  103. package/dist/question/ui.js.map +1 -1
  104. package/dist/scripts/__tests__/codex-native-hook.test.js +265 -3
  105. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  106. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  107. package/dist/scripts/codex-native-hook.js +47 -9
  108. package/dist/scripts/codex-native-hook.js.map +1 -1
  109. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  110. package/dist/scripts/codex-native-pre-post.js +47 -0
  111. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  112. package/dist/scripts/notify-hook/__tests__/operational-events.test.d.ts +2 -0
  113. package/dist/scripts/notify-hook/__tests__/operational-events.test.d.ts.map +1 -0
  114. package/dist/scripts/notify-hook/__tests__/operational-events.test.js +24 -0
  115. package/dist/scripts/notify-hook/__tests__/operational-events.test.js.map +1 -0
  116. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  117. package/dist/scripts/notify-hook/team-dispatch.js +9 -0
  118. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  119. package/dist/team/__tests__/events.test.js +25 -0
  120. package/dist/team/__tests__/events.test.js.map +1 -1
  121. package/dist/team/__tests__/model-contract.test.js +3 -3
  122. package/dist/team/__tests__/model-contract.test.js.map +1 -1
  123. package/dist/team/__tests__/runtime.test.js +18 -7
  124. package/dist/team/__tests__/runtime.test.js.map +1 -1
  125. package/dist/team/__tests__/scaling.test.js +1 -1
  126. package/dist/team/__tests__/tmux-session.test.js +296 -5
  127. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  128. package/dist/team/runtime.d.ts.map +1 -1
  129. package/dist/team/runtime.js +30 -0
  130. package/dist/team/runtime.js.map +1 -1
  131. package/dist/team/tmux-session.d.ts.map +1 -1
  132. package/dist/team/tmux-session.js +60 -3
  133. package/dist/team/tmux-session.js.map +1 -1
  134. package/dist/utils/__tests__/agents-model-table.test.js +1 -1
  135. package/dist/utils/__tests__/agents-model-table.test.js.map +1 -1
  136. package/package.json +2 -2
  137. package/skills/deep-interview/SKILL.md +13 -2
  138. package/skills/ultrawork/SKILL.md +91 -59
  139. package/src/scripts/__tests__/codex-native-hook.test.ts +318 -3
  140. package/src/scripts/codex-native-hook.ts +49 -6
  141. package/src/scripts/codex-native-pre-post.ts +45 -0
  142. package/src/scripts/notify-hook/__tests__/operational-events.test.ts +24 -0
  143. package/src/scripts/notify-hook/team-dispatch.ts +9 -0
  144. package/templates/AGENTS.md +1 -1
@@ -62,7 +62,10 @@ import {
62
62
  promptSignature,
63
63
  type TriageStateFile,
64
64
  } from "../hooks/triage-state.js";
65
- import { isPendingDeepInterviewQuestionEnforcement } from "../question/deep-interview.js";
65
+ import {
66
+ isPendingDeepInterviewQuestionEnforcement,
67
+ reconcileDeepInterviewQuestionEnforcementFromAnsweredRecords,
68
+ } from "../question/deep-interview.js";
66
69
  import { resolveOmxCliEntryPath } from "../utils/paths.js";
67
70
 
68
71
  type CodexHookEventName =
@@ -566,16 +569,49 @@ async function buildSessionStartContext(
566
569
  return sections.length > 0 ? sections.join("\n\n") : null;
567
570
  }
568
571
 
569
- function buildDeepInterviewQuestionBridgeInstruction(cwd: string): string {
572
+ function resolveQuestionLeaderPaneHint(cwd: string, payload?: CodexHookPayload): string {
573
+ const payloadSessionId = safeString(payload?.session_id).trim();
574
+ const envSessionId = safeString(process.env.OMX_SESSION_ID || process.env.CODEX_SESSION_ID || process.env.SESSION_ID).trim();
575
+ const sessionId = payloadSessionId || envSessionId;
576
+ const candidatePaths = [
577
+ ...(sessionId ? [getStatePath('deep-interview', cwd, sessionId), getStatePath('ralplan', cwd, sessionId), getStatePath('ralph', cwd, sessionId)] : []),
578
+ getStatePath('deep-interview', cwd),
579
+ getStatePath('ralplan', cwd),
580
+ getStatePath('ralph', cwd),
581
+ ];
582
+
583
+ for (const path of candidatePaths) {
584
+ try {
585
+ if (!existsSync(path)) continue;
586
+ const parsed = JSON.parse(readFileSync(path, 'utf-8')) as Record<string, unknown>;
587
+ const pane = safeString(parsed?.tmux_pane_id).trim();
588
+ if (/^%\d+$/.test(pane)) return pane;
589
+ } catch {
590
+ // best effort only
591
+ }
592
+ }
593
+
594
+ const envPane = safeString(process.env.TMUX_PANE).trim();
595
+ return /^%\d+$/.test(envPane) ? envPane : '';
596
+ }
597
+
598
+ function buildDeepInterviewQuestionBridgeInstruction(cwd: string, payload?: CodexHookPayload): string {
570
599
  const omxBin = resolveOmxCliEntryPath({ cwd }) || process.argv[1] || "omx";
571
- const bridgeCommand = `${shellEscapeSingle(process.execPath)} ${shellEscapeSingle(omxBin)} question`;
572
- return `Deep-interview must ask each interview round via \`omx question\`; do not fall back to \`request_user_input\` or plain-text questioning. After starting \`omx question\` in a background terminal, wait for that terminal to finish and read the JSON answer before continuing the interview. If bare \`omx question\` is unavailable in this reused session, use the current-session CLI bridge command: \`${bridgeCommand}\`. Stop remains blocked while a deep-interview question obligation is pending.`;
600
+ const leaderPaneHint = resolveQuestionLeaderPaneHint(cwd, payload);
601
+ const bridgeCommand = leaderPaneHint
602
+ ? `OMX_QUESTION_RETURN_PANE=${shellEscapeSingle(leaderPaneHint)} ${shellEscapeSingle(process.execPath)} ${shellEscapeSingle(omxBin)} question`
603
+ : `${shellEscapeSingle(process.execPath)} ${shellEscapeSingle(omxBin)} question`;
604
+ const enforcementNote = leaderPaneHint
605
+ ? ` When using Bash/background-terminal tool paths, preserve the leader pane by exporting \`OMX_QUESTION_RETURN_PANE=${leaderPaneHint}\` (or equivalent) before invoking \`omx question\`.`
606
+ : '';
607
+ return `Deep-interview must ask each interview round via \`omx question\`; do not fall back to \`request_user_input\` or plain-text questioning. After starting \`omx question\` in a background terminal, wait for that terminal to finish and read the JSON answer before continuing the interview. If bare \`omx question\` is unavailable in this reused session, use the current-session CLI bridge command: \`${bridgeCommand}\`.${enforcementNote} Stop remains blocked while a deep-interview question obligation is pending.`;
573
608
  }
574
609
 
575
610
  function buildAdditionalContextMessage(
576
611
  prompt: string,
577
612
  skillState?: SkillActiveState | null,
578
613
  cwd: string = process.cwd(),
614
+ payload?: CodexHookPayload,
579
615
  ): string | null {
580
616
  if (!prompt) return null;
581
617
  const promptPriorityMessage = buildPromptPriorityMessage(prompt);
@@ -596,7 +632,10 @@ function buildAdditionalContextMessage(
596
632
  ? "Prompt-side `$ralph` activation seeds Ralph workflow state only; it does not invoke `omx ralph`. Use `omx ralph --prd ...` only when you explicitly want the PRD-gated CLI startup path."
597
633
  : null;
598
634
  const deepInterviewPromptActivationNote = skillState?.initialized_mode === "deep-interview"
599
- ? buildDeepInterviewQuestionBridgeInstruction(cwd)
635
+ ? buildDeepInterviewQuestionBridgeInstruction(cwd, payload)
636
+ : null;
637
+ const ultraworkPromptActivationNote = skillState?.initialized_mode === "ultrawork"
638
+ ? "Ultrawork protocol: ground the task before editing, define pass/fail acceptance criteria, keep shared-file work local, and use direct-tool plus background evidence lanes only for truly independent work. Direct ultrawork provides lightweight verification only; Ralph owns persistence and the full verified-completion promise."
600
639
  : null;
601
640
  const combinedTransitionMessage = (() => {
602
641
  if (!skillState?.transition_message) return null;
@@ -648,6 +687,7 @@ function buildAdditionalContextMessage(
648
687
  promptPriorityMessage,
649
688
  initializedStateMessage,
650
689
  deepInterviewPromptActivationNote,
690
+ ultraworkPromptActivationNote,
651
691
  "Use the durable OMX team runtime via `omx team ...` for coordinated execution; do not replace it with in-process fanout.",
652
692
  "If you need runtime syntax, run `omx team --help` yourself.",
653
693
  "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
@@ -664,6 +704,7 @@ function buildAdditionalContextMessage(
664
704
  promptPriorityMessage,
665
705
  `skill: ${skillState.initialized_mode} activated and initial state initialized at ${skillState.initialized_state_path}; write subsequent updates via omx_state MCP.`,
666
706
  deepInterviewPromptActivationNote,
707
+ ultraworkPromptActivationNote,
667
708
  ralphPromptActivationNote,
668
709
  "Follow AGENTS.md routing and preserve workflow transition and planning-safety rules.",
669
710
  ].join(" ");
@@ -1049,6 +1090,7 @@ async function buildDeepInterviewQuestionStopOutput(
1049
1090
  sessionId: string,
1050
1091
  threadId: string,
1051
1092
  ): Promise<{ output: Record<string, unknown>; obligationId: string } | null> {
1093
+ await reconcileDeepInterviewQuestionEnforcementFromAnsweredRecords(cwd, sessionId);
1052
1094
  const modeState = await readStopSessionPinnedState("deep-interview-state.json", cwd, sessionId);
1053
1095
  if (!modeState) return null;
1054
1096
 
@@ -1483,6 +1525,7 @@ async function buildStopHookOutput(
1483
1525
  safeString(autopilotOutput.stopReason),
1484
1526
  autopilotOutput,
1485
1527
  canonicalSessionId,
1528
+ { allowRepeatDuringStopHook: false },
1486
1529
  );
1487
1530
  }
1488
1531
 
@@ -1784,7 +1827,7 @@ export async function dispatchCodexNativeHook(
1784
1827
  if (hookEventName === "SessionStart" || hookEventName === "UserPromptSubmit") {
1785
1828
  const additionalContext = hookEventName === "SessionStart"
1786
1829
  ? await buildSessionStartContext(cwd, canonicalSessionId || nativeSessionId)
1787
- : (buildAdditionalContextMessage(readPromptText(payload), skillState, cwd) ?? triageAdditionalContext);
1830
+ : (buildAdditionalContextMessage(readPromptText(payload), skillState, cwd, payload) ?? triageAdditionalContext);
1788
1831
  if (additionalContext) {
1789
1832
  outputJson = {
1790
1833
  hookSpecificOutput: {
@@ -562,6 +562,49 @@ function buildGitCommitEnforcementOutput(commandText: string): Record<string, un
562
562
  };
563
563
  }
564
564
 
565
+ function commandInvokesOmxQuestion(command: string): boolean {
566
+ const tokens = tokenizeShellCommand(command)?.map((token) => token.toLowerCase()) ?? [];
567
+ for (let index = 0; index < tokens.length; index += 1) {
568
+ const rawToken = tokens[index] || '';
569
+ const token = rawToken.replace(/\\/g, '/').split('/').pop() || '';
570
+ if ((token === 'omx' || token === 'omx.js') && tokens[index + 1] === 'question') return true;
571
+ if ((token === 'node' || token === 'node.exe') && /(?:^|\/)omx\.js$/.test(tokens[index + 1] || '') && tokens[index + 2] === 'question') return true;
572
+ }
573
+ return /\bomx\s+question\b/i.test(command) || /\bomx\.js['"]?\s+question\b/i.test(command);
574
+ }
575
+
576
+ function isQuestionReturnPaneAssignment(token: string): boolean {
577
+ const equalsIndex = token.indexOf('=');
578
+ if (equalsIndex <= 0) return false;
579
+ const name = token.slice(0, equalsIndex);
580
+ if (!['OMX_QUESTION_RETURN_PANE', 'OMX_LEADER_PANE_ID', 'TMUX_PANE'].includes(name)) return false;
581
+ const value = token.slice(equalsIndex + 1);
582
+ return /^%\d+$/.test(value) || /^\$\{?TMUX_PANE\}?$/.test(value);
583
+ }
584
+
585
+ function commandHasQuestionReturnPane(command: string): boolean {
586
+ return (tokenizeShellCommand(command) ?? []).some(isQuestionReturnPaneAssignment);
587
+ }
588
+
589
+ function buildOmxQuestionPreToolUseEnforcementOutput(command: string): Record<string, unknown> | null {
590
+ if (!commandInvokesOmxQuestion(command)) return null;
591
+ if (commandHasQuestionReturnPane(command)) return null;
592
+
593
+ return {
594
+ decision: "block",
595
+ reason: "omx question Bash invocations must preserve the leader pane return target.",
596
+ hookSpecificOutput: {
597
+ hookEventName: "PreToolUse",
598
+ additionalContext: [
599
+ "omx question leader-pane enforcement triggered.",
600
+ "Prefix the Bash command with `OMX_QUESTION_RETURN_PANE=$TMUX_PANE` (or a concrete `%pane` value) so `[omx question answered]` returns to the leader pane even when the tool path drops/stales TMUX_PANE.",
601
+ `Original command: ${command}`,
602
+ ].join("\n"),
603
+ },
604
+ systemMessage: "omx question is blocked from Bash until the command preserves the leader pane with `OMX_QUESTION_RETURN_PANE=$TMUX_PANE` or an explicit `%pane` value.",
605
+ };
606
+ }
607
+
565
608
  export function buildNativePreToolUseOutput(
566
609
  payload: CodexHookPayload,
567
610
  ): Record<string, unknown> | null {
@@ -569,6 +612,8 @@ export function buildNativePreToolUseOutput(
569
612
  if (!normalized.isBash) return null;
570
613
  const gitCommitEnforcement = buildGitCommitEnforcementOutput(normalized.normalizedCommand);
571
614
  if (gitCommitEnforcement) return gitCommitEnforcement;
615
+ const questionEnforcement = buildOmxQuestionPreToolUseEnforcementOutput(normalized.normalizedCommand);
616
+ if (questionEnforcement) return questionEnforcement;
572
617
  if (!matchesDestructiveFixture(normalized.normalizedCommand)) return null;
573
618
 
574
619
  return {
@@ -0,0 +1,24 @@
1
+ import assert from 'node:assert/strict';
2
+ import { describe, it } from 'node:test';
3
+ import { buildOperationalContext } from '../operational-events.js';
4
+
5
+ describe('buildOperationalContext', () => {
6
+ it('preserves newly added canonical runtime event names in normalized_event', () => {
7
+ for (const normalizedEvent of [
8
+ 'run.heartbeat',
9
+ 'run.blocked_on_user',
10
+ 'run.blocked_on_system',
11
+ 'worker.assigned',
12
+ 'worker.stalled',
13
+ 'worker.recovered',
14
+ ]) {
15
+ const context = buildOperationalContext({
16
+ cwd: process.cwd(),
17
+ normalizedEvent,
18
+ status: normalizedEvent,
19
+ });
20
+ assert.equal(context.normalized_event, normalizedEvent);
21
+ assert.equal(context.status, normalizedEvent);
22
+ }
23
+ });
24
+ });
@@ -120,6 +120,15 @@ async function emitOperationalHookEvent(cwd, eventName, context) {
120
120
  }
121
121
  }
122
122
 
123
+ function mapDispatchFailureToCanonicalEvent(reason) {
124
+ const normalized = safeString(reason).toLowerCase();
125
+ if (!normalized) return 'run.blocked_on_system';
126
+ if (normalized.includes('missing') || normalized.includes('deferred') || normalized.includes('rejected')) {
127
+ return 'run.blocked_on_user';
128
+ }
129
+ return 'run.blocked_on_system';
130
+ }
131
+
123
132
  function resolveIssueDispatchCooldownMs(env = process.env) {
124
133
  const raw = safeString(env[ISSUE_DISPATCH_COOLDOWN_ENV]).trim();
125
134
  if (raw === '') return DEFAULT_ISSUE_DISPATCH_COOLDOWN_MS;
@@ -169,7 +169,7 @@ Rules:
169
169
  - Child agents should report recommended handoffs upward.
170
170
  - Child agents should finish their assigned role, not recursively orchestrate unless explicitly told to do so.
171
171
  - Prefer inheriting the leader model by omitting `spawn_agent.model` unless a task truly requires a different model.
172
- - Do not hardcode stale frontier-model overrides for Codex native child agents. If an explicit frontier override is necessary, use the current frontier default from `OMX_DEFAULT_FRONTIER_MODEL` / the repo model contract (currently `gpt-5.4`), not older values such as `gpt-5.2`.
172
+ - Do not hardcode stale frontier-model overrides for Codex native child agents. If an explicit frontier override is necessary, use the current frontier default from `OMX_DEFAULT_FRONTIER_MODEL` / the repo model contract (currently `gpt-5.5`), not older values such as `gpt-5.2`.
173
173
  - Prefer role-appropriate `reasoning_effort` over explicit `model` overrides when the only goal is to make a child think harder or lighter.
174
174
  </child_agent_protocol>
175
175