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
|
@@ -5,14 +5,16 @@
|
|
|
5
5
|
* Flow per run: acquire persistent connection → thread/start or thread/resume
|
|
6
6
|
* → turn/start → consume notifications (agent message deltas, approval server
|
|
7
7
|
* requests, …) until turn/completed → synthesize ResultEvent.
|
|
8
|
+
*
|
|
9
|
+
* Shares the ConnectionBasedRunner base with kimi/opencode: same notification
|
|
10
|
+
* queue + waitResolve pump, forceFinish/stopRequested semantics, turn idle
|
|
11
|
+
* watchdog, and ConnectionLostError respawn-and-retry-once.
|
|
8
12
|
*/
|
|
9
|
-
import { ConnectionManager } from '
|
|
10
|
-
import { ConnectionLostError } from './client.js';
|
|
13
|
+
import { ConnectionManager, } from '../../common/jsonrpc/connection-manager.js';
|
|
11
14
|
import { CodexAppServerTranslator } from './translator.js';
|
|
15
|
+
import { RpcErrorCode } from '../../common/acp/protocol-types.js';
|
|
12
16
|
import { getLogger } from '../../../logger/index.js';
|
|
13
|
-
import {
|
|
14
|
-
/** How long to wait for turn output notifications before failing. */
|
|
15
|
-
const TURN_IDLE_TIMEOUT_MS = 10 * 60 * 1000;
|
|
17
|
+
import { ConnectionBasedRunner } from '../../common/connection-based-runner.js';
|
|
16
18
|
/**
|
|
17
19
|
* Build the protocol response body for a command/file approval decision.
|
|
18
20
|
* Structured decisions (acceptWithExecpolicyAmendment) carry the payload the
|
|
@@ -32,194 +34,111 @@ function buildApprovalResponse(action, view) {
|
|
|
32
34
|
}
|
|
33
35
|
return { decision: action };
|
|
34
36
|
}
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
/**
|
|
38
|
+
* Map the client-side SandboxMode enum to the response-side SandboxPolicy
|
|
39
|
+
* object used by `thread/settings/update` (`sandboxPolicy`).
|
|
40
|
+
*/
|
|
41
|
+
export function sandboxModeToSandboxPolicy(mode) {
|
|
42
|
+
switch (mode) {
|
|
43
|
+
case 'read-only':
|
|
44
|
+
return { type: 'readOnly', networkAccess: false };
|
|
45
|
+
case 'workspace-write':
|
|
46
|
+
return {
|
|
47
|
+
type: 'workspaceWrite',
|
|
48
|
+
writableRoots: [],
|
|
49
|
+
networkAccess: false,
|
|
50
|
+
excludeTmpdirEnvVar: false,
|
|
51
|
+
excludeSlashTmp: false,
|
|
52
|
+
};
|
|
53
|
+
case 'danger-full-access':
|
|
54
|
+
return { type: 'dangerFullAccess' };
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export class CodexAppServerRunner extends ConnectionBasedRunner {
|
|
39
58
|
connectionManager;
|
|
40
|
-
currentClient = null;
|
|
41
59
|
currentTranslator = null;
|
|
42
|
-
|
|
43
|
-
currentTurnId = null;
|
|
44
|
-
lastEventAt = 0;
|
|
45
|
-
_isRunning = false;
|
|
60
|
+
activeThreadId = null;
|
|
46
61
|
model;
|
|
47
62
|
modelProvider;
|
|
48
63
|
reasoningEffort;
|
|
49
64
|
sandboxConfig;
|
|
50
65
|
approvalPolicyConfig;
|
|
51
|
-
turnTimeoutMs;
|
|
52
66
|
/** Pending approval requests: requestId (JSON-RPC id) → kind + view. */
|
|
53
67
|
pendingApprovals = new Map();
|
|
54
|
-
/** Notification queue consumed by the active run generator. */
|
|
55
|
-
notificationQueue = [];
|
|
56
|
-
waitResolve = null;
|
|
57
|
-
forceFinish = false;
|
|
58
|
-
/** stop() 在 turn/start 响应前到达时置位,turn/start 完成后补发 interrupt。 */
|
|
59
|
-
stopRequested = false;
|
|
60
68
|
constructor(opts) {
|
|
61
|
-
|
|
62
|
-
this.sessionReader = opts.sessionReader;
|
|
69
|
+
super({ kind: opts.kind, sessionReader: opts.sessionReader }, opts.turnTimeoutMs);
|
|
63
70
|
this.model = opts.model;
|
|
64
71
|
this.modelProvider = opts.modelProvider;
|
|
65
72
|
this.reasoningEffort = opts.reasoningEffort;
|
|
66
73
|
this.sandboxConfig = opts.sandbox;
|
|
67
74
|
this.approvalPolicyConfig = opts.approvalPolicy;
|
|
68
|
-
this.turnTimeoutMs = opts.turnTimeoutMs ?? TURN_IDLE_TIMEOUT_MS;
|
|
69
75
|
const managerOpts = {
|
|
70
76
|
binary: opts.binary ?? 'codex',
|
|
71
|
-
args: opts.appServerArgs,
|
|
77
|
+
args: opts.appServerArgs ?? ['app-server', '--stdio'],
|
|
72
78
|
env: opts.env,
|
|
73
79
|
requestTimeoutMs: opts.requestTimeoutMs,
|
|
74
80
|
idleTtlMs: opts.idleTtlMs,
|
|
81
|
+
// `experimentalApi: true` is required for `thread/settings/update`, which
|
|
82
|
+
// lets config.save push approval/sandbox changes to a live thread instead
|
|
83
|
+
// of waiting for the workspace-lifetime runner to be recreated.
|
|
84
|
+
initializeParams: {
|
|
85
|
+
clientInfo: {
|
|
86
|
+
name: 'lark-remote',
|
|
87
|
+
version: '1.0.0',
|
|
88
|
+
},
|
|
89
|
+
capabilities: {
|
|
90
|
+
experimentalApi: true,
|
|
91
|
+
},
|
|
92
|
+
},
|
|
75
93
|
};
|
|
76
94
|
this.connectionManager = new ConnectionManager(managerOpts);
|
|
77
95
|
}
|
|
78
|
-
get
|
|
79
|
-
return
|
|
96
|
+
get logTag() {
|
|
97
|
+
return 'codex-app-server-runner';
|
|
80
98
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
99
|
+
get turnTimeoutErrorMessage() {
|
|
100
|
+
return 'Codex app-server turn timed out';
|
|
101
|
+
}
|
|
102
|
+
get turnInterruptedErrorMessage() {
|
|
103
|
+
return 'Codex app-server turn interrupted';
|
|
104
|
+
}
|
|
105
|
+
currentSessionId() {
|
|
106
|
+
return this.activeThreadId;
|
|
107
|
+
}
|
|
108
|
+
shouldDeferStop() {
|
|
109
|
+
return !this.currentTurnId;
|
|
110
|
+
}
|
|
111
|
+
async cancelCurrentTurn() {
|
|
112
|
+
if (!this.currentClient || !this.activeThreadId || !this.currentTurnId)
|
|
113
|
+
return;
|
|
94
114
|
try {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// 连接在 setup 期间丢失(进程被误杀/异常退出):重拉进程并重试一次。
|
|
100
|
-
// thread/resume 会按持久化记录(rollout)恢复线程,继续原会话处理。
|
|
101
|
-
if (err instanceof ConnectionLostError && !this.stopRequested) {
|
|
102
|
-
getLogger().warn(`[codex-app-server-runner] connection lost during turn setup (${err.message}), respawning app-server and retrying once`);
|
|
103
|
-
// 旧连接可能仍挂在 slot 里(close 事件尚未处理完):先强制释放,
|
|
104
|
-
// 保证重试会拉起全新进程;并摘掉旧 client 的 hooks,避免其残留的
|
|
105
|
-
// onClose 把错误结果塞进重试后的通知队列。
|
|
106
|
-
await this.connectionManager.release(opts.cwd);
|
|
107
|
-
const staleClient = this.currentClient;
|
|
108
|
-
if (staleClient) {
|
|
109
|
-
staleClient.setHooks({
|
|
110
|
-
onNotification: () => { },
|
|
111
|
-
onServerRequest: () => { },
|
|
112
|
-
onClose: () => { },
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
this.resetRunState();
|
|
116
|
-
await this.setupTurn(message, opts);
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
throw err;
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
if (this.stopRequested) {
|
|
123
|
-
// 启动期 /stop:turn/start 已完成,补发 interrupt,避免 server 端 turn
|
|
124
|
-
// 无人接管继续执行。
|
|
125
|
-
getLogger().warn('[codex-app-server-runner] stop requested before turn/start resolved');
|
|
126
|
-
try {
|
|
127
|
-
await this.currentClient?.request('turn/interrupt', {
|
|
128
|
-
threadId: this.currentThreadId,
|
|
129
|
-
turnId: this.currentTurnId,
|
|
130
|
-
});
|
|
131
|
-
}
|
|
132
|
-
catch (err) {
|
|
133
|
-
getLogger().warn(`[codex-app-server-runner] deferred interrupt failed: ${err.message}`);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
// §9.22 守卫前提:桥的 pre-init result guard 和 run-state reducer 都以
|
|
137
|
-
// system.init 作为「本轮真实开始」的标记,而 app-server 协议没有 init 事件。
|
|
138
|
-
// 缺了它,成功的 result 会被当作 pre-init 丢弃,卡片终态停在 running,
|
|
139
|
-
// 最后兜底成「输出流已结束,但未收到 result 事件」(2026-08-13 事故)。
|
|
140
|
-
// 与 exec runner 的 syntheticInitEvent 模式一致:turn setup 成功即发 init,
|
|
141
|
-
// 保证 turn_started / turn_diff / result 全部落在 init 之后。
|
|
142
|
-
yield syntheticInitEvent(this.currentThreadId ?? opts.sessionId ?? '');
|
|
143
|
-
yield* this.consumeTurn();
|
|
115
|
+
await this.currentClient.request('turn/interrupt', {
|
|
116
|
+
threadId: this.activeThreadId,
|
|
117
|
+
turnId: this.currentTurnId,
|
|
118
|
+
});
|
|
144
119
|
}
|
|
145
120
|
catch (err) {
|
|
146
|
-
getLogger().
|
|
147
|
-
// 与 spawn 失败路径(spawning-runner §9.22)同理:error result 前必须补
|
|
148
|
-
// init,否则守卫把错误结果丢弃,卡片只显示通用「输出流已结束」而非具体
|
|
149
|
-
// 错误信息。
|
|
150
|
-
yield syntheticInitEvent(this.currentThreadId ?? opts.sessionId ?? '');
|
|
151
|
-
yield {
|
|
152
|
-
type: 'result',
|
|
153
|
-
subtype: 'error',
|
|
154
|
-
// 优先上报本次 setup 创建/恢复的线程 id:thread/start 成功后 turn/start
|
|
155
|
-
// 失败时 opts.sessionId 仍是旧值,漏报会导致下条消息再开一个孤儿线程
|
|
156
|
-
// (review P3-10)。
|
|
157
|
-
session_id: this.currentThreadId ?? opts.sessionId ?? '',
|
|
158
|
-
errorMessage: err.message,
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
finally {
|
|
162
|
-
this._isRunning = false;
|
|
163
|
-
this.currentTranslator = null;
|
|
164
|
-
this.currentTurnId = null;
|
|
165
|
-
this.stopRequested = false;
|
|
166
|
-
this.currentThreadId = null;
|
|
167
|
-
// 无论成功失败都要重新武装 idle TTL(无 slot 时是 no-op)。
|
|
168
|
-
this.connectionManager.notifyIdle(opts.cwd);
|
|
121
|
+
getLogger().warn(`[${this.logTag}] interrupt failed: ${err.message}`);
|
|
169
122
|
}
|
|
170
123
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
async
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
getLogger().warn('[codex-app-server-runner] client connection closed');
|
|
184
|
-
this.failTurn('Codex app-server connection closed');
|
|
185
|
-
},
|
|
186
|
-
});
|
|
187
|
-
const threadParams = this.buildThreadParams(opts.cwd);
|
|
188
|
-
let threadId;
|
|
189
|
-
if (opts.sessionId) {
|
|
190
|
-
const resumeParams = {
|
|
191
|
-
threadId: opts.sessionId,
|
|
192
|
-
...threadParams,
|
|
193
|
-
};
|
|
194
|
-
await client.request('thread/resume', resumeParams);
|
|
195
|
-
threadId = opts.sessionId;
|
|
196
|
-
}
|
|
197
|
-
else {
|
|
198
|
-
const threadResult = await client.request('thread/start', threadParams);
|
|
199
|
-
// 会话键假设(review P2-4):主线程 thread.id === session_meta.session_id,
|
|
200
|
-
// 故 thread.id 可同时用作协议 threadId 与 store/session reader 的 session
|
|
201
|
-
// 键(bridge 用 turn_started 通知的 threadId 写回,/resume、Compact 按此
|
|
202
|
-
// 键定位 JSONL)。forked/subagent 线程二者会分叉(openai/codex#29327),
|
|
203
|
-
// 桥只把主线程作为顶层会话,不在此链路内。
|
|
204
|
-
threadId = threadResult.thread.id;
|
|
205
|
-
}
|
|
206
|
-
this.currentThreadId = threadId;
|
|
207
|
-
const translator = new CodexAppServerTranslator();
|
|
208
|
-
this.currentTranslator = translator;
|
|
209
|
-
const turnParams = this.buildTurnParams(message, opts);
|
|
210
|
-
const turnResult = await client.request('turn/start', turnParams);
|
|
211
|
-
this.currentTurnId = turnResult.turn.id;
|
|
124
|
+
clearTurnState() {
|
|
125
|
+
this.currentTranslator = null;
|
|
126
|
+
this.activeThreadId = null;
|
|
127
|
+
}
|
|
128
|
+
async releaseConnection(cwd) {
|
|
129
|
+
await this.connectionManager.release(cwd);
|
|
130
|
+
}
|
|
131
|
+
notifyIdle(cwd) {
|
|
132
|
+
this.connectionManager.notifyIdle(cwd);
|
|
133
|
+
}
|
|
134
|
+
async disposeConnections() {
|
|
135
|
+
await this.connectionManager.disposeAll();
|
|
212
136
|
}
|
|
213
137
|
/**
|
|
214
138
|
* Run a compact operation on the current thread.
|
|
215
139
|
*/
|
|
216
140
|
async *runCompact(_message, opts) {
|
|
217
|
-
|
|
218
|
-
throw new Error('CodexAppServerRunner is already running');
|
|
219
|
-
}
|
|
220
|
-
this._isRunning = true;
|
|
221
|
-
this.resetRunState();
|
|
222
|
-
try {
|
|
141
|
+
yield* this.executeTurn(opts, async () => {
|
|
223
142
|
const client = await this.connectionManager.acquire(opts.cwd);
|
|
224
143
|
this.connectionManager.notifyActivity(opts.cwd);
|
|
225
144
|
this.currentClient = client;
|
|
@@ -231,7 +150,7 @@ export class CodexAppServerRunner {
|
|
|
231
150
|
if (!opts.sessionId) {
|
|
232
151
|
throw new Error('compact requires a sessionId');
|
|
233
152
|
}
|
|
234
|
-
this.
|
|
153
|
+
this.activeThreadId = opts.sessionId;
|
|
235
154
|
const translator = new CodexAppServerTranslator();
|
|
236
155
|
translator.setOperationKind('compact');
|
|
237
156
|
this.currentTranslator = translator;
|
|
@@ -246,55 +165,7 @@ export class CodexAppServerRunner {
|
|
|
246
165
|
};
|
|
247
166
|
await client.request('thread/resume', resumeParams);
|
|
248
167
|
await client.request('thread/compact/start', { threadId: opts.sessionId });
|
|
249
|
-
|
|
250
|
-
yield syntheticInitEvent(this.currentThreadId ?? opts.sessionId ?? '');
|
|
251
|
-
yield* this.consumeTurn();
|
|
252
|
-
}
|
|
253
|
-
catch (err) {
|
|
254
|
-
getLogger().error(`[codex-app-server-runner] runCompact error: ${err.message}`);
|
|
255
|
-
yield syntheticInitEvent(this.currentThreadId ?? opts.sessionId ?? '');
|
|
256
|
-
yield {
|
|
257
|
-
type: 'result',
|
|
258
|
-
subtype: 'error',
|
|
259
|
-
session_id: this.currentThreadId ?? opts.sessionId ?? '',
|
|
260
|
-
errorMessage: err.message,
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
finally {
|
|
264
|
-
this._isRunning = false;
|
|
265
|
-
this.currentTranslator = null;
|
|
266
|
-
this.currentTurnId = null;
|
|
267
|
-
this.stopRequested = false;
|
|
268
|
-
this.currentThreadId = null;
|
|
269
|
-
this.connectionManager.notifyIdle(opts.cwd);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
/**
|
|
273
|
-
* Stop the current turn by interrupting it.
|
|
274
|
-
*/
|
|
275
|
-
async stop(_opts) {
|
|
276
|
-
if (!this._isRunning || !this.currentClient || !this.currentThreadId)
|
|
277
|
-
return;
|
|
278
|
-
if (!this.currentTurnId) {
|
|
279
|
-
// turn/start 尚未响应:先标记,等 run() 在 turn/start 完成后补发 interrupt。
|
|
280
|
-
this.stopRequested = true;
|
|
281
|
-
this.forceFinish = true;
|
|
282
|
-
this.wakeWaiters();
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
try {
|
|
286
|
-
await this.currentClient.request('turn/interrupt', {
|
|
287
|
-
threadId: this.currentThreadId,
|
|
288
|
-
turnId: this.currentTurnId ?? '',
|
|
289
|
-
});
|
|
290
|
-
}
|
|
291
|
-
catch (err) {
|
|
292
|
-
getLogger().warn(`[codex-app-server-runner] stop error: ${err.message}`);
|
|
293
|
-
}
|
|
294
|
-
// Unblock the turn loop regardless of whether the server sends a
|
|
295
|
-
// turn/completed notification afterwards.
|
|
296
|
-
this.forceFinish = true;
|
|
297
|
-
this.wakeWaiters();
|
|
168
|
+
});
|
|
298
169
|
}
|
|
299
170
|
/**
|
|
300
171
|
* Respond to an approval server request. `response` is the bridge
|
|
@@ -323,30 +194,7 @@ export class CodexAppServerRunner {
|
|
|
323
194
|
client.respond(requestId, buildApprovalResponse(action, pending.view));
|
|
324
195
|
}
|
|
325
196
|
this.pendingApprovals.delete(requestId);
|
|
326
|
-
getLogger().info(`[
|
|
327
|
-
}
|
|
328
|
-
/**
|
|
329
|
-
* Dispose the runner: release the connection.
|
|
330
|
-
*/
|
|
331
|
-
async dispose() {
|
|
332
|
-
try {
|
|
333
|
-
await this.connectionManager.disposeAll();
|
|
334
|
-
}
|
|
335
|
-
catch (err) {
|
|
336
|
-
getLogger().warn(`[codex-app-server-runner] dispose error: ${err.message}`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
getUsageAuthority() {
|
|
340
|
-
return 'live';
|
|
341
|
-
}
|
|
342
|
-
killOrphan() {
|
|
343
|
-
// No-op: the app-server connection is managed by ConnectionManager.
|
|
344
|
-
}
|
|
345
|
-
registerExitHandlers() {
|
|
346
|
-
// No-op: connection manager handles cleanup on exit.
|
|
347
|
-
}
|
|
348
|
-
unregisterExitHandlers() {
|
|
349
|
-
// No-op.
|
|
197
|
+
getLogger().info(`[${this.logTag}] approval responded requestId=${requestId} kind=${pending.kind} action=${action}`);
|
|
350
198
|
}
|
|
351
199
|
getStatusInfo() {
|
|
352
200
|
return {
|
|
@@ -361,6 +209,45 @@ export class CodexAppServerRunner {
|
|
|
361
209
|
},
|
|
362
210
|
};
|
|
363
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Unified approval-mode hot-update entry (called by the bridge via duck
|
|
214
|
+
* typing on active runs). Codex's `thread/settings/update` applies the new
|
|
215
|
+
* approval/sandbox policy to subsequent turns, even while the current turn
|
|
216
|
+
* is still active. This lets `/config` take effect without waiting for this
|
|
217
|
+
* workspace-lifetime runner to be recreated/evicted. Local fields are always
|
|
218
|
+
* updated so `/status` reflects the new config immediately; if there is no
|
|
219
|
+
* live thread, the next `thread/start`/`thread/resume` uses the new values
|
|
220
|
+
* anyway.
|
|
221
|
+
*/
|
|
222
|
+
async updateApprovalMode(settings) {
|
|
223
|
+
if (settings.approvalPolicy !== undefined) {
|
|
224
|
+
this.approvalPolicyConfig = settings.approvalPolicy;
|
|
225
|
+
}
|
|
226
|
+
if (settings.sandbox !== undefined) {
|
|
227
|
+
this.sandboxConfig = settings.sandbox;
|
|
228
|
+
}
|
|
229
|
+
if (!this.currentClient || !this.activeThreadId) {
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
const params = {
|
|
233
|
+
threadId: this.activeThreadId,
|
|
234
|
+
...(settings.approvalPolicy !== undefined ? { approvalPolicy: settings.approvalPolicy } : {}),
|
|
235
|
+
...(settings.sandbox !== undefined
|
|
236
|
+
? { sandboxPolicy: sandboxModeToSandboxPolicy(settings.sandbox) }
|
|
237
|
+
: {}),
|
|
238
|
+
};
|
|
239
|
+
try {
|
|
240
|
+
await this.currentClient.request('thread/settings/update', params);
|
|
241
|
+
getLogger().info(`[${this.logTag}] thread/settings/update applied for thread=${this.activeThreadId}`);
|
|
242
|
+
}
|
|
243
|
+
catch (err) {
|
|
244
|
+
// Config save must not fail because a live thread rejected an experimental
|
|
245
|
+
// update (e.g. older Codex without thread/settings/update). The local
|
|
246
|
+
// snapshot is already updated, so /status and the next thread start/resume
|
|
247
|
+
// still use the new values.
|
|
248
|
+
getLogger().warn(`[${this.logTag}] thread/settings/update failed: ${err.message}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
364
251
|
// =========================================================================
|
|
365
252
|
// Internal
|
|
366
253
|
// =========================================================================
|
|
@@ -376,7 +263,7 @@ export class CodexAppServerRunner {
|
|
|
376
263
|
}
|
|
377
264
|
buildTurnParams(message, opts) {
|
|
378
265
|
const params = {
|
|
379
|
-
threadId: this.
|
|
266
|
+
threadId: this.activeThreadId ?? '',
|
|
380
267
|
input: [{ type: 'text', text: message }],
|
|
381
268
|
...((opts.model ?? this.model) ? { model: opts.model ?? this.model } : {}),
|
|
382
269
|
...((opts.reasoningEffort ?? this.reasoningEffort)
|
|
@@ -386,79 +273,46 @@ export class CodexAppServerRunner {
|
|
|
386
273
|
return params;
|
|
387
274
|
}
|
|
388
275
|
/**
|
|
389
|
-
*
|
|
390
|
-
*
|
|
391
|
-
*/
|
|
392
|
-
async *consumeTurn() {
|
|
393
|
-
while (true) {
|
|
394
|
-
const timedOut = await this.waitForEvents(this.nextTurnIdleDeadline());
|
|
395
|
-
if (timedOut) {
|
|
396
|
-
await this.interruptCurrentTurn();
|
|
397
|
-
getLogger().warn('[codex-app-server-runner] turn wait timed out');
|
|
398
|
-
yield {
|
|
399
|
-
type: 'result',
|
|
400
|
-
subtype: 'error',
|
|
401
|
-
session_id: this.currentThreadId ?? '',
|
|
402
|
-
errorMessage: 'Codex app-server turn timed out',
|
|
403
|
-
};
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
if (this.forceFinish) {
|
|
407
|
-
yield {
|
|
408
|
-
type: 'result',
|
|
409
|
-
subtype: 'interrupted',
|
|
410
|
-
session_id: this.currentThreadId ?? '',
|
|
411
|
-
errorMessage: 'Codex app-server turn interrupted',
|
|
412
|
-
};
|
|
413
|
-
return;
|
|
414
|
-
}
|
|
415
|
-
while (this.notificationQueue.length > 0) {
|
|
416
|
-
const ev = this.notificationQueue.shift();
|
|
417
|
-
yield ev;
|
|
418
|
-
if (ev.type === 'result') {
|
|
419
|
-
return;
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Compute the current idle deadline. 0 disables the idle timeout; otherwise
|
|
426
|
-
* the deadline is based on the last received event, not the turn start time.
|
|
276
|
+
* Acquire the connection and set up the turn: thread/start(新会话)或
|
|
277
|
+
* thread/resume(按 sessionId 恢复既有线程)→ turn/start。
|
|
427
278
|
*/
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
279
|
+
async setupTurn(message, opts) {
|
|
280
|
+
const client = await this.connectionManager.acquire(opts.cwd);
|
|
281
|
+
this.connectionManager.notifyActivity(opts.cwd);
|
|
282
|
+
this.currentClient = client;
|
|
283
|
+
client.setHooks({
|
|
284
|
+
onNotification: (method, params) => this.handleNotification(method, params),
|
|
285
|
+
onServerRequest: (id, method, params) => this.handleServerRequest(id, method, params),
|
|
286
|
+
onClose: () => {
|
|
287
|
+
getLogger().warn(`[${this.logTag}] client connection closed`);
|
|
288
|
+
this.failTurn('Codex app-server connection closed');
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
const threadParams = this.buildThreadParams(opts.cwd);
|
|
292
|
+
let threadId;
|
|
293
|
+
if (opts.sessionId) {
|
|
294
|
+
const resumeParams = {
|
|
295
|
+
threadId: opts.sessionId,
|
|
296
|
+
...threadParams,
|
|
297
|
+
};
|
|
298
|
+
await client.request('thread/resume', resumeParams);
|
|
299
|
+
threadId = opts.sessionId;
|
|
444
300
|
}
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
setTimeout(() => {
|
|
454
|
-
if (this.waitResolve === resolve) {
|
|
455
|
-
this.waitResolve = null;
|
|
456
|
-
resolve();
|
|
457
|
-
}
|
|
458
|
-
}, Math.min(remaining, 30_000));
|
|
459
|
-
});
|
|
301
|
+
else {
|
|
302
|
+
const threadResult = await client.request('thread/start', threadParams);
|
|
303
|
+
// 会话键假设(review P2-4):主线程 thread.id === session_meta.session_id,
|
|
304
|
+
// 故 thread.id 可同时用作协议 threadId 与 store/session reader 的 session
|
|
305
|
+
// 键(bridge 用 turn_started 通知的 threadId 写回,/resume、Compact 按此
|
|
306
|
+
// 键定位 JSONL)。forked/subagent 线程二者会分叉(openai/codex#29327),
|
|
307
|
+
// 桥只把主线程作为顶层会话,不在此链路内。
|
|
308
|
+
threadId = threadResult.thread.id;
|
|
460
309
|
}
|
|
461
|
-
|
|
310
|
+
this.activeThreadId = threadId;
|
|
311
|
+
const translator = new CodexAppServerTranslator();
|
|
312
|
+
this.currentTranslator = translator;
|
|
313
|
+
const turnParams = this.buildTurnParams(message, opts);
|
|
314
|
+
const turnResult = await client.request('turn/start', turnParams);
|
|
315
|
+
this.currentTurnId = turnResult.turn.id;
|
|
462
316
|
}
|
|
463
317
|
handleNotification(method, params) {
|
|
464
318
|
const events = this.currentTranslator?.handleNotification(method, params) ?? [];
|
|
@@ -470,48 +324,17 @@ export class CodexAppServerRunner {
|
|
|
470
324
|
// 未处理/不支持的服务端请求必须显式响应(error 即"拒绝",turn 继续),
|
|
471
325
|
// 否则服务端会一直等待响应(applyPatchApproval、item/tool/requestUserInput、
|
|
472
326
|
// mcpServer/elicitation/request 等)。
|
|
473
|
-
this.currentClient?.respondError(id,
|
|
327
|
+
this.currentClient?.respondError(id, RpcErrorCode.METHOD_NOT_FOUND, `Unsupported server request: ${method}`);
|
|
474
328
|
return;
|
|
475
329
|
}
|
|
476
330
|
for (const ev of events) {
|
|
477
331
|
if (ev.type === 'approval_requested') {
|
|
478
332
|
this.pendingApprovals.set(ev.requestId, { kind: ev.kind, view: ev.view });
|
|
479
|
-
getLogger().info(`[
|
|
333
|
+
getLogger().info(`[${this.logTag}] approval requested requestId=${ev.requestId} kind=${ev.kind}`);
|
|
480
334
|
}
|
|
481
335
|
}
|
|
482
336
|
this.pushEvents(events);
|
|
483
337
|
}
|
|
484
|
-
pushEvents(events) {
|
|
485
|
-
if (events.length === 0)
|
|
486
|
-
return;
|
|
487
|
-
this.lastEventAt = Date.now();
|
|
488
|
-
this.notificationQueue.push(...events);
|
|
489
|
-
this.wakeWaiters();
|
|
490
|
-
}
|
|
491
|
-
wakeWaiters() {
|
|
492
|
-
if (this.waitResolve) {
|
|
493
|
-
const resolve = this.waitResolve;
|
|
494
|
-
this.waitResolve = null;
|
|
495
|
-
resolve();
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
failTurn(message) {
|
|
499
|
-
this.pushEvents([
|
|
500
|
-
{
|
|
501
|
-
type: 'result',
|
|
502
|
-
subtype: 'error',
|
|
503
|
-
session_id: this.currentThreadId ?? '',
|
|
504
|
-
errorMessage: message,
|
|
505
|
-
},
|
|
506
|
-
]);
|
|
507
|
-
}
|
|
508
|
-
resetRunState() {
|
|
509
|
-
this.notificationQueue = [];
|
|
510
|
-
this.waitResolve = null;
|
|
511
|
-
this.lastEventAt = Date.now();
|
|
512
|
-
this.forceFinish = false;
|
|
513
|
-
this.pendingApprovals.clear();
|
|
514
|
-
}
|
|
515
338
|
buildGrantedPermissions(view) {
|
|
516
339
|
const items = view.permissions?.items ?? [];
|
|
517
340
|
const read = [];
|
|
@@ -7,29 +7,9 @@
|
|
|
7
7
|
* completes. Method names and parameter shapes follow the real Codex
|
|
8
8
|
* app-server v2 protocol (see protocol-types.ts).
|
|
9
9
|
*/
|
|
10
|
-
import type { AgentEvent } from '../../types.js';
|
|
10
|
+
import type { AgentEvent, TurnStartedEvent, TurnDiffEvent } from '../../types.js';
|
|
11
11
|
import type { ApprovalRequestedEvent, ApprovalResolvedEvent } from '../../types.js';
|
|
12
12
|
import { type TurnCompletedNotification } from './protocol-types.js';
|
|
13
|
-
export interface TurnStartedEvent {
|
|
14
|
-
type: 'turn_started';
|
|
15
|
-
threadId: string;
|
|
16
|
-
turnId: string;
|
|
17
|
-
operationKind: 'turn' | 'compaction';
|
|
18
|
-
}
|
|
19
|
-
export interface TurnDiffEvent {
|
|
20
|
-
type: 'turn_diff';
|
|
21
|
-
text?: string;
|
|
22
|
-
reasoning?: string;
|
|
23
|
-
toolOutput?: string;
|
|
24
|
-
plan?: string;
|
|
25
|
-
fileChanges?: Array<{
|
|
26
|
-
path: string;
|
|
27
|
-
kind: 'add' | 'update' | 'delete';
|
|
28
|
-
diff?: string;
|
|
29
|
-
}>;
|
|
30
|
-
threadId: string;
|
|
31
|
-
turnId: string;
|
|
32
|
-
}
|
|
33
13
|
export type TranslatorEvent = AgentEvent | TurnStartedEvent | TurnDiffEvent | ApprovalRequestedEvent | ApprovalResolvedEvent;
|
|
34
14
|
export declare class CodexAppServerTranslator {
|
|
35
15
|
private turnId;
|