@coralai/sps-cli 0.10.2 → 0.11.1

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 (62) hide show
  1. package/README.md +63 -23
  2. package/dist/commands/workerDashboard.d.ts.map +1 -1
  3. package/dist/commands/workerDashboard.js +39 -11
  4. package/dist/commands/workerDashboard.js.map +1 -1
  5. package/dist/core/config.d.ts +1 -0
  6. package/dist/core/config.d.ts.map +1 -1
  7. package/dist/core/config.js +1 -0
  8. package/dist/core/config.js.map +1 -1
  9. package/dist/core/state.d.ts +10 -0
  10. package/dist/core/state.d.ts.map +1 -1
  11. package/dist/core/state.js +5 -0
  12. package/dist/core/state.js.map +1 -1
  13. package/dist/engines/CloseoutEngine.d.ts.map +1 -1
  14. package/dist/engines/CloseoutEngine.js +79 -28
  15. package/dist/engines/CloseoutEngine.js.map +1 -1
  16. package/dist/engines/ExecutionEngine.d.ts +5 -0
  17. package/dist/engines/ExecutionEngine.d.ts.map +1 -1
  18. package/dist/engines/ExecutionEngine.js +80 -16
  19. package/dist/engines/ExecutionEngine.js.map +1 -1
  20. package/dist/engines/MonitorEngine.d.ts.map +1 -1
  21. package/dist/engines/MonitorEngine.js +6 -1
  22. package/dist/engines/MonitorEngine.js.map +1 -1
  23. package/dist/interfaces/WorkerProvider.d.ts +68 -15
  24. package/dist/interfaces/WorkerProvider.d.ts.map +1 -1
  25. package/dist/models/types.d.ts +3 -1
  26. package/dist/models/types.d.ts.map +1 -1
  27. package/dist/providers/ClaudePrintProvider.d.ts +54 -0
  28. package/dist/providers/ClaudePrintProvider.d.ts.map +1 -0
  29. package/dist/providers/ClaudePrintProvider.js +279 -0
  30. package/dist/providers/ClaudePrintProvider.js.map +1 -0
  31. package/dist/providers/ClaudeTmuxProvider.d.ts +94 -0
  32. package/dist/providers/ClaudeTmuxProvider.d.ts.map +1 -0
  33. package/dist/providers/ClaudeTmuxProvider.js +331 -0
  34. package/dist/providers/ClaudeTmuxProvider.js.map +1 -0
  35. package/dist/providers/ClaudeWorkerProvider.d.ts +5 -93
  36. package/dist/providers/ClaudeWorkerProvider.d.ts.map +1 -1
  37. package/dist/providers/ClaudeWorkerProvider.js +3 -303
  38. package/dist/providers/ClaudeWorkerProvider.js.map +1 -1
  39. package/dist/providers/CodexExecProvider.d.ts +36 -0
  40. package/dist/providers/CodexExecProvider.d.ts.map +1 -0
  41. package/dist/providers/CodexExecProvider.js +238 -0
  42. package/dist/providers/CodexExecProvider.js.map +1 -0
  43. package/dist/providers/CodexTmuxProvider.d.ts +71 -0
  44. package/dist/providers/CodexTmuxProvider.d.ts.map +1 -0
  45. package/dist/providers/CodexTmuxProvider.js +351 -0
  46. package/dist/providers/CodexTmuxProvider.js.map +1 -0
  47. package/dist/providers/CodexWorkerProvider.d.ts +5 -70
  48. package/dist/providers/CodexWorkerProvider.d.ts.map +1 -1
  49. package/dist/providers/CodexWorkerProvider.js +3 -328
  50. package/dist/providers/CodexWorkerProvider.js.map +1 -1
  51. package/dist/providers/outputParser.d.ts +50 -0
  52. package/dist/providers/outputParser.d.ts.map +1 -0
  53. package/dist/providers/outputParser.js +219 -0
  54. package/dist/providers/outputParser.js.map +1 -0
  55. package/dist/providers/registry.d.ts.map +1 -1
  56. package/dist/providers/registry.js +18 -6
  57. package/dist/providers/registry.js.map +1 -1
  58. package/dist/providers/streamRenderer.d.ts +13 -0
  59. package/dist/providers/streamRenderer.d.ts.map +1 -0
  60. package/dist/providers/streamRenderer.js +106 -0
  61. package/dist/providers/streamRenderer.js.map +1 -0
  62. package/package.json +1 -1
@@ -1,306 +1,6 @@
1
- import { execFileSync } from 'node:child_process';
2
- import { existsSync, readFileSync } from 'node:fs';
3
- /** Completion keywords detected in pane text (priority 3). */
4
- const COMPLETION_KEYWORDS = /\b(done|完成|全部完成|MR created|merge request|已提交|已推送)\b|🎉/i;
5
- /** Confirmation prompt patterns (priority 2 / detectWaiting). */
6
- const CONFIRMATION_PROMPT = /(Do you want to proceed|y\/n|press enter|confirm|approve)/i;
7
- /** Destructive operation indicators. */
8
- const DESTRUCTIVE_PATTERN = /(delete|remove|drop|rm -rf|truncate|destroy)/i;
9
- /** Blocked indicators in pane text. */
10
- const BLOCKED_PATTERN = /(error|fatal|panic|BLOCKED|stuck|cannot proceed|timed out|rate.?limit)/i;
11
1
  /**
12
- * Run a tmux command via execFileSync and return stdout.
13
- * Returns null when the command fails (e.g. session does not exist).
14
- * On "server exited unexpectedly", auto-cleans stale socket and retries once.
2
+ * @deprecated Use ClaudeTmuxProvider or ClaudePrintProvider directly.
3
+ * This re-export exists for backward compatibility with existing imports.
15
4
  */
16
- function tmux(args) {
17
- try {
18
- return execFileSync('tmux', args, {
19
- encoding: 'utf-8',
20
- timeout: 10_000,
21
- stdio: ['ignore', 'pipe', 'pipe'],
22
- });
23
- }
24
- catch (err) {
25
- const stderr = err.stderr ?? '';
26
- if (stderr.includes('server exited unexpectedly') || stderr.includes('no server running')) {
27
- // Stale tmux socket — clean up and retry
28
- try {
29
- const uid = process.getuid?.() ?? 1000;
30
- const { rmSync } = require('node:fs');
31
- rmSync(`/tmp/tmux-${uid}`, { recursive: true, force: true });
32
- process.stderr.write('[worker] Cleaned stale tmux socket, retrying\n');
33
- return execFileSync('tmux', args, {
34
- encoding: 'utf-8',
35
- timeout: 10_000,
36
- stdio: ['ignore', 'pipe', 'pipe'],
37
- });
38
- }
39
- catch {
40
- return null;
41
- }
42
- }
43
- return null;
44
- }
45
- }
46
- /** Check whether a tmux session exists. */
47
- function sessionExists(session) {
48
- return tmux(['has-session', '-t', session]) !== null;
49
- }
50
- /** Capture recent pane text from a tmux session. */
51
- function capturePaneText(session, lines) {
52
- return tmux(['capture-pane', '-t', session, '-p', '-S', `-${lines}`]) ?? '';
53
- }
54
- export class ClaudeWorkerProvider {
55
- config;
56
- constructor(config) {
57
- this.config = config;
58
- }
59
- /**
60
- * Ensure worktree directory exists and is clean.
61
- * This is largely a no-op when the worktree is already prepared.
62
- */
63
- async prepareEnv(worktree, _seq) {
64
- if (!existsSync(worktree)) {
65
- throw new Error(`Worktree directory does not exist: ${worktree}`);
66
- }
67
- // Verify the directory is a git worktree / repo
68
- try {
69
- execFileSync('git', ['-C', worktree, 'rev-parse', '--is-inside-work-tree'], {
70
- encoding: 'utf-8',
71
- timeout: 5_000,
72
- stdio: ['ignore', 'pipe', 'pipe'],
73
- });
74
- }
75
- catch {
76
- throw new Error(`Directory is not a git worktree: ${worktree}`);
77
- }
78
- }
79
- /**
80
- * Launch a Claude Code worker inside a tmux session.
81
- *
82
- * Session reuse strategy (WORKER_SESSION_REUSE=true):
83
- * 1. Session exists + Claude running → reuse: /clear + cd worktree (keep context hot)
84
- * 2. Session exists + Claude not running → reuse session: cd + start claude
85
- * 3. No session → create new session + start claude
86
- */
87
- async launch(session, worktree) {
88
- const claudeCmd = 'claude --dangerously-skip-permissions';
89
- if (sessionExists(session)) {
90
- const pane = capturePaneText(session, 10);
91
- const claudeAlive = /❯\s*$/m.test(pane) || /bypass permissions/i.test(pane) || /shortcuts/i.test(pane);
92
- if (claudeAlive) {
93
- // Claude is running — reuse instance: clear conversation + switch worktree
94
- this.log.info(`Reusing live Claude session ${session}`);
95
- tmux(['send-keys', '-t', session, '/clear', 'Enter']);
96
- await this.sleep(1_000);
97
- tmux(['send-keys', '-t', session, `cd ${worktree}`, 'Enter']);
98
- await this.sleep(500);
99
- return;
100
- }
101
- // Session exists but Claude not running — cd and start claude
102
- this.log.info(`Reusing tmux session ${session} (Claude not running)`);
103
- tmux(['send-keys', '-t', session, `cd ${worktree}`, 'Enter']);
104
- await this.sleep(500);
105
- tmux(['send-keys', '-t', session, claudeCmd, 'Enter']);
106
- return;
107
- }
108
- // No session — create new
109
- const result = tmux(['new-session', '-d', '-s', session, '-c', worktree]);
110
- if (result === null && !sessionExists(session)) {
111
- throw new Error(`Failed to create tmux session: ${session}`);
112
- }
113
- tmux(['send-keys', '-t', session, claudeCmd, 'Enter']);
114
- }
115
- log = { info: (msg) => process.stderr.write(`[worker] ${msg}\n`) };
116
- /**
117
- * Poll tmux pane text until Claude's ready prompt appears.
118
- * Default timeout: 30 seconds, poll interval: 2 seconds.
119
- */
120
- async waitReady(session, timeoutMs = 30_000) {
121
- const pollInterval = 2_000;
122
- const deadline = Date.now() + timeoutMs;
123
- // Wait at least 3s for Claude to start loading before polling
124
- await this.sleep(3_000);
125
- while (Date.now() < deadline) {
126
- const text = capturePaneText(session, 15);
127
- // Match Claude Code's actual ready state:
128
- // - The ❯ prompt on its own line (Claude's input prompt)
129
- // - "bypass permissions" indicator (only appears after Claude is fully loaded)
130
- // - "? for shortcuts" (appears at bottom when ready)
131
- if (/bypass permissions/i.test(text) ||
132
- /\? for shortcuts/i.test(text) ||
133
- /tips for shortcuts/i.test(text)) {
134
- return true;
135
- }
136
- // Also match the ❯ prompt but only if Claude banner has appeared
137
- if (/Claude Code/i.test(text) && /❯\s*$/m.test(text)) {
138
- return true;
139
- }
140
- await this.sleep(pollInterval);
141
- }
142
- return false;
143
- }
144
- /**
145
- * Send a task prompt file to the Claude session.
146
- */
147
- async sendTask(session, promptFile) {
148
- if (!existsSync(promptFile)) {
149
- throw new Error(`Prompt file does not exist: ${promptFile}`);
150
- }
151
- // Write prompt content to a temp file, load into tmux buffer, paste, then Enter.
152
- const content = readFileSync(promptFile, 'utf-8').trim();
153
- const bufferFile = `/tmp/sps-task-${Date.now()}.txt`;
154
- const { writeFileSync: writeTmp, unlinkSync } = await import('node:fs');
155
- writeTmp(bufferFile, content);
156
- tmux(['load-buffer', bufferFile]);
157
- tmux(['paste-buffer', '-t', session]);
158
- try {
159
- unlinkSync(bufferFile);
160
- }
161
- catch { /* cleanup */ }
162
- // Small delay to let paste complete before sending Enter
163
- await this.sleep(500);
164
- tmux(['send-keys', '-t', session, 'Enter']);
165
- }
166
- /**
167
- * Inspect a tmux session: check if alive and capture pane text.
168
- */
169
- async inspect(session) {
170
- const alive = sessionExists(session);
171
- const paneText = alive ? capturePaneText(session, 50) : '';
172
- return { alive, paneText };
173
- }
174
- /**
175
- * Multi-layer completion detection chain.
176
- *
177
- * Priority order:
178
- * 1. task_completed marker file in logDir
179
- * 2. Waiting for confirmation prompts (delegates to detectWaiting)
180
- * 3. Completion keywords in pane text
181
- * 4. MR exists on GitLab (skipped — returns ALIVE)
182
- * 5. tmux session alive → ALIVE
183
- * 6. Session dead + restart limit exceeded → DEAD_EXCEEDED
184
- */
185
- async detectCompleted(session, logDir, _branch) {
186
- // Priority 1: task_completed marker file
187
- const markerPath = `${logDir}/task_completed`;
188
- if (existsSync(markerPath)) {
189
- return 'COMPLETED';
190
- }
191
- // Priority 2: waiting for confirmation
192
- const waitState = await this.detectWaiting(session);
193
- if (waitState.waiting) {
194
- return waitState.destructive ? 'NEEDS_INPUT' : 'AUTO_CONFIRM';
195
- }
196
- // Priority 3: completion keywords in pane text
197
- const paneText = capturePaneText(session, 50);
198
- if (COMPLETION_KEYWORDS.test(paneText)) {
199
- return 'COMPLETED';
200
- }
201
- // Priority 4: MR exists (skipped for now)
202
- // Priority 5: session alive
203
- if (sessionExists(session)) {
204
- return 'ALIVE';
205
- }
206
- // Priority 6: session dead — check restart limit
207
- // The restart count is tracked externally by the engine via state.json.
208
- // Here we simply report DEAD_EXCEEDED vs DEAD so the engine can decide.
209
- // Without access to the restart counter, report DEAD and let the caller
210
- // escalate to DEAD_EXCEEDED if the limit is reached.
211
- return 'DEAD';
212
- }
213
- /**
214
- * Detect whether the worker is waiting for user confirmation.
215
- * Returns whether the prompt is destructive (delete/remove/drop etc.).
216
- */
217
- async detectWaiting(session) {
218
- const paneText = capturePaneText(session, 30);
219
- const match = paneText.match(CONFIRMATION_PROMPT);
220
- if (!match) {
221
- return { waiting: false, destructive: false, prompt: '' };
222
- }
223
- // Extract the line containing the prompt for context
224
- const lines = paneText.split('\n');
225
- const promptLine = lines.find((l) => CONFIRMATION_PROMPT.test(l))?.trim() ?? match[0];
226
- const destructive = DESTRUCTIVE_PATTERN.test(paneText);
227
- return { waiting: true, destructive, prompt: promptLine };
228
- }
229
- /**
230
- * Check pane text for blocked indicators (errors, stuck states).
231
- */
232
- async detectBlocked(session) {
233
- const paneText = capturePaneText(session, 30);
234
- return BLOCKED_PATTERN.test(paneText);
235
- }
236
- /**
237
- * Send a fix prompt to the Claude session (e.g. after CI failure).
238
- */
239
- async sendFix(session, fixPrompt) {
240
- // Escape any single quotes in the prompt for safe tmux transmission
241
- const escaped = fixPrompt.replace(/'/g, "'\\''");
242
- tmux(['send-keys', '-t', session, escaped, 'Enter']);
243
- }
244
- /**
245
- * Send conflict resolution instructions to the Claude session.
246
- */
247
- async resolveConflict(session, worktree, branch) {
248
- const instruction = [
249
- `There is a merge conflict on branch ${branch}.`,
250
- `Working directory: ${worktree}`,
251
- 'Please resolve the conflict:',
252
- `1. Run: git fetch origin && git rebase origin/${this.config.GITLAB_MERGE_BRANCH}`,
253
- '2. Resolve any conflicts in the affected files',
254
- '3. Run: git add . && git rebase --continue',
255
- '4. Run: git push --force-with-lease',
256
- ].join('\n');
257
- tmux(['send-keys', '-t', session, instruction, 'Enter']);
258
- }
259
- /**
260
- * Release a worker session after task completion.
261
- *
262
- * WORKER_SESSION_REUSE=true: do nothing — keep Claude running so the
263
- * next task can hot-reuse the session via /clear + cd (preserves
264
- * session state, env vars, loaded MCP servers, etc.).
265
- *
266
- * WORKER_SESSION_REUSE=false: exit Claude but keep tmux session alive
267
- * (next launch will restart Claude in the existing session).
268
- */
269
- async release(session) {
270
- if (!sessionExists(session))
271
- return;
272
- if (this.config.WORKER_SESSION_REUSE) {
273
- // Keep everything alive — next launch() will /clear + cd + send prompt
274
- this.log.info(`Session ${session} kept alive for reuse`);
275
- return;
276
- }
277
- // Exit Claude but keep tmux session
278
- tmux(['send-keys', '-t', session, '/exit', 'Enter']);
279
- }
280
- /**
281
- * Force-stop a worker session (error recovery, cleanup).
282
- * Always exits Claude and kills the tmux session.
283
- */
284
- async stop(session) {
285
- if (!sessionExists(session))
286
- return;
287
- tmux(['send-keys', '-t', session, '/exit', 'Enter']);
288
- for (let i = 0; i < 5; i++) {
289
- await this.sleep(1_000);
290
- if (!sessionExists(session))
291
- return;
292
- }
293
- tmux(['kill-session', '-t', session]);
294
- }
295
- /**
296
- * Capture the last 100 lines of pane text as a summary.
297
- */
298
- async collectSummary(session) {
299
- return capturePaneText(session, 100);
300
- }
301
- /** Helper: sleep for the given milliseconds. */
302
- sleep(ms) {
303
- return new Promise((resolve) => setTimeout(resolve, ms));
304
- }
305
- }
5
+ export { ClaudeTmuxProvider as ClaudeWorkerProvider } from './ClaudeTmuxProvider.js';
306
6
  //# sourceMappingURL=ClaudeWorkerProvider.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ClaudeWorkerProvider.js","sourceRoot":"","sources":["../../src/providers/ClaudeWorkerProvider.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ClaudeWorkerProvider.js","sourceRoot":"","sources":["../../src/providers/ClaudeWorkerProvider.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EAAE,kBAAkB,IAAI,oBAAoB,EAAE,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { ProjectConfig } from '../core/config.js';
2
+ import type { WorkerProvider, LaunchResult } from '../interfaces/WorkerProvider.js';
3
+ import type { WorkerStatus } from '../models/types.js';
4
+ export declare class CodexExecProvider implements WorkerProvider {
5
+ private readonly config;
6
+ constructor(config: ProjectConfig);
7
+ prepareEnv(worktree: string, _seq: string): Promise<void>;
8
+ launch(session: string, worktree: string, promptFile: string): Promise<LaunchResult>;
9
+ inspect(session: string): Promise<{
10
+ alive: boolean;
11
+ paneText: string;
12
+ pid?: number;
13
+ exitCode?: number;
14
+ }>;
15
+ detectCompleted(session: string, logDir: string, branch: string): Promise<WorkerStatus>;
16
+ detectWaiting(_session: string): Promise<{
17
+ waiting: boolean;
18
+ destructive: boolean;
19
+ prompt: string;
20
+ }>;
21
+ detectBlocked(_session: string): Promise<boolean>;
22
+ sendFix(session: string, fixPrompt: string, resumeSessionId?: string): Promise<LaunchResult>;
23
+ resolveConflict(session: string, worktree: string, branch: string, resumeSessionId?: string): Promise<LaunchResult>;
24
+ release(_session: string): Promise<void>;
25
+ stop(session: string): Promise<void>;
26
+ collectSummary(session: string): Promise<string>;
27
+ private spawnCodex;
28
+ extractSessionIdAsync(session: string): Promise<string | null>;
29
+ /**
30
+ * Look up worker slot info from state.json by tmuxSession name.
31
+ * Fallback when activeProcesses map is empty (SPS restarted).
32
+ */
33
+ private findSlotBySession;
34
+ private log;
35
+ }
36
+ //# sourceMappingURL=CodexExecProvider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CodexExecProvider.d.ts","sourceRoot":"","sources":["../../src/providers/CodexExecProvider.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AACpF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAwBvD,qBAAa,iBAAkB,YAAW,cAAc;IACtD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAgB;gBAE3B,MAAM,EAAE,aAAa;IAI3B,UAAU,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAazD,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAcpF,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;QACtC,KAAK,EAAE,OAAO,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;IA6BI,eAAe,CACnB,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,YAAY,CAAC;IA2ClB,aAAa,CACjB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAKhE,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIjD,OAAO,CACX,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,YAAY,CAAC;IAUlB,eAAe,CACnB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,eAAe,CAAC,EAAE,MAAM,GACvB,OAAO,CAAC,YAAY,CAAC;IAmBlB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAWpC,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAQtD,OAAO,CAAC,UAAU;IAkEZ,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAOpE;;;OAGG;IACH,OAAO,CAAC,iBAAiB;IAgCzB,OAAO,CAAC,GAAG;CAGZ"}
@@ -0,0 +1,238 @@
1
+ /**
2
+ * CodexExecProvider — one-shot print-mode worker using `codex exec`.
3
+ *
4
+ * Eliminates all tmux interaction. Process lifecycle = task lifecycle.
5
+ * Uses `codex exec resume <sessionId>` for context continuity.
6
+ */
7
+ import { spawn, execFileSync } from 'node:child_process';
8
+ import { existsSync, readFileSync, createWriteStream } from 'node:fs';
9
+ import { resolve } from 'node:path';
10
+ import { tailFile, parseCodexSessionId, isProcessAlive, killProcessGroup, branchCommitsAhead, branchPushed, } from './outputParser.js';
11
+ import { readState } from '../core/state.js';
12
+ /** Completion indicators. */
13
+ const COMPLETION_KEYWORDS = /\b(done|completed|finished|committed|pushed|MR created|merge request)\b/i;
14
+ /**
15
+ * Track spawned child processes by session name.
16
+ */
17
+ const activeProcesses = new Map();
18
+ export class CodexExecProvider {
19
+ config;
20
+ constructor(config) {
21
+ this.config = config;
22
+ }
23
+ async prepareEnv(worktree, _seq) {
24
+ if (!existsSync(worktree)) {
25
+ throw new Error(`Worktree directory does not exist: ${worktree}`);
26
+ }
27
+ try {
28
+ execFileSync('git', ['-C', worktree, 'rev-parse', '--is-inside-work-tree'], {
29
+ encoding: 'utf-8', timeout: 5_000, stdio: ['ignore', 'pipe', 'pipe'],
30
+ });
31
+ }
32
+ catch {
33
+ throw new Error(`Directory is not a git worktree: ${worktree}`);
34
+ }
35
+ }
36
+ async launch(session, worktree, promptFile) {
37
+ if (!existsSync(promptFile)) {
38
+ throw new Error(`Prompt file does not exist: ${promptFile}`);
39
+ }
40
+ const prompt = readFileSync(promptFile, 'utf-8').trim();
41
+ const outputFile = resolve(this.config.raw.LOGS_DIR || `/tmp/sps-${this.config.PROJECT_NAME}`, `${session}-${Date.now()}.jsonl`);
42
+ return this.spawnCodex(session, worktree, prompt, outputFile);
43
+ }
44
+ async inspect(session) {
45
+ const proc = activeProcesses.get(session);
46
+ if (proc) {
47
+ const pid = proc.child.pid ?? 0;
48
+ const alive = pid > 0 && isProcessAlive(pid);
49
+ return {
50
+ alive,
51
+ paneText: tailFile(proc.outputFile, 50),
52
+ pid,
53
+ exitCode: proc.exitCode ?? undefined,
54
+ };
55
+ }
56
+ // Fallback: recover from state.json
57
+ const slotInfo = this.findSlotBySession(session);
58
+ if (slotInfo?.pid) {
59
+ const alive = isProcessAlive(slotInfo.pid);
60
+ const paneText = slotInfo.outputFile ? tailFile(slotInfo.outputFile, 50) : '';
61
+ return {
62
+ alive,
63
+ paneText,
64
+ pid: slotInfo.pid,
65
+ exitCode: alive ? undefined : (slotInfo.exitCode ?? undefined),
66
+ };
67
+ }
68
+ return { alive: false, paneText: '', pid: undefined, exitCode: undefined };
69
+ }
70
+ async detectCompleted(session, logDir, branch) {
71
+ const markerPath = `${logDir}/task_completed`;
72
+ if (existsSync(markerPath)) {
73
+ return 'COMPLETED';
74
+ }
75
+ // Resolve process info — in-memory or state.json fallback
76
+ const proc = activeProcesses.get(session);
77
+ const slotInfo = !proc ? this.findSlotBySession(session) : null;
78
+ const pid = proc?.child.pid ?? slotInfo?.pid ?? 0;
79
+ const exitCode = proc?.exitCode ?? slotInfo?.exitCode ?? null;
80
+ if (!pid && !proc) {
81
+ return 'DEAD';
82
+ }
83
+ // Process still running
84
+ if (pid > 0 && isProcessAlive(pid)) {
85
+ return 'ALIVE';
86
+ }
87
+ // Process exited — verify with git artifacts
88
+ const worktree = slotInfo?.worktree ?? null;
89
+ const baseBranch = this.config.GITLAB_MERGE_BRANCH;
90
+ if (worktree && branch) {
91
+ const pushed = branchPushed(worktree, branch);
92
+ const commitsAhead = pushed
93
+ ? branchCommitsAhead(worktree, branch, baseBranch)
94
+ : 0;
95
+ if (pushed && commitsAhead > 0) {
96
+ return 'COMPLETED';
97
+ }
98
+ }
99
+ if (exitCode === 0) {
100
+ return 'EXITED_INCOMPLETE';
101
+ }
102
+ return 'DEAD';
103
+ }
104
+ async detectWaiting(_session) {
105
+ // codex exec with --dangerously-bypass-approvals-and-sandbox never waits
106
+ return { waiting: false, destructive: false, prompt: '' };
107
+ }
108
+ async detectBlocked(_session) {
109
+ return false;
110
+ }
111
+ async sendFix(session, fixPrompt, resumeSessionId) {
112
+ const worktree = '.';
113
+ const outputFile = resolve(this.config.raw.LOGS_DIR || `/tmp/sps-${this.config.PROJECT_NAME}`, `${session}-fix-${Date.now()}.jsonl`);
114
+ return this.spawnCodex(session, worktree, fixPrompt, outputFile, resumeSessionId);
115
+ }
116
+ async resolveConflict(session, worktree, branch, resumeSessionId) {
117
+ const instruction = [
118
+ `There is a merge conflict on branch ${branch}.`,
119
+ `Working directory: ${worktree}`,
120
+ 'Please resolve the conflict:',
121
+ `1. Run: git fetch origin && git rebase origin/${this.config.GITLAB_MERGE_BRANCH}`,
122
+ '2. Resolve any conflicts in the affected files',
123
+ '3. Run: git add . && git rebase --continue',
124
+ '4. Run: git push --force-with-lease',
125
+ ].join('\n');
126
+ const outputFile = resolve(this.config.raw.LOGS_DIR || `/tmp/sps-${this.config.PROJECT_NAME}`, `${session}-conflict-${Date.now()}.jsonl`);
127
+ return this.spawnCodex(session, worktree, instruction, outputFile, resumeSessionId);
128
+ }
129
+ async release(_session) {
130
+ activeProcesses.delete(_session);
131
+ }
132
+ async stop(session) {
133
+ const proc = activeProcesses.get(session);
134
+ if (!proc)
135
+ return;
136
+ const pid = proc.child.pid;
137
+ if (pid && isProcessAlive(pid)) {
138
+ await killProcessGroup(pid);
139
+ }
140
+ activeProcesses.delete(session);
141
+ }
142
+ async collectSummary(session) {
143
+ const proc = activeProcesses.get(session);
144
+ if (!proc)
145
+ return '';
146
+ return tailFile(proc.outputFile, 100);
147
+ }
148
+ // ─── Internal ────────────────────────────────────────────────────
149
+ spawnCodex(session, worktree, prompt, outputFile, resumeSessionId) {
150
+ // Ensure output directory exists
151
+ const { mkdirSync } = require('node:fs');
152
+ const { dirname } = require('node:path');
153
+ try {
154
+ mkdirSync(dirname(outputFile), { recursive: true });
155
+ }
156
+ catch { /* exists */ }
157
+ const outStream = createWriteStream(outputFile, { flags: 'a' });
158
+ let args;
159
+ if (resumeSessionId) {
160
+ // codex exec resume <session_id> "prompt" --json ...
161
+ args = [
162
+ 'exec', 'resume', resumeSessionId, '-',
163
+ '--json',
164
+ '--dangerously-bypass-approvals-and-sandbox',
165
+ ];
166
+ }
167
+ else {
168
+ // codex exec "prompt" --json ...
169
+ args = [
170
+ 'exec', '-',
171
+ '--json',
172
+ '--dangerously-bypass-approvals-and-sandbox',
173
+ ];
174
+ }
175
+ const child = spawn('codex', args, {
176
+ cwd: worktree,
177
+ stdio: ['pipe', 'pipe', 'pipe'],
178
+ detached: true,
179
+ env: { ...process.env },
180
+ });
181
+ child.stdout?.pipe(outStream);
182
+ child.stderr?.on('data', (chunk) => {
183
+ outStream.write(chunk);
184
+ });
185
+ // Write prompt to stdin and close
186
+ child.stdin?.write(prompt);
187
+ child.stdin?.end();
188
+ const entry = { child, outputFile, exitCode: null };
189
+ activeProcesses.set(session, entry);
190
+ child.on('exit', (code) => {
191
+ entry.exitCode = code ?? 1;
192
+ outStream.end();
193
+ });
194
+ child.unref();
195
+ this.log(`Spawned codex exec for ${session} (pid=${child.pid}), output=${outputFile}`);
196
+ return {
197
+ pid: child.pid ?? 0,
198
+ outputFile,
199
+ sessionId: resumeSessionId,
200
+ };
201
+ }
202
+ async extractSessionIdAsync(session) {
203
+ await new Promise((r) => setTimeout(r, 5_000));
204
+ const proc = activeProcesses.get(session);
205
+ if (!proc)
206
+ return null;
207
+ return parseCodexSessionId(proc.outputFile);
208
+ }
209
+ /**
210
+ * Look up worker slot info from state.json by tmuxSession name.
211
+ * Fallback when activeProcesses map is empty (SPS restarted).
212
+ */
213
+ findSlotBySession(session) {
214
+ try {
215
+ const stateFile = resolve(process.env.HOME || '~', '.projects', this.config.PROJECT_NAME, 'runtime', 'state.json');
216
+ if (!existsSync(stateFile))
217
+ return null;
218
+ const state = readState(stateFile, this.config.MAX_CONCURRENT_WORKERS);
219
+ for (const slot of Object.values(state.workers)) {
220
+ if (slot.tmuxSession === session && slot.mode === 'print') {
221
+ return {
222
+ pid: slot.pid ?? null,
223
+ outputFile: slot.outputFile ?? null,
224
+ exitCode: slot.exitCode ?? null,
225
+ sessionId: slot.sessionId ?? null,
226
+ worktree: slot.worktree ?? null,
227
+ };
228
+ }
229
+ }
230
+ }
231
+ catch { /* state read error */ }
232
+ return null;
233
+ }
234
+ log(msg) {
235
+ process.stderr.write(`[codex-exec] ${msg}\n`);
236
+ }
237
+ }
238
+ //# sourceMappingURL=CodexExecProvider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"CodexExecProvider.js","sourceRoot":"","sources":["../../src/providers/CodexExecProvider.ts"],"names":[],"mappings":"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"}