remcodex 0.1.0-beta.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 (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +331 -0
  3. package/dist/server/src/app.js +186 -0
  4. package/dist/server/src/cli.js +270 -0
  5. package/dist/server/src/controllers/codex-options.controller.js +199 -0
  6. package/dist/server/src/controllers/message.controller.js +21 -0
  7. package/dist/server/src/controllers/project.controller.js +44 -0
  8. package/dist/server/src/controllers/session.controller.js +175 -0
  9. package/dist/server/src/db/client.js +10 -0
  10. package/dist/server/src/db/migrations.js +32 -0
  11. package/dist/server/src/gateways/ws.gateway.js +60 -0
  12. package/dist/server/src/services/codex-app-server-runner.js +363 -0
  13. package/dist/server/src/services/codex-exec-runner.js +147 -0
  14. package/dist/server/src/services/codex-rollout-sync.js +977 -0
  15. package/dist/server/src/services/codex-runner.js +11 -0
  16. package/dist/server/src/services/codex-stream-events.js +478 -0
  17. package/dist/server/src/services/event-store.js +328 -0
  18. package/dist/server/src/services/project-manager.js +130 -0
  19. package/dist/server/src/services/pty-runner.js +72 -0
  20. package/dist/server/src/services/session-manager.js +1586 -0
  21. package/dist/server/src/services/session-timeline-service.js +181 -0
  22. package/dist/server/src/types/codex-launch.js +2 -0
  23. package/dist/server/src/types/models.js +37 -0
  24. package/dist/server/src/utils/ansi.js +143 -0
  25. package/dist/server/src/utils/codex-launch.js +102 -0
  26. package/dist/server/src/utils/codex-quota.js +179 -0
  27. package/dist/server/src/utils/codex-status.js +163 -0
  28. package/dist/server/src/utils/codex-ui-options.js +114 -0
  29. package/dist/server/src/utils/command.js +46 -0
  30. package/dist/server/src/utils/errors.js +16 -0
  31. package/dist/server/src/utils/ids.js +7 -0
  32. package/dist/server/src/utils/node-pty.js +29 -0
  33. package/package.json +36 -0
  34. package/scripts/fix-node-pty-helper.js +36 -0
  35. package/web/api.js +175 -0
  36. package/web/app.js +8082 -0
  37. package/web/components/composer.js +627 -0
  38. package/web/components/session-workbench.js +173 -0
  39. package/web/i18n/index.js +171 -0
  40. package/web/i18n/locales/de.js +50 -0
  41. package/web/i18n/locales/en.js +320 -0
  42. package/web/i18n/locales/es.js +50 -0
  43. package/web/i18n/locales/fr.js +50 -0
  44. package/web/i18n/locales/ja.js +50 -0
  45. package/web/i18n/locales/ko.js +50 -0
  46. package/web/i18n/locales/pt-BR.js +50 -0
  47. package/web/i18n/locales/ru.js +50 -0
  48. package/web/i18n/locales/zh-CN.js +320 -0
  49. package/web/i18n/locales/zh-Hant.js +53 -0
  50. package/web/index.html +23 -0
  51. package/web/message-rich-text.js +218 -0
  52. package/web/session-command-activity.js +980 -0
  53. package/web/session-event-adapter.js +826 -0
  54. package/web/session-timeline-reducer.js +728 -0
  55. package/web/session-timeline-renderer.js +656 -0
  56. package/web/session-ws.js +31 -0
  57. package/web/styles.css +5665 -0
  58. package/web/vendor/markdown-it.js +6969 -0
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createSessionRouter = createSessionRouter;
4
+ const express_1 = require("express");
5
+ function createSessionRouter(sessionManager, eventStore, projectManager, codexRolloutSync, sessionTimeline) {
6
+ const router = (0, express_1.Router)();
7
+ router.get("/", (_request, response) => {
8
+ const items = sessionManager.listSessions().map((session) => ({
9
+ sessionId: session.id,
10
+ title: session.title,
11
+ projectId: session.project_id,
12
+ status: session.status,
13
+ liveBusy: sessionManager.isLiveBusy(session.id),
14
+ codexThreadId: session.codex_thread_id,
15
+ sourceKind: session.source_kind,
16
+ sourceRolloutPath: session.source_rollout_path,
17
+ sourceThreadId: session.source_thread_id,
18
+ sourceRolloutHasOpenTurn: session.source_rollout_has_open_turn === 1,
19
+ pendingApproval: sessionManager.getPendingApproval(session.id),
20
+ lastEventAt: session.last_event_at,
21
+ lastAssistantContent: session.last_assistant_content,
22
+ lastCommand: session.last_command,
23
+ eventCount: session.event_count,
24
+ createdAt: session.created_at,
25
+ updatedAt: session.updated_at,
26
+ }));
27
+ response.json({ items });
28
+ });
29
+ router.post("/import-codex", (request, response, next) => {
30
+ try {
31
+ const body = request.body;
32
+ const result = codexRolloutSync.importRollout(body.rolloutPath ?? "");
33
+ response.status(result.imported ? 201 : 200).json(result);
34
+ }
35
+ catch (error) {
36
+ next(error);
37
+ }
38
+ });
39
+ router.post("/", (request, response, next) => {
40
+ try {
41
+ const body = request.body;
42
+ const session = sessionManager.createSession({
43
+ title: body.title,
44
+ projectId: body.projectId ?? "",
45
+ });
46
+ response.status(201).json({
47
+ sessionId: session.id,
48
+ status: session.status,
49
+ });
50
+ }
51
+ catch (error) {
52
+ next(error);
53
+ }
54
+ });
55
+ router.get("/:sessionId", (request, response, next) => {
56
+ try {
57
+ const session = sessionManager.getSession(request.params.sessionId);
58
+ if (!session) {
59
+ response.status(404).json({ error: "Session not found." });
60
+ return;
61
+ }
62
+ const project = projectManager.getProject(session.project_id);
63
+ response.json({
64
+ sessionId: session.id,
65
+ title: session.title,
66
+ projectId: session.project_id,
67
+ projectName: project?.name ?? null,
68
+ projectPath: project?.path ?? null,
69
+ status: session.status,
70
+ liveBusy: sessionManager.isLiveBusy(session.id),
71
+ pid: session.pid,
72
+ codexThreadId: session.codex_thread_id,
73
+ sourceKind: session.source_kind,
74
+ sourceRolloutPath: session.source_rollout_path,
75
+ sourceThreadId: session.source_thread_id,
76
+ sourceRolloutHasOpenTurn: session.source_rollout_has_open_turn === 1,
77
+ pendingApproval: sessionManager.getPendingApproval(session.id),
78
+ createdAt: session.created_at,
79
+ updatedAt: session.updated_at,
80
+ });
81
+ }
82
+ catch (error) {
83
+ next(error);
84
+ }
85
+ });
86
+ router.get("/:sessionId/events", (request, response, next) => {
87
+ try {
88
+ const after = Number.parseInt(String(request.query.after ?? request.query.cursor ?? "0"), 10);
89
+ const before = Number.parseInt(String(request.query.before ?? "0"), 10);
90
+ const limit = Number.parseInt(String(request.query.limit ?? "200"), 10);
91
+ const safeAfter = Number.isNaN(after) ? 0 : after;
92
+ const safeBefore = Number.isNaN(before) ? 0 : before;
93
+ const safeLimit = Number.isNaN(limit) ? 200 : limit;
94
+ if (!sessionManager.hasSession(request.params.sessionId)) {
95
+ response.status(404).json({ error: "Session not found." });
96
+ return;
97
+ }
98
+ const data = eventStore.list(request.params.sessionId, {
99
+ after: safeAfter,
100
+ before: safeBefore,
101
+ limit: safeLimit,
102
+ });
103
+ response.json(data);
104
+ }
105
+ catch (error) {
106
+ next(error);
107
+ }
108
+ });
109
+ router.get("/:sessionId/timeline", (request, response, next) => {
110
+ try {
111
+ const after = Number.parseInt(String(request.query.after ?? request.query.cursor ?? "0"), 10);
112
+ const before = Number.parseInt(String(request.query.before ?? "0"), 10);
113
+ const limit = Number.parseInt(String(request.query.limit ?? "200"), 10);
114
+ const safeAfter = Number.isNaN(after) ? 0 : after;
115
+ const safeBefore = Number.isNaN(before) ? 0 : before;
116
+ const safeLimit = Number.isNaN(limit) ? 200 : limit;
117
+ if (!sessionManager.hasSession(request.params.sessionId)) {
118
+ response.status(404).json({ error: "Session not found." });
119
+ return;
120
+ }
121
+ const data = sessionTimeline.list(request.params.sessionId, {
122
+ after: safeAfter,
123
+ before: safeBefore,
124
+ limit: safeLimit,
125
+ });
126
+ response.json(data);
127
+ }
128
+ catch (error) {
129
+ next(error);
130
+ }
131
+ });
132
+ router.post("/:sessionId/sync", (request, response, next) => {
133
+ try {
134
+ const result = codexRolloutSync.syncImportedSession(request.params.sessionId);
135
+ response.json(result);
136
+ }
137
+ catch (error) {
138
+ next(error);
139
+ }
140
+ });
141
+ router.post("/:sessionId/stop", (request, response, next) => {
142
+ try {
143
+ const result = sessionManager.stopSession(request.params.sessionId);
144
+ response.json(result);
145
+ }
146
+ catch (error) {
147
+ next(error);
148
+ }
149
+ });
150
+ router.post("/:sessionId/approvals/:requestId", (request, response, next) => {
151
+ try {
152
+ const body = request.body;
153
+ const decision = body.decision === "accept" ||
154
+ body.decision === "acceptForSession" ||
155
+ body.decision === "decline"
156
+ ? body.decision
157
+ : null;
158
+ if (!decision) {
159
+ response.status(400).json({ error: "decision is required." });
160
+ return;
161
+ }
162
+ const requestId = String(request.params.requestId || "").trim();
163
+ if (!requestId) {
164
+ response.status(400).json({ error: "requestId is invalid." });
165
+ return;
166
+ }
167
+ const result = sessionManager.resolveApproval(request.params.sessionId, requestId, decision);
168
+ response.json(result);
169
+ }
170
+ catch (error) {
171
+ next(error);
172
+ }
173
+ });
174
+ return router;
175
+ }
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createDatabase = createDatabase;
7
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
8
+ function createDatabase(file) {
9
+ return new better_sqlite3_1.default(file);
10
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runMigrations = runMigrations;
7
+ const node_fs_1 = require("node:fs");
8
+ const node_path_1 = __importDefault(require("node:path"));
9
+ function ensureColumn(db, table, column, definition) {
10
+ const rows = db.prepare(`PRAGMA table_info(${table})`).all();
11
+ if (rows.some((row) => row.name === column)) {
12
+ return;
13
+ }
14
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`);
15
+ }
16
+ function runMigrations(db) {
17
+ const schemaFile = node_path_1.default.join(process.cwd(), "server", "src", "db", "schema.sql");
18
+ const schema = (0, node_fs_1.readFileSync)(schemaFile, "utf8");
19
+ db.exec(schema);
20
+ ensureColumn(db, "sessions", "source_kind", "TEXT NOT NULL DEFAULT 'native'");
21
+ ensureColumn(db, "sessions", "source_rollout_path", "TEXT");
22
+ ensureColumn(db, "sessions", "source_thread_id", "TEXT");
23
+ ensureColumn(db, "sessions", "source_sync_cursor", "INTEGER");
24
+ ensureColumn(db, "sessions", "source_last_synced_at", "TEXT");
25
+ ensureColumn(db, "sessions", "source_rollout_has_open_turn", "INTEGER NOT NULL DEFAULT 0");
26
+ // Older imports mapped external open turns to status=running; that is not local runner state.
27
+ db.exec(`UPDATE sessions
28
+ SET status = 'waiting_input'
29
+ WHERE source_kind = 'imported_rollout'
30
+ AND pid IS NULL
31
+ AND status = 'running'`);
32
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerSessionGateway = registerSessionGateway;
4
+ const ws_1 = require("ws");
5
+ function registerSessionGateway(server, options) {
6
+ const wss = new ws_1.WebSocketServer({ noServer: true });
7
+ server.on("upgrade", (request, socket, head) => {
8
+ const url = new URL(request.url ?? "/", `http://${request.headers.host}`);
9
+ const matched = url.pathname.match(/^\/ws\/sessions\/([^/]+)$/);
10
+ if (!matched) {
11
+ socket.destroy();
12
+ return;
13
+ }
14
+ const sessionId = matched[1];
15
+ if (!options.sessionManager.hasSession(sessionId)) {
16
+ socket.destroy();
17
+ return;
18
+ }
19
+ wss.handleUpgrade(request, socket, head, (ws) => {
20
+ const sessionSocket = ws;
21
+ sessionSocket.sessionId = sessionId;
22
+ sessionSocket.isAlive = true;
23
+ wss.emit("connection", sessionSocket, request);
24
+ });
25
+ });
26
+ wss.on("connection", (ws) => {
27
+ const sessionSocket = ws;
28
+ const sessionId = sessionSocket.sessionId;
29
+ if (!sessionId) {
30
+ ws.close();
31
+ return;
32
+ }
33
+ const unsubscribe = options.eventStore.subscribe(sessionId, (event) => {
34
+ if (ws.readyState === ws_1.WebSocket.OPEN) {
35
+ ws.send(JSON.stringify(event));
36
+ }
37
+ });
38
+ const heartbeat = setInterval(() => {
39
+ if (!sessionSocket.isAlive) {
40
+ ws.terminate();
41
+ return;
42
+ }
43
+ sessionSocket.isAlive = false;
44
+ ws.ping();
45
+ }, 30000);
46
+ heartbeat.unref();
47
+ ws.on("pong", () => {
48
+ sessionSocket.isAlive = true;
49
+ });
50
+ ws.on("close", () => {
51
+ clearInterval(heartbeat);
52
+ unsubscribe();
53
+ });
54
+ ws.on("error", () => {
55
+ clearInterval(heartbeat);
56
+ unsubscribe();
57
+ ws.close();
58
+ });
59
+ });
60
+ }
@@ -0,0 +1,363 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.CodexAppServerRunner = void 0;
7
+ const node_child_process_1 = require("node:child_process");
8
+ const node_os_1 = __importDefault(require("node:os"));
9
+ const node_path_1 = __importDefault(require("node:path"));
10
+ class CodexAppServerRunner {
11
+ command;
12
+ cwd;
13
+ process = null;
14
+ jsonListeners = new Set();
15
+ textListeners = new Set();
16
+ exitListeners = new Set();
17
+ pendingRequests = new Map();
18
+ stdoutBuffer = "";
19
+ requestId = 1;
20
+ finalized = false;
21
+ stopTimer = null;
22
+ constructor(command, cwd) {
23
+ this.command = command;
24
+ this.cwd = cwd;
25
+ }
26
+ start(prompt, threadId, launch) {
27
+ this.process = (0, node_child_process_1.spawn)(this.command, ["app-server", "--listen", "stdio://"], {
28
+ cwd: this.cwd,
29
+ env: process.env,
30
+ stdio: "pipe",
31
+ });
32
+ if (!this.process.pid) {
33
+ throw new Error("Failed to start codex app-server process.");
34
+ }
35
+ this.process.stdout.on("data", (chunk) => {
36
+ this.handleStdout(chunk.toString("utf8"));
37
+ });
38
+ this.process.stderr.on("data", (chunk) => {
39
+ this.emitText("stderr", chunk.toString("utf8"));
40
+ });
41
+ this.process.on("close", (exitCode) => {
42
+ this.clearStopTimer();
43
+ this.flushStdoutRemainder();
44
+ this.rejectPendingRequests(new Error("Codex app-server closed before replying to all requests."));
45
+ this.process = null;
46
+ if (!this.finalized) {
47
+ this.finalized = true;
48
+ this.exitListeners.forEach((listener) => listener(exitCode));
49
+ }
50
+ });
51
+ this.process.on("error", (error) => {
52
+ this.emitText("stderr", `Failed to spawn Codex app-server: ${error.message}`);
53
+ });
54
+ void this.bootstrap(prompt, threadId, launch).catch((error) => {
55
+ this.emitText("stderr", `Failed to initialize Codex app-server: ${this.messageOf(error)}`);
56
+ this.finish(1);
57
+ });
58
+ return this.process.pid;
59
+ }
60
+ stop() {
61
+ if (!this.process) {
62
+ return;
63
+ }
64
+ this.process.stdin.end();
65
+ this.process.kill("SIGINT");
66
+ this.stopTimer = setTimeout(() => {
67
+ this.process?.kill("SIGTERM");
68
+ }, 1500);
69
+ this.stopTimer.unref();
70
+ }
71
+ respond(requestId, result) {
72
+ if (!this.process || !Number.isFinite(requestId)) {
73
+ return false;
74
+ }
75
+ this.respondJsonRpc(requestId, result);
76
+ return true;
77
+ }
78
+ onJsonEvent(listener) {
79
+ this.jsonListeners.add(listener);
80
+ return () => {
81
+ this.jsonListeners.delete(listener);
82
+ };
83
+ }
84
+ onText(listener) {
85
+ this.textListeners.add(listener);
86
+ return () => {
87
+ this.textListeners.delete(listener);
88
+ };
89
+ }
90
+ onExit(listener) {
91
+ this.exitListeners.add(listener);
92
+ return () => {
93
+ this.exitListeners.delete(listener);
94
+ };
95
+ }
96
+ isAlive() {
97
+ return this.process !== null;
98
+ }
99
+ async bootstrap(prompt, threadId, launch) {
100
+ await this.request("initialize", {
101
+ clientInfo: {
102
+ name: "remote-agent-console",
103
+ version: "0.1.0",
104
+ },
105
+ capabilities: {
106
+ experimentalApi: true,
107
+ },
108
+ });
109
+ this.notify("initialized");
110
+ const approvalPolicy = "on-request";
111
+ const sandboxMode = launch?.sandbox ?? "workspace-write";
112
+ const config = this.buildConfig(launch);
113
+ const threadResult = (threadId
114
+ ? await this.request("thread/resume", {
115
+ threadId,
116
+ cwd: this.cwd,
117
+ approvalPolicy,
118
+ sandbox: sandboxMode,
119
+ config,
120
+ persistExtendedHistory: true,
121
+ })
122
+ : await this.request("thread/start", {
123
+ cwd: this.cwd,
124
+ approvalPolicy,
125
+ sandbox: sandboxMode,
126
+ config,
127
+ experimentalRawEvents: false,
128
+ persistExtendedHistory: true,
129
+ }));
130
+ const resolvedThreadId = threadResult.thread?.id ?? threadId;
131
+ if (!resolvedThreadId) {
132
+ throw new Error("Codex app-server did not return a thread id.");
133
+ }
134
+ await this.request("turn/start", {
135
+ threadId: resolvedThreadId,
136
+ input: [
137
+ {
138
+ type: "text",
139
+ text: prompt,
140
+ text_elements: [],
141
+ },
142
+ ],
143
+ approvalPolicy,
144
+ sandboxPolicy: this.buildSandboxPolicy(sandboxMode, launch),
145
+ model: launch?.model ?? null,
146
+ effort: launch?.reasoningEffort ?? null,
147
+ });
148
+ }
149
+ buildConfig(launch) {
150
+ const config = {};
151
+ if (launch?.profile) {
152
+ config.profile = launch.profile;
153
+ }
154
+ if (launch?.enableFeatures?.length) {
155
+ for (const name of launch.enableFeatures) {
156
+ config[`features.${name}`] = true;
157
+ }
158
+ }
159
+ if (launch?.disableFeatures?.length) {
160
+ for (const name of launch.disableFeatures) {
161
+ config[`features.${name}`] = false;
162
+ }
163
+ }
164
+ return Object.keys(config).length > 0 ? config : null;
165
+ }
166
+ buildSandboxPolicy(sandboxMode, launch) {
167
+ if (sandboxMode === "read-only") {
168
+ return {
169
+ type: "readOnly",
170
+ access: {
171
+ type: "fullAccess",
172
+ },
173
+ networkAccess: false,
174
+ };
175
+ }
176
+ if (sandboxMode === "danger-full-access") {
177
+ return {
178
+ type: "dangerFullAccess",
179
+ };
180
+ }
181
+ return {
182
+ type: "workspaceWrite",
183
+ writableRoots: this.workspaceWritableRoots(launch?.additionalWritableRoots),
184
+ readOnlyAccess: {
185
+ type: "fullAccess",
186
+ },
187
+ networkAccess: false,
188
+ excludeTmpdirEnvVar: false,
189
+ excludeSlashTmp: false,
190
+ };
191
+ }
192
+ workspaceWritableRoots(additionalRoots) {
193
+ const roots = new Set();
194
+ roots.add(this.cwd);
195
+ for (const root of additionalRoots ?? []) {
196
+ if (typeof root === "string" && root.trim()) {
197
+ roots.add(root.trim());
198
+ }
199
+ }
200
+ roots.add(node_path_1.default.join(node_os_1.default.homedir(), ".codex", "memories"));
201
+ return [...roots];
202
+ }
203
+ request(method, params) {
204
+ const id = this.requestId++;
205
+ const request = {
206
+ jsonrpc: "2.0",
207
+ id,
208
+ method,
209
+ params,
210
+ };
211
+ return new Promise((resolve, reject) => {
212
+ this.pendingRequests.set(id, { resolve, reject });
213
+ this.writeJson(request);
214
+ });
215
+ }
216
+ notify(method, params) {
217
+ this.writeJson({
218
+ jsonrpc: "2.0",
219
+ method,
220
+ params,
221
+ });
222
+ }
223
+ writeJson(message) {
224
+ if (!this.process) {
225
+ throw new Error("Codex app-server process is not running.");
226
+ }
227
+ this.process.stdin.write(`${JSON.stringify(message)}\n`);
228
+ }
229
+ handleStdout(chunk) {
230
+ this.stdoutBuffer += chunk;
231
+ const lines = this.stdoutBuffer.split(/\r?\n/);
232
+ this.stdoutBuffer = lines.pop() ?? "";
233
+ for (const line of lines) {
234
+ this.handleJsonLine(line);
235
+ }
236
+ }
237
+ flushStdoutRemainder() {
238
+ if (!this.stdoutBuffer.trim()) {
239
+ this.stdoutBuffer = "";
240
+ return;
241
+ }
242
+ this.handleJsonLine(this.stdoutBuffer);
243
+ this.stdoutBuffer = "";
244
+ }
245
+ handleJsonLine(line) {
246
+ const trimmed = line.trim();
247
+ if (!trimmed) {
248
+ return;
249
+ }
250
+ try {
251
+ const message = JSON.parse(trimmed);
252
+ this.handleJsonMessage(message);
253
+ }
254
+ catch {
255
+ this.emitText("stdout", trimmed);
256
+ }
257
+ }
258
+ handleJsonMessage(message) {
259
+ if (!message || typeof message !== "object") {
260
+ return;
261
+ }
262
+ if ("id" in message && typeof message.id === "number" && !("method" in message)) {
263
+ const response = message;
264
+ const pending = this.pendingRequests.get(message.id);
265
+ if (!pending) {
266
+ return;
267
+ }
268
+ this.pendingRequests.delete(message.id);
269
+ if (response.error) {
270
+ pending.reject(new Error(response.error.message || `JSON-RPC request failed (${message.id}).`));
271
+ return;
272
+ }
273
+ pending.resolve(response.result);
274
+ return;
275
+ }
276
+ if (!("method" in message) || typeof message.method !== "string") {
277
+ return;
278
+ }
279
+ if ("id" in message && typeof message.id === "number") {
280
+ this.handleServerRequest(message);
281
+ return;
282
+ }
283
+ this.handleNotification(message);
284
+ }
285
+ handleServerRequest(message) {
286
+ this.jsonListeners.forEach((listener) => listener(message));
287
+ switch (message.method) {
288
+ case "item/commandExecution/requestApproval":
289
+ case "item/fileChange/requestApproval":
290
+ case "item/permissions/requestApproval":
291
+ case "execCommandApproval":
292
+ case "applyPatchApproval":
293
+ return;
294
+ default:
295
+ this.respondJsonRpcError(message.id, -32601, `Unsupported server request: ${message.method}`);
296
+ }
297
+ }
298
+ handleNotification(message) {
299
+ const method = message.method;
300
+ this.jsonListeners.forEach((listener) => listener(message));
301
+ if (method === "turn/completed") {
302
+ const params = message.params;
303
+ const turn = params?.turn;
304
+ this.finish(turn?.status === "failed" ? 1 : 0);
305
+ }
306
+ }
307
+ respondJsonRpc(id, result) {
308
+ this.writeJson({
309
+ jsonrpc: "2.0",
310
+ id,
311
+ result,
312
+ });
313
+ }
314
+ respondJsonRpcError(id, code, message) {
315
+ this.writeJson({
316
+ jsonrpc: "2.0",
317
+ id,
318
+ error: {
319
+ code,
320
+ message,
321
+ },
322
+ });
323
+ }
324
+ finish(exitCode) {
325
+ if (this.finalized) {
326
+ return;
327
+ }
328
+ this.finalized = true;
329
+ this.exitListeners.forEach((listener) => listener(exitCode));
330
+ if (!this.process) {
331
+ return;
332
+ }
333
+ this.process.stdin.end();
334
+ this.process.kill("SIGTERM");
335
+ }
336
+ rejectPendingRequests(error) {
337
+ for (const pending of this.pendingRequests.values()) {
338
+ pending.reject(error);
339
+ }
340
+ this.pendingRequests.clear();
341
+ }
342
+ clearStopTimer() {
343
+ if (!this.stopTimer) {
344
+ return;
345
+ }
346
+ clearTimeout(this.stopTimer);
347
+ this.stopTimer = null;
348
+ }
349
+ emitText(stream, text) {
350
+ if (!text) {
351
+ return;
352
+ }
353
+ const normalized = text.trimEnd();
354
+ if (!normalized) {
355
+ return;
356
+ }
357
+ this.textListeners.forEach((listener) => listener(stream, normalized));
358
+ }
359
+ messageOf(error) {
360
+ return error instanceof Error ? error.message : String(error);
361
+ }
362
+ }
363
+ exports.CodexAppServerRunner = CodexAppServerRunner;