lark-remote 0.5.18 → 0.5.20
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 +32 -0
- package/dist/bridge/approval-actions.d.ts +1 -1
- package/dist/bridge/approval-actions.js +1 -1
- package/dist/bridge/bash-run.d.ts +71 -0
- package/dist/bridge/bash-run.js +217 -0
- package/dist/bridge/compact.d.ts +155 -0
- package/dist/bridge/compact.js +450 -0
- package/dist/bridge/final-usage.js +4 -4
- package/dist/bridge/inbound-media.js +1 -1
- package/dist/bridge/index.d.ts +120 -119
- package/dist/bridge/index.js +257 -695
- package/dist/bridge/job-watch.d.ts +68 -0
- package/dist/bridge/job-watch.js +146 -0
- package/dist/bridge/queue-manager.d.ts +3 -3
- package/dist/bridge/queue-manager.js +10 -29
- package/dist/card/approval-render.d.ts +2 -2
- package/dist/card/approval-render.js +109 -97
- package/dist/card/bash-card-session.js +2 -0
- package/dist/card/card-budget.js +1 -1
- package/dist/card/card-helpers.d.ts +3 -3
- package/dist/card/card-helpers.js +6 -6
- package/dist/card/card-session.d.ts +17 -0
- package/dist/card/card-session.js +35 -0
- package/dist/card/final-answer-file.js +1 -1
- package/dist/card/run-card-session.js +2 -0
- package/dist/card/run-renderer.d.ts +3 -3
- package/dist/card/run-renderer.js +77 -102
- package/dist/card/run-state.d.ts +35 -8
- package/dist/card/run-state.js +117 -90
- package/dist/card/tool-render.d.ts +1 -1
- package/dist/card/tool-render.js +1 -1
- package/dist/card/usage-format.d.ts +12 -0
- package/dist/card/usage-format.js +8 -3
- package/dist/cli.js +15 -3
- package/dist/clone.js +2 -2
- package/dist/config/codex-config.js +130 -106
- package/dist/config/index.d.ts +16 -2
- package/dist/config/index.js +23 -8
- package/dist/connector/index.js +6 -6
- package/dist/error-classification.js +3 -2
- package/dist/inbound/placeholder.d.ts +1 -1
- package/dist/inbound/placeholder.js +1 -1
- package/dist/index.js +203 -133
- package/dist/logger/index.js +6 -6
- package/dist/message-handlers.d.ts +0 -5
- package/dist/message-handlers.js +4 -2
- package/dist/model-catalog.d.ts +2 -2
- package/dist/model-catalog.js +3 -8
- package/dist/platform/identity.js +1 -1
- package/dist/platform/path.d.ts +1 -1
- package/dist/platform/path.js +1 -1
- package/dist/platform/sleep-blocker.js +1 -1
- package/dist/platform/terminator-win32.js +2 -1
- package/dist/router/commands/resume.js +2 -20
- package/dist/router/config/dsh.d.ts +4 -8
- package/dist/router/config/dsh.js +3 -10
- package/dist/router/config/kimi.js +1 -1
- package/dist/router/config/types.d.ts +1 -1
- package/dist/router/config-diff.js +1 -1
- package/dist/router/index.d.ts +56 -11
- package/dist/router/index.js +222 -202
- package/dist/runner/claude/session.d.ts +1 -1
- package/dist/runner/claude/session.js +6 -4
- package/dist/runner/codex/app-server/runner.d.ts +9 -2
- package/dist/runner/codex/app-server/runner.js +30 -25
- package/dist/runner/codex/app-server/translator.d.ts +0 -5
- package/dist/runner/codex/app-server/translator.js +4 -17
- package/dist/runner/common/acp/acp-approval.d.ts +1 -1
- package/dist/runner/common/acp/acp-approval.js +1 -1
- package/dist/runner/common/acp/base-acp-runner.d.ts +5 -2
- package/dist/runner/common/acp/base-acp-runner.js +26 -65
- package/dist/runner/common/acp/base-translator.d.ts +12 -2
- package/dist/runner/common/acp/base-translator.js +31 -9
- package/dist/runner/common/acp/protocol-types.d.ts +1 -1
- package/dist/runner/common/acp/protocol-types.js +2 -2
- package/dist/runner/common/connection-based-runner.d.ts +32 -1
- package/dist/runner/common/connection-based-runner.js +32 -2
- package/dist/runner/common/jsonrpc/connection-manager.d.ts +2 -2
- package/dist/runner/common/jsonrpc/connection-manager.js +4 -4
- package/dist/runner/common/spawning-runner.d.ts +1 -1
- package/dist/runner/common/spawning-runner.js +1 -1
- package/dist/runner/dsh/catalog-probe.d.ts +17 -0
- package/dist/runner/dsh/catalog-probe.js +10 -0
- package/dist/runner/dsh/client.d.ts +13 -8
- package/dist/runner/dsh/client.js +23 -15
- package/dist/runner/dsh/exit-cancel.d.ts +2 -2
- package/dist/runner/dsh/exit-cancel.js +2 -2
- package/dist/runner/dsh/inflight.d.ts +1 -1
- package/dist/runner/dsh/inflight.js +1 -1
- package/dist/runner/dsh/runner.d.ts +48 -2
- package/dist/runner/dsh/runner.js +288 -199
- package/dist/runner/dsh/translator.d.ts +1 -1
- package/dist/runner/dsh/translator.js +1 -1
- package/dist/runner/index.d.ts +1 -1
- package/dist/runner/kimi/acp/runner.d.ts +1 -2
- package/dist/runner/kimi/acp/runner.js +1 -2
- package/dist/runner/kimi/acp/translator.d.ts +1 -1
- package/dist/runner/kimi/acp/translator.js +3 -4
- package/dist/runner/opencode/acp/runner.d.ts +2 -2
- package/dist/runner/opencode/acp/runner.js +4 -4
- package/dist/runner/pi/rpc/protocol-types.d.ts +1 -1
- package/dist/runner/pi/rpc/protocol-types.js +1 -1
- package/dist/runner/pi/rpc/runner.d.ts +3 -0
- package/dist/runner/pi/rpc/runner.js +27 -42
- package/dist/runner/types.d.ts +25 -8
- package/dist/runner/types.js +1 -1
- package/dist/session/claude/sessions.d.ts +45 -0
- package/dist/session/claude/sessions.js +97 -71
- package/dist/session/codex/rollout-reader.js +4 -4
- package/dist/session/common/throw-session-list-error.d.ts +10 -0
- package/dist/session/common/throw-session-list-error.js +16 -0
- package/dist/session/dsh/sessions.js +11 -5
- package/dist/session/kimi/sessions.d.ts +40 -1
- package/dist/session/kimi/sessions.js +244 -192
- package/dist/session/opencode/sessions.d.ts +153 -1
- package/dist/session/opencode/sessions.js +354 -321
- package/dist/session/pi/sessions.js +4 -5
- package/dist/session/session-store.d.ts +1 -1
- package/dist/session/session-store.js +1 -1
- package/dist/update/version-check.js +2 -1
- package/dist/workspace/index.d.ts +1 -1
- package/dist/workspace/index.js +1 -1
- package/package.json +1 -1
|
@@ -432,41 +432,6 @@ export class DshRunner {
|
|
|
432
432
|
yield interruptedResult(sessionId);
|
|
433
433
|
return;
|
|
434
434
|
}
|
|
435
|
-
// provider/模型/推理强度对齐:会话首次 run 时 selectModel 一次
|
|
436
|
-
// (该调用同时写服务端全局默认,故不重复写);同一 session 后续 run
|
|
437
|
-
// 跳过。显式配置 provider 时失败必须 fail closed:静默回退到其他
|
|
438
|
-
// provider 会让用户以为切换成功,实际模型与账单都错。
|
|
439
|
-
if ((this.configuredModel || this.configuredProvider) &&
|
|
440
|
-
!this.alignedSessions.has(sessionId)) {
|
|
441
|
-
try {
|
|
442
|
-
const selection = await this.resolveModelSelection(abort.signal);
|
|
443
|
-
await this.client.selectModel({ sessionId, ...selection }, { signal: abort.signal });
|
|
444
|
-
this.alignedSessions.add(sessionId);
|
|
445
|
-
}
|
|
446
|
-
catch (err) {
|
|
447
|
-
if (abort.signal.aborted) {
|
|
448
|
-
getLogger().info(`[dsh] run stopped during selectModel session=${sessionId}`);
|
|
449
|
-
terminalEmitted = true;
|
|
450
|
-
yield interruptedResult(sessionId);
|
|
451
|
-
return;
|
|
452
|
-
}
|
|
453
|
-
if (this.configuredProvider) {
|
|
454
|
-
throw new Error(`Provider "${this.configuredProvider}" 的模型对齐失败:${err.message}。` +
|
|
455
|
-
'请在 DSH Web UI → 设置 → Models 检查该 Provider 的凭据与模型目录', { cause: err });
|
|
456
|
-
}
|
|
457
|
-
getLogger().warn(`[dsh] selectModel failed for session ${sessionId} (run continues): ${err.message}`);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
// 权限预设对齐:与模型对齐同语义,同一 session 只执行一次
|
|
461
|
-
// `/permission <preset>`。失败必须 fail closed——用户明确选了安全策略,
|
|
462
|
-
// 不能在错误的权限下继续跑 prompt。
|
|
463
|
-
await this.alignPermissionPreset(sessionId, abort.signal);
|
|
464
|
-
// Abort 可能在上述 unary 完成与检查之间到达:不进入 prompt/turn。
|
|
465
|
-
if (abort.signal.aborted) {
|
|
466
|
-
terminalEmitted = true;
|
|
467
|
-
yield interruptedResult(sessionId);
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
435
|
yield {
|
|
471
436
|
type: 'system',
|
|
472
437
|
subtype: 'init',
|
|
@@ -474,24 +439,18 @@ export class DshRunner {
|
|
|
474
439
|
cwd: opts.cwd,
|
|
475
440
|
model: this.configuredModel ?? 'DSH',
|
|
476
441
|
};
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
// 只有确实存在 goal 才产事件:没有 goal 的会话保持事件序列不变(少一次
|
|
487
|
-
// 渲染,也不让"读不到"变成一张多出来的卡片事件)。
|
|
488
|
-
if (seedGoal.goal)
|
|
489
|
-
yield goalStateEvent(seedGoal.goal, true);
|
|
442
|
+
// 准备阶段(模型对齐 + 权限预设对齐 + goal seed + abort 检查)抽到
|
|
443
|
+
// prepareTurn:对齐失败 fail closed(显式 provider 时 throw,由外层 catch
|
|
444
|
+
// 转 error result);被 stop 时返回 interrupted 形态,这里统一 yield
|
|
445
|
+
// interrupted result 收尾。
|
|
446
|
+
const prepared = await this.prepareTurn(sessionId, abort);
|
|
447
|
+
if (!prepared.ok) {
|
|
448
|
+
terminalEmitted = true;
|
|
449
|
+
yield interruptedResult(sessionId);
|
|
450
|
+
return;
|
|
490
451
|
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
// 「没有 goal」写成「已完成」——正是 GoalStateEvent 契约禁止的推断。
|
|
494
|
-
yield goalStateEvent(null, false);
|
|
452
|
+
for (const goalEvent of prepared.goalEvents) {
|
|
453
|
+
yield goalEvent;
|
|
495
454
|
}
|
|
496
455
|
try {
|
|
497
456
|
const submitted = await this.submitPrompt(sessionId, opts.cwd, message, abort.signal);
|
|
@@ -504,7 +463,7 @@ export class DshRunner {
|
|
|
504
463
|
return;
|
|
505
464
|
}
|
|
506
465
|
try {
|
|
507
|
-
for await (const ev of this.runTurn(sessionId, opts.cwd, submitted.prePromptSeq, promptRequestId, abort, { last: lastGoal, known:
|
|
466
|
+
for await (const ev of this.runTurn(sessionId, opts.cwd, submitted.prePromptSeq, promptRequestId, abort, { last: prepared.lastGoal, known: prepared.goalKnown })) {
|
|
508
467
|
yield ev;
|
|
509
468
|
if (ev.type === 'result') {
|
|
510
469
|
terminalEmitted = true;
|
|
@@ -573,10 +532,80 @@ export class DshRunner {
|
|
|
573
532
|
};
|
|
574
533
|
}
|
|
575
534
|
}
|
|
535
|
+
/**
|
|
536
|
+
* 准备一个 turn(run() 的准备阶段):模型对齐 + 权限预设对齐 + goal seed 探测。
|
|
537
|
+
*
|
|
538
|
+
* 红线(逐条保留):
|
|
539
|
+
* - 对齐失败 fail closed:显式配置 provider 时 selectModel 失败必须 throw
|
|
540
|
+
* (静默回退到其他 provider 会让用户以为切换成功,实际模型与账单都错);
|
|
541
|
+
* 权限预设失败由 alignPermissionPreset throw(用户明确选了安全策略,不能在
|
|
542
|
+
* 错误的权限下继续跑 prompt)。
|
|
543
|
+
* - 准备期间被 stop(abort)→ 返回 { ok:false },由调用方 yield interrupted
|
|
544
|
+
* result 收尾。
|
|
545
|
+
* - goal seed 只探测不推断:读不到按 unknown 上报,不冒充「没有 goal」。
|
|
546
|
+
*/
|
|
547
|
+
async prepareTurn(sessionId, abort) {
|
|
548
|
+
// provider/模型/推理强度对齐:会话首次 run 时 selectModel 一次
|
|
549
|
+
// (该调用同时写服务端全局默认,故不重复写);同一 session 后续 run
|
|
550
|
+
// 跳过。显式配置 provider 时失败必须 fail closed:静默回退到其他
|
|
551
|
+
// provider 会让用户以为切换成功,实际模型与账单都错。
|
|
552
|
+
if ((this.configuredModel || this.configuredProvider) && !this.alignedSessions.has(sessionId)) {
|
|
553
|
+
try {
|
|
554
|
+
const selection = await this.resolveModelSelection(abort.signal);
|
|
555
|
+
await this.client.selectModel({ sessionId, ...selection }, { signal: abort.signal });
|
|
556
|
+
this.alignedSessions.add(sessionId);
|
|
557
|
+
}
|
|
558
|
+
catch (err) {
|
|
559
|
+
if (abort.signal.aborted) {
|
|
560
|
+
getLogger().info(`[dsh] run stopped during selectModel session=${sessionId}`);
|
|
561
|
+
return { ok: false };
|
|
562
|
+
}
|
|
563
|
+
if (this.configuredProvider) {
|
|
564
|
+
throw new Error(`Provider "${this.configuredProvider}" 的模型对齐失败:${err.message}。` +
|
|
565
|
+
'请在 DSH Web UI → 设置 → Models 检查该 Provider 的凭据与模型目录', { cause: err });
|
|
566
|
+
}
|
|
567
|
+
getLogger().warn(`[dsh] selectModel failed for session ${sessionId} (run continues): ${err.message}`);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
// 权限预设对齐:与模型对齐同语义,同一 session 只执行一次
|
|
571
|
+
// `/permission <preset>`。失败必须 fail closed——用户明确选了安全策略,
|
|
572
|
+
// 不能在错误的权限下继续跑 prompt。
|
|
573
|
+
await this.alignPermissionPreset(sessionId, abort.signal);
|
|
574
|
+
// Abort 可能在上述 unary 完成与检查之间到达:不进入 prompt/turn。
|
|
575
|
+
if (abort.signal.aborted) {
|
|
576
|
+
return { ok: false };
|
|
577
|
+
}
|
|
578
|
+
// Goal seed:卡片从第一帧就知道这个会话有没有长期目标。放在 init 之后、
|
|
579
|
+
// prompt 之前——用户发消息时若是 queued,卡片会长时间停在「运行中」,
|
|
580
|
+
// 这期间"目标是什么、第几轮"是唯一能解释"为什么还不停"的信息。
|
|
581
|
+
const seedGoal = await this.client.goalState(sessionId, {
|
|
582
|
+
signal: abort.signal,
|
|
583
|
+
timeoutMs: GOAL_PROBE_TIMEOUT_MS,
|
|
584
|
+
});
|
|
585
|
+
const lastGoal = seedGoal.known ? seedGoal.goal : undefined;
|
|
586
|
+
const goalEvents = [];
|
|
587
|
+
if (seedGoal.known) {
|
|
588
|
+
// 只有确实存在 goal 才产事件:没有 goal 的会话保持事件序列不变(少一次
|
|
589
|
+
// 渲染,也不让"读不到"变成一张多出来的卡片事件)。
|
|
590
|
+
if (seedGoal.goal)
|
|
591
|
+
goalEvents.push(goalStateEvent(seedGoal.goal, true));
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
// 读不到 ≠ 没有目标。必须显式把 unknown 交给卡片,否则收尾只能按
|
|
595
|
+
// 「没有 goal」写成「已完成」——正是 GoalStateEvent 契约禁止的推断。
|
|
596
|
+
goalEvents.push(goalStateEvent(null, false));
|
|
597
|
+
}
|
|
598
|
+
return { ok: true, lastGoal, goalKnown: seedGoal.known, goalEvents };
|
|
599
|
+
}
|
|
576
600
|
/**
|
|
577
601
|
* Consume one turn from both transports. `session/follow` owns transcript
|
|
578
602
|
* events; `$events` owns the answerable approval waterfall. The merged
|
|
579
603
|
* stream keeps either transport from starving the other.
|
|
604
|
+
*
|
|
605
|
+
* 事件源分派:projection(goal + 状态条)→ handleProjectionItem;
|
|
606
|
+
* $events waterfall(审批/提问)→ handleApprovalWaterfallItem;
|
|
607
|
+
* assistant-stream 直转发(translator 持有本 turn 的 usage 累计状态);
|
|
608
|
+
* session/follow 事件 → handleSessionEvent(含 result 收尾的 jobs + goal 复核)。
|
|
580
609
|
*/
|
|
581
610
|
async *runTurn(sessionId, cwd, prePromptSeq, promptRequestId, abort, goal) {
|
|
582
611
|
const streamAbort = new AbortController();
|
|
@@ -600,165 +629,41 @@ export class DshRunner {
|
|
|
600
629
|
for await (const item of mergeAsync(sources)) {
|
|
601
630
|
if (abort.signal.aborted)
|
|
602
631
|
return;
|
|
632
|
+
// 事件源 1:Host-wide 投影(goal + 状态条)。
|
|
603
633
|
if (item.kind === 'projection') {
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
if (item.sessionId !== sessionId)
|
|
607
|
-
continue;
|
|
608
|
-
// goal 是会话级长期目标,单独成事件:卡片要在 agent 创建目标的当下
|
|
609
|
-
// 就显示它(否则用户只会在一小时后看到「完成」才发现有个 40 轮任务)。
|
|
610
|
-
if (item.key === 'goal') {
|
|
611
|
-
const parsed = goalFromProjection(item.value);
|
|
612
|
-
if (parsed !== undefined && !sameGoal(parsed, goal.last)) {
|
|
613
|
-
goal.last = parsed;
|
|
614
|
-
yield goalStateEvent(parsed, true);
|
|
615
|
-
}
|
|
616
|
-
continue;
|
|
617
|
-
}
|
|
618
|
-
// 只在消费字段真的变了时才产出事件:投影帧本身按 key 去重推送,
|
|
619
|
-
// 这里再挡掉"值没变"的重复帧。不需要时间节流——卡片更新本来就有
|
|
620
|
-
// 100ms 合批窗口,与正文 delta 共用同一条节流路径。
|
|
621
|
-
if (!applyProjection(live, item.key, item.value))
|
|
622
|
-
continue;
|
|
623
|
-
yield statusEventOf(live);
|
|
624
|
-
continue;
|
|
625
|
-
}
|
|
626
|
-
if (item.kind === 'ready') {
|
|
627
|
-
this.approvalChannelAvailable = true;
|
|
628
|
-
continue;
|
|
629
|
-
}
|
|
630
|
-
if (item.kind === 'unavailable') {
|
|
631
|
-
this.approvalChannelAvailable = false;
|
|
632
|
-
getLogger().warn(`[dsh] approval channel unavailable: ${item.message}`);
|
|
633
|
-
continue;
|
|
634
|
-
}
|
|
635
|
-
if (item.kind === 'request') {
|
|
636
|
-
this.approvalTracker.onWaterfall({
|
|
637
|
-
eventId: item.eventId,
|
|
638
|
-
clientId: item.clientId,
|
|
639
|
-
...(item.callId ? { callId: item.callId } : {}),
|
|
640
|
-
toolName: item.toolName,
|
|
641
|
-
...(item.reason ? { reason: item.reason } : {}),
|
|
642
|
-
});
|
|
643
|
-
continue;
|
|
644
|
-
}
|
|
645
|
-
if (item.kind === 'question') {
|
|
646
|
-
// 同一 waterfall 在重连后会带新的 clientId 重投:覆盖写回,卡片的
|
|
647
|
-
// requestId(eventId)不变,coordinator 按重投语义刷新视图。
|
|
648
|
-
this.pendingQuestions.set(item.eventId, {
|
|
649
|
-
clientId: item.clientId,
|
|
650
|
-
questions: item.questions,
|
|
651
|
-
});
|
|
652
|
-
getLogger().info(`[dsh] user question surfaced eventId=${item.eventId} questions=${item.questions.length}`);
|
|
653
|
-
yield this.buildQuestionRequested(sessionId, item.eventId, item.questions);
|
|
654
|
-
continue;
|
|
655
|
-
}
|
|
656
|
-
if (item.kind === 'cancelled') {
|
|
657
|
-
// 提问与审批共用 cancel 帧:提问在别处(DSH Web UI)被回答或超时后
|
|
658
|
-
// 也要清掉飞书卡片,否则上面还留着可点但已无 responder 的问题区。
|
|
659
|
-
if (this.pendingQuestions.delete(item.eventId)) {
|
|
660
|
-
getLogger().info(`[dsh] user question resolved elsewhere eventId=${item.eventId}`);
|
|
661
|
-
yield this.approvalResolved(item.eventId);
|
|
662
|
-
continue;
|
|
634
|
+
for (const ev of this.handleProjectionItem(item, sessionId, goal, live)) {
|
|
635
|
+
yield ev;
|
|
663
636
|
}
|
|
664
|
-
const binding = this.approvalTracker.onCancelled(item.eventId);
|
|
665
|
-
if (binding)
|
|
666
|
-
yield this.approvalResolved(binding.audit.approvalId);
|
|
667
637
|
continue;
|
|
668
638
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
const
|
|
677
|
-
|
|
678
|
-
getLogger().warn(`[dsh] approval requested approvalId=${item.approvalId} tool=${item.toolName}; live channel unavailable — falling back to Web UI`);
|
|
679
|
-
yield this.approvalFallbackText(item.toolName);
|
|
680
|
-
continue;
|
|
639
|
+
// 事件源 2:$events 审批/提问 waterfall。
|
|
640
|
+
if (item.kind === 'ready' ||
|
|
641
|
+
item.kind === 'unavailable' ||
|
|
642
|
+
item.kind === 'request' ||
|
|
643
|
+
item.kind === 'question' ||
|
|
644
|
+
item.kind === 'cancelled' ||
|
|
645
|
+
item.kind === 'approval') {
|
|
646
|
+
for (const ev of this.handleApprovalWaterfallItem(item, sessionId, cwd, emittedApprovals)) {
|
|
647
|
+
yield ev;
|
|
681
648
|
}
|
|
682
|
-
if (emittedApprovals.has(item.approvalId))
|
|
683
|
-
continue;
|
|
684
|
-
emittedApprovals.add(item.approvalId);
|
|
685
|
-
yield this.buildApprovalRequested(sessionId, audit, binding, cwd);
|
|
686
649
|
continue;
|
|
687
650
|
}
|
|
651
|
+
// 事件源 3a:assistant 流式帧(translator 持有本 turn 的 usage 累计状态,
|
|
652
|
+
// 必须与 3b 共用同一实例)。
|
|
688
653
|
if (item.kind === 'assistant-stream') {
|
|
689
654
|
for (const ev of translator.streamFrameToAgentEvents(item.frame, sessionId)) {
|
|
690
655
|
yield ev;
|
|
691
656
|
}
|
|
692
657
|
continue;
|
|
693
658
|
}
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
if (parsed !== undefined && !sameGoal(parsed, goal.last)) {
|
|
700
|
-
goal.last = parsed;
|
|
701
|
-
yield goalStateEvent(parsed, true);
|
|
702
|
-
}
|
|
703
|
-
continue;
|
|
704
|
-
}
|
|
705
|
-
if (event.type === 'tool/call') {
|
|
706
|
-
const data = event.data;
|
|
707
|
-
if (data.callId && data.name) {
|
|
708
|
-
this.toolCalls.set(data.callId, {
|
|
709
|
-
name: data.name,
|
|
710
|
-
arguments: data.arguments ?? '',
|
|
711
|
-
...(data.turn === undefined ? {} : { turn: data.turn }),
|
|
712
|
-
});
|
|
713
|
-
if (this.toolCalls.size > 100) {
|
|
714
|
-
const oldest = this.toolCalls.keys().next().value;
|
|
715
|
-
if (oldest !== undefined)
|
|
716
|
-
this.toolCalls.delete(oldest);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
// 记账后继续往下走:tool/call 同时是卡片工具行的来源(translator 翻
|
|
720
|
-
// 译成 assistant.tool_use)。早期版本在这里 `continue`,把调用轨迹整个
|
|
721
|
-
// 吞掉——卡片因此只有正文、没有任何工具行。
|
|
722
|
-
}
|
|
723
|
-
if (event.type === 'approval/decided') {
|
|
724
|
-
const data = event.data;
|
|
725
|
-
if (!data.id)
|
|
726
|
-
continue;
|
|
727
|
-
this.approvalTracker.onDecided(data.id);
|
|
728
|
-
yield this.approvalResolved(data.id);
|
|
729
|
-
continue;
|
|
730
|
-
}
|
|
731
|
-
for (const ev of translator.eventToAgentEvents(event, sessionId)) {
|
|
732
|
-
if (ev.type === 'result') {
|
|
733
|
-
if (!abort.signal.aborted) {
|
|
734
|
-
for (const jobsEvent of await this.collectBackgroundJobs(sessionId, abort.signal)) {
|
|
735
|
-
yield jobsEvent;
|
|
736
|
-
}
|
|
737
|
-
// 收尾复核:这一轮结束时 goal 是否仍 active,决定卡片能不能收尾。
|
|
738
|
-
// 必须现读宿主,不能沿用过程中的最后一次观测——goal 可能在本轮
|
|
739
|
-
// 最后一个 step 才被 complete,也可能刚被 driver 置为 blocked。
|
|
740
|
-
const finalGoal = await this.client.goalState(sessionId, {
|
|
741
|
-
signal,
|
|
742
|
-
timeoutMs: GOAL_PROBE_TIMEOUT_MS,
|
|
743
|
-
});
|
|
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;
|
|
754
|
-
goal.last = finalGoal.goal;
|
|
755
|
-
yield goalStateEvent(finalGoal.goal, true);
|
|
756
|
-
}
|
|
757
|
-
}
|
|
758
|
-
yield attachContextLimit(ev, live.contextLimit);
|
|
759
|
-
return;
|
|
760
|
-
}
|
|
659
|
+
// 事件源 3b:session/follow 事件(正文/工具行/result 收尾,含 jobs +
|
|
660
|
+
// goal 复核)。result 之后的收尾事件由 handler 按序返回,见到 result
|
|
661
|
+
// 即结束本 turn(与原 yield-then-return 逐字等价)。
|
|
662
|
+
const eventEvents = await this.handleSessionEvent(item.event, sessionId, translator, goal, live, abort, signal);
|
|
663
|
+
for (const ev of eventEvents) {
|
|
761
664
|
yield ev;
|
|
665
|
+
if (ev.type === 'result')
|
|
666
|
+
return;
|
|
762
667
|
}
|
|
763
668
|
}
|
|
764
669
|
}
|
|
@@ -769,6 +674,174 @@ export class DshRunner {
|
|
|
769
674
|
this.toolCalls.clear();
|
|
770
675
|
}
|
|
771
676
|
}
|
|
677
|
+
/**
|
|
678
|
+
* 事件源 handler:Host-wide 投影(goal + 状态条)。
|
|
679
|
+
* 返回要回放的事件(可能为空——未变化/非本 session 的帧不产事件)。
|
|
680
|
+
*/
|
|
681
|
+
handleProjectionItem(item, sessionId, goal, live) {
|
|
682
|
+
// 全 Host 流:只认本 session。未知 key 直接忽略(投影是能力发现式
|
|
683
|
+
// 扩展的,不能因为多了个 key 就报错)。
|
|
684
|
+
if (item.sessionId !== sessionId)
|
|
685
|
+
return [];
|
|
686
|
+
// goal 是会话级长期目标,单独成事件:卡片要在 agent 创建目标的当下
|
|
687
|
+
// 就显示它(否则用户只会在一小时后看到「完成」才发现有个 40 轮任务)。
|
|
688
|
+
if (item.key === 'goal') {
|
|
689
|
+
const parsed = goalFromProjection(item.value);
|
|
690
|
+
if (parsed !== undefined && !sameGoal(parsed, goal.last)) {
|
|
691
|
+
goal.last = parsed;
|
|
692
|
+
return [goalStateEvent(parsed, true)];
|
|
693
|
+
}
|
|
694
|
+
return [];
|
|
695
|
+
}
|
|
696
|
+
// 只在消费字段真的变了时才产出事件:投影帧本身按 key 去重推送,
|
|
697
|
+
// 这里再挡掉"值没变"的重复帧。不需要时间节流——卡片更新本来就有
|
|
698
|
+
// 100ms 合批窗口,与正文 delta 共用同一条节流路径。
|
|
699
|
+
if (!applyProjection(live, item.key, item.value))
|
|
700
|
+
return [];
|
|
701
|
+
return [statusEventOf(live)];
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* 事件源 handler:$events 审批/提问 waterfall(channel 状态、审批 waterfall
|
|
705
|
+
* 登记、提问卡、cancel 回流、审批卡)。
|
|
706
|
+
* 返回要回放的事件;channel 状态类帧(ready/unavailable/request)只改内部
|
|
707
|
+
* 状态、不产事件。
|
|
708
|
+
*/
|
|
709
|
+
handleApprovalWaterfallItem(item, sessionId, cwd, emittedApprovals) {
|
|
710
|
+
switch (item.kind) {
|
|
711
|
+
case 'ready':
|
|
712
|
+
this.approvalChannelAvailable = true;
|
|
713
|
+
return [];
|
|
714
|
+
case 'unavailable':
|
|
715
|
+
this.approvalChannelAvailable = false;
|
|
716
|
+
getLogger().warn(`[dsh] approval channel unavailable: ${item.message}`);
|
|
717
|
+
return [];
|
|
718
|
+
case 'request':
|
|
719
|
+
this.approvalTracker.onWaterfall({
|
|
720
|
+
eventId: item.eventId,
|
|
721
|
+
clientId: item.clientId,
|
|
722
|
+
...(item.callId ? { callId: item.callId } : {}),
|
|
723
|
+
toolName: item.toolName,
|
|
724
|
+
...(item.reason ? { reason: item.reason } : {}),
|
|
725
|
+
});
|
|
726
|
+
return [];
|
|
727
|
+
case 'question': {
|
|
728
|
+
// 同一 waterfall 在重连后会带新的 clientId 重投:覆盖写回,卡片的
|
|
729
|
+
// requestId(eventId)不变,coordinator 按重投语义刷新视图。
|
|
730
|
+
this.pendingQuestions.set(item.eventId, {
|
|
731
|
+
clientId: item.clientId,
|
|
732
|
+
questions: item.questions,
|
|
733
|
+
});
|
|
734
|
+
getLogger().info(`[dsh] user question surfaced eventId=${item.eventId} questions=${item.questions.length}`);
|
|
735
|
+
return [this.buildQuestionRequested(sessionId, item.eventId, item.questions)];
|
|
736
|
+
}
|
|
737
|
+
case 'cancelled': {
|
|
738
|
+
// 提问与审批共用 cancel 帧:提问在别处(DSH Web UI)被回答或超时后
|
|
739
|
+
// 也要清掉飞书卡片,否则上面还留着可点但已无 responder 的问题区。
|
|
740
|
+
if (this.pendingQuestions.delete(item.eventId)) {
|
|
741
|
+
getLogger().info(`[dsh] user question resolved elsewhere eventId=${item.eventId}`);
|
|
742
|
+
return [this.approvalResolved(item.eventId)];
|
|
743
|
+
}
|
|
744
|
+
const binding = this.approvalTracker.onCancelled(item.eventId);
|
|
745
|
+
return binding ? [this.approvalResolved(binding.audit.approvalId)] : [];
|
|
746
|
+
}
|
|
747
|
+
case 'approval': {
|
|
748
|
+
const audit = {
|
|
749
|
+
approvalId: item.approvalId,
|
|
750
|
+
toolName: item.toolName,
|
|
751
|
+
...(item.callId ? { callId: item.callId } : {}),
|
|
752
|
+
...(item.reason ? { reason: item.reason } : {}),
|
|
753
|
+
};
|
|
754
|
+
const binding = this.approvalTracker.onAuditAsked(audit);
|
|
755
|
+
if (!this.approvalChannelAvailable) {
|
|
756
|
+
getLogger().warn(`[dsh] approval requested approvalId=${item.approvalId} tool=${item.toolName}; live channel unavailable — falling back to Web UI`);
|
|
757
|
+
return [this.approvalFallbackText(item.toolName)];
|
|
758
|
+
}
|
|
759
|
+
if (emittedApprovals.has(item.approvalId))
|
|
760
|
+
return [];
|
|
761
|
+
emittedApprovals.add(item.approvalId);
|
|
762
|
+
return [this.buildApprovalRequested(sessionId, audit, binding, cwd)];
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
/**
|
|
767
|
+
* 事件源 handler:`session/follow` 会话事件。
|
|
768
|
+
* - goal/change:权威变更,不等投影直接更新;
|
|
769
|
+
* - tool/call:先记账(审批卡命令展示用)再落入 translator(卡片工具行来源);
|
|
770
|
+
* - approval/decided:审批回流,清审批卡;
|
|
771
|
+
* - 其余事件经 translator 翻译;result 事件先做收尾(后台任务名单 + goal
|
|
772
|
+
* 复核)再随 result 一起返回。
|
|
773
|
+
*/
|
|
774
|
+
async handleSessionEvent(event, sessionId, translator, goal, live, abort, signal) {
|
|
775
|
+
if (event.type === 'goal/change') {
|
|
776
|
+
// 权威变更(create/edit/pause/resume/complete/block/clear):不等投影
|
|
777
|
+
// 推送,直接按事件更新——agent 刚 create 的那一刻卡片就该显示目标。
|
|
778
|
+
const parsed = goalFromChange(event.data);
|
|
779
|
+
if (parsed !== undefined && !sameGoal(parsed, goal.last)) {
|
|
780
|
+
goal.last = parsed;
|
|
781
|
+
return [goalStateEvent(parsed, true)];
|
|
782
|
+
}
|
|
783
|
+
return [];
|
|
784
|
+
}
|
|
785
|
+
if (event.type === 'tool/call') {
|
|
786
|
+
const data = event.data;
|
|
787
|
+
if (data.callId && data.name) {
|
|
788
|
+
this.toolCalls.set(data.callId, {
|
|
789
|
+
name: data.name,
|
|
790
|
+
arguments: data.arguments ?? '',
|
|
791
|
+
...(data.turn === undefined ? {} : { turn: data.turn }),
|
|
792
|
+
});
|
|
793
|
+
if (this.toolCalls.size > 100) {
|
|
794
|
+
const oldest = this.toolCalls.keys().next().value;
|
|
795
|
+
if (oldest !== undefined)
|
|
796
|
+
this.toolCalls.delete(oldest);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
// 记账后继续往下走:tool/call 同时是卡片工具行的来源(translator 翻
|
|
800
|
+
// 译成 assistant.tool_use)。早期版本在这里 `continue`,把调用轨迹整个
|
|
801
|
+
// 吞掉——卡片因此只有正文、没有任何工具行。
|
|
802
|
+
}
|
|
803
|
+
else if (event.type === 'approval/decided') {
|
|
804
|
+
const data = event.data;
|
|
805
|
+
if (!data.id)
|
|
806
|
+
return [];
|
|
807
|
+
this.approvalTracker.onDecided(data.id);
|
|
808
|
+
return [this.approvalResolved(data.id)];
|
|
809
|
+
}
|
|
810
|
+
const events = [];
|
|
811
|
+
for (const ev of translator.eventToAgentEvents(event, sessionId)) {
|
|
812
|
+
if (ev.type === 'result') {
|
|
813
|
+
if (!abort.signal.aborted) {
|
|
814
|
+
for (const jobsEvent of await this.collectBackgroundJobs(sessionId, abort.signal)) {
|
|
815
|
+
events.push(jobsEvent);
|
|
816
|
+
}
|
|
817
|
+
// 收尾复核:这一轮结束时 goal 是否仍 active,决定卡片能不能收尾。
|
|
818
|
+
// 必须现读宿主,不能沿用过程中的最后一次观测——goal 可能在本轮
|
|
819
|
+
// 最后一个 step 才被 complete,也可能刚被 driver 置为 blocked。
|
|
820
|
+
const finalGoal = await this.client.goalState(sessionId, {
|
|
821
|
+
signal,
|
|
822
|
+
timeoutMs: GOAL_PROBE_TIMEOUT_MS,
|
|
823
|
+
});
|
|
824
|
+
if (!finalGoal.known) {
|
|
825
|
+
// 收尾读不到:seed 已报过 unknown 就不重复发,否则把状态降级
|
|
826
|
+
// 交给卡片(卡片据此不再宣布完成)。
|
|
827
|
+
if (goal.known) {
|
|
828
|
+
goal.known = false;
|
|
829
|
+
events.push(goalStateEvent(null, false));
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
else if (!goal.known || !sameGoal(finalGoal.goal, goal.last)) {
|
|
833
|
+
goal.known = true;
|
|
834
|
+
goal.last = finalGoal.goal;
|
|
835
|
+
events.push(goalStateEvent(finalGoal.goal, true));
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
events.push(attachContextLimit(ev, live.contextLimit));
|
|
839
|
+
return events;
|
|
840
|
+
}
|
|
841
|
+
events.push(ev);
|
|
842
|
+
}
|
|
843
|
+
return events;
|
|
844
|
+
}
|
|
772
845
|
/**
|
|
773
846
|
* 收尾时仍在跑的后台任务。
|
|
774
847
|
*
|
|
@@ -796,6 +869,22 @@ export class DshRunner {
|
|
|
796
869
|
},
|
|
797
870
|
];
|
|
798
871
|
}
|
|
872
|
+
/**
|
|
873
|
+
* 订阅本 session 的后台任务名单(跨 turn 生命周期)。
|
|
874
|
+
*
|
|
875
|
+
* 卡片收尾后仍要靠它把「任务结束了」推给用户:run 的事件流在 result 就关了,
|
|
876
|
+
* 而后台任务可能再跑半小时。名单是整份替换,消费方自己做 diff。
|
|
877
|
+
*/
|
|
878
|
+
async *watchJobs(sessionId, signal) {
|
|
879
|
+
for await (const rows of this.client.watchJobs(sessionId, signal)) {
|
|
880
|
+
yield rows.map((job) => ({
|
|
881
|
+
id: job.id,
|
|
882
|
+
label: job.label ?? job.id,
|
|
883
|
+
...(job.status === undefined ? {} : { status: job.status }),
|
|
884
|
+
...(job.progress === undefined ? {} : { progress: job.progress }),
|
|
885
|
+
}));
|
|
886
|
+
}
|
|
887
|
+
}
|
|
799
888
|
/** Kill one background job of this runner's session (card button path). */
|
|
800
889
|
async killJob(sessionId, jobId) {
|
|
801
890
|
return await this.client.jobKill(sessionId, jobId);
|
|
@@ -1393,7 +1482,7 @@ export class DshRunner {
|
|
|
1393
1482
|
*
|
|
1394
1483
|
* turn 跑在常驻共享的 `dsh web` host 里,不在本进程的子进程树上;所以
|
|
1395
1484
|
* "杀自己的子进程"这套退出清理对它无效,桥接一退出,turn 继续无人接收地跑
|
|
1396
|
-
*
|
|
1485
|
+
* (孤儿 turn 在桥接死后可能再跑数小时)。
|
|
1397
1486
|
* 这里复用进程级退出分发器,用同步 curl 发一次 `session/cancel`。
|
|
1398
1487
|
*/
|
|
1399
1488
|
registerExitHandlers() {
|
|
@@ -41,7 +41,7 @@ export declare class DshTranslator {
|
|
|
41
41
|
*
|
|
42
42
|
* live chunk 是逐 delta 拼接、会被状态层的 MAX_TEXT_CHARS 窗口截断;
|
|
43
43
|
* durable `assistant/message` 一次就到齐全文。收 turn 时把它挂在 result 上,
|
|
44
|
-
*
|
|
44
|
+
* 卡片才能渲染完整回答。
|
|
45
45
|
*/
|
|
46
46
|
private lastMessageText?;
|
|
47
47
|
/** Translate one live assistant-stream chunk into AgentEvents. */
|
|
@@ -39,7 +39,7 @@ export class DshTranslator {
|
|
|
39
39
|
*
|
|
40
40
|
* live chunk 是逐 delta 拼接、会被状态层的 MAX_TEXT_CHARS 窗口截断;
|
|
41
41
|
* durable `assistant/message` 一次就到齐全文。收 turn 时把它挂在 result 上,
|
|
42
|
-
*
|
|
42
|
+
* 卡片才能渲染完整回答。
|
|
43
43
|
*/
|
|
44
44
|
lastMessageText;
|
|
45
45
|
/** Translate one live assistant-stream chunk into AgentEvents. */
|
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, AgentRunner, TurnStartedEvent, TurnDiffEvent, FinalAnswerEvent, NoticeEvent, SessionInfoEvent, ToolHint, ApprovalView, ApprovalRequestedEvent, ApprovalResolvedEvent, GoalView, GoalStateEvent, } from './types.js';
|
|
18
|
+
export type { ResultEvent, PlanEvent, FileChangeEvent, AgentEvent, Runner, RunnerLifetime, AgentRunner, TurnStartedEvent, TurnDiffEvent, FinalAnswerEvent, NoticeEvent, SessionInfoEvent, ToolHint, ApprovalView, ApprovalRequestedEvent, ApprovalResolvedEvent, BackgroundJobRoster, BackgroundJobsEvent, AssistantEvent, RunStatusEvent, UserEvent, GoalView, GoalStateEvent, } from './types.js';
|
|
19
19
|
export { resolveAgentChoices } from './resolve-agent-choices.js';
|
|
20
20
|
export { syncAgentChoices } from './sync-agent-choices.js';
|
|
@@ -109,8 +109,7 @@ export declare class KimiAcpRunner extends BaseAcpRunner<KimiAcpTranslator> {
|
|
|
109
109
|
* before producing the result. There is NO total timeout — silence beyond
|
|
110
110
|
* compactIdleTimeoutMs yields 'unknown' (never a fake success). Keeps the
|
|
111
111
|
* connection alive while the background compaction finishes — otherwise
|
|
112
|
-
* dispose/exit kills it and the record never lands
|
|
113
|
-
* 2026-08-31 事故).
|
|
112
|
+
* dispose/exit kills it and the record never lands.
|
|
114
113
|
*/
|
|
115
114
|
protected compactBaseline(sessionId: string, opts: SpawnOptions): number;
|
|
116
115
|
protected onCompactPromptFired(promptPromise: Promise<SessionPromptResult>, translator: KimiAcpTranslator, sessionId: string, opts: SpawnOptions, baselineTotalCount: number): void;
|
|
@@ -292,8 +292,7 @@ export class KimiAcpRunner extends BaseAcpRunner {
|
|
|
292
292
|
* before producing the result. There is NO total timeout — silence beyond
|
|
293
293
|
* compactIdleTimeoutMs yields 'unknown' (never a fake success). Keeps the
|
|
294
294
|
* connection alive while the background compaction finishes — otherwise
|
|
295
|
-
* dispose/exit kills it and the record never lands
|
|
296
|
-
* 2026-08-31 事故).
|
|
295
|
+
* dispose/exit kills it and the record never lands.
|
|
297
296
|
*/
|
|
298
297
|
compactBaseline(sessionId, opts) {
|
|
299
298
|
const compactionReader = this.sessionReader.readCompactionRecords;
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* assistant deltas — see handleAgentMessageChunk), plus question elicitation.
|
|
10
10
|
*
|
|
11
11
|
* Wire envelope (source: kimi-code packages/acp-server/src/events-map.ts,
|
|
12
|
-
* verified
|
|
12
|
+
* verified against kimi 0.36.0):
|
|
13
13
|
* session/update params = {sessionId, update: {sessionUpdate: '<kind>', ...}}
|
|
14
14
|
* — the discriminator is `update.sessionUpdate`, NOT a nested `event.type`.
|
|
15
15
|
*
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
* assistant deltas — see handleAgentMessageChunk), plus question elicitation.
|
|
10
10
|
*
|
|
11
11
|
* Wire envelope (source: kimi-code packages/acp-server/src/events-map.ts,
|
|
12
|
-
* verified
|
|
12
|
+
* verified against kimi 0.36.0):
|
|
13
13
|
* session/update params = {sessionId, update: {sessionUpdate: '<kind>', ...}}
|
|
14
14
|
* — the discriminator is `update.sessionUpdate`, NOT a nested `event.type`.
|
|
15
15
|
*
|
|
@@ -69,8 +69,7 @@ export class KimiAcpTranslator extends BaseAcpTranslator {
|
|
|
69
69
|
// snapshot and emit it as turn_diff. The card reducer's turn_diff path
|
|
70
70
|
// REPLACES the block in place (snapshot semantics). Emitting the full
|
|
71
71
|
// text as assistant/text (delta semantics) made the reducer append the
|
|
72
|
-
// full text to itself on every chunk — duplicated card output
|
|
73
|
-
// (2026-08-17 live regression, reproduced with 3 chunks).
|
|
72
|
+
// full text to itself on every chunk — duplicated card output.
|
|
74
73
|
const key = 'text';
|
|
75
74
|
const prev = this.textByItem.get(key) ?? '';
|
|
76
75
|
const next = prev + event.content.text;
|
|
@@ -130,7 +129,7 @@ export class KimiAcpTranslator extends BaseAcpTranslator {
|
|
|
130
129
|
getLogger().info(`[kimi-acp-translator] permission request without toolCall, auto-responding cancelled (requestId=${requestId})`);
|
|
131
130
|
return [];
|
|
132
131
|
}
|
|
133
|
-
// 命令恢复(
|
|
132
|
+
// 命令恢复(kimi 0.42.0):审批门在 tool.call.started
|
|
134
133
|
// 之前触发,request_permission 的 toolCall 只有 title("Bash")+ content
|
|
135
134
|
// 里一句截断摘要("Requesting approval to Running: <cmd>…"),无 rawInput;
|
|
136
135
|
// 完整命令只从审批前的 tool_call/tool_call_update 流式 args 累积文本拿到
|