@excitedjs/dreamux 0.1.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.
- package/LICENSE +21 -0
- package/README.md +223 -0
- package/bin/dreamux +31 -0
- package/bin/server +35 -0
- package/bin/server-ctl +28 -0
- package/db/migrations/0001_init.sql +49 -0
- package/dist/admin/methods.js +103 -0
- package/dist/admin/methods.js.map +1 -0
- package/dist/admin/protocol.js +15 -0
- package/dist/admin/protocol.js.map +1 -0
- package/dist/admin/socket.js +251 -0
- package/dist/admin/socket.js.map +1 -0
- package/dist/cli/dreamux.js +105 -0
- package/dist/cli/dreamux.js.map +1 -0
- package/dist/cli/server-ctl.js +172 -0
- package/dist/cli/server-ctl.js.map +1 -0
- package/dist/cli/server.js +88 -0
- package/dist/cli/server.js.map +1 -0
- package/dist/codex/events.js +82 -0
- package/dist/codex/events.js.map +1 -0
- package/dist/codex/handshake.js +85 -0
- package/dist/codex/handshake.js.map +1 -0
- package/dist/codex/rpc.js +200 -0
- package/dist/codex/rpc.js.map +1 -0
- package/dist/codex/supervisor.js +184 -0
- package/dist/codex/supervisor.js.map +1 -0
- package/dist/codex/types.js +10 -0
- package/dist/codex/types.js.map +1 -0
- package/dist/db/repository.js +207 -0
- package/dist/db/repository.js.map +1 -0
- package/dist/db/schema.js +29 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/db/types.js +2 -0
- package/dist/db/types.js.map +1 -0
- package/dist/dispatcher/approval.js +43 -0
- package/dist/dispatcher/approval.js.map +1 -0
- package/dist/dispatcher/runtime.js +262 -0
- package/dist/dispatcher/runtime.js.map +1 -0
- package/dist/dispatcher/turn-manager.js +167 -0
- package/dist/dispatcher/turn-manager.js.map +1 -0
- package/dist/feishu/bot.js +137 -0
- package/dist/feishu/bot.js.map +1 -0
- package/dist/feishu/content.js +108 -0
- package/dist/feishu/content.js.map +1 -0
- package/dist/feishu/render.js +600 -0
- package/dist/feishu/render.js.map +1 -0
- package/dist/feishu/types.js +9 -0
- package/dist/feishu/types.js.map +1 -0
- package/dist/runtime/codex-args.js +92 -0
- package/dist/runtime/codex-args.js.map +1 -0
- package/dist/runtime/config.js +351 -0
- package/dist/runtime/config.js.map +1 -0
- package/dist/runtime/paths.js +77 -0
- package/dist/runtime/paths.js.map +1 -0
- package/dist/runtime/secrets.js +18 -0
- package/dist/runtime/secrets.js.map +1 -0
- package/dist/server.js +234 -0
- package/dist/server.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Collects a Codex turn from the JSON-RPC notification stream.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from claudemux's `plugins/claudemux/core/src/engines/codex/events.ts`.
|
|
5
|
+
* We drop the token-usage bookkeeping (P0 doesn't need it) and the
|
|
6
|
+
* `notLoaded` items merging (assistant text is enough for outbound).
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Subscribe to turn notifications for one thread. Returns a collector
|
|
10
|
+
* whose `awaitTurn()` resolves on `turn/completed`. Items arriving on the
|
|
11
|
+
* parallel `item/completed` stream are buffered and merged in.
|
|
12
|
+
*/
|
|
13
|
+
export function subscribeTurnCollection(client, threadId) {
|
|
14
|
+
const itemsByTurn = new Map();
|
|
15
|
+
let cached = null;
|
|
16
|
+
let awaiting = null;
|
|
17
|
+
let resolveTurn = null;
|
|
18
|
+
let done = false;
|
|
19
|
+
client.onNotification((notif) => {
|
|
20
|
+
if (done)
|
|
21
|
+
return;
|
|
22
|
+
if (notif.method === 'item/completed') {
|
|
23
|
+
const params = notif.params;
|
|
24
|
+
if (params.threadId !== threadId)
|
|
25
|
+
return;
|
|
26
|
+
const bucket = itemsByTurn.get(params.turnId) ?? [];
|
|
27
|
+
bucket.push(params.item);
|
|
28
|
+
itemsByTurn.set(params.turnId, bucket);
|
|
29
|
+
}
|
|
30
|
+
else if (notif.method === 'turn/completed') {
|
|
31
|
+
const params = notif.params;
|
|
32
|
+
if (params.threadId !== threadId)
|
|
33
|
+
return;
|
|
34
|
+
done = true;
|
|
35
|
+
const items = itemsByTurn.get(params.turn.id) ?? params.turn.items ?? [];
|
|
36
|
+
cached = { threadId, turnId: params.turn.id, items };
|
|
37
|
+
if (resolveTurn !== null) {
|
|
38
|
+
resolveTurn(cached);
|
|
39
|
+
resolveTurn = null;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
awaitTurn() {
|
|
45
|
+
if (cached !== null)
|
|
46
|
+
return Promise.resolve(cached);
|
|
47
|
+
if (awaiting !== null)
|
|
48
|
+
return awaiting;
|
|
49
|
+
awaiting = new Promise((res) => {
|
|
50
|
+
resolveTurn = res;
|
|
51
|
+
});
|
|
52
|
+
return awaiting;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Send a `turn/start` request and await `turn/completed`.
|
|
58
|
+
* Returns the collected turn, or throws on RPC failure.
|
|
59
|
+
*/
|
|
60
|
+
export async function runTurn(client, threadId, prompt, cwd) {
|
|
61
|
+
const collector = subscribeTurnCollection(client, threadId);
|
|
62
|
+
const input = [
|
|
63
|
+
{ type: 'text', text: prompt, text_elements: [] },
|
|
64
|
+
];
|
|
65
|
+
await client.request('turn/start', cwd === null ? { threadId, input } : { threadId, input, cwd });
|
|
66
|
+
return collector.awaitTurn();
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Extract the final assistant message text from a collected turn.
|
|
70
|
+
* Returns null if the turn had no assistant message — caller decides
|
|
71
|
+
* what to surface to the user (see issue #2 §"开放问题 Q4").
|
|
72
|
+
*/
|
|
73
|
+
export function extractAssistantText(turn) {
|
|
74
|
+
const messages = turn.items.filter((it) => it.type === 'agentMessage');
|
|
75
|
+
if (messages.length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
const last = messages[messages.length - 1];
|
|
78
|
+
return typeof last?.text === 'string' && last.text.length > 0
|
|
79
|
+
? last.text
|
|
80
|
+
: null;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/codex/events.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex app-server LSP-style init handshake (required by codex 0.134+).
|
|
3
|
+
*
|
|
4
|
+
* Without this, every business RPC (`thread/start`, `turn/start`, …) is
|
|
5
|
+
* answered with `Not initialized` and the daemon never moves. The sequence
|
|
6
|
+
* is the canonical LSP one:
|
|
7
|
+
*
|
|
8
|
+
* 1. client → `initialize` request with ClientInfo + capabilities
|
|
9
|
+
* 2. server → InitializeResponse (userAgent, codexHome, platform info)
|
|
10
|
+
* 3. client → `initialized` notification (no params)
|
|
11
|
+
*
|
|
12
|
+
* Steps 1 + 3 are both required: codex's `Not initialized` guard is cleared
|
|
13
|
+
* only once the notification arrives. Capabilities can be omitted (the spec
|
|
14
|
+
* accepts `null`) — we pass an explicit default so the negotiation choices
|
|
15
|
+
* are visible in the source.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Our default ClientInfo. Picked once at module load so the version doesn't
|
|
19
|
+
* have to be threaded through every call site. Keep in sync with package.json.
|
|
20
|
+
*/
|
|
21
|
+
const DREAMUX_CLIENT_INFO = {
|
|
22
|
+
name: 'dreamux-server',
|
|
23
|
+
title: 'dreamux Codex-host server',
|
|
24
|
+
version: '0.1.0',
|
|
25
|
+
};
|
|
26
|
+
const DEFAULT_CAPABILITIES = {
|
|
27
|
+
// We don't currently consume any experimental method, but turning this on
|
|
28
|
+
// is harmless — codex only widens the set of method/field shapes it'll
|
|
29
|
+
// emit. Keeping it true avoids a future surprise if we start consuming an
|
|
30
|
+
// experimental notification (e.g. turn/streaming) and the daemon was
|
|
31
|
+
// silently dropping it because of this flag.
|
|
32
|
+
experimentalApi: true,
|
|
33
|
+
// We do not handle attestation requests; do not invite them.
|
|
34
|
+
requestAttestation: false,
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Default cap on the initialize round-trip. See HandshakeOptions.timeoutMs.
|
|
38
|
+
* 10s mirrors CodexProcess's spawn ready-probe so a hung handshake doesn't
|
|
39
|
+
* outlive the codex spawn that produced it.
|
|
40
|
+
*/
|
|
41
|
+
const DEFAULT_HANDSHAKE_TIMEOUT_MS = 10_000;
|
|
42
|
+
/**
|
|
43
|
+
* Drive the full `initialize` + `initialized` exchange. Returns the
|
|
44
|
+
* server's InitializeResponse (userAgent, codexHome, platform) so callers
|
|
45
|
+
* can log it or surface it via admin.
|
|
46
|
+
*
|
|
47
|
+
* Times out (rejects) if the daemon does not reply within
|
|
48
|
+
* `options.timeoutMs`. The pending request stays in the client's `pending`
|
|
49
|
+
* map after timeout — callers that recover by tearing down the client
|
|
50
|
+
* (e.g. DispatcherRuntime's cleanupOnFailure) will then drop those
|
|
51
|
+
* pending entries when the WS connection closes.
|
|
52
|
+
*/
|
|
53
|
+
export async function performInitializeHandshake(client, options = {}) {
|
|
54
|
+
const params = {
|
|
55
|
+
clientInfo: options.clientInfo ?? DREAMUX_CLIENT_INFO,
|
|
56
|
+
capabilities: options.capabilities === undefined
|
|
57
|
+
? DEFAULT_CAPABILITIES
|
|
58
|
+
: options.capabilities,
|
|
59
|
+
};
|
|
60
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS;
|
|
61
|
+
const response = await withTimeout(client.request('initialize', params), timeoutMs, `codex initialize handshake did not respond within ${timeoutMs}ms — the daemon may be hung, crashed mid-response, or speaking an incompatible protocol`);
|
|
62
|
+
// Per LSP convention the `initialized` notification carries no params.
|
|
63
|
+
// codex's ClientNotification union accepts the bare `{ method: "initialized" }`
|
|
64
|
+
// envelope without a `params` field.
|
|
65
|
+
client.notify('initialized', undefined);
|
|
66
|
+
return response;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Race `p` against a timeout. Rejects with `Error(msg)` if the timeout wins.
|
|
70
|
+
* The timer is `unref`ed so it doesn't keep the event loop alive on its own.
|
|
71
|
+
*/
|
|
72
|
+
function withTimeout(p, ms, msg) {
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
const timer = setTimeout(() => reject(new Error(msg)), ms);
|
|
75
|
+
timer.unref();
|
|
76
|
+
p.then((v) => {
|
|
77
|
+
clearTimeout(timer);
|
|
78
|
+
resolve(v);
|
|
79
|
+
}, (err) => {
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
reject(err);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=handshake.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"handshake.js","sourceRoot":"","sources":["../../src/codex/handshake.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAUH;;;GAGG;AACH,MAAM,mBAAmB,GAAe;IACtC,IAAI,EAAE,gBAAgB;IACtB,KAAK,EAAE,2BAA2B;IAClC,OAAO,EAAE,OAAO;CACjB,CAAC;AAEF,MAAM,oBAAoB,GAA2B;IACnD,0EAA0E;IAC1E,uEAAuE;IACvE,0EAA0E;IAC1E,qEAAqE;IACrE,6CAA6C;IAC7C,eAAe,EAAE,IAAI;IACrB,6DAA6D;IAC7D,kBAAkB,EAAE,KAAK;CAC1B,CAAC;AAgBF;;;;GAIG;AACH,MAAM,4BAA4B,GAAG,MAAM,CAAC;AAE5C;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,MAAqB,EACrB,UAA4B,EAAE;IAE9B,MAAM,MAAM,GAAqB;QAC/B,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,mBAAmB;QACrD,YAAY,EACV,OAAO,CAAC,YAAY,KAAK,SAAS;YAChC,CAAC,CAAC,oBAAoB;YACtB,CAAC,CAAC,OAAO,CAAC,YAAY;KAC3B,CAAC;IACF,MAAM,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,4BAA4B,CAAC;IACpE,MAAM,QAAQ,GAAG,MAAM,WAAW,CAChC,MAAM,CAAC,OAAO,CAAqB,YAAY,EAAE,MAAM,CAAC,EACxD,SAAS,EACT,qDAAqD,SAAS,yFAAyF,CACxJ,CAAC;IACF,uEAAuE;IACvE,gFAAgF;IAChF,qCAAqC;IACrC,MAAM,CAAC,MAAM,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;IACxC,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,SAAS,WAAW,CAClB,CAAa,EACb,EAAU,EACV,GAAW;IAEX,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACxC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC3D,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,CAAC,CAAC,IAAI,CACJ,CAAC,CAAC,EAAE,EAAE;YACJ,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,OAAO,CAAC,CAAC,CAAC,CAAC;QACb,CAAC,EACD,CAAC,GAAG,EAAE,EAAE;YACN,YAAY,CAAC,KAAK,CAAC,CAAC;YACpB,MAAM,CAAC,GAAG,CAAC,CAAC;QACd,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex app-server WebSocket JSON-RPC client.
|
|
3
|
+
*
|
|
4
|
+
* Adapted from claudemux's `plugins/claudemux/core/src/engines/codex/rpc.ts`
|
|
5
|
+
* (excitedjs/dreamux#2 §"关键代码复用结论"). Two differences:
|
|
6
|
+
* - uses the public `ws` npm package instead of the vendored `#ws`;
|
|
7
|
+
* - replaces the `codex-protocol` import with the in-tree `./types.ts`.
|
|
8
|
+
*
|
|
9
|
+
* The wire envelope codex emits is *not* strict JSON-RPC 2.0 — the
|
|
10
|
+
* `jsonrpc` version field is omitted. Frame routing is by structural probe:
|
|
11
|
+
* - method + id + params → request
|
|
12
|
+
* - method + params → notification
|
|
13
|
+
* - id + result|error → response
|
|
14
|
+
*/
|
|
15
|
+
import WebSocket from 'ws';
|
|
16
|
+
/**
|
|
17
|
+
* A long-running WebSocket connection to one codex app-server daemon.
|
|
18
|
+
* One instance per Dispatcher; lifetime matches the dispatcher runtime.
|
|
19
|
+
*/
|
|
20
|
+
export class CodexWsClient {
|
|
21
|
+
ws;
|
|
22
|
+
pending = new Map();
|
|
23
|
+
notifHandlers = [];
|
|
24
|
+
closeHandlers = [];
|
|
25
|
+
serverReqHandler = async () => {
|
|
26
|
+
throw new Error('codex sent a server-request but no handler is installed. ' +
|
|
27
|
+
'Install one via setServerRequestHandler() before driving turns. ' +
|
|
28
|
+
'See issue #2 §"信任模型" — approval handlers must fail loudly, not return null.');
|
|
29
|
+
};
|
|
30
|
+
nextId = 1;
|
|
31
|
+
opened;
|
|
32
|
+
closed = false;
|
|
33
|
+
closeReason = null;
|
|
34
|
+
constructor(opts) {
|
|
35
|
+
// `perMessageDeflate: false` is load-bearing — the codex app-server's
|
|
36
|
+
// WebSocket upgrade is strict about Sec-WebSocket-Extensions and would
|
|
37
|
+
// reject the `ws` package's default permessage-deflate proposal.
|
|
38
|
+
// Verified empirically against codex 0.133.0 by claudemux.
|
|
39
|
+
const wsOpts = { perMessageDeflate: false };
|
|
40
|
+
if (opts.socketPath !== undefined) {
|
|
41
|
+
this.ws = new WebSocket(`ws+unix://${opts.socketPath}`, wsOpts);
|
|
42
|
+
}
|
|
43
|
+
else if (opts.url !== undefined) {
|
|
44
|
+
this.ws = new WebSocket(opts.url, wsOpts);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
throw new Error('CodexWsClient: socketPath or url required');
|
|
48
|
+
}
|
|
49
|
+
this.opened = new Promise((res, rej) => {
|
|
50
|
+
this.ws.once('open', () => res());
|
|
51
|
+
this.ws.once('error', (e) => rej(e instanceof Error ? e : new Error(String(e))));
|
|
52
|
+
});
|
|
53
|
+
this.ws.on('message', (data) => this.onFrame(data));
|
|
54
|
+
this.ws.on('close', () => this.tearDown(new Error('codex daemon closed the connection')));
|
|
55
|
+
this.ws.on('error', (e) => this.tearDown(e instanceof Error ? e : new Error(String(e))));
|
|
56
|
+
}
|
|
57
|
+
ready() {
|
|
58
|
+
return this.opened;
|
|
59
|
+
}
|
|
60
|
+
onNotification(handler) {
|
|
61
|
+
this.notifHandlers.push(handler);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Install handler for server→client requests (approval, attestation, etc).
|
|
65
|
+
* The handler's return value becomes the response `result`; a throw becomes
|
|
66
|
+
* the response `error.message`.
|
|
67
|
+
*
|
|
68
|
+
* For dreamux MVP this should fail-fast on any approval request — see
|
|
69
|
+
* issue #2 §"信任模型" (approval-policy=never + fail-fast handler).
|
|
70
|
+
*/
|
|
71
|
+
setServerRequestHandler(handler) {
|
|
72
|
+
this.serverReqHandler = handler;
|
|
73
|
+
}
|
|
74
|
+
onClose(handler) {
|
|
75
|
+
if (this.closed) {
|
|
76
|
+
handler(this.closeReason ?? new Error('codex client closed'));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.closeHandlers.push(handler);
|
|
80
|
+
}
|
|
81
|
+
request(method, params) {
|
|
82
|
+
if (this.closed) {
|
|
83
|
+
return Promise.reject(this.closeReason ?? new Error('codex client closed'));
|
|
84
|
+
}
|
|
85
|
+
const id = this.nextId++;
|
|
86
|
+
const envelope = { method, id, params };
|
|
87
|
+
return new Promise((resolve, reject) => {
|
|
88
|
+
this.pending.set(id, {
|
|
89
|
+
resolve: resolve,
|
|
90
|
+
reject,
|
|
91
|
+
});
|
|
92
|
+
try {
|
|
93
|
+
this.ws.send(JSON.stringify(envelope));
|
|
94
|
+
}
|
|
95
|
+
catch (e) {
|
|
96
|
+
this.pending.delete(id);
|
|
97
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Send a JSON-RPC notification (no `id`, no response expected). codex 0.134+
|
|
103
|
+
* uses these for the `initialized` handshake confirmation, among others.
|
|
104
|
+
*/
|
|
105
|
+
notify(method, params) {
|
|
106
|
+
if (this.closed) {
|
|
107
|
+
throw this.closeReason ?? new Error('codex client closed');
|
|
108
|
+
}
|
|
109
|
+
const envelope = { method, params };
|
|
110
|
+
this.ws.send(JSON.stringify(envelope));
|
|
111
|
+
}
|
|
112
|
+
close() {
|
|
113
|
+
this.tearDown(new Error('codex client closed by caller'));
|
|
114
|
+
this.ws.terminate();
|
|
115
|
+
}
|
|
116
|
+
onFrame(data) {
|
|
117
|
+
let parsed;
|
|
118
|
+
try {
|
|
119
|
+
const text = typeof data === 'string' ? data : data.toString('utf8');
|
|
120
|
+
parsed = JSON.parse(text);
|
|
121
|
+
}
|
|
122
|
+
catch (e) {
|
|
123
|
+
this.tearDown(new Error(`codex daemon sent a non-JSON frame: ${e.message}`));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (typeof parsed !== 'object' || parsed === null) {
|
|
127
|
+
this.tearDown(new Error('codex daemon sent a non-object envelope'));
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const env = parsed;
|
|
131
|
+
const hasMethod = typeof env['method'] === 'string';
|
|
132
|
+
const hasId = typeof env['id'] === 'number';
|
|
133
|
+
const hasResult = 'result' in env;
|
|
134
|
+
const hasError = 'error' in env;
|
|
135
|
+
if (hasMethod && hasId) {
|
|
136
|
+
this.handleServerRequest(env).catch((err) => this.tearDown(err instanceof Error ? err : new Error(String(err))));
|
|
137
|
+
}
|
|
138
|
+
else if (hasMethod) {
|
|
139
|
+
this.dispatchNotification(env);
|
|
140
|
+
}
|
|
141
|
+
else if (hasId && (hasResult || hasError)) {
|
|
142
|
+
this.handleResponse(env);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
this.tearDown(new Error('codex daemon sent envelope with neither id nor method'));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
handleResponse(env) {
|
|
149
|
+
const pending = this.pending.get(env.id);
|
|
150
|
+
if (pending === undefined)
|
|
151
|
+
return;
|
|
152
|
+
this.pending.delete(env.id);
|
|
153
|
+
if ('error' in env) {
|
|
154
|
+
pending.reject(new Error(env.error.message));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
pending.resolve(env.result);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
dispatchNotification(notif) {
|
|
161
|
+
for (const h of this.notifHandlers) {
|
|
162
|
+
try {
|
|
163
|
+
h(notif);
|
|
164
|
+
}
|
|
165
|
+
catch {
|
|
166
|
+
// Handler throws must not poison the dispatch loop.
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
async handleServerRequest(env) {
|
|
171
|
+
try {
|
|
172
|
+
const result = await this.serverReqHandler(env);
|
|
173
|
+
const reply = { id: env.id, result };
|
|
174
|
+
this.ws.send(JSON.stringify(reply));
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
178
|
+
const reply = { id: env.id, error: { message } };
|
|
179
|
+
this.ws.send(JSON.stringify(reply));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
tearDown(reason) {
|
|
183
|
+
if (this.closed)
|
|
184
|
+
return;
|
|
185
|
+
this.closed = true;
|
|
186
|
+
this.closeReason = reason;
|
|
187
|
+
for (const { reject } of this.pending.values())
|
|
188
|
+
reject(reason);
|
|
189
|
+
this.pending.clear();
|
|
190
|
+
for (const handler of this.closeHandlers) {
|
|
191
|
+
try {
|
|
192
|
+
handler(reason);
|
|
193
|
+
}
|
|
194
|
+
catch {
|
|
195
|
+
// Close observers are cleanup hooks; one throw must not mask teardown.
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
//# sourceMappingURL=rpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc.js","sourceRoot":"","sources":["../../src/codex/rpc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Codex app-server child process supervisor.
|
|
3
|
+
*
|
|
4
|
+
* Heavily simplified from claudemux's
|
|
5
|
+
* `plugins/claudemux/core/src/engines/codex/supervisor.ts`. Differences:
|
|
6
|
+
* - one process per Dispatcher, owned in-memory (no /tmp registry)
|
|
7
|
+
* - no IPC bridge subprocess (the server holds the WS itself)
|
|
8
|
+
* - no spawn lock / borrow lock (Dispatcher is the single owner)
|
|
9
|
+
* - lifecycle bound to DispatcherRuntime, not a CLI invocation
|
|
10
|
+
*
|
|
11
|
+
* Issue #2 §"实现陷阱": codex CLI is a node wrapper that spawns the rust
|
|
12
|
+
* binary as a child; both land in the same process group. Reap must
|
|
13
|
+
* SIGKILL the whole group, not just the leader, or the rust process leaks.
|
|
14
|
+
*/
|
|
15
|
+
import { spawn as spawnChild, } from 'node:child_process';
|
|
16
|
+
import { closeSync, existsSync, mkdirSync, openSync, rmSync, statSync, } from 'node:fs';
|
|
17
|
+
import { dirname } from 'node:path';
|
|
18
|
+
/** A handle to one running codex app-server child process. */
|
|
19
|
+
export class CodexProcess {
|
|
20
|
+
opts;
|
|
21
|
+
socketPath;
|
|
22
|
+
cwd;
|
|
23
|
+
child = null;
|
|
24
|
+
_pid = null;
|
|
25
|
+
reaped = false;
|
|
26
|
+
constructor(opts) {
|
|
27
|
+
this.opts = opts;
|
|
28
|
+
this.socketPath = opts.socketPath;
|
|
29
|
+
this.cwd = opts.cwd;
|
|
30
|
+
}
|
|
31
|
+
get pid() {
|
|
32
|
+
return this._pid;
|
|
33
|
+
}
|
|
34
|
+
/** Spawn the daemon and resolve once its listen socket is bound. */
|
|
35
|
+
async start() {
|
|
36
|
+
if (this.child !== null) {
|
|
37
|
+
throw new Error('CodexProcess.start: already started');
|
|
38
|
+
}
|
|
39
|
+
const binPath = this.opts.binPath ?? (process.env['CODEX_HOST_CODEX_BIN'] || 'codex');
|
|
40
|
+
const args = [
|
|
41
|
+
'app-server',
|
|
42
|
+
'--listen',
|
|
43
|
+
`unix://${this.opts.socketPath}`,
|
|
44
|
+
...(this.opts.extraArgs ?? []),
|
|
45
|
+
];
|
|
46
|
+
mkdirSync(dirname(this.opts.socketPath), { recursive: true });
|
|
47
|
+
mkdirSync(this.opts.cwd, { recursive: true });
|
|
48
|
+
mkdirSync(dirname(this.opts.stdoutLogPath), { recursive: true });
|
|
49
|
+
// Stale socket from a previous crashed run would otherwise prevent
|
|
50
|
+
// the daemon from binding.
|
|
51
|
+
if (existsSync(this.opts.socketPath)) {
|
|
52
|
+
try {
|
|
53
|
+
rmSync(this.opts.socketPath, { force: true });
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
/* ignore — bind will fail loudly if it really is busy */
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const stdoutFd = openSync(this.opts.stdoutLogPath, 'a', 0o600);
|
|
60
|
+
const stderrFd = openSync(this.opts.stderrLogPath, 'a', 0o600);
|
|
61
|
+
const spawnOpts = {
|
|
62
|
+
cwd: this.opts.cwd,
|
|
63
|
+
env: this.opts.env ?? process.env,
|
|
64
|
+
detached: true, // its own process group, so we can group-kill on reap
|
|
65
|
+
stdio: ['ignore', stdoutFd, stderrFd],
|
|
66
|
+
};
|
|
67
|
+
let child;
|
|
68
|
+
try {
|
|
69
|
+
child = await new Promise((resolve, reject) => {
|
|
70
|
+
let settled = false;
|
|
71
|
+
const c = spawnChild(binPath, args, spawnOpts);
|
|
72
|
+
c.once('error', (e) => {
|
|
73
|
+
if (settled)
|
|
74
|
+
return;
|
|
75
|
+
settled = true;
|
|
76
|
+
reject(e instanceof Error ? e : new Error(String(e)));
|
|
77
|
+
});
|
|
78
|
+
c.once('spawn', () => {
|
|
79
|
+
if (settled)
|
|
80
|
+
return;
|
|
81
|
+
settled = true;
|
|
82
|
+
resolve(c);
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
closeSync(stdoutFd);
|
|
88
|
+
closeSync(stderrFd);
|
|
89
|
+
}
|
|
90
|
+
if (child.pid === undefined) {
|
|
91
|
+
throw new Error('codex daemon spawned without a pid');
|
|
92
|
+
}
|
|
93
|
+
this.child = child;
|
|
94
|
+
this._pid = child.pid;
|
|
95
|
+
// Future post-spawn `error` emissions must not crash the supervisor.
|
|
96
|
+
child.on('error', () => {
|
|
97
|
+
/* daemon-side error, can no longer affect this process */
|
|
98
|
+
});
|
|
99
|
+
try {
|
|
100
|
+
await waitForSocket(this.opts.socketPath, child.pid, this.opts.readyTimeoutMs ?? 10000);
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
await this.reap();
|
|
104
|
+
throw e;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** SIGTERM → 1s wait → SIGKILL group. Idempotent. */
|
|
108
|
+
async reap() {
|
|
109
|
+
if (this.reaped)
|
|
110
|
+
return;
|
|
111
|
+
this.reaped = true;
|
|
112
|
+
const pid = this._pid;
|
|
113
|
+
if (pid !== null) {
|
|
114
|
+
if (isProcessAlive(pid)) {
|
|
115
|
+
killProcessGroup(pid, 'SIGTERM');
|
|
116
|
+
const deadline = Date.now() + 1000;
|
|
117
|
+
while (Date.now() < deadline) {
|
|
118
|
+
if (!isProcessAlive(pid))
|
|
119
|
+
break;
|
|
120
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Always SIGKILL the group, even if the leader is already dead —
|
|
124
|
+
// a reparented child (rust binary outliving its node wrapper) is
|
|
125
|
+
// the exact failure this guards against.
|
|
126
|
+
killProcessGroup(pid, 'SIGKILL');
|
|
127
|
+
}
|
|
128
|
+
if (existsSync(this.opts.socketPath)) {
|
|
129
|
+
try {
|
|
130
|
+
rmSync(this.opts.socketPath, { force: true });
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
/* best effort */
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
this.child = null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
async function waitForSocket(path, pid, timeoutMs) {
|
|
140
|
+
const deadline = Date.now() + timeoutMs;
|
|
141
|
+
while (Date.now() < deadline) {
|
|
142
|
+
if (existsSync(path)) {
|
|
143
|
+
try {
|
|
144
|
+
const st = statSync(path);
|
|
145
|
+
if (st.isSocket())
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
/* race; keep polling */
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!isProcessAlive(pid)) {
|
|
153
|
+
throw new Error(`codex daemon (pid ${pid}) exited before binding ${path}`);
|
|
154
|
+
}
|
|
155
|
+
await new Promise((r) => setTimeout(r, 25));
|
|
156
|
+
}
|
|
157
|
+
throw new Error(`codex daemon (pid ${pid}) did not bind ${path} within ${timeoutMs}ms`);
|
|
158
|
+
}
|
|
159
|
+
export function isProcessAlive(pid) {
|
|
160
|
+
if (!Number.isFinite(pid) || pid <= 0)
|
|
161
|
+
return false;
|
|
162
|
+
try {
|
|
163
|
+
process.kill(pid, 0);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
catch (e) {
|
|
167
|
+
const errno = e.code;
|
|
168
|
+
return errno === 'EPERM';
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
export function killProcessGroup(pgid, signal) {
|
|
172
|
+
if (!Number.isFinite(pgid) || pgid <= 0)
|
|
173
|
+
return;
|
|
174
|
+
try {
|
|
175
|
+
process.kill(-pgid, signal);
|
|
176
|
+
}
|
|
177
|
+
catch (e) {
|
|
178
|
+
const errno = e.code;
|
|
179
|
+
if (errno === 'ESRCH' || errno === 'EPERM')
|
|
180
|
+
return;
|
|
181
|
+
throw e;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=supervisor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"supervisor.js","sourceRoot":"","sources":["../../src/codex/supervisor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal subset of the Codex app-server JSON-RPC protocol used by dreamux.
|
|
3
|
+
*
|
|
4
|
+
* The full schema lives in the codex repo (ts-rs generated); we hand-curate
|
|
5
|
+
* the shapes we actually consume / send. This avoids vendoring the whole
|
|
6
|
+
* codex-protocol package — at the cost of a CI drift gate that should be
|
|
7
|
+
* added in a follow-up (see issue #2 §"Codex 协议处理").
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/codex/types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|