oh-my-codex 0.18.7 → 0.18.8

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 (259) hide show
  1. package/Cargo.lock +6 -6
  2. package/Cargo.toml +1 -1
  3. package/README.md +5 -5
  4. package/crates/omx-sparkshell/tests/execution.rs +1 -1
  5. package/dist/agents/__tests__/native-config.test.js +42 -1
  6. package/dist/agents/__tests__/native-config.test.js.map +1 -1
  7. package/dist/agents/definitions.d.ts +8 -0
  8. package/dist/agents/definitions.d.ts.map +1 -1
  9. package/dist/agents/definitions.js +1 -0
  10. package/dist/agents/definitions.js.map +1 -1
  11. package/dist/agents/native-config.d.ts +5 -1
  12. package/dist/agents/native-config.d.ts.map +1 -1
  13. package/dist/agents/native-config.js +17 -2
  14. package/dist/agents/native-config.js.map +1 -1
  15. package/dist/cli/__tests__/codex-plugin-layout.test.js +512 -1
  16. package/dist/cli/__tests__/codex-plugin-layout.test.js.map +1 -1
  17. package/dist/cli/__tests__/doctor-warning-copy.test.js +39 -0
  18. package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
  19. package/dist/cli/__tests__/index.test.js +61 -5
  20. package/dist/cli/__tests__/index.test.js.map +1 -1
  21. package/dist/cli/__tests__/package-bin-contract.test.js +8 -4
  22. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  23. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js +13 -0
  24. package/dist/cli/__tests__/ralph-goal-mode-contract.test.js.map +1 -1
  25. package/dist/cli/__tests__/ralph.test.js +14 -0
  26. package/dist/cli/__tests__/ralph.test.js.map +1 -1
  27. package/dist/cli/__tests__/setup-install-mode.test.js +89 -0
  28. package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
  29. package/dist/cli/__tests__/setup-refresh.test.js +65 -0
  30. package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
  31. package/dist/cli/__tests__/state.test.js +21 -0
  32. package/dist/cli/__tests__/state.test.js.map +1 -1
  33. package/dist/cli/__tests__/team.test.js +2 -2
  34. package/dist/cli/__tests__/update.test.js +110 -2
  35. package/dist/cli/__tests__/update.test.js.map +1 -1
  36. package/dist/cli/doctor.d.ts.map +1 -1
  37. package/dist/cli/doctor.js +8 -1
  38. package/dist/cli/doctor.js.map +1 -1
  39. package/dist/cli/index.d.ts +11 -2
  40. package/dist/cli/index.d.ts.map +1 -1
  41. package/dist/cli/index.js +108 -15
  42. package/dist/cli/index.js.map +1 -1
  43. package/dist/cli/plugin-marketplace.d.ts +14 -2
  44. package/dist/cli/plugin-marketplace.d.ts.map +1 -1
  45. package/dist/cli/plugin-marketplace.js +62 -15
  46. package/dist/cli/plugin-marketplace.js.map +1 -1
  47. package/dist/cli/ralph.d.ts.map +1 -1
  48. package/dist/cli/ralph.js +3 -1
  49. package/dist/cli/ralph.js.map +1 -1
  50. package/dist/cli/setup-preferences.d.ts +2 -0
  51. package/dist/cli/setup-preferences.d.ts.map +1 -1
  52. package/dist/cli/setup-preferences.js +4 -0
  53. package/dist/cli/setup-preferences.js.map +1 -1
  54. package/dist/cli/setup.d.ts +3 -0
  55. package/dist/cli/setup.d.ts.map +1 -1
  56. package/dist/cli/setup.js +166 -27
  57. package/dist/cli/setup.js.map +1 -1
  58. package/dist/cli/state.d.ts.map +1 -1
  59. package/dist/cli/state.js +8 -1
  60. package/dist/cli/state.js.map +1 -1
  61. package/dist/cli/tmux-hook.d.ts.map +1 -1
  62. package/dist/cli/tmux-hook.js +16 -0
  63. package/dist/cli/tmux-hook.js.map +1 -1
  64. package/dist/cli/update.d.ts +2 -0
  65. package/dist/cli/update.d.ts.map +1 -1
  66. package/dist/cli/update.js +47 -3
  67. package/dist/cli/update.js.map +1 -1
  68. package/dist/config/__tests__/generator-notify.test.js +1 -0
  69. package/dist/config/__tests__/generator-notify.test.js.map +1 -1
  70. package/dist/config/generator.d.ts +2 -2
  71. package/dist/config/generator.d.ts.map +1 -1
  72. package/dist/config/generator.js +2 -2
  73. package/dist/config/generator.js.map +1 -1
  74. package/dist/config/team-mode.d.ts +12 -0
  75. package/dist/config/team-mode.d.ts.map +1 -0
  76. package/dist/config/team-mode.js +91 -0
  77. package/dist/config/team-mode.js.map +1 -0
  78. package/dist/hooks/__tests__/agents-overlay.test.js +88 -0
  79. package/dist/hooks/__tests__/agents-overlay.test.js.map +1 -1
  80. package/dist/hooks/__tests__/code-review-skill-contract.test.js +8 -0
  81. package/dist/hooks/__tests__/code-review-skill-contract.test.js.map +1 -1
  82. package/dist/hooks/__tests__/keyword-detector.test.js +423 -3
  83. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  84. package/dist/hooks/__tests__/notify-fallback-watcher.test.js +1 -1
  85. package/dist/hooks/__tests__/notify-fallback-watcher.test.js.map +1 -1
  86. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js +189 -0
  87. package/dist/hooks/__tests__/notify-hook-auto-nudge.test.js.map +1 -1
  88. package/dist/hooks/__tests__/notify-hook-team-leader-nudge.test.js +35 -2
  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 +3 -3
  91. package/dist/hooks/__tests__/notify-hook-tmux-heal.test.js.map +1 -1
  92. package/dist/hooks/__tests__/skill-guidance-contract.test.js +21 -0
  93. package/dist/hooks/__tests__/skill-guidance-contract.test.js.map +1 -1
  94. package/dist/hooks/agents-overlay.d.ts.map +1 -1
  95. package/dist/hooks/agents-overlay.js +36 -50
  96. package/dist/hooks/agents-overlay.js.map +1 -1
  97. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js +31 -0
  98. package/dist/hooks/extensibility/__tests__/plugin-runner.test.js.map +1 -1
  99. package/dist/hooks/extensibility/plugin-runner.js +17 -21
  100. package/dist/hooks/extensibility/plugin-runner.js.map +1 -1
  101. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  102. package/dist/hooks/keyword-detector.js +258 -12
  103. package/dist/hooks/keyword-detector.js.map +1 -1
  104. package/dist/hooks/prompt-guidance-contract.d.ts.map +1 -1
  105. package/dist/hooks/prompt-guidance-contract.js +6 -0
  106. package/dist/hooks/prompt-guidance-contract.js.map +1 -1
  107. package/dist/hooks/session.d.ts +1 -0
  108. package/dist/hooks/session.d.ts.map +1 -1
  109. package/dist/hooks/session.js.map +1 -1
  110. package/dist/hud/__tests__/authority.test.js +435 -32
  111. package/dist/hud/__tests__/authority.test.js.map +1 -1
  112. package/dist/hud/__tests__/hud-tmux-injection.test.js +2 -1
  113. package/dist/hud/__tests__/hud-tmux-injection.test.js.map +1 -1
  114. package/dist/hud/__tests__/index.test.js +42 -0
  115. package/dist/hud/__tests__/index.test.js.map +1 -1
  116. package/dist/hud/__tests__/reconcile.test.js +521 -15
  117. package/dist/hud/__tests__/reconcile.test.js.map +1 -1
  118. package/dist/hud/__tests__/render.test.js +61 -0
  119. package/dist/hud/__tests__/render.test.js.map +1 -1
  120. package/dist/hud/__tests__/state.test.js +132 -4
  121. package/dist/hud/__tests__/state.test.js.map +1 -1
  122. package/dist/hud/__tests__/tmux.test.js +180 -21
  123. package/dist/hud/__tests__/tmux.test.js.map +1 -1
  124. package/dist/hud/authority.d.ts +5 -0
  125. package/dist/hud/authority.d.ts.map +1 -1
  126. package/dist/hud/authority.js +324 -28
  127. package/dist/hud/authority.js.map +1 -1
  128. package/dist/hud/index.d.ts +3 -2
  129. package/dist/hud/index.d.ts.map +1 -1
  130. package/dist/hud/index.js +42 -19
  131. package/dist/hud/index.js.map +1 -1
  132. package/dist/hud/reconcile.d.ts +3 -3
  133. package/dist/hud/reconcile.d.ts.map +1 -1
  134. package/dist/hud/reconcile.js +128 -19
  135. package/dist/hud/reconcile.js.map +1 -1
  136. package/dist/hud/render.d.ts.map +1 -1
  137. package/dist/hud/render.js +35 -0
  138. package/dist/hud/render.js.map +1 -1
  139. package/dist/hud/state.d.ts.map +1 -1
  140. package/dist/hud/state.js +61 -62
  141. package/dist/hud/state.js.map +1 -1
  142. package/dist/hud/tmux.d.ts +24 -6
  143. package/dist/hud/tmux.d.ts.map +1 -1
  144. package/dist/hud/tmux.js +136 -38
  145. package/dist/hud/tmux.js.map +1 -1
  146. package/dist/hud/types.d.ts +11 -0
  147. package/dist/hud/types.d.ts.map +1 -1
  148. package/dist/hud/types.js.map +1 -1
  149. package/dist/mcp/__tests__/state-paths.test.js +71 -1
  150. package/dist/mcp/__tests__/state-paths.test.js.map +1 -1
  151. package/dist/mcp/state-paths.d.ts +32 -0
  152. package/dist/mcp/state-paths.d.ts.map +1 -1
  153. package/dist/mcp/state-paths.js +113 -17
  154. package/dist/mcp/state-paths.js.map +1 -1
  155. package/dist/mcp/state-server.d.ts +4 -4
  156. package/dist/scripts/__tests__/codex-native-hook.test.js +593 -11
  157. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  158. package/dist/scripts/__tests__/notify-state-io.test.js +72 -1
  159. package/dist/scripts/__tests__/notify-state-io.test.js.map +1 -1
  160. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts +2 -0
  161. package/dist/scripts/__tests__/notify-tmux-injection.test.d.ts.map +1 -0
  162. package/dist/scripts/__tests__/notify-tmux-injection.test.js +57 -0
  163. package/dist/scripts/__tests__/notify-tmux-injection.test.js.map +1 -0
  164. package/dist/scripts/__tests__/run-test-files.test.js +74 -0
  165. package/dist/scripts/__tests__/run-test-files.test.js.map +1 -1
  166. package/dist/scripts/__tests__/verify-native-agents.test.js +65 -0
  167. package/dist/scripts/__tests__/verify-native-agents.test.js.map +1 -1
  168. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  169. package/dist/scripts/codex-native-hook.js +88 -31
  170. package/dist/scripts/codex-native-hook.js.map +1 -1
  171. package/dist/scripts/eval/eval-parity-smoke.js +1 -1
  172. package/dist/scripts/eval/eval-parity-smoke.js.map +1 -1
  173. package/dist/scripts/notify-hook/auto-nudge.d.ts.map +1 -1
  174. package/dist/scripts/notify-hook/auto-nudge.js +3 -1
  175. package/dist/scripts/notify-hook/auto-nudge.js.map +1 -1
  176. package/dist/scripts/notify-hook/ralph-session-resume.d.ts.map +1 -1
  177. package/dist/scripts/notify-hook/ralph-session-resume.js +3 -10
  178. package/dist/scripts/notify-hook/ralph-session-resume.js.map +1 -1
  179. package/dist/scripts/notify-hook/state-io.d.ts.map +1 -1
  180. package/dist/scripts/notify-hook/state-io.js +62 -38
  181. package/dist/scripts/notify-hook/state-io.js.map +1 -1
  182. package/dist/scripts/notify-hook/team-leader-nudge.d.ts.map +1 -1
  183. package/dist/scripts/notify-hook/team-leader-nudge.js +7 -0
  184. package/dist/scripts/notify-hook/team-leader-nudge.js.map +1 -1
  185. package/dist/scripts/notify-hook/tmux-injection.d.ts +7 -0
  186. package/dist/scripts/notify-hook/tmux-injection.d.ts.map +1 -1
  187. package/dist/scripts/notify-hook/tmux-injection.js +24 -18
  188. package/dist/scripts/notify-hook/tmux-injection.js.map +1 -1
  189. package/dist/scripts/notify-hook.js +75 -11
  190. package/dist/scripts/notify-hook.js.map +1 -1
  191. package/dist/scripts/run-test-files.js +193 -22
  192. package/dist/scripts/run-test-files.js.map +1 -1
  193. package/dist/scripts/sync-plugin-mirror.d.ts.map +1 -1
  194. package/dist/scripts/sync-plugin-mirror.js +61 -3
  195. package/dist/scripts/sync-plugin-mirror.js.map +1 -1
  196. package/dist/scripts/verify-native-agents.d.ts.map +1 -1
  197. package/dist/scripts/verify-native-agents.js +58 -1
  198. package/dist/scripts/verify-native-agents.js.map +1 -1
  199. package/dist/state/__tests__/operations.test.js +113 -0
  200. package/dist/state/__tests__/operations.test.js.map +1 -1
  201. package/dist/state/__tests__/skill-active.test.js +3 -16
  202. package/dist/state/__tests__/skill-active.test.js.map +1 -1
  203. package/dist/state/__tests__/workflow-transition.test.js +25 -0
  204. package/dist/state/__tests__/workflow-transition.test.js.map +1 -1
  205. package/dist/state/operations.d.ts.map +1 -1
  206. package/dist/state/operations.js +57 -2
  207. package/dist/state/operations.js.map +1 -1
  208. package/dist/state/skill-active.d.ts.map +1 -1
  209. package/dist/state/skill-active.js +7 -39
  210. package/dist/state/skill-active.js.map +1 -1
  211. package/dist/state/workflow-transition-reconcile.d.ts.map +1 -1
  212. package/dist/state/workflow-transition-reconcile.js +10 -14
  213. package/dist/state/workflow-transition-reconcile.js.map +1 -1
  214. package/dist/team/__tests__/runtime.test.js +1 -1
  215. package/dist/team/__tests__/runtime.test.js.map +1 -1
  216. package/dist/team/__tests__/scaling.test.js +9 -4
  217. package/dist/team/__tests__/scaling.test.js.map +1 -1
  218. package/dist/team/__tests__/tmux-session.test.js +195 -2
  219. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  220. package/dist/team/__tests__/worker-runtime-identity.test.js +4 -2
  221. package/dist/team/__tests__/worker-runtime-identity.test.js.map +1 -1
  222. package/dist/team/scaling.d.ts.map +1 -1
  223. package/dist/team/scaling.js +3 -2
  224. package/dist/team/scaling.js.map +1 -1
  225. package/dist/team/tmux-session.d.ts +2 -0
  226. package/dist/team/tmux-session.d.ts.map +1 -1
  227. package/dist/team/tmux-session.js +142 -12
  228. package/dist/team/tmux-session.js.map +1 -1
  229. package/dist/verification/__tests__/ci-rust-gates.test.js +81 -1
  230. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  231. package/package.json +8 -8
  232. package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
  233. package/plugins/oh-my-codex/hooks/codex-native-hook.mjs +334 -21
  234. package/plugins/oh-my-codex/hooks/hooks.json +1 -2
  235. package/plugins/oh-my-codex/skills/autopilot/SKILL.md +3 -1
  236. package/plugins/oh-my-codex/skills/code-review/SKILL.md +7 -7
  237. package/plugins/oh-my-codex/skills/ralph/SKILL.md +22 -22
  238. package/plugins/oh-my-codex/skills/ultraqa/SKILL.md +9 -0
  239. package/skills/autopilot/SKILL.md +3 -1
  240. package/skills/code-review/SKILL.md +7 -7
  241. package/skills/ralph/SKILL.md +22 -22
  242. package/skills/ultraqa/SKILL.md +9 -0
  243. package/src/scripts/__tests__/codex-native-hook.test.ts +686 -13
  244. package/src/scripts/__tests__/notify-state-io.test.ts +95 -0
  245. package/src/scripts/__tests__/notify-tmux-injection.test.ts +82 -0
  246. package/src/scripts/__tests__/run-test-files.test.ts +102 -0
  247. package/src/scripts/__tests__/verify-native-agents.test.ts +75 -0
  248. package/src/scripts/codex-native-hook.ts +105 -28
  249. package/src/scripts/demo-team-e2e.sh +10 -7
  250. package/src/scripts/eval/eval-parity-smoke.ts +1 -1
  251. package/src/scripts/notify-hook/auto-nudge.ts +3 -1
  252. package/src/scripts/notify-hook/ralph-session-resume.ts +2 -8
  253. package/src/scripts/notify-hook/state-io.ts +75 -37
  254. package/src/scripts/notify-hook/team-leader-nudge.ts +7 -0
  255. package/src/scripts/notify-hook/tmux-injection.ts +35 -19
  256. package/src/scripts/notify-hook.ts +91 -4
  257. package/src/scripts/run-test-files.ts +192 -22
  258. package/src/scripts/sync-plugin-mirror.ts +98 -9
  259. package/src/scripts/verify-native-agents.ts +65 -1
@@ -1,7 +1,7 @@
1
1
  import { describe, it, mock } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { existsSync } from 'node:fs';
4
- import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { mkdtemp, mkdir, readFile, rm, symlink, writeFile } from 'node:fs/promises';
5
5
  import { tmpdir } from 'node:os';
6
6
  import { join } from 'node:path';
7
7
  import { detectKeywords, detectPrimaryKeyword, recordSkillActivation, DEEP_INTERVIEW_STATE_FILE, DEEP_INTERVIEW_BLOCKED_APPROVAL_INPUTS, DEEP_INTERVIEW_INPUT_LOCK_MESSAGE, persistDeepInterviewModeState, } from '../keyword-detector.js';
@@ -23,6 +23,54 @@ async function withIsolatedHome(prefix, run) {
23
23
  await rm(homeDir, { recursive: true, force: true });
24
24
  }
25
25
  }
26
+ const AUTOPILOT_TEST_NOW = '2026-05-30T00:00:00.000Z';
27
+ const AUTOPILOT_TEST_STARTED_AT = '2026-05-29T00:00:00.000Z';
28
+ const AUTOPILOT_TEST_UPDATED_AT = '2026-05-29T00:10:00.000Z';
29
+ async function writeActiveAutopilotSkillState(stateDir, sessionId, phase = 'ralplan') {
30
+ await mkdir(join(stateDir, 'sessions', sessionId), { recursive: true });
31
+ await writeFile(join(stateDir, 'sessions', sessionId, SKILL_ACTIVE_STATE_FILE), JSON.stringify({
32
+ version: 1,
33
+ active: true,
34
+ skill: 'autopilot',
35
+ keyword: '$autopilot',
36
+ phase,
37
+ activated_at: AUTOPILOT_TEST_STARTED_AT,
38
+ updated_at: AUTOPILOT_TEST_UPDATED_AT,
39
+ session_id: sessionId,
40
+ active_skills: [{ skill: 'autopilot', active: true, phase, session_id: sessionId }],
41
+ }, null, 2));
42
+ }
43
+ async function readAutopilotModeState(stateDir, sessionId) {
44
+ return JSON.parse(await readFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), 'utf-8'));
45
+ }
46
+ async function continueAutopilotTestState(stateDir, cwd, sessionId, suffix, text = 'continue') {
47
+ await recordSkillActivation({
48
+ stateDir,
49
+ sourceCwd: cwd,
50
+ text,
51
+ sessionId,
52
+ threadId: `thread-${suffix}`,
53
+ turnId: `turn-${suffix}`,
54
+ nowIso: AUTOPILOT_TEST_NOW,
55
+ });
56
+ }
57
+ async function assertAutopilotRecoverySnapshot(cwd, modeState, expectedPath, expectedReason) {
58
+ const snapshotPath = modeState.state?.handoff_artifacts?.context_snapshot_path ?? '';
59
+ if (typeof expectedPath === 'string')
60
+ assert.equal(snapshotPath, expectedPath);
61
+ else
62
+ assert.match(snapshotPath, expectedPath);
63
+ assert.equal(modeState.state?.handoff_artifacts?.context_snapshot?.kind, 'recovery');
64
+ assert.equal(modeState.state?.handoff_artifacts?.context_snapshot?.recovery?.reason, expectedReason);
65
+ assert.equal(modeState.state?.context_snapshot_recovery?.status, 'degraded');
66
+ assert.equal(modeState.state?.context_snapshot_recovery?.reason, expectedReason);
67
+ const recoverySnapshot = await readFile(join(cwd, snapshotPath), 'utf-8');
68
+ assert.match(recoverySnapshot, /recovery status: degraded/);
69
+ assert.match(recoverySnapshot, new RegExp(`recovery reason: ${expectedReason}`));
70
+ assert.match(recoverySnapshot, /do not treat the continuation input as the task seed/);
71
+ assert.doesNotMatch(recoverySnapshot, /task seed: continue/);
72
+ return snapshotPath;
73
+ }
26
74
  describe('keyword detector team compatibility', () => {
27
75
  it('keeps explicit $skill order in detectKeywords results (left-to-right)', () => {
28
76
  const matches = detectKeywords('$analyze $ultraqa $code-review now');
@@ -496,6 +544,12 @@ describe('keyword detector skill-active-state lifecycle', () => {
496
544
  rationale: 'Autopilot starts at the deep-interview gate by default; clear bounded tasks may skip only with an explicit persisted skip reason.',
497
545
  });
498
546
  assert.deepEqual(modeState.state.handoff_artifacts, {
547
+ context_snapshot_path: '.omx/context/please-run-and-keep-going-20260225T000000Z.md',
548
+ context_snapshot: {
549
+ path: '.omx/context/please-run-and-keep-going-20260225T000000Z.md',
550
+ kind: 'canonical',
551
+ original_task_status: 'activation-prompt',
552
+ },
499
553
  deep_interview: null,
500
554
  ralplan: null,
501
555
  ralplan_consensus_gate: {
@@ -514,6 +568,294 @@ describe('keyword detector skill-active-state lifecycle', () => {
514
568
  assert.equal(modeState.state.review_verdict, null);
515
569
  assert.equal(modeState.state.qa_verdict, null);
516
570
  assert.equal(modeState.state.return_to_ralplan_reason, null);
571
+ const snapshot = await readFile(join(cwd, '.omx', 'context', 'please-run-and-keep-going-20260225T000000Z.md'), 'utf-8');
572
+ assert.match(snapshot, /activation prompt \/ task seed: please run \$autopilot and keep going/);
573
+ assert.match(snapshot, /scope note: this seed captures the Autopilot activation prompt/);
574
+ }
575
+ finally {
576
+ await rm(cwd, { recursive: true, force: true });
577
+ }
578
+ });
579
+ it('migrates legacy Autopilot context snapshot paths into handoff artifacts', async () => {
580
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-legacy-context-'));
581
+ const stateDir = join(cwd, '.omx', 'state');
582
+ const sessionId = 'sess-autopilot-legacy-context';
583
+ try {
584
+ await writeActiveAutopilotSkillState(stateDir, sessionId, 'deep-interview');
585
+ await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
586
+ active: true,
587
+ mode: 'autopilot',
588
+ current_phase: 'deep-interview',
589
+ started_at: AUTOPILOT_TEST_STARTED_AT,
590
+ context_snapshot_path: '.omx/context/legacy-task-20260529T000000Z.md',
591
+ state: { handoff_artifacts: { deep_interview: null } },
592
+ }, null, 2));
593
+ await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
594
+ await writeFile(join(cwd, '.omx', 'context', 'legacy-task-20260529T000000Z.md'), '# legacy task');
595
+ await continueAutopilotTestState(stateDir, cwd, sessionId, 'legacy');
596
+ const modeState = await readAutopilotModeState(stateDir, sessionId);
597
+ assert.equal(modeState.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/legacy-task-20260529T000000Z.md');
598
+ assert.deepEqual(modeState.state?.handoff_artifacts?.context_snapshot, {
599
+ path: '.omx/context/legacy-task-20260529T000000Z.md',
600
+ kind: 'legacy',
601
+ original_task_status: 'legacy-unverified',
602
+ });
603
+ assert.equal(existsSync(join(cwd, '.omx', 'context', 'continue-20260530T000000Z.md')), false);
604
+ }
605
+ finally {
606
+ await rm(cwd, { recursive: true, force: true });
607
+ }
608
+ });
609
+ it('rejects unsafe legacy Autopilot context snapshot paths without writing outside .omx/context', async () => {
610
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-unsafe-context-'));
611
+ const stateDir = join(cwd, '.omx', 'state');
612
+ const sessionId = 'sess-autopilot-unsafe-context';
613
+ try {
614
+ await writeActiveAutopilotSkillState(stateDir, sessionId, 'deep-interview');
615
+ await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
616
+ active: true,
617
+ mode: 'autopilot',
618
+ current_phase: 'deep-interview',
619
+ started_at: AUTOPILOT_TEST_STARTED_AT,
620
+ context_snapshot_path: '.omx/context/../../escape.md',
621
+ state: { handoff_artifacts: { deep_interview: null } },
622
+ }, null, 2));
623
+ const result = await recordSkillActivation({
624
+ stateDir,
625
+ text: 'continue',
626
+ sessionId,
627
+ threadId: 'thread-unsafe',
628
+ turnId: 'turn-unsafe',
629
+ nowIso: '2026-05-30T00:00:00.000Z',
630
+ });
631
+ assert.ok(result);
632
+ assert.equal(existsSync(join(cwd, '.omx', 'escape.md')), false);
633
+ const modeState = await readAutopilotModeState(stateDir, sessionId);
634
+ assert.equal(modeState.context_snapshot_path, undefined);
635
+ await assertAutopilotRecoverySnapshot(cwd, modeState, '.omx/context/autopilot-recovery-20260530T000000Z.md', 'missing-or-unsafe-legacy-context-snapshot');
636
+ }
637
+ finally {
638
+ await rm(cwd, { recursive: true, force: true });
639
+ }
640
+ });
641
+ it('does not snapshot bare continuation text when active Autopilot mode state is corrupt', async () => {
642
+ const expectedReasons = {
643
+ 'missing-current-phase': 'nonpreservable-autopilot-mode-state-missing-current-phase',
644
+ 'malformed-json': 'malformed-autopilot-mode-state',
645
+ 'array-json': 'malformed-autopilot-mode-state',
646
+ };
647
+ for (const fixture of ['missing-current-phase', 'malformed-json', 'array-json']) {
648
+ const cwd = await mkdtemp(join(tmpdir(), `omx-keyword-autopilot-corrupt-continuation-${fixture}-`));
649
+ const stateDir = join(cwd, '.omx', 'state');
650
+ const sessionId = `sess-autopilot-corrupt-continuation-${fixture}`;
651
+ try {
652
+ await writeActiveAutopilotSkillState(stateDir, sessionId);
653
+ const modeStatePath = join(stateDir, 'sessions', sessionId, 'autopilot-state.json');
654
+ if (fixture === 'missing-current-phase') {
655
+ await writeFile(modeStatePath, JSON.stringify({
656
+ active: true,
657
+ mode: 'autopilot',
658
+ started_at: AUTOPILOT_TEST_STARTED_AT,
659
+ state: { handoff_artifacts: {} },
660
+ }, null, 2));
661
+ }
662
+ else if (fixture === 'malformed-json') {
663
+ await writeFile(modeStatePath, '{ "active": true, "mode": "autopilot",');
664
+ }
665
+ else {
666
+ await writeFile(modeStatePath, '[]');
667
+ }
668
+ await continueAutopilotTestState(stateDir, cwd, sessionId, fixture);
669
+ assert.equal(existsSync(join(cwd, '.omx', 'context', 'continue-20260530T000000Z.md')), false);
670
+ await assertAutopilotRecoverySnapshot(cwd, JSON.parse(await readFile(modeStatePath, 'utf-8')), /^\.omx\/context\/autopilot-recovery-20260530T000000Z(?:-\d+)?\.md$/, expectedReasons[fixture]);
671
+ }
672
+ finally {
673
+ await rm(cwd, { recursive: true, force: true });
674
+ }
675
+ }
676
+ });
677
+ it('rejects nested symlink Autopilot context snapshot candidates during reuse', async () => {
678
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-nested-symlink-context-'));
679
+ const outside = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-nested-symlink-outside-'));
680
+ const stateDir = join(cwd, '.omx', 'state');
681
+ const sessionId = 'sess-autopilot-nested-symlink-context';
682
+ try {
683
+ await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
684
+ await symlink(outside, join(cwd, '.omx', 'context', 'link'));
685
+ await writeFile(join(outside, 'exfil.md'), '# outside context');
686
+ await writeActiveAutopilotSkillState(stateDir, sessionId);
687
+ await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
688
+ active: true,
689
+ mode: 'autopilot',
690
+ current_phase: 'ralplan',
691
+ started_at: AUTOPILOT_TEST_STARTED_AT,
692
+ state: { handoff_artifacts: { context_snapshot_path: '.omx/context/link/exfil.md' } },
693
+ }, null, 2));
694
+ await continueAutopilotTestState(stateDir, cwd, sessionId, 'nested-symlink');
695
+ await assertAutopilotRecoverySnapshot(cwd, await readAutopilotModeState(stateDir, sessionId), '.omx/context/autopilot-recovery-20260530T000000Z.md', 'missing-or-unsafe-legacy-context-snapshot');
696
+ assert.equal(existsSync(join(outside, 'exfil.md')), true);
697
+ }
698
+ finally {
699
+ await rm(cwd, { recursive: true, force: true });
700
+ await rm(outside, { recursive: true, force: true });
701
+ }
702
+ });
703
+ it('rejects typed canonical Autopilot recovery snapshot candidates during reuse', async () => {
704
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-typed-recovery-context-'));
705
+ const stateDir = join(cwd, '.omx', 'state');
706
+ const sessionId = 'sess-autopilot-typed-recovery-context';
707
+ try {
708
+ await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
709
+ await writeFile(join(cwd, '.omx', 'context', 'autopilot-recovery-20260529T000000Z.md'), '# stale degraded recovery');
710
+ await writeActiveAutopilotSkillState(stateDir, sessionId);
711
+ await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
712
+ active: true,
713
+ mode: 'autopilot',
714
+ current_phase: 'ralplan',
715
+ started_at: AUTOPILOT_TEST_STARTED_AT,
716
+ state: {
717
+ handoff_artifacts: {
718
+ context_snapshot: {
719
+ path: '.omx/context/autopilot-recovery-20260529T000000Z.md',
720
+ kind: 'canonical',
721
+ },
722
+ },
723
+ },
724
+ }, null, 2));
725
+ await continueAutopilotTestState(stateDir, cwd, sessionId, 'typed-recovery');
726
+ await assertAutopilotRecoverySnapshot(cwd, await readAutopilotModeState(stateDir, sessionId), '.omx/context/autopilot-recovery-20260530T000000Z.md', 'missing-or-unsafe-legacy-context-snapshot');
727
+ assert.equal(existsSync(join(cwd, '.omx', 'context', 'autopilot-recovery-20260529T000000Z.md')), true);
728
+ }
729
+ finally {
730
+ await rm(cwd, { recursive: true, force: true });
731
+ }
732
+ });
733
+ it('rejects oversized Autopilot context snapshot candidates during reuse', async () => {
734
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-oversized-context-'));
735
+ const stateDir = join(cwd, '.omx', 'state');
736
+ const sessionId = 'sess-autopilot-oversized-context';
737
+ try {
738
+ await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
739
+ await writeFile(join(cwd, '.omx', 'context', 'oversized-legacy-20260529T000000Z.md'), 'x'.repeat((1024 * 1024) + 1));
740
+ await writeActiveAutopilotSkillState(stateDir, sessionId);
741
+ await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
742
+ active: true,
743
+ mode: 'autopilot',
744
+ current_phase: 'ralplan',
745
+ started_at: AUTOPILOT_TEST_STARTED_AT,
746
+ state: {
747
+ handoff_artifacts: {
748
+ context_snapshot_path: '.omx/context/oversized-legacy-20260529T000000Z.md',
749
+ },
750
+ },
751
+ }, null, 2));
752
+ await continueAutopilotTestState(stateDir, cwd, sessionId, 'oversized-context');
753
+ await assertAutopilotRecoverySnapshot(cwd, await readAutopilotModeState(stateDir, sessionId), '.omx/context/autopilot-recovery-20260530T000000Z.md', 'missing-or-unsafe-legacy-context-snapshot');
754
+ assert.equal(existsSync(join(cwd, '.omx', 'context', 'oversized-legacy-20260529T000000Z.md')), true);
755
+ }
756
+ finally {
757
+ await rm(cwd, { recursive: true, force: true });
758
+ }
759
+ });
760
+ it('does not promote degraded recovery snapshots to canonical context on reactivation', async () => {
761
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-recovery-reactivation-'));
762
+ const stateDir = join(cwd, '.omx', 'state');
763
+ const sessionId = 'sess-autopilot-recovery-reactivation';
764
+ try {
765
+ await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
766
+ await writeFile(join(cwd, '.omx', 'context', 'autopilot-recovery-20260529T000000Z.md'), '# degraded recovery');
767
+ await writeActiveAutopilotSkillState(stateDir, sessionId, 'complete');
768
+ await writeFile(join(stateDir, 'sessions', sessionId, 'autopilot-state.json'), JSON.stringify({
769
+ active: true,
770
+ mode: 'autopilot',
771
+ current_phase: 'complete',
772
+ completed_at: AUTOPILOT_TEST_UPDATED_AT,
773
+ state: {
774
+ handoff_artifacts: {
775
+ context_snapshot_path: '.omx/context/autopilot-recovery-20260529T000000Z.md',
776
+ context_snapshot: {
777
+ path: '.omx/context/autopilot-recovery-20260529T000000Z.md',
778
+ kind: 'recovery',
779
+ recovery: { status: 'degraded', reason: 'missing-or-unsafe-legacy-context-snapshot' },
780
+ },
781
+ },
782
+ context_snapshot_recovery: { status: 'degraded', reason: 'missing-or-unsafe-legacy-context-snapshot' },
783
+ },
784
+ }, null, 2));
785
+ await continueAutopilotTestState(stateDir, cwd, sessionId, 'recovery-reactivation', '$autopilot implement the real task');
786
+ const modeState = await readAutopilotModeState(stateDir, sessionId);
787
+ assert.equal(modeState.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/implement-the-real-task-20260530T000000Z.md');
788
+ assert.deepEqual(modeState.state?.handoff_artifacts?.context_snapshot, {
789
+ path: '.omx/context/implement-the-real-task-20260530T000000Z.md',
790
+ kind: 'canonical',
791
+ original_task_status: 'activation-prompt',
792
+ });
793
+ assert.equal(modeState.state?.context_snapshot_recovery, undefined);
794
+ const snapshot = await readFile(join(cwd, '.omx', 'context', 'implement-the-real-task-20260530T000000Z.md'), 'utf-8');
795
+ assert.match(snapshot, /activation prompt \/ task seed: \$autopilot implement the real task/);
796
+ }
797
+ finally {
798
+ await rm(cwd, { recursive: true, force: true });
799
+ }
800
+ });
801
+ it('does not follow symlinked Autopilot context directories when writing snapshots', async () => {
802
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-symlink-context-'));
803
+ const outside = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-symlink-outside-'));
804
+ const stateDir = join(cwd, '.omx', 'state');
805
+ try {
806
+ await mkdir(join(cwd, '.omx'), { recursive: true });
807
+ await symlink(outside, join(cwd, '.omx', 'context'));
808
+ await mkdir(stateDir, { recursive: true });
809
+ const warnings = [];
810
+ mock.method(console, 'warn', (...args) => {
811
+ warnings.push(args);
812
+ });
813
+ await recordSkillActivation({
814
+ stateDir,
815
+ sourceCwd: cwd,
816
+ text: '$autopilot symlink escape',
817
+ sessionId: 'sess-autopilot-symlink-context',
818
+ threadId: 'thread-symlink-context',
819
+ turnId: 'turn-symlink-context',
820
+ nowIso: '2026-05-30T00:00:00.000Z',
821
+ });
822
+ assert.equal(warnings.length, 1);
823
+ assert.match(String(warnings[0][1]), /symbolic link/);
824
+ assert.equal(existsSync(join(outside, 'symlink-escape-20260530T000000Z.md')), false);
825
+ assert.equal(existsSync(join(stateDir, 'sessions', 'sess-autopilot-symlink-context', 'autopilot-state.json')), false);
826
+ }
827
+ finally {
828
+ await rm(cwd, { recursive: true, force: true });
829
+ await rm(outside, { recursive: true, force: true });
830
+ }
831
+ });
832
+ it('allocates unique Autopilot context snapshot paths for same-second matching slugs', async () => {
833
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-autopilot-context-collision-'));
834
+ const stateDir = join(cwd, '.omx', 'state');
835
+ try {
836
+ await mkdir(stateDir, { recursive: true });
837
+ await recordSkillActivation({
838
+ stateDir,
839
+ sourceCwd: cwd,
840
+ text: '$autopilot same task',
841
+ sessionId: 'sess-autopilot-collision-a',
842
+ threadId: 'thread-collision',
843
+ turnId: 'turn-collision-a',
844
+ nowIso: '2026-05-30T00:00:00.000Z',
845
+ });
846
+ await recordSkillActivation({
847
+ stateDir,
848
+ sourceCwd: cwd,
849
+ text: '$autopilot same task',
850
+ sessionId: 'sess-autopilot-collision-b',
851
+ threadId: 'thread-collision',
852
+ turnId: 'turn-collision-b',
853
+ nowIso: '2026-05-30T00:00:00.000Z',
854
+ });
855
+ const first = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot-collision-a', 'autopilot-state.json'), 'utf-8'));
856
+ const second = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot-collision-b', 'autopilot-state.json'), 'utf-8'));
857
+ assert.equal(first.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/same-task-20260530T000000Z.md');
858
+ assert.equal(second.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/same-task-20260530T000000Z-2.md');
517
859
  }
518
860
  finally {
519
861
  await rm(cwd, { recursive: true, force: true });
@@ -589,6 +931,12 @@ describe('keyword detector skill-active-state lifecycle', () => {
589
931
  assert.equal(modeState.run_outcome, undefined);
590
932
  assert.equal(modeState.handoff_artifacts, undefined);
591
933
  assert.deepEqual(modeState.state?.handoff_artifacts, {
934
+ context_snapshot_path: '.omx/context/investigate-the-next-issue-20260530T000000Z.md',
935
+ context_snapshot: {
936
+ path: '.omx/context/investigate-the-next-issue-20260530T000000Z.md',
937
+ kind: 'canonical',
938
+ original_task_status: 'activation-prompt',
939
+ },
592
940
  deep_interview: null,
593
941
  ralplan: null,
594
942
  ralplan_consensus_gate: {
@@ -1167,6 +1515,72 @@ describe('keyword detector skill-active-state lifecycle', () => {
1167
1515
  await rm(cwd, { recursive: true, force: true });
1168
1516
  }
1169
1517
  });
1518
+ it('does not activate team state when persisted Team mode is disabled', async () => {
1519
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-disabled-'));
1520
+ const stateDir = join(cwd, '.omx', 'state');
1521
+ try {
1522
+ await mkdir(join(cwd, '.omx'), { recursive: true });
1523
+ await mkdir(stateDir, { recursive: true });
1524
+ await writeFile(join(cwd, '.omx', 'setup-scope.json'), JSON.stringify({ scope: 'project', teamMode: 'disabled' }, null, 2));
1525
+ const result = await recordSkillActivation({
1526
+ stateDir,
1527
+ text: '$team coordinate the hotfix',
1528
+ sessionId: 'sess-team-disabled',
1529
+ nowIso: '2026-04-08T00:00:00.000Z',
1530
+ });
1531
+ assert.equal(result, null);
1532
+ assert.equal(existsSync(join(stateDir, 'team-state.json')), false);
1533
+ assert.equal(existsSync(join(stateDir, 'sessions', 'sess-team-disabled', SKILL_ACTIVE_STATE_FILE)), false);
1534
+ }
1535
+ finally {
1536
+ await rm(cwd, { recursive: true, force: true });
1537
+ }
1538
+ });
1539
+ it('ignores disabled Team when selecting the primary workflow', async () => {
1540
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-disabled-primary-'));
1541
+ const stateDir = join(cwd, '.omx', 'state');
1542
+ try {
1543
+ await mkdir(join(cwd, '.omx'), { recursive: true });
1544
+ await mkdir(stateDir, { recursive: true });
1545
+ await writeFile(join(cwd, '.omx', 'setup-scope.json'), JSON.stringify({ scope: 'project', teamMode: 'disabled' }, null, 2));
1546
+ const result = await recordSkillActivation({
1547
+ stateDir,
1548
+ text: '$team $ralph ship this fix',
1549
+ sessionId: 'sess-team-disabled-primary',
1550
+ nowIso: '2026-04-10T01:00:00.000Z',
1551
+ });
1552
+ assert.equal(result?.skill, 'ralph');
1553
+ assert.deepEqual(result?.active_skills?.map((entry) => entry.skill), ['ralph']);
1554
+ assert.equal(existsSync(join(stateDir, 'team-state.json')), false);
1555
+ assert.equal(existsSync(join(stateDir, 'sessions', 'sess-team-disabled-primary', 'ralph-state.json')), true);
1556
+ }
1557
+ finally {
1558
+ await rm(cwd, { recursive: true, force: true });
1559
+ }
1560
+ });
1561
+ it('filters deferred team handoffs when persisted Team mode is disabled', async () => {
1562
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-disabled-deferred-'));
1563
+ const stateDir = join(cwd, '.omx', 'state');
1564
+ try {
1565
+ await mkdir(join(cwd, '.omx'), { recursive: true });
1566
+ await mkdir(stateDir, { recursive: true });
1567
+ await writeFile(join(cwd, '.omx', 'setup-scope.json'), JSON.stringify({ scope: 'project', teamMode: 'disabled' }, null, 2));
1568
+ const result = await recordSkillActivation({
1569
+ stateDir,
1570
+ text: '$ralplan $team $ralph ship this fix',
1571
+ sessionId: 'sess-team-disabled-deferred',
1572
+ nowIso: '2026-04-10T00:00:00.000Z',
1573
+ });
1574
+ assert.equal(result?.skill, 'ralplan');
1575
+ assert.deepEqual(result?.active_skills?.map((entry) => entry.skill), ['ralplan']);
1576
+ assert.deepEqual(result?.deferred_skills, ['ralph']);
1577
+ assert.equal(existsSync(join(stateDir, 'team-state.json')), false);
1578
+ assert.equal(existsSync(join(stateDir, 'sessions', 'sess-team-disabled-deferred', 'team-state.json')), false);
1579
+ }
1580
+ finally {
1581
+ await rm(cwd, { recursive: true, force: true });
1582
+ }
1583
+ });
1170
1584
  it('preserves active team root state when $team is re-entered from prompt routing', async () => {
1171
1585
  const cwd = await mkdtemp(join(tmpdir(), 'omx-keyword-state-team-preserve-'));
1172
1586
  const stateDir = join(cwd, '.omx', 'state');
@@ -2084,6 +2498,8 @@ deepMaxRounds = 21
2084
2498
  session_id: 'sess-autopilot',
2085
2499
  state: { context_snapshot_path: '.omx/context/existing.md' },
2086
2500
  }));
2501
+ await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
2502
+ await writeFile(join(cwd, '.omx', 'context', 'existing.md'), '# existing context');
2087
2503
  const result = await recordSkillActivation({
2088
2504
  stateDir,
2089
2505
  text: 'autopilot keep going',
@@ -2097,7 +2513,8 @@ deepMaxRounds = 21
2097
2513
  const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot', 'autopilot-state.json'), 'utf-8'));
2098
2514
  assert.equal(modeState.current_phase, 'code-review');
2099
2515
  assert.equal(modeState.started_at, '2026-02-25T00:00:00.000Z');
2100
- assert.equal(modeState.state?.context_snapshot_path, '.omx/context/existing.md');
2516
+ assert.equal(modeState.state?.context_snapshot_path, undefined);
2517
+ assert.equal(modeState.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/existing.md');
2101
2518
  }
2102
2519
  finally {
2103
2520
  await rm(cwd, { recursive: true, force: true });
@@ -2267,6 +2684,8 @@ deepMaxRounds = 21
2267
2684
  session_id: 'sess-autopilot-bare',
2268
2685
  state: { context_snapshot_path: '.omx/context/autopilot.md' },
2269
2686
  }, null, 2));
2687
+ await mkdir(join(cwd, '.omx', 'context'), { recursive: true });
2688
+ await writeFile(join(cwd, '.omx', 'context', 'autopilot.md'), '# autopilot context');
2270
2689
  const result = await recordSkillActivation({
2271
2690
  stateDir,
2272
2691
  text: '\\ keep going now',
@@ -2279,7 +2698,8 @@ deepMaxRounds = 21
2279
2698
  assert.equal(result.transition_error, undefined);
2280
2699
  const modeState = JSON.parse(await readFile(join(stateDir, 'sessions', 'sess-autopilot-bare', 'autopilot-state.json'), 'utf-8'));
2281
2700
  assert.equal(modeState.current_phase, 'code-review');
2282
- assert.equal(modeState.state?.context_snapshot_path, '.omx/context/autopilot.md');
2701
+ assert.equal(modeState.state?.context_snapshot_path, undefined);
2702
+ assert.equal(modeState.state?.handoff_artifacts?.context_snapshot_path, '.omx/context/autopilot.md');
2283
2703
  assert.equal(existsSync(join(stateDir, 'sessions', 'sess-autopilot-bare', 'ralph-state.json')), false);
2284
2704
  }
2285
2705
  finally {