oh-my-codex 0.11.12 → 0.11.13

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 (248) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.md +23 -0
  4. package/README.vi.md +144 -185
  5. package/crates/omx-runtime-core/src/engine.rs +122 -4
  6. package/crates/omx-runtime-core/src/lib.rs +17 -0
  7. package/dist/cli/__tests__/autoresearch.test.js +11 -0
  8. package/dist/cli/__tests__/autoresearch.test.js.map +1 -1
  9. package/dist/cli/__tests__/cleanup.test.js +117 -4
  10. package/dist/cli/__tests__/cleanup.test.js.map +1 -1
  11. package/dist/cli/__tests__/error-handling-warnings.test.js +13 -0
  12. package/dist/cli/__tests__/error-handling-warnings.test.js.map +1 -1
  13. package/dist/cli/__tests__/exec.test.js +6 -0
  14. package/dist/cli/__tests__/exec.test.js.map +1 -1
  15. package/dist/cli/__tests__/index.test.js +94 -1
  16. package/dist/cli/__tests__/index.test.js.map +1 -1
  17. package/dist/cli/__tests__/launch-fallback.test.js +3 -0
  18. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  19. package/dist/cli/__tests__/package-bin-contract.test.js +10 -0
  20. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  21. package/dist/cli/__tests__/packaged-script-resolution.test.js +4 -3
  22. package/dist/cli/__tests__/packaged-script-resolution.test.js.map +1 -1
  23. package/dist/cli/__tests__/resume.test.js +6 -0
  24. package/dist/cli/__tests__/resume.test.js.map +1 -1
  25. package/dist/cli/__tests__/setup-refresh.test.js +29 -12
  26. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  27. package/dist/cli/__tests__/star-prompt.test.js +16 -0
  28. package/dist/cli/__tests__/star-prompt.test.js.map +1 -1
  29. package/dist/cli/__tests__/uninstall.test.js +112 -1
  30. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  31. package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts +2 -0
  32. package/dist/cli/__tests__/windows-popup-loop-contract.test.d.ts.map +1 -0
  33. package/dist/cli/__tests__/windows-popup-loop-contract.test.js +30 -0
  34. package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -0
  35. package/dist/cli/cleanup.d.ts +2 -0
  36. package/dist/cli/cleanup.d.ts.map +1 -1
  37. package/dist/cli/cleanup.js +26 -1
  38. package/dist/cli/cleanup.js.map +1 -1
  39. package/dist/cli/index.d.ts +7 -0
  40. package/dist/cli/index.d.ts.map +1 -1
  41. package/dist/cli/index.js +161 -50
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/cli/setup.d.ts.map +1 -1
  44. package/dist/cli/setup.js +15 -14
  45. package/dist/cli/setup.js.map +1 -1
  46. package/dist/cli/star-prompt.d.ts.map +1 -1
  47. package/dist/cli/star-prompt.js +1 -0
  48. package/dist/cli/star-prompt.js.map +1 -1
  49. package/dist/cli/team.d.ts.map +1 -1
  50. package/dist/cli/team.js +5 -1
  51. package/dist/cli/team.js.map +1 -1
  52. package/dist/cli/uninstall.d.ts.map +1 -1
  53. package/dist/cli/uninstall.js +26 -0
  54. package/dist/cli/uninstall.js.map +1 -1
  55. package/dist/cli/update.d.ts.map +1 -1
  56. package/dist/cli/update.js +1 -0
  57. package/dist/cli/update.js.map +1 -1
  58. package/dist/config/__tests__/generator-idempotent.test.js +4 -4
  59. package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
  60. package/dist/config/__tests__/mcp-registry.test.js +13 -16
  61. package/dist/config/__tests__/mcp-registry.test.js.map +1 -1
  62. package/dist/config/mcp-registry.d.ts +1 -0
  63. package/dist/config/mcp-registry.d.ts.map +1 -1
  64. package/dist/config/mcp-registry.js +4 -4
  65. package/dist/config/mcp-registry.js.map +1 -1
  66. package/dist/config/models.d.ts +1 -0
  67. package/dist/config/models.d.ts.map +1 -1
  68. package/dist/config/models.js +39 -1
  69. package/dist/config/models.js.map +1 -1
  70. package/dist/hooks/__tests__/keyword-detector.test.js +12 -1
  71. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  72. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +499 -17
  73. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  74. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +140 -14
  75. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  76. package/dist/hooks/__tests__/notify-hook-modules.test.js +5 -0
  77. package/dist/hooks/__tests__/notify-hook-modules.test.js.map +1 -1
  78. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts +2 -0
  79. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.d.ts.map +1 -0
  80. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js +597 -0
  81. package/dist/hooks/__tests__/notify-hook-ralph-resume.test.js.map +1 -0
  82. package/dist/hooks/__tests__/notify-hook-regression-205.test.js +15 -1
  83. package/dist/hooks/__tests__/notify-hook-regression-205.test.js.map +1 -1
  84. package/dist/hooks/__tests__/notify-hook-session-scope.test.js +73 -53
  85. package/dist/hooks/__tests__/notify-hook-session-scope.test.js.map +1 -1
  86. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js +193 -2
  87. package/dist/hooks/__tests__/notify-hook-team-dispatch.test.js.map +1 -1
  88. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +183 -0
  89. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js.map +1 -1
  90. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js +255 -97
  91. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  92. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js +0 -0
  93. package/dist/hooks/__tests__/notify-hook-tmux-scrollback.test.js.map +1 -1
  94. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js +46 -0
  95. package/dist/hooks/__tests__/notify-hook-worker-idle.test.js.map +1 -1
  96. package/dist/hooks/keyword-detector.d.ts +1 -0
  97. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  98. package/dist/hooks/keyword-detector.js +48 -0
  99. package/dist/hooks/keyword-detector.js.map +1 -1
  100. package/dist/hooks/session.d.ts.map +1 -1
  101. package/dist/hooks/session.js +1 -0
  102. package/dist/hooks/session.js.map +1 -1
  103. package/dist/hud/__tests__/state.test.js +70 -1
  104. package/dist/hud/__tests__/state.test.js.map +1 -1
  105. package/dist/hud/state.d.ts.map +1 -1
  106. package/dist/hud/state.js +10 -37
  107. package/dist/hud/state.js.map +1 -1
  108. package/dist/mcp/state-server.d.ts.map +1 -1
  109. package/dist/mcp/state-server.js +5 -0
  110. package/dist/mcp/state-server.js.map +1 -1
  111. package/dist/modes/__tests__/base-session-scope.test.js +46 -0
  112. package/dist/modes/__tests__/base-session-scope.test.js.map +1 -1
  113. package/dist/modes/base.d.ts.map +1 -1
  114. package/dist/modes/base.js +4 -0
  115. package/dist/modes/base.js.map +1 -1
  116. package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts +2 -0
  117. package/dist/notifications/__tests__/custom-alias-enablement.test.d.ts.map +1 -0
  118. package/dist/notifications/__tests__/custom-alias-enablement.test.js +84 -0
  119. package/dist/notifications/__tests__/custom-alias-enablement.test.js.map +1 -0
  120. package/dist/notifications/__tests__/idle-cooldown.test.js +55 -0
  121. package/dist/notifications/__tests__/idle-cooldown.test.js.map +1 -1
  122. package/dist/notifications/idle-cooldown.d.ts +8 -6
  123. package/dist/notifications/idle-cooldown.d.ts.map +1 -1
  124. package/dist/notifications/idle-cooldown.js +53 -22
  125. package/dist/notifications/idle-cooldown.js.map +1 -1
  126. package/dist/notifications/notifier.js +1 -1
  127. package/dist/notifications/notifier.js.map +1 -1
  128. package/dist/notifications/reply-listener.d.ts.map +1 -1
  129. package/dist/notifications/reply-listener.js +1 -0
  130. package/dist/notifications/reply-listener.js.map +1 -1
  131. package/dist/openclaw/config.js +2 -2
  132. package/dist/openclaw/config.js.map +1 -1
  133. package/dist/runtime/bridge.d.ts +1 -0
  134. package/dist/runtime/bridge.d.ts.map +1 -1
  135. package/dist/runtime/bridge.js +2 -6
  136. package/dist/runtime/bridge.js.map +1 -1
  137. package/dist/scripts/notify-fallback-watcher.js +97 -59
  138. package/dist/scripts/notify-fallback-watcher.js.map +1 -1
  139. package/dist/scripts/notify-hook/auto-nudge.d.ts +2 -1
  140. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  141. package/dist/scripts/notify-hook/auto-nudge.js +72 -238
  142. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  143. package/dist/scripts/notify-hook/managed-tmux.d.ts +19 -0
  144. package/dist/scripts/notify-hook/managed-tmux.d.ts.map +1 -0
  145. package/dist/scripts/notify-hook/managed-tmux.js +320 -0
  146. package/dist/scripts/notify-hook/managed-tmux.js.map +1 -0
  147. package/dist/scripts/notify-hook/ralph-session-resume.d.ts +22 -0
  148. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -0
  149. package/dist/scripts/notify-hook/ralph-session-resume.js +277 -0
  150. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -0
  151. package/dist/scripts/notify-hook/state-io.d.ts +1 -1
  152. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  153. package/dist/scripts/notify-hook/state-io.js +2 -10
  154. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  155. package/dist/scripts/notify-hook/team-dispatch.d.ts.map +1 -1
  156. package/dist/scripts/notify-hook/team-dispatch.js +60 -59
  157. package/dist/scripts/notify-hook/team-dispatch.js.map +1 -1
  158. package/dist/scripts/notify-hook/team-leader-nudge.d.ts +2 -1
  159. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  160. package/dist/scripts/notify-hook/team-leader-nudge.js +13 -5
  161. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  162. package/dist/scripts/notify-hook/team-tmux-guard.d.ts.map +1 -1
  163. package/dist/scripts/notify-hook/team-tmux-guard.js +1 -19
  164. package/dist/scripts/notify-hook/team-tmux-guard.js.map +1 -1
  165. package/dist/scripts/notify-hook/team-worker.js +4 -4
  166. package/dist/scripts/notify-hook/team-worker.js.map +1 -1
  167. package/dist/scripts/notify-hook/tmux-injection.d.ts +1 -1
  168. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  169. package/dist/scripts/notify-hook/tmux-injection.js +102 -35
  170. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  171. package/dist/scripts/notify-hook.js +144 -20
  172. package/dist/scripts/notify-hook.js.map +1 -1
  173. package/dist/scripts/tmux-hook-engine.d.ts +1 -0
  174. package/dist/scripts/tmux-hook-engine.d.ts.map +1 -1
  175. package/dist/scripts/tmux-hook-engine.js +3 -0
  176. package/dist/scripts/tmux-hook-engine.js.map +1 -1
  177. package/dist/team/__tests__/api-interop.test.js +96 -4
  178. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  179. package/dist/team/__tests__/leader-activity.test.js +107 -2
  180. package/dist/team/__tests__/leader-activity.test.js.map +1 -1
  181. package/dist/team/__tests__/runtime-cli.test.js +32 -0
  182. package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
  183. package/dist/team/__tests__/runtime.test.js +148 -0
  184. package/dist/team/__tests__/runtime.test.js.map +1 -1
  185. package/dist/team/__tests__/shutdown-fallback.test.js +13 -0
  186. package/dist/team/__tests__/shutdown-fallback.test.js.map +1 -1
  187. package/dist/team/__tests__/state-root.test.js +11 -1
  188. package/dist/team/__tests__/state-root.test.js.map +1 -1
  189. package/dist/team/__tests__/state.test.js +16 -5
  190. package/dist/team/__tests__/state.test.js.map +1 -1
  191. package/dist/team/__tests__/tmux-session.test.js +460 -2
  192. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  193. package/dist/team/api-interop.d.ts.map +1 -1
  194. package/dist/team/api-interop.js +34 -7
  195. package/dist/team/api-interop.js.map +1 -1
  196. package/dist/team/commit-hygiene.d.ts +60 -0
  197. package/dist/team/commit-hygiene.d.ts.map +1 -0
  198. package/dist/team/commit-hygiene.js +232 -0
  199. package/dist/team/commit-hygiene.js.map +1 -0
  200. package/dist/team/leader-activity.d.ts.map +1 -1
  201. package/dist/team/leader-activity.js +17 -35
  202. package/dist/team/leader-activity.js.map +1 -1
  203. package/dist/team/runtime-cli.d.ts +9 -1
  204. package/dist/team/runtime-cli.d.ts.map +1 -1
  205. package/dist/team/runtime-cli.js +15 -6
  206. package/dist/team/runtime-cli.js.map +1 -1
  207. package/dist/team/runtime.d.ts +7 -2
  208. package/dist/team/runtime.d.ts.map +1 -1
  209. package/dist/team/runtime.js +391 -63
  210. package/dist/team/runtime.js.map +1 -1
  211. package/dist/team/state/dispatch.js +1 -1
  212. package/dist/team/state/dispatch.js.map +1 -1
  213. package/dist/team/state/mailbox.d.ts +1 -0
  214. package/dist/team/state/mailbox.d.ts.map +1 -1
  215. package/dist/team/state/mailbox.js +54 -8
  216. package/dist/team/state/mailbox.js.map +1 -1
  217. package/dist/team/state-root.d.ts +1 -1
  218. package/dist/team/state-root.d.ts.map +1 -1
  219. package/dist/team/state-root.js +8 -3
  220. package/dist/team/state-root.js.map +1 -1
  221. package/dist/team/state.d.ts.map +1 -1
  222. package/dist/team/state.js +66 -3
  223. package/dist/team/state.js.map +1 -1
  224. package/dist/team/tmux-session.d.ts.map +1 -1
  225. package/dist/team/tmux-session.js +69 -27
  226. package/dist/team/tmux-session.js.map +1 -1
  227. package/dist/utils/__tests__/platform-command.test.js +101 -2
  228. package/dist/utils/__tests__/platform-command.test.js.map +1 -1
  229. package/dist/utils/git-layout.d.ts +8 -0
  230. package/dist/utils/git-layout.d.ts.map +1 -0
  231. package/dist/utils/git-layout.js +58 -0
  232. package/dist/utils/git-layout.js.map +1 -0
  233. package/dist/utils/platform-command.d.ts.map +1 -1
  234. package/dist/utils/platform-command.js +32 -1
  235. package/dist/utils/platform-command.js.map +1 -1
  236. package/package.json +6 -6
  237. package/src/scripts/notify-fallback-watcher.ts +96 -58
  238. package/src/scripts/notify-hook/auto-nudge.ts +75 -230
  239. package/src/scripts/notify-hook/managed-tmux.ts +324 -0
  240. package/src/scripts/notify-hook/ralph-session-resume.ts +337 -0
  241. package/src/scripts/notify-hook/state-io.ts +2 -10
  242. package/src/scripts/notify-hook/team-dispatch.ts +70 -54
  243. package/src/scripts/notify-hook/team-leader-nudge.ts +19 -5
  244. package/src/scripts/notify-hook/team-tmux-guard.ts +0 -20
  245. package/src/scripts/notify-hook/team-worker.ts +4 -4
  246. package/src/scripts/notify-hook/tmux-injection.ts +103 -33
  247. package/src/scripts/notify-hook.ts +150 -21
  248. package/src/scripts/tmux-hook-engine.ts +4 -0
@@ -5,17 +5,22 @@
5
5
  */
6
6
 
7
7
  import { readFile, writeFile } from 'fs/promises';
8
- import { readFileSync } from 'fs';
9
- import { execFileSync } from 'child_process';
10
- import { basename, dirname, join, resolve as resolvePath } from 'path';
8
+ import { join } from 'path';
11
9
  import { homedir } from 'os';
12
10
  import { asNumber, safeString } from './utils.js';
13
11
  import { readJsonIfExists, getScopedStateDirsForCurrentSession, readdir } from './state-io.js';
14
12
  import { runProcess } from './process-runner.js';
15
13
  import { logTmuxHookEvent } from './log.js';
16
14
  import { evaluatePaneInjectionReadiness, mapPaneInjectionReadinessReason, sendPaneInput } from './team-tmux-guard.js';
17
- import { buildCapturePaneArgv, DEFAULT_MARKER } from '../tmux-hook-engine.js';
18
- import { readSessionState, isSessionStale } from '../../hooks/session.js';
15
+ import { buildCapturePaneArgv, DEFAULT_MARKER, tmuxHookExplicitlyDisablesInjection } from '../tmux-hook-engine.js';
16
+ import {
17
+ isManagedOmxSession,
18
+ resolveManagedCurrentPane,
19
+ resolveManagedPaneFromAnchor,
20
+ resolveManagedSessionPane,
21
+ resolveInvocationSessionId,
22
+ verifyManagedPaneTarget,
23
+ } from './managed-tmux.js';
19
24
 
20
25
  export const SKILL_ACTIVE_STATE_FILE = 'skill-active-state.json';
21
26
  export const DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS = ['yes', 'y', 'proceed', 'continue', 'ok', 'sure', 'go ahead', 'next i should'];
@@ -55,22 +60,29 @@ export function normalizeBlockedAutoApprovalInput(text) {
55
60
  .trim();
56
61
  }
57
62
 
63
+ function buildBlockedAutoApprovalMatcher(blockedInputs) {
64
+ const normalizedBlockedInputs = blockedInputs.map((entry) => normalizeBlockedAutoApprovalInput(entry)).filter(Boolean);
65
+ return {
66
+ exactMatches: new Set(normalizedBlockedInputs),
67
+ prefixedMatches: normalizedBlockedInputs.filter((entry) => DEEP_INTERVIEW_BLOCKED_APPROVAL_PREFIXES.has(entry)),
68
+ blockedTokenSet: new Set(normalizedBlockedInputs.flatMap((entry) => entry.split(/\s+/).filter(Boolean))),
69
+ };
70
+ }
71
+
58
72
  export function isBlockedAutoApprovalInput(text, blockedInputs = DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS) {
59
73
  const normalized = normalizeBlockedAutoApprovalInput(text);
60
74
  if (!normalized) return false;
61
- if (blockedInputs.some((entry) => normalizeBlockedAutoApprovalInput(entry) === normalized)) return true;
62
- if (
63
- blockedInputs
64
- .map((entry) => normalizeBlockedAutoApprovalInput(entry))
65
- .filter((entry) => DEEP_INTERVIEW_BLOCKED_APPROVAL_PREFIXES.has(entry))
66
- .some((prefix) => normalized.startsWith(`${prefix} `))
67
- ) return true;
75
+ const normalizedBlockedInputs = blockedInputs.map((entry) => normalizeBlockedAutoApprovalInput(entry)).filter(Boolean);
76
+ if (normalizedBlockedInputs.includes(normalized)) return true;
77
+
78
+ const blockedPrefixes = normalizedBlockedInputs.filter((entry) => DEEP_INTERVIEW_BLOCKED_APPROVAL_PREFIXES.has(entry));
79
+ if (blockedPrefixes.some((prefix) => normalized.startsWith(`${prefix} `))) return true;
68
80
 
69
81
  const tokens = normalized.split(/\s+/).filter(Boolean);
70
82
  if (tokens.length === 0) return false;
71
83
 
72
84
  const blockedTokenSet = new Set(
73
- blockedInputs.flatMap((entry) => normalizeBlockedAutoApprovalInput(entry).split(/\s+/).filter(Boolean)),
85
+ normalizedBlockedInputs.flatMap((entry) => entry.split(/\s+/).filter(Boolean)),
74
86
  );
75
87
  return tokens.every((token) => blockedTokenSet.has(token));
76
88
  }
@@ -179,6 +191,11 @@ export async function isDeepInterviewStateActive(stateDir) {
179
191
  return Boolean(modeState && modeState.active === true);
180
192
  }
181
193
 
194
+ export async function isDeepInterviewInputLockActive(stateDir) {
195
+ const skillState = await loadSkillActiveState(stateDir);
196
+ return isDeepInterviewAutoApprovalLocked(skillState);
197
+ }
198
+
182
199
  export async function resolveAutoNudgeSignature(stateDir, payload, lastMessage = '') {
183
200
  const normalizedMessage = normalizeAutoNudgeSignatureText(lastMessage);
184
201
  const hudState = await readJsonIfExists(join(stateDir, 'hud-state.json'), null);
@@ -354,6 +371,13 @@ export async function loadAutoNudgeConfig() {
354
371
  return normalizeAutoNudgeConfig(raw.autoNudge);
355
372
  }
356
373
 
374
+ async function localTmuxInjectionDisabled(cwd) {
375
+ const normalizedCwd = safeString(cwd).trim();
376
+ if (!normalizedCwd) return false;
377
+ const raw = await readJsonIfExists(join(normalizedCwd, '.omx', 'tmux-hook.json'), null);
378
+ return tmuxHookExplicitlyDisablesInjection(raw);
379
+ }
380
+
357
381
  export function detectStallPattern(text, patterns) {
358
382
  if (!text || typeof text !== 'string') return false;
359
383
  const normalized = normalizeStallDetectionText(text);
@@ -375,233 +399,52 @@ export async function capturePane(paneId, lines = 10) {
375
399
  }
376
400
  }
377
401
 
378
- function resolveCodexPaneByCwdFallback(cwd) {
379
- const normalizedCwd = safeString(cwd).trim();
380
- if (!normalizedCwd) return '';
381
-
382
- try {
383
- const panes = execFileSync('tmux', [
384
- 'list-panes', '-a', '-F', '#{pane_id} #{pane_current_path} #{pane_current_command} #{pane_start_command}',
385
- ], { encoding: 'utf-8', timeout: 2000, windowsHide: true })
386
- .trim()
387
- .split('\n')
388
- .filter(Boolean);
389
-
390
- for (const line of panes) {
391
- const [paneId, panePath = '', paneCommand = '', startCommand = ''] = line.split('\t');
392
- const normalizedPanePath = safeString(panePath).trim();
393
- const normalizedStart = safeString(startCommand).toLowerCase();
394
- const normalizedCommand = safeString(paneCommand).trim().toLowerCase();
395
- if (!paneId || normalizedPanePath !== normalizedCwd) continue;
396
- if (/\bomx\b.*\bhud\b.*--watch/i.test(normalizedStart)) continue;
397
- if (normalizedStart.includes('codex')) return paneId;
398
- if (normalizedCommand === 'codex' || normalizedCommand === 'node' || normalizedCommand === 'npx') return paneId;
399
- }
400
- } catch {
401
- // Fall back to empty when tmux scan is unavailable.
402
- }
403
-
404
- return '';
405
- }
406
-
407
- async function resolveCodexPaneFromAnchor(anchorPane) {
408
- const paneId = safeString(anchorPane).trim();
409
- if (!paneId) return '';
410
-
411
- try {
412
- const sessionResult = await runProcess('tmux', ['display-message', '-t', paneId, '-p', '#S'], 2000);
413
- const sessionName = safeString(sessionResult.stdout).trim();
414
- if (!sessionName) return '';
415
-
416
- const panesResult = await runProcess(
417
- 'tmux',
418
- ['list-panes', '-s', '-t', sessionName, '-F', '#{pane_id}\t#{pane_current_command}\t#{pane_start_command}'],
419
- 2000,
420
- );
421
- const panes = safeString(panesResult.stdout).trim().split('\n').filter(Boolean);
422
- for (const line of panes) {
423
- const [candidatePaneId, , rawStartCommand = ''] = line.split('\t');
424
- const startCommand = safeString(rawStartCommand).toLowerCase();
425
- if (!candidatePaneId) continue;
426
- if (/\bomx\b.*\bhud\b.*--watch/i.test(startCommand)) continue;
427
- if (startCommand.includes('codex')) return candidatePaneId;
428
- }
429
- } catch {
430
- // Fall back to the anchored pane when session scanning is unavailable.
431
- }
432
-
433
- return '';
434
- }
435
-
436
- function resolveInvocationSessionId(payload) {
437
- return safeString(
438
- payload?.session_id
439
- || payload?.['session-id']
440
- || process.env.OMX_SESSION_ID
441
- || process.env.CODEX_SESSION_ID
442
- || process.env.SESSION_ID
443
- || '',
444
- ).trim();
445
- }
446
-
447
-
448
- function sanitizeTmuxToken(value) {
449
- const cleaned = safeString(value)
450
- .toLowerCase()
451
- .replace(/[^a-z0-9]+/g, '-')
452
- .replace(/^-+|-+$/g, '');
453
- return cleaned || 'unknown';
454
- }
455
-
456
- function buildExpectedManagedTmuxSessionName(cwd, sessionId) {
457
- const parentPath = dirname(cwd);
458
- const parentDir = basename(parentPath);
459
- const dirName = basename(cwd);
460
- const grandparentPath = dirname(parentPath);
461
- const grandparentDir = basename(grandparentPath);
462
- const repoDir = parentDir.endsWith('.omx-worktrees')
463
- ? parentDir.slice(0, -'.omx-worktrees'.length)
464
- : parentDir === 'worktrees' && grandparentDir === '.omx'
465
- ? basename(dirname(grandparentPath))
466
- : null;
467
- const dirToken = repoDir
468
- ? sanitizeTmuxToken(`${repoDir}-${dirName}`)
469
- : sanitizeTmuxToken(dirName);
470
- let branchToken = 'detached';
471
- try {
472
- const branch = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
473
- cwd,
474
- encoding: 'utf-8',
475
- stdio: ['ignore', 'pipe', 'ignore'],
476
- timeout: 2000,
477
- }).trim();
478
- if (branch) branchToken = sanitizeTmuxToken(branch);
479
- } catch {
480
- // best effort only
481
- }
482
- const sessionToken = sanitizeTmuxToken(safeString(sessionId).replace(/^omx-/, ''));
483
- const name = `omx-${dirToken}-${branchToken}-${sessionToken}`;
484
- return name.length > 120 ? name.slice(0, 120) : name;
485
- }
486
-
487
- function readCurrentTmuxSessionName() {
488
- if (!process.env.TMUX) return '';
489
- try {
490
- return execFileSync('tmux', ['display-message', '-p', '#S'], {
491
- encoding: 'utf-8',
492
- stdio: ['ignore', 'pipe', 'ignore'],
493
- timeout: 2000,
494
- }).trim();
495
- } catch {
496
- return '';
497
- }
498
- }
499
-
500
- function readParentPid(pid) {
501
- if (!Number.isInteger(pid) || pid <= 1) return null;
502
- try {
503
- if (process.platform === 'linux') {
504
- const stat = readFileSync(`/proc/${pid}/stat`, 'utf-8');
505
- const commandEnd = stat.lastIndexOf(')');
506
- if (commandEnd === -1) return null;
507
- const remainder = stat.slice(commandEnd + 1).trim();
508
- const fields = remainder.split(/\s+/);
509
- if (fields.length === 0) return null;
510
- const ppid = Number(fields[1]);
511
- return Number.isFinite(ppid) && ppid > 0 ? ppid : null;
512
- }
513
- const raw = execFileSync('ps', ['-o', 'ppid=', '-p', String(pid)], {
514
- encoding: 'utf-8',
515
- timeout: 2000,
516
- }).trim();
517
- const ppid = Number(raw);
518
- return Number.isFinite(ppid) && ppid > 0 ? ppid : null;
519
- } catch {
520
- return null;
521
- }
522
- }
523
-
524
- function processHasAncestorPid(targetPid, currentPid = process.pid) {
525
- if (!Number.isInteger(targetPid) || targetPid <= 1) return false;
526
- let pid = Number.isInteger(currentPid) && currentPid > 1 ? currentPid : process.pid;
527
- for (let depth = 0; depth < 64 && pid > 1; depth += 1) {
528
- if (pid === targetPid) return true;
529
- const parent = readParentPid(pid);
530
- if (!parent || parent === pid) break;
531
- pid = parent;
532
- }
533
- return false;
534
- }
535
-
536
- async function isManagedOmxSessionForAutoNudge(cwd, payload) {
537
- if (safeString(process.env.OMX_TEAM_WORKER || '').trim() !== '') return true;
402
+ export async function resolveNudgePaneTarget(stateDir: any, cwd = '', payload: any = undefined) {
403
+ const allowTeamWorker = safeString(process.env.OMX_TEAM_WORKER || '').trim() !== '';
404
+ const managedCurrentPane = await resolveManagedCurrentPane(cwd, payload, { allowTeamWorker });
405
+ if (managedCurrentPane) return managedCurrentPane;
538
406
 
539
407
  const invocationSessionId = resolveInvocationSessionId(payload);
540
- if (!invocationSessionId) return false;
541
-
542
- try {
543
- const sessionState = await readSessionState(cwd);
544
- if (!sessionState) return false;
545
- if (resolvePath(safeString(sessionState.cwd || cwd)) !== resolvePath(cwd)) return false;
546
- if (safeString(sessionState.session_id).trim() !== invocationSessionId) return false;
547
- if (isSessionStale(sessionState)) return false;
548
-
549
- const currentTmuxSession = readCurrentTmuxSessionName();
550
- if (currentTmuxSession) {
551
- const expectedTmuxSession = buildExpectedManagedTmuxSessionName(cwd, invocationSessionId);
552
- if (currentTmuxSession === expectedTmuxSession) return true;
553
- }
554
-
555
- return processHasAncestorPid(sessionState.pid);
556
- } catch {
557
- return false;
558
- }
559
- }
560
-
561
- export async function resolveNudgePaneTarget(stateDir: any, cwd = '') {
562
- // Use canonical codex pane resolver — validates pane is running an agent, not a shell
563
- const { resolveCodexPane } = await import('../tmux-hook-engine.js');
564
- const codexPane = resolveCodexPane();
565
- if (codexPane) return codexPane;
566
-
567
- let fallbackPane = '';
568
-
569
- try {
570
- const scopedDirs = await getScopedStateDirsForCurrentSession(stateDir);
571
- for (const dir of scopedDirs) {
572
- const files = await readdir(dir).catch(() => []);
573
- for (const f of files) {
574
- if (!f.endsWith('-state.json')) continue;
575
- const path = join(dir, f);
576
- try {
577
- const state = JSON.parse(await readFile(path, 'utf-8'));
578
- if (state && state.active && state.tmux_pane_id) {
579
- const anchoredPane = safeString(state.tmux_pane_id).trim();
580
- if (!anchoredPane) continue;
581
- const upgradedPane = await resolveCodexPaneFromAnchor(anchoredPane);
582
- if (upgradedPane) return upgradedPane;
583
- if (!fallbackPane) fallbackPane = anchoredPane;
584
- }
585
- } catch {
586
- // skip malformed state
587
- }
408
+ const scopedDirs = await getScopedStateDirsForCurrentSession(stateDir, invocationSessionId).catch(() => []);
409
+ for (const dir of scopedDirs) {
410
+ const files = await readdir(dir).catch(() => []);
411
+ for (const f of files) {
412
+ if (!f.endsWith('-state.json')) continue;
413
+ const path = join(dir, f);
414
+ try {
415
+ const state = JSON.parse(await readFile(path, 'utf-8'));
416
+ if (!state || !state.active || !state.tmux_pane_id) continue;
417
+ const anchoredPane = safeString(state.tmux_pane_id).trim();
418
+ if (!anchoredPane) continue;
419
+ const managedPane = await resolveManagedPaneFromAnchor(anchoredPane, cwd, payload, { allowTeamWorker });
420
+ if (managedPane) return managedPane;
421
+ const verdict = await verifyManagedPaneTarget(anchoredPane, cwd, payload, { allowTeamWorker });
422
+ if (verdict.ok) return anchoredPane;
423
+ } catch {
424
+ // skip malformed state
588
425
  }
589
426
  }
590
- } catch {
591
- // Non-critical
592
427
  }
593
428
 
594
- if (fallbackPane) return fallbackPane;
595
-
596
- return resolveCodexPaneByCwdFallback(cwd);
429
+ return await resolveManagedSessionPane(cwd, payload);
597
430
  }
598
431
 
599
432
  export async function maybeAutoNudge({ cwd, stateDir, logsDir, payload }) {
600
433
  const config = await loadAutoNudgeConfig();
601
434
  if (!config.enabled) return;
435
+ if (await localTmuxInjectionDisabled(cwd)) {
436
+ await logTmuxHookEvent(logsDir, {
437
+ timestamp: new Date().toISOString(),
438
+ type: 'auto_nudge_skipped',
439
+ reason: 'tmux_hook_disabled',
440
+ }).catch(() => {});
441
+ return;
442
+ }
602
443
 
603
- const managedSession = await isManagedOmxSessionForAutoNudge(cwd, payload);
444
+ const sourceName = safeString(payload?.source || '');
445
+ const managedSession = await isManagedOmxSession(cwd, payload, { allowTeamWorker: true });
604
446
  if (!managedSession) {
447
+ if (sourceName === 'notify-fallback-watcher-stall') return;
605
448
  await logTmuxHookEvent(logsDir, {
606
449
  timestamp: new Date().toISOString(),
607
450
  type: 'auto_nudge_skipped',
@@ -630,7 +473,7 @@ export async function maybeAutoNudge({ cwd, stateDir, logsDir, payload }) {
630
473
  if (!nudgeState || typeof nudgeState !== 'object') {
631
474
  nudgeState = { nudgeCount: 0, lastNudgeAt: '', lastSignature: '', lastSemanticSignature: '' };
632
475
  }
633
- const paneId = await resolveNudgePaneTarget(stateDir, cwd);
476
+ const paneId = await resolveNudgePaneTarget(stateDir, cwd, payload);
634
477
 
635
478
  let detected = detectStallPattern(lastMessage, config.patterns);
636
479
  let source = 'payload';
@@ -681,7 +524,6 @@ export async function maybeAutoNudge({ cwd, stateDir, logsDir, payload }) {
681
524
  return;
682
525
  }
683
526
 
684
- const sourceName = safeString(payload?.source || '');
685
527
  const isFallbackWatcherSource = sourceName === 'notify-fallback-watcher-stall';
686
528
  if (!isFallbackWatcherSource && config.stallMs > 0) {
687
529
  nudgeState.pendingSignature = signature;
@@ -713,7 +555,7 @@ export async function maybeAutoNudge({ cwd, stateDir, logsDir, payload }) {
713
555
  }
714
556
 
715
557
  const deepInterviewLockActive = isDeepInterviewAutoApprovalLocked(skillState) && !releaseReason;
716
- if (deepInterviewLockActive && isBlockedAutoApprovalInput(config.response, skillState.input_lock?.blocked_inputs)) {
558
+ if (deepInterviewLockActive) {
717
559
  const blockedMessage = skillState.input_lock?.message || DEEP_INTERVIEW_INPUT_LOCK_MESSAGE;
718
560
  await logTmuxHookEvent(logsDir, {
719
561
  timestamp: new Date().toISOString(),
@@ -722,6 +564,9 @@ export async function maybeAutoNudge({ cwd, stateDir, logsDir, payload }) {
722
564
  response: config.response,
723
565
  source,
724
566
  blocked_by: 'deep-interview-lock',
567
+ block_kind: isBlockedAutoApprovalInput(config.response, skillState.input_lock?.blocked_inputs)
568
+ ? 'blocked-auto-approval'
569
+ : 'input-lock-active',
725
570
  message: blockedMessage,
726
571
  suppressed: true,
727
572
  }).catch(() => {});