lark-remote 0.1.6 → 0.1.7
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/CHANGELOG.md +23 -1
- package/README.en.md +9 -1
- package/README.md +14 -2
- package/dist/bridge/approval-coordinator.d.ts +57 -34
- package/dist/bridge/approval-coordinator.js +191 -40
- package/dist/bridge/inbound-media.d.ts +62 -0
- package/dist/bridge/inbound-media.js +282 -0
- package/dist/bridge/index.d.ts +78 -7
- package/dist/bridge/index.js +202 -79
- package/dist/bridge/queue-manager.d.ts +2 -1
- package/dist/bridge/queue-manager.js +1 -0
- package/dist/card/approval-render.js +152 -36
- package/dist/card/bash-card-session.d.ts +2 -24
- package/dist/card/bash-card-session.js +6 -73
- package/dist/card/bash-renderer.js +2 -3
- package/dist/card/card-budget.js +29 -75
- package/dist/card/card-session.d.ts +48 -0
- package/dist/card/card-session.js +86 -0
- package/dist/card/card-shared.d.ts +1 -12
- package/dist/card/card-shared.js +1 -26
- package/dist/card/run-card-session.d.ts +2 -55
- package/dist/card/run-card-session.js +6 -104
- package/dist/card/run-renderer.js +99 -232
- package/dist/card/run-state.d.ts +13 -0
- package/dist/card/run-state.js +25 -16
- package/dist/card/text-truncate.d.ts +13 -12
- package/dist/card/text-truncate.js +13 -63
- package/dist/common/fs.d.ts +8 -0
- package/dist/common/fs.js +17 -0
- package/dist/common/guards.d.ts +0 -20
- package/dist/common/guards.js +0 -43
- package/dist/common/sleep.d.ts +2 -0
- package/dist/common/sleep.js +5 -0
- package/dist/common/truncate.d.ts +39 -0
- package/dist/common/truncate.js +92 -0
- package/dist/config/codex-config.d.ts +1 -1
- package/dist/config/codex-config.js +1 -1
- package/dist/config/index.d.ts +88 -23
- package/dist/config/index.js +105 -34
- package/dist/connector/index.d.ts +69 -2
- package/dist/connector/index.js +107 -1
- package/dist/index.js +123 -105
- package/dist/instance-lock.js +4 -12
- package/dist/order/alias-resolve.d.ts +13 -0
- package/dist/order/alias-resolve.js +22 -0
- package/dist/order/alias-store.d.ts +38 -0
- package/dist/order/alias-store.js +106 -0
- package/dist/persistence/atomic-write.d.ts +8 -2
- package/dist/persistence/atomic-write.js +23 -7
- package/dist/restart.js +2 -1
- package/dist/router/card-helpers.d.ts +63 -0
- package/dist/router/card-helpers.js +146 -0
- package/dist/router/config/claude.js +25 -1
- package/dist/router/config/codex.js +29 -50
- package/dist/router/config/common/model-patch.d.ts +22 -0
- package/dist/router/config/common/model-patch.js +26 -0
- package/dist/router/config/common/render.js +5 -4
- package/dist/router/config/index.d.ts +12 -0
- package/dist/router/config/index.js +25 -1
- package/dist/router/config/kimi.d.ts +4 -1
- package/dist/router/config/kimi.js +27 -0
- package/dist/router/config/opencode.js +18 -5
- package/dist/router/config/pi.js +4 -5
- package/dist/router/config/types.d.ts +11 -2
- package/dist/router/index.d.ts +56 -21
- package/dist/router/index.js +373 -548
- package/dist/router/utils.d.ts +2 -0
- package/dist/router/utils.js +4 -0
- package/dist/runner/agent-choices-common.d.ts +16 -0
- package/dist/runner/agent-choices-common.js +31 -0
- package/dist/runner/claude/runner.d.ts +47 -10
- package/dist/runner/claude/runner.js +117 -46
- package/dist/runner/claude/session.d.ts +166 -0
- package/dist/runner/claude/session.js +749 -0
- package/dist/runner/codex/app-server/protocol-types.d.ts +31 -39
- package/dist/runner/codex/app-server/protocol-types.js +1 -12
- package/dist/runner/codex/app-server/runner.d.ts +42 -59
- package/dist/runner/codex/app-server/runner.js +160 -337
- package/dist/runner/codex/app-server/translator.d.ts +1 -21
- package/dist/runner/codex/app-server/translator.js +0 -18
- package/dist/runner/codex/index.d.ts +0 -2
- package/dist/runner/codex/index.js +0 -2
- package/dist/runner/common/acp/protocol-helpers.d.ts +14 -0
- package/dist/runner/common/acp/protocol-helpers.js +21 -0
- package/dist/runner/common/acp/protocol-types.d.ts +238 -0
- package/dist/runner/common/acp/protocol-types.js +75 -0
- package/dist/runner/common/connection-based-runner.d.ts +111 -0
- package/dist/runner/common/connection-based-runner.js +261 -0
- package/dist/runner/{codex/app-server → common/jsonrpc}/client.d.ts +14 -7
- package/dist/runner/{codex/app-server → common/jsonrpc}/client.js +27 -18
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
- package/dist/runner/{codex/app-server → common/jsonrpc}/connection-manager.js +63 -28
- package/dist/runner/{codex/app-server → common/jsonrpc}/transport.d.ts +5 -4
- package/dist/runner/{codex/app-server → common/jsonrpc}/transport.js +18 -19
- package/dist/runner/common/jsonrpc/types.d.ts +32 -0
- package/dist/runner/common/jsonrpc/types.js +9 -0
- package/dist/runner/common/process-stopper.d.ts +2 -1
- package/dist/runner/common/process-stopper.js +1 -0
- package/dist/runner/common/runner-utils.d.ts +6 -13
- package/dist/runner/common/runner-utils.js +6 -47
- package/dist/runner/common/spawning-runner.d.ts +24 -70
- package/dist/runner/common/spawning-runner.js +98 -152
- package/dist/runner/index.d.ts +4 -4
- package/dist/runner/index.js +4 -4
- package/dist/runner/kimi/acp/runner.d.ts +106 -0
- package/dist/runner/kimi/acp/runner.js +448 -0
- package/dist/runner/kimi/acp/translator.d.ts +124 -0
- package/dist/runner/kimi/acp/translator.js +336 -0
- package/dist/runner/kimi/index.d.ts +1 -1
- package/dist/runner/kimi/index.js +1 -1
- package/dist/runner/opencode/acp/index.d.ts +1 -0
- package/dist/runner/opencode/acp/index.js +2 -0
- package/dist/runner/opencode/acp/runner.d.ts +121 -0
- package/dist/runner/opencode/acp/runner.js +429 -0
- package/dist/runner/opencode/acp/translator.d.ts +119 -0
- package/dist/runner/opencode/acp/translator.js +345 -0
- package/dist/runner/opencode/index.d.ts +1 -1
- package/dist/runner/opencode/index.js +1 -1
- package/dist/runner/pi/index.d.ts +1 -1
- package/dist/runner/pi/index.js +1 -1
- package/dist/runner/pi/rpc/client.d.ts +52 -0
- package/dist/runner/pi/rpc/client.js +114 -0
- package/dist/runner/pi/rpc/index.d.ts +4 -0
- package/dist/runner/pi/rpc/index.js +4 -0
- package/dist/runner/pi/rpc/protocol-types.d.ts +127 -0
- package/dist/runner/pi/rpc/protocol-types.js +21 -0
- package/dist/runner/pi/rpc/runner.d.ts +80 -0
- package/dist/runner/pi/rpc/runner.js +199 -0
- package/dist/runner/pi/rpc/translator.d.ts +55 -0
- package/dist/runner/pi/rpc/translator.js +258 -0
- package/dist/runner/registry.d.ts +7 -8
- package/dist/runner/registry.js +3 -4
- package/dist/runner/resolve-agent-choices.js +18 -59
- package/dist/runner/sync-agent-choices.js +16 -67
- package/dist/runner/types.d.ts +43 -13
- package/dist/session/claude/session-index.js +2 -3
- package/dist/session/claude/session-reader.js +3 -6
- package/dist/session/claude/sessions.js +6 -13
- package/dist/session/codex/rollout-reader.d.ts +2 -2
- package/dist/session/codex/rollout-reader.js +14 -17
- package/dist/session/codex/sessions.d.ts +1 -1
- package/dist/session/codex/sessions.js +2 -2
- package/dist/session/common/content-blocks.d.ts +7 -0
- package/dist/session/common/content-blocks.js +13 -9
- package/dist/session/common/jsonl.d.ts +8 -25
- package/dist/session/common/jsonl.js +126 -261
- package/dist/session/common/pagination.d.ts +28 -0
- package/dist/session/common/pagination.js +33 -0
- package/dist/session/kimi/sessions.d.ts +34 -1
- package/dist/session/kimi/sessions.js +208 -98
- package/dist/session/opencode/sessions.d.ts +17 -3
- package/dist/session/opencode/sessions.js +29 -29
- package/dist/session/pi/sessions.d.ts +5 -1
- package/dist/session/pi/sessions.js +12 -16
- package/dist/session/session-store.d.ts +11 -1
- package/dist/session/session-store.js +75 -178
- package/dist/update/index.d.ts +2 -2
- package/dist/update/index.js +2 -2
- package/dist/update/install.d.ts +20 -2
- package/dist/update/install.js +51 -7
- package/package.json +1 -1
- package/dist/runner/codex/app-server/connection-manager.d.ts +0 -56
- package/dist/runner/codex/argv.d.ts +0 -33
- package/dist/runner/codex/argv.js +0 -40
- package/dist/runner/codex/factory.d.ts +0 -29
- package/dist/runner/codex/factory.js +0 -39
- package/dist/runner/codex/jsonl.d.ts +0 -37
- package/dist/runner/codex/jsonl.js +0 -221
- package/dist/runner/codex/runner.d.ts +0 -66
- package/dist/runner/codex/runner.js +0 -129
- package/dist/runner/common/exec-translator.d.ts +0 -73
- package/dist/runner/common/exec-translator.js +0 -105
- package/dist/runner/kimi/jsonl.d.ts +0 -28
- package/dist/runner/kimi/jsonl.js +0 -143
- package/dist/runner/kimi/runner.d.ts +0 -49
- package/dist/runner/kimi/runner.js +0 -105
- package/dist/runner/opencode/argv.d.ts +0 -32
- package/dist/runner/opencode/argv.js +0 -35
- package/dist/runner/opencode/jsonl.d.ts +0 -68
- package/dist/runner/opencode/jsonl.js +0 -295
- package/dist/runner/opencode/runner.d.ts +0 -61
- package/dist/runner/opencode/runner.js +0 -93
- package/dist/runner/pi/jsonl.d.ts +0 -128
- package/dist/runner/pi/jsonl.js +0 -249
- package/dist/runner/pi/runner.d.ts +0 -36
- package/dist/runner/pi/runner.js +0 -76
- package/dist/session/common/catch-up.d.ts +0 -12
- package/dist/session/common/catch-up.js +0 -31
|
@@ -1,60 +1,89 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ConnectionManager: manages
|
|
2
|
+
* ConnectionManager: manages agent-server connections per workspace, shared by
|
|
3
|
+
* all workspace-lifetime JSON-line runners (codex app-server, kimi acp,
|
|
4
|
+
* opencode acp, and pi rpc).
|
|
3
5
|
*
|
|
4
6
|
* Each workspace gets its own connection (transport + client). Connections are
|
|
5
7
|
* created on demand via `acquire()`, cached for reuse, and released after an
|
|
6
8
|
* idle timeout (30 minutes default).
|
|
9
|
+
*
|
|
10
|
+
* The manager is protocol-agnostic over the concrete client. The default
|
|
11
|
+
* `clientFactory` builds a JsonRpcClient with the `initialize` handshake (the
|
|
12
|
+
* ACP-style reference protocol); protocols that are NOT JSON-RPC (pi's
|
|
13
|
+
* `--mode rpc`, which has no handshake and binds the session at spawn) inject
|
|
14
|
+
* their own `clientFactory` + `buildArgs` + `shouldReuse` + `bindSession`.
|
|
7
15
|
*/
|
|
8
16
|
import { JsonlRpcTransport } from './transport.js';
|
|
9
|
-
import {
|
|
17
|
+
import { JsonRpcClient } from './client.js';
|
|
10
18
|
import { getLogger } from '../../../logger/index.js';
|
|
11
19
|
const DEFAULT_IDLE_TTL_MS = 30 * 60 * 1000; // 30 minutes
|
|
12
20
|
const DEFAULT_REQUEST_TIMEOUT_MS = 60_000; // 60 seconds
|
|
13
21
|
export class ConnectionManager {
|
|
14
22
|
slots = new Map();
|
|
15
23
|
binary;
|
|
16
|
-
|
|
24
|
+
buildArgs;
|
|
17
25
|
env;
|
|
18
26
|
requestTimeoutMs;
|
|
19
27
|
idleTtlMs;
|
|
28
|
+
initializeParams;
|
|
29
|
+
clientFactory;
|
|
30
|
+
shouldReuse;
|
|
31
|
+
logTag;
|
|
20
32
|
/** Callback when a connection is lost — cleared from slot map. */
|
|
21
33
|
onConnectionLost;
|
|
22
34
|
constructor(opts) {
|
|
23
35
|
this.binary = opts.binary;
|
|
24
|
-
this.
|
|
36
|
+
this.buildArgs = opts.buildArgs ?? (() => opts.args ?? []);
|
|
25
37
|
this.env = opts.env ?? {};
|
|
26
38
|
this.requestTimeoutMs = opts.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
27
39
|
this.idleTtlMs = opts.idleTtlMs ?? DEFAULT_IDLE_TTL_MS;
|
|
40
|
+
this.initializeParams = opts.initializeParams ?? {};
|
|
41
|
+
this.clientFactory =
|
|
42
|
+
opts.clientFactory ??
|
|
43
|
+
((ctx) => new JsonRpcClient(ctx.transport, {
|
|
44
|
+
onNotification: () => { },
|
|
45
|
+
onServerRequest: () => { },
|
|
46
|
+
onClose: ctx.baseOnClose,
|
|
47
|
+
}, ctx.requestTimeoutMs, this.initializeParams));
|
|
48
|
+
this.shouldReuse = opts.shouldReuse ?? (({ client }) => client.ready && client.healthy);
|
|
49
|
+
this.logTag = opts.logTag ?? 'jsonrpc-connection-manager';
|
|
28
50
|
}
|
|
29
51
|
/**
|
|
30
52
|
* Acquire a connection for the given workspace.
|
|
31
|
-
* Creates a new connection if one does not exist or the previous one was lost
|
|
53
|
+
* Creates a new connection if one does not exist or the previous one was lost
|
|
54
|
+
* (or, for session-bound protocols, bound to a different session).
|
|
32
55
|
* Serializes creation so concurrent calls share the same connection.
|
|
33
56
|
*/
|
|
34
|
-
async acquire(workspace) {
|
|
57
|
+
async acquire(workspace, req = {}) {
|
|
35
58
|
const existing = this.slots.get(workspace);
|
|
36
59
|
if (existing) {
|
|
37
60
|
// If a create is in flight, wait for it
|
|
38
61
|
if (existing.createPromise) {
|
|
39
62
|
return existing.createPromise;
|
|
40
63
|
}
|
|
41
|
-
//
|
|
42
|
-
|
|
64
|
+
// Reuse when the (session-aware) reuse predicate holds — e.g. the client
|
|
65
|
+
// is ready and its transport is alive (default), or it is bound to the
|
|
66
|
+
// requested session (pi). Otherwise replace it so the next run respawns
|
|
67
|
+
// the process.
|
|
68
|
+
if (this.shouldReuse({
|
|
69
|
+
client: existing.client,
|
|
70
|
+
req,
|
|
71
|
+
boundSessionId: existing.boundSessionId,
|
|
72
|
+
})) {
|
|
43
73
|
return existing.client;
|
|
44
74
|
}
|
|
45
|
-
// Client is disposed, dead (e.g. app-server process killed externally),
|
|
46
|
-
// or not ready — replace it so the next run respawns the process.
|
|
47
75
|
this.disposeSlot(workspace);
|
|
48
76
|
}
|
|
49
|
-
const createPromise = this.createClient(workspace);
|
|
77
|
+
const createPromise = this.createClient(workspace, req);
|
|
50
78
|
this.slots.set(workspace, {
|
|
51
79
|
client: null, // placeholder
|
|
80
|
+
boundSessionId: req.sessionId,
|
|
52
81
|
idleTimer: null,
|
|
53
|
-
createPromise
|
|
82
|
+
createPromise,
|
|
54
83
|
});
|
|
55
84
|
try {
|
|
56
85
|
const client = await createPromise;
|
|
57
|
-
//
|
|
86
|
+
// 竞态:创建期间 release()/disposeAll() 已把 slot 删掉(并可能已
|
|
58
87
|
// dispose 连接)。此时不能再把新连接塞回 slot——否则会复活一个无人管理、
|
|
59
88
|
// 无 idle timer 的连接泄漏。归还给调用者继续使用,但不再缓存。
|
|
60
89
|
const slotAfterCreate = this.slots.get(workspace);
|
|
@@ -63,6 +92,7 @@ export class ConnectionManager {
|
|
|
63
92
|
}
|
|
64
93
|
this.slots.set(workspace, {
|
|
65
94
|
client,
|
|
95
|
+
boundSessionId: req.sessionId,
|
|
66
96
|
idleTimer: null,
|
|
67
97
|
createPromise: null,
|
|
68
98
|
});
|
|
@@ -78,6 +108,16 @@ export class ConnectionManager {
|
|
|
78
108
|
throw err;
|
|
79
109
|
}
|
|
80
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Record the session id a fresh connection actually bound to (e.g. discovered
|
|
113
|
+
* via a protocol `get_state` after a fresh spawn). This lets a subsequent run
|
|
114
|
+
* resume the SAME session on the live connection instead of respawning it.
|
|
115
|
+
*/
|
|
116
|
+
bindSession(workspace, sessionId) {
|
|
117
|
+
const slot = this.slots.get(workspace);
|
|
118
|
+
if (slot)
|
|
119
|
+
slot.boundSessionId = sessionId;
|
|
120
|
+
}
|
|
81
121
|
/**
|
|
82
122
|
* Release (dispose) a connection for the given workspace.
|
|
83
123
|
*/
|
|
@@ -108,7 +148,7 @@ export class ConnectionManager {
|
|
|
108
148
|
if (slot) {
|
|
109
149
|
this.clearIdleTimer(slot);
|
|
110
150
|
slot.idleTimer = setTimeout(() => {
|
|
111
|
-
getLogger().info(`[
|
|
151
|
+
getLogger().info(`[${this.logTag}] idle timeout for workspace=${workspace}, releasing connection`);
|
|
112
152
|
this.disposeSlot(workspace);
|
|
113
153
|
}, this.idleTtlMs);
|
|
114
154
|
}
|
|
@@ -116,23 +156,19 @@ export class ConnectionManager {
|
|
|
116
156
|
// =========================================================================
|
|
117
157
|
// Internal
|
|
118
158
|
// =========================================================================
|
|
119
|
-
async createClient(workspace) {
|
|
159
|
+
async createClient(workspace, req) {
|
|
120
160
|
const transport = new JsonlRpcTransport({
|
|
121
161
|
binary: this.binary,
|
|
122
|
-
args: this.
|
|
162
|
+
args: this.buildArgs(req),
|
|
123
163
|
cwd: workspace,
|
|
124
164
|
env: this.env,
|
|
125
165
|
});
|
|
126
166
|
let client = null;
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
// Server requests are handled by the runner via translator
|
|
133
|
-
},
|
|
134
|
-
onClose: () => {
|
|
135
|
-
getLogger().info(`[codex-connection-manager] connection closed workspace=${workspace}`);
|
|
167
|
+
client = this.clientFactory({
|
|
168
|
+
transport,
|
|
169
|
+
requestTimeoutMs: this.requestTimeoutMs,
|
|
170
|
+
baseOnClose: () => {
|
|
171
|
+
getLogger().info(`[${this.logTag}] connection closed workspace=${workspace}`);
|
|
136
172
|
// 只清理自己创建的 slot:并发 release + 重新 acquire 后 slot 可能已被
|
|
137
173
|
// 新连接占据,误删会破坏新连接。
|
|
138
174
|
if (this.slots.get(workspace)?.client === client) {
|
|
@@ -140,8 +176,7 @@ export class ConnectionManager {
|
|
|
140
176
|
this.onConnectionLost?.(workspace);
|
|
141
177
|
}
|
|
142
178
|
},
|
|
143
|
-
};
|
|
144
|
-
client = new CodexAppServerClient(transport, hooks, this.requestTimeoutMs);
|
|
179
|
+
});
|
|
145
180
|
await client.connect();
|
|
146
181
|
return client;
|
|
147
182
|
}
|
|
@@ -162,7 +197,7 @@ export class ConnectionManager {
|
|
|
162
197
|
await slot.client.dispose();
|
|
163
198
|
}
|
|
164
199
|
catch (err) {
|
|
165
|
-
getLogger().warn(`[
|
|
200
|
+
getLogger().warn(`[${this.logTag}] dispose error workspace=${workspace}: ${err}`);
|
|
166
201
|
}
|
|
167
202
|
}
|
|
168
203
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JSONL-RPC transport
|
|
2
|
+
* JSONL-RPC transport shared by JSON-line-RPC agent servers
|
|
3
|
+
* (codex app-server, kimi acp, and future ACP-style integrations).
|
|
3
4
|
*
|
|
4
|
-
* Wraps a child process
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Wraps a child process with JSON-line-based bidirectional communication:
|
|
6
|
+
* each line on stdout is a JSON message from the server; each `write()`
|
|
7
|
+
* sends a JSON line to stdin.
|
|
7
8
|
*/
|
|
8
9
|
export interface TransportEvents {
|
|
9
10
|
onMessage(msg: unknown): void;
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JSONL-RPC transport
|
|
2
|
+
* JSONL-RPC transport shared by JSON-line-RPC agent servers
|
|
3
|
+
* (codex app-server, kimi acp, and future ACP-style integrations).
|
|
3
4
|
*
|
|
4
|
-
* Wraps a child process
|
|
5
|
-
*
|
|
6
|
-
*
|
|
5
|
+
* Wraps a child process with JSON-line-based bidirectional communication:
|
|
6
|
+
* each line on stdout is a JSON message from the server; each `write()`
|
|
7
|
+
* sends a JSON line to stdin.
|
|
7
8
|
*/
|
|
8
9
|
import { spawn } from 'node:child_process';
|
|
9
|
-
import { ProcessStopper } from '
|
|
10
|
+
import { ProcessStopper } from '../process-stopper.js';
|
|
10
11
|
import { getLogger } from '../../../logger/index.js';
|
|
11
12
|
/** Max bytes for a single line before we disconnect (10MB). */
|
|
12
13
|
const MAX_LINE_BYTES = 10 * 1024 * 1024;
|
|
@@ -40,11 +41,9 @@ export class JsonlRpcTransport {
|
|
|
40
41
|
*/
|
|
41
42
|
async start(events) {
|
|
42
43
|
this.events = events;
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
// providerEnvKeys),代理环境依赖 HTTP(S)_PROXY、TMPDIR 等;任何白名单
|
|
47
|
-
// 收窄都会打断认证或网络。调用方 this.env 覆盖 process.env 同名键。
|
|
44
|
+
// agent 是用户自己的可信二进制,provider 认证依赖 API key /
|
|
45
|
+
// 自定义 provider 的 env_key,代理环境依赖 HTTP(S)_PROXY、TMPDIR 等;
|
|
46
|
+
// 任何白名单收窄都会打断认证或网络。调用方 this.env 覆盖 process.env 同名键。
|
|
48
47
|
const childEnv = { ...process.env, ...this.env };
|
|
49
48
|
const proc = spawn(this.binary, this.args, {
|
|
50
49
|
cwd: this.cwd,
|
|
@@ -64,9 +63,9 @@ export class JsonlRpcTransport {
|
|
|
64
63
|
events.onClose(reason);
|
|
65
64
|
return;
|
|
66
65
|
}
|
|
67
|
-
getLogger().info(`[
|
|
66
|
+
getLogger().info(`[jsonrpc-transport] spawned pid=${proc.pid} binary=${this.binary}`);
|
|
68
67
|
proc.stdin?.on('error', (err) => {
|
|
69
|
-
getLogger().warn(`[
|
|
68
|
+
getLogger().warn(`[jsonrpc-transport] stdin error: ${err.message}`);
|
|
70
69
|
this.handleClose('epipe');
|
|
71
70
|
});
|
|
72
71
|
// Stderr: rolling buffer, last 64KB retained, only log
|
|
@@ -74,7 +73,7 @@ export class JsonlRpcTransport {
|
|
|
74
73
|
proc.stderr?.on('data', (chunk) => {
|
|
75
74
|
const text = chunk.toString('utf-8');
|
|
76
75
|
stderrBuf = (stderrBuf + text).slice(-MAX_STDERR_BYTES);
|
|
77
|
-
getLogger().debug(`[
|
|
76
|
+
getLogger().debug(`[jsonrpc-transport] stderr: ${text.trimEnd()}`);
|
|
78
77
|
});
|
|
79
78
|
// Stdout: line-split with 10MB line limit
|
|
80
79
|
let remainder = '';
|
|
@@ -87,7 +86,7 @@ export class JsonlRpcTransport {
|
|
|
87
86
|
if (line.length === 0)
|
|
88
87
|
continue;
|
|
89
88
|
if (line.length > MAX_LINE_BYTES) {
|
|
90
|
-
getLogger().error(`[
|
|
89
|
+
getLogger().error(`[jsonrpc-transport] line exceeds ${MAX_LINE_BYTES} bytes, disconnecting`);
|
|
91
90
|
this.handleClose('parse_error');
|
|
92
91
|
return;
|
|
93
92
|
}
|
|
@@ -96,7 +95,7 @@ export class JsonlRpcTransport {
|
|
|
96
95
|
events.onMessage(msg);
|
|
97
96
|
}
|
|
98
97
|
catch (err) {
|
|
99
|
-
getLogger().warn(`[
|
|
98
|
+
getLogger().warn(`[jsonrpc-transport] failed to parse JSON: ${err.message} line=${line.slice(0, 200)}`);
|
|
100
99
|
}
|
|
101
100
|
}
|
|
102
101
|
});
|
|
@@ -116,11 +115,11 @@ export class JsonlRpcTransport {
|
|
|
116
115
|
}
|
|
117
116
|
const reason = signal ? `signal:${signal}` : `exit:${code}`;
|
|
118
117
|
events.onClose(reason);
|
|
119
|
-
getLogger().info(`[
|
|
118
|
+
getLogger().info(`[jsonrpc-transport] process exited pid=${proc.pid} code=${code} signal=${signal}`);
|
|
120
119
|
};
|
|
121
120
|
proc.once('exit', onExit);
|
|
122
121
|
proc.once('error', (err) => {
|
|
123
|
-
getLogger().error(`[
|
|
122
|
+
getLogger().error(`[jsonrpc-transport] process error: ${err.message}`);
|
|
124
123
|
this.handleClose(`error:${err.message}`);
|
|
125
124
|
});
|
|
126
125
|
}
|
|
@@ -219,13 +218,13 @@ export class JsonlRpcTransport {
|
|
|
219
218
|
// 异常路径(EPIPE/超长行/进程 error)也要收掉子进程,避免孤儿进程
|
|
220
219
|
// 继续运行(如超长行场景子进程还在往 stdout 灌数据)。
|
|
221
220
|
void this.stopper.stop(proc).catch((err) => {
|
|
222
|
-
getLogger().warn(`[
|
|
221
|
+
getLogger().warn(`[jsonrpc-transport] failed to stop process during close: ${err instanceof Error ? err.message : String(err)}`);
|
|
223
222
|
});
|
|
224
223
|
}
|
|
225
224
|
// 统一关闭出口:EPIPE/超长行/进程 error 都必须通知上层(connection-manager
|
|
226
225
|
// 靠 onClose 删除 slot、client 靠它 failPending),否则 slot 悬挂死连接。
|
|
227
226
|
this.events?.onClose(reason);
|
|
228
|
-
getLogger().info(`[
|
|
227
|
+
getLogger().info(`[jsonrpc-transport] closed: ${reason}`);
|
|
229
228
|
}
|
|
230
229
|
}
|
|
231
230
|
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC 2.0 base types shared by all JSON-line-RPC agent protocols
|
|
3
|
+
* (codex app-server, kimi acp, and future ACP-style integrations).
|
|
4
|
+
*
|
|
5
|
+
* Protocol-specific method/params types live in each protocol's own
|
|
6
|
+
* protocol-types.ts; only the wire envelope lives here.
|
|
7
|
+
*/
|
|
8
|
+
export interface JsonRpcRequest {
|
|
9
|
+
jsonrpc: '2.0';
|
|
10
|
+
id: number | string;
|
|
11
|
+
method: string;
|
|
12
|
+
params?: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface JsonRpcNotification {
|
|
15
|
+
jsonrpc: '2.0';
|
|
16
|
+
method: string;
|
|
17
|
+
params?: unknown;
|
|
18
|
+
}
|
|
19
|
+
export interface JsonRpcSuccessResponse {
|
|
20
|
+
jsonrpc: '2.0';
|
|
21
|
+
id: number | string;
|
|
22
|
+
result: unknown;
|
|
23
|
+
}
|
|
24
|
+
export interface JsonRpcErrorResponse {
|
|
25
|
+
jsonrpc: '2.0';
|
|
26
|
+
id: number | string | null;
|
|
27
|
+
error: {
|
|
28
|
+
code: number;
|
|
29
|
+
message: string;
|
|
30
|
+
data?: unknown;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON-RPC 2.0 base types shared by all JSON-line-RPC agent protocols
|
|
3
|
+
* (codex app-server, kimi acp, and future ACP-style integrations).
|
|
4
|
+
*
|
|
5
|
+
* Protocol-specific method/params types live in each protocol's own
|
|
6
|
+
* protocol-types.ts; only the wire envelope lives here.
|
|
7
|
+
*/
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -23,6 +23,7 @@ export declare class ProcessStopper {
|
|
|
23
23
|
* Pass `immediate: true` to skip the grace window — SIGTERM and SIGKILL
|
|
24
24
|
* are sent back-to-back. Safe to call on an already-exited process.
|
|
25
25
|
*/
|
|
26
|
-
|
|
26
|
+
/** proc 可为 null(调用方已清理 currentProcess);nullish 直接返回。 */
|
|
27
|
+
stop(proc: ChildProcess | null, opts?: StopOptions): Promise<void>;
|
|
27
28
|
}
|
|
28
29
|
export {};
|
|
@@ -19,6 +19,7 @@ export class ProcessStopper {
|
|
|
19
19
|
* Pass `immediate: true` to skip the grace window — SIGTERM and SIGKILL
|
|
20
20
|
* are sent back-to-back. Safe to call on an already-exited process.
|
|
21
21
|
*/
|
|
22
|
+
/** proc 可为 null(调用方已清理 currentProcess);nullish 直接返回。 */
|
|
22
23
|
async stop(proc, opts) {
|
|
23
24
|
if (!proc || proc.exitCode !== null || proc.signalCode !== null) {
|
|
24
25
|
return;
|
|
@@ -1,21 +1,14 @@
|
|
|
1
|
-
import type { ChildProcess } from 'node:child_process';
|
|
2
1
|
import type { AgentEvent } from '../types.js';
|
|
3
2
|
/**
|
|
4
|
-
* Return the stdio config ['pipe','pipe','pipe'] used by
|
|
5
|
-
*
|
|
3
|
+
* Return the stdio config ['pipe','pipe','pipe'] used by the runner that
|
|
4
|
+
* writes the prompt to the child's stdin (claude).
|
|
6
5
|
*/
|
|
7
6
|
export declare function pipeAllStdio(): ('ignore' | 'pipe')[];
|
|
8
|
-
/**
|
|
9
|
-
* Write the prompt message to the child's stdin and close it.
|
|
10
|
-
* Used by codex/opencode runners which pass the prompt via stdin
|
|
11
|
-
* rather than argv.
|
|
12
|
-
*/
|
|
13
|
-
export declare function endStdinWithPrompt(proc: ChildProcess, message: string): void;
|
|
14
7
|
/**
|
|
15
8
|
* Build a synthetic system.init event for pre-spawn failure paths.
|
|
16
9
|
*
|
|
17
|
-
* When the runner exits before spawning a process (
|
|
18
|
-
*
|
|
10
|
+
* When the runner exits before spawning a process (e.g. ENOENT spawn failure),
|
|
11
|
+
* no real system.init arrives from stdout. Without a
|
|
19
12
|
* synthetic init, the bridge's pre-init result guard (§9.22) and the run-state
|
|
20
13
|
* reducer's sessionId check would silently drop the error result, leaving the
|
|
21
14
|
* card showing "输出流已结束,但未收到 result 事件" instead of the actual
|
|
@@ -26,8 +19,8 @@ export declare function syntheticInitEvent(sessionId?: string): AgentEvent;
|
|
|
26
19
|
/**
|
|
27
20
|
* Build a standardized auth/error result event for yield.
|
|
28
21
|
*
|
|
29
|
-
* Used
|
|
30
|
-
*
|
|
22
|
+
* Used when initialization or request sending fails (e.g. not logged in,
|
|
23
|
+
* session creation failed). Produces a
|
|
31
24
|
* `result`/`error` event so the bridge can render a card instead of crashing.
|
|
32
25
|
*/
|
|
33
26
|
export declare function authErrorEvent(errorMessage: string, sessionId?: string): AgentEvent;
|
|
@@ -1,56 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Return the stdio config ['pipe','pipe','pipe'] used by
|
|
3
|
-
*
|
|
2
|
+
* Return the stdio config ['pipe','pipe','pipe'] used by the runner that
|
|
3
|
+
* writes the prompt to the child's stdin (claude).
|
|
4
4
|
*/
|
|
5
5
|
export function pipeAllStdio() {
|
|
6
6
|
return ['pipe', 'pipe', 'pipe'];
|
|
7
7
|
}
|
|
8
|
-
/**
|
|
9
|
-
* Write the prompt message to the child's stdin and close it.
|
|
10
|
-
* Used by codex/opencode runners which pass the prompt via stdin
|
|
11
|
-
* rather than argv.
|
|
12
|
-
*/
|
|
13
|
-
export function endStdinWithPrompt(proc, message) {
|
|
14
|
-
const stdin = proc.stdin;
|
|
15
|
-
if (!stdin)
|
|
16
|
-
return;
|
|
17
|
-
// Guard against async EPIPE: proc.stdin.end() may emit an 'error' event
|
|
18
|
-
// asynchronously after the call returns (the child may have already exited),
|
|
19
|
-
// which the synchronous try/catch below cannot catch.
|
|
20
|
-
// Check for .once availability — in test environments stdin may be a mock
|
|
21
|
-
// object that lacks EventEmitter methods.
|
|
22
|
-
if (typeof stdin.once === 'function') {
|
|
23
|
-
stdin.once('error', (err) => {
|
|
24
|
-
if (err.code === 'EPIPE') {
|
|
25
|
-
// Child exited before we could write — not an error, just a race.
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
// Re-throw unexpected errors so they surface as unhandled-exception
|
|
29
|
-
// rather than being silently swallowed by the EventEmitter default handler.
|
|
30
|
-
throw err;
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
try {
|
|
35
|
-
stdin.end(message, 'utf-8');
|
|
36
|
-
}
|
|
37
|
-
catch (err) {
|
|
38
|
-
if (err &&
|
|
39
|
-
typeof err === 'object' &&
|
|
40
|
-
'code' in err &&
|
|
41
|
-
err.code === 'EPIPE') {
|
|
42
|
-
// Child exited before we could write — not an error, just a race.
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
45
|
-
throw err;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
8
|
/**
|
|
50
9
|
* Build a synthetic system.init event for pre-spawn failure paths.
|
|
51
10
|
*
|
|
52
|
-
* When the runner exits before spawning a process (
|
|
53
|
-
*
|
|
11
|
+
* When the runner exits before spawning a process (e.g. ENOENT spawn failure),
|
|
12
|
+
* no real system.init arrives from stdout. Without a
|
|
54
13
|
* synthetic init, the bridge's pre-init result guard (§9.22) and the run-state
|
|
55
14
|
* reducer's sessionId check would silently drop the error result, leaving the
|
|
56
15
|
* card showing "输出流已结束,但未收到 result 事件" instead of the actual
|
|
@@ -70,8 +29,8 @@ export function syntheticInitEvent(sessionId = '') {
|
|
|
70
29
|
/**
|
|
71
30
|
* Build a standardized auth/error result event for yield.
|
|
72
31
|
*
|
|
73
|
-
* Used
|
|
74
|
-
*
|
|
32
|
+
* Used when initialization or request sending fails (e.g. not logged in,
|
|
33
|
+
* session creation failed). Produces a
|
|
75
34
|
* `result`/`error` event so the bridge can render a card instead of crashing.
|
|
76
35
|
*/
|
|
77
36
|
export function authErrorEvent(errorMessage, sessionId = '') {
|
|
@@ -37,8 +37,8 @@ export declare function unregisterExitCleanup(handler: ExitCleanupHandler): void
|
|
|
37
37
|
*
|
|
38
38
|
* `translate()` returns `AgentEvent | AgentEvent[] | null` (single event,
|
|
39
39
|
* multiple events, or filtered-out). `isTerminal()` / `finish()` / `getTerminalError()`
|
|
40
|
-
* / `hasAgentTerminalError()` are folded into the unified result event
|
|
41
|
-
* `
|
|
40
|
+
* / `hasAgentTerminalError()` are folded into the unified result event at the
|
|
41
|
+
* end of `run()`.
|
|
42
42
|
*
|
|
43
43
|
* `getSessionId()` / `getLastUsage()` are optional because ClaudeRunner uses
|
|
44
44
|
* the stateless `translate()` hook (no translator object) and reads neither.
|
|
@@ -56,26 +56,21 @@ export interface RunnerTranslator {
|
|
|
56
56
|
* Abstract base class for runners that spawn a child process per turn.
|
|
57
57
|
*
|
|
58
58
|
* Encapsulates the spawn + completion + cleanup orchestration shared by
|
|
59
|
-
* Claude/
|
|
59
|
+
* Claude/Pi runners. Subclasses override:
|
|
60
60
|
* - buildArgv(opts) → agent-specific CLI flags (required)
|
|
61
|
-
* - createTranslator(opts) → stateful translator (
|
|
61
|
+
* - createTranslator(opts) → stateful translator (pi)
|
|
62
62
|
* OR translate(raw, ctx) → stateless passthrough (claude)
|
|
63
63
|
*
|
|
64
64
|
* Optional hooks (with sensible defaults):
|
|
65
65
|
* - getStdio() → stdio config (default ['ignore','pipe','pipe'])
|
|
66
|
-
* - getSpawnEnv(opts) → env vars (default process.env)
|
|
67
|
-
* - setupStdin(proc, message) → write prompt to stdin (default no-op)
|
|
68
66
|
* - createStreamReader(stdout) → JSONL or readline parser (default createJSONLStream)
|
|
69
|
-
* -
|
|
70
|
-
* - awaitCompletion(proc, p) → wait for exit (default return p; kimi adds 5s race)
|
|
71
|
-
* - awaitSpawnError(proc) → wait for spawn error (default once('error'); kimi adds 5s race)
|
|
72
|
-
* - resolveTranslatorError(t, c) → translatorError precedence (default honors agent errors)
|
|
67
|
+
* - awaitSpawnError(proc) → wait for spawn error (default once('error'))
|
|
73
68
|
*
|
|
74
69
|
* The base class always emits a unified result event at the end of `run()`
|
|
75
70
|
* via `buildResultEvent(...)` — never throws on non-zero exit. This matches
|
|
76
|
-
* the contract pinned by
|
|
77
|
-
*
|
|
78
|
-
*
|
|
71
|
+
* the contract pinned by the agent subclass anchors and the bridge's
|
|
72
|
+
* `runAgentStreamToEnd` error handling (which accepts both thrown errors and
|
|
73
|
+
* yielded error-result events).
|
|
79
74
|
*/
|
|
80
75
|
export declare abstract class SpawningRunner {
|
|
81
76
|
protected currentProcess: ChildProcess | null;
|
|
@@ -84,6 +79,8 @@ export declare abstract class SpawningRunner {
|
|
|
84
79
|
protected readonly spawnHeartbeat: SpawnHeartbeat;
|
|
85
80
|
protected binary: string;
|
|
86
81
|
protected stopGraceMs: number;
|
|
82
|
+
/** Accumulated stderr tail for the current run (filled by spawnChild). */
|
|
83
|
+
protected spawnStderr: string;
|
|
87
84
|
/**
|
|
88
85
|
* Log prefix used in all operational log lines emitted by this runner
|
|
89
86
|
* (spawn, pid file, non-zero exit, stderr, killOrphan, stop cleanup) and
|
|
@@ -131,58 +128,19 @@ export declare abstract class SpawningRunner {
|
|
|
131
128
|
get isRunning(): boolean;
|
|
132
129
|
/**
|
|
133
130
|
* stdio config for spawn. Default: stdin ignored (claude/kimi/pi pass the
|
|
134
|
-
* prompt via argv).
|
|
135
|
-
* they can write the prompt to stdin via `setupStdin`.
|
|
131
|
+
* prompt via argv).
|
|
136
132
|
*/
|
|
137
133
|
protected getStdio(): ('ignore' | 'pipe')[];
|
|
138
|
-
/**
|
|
139
|
-
* Env vars for spawn. Default: process.env. opencode overrides to sync
|
|
140
|
-
* PWD=opts.cwd (opencode's directory detection reads PWD, not cwd).
|
|
141
|
-
*/
|
|
142
|
-
protected getSpawnEnv(_opts: SpawnOptions): NodeJS.ProcessEnv;
|
|
143
|
-
/**
|
|
144
|
-
* Write the prompt to the child's stdin (called after spawn succeeds).
|
|
145
|
-
* Default: no-op. codex/opencode override to `proc.stdin.end(message)`.
|
|
146
|
-
*/
|
|
147
|
-
protected setupStdin(_proc: ChildProcess, _message: string): void;
|
|
148
134
|
/**
|
|
149
135
|
* Create an async iterator over the child's stdout. Default: createJSONLStream
|
|
150
136
|
* (claude/kimi/pi) with P1-4 backpressure enabled (pauseThreshold=100).
|
|
151
|
-
* Subclasses that need to silently skip non-JSON lines should override
|
|
152
|
-
* `handleBadStreamLine` instead of this method.
|
|
153
137
|
*/
|
|
154
138
|
protected createStreamReader(stdout: Readable): AsyncGenerator<unknown>;
|
|
155
|
-
/**
|
|
156
|
-
* Hook called when createJSONLStream encounters a line that cannot be parsed
|
|
157
|
-
* as JSON. Default: log a warning via getLogger().warn (same format as
|
|
158
|
-
* jsonl-stream's built-in warning). Subclasses override to change behavior
|
|
159
|
-
* (e.g. codex/opencode silently skip by providing an empty override).
|
|
160
|
-
*/
|
|
161
|
-
protected handleBadStreamLine(line: string): void;
|
|
162
139
|
/**
|
|
163
140
|
* Create a stateful translator for this run. Default: null (uses the
|
|
164
|
-
* stateless
|
|
165
|
-
* return their translator instance.
|
|
141
|
+
* stateless passthrough). pi overrides to return its translator instance.
|
|
166
142
|
*/
|
|
167
143
|
protected createTranslator(_opts: SpawnOptions): RunnerTranslator | null;
|
|
168
|
-
/**
|
|
169
|
-
* Pre-spawn validation. Return an AgentEvent to yield it and abort run()
|
|
170
|
-
* (e.g. codex's API key check yields an error result and returns).
|
|
171
|
-
* Default: null (no validation).
|
|
172
|
-
*/
|
|
173
|
-
protected validateBeforeRun(_opts: SpawnOptions): AgentEvent | null;
|
|
174
|
-
/**
|
|
175
|
-
* Await process completion. Default: return the completion promise as-is.
|
|
176
|
-
* kimi overrides to add a 5s timeout race + SIGKILL fallback (kimi
|
|
177
|
-
* sometimes hangs after stdout closes).
|
|
178
|
-
*/
|
|
179
|
-
protected awaitCompletion(_proc: ChildProcess, completion: Promise<{
|
|
180
|
-
code: number | null;
|
|
181
|
-
signal: NodeJS.Signals | null;
|
|
182
|
-
}>): Promise<{
|
|
183
|
-
code: number | null;
|
|
184
|
-
signal: NodeJS.Signals | null;
|
|
185
|
-
}>;
|
|
186
144
|
/**
|
|
187
145
|
* Await the spawn 'error' event when proc.pid === undefined. Races a
|
|
188
146
|
* finite timeout (review P2-11) against the 'error' event: Node guarantees
|
|
@@ -194,24 +152,20 @@ export declare abstract class SpawningRunner {
|
|
|
194
152
|
*/
|
|
195
153
|
protected awaitSpawnError(proc: ChildProcess): Promise<Error | undefined>;
|
|
196
154
|
/**
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
* (signal / non-zero code / user stop). The signal/code is the root
|
|
205
|
-
* cause; the early stream-end is a symptom.
|
|
206
|
-
* - otherwise: undefined (signal/code/stoppedByUser drives the message).
|
|
155
|
+
* Spawn the agent binary with the subclass's argv and run the shared spawn
|
|
156
|
+
* lead-in: pid-undefined check, pid-file write, heartbeat start, and stderr
|
|
157
|
+
* accumulation (tail-capped).
|
|
158
|
+
*
|
|
159
|
+
* Throws a `SpawnChildError` (carrying the user-facing spawn failure
|
|
160
|
+
* message) when the process fails to spawn (binary missing / pid undefined).
|
|
161
|
+
* On success returns the spawned child plus the accumulated stderr tail.
|
|
207
162
|
*
|
|
208
|
-
*
|
|
209
|
-
*
|
|
163
|
+
* Shared by the base `run()` (yields an authErrorEvent on failure) and
|
|
164
|
+
* ClaudeSession's `doStartProcess` (returns the error message), which would
|
|
165
|
+
* otherwise duplicate ~60 lines of spawn lead-in with two copies of the
|
|
166
|
+
* STDERR_TAIL_BYTES cap.
|
|
210
167
|
*/
|
|
211
|
-
protected
|
|
212
|
-
code: number | null;
|
|
213
|
-
signal: NodeJS.Signals | null;
|
|
214
|
-
}): string | undefined;
|
|
168
|
+
protected spawnChild(opts: SpawnOptions): Promise<ChildProcess>;
|
|
215
169
|
/**
|
|
216
170
|
* Spawn the agent binary with argv built by buildArgv(opts), iterate its
|
|
217
171
|
* stdout stream, and yield translated events. Always emits a unified
|