oh-my-codex 0.12.3 → 0.12.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 (176) hide show
  1. package/Cargo.lock +5 -5
  2. package/Cargo.toml +1 -1
  3. package/README.md +2 -0
  4. package/dist/cli/__tests__/index.test.js +73 -12
  5. package/dist/cli/__tests__/index.test.js.map +1 -1
  6. package/dist/cli/__tests__/launch-fallback.test.js +8 -27
  7. package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
  8. package/dist/cli/__tests__/mcp-parity.test.d.ts +2 -0
  9. package/dist/cli/__tests__/mcp-parity.test.d.ts.map +1 -0
  10. package/dist/cli/__tests__/mcp-parity.test.js +111 -0
  11. package/dist/cli/__tests__/mcp-parity.test.js.map +1 -0
  12. package/dist/cli/__tests__/nested-help-routing.test.js +13 -0
  13. package/dist/cli/__tests__/nested-help-routing.test.js.map +1 -1
  14. package/dist/cli/__tests__/package-bin-contract.test.js +6 -1
  15. package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
  16. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.d.ts +2 -0
  17. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.d.ts.map +1 -0
  18. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js +189 -0
  19. package/dist/cli/__tests__/setup-hooks-shared-ownership.test.js.map +1 -0
  20. package/dist/cli/__tests__/setup-scope.test.js +48 -0
  21. package/dist/cli/__tests__/setup-scope.test.js.map +1 -1
  22. package/dist/cli/__tests__/state.test.d.ts +2 -0
  23. package/dist/cli/__tests__/state.test.d.ts.map +1 -0
  24. package/dist/cli/__tests__/state.test.js +46 -0
  25. package/dist/cli/__tests__/state.test.js.map +1 -0
  26. package/dist/cli/__tests__/team.test.js +238 -2
  27. package/dist/cli/__tests__/team.test.js.map +1 -1
  28. package/dist/cli/__tests__/uninstall.test.js +37 -2
  29. package/dist/cli/__tests__/uninstall.test.js.map +1 -1
  30. package/dist/cli/index.d.ts +6 -13
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli/index.js +47 -60
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/mcp-parity.d.ts +22 -0
  35. package/dist/cli/mcp-parity.d.ts.map +1 -0
  36. package/dist/cli/mcp-parity.js +227 -0
  37. package/dist/cli/mcp-parity.js.map +1 -0
  38. package/dist/cli/setup.d.ts.map +1 -1
  39. package/dist/cli/setup.js +5 -2
  40. package/dist/cli/setup.js.map +1 -1
  41. package/dist/cli/state.d.ts +8 -0
  42. package/dist/cli/state.d.ts.map +1 -0
  43. package/dist/cli/state.js +71 -0
  44. package/dist/cli/state.js.map +1 -0
  45. package/dist/cli/team.d.ts.map +1 -1
  46. package/dist/cli/team.js +6 -5
  47. package/dist/cli/team.js.map +1 -1
  48. package/dist/cli/uninstall.d.ts.map +1 -1
  49. package/dist/cli/uninstall.js +18 -4
  50. package/dist/cli/uninstall.js.map +1 -1
  51. package/dist/config/__tests__/codex-hooks.test.d.ts +2 -0
  52. package/dist/config/__tests__/codex-hooks.test.d.ts.map +1 -0
  53. package/dist/config/__tests__/codex-hooks.test.js +53 -0
  54. package/dist/config/__tests__/codex-hooks.test.js.map +1 -0
  55. package/dist/config/codex-hooks.d.ts +16 -7
  56. package/dist/config/codex-hooks.d.ts.map +1 -1
  57. package/dist/config/codex-hooks.js +134 -2
  58. package/dist/config/codex-hooks.js.map +1 -1
  59. package/dist/hooks/__tests__/keyword-detector.test.js +6 -0
  60. package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
  61. package/dist/hooks/keyword-detector.d.ts.map +1 -1
  62. package/dist/hooks/keyword-detector.js +6 -0
  63. package/dist/hooks/keyword-detector.js.map +1 -1
  64. package/dist/hud/__tests__/reconcile.test.d.ts +2 -0
  65. package/dist/hud/__tests__/reconcile.test.d.ts.map +1 -0
  66. package/dist/hud/__tests__/reconcile.test.js +83 -0
  67. package/dist/hud/__tests__/reconcile.test.js.map +1 -0
  68. package/dist/hud/__tests__/render.test.js +43 -0
  69. package/dist/hud/__tests__/render.test.js.map +1 -1
  70. package/dist/hud/constants.d.ts +2 -1
  71. package/dist/hud/constants.d.ts.map +1 -1
  72. package/dist/hud/constants.js +2 -1
  73. package/dist/hud/constants.js.map +1 -1
  74. package/dist/hud/index.d.ts +4 -1
  75. package/dist/hud/index.d.ts.map +1 -1
  76. package/dist/hud/index.js +11 -5
  77. package/dist/hud/index.js.map +1 -1
  78. package/dist/hud/reconcile.d.ts +23 -0
  79. package/dist/hud/reconcile.d.ts.map +1 -0
  80. package/dist/hud/reconcile.js +71 -0
  81. package/dist/hud/reconcile.js.map +1 -0
  82. package/dist/hud/render.d.ts +6 -1
  83. package/dist/hud/render.d.ts.map +1 -1
  84. package/dist/hud/render.js +77 -3
  85. package/dist/hud/render.js.map +1 -1
  86. package/dist/hud/tmux.d.ts +26 -0
  87. package/dist/hud/tmux.d.ts.map +1 -0
  88. package/dist/hud/tmux.js +126 -0
  89. package/dist/hud/tmux.js.map +1 -0
  90. package/dist/mcp/bootstrap.d.ts.map +1 -1
  91. package/dist/mcp/bootstrap.js +16 -6
  92. package/dist/mcp/bootstrap.js.map +1 -1
  93. package/dist/mcp/code-intel-server.d.ts +298 -0
  94. package/dist/mcp/code-intel-server.d.ts.map +1 -1
  95. package/dist/mcp/code-intel-server.js +9 -5
  96. package/dist/mcp/code-intel-server.js.map +1 -1
  97. package/dist/mcp/memory-server.d.ts +195 -1
  98. package/dist/mcp/memory-server.d.ts.map +1 -1
  99. package/dist/mcp/memory-server.js +9 -5
  100. package/dist/mcp/memory-server.js.map +1 -1
  101. package/dist/mcp/trace-server.d.ts +51 -0
  102. package/dist/mcp/trace-server.d.ts.map +1 -1
  103. package/dist/mcp/trace-server.js +9 -5
  104. package/dist/mcp/trace-server.js.map +1 -1
  105. package/dist/scripts/__tests__/codex-native-hook.test.js +455 -8
  106. package/dist/scripts/__tests__/codex-native-hook.test.js.map +1 -1
  107. package/dist/scripts/codex-native-hook.d.ts.map +1 -1
  108. package/dist/scripts/codex-native-hook.js +159 -52
  109. package/dist/scripts/codex-native-hook.js.map +1 -1
  110. package/dist/scripts/codex-native-pre-post.d.ts +5 -0
  111. package/dist/scripts/codex-native-pre-post.d.ts.map +1 -1
  112. package/dist/scripts/codex-native-pre-post.js +86 -0
  113. package/dist/scripts/codex-native-pre-post.js.map +1 -1
  114. package/dist/scripts/notify-hook/operational-events.d.ts.map +1 -1
  115. package/dist/scripts/notify-hook/operational-events.js +7 -2
  116. package/dist/scripts/notify-hook/operational-events.js.map +1 -1
  117. package/dist/state/__tests__/operations-ralph-phase.test.d.ts +2 -0
  118. package/dist/state/__tests__/operations-ralph-phase.test.d.ts.map +1 -0
  119. package/dist/state/__tests__/operations-ralph-phase.test.js +82 -0
  120. package/dist/state/__tests__/operations-ralph-phase.test.js.map +1 -0
  121. package/dist/state/__tests__/operations.test.d.ts +2 -0
  122. package/dist/state/__tests__/operations.test.d.ts.map +1 -0
  123. package/dist/state/__tests__/operations.test.js +200 -0
  124. package/dist/state/__tests__/operations.test.js.map +1 -0
  125. package/dist/state/__tests__/path-traversal.test.d.ts +2 -0
  126. package/dist/state/__tests__/path-traversal.test.d.ts.map +1 -0
  127. package/dist/state/__tests__/path-traversal.test.js +49 -0
  128. package/dist/state/__tests__/path-traversal.test.js.map +1 -0
  129. package/dist/state/operations.d.ts +11 -0
  130. package/dist/state/operations.d.ts.map +1 -0
  131. package/dist/state/operations.js +233 -0
  132. package/dist/state/operations.js.map +1 -0
  133. package/dist/team/__tests__/api-interop.test.js +24 -2
  134. package/dist/team/__tests__/api-interop.test.js.map +1 -1
  135. package/dist/team/__tests__/delivery-e2e-smoke.test.js +9 -1
  136. package/dist/team/__tests__/delivery-e2e-smoke.test.js.map +1 -1
  137. package/dist/team/__tests__/runtime-cli.test.js +45 -0
  138. package/dist/team/__tests__/runtime-cli.test.js.map +1 -1
  139. package/dist/team/__tests__/runtime.test.js +191 -66
  140. package/dist/team/__tests__/runtime.test.js.map +1 -1
  141. package/dist/team/__tests__/tmux-session.test.js +33 -0
  142. package/dist/team/__tests__/tmux-session.test.js.map +1 -1
  143. package/dist/team/api-interop.d.ts.map +1 -1
  144. package/dist/team/api-interop.js +2 -1
  145. package/dist/team/api-interop.js.map +1 -1
  146. package/dist/team/runtime-cli.d.ts.map +1 -1
  147. package/dist/team/runtime-cli.js +21 -2
  148. package/dist/team/runtime-cli.js.map +1 -1
  149. package/dist/team/runtime.d.ts +8 -0
  150. package/dist/team/runtime.d.ts.map +1 -1
  151. package/dist/team/runtime.js +179 -78
  152. package/dist/team/runtime.js.map +1 -1
  153. package/dist/team/state/dispatch.d.ts.map +1 -1
  154. package/dist/team/state/dispatch.js +9 -0
  155. package/dist/team/state/dispatch.js.map +1 -1
  156. package/dist/team/tmux-session.js +3 -3
  157. package/dist/team/tmux-session.js.map +1 -1
  158. package/dist/team/worktree.d.ts +2 -0
  159. package/dist/team/worktree.d.ts.map +1 -1
  160. package/dist/team/worktree.js +7 -1
  161. package/dist/team/worktree.js.map +1 -1
  162. package/dist/utils/__tests__/paths.test.js +76 -1
  163. package/dist/utils/__tests__/paths.test.js.map +1 -1
  164. package/dist/utils/paths.d.ts +6 -0
  165. package/dist/utils/paths.d.ts.map +1 -1
  166. package/dist/utils/paths.js +14 -0
  167. package/dist/utils/paths.js.map +1 -1
  168. package/dist/verification/__tests__/ci-rust-gates.test.js +59 -11
  169. package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
  170. package/dist/verification/__tests__/ralph-persistence-gate.test.js +1 -4
  171. package/dist/verification/__tests__/ralph-persistence-gate.test.js.map +1 -1
  172. package/package.json +6 -1
  173. package/src/scripts/__tests__/codex-native-hook.test.ts +600 -8
  174. package/src/scripts/codex-native-hook.ts +236 -60
  175. package/src/scripts/codex-native-pre-post.ts +104 -0
  176. package/src/scripts/notify-hook/operational-events.ts +6 -2
@@ -0,0 +1,200 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { chmod, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { existsSync } from 'node:fs';
5
+ import { join } from 'node:path';
6
+ import { tmpdir } from 'node:os';
7
+ import { executeStateOperation } from '../operations.js';
8
+ async function withAmbientTmuxEnv(env, run) {
9
+ const previousTmux = process.env.TMUX;
10
+ const previousTmuxPane = process.env.TMUX_PANE;
11
+ const previousPath = process.env.PATH;
12
+ if (typeof env.TMUX === 'string')
13
+ process.env.TMUX = env.TMUX;
14
+ else
15
+ delete process.env.TMUX;
16
+ if (typeof env.TMUX_PANE === 'string')
17
+ process.env.TMUX_PANE = env.TMUX_PANE;
18
+ else
19
+ delete process.env.TMUX_PANE;
20
+ if (typeof env.PATH === 'string')
21
+ process.env.PATH = env.PATH;
22
+ else if ('PATH' in env)
23
+ delete process.env.PATH;
24
+ try {
25
+ return await run();
26
+ }
27
+ finally {
28
+ if (typeof previousTmux === 'string')
29
+ process.env.TMUX = previousTmux;
30
+ else
31
+ delete process.env.TMUX;
32
+ if (typeof previousTmuxPane === 'string')
33
+ process.env.TMUX_PANE = previousTmuxPane;
34
+ else
35
+ delete process.env.TMUX_PANE;
36
+ if (typeof previousPath === 'string')
37
+ process.env.PATH = previousPath;
38
+ else
39
+ delete process.env.PATH;
40
+ }
41
+ }
42
+ async function createFakeTmuxBin(wd) {
43
+ const fakeBin = join(wd, 'bin');
44
+ await mkdir(fakeBin, { recursive: true });
45
+ const tmuxPath = join(fakeBin, 'tmux');
46
+ await writeFile(tmuxPath, `#!/usr/bin/env bash
47
+ set -eu
48
+ cmd="\${1:-}"
49
+ shift || true
50
+ if [[ "$cmd" == "display-message" ]]; then
51
+ target=""
52
+ format=""
53
+ while (($#)); do
54
+ case "$1" in
55
+ -p) shift ;;
56
+ -t) target="$2"; shift 2 ;;
57
+ *) format="$1"; shift ;;
58
+ esac
59
+ done
60
+ if [[ -z "$target" && "$format" == "#{pane_id}" ]]; then
61
+ echo "%777"
62
+ exit 0
63
+ fi
64
+ if [[ -z "$target" && "$format" == "#S" ]]; then
65
+ echo "maintainer-default"
66
+ exit 0
67
+ fi
68
+ if [[ "$target" == "%777" && "$format" == "#{pane_id}" ]]; then
69
+ echo "%777"
70
+ exit 0
71
+ fi
72
+ if [[ "$target" == "%777" && "$format" == "#S" ]]; then
73
+ echo "maintainer-default"
74
+ exit 0
75
+ fi
76
+ fi
77
+ if [[ "$cmd" == "list-sessions" ]]; then
78
+ echo "maintainer-default"
79
+ exit 0
80
+ fi
81
+ exit 1
82
+ `);
83
+ await chmod(tmuxPath, 0o755);
84
+ return fakeBin;
85
+ }
86
+ describe('state operations directory initialization', () => {
87
+ it('creates .omx/state for state operations without setup', async () => {
88
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-test-'));
89
+ try {
90
+ const stateDir = join(wd, '.omx', 'state');
91
+ const tmuxHookConfig = join(wd, '.omx', 'tmux-hook.json');
92
+ assert.equal(existsSync(stateDir), false);
93
+ assert.equal(existsSync(tmuxHookConfig), false);
94
+ const response = await executeStateOperation('state_list_active', {
95
+ workingDirectory: wd,
96
+ });
97
+ assert.equal(existsSync(stateDir), true);
98
+ assert.equal(existsSync(tmuxHookConfig), true);
99
+ assert.deepEqual(response.payload, { active_modes: [] });
100
+ }
101
+ finally {
102
+ await rm(wd, { recursive: true, force: true });
103
+ }
104
+ });
105
+ it('bootstraps tmux-hook from the current tmux pane when available', async () => {
106
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-live-'));
107
+ try {
108
+ const tmuxHookConfig = join(wd, '.omx', 'tmux-hook.json');
109
+ const fakeBin = await createFakeTmuxBin(wd);
110
+ await withAmbientTmuxEnv({
111
+ TMUX: '/tmp/maintainer-default,123,0',
112
+ TMUX_PANE: '%777',
113
+ PATH: `${fakeBin}:${process.env.PATH || ''}`,
114
+ }, async () => {
115
+ const response = await executeStateOperation('state_list_active', {
116
+ workingDirectory: wd,
117
+ });
118
+ assert.deepEqual(response.payload, { active_modes: [] });
119
+ });
120
+ const tmuxConfig = JSON.parse(await readFile(tmuxHookConfig, 'utf-8'));
121
+ assert.deepEqual(tmuxConfig.target, { type: 'pane', value: '%777' });
122
+ }
123
+ finally {
124
+ await rm(wd, { recursive: true, force: true });
125
+ }
126
+ });
127
+ it('writes and reads deep-interview state', async () => {
128
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-readwrite-'));
129
+ try {
130
+ const writeResponse = await executeStateOperation('state_write', {
131
+ workingDirectory: wd,
132
+ mode: 'deep-interview',
133
+ active: true,
134
+ current_phase: 'deep-interview',
135
+ state: {
136
+ current_focus: 'intent',
137
+ threshold: 0.2,
138
+ },
139
+ });
140
+ assert.equal(writeResponse.isError, undefined);
141
+ assert.deepEqual(writeResponse.payload, {
142
+ success: true,
143
+ mode: 'deep-interview',
144
+ path: join(wd, '.omx', 'state', 'deep-interview-state.json'),
145
+ });
146
+ const readResponse = await executeStateOperation('state_read', {
147
+ workingDirectory: wd,
148
+ mode: 'deep-interview',
149
+ });
150
+ assert.equal(readResponse.isError, undefined);
151
+ const readBody = readResponse.payload;
152
+ assert.equal(readBody.active, true);
153
+ assert.equal(readBody.current_phase, 'deep-interview');
154
+ assert.equal(readBody.current_focus, 'intent');
155
+ assert.equal(readBody.threshold, 0.2);
156
+ }
157
+ finally {
158
+ await rm(wd, { recursive: true, force: true });
159
+ }
160
+ });
161
+ it('creates session-scoped state directory when session_id is provided', async () => {
162
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-session-'));
163
+ try {
164
+ const sessionDir = join(wd, '.omx', 'state', 'sessions', 'sess1');
165
+ assert.equal(existsSync(sessionDir), false);
166
+ const response = await executeStateOperation('state_get_status', {
167
+ workingDirectory: wd,
168
+ session_id: 'sess1',
169
+ });
170
+ assert.equal(existsSync(sessionDir), true);
171
+ assert.deepEqual(response.payload, { statuses: {} });
172
+ }
173
+ finally {
174
+ await rm(wd, { recursive: true, force: true });
175
+ }
176
+ });
177
+ it('serializes concurrent state_write calls per mode file and preserves merged fields', async () => {
178
+ const wd = await mkdtemp(join(tmpdir(), 'omx-state-ops-concurrency-'));
179
+ try {
180
+ const writes = Array.from({ length: 16 }, (_, i) => executeStateOperation('state_write', {
181
+ workingDirectory: wd,
182
+ mode: 'team',
183
+ state: { [`k${i}`]: i },
184
+ }));
185
+ const responses = await Promise.all(writes);
186
+ for (const response of responses) {
187
+ assert.equal(response.isError, undefined);
188
+ }
189
+ const filePath = join(wd, '.omx', 'state', 'team-state.json');
190
+ const state = JSON.parse(await readFile(filePath, 'utf-8'));
191
+ for (let i = 0; i < 16; i++) {
192
+ assert.equal(state[`k${i}`], i);
193
+ }
194
+ }
195
+ finally {
196
+ await rm(wd, { recursive: true, force: true });
197
+ }
198
+ });
199
+ });
200
+ //# sourceMappingURL=operations.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.test.js","sourceRoot":"","sources":["../../../src/state/__tests__/operations.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=path-traversal.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-traversal.test.d.ts","sourceRoot":"","sources":["../../../src/state/__tests__/path-traversal.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,49 @@
1
+ import { describe, it } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { mkdtemp, rm } from 'fs/promises';
4
+ import { join } from 'path';
5
+ import { tmpdir } from 'os';
6
+ import { executeStateOperation } from '../operations.js';
7
+ describe('state CLI path traversal prevention', () => {
8
+ it('rejects invalid workingDirectory inputs containing NUL bytes', async () => {
9
+ const resp = await executeStateOperation('state_read', {
10
+ mode: 'team',
11
+ workingDirectory: 'bad\0path',
12
+ });
13
+ assert.equal(resp.isError, true);
14
+ const body = resp.payload;
15
+ assert.match(body.error || '', /NUL byte/);
16
+ });
17
+ it('rejects traversal in mode for state_write', async () => {
18
+ const wd = await mkdtemp(join(tmpdir(), 'omx-traversal-'));
19
+ try {
20
+ const resp = await executeStateOperation('state_write', {
21
+ mode: '../../outside',
22
+ state: { active: true },
23
+ workingDirectory: wd,
24
+ });
25
+ assert.equal(resp.isError, true);
26
+ const body = resp.payload;
27
+ assert.ok(typeof body.error === 'string' && body.error.length > 0);
28
+ }
29
+ finally {
30
+ await rm(wd, { recursive: true, force: true });
31
+ }
32
+ });
33
+ it('rejects unsupported mode names for state_read', async () => {
34
+ const wd = await mkdtemp(join(tmpdir(), 'omx-traversal-'));
35
+ try {
36
+ const resp = await executeStateOperation('state_read', {
37
+ mode: 'custom_mode',
38
+ workingDirectory: wd,
39
+ });
40
+ assert.equal(resp.isError, true);
41
+ const body = resp.payload;
42
+ assert.ok(typeof body.error === 'string' && body.error.includes('mode must be one of'));
43
+ }
44
+ finally {
45
+ await rm(wd, { recursive: true, force: true });
46
+ }
47
+ });
48
+ });
49
+ //# sourceMappingURL=path-traversal.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"path-traversal.test.js","sourceRoot":"","sources":["../../../src/state/__tests__/path-traversal.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,CAAC;AAE5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAEzD,QAAQ,CAAC,qCAAqC,EAAE,GAAG,EAAE;IACnD,EAAE,CAAC,8DAA8D,EAAE,KAAK,IAAI,EAAE;QAC5E,MAAM,IAAI,GAAG,MAAM,qBAAqB,CAAC,YAAY,EAAE;YACrD,IAAI,EAAE,MAAM;YACZ,gBAAgB,EAAE,WAAW;SAC9B,CAAC,CAAC;QACH,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACjC,MAAM,IAAI,GAAG,IAAI,CAAC,OAA6B,CAAC;QAChD,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,EAAE,UAAU,CAAC,CAAC;IAC7C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;QACzD,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,qBAAqB,CAAC,aAAa,EAAE;gBACtD,IAAI,EAAE,eAAe;gBACrB,KAAK,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE;gBACvB,gBAAgB,EAAE,EAAE;aACrB,CAAC,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACjC,MAAM,IAAI,GAAG,IAAI,CAAC,OAA6B,CAAC;YAChD,MAAM,CAAC,EAAE,CAAC,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;QACrE,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,EAAE,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,MAAM,EAAE,EAAE,gBAAgB,CAAC,CAAC,CAAC;QAC3D,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,qBAAqB,CAAC,YAAY,EAAE;gBACrD,IAAI,EAAE,aAAa;gBACnB,gBAAgB,EAAE,EAAE;aACrB,CAAC,CAAC;YACH,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YACjC,MAAM,IAAI,GAAG,IAAI,CAAC,OAA6B,CAAC;YAChD,MAAM,CAAC,EAAE,CAAC,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,IAAI,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAC,CAAC;QAC1F,CAAC;gBAAS,CAAC;YACT,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ export declare const SUPPORTED_STATE_READ_MODES: readonly ["autopilot", "team", "ralph", "ultrawork", "ultraqa", "ralplan", "deep-interview"];
2
+ export type SupportedStateReadMode = (typeof SUPPORTED_STATE_READ_MODES)[number];
3
+ export type StateOperationName = 'state_read' | 'state_write' | 'state_clear' | 'state_list_active' | 'state_get_status';
4
+ export interface StateOperationResponse {
5
+ payload: unknown;
6
+ isError?: boolean;
7
+ }
8
+ export declare function listStateStatuses(cwd: string, explicitSessionId?: string, mode?: string): Promise<Record<string, unknown>>;
9
+ export declare function listActiveStateModes(workingDirectory?: string, explicitSessionId?: string): Promise<string[]>;
10
+ export declare function executeStateOperation(name: StateOperationName, rawArgs?: Record<string, unknown>): Promise<StateOperationResponse>;
11
+ //# sourceMappingURL=operations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.d.ts","sourceRoot":"","sources":["../../src/state/operations.ts"],"names":[],"mappings":"AAmBA,eAAO,MAAM,0BAA0B,8FAQ7B,CAAC;AAEX,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,0BAA0B,CAAC,CAAC,MAAM,CAAC,CAAC;AACjF,MAAM,MAAM,kBAAkB,GAC1B,YAAY,GACZ,aAAa,GACb,aAAa,GACb,mBAAmB,GACnB,kBAAkB,CAAC;AAEvB,MAAM,WAAW,sBAAsB;IACrC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAwDD,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,MAAM,EACX,iBAAiB,CAAC,EAAE,MAAM,EAC1B,IAAI,CAAC,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CA6BlC;AAGD,wBAAsB,oBAAoB,CACxC,gBAAgB,CAAC,EAAE,MAAM,EACzB,iBAAiB,CAAC,EAAE,MAAM,GACzB,OAAO,CAAC,MAAM,EAAE,CAAC,CAOnB;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,kBAAkB,EACxB,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACpC,OAAO,CAAC,sBAAsB,CAAC,CAoJjC"}
@@ -0,0 +1,233 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { mkdir, readFile, readdir, rename, unlink, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { withModeRuntimeContext } from './mode-state-context.js';
5
+ import { getAllScopedStatePaths, getReadScopedStateDirs, getReadScopedStatePaths, getStateDir, getStatePath, resolveStateScope, resolveWorkingDirectoryForState, validateSessionId, validateStateModeSegment, } from '../mcp/state-paths.js';
6
+ import { ensureCanonicalRalphArtifacts } from '../ralph/persistence.js';
7
+ import { RALPH_PHASES, validateAndNormalizeRalphState } from '../ralph/contract.js';
8
+ export const SUPPORTED_STATE_READ_MODES = [
9
+ 'autopilot',
10
+ 'team',
11
+ 'ralph',
12
+ 'ultrawork',
13
+ 'ultraqa',
14
+ 'ralplan',
15
+ 'deep-interview',
16
+ ];
17
+ const stateWriteQueues = new Map();
18
+ async function withStateWriteLock(path, fn) {
19
+ const tail = stateWriteQueues.get(path) ?? Promise.resolve();
20
+ let release;
21
+ const gate = new Promise((resolve) => {
22
+ release = resolve;
23
+ });
24
+ const queued = tail.finally(() => gate);
25
+ stateWriteQueues.set(path, queued);
26
+ await tail.catch(() => { });
27
+ try {
28
+ return await fn();
29
+ }
30
+ finally {
31
+ release();
32
+ if (stateWriteQueues.get(path) === queued) {
33
+ stateWriteQueues.delete(path);
34
+ }
35
+ }
36
+ }
37
+ async function writeAtomicFile(path, data) {
38
+ const tmpPath = `${path}.tmp.${process.pid}.${Date.now()}.${Math.random().toString(16).slice(2)}`;
39
+ await writeFile(tmpPath, data, 'utf-8');
40
+ try {
41
+ await rename(tmpPath, path);
42
+ }
43
+ catch (error) {
44
+ await unlink(tmpPath).catch(() => { });
45
+ throw error;
46
+ }
47
+ }
48
+ function readModeSupportsStrictValidation(mode) {
49
+ return SUPPORTED_STATE_READ_MODES.includes(mode);
50
+ }
51
+ function validateStrictReadableMode(mode) {
52
+ const normalized = validateStateModeSegment(mode);
53
+ if (!readModeSupportsStrictValidation(normalized)) {
54
+ throw new Error(`mode must be one of: ${SUPPORTED_STATE_READ_MODES.join(', ')}`);
55
+ }
56
+ return normalized;
57
+ }
58
+ async function initializeStateEnvironment(cwd, effectiveSessionId) {
59
+ await mkdir(getStateDir(cwd), { recursive: true });
60
+ if (effectiveSessionId) {
61
+ await mkdir(getStateDir(cwd, effectiveSessionId), { recursive: true });
62
+ }
63
+ const { ensureTmuxHookInitialized } = await import('../cli/tmux-hook.js');
64
+ await ensureTmuxHookInitialized(cwd);
65
+ }
66
+ export async function listStateStatuses(cwd, explicitSessionId, mode) {
67
+ const stateDirs = await getReadScopedStateDirs(cwd, explicitSessionId);
68
+ const statuses = {};
69
+ const seenModes = new Set();
70
+ for (const stateDir of stateDirs) {
71
+ if (!existsSync(stateDir))
72
+ continue;
73
+ const files = await readdir(stateDir);
74
+ for (const file of files) {
75
+ if (!file.endsWith('-state.json'))
76
+ continue;
77
+ const currentMode = file.replace('-state.json', '');
78
+ if (mode && currentMode !== mode)
79
+ continue;
80
+ if (seenModes.has(currentMode))
81
+ continue;
82
+ seenModes.add(currentMode);
83
+ try {
84
+ const data = JSON.parse(await readFile(join(stateDir, file), 'utf-8'));
85
+ statuses[currentMode] = {
86
+ active: data.active,
87
+ phase: data.current_phase,
88
+ path: join(stateDir, file),
89
+ data,
90
+ };
91
+ }
92
+ catch {
93
+ statuses[currentMode] = { error: 'malformed state file' };
94
+ }
95
+ }
96
+ }
97
+ return statuses;
98
+ }
99
+ export async function listActiveStateModes(workingDirectory, explicitSessionId) {
100
+ const cwd = resolveWorkingDirectoryForState(workingDirectory);
101
+ const sessionId = validateSessionId(explicitSessionId);
102
+ const statuses = await listStateStatuses(cwd, sessionId);
103
+ return Object.entries(statuses)
104
+ .filter(([, status]) => Boolean(status.active))
105
+ .map(([mode]) => mode);
106
+ }
107
+ export async function executeStateOperation(name, rawArgs = {}) {
108
+ let cwd;
109
+ let explicitSessionId;
110
+ try {
111
+ cwd = resolveWorkingDirectoryForState(rawArgs.workingDirectory);
112
+ explicitSessionId = validateSessionId(rawArgs.session_id);
113
+ }
114
+ catch (error) {
115
+ return {
116
+ payload: { error: error.message },
117
+ isError: true,
118
+ };
119
+ }
120
+ try {
121
+ const stateScope = await resolveStateScope(cwd, explicitSessionId);
122
+ const effectiveSessionId = stateScope.sessionId;
123
+ await initializeStateEnvironment(cwd, effectiveSessionId);
124
+ switch (name) {
125
+ case 'state_read': {
126
+ const mode = validateStrictReadableMode(rawArgs.mode);
127
+ const paths = await getReadScopedStatePaths(mode, cwd, explicitSessionId);
128
+ const path = paths.find((candidate) => existsSync(candidate));
129
+ if (!path) {
130
+ return { payload: { exists: false, mode } };
131
+ }
132
+ const data = JSON.parse(await readFile(path, 'utf-8'));
133
+ return { payload: data };
134
+ }
135
+ case 'state_write': {
136
+ const mode = validateStateModeSegment(rawArgs.mode);
137
+ const path = getStatePath(mode, cwd, effectiveSessionId);
138
+ const { mode: _mode, workingDirectory: _workingDirectory, session_id: _sessionId, state: customState, ...fields } = rawArgs;
139
+ let validationError = null;
140
+ await withStateWriteLock(path, async () => {
141
+ let existing = {};
142
+ if (existsSync(path)) {
143
+ try {
144
+ existing = JSON.parse(await readFile(path, 'utf-8'));
145
+ }
146
+ catch (error) {
147
+ process.stderr.write(`[state] Failed to parse state file: ${error}\n`);
148
+ }
149
+ }
150
+ const mergedRaw = {
151
+ ...existing,
152
+ ...fields,
153
+ ...(customState || {}),
154
+ };
155
+ if (mode === 'ralph' &&
156
+ effectiveSessionId &&
157
+ typeof mergedRaw.owner_omx_session_id !== 'string') {
158
+ mergedRaw.owner_omx_session_id = effectiveSessionId;
159
+ }
160
+ if (mode === 'ralph') {
161
+ const originalPhase = mergedRaw.current_phase;
162
+ const validation = validateAndNormalizeRalphState(mergedRaw);
163
+ if (!validation.ok || !validation.state) {
164
+ validationError = validation.error || `ralph.current_phase must be one of: ${RALPH_PHASES.join(', ')}`;
165
+ return;
166
+ }
167
+ if (typeof originalPhase === 'string' &&
168
+ typeof validation.state.current_phase === 'string' &&
169
+ validation.state.current_phase !== originalPhase) {
170
+ validation.state.ralph_phase_normalized_from = originalPhase;
171
+ }
172
+ Object.assign(mergedRaw, validation.state);
173
+ await ensureCanonicalRalphArtifacts(cwd, effectiveSessionId);
174
+ }
175
+ const merged = withModeRuntimeContext(existing, mergedRaw);
176
+ await writeAtomicFile(path, JSON.stringify(merged, null, 2));
177
+ });
178
+ if (validationError) {
179
+ return {
180
+ payload: { error: validationError },
181
+ isError: true,
182
+ };
183
+ }
184
+ return { payload: { success: true, mode, path } };
185
+ }
186
+ case 'state_clear': {
187
+ const mode = validateStateModeSegment(rawArgs.mode);
188
+ const allSessions = rawArgs.all_sessions === true;
189
+ if (!allSessions) {
190
+ const path = getStatePath(mode, cwd, effectiveSessionId);
191
+ if (existsSync(path)) {
192
+ await unlink(path);
193
+ }
194
+ return { payload: { cleared: true, mode, path } };
195
+ }
196
+ const removedPaths = [];
197
+ const paths = await getAllScopedStatePaths(mode, cwd);
198
+ for (const path of paths) {
199
+ if (!existsSync(path))
200
+ continue;
201
+ await unlink(path);
202
+ removedPaths.push(path);
203
+ }
204
+ return {
205
+ payload: {
206
+ cleared: true,
207
+ mode,
208
+ all_sessions: true,
209
+ removed: removedPaths.length,
210
+ paths: removedPaths,
211
+ warning: 'all_sessions clears global and session-scoped state files',
212
+ },
213
+ };
214
+ }
215
+ case 'state_list_active': {
216
+ const activeModes = await listActiveStateModes(cwd, explicitSessionId);
217
+ return { payload: { active_modes: activeModes } };
218
+ }
219
+ case 'state_get_status': {
220
+ const mode = typeof rawArgs.mode === 'string' ? rawArgs.mode.trim() : undefined;
221
+ const statuses = await listStateStatuses(cwd, explicitSessionId, mode || undefined);
222
+ return { payload: { statuses } };
223
+ }
224
+ }
225
+ }
226
+ catch (error) {
227
+ return {
228
+ payload: { error: error.message },
229
+ isError: true,
230
+ };
231
+ }
232
+ }
233
+ //# sourceMappingURL=operations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"operations.js","sourceRoot":"","sources":["../../src/state/operations.ts"],"names":[],"mappings":"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"}
@@ -1692,7 +1692,7 @@ describe('executeTeamApiOperation: cleanup', () => {
1692
1692
  const result = await executeTeamApiOperation('cleanup', {}, '/tmp');
1693
1693
  assert.equal(result.ok, false);
1694
1694
  });
1695
- it('routes cleanup through the shutdown gate for failed tasks on normal teams', async () => {
1695
+ it('requires confirm_issues for failed-task cleanup on normal teams', async () => {
1696
1696
  const { cwd, cleanup } = await setupTeam('cleanup-gate');
1697
1697
  try {
1698
1698
  await createTask('cleanup-gate', {
@@ -1705,7 +1705,7 @@ describe('executeTeamApiOperation: cleanup', () => {
1705
1705
  }, cwd);
1706
1706
  assert.equal(result.ok, false);
1707
1707
  if (!result.ok) {
1708
- assert.match(result.error.message, /shutdown_gate_blocked:pending=0,blocked=0,in_progress=0,failed=1/);
1708
+ assert.match(result.error.message, /shutdown_confirm_issues_required:failed=1/);
1709
1709
  }
1710
1710
  const summary = await executeTeamApiOperation('get-summary', {
1711
1711
  team_name: 'cleanup-gate',
@@ -1716,6 +1716,28 @@ describe('executeTeamApiOperation: cleanup', () => {
1716
1716
  await cleanup();
1717
1717
  }
1718
1718
  });
1719
+ it('allows cleanup with confirm_issues when failed tasks remain', async () => {
1720
+ const { cwd, cleanup } = await setupTeam('cleanup-confirm-issues');
1721
+ try {
1722
+ await createTask('cleanup-confirm-issues', {
1723
+ subject: 'failed task',
1724
+ description: 'requires explicit confirmation',
1725
+ status: 'failed',
1726
+ }, cwd);
1727
+ const result = await executeTeamApiOperation('cleanup', {
1728
+ team_name: 'cleanup-confirm-issues',
1729
+ confirm_issues: true,
1730
+ }, cwd);
1731
+ assert.equal(result.ok, true);
1732
+ if (result.ok) {
1733
+ assert.equal(result.data.team_name, 'cleanup-confirm-issues');
1734
+ assert.equal(result.data.cleanup_mode, 'shutdown');
1735
+ }
1736
+ }
1737
+ finally {
1738
+ await cleanup();
1739
+ }
1740
+ });
1719
1741
  });
1720
1742
  describe('executeTeamApiOperation: orphan-cleanup', () => {
1721
1743
  it('uses destructive orphan cleanup explicitly', async () => {