@northflare/runner 0.0.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 (154) hide show
  1. package/DEBUG_LOGGING.md +60 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION_PLAN.md +52 -0
  4. package/README.md +220 -0
  5. package/SDK_IMPLEMENTATION_GUIDE.md +1036 -0
  6. package/bin/northflare-runner +367 -0
  7. package/coverage/base.css +224 -0
  8. package/coverage/block-navigation.js +87 -0
  9. package/coverage/coverage-final.json +12 -0
  10. package/coverage/favicon.png +0 -0
  11. package/coverage/index.html +176 -0
  12. package/coverage/lib/index.html +116 -0
  13. package/coverage/lib/preload-script.js.html +964 -0
  14. package/coverage/prettify.css +1 -0
  15. package/coverage/prettify.js +2 -0
  16. package/coverage/sort-arrow-sprite.png +0 -0
  17. package/coverage/sorter.js +196 -0
  18. package/coverage/src/collections/index.html +116 -0
  19. package/coverage/src/collections/runner-messages.ts.html +312 -0
  20. package/coverage/src/components/claude-manager.ts.html +1290 -0
  21. package/coverage/src/components/index.html +146 -0
  22. package/coverage/src/components/message-handler.ts.html +730 -0
  23. package/coverage/src/components/repository-manager.ts.html +841 -0
  24. package/coverage/src/index.html +131 -0
  25. package/coverage/src/index.ts.html +448 -0
  26. package/coverage/src/runner.ts.html +1239 -0
  27. package/coverage/src/utils/config.ts.html +780 -0
  28. package/coverage/src/utils/console.ts.html +121 -0
  29. package/coverage/src/utils/index.html +161 -0
  30. package/coverage/src/utils/logger.ts.html +475 -0
  31. package/coverage/src/utils/status-line.ts.html +445 -0
  32. package/dist/collections/runner-messages.d.ts +52 -0
  33. package/dist/collections/runner-messages.d.ts.map +1 -0
  34. package/dist/collections/runner-messages.js +161 -0
  35. package/dist/collections/runner-messages.js.map +1 -0
  36. package/dist/components/claude-manager.d.ts +39 -0
  37. package/dist/components/claude-manager.d.ts.map +1 -0
  38. package/dist/components/claude-manager.js +783 -0
  39. package/dist/components/claude-manager.js.map +1 -0
  40. package/dist/components/claude-sdk-manager.d.ts +47 -0
  41. package/dist/components/claude-sdk-manager.d.ts.map +1 -0
  42. package/dist/components/claude-sdk-manager.js +1088 -0
  43. package/dist/components/claude-sdk-manager.js.map +1 -0
  44. package/dist/components/enhanced-repository-manager.d.ts +134 -0
  45. package/dist/components/enhanced-repository-manager.d.ts.map +1 -0
  46. package/dist/components/enhanced-repository-manager.js +602 -0
  47. package/dist/components/enhanced-repository-manager.js.map +1 -0
  48. package/dist/components/message-handler-sse.d.ts +46 -0
  49. package/dist/components/message-handler-sse.d.ts.map +1 -0
  50. package/dist/components/message-handler-sse.js +734 -0
  51. package/dist/components/message-handler-sse.js.map +1 -0
  52. package/dist/components/message-handler.d.ts +35 -0
  53. package/dist/components/message-handler.d.ts.map +1 -0
  54. package/dist/components/message-handler.js +689 -0
  55. package/dist/components/message-handler.js.map +1 -0
  56. package/dist/components/repository-manager.d.ts +51 -0
  57. package/dist/components/repository-manager.d.ts.map +1 -0
  58. package/dist/components/repository-manager.js +295 -0
  59. package/dist/components/repository-manager.js.map +1 -0
  60. package/dist/index.d.ts +9 -0
  61. package/dist/index.d.ts.map +1 -0
  62. package/dist/index.js +166 -0
  63. package/dist/index.js.map +1 -0
  64. package/dist/runner-sse.d.ts +57 -0
  65. package/dist/runner-sse.d.ts.map +1 -0
  66. package/dist/runner-sse.js +698 -0
  67. package/dist/runner-sse.js.map +1 -0
  68. package/dist/runner.d.ts +51 -0
  69. package/dist/runner.d.ts.map +1 -0
  70. package/dist/runner.js +530 -0
  71. package/dist/runner.js.map +1 -0
  72. package/dist/services/RunnerAPIClient.d.ts +30 -0
  73. package/dist/services/RunnerAPIClient.d.ts.map +1 -0
  74. package/dist/services/RunnerAPIClient.js +112 -0
  75. package/dist/services/RunnerAPIClient.js.map +1 -0
  76. package/dist/services/SSEClient.d.ts +60 -0
  77. package/dist/services/SSEClient.d.ts.map +1 -0
  78. package/dist/services/SSEClient.js +204 -0
  79. package/dist/services/SSEClient.js.map +1 -0
  80. package/dist/types/claude.d.ts +45 -0
  81. package/dist/types/claude.d.ts.map +1 -0
  82. package/dist/types/claude.js +6 -0
  83. package/dist/types/claude.js.map +1 -0
  84. package/dist/types/index.d.ts +47 -0
  85. package/dist/types/index.d.ts.map +1 -0
  86. package/dist/types/index.js +23 -0
  87. package/dist/types/index.js.map +1 -0
  88. package/dist/types/messages.d.ts +31 -0
  89. package/dist/types/messages.d.ts.map +1 -0
  90. package/dist/types/messages.js +6 -0
  91. package/dist/types/messages.js.map +1 -0
  92. package/dist/types/runner-interface.d.ts +24 -0
  93. package/dist/types/runner-interface.d.ts.map +1 -0
  94. package/dist/types/runner-interface.js +6 -0
  95. package/dist/types/runner-interface.js.map +1 -0
  96. package/dist/utils/StateManager.d.ts +52 -0
  97. package/dist/utils/StateManager.d.ts.map +1 -0
  98. package/dist/utils/StateManager.js +162 -0
  99. package/dist/utils/StateManager.js.map +1 -0
  100. package/dist/utils/config.d.ts +41 -0
  101. package/dist/utils/config.d.ts.map +1 -0
  102. package/dist/utils/config.js +250 -0
  103. package/dist/utils/config.js.map +1 -0
  104. package/dist/utils/console.d.ts +11 -0
  105. package/dist/utils/console.d.ts.map +1 -0
  106. package/dist/utils/console.js +15 -0
  107. package/dist/utils/console.js.map +1 -0
  108. package/dist/utils/expand-env.d.ts +2 -0
  109. package/dist/utils/expand-env.d.ts.map +1 -0
  110. package/dist/utils/expand-env.js +20 -0
  111. package/dist/utils/expand-env.js.map +1 -0
  112. package/dist/utils/logger.d.ts +9 -0
  113. package/dist/utils/logger.d.ts.map +1 -0
  114. package/dist/utils/logger.js +108 -0
  115. package/dist/utils/logger.js.map +1 -0
  116. package/dist/utils/status-line.d.ts +37 -0
  117. package/dist/utils/status-line.d.ts.map +1 -0
  118. package/dist/utils/status-line.js +113 -0
  119. package/dist/utils/status-line.js.map +1 -0
  120. package/docs/claude-manager.md +91 -0
  121. package/exceptions.log +22 -0
  122. package/lib/preload-script.js +293 -0
  123. package/package.json +55 -0
  124. package/rejections.log +63 -0
  125. package/runner.log +488 -0
  126. package/src/components/claude-sdk-manager.ts +1354 -0
  127. package/src/components/enhanced-repository-manager.ts +823 -0
  128. package/src/components/message-handler-sse.ts +1011 -0
  129. package/src/components/repository-manager.ts +337 -0
  130. package/src/index.ts +166 -0
  131. package/src/runner-sse.ts +847 -0
  132. package/src/services/RunnerAPIClient.ts +135 -0
  133. package/src/services/SSEClient.ts +258 -0
  134. package/src/types/claude.ts +55 -0
  135. package/src/types/computer-name.d.ts +4 -0
  136. package/src/types/index.ts +63 -0
  137. package/src/types/messages.ts +39 -0
  138. package/src/types/runner-interface.ts +34 -0
  139. package/src/utils/StateManager.ts +187 -0
  140. package/src/utils/codex-sdk.js +448 -0
  141. package/src/utils/config.ts +315 -0
  142. package/src/utils/console.ts +13 -0
  143. package/src/utils/expand-env.ts +22 -0
  144. package/src/utils/logger.ts +131 -0
  145. package/src/utils/sdk-demo.js +34 -0
  146. package/src/utils/status-line.ts +121 -0
  147. package/test-debug.sh +26 -0
  148. package/tests/retry-strategies.test.ts +410 -0
  149. package/tests/sdk-integration.test.ts +329 -0
  150. package/tests/sdk-streaming.test.ts +1180 -0
  151. package/tests/setup.ts +5 -0
  152. package/tests/test-claude-manager.ts +120 -0
  153. package/tsconfig.json +36 -0
  154. package/vitest.config.ts +27 -0
@@ -0,0 +1,187 @@
1
+ /**
2
+ * State Manager for persisting runner state
3
+ *
4
+ * Manages persistent state including lastProcessedAt timestamp
5
+ * to enable proper catch-up on restart.
6
+ */
7
+
8
+ import fs from 'fs/promises';
9
+ import path from 'path';
10
+ import { createLogger } from './logger';
11
+
12
+ const logger = createLogger('StateManager');
13
+
14
+ export interface RunnerState {
15
+ runnerId: string;
16
+ runnerUid: string | null;
17
+ lastProcessedAt: string | null; // ISO string
18
+ isActiveRunner: boolean;
19
+ updatedAt: string; // ISO string
20
+ }
21
+
22
+ export class StateManager {
23
+ private statePath: string;
24
+ private state: RunnerState | null = null;
25
+ private saveTimer: NodeJS.Timeout | null = null;
26
+
27
+ constructor(dataDir: string, runnerId: string) {
28
+ // Namespace state file by runnerId (stable external ID) to separate different users
29
+ const stateFile = `runner-state-${runnerId}.json`;
30
+ this.statePath = path.join(dataDir, stateFile);
31
+ }
32
+
33
+ /**
34
+ * Load state from disk
35
+ */
36
+ async loadState(): Promise<RunnerState | null> {
37
+ try {
38
+ const content = await fs.readFile(this.statePath, 'utf-8');
39
+ this.state = JSON.parse(content);
40
+ logger.info('Loaded runner state', {
41
+ runnerId: this.state?.runnerId,
42
+ runnerUid: this.state?.runnerUid,
43
+ lastProcessedAt: this.state?.lastProcessedAt,
44
+ isActiveRunner: this.state?.isActiveRunner,
45
+ });
46
+ return this.state;
47
+ } catch (error) {
48
+ if ((error as any).code === 'ENOENT') {
49
+ logger.debug('No state file found, starting fresh');
50
+ return null;
51
+ }
52
+ logger.error('Failed to load state file:', error);
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Save state to disk
59
+ */
60
+ async saveState(state: RunnerState): Promise<void> {
61
+ this.state = state;
62
+
63
+ // Cancel any pending save
64
+ if (this.saveTimer) {
65
+ clearTimeout(this.saveTimer);
66
+ }
67
+
68
+ // Debounce saves to avoid excessive disk writes
69
+ this.saveTimer = setTimeout(async () => {
70
+ try {
71
+ // Ensure directory exists
72
+ const dir = path.dirname(this.statePath);
73
+ await fs.mkdir(dir, { recursive: true });
74
+
75
+ // Update timestamp
76
+ state.updatedAt = new Date().toISOString();
77
+
78
+ // Write atomically by writing to temp file first
79
+ const tempPath = `${this.statePath}.tmp`;
80
+ await fs.writeFile(tempPath, JSON.stringify(state, null, 2), 'utf-8');
81
+ await fs.rename(tempPath, this.statePath);
82
+
83
+ logger.debug('Saved runner state', {
84
+ runnerId: state.runnerId,
85
+ lastProcessedAt: state.lastProcessedAt,
86
+ isActiveRunner: state.isActiveRunner,
87
+ });
88
+ } catch (error) {
89
+ logger.error('Failed to save state file:', error);
90
+ throw error;
91
+ }
92
+ }, 1000); // 1 second debounce
93
+ }
94
+
95
+ /**
96
+ * Update lastProcessedAt timestamp
97
+ */
98
+ async updateLastProcessedAt(timestamp: Date | string): Promise<void> {
99
+ if (!this.state) {
100
+ throw new Error('State not loaded');
101
+ }
102
+
103
+ const isoString = timestamp instanceof Date ? timestamp.toISOString() : timestamp;
104
+
105
+ const updatedState: RunnerState = {
106
+ ...this.state,
107
+ lastProcessedAt: isoString,
108
+ };
109
+
110
+ await this.saveState(updatedState);
111
+ }
112
+
113
+ /**
114
+ * Update runner registration details
115
+ */
116
+ async updateRunnerRegistration(
117
+ runnerId: string,
118
+ runnerUid: string | null,
119
+ lastProcessedAt: Date | null
120
+ ): Promise<void> {
121
+ const state: RunnerState = this.state || {
122
+ runnerId: '',
123
+ runnerUid: null,
124
+ lastProcessedAt: null,
125
+ isActiveRunner: false,
126
+ updatedAt: new Date().toISOString(),
127
+ };
128
+
129
+ const updatedState: RunnerState = {
130
+ ...state,
131
+ runnerId,
132
+ runnerUid,
133
+ lastProcessedAt: lastProcessedAt ? lastProcessedAt.toISOString() : null,
134
+ };
135
+
136
+ await this.saveState(updatedState);
137
+ }
138
+
139
+ /**
140
+ * Update active runner status
141
+ */
142
+ async updateActiveStatus(isActive: boolean): Promise<void> {
143
+ if (!this.state) {
144
+ throw new Error('State not loaded');
145
+ }
146
+
147
+ const updatedState: RunnerState = {
148
+ ...this.state,
149
+ isActiveRunner: isActive,
150
+ };
151
+
152
+ await this.saveState(updatedState);
153
+ }
154
+
155
+ /**
156
+ * Get current state
157
+ */
158
+ getState(): RunnerState | null {
159
+ return this.state;
160
+ }
161
+
162
+ /**
163
+ * Get lastProcessedAt as Date
164
+ */
165
+ getLastProcessedAt(): Date | null {
166
+ if (!this.state || !this.state.lastProcessedAt) {
167
+ return null;
168
+ }
169
+ return new Date(this.state.lastProcessedAt);
170
+ }
171
+
172
+ /**
173
+ * Clear state (for testing or reset)
174
+ */
175
+ async clearState(): Promise<void> {
176
+ this.state = null;
177
+ try {
178
+ await fs.unlink(this.statePath);
179
+ logger.info('Cleared runner state');
180
+ } catch (error) {
181
+ if ((error as any).code !== 'ENOENT') {
182
+ logger.error('Failed to clear state file:', error);
183
+ throw error;
184
+ }
185
+ }
186
+ }
187
+ }
@@ -0,0 +1,448 @@
1
+ /* Example usage:
2
+ query({
3
+ prompt: "Hello, how are you?",
4
+ options: {
5
+ pathToCodexExecutable: "./codex",
6
+ cwd: "/Users/joe/Documents/myproject/",
7
+ resume: "0199a99f-28aa-7872-96f6-24ce1e3a05fc", // Optional, resume from a previous session
8
+ configOverrides: {
9
+ model: "gpt-5-codex",
10
+ model_reasoning_effort: "high",
11
+ sandbox_mode: "danger-full-access",
12
+ }
13
+ }
14
+ })
15
+
16
+ Output:
17
+ sandbox_mode: "danger-full-access",
18
+ }
19
+ }})
20
+
21
+ Output:
22
+
23
+ [
24
+ {
25
+ id: '',
26
+ msg: {
27
+ type: 'session_configured',
28
+ session_id: '0199a9ce-1f38-7332-b46b-9711279899f1',
29
+ model: 'gpt-5-codex',
30
+ reasoning_effort: 'high',
31
+ history_log_id: 1399031533,
32
+ history_entry_count: 475,
33
+ rollout_path: '/Users/toby/.codex/sessions/2025/10/03/rollout-2025-10-03T13-21-21-0199a9ce-1f38-7332-b46b-9711279899f1.jsonl'
34
+ }
35
+ },
36
+ {
37
+ id: 'submission-1',
38
+ msg: { type: 'task_started', model_context_window: 272000 }
39
+ },
40
+ {
41
+ id: 'submission-1',
42
+ msg: { type: 'token_count', info: null, rate_limits: [Object] }
43
+ },
44
+ {
45
+ id: 'submission-1',
46
+ msg: { type: 'agent_reasoning_section_break' }
47
+ },
48
+ {
49
+ id: 'submission-1',
50
+ msg: { type: 'agent_reasoning_delta', delta: '**Preparing' }
51
+ },
52
+ {
53
+ id: 'submission-1',
54
+ msg: { type: 'agent_reasoning_delta', delta: ' a' }
55
+ },
56
+ {
57
+ id: 'submission-1',
58
+ msg: { type: 'agent_reasoning_delta', delta: ' friendly' }
59
+ },
60
+ {
61
+ id: 'submission-1',
62
+ msg: { type: 'agent_reasoning_delta', delta: ' response' }
63
+ },
64
+ {
65
+ id: 'submission-1',
66
+ msg: { type: 'agent_reasoning_delta', delta: '**' }
67
+ },
68
+ {
69
+ id: 'submission-1',
70
+ msg: {
71
+ type: 'agent_reasoning',
72
+ text: '**Preparing a friendly response**'
73
+ }
74
+ },
75
+ {
76
+ id: 'submission-1',
77
+ msg: { type: 'agent_message_delta', delta: 'Hey' }
78
+ },
79
+ {
80
+ id: 'submission-1',
81
+ msg: { type: 'agent_message_delta', delta: ' there' }
82
+ },
83
+ {
84
+ id: 'submission-1',
85
+ msg: { type: 'agent_message_delta', delta: '!' }
86
+ },
87
+ {
88
+ id: 'submission-1',
89
+ msg: { type: 'agent_message_delta', delta: ' How' }
90
+ },
91
+ {
92
+ id: 'submission-1',
93
+ msg: { type: 'agent_message_delta', delta: ' can' }
94
+ },
95
+ {
96
+ id: 'submission-1',
97
+ msg: { type: 'agent_message_delta', delta: ' I' }
98
+ },
99
+ {
100
+ id: 'submission-1',
101
+ msg: { type: 'agent_message_delta', delta: ' help' }
102
+ },
103
+ {
104
+ id: 'submission-1',
105
+ msg: { type: 'agent_message_delta', delta: ' today' }
106
+ },
107
+ {
108
+ id: 'submission-1',
109
+ msg: { type: 'agent_message_delta', delta: '?' }
110
+ },
111
+ {
112
+ id: 'submission-1',
113
+ msg: {
114
+ type: 'agent_message',
115
+ message: 'Hey there! How can I help today?'
116
+ }
117
+ },
118
+ {
119
+ id: 'submission-1',
120
+ msg: { type: 'token_count', info: [Object], rate_limits: [Object] }
121
+ },
122
+ {
123
+ id: 'submission-1',
124
+ msg: {
125
+ type: 'task_complete',
126
+ last_agent_message: 'Hey there! How can I help today?'
127
+ }
128
+ }
129
+ ]
130
+ */
131
+
132
+ import { spawn } from "node:child_process";
133
+ import { createInterface } from "node:readline";
134
+
135
+ class ProcessTransport {
136
+ constructor(options = {}) {
137
+ const { command = "codex", args = [], cwd, env, onStderr } = options;
138
+
139
+ this.child = spawn(command, args, {
140
+ cwd,
141
+ env: { ...process.env, ...env },
142
+ stdio: ["pipe", "pipe", "pipe"],
143
+ });
144
+
145
+ if (!this.child.stdin || !this.child.stdout) {
146
+ throw new Error("Codex CLI requires piped stdin/stdout.");
147
+ }
148
+
149
+ if (this.child.stderr) {
150
+ const stderrHandler = (data) => {
151
+ if (onStderr) {
152
+ onStderr(data.toString());
153
+ } else {
154
+ process.stderr.write(data);
155
+ }
156
+ };
157
+ this.child.stderr.on("data", stderrHandler);
158
+ }
159
+
160
+ this.stdin = this.child.stdin;
161
+ this.stdout = this.child.stdout;
162
+ this.closed = false;
163
+
164
+ this.exitPromise = new Promise((resolve, reject) => {
165
+ this.child.once("exit", (code, signal) => {
166
+ if (code === 0 || signal === "SIGTERM" || signal === null) {
167
+ resolve({ code, signal });
168
+ } else {
169
+ const exitReason = signal ? `signal ${signal}` : `exit code ${code}`;
170
+ const error = new Error(
171
+ `codex process terminated with ${exitReason}`
172
+ );
173
+ error.code = code;
174
+ error.signal = signal;
175
+ reject(error);
176
+ }
177
+ });
178
+ this.child.once("error", (error) => {
179
+ reject(error);
180
+ });
181
+ });
182
+ }
183
+
184
+ async *readMessages() {
185
+ const rl = createInterface({ input: this.stdout });
186
+ try {
187
+ for await (const line of rl) {
188
+ const trimmed = line.trim();
189
+ if (!trimmed) {
190
+ continue;
191
+ }
192
+ let parsed;
193
+ try {
194
+ parsed = JSON.parse(trimmed);
195
+ } catch (error) {
196
+ const parseError = new Error(
197
+ `Failed to parse Codex JSON output: ${error.message}\n${trimmed}`
198
+ );
199
+ parseError.cause = error;
200
+ throw parseError;
201
+ }
202
+ yield parsed;
203
+ }
204
+ await this.exitPromise;
205
+ } finally {
206
+ rl.close();
207
+ }
208
+ }
209
+
210
+ async write(payload) {
211
+ if (this.closed) {
212
+ throw new Error("Cannot write to closed Codex transport.");
213
+ }
214
+ if (!this.stdin || this.stdin.destroyed) {
215
+ throw new Error("Codex stdin is not available.");
216
+ }
217
+
218
+ const serialized =
219
+ typeof payload === "string" ? payload : JSON.stringify(payload);
220
+ const message = serialized.endsWith("\n") ? serialized : `${serialized}\n`;
221
+ const stream = this.stdin;
222
+
223
+ await new Promise((resolve, reject) => {
224
+ let settled = false;
225
+ const finish = () => {
226
+ if (settled) return;
227
+ settled = true;
228
+ stream.off("error", handleError);
229
+ resolve();
230
+ };
231
+ const handleError = (error) => {
232
+ if (settled) return;
233
+ settled = true;
234
+ stream.off("drain", finish);
235
+ stream.off("error", handleError);
236
+ reject(error);
237
+ };
238
+
239
+ stream.once("error", handleError);
240
+ const ok = stream.write(message, finish);
241
+ if (!ok) {
242
+ stream.once("drain", finish);
243
+ }
244
+ });
245
+ }
246
+
247
+ async close() {
248
+ if (this.closed) {
249
+ return;
250
+ }
251
+ this.closed = true;
252
+
253
+ if (this.stdin && !this.stdin.destroyed) {
254
+ this.stdin.end();
255
+ }
256
+
257
+ if (this.child && !this.child.killed) {
258
+ this.child.kill("SIGTERM");
259
+ }
260
+
261
+ try {
262
+ await this.exitPromise;
263
+ } catch {
264
+ // Swallow shutdown errors when closing intentionally.
265
+ }
266
+ }
267
+
268
+ interruptProcess() {
269
+ if (this.child && !this.child.killed) {
270
+ this.child.kill("SIGINT");
271
+ }
272
+ }
273
+ }
274
+
275
+ class Query {
276
+ constructor(transport) {
277
+ this.transport = transport;
278
+ this._nextSubmissionId = 0;
279
+ this._sessionId = null;
280
+ this._writeError = null;
281
+ this._finished = false;
282
+ this._messageIterator = this._iterateMessages();
283
+ }
284
+
285
+ _generateSubmissionId() {
286
+ this._nextSubmissionId += 1;
287
+ return `submission-${this._nextSubmissionId}`;
288
+ }
289
+
290
+ _registerWrite(promise) {
291
+ promise.catch((error) => {
292
+ if (!this._writeError) {
293
+ this._writeError = error;
294
+ void this.transport.close();
295
+ }
296
+ });
297
+ }
298
+
299
+ async *_iterateMessages() {
300
+ try {
301
+ for await (const message of this.transport.readMessages()) {
302
+ if (this._writeError) {
303
+ throw this._writeError;
304
+ }
305
+ const msgType = message?.msg?.type;
306
+ if (!this._sessionId && msgType === "session_configured") {
307
+ this._sessionId = message.msg.session_id ?? null;
308
+ }
309
+ yield message;
310
+ }
311
+ if (this._writeError) {
312
+ throw this._writeError;
313
+ }
314
+ } finally {
315
+ this._finished = true;
316
+ }
317
+ }
318
+
319
+ [Symbol.asyncIterator]() {
320
+ return this._messageIterator;
321
+ }
322
+
323
+ next() {
324
+ if (this._writeError) {
325
+ return Promise.reject(this._writeError);
326
+ }
327
+ return this._messageIterator.next();
328
+ }
329
+
330
+ get sessionId() {
331
+ return this._sessionId;
332
+ }
333
+
334
+ async submit(op, id) {
335
+ if (!op || typeof op !== "object") {
336
+ throw new Error("submit expects a Codex op object.");
337
+ }
338
+ const submissionId = id ?? this._generateSubmissionId();
339
+ const submission = { id: submissionId, op };
340
+ await this.transport.write(submission);
341
+ return submissionId;
342
+ }
343
+
344
+ async submitRaw(submission) {
345
+ if (!submission || typeof submission !== "object") {
346
+ throw new Error("submitRaw expects a submission object.");
347
+ }
348
+ if (!submission.id || !submission.op) {
349
+ throw new Error("Submission must include `id` and `op` fields.");
350
+ }
351
+ await this.transport.write(submission);
352
+ return submission.id;
353
+ }
354
+
355
+ async submitUserMessage(text, { items, id } = {}) {
356
+ const messageItems = Array.isArray(items) ? [...items] : [];
357
+ if (typeof text === "string" && text.length > 0) {
358
+ messageItems.push({ type: "text", text });
359
+ }
360
+ if (messageItems.length === 0) {
361
+ throw new Error("submitUserMessage requires text or items.");
362
+ }
363
+ return this.submit({ type: "user_input", items: messageItems }, id);
364
+ }
365
+
366
+ async interrupt(id) {
367
+ return this.submit({ type: "interrupt" }, id);
368
+ }
369
+
370
+ async shutdown(id) {
371
+ return this.submit({ type: "shutdown" }, id);
372
+ }
373
+
374
+ async close() {
375
+ await this.transport.close();
376
+ }
377
+
378
+ kill() {
379
+ this.transport.interruptProcess();
380
+ }
381
+ }
382
+
383
+ function normalizeConfigOverrides(overrides) {
384
+ if (!overrides) {
385
+ return [];
386
+ }
387
+ if (Array.isArray(overrides)) {
388
+ return overrides;
389
+ }
390
+ if (typeof overrides === "object") {
391
+ return Object.entries(overrides).map(([key, value]) =>
392
+ value === undefined || value === null ? key : `${key}=${value}`
393
+ );
394
+ }
395
+ throw new Error("configOverrides must be an array or object.");
396
+ }
397
+
398
+ function buildSpawnConfig(options = {}) {
399
+ const {
400
+ pathToCodexExecutable,
401
+ resume,
402
+ configOverrides,
403
+ extraArgs,
404
+ cwd,
405
+ env,
406
+ stderr,
407
+ } = options;
408
+
409
+ const command = pathToCodexExecutable ?? "codex";
410
+ const args = ["proto"];
411
+
412
+ const overrides = normalizeConfigOverrides(configOverrides);
413
+ for (const override of overrides) {
414
+ args.push("-c", override);
415
+ }
416
+
417
+ if (resume) {
418
+ args.push("--resume", resume);
419
+ }
420
+
421
+ if (Array.isArray(extraArgs)) {
422
+ args.push(...extraArgs);
423
+ }
424
+
425
+ return {
426
+ command,
427
+ args,
428
+ cwd,
429
+ env,
430
+ onStderr: stderr,
431
+ };
432
+ }
433
+
434
+ export function query({ prompt, options = {} } = {}) {
435
+ const spawnConfig = buildSpawnConfig(options);
436
+ const transport = new ProcessTransport(spawnConfig);
437
+ const q = new Query(transport);
438
+
439
+ if (typeof prompt === "string" && prompt.trim().length > 0) {
440
+ q._registerWrite(q.submitUserMessage(prompt.trim()));
441
+ } else if (Array.isArray(prompt) && prompt.length > 0) {
442
+ q._registerWrite(q.submitUserMessage("", { items: prompt }));
443
+ }
444
+
445
+ return q;
446
+ }
447
+
448
+ export { Query };