cyrus-codex-runner 0.2.64-test.7 → 0.2.64

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 (55) hide show
  1. package/dist/CodexEventMapper.d.ts +56 -0
  2. package/dist/CodexEventMapper.d.ts.map +1 -0
  3. package/dist/CodexEventMapper.js +469 -0
  4. package/dist/CodexEventMapper.js.map +1 -0
  5. package/dist/CodexRunner.d.ts +37 -46
  6. package/dist/CodexRunner.d.ts.map +1 -1
  7. package/dist/CodexRunner.js +136 -851
  8. package/dist/CodexRunner.js.map +1 -1
  9. package/dist/CodexSkillStager.d.ts +42 -0
  10. package/dist/CodexSkillStager.d.ts.map +1 -0
  11. package/dist/CodexSkillStager.js +182 -0
  12. package/dist/CodexSkillStager.js.map +1 -0
  13. package/dist/backend/AppServerCodexBackend.d.ts +74 -0
  14. package/dist/backend/AppServerCodexBackend.d.ts.map +1 -0
  15. package/dist/backend/AppServerCodexBackend.js +352 -0
  16. package/dist/backend/AppServerCodexBackend.js.map +1 -0
  17. package/dist/backend/appServerClient.d.ts +75 -0
  18. package/dist/backend/appServerClient.d.ts.map +1 -0
  19. package/dist/backend/appServerClient.js +223 -0
  20. package/dist/backend/appServerClient.js.map +1 -0
  21. package/dist/backend/appServerEvents.d.ts +9 -0
  22. package/dist/backend/appServerEvents.d.ts.map +1 -0
  23. package/dist/backend/appServerEvents.js +110 -0
  24. package/dist/backend/appServerEvents.js.map +1 -0
  25. package/dist/backend/appServerProcess.d.ts +38 -0
  26. package/dist/backend/appServerProcess.d.ts.map +1 -0
  27. package/dist/backend/appServerProcess.js +283 -0
  28. package/dist/backend/appServerProcess.js.map +1 -0
  29. package/dist/backend/codexBinary.d.ts +24 -0
  30. package/dist/backend/codexBinary.d.ts.map +1 -0
  31. package/dist/backend/codexBinary.js +44 -0
  32. package/dist/backend/codexBinary.js.map +1 -0
  33. package/dist/backend/types.d.ts +210 -0
  34. package/dist/backend/types.d.ts.map +1 -0
  35. package/dist/backend/types.js +2 -0
  36. package/dist/backend/types.js.map +1 -0
  37. package/dist/config/CodexConfigBuilder.d.ts +40 -0
  38. package/dist/config/CodexConfigBuilder.d.ts.map +1 -0
  39. package/dist/config/CodexConfigBuilder.js +182 -0
  40. package/dist/config/CodexConfigBuilder.js.map +1 -0
  41. package/dist/config/mcpConfigTranslator.d.ts +17 -0
  42. package/dist/config/mcpConfigTranslator.d.ts.map +1 -0
  43. package/dist/config/mcpConfigTranslator.js +245 -0
  44. package/dist/config/mcpConfigTranslator.js.map +1 -0
  45. package/dist/config/sandboxPolicy.d.ts +43 -0
  46. package/dist/config/sandboxPolicy.d.ts.map +1 -0
  47. package/dist/config/sandboxPolicy.js +56 -0
  48. package/dist/config/sandboxPolicy.js.map +1 -0
  49. package/dist/index.d.ts +3 -1
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +1 -0
  52. package/dist/index.js.map +1 -1
  53. package/dist/types.d.ts +9 -7
  54. package/dist/types.d.ts.map +1 -1
  55. package/package.json +5 -4
@@ -0,0 +1,352 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { translateAppServerItem, } from "./appServerEvents.js";
3
+ import { AppServerProcessManager, defaultAppServerProcessManager, } from "./appServerProcess.js";
4
+ /**
5
+ * Backend that drives one Codex app-server thread over the process-wide shared
6
+ * JSON-RPC connection. The app-server process is shared; this class owns only
7
+ * per-thread state and supports injecting input into an active turn via
8
+ * `turn/steer` ({@link supportsSteer} is true).
9
+ */
10
+ export class AppServerCodexBackend extends EventEmitter {
11
+ supportsSteer = true;
12
+ appServer = null;
13
+ threadId = null;
14
+ activeTurnId = null;
15
+ turnActive = false;
16
+ lastUsage = {
17
+ input_tokens: 0,
18
+ output_tokens: 0,
19
+ cached_input_tokens: 0,
20
+ };
21
+ /** Structured-output schema for turns, captured at open() for turn/start. */
22
+ outputSchema;
23
+ /** Resolver for the in-flight {@link runTurn} promise. */
24
+ turnResolve = null;
25
+ turnReject = null;
26
+ /** Watchdog: fails a turn that goes fully silent for too long. */
27
+ idleTimer = null;
28
+ turnIdleTimeoutMs;
29
+ processManager;
30
+ threadHandler = {
31
+ onNotification: (method, params) => this.onNotification(method, params),
32
+ onProcessGone: () => this.onProcessGone(),
33
+ onProcessError: (error) => this.onProcessError(error),
34
+ };
35
+ /**
36
+ * @param processManagerOrFactory Overridable shared process manager. Tests may
37
+ * still pass the older client factory shape; it is wrapped in an isolated
38
+ * manager for compatibility.
39
+ * @param options.turnIdleTimeoutMs Fail an in-flight turn if the app-server
40
+ * emits no notifications for this long (default 5min; Codex streams
41
+ * continuously, so prolonged silence means a wedged turn). 0 disables it.
42
+ * @param options.requestTimeoutMs Forwarded when wrapping a test client factory.
43
+ */
44
+ constructor(processManagerOrFactory = defaultAppServerProcessManager, options) {
45
+ super();
46
+ this.processManager =
47
+ typeof processManagerOrFactory === "function"
48
+ ? new AppServerProcessManager(processManagerOrFactory, {
49
+ ...(options?.requestTimeoutMs !== undefined
50
+ ? { requestTimeoutMs: options.requestTimeoutMs }
51
+ : {}),
52
+ idleCloseMs: 0,
53
+ })
54
+ : processManagerOrFactory;
55
+ this.turnIdleTimeoutMs = options?.turnIdleTimeoutMs ?? 300_000;
56
+ }
57
+ async open(config) {
58
+ this.outputSchema = config.outputSchema;
59
+ const appServer = await this.processManager.acquire(config);
60
+ this.appServer = appServer;
61
+ try {
62
+ const threadId = config.resumeSessionId
63
+ ? await this.resumeThread(config)
64
+ : await this.startThread(config);
65
+ this.threadId = threadId;
66
+ appServer.registerThread(threadId, this.threadHandler);
67
+ this.emit("event", { kind: "thread-started", threadId });
68
+ return { threadId };
69
+ }
70
+ catch (error) {
71
+ this.appServer = null;
72
+ appServer.release();
73
+ throw error;
74
+ }
75
+ }
76
+ async runTurn(input) {
77
+ if (!this.appServer || !this.threadId) {
78
+ throw new Error("AppServerCodexBackend.runTurn called before open()");
79
+ }
80
+ const turnPromise = new Promise((resolve, reject) => {
81
+ this.turnResolve = resolve;
82
+ this.turnReject = reject;
83
+ });
84
+ this.turnActive = true;
85
+ this.armIdleWatchdog();
86
+ try {
87
+ const result = await this.appServer.request("turn/start", {
88
+ threadId: this.threadId,
89
+ input: this.toProtocolInput(input),
90
+ ...(this.outputSchema !== undefined
91
+ ? { outputSchema: this.outputSchema }
92
+ : {}),
93
+ });
94
+ this.activeTurnId = result?.turn?.id ?? this.activeTurnId;
95
+ // NOTE: the turn is not steerable the instant turn/start returns — the
96
+ // server only accepts turn/steer once it has emitted the `turn/started`
97
+ // notification. The runner is signalled to flush buffered follow-ups
98
+ // from that notification handler, not here.
99
+ }
100
+ catch (error) {
101
+ this.turnActive = false;
102
+ this.turnResolve = null;
103
+ this.turnReject = null;
104
+ throw error;
105
+ }
106
+ await turnPromise;
107
+ }
108
+ async steer(input) {
109
+ if (!this.appServer || !this.threadId) {
110
+ throw new Error("AppServerCodexBackend.steer called before open()");
111
+ }
112
+ if (!this.turnActive || !this.activeTurnId) {
113
+ throw new Error("Cannot steer: no active turn");
114
+ }
115
+ await this.appServer.request("turn/steer", {
116
+ threadId: this.threadId,
117
+ expectedTurnId: this.activeTurnId,
118
+ input: this.toProtocolInput(input),
119
+ });
120
+ }
121
+ isTurnActive() {
122
+ // A turn is steerable only once turn/start has returned its id — during
123
+ // the brief turn/start request itself, turnActive is true but there is no
124
+ // id to target yet.
125
+ return this.turnActive && this.activeTurnId !== null;
126
+ }
127
+ async interrupt() {
128
+ if (!this.appServer || !this.threadId || !this.activeTurnId) {
129
+ return;
130
+ }
131
+ try {
132
+ await this.appServer.request("turn/interrupt", {
133
+ threadId: this.threadId,
134
+ turnId: this.activeTurnId,
135
+ });
136
+ }
137
+ catch {
138
+ // Interrupt is best-effort; the turn may already have ended.
139
+ }
140
+ }
141
+ async close() {
142
+ const appServer = this.appServer;
143
+ const threadId = this.threadId;
144
+ const turnId = this.activeTurnId;
145
+ this.appServer = null;
146
+ this.threadId = null;
147
+ this.activeTurnId = null;
148
+ if (appServer && threadId && turnId) {
149
+ void appServer
150
+ .request("turn/interrupt", { threadId, turnId })
151
+ .catch(() => undefined);
152
+ }
153
+ if (appServer && threadId) {
154
+ appServer.unregisterThread(threadId, this.threadHandler);
155
+ }
156
+ this.settleTurn(new Error("app-server backend closed"));
157
+ appServer?.release();
158
+ }
159
+ // ---- Thread setup -------------------------------------------------------
160
+ async startThread(config) {
161
+ const result = await this.appServer?.request("thread/start", this.threadOptionsParams(config));
162
+ const id = result?.thread?.id;
163
+ if (!id) {
164
+ throw new Error("thread/start did not return a thread id");
165
+ }
166
+ return id;
167
+ }
168
+ async resumeThread(config) {
169
+ const result = await this.appServer?.request("thread/resume", {
170
+ threadId: config.resumeSessionId,
171
+ ...this.threadOptionsParams(config),
172
+ });
173
+ // Resuming returns the same id we asked for; fall back to it defensively.
174
+ return result?.thread?.id ?? config.resumeSessionId ?? "";
175
+ }
176
+ threadOptionsParams(config) {
177
+ const sandbox = config.sandbox;
178
+ // `sandbox` (coarse mode) and `permissions` (named profile) are mutually
179
+ // exclusive on thread/start; pick exactly one based on the resolved arm.
180
+ const sandboxParams = sandbox.kind === "profile"
181
+ ? { permissions: sandbox.profileId }
182
+ : { sandbox: sandbox.mode };
183
+ return {
184
+ ...(config.workingDirectory ? { cwd: config.workingDirectory } : {}),
185
+ approvalPolicy: config.approvalPolicy,
186
+ ...sandboxParams,
187
+ ...(config.model ? { model: config.model } : {}),
188
+ ...(config.developerInstructions
189
+ ? { developerInstructions: config.developerInstructions }
190
+ : {}),
191
+ config: this.buildThreadConfig(config),
192
+ };
193
+ }
194
+ /**
195
+ * Build the free-form Codex `config` for thread/start. The app-server has no
196
+ * `--add-dir` flag, so:
197
+ * - `workspace-mode`: writable roots + network ride on `sandbox_workspace_write`
198
+ * (only meaningful in workspace-write mode; omitted otherwise).
199
+ * - `profile`: the granular permission profile body is registered under
200
+ * `permissions.<id>` and selected via the `permissions` thread param.
201
+ * MCP servers etc. ride along in configOverrides.
202
+ */
203
+ buildThreadConfig(config) {
204
+ const base = config.configOverrides
205
+ ? { ...config.configOverrides }
206
+ : {};
207
+ const sandbox = config.sandbox;
208
+ if (sandbox.kind === "profile") {
209
+ base.permissions = {
210
+ [sandbox.profileId]: {
211
+ filesystem: { ...sandbox.filesystem },
212
+ network: { enabled: sandbox.networkAccess },
213
+ },
214
+ };
215
+ }
216
+ else if (sandbox.mode === "workspace-write") {
217
+ base.sandbox_workspace_write = {
218
+ network_access: sandbox.networkAccess,
219
+ ...(sandbox.writableRoots.length > 0
220
+ ? { writable_roots: [...sandbox.writableRoots] }
221
+ : {}),
222
+ };
223
+ }
224
+ return base;
225
+ }
226
+ toProtocolInput(input) {
227
+ return input.map((item) => item.type === "text"
228
+ ? { type: "text", text: item.text }
229
+ : { type: "localImage", path: item.path });
230
+ }
231
+ // ---- Notification / request handling ------------------------------------
232
+ onNotification(method, params) {
233
+ // Any notification is a sign of life — reset the idle watchdog.
234
+ if (this.turnActive) {
235
+ this.armIdleWatchdog();
236
+ }
237
+ const p = (params ?? {});
238
+ switch (method) {
239
+ case "turn/started": {
240
+ // The server now accepts turn/steer for this turn. Capture the id
241
+ // (defensively) and signal the runner to flush buffered follow-ups.
242
+ const turn = p.turn;
243
+ if (turn?.id) {
244
+ this.activeTurnId = turn.id;
245
+ }
246
+ this.emit("event", { kind: "turn-started" });
247
+ break;
248
+ }
249
+ case "item/started": {
250
+ const item = translateAppServerItem(p.item);
251
+ if (item)
252
+ this.emit("event", { kind: "item-started", item });
253
+ break;
254
+ }
255
+ case "item/completed": {
256
+ const item = translateAppServerItem(p.item);
257
+ if (item)
258
+ this.emit("event", { kind: "item-completed", item });
259
+ break;
260
+ }
261
+ case "thread/tokenUsage/updated": {
262
+ this.lastUsage = this.readUsage(p);
263
+ break;
264
+ }
265
+ case "turn/completed": {
266
+ this.onTurnCompleted(p);
267
+ break;
268
+ }
269
+ default:
270
+ // Other notifications (rate limits, mcp startup, warnings, deltas)
271
+ // are not needed for the current activity mapping.
272
+ break;
273
+ }
274
+ }
275
+ onTurnCompleted(params) {
276
+ const turn = (params.turn ?? {});
277
+ this.turnActive = false;
278
+ this.activeTurnId = null;
279
+ if (turn.status === "failed") {
280
+ const message = turn.error?.message || "Codex turn failed";
281
+ this.emit("event", { kind: "turn-failed", message });
282
+ }
283
+ else {
284
+ this.emit("event", { kind: "turn-completed", usage: this.lastUsage });
285
+ }
286
+ this.settleTurn();
287
+ }
288
+ readUsage(params) {
289
+ const total = (params.tokenUsage?.total ?? {});
290
+ return {
291
+ input_tokens: numberOr(total.inputTokens, this.lastUsage.input_tokens),
292
+ output_tokens: numberOr(total.outputTokens, this.lastUsage.output_tokens),
293
+ cached_input_tokens: numberOr(total.cachedInputTokens, this.lastUsage.cached_input_tokens),
294
+ };
295
+ }
296
+ onProcessGone() {
297
+ if (this.turnActive) {
298
+ this.emit("event", {
299
+ kind: "turn-failed",
300
+ message: "codex app-server exited before the turn completed",
301
+ });
302
+ }
303
+ this.settleTurn();
304
+ }
305
+ onProcessError(err) {
306
+ const message = err instanceof Error ? err.message : String(err);
307
+ this.emit("event", { kind: "error", message });
308
+ }
309
+ /** Resolve or reject the in-flight runTurn promise exactly once. */
310
+ settleTurn(error) {
311
+ this.clearIdleWatchdog();
312
+ const resolve = this.turnResolve;
313
+ const reject = this.turnReject;
314
+ this.turnResolve = null;
315
+ this.turnReject = null;
316
+ this.turnActive = false;
317
+ if (error && reject) {
318
+ reject(error);
319
+ }
320
+ else if (resolve) {
321
+ resolve();
322
+ }
323
+ }
324
+ /** (Re)start the idle watchdog for the current turn. */
325
+ armIdleWatchdog() {
326
+ if (this.turnIdleTimeoutMs <= 0) {
327
+ return;
328
+ }
329
+ this.clearIdleWatchdog();
330
+ this.idleTimer = setTimeout(() => {
331
+ if (!this.turnActive) {
332
+ return;
333
+ }
334
+ this.emit("event", {
335
+ kind: "turn-failed",
336
+ message: `codex app-server produced no activity for ${this.turnIdleTimeoutMs}ms`,
337
+ });
338
+ this.settleTurn();
339
+ }, this.turnIdleTimeoutMs);
340
+ this.idleTimer.unref?.();
341
+ }
342
+ clearIdleWatchdog() {
343
+ if (this.idleTimer) {
344
+ clearTimeout(this.idleTimer);
345
+ this.idleTimer = null;
346
+ }
347
+ }
348
+ }
349
+ function numberOr(value, fallback) {
350
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
351
+ }
352
+ //# sourceMappingURL=AppServerCodexBackend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AppServerCodexBackend.js","sourceRoot":"","sources":["../../src/backend/AppServerCodexBackend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ import { EventEmitter } from "node:events";
2
+ /** Handles a server→client notification (no response expected). */
3
+ export type NotificationHandler = (method: string, params: unknown) => void;
4
+ /** Handles a server→client request; the returned value becomes the response. */
5
+ export type ServerRequestHandler = (method: string, params: unknown) => unknown | Promise<unknown>;
6
+ export interface AppServerClientOptions {
7
+ binaryPath: string;
8
+ args?: string[];
9
+ env?: Record<string, string>;
10
+ /** Optional logger; defaults to console. */
11
+ logger?: Pick<typeof console, "warn" | "error">;
12
+ /**
13
+ * Per-request timeout in milliseconds for control-plane calls
14
+ * (`initialize`, `thread/*`, `turn/start`, `turn/steer`, `turn/interrupt`).
15
+ * A wedged app-server then rejects the pending request instead of hanging
16
+ * the session forever. Defaults to 60s. Set to 0 to disable.
17
+ */
18
+ requestTimeoutMs?: number;
19
+ }
20
+ /**
21
+ * The slice of {@link AppServerClient} the backend depends on. Declaring it lets
22
+ * tests inject a fake transport without spawning a process (Dependency
23
+ * Inversion).
24
+ */
25
+ export interface IAppServerClient {
26
+ setNotificationHandler(handler: NotificationHandler): void;
27
+ setServerRequestHandler(handler: ServerRequestHandler): void;
28
+ on(event: "exit", listener: (...args: unknown[]) => void): unknown;
29
+ on(event: "error", listener: (...args: unknown[]) => void): unknown;
30
+ on(event: string, listener: (...args: unknown[]) => void): unknown;
31
+ start(): void;
32
+ request<T = unknown>(method: string, params: unknown): Promise<T>;
33
+ close(): Promise<void>;
34
+ }
35
+ /** Factory used by the backend to create a client; overridable in tests. */
36
+ export type AppServerClientFactory = (options: AppServerClientOptions) => IAppServerClient;
37
+ /**
38
+ * Minimal JSON-RPC 2.0 client over a `codex app-server` child process speaking
39
+ * newline-delimited JSON on stdio. Single responsibility: framing + request
40
+ * correlation + dispatch of notifications/server-requests. Knows nothing about
41
+ * Codex semantics.
42
+ */
43
+ export declare class AppServerClient extends EventEmitter {
44
+ private readonly options;
45
+ private child;
46
+ private rl;
47
+ private nextId;
48
+ private readonly pending;
49
+ private notificationHandler;
50
+ private serverRequestHandler;
51
+ private closed;
52
+ private readonly logger;
53
+ constructor(options: AppServerClientOptions);
54
+ setNotificationHandler(handler: NotificationHandler): void;
55
+ setServerRequestHandler(handler: ServerRequestHandler): void;
56
+ start(): void;
57
+ request<T = unknown>(method: string, params: unknown): Promise<T>;
58
+ /** Clear a pending request's timeout (called when settled). */
59
+ private settlePending;
60
+ notify(method: string, params: unknown): void;
61
+ close(): Promise<void>;
62
+ /**
63
+ * Terminate the child and any grandchildren. On POSIX the child was spawned
64
+ * `detached`, so it leads its own process group; signalling the negative pid
65
+ * reaps the whole group (the Node bin shim + the native codex binary) at
66
+ * once. Falls back to a direct kill if the group is already gone or on
67
+ * Windows (no process groups).
68
+ */
69
+ private terminateChild;
70
+ private write;
71
+ private onLine;
72
+ private handleServerRequest;
73
+ private failAllPending;
74
+ }
75
+ //# sourceMappingURL=appServerClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"appServerClient.d.ts","sourceRoot":"","sources":["../../src/backend/appServerClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,223 @@
1
+ import { spawn } from "node:child_process";
2
+ import { EventEmitter } from "node:events";
3
+ import readline from "node:readline";
4
+ /** Default control-plane request timeout. Turn execution is awaited separately
5
+ * (via notifications), so this only bounds quick request/response calls. */
6
+ const DEFAULT_REQUEST_TIMEOUT_MS = 60_000;
7
+ /**
8
+ * Minimal JSON-RPC 2.0 client over a `codex app-server` child process speaking
9
+ * newline-delimited JSON on stdio. Single responsibility: framing + request
10
+ * correlation + dispatch of notifications/server-requests. Knows nothing about
11
+ * Codex semantics.
12
+ */
13
+ export class AppServerClient extends EventEmitter {
14
+ options;
15
+ child = null;
16
+ rl = null;
17
+ nextId = 1;
18
+ pending = new Map();
19
+ notificationHandler = null;
20
+ serverRequestHandler = null;
21
+ closed = false;
22
+ logger;
23
+ constructor(options) {
24
+ super();
25
+ this.options = options;
26
+ this.logger = options.logger ?? console;
27
+ }
28
+ setNotificationHandler(handler) {
29
+ this.notificationHandler = handler;
30
+ }
31
+ setServerRequestHandler(handler) {
32
+ this.serverRequestHandler = handler;
33
+ }
34
+ start() {
35
+ if (this.child) {
36
+ throw new Error("AppServerClient already started");
37
+ }
38
+ const args = this.options.args ?? ["app-server", "--listen", "stdio://"];
39
+ const child = spawn(this.options.binaryPath, args, {
40
+ stdio: ["pipe", "pipe", "pipe"],
41
+ // Run in its own process group (POSIX) so teardown can signal the whole
42
+ // tree at once. We launch codex via the `@openai/codex` Node bin shim,
43
+ // which spawns the native binary as a grandchild; killing only the shim
44
+ // would leave the native process to linger until its stdin pipe closes.
45
+ // Group-killing reaps both immediately. Not supported on Windows.
46
+ ...(process.platform !== "win32" ? { detached: true } : {}),
47
+ ...(this.options.env ? { env: this.options.env } : {}),
48
+ });
49
+ this.child = child;
50
+ child.once("error", (err) => {
51
+ this.failAllPending(err);
52
+ this.emit("error", err);
53
+ });
54
+ child.once("exit", (code, signal) => {
55
+ this.closed = true;
56
+ const reason = new Error(`codex app-server exited (code=${code ?? "null"}, signal=${signal ?? "null"})`);
57
+ this.failAllPending(reason);
58
+ this.emit("exit", code, signal);
59
+ });
60
+ child.stderr.on("data", (data) => {
61
+ const text = data.toString().trim();
62
+ if (text) {
63
+ this.emit("stderr", text);
64
+ }
65
+ });
66
+ this.rl = readline.createInterface({
67
+ input: child.stdout,
68
+ crlfDelay: Number.POSITIVE_INFINITY,
69
+ });
70
+ this.rl.on("line", (line) => this.onLine(line));
71
+ }
72
+ request(method, params) {
73
+ if (this.closed || !this.child) {
74
+ return Promise.reject(new Error(`Cannot send ${method}: app-server is not running`));
75
+ }
76
+ const id = this.nextId++;
77
+ const message = { jsonrpc: "2.0", id, method, params };
78
+ const timeoutMs = this.options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
79
+ return new Promise((resolve, reject) => {
80
+ const entry = {
81
+ resolve: resolve,
82
+ reject,
83
+ method,
84
+ };
85
+ if (timeoutMs > 0) {
86
+ entry.timer = setTimeout(() => {
87
+ if (this.pending.delete(id)) {
88
+ reject(new Error(`${method} timed out after ${timeoutMs}ms`));
89
+ }
90
+ }, timeoutMs);
91
+ // Don't keep the event loop alive solely for this timer.
92
+ entry.timer.unref?.();
93
+ }
94
+ this.pending.set(id, entry);
95
+ this.write(message);
96
+ });
97
+ }
98
+ /** Clear a pending request's timeout (called when settled). */
99
+ settlePending(id) {
100
+ const entry = this.pending.get(id);
101
+ if (entry) {
102
+ this.pending.delete(id);
103
+ if (entry.timer) {
104
+ clearTimeout(entry.timer);
105
+ }
106
+ }
107
+ return entry;
108
+ }
109
+ notify(method, params) {
110
+ if (this.closed || !this.child) {
111
+ return;
112
+ }
113
+ this.write({ jsonrpc: "2.0", method, params });
114
+ }
115
+ async close() {
116
+ this.closed = true;
117
+ this.rl?.close();
118
+ this.rl = null;
119
+ const child = this.child;
120
+ this.child = null;
121
+ if (child && !child.killed) {
122
+ this.terminateChild(child);
123
+ }
124
+ this.failAllPending(new Error("app-server client closed"));
125
+ }
126
+ /**
127
+ * Terminate the child and any grandchildren. On POSIX the child was spawned
128
+ * `detached`, so it leads its own process group; signalling the negative pid
129
+ * reaps the whole group (the Node bin shim + the native codex binary) at
130
+ * once. Falls back to a direct kill if the group is already gone or on
131
+ * Windows (no process groups).
132
+ */
133
+ terminateChild(child) {
134
+ try {
135
+ if (process.platform !== "win32" && typeof child.pid === "number") {
136
+ process.kill(-child.pid, "SIGTERM");
137
+ return;
138
+ }
139
+ }
140
+ catch {
141
+ // Group already dead or unavailable — fall through to a direct kill.
142
+ }
143
+ try {
144
+ child.kill();
145
+ }
146
+ catch {
147
+ // best-effort
148
+ }
149
+ }
150
+ write(message) {
151
+ try {
152
+ this.child?.stdin.write(`${JSON.stringify(message)}\n`);
153
+ }
154
+ catch (err) {
155
+ this.logger.error("[AppServerClient] write failed", err);
156
+ }
157
+ }
158
+ onLine(line) {
159
+ const trimmed = line.trim();
160
+ if (!trimmed) {
161
+ return;
162
+ }
163
+ let message;
164
+ try {
165
+ message = JSON.parse(trimmed);
166
+ }
167
+ catch {
168
+ this.logger.warn(`[AppServerClient] non-JSON line: ${trimmed.slice(0, 200)}`);
169
+ return;
170
+ }
171
+ // Response to a client request.
172
+ if (message.id !== undefined &&
173
+ (message.result !== undefined || message.error !== undefined)) {
174
+ const entry = this.settlePending(message.id);
175
+ if (!entry) {
176
+ return;
177
+ }
178
+ if (message.error) {
179
+ entry.reject(new Error(`${entry.method} failed: ${message.error.message ?? "unknown error"}`));
180
+ }
181
+ else {
182
+ entry.resolve(message.result);
183
+ }
184
+ return;
185
+ }
186
+ // Server→client request (expects a response).
187
+ if (message.id !== undefined && message.method) {
188
+ void this.handleServerRequest(message.id, message.method, message.params);
189
+ return;
190
+ }
191
+ // Notification.
192
+ if (message.method) {
193
+ try {
194
+ this.notificationHandler?.(message.method, message.params);
195
+ }
196
+ catch (err) {
197
+ this.logger.error(`[AppServerClient] notification handler threw for ${message.method}`, err);
198
+ }
199
+ }
200
+ }
201
+ async handleServerRequest(id, method, params) {
202
+ let result = {};
203
+ try {
204
+ if (this.serverRequestHandler) {
205
+ result = (await this.serverRequestHandler(method, params)) ?? {};
206
+ }
207
+ }
208
+ catch (err) {
209
+ this.logger.error(`[AppServerClient] server-request handler threw for ${method}`, err);
210
+ }
211
+ this.write({ jsonrpc: "2.0", id, result });
212
+ }
213
+ failAllPending(reason) {
214
+ for (const [, entry] of this.pending) {
215
+ if (entry.timer) {
216
+ clearTimeout(entry.timer);
217
+ }
218
+ entry.reject(reason);
219
+ }
220
+ this.pending.clear();
221
+ }
222
+ }
223
+ //# sourceMappingURL=appServerClient.js.map