ever-terminal 1.0.0
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.
- package/LICENSE +21 -0
- package/README.md +266 -0
- package/dist/claude/provider.js +234 -0
- package/dist/claude/session.js +719 -0
- package/dist/claude/summarize.js +97 -0
- package/dist/cli.js +414 -0
- package/dist/codex/app-server.js +300 -0
- package/dist/codex/memory.js +61 -0
- package/dist/codex/provider.js +362 -0
- package/dist/codex/session.js +1091 -0
- package/dist/codex/status.js +16 -0
- package/dist/codex/storage.js +83 -0
- package/dist/codex/summarize.js +69 -0
- package/dist/debug.js +9 -0
- package/dist/expose/providers/bore.js +14 -0
- package/dist/expose/providers/ngrok.js +35 -0
- package/dist/expose/providers/pinggy.js +22 -0
- package/dist/expose/registry.js +22 -0
- package/dist/expose/run.js +75 -0
- package/dist/expose/types.js +1 -0
- package/dist/index.js +78 -0
- package/dist/logger.js +44 -0
- package/dist/routes/core.js +290 -0
- package/dist/routes/events.js +104 -0
- package/dist/session.js +18 -0
- package/dist/startup/common.js +318 -0
- package/dist/startup/instance.js +89 -0
- package/dist/summary-format.js +17 -0
- package/dist/types.js +1 -0
- package/dist/update.js +56 -0
- package/dist/util/spawn-shim.js +25 -0
- package/package.json +79 -0
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import { exec } from "node:child_process";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { debugLog } from "../debug.js";
|
|
5
|
+
import { ensureCodexAppServerStarted } from "../startup/common.js";
|
|
6
|
+
const execAsync = promisify(exec);
|
|
7
|
+
let codexVersionCache = null;
|
|
8
|
+
async function probeCodexVersion() {
|
|
9
|
+
if (codexVersionCache !== null)
|
|
10
|
+
return codexVersionCache;
|
|
11
|
+
try {
|
|
12
|
+
const { stdout } = await execAsync("codex --version", { timeout: 3000 });
|
|
13
|
+
codexVersionCache = stdout.trim() || "0.0.0";
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
codexVersionCache = "0.0.0";
|
|
17
|
+
}
|
|
18
|
+
return codexVersionCache;
|
|
19
|
+
}
|
|
20
|
+
export class CodexAppServerClient {
|
|
21
|
+
ws = null;
|
|
22
|
+
wsUrl;
|
|
23
|
+
initialized = false;
|
|
24
|
+
nextId = 1;
|
|
25
|
+
handleNotification;
|
|
26
|
+
handleServerRequest;
|
|
27
|
+
handleClose;
|
|
28
|
+
pending = new Map();
|
|
29
|
+
initPromise = null;
|
|
30
|
+
constructor(wsUrl) {
|
|
31
|
+
this.wsUrl = wsUrl;
|
|
32
|
+
}
|
|
33
|
+
async threadList(params) {
|
|
34
|
+
const result = await this.call("thread/list", params);
|
|
35
|
+
return {
|
|
36
|
+
data: Array.isArray(result?.data) ? result.data : [],
|
|
37
|
+
nextCursor: result?.nextCursor ?? null,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async threadRead(threadId, includeTurns = true) {
|
|
41
|
+
const result = await this.call("thread/read", {
|
|
42
|
+
threadId,
|
|
43
|
+
includeTurns,
|
|
44
|
+
});
|
|
45
|
+
return result?.thread ?? null;
|
|
46
|
+
}
|
|
47
|
+
/** Paginated turn listing used by getCodexSessionHistory. */
|
|
48
|
+
async threadTurnsList(params) {
|
|
49
|
+
const result = await this.call("thread/turns/list", params);
|
|
50
|
+
return {
|
|
51
|
+
data: Array.isArray(result?.data) ? result.data : [],
|
|
52
|
+
nextCursor: result?.nextCursor ?? null,
|
|
53
|
+
backwardsCursor: result?.backwardsCursor ?? null,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
async threadStart(params) {
|
|
57
|
+
const result = await this.call("thread/start", params);
|
|
58
|
+
return result?.thread ?? null;
|
|
59
|
+
}
|
|
60
|
+
async threadResume(params) {
|
|
61
|
+
const result = await this.call("thread/resume", params);
|
|
62
|
+
return result?.thread ?? null;
|
|
63
|
+
}
|
|
64
|
+
async turnStart(params) {
|
|
65
|
+
const result = await this.call("turn/start", params);
|
|
66
|
+
return result?.turn ?? null;
|
|
67
|
+
}
|
|
68
|
+
async turnInterrupt(threadId, turnId) {
|
|
69
|
+
await this.call("turn/interrupt", { threadId, turnId });
|
|
70
|
+
}
|
|
71
|
+
async threadUnsubscribe(threadId) {
|
|
72
|
+
const result = await this.call("thread/unsubscribe", { threadId });
|
|
73
|
+
return result?.status ?? "unknown";
|
|
74
|
+
}
|
|
75
|
+
async getAccount() {
|
|
76
|
+
try {
|
|
77
|
+
const result = await this.call("account/get", {});
|
|
78
|
+
return result?.account ?? null;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
respondToServerRequest(id, result) {
|
|
85
|
+
this.send({
|
|
86
|
+
jsonrpc: "2.0",
|
|
87
|
+
id,
|
|
88
|
+
result,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** Connect and initialize eagerly (for receiving notifications at startup). */
|
|
92
|
+
async connect() {
|
|
93
|
+
await this.ensureInitialized();
|
|
94
|
+
}
|
|
95
|
+
async close() {
|
|
96
|
+
if (this.ws) {
|
|
97
|
+
this.ws.close();
|
|
98
|
+
this.ws = null;
|
|
99
|
+
}
|
|
100
|
+
this.initialized = false;
|
|
101
|
+
this.initPromise = null;
|
|
102
|
+
for (const [, p] of this.pending) {
|
|
103
|
+
clearTimeout(p.timer);
|
|
104
|
+
p.reject(new Error("Client closed"));
|
|
105
|
+
}
|
|
106
|
+
this.pending.clear();
|
|
107
|
+
}
|
|
108
|
+
send(msg) {
|
|
109
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
110
|
+
return;
|
|
111
|
+
this.ws.send(JSON.stringify(msg));
|
|
112
|
+
}
|
|
113
|
+
async call(method, params) {
|
|
114
|
+
await this.ensureInitialized();
|
|
115
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
116
|
+
throw new Error("codex app-server not connected");
|
|
117
|
+
}
|
|
118
|
+
const id = this.nextId++;
|
|
119
|
+
const req = {
|
|
120
|
+
jsonrpc: "2.0",
|
|
121
|
+
id,
|
|
122
|
+
method,
|
|
123
|
+
params,
|
|
124
|
+
};
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
const timer = setTimeout(() => {
|
|
127
|
+
this.pending.delete(id);
|
|
128
|
+
reject(new Error(`RPC timeout: ${method}`));
|
|
129
|
+
}, 30000);
|
|
130
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
131
|
+
this.send(req);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
async ensureInitialized() {
|
|
135
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN && this.initialized)
|
|
136
|
+
return;
|
|
137
|
+
if (this.initPromise)
|
|
138
|
+
return this.initPromise;
|
|
139
|
+
this.initPromise = this.connectAndInitialize().finally(() => {
|
|
140
|
+
this.initPromise = null;
|
|
141
|
+
});
|
|
142
|
+
return this.initPromise;
|
|
143
|
+
}
|
|
144
|
+
async connectAndInitialize() {
|
|
145
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
146
|
+
const started = await ensureCodexAppServerStarted();
|
|
147
|
+
if (!started) {
|
|
148
|
+
throw new Error("codex app-server failed to start (see [codex] logs above)");
|
|
149
|
+
}
|
|
150
|
+
await this.connectWebSocket();
|
|
151
|
+
}
|
|
152
|
+
if (!this.initialized) {
|
|
153
|
+
await this.callRaw("initialize", {
|
|
154
|
+
clientInfo: {
|
|
155
|
+
name: "codex",
|
|
156
|
+
version: await probeCodexVersion(),
|
|
157
|
+
},
|
|
158
|
+
capabilities: {
|
|
159
|
+
experimentalApi: true,
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
this.initialized = true;
|
|
163
|
+
// Send initialized notification
|
|
164
|
+
this.send({ jsonrpc: "2.0", method: "initialized" });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
connectWebSocket() {
|
|
168
|
+
return new Promise((resolve, reject) => {
|
|
169
|
+
const ws = new WebSocket(this.wsUrl);
|
|
170
|
+
const connectTimeout = setTimeout(() => {
|
|
171
|
+
ws.close();
|
|
172
|
+
reject(new Error(`WebSocket connect timeout: ${this.wsUrl}`));
|
|
173
|
+
}, 10000);
|
|
174
|
+
ws.on("open", () => {
|
|
175
|
+
clearTimeout(connectTimeout);
|
|
176
|
+
this.ws = ws;
|
|
177
|
+
resolve();
|
|
178
|
+
});
|
|
179
|
+
ws.on("message", (data) => {
|
|
180
|
+
const text = typeof data === "string" ? data : data.toString();
|
|
181
|
+
// app-server may send multiple JSON-RPC messages in one frame (newline-delimited)
|
|
182
|
+
for (const line of text.split("\n")) {
|
|
183
|
+
if (line.trim())
|
|
184
|
+
this.handleLine(line);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
ws.on("error", (err) => {
|
|
188
|
+
clearTimeout(connectTimeout);
|
|
189
|
+
console.error(`[codex-app-server] ws error: ${err.message}`);
|
|
190
|
+
const e = new Error(`codex app-server ws error: ${err.message}`);
|
|
191
|
+
for (const [, p] of this.pending) {
|
|
192
|
+
clearTimeout(p.timer);
|
|
193
|
+
p.reject(e);
|
|
194
|
+
}
|
|
195
|
+
this.pending.clear();
|
|
196
|
+
this.ws = null;
|
|
197
|
+
this.initialized = false;
|
|
198
|
+
try {
|
|
199
|
+
this.handleClose?.(e);
|
|
200
|
+
}
|
|
201
|
+
catch { }
|
|
202
|
+
reject(e);
|
|
203
|
+
});
|
|
204
|
+
ws.on("close", () => {
|
|
205
|
+
const err = new Error("codex app-server ws closed");
|
|
206
|
+
for (const [, p] of this.pending) {
|
|
207
|
+
clearTimeout(p.timer);
|
|
208
|
+
p.reject(err);
|
|
209
|
+
}
|
|
210
|
+
this.pending.clear();
|
|
211
|
+
this.ws = null;
|
|
212
|
+
this.initialized = false;
|
|
213
|
+
try {
|
|
214
|
+
this.handleClose?.(err);
|
|
215
|
+
}
|
|
216
|
+
catch { }
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
async callRaw(method, params) {
|
|
221
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
222
|
+
throw new Error("codex app-server not connected");
|
|
223
|
+
}
|
|
224
|
+
const id = this.nextId++;
|
|
225
|
+
const req = {
|
|
226
|
+
jsonrpc: "2.0",
|
|
227
|
+
id,
|
|
228
|
+
method,
|
|
229
|
+
params,
|
|
230
|
+
};
|
|
231
|
+
return new Promise((resolve, reject) => {
|
|
232
|
+
const timer = setTimeout(() => {
|
|
233
|
+
this.pending.delete(id);
|
|
234
|
+
reject(new Error(`RPC timeout: ${method}`));
|
|
235
|
+
}, 10000);
|
|
236
|
+
this.pending.set(id, { resolve, reject, timer });
|
|
237
|
+
this.send(req);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
handleLine(line) {
|
|
241
|
+
const trimmed = line.trim();
|
|
242
|
+
if (!trimmed)
|
|
243
|
+
return;
|
|
244
|
+
let msg;
|
|
245
|
+
try {
|
|
246
|
+
msg = JSON.parse(trimmed);
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
// JSON-RPC notification (no id)
|
|
252
|
+
if (msg?.id === undefined || msg?.id === null) {
|
|
253
|
+
if (typeof msg?.method === "string") {
|
|
254
|
+
debugLog("codex-app-server", `notification ${msg.method}`, toOneLineJson(msg.params ?? {}));
|
|
255
|
+
try {
|
|
256
|
+
this.handleNotification?.(msg.method, msg.params ?? {});
|
|
257
|
+
}
|
|
258
|
+
catch { }
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
// JSON-RPC server request (has method + id)
|
|
263
|
+
if (typeof msg?.method === "string" && msg?.id !== undefined) {
|
|
264
|
+
debugLog("codex-app-server", `server_request id=${String(msg.id)} method=${msg.method}`, toOneLineJson(msg.params ?? {}));
|
|
265
|
+
try {
|
|
266
|
+
this.handleServerRequest?.(msg.id, msg.method, msg.params ?? {});
|
|
267
|
+
}
|
|
268
|
+
catch { }
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
// JSON-RPC response
|
|
272
|
+
const pending = this.pending.get(msg.id);
|
|
273
|
+
if (!pending)
|
|
274
|
+
return;
|
|
275
|
+
this.pending.delete(msg.id);
|
|
276
|
+
clearTimeout(pending.timer);
|
|
277
|
+
if (msg.error) {
|
|
278
|
+
const err = Object.assign(new Error(msg.error.message || "RPC error"), {
|
|
279
|
+
rpcCode: msg.error.code,
|
|
280
|
+
rpcData: msg.error.data,
|
|
281
|
+
});
|
|
282
|
+
pending.reject(err);
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
pending.resolve(msg.result);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function toOneLineJson(value) {
|
|
289
|
+
try {
|
|
290
|
+
const raw = JSON.stringify(value);
|
|
291
|
+
if (!raw)
|
|
292
|
+
return "";
|
|
293
|
+
return raw.length > 1000
|
|
294
|
+
? raw.slice(0, 1000) + "...(truncated)"
|
|
295
|
+
: raw;
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
return "";
|
|
299
|
+
}
|
|
300
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const threadMeta = new Map();
|
|
2
|
+
const threadMessages = new Map();
|
|
3
|
+
export function recordThreadMeta(id, cwd, title) {
|
|
4
|
+
if (!id)
|
|
5
|
+
return;
|
|
6
|
+
const prev = threadMeta.get(id);
|
|
7
|
+
threadMeta.set(id, {
|
|
8
|
+
id,
|
|
9
|
+
cwd: cwd || prev?.cwd || "",
|
|
10
|
+
title: (title || prev?.title || "Codex session").slice(0, 64),
|
|
11
|
+
updatedAt: Date.now(),
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export function appendThreadMessage(id, role, text) {
|
|
15
|
+
if (!id || !text.trim())
|
|
16
|
+
return;
|
|
17
|
+
const arr = threadMessages.get(id) ?? [];
|
|
18
|
+
arr.push({ role, text: text.trim(), ts: Date.now() });
|
|
19
|
+
if (arr.length > 80)
|
|
20
|
+
arr.splice(0, arr.length - 80);
|
|
21
|
+
threadMessages.set(id, arr);
|
|
22
|
+
const meta = threadMeta.get(id);
|
|
23
|
+
if (meta) {
|
|
24
|
+
meta.updatedAt = Date.now();
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function listInMemorySessions(cwd) {
|
|
28
|
+
return Array.from(threadMeta.values())
|
|
29
|
+
.filter((m) => !cwd || m.cwd === cwd)
|
|
30
|
+
.sort((a, b) => b.updatedAt - a.updatedAt)
|
|
31
|
+
.slice(0, 10)
|
|
32
|
+
.map((m) => ({
|
|
33
|
+
id: m.id,
|
|
34
|
+
title: m.title || "Codex session",
|
|
35
|
+
timestamp: new Date(m.updatedAt).toISOString(),
|
|
36
|
+
cwd: m.cwd || "",
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
39
|
+
export function getInMemoryHistory(threadId, limit) {
|
|
40
|
+
const rows = threadMessages.get(threadId) ?? [];
|
|
41
|
+
if (rows.length === 0)
|
|
42
|
+
return [];
|
|
43
|
+
const rounds = [];
|
|
44
|
+
let cur = [];
|
|
45
|
+
for (const m of rows) {
|
|
46
|
+
if (m.role === "user") {
|
|
47
|
+
if (cur.length > 0)
|
|
48
|
+
rounds.push(cur);
|
|
49
|
+
cur = [{ role: "user", text: m.text }];
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
cur.push({ role: "assistant", text: m.text });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (cur.length > 0)
|
|
56
|
+
rounds.push(cur);
|
|
57
|
+
return rounds
|
|
58
|
+
.slice(-limit)
|
|
59
|
+
.flat()
|
|
60
|
+
.map((m) => ({ role: m.role, text: m.text.slice(0, 800) }));
|
|
61
|
+
}
|