lark-remote 0.1.5 → 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 +39 -0
- package/README.en.md +5 -1
- package/README.md +10 -2
- package/dist/bridge/approval-coordinator.d.ts +131 -0
- package/dist/bridge/approval-coordinator.js +409 -0
- package/dist/bridge/inbound-media.d.ts +62 -0
- package/dist/bridge/inbound-media.js +282 -0
- package/dist/bridge/index.d.ts +138 -5
- package/dist/bridge/index.js +569 -65
- package/dist/bridge/queue-manager.d.ts +11 -1
- package/dist/bridge/queue-manager.js +20 -11
- package/dist/card/approval-render.d.ts +16 -0
- package/dist/card/approval-render.js +363 -0
- 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 +13 -10
- package/dist/card/card-shared.js +27 -24
- 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 +140 -202
- package/dist/card/run-state.d.ts +38 -3
- package/dist/card/run-state.js +221 -11
- 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/dir.js +1 -1
- package/dist/config/index.d.ts +116 -9
- package/dist/config/index.js +122 -29
- package/dist/connector/index.d.ts +74 -2
- package/dist/connector/index.js +119 -1
- package/dist/index.js +139 -99
- 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.d.ts +1 -1
- package/dist/router/config/codex.js +33 -5
- 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 +9 -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 +12 -3
- package/dist/router/index.d.ts +77 -17
- package/dist/router/index.js +455 -549
- package/dist/router/utils.d.ts +4 -0
- package/dist/router/utils.js +9 -2
- 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 +519 -0
- package/dist/runner/codex/app-server/protocol-types.js +56 -0
- package/dist/runner/codex/app-server/runner.d.ts +104 -0
- package/dist/runner/codex/app-server/runner.js +377 -0
- package/dist/runner/codex/app-server/translator.d.ts +89 -0
- package/dist/runner/codex/app-server/translator.js +716 -0
- package/dist/runner/codex/index.d.ts +1 -1
- package/dist/runner/codex/index.js +1 -1
- 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/common/jsonrpc/client.d.ts +89 -0
- package/dist/runner/common/jsonrpc/client.js +243 -0
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +125 -0
- package/dist/runner/common/jsonrpc/connection-manager.js +205 -0
- package/dist/runner/common/jsonrpc/transport.d.ts +50 -0
- package/dist/runner/common/jsonrpc/transport.js +230 -0
- 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/common/usage.d.ts +2 -0
- package/dist/runner/common/usage.js +2 -0
- package/dist/runner/index.d.ts +5 -5
- 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 +153 -2
- 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 +57 -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/argv.d.ts +0 -33
- package/dist/runner/codex/argv.js +0 -40
- 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 -63
- package/dist/runner/codex/runner.js +0 -124
- 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,377 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodexAppServerRunner: workspace-lifetime runner using the Codex App Server
|
|
3
|
+
* protocol (`codex app-server`, JSON-RPC over stdio).
|
|
4
|
+
*
|
|
5
|
+
* Flow per run: acquire persistent connection → thread/start or thread/resume
|
|
6
|
+
* → turn/start → consume notifications (agent message deltas, approval server
|
|
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.
|
|
12
|
+
*/
|
|
13
|
+
import { ConnectionManager, } from '../../common/jsonrpc/connection-manager.js';
|
|
14
|
+
import { CodexAppServerTranslator } from './translator.js';
|
|
15
|
+
import { RpcErrorCode } from '../../common/acp/protocol-types.js';
|
|
16
|
+
import { getLogger } from '../../../logger/index.js';
|
|
17
|
+
import { ConnectionBasedRunner } from '../../common/connection-based-runner.js';
|
|
18
|
+
/**
|
|
19
|
+
* Build the protocol response body for a command/file approval decision.
|
|
20
|
+
* Structured decisions (acceptWithExecpolicyAmendment) carry the payload the
|
|
21
|
+
* server originally offered; if it is missing, fall back to plain accept.
|
|
22
|
+
*/
|
|
23
|
+
function buildApprovalResponse(action, view) {
|
|
24
|
+
if (action === 'accept_for_session') {
|
|
25
|
+
return { decision: 'acceptForSession' };
|
|
26
|
+
}
|
|
27
|
+
if (action === 'accept_with_execpolicy_amendment') {
|
|
28
|
+
const payload = view.decisionPayloads?.['acceptWithExecpolicyAmendment'];
|
|
29
|
+
if (payload) {
|
|
30
|
+
return { decision: { acceptWithExecpolicyAmendment: payload } };
|
|
31
|
+
}
|
|
32
|
+
getLogger().warn('[codex-app-server-runner] acceptWithExecpolicyAmendment payload missing, falling back to accept');
|
|
33
|
+
return { decision: 'accept' };
|
|
34
|
+
}
|
|
35
|
+
return { decision: action };
|
|
36
|
+
}
|
|
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 {
|
|
58
|
+
connectionManager;
|
|
59
|
+
currentTranslator = null;
|
|
60
|
+
activeThreadId = null;
|
|
61
|
+
model;
|
|
62
|
+
modelProvider;
|
|
63
|
+
reasoningEffort;
|
|
64
|
+
sandboxConfig;
|
|
65
|
+
approvalPolicyConfig;
|
|
66
|
+
/** Pending approval requests: requestId (JSON-RPC id) → kind + view. */
|
|
67
|
+
pendingApprovals = new Map();
|
|
68
|
+
constructor(opts) {
|
|
69
|
+
super({ kind: opts.kind, sessionReader: opts.sessionReader }, opts.turnTimeoutMs);
|
|
70
|
+
this.model = opts.model;
|
|
71
|
+
this.modelProvider = opts.modelProvider;
|
|
72
|
+
this.reasoningEffort = opts.reasoningEffort;
|
|
73
|
+
this.sandboxConfig = opts.sandbox;
|
|
74
|
+
this.approvalPolicyConfig = opts.approvalPolicy;
|
|
75
|
+
const managerOpts = {
|
|
76
|
+
binary: opts.binary ?? 'codex',
|
|
77
|
+
args: opts.appServerArgs ?? ['app-server', '--stdio'],
|
|
78
|
+
env: opts.env,
|
|
79
|
+
requestTimeoutMs: opts.requestTimeoutMs,
|
|
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
|
+
},
|
|
93
|
+
};
|
|
94
|
+
this.connectionManager = new ConnectionManager(managerOpts);
|
|
95
|
+
}
|
|
96
|
+
get logTag() {
|
|
97
|
+
return 'codex-app-server-runner';
|
|
98
|
+
}
|
|
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;
|
|
114
|
+
try {
|
|
115
|
+
await this.currentClient.request('turn/interrupt', {
|
|
116
|
+
threadId: this.activeThreadId,
|
|
117
|
+
turnId: this.currentTurnId,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
catch (err) {
|
|
121
|
+
getLogger().warn(`[${this.logTag}] interrupt failed: ${err.message}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
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();
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Run a compact operation on the current thread.
|
|
139
|
+
*/
|
|
140
|
+
async *runCompact(_message, opts) {
|
|
141
|
+
yield* this.executeTurn(opts, async () => {
|
|
142
|
+
const client = await this.connectionManager.acquire(opts.cwd);
|
|
143
|
+
this.connectionManager.notifyActivity(opts.cwd);
|
|
144
|
+
this.currentClient = client;
|
|
145
|
+
client.setHooks({
|
|
146
|
+
onNotification: (method, params) => this.handleNotification(method, params),
|
|
147
|
+
onServerRequest: (id, method, params) => this.handleServerRequest(id, method, params),
|
|
148
|
+
onClose: () => this.failTurn('Codex app-server connection closed'),
|
|
149
|
+
});
|
|
150
|
+
if (!opts.sessionId) {
|
|
151
|
+
throw new Error('compact requires a sessionId');
|
|
152
|
+
}
|
|
153
|
+
this.activeThreadId = opts.sessionId;
|
|
154
|
+
const translator = new CodexAppServerTranslator();
|
|
155
|
+
translator.setOperationKind('compact');
|
|
156
|
+
this.currentTranslator = translator;
|
|
157
|
+
// 冷连接兜底:thread/compact/start 要求线程已加载。真实协议里对未加载
|
|
158
|
+
// 的线程直接 compact 返回 -32600 "thread not found"(codex-cli 0.147.0
|
|
159
|
+
// 实测),只有先 thread/resume(或 thread/start)把线程载入内存才能
|
|
160
|
+
// 压缩。连接池复用期间线程本就在内存,resume 幂等无害;连接被
|
|
161
|
+
// /stop、idle TTL 或进程重建后,这一步是 Compact 能工作的前提。
|
|
162
|
+
const resumeParams = {
|
|
163
|
+
threadId: opts.sessionId,
|
|
164
|
+
...this.buildThreadParams(opts.cwd),
|
|
165
|
+
};
|
|
166
|
+
await client.request('thread/resume', resumeParams);
|
|
167
|
+
await client.request('thread/compact/start', { threadId: opts.sessionId });
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Respond to an approval server request. `response` is the bridge
|
|
172
|
+
* ApprovalAction (`{ action: 'accept' | 'accept_for_session' | 'decline' | 'cancel' }`).
|
|
173
|
+
*/
|
|
174
|
+
async respondApproval(requestId, response) {
|
|
175
|
+
const client = this.currentClient;
|
|
176
|
+
const pending = this.pendingApprovals.get(requestId);
|
|
177
|
+
if (!client || !pending)
|
|
178
|
+
return;
|
|
179
|
+
const action = response?.action ?? 'decline';
|
|
180
|
+
if (pending.kind === 'permissions') {
|
|
181
|
+
// 权限审批的响应没有 decision 字段(真实协议只回 granted profile +
|
|
182
|
+
// scope):decline/cancel 必须返回空授权(拒绝全部),否则会把用户已
|
|
183
|
+
// 勾选的条目当作授予返回(2026-08-12 review 发现:勾选后点「拒绝」实
|
|
184
|
+
// 际授予了所选权限)。accept/acceptForSession 才带用户勾选的条目。
|
|
185
|
+
const denied = action === 'decline' || action === 'cancel';
|
|
186
|
+
client.respond(requestId, {
|
|
187
|
+
permissions: denied
|
|
188
|
+
? this.denyAllPermissions()
|
|
189
|
+
: this.buildGrantedPermissions(pending.view),
|
|
190
|
+
scope: action === 'accept_for_session' ? 'session' : 'turn',
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
client.respond(requestId, buildApprovalResponse(action, pending.view));
|
|
195
|
+
}
|
|
196
|
+
this.pendingApprovals.delete(requestId);
|
|
197
|
+
getLogger().info(`[${this.logTag}] approval responded requestId=${requestId} kind=${pending.kind} action=${action}`);
|
|
198
|
+
}
|
|
199
|
+
getStatusInfo() {
|
|
200
|
+
return {
|
|
201
|
+
kind: this.kind,
|
|
202
|
+
model: this.model ?? '(app-server)',
|
|
203
|
+
provider: this.modelProvider,
|
|
204
|
+
reasoning: this.reasoningEffort,
|
|
205
|
+
extras: {
|
|
206
|
+
mode: 'app-server',
|
|
207
|
+
...(this.sandboxConfig ? { sandbox: this.sandboxConfig } : {}),
|
|
208
|
+
...(this.approvalPolicyConfig ? { approvalPolicy: String(this.approvalPolicyConfig) } : {}),
|
|
209
|
+
},
|
|
210
|
+
};
|
|
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
|
+
}
|
|
251
|
+
// =========================================================================
|
|
252
|
+
// Internal
|
|
253
|
+
// =========================================================================
|
|
254
|
+
buildThreadParams(cwd) {
|
|
255
|
+
const params = {
|
|
256
|
+
cwd,
|
|
257
|
+
...(this.model ? { model: this.model } : {}),
|
|
258
|
+
...(this.modelProvider ? { modelProvider: this.modelProvider } : {}),
|
|
259
|
+
...(this.approvalPolicyConfig ? { approvalPolicy: this.approvalPolicyConfig } : {}),
|
|
260
|
+
...(this.sandboxConfig ? { sandbox: this.sandboxConfig } : {}),
|
|
261
|
+
};
|
|
262
|
+
return params;
|
|
263
|
+
}
|
|
264
|
+
buildTurnParams(message, opts) {
|
|
265
|
+
const params = {
|
|
266
|
+
threadId: this.activeThreadId ?? '',
|
|
267
|
+
input: [{ type: 'text', text: message }],
|
|
268
|
+
...((opts.model ?? this.model) ? { model: opts.model ?? this.model } : {}),
|
|
269
|
+
...((opts.reasoningEffort ?? this.reasoningEffort)
|
|
270
|
+
? { effort: opts.reasoningEffort ?? this.reasoningEffort }
|
|
271
|
+
: {}),
|
|
272
|
+
};
|
|
273
|
+
return params;
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Acquire the connection and set up the turn: thread/start(新会话)或
|
|
277
|
+
* thread/resume(按 sessionId 恢复既有线程)→ turn/start。
|
|
278
|
+
*/
|
|
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;
|
|
300
|
+
}
|
|
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;
|
|
309
|
+
}
|
|
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;
|
|
316
|
+
}
|
|
317
|
+
handleNotification(method, params) {
|
|
318
|
+
const events = this.currentTranslator?.handleNotification(method, params) ?? [];
|
|
319
|
+
this.pushEvents(events);
|
|
320
|
+
}
|
|
321
|
+
handleServerRequest(id, method, params) {
|
|
322
|
+
const events = this.currentTranslator?.handleServerRequest(id, method, params) ?? [];
|
|
323
|
+
if (events.length === 0) {
|
|
324
|
+
// 未处理/不支持的服务端请求必须显式响应(error 即"拒绝",turn 继续),
|
|
325
|
+
// 否则服务端会一直等待响应(applyPatchApproval、item/tool/requestUserInput、
|
|
326
|
+
// mcpServer/elicitation/request 等)。
|
|
327
|
+
this.currentClient?.respondError(id, RpcErrorCode.METHOD_NOT_FOUND, `Unsupported server request: ${method}`);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
for (const ev of events) {
|
|
331
|
+
if (ev.type === 'approval_requested') {
|
|
332
|
+
this.pendingApprovals.set(ev.requestId, { kind: ev.kind, view: ev.view });
|
|
333
|
+
getLogger().info(`[${this.logTag}] approval requested requestId=${ev.requestId} kind=${ev.kind}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
this.pushEvents(events);
|
|
337
|
+
}
|
|
338
|
+
buildGrantedPermissions(view) {
|
|
339
|
+
const items = view.permissions?.items ?? [];
|
|
340
|
+
const read = [];
|
|
341
|
+
const write = [];
|
|
342
|
+
let networkEnabled = false;
|
|
343
|
+
for (const item of items) {
|
|
344
|
+
if (!item.selected)
|
|
345
|
+
continue;
|
|
346
|
+
if (item.target.kind === 'fsRead') {
|
|
347
|
+
read.push(item.target.path);
|
|
348
|
+
}
|
|
349
|
+
else if (item.target.kind === 'fsWrite') {
|
|
350
|
+
write.push(item.target.path);
|
|
351
|
+
}
|
|
352
|
+
else if (item.target.kind === 'network') {
|
|
353
|
+
networkEnabled = true;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
// 真实 GrantedPermissionProfile 形状(v1 schema):fileSystem 用
|
|
357
|
+
// entries[{path, access}](read/write 为 legacy 字符串数组,即将移除),
|
|
358
|
+
// network 是 { enabled } 布尔开关,不是 host 列表。
|
|
359
|
+
return {
|
|
360
|
+
fileSystem: {
|
|
361
|
+
entries: [
|
|
362
|
+
...read.map((path) => ({ path, access: 'read' })),
|
|
363
|
+
...write.map((path) => ({ path, access: 'write' })),
|
|
364
|
+
],
|
|
365
|
+
},
|
|
366
|
+
network: { enabled: networkEnabled },
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
/** 拒绝权限审批:空 fileSystem + 关闭网络(真实 GrantedPermissionProfile)。 */
|
|
370
|
+
denyAllPermissions() {
|
|
371
|
+
return {
|
|
372
|
+
fileSystem: { entries: [] },
|
|
373
|
+
network: { enabled: false },
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
//# sourceMappingURL=runner.js.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodexAppServerTranslator: translates protocol notifications and server
|
|
3
|
+
* requests into AgentEvents.
|
|
4
|
+
*
|
|
5
|
+
* The translator is stateful per turn — it tracks the current turn's state,
|
|
6
|
+
* accumulates deltas, and synthesizes the final ResultEvent when the turn
|
|
7
|
+
* completes. Method names and parameter shapes follow the real Codex
|
|
8
|
+
* app-server v2 protocol (see protocol-types.ts).
|
|
9
|
+
*/
|
|
10
|
+
import type { AgentEvent, TurnStartedEvent, TurnDiffEvent } from '../../types.js';
|
|
11
|
+
import type { ApprovalRequestedEvent, ApprovalResolvedEvent } from '../../types.js';
|
|
12
|
+
import { type TurnCompletedNotification } from './protocol-types.js';
|
|
13
|
+
export type TranslatorEvent = AgentEvent | TurnStartedEvent | TurnDiffEvent | ApprovalRequestedEvent | ApprovalResolvedEvent;
|
|
14
|
+
export declare class CodexAppServerTranslator {
|
|
15
|
+
private turnId;
|
|
16
|
+
private threadId;
|
|
17
|
+
private turnStatus;
|
|
18
|
+
/** Full agentMessage text per item id (item-scoped snapshot). */
|
|
19
|
+
private textByItem;
|
|
20
|
+
/**
|
|
21
|
+
* Reasoning content per item id: paragraphs keyed by `contentIndex`
|
|
22
|
+
* (textDelta) plus the summary stream (summaryTextDelta). Each reasoning
|
|
23
|
+
* item is isolated so interleaved items keep their own chronology.
|
|
24
|
+
*/
|
|
25
|
+
private reasoningByItem;
|
|
26
|
+
/** Full command output per item id (item-scoped snapshot). */
|
|
27
|
+
private toolOutputByItem;
|
|
28
|
+
/** Full plan text per item id (item-scoped snapshot). */
|
|
29
|
+
private planByItem;
|
|
30
|
+
/** commandExecution items that emitted item/started (tool block position anchor). */
|
|
31
|
+
private startedToolItems;
|
|
32
|
+
/** fileChange items seen this turn, keyed by item id (item/started). */
|
|
33
|
+
private fileChangeItems;
|
|
34
|
+
/** File approvals whose item/started has not arrived yet (out-of-order flow). */
|
|
35
|
+
private pendingFileApprovalByItemId;
|
|
36
|
+
private usage;
|
|
37
|
+
private operationKind;
|
|
38
|
+
/**
|
|
39
|
+
* Handle a notification from the server and return events.
|
|
40
|
+
*/
|
|
41
|
+
handleNotification(method: string, params: unknown): TranslatorEvent[];
|
|
42
|
+
/**
|
|
43
|
+
* Handle a server request (incoming JSON-RPC request from server, e.g. approval).
|
|
44
|
+
*/
|
|
45
|
+
handleServerRequest(id: number | string, method: string, params: unknown): TranslatorEvent[];
|
|
46
|
+
/**
|
|
47
|
+
* Synthesize a ResultEvent from the current turn state.
|
|
48
|
+
* Called when the turn completes.
|
|
49
|
+
*/
|
|
50
|
+
handleTurnCompleted(params?: TurnCompletedNotification['params']): TranslatorEvent[];
|
|
51
|
+
/**
|
|
52
|
+
* Get the current usage stats.
|
|
53
|
+
*/
|
|
54
|
+
getUsage(): {
|
|
55
|
+
inputTokens: number;
|
|
56
|
+
outputTokens: number;
|
|
57
|
+
};
|
|
58
|
+
/**
|
|
59
|
+
* Get the current turn status.
|
|
60
|
+
*/
|
|
61
|
+
getTurnStatus(): string;
|
|
62
|
+
/**
|
|
63
|
+
* Set the operation kind (exec vs compact).
|
|
64
|
+
*/
|
|
65
|
+
setOperationKind(kind: 'exec' | 'compact'): void;
|
|
66
|
+
private handleTurnStarted;
|
|
67
|
+
private handleAgentMessageDelta;
|
|
68
|
+
private handleReasoningDelta;
|
|
69
|
+
private handleReasoningPartAdded;
|
|
70
|
+
private handleItemCompleted;
|
|
71
|
+
private handleReasoningTextDelta;
|
|
72
|
+
private handleCommandOutputDelta;
|
|
73
|
+
private handlePlanUpdated;
|
|
74
|
+
private handleTokenUsageUpdated;
|
|
75
|
+
private handleError;
|
|
76
|
+
private handleThreadCompacted;
|
|
77
|
+
private handleServerRequestResolved;
|
|
78
|
+
private handleCommandApproval;
|
|
79
|
+
private handleFileApproval;
|
|
80
|
+
private handleItemStarted;
|
|
81
|
+
private handlePermissionsApproval;
|
|
82
|
+
private buildPermissionItems;
|
|
83
|
+
/** Get (or lazily create) the reasoning accumulator for one item. */
|
|
84
|
+
private reasoningEntry;
|
|
85
|
+
/** Compose the display snapshot for one reasoning item (content + summary). */
|
|
86
|
+
private reasoningSnapshotFor;
|
|
87
|
+
/** Receive-time stamp, aligned with the exec JSONL parse path. */
|
|
88
|
+
private now;
|
|
89
|
+
}
|