oh-my-codex 0.21.39 → 0.21.40

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.
@@ -1,6 +1,7 @@
1
1
  import { describe, it } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import { spawn } from 'node:child_process';
4
+ import { createHash } from 'node:crypto';
4
5
  import { once } from 'node:events';
5
6
  import { existsSync } from 'node:fs';
6
7
  import { link, lstat, mkdir, mkdtemp, readFile, readdir, readlink, rename, rm, symlink, writeFile } from 'node:fs/promises';
@@ -68,6 +69,38 @@ async function withOwnerEnvironment(sessionId, run) {
68
69
  process.env.OMX_SESSION_ID = previous;
69
70
  }
70
71
  }
72
+ async function withEnvironment(values, run) {
73
+ const previous = new Map(Object.keys(values).map((key) => [key, process.env[key]]));
74
+ for (const [key, value] of Object.entries(values)) {
75
+ if (value === undefined)
76
+ delete process.env[key];
77
+ else
78
+ process.env[key] = value;
79
+ }
80
+ try {
81
+ await run();
82
+ }
83
+ finally {
84
+ for (const [key, value] of previous) {
85
+ if (value === undefined)
86
+ delete process.env[key];
87
+ else
88
+ process.env[key] = value;
89
+ }
90
+ }
91
+ }
92
+ const MANAGED_CODEX_DAEMON_CMDLINE = '/home/test/.codex/packages/app-server/bin/codex app-server --remote-control --listen unix:// --managed-daemon';
93
+ function linuxProcessObservation(cmdline, birth) {
94
+ return {
95
+ kind: 'identity',
96
+ cmdline,
97
+ identity: {
98
+ platform: 'linux',
99
+ birth,
100
+ cmdline_hash: createHash('sha256').update(cmdline).digest('hex'),
101
+ },
102
+ };
103
+ }
71
104
  /**
72
105
  * Identity fields for a pointer that belongs to the HOST platform.
73
106
  *
@@ -774,7 +807,7 @@ describe('isSessionStale', () => {
774
807
  }), false);
775
808
  });
776
809
  });
777
- describe('verified-dead selected session pointer recovery', { concurrency: false }, () => {
810
+ describe('selected session pointer recovery', { concurrency: false }, () => {
778
811
  async function writePointer(cwd, overrides = {}) {
779
812
  const context = resolveSessionPointerContext(cwd);
780
813
  await mkdir(context.baseStateDir, { recursive: true });
@@ -789,6 +822,152 @@ describe('verified-dead selected session pointer recovery', { concurrency: false
789
822
  await writeFile(context.sessionPath, body, 'utf-8');
790
823
  return { path: context.sessionPath, body };
791
824
  }
825
+ async function writeManagedDaemonPointer(cwd) {
826
+ const context = resolveSessionPointerContext(cwd);
827
+ await mkdir(context.baseStateDir, { recursive: true });
828
+ const body = JSON.stringify({
829
+ session_id: '01a18453-9e49-7c21-9c51-cf411286e627',
830
+ native_session_id: '01a18453-9e49-7c21-9c51-cf411286e627',
831
+ started_at: '2026-10-28T23:03:37.205Z',
832
+ cwd,
833
+ state_root: context.baseStateDir,
834
+ pid: 57617,
835
+ platform: 'linux',
836
+ identity_schema_version: 2,
837
+ process_identity: linuxProcessObservation(MANAGED_CODEX_DAEMON_CMDLINE, '87768').identity,
838
+ pid_start_ticks: 87768,
839
+ pid_cmdline: MANAGED_CODEX_DAEMON_CMDLINE,
840
+ });
841
+ await writeFile(context.sessionPath, body, 'utf-8');
842
+ return { path: context.sessionPath, body };
843
+ }
844
+ it('lets resume replace a live managed-daemon owner without inheriting a stale OMX selector', async () => {
845
+ const root = await mkdtemp(join(tmpdir(), 'omx-session-pointer-daemon-resume-'));
846
+ const cwd = join(root, 'workspace');
847
+ const home = join(root, 'home');
848
+ const codexHome = join(root, 'codex-home');
849
+ const omxRoot = join(root, 'omx-root');
850
+ await mkdir(cwd, { recursive: true });
851
+ try {
852
+ await withEnvironment({
853
+ HOME: home,
854
+ CODEX_HOME: codexHome,
855
+ OMX_ROOT: omxRoot,
856
+ OMX_SESSION_ID: 'omx-inherited-stale',
857
+ }, async () => {
858
+ const pointer = await writeManagedDaemonPointer(cwd);
859
+ await withPointerDependencies({
860
+ runtimePlatform: 'linux',
861
+ probePid: () => 'alive',
862
+ observeProcess: () => linuxProcessObservation(MANAGED_CODEX_DAEMON_CMDLINE, '87768'),
863
+ }, async () => {
864
+ const context = resolveSessionPointerContext(cwd);
865
+ assert.equal((await readSessionPointer(context)).status, 'non-runtime-owner');
866
+ const established = await establishLaunchSessionBinding(cwd, 'omx-resume-after-daemon');
867
+ assert.equal(established.kind, 'committed-released');
868
+ if (established.kind !== 'committed-released')
869
+ return;
870
+ const resumedState = await readSessionState(cwd);
871
+ assert.equal(resumedState?.session_id, 'omx-resume-after-daemon');
872
+ assert.equal(resumedState?.owner_omx_session_id, undefined, 'unverified inherited OMX_SESSION_ID must not become owner authority');
873
+ assert.equal(await closeLaunchSessionBindingOnce(established.binding).then((result) => result.status), 'closed');
874
+ });
875
+ assert.notEqual(await readFile(pointer.path, 'utf-8'), pointer.body);
876
+ });
877
+ }
878
+ finally {
879
+ await rm(root, { recursive: true, force: true });
880
+ }
881
+ });
882
+ it('quarantines a live managed-daemon pointer and permits a subsequent launch without stopping the daemon', async () => {
883
+ const root = await mkdtemp(join(tmpdir(), 'omx-session-pointer-daemon-recover-'));
884
+ const cwd = join(root, 'workspace');
885
+ await mkdir(cwd, { recursive: true });
886
+ try {
887
+ await withEnvironment({
888
+ HOME: join(root, 'home'),
889
+ CODEX_HOME: join(root, 'codex-home'),
890
+ OMX_ROOT: join(root, 'omx-root'),
891
+ OMX_SESSION_ID: 'omx-inherited-stale',
892
+ }, async () => {
893
+ const pointer = await writeManagedDaemonPointer(cwd);
894
+ await withPointerDependencies({
895
+ runtimePlatform: 'linux',
896
+ probePid: () => 'alive',
897
+ observeProcess: () => linuxProcessObservation(MANAGED_CODEX_DAEMON_CMDLINE, '87768'),
898
+ token: () => SUCCESSOR_TOKEN,
899
+ }, async () => {
900
+ const recovered = await recoverDeadSessionPointer(cwd);
901
+ assert.equal(recovered.status, 'recovered', recovered.reason);
902
+ assert.equal(recovered.recovered, true);
903
+ assert.match(recovered.reason, /managed Codex daemon/);
904
+ assert.ok(recovered.quarantinePath);
905
+ assert.equal(await readFile(recovered.quarantinePath, 'utf-8'), pointer.body);
906
+ assert.equal((await readSessionPointer(resolveSessionPointerContext(cwd))).status, 'absent');
907
+ const established = await establishLaunchSessionBinding(cwd, 'omx-resume-after-recovery');
908
+ assert.equal(established.kind, 'committed-released');
909
+ if (established.kind === 'committed-released') {
910
+ assert.equal(await closeLaunchSessionBindingOnce(established.binding).then((result) => result.status), 'closed');
911
+ }
912
+ });
913
+ });
914
+ }
915
+ finally {
916
+ await rm(root, { recursive: true, force: true });
917
+ }
918
+ });
919
+ it('continues refusing recovery and a fresh launch for a live interactive OMX owner', async () => {
920
+ const root = await mkdtemp(join(tmpdir(), 'omx-session-pointer-interactive-owner-'));
921
+ const cwd = join(root, 'workspace');
922
+ const interactiveCmdline = '/home/test/.local/bin/codex resume active-conversation';
923
+ const interactiveObservation = linuxProcessObservation(interactiveCmdline, '99123');
924
+ await mkdir(cwd, { recursive: true });
925
+ try {
926
+ await withEnvironment({
927
+ HOME: join(root, 'home'),
928
+ CODEX_HOME: join(root, 'codex-home'),
929
+ OMX_ROOT: join(root, 'omx-root'),
930
+ OMX_SESSION_ID: undefined,
931
+ }, async () => {
932
+ const context = resolveSessionPointerContext(cwd);
933
+ await mkdir(context.baseStateDir, { recursive: true });
934
+ const pointerBody = JSON.stringify({
935
+ session_id: 'omx-live-interactive',
936
+ owner_omx_session_id: 'omx-live-interactive',
937
+ launch_lineage_token: TEST_TOKEN,
938
+ started_at: '2026-10-28T23:03:37.205Z',
939
+ cwd,
940
+ state_root: context.baseStateDir,
941
+ pid: 57618,
942
+ platform: 'linux',
943
+ identity_schema_version: 2,
944
+ process_identity: interactiveObservation.identity,
945
+ pid_start_ticks: 99123,
946
+ pid_cmdline: interactiveCmdline,
947
+ });
948
+ await writeFile(context.sessionPath, pointerBody, 'utf-8');
949
+ await withPointerDependencies({
950
+ runtimePlatform: 'linux',
951
+ probePid: () => 'alive',
952
+ observeProcess: () => interactiveObservation,
953
+ }, async () => {
954
+ const established = await establishLaunchSessionBinding(cwd, 'omx-competing-launch');
955
+ assert.equal(established.kind, 'precommit-aborted');
956
+ if (established.kind === 'precommit-aborted')
957
+ assert.equal(established.abort.code, 'session_pointer_owner_conflict');
958
+ const refused = await recoverDeadSessionPointer(cwd);
959
+ assert.equal(refused.status, 'usable');
960
+ assert.equal(refused.recovered, false);
961
+ assert.equal(refused.action, 'none');
962
+ assert.match(refused.reason, /usable or live/);
963
+ });
964
+ assert.equal(await readFile(context.sessionPath, 'utf-8'), pointerBody);
965
+ });
966
+ }
967
+ finally {
968
+ await rm(root, { recursive: true, force: true });
969
+ }
970
+ });
792
971
  it('quarantines exact dead evidence, is idempotent, and permits relaunch', async () => {
793
972
  const cwd = await mkdtemp(join(tmpdir(), 'omx-session-pointer-recover-dead-'));
794
973
  try {
@@ -3451,6 +3630,137 @@ describe('session pointer transaction', () => {
3451
3630
  await rm(cwd, { recursive: true, force: true });
3452
3631
  }
3453
3632
  });
3633
+ it('preserves managed-daemon owner sidecars without publishing them as canonical runtime owners', async () => {
3634
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-session-daemon-owner-sidecar-'));
3635
+ const nativeSessionId = 'native-daemon-sidecar';
3636
+ const observation = linuxProcessObservation(MANAGED_CODEX_DAEMON_CMDLINE, '87768');
3637
+ try {
3638
+ const context = resolveSessionPointerContext(cwd);
3639
+ const sidecarDir = join(context.baseStateDir, 'sessions', nativeSessionId);
3640
+ const sidecarPath = join(sidecarDir, 'session-owner.json');
3641
+ await mkdir(sidecarDir, { recursive: true });
3642
+ await writeFile(sidecarPath, JSON.stringify({
3643
+ session_id: nativeSessionId,
3644
+ native_session_id: nativeSessionId,
3645
+ started_at: '2026-10-28T23:03:37.205Z',
3646
+ cwd,
3647
+ state_root: context.baseStateDir,
3648
+ pid: 57617,
3649
+ platform: 'linux',
3650
+ identity_schema_version: 2,
3651
+ process_identity: observation.identity,
3652
+ pid_start_ticks: 87768,
3653
+ pid_cmdline: MANAGED_CODEX_DAEMON_CMDLINE,
3654
+ }), 'utf-8');
3655
+ await withPointerDependencies({
3656
+ runtimePlatform: 'linux',
3657
+ probePid: () => 'alive',
3658
+ observeProcess: () => observation,
3659
+ }, async () => {
3660
+ assert.equal((await readNativeSessionOwnerEvidence(cwd, nativeSessionId)).status, 'usable');
3661
+ const sidecarBefore = await readFile(sidecarPath, 'utf-8');
3662
+ const owner = await writeNativeSessionOwner(cwd, nativeSessionId, {
3663
+ pid: 57617,
3664
+ platform: 'linux',
3665
+ });
3666
+ assert.equal(owner.session_id, nativeSessionId);
3667
+ const sidecarAfterOwnerWrite = await readFile(sidecarPath, 'utf-8');
3668
+ assert.equal(JSON.parse(sidecarAfterOwnerWrite).session_id, nativeSessionId);
3669
+ await assert.rejects(reconcileNativeSessionStart(cwd, nativeSessionId, {
3670
+ pid: 57617,
3671
+ platform: 'linux',
3672
+ }), (error) => isSessionPointerLaunchAbort(error)
3673
+ && error.code === 'session_pointer_owner_conflict');
3674
+ assert.equal((await readSessionPointer(context)).status, 'absent');
3675
+ assert.equal(await readFile(sidecarPath, 'utf-8'), sidecarAfterOwnerWrite);
3676
+ assert.notEqual(sidecarAfterOwnerWrite, sidecarBefore);
3677
+ });
3678
+ }
3679
+ finally {
3680
+ await rm(cwd, { recursive: true, force: true });
3681
+ }
3682
+ });
3683
+ it('P1: rejects SessionStart republishing a managed daemon to the canonical pointer', async () => {
3684
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-session-daemon-canonical-republish-'));
3685
+ const nativeSessionId = 'native-daemon-codex';
3686
+ const daemonObservation = linuxProcessObservation(MANAGED_CODEX_DAEMON_CMDLINE, '87768');
3687
+ try {
3688
+ const context = resolveSessionPointerContext(cwd);
3689
+ // Setup: Create canonical pointer with managed daemon owner (non-runtime-owner status)
3690
+ const pointerPath = join(context.baseStateDir, 'session.json');
3691
+ await mkdir(dirname(pointerPath), { recursive: true });
3692
+ const initialDaemonState = {
3693
+ session_id: 'omx-session-from-daemon',
3694
+ native_session_id: nativeSessionId,
3695
+ started_at: '2026-10-28T23:00:00.000Z',
3696
+ cwd,
3697
+ state_root: context.baseStateDir,
3698
+ pid: 57617,
3699
+ platform: 'linux',
3700
+ identity_schema_version: 2,
3701
+ process_identity: daemonObservation.identity,
3702
+ pid_start_ticks: 87768,
3703
+ pid_cmdline: MANAGED_CODEX_DAEMON_CMDLINE,
3704
+ };
3705
+ await writeFile(pointerPath, JSON.stringify(initialDaemonState), 'utf-8');
3706
+ await withPointerDependencies({
3707
+ runtimePlatform: 'linux',
3708
+ probePid: () => 'alive',
3709
+ observeProcess: () => daemonObservation,
3710
+ }, async () => {
3711
+ // Verify the canonical pointer is classified as non-runtime-owner
3712
+ const canonicalPointerStatus = (await readSessionPointer(context)).status;
3713
+ assert.equal(canonicalPointerStatus, 'non-runtime-owner', 'canonical pointer should be non-runtime-owner before SessionStart');
3714
+ await assert.rejects(reconcileNativeSessionStart(cwd, nativeSessionId, {
3715
+ pid: 57617,
3716
+ platform: 'linux',
3717
+ }), (error) => isSessionPointerLaunchAbort(error)
3718
+ && error.code === 'session_pointer_unusable'
3719
+ && error.pointerStatus === 'non-runtime-owner');
3720
+ assert.equal(await readFile(pointerPath, 'utf-8'), JSON.stringify(initialDaemonState));
3721
+ assert.equal((await readSessionPointer(context)).status, 'non-runtime-owner');
3722
+ });
3723
+ }
3724
+ finally {
3725
+ await rm(cwd, { recursive: true, force: true });
3726
+ }
3727
+ });
3728
+ it('retains the OMX owner when managed-daemon SessionStart reconciles a launched pointer', async () => {
3729
+ const cwd = await mkdtemp(join(tmpdir(), 'omx-session-daemon-reconcile-launch-owner-'));
3730
+ const ownerPid = 57616;
3731
+ const daemonPid = 57617;
3732
+ const ownerObservation = linuxProcessObservation('/usr/local/bin/omx', '87767');
3733
+ const daemonObservation = linuxProcessObservation(MANAGED_CODEX_DAEMON_CMDLINE, '87768');
3734
+ try {
3735
+ const context = resolveSessionPointerContext(cwd);
3736
+ await withPointerDependencies({
3737
+ runtimePlatform: 'linux',
3738
+ probePid: () => 'alive',
3739
+ observeProcess: (pid) => pid === daemonPid ? daemonObservation : ownerObservation,
3740
+ }, async () => {
3741
+ const launched = await writeSessionStart(cwd, 'omx-launch-owner', {
3742
+ pid: ownerPid,
3743
+ platform: 'linux',
3744
+ });
3745
+ const reconciled = await reconcileNativeSessionStart(cwd, 'native-daemon-reconcile', {
3746
+ pid: daemonPid,
3747
+ platform: 'linux',
3748
+ });
3749
+ assert.equal(reconciled.session_id, launched.session_id);
3750
+ assert.equal(reconciled.native_session_id, 'native-daemon-reconcile');
3751
+ assert.equal(reconciled.pid, ownerPid);
3752
+ assert.equal(reconciled.process_identity?.birth, ownerObservation.identity.birth);
3753
+ assert.notEqual(reconciled.process_identity?.cmdline_hash, daemonObservation.identity.cmdline_hash);
3754
+ const pointerAfter = await readSessionPointer(context);
3755
+ assert.equal(pointerAfter.status, 'usable');
3756
+ assert.equal(pointerAfter.state?.pid, ownerPid);
3757
+ assert.equal(pointerAfter.state?.native_session_id, 'native-daemon-reconcile');
3758
+ });
3759
+ }
3760
+ finally {
3761
+ await rm(cwd, { recursive: true, force: true });
3762
+ }
3763
+ });
3454
3764
  it('rejects a symlinked native owner state root', async () => {
3455
3765
  const cwd = await mkdtemp(join(tmpdir(), 'omx-session-owner-root-symlink-'));
3456
3766
  try {