lark-remote 0.5.13 → 0.5.15
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 +31 -0
- package/dist/agent-kinds.d.ts +27 -0
- package/dist/agent-kinds.js +38 -0
- package/dist/bridge/approval-actions.d.ts +95 -0
- package/dist/bridge/approval-actions.js +104 -0
- package/dist/bridge/approval-coordinator.d.ts +13 -0
- package/dist/bridge/approval-coordinator.js +18 -0
- package/dist/bridge/final-usage.d.ts +8 -17
- package/dist/bridge/final-usage.js +1 -18
- package/dist/bridge/index.d.ts +46 -39
- package/dist/bridge/index.js +269 -240
- package/dist/bridge/queue-manager.d.ts +25 -1
- package/dist/bridge/queue-manager.js +179 -160
- package/dist/bridge/run-approval.d.ts +2 -1
- package/dist/bridge/run-usage.d.ts +21 -30
- package/dist/bridge/run-usage.js +47 -51
- package/dist/bridge/session-writeback.d.ts +3 -2
- package/dist/card/approval-render.d.ts +3 -2
- package/dist/card/approval-render.js +3 -2
- package/dist/{router → card}/card-helpers.d.ts +7 -7
- package/dist/{router → card}/card-helpers.js +8 -8
- package/dist/card/card-session.d.ts +4 -2
- package/dist/card/card-session.js +8 -6
- package/dist/card/card-shared.d.ts +1 -1
- package/dist/card/card-shared.js +1 -1
- package/dist/card/final-answer-overflow.d.ts +0 -6
- package/dist/card/final-answer-overflow.js +1 -1
- package/dist/card/run-card-session.d.ts +2 -2
- package/dist/card/run-card-session.js +2 -2
- package/dist/card/run-renderer.js +29 -30
- package/dist/card/run-state.d.ts +28 -50
- package/dist/card/run-state.js +23 -0
- package/dist/card/usage-format.d.ts +30 -0
- package/dist/{router → card}/usage-format.js +3 -10
- package/dist/clone.js +6 -1
- package/dist/config/index.d.ts +16 -20
- package/dist/config/index.js +0 -16
- package/dist/config/pi-config.js +6 -4
- package/dist/connector/index.d.ts +2 -4
- package/dist/connector/index.js +7 -11
- package/dist/{runner/dsh → dsh}/auth.js +1 -1
- package/dist/dsh/protocol.d.ts +9 -0
- package/dist/dsh/protocol.js +61 -0
- package/dist/index.js +10 -5
- package/dist/model-catalog.d.ts +14 -0
- package/dist/model-catalog.js +25 -0
- package/dist/platform/path.d.ts +9 -0
- package/dist/platform/path.js +18 -0
- package/dist/router/active-run-usage.d.ts +4 -0
- package/dist/router/active-run-usage.js +12 -0
- package/dist/router/commands/ls.js +1 -1
- package/dist/router/commands/order.js +1 -1
- package/dist/router/commands/resume.d.ts +5 -4
- package/dist/router/commands/resume.js +4 -4
- package/dist/router/commands/ws.js +1 -1
- package/dist/router/config/claude.js +2 -1
- package/dist/router/config/common/render.d.ts +5 -1
- package/dist/router/config/common/render.js +6 -13
- package/dist/router/config/dsh.d.ts +1 -1
- package/dist/router/config/index.d.ts +1 -1
- package/dist/router/config/types.d.ts +11 -3
- package/dist/router/index.d.ts +41 -11
- package/dist/router/index.js +169 -233
- package/dist/runner/agent-choices-common.d.ts +1 -1
- package/dist/runner/claude/runner.d.ts +20 -2
- package/dist/runner/claude/runner.js +2 -15
- package/dist/runner/codex/app-server/runner.d.ts +3 -1
- package/dist/runner/codex/app-server/translator.d.ts +11 -0
- package/dist/runner/codex/app-server/translator.js +56 -149
- package/dist/runner/common/acp/base-acp-runner.d.ts +19 -1
- package/dist/runner/common/acp/base-acp-runner.js +37 -3
- package/dist/runner/common/connection-based-runner.d.ts +3 -1
- package/dist/runner/common/jsonrpc/transport.js +17 -7
- package/dist/runner/dsh/client.d.ts +1 -4
- package/dist/runner/dsh/client.js +7 -55
- package/dist/runner/dsh/daemon.js +9 -2
- package/dist/runner/dsh/exit-cancel.js +1 -1
- package/dist/runner/dsh/mux-stream.js +21 -2
- package/dist/runner/dsh/runner.d.ts +21 -1
- package/dist/runner/dsh/runner.js +97 -78
- package/dist/runner/dsh/translator.d.ts +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +22 -1
- package/dist/runner/kimi/acp/runner.js +83 -60
- package/dist/runner/opencode/acp/error-monitor.d.ts +11 -0
- package/dist/runner/opencode/acp/error-monitor.js +31 -2
- package/dist/runner/opencode/acp/runner.d.ts +14 -1
- package/dist/runner/opencode/acp/runner.js +13 -2
- package/dist/runner/pi/rpc/runner.d.ts +2 -1
- package/dist/runner/probe.d.ts +1 -1
- package/dist/runner/probe.js +10 -9
- package/dist/runner/registry.d.ts +2 -1
- package/dist/runner/types.d.ts +20 -154
- package/dist/runner/types.js +4 -24
- package/dist/session/claude/session-index.d.ts +11 -4
- package/dist/session/claude/session-index.js +30 -8
- package/dist/session/claude/session-reader.d.ts +1 -1
- package/dist/session/claude/sessions.d.ts +2 -2
- package/dist/session/claude/sessions.js +41 -26
- package/dist/session/codex/rollout-reader.d.ts +1 -1
- package/dist/session/codex/rollout-reader.js +14 -13
- package/dist/session/codex/sessions.d.ts +1 -1
- package/dist/session/codex/sessions.js +4 -2
- package/dist/session/common/jsonl.js +7 -1
- package/dist/session/common/pagination.d.ts +2 -0
- package/dist/session/common/pagination.js +1 -1
- package/dist/session/common/recency.d.ts +6 -0
- package/dist/session/common/recency.js +7 -5
- package/dist/session/common/two-pass.d.ts +1 -1
- package/dist/session/common/usage-accumulator.d.ts +1 -1
- package/dist/session/dsh/sessions.d.ts +1 -1
- package/dist/session/dsh/sessions.js +37 -12
- package/dist/session/kimi/sessions.d.ts +1 -1
- package/dist/session/kimi/sessions.js +72 -27
- package/dist/session/opencode/sessions.d.ts +1 -1
- package/dist/session/opencode/sessions.js +164 -81
- package/dist/session/pi/sessions.d.ts +1 -1
- package/dist/session/pi/sessions.js +20 -7
- package/dist/session/registry.d.ts +2 -1
- package/dist/session/session-store.d.ts +1 -2
- package/dist/session/session-store.js +14 -7
- package/dist/session-contract.d.ts +178 -0
- package/dist/session-contract.js +9 -0
- package/package.json +1 -1
- package/dist/router/usage-format.d.ts +0 -60
- /package/dist/{runner/dsh → dsh}/auth.d.ts +0 -0
- /package/dist/{runner/dsh → dsh}/home.d.ts +0 -0
- /package/dist/{runner/dsh → dsh}/home.js +0 -0
- /package/dist/{runner/dsh → dsh}/types.d.ts +0 -0
- /package/dist/{runner/dsh → dsh}/types.js +0 -0
|
@@ -21,8 +21,9 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import { randomUUID } from 'node:crypto';
|
|
23
23
|
import WebSocket from 'ws';
|
|
24
|
+
import { dshArgs, extractDshToken } from '../../dsh/protocol.js';
|
|
24
25
|
import { getLogger } from '../../logger/index.js';
|
|
25
|
-
import { DSH_COOKIE_LIFETIME_MS, isDshCredentialsNotFound, mintDshBrowserCookie, readDshBrowserSecret, resolveDshCredentialsFile, } from '
|
|
26
|
+
import { DSH_COOKIE_LIFETIME_MS, isDshCredentialsNotFound, mintDshBrowserCookie, readDshBrowserSecret, resolveDshCredentialsFile, } from '../../dsh/auth.js';
|
|
26
27
|
import { DEFAULT_REQUEST_TIMEOUT_MS } from '../common/defaults.js';
|
|
27
28
|
import { goalFromProjection } from './goal-view.js';
|
|
28
29
|
import { DshError } from './errors.js';
|
|
@@ -111,60 +112,6 @@ function inboxItemsOf(values) {
|
|
|
111
112
|
}
|
|
112
113
|
return out;
|
|
113
114
|
}
|
|
114
|
-
/** Extract the `token` query parameter from a pasted `dsh web` URL. */
|
|
115
|
-
export function extractDshToken(raw) {
|
|
116
|
-
const value = (raw ?? '').trim();
|
|
117
|
-
if (!value)
|
|
118
|
-
return '';
|
|
119
|
-
if (!value.includes('token=') && !value.includes('://'))
|
|
120
|
-
return value;
|
|
121
|
-
try {
|
|
122
|
-
const url = new URL(value.startsWith('?') ? `http://dsh.invalid/${value}` : value);
|
|
123
|
-
const token = url.searchParams.get('token');
|
|
124
|
-
if (token)
|
|
125
|
-
return token;
|
|
126
|
-
// 带 scheme 却没有 token 参数的输入是误贴的 host URL(如
|
|
127
|
-
// `http://127.0.0.1:3080`):整串不是合法 token,参与握手只会得到
|
|
128
|
-
// 指向性缺失的 401。返回空串让上游走「token 缺失」的诊断路径。
|
|
129
|
-
if (value.includes('://'))
|
|
130
|
-
return '';
|
|
131
|
-
return value;
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
return value;
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
/**
|
|
138
|
-
* Remote methods take named parameters. Most session methods call their single
|
|
139
|
-
* argument `request`; `session/list` reserves `_request`; catalog methods take
|
|
140
|
-
* no arguments. `commands/*` address the receiving agent by its wire field
|
|
141
|
-
* `agentId`, so their payload is already in wire shape (pass-through).
|
|
142
|
-
* The gateway rejects any other `args` shape.
|
|
143
|
-
*/
|
|
144
|
-
const DSH_ARGS_KEY = {
|
|
145
|
-
'session/list': '_request',
|
|
146
|
-
'session/create': 'request',
|
|
147
|
-
'session/prompt': 'request',
|
|
148
|
-
'session/cancel': 'request',
|
|
149
|
-
'session/selectModel': 'request',
|
|
150
|
-
'session/projections': 'request',
|
|
151
|
-
'session/page': 'request',
|
|
152
|
-
'session/follow': 'request',
|
|
153
|
-
'session/updateQueue': 'request',
|
|
154
|
-
'job/list': 'request',
|
|
155
|
-
'job/kill': 'request',
|
|
156
|
-
'session/modelCatalog': null,
|
|
157
|
-
'agentPresets/list': null,
|
|
158
|
-
'permissionPresets/catalog': null,
|
|
159
|
-
};
|
|
160
|
-
export function dshArgs(method, payload) {
|
|
161
|
-
const key = DSH_ARGS_KEY[method];
|
|
162
|
-
if (key === null)
|
|
163
|
-
return {};
|
|
164
|
-
if (key === undefined)
|
|
165
|
-
return payload;
|
|
166
|
-
return { [key]: payload };
|
|
167
|
-
}
|
|
168
115
|
export class DshClient {
|
|
169
116
|
baseUrl;
|
|
170
117
|
token;
|
|
@@ -483,6 +430,11 @@ export class DshClient {
|
|
|
483
430
|
// 拿不到投影 = 宿主没这个会话的投影,不等于"没有 goal"。
|
|
484
431
|
if (!snapshot)
|
|
485
432
|
return { known: false, goal: null };
|
|
433
|
+
// 投影快照存在但没有 goal 域 = 宿主明确没有目标(未用过 goal 的会话就是这样)。
|
|
434
|
+
// 不能把缺失当成"读不懂":goalFromProjection(undefined) 也返回 undefined,
|
|
435
|
+
// 那会把每次普通会话都误报成「目标状态未确认」。
|
|
436
|
+
if (!('goal' in snapshot.values))
|
|
437
|
+
return { known: true, goal: null };
|
|
486
438
|
const parsed = goalFromProjection(snapshot.values.goal);
|
|
487
439
|
if (parsed === undefined)
|
|
488
440
|
return { known: false, goal: null };
|
|
@@ -15,7 +15,8 @@ import fsp from 'node:fs/promises';
|
|
|
15
15
|
import path from 'node:path';
|
|
16
16
|
import { DEFAULT_DSH_HOST } from '../../config/index.js';
|
|
17
17
|
import { mergeProcessEnv, spawnProcess } from '../../platform/spawn.js';
|
|
18
|
-
import {
|
|
18
|
+
import { getLogger } from '../../logger/index.js';
|
|
19
|
+
import { resolveDshHome } from '../../dsh/home.js';
|
|
19
20
|
const DEFAULT_START_TIMEOUT_MS = 30_000;
|
|
20
21
|
const READY_POLL_MS = 100;
|
|
21
22
|
/** User-facing daemon failure; the bridge renders `message` in the chat card. */
|
|
@@ -174,7 +175,13 @@ export class DshDaemon {
|
|
|
174
175
|
const stats = await fsp.stat(lockDir);
|
|
175
176
|
return Date.now() - stats.mtimeMs > this.startTimeoutMs() * 2;
|
|
176
177
|
}
|
|
177
|
-
catch {
|
|
178
|
+
catch (err) {
|
|
179
|
+
// ENOENT = 锁目录刚被持有者清掉(正常,等下一轮重查)。
|
|
180
|
+
// 其余(EACCES/EPERM…)= 无法判断是否陈旧:保守判「不陈旧」继续等,
|
|
181
|
+
// 但必须留痕,否则超时文案会误报成「等待其他 lark-remote」。
|
|
182
|
+
if (err.code !== 'ENOENT') {
|
|
183
|
+
getLogger().warn(`[dsh] cannot stat start lock ${lockDir}: ${err?.message ?? String(err)}`);
|
|
184
|
+
}
|
|
178
185
|
return false;
|
|
179
186
|
}
|
|
180
187
|
}
|
|
@@ -18,7 +18,7 @@ import path from 'node:path';
|
|
|
18
18
|
import { getLogger } from '../../logger/index.js';
|
|
19
19
|
import { isWin32 } from '../../platform/select.js';
|
|
20
20
|
import { spawnProcessSync } from '../../platform/spawn.js';
|
|
21
|
-
import { DSH_COOKIE_LIFETIME_MS, mintDshBrowserCookie, readDshBrowserSecretSync, resolveDshCredentialsFile, } from '
|
|
21
|
+
import { DSH_COOKIE_LIFETIME_MS, mintDshBrowserCookie, readDshBrowserSecretSync, resolveDshCredentialsFile, } from '../../dsh/auth.js';
|
|
22
22
|
/** Upper bound on the exit-path cancel:退出不能被一个卡住的 host 拖住。 */
|
|
23
23
|
const EXIT_CANCEL_TIMEOUT_MS = 2_000;
|
|
24
24
|
/**
|
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
import { getLogger } from '../../logger/index.js';
|
|
2
2
|
import { DshError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* onerror 的事件对象在 ws 实现之间形态不一({message} / {error} / {type})。
|
|
5
|
+
* 尽力取出可读描述;取不到就不追加,别拿 '[object Object]' 充数。
|
|
6
|
+
*/
|
|
7
|
+
function describeSocketError(event) {
|
|
8
|
+
if (!event || typeof event !== 'object')
|
|
9
|
+
return undefined;
|
|
10
|
+
const e = event;
|
|
11
|
+
if (typeof e.message === 'string' && e.message)
|
|
12
|
+
return e.message;
|
|
13
|
+
if (e.error instanceof Error && e.error.message)
|
|
14
|
+
return e.error.message;
|
|
15
|
+
if (typeof e.error === 'string' && e.error)
|
|
16
|
+
return e.error;
|
|
17
|
+
if (typeof e.type === 'string' && e.type)
|
|
18
|
+
return e.type;
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
3
21
|
export async function* openMuxStream(opts) {
|
|
4
22
|
const cookie = await opts.authCookie();
|
|
5
23
|
const socket = opts.webSocketFactory(`${opts.baseUrl.replace(/^http/, 'ws')}/api/remote.mux`, cookie ? { headers: { cookie } } : undefined);
|
|
@@ -87,10 +105,11 @@ export async function* openMuxStream(opts) {
|
|
|
87
105
|
settle(undefined);
|
|
88
106
|
}
|
|
89
107
|
};
|
|
90
|
-
socket.onerror = () => {
|
|
108
|
+
socket.onerror = (event) => {
|
|
91
109
|
if (ended)
|
|
92
110
|
return;
|
|
93
|
-
|
|
111
|
+
const detail = describeSocketError(event);
|
|
112
|
+
failure = new DshError('transport', `DSH remote mux WebSocket error${detail ? `: ${detail}` : ''}`);
|
|
94
113
|
ended = true;
|
|
95
114
|
settle(undefined);
|
|
96
115
|
};
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* approval on the same card. If `$events` is unavailable, the runner falls
|
|
18
18
|
* back to the Web UI prompt.
|
|
19
19
|
*/
|
|
20
|
-
import type {
|
|
20
|
+
import type { AgentSessionReader } from '../../session-contract.js';
|
|
21
|
+
import type { AgentEvent, AgentStatusInfo, SpawnOptions, StopOutcome } from '../types.js';
|
|
21
22
|
import type { AgentRunner } from '../types.js';
|
|
22
23
|
import { DshClient, type WebSocketFactory } from './client.js';
|
|
23
24
|
import type { DshDaemon } from './daemon.js';
|
|
@@ -134,6 +135,25 @@ export declare class DshRunner implements AgentRunner {
|
|
|
134
135
|
* the live catalog for that provider's default (or first) model.
|
|
135
136
|
*/
|
|
136
137
|
private resolveModelSelection;
|
|
138
|
+
/**
|
|
139
|
+
* 新建 DSH session 并取回 sessionId;run 在挂起期间被 stop 时返回 undefined。
|
|
140
|
+
*
|
|
141
|
+
* 红线:不把 run 的 abort signal 直接交给 create —— abort 掉请求会丢掉刚创建的
|
|
142
|
+
* sessionId,服务端若已完成创建就留下一个无法 cancel 的孤儿 session。改为 abort
|
|
143
|
+
* 时立即放行 run(队列不被挂起),create 后台落定后再补 cancel。
|
|
144
|
+
*/
|
|
145
|
+
private acquireSessionId;
|
|
146
|
+
/**
|
|
147
|
+
* 把本轮消息交给 DSH 的排队收件箱,返回本轮的身份与起始水位。
|
|
148
|
+
*
|
|
149
|
+
* 三条不变量(都关系到「迟到/抢先的 stop 能否被正确撤销」):
|
|
150
|
+
* - 起始水位取 prompt **之前**的 latestEventSeq:session/page 返回整个会话,
|
|
151
|
+
* 从 0 开始会把首轮已完成 turn 在每次后续消息时重放一遍。
|
|
152
|
+
* - 身份(requestId)在请求发出**之前**就公布:stop 可能落在 session/prompt 的
|
|
153
|
+
* 在途窗口里,那时它必须知道宿主受理后该撤销哪个收件项。
|
|
154
|
+
* - `promptAdmitted` 只在宿主接受后置真:撤销路径据此区分「还没进去」与「已排队」。
|
|
155
|
+
*/
|
|
156
|
+
private submitPrompt;
|
|
137
157
|
run(message: string, opts: SpawnOptions): AsyncGenerator<AgentEvent>;
|
|
138
158
|
/**
|
|
139
159
|
* Consume one turn from both transports. `session/follow` owns transcript
|
|
@@ -178,6 +178,20 @@ function parseToolArguments(raw) {
|
|
|
178
178
|
return {};
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* 用户 stop 的终态结果(run() 的 5 个中止点各 yield 同一形状)。
|
|
183
|
+
*
|
|
184
|
+
* 必须 yield result 而不是裸 return:调用方靠 result 收尾 run 卡,否则卡片
|
|
185
|
+
* 停留在「运行中」(见 run() 内 startup abort 注释)。
|
|
186
|
+
*/
|
|
187
|
+
function interruptedResult(sessionId) {
|
|
188
|
+
return {
|
|
189
|
+
type: 'result',
|
|
190
|
+
subtype: 'interrupted',
|
|
191
|
+
session_id: sessionId,
|
|
192
|
+
errorMessage: 'run stopped',
|
|
193
|
+
};
|
|
194
|
+
}
|
|
181
195
|
export class DshRunner {
|
|
182
196
|
kind = 'dsh';
|
|
183
197
|
/**
|
|
@@ -323,6 +337,57 @@ export class DshRunner {
|
|
|
323
337
|
}
|
|
324
338
|
return { provider, model, ...(reasoningEffort ? { reasoningEffort } : {}) };
|
|
325
339
|
}
|
|
340
|
+
/**
|
|
341
|
+
* 新建 DSH session 并取回 sessionId;run 在挂起期间被 stop 时返回 undefined。
|
|
342
|
+
*
|
|
343
|
+
* 红线:不把 run 的 abort signal 直接交给 create —— abort 掉请求会丢掉刚创建的
|
|
344
|
+
* sessionId,服务端若已完成创建就留下一个无法 cancel 的孤儿 session。改为 abort
|
|
345
|
+
* 时立即放行 run(队列不被挂起),create 后台落定后再补 cancel。
|
|
346
|
+
*/
|
|
347
|
+
async acquireSessionId(cwd, signal) {
|
|
348
|
+
const createPromise = this.client.createSession({
|
|
349
|
+
cwd,
|
|
350
|
+
...(this.agentPreset ? { agentPreset: this.agentPreset } : {}),
|
|
351
|
+
});
|
|
352
|
+
const created = await this.awaitOrAbort(createPromise, signal);
|
|
353
|
+
if (created)
|
|
354
|
+
return created.sessionId;
|
|
355
|
+
void createPromise.then((c) => this.cancelSessionQuietly(c.sessionId), () => {
|
|
356
|
+
/* create failed after stop — nothing to cancel */
|
|
357
|
+
});
|
|
358
|
+
getLogger().info('[dsh] run stopped during session/create; will cancel if it settles');
|
|
359
|
+
return undefined;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* 把本轮消息交给 DSH 的排队收件箱,返回本轮的身份与起始水位。
|
|
363
|
+
*
|
|
364
|
+
* 三条不变量(都关系到「迟到/抢先的 stop 能否被正确撤销」):
|
|
365
|
+
* - 起始水位取 prompt **之前**的 latestEventSeq:session/page 返回整个会话,
|
|
366
|
+
* 从 0 开始会把首轮已完成 turn 在每次后续消息时重放一遍。
|
|
367
|
+
* - 身份(requestId)在请求发出**之前**就公布:stop 可能落在 session/prompt 的
|
|
368
|
+
* 在途窗口里,那时它必须知道宿主受理后该撤销哪个收件项。
|
|
369
|
+
* - `promptAdmitted` 只在宿主接受后置真:撤销路径据此区分「还没进去」与「已排队」。
|
|
370
|
+
*/
|
|
371
|
+
async submitPrompt(sessionId, cwd, message, signal) {
|
|
372
|
+
const prePromptSeq = await this.client.latestEventSeq(sessionId, { signal });
|
|
373
|
+
const promptRequestId = newPromptRequestId();
|
|
374
|
+
this.activePromptRequestId = promptRequestId;
|
|
375
|
+
this.promptAdmitted = false;
|
|
376
|
+
this.inflight?.record({
|
|
377
|
+
sessionId,
|
|
378
|
+
promptRequestId,
|
|
379
|
+
baseUrl: this.client.baseUrl,
|
|
380
|
+
cwd,
|
|
381
|
+
});
|
|
382
|
+
await this.client.unary('session/prompt', {
|
|
383
|
+
requestId: promptRequestId,
|
|
384
|
+
sessionId,
|
|
385
|
+
mode: 'queue',
|
|
386
|
+
content: [{ type: 'text', text: message }],
|
|
387
|
+
}, { signal });
|
|
388
|
+
this.promptAdmitted = true;
|
|
389
|
+
return { prePromptSeq, promptRequestId };
|
|
390
|
+
}
|
|
326
391
|
async *run(message, opts) {
|
|
327
392
|
if (this.running) {
|
|
328
393
|
throw new Error('DshRunner is already running');
|
|
@@ -346,29 +411,13 @@ export class DshRunner {
|
|
|
346
411
|
// session/create 必须包进同一 try/finally,否则创建失败(服务端不可达/报错)
|
|
347
412
|
// 会在进入旧 try 之前抛异常,finally 不执行 → running 恒 true、activeAbort 残留。
|
|
348
413
|
if (!sessionId) {
|
|
349
|
-
const
|
|
350
|
-
|
|
351
|
-
...(this.agentPreset ? { agentPreset: this.agentPreset } : {}),
|
|
352
|
-
});
|
|
353
|
-
// 不把 run 的 abort signal 直接交给 create:abort 掉请求会丢掉刚创建
|
|
354
|
-
// 的 sessionId,服务端若已完成创建就留下一个无法 cancel 的孤儿 session。
|
|
355
|
-
// 改为 abort 时立即放行 run(队列不被挂起),create 后台落定后再补 cancel。
|
|
356
|
-
const created = await this.awaitOrAbort(createPromise, abort.signal);
|
|
357
|
-
if (!created) {
|
|
358
|
-
void createPromise.then((c) => this.cancelSessionQuietly(c.sessionId), () => {
|
|
359
|
-
/* create failed after stop — nothing to cancel */
|
|
360
|
-
});
|
|
361
|
-
getLogger().info('[dsh] run stopped during session/create; will cancel if it settles');
|
|
414
|
+
const created = await this.acquireSessionId(opts.cwd, abort.signal);
|
|
415
|
+
if (created === undefined) {
|
|
362
416
|
terminalEmitted = true;
|
|
363
|
-
yield
|
|
364
|
-
type: 'result',
|
|
365
|
-
subtype: 'interrupted',
|
|
366
|
-
session_id: '',
|
|
367
|
-
errorMessage: 'run stopped',
|
|
368
|
-
};
|
|
417
|
+
yield interruptedResult('');
|
|
369
418
|
return;
|
|
370
419
|
}
|
|
371
|
-
sessionId = created
|
|
420
|
+
sessionId = created;
|
|
372
421
|
}
|
|
373
422
|
this.activeSessionId = sessionId;
|
|
374
423
|
// Startup abort:createSession 挂起期间 stop() 已 abort(此时 sessionId 尚
|
|
@@ -380,12 +429,7 @@ export class DshRunner {
|
|
|
380
429
|
await this.cancelSessionQuietly(sessionId);
|
|
381
430
|
getLogger().info(`[dsh] run aborted during startup, cancelled session ${sessionId}`);
|
|
382
431
|
terminalEmitted = true;
|
|
383
|
-
yield
|
|
384
|
-
type: 'result',
|
|
385
|
-
subtype: 'interrupted',
|
|
386
|
-
session_id: sessionId,
|
|
387
|
-
errorMessage: 'run stopped',
|
|
388
|
-
};
|
|
432
|
+
yield interruptedResult(sessionId);
|
|
389
433
|
return;
|
|
390
434
|
}
|
|
391
435
|
// provider/模型/推理强度对齐:会话首次 run 时 selectModel 一次
|
|
@@ -403,12 +447,7 @@ export class DshRunner {
|
|
|
403
447
|
if (abort.signal.aborted) {
|
|
404
448
|
getLogger().info(`[dsh] run stopped during selectModel session=${sessionId}`);
|
|
405
449
|
terminalEmitted = true;
|
|
406
|
-
yield
|
|
407
|
-
type: 'result',
|
|
408
|
-
subtype: 'interrupted',
|
|
409
|
-
session_id: sessionId,
|
|
410
|
-
errorMessage: 'run stopped',
|
|
411
|
-
};
|
|
450
|
+
yield interruptedResult(sessionId);
|
|
412
451
|
return;
|
|
413
452
|
}
|
|
414
453
|
if (this.configuredProvider) {
|
|
@@ -425,12 +464,7 @@ export class DshRunner {
|
|
|
425
464
|
// Abort 可能在上述 unary 完成与检查之间到达:不进入 prompt/turn。
|
|
426
465
|
if (abort.signal.aborted) {
|
|
427
466
|
terminalEmitted = true;
|
|
428
|
-
yield
|
|
429
|
-
type: 'result',
|
|
430
|
-
subtype: 'interrupted',
|
|
431
|
-
session_id: sessionId,
|
|
432
|
-
errorMessage: 'run stopped',
|
|
433
|
-
};
|
|
467
|
+
yield interruptedResult(sessionId);
|
|
434
468
|
return;
|
|
435
469
|
}
|
|
436
470
|
yield {
|
|
@@ -448,53 +482,29 @@ export class DshRunner {
|
|
|
448
482
|
timeoutMs: GOAL_PROBE_TIMEOUT_MS,
|
|
449
483
|
});
|
|
450
484
|
const lastGoal = seedGoal.known ? seedGoal.goal : undefined;
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
485
|
+
if (seedGoal.known) {
|
|
486
|
+
// 只有确实存在 goal 才产事件:没有 goal 的会话保持事件序列不变(少一次
|
|
487
|
+
// 渲染,也不让"读不到"变成一张多出来的卡片事件)。
|
|
488
|
+
if (seedGoal.goal)
|
|
489
|
+
yield goalStateEvent(seedGoal.goal, true);
|
|
490
|
+
}
|
|
491
|
+
else {
|
|
492
|
+
// 读不到 ≠ 没有目标。必须显式把 unknown 交给卡片,否则收尾只能按
|
|
493
|
+
// 「没有 goal」写成「已完成」——正是 GoalStateEvent 契约禁止的推断。
|
|
494
|
+
yield goalStateEvent(null, false);
|
|
495
|
+
}
|
|
455
496
|
try {
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
// would replay the first completed turn on every follow-up prompt.
|
|
459
|
-
const prePromptSeq = await this.client.latestEventSeq(sessionId, {
|
|
460
|
-
signal: abort.signal,
|
|
461
|
-
});
|
|
462
|
-
// Keep the prompt identity: DSH echoes it into the durable user/message
|
|
463
|
-
// source.rpcId, which is how runTurn tells this run's turn from a turn
|
|
464
|
-
// it is merely queued behind.
|
|
465
|
-
promptRequestId = newPromptRequestId();
|
|
466
|
-
// Publish the identity before the request leaves: a stop that arrives
|
|
467
|
-
// while `session/prompt` is in flight must still know which inbox item
|
|
468
|
-
// (if any) to withdraw once the host admits it.
|
|
469
|
-
this.activePromptRequestId = promptRequestId;
|
|
470
|
-
this.promptAdmitted = false;
|
|
471
|
-
this.inflight?.record({
|
|
472
|
-
sessionId,
|
|
473
|
-
promptRequestId,
|
|
474
|
-
baseUrl: this.client.baseUrl,
|
|
475
|
-
cwd: opts.cwd,
|
|
476
|
-
});
|
|
477
|
-
await this.client.unary('session/prompt', {
|
|
478
|
-
requestId: promptRequestId,
|
|
479
|
-
sessionId,
|
|
480
|
-
mode: 'queue',
|
|
481
|
-
content: [{ type: 'text', text: message }],
|
|
482
|
-
}, { signal: abort.signal });
|
|
483
|
-
this.promptAdmitted = true;
|
|
497
|
+
const submitted = await this.submitPrompt(sessionId, opts.cwd, message, abort.signal);
|
|
498
|
+
promptRequestId = submitted.promptRequestId;
|
|
484
499
|
if (abort.signal.aborted) {
|
|
485
500
|
// stop() 在 prompt 已受理后到达:cancel 由 stop() 发出,这里直接收尾,
|
|
486
501
|
// 不再打开 mux(否则会看到 cancel 产生的 turn/end 事件)。
|
|
487
502
|
terminalEmitted = true;
|
|
488
|
-
yield
|
|
489
|
-
type: 'result',
|
|
490
|
-
subtype: 'interrupted',
|
|
491
|
-
session_id: sessionId,
|
|
492
|
-
errorMessage: 'run stopped',
|
|
493
|
-
};
|
|
503
|
+
yield interruptedResult(sessionId);
|
|
494
504
|
return;
|
|
495
505
|
}
|
|
496
506
|
try {
|
|
497
|
-
for await (const ev of this.runTurn(sessionId, opts.cwd, prePromptSeq, promptRequestId, abort, { last: lastGoal })) {
|
|
507
|
+
for await (const ev of this.runTurn(sessionId, opts.cwd, submitted.prePromptSeq, promptRequestId, abort, { last: lastGoal, known: seedGoal.known })) {
|
|
498
508
|
yield ev;
|
|
499
509
|
if (ev.type === 'result') {
|
|
500
510
|
terminalEmitted = true;
|
|
@@ -731,7 +741,16 @@ export class DshRunner {
|
|
|
731
741
|
signal,
|
|
732
742
|
timeoutMs: GOAL_PROBE_TIMEOUT_MS,
|
|
733
743
|
});
|
|
734
|
-
if (finalGoal.known
|
|
744
|
+
if (!finalGoal.known) {
|
|
745
|
+
// 收尾读不到:seed 已报过 unknown 就不重复发,否则把状态降级
|
|
746
|
+
// 交给卡片(卡片据此不再宣布完成)。
|
|
747
|
+
if (goal.known) {
|
|
748
|
+
goal.known = false;
|
|
749
|
+
yield goalStateEvent(null, false);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
else if (!goal.known || !sameGoal(finalGoal.goal, goal.last)) {
|
|
753
|
+
goal.known = true;
|
|
735
754
|
goal.last = finalGoal.goal;
|
|
736
755
|
yield goalStateEvent(finalGoal.goal, true);
|
|
737
756
|
}
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
* cache_creation_tokens (cacheWrite = cache creation) + total_tokens sum.
|
|
25
25
|
*/
|
|
26
26
|
import type { AgentEvent, ResultEvent } from '../types.js';
|
|
27
|
-
import type { DshAssistantStreamFrame, DshSessionEvent, DshTokenUsage } from '
|
|
27
|
+
import type { DshAssistantStreamFrame, DshSessionEvent, DshTokenUsage } from '../../dsh/types.js';
|
|
28
28
|
export declare class DshTranslator {
|
|
29
29
|
/** 本 turn 累计用量:DSH 每次模型调用(step)一条 usage,工具往返 = 多条。 */
|
|
30
30
|
private turnUsage;
|
package/dist/runner/index.d.ts
CHANGED
|
@@ -15,6 +15,6 @@ export { KimiAcpRunner } from './kimi/index.js';
|
|
|
15
15
|
export { DshRunner } from './dsh/index.js';
|
|
16
16
|
export { BashProcessRunner, type BashRunner } from './bash/index.js';
|
|
17
17
|
export { AgentRegistry } from './registry.js';
|
|
18
|
-
export type { ResultEvent, PlanEvent, FileChangeEvent, AgentEvent, Runner, RunnerLifetime,
|
|
18
|
+
export type { ResultEvent, PlanEvent, FileChangeEvent, AgentEvent, Runner, RunnerLifetime, AgentRunner, TurnStartedEvent, TurnDiffEvent, FinalAnswerEvent, NoticeEvent, SessionInfoEvent, ToolHint, ApprovalView, ApprovalRequestedEvent, ApprovalResolvedEvent, GoalView, GoalStateEvent, } from './types.js';
|
|
19
19
|
export { resolveAgentChoices } from './resolve-agent-choices.js';
|
|
20
20
|
export { syncAgentChoices } from './sync-agent-choices.js';
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
* - AskUserQuestion flows through elicitation forms + a request_permission
|
|
18
18
|
* question bridge (buildQuestionResponse)
|
|
19
19
|
*/
|
|
20
|
-
import type { AgentKind
|
|
20
|
+
import type { AgentKind } from '../../../agent-kinds.js';
|
|
21
|
+
import type { AgentSessionReader } from '../../../session-contract.js';
|
|
22
|
+
import type { AgentStatusInfo, SpawnOptions } from '../../types.js';
|
|
21
23
|
import { JsonRpcClient } from '../../common/jsonrpc/client.js';
|
|
22
24
|
import { KimiAcpTranslator } from './translator.js';
|
|
23
25
|
import { type SessionPromptResult } from '../../common/acp/protocol-types.js';
|
|
@@ -167,6 +169,25 @@ export declare class KimiAcpRunner extends BaseAcpRunner<KimiAcpTranslator> {
|
|
|
167
169
|
* stdout(+stderr) 到缓冲,立即回 {terminalId}。
|
|
168
170
|
*/
|
|
169
171
|
private handleTerminalCreate;
|
|
172
|
+
/**
|
|
173
|
+
* 走 shell seam:argv 逐 token 保留,只把「可执行文件在哪」收口——win32 上
|
|
174
|
+
* Git Bash 缺失时在这里抛 ShellUnavailableError,由 handleTerminalRequest 转成
|
|
175
|
+
* 明确的 Terminal request failed 回执,而不是一个语焉不详的 spawn ENOENT。
|
|
176
|
+
*/
|
|
177
|
+
private spawnTerminalProcess;
|
|
178
|
+
/**
|
|
179
|
+
* 有界输出缓冲:每个流独立 decoder(多字节字符跨 chunk 拆分时由 decoder 保留
|
|
180
|
+
* 不完整序列,避免按 chunk 单独 toString 产生 U+FFFD 替换符)。
|
|
181
|
+
*/
|
|
182
|
+
private attachTerminalOutputBuffer;
|
|
183
|
+
/**
|
|
184
|
+
* 退出/错误收尾管线:tool_result 必须先入队,再放行 `wait_for_exit`。
|
|
185
|
+
*/
|
|
186
|
+
private wireTerminalExit;
|
|
187
|
+
/** Bash 面板的 tool_result 帧(输出全量 + 是否失败)。 */
|
|
188
|
+
private pushTerminalToolResult;
|
|
189
|
+
/** Bash 面板的 tool_use 帧(真实命令 + cwd,create 时立刻可见)。 */
|
|
190
|
+
private pushTerminalToolUse;
|
|
170
191
|
/** terminal/output: 回累计全量输出 + truncated + 当前退出状态(可未退出)。 */
|
|
171
192
|
private handleTerminalOutput;
|
|
172
193
|
/** terminal/wait_for_exit: 未退出则等 exit promise 再回 {exitCode, signal}。 */
|