lark-remote 0.4.1 → 0.4.3
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 +2 -2
- package/README.md +2 -2
- package/dist/bridge/approval-coordinator.d.ts +2 -0
- package/dist/bridge/approval-coordinator.js +16 -7
- package/dist/bridge/index.d.ts +22 -0
- package/dist/bridge/index.js +138 -56
- package/dist/card/approval-render.js +9 -8
- package/dist/card/card-budget.js +39 -26
- package/dist/card/run-renderer.js +9 -2
- package/dist/card/text-truncate.d.ts +9 -10
- package/dist/card/text-truncate.js +48 -43
- package/dist/cli.js +17 -0
- package/dist/config/codex-config.d.ts +3 -0
- package/dist/config/codex-config.js +4 -1
- package/dist/config/dir.js +6 -6
- package/dist/config/index.d.ts +7 -5
- package/dist/config/index.js +52 -13
- package/dist/config/kimi-config.d.ts +4 -1
- package/dist/config/kimi-config.js +5 -2
- package/dist/config/pi-config.d.ts +2 -7
- package/dist/config/pi-config.js +6 -6
- package/dist/config/wizard.js +8 -4
- package/dist/connector/index.d.ts +19 -0
- package/dist/connector/index.js +134 -23
- package/dist/inbound/placeholder.d.ts +3 -2
- package/dist/inbound/placeholder.js +17 -7
- package/dist/index.js +28 -6
- package/dist/instance-lock.d.ts +31 -15
- package/dist/instance-lock.js +64 -62
- package/dist/platform/agent-stopper.d.ts +29 -7
- package/dist/platform/agent-stopper.js +40 -8
- package/dist/platform/identity.d.ts +59 -3
- package/dist/platform/identity.js +105 -30
- package/dist/platform/path.d.ts +0 -7
- package/dist/platform/path.js +1 -51
- package/dist/platform/shell.d.ts +14 -0
- package/dist/platform/shell.js +35 -2
- package/dist/platform/sleep-blocker.d.ts +6 -0
- package/dist/platform/sleep-blocker.js +31 -5
- package/dist/platform/spawn.d.ts +1 -1
- package/dist/platform/spawn.js +1 -1
- package/dist/platform/terminator-posix.d.ts +1 -29
- package/dist/platform/terminator-posix.js +32 -30
- package/dist/platform/terminator-win32.js +4 -2
- package/dist/platform/terminator.d.ts +2 -2
- package/dist/platform/terminator.js +17 -2
- package/dist/platform/types.d.ts +3 -0
- package/dist/router/card-action-feedback.d.ts +17 -0
- package/dist/router/card-action-feedback.js +31 -0
- package/dist/router/card-helpers.d.ts +38 -2
- package/dist/router/card-helpers.js +79 -2
- package/dist/router/config/claude.js +8 -5
- package/dist/router/index.d.ts +41 -6
- package/dist/router/index.js +254 -84
- package/dist/runner/claude/runner.js +1 -1
- package/dist/runner/claude/session.d.ts +2 -6
- package/dist/runner/claude/session.js +7 -8
- package/dist/runner/codex/app-server/protocol-types.d.ts +4 -0
- package/dist/runner/codex/app-server/protocol-types.js +4 -0
- package/dist/runner/codex/app-server/runner.js +3 -0
- package/dist/runner/common/acp/base-acp-runner.js +7 -0
- package/dist/runner/common/connection-based-runner.d.ts +18 -0
- package/dist/runner/common/connection-based-runner.js +31 -0
- package/dist/runner/common/jsonrpc/client.js +6 -1
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +20 -0
- package/dist/runner/common/jsonrpc/connection-manager.js +27 -1
- package/dist/runner/common/jsonrpc/transport.d.ts +29 -1
- package/dist/runner/common/jsonrpc/transport.js +79 -10
- package/dist/runner/common/spawning-runner.d.ts +49 -19
- package/dist/runner/common/spawning-runner.js +94 -54
- package/dist/runner/dsh/client.d.ts +1 -3
- package/dist/runner/dsh/client.js +0 -4
- package/dist/runner/dsh/types.d.ts +0 -36
- package/dist/runner/kimi/acp/runner.d.ts +18 -1
- package/dist/runner/kimi/acp/runner.js +100 -42
- package/dist/runner/kimi/acp/translator.d.ts +0 -1
- package/dist/runner/opencode/acp/translator.d.ts +0 -1
- package/dist/runner/pi/rpc/protocol-types.d.ts +4 -0
- package/dist/runner/pi/rpc/runner.js +3 -0
- package/dist/runner/resolve-agent-choices.js +0 -5
- package/package.json +2 -2
- package/dist/runner/common/process-stopper.d.ts +0 -7
- package/dist/runner/common/process-stopper.js +0 -8
- package/dist/runner/dsh/fake-dsh-server.d.ts +0 -60
- package/dist/runner/dsh/fake-dsh-server.js +0 -156
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* - AskUserQuestion flows through elicitation forms + a request_permission
|
|
18
18
|
* question bridge (buildQuestionResponse)
|
|
19
19
|
*/
|
|
20
|
-
import {
|
|
20
|
+
import { mergeProcessEnv, useDetachedProcessGroup } from '../../../platform/spawn.js';
|
|
21
|
+
import { createShellBackend } from '../../../platform/shell.js';
|
|
21
22
|
import { StringDecoder } from 'node:string_decoder';
|
|
22
23
|
import { ConnectionManager, } from '../../common/jsonrpc/connection-manager.js';
|
|
23
24
|
import { KimiAcpTranslator } from './translator.js';
|
|
@@ -26,7 +27,7 @@ import { KIMI_APPROVAL_KINDS, buildAcpPermissionOutcome, } from '../../common/ac
|
|
|
26
27
|
import { mapAnswersByIndex } from '../../question-common.js';
|
|
27
28
|
import { getLogger } from '../../../logger/index.js';
|
|
28
29
|
import { BaseAcpRunner } from '../../common/acp/base-acp-runner.js';
|
|
29
|
-
import {
|
|
30
|
+
import { createTerminator } from '../../../platform/terminator.js';
|
|
30
31
|
/** §5.2: compaction 等待的默认沉默超时(10 分钟,独立于 turn idle 默认 30 分钟)。 */
|
|
31
32
|
const COMPACT_IDLE_TIMEOUT_MS = 10 * 60_000;
|
|
32
33
|
/** Poll interval for the wire.jsonl compaction record. */
|
|
@@ -134,6 +135,7 @@ function buildQuestionResponse(action, pending, response) {
|
|
|
134
135
|
// =============================================================================
|
|
135
136
|
export class KimiAcpRunner extends BaseAcpRunner {
|
|
136
137
|
permissionMode;
|
|
138
|
+
thinkingEffort;
|
|
137
139
|
compactIdleTimeoutMs;
|
|
138
140
|
/**
|
|
139
141
|
* §5.2 compaction baseline: wire.jsonl records sampled by compactBaseline()
|
|
@@ -155,7 +157,17 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
155
157
|
bashToolCallIds = new Set();
|
|
156
158
|
/** 当前 run 的 session cwd(terminal/create 未给 cwd 时回退用)。 */
|
|
157
159
|
activeCwd = null;
|
|
158
|
-
|
|
160
|
+
/**
|
|
161
|
+
* 终端子进程终止器(terminal/kill|release 与 dispose 清理用)。agent 必须是
|
|
162
|
+
* 'kimi':win32 上优雅段靠协议停止通道,查错 key 会静默退化成直接树杀。
|
|
163
|
+
*/
|
|
164
|
+
terminator = createTerminator({ graceMs: 2_000, agent: 'kimi' });
|
|
165
|
+
/**
|
|
166
|
+
* shell seam:terminal/create 的可执行解析走这里(design.md §7.2)。kimi 的
|
|
167
|
+
* ACP 服务端固定要 `bash -c`,win32 上没有 Git Bash 时必须在 seam 里变成
|
|
168
|
+
* 明确错误,而不是一个语焉不详的 spawn ENOENT。
|
|
169
|
+
*/
|
|
170
|
+
shell = createShellBackend();
|
|
159
171
|
constructor(opts) {
|
|
160
172
|
const managerOpts = {
|
|
161
173
|
binary: opts.binary ?? 'kimi',
|
|
@@ -189,6 +201,7 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
189
201
|
turnIdleTimeoutMs: opts.turnIdleTimeoutMs,
|
|
190
202
|
}, new ConnectionManager(managerOpts));
|
|
191
203
|
this.model = opts.model;
|
|
204
|
+
this.thinkingEffort = opts.thinkingEffort;
|
|
192
205
|
this.permissionMode = opts.permissionMode ?? 'manual';
|
|
193
206
|
this.compactIdleTimeoutMs = opts.compactIdleTimeoutMs ?? COMPACT_IDLE_TIMEOUT_MS;
|
|
194
207
|
}
|
|
@@ -226,6 +239,25 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
226
239
|
getLogger().warn(`[${this.logTag}] set_config_option model failed (non-fatal): ${err.message}`);
|
|
227
240
|
}
|
|
228
241
|
}
|
|
242
|
+
// A5: 下发配置的思考强度。configId 与取值不是猜的——kimi 真机(0.43.1)
|
|
243
|
+
// `session/new` 就通告 `{id:'thinking', category:'thought_level',
|
|
244
|
+
// options:[low|high|max]}`,set 成功后响应里 `currentValue` 变成新值;
|
|
245
|
+
// 未知 configId / 非法取值回 -32602(不静默接受),所以失败告警即可判定。
|
|
246
|
+
// 换模型后新模型可能不支持当前档 → 服务端拒绝、保持其默认档,卡片侧
|
|
247
|
+
// `thinkingEffortPatchForModel` 已按 supportEfforts 预挑兼容档。
|
|
248
|
+
if (this.thinkingEffort) {
|
|
249
|
+
try {
|
|
250
|
+
await client.request('session/set_config_option', {
|
|
251
|
+
sessionId,
|
|
252
|
+
configId: 'thinking',
|
|
253
|
+
value: this.thinkingEffort,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
catch (err) {
|
|
257
|
+
getLogger().warn(`[${this.logTag}] set_config_option thinking failed (non-fatal): ` +
|
|
258
|
+
`${err.message}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
229
261
|
// Set permission mode (§5.1): EVERY fresh ACP session starts in
|
|
230
262
|
// 'default' (= manual approvals, acp-server modes.ts DEFAULT_MODE_ID),
|
|
231
263
|
// so the mode must be sent unconditionally — skipping yolo leaves the
|
|
@@ -266,10 +298,11 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
266
298
|
// Derived from the pre-fire snapshot captured by compactBaseline() —
|
|
267
299
|
// re-reading here would race the prompt fire and misclassify new records.
|
|
268
300
|
const baselineTerminalCount = this.compactBaselineRecords.filter(isCompactionTerminalLike).length;
|
|
269
|
-
|
|
301
|
+
let promptRejected = false;
|
|
302
|
+
// prompt 结算只供两样东西:成功 result 的翻译素材(stopReason/usage)与 §5.4
|
|
303
|
+
// 探针。终态判定不再挂在它后面(A7)。
|
|
304
|
+
const promptSettled = promptPromise.then((result) => {
|
|
270
305
|
this.promptSettled = true;
|
|
271
|
-
if (this.stopRequested)
|
|
272
|
-
return; // already cancelled
|
|
273
306
|
if (compactionReader) {
|
|
274
307
|
// §5.4 可选防线(日志探针):prompt settle 后既无新 begin 也无新
|
|
275
308
|
// terminal → compact 未被引擎接受(可能已在跑或被拒绝)。不解析
|
|
@@ -278,31 +311,50 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
278
311
|
if (totalNow === baselineTotalCount) {
|
|
279
312
|
getLogger().warn(`[${this.logTag}] compact 未被引擎接受(无新 begin/terminal 记录,可能已在跑或被拒绝)`);
|
|
280
313
|
}
|
|
281
|
-
const outcome = await this.waitForCompactionTerminal(compactionReader, opts, baselineTerminalCount);
|
|
282
|
-
if (this.forceFinish)
|
|
283
|
-
return; // stopped while polling
|
|
284
|
-
if (outcome === 'completed') {
|
|
285
|
-
// 压缩完成:正常翻译 prompt 结果(subtype success)。
|
|
286
|
-
const resultEvent = translator.handlePromptResponse(sessionId, result);
|
|
287
|
-
this.pushEvents([resultEvent]);
|
|
288
|
-
}
|
|
289
|
-
else if (outcome === 'cancelled') {
|
|
290
|
-
this.pushEvents([
|
|
291
|
-
translator.produceErrorResult(sessionId, '压缩未完成:已被取消或压缩请求失败(可重试)'),
|
|
292
|
-
]);
|
|
293
|
-
}
|
|
294
|
-
else if (outcome === 'unknown') {
|
|
295
|
-
this.pushEvents([
|
|
296
|
-
translator.produceErrorResult(sessionId, `压缩状态未知:超过 ${formatIdleWindow(this.compactIdleTimeoutMs)} 未观察到完成/取消记录,压缩可能仍在后台进行`),
|
|
297
|
-
]);
|
|
298
|
-
}
|
|
299
|
-
// outcome === 'stopped':不推 result,由 consumeTurn 的 interrupted 兜底。
|
|
300
314
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
315
|
+
return result;
|
|
316
|
+
}, (err) => {
|
|
317
|
+
promptRejected = true;
|
|
318
|
+
this.handlePromptRejected(err, translator, sessionId, 'compact');
|
|
319
|
+
return undefined;
|
|
320
|
+
});
|
|
321
|
+
if (!compactionReader) {
|
|
322
|
+
// 读不到 compaction 记录(reader 未实现):沿用「prompt 结算即成功」的口径。
|
|
323
|
+
void promptSettled.then((result) => {
|
|
324
|
+
if (result)
|
|
325
|
+
this.pushEvents([translator.handlePromptResponse(sessionId, result)]);
|
|
326
|
+
});
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// A7:语义等待在 prompt 触发的那一刻武装。引擎静默超过 compactIdleTimeoutMs
|
|
330
|
+
// 就给出 compact 语义终态,不等 session/prompt 结算——否则挂住的 prompt 会把
|
|
331
|
+
// 用户拖到通用 turnIdleTimeout(默认 30min),拿到的还是一个语义泛化的 turn
|
|
332
|
+
// 超时,而不是「压缩状态未知 / 可重试」。
|
|
333
|
+
void this.waitForCompactionTerminal(compactionReader, opts, baselineTerminalCount).then(async (outcome) => {
|
|
334
|
+
// outcome === 'stopped':不推 result,由 consumeTurn 的 interrupted 兜底。
|
|
335
|
+
if (outcome === 'stopped' || this.forceFinish || this.stopRequested)
|
|
336
|
+
return;
|
|
337
|
+
if (outcome === 'cancelled') {
|
|
338
|
+
this.pushEvents([
|
|
339
|
+
translator.produceErrorResult(sessionId, '压缩未完成:已被取消或压缩请求失败(可重试)'),
|
|
340
|
+
]);
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
if (outcome === 'unknown') {
|
|
344
|
+
this.pushEvents([
|
|
345
|
+
translator.produceErrorResult(sessionId, `压缩状态未知:超过 ${formatIdleWindow(this.compactIdleTimeoutMs)} 未观察到完成/取消记录,压缩可能仍在后台进行`),
|
|
346
|
+
]);
|
|
347
|
+
return;
|
|
304
348
|
}
|
|
305
|
-
|
|
349
|
+
// completed:成功 result 需要 prompt 的 stopReason/usage。§5.2 的正常时序里
|
|
350
|
+
// prompt 先于终态记录结算,这里通常是在等一个已完成的 promise。
|
|
351
|
+
const result = await promptSettled;
|
|
352
|
+
if (!result || promptRejected)
|
|
353
|
+
return; // handlePromptRejected 已产 error result
|
|
354
|
+
if (this.forceFinish || this.stopRequested)
|
|
355
|
+
return;
|
|
356
|
+
this.pushEvents([translator.handlePromptResponse(sessionId, result)]);
|
|
357
|
+
});
|
|
306
358
|
}
|
|
307
359
|
buildApprovalOutcome(action, pending, response) {
|
|
308
360
|
return pending.kind === 'question'
|
|
@@ -501,17 +553,23 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
501
553
|
: undefined;
|
|
502
554
|
const cwd = params.cwd ?? this.activeCwd ?? undefined;
|
|
503
555
|
const outputLimit = params.outputByteLimit ?? TERMINAL_DEFAULT_OUTPUT_LIMIT;
|
|
504
|
-
|
|
556
|
+
// 走 shell seam(design.md §7.2):argv 逐 token 保留,只把「可执行文件在哪」
|
|
557
|
+
// 收口——win32 上 Git Bash 缺失时在这里抛 ShellUnavailableError,由
|
|
558
|
+
// handleTerminalRequest 转成明确的 Terminal request failed 回执,而不是一个
|
|
559
|
+
// 语焉不详的 spawn ENOENT。
|
|
560
|
+
const proc = this.shell.spawnArgv(params.command, params.args ?? [], {
|
|
505
561
|
cwd,
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
562
|
+
options: {
|
|
563
|
+
// env 覆盖大小写不敏感合并(win32 PATH/Path 双键防护,v2 §8.3)
|
|
564
|
+
env: env ? mergeProcessEnv(process.env, env) : undefined,
|
|
565
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
566
|
+
// Terminator 用负 PID 杀进程组(kill(-pgid));子进程必须是组长
|
|
567
|
+
// 才能命中,否则 kill(-pid) 抛 ESRCH 被吞 → kill/release/清理全失效。
|
|
568
|
+
// 与 JsonlRpcTransport / spawning-runner 同模式;win32 不 detached
|
|
569
|
+
// (`.cmd` 垫片在 DETACHED_PROCESS 下丢 stdio),树杀由 taskkill /T 负责。
|
|
570
|
+
detached: useDetachedProcessGroup(),
|
|
571
|
+
windowsHide: true,
|
|
572
|
+
},
|
|
515
573
|
});
|
|
516
574
|
// 每个流独立 decoder:多字节字符跨 chunk 拆分时由 decoder 保留不完整
|
|
517
575
|
// 序列,避免按 chunk 单独 toString 产生 U+FFFD 替换符。
|
|
@@ -651,7 +709,7 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
651
709
|
this.currentClient?.respondError(id, RpcErrorCode.INVALID_PARAMS, `Unknown terminal: ${params.terminalId}`);
|
|
652
710
|
return;
|
|
653
711
|
}
|
|
654
|
-
await this.
|
|
712
|
+
await this.terminator.stop(handle.proc, { immediate: true });
|
|
655
713
|
this.currentClient?.respond(id, {});
|
|
656
714
|
}
|
|
657
715
|
/** terminal/release: 从 map 删除;进程仍在则一并杀,回 {}。 */
|
|
@@ -663,7 +721,7 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
663
721
|
}
|
|
664
722
|
this.terminals.delete(params.terminalId);
|
|
665
723
|
// stop 对已退出进程是 no-op;仍在跑则立即 SIGTERM+SIGKILL。
|
|
666
|
-
await this.
|
|
724
|
+
await this.terminator.stop(handle.proc, { immediate: true });
|
|
667
725
|
this.currentClient?.respond(id, {});
|
|
668
726
|
}
|
|
669
727
|
/** Kill every live terminal (dispose/connection-close 清理钩子)。 */
|
|
@@ -671,7 +729,7 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
671
729
|
const handles = [...this.terminals.values()];
|
|
672
730
|
this.terminals.clear();
|
|
673
731
|
for (const handle of handles) {
|
|
674
|
-
await this.
|
|
732
|
+
await this.terminator.stop(handle.proc, { immediate: true });
|
|
675
733
|
}
|
|
676
734
|
}
|
|
677
735
|
// =========================================================================
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
*/
|
|
28
28
|
import { type RequestPermissionParams, type AgentMessageChunkEvent, type AgentThoughtChunkEvent } from '../../common/acp/protocol-types.js';
|
|
29
29
|
import { BaseAcpTranslator, type AcpTranslatorEvent } from '../../common/acp/base-translator.js';
|
|
30
|
-
export type { AcpTurnStartedEvent, AcpLiveUsage, AcpUsageEvent, AcpTranslatorEvent, } from '../../common/acp/base-translator.js';
|
|
31
30
|
export declare class KimiAcpTranslator extends BaseAcpTranslator {
|
|
32
31
|
protected readonly logTag = "kimi-acp-translator";
|
|
33
32
|
/** Accumulated text per tool-call or message (keyed by a logical item id). */
|
|
@@ -26,7 +26,6 @@
|
|
|
26
26
|
*/
|
|
27
27
|
import type { RequestPermissionParams, AgentMessageChunkEvent, AgentThoughtChunkEvent, ToolCallUpdateEvent, UsageUpdateEvent } from '../../common/acp/protocol-types.js';
|
|
28
28
|
import { BaseAcpTranslator, type AcpTranslatorEvent } from '../../common/acp/base-translator.js';
|
|
29
|
-
export type { AcpTurnStartedEvent as OpencodeAcpTurnStartedEvent, AcpLiveUsage as OpencodeAcpLiveUsage, AcpUsageEvent as OpencodeAcpUsageEvent, AcpTranslatorEvent as OpencodeAcpTranslatorEvent, } from '../../common/acp/base-translator.js';
|
|
30
29
|
export declare class OpencodeAcpTranslator extends BaseAcpTranslator {
|
|
31
30
|
protected readonly logTag = "opencode-acp-translator";
|
|
32
31
|
protected handleAgentMessageChunk(event: AgentMessageChunkEvent, _sessionId: string): AcpTranslatorEvent[];
|
|
@@ -32,6 +32,10 @@ export type PiRpcCommand = {
|
|
|
32
32
|
} | {
|
|
33
33
|
type: 'get_state';
|
|
34
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* get_state 的完整返回体。桥只读 sessionId,其余字段照上游 schema 保留:当前没有
|
|
37
|
+
* consumer 是有意为之(静态扫描报的"零引用导出类型"即指此处),别据此删除。
|
|
38
|
+
*/
|
|
35
39
|
export interface PiRpcGetStateData {
|
|
36
40
|
model?: unknown;
|
|
37
41
|
sessionId: string;
|
|
@@ -53,6 +53,9 @@ export class PiRpcRunner extends ConnectionBasedRunner {
|
|
|
53
53
|
idleTtlMs: opts.idleTtlMs,
|
|
54
54
|
logTag: 'pi-rpc-connection-manager',
|
|
55
55
|
});
|
|
56
|
+
// 协议停止通道(design §3.3):pi 的 cancel 是 `{type:'abort'}` 通知,已由
|
|
57
|
+
// cancelCurrentTurn 实现;登记后 win32 优雅停止才有通道可用(否则一路树杀)。
|
|
58
|
+
this.manager.stopper = ({ client }) => this.buildCooperativeStop(client);
|
|
56
59
|
}
|
|
57
60
|
get logTag() {
|
|
58
61
|
return 'pi-rpc-runner';
|
|
@@ -20,11 +20,6 @@ export function resolveAgentChoices(config) {
|
|
|
20
20
|
}
|
|
21
21
|
// Clone to avoid mutation
|
|
22
22
|
const resolved = structuredClone(config);
|
|
23
|
-
// agents.codex 在 schema 中必填(带默认值),此处按旧语义补空对象;
|
|
24
|
-
// 运行时只往 [agent] 槽位写字段,不依赖 codex 默认值。
|
|
25
|
-
if (!resolved.agents) {
|
|
26
|
-
resolved.agents = {};
|
|
27
|
-
}
|
|
28
23
|
const agents = resolved.agents;
|
|
29
24
|
const target = (agents[agent] ??= {});
|
|
30
25
|
const source = agentChoices;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lark-remote",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "Feishu (Lark) private-chat bridge to local coding agents — Claude Code / Codex / opencode / pi / kimi, with live-streaming CardKit 2.0 cards",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -94,7 +94,7 @@
|
|
|
94
94
|
"type": "module",
|
|
95
95
|
"dependencies": {
|
|
96
96
|
"@iarna/toml": "^2.2.5",
|
|
97
|
-
"@larksuite/channel": "^0.
|
|
97
|
+
"@larksuite/channel": "^0.7.1",
|
|
98
98
|
"axios": "^1.18.0",
|
|
99
99
|
"cross-spawn": "^7.0.6",
|
|
100
100
|
"form-data": "^4.0.6",
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 已迁移到平台 seam:`src/platform/terminator-posix.ts`(design.md §3.2)。
|
|
3
|
-
*
|
|
4
|
-
* 本文件只保留 re-export,使既有调用方(bash runner、jsonrpc transport 等)
|
|
5
|
-
* 与既有测试不受影响;M2 调用点迁移时逐个换成 `createTerminator()` 后删除。
|
|
6
|
-
*/
|
|
7
|
-
export { ProcessStopper } from '../../platform/terminator-posix.js';
|
|
8
|
-
//# sourceMappingURL=process-stopper.js.map
|
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fake DSH Web Host HTTP server for agent-dsh tests.
|
|
3
|
-
*
|
|
4
|
-
* Mimics the real DSH wire protocol (deepseek-harness apiproxy handler.ts):
|
|
5
|
-
* - POST /api/<method>: body {type:'client-request',rpcId,method,payload},
|
|
6
|
-
* response {type:'server-response',rpcId,result:{ok:true,value}|{ok:false,error}}.
|
|
7
|
-
* - SSE GET /api/events.mux: frames `data: {type:'server-request',rpcId,
|
|
8
|
-
* method:<frame.type>, payload:<frame>}\n\n`.
|
|
9
|
-
*
|
|
10
|
-
* The mux frame queue is consumed ACROSS connections (shared pointer): the
|
|
11
|
-
* first connection drains a prefix then closes; a reconnect drains the rest —
|
|
12
|
-
* which is exactly how a server that drops mid-turn is modelled. Unary
|
|
13
|
-
* handlers and the history value are registered by the test.
|
|
14
|
-
*/
|
|
15
|
-
import { type Server } from 'node:http';
|
|
16
|
-
export interface FakeRpcResult {
|
|
17
|
-
ok: true;
|
|
18
|
-
value: unknown;
|
|
19
|
-
}
|
|
20
|
-
export interface FakeRpcError {
|
|
21
|
-
ok: false;
|
|
22
|
-
error: {
|
|
23
|
-
code: string;
|
|
24
|
-
message: string;
|
|
25
|
-
details?: Record<string, unknown>;
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
export type FakeUnaryHandler = (payload: unknown, rpcId: string) => FakeRpcResult | FakeRpcError;
|
|
29
|
-
export interface DshServerRequestRecord {
|
|
30
|
-
method: string;
|
|
31
|
-
rpcId: string;
|
|
32
|
-
payload: unknown;
|
|
33
|
-
}
|
|
34
|
-
export declare class FakeDshServer {
|
|
35
|
-
readonly server: Server;
|
|
36
|
-
private readonly unaryHandlers;
|
|
37
|
-
private muxFrames;
|
|
38
|
-
private historyValue;
|
|
39
|
-
private port;
|
|
40
|
-
private closedConnections;
|
|
41
|
-
readonly requests: DshServerRequestRecord[];
|
|
42
|
-
constructor();
|
|
43
|
-
get baseUrl(): string;
|
|
44
|
-
get connectionCount(): number;
|
|
45
|
-
start(): Promise<void>;
|
|
46
|
-
stop(): Promise<void>;
|
|
47
|
-
register(method: string, handler: FakeUnaryHandler): void;
|
|
48
|
-
setMuxFrames(frames: Array<object | {
|
|
49
|
-
close: true;
|
|
50
|
-
}>): void;
|
|
51
|
-
setHistoryValue(value: unknown): void;
|
|
52
|
-
/** Synchronous unary dispatch (no HTTP) — used by tests that inject the
|
|
53
|
-
* reader's sync transport to avoid the spawnSync-in-process deadlock. */
|
|
54
|
-
dispatchSync(method: string, payload: unknown, rpcId?: string): {
|
|
55
|
-
type: string;
|
|
56
|
-
rpcId: string;
|
|
57
|
-
result: FakeRpcResult | FakeRpcError;
|
|
58
|
-
};
|
|
59
|
-
private handleMux;
|
|
60
|
-
}
|
|
@@ -1,156 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fake DSH Web Host HTTP server for agent-dsh tests.
|
|
3
|
-
*
|
|
4
|
-
* Mimics the real DSH wire protocol (deepseek-harness apiproxy handler.ts):
|
|
5
|
-
* - POST /api/<method>: body {type:'client-request',rpcId,method,payload},
|
|
6
|
-
* response {type:'server-response',rpcId,result:{ok:true,value}|{ok:false,error}}.
|
|
7
|
-
* - SSE GET /api/events.mux: frames `data: {type:'server-request',rpcId,
|
|
8
|
-
* method:<frame.type>, payload:<frame>}\n\n`.
|
|
9
|
-
*
|
|
10
|
-
* The mux frame queue is consumed ACROSS connections (shared pointer): the
|
|
11
|
-
* first connection drains a prefix then closes; a reconnect drains the rest —
|
|
12
|
-
* which is exactly how a server that drops mid-turn is modelled. Unary
|
|
13
|
-
* handlers and the history value are registered by the test.
|
|
14
|
-
*/
|
|
15
|
-
import { createServer } from 'node:http';
|
|
16
|
-
import { randomUUID } from 'node:crypto';
|
|
17
|
-
export class FakeDshServer {
|
|
18
|
-
server;
|
|
19
|
-
unaryHandlers = new Map();
|
|
20
|
-
muxFrames = [];
|
|
21
|
-
historyValue = { events: [] };
|
|
22
|
-
port = 0;
|
|
23
|
-
closedConnections = 0;
|
|
24
|
-
requests = [];
|
|
25
|
-
constructor() {
|
|
26
|
-
this.server = createServer((req, res) => {
|
|
27
|
-
const url = req.url ?? '';
|
|
28
|
-
const path = url.split('?')[0];
|
|
29
|
-
if (req.method === 'GET' && path === '/api/events.mux') {
|
|
30
|
-
this.closedConnections++;
|
|
31
|
-
this.handleMux(res);
|
|
32
|
-
return;
|
|
33
|
-
}
|
|
34
|
-
if (req.method !== 'POST' || !path.startsWith('/api/')) {
|
|
35
|
-
res.writeHead(404);
|
|
36
|
-
res.end('not found');
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
let raw = '';
|
|
40
|
-
req.on('data', (c) => (raw += c));
|
|
41
|
-
req.on('end', () => {
|
|
42
|
-
let body = {};
|
|
43
|
-
try {
|
|
44
|
-
body = JSON.parse(raw);
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
res.writeHead(400);
|
|
48
|
-
res.end('bad request');
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
const method = path.slice('/api/'.length);
|
|
52
|
-
if (body.type !== 'client-request' || !body.rpcId || body.method !== method) {
|
|
53
|
-
res.writeHead(400);
|
|
54
|
-
res.end('bad request');
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
const rpcId = body.rpcId;
|
|
58
|
-
this.requests.push({ method, rpcId, payload: body.payload });
|
|
59
|
-
const handler = this.unaryHandlers.get(method);
|
|
60
|
-
let result;
|
|
61
|
-
if (handler) {
|
|
62
|
-
result = handler(body.payload, rpcId);
|
|
63
|
-
}
|
|
64
|
-
else if (method === 'session.history') {
|
|
65
|
-
result = { ok: true, value: this.historyValue };
|
|
66
|
-
}
|
|
67
|
-
else if (method === 'session.list') {
|
|
68
|
-
result = { ok: true, value: { items: [] } };
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
result = { ok: true, value: {} };
|
|
72
|
-
}
|
|
73
|
-
res.writeHead(200, { 'content-type': 'application/json' });
|
|
74
|
-
res.end(JSON.stringify({ type: 'server-response', rpcId, result }));
|
|
75
|
-
});
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
get baseUrl() {
|
|
79
|
-
return `http://127.0.0.1:${this.port}`;
|
|
80
|
-
}
|
|
81
|
-
get connectionCount() {
|
|
82
|
-
return this.closedConnections;
|
|
83
|
-
}
|
|
84
|
-
async start() {
|
|
85
|
-
await new Promise((resolve) => this.server.listen(0, '127.0.0.1', resolve));
|
|
86
|
-
const addr = this.server.address();
|
|
87
|
-
this.port = addr.port;
|
|
88
|
-
}
|
|
89
|
-
async stop() {
|
|
90
|
-
await new Promise((resolve) => this.server.close(() => resolve()));
|
|
91
|
-
}
|
|
92
|
-
register(method, handler) {
|
|
93
|
-
this.unaryHandlers.set(method, handler);
|
|
94
|
-
}
|
|
95
|
-
setMuxFrames(frames) {
|
|
96
|
-
this.muxFrames = frames;
|
|
97
|
-
}
|
|
98
|
-
setHistoryValue(value) {
|
|
99
|
-
this.historyValue = value;
|
|
100
|
-
}
|
|
101
|
-
/** Synchronous unary dispatch (no HTTP) — used by tests that inject the
|
|
102
|
-
* reader's sync transport to avoid the spawnSync-in-process deadlock. */
|
|
103
|
-
dispatchSync(method, payload, rpcId = `rpc-${randomUUID()}`) {
|
|
104
|
-
this.requests.push({ method, rpcId, payload });
|
|
105
|
-
const handler = this.unaryHandlers.get(method);
|
|
106
|
-
let result;
|
|
107
|
-
if (handler) {
|
|
108
|
-
result = handler(payload, rpcId);
|
|
109
|
-
}
|
|
110
|
-
else if (method === 'session.history') {
|
|
111
|
-
result = { ok: true, value: this.historyValue };
|
|
112
|
-
}
|
|
113
|
-
else if (method === 'session.list') {
|
|
114
|
-
result = { ok: true, value: { items: [] } };
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
117
|
-
result = { ok: true, value: {} };
|
|
118
|
-
}
|
|
119
|
-
return { type: 'server-response', rpcId, result };
|
|
120
|
-
}
|
|
121
|
-
handleMux(res) {
|
|
122
|
-
res.writeHead(200, {
|
|
123
|
-
'content-type': 'text/event-stream',
|
|
124
|
-
'cache-control': 'no-cache',
|
|
125
|
-
connection: 'keep-alive',
|
|
126
|
-
});
|
|
127
|
-
// Drain the SHARED script (shift): the first connection consumes a prefix
|
|
128
|
-
// then closes (a {close:true} marker or an exhausted queue); a reconnect
|
|
129
|
-
// continues from where it left off — the natural model of a mid-turn drop.
|
|
130
|
-
const timer = setInterval(() => {
|
|
131
|
-
const item = this.muxFrames.shift();
|
|
132
|
-
if (item === undefined) {
|
|
133
|
-
clearInterval(timer);
|
|
134
|
-
res.end();
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
if ('close' in item && item.close === true) {
|
|
138
|
-
clearInterval(timer);
|
|
139
|
-
res.end();
|
|
140
|
-
return;
|
|
141
|
-
}
|
|
142
|
-
const frame = item;
|
|
143
|
-
res.write(`data: ${JSON.stringify({
|
|
144
|
-
type: 'server-request',
|
|
145
|
-
rpcId: `mux-${randomUUID()}`,
|
|
146
|
-
method: frame.type,
|
|
147
|
-
payload: frame,
|
|
148
|
-
})}\n\n`);
|
|
149
|
-
}, 5);
|
|
150
|
-
reqClose(res, timer);
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
function reqClose(res, timer) {
|
|
154
|
-
res.on('close', () => clearInterval(timer));
|
|
155
|
-
}
|
|
156
|
-
//# sourceMappingURL=fake-dsh-server.js.map
|