@zetaloop/chappie 1.0.0 → 1.1.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/.codex-plugin/plugin.json +1 -1
- package/README.md +1 -1
- package/dist/{chunk-DM523QRR.js → chunk-3MLW5U2C.js} +2 -2
- package/dist/{chunk-DN7OHZK6.js → chunk-DDBZIUBF.js} +1 -1
- package/dist/{chunk-5CKVVDZ2.js → chunk-G4LG7HH2.js} +3 -9
- package/dist/{chunk-K2VE2MDU.js → chunk-QNDFCQ66.js} +2 -2
- package/dist/{chunk-OWAAWZNT.js → chunk-QTALHBOI.js} +1 -1
- package/dist/{chunk-GX4JWLHH.js → chunk-RQKWETGT.js} +1 -1
- package/dist/cli.js +18 -11
- package/dist/{codex-RI2XL4HV.js → codex-XVRO56TR.js} +735 -135
- package/dist/{codex-plugin-NSKWB3WS.js → codex-plugin-BFH2DWSA.js} +40 -14
- package/dist/index.js +6 -18
- package/dist/omp.js +10 -22
- package/dist/opencode.js +7 -8
- package/dist/{stdio-KJVCYPXJ.js → stdio-OC7FLZCL.js} +9 -9
- package/docs/setup.md +23 -22
- package/docs/tools.md +24 -24
- package/package.json +1 -1
- package/dist/{chunk-GWYP7B66.js → chunk-ASDHNBR2.js} +1321 -1321
|
@@ -3,25 +3,27 @@ import {
|
|
|
3
3
|
ProviderOutput,
|
|
4
4
|
Session,
|
|
5
5
|
createMessage
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-G4LG7HH2.js";
|
|
7
7
|
import {
|
|
8
8
|
definitions,
|
|
9
9
|
localTools
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-3MLW5U2C.js";
|
|
11
11
|
import "./chunk-EIPDEA3I.js";
|
|
12
12
|
import {
|
|
13
13
|
package_default
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-DDBZIUBF.js";
|
|
15
15
|
import {
|
|
16
16
|
historyPage,
|
|
17
17
|
historyResult,
|
|
18
18
|
readConfig,
|
|
19
19
|
src_default
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-ASDHNBR2.js";
|
|
21
21
|
import {
|
|
22
|
+
__callDispose,
|
|
22
23
|
__commonJS,
|
|
23
24
|
__require,
|
|
24
|
-
__toESM
|
|
25
|
+
__toESM,
|
|
26
|
+
__using
|
|
25
27
|
} from "./chunk-UOFCCHBS.js";
|
|
26
28
|
|
|
27
29
|
// node_modules/.pnpm/isexe@2.0.0/node_modules/isexe/windows.js
|
|
@@ -4197,10 +4199,12 @@ import {
|
|
|
4197
4199
|
createServer
|
|
4198
4200
|
} from "node:http";
|
|
4199
4201
|
import { homedir, hostname } from "node:os";
|
|
4200
|
-
import { join } from "node:path";
|
|
4202
|
+
import { join as join2 } from "node:path";
|
|
4201
4203
|
|
|
4202
4204
|
// src/appserver.ts
|
|
4203
4205
|
var import_cross_spawn = __toESM(require_cross_spawn(), 1);
|
|
4206
|
+
import { addAbortListener } from "node:events";
|
|
4207
|
+
import { createInterface } from "node:readline";
|
|
4204
4208
|
import { Duplex, Readable, Writable } from "node:stream";
|
|
4205
4209
|
|
|
4206
4210
|
// node_modules/.pnpm/ws@8.21.3/node_modules/ws/wrapper.mjs
|
|
@@ -4215,7 +4219,7 @@ var import_websocket_server = __toESM(require_websocket_server(), 1);
|
|
|
4215
4219
|
var wrapper_default = import_websocket.default;
|
|
4216
4220
|
|
|
4217
4221
|
// src/appserver.ts
|
|
4218
|
-
var AppServer = class {
|
|
4222
|
+
var AppServer = class _AppServer {
|
|
4219
4223
|
#pending = /* @__PURE__ */ new Map();
|
|
4220
4224
|
#receive;
|
|
4221
4225
|
#stopped = Promise.withResolvers();
|
|
@@ -4225,6 +4229,35 @@ var AppServer = class {
|
|
|
4225
4229
|
constructor(receive) {
|
|
4226
4230
|
this.#receive = receive;
|
|
4227
4231
|
}
|
|
4232
|
+
static async query(method, params = {}, signal) {
|
|
4233
|
+
signal?.throwIfAborted();
|
|
4234
|
+
const child = (0, import_cross_spawn.spawn)("codex", ["app-server"], {
|
|
4235
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
4236
|
+
signal
|
|
4237
|
+
});
|
|
4238
|
+
const rpc = new _AppServer(() => {
|
|
4239
|
+
});
|
|
4240
|
+
const reader = createInterface({ input: child.stdout });
|
|
4241
|
+
reader.on("line", (line) => rpc.#message(line));
|
|
4242
|
+
reader.on("error", (error) => rpc.#ended(error));
|
|
4243
|
+
child.on("error", (error) => rpc.#ended(error));
|
|
4244
|
+
child.stdin.on("error", (error) => rpc.#ended(error));
|
|
4245
|
+
child.once(
|
|
4246
|
+
"exit",
|
|
4247
|
+
() => rpc.#ended(new Error("Codex query process exited"))
|
|
4248
|
+
);
|
|
4249
|
+
rpc.#send = (message) => {
|
|
4250
|
+
child.stdin.write(`${JSON.stringify(message)}
|
|
4251
|
+
`);
|
|
4252
|
+
};
|
|
4253
|
+
try {
|
|
4254
|
+
await rpc.#initialize();
|
|
4255
|
+
return await rpc.request(method, params);
|
|
4256
|
+
} finally {
|
|
4257
|
+
reader.close();
|
|
4258
|
+
if (!child.killed) child.kill();
|
|
4259
|
+
}
|
|
4260
|
+
}
|
|
4228
4261
|
get closed() {
|
|
4229
4262
|
return this.#stopped.promise;
|
|
4230
4263
|
}
|
|
@@ -4271,27 +4304,42 @@ var AppServer = class {
|
|
|
4271
4304
|
socket.terminate();
|
|
4272
4305
|
stream?.destroy();
|
|
4273
4306
|
};
|
|
4274
|
-
|
|
4275
|
-
|
|
4307
|
+
try {
|
|
4308
|
+
await ready.promise;
|
|
4309
|
+
this.#send = (message) => socket.send(JSON.stringify(message));
|
|
4310
|
+
await this.#initialize();
|
|
4311
|
+
} catch (error) {
|
|
4312
|
+
this.close();
|
|
4313
|
+
throw error;
|
|
4314
|
+
}
|
|
4315
|
+
}
|
|
4316
|
+
async #initialize() {
|
|
4276
4317
|
await this.request("initialize", {
|
|
4277
4318
|
clientInfo: { name: "chappie", version: package_default.version },
|
|
4278
4319
|
capabilities: { experimentalApi: true }
|
|
4279
4320
|
});
|
|
4280
4321
|
this.#send?.({ method: "initialized" });
|
|
4281
4322
|
}
|
|
4282
|
-
request(method, params = {}) {
|
|
4283
|
-
|
|
4284
|
-
return Promise.reject(new Error("Codex app-server is not connected"));
|
|
4285
|
-
const id = this.#nextId++;
|
|
4286
|
-
const completion = Promise.withResolvers();
|
|
4287
|
-
this.#pending.set(id, completion);
|
|
4323
|
+
async request(method, params = {}, signal) {
|
|
4324
|
+
var _stack = [];
|
|
4288
4325
|
try {
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
this.#
|
|
4292
|
-
completion.
|
|
4326
|
+
signal?.throwIfAborted();
|
|
4327
|
+
if (!this.#send) throw new Error("Codex app-server is not connected");
|
|
4328
|
+
const id = this.#nextId++;
|
|
4329
|
+
const completion = Promise.withResolvers();
|
|
4330
|
+
this.#pending.set(id, completion);
|
|
4331
|
+
const _listener = __using(_stack, signal ? addAbortListener(signal, () => completion.reject(signal.reason)) : void 0);
|
|
4332
|
+
try {
|
|
4333
|
+
this.#send({ id, method, params });
|
|
4334
|
+
return await completion.promise;
|
|
4335
|
+
} finally {
|
|
4336
|
+
this.#pending.delete(id);
|
|
4337
|
+
}
|
|
4338
|
+
} catch (_) {
|
|
4339
|
+
var _error = _, _hasError = true;
|
|
4340
|
+
} finally {
|
|
4341
|
+
__callDispose(_stack, _error, _hasError);
|
|
4293
4342
|
}
|
|
4294
|
-
return completion.promise;
|
|
4295
4343
|
}
|
|
4296
4344
|
close() {
|
|
4297
4345
|
this.#close?.();
|
|
@@ -4313,7 +4361,6 @@ var AppServer = class {
|
|
|
4313
4361
|
if (typeof message.id !== "number") return;
|
|
4314
4362
|
const pending = this.#pending.get(message.id);
|
|
4315
4363
|
if (!pending) return;
|
|
4316
|
-
this.#pending.delete(message.id);
|
|
4317
4364
|
if (message.error) pending.reject(new Error(message.error.message));
|
|
4318
4365
|
else pending.resolve(message.result);
|
|
4319
4366
|
}
|
|
@@ -4325,8 +4372,194 @@ var AppServer = class {
|
|
|
4325
4372
|
}
|
|
4326
4373
|
};
|
|
4327
4374
|
|
|
4328
|
-
// src/
|
|
4375
|
+
// src/desktop.ts
|
|
4329
4376
|
import { randomUUID } from "node:crypto";
|
|
4377
|
+
import { once } from "node:events";
|
|
4378
|
+
import { connect } from "node:net";
|
|
4379
|
+
import { join } from "node:path";
|
|
4380
|
+
var Desktop = class {
|
|
4381
|
+
#receive;
|
|
4382
|
+
#stopped = Promise.withResolvers();
|
|
4383
|
+
#pending = /* @__PURE__ */ new Map();
|
|
4384
|
+
#snapshots = /* @__PURE__ */ new Map();
|
|
4385
|
+
#socket;
|
|
4386
|
+
#length = 0;
|
|
4387
|
+
#clientId = "initializing-client";
|
|
4388
|
+
constructor(receive) {
|
|
4389
|
+
this.#receive = receive;
|
|
4390
|
+
}
|
|
4391
|
+
get closed() {
|
|
4392
|
+
return this.#stopped.promise;
|
|
4393
|
+
}
|
|
4394
|
+
async connect(home) {
|
|
4395
|
+
const socket = connect(
|
|
4396
|
+
process.platform === "win32" ? "\\\\.\\pipe\\codex-ipc" : join(home, "ipc", "ipc.sock")
|
|
4397
|
+
);
|
|
4398
|
+
this.#socket = socket;
|
|
4399
|
+
socket.on("error", (error) => this.#ended(error));
|
|
4400
|
+
socket.once(
|
|
4401
|
+
"close",
|
|
4402
|
+
() => this.#ended(new Error("Codex desktop connection closed"))
|
|
4403
|
+
);
|
|
4404
|
+
socket.on("readable", () => {
|
|
4405
|
+
try {
|
|
4406
|
+
for (; ; ) {
|
|
4407
|
+
if (this.#length === 0) {
|
|
4408
|
+
const header = socket.read(4);
|
|
4409
|
+
if (!header) break;
|
|
4410
|
+
this.#length = header.readUInt32LE();
|
|
4411
|
+
if (this.#length === 0 || this.#length > 268435456)
|
|
4412
|
+
throw new Error(
|
|
4413
|
+
`Invalid Codex desktop frame length: ${this.#length}`
|
|
4414
|
+
);
|
|
4415
|
+
}
|
|
4416
|
+
const body = socket.read(this.#length);
|
|
4417
|
+
if (!body) break;
|
|
4418
|
+
this.#length = 0;
|
|
4419
|
+
this.#message(JSON.parse(body.toString("utf8")));
|
|
4420
|
+
}
|
|
4421
|
+
} catch (error) {
|
|
4422
|
+
socket.destroy(
|
|
4423
|
+
error instanceof Error ? error : new Error(String(error))
|
|
4424
|
+
);
|
|
4425
|
+
}
|
|
4426
|
+
});
|
|
4427
|
+
await once(socket, "connect");
|
|
4428
|
+
const response = await this.request(
|
|
4429
|
+
"initialize",
|
|
4430
|
+
{ clientType: "chappie" },
|
|
4431
|
+
0
|
|
4432
|
+
);
|
|
4433
|
+
this.#clientId = response.result.clientId;
|
|
4434
|
+
}
|
|
4435
|
+
async request(method, params, version, owner, timeoutMs) {
|
|
4436
|
+
const requestId = randomUUID();
|
|
4437
|
+
const pending = Promise.withResolvers();
|
|
4438
|
+
this.#pending.set(requestId, pending);
|
|
4439
|
+
try {
|
|
4440
|
+
this.#send({
|
|
4441
|
+
type: "request",
|
|
4442
|
+
requestId,
|
|
4443
|
+
sourceClientId: this.#clientId,
|
|
4444
|
+
method,
|
|
4445
|
+
params,
|
|
4446
|
+
version,
|
|
4447
|
+
timeoutMs,
|
|
4448
|
+
...owner ? { targetClientId: owner } : {}
|
|
4449
|
+
});
|
|
4450
|
+
const message = await pending.promise;
|
|
4451
|
+
return {
|
|
4452
|
+
result: message.result,
|
|
4453
|
+
handledByClientId: message.handledByClientId
|
|
4454
|
+
};
|
|
4455
|
+
} finally {
|
|
4456
|
+
this.#pending.delete(requestId);
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
async owner(threadId, clientId) {
|
|
4460
|
+
try {
|
|
4461
|
+
const result = await this.request(
|
|
4462
|
+
"thread-owner-discovery",
|
|
4463
|
+
{ hostId: "local", conversationId: threadId },
|
|
4464
|
+
1,
|
|
4465
|
+
clientId
|
|
4466
|
+
);
|
|
4467
|
+
return result.handledByClientId;
|
|
4468
|
+
} catch (error) {
|
|
4469
|
+
if (error.code === "no-client-found")
|
|
4470
|
+
return void 0;
|
|
4471
|
+
throw error;
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
follow(threadId, clientId) {
|
|
4475
|
+
this.#send({
|
|
4476
|
+
type: "broadcast",
|
|
4477
|
+
method: "thread-stream-following-changed",
|
|
4478
|
+
sourceClientId: this.#clientId,
|
|
4479
|
+
...clientId ? { targetClientIds: [clientId] } : {},
|
|
4480
|
+
version: 1,
|
|
4481
|
+
params: { hostId: "local", conversationId: threadId, following: true }
|
|
4482
|
+
});
|
|
4483
|
+
}
|
|
4484
|
+
async snapshot(threadId, owner) {
|
|
4485
|
+
const pending = { owner, ...Promise.withResolvers() };
|
|
4486
|
+
const requests = this.#snapshots.get(threadId) ?? /* @__PURE__ */ new Set();
|
|
4487
|
+
this.#snapshots.set(threadId, requests);
|
|
4488
|
+
requests.add(pending);
|
|
4489
|
+
const timer = setTimeout(
|
|
4490
|
+
() => pending.reject(new Error("Codex desktop snapshot timed out")),
|
|
4491
|
+
1e4
|
|
4492
|
+
);
|
|
4493
|
+
try {
|
|
4494
|
+
this.follow(threadId);
|
|
4495
|
+
return await pending.promise;
|
|
4496
|
+
} finally {
|
|
4497
|
+
clearTimeout(timer);
|
|
4498
|
+
requests.delete(pending);
|
|
4499
|
+
if (requests.size === 0) this.#snapshots.delete(threadId);
|
|
4500
|
+
}
|
|
4501
|
+
}
|
|
4502
|
+
close() {
|
|
4503
|
+
this.#socket?.destroy();
|
|
4504
|
+
this.#ended(new Error("Codex desktop connection closed"));
|
|
4505
|
+
}
|
|
4506
|
+
#send(message) {
|
|
4507
|
+
if (!this.#socket?.writable)
|
|
4508
|
+
throw new Error("Codex desktop is not connected");
|
|
4509
|
+
const body = Buffer.from(JSON.stringify(message));
|
|
4510
|
+
const size = Buffer.alloc(4);
|
|
4511
|
+
size.writeUInt32LE(body.length);
|
|
4512
|
+
this.#socket.write(Buffer.concat([size, body]));
|
|
4513
|
+
}
|
|
4514
|
+
#message(message) {
|
|
4515
|
+
if (message.type === "client-discovery-request") {
|
|
4516
|
+
this.#send({
|
|
4517
|
+
type: "client-discovery-response",
|
|
4518
|
+
requestId: message.requestId,
|
|
4519
|
+
response: { canHandle: false }
|
|
4520
|
+
});
|
|
4521
|
+
return;
|
|
4522
|
+
}
|
|
4523
|
+
if (message.type === "response" && message.requestId) {
|
|
4524
|
+
const pending = this.#pending.get(message.requestId);
|
|
4525
|
+
if (message.resultType === "error")
|
|
4526
|
+
pending?.reject(
|
|
4527
|
+
Object.assign(new Error(message.error), { code: message.error })
|
|
4528
|
+
);
|
|
4529
|
+
else pending?.resolve(message);
|
|
4530
|
+
return;
|
|
4531
|
+
}
|
|
4532
|
+
const params = message.params;
|
|
4533
|
+
if (message.method === "thread-stream-state-changed" && params) {
|
|
4534
|
+
const change = params.change;
|
|
4535
|
+
if (change.type === "snapshot" && typeof params.conversationId === "string") {
|
|
4536
|
+
for (const pending of this.#snapshots.get(params.conversationId) ?? [])
|
|
4537
|
+
if (pending.owner === message.sourceClientId)
|
|
4538
|
+
pending.resolve(change.conversationState);
|
|
4539
|
+
}
|
|
4540
|
+
}
|
|
4541
|
+
if (message.method === "client-status-changed" && params?.status === "disconnected") {
|
|
4542
|
+
for (const requests of this.#snapshots.values())
|
|
4543
|
+
for (const pending of requests)
|
|
4544
|
+
if (pending.owner === params.clientId)
|
|
4545
|
+
pending.reject(
|
|
4546
|
+
new Error("Codex desktop session owner disconnected")
|
|
4547
|
+
);
|
|
4548
|
+
}
|
|
4549
|
+
this.#receive(message);
|
|
4550
|
+
}
|
|
4551
|
+
#ended(error) {
|
|
4552
|
+
for (const pending of this.#pending.values()) pending.reject(error);
|
|
4553
|
+
this.#pending.clear();
|
|
4554
|
+
for (const requests of this.#snapshots.values())
|
|
4555
|
+
for (const pending of requests) pending.reject(error);
|
|
4556
|
+
this.#snapshots.clear();
|
|
4557
|
+
this.#stopped.resolve();
|
|
4558
|
+
}
|
|
4559
|
+
};
|
|
4560
|
+
|
|
4561
|
+
// src/responses.ts
|
|
4562
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
4330
4563
|
function responseTools(tools, namespace) {
|
|
4331
4564
|
return tools.flatMap((tool) => {
|
|
4332
4565
|
if (tool.type === "namespace" && tool.name)
|
|
@@ -4398,7 +4631,7 @@ var ResponsesOutput = class extends ProviderOutput {
|
|
|
4398
4631
|
"pending"
|
|
4399
4632
|
);
|
|
4400
4633
|
const items = [];
|
|
4401
|
-
const id = `resp_${
|
|
4634
|
+
const id = `resp_${randomUUID2()}`;
|
|
4402
4635
|
const created = Math.floor(Date.now() / 1e3);
|
|
4403
4636
|
let sequence = 0;
|
|
4404
4637
|
let heartbeat;
|
|
@@ -4438,7 +4671,7 @@ data: ${JSON.stringify({ type, sequence_number: sequence++, ...fields })}
|
|
|
4438
4671
|
break;
|
|
4439
4672
|
case "text_start": {
|
|
4440
4673
|
const item = {
|
|
4441
|
-
id: `msg_${
|
|
4674
|
+
id: `msg_${randomUUID2()}`,
|
|
4442
4675
|
type: "message",
|
|
4443
4676
|
role: "assistant",
|
|
4444
4677
|
status: "in_progress",
|
|
@@ -4500,7 +4733,7 @@ data: ${JSON.stringify({ type, sequence_number: sequence++, ...fields })}
|
|
|
4500
4733
|
if (!tool) throw new Error(`Unknown Codex tool: ${call.name}`);
|
|
4501
4734
|
const { custom, ...wire } = tool.wire;
|
|
4502
4735
|
const item = {
|
|
4503
|
-
id: `${custom ? "ctc" : "fc"}_${
|
|
4736
|
+
id: `${custom ? "ctc" : "fc"}_${randomUUID2()}`,
|
|
4504
4737
|
type: custom ? "custom_tool_call" : "function_call",
|
|
4505
4738
|
call_id: call.id,
|
|
4506
4739
|
...wire,
|
|
@@ -4588,23 +4821,70 @@ data: ${JSON.stringify({ type, sequence_number: sequence++, ...fields })}
|
|
|
4588
4821
|
|
|
4589
4822
|
// src/codex.ts
|
|
4590
4823
|
var codexHome = await realpath(
|
|
4591
|
-
process.env.CODEX_HOME ??
|
|
4824
|
+
process.env.CODEX_HOME ?? join2(homedir(), ".codex")
|
|
4592
4825
|
);
|
|
4826
|
+
var RequestError = class extends Error {
|
|
4827
|
+
};
|
|
4828
|
+
function parseObject(text) {
|
|
4829
|
+
let value;
|
|
4830
|
+
try {
|
|
4831
|
+
value = JSON.parse(text);
|
|
4832
|
+
} catch (error) {
|
|
4833
|
+
throw new RequestError(
|
|
4834
|
+
error instanceof Error ? error.message : String(error),
|
|
4835
|
+
{ cause: error }
|
|
4836
|
+
);
|
|
4837
|
+
}
|
|
4838
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4839
|
+
throw new RequestError("A JSON object is required");
|
|
4840
|
+
return value;
|
|
4841
|
+
}
|
|
4842
|
+
async function* loaded(connection) {
|
|
4843
|
+
let cursor = null;
|
|
4844
|
+
do {
|
|
4845
|
+
const page = await connection.request(
|
|
4846
|
+
"thread/loaded/list",
|
|
4847
|
+
cursor ? { cursor } : {}
|
|
4848
|
+
);
|
|
4849
|
+
yield* page.data;
|
|
4850
|
+
cursor = page.nextCursor;
|
|
4851
|
+
} while (cursor);
|
|
4852
|
+
}
|
|
4853
|
+
function desktopThread(snapshot) {
|
|
4854
|
+
return {
|
|
4855
|
+
id: snapshot.id,
|
|
4856
|
+
cwd: snapshot.latestThreadSettings?.cwd ?? snapshot.cwd,
|
|
4857
|
+
name: snapshot.title,
|
|
4858
|
+
model: snapshot.latestThreadSettings?.model ?? snapshot.latestModel,
|
|
4859
|
+
modelProvider: snapshot.latestThreadSettings?.modelProvider ?? snapshot.modelProvider,
|
|
4860
|
+
status: snapshot.threadRuntimeStatus ?? { type: "idle" }
|
|
4861
|
+
};
|
|
4862
|
+
}
|
|
4863
|
+
function desktopTurns(snapshot) {
|
|
4864
|
+
const history = snapshot.turnHistory?.history;
|
|
4865
|
+
return history ? history.islands.flatMap(
|
|
4866
|
+
(island) => island.entries.map(({ value }) => ({
|
|
4867
|
+
key: value,
|
|
4868
|
+
turn: history.entitiesByKey[value]
|
|
4869
|
+
}))
|
|
4870
|
+
) : snapshot.turns.map((turn, key) => ({ key, turn }));
|
|
4871
|
+
}
|
|
4593
4872
|
var CodexSession = class {
|
|
4594
4873
|
session;
|
|
4595
4874
|
tools;
|
|
4596
|
-
|
|
4875
|
+
connection;
|
|
4597
4876
|
#inputs = /* @__PURE__ */ new Map();
|
|
4598
4877
|
#seenInputs = /* @__PURE__ */ new Set();
|
|
4599
4878
|
#results = /* @__PURE__ */ new Map();
|
|
4600
4879
|
#attached = false;
|
|
4880
|
+
#desktopTurn;
|
|
4601
4881
|
thread;
|
|
4602
4882
|
turn;
|
|
4603
4883
|
output;
|
|
4604
4884
|
catalog;
|
|
4605
|
-
constructor(thread,
|
|
4885
|
+
constructor(thread, connection, config) {
|
|
4606
4886
|
this.thread = thread;
|
|
4607
|
-
this
|
|
4887
|
+
this.connection = connection;
|
|
4608
4888
|
this.session = new Session(this, config);
|
|
4609
4889
|
this.tools = localTools(this.session);
|
|
4610
4890
|
}
|
|
@@ -4619,15 +4899,16 @@ var CodexSession = class {
|
|
|
4619
4899
|
};
|
|
4620
4900
|
}
|
|
4621
4901
|
active() {
|
|
4622
|
-
return this.thread.modelProvider === "chappie";
|
|
4902
|
+
return this.thread.modelProvider === "chappie" && this.thread.status.type !== "notLoaded";
|
|
4623
4903
|
}
|
|
4624
4904
|
isIdle() {
|
|
4625
|
-
return this.thread.status.type === "idle";
|
|
4905
|
+
return this.thread.status.type === "idle" || this.thread.status.type === "systemError";
|
|
4626
4906
|
}
|
|
4627
4907
|
async inspect(signal) {
|
|
4628
4908
|
await this.session.ready(signal);
|
|
4629
|
-
const
|
|
4630
|
-
const
|
|
4909
|
+
const request = this.connection instanceof AppServer ? this.connection.request.bind(this.connection) : AppServer.query;
|
|
4910
|
+
const result = await request("skills/list", { cwds: [this.thread.cwd] }, signal);
|
|
4911
|
+
const path = join2(codexHome, "AGENTS.md");
|
|
4631
4912
|
let globalAgents;
|
|
4632
4913
|
try {
|
|
4633
4914
|
await access(path);
|
|
@@ -4642,10 +4923,49 @@ var CodexSession = class {
|
|
|
4642
4923
|
};
|
|
4643
4924
|
}
|
|
4644
4925
|
async history(range) {
|
|
4926
|
+
if (!(this.connection instanceof AppServer)) {
|
|
4927
|
+
const { desktop } = this.connection;
|
|
4928
|
+
const { handledByClientId } = await desktop.request(
|
|
4929
|
+
"thread-follower-load-complete-history",
|
|
4930
|
+
{ conversationId: this.thread.id },
|
|
4931
|
+
1,
|
|
4932
|
+
void 0,
|
|
4933
|
+
3e5
|
|
4934
|
+
);
|
|
4935
|
+
const snapshot = await desktop.snapshot(
|
|
4936
|
+
this.thread.id,
|
|
4937
|
+
handledByClientId
|
|
4938
|
+
);
|
|
4939
|
+
const page2 = historyPage(
|
|
4940
|
+
desktopTurns(snapshot).flatMap(
|
|
4941
|
+
({ turn }) => turn.items.map((item) => ({
|
|
4942
|
+
id: item.id,
|
|
4943
|
+
item,
|
|
4944
|
+
turnId: turn.turnId,
|
|
4945
|
+
timestamp: turn.turnStartedAtMs
|
|
4946
|
+
}))
|
|
4947
|
+
),
|
|
4948
|
+
range
|
|
4949
|
+
);
|
|
4950
|
+
return historyResult(
|
|
4951
|
+
{
|
|
4952
|
+
entries: await Promise.all(
|
|
4953
|
+
page2.entries.map(async ({ item, turnId, timestamp }) => ({
|
|
4954
|
+
...await itemMessage(item),
|
|
4955
|
+
id: item.id,
|
|
4956
|
+
turnId,
|
|
4957
|
+
...timestamp === void 0 ? {} : { timestamp: new Date(timestamp).toISOString() }
|
|
4958
|
+
}))
|
|
4959
|
+
),
|
|
4960
|
+
hasMore: page2.hasMore
|
|
4961
|
+
},
|
|
4962
|
+
this.thread.id
|
|
4963
|
+
);
|
|
4964
|
+
}
|
|
4645
4965
|
const items = [];
|
|
4646
4966
|
let cursor = null;
|
|
4647
4967
|
do {
|
|
4648
|
-
const page2 = await this
|
|
4968
|
+
const page2 = await this.connection.request("thread/items/list", {
|
|
4649
4969
|
threadId: this.thread.id,
|
|
4650
4970
|
sortDirection: "desc",
|
|
4651
4971
|
limit: range.limit + 1,
|
|
@@ -4697,33 +5017,58 @@ var CodexSession = class {
|
|
|
4697
5017
|
if (!this.isIdle()) return;
|
|
4698
5018
|
this.thread.status = { type: "active" };
|
|
4699
5019
|
try {
|
|
4700
|
-
const
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
5020
|
+
const input = { threadId: this.thread.id, input: [] };
|
|
5021
|
+
if (this.connection instanceof AppServer) {
|
|
5022
|
+
const result = await this.connection.request(
|
|
5023
|
+
"turn/start",
|
|
5024
|
+
input
|
|
5025
|
+
);
|
|
5026
|
+
this.turn = result.turn.id;
|
|
5027
|
+
} else {
|
|
5028
|
+
await this.connection.desktop.request(
|
|
5029
|
+
"thread-follower-start-turn",
|
|
5030
|
+
{ conversationId: this.thread.id, turnStart: { request: input } },
|
|
5031
|
+
2
|
|
5032
|
+
);
|
|
5033
|
+
}
|
|
4705
5034
|
} catch (error) {
|
|
4706
5035
|
this.thread.status = { type: "idle" };
|
|
4707
5036
|
throw error;
|
|
4708
5037
|
}
|
|
4709
5038
|
}
|
|
4710
5039
|
async abort() {
|
|
4711
|
-
if (this.turn)
|
|
4712
|
-
|
|
5040
|
+
if (!this.turn) return;
|
|
5041
|
+
if (this.connection instanceof AppServer)
|
|
5042
|
+
await this.connection.request("turn/interrupt", {
|
|
4713
5043
|
threadId: this.thread.id,
|
|
4714
5044
|
turnId: this.turn
|
|
4715
5045
|
});
|
|
5046
|
+
else
|
|
5047
|
+
await this.connection.desktop.request(
|
|
5048
|
+
"thread-follower-interrupt-turn",
|
|
5049
|
+
{
|
|
5050
|
+
conversationId: this.thread.id,
|
|
5051
|
+
mode: "user-stop",
|
|
5052
|
+
expectedTurnId: this.turn
|
|
5053
|
+
},
|
|
5054
|
+
4
|
|
5055
|
+
);
|
|
4716
5056
|
}
|
|
4717
5057
|
async item(item) {
|
|
5058
|
+
await this.#capture(item);
|
|
5059
|
+
this.session.historyChanged();
|
|
5060
|
+
}
|
|
5061
|
+
async #capture(item) {
|
|
4718
5062
|
if (!this.active()) return;
|
|
4719
5063
|
if (this.output?.message.content.some(
|
|
4720
5064
|
(call) => call.type === "toolCall" && call.id === item.id
|
|
4721
5065
|
))
|
|
4722
5066
|
this.#results.set(item.id, item);
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
this.#
|
|
4726
|
-
|
|
5067
|
+
const id = item.type === "userMessage" ? item.id : item.type === "steeringUserMessage" ? item.serverUserMessageId : void 0;
|
|
5068
|
+
if (id && (item.content ?? item.input)?.length && !this.#seenInputs.has(id)) {
|
|
5069
|
+
this.#seenInputs.add(id);
|
|
5070
|
+
this.#inputs.set(id, {
|
|
5071
|
+
id,
|
|
4727
5072
|
sessionId: this.thread.id,
|
|
4728
5073
|
message: {
|
|
4729
5074
|
role: "user",
|
|
@@ -4732,19 +5077,80 @@ var CodexSession = class {
|
|
|
4732
5077
|
}
|
|
4733
5078
|
});
|
|
4734
5079
|
}
|
|
5080
|
+
}
|
|
5081
|
+
async snapshot(snapshot) {
|
|
5082
|
+
Object.assign(this.thread, desktopThread(snapshot));
|
|
5083
|
+
const turns = desktopTurns(snapshot);
|
|
5084
|
+
const current = turns.find(({ turn }) => turn.turnId === this.turn) ?? turns.findLast(({ turn }) => turn.status === "inProgress");
|
|
5085
|
+
if (current) {
|
|
5086
|
+
this.#desktopTurn = current.key;
|
|
5087
|
+
if (current.turn.turnId) this.turn = current.turn.turnId;
|
|
5088
|
+
for (const item of current.turn.items) await this.#capture(item);
|
|
5089
|
+
if (current.turn.turnId && current.turn.status !== "inProgress")
|
|
5090
|
+
await this.settled({
|
|
5091
|
+
id: current.turn.turnId,
|
|
5092
|
+
status: current.turn.status,
|
|
5093
|
+
items: current.turn.items,
|
|
5094
|
+
error: current.turn.error ?? null
|
|
5095
|
+
});
|
|
5096
|
+
}
|
|
5097
|
+
this.session.update();
|
|
5098
|
+
}
|
|
5099
|
+
async patches(patches) {
|
|
5100
|
+
if (this.connection instanceof AppServer) return;
|
|
5101
|
+
let refresh = false;
|
|
5102
|
+
for (const patch of patches) {
|
|
5103
|
+
if ([
|
|
5104
|
+
"title",
|
|
5105
|
+
"cwd",
|
|
5106
|
+
"latestModel",
|
|
5107
|
+
"modelProvider",
|
|
5108
|
+
"latestThreadSettings",
|
|
5109
|
+
"threadRuntimeStatus"
|
|
5110
|
+
].includes(String(patch.path[0])))
|
|
5111
|
+
refresh = true;
|
|
5112
|
+
const path = patch.path[0] === "turnHistory" && patch.path[1] === "history" && patch.path[2] === "entitiesByKey" ? patch.path.slice(3) : patch.path[0] === "turns" ? patch.path.slice(1) : [];
|
|
5113
|
+
if (path[0] !== this.#desktopTurn) continue;
|
|
5114
|
+
if (path.length === 1 || path[1] === "status" || path[1] === "error" || path[1] === "turnId")
|
|
5115
|
+
refresh = true;
|
|
5116
|
+
if (path[1] === "items" && patch.op !== "remove") {
|
|
5117
|
+
if (path.length === 2 && Array.isArray(patch.value))
|
|
5118
|
+
for (const item of patch.value) await this.#capture(item);
|
|
5119
|
+
else if (path.length === 3 && patch.value && typeof patch.value === "object")
|
|
5120
|
+
await this.#capture(patch.value);
|
|
5121
|
+
}
|
|
5122
|
+
}
|
|
5123
|
+
if (refresh)
|
|
5124
|
+
await this.snapshot(
|
|
5125
|
+
await this.connection.desktop.snapshot(
|
|
5126
|
+
this.thread.id,
|
|
5127
|
+
this.connection.owner
|
|
5128
|
+
)
|
|
5129
|
+
);
|
|
4735
5130
|
this.session.historyChanged();
|
|
4736
5131
|
}
|
|
4737
|
-
async respond(request, response, signal) {
|
|
5132
|
+
async respond(request, context, response, signal) {
|
|
5133
|
+
if (context?.request_kind === "turn" && context.turn_id)
|
|
5134
|
+
this.turn = context.turn_id;
|
|
4738
5135
|
if (!this.#attached) {
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
5136
|
+
if (this.connection instanceof AppServer) {
|
|
5137
|
+
const result = await this.connection.request("thread/resume", {
|
|
5138
|
+
threadId: this.thread.id,
|
|
5139
|
+
excludeTurns: true,
|
|
5140
|
+
initialTurnsPage: { limit: 1, itemsView: "full" }
|
|
5141
|
+
});
|
|
5142
|
+
this.thread = result.thread;
|
|
5143
|
+
for (const turn of result.initialTurnsPage.data) {
|
|
5144
|
+
if (turn.status === "inProgress") this.turn = turn.id;
|
|
5145
|
+
for (const item of turn.items) await this.item(item);
|
|
5146
|
+
}
|
|
5147
|
+
} else {
|
|
5148
|
+
await this.snapshot(
|
|
5149
|
+
await this.connection.desktop.snapshot(
|
|
5150
|
+
this.thread.id,
|
|
5151
|
+
this.connection.owner
|
|
5152
|
+
)
|
|
5153
|
+
);
|
|
4748
5154
|
}
|
|
4749
5155
|
this.#attached = true;
|
|
4750
5156
|
}
|
|
@@ -4766,15 +5172,14 @@ var CodexSession = class {
|
|
|
4766
5172
|
toolName: call.name,
|
|
4767
5173
|
content: responseContent(item.output),
|
|
4768
5174
|
isError: itemFailed(native),
|
|
4769
|
-
...native?.result?.
|
|
5175
|
+
...native?.result?._meta?.details ? { details: native.result._meta.details } : {}
|
|
4770
5176
|
});
|
|
4771
5177
|
}
|
|
4772
5178
|
this.session.complete(previous.message, results);
|
|
4773
5179
|
this.output = void 0;
|
|
4774
5180
|
this.#results.clear();
|
|
4775
5181
|
}
|
|
4776
|
-
const
|
|
4777
|
-
const kind = metadata ? JSON.parse(metadata).request_kind : void 0;
|
|
5182
|
+
const kind = context?.request_kind;
|
|
4778
5183
|
if (kind === "compaction") {
|
|
4779
5184
|
const output2 = new ResponsesOutput(request, [], response, signal);
|
|
4780
5185
|
try {
|
|
@@ -4784,11 +5189,11 @@ var CodexSession = class {
|
|
|
4784
5189
|
}
|
|
4785
5190
|
return;
|
|
4786
5191
|
}
|
|
4787
|
-
this.catalog = responseTools(request.tools ?? []).
|
|
5192
|
+
this.catalog = responseTools(request.tools ?? []).map((tool) => {
|
|
4788
5193
|
const local = ["chappie", "mcp__chappie"].includes(
|
|
4789
5194
|
tool.wire.namespace ?? ""
|
|
4790
5195
|
) ? definitions.find((definition) => definition.name === tool.wire.name) : void 0;
|
|
4791
|
-
return local ?
|
|
5196
|
+
return local ? { ...tool, name: local.name } : tool;
|
|
4792
5197
|
});
|
|
4793
5198
|
const output = new ResponsesOutput(request, this.catalog, response, signal);
|
|
4794
5199
|
this.output = output;
|
|
@@ -4821,7 +5226,7 @@ var CodexSession = class {
|
|
|
4821
5226
|
}
|
|
4822
5227
|
],
|
|
4823
5228
|
isError: !item || item.status === "inProgress" || itemFailed(item),
|
|
4824
|
-
...item?.result?.
|
|
5229
|
+
...item?.result?._meta?.details ? { details: item.result._meta.details } : {}
|
|
4825
5230
|
};
|
|
4826
5231
|
})
|
|
4827
5232
|
);
|
|
@@ -4834,6 +5239,7 @@ var CodexSession = class {
|
|
|
4834
5239
|
}
|
|
4835
5240
|
this.#results.clear();
|
|
4836
5241
|
this.#seenInputs.clear();
|
|
5242
|
+
this.#desktopTurn = void 0;
|
|
4837
5243
|
this.catalog = void 0;
|
|
4838
5244
|
await this.session.settled(
|
|
4839
5245
|
turn.error ? new Error(turn.error.message) : void 0
|
|
@@ -4856,21 +5262,27 @@ async function itemMessage(item) {
|
|
|
4856
5262
|
const message = { ...item };
|
|
4857
5263
|
let content;
|
|
4858
5264
|
switch (item.type) {
|
|
5265
|
+
case "steeringUserMessage":
|
|
4859
5266
|
case "userMessage":
|
|
4860
5267
|
delete message.content;
|
|
5268
|
+
delete message.input;
|
|
5269
|
+
delete message.restoreMessage;
|
|
5270
|
+
delete message.compareKey;
|
|
4861
5271
|
message.role = "user";
|
|
4862
5272
|
content = (await Promise.all(
|
|
4863
|
-
(item.content ?? []).map(
|
|
4864
|
-
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
|
|
4869
|
-
|
|
4870
|
-
|
|
4871
|
-
|
|
4872
|
-
|
|
4873
|
-
|
|
5273
|
+
(item.content ?? item.input ?? []).map(
|
|
5274
|
+
async (block) => {
|
|
5275
|
+
if (block.type === "text" && typeof block.text === "string")
|
|
5276
|
+
return [{ type: "text", text: block.text }];
|
|
5277
|
+
if (block.type === "localImage" && typeof block.path === "string")
|
|
5278
|
+
return [await image(block.path)];
|
|
5279
|
+
if (block.type === "image" && typeof block.url === "string")
|
|
5280
|
+
return responseContent([
|
|
5281
|
+
{ type: "input_image", image_url: block.url }
|
|
5282
|
+
]);
|
|
5283
|
+
return [{ type: "text", text: JSON.stringify(block) }];
|
|
5284
|
+
}
|
|
5285
|
+
)
|
|
4874
5286
|
)).flat();
|
|
4875
5287
|
break;
|
|
4876
5288
|
case "agentMessage":
|
|
@@ -4886,8 +5298,7 @@ async function itemMessage(item) {
|
|
|
4886
5298
|
if (item.result) {
|
|
4887
5299
|
const { content: blocks, ...metadata } = item.result;
|
|
4888
5300
|
message.result = metadata;
|
|
4889
|
-
if (metadata.
|
|
4890
|
-
message.details = metadata.structuredContent.details;
|
|
5301
|
+
if (metadata._meta?.details) message.details = metadata._meta.details;
|
|
4891
5302
|
content = responseContent(blocks);
|
|
4892
5303
|
} else {
|
|
4893
5304
|
delete message.error;
|
|
@@ -4923,42 +5334,233 @@ async function serveCodex() {
|
|
|
4923
5334
|
const clients = /* @__PURE__ */ new Set();
|
|
4924
5335
|
const stopped = Promise.withResolvers();
|
|
4925
5336
|
const stop = () => stopped.resolve();
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
5337
|
+
const events = { rpc: Promise.resolve(), desktop: Promise.resolve() };
|
|
5338
|
+
let rpc;
|
|
5339
|
+
let desktop;
|
|
5340
|
+
let rpcConnecting;
|
|
5341
|
+
let desktopConnecting;
|
|
5342
|
+
function register(thread, connection) {
|
|
4931
5343
|
let state = sessions.get(thread.id);
|
|
5344
|
+
if (state && state.connection !== connection) {
|
|
5345
|
+
if (state.connection instanceof AppServer || connection instanceof AppServer || state.connection.desktop !== connection.desktop) {
|
|
5346
|
+
state.session.close();
|
|
5347
|
+
state = void 0;
|
|
5348
|
+
} else state.connection.owner = connection.owner;
|
|
5349
|
+
}
|
|
4932
5350
|
if (state) state.thread = thread;
|
|
4933
5351
|
else {
|
|
4934
|
-
state = new CodexSession(thread,
|
|
5352
|
+
state = new CodexSession(thread, connection, config);
|
|
4935
5353
|
sessions.set(thread.id, state);
|
|
4936
5354
|
}
|
|
4937
5355
|
state.session.update();
|
|
4938
5356
|
return state;
|
|
4939
5357
|
}
|
|
4940
|
-
|
|
5358
|
+
function disconnected(connection) {
|
|
5359
|
+
if (connection === rpc) rpc = void 0;
|
|
5360
|
+
if (connection === desktop) desktop = void 0;
|
|
5361
|
+
for (const [id, state] of sessions)
|
|
5362
|
+
if (state.connection === connection || !(state.connection instanceof AppServer) && state.connection.desktop === connection) {
|
|
5363
|
+
state.session.close();
|
|
5364
|
+
sessions.delete(id);
|
|
5365
|
+
}
|
|
5366
|
+
}
|
|
5367
|
+
function attachDesktop() {
|
|
5368
|
+
desktopConnecting ??= (async () => {
|
|
5369
|
+
if (desktop) return desktop;
|
|
5370
|
+
const connection = new Desktop((message) => {
|
|
5371
|
+
events.desktop = events.desktop.then(async () => {
|
|
5372
|
+
await desktopConnecting;
|
|
5373
|
+
if (desktop === connection)
|
|
5374
|
+
await receiveDesktop(connection, message);
|
|
5375
|
+
}).catch((error) => console.error(error));
|
|
5376
|
+
});
|
|
5377
|
+
desktop = connection;
|
|
5378
|
+
void connection.closed.then(() => disconnected(connection));
|
|
5379
|
+
try {
|
|
5380
|
+
await connection.connect(codexHome);
|
|
5381
|
+
return connection;
|
|
5382
|
+
} catch (error) {
|
|
5383
|
+
connection.close();
|
|
5384
|
+
if (!["ENOENT", "ECONNREFUSED"].includes(
|
|
5385
|
+
error.code ?? ""
|
|
5386
|
+
))
|
|
5387
|
+
throw error;
|
|
5388
|
+
return void 0;
|
|
5389
|
+
}
|
|
5390
|
+
})().finally(() => {
|
|
5391
|
+
desktopConnecting = void 0;
|
|
5392
|
+
});
|
|
5393
|
+
return desktopConnecting;
|
|
5394
|
+
}
|
|
5395
|
+
function attachServer() {
|
|
5396
|
+
rpcConnecting ??= (async () => {
|
|
5397
|
+
if (rpc) return rpc;
|
|
5398
|
+
if (!config.codex?.appServer) {
|
|
5399
|
+
try {
|
|
5400
|
+
await access(
|
|
5401
|
+
join2(codexHome, "app-server-control", "app-server-control.sock")
|
|
5402
|
+
);
|
|
5403
|
+
} catch (error) {
|
|
5404
|
+
if (error.code === "ENOENT")
|
|
5405
|
+
return void 0;
|
|
5406
|
+
throw error;
|
|
5407
|
+
}
|
|
5408
|
+
}
|
|
5409
|
+
const connection = new AppServer((message) => {
|
|
5410
|
+
events.rpc = events.rpc.then(async () => {
|
|
5411
|
+
await rpcConnecting;
|
|
5412
|
+
if (rpc === connection) await receive(message, connection);
|
|
5413
|
+
}).catch((error) => console.error(error));
|
|
5414
|
+
});
|
|
5415
|
+
rpc = connection;
|
|
5416
|
+
void connection.closed.then(() => disconnected(connection));
|
|
5417
|
+
await connection.connect(appServer);
|
|
5418
|
+
for await (const threadId of loaded(connection)) {
|
|
5419
|
+
try {
|
|
5420
|
+
const { thread } = await connection.request(
|
|
5421
|
+
"thread/read",
|
|
5422
|
+
{ threadId }
|
|
5423
|
+
);
|
|
5424
|
+
register(thread, connection);
|
|
5425
|
+
} catch (error) {
|
|
5426
|
+
console.error(error);
|
|
5427
|
+
}
|
|
5428
|
+
}
|
|
5429
|
+
return connection;
|
|
5430
|
+
})().finally(() => {
|
|
5431
|
+
rpcConnecting = void 0;
|
|
5432
|
+
});
|
|
5433
|
+
return rpcConnecting;
|
|
5434
|
+
}
|
|
5435
|
+
function attach() {
|
|
5436
|
+
void attachDesktop().catch((error) => console.error(error));
|
|
5437
|
+
void attachServer().catch((error) => console.error(error));
|
|
5438
|
+
}
|
|
5439
|
+
const get = (threadId, control = false) => {
|
|
4941
5440
|
const existing = sessions.get(threadId);
|
|
4942
|
-
if (existing
|
|
5441
|
+
if (existing && (!control || existing.thread.status.type !== "notLoaded"))
|
|
5442
|
+
return Promise.resolve(existing);
|
|
4943
5443
|
let pending = loading.get(threadId);
|
|
4944
5444
|
if (!pending) {
|
|
4945
|
-
pending =
|
|
5445
|
+
pending = (async () => {
|
|
5446
|
+
const connection = await Promise.any([
|
|
5447
|
+
attachServer().then(async (connection2) => {
|
|
5448
|
+
if (connection2) {
|
|
5449
|
+
for await (const id of loaded(connection2))
|
|
5450
|
+
if (id === threadId) return connection2;
|
|
5451
|
+
}
|
|
5452
|
+
throw new RequestError(
|
|
5453
|
+
"Thread is not loaded in the Codex app-server"
|
|
5454
|
+
);
|
|
5455
|
+
}),
|
|
5456
|
+
attachDesktop().then(async (desktop2) => {
|
|
5457
|
+
const owner = await desktop2?.owner(threadId);
|
|
5458
|
+
if (desktop2 && owner) return { desktop: desktop2, owner };
|
|
5459
|
+
throw new RequestError("Thread has no Codex desktop owner");
|
|
5460
|
+
})
|
|
5461
|
+
]).catch((error) => {
|
|
5462
|
+
const Failure = error.errors.every(
|
|
5463
|
+
(reason) => reason instanceof RequestError
|
|
5464
|
+
) ? RequestError : Error;
|
|
5465
|
+
throw new Failure(
|
|
5466
|
+
`No connected Codex client owns thread ${threadId}: ${error.errors.map(String).join("; ")}`,
|
|
5467
|
+
{ cause: error }
|
|
5468
|
+
);
|
|
5469
|
+
});
|
|
5470
|
+
if (connection instanceof AppServer) {
|
|
5471
|
+
const { thread } = await connection.request(
|
|
5472
|
+
"thread/read",
|
|
5473
|
+
{ threadId }
|
|
5474
|
+
);
|
|
5475
|
+
return register(thread, connection);
|
|
5476
|
+
}
|
|
5477
|
+
const snapshot = await connection.desktop.snapshot(
|
|
5478
|
+
threadId,
|
|
5479
|
+
connection.owner
|
|
5480
|
+
);
|
|
5481
|
+
const state = register(desktopThread(snapshot), connection);
|
|
5482
|
+
await state.snapshot(snapshot);
|
|
5483
|
+
return state;
|
|
5484
|
+
})().finally(() => loading.delete(threadId));
|
|
4946
5485
|
loading.set(threadId, pending);
|
|
4947
5486
|
}
|
|
4948
|
-
return pending
|
|
5487
|
+
return control ? pending : pending.catch(async (error) => {
|
|
5488
|
+
const connection = await attachServer();
|
|
5489
|
+
if (!connection) throw error;
|
|
5490
|
+
const { thread } = await connection.request(
|
|
5491
|
+
"thread/read",
|
|
5492
|
+
{ threadId }
|
|
5493
|
+
);
|
|
5494
|
+
return register(thread, connection);
|
|
5495
|
+
});
|
|
4949
5496
|
};
|
|
4950
|
-
async function
|
|
5497
|
+
async function receiveDesktop(connection, message) {
|
|
5498
|
+
const params = message.params;
|
|
5499
|
+
if (!params) return;
|
|
5500
|
+
if (message.method === "client-status-changed") {
|
|
5501
|
+
if (params.status === "connected" && params.isSelf !== true && typeof params.clientId === "string") {
|
|
5502
|
+
for (const [id, state2] of sessions)
|
|
5503
|
+
if (!(state2.connection instanceof AppServer) && state2.connection.desktop === connection)
|
|
5504
|
+
connection.follow(id, params.clientId);
|
|
5505
|
+
}
|
|
5506
|
+
if (params.status === "disconnected") {
|
|
5507
|
+
for (const [id, state2] of sessions)
|
|
5508
|
+
if (!(state2.connection instanceof AppServer) && state2.connection.owner === params.clientId) {
|
|
5509
|
+
state2.session.close();
|
|
5510
|
+
sessions.delete(id);
|
|
5511
|
+
}
|
|
5512
|
+
}
|
|
5513
|
+
return;
|
|
5514
|
+
}
|
|
5515
|
+
if (params.hostId !== "local" || typeof params.conversationId !== "string" || !message.sourceClientId)
|
|
5516
|
+
return;
|
|
5517
|
+
const threadId = params.conversationId;
|
|
5518
|
+
if (message.method === "thread-stream-following-changed" && params.following === true || message.method === "thread-stream-following-status-requested") {
|
|
5519
|
+
if (sessions.has(threadId) && message.method === "thread-stream-following-changed")
|
|
5520
|
+
return;
|
|
5521
|
+
const owner = message.method === "thread-stream-following-status-requested" ? message.sourceClientId : await connection.owner(threadId, message.sourceClientId);
|
|
5522
|
+
if (owner) {
|
|
5523
|
+
const snapshot = await connection.snapshot(threadId, owner);
|
|
5524
|
+
await register(desktopThread(snapshot), {
|
|
5525
|
+
desktop: connection,
|
|
5526
|
+
owner
|
|
5527
|
+
}).snapshot(snapshot);
|
|
5528
|
+
}
|
|
5529
|
+
return;
|
|
5530
|
+
}
|
|
5531
|
+
const state = sessions.get(threadId);
|
|
5532
|
+
if (!state || state.connection instanceof AppServer || state.connection.desktop !== connection)
|
|
5533
|
+
return;
|
|
5534
|
+
if (message.method === "thread-stream-state-changed") {
|
|
5535
|
+
const change = params.change;
|
|
5536
|
+
if (state.connection.owner !== message.sourceClientId) {
|
|
5537
|
+
const owner = await connection.owner(threadId, message.sourceClientId);
|
|
5538
|
+
if (!owner) return;
|
|
5539
|
+
const snapshot = change.type === "snapshot" ? change.conversationState : await connection.snapshot(threadId, owner);
|
|
5540
|
+
await register(desktopThread(snapshot), {
|
|
5541
|
+
desktop: connection,
|
|
5542
|
+
owner
|
|
5543
|
+
}).snapshot(snapshot);
|
|
5544
|
+
} else if (change.type === "snapshot")
|
|
5545
|
+
await state.snapshot(change.conversationState);
|
|
5546
|
+
else if (change.type === "patches") await state.patches(change.patches);
|
|
5547
|
+
} else if (message.method === "thread-archived" && state.connection.owner === message.sourceClientId) {
|
|
5548
|
+
state.session.close();
|
|
5549
|
+
sessions.delete(threadId);
|
|
5550
|
+
}
|
|
5551
|
+
}
|
|
5552
|
+
async function receive(message, connection) {
|
|
4951
5553
|
const params = message.params;
|
|
4952
5554
|
if (!params) return;
|
|
4953
5555
|
if (message.id !== void 0) return;
|
|
4954
5556
|
if (message.method === "thread/started") {
|
|
4955
5557
|
const thread = params.thread;
|
|
4956
|
-
register(thread);
|
|
5558
|
+
register(thread, connection);
|
|
4957
5559
|
return;
|
|
4958
5560
|
}
|
|
4959
5561
|
if (typeof params.threadId !== "string") return;
|
|
4960
5562
|
const state = sessions.get(params.threadId);
|
|
4961
|
-
if (!state) return;
|
|
5563
|
+
if (!state || state.connection !== connection) return;
|
|
4962
5564
|
switch (message.method) {
|
|
4963
5565
|
case "thread/settings/updated": {
|
|
4964
5566
|
const settings = params.threadSettings;
|
|
@@ -4972,6 +5574,7 @@ async function serveCodex() {
|
|
|
4972
5574
|
break;
|
|
4973
5575
|
case "thread/status/changed":
|
|
4974
5576
|
state.thread.status = params.status;
|
|
5577
|
+
state.session.update();
|
|
4975
5578
|
break;
|
|
4976
5579
|
case "thread/closed":
|
|
4977
5580
|
case "thread/archived":
|
|
@@ -4994,13 +5597,16 @@ async function serveCodex() {
|
|
|
4994
5597
|
}
|
|
4995
5598
|
const server = createServer((request, response) => {
|
|
4996
5599
|
void handle(request, response).catch((error) => {
|
|
5600
|
+
const status = error instanceof RequestError ? 400 : 500;
|
|
5601
|
+
if (status === 500) console.error(error);
|
|
4997
5602
|
if (response.headersSent)
|
|
4998
5603
|
response.destroy(error instanceof Error ? error : void 0);
|
|
4999
5604
|
else {
|
|
5000
|
-
response.writeHead(
|
|
5605
|
+
response.writeHead(status, { "Content-Type": "application/json" });
|
|
5001
5606
|
response.end(
|
|
5002
5607
|
JSON.stringify({
|
|
5003
5608
|
error: {
|
|
5609
|
+
type: status === 400 ? "invalid_request_error" : "server_error",
|
|
5004
5610
|
message: error instanceof Error ? error.message : String(error)
|
|
5005
5611
|
}
|
|
5006
5612
|
})
|
|
@@ -5016,7 +5622,7 @@ async function serveCodex() {
|
|
|
5016
5622
|
controller.abort(new Error("Codex request cancelled"));
|
|
5017
5623
|
});
|
|
5018
5624
|
if (request.method === "GET" && route === "/client") {
|
|
5019
|
-
|
|
5625
|
+
attach();
|
|
5020
5626
|
clients.add(response);
|
|
5021
5627
|
response.once("close", () => {
|
|
5022
5628
|
clients.delete(response);
|
|
@@ -5027,43 +5633,54 @@ async function serveCodex() {
|
|
|
5027
5633
|
return;
|
|
5028
5634
|
}
|
|
5029
5635
|
if (request.method === "GET" && route === "/") {
|
|
5030
|
-
await connected;
|
|
5031
5636
|
response.setHeader("Content-Type", "application/json");
|
|
5032
5637
|
response.end(JSON.stringify({ name: "chappie", codexHome, appServer }));
|
|
5033
5638
|
return;
|
|
5034
5639
|
}
|
|
5035
|
-
if (request.method !== "POST")
|
|
5036
|
-
|
|
5037
|
-
|
|
5038
|
-
|
|
5039
|
-
}
|
|
5040
|
-
await connected;
|
|
5640
|
+
if (request.method !== "POST")
|
|
5641
|
+
throw new RequestError(
|
|
5642
|
+
`Unsupported Chappie request: ${request.method} ${route}`
|
|
5643
|
+
);
|
|
5041
5644
|
const chunks = [];
|
|
5042
5645
|
for await (const chunk of request) chunks.push(Buffer.from(chunk));
|
|
5043
|
-
const body =
|
|
5646
|
+
const body = parseObject(Buffer.concat(chunks).toString("utf8"));
|
|
5044
5647
|
if (route === "/v1/responses") {
|
|
5648
|
+
const payload = body;
|
|
5649
|
+
const metadata = payload.client_metadata?.["x-codex-turn-metadata"];
|
|
5650
|
+
const context = metadata ? parseObject(metadata) : void 0;
|
|
5651
|
+
if (context?.thread_source === "memory_consolidation")
|
|
5652
|
+
throw new RequestError(
|
|
5653
|
+
"Background memory generation is not supported by Chappie"
|
|
5654
|
+
);
|
|
5045
5655
|
const threadId = request.headers["thread-id"];
|
|
5046
5656
|
if (typeof threadId !== "string")
|
|
5047
|
-
throw new
|
|
5048
|
-
const state = await get(threadId);
|
|
5049
|
-
await events;
|
|
5657
|
+
throw new RequestError("Codex did not provide a thread-id header");
|
|
5658
|
+
const state = await get(threadId, true);
|
|
5659
|
+
await (state.connection instanceof AppServer ? events.rpc : events.desktop);
|
|
5050
5660
|
if (!state.active()) {
|
|
5051
|
-
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5055
|
-
|
|
5661
|
+
if (state.connection instanceof AppServer) {
|
|
5662
|
+
const { thread } = await state.connection.request(
|
|
5663
|
+
"thread/read",
|
|
5664
|
+
{ threadId }
|
|
5665
|
+
);
|
|
5666
|
+
register(thread, state.connection);
|
|
5667
|
+
} else {
|
|
5668
|
+
await state.snapshot(
|
|
5669
|
+
await state.connection.desktop.snapshot(
|
|
5670
|
+
threadId,
|
|
5671
|
+
state.connection.owner
|
|
5672
|
+
)
|
|
5673
|
+
);
|
|
5674
|
+
}
|
|
5056
5675
|
}
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5060
|
-
controller.signal
|
|
5061
|
-
);
|
|
5676
|
+
if (!state.active())
|
|
5677
|
+
throw new RequestError("Chappie is not active for this session");
|
|
5678
|
+
await state.respond(payload, context, response, controller.signal);
|
|
5062
5679
|
return;
|
|
5063
5680
|
}
|
|
5064
5681
|
if (route === "/resource") {
|
|
5065
5682
|
if (typeof body.threadId !== "string" || typeof body.uri !== "string")
|
|
5066
|
-
throw new
|
|
5683
|
+
throw new RequestError("Resource requests require threadId and uri");
|
|
5067
5684
|
const state = await get(body.threadId);
|
|
5068
5685
|
const resource = await state.session.resource(
|
|
5069
5686
|
body.uri,
|
|
@@ -5075,20 +5692,18 @@ async function serveCodex() {
|
|
|
5075
5692
|
}
|
|
5076
5693
|
if (route.startsWith("/tools/")) {
|
|
5077
5694
|
if (typeof body.threadId !== "string")
|
|
5078
|
-
throw new
|
|
5695
|
+
throw new RequestError("Codex did not provide threadId metadata");
|
|
5079
5696
|
const state = await get(body.threadId);
|
|
5080
5697
|
const name = decodeURIComponent(route.slice("/tools/".length));
|
|
5081
5698
|
const tool = state.tools.find((tool2) => tool2.name === name);
|
|
5082
|
-
if (!tool) throw new
|
|
5699
|
+
if (!tool) throw new RequestError(`Unknown Chappie tool: ${name}`);
|
|
5083
5700
|
const result = await tool.execute(body.arguments, controller.signal);
|
|
5084
5701
|
response.setHeader("Content-Type", "application/json");
|
|
5085
5702
|
response.end(JSON.stringify(result));
|
|
5086
5703
|
return;
|
|
5087
5704
|
}
|
|
5088
|
-
|
|
5089
|
-
response.end();
|
|
5705
|
+
throw new RequestError(`Unknown Chappie endpoint: ${route}`);
|
|
5090
5706
|
}
|
|
5091
|
-
let connected;
|
|
5092
5707
|
try {
|
|
5093
5708
|
try {
|
|
5094
5709
|
await new Promise((resolve, reject) => {
|
|
@@ -5109,32 +5724,17 @@ async function serveCodex() {
|
|
|
5109
5724
|
process.send?.({ ready: true });
|
|
5110
5725
|
return;
|
|
5111
5726
|
}
|
|
5112
|
-
|
|
5113
|
-
await connected;
|
|
5727
|
+
attach();
|
|
5114
5728
|
process.send?.({ ready: true });
|
|
5115
|
-
let cursor = null;
|
|
5116
|
-
do {
|
|
5117
|
-
const page = await rpc.request(
|
|
5118
|
-
"thread/loaded/list",
|
|
5119
|
-
cursor ? { cursor } : {}
|
|
5120
|
-
);
|
|
5121
|
-
for (const id of page.data) {
|
|
5122
|
-
try {
|
|
5123
|
-
await get(id);
|
|
5124
|
-
} catch (error) {
|
|
5125
|
-
console.error(error);
|
|
5126
|
-
}
|
|
5127
|
-
}
|
|
5128
|
-
cursor = page.nextCursor;
|
|
5129
|
-
} while (cursor);
|
|
5130
5729
|
process.once("SIGINT", stop);
|
|
5131
5730
|
process.once("SIGTERM", stop);
|
|
5132
|
-
await
|
|
5731
|
+
await stopped.promise;
|
|
5133
5732
|
process.off("SIGINT", stop);
|
|
5134
5733
|
process.off("SIGTERM", stop);
|
|
5135
5734
|
} finally {
|
|
5136
5735
|
for (const state of sessions.values()) state.session.close();
|
|
5137
|
-
rpc
|
|
5736
|
+
rpc?.close();
|
|
5737
|
+
desktop?.close();
|
|
5138
5738
|
server.closeAllConnections();
|
|
5139
5739
|
await new Promise((resolve) => server.close(() => resolve()));
|
|
5140
5740
|
}
|