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
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import { ConnectionLostError } from './jsonrpc/client.js';
|
|
2
|
+
import { getLogger } from '../../logger/index.js';
|
|
3
|
+
import { syntheticInitEvent } from './runner-utils.js';
|
|
4
|
+
/** How long to wait for turn output notifications before failing. */
|
|
5
|
+
export const TURN_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
6
|
+
export class ConnectionBasedRunner {
|
|
7
|
+
kind;
|
|
8
|
+
sessionReader;
|
|
9
|
+
lifetime = 'workspace';
|
|
10
|
+
currentClient = null;
|
|
11
|
+
currentTurnId = null;
|
|
12
|
+
lastEventAt = 0;
|
|
13
|
+
_isRunning = false;
|
|
14
|
+
turnIdleTimeoutMs;
|
|
15
|
+
/** Notification queue consumed by the active run generator. */
|
|
16
|
+
notificationQueue = [];
|
|
17
|
+
waitResolve = null;
|
|
18
|
+
forceFinish = false;
|
|
19
|
+
/** stop() 在 turn/start 或 prompt 响应前到达时置位,完成后再补发 cancel/interrupt。 */
|
|
20
|
+
stopRequested = false;
|
|
21
|
+
constructor(opts, defaultTurnIdleTimeoutMs = TURN_IDLE_TIMEOUT_MS) {
|
|
22
|
+
this.kind = opts.kind;
|
|
23
|
+
this.sessionReader = opts.sessionReader;
|
|
24
|
+
this.turnIdleTimeoutMs = opts.turnIdleTimeoutMs ?? defaultTurnIdleTimeoutMs;
|
|
25
|
+
}
|
|
26
|
+
get isRunning() {
|
|
27
|
+
return this._isRunning;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Run a message. Delegates to executeTurn() with the standard setup.
|
|
31
|
+
*/
|
|
32
|
+
async *run(message, opts) {
|
|
33
|
+
yield* this.executeTurn(opts, () => this.setupTurn(message, opts));
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Shared turn lifecycle used by run() and runCompact(): isRunning guard →
|
|
37
|
+
* resetRunState → setupFn (with connection-lost respawn-and-retry-once) →
|
|
38
|
+
* deferred stop → synthetic init → consumeTurn; catch yields synthetic init
|
|
39
|
+
* + error result; finally resets flags + notifyIdle.
|
|
40
|
+
*
|
|
41
|
+
* `setupFn` performs the turn-specific setup (acquire, session new/resume,
|
|
42
|
+
* prompt, etc.) and may push events into the notification queue.
|
|
43
|
+
*/
|
|
44
|
+
async *executeTurn(opts, setupFn) {
|
|
45
|
+
if (this._isRunning) {
|
|
46
|
+
throw new Error(`${this.constructor.name} is already running`);
|
|
47
|
+
}
|
|
48
|
+
this._isRunning = true;
|
|
49
|
+
this.resetRunState();
|
|
50
|
+
try {
|
|
51
|
+
try {
|
|
52
|
+
await setupFn();
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
// 连接在 setup 期间丢失(进程被误杀/异常退出):重拉进程并重试一次。
|
|
56
|
+
if (err instanceof ConnectionLostError && !this.stopRequested) {
|
|
57
|
+
getLogger().warn(`[${this.logTag}] connection lost during turn setup (${err.message}), respawning and retrying once`);
|
|
58
|
+
await this.respawnAfterConnectionLost(opts);
|
|
59
|
+
await setupFn();
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
throw err;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (this.stopRequested) {
|
|
66
|
+
// 启动期 /stop:turn/start 或 prompt 尚未完成,补发 cancel/interrupt,
|
|
67
|
+
// 避免 server 端 turn 无人接管继续执行。
|
|
68
|
+
getLogger().warn(`[${this.logTag}] stop requested before turn resolved`);
|
|
69
|
+
try {
|
|
70
|
+
await this.cancelCurrentTurn();
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
getLogger().warn(`[${this.logTag}] deferred cancel failed: ${err.message}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// §9.22 守卫前提:桥的 pre-init result guard 和 run-state reducer 都以
|
|
77
|
+
// system.init 作为「本轮真实开始」的标记,而这些协议没有 init 事件。
|
|
78
|
+
// 缺了它,成功的 result 会被当作 pre-init 丢弃,卡片终态停在 running。
|
|
79
|
+
yield syntheticInitEvent(this.currentSessionId() ?? opts.sessionId ?? '');
|
|
80
|
+
yield* this.consumeTurn();
|
|
81
|
+
}
|
|
82
|
+
catch (err) {
|
|
83
|
+
getLogger().error(`[${this.logTag}] run error: ${err.message}`);
|
|
84
|
+
yield syntheticInitEvent(this.currentSessionId() ?? opts.sessionId ?? '');
|
|
85
|
+
yield {
|
|
86
|
+
type: 'result',
|
|
87
|
+
subtype: 'error',
|
|
88
|
+
// 优先上报本次 setup 创建/恢复的 session/thread id。
|
|
89
|
+
session_id: this.currentSessionId() ?? opts.sessionId ?? '',
|
|
90
|
+
errorMessage: err.message,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
this._isRunning = false;
|
|
95
|
+
this.currentTurnId = null;
|
|
96
|
+
this.stopRequested = false;
|
|
97
|
+
this.clearTurnState();
|
|
98
|
+
this.notifyIdle(opts.cwd);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Stop the current turn by cancelling it. Shared skeleton: if the turn/start
|
|
103
|
+
* or prompt has not yet resolved (shouldDeferStop), mark stopRequested and
|
|
104
|
+
* unblock; otherwise cancel now via cancelCurrentTurn().
|
|
105
|
+
*/
|
|
106
|
+
async stop(_opts) {
|
|
107
|
+
if (!this._isRunning || !this.currentClient || !this.currentSessionId())
|
|
108
|
+
return;
|
|
109
|
+
if (this.shouldDeferStop()) {
|
|
110
|
+
// turn/start 或 prompt 尚未响应:先标记,等 run() 完成后再补发 cancel。
|
|
111
|
+
this.stopRequested = true;
|
|
112
|
+
this.forceFinish = true;
|
|
113
|
+
this.wakeWaiters();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
// turn/start 或 prompt 已 resolved:立即 cancel;同时置 stopRequested 抑制
|
|
117
|
+
// 尚未结算的 prompt 结果翻译(opencode 语义),对已结算方无害。
|
|
118
|
+
this.stopRequested = true;
|
|
119
|
+
try {
|
|
120
|
+
await this.cancelCurrentTurn();
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
getLogger().warn(`[${this.logTag}] stop error: ${err.message}`);
|
|
124
|
+
}
|
|
125
|
+
// Unblock the turn loop regardless of whether the server sends a
|
|
126
|
+
// completed notification afterwards.
|
|
127
|
+
this.forceFinish = true;
|
|
128
|
+
this.wakeWaiters();
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Dispose the runner: release the connection.
|
|
132
|
+
*/
|
|
133
|
+
async dispose() {
|
|
134
|
+
try {
|
|
135
|
+
await this.disposeConnections();
|
|
136
|
+
}
|
|
137
|
+
catch (err) {
|
|
138
|
+
getLogger().warn(`[${this.logTag}] dispose error: ${err.message}`);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
getUsageAuthority() {
|
|
142
|
+
return 'live';
|
|
143
|
+
}
|
|
144
|
+
killOrphan() {
|
|
145
|
+
// No-op: the connection is managed by ConnectionManager.
|
|
146
|
+
}
|
|
147
|
+
registerExitHandlers() {
|
|
148
|
+
// No-op: connection manager handles cleanup on exit.
|
|
149
|
+
}
|
|
150
|
+
unregisterExitHandlers() {
|
|
151
|
+
// No-op.
|
|
152
|
+
}
|
|
153
|
+
// =========================================================================
|
|
154
|
+
// Shared turn machinery
|
|
155
|
+
// =========================================================================
|
|
156
|
+
/**
|
|
157
|
+
* Consume the notification queue until a result event arrives or the turn
|
|
158
|
+
* times out / is interrupted. forceFinish drains the queue first (the
|
|
159
|
+
* deferred cancel's own events may still be in it) before yielding the
|
|
160
|
+
* interrupted result.
|
|
161
|
+
*/
|
|
162
|
+
async *consumeTurn() {
|
|
163
|
+
while (true) {
|
|
164
|
+
const timedOut = await this.waitForEvents(this.nextTurnIdleDeadline());
|
|
165
|
+
if (timedOut) {
|
|
166
|
+
await this.cancelCurrentTurn();
|
|
167
|
+
getLogger().warn(`[${this.logTag}] turn wait timed out`);
|
|
168
|
+
yield {
|
|
169
|
+
type: 'result',
|
|
170
|
+
subtype: 'error',
|
|
171
|
+
session_id: this.currentSessionId() ?? '',
|
|
172
|
+
errorMessage: this.turnTimeoutErrorMessage,
|
|
173
|
+
};
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
if (this.forceFinish && this.notificationQueue.length === 0) {
|
|
177
|
+
yield {
|
|
178
|
+
type: 'result',
|
|
179
|
+
subtype: 'interrupted',
|
|
180
|
+
session_id: this.currentSessionId() ?? '',
|
|
181
|
+
errorMessage: this.turnInterruptedErrorMessage,
|
|
182
|
+
};
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
while (this.notificationQueue.length > 0) {
|
|
186
|
+
const ev = this.notificationQueue.shift();
|
|
187
|
+
yield ev;
|
|
188
|
+
if (ev.type === 'result') {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/** Compute the current idle deadline. 0 disables; otherwise rolling on last event. */
|
|
195
|
+
nextTurnIdleDeadline() {
|
|
196
|
+
if (this.turnIdleTimeoutMs <= 0)
|
|
197
|
+
return Number.POSITIVE_INFINITY;
|
|
198
|
+
return this.lastEventAt + this.turnIdleTimeoutMs;
|
|
199
|
+
}
|
|
200
|
+
async waitForEvents(deadline) {
|
|
201
|
+
while (this.notificationQueue.length === 0 && !this.forceFinish) {
|
|
202
|
+
const remaining = deadline - Date.now();
|
|
203
|
+
if (remaining <= 0)
|
|
204
|
+
return true;
|
|
205
|
+
await new Promise((resolve) => {
|
|
206
|
+
this.waitResolve = resolve;
|
|
207
|
+
setTimeout(() => {
|
|
208
|
+
if (this.waitResolve === resolve) {
|
|
209
|
+
this.waitResolve = null;
|
|
210
|
+
resolve();
|
|
211
|
+
}
|
|
212
|
+
}, Math.min(remaining, 30_000));
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
pushEvents(events) {
|
|
218
|
+
if (events.length === 0)
|
|
219
|
+
return;
|
|
220
|
+
this.lastEventAt = Date.now();
|
|
221
|
+
this.notificationQueue.push(...events);
|
|
222
|
+
this.wakeWaiters();
|
|
223
|
+
}
|
|
224
|
+
wakeWaiters() {
|
|
225
|
+
if (this.waitResolve) {
|
|
226
|
+
const resolve = this.waitResolve;
|
|
227
|
+
this.waitResolve = null;
|
|
228
|
+
resolve();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
failTurn(message) {
|
|
232
|
+
this.pushEvents([
|
|
233
|
+
{
|
|
234
|
+
type: 'result',
|
|
235
|
+
subtype: 'error',
|
|
236
|
+
session_id: this.currentSessionId() ?? '',
|
|
237
|
+
errorMessage: message,
|
|
238
|
+
},
|
|
239
|
+
]);
|
|
240
|
+
}
|
|
241
|
+
resetRunState() {
|
|
242
|
+
this.notificationQueue = [];
|
|
243
|
+
this.waitResolve = null;
|
|
244
|
+
this.lastEventAt = Date.now();
|
|
245
|
+
this.forceFinish = false;
|
|
246
|
+
}
|
|
247
|
+
/** Release the connection + detach stale client hooks after a lost connection. */
|
|
248
|
+
async respawnAfterConnectionLost(opts) {
|
|
249
|
+
await this.releaseConnection(opts.cwd);
|
|
250
|
+
const staleClient = this.currentClient;
|
|
251
|
+
if (staleClient?.setHooks) {
|
|
252
|
+
staleClient.setHooks({
|
|
253
|
+
onNotification: () => { },
|
|
254
|
+
onServerRequest: () => { },
|
|
255
|
+
onClose: () => { },
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
this.resetRunState();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
//# sourceMappingURL=connection-based-runner.js.map
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JSON-RPC client for
|
|
2
|
+
* Generic JSON-RPC client for JSON-line-RPC agent servers
|
|
3
|
+
* (codex app-server, kimi acp, and future ACP-style integrations).
|
|
3
4
|
*
|
|
4
5
|
* Manages the handshake, request/response matching, notification dispatch,
|
|
5
|
-
* and timeout handling over a JsonlRpcTransport.
|
|
6
|
+
* and timeout handling over a JsonlRpcTransport. The `initialize` params
|
|
7
|
+
* shape is protocol-specific and injected by the caller (constructor arg);
|
|
8
|
+
* the default is the ACP handshake shape (ACP is the reference protocol).
|
|
6
9
|
*/
|
|
7
10
|
import type { JsonlRpcTransport } from './transport.js';
|
|
8
|
-
import { type InitializeResult } from './protocol-types.js';
|
|
9
11
|
export declare class RpcError extends Error {
|
|
10
12
|
readonly code: number;
|
|
11
13
|
readonly data?: unknown;
|
|
@@ -22,7 +24,7 @@ export interface ClientHooks {
|
|
|
22
24
|
onServerRequest(id: number | string, method: string, params: unknown): void;
|
|
23
25
|
onClose(): void;
|
|
24
26
|
}
|
|
25
|
-
export declare class
|
|
27
|
+
export declare class JsonRpcClient<InitializeResult = unknown> {
|
|
26
28
|
private transport;
|
|
27
29
|
/**
|
|
28
30
|
* Lifecycle hooks owned by the connection layer (ConnectionManager): slot
|
|
@@ -32,17 +34,18 @@ export declare class CodexAppServerClient {
|
|
|
32
34
|
/** Per-run hooks from the runner, layered over baseHooks via setHooks(). */
|
|
33
35
|
private runHooks;
|
|
34
36
|
private requestTimeoutMs;
|
|
37
|
+
private initializeParams;
|
|
35
38
|
private pendingRequests;
|
|
36
39
|
private nextId;
|
|
37
40
|
private _ready;
|
|
38
41
|
private _disposed;
|
|
39
|
-
constructor(transport: JsonlRpcTransport, hooks: ClientHooks, requestTimeoutMs
|
|
42
|
+
constructor(transport: JsonlRpcTransport, hooks: ClientHooks, requestTimeoutMs: number | undefined, initializeParams: object);
|
|
40
43
|
/**
|
|
41
44
|
* Replace the per-run hooks (used by the runner to bridge
|
|
42
45
|
* notifications/server requests into the active turn's translator).
|
|
43
46
|
* Connection-layer hooks (slot cleanup / onConnectionLost) are preserved:
|
|
44
47
|
* review P2-1 — an overwrite here previously silenced onClose slot cleanup,
|
|
45
|
-
* leaving dead slots behind after the
|
|
48
|
+
* leaving dead slots behind after the server process was killed.
|
|
46
49
|
*/
|
|
47
50
|
setHooks(hooks: ClientHooks): void;
|
|
48
51
|
get ready(): boolean;
|
|
@@ -59,8 +62,12 @@ export declare class CodexAppServerClient {
|
|
|
59
62
|
connect(): Promise<InitializeResult>;
|
|
60
63
|
/**
|
|
61
64
|
* Send a JSON-RPC request and wait for the response.
|
|
65
|
+
* `timeoutMs` overrides the default request timeout — turn-scoped requests
|
|
66
|
+
* (e.g. session/prompt) hold their response open for the whole turn, so
|
|
67
|
+
* callers pass the turn idle timeout (bounded by the runner's turn idle
|
|
68
|
+
* watchdog, not the control-plane request timeout).
|
|
62
69
|
*/
|
|
63
|
-
request<P, R>(method: string, params?: P): Promise<R>;
|
|
70
|
+
request<P, R>(method: string, params?: P, timeoutMs?: number): Promise<R>;
|
|
64
71
|
/**
|
|
65
72
|
* Send a JSON-RPC notification (no response expected).
|
|
66
73
|
*/
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* JSON-RPC client for
|
|
2
|
+
* Generic JSON-RPC client for JSON-line-RPC agent servers
|
|
3
|
+
* (codex app-server, kimi acp, and future ACP-style integrations).
|
|
3
4
|
*
|
|
4
5
|
* Manages the handshake, request/response matching, notification dispatch,
|
|
5
|
-
* and timeout handling over a JsonlRpcTransport.
|
|
6
|
+
* and timeout handling over a JsonlRpcTransport. The `initialize` params
|
|
7
|
+
* shape is protocol-specific and injected by the caller (constructor arg);
|
|
8
|
+
* the default is the ACP handshake shape (ACP is the reference protocol).
|
|
6
9
|
*/
|
|
7
10
|
import { getLogger } from '../../../logger/index.js';
|
|
8
11
|
// =============================================================================
|
|
@@ -33,7 +36,7 @@ export class ConnectionLostError extends Error {
|
|
|
33
36
|
// =============================================================================
|
|
34
37
|
// Client
|
|
35
38
|
// =============================================================================
|
|
36
|
-
export class
|
|
39
|
+
export class JsonRpcClient {
|
|
37
40
|
transport;
|
|
38
41
|
/**
|
|
39
42
|
* Lifecycle hooks owned by the connection layer (ConnectionManager): slot
|
|
@@ -43,21 +46,23 @@ export class CodexAppServerClient {
|
|
|
43
46
|
/** Per-run hooks from the runner, layered over baseHooks via setHooks(). */
|
|
44
47
|
runHooks = null;
|
|
45
48
|
requestTimeoutMs;
|
|
49
|
+
initializeParams;
|
|
46
50
|
pendingRequests = new Map();
|
|
47
51
|
nextId = 1;
|
|
48
52
|
_ready = false;
|
|
49
53
|
_disposed = false;
|
|
50
|
-
constructor(transport, hooks, requestTimeoutMs = 60_000) {
|
|
54
|
+
constructor(transport, hooks, requestTimeoutMs = 60_000, initializeParams) {
|
|
51
55
|
this.transport = transport;
|
|
52
56
|
this.baseHooks = hooks;
|
|
53
57
|
this.requestTimeoutMs = requestTimeoutMs;
|
|
58
|
+
this.initializeParams = initializeParams;
|
|
54
59
|
}
|
|
55
60
|
/**
|
|
56
61
|
* Replace the per-run hooks (used by the runner to bridge
|
|
57
62
|
* notifications/server requests into the active turn's translator).
|
|
58
63
|
* Connection-layer hooks (slot cleanup / onConnectionLost) are preserved:
|
|
59
64
|
* review P2-1 — an overwrite here previously silenced onClose slot cleanup,
|
|
60
|
-
* leaving dead slots behind after the
|
|
65
|
+
* leaving dead slots behind after the server process was killed.
|
|
61
66
|
*/
|
|
62
67
|
setHooks(hooks) {
|
|
63
68
|
this.runHooks = hooks;
|
|
@@ -92,26 +97,27 @@ export class CodexAppServerClient {
|
|
|
92
97
|
// ENOENT/启动失败时 transport 已在 start() 内触发 onClose 并置 closed;
|
|
93
98
|
// 直接 fail-fast,避免 initialize 请求挂满 requestTimeoutMs 才报错。
|
|
94
99
|
if (this.transport.closed) {
|
|
95
|
-
throw new ConnectionLostError('
|
|
100
|
+
throw new ConnectionLostError('JSON-RPC server failed to start (binary missing or crashed)');
|
|
96
101
|
}
|
|
97
|
-
// Send initialize request
|
|
98
|
-
const
|
|
99
|
-
clientInfo: {
|
|
100
|
-
name: 'lark-remote',
|
|
101
|
-
version: '1.0.0',
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
const result = await this.request('initialize', initParams);
|
|
102
|
+
// Send initialize request (shape injected by the caller)
|
|
103
|
+
const result = await this.request('initialize', this.initializeParams);
|
|
105
104
|
this._ready = true;
|
|
106
105
|
return result;
|
|
107
106
|
}
|
|
108
107
|
/**
|
|
109
108
|
* Send a JSON-RPC request and wait for the response.
|
|
109
|
+
* `timeoutMs` overrides the default request timeout — turn-scoped requests
|
|
110
|
+
* (e.g. session/prompt) hold their response open for the whole turn, so
|
|
111
|
+
* callers pass the turn idle timeout (bounded by the runner's turn idle
|
|
112
|
+
* watchdog, not the control-plane request timeout).
|
|
110
113
|
*/
|
|
111
|
-
async request(method, params) {
|
|
114
|
+
async request(method, params, timeoutMs) {
|
|
112
115
|
if (this._disposed) {
|
|
113
116
|
throw new ConnectionLostError('client is disposed');
|
|
114
117
|
}
|
|
118
|
+
if (this.transport.closed) {
|
|
119
|
+
throw new ConnectionLostError('transport closed');
|
|
120
|
+
}
|
|
115
121
|
const id = this.nextId++;
|
|
116
122
|
const request = {
|
|
117
123
|
jsonrpc: '2.0',
|
|
@@ -120,10 +126,13 @@ export class CodexAppServerClient {
|
|
|
120
126
|
params,
|
|
121
127
|
};
|
|
122
128
|
return new Promise((resolve, reject) => {
|
|
129
|
+
// Clamp to setTimeout's max: callers may pass Infinity for turn-scoped
|
|
130
|
+
// requests (session/prompt) — Infinity would fire immediately in Node.
|
|
131
|
+
const effectiveTimeout = Math.min(timeoutMs ?? this.requestTimeoutMs, 2_147_483_647);
|
|
123
132
|
const timer = setTimeout(() => {
|
|
124
133
|
this.pendingRequests.delete(id);
|
|
125
134
|
reject(new RpcTimeoutError(`request timed out: ${method}`));
|
|
126
|
-
},
|
|
135
|
+
}, effectiveTimeout);
|
|
127
136
|
this.pendingRequests.set(id, {
|
|
128
137
|
resolve: resolve,
|
|
129
138
|
reject,
|
|
@@ -214,14 +223,14 @@ export class CodexAppServerClient {
|
|
|
214
223
|
return;
|
|
215
224
|
}
|
|
216
225
|
if ('method' in message && 'id' in message) {
|
|
217
|
-
// Server request (incoming request from server)
|
|
226
|
+
// Server request (incoming request from server — reverse RPC)
|
|
218
227
|
const id = message.id;
|
|
219
228
|
const method = message.method;
|
|
220
229
|
this.baseHooks.onServerRequest(id, method, message.params);
|
|
221
230
|
this.runHooks?.onServerRequest(id, method, message.params);
|
|
222
231
|
return;
|
|
223
232
|
}
|
|
224
|
-
getLogger().warn(`[
|
|
233
|
+
getLogger().warn(`[jsonrpc-client] unknown message type: ${JSON.stringify(message).slice(0, 200)}`);
|
|
225
234
|
}
|
|
226
235
|
failPending(err) {
|
|
227
236
|
for (const [_id, pending] of this.pendingRequests) {
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
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).
|
|
5
|
+
*
|
|
6
|
+
* Each workspace gets its own connection (transport + client). Connections are
|
|
7
|
+
* created on demand via `acquire()`, cached for reuse, and released after an
|
|
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`.
|
|
15
|
+
*/
|
|
16
|
+
import { JsonlRpcTransport } from './transport.js';
|
|
17
|
+
import { JsonRpcClient } from './client.js';
|
|
18
|
+
/** Minimal client surface the manager needs from any protocol client. */
|
|
19
|
+
export interface ConnectionClient {
|
|
20
|
+
ready: boolean;
|
|
21
|
+
healthy: boolean;
|
|
22
|
+
connect(): Promise<unknown>;
|
|
23
|
+
dispose(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
/** Request context for acquire() — currently only an optional session id. */
|
|
26
|
+
export interface AcquireRequest {
|
|
27
|
+
sessionId?: string;
|
|
28
|
+
}
|
|
29
|
+
/** Context passed to clientFactory to build + wire a concrete client. */
|
|
30
|
+
export interface ClientFactoryContext {
|
|
31
|
+
transport: JsonlRpcTransport;
|
|
32
|
+
requestTimeoutMs: number;
|
|
33
|
+
/** Wire into the client's close callback so slot cleanup + onConnectionLost fire. */
|
|
34
|
+
baseOnClose: () => void;
|
|
35
|
+
}
|
|
36
|
+
export interface ConnectionManagerOptions<TClient extends ConnectionClient = JsonRpcClient> {
|
|
37
|
+
/** Path to the server binary. */
|
|
38
|
+
binary: string;
|
|
39
|
+
/**
|
|
40
|
+
* Args to spawn the server with. Defaults to `[]` — each protocol's runner
|
|
41
|
+
* injects its own default (e.g. codex `['app-server', '--stdio']`, kimi
|
|
42
|
+
* `['acp']`).
|
|
43
|
+
*/
|
|
44
|
+
args?: string[];
|
|
45
|
+
/**
|
|
46
|
+
* Optional arg builder over the acquire request — used by protocols that bind
|
|
47
|
+
* a session at spawn (pi: `--session-id`). Defaults to `() => this.args`.
|
|
48
|
+
*/
|
|
49
|
+
buildArgs?: (req: AcquireRequest) => string[];
|
|
50
|
+
/** Environment variables to pass to the binary. */
|
|
51
|
+
env?: Record<string, string | undefined>;
|
|
52
|
+
/** Request timeout in milliseconds. */
|
|
53
|
+
requestTimeoutMs?: number;
|
|
54
|
+
/** Idle TTL in milliseconds. */
|
|
55
|
+
idleTtlMs?: number;
|
|
56
|
+
/**
|
|
57
|
+
* Initialize handshake params for the default JsonRpcClient path. Ignored
|
|
58
|
+
* when `clientFactory` is provided.
|
|
59
|
+
*/
|
|
60
|
+
initializeParams?: object;
|
|
61
|
+
/**
|
|
62
|
+
* Build + wire a concrete protocol client. Defaults to a JsonRpcClient with
|
|
63
|
+
* the initialize handshake. Non-JSON-RPC protocols inject their own client.
|
|
64
|
+
*/
|
|
65
|
+
clientFactory?: (ctx: ClientFactoryContext) => TClient;
|
|
66
|
+
/**
|
|
67
|
+
* Whether an existing (cached) connection can be reused for a request.
|
|
68
|
+
* Defaults to `client.ready && client.healthy`. Session-bound protocols (pi)
|
|
69
|
+
* additionally require the requested session id to match the bound one.
|
|
70
|
+
*/
|
|
71
|
+
shouldReuse?: (ctx: {
|
|
72
|
+
client: TClient;
|
|
73
|
+
req: AcquireRequest;
|
|
74
|
+
boundSessionId: string | undefined;
|
|
75
|
+
}) => boolean;
|
|
76
|
+
/** Log tag prefix for operational log lines. */
|
|
77
|
+
logTag?: string;
|
|
78
|
+
}
|
|
79
|
+
export declare class ConnectionManager<TClient extends ConnectionClient = JsonRpcClient> {
|
|
80
|
+
private slots;
|
|
81
|
+
private readonly binary;
|
|
82
|
+
private readonly buildArgs;
|
|
83
|
+
private readonly env;
|
|
84
|
+
private readonly requestTimeoutMs;
|
|
85
|
+
private readonly idleTtlMs;
|
|
86
|
+
private readonly initializeParams;
|
|
87
|
+
private readonly clientFactory;
|
|
88
|
+
private readonly shouldReuse;
|
|
89
|
+
private readonly logTag;
|
|
90
|
+
/** Callback when a connection is lost — cleared from slot map. */
|
|
91
|
+
onConnectionLost?: (workspace: string) => void;
|
|
92
|
+
constructor(opts: ConnectionManagerOptions<TClient>);
|
|
93
|
+
/**
|
|
94
|
+
* Acquire a connection for the given workspace.
|
|
95
|
+
* Creates a new connection if one does not exist or the previous one was lost
|
|
96
|
+
* (or, for session-bound protocols, bound to a different session).
|
|
97
|
+
* Serializes creation so concurrent calls share the same connection.
|
|
98
|
+
*/
|
|
99
|
+
acquire(workspace: string, req?: AcquireRequest): Promise<TClient>;
|
|
100
|
+
/**
|
|
101
|
+
* Record the session id a fresh connection actually bound to (e.g. discovered
|
|
102
|
+
* via a protocol `get_state` after a fresh spawn). This lets a subsequent run
|
|
103
|
+
* resume the SAME session on the live connection instead of respawning it.
|
|
104
|
+
*/
|
|
105
|
+
bindSession(workspace: string, sessionId: string): void;
|
|
106
|
+
/**
|
|
107
|
+
* Release (dispose) a connection for the given workspace.
|
|
108
|
+
*/
|
|
109
|
+
release(workspace: string): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Dispose all connections.
|
|
112
|
+
*/
|
|
113
|
+
disposeAll(): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Notify that the workspace is active — disarm the idle timer.
|
|
116
|
+
*/
|
|
117
|
+
notifyActivity(workspace: string): void;
|
|
118
|
+
/**
|
|
119
|
+
* Notify that the workspace is idle — arm the idle timer.
|
|
120
|
+
*/
|
|
121
|
+
notifyIdle(workspace: string): void;
|
|
122
|
+
private createClient;
|
|
123
|
+
private clearIdleTimer;
|
|
124
|
+
private disposeSlot;
|
|
125
|
+
}
|