mocode-ai 1.4.2 → 1.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/README.md +13 -1
- package/dist/agent/core.js +14 -936
- package/dist/agent/index.js +37 -13
- package/dist/agent/model-turn.js +218 -0
- package/dist/agent/pipeline.js +18 -0
- package/dist/agent/run-contracts.js +1 -0
- package/dist/agent/run-coordinator.js +758 -0
- package/dist/agent/runtime-context.js +118 -24
- package/dist/agent/spawn.js +11 -7
- package/dist/agent/stages/context-trimmer.js +63 -0
- package/dist/agent/stages/contracts.js +12 -0
- package/dist/agent/stages/history-manager.js +178 -0
- package/dist/agent/stages/legacy-adapters.js +19 -0
- package/dist/agent/stages/model-runner.js +29 -0
- package/dist/agent/stages/run-policy.js +73 -0
- package/dist/agent/stages/tool-dispatcher.js +341 -0
- package/dist/agent/tool-helpers.js +12 -12
- package/dist/agent/tool-turn.js +87 -0
- package/dist/agent/trace-state.js +97 -101
- package/dist/agent/turn-lifecycle.js +110 -0
- package/dist/config/index.js +14 -0
- package/dist/host/stdio.js +101 -40
- package/dist/llm/index.js +51 -35
- package/dist/llm/providers/anthropic.js +16 -10
- package/dist/llm/runtime.js +1 -0
- package/dist/permissions/index.js +21 -5
- package/dist/repl/commands/compact.js +2 -2
- package/dist/repl/commands/session.js +3 -12
- package/dist/repl/message-format.js +5 -0
- package/dist/repl/runtime.js +95 -55
- package/dist/rollback/index.js +29 -624
- package/dist/rollback/store.js +593 -0
- package/dist/runtime/index.js +1 -0
- package/dist/runtime/runtime.js +307 -0
- package/dist/session/compact.js +22 -14
- package/dist/session/index.js +1 -0
- package/dist/session/persist.js +10 -146
- package/dist/session/scheduler.js +28 -16
- package/dist/session/state.js +16 -12
- package/dist/session/store.js +218 -0
- package/dist/session/trace.js +5 -15
- package/dist/tools/policy.js +19 -15
- package/dist/tools/registry.js +21 -229
- package/dist/tools/router.js +5 -3
- package/dist/tools/tool-runtime.js +267 -0
- package/dist/ui/layout-internal/content-write.js +4 -0
- package/package.json +7 -3
package/dist/repl/runtime.js
CHANGED
|
@@ -4,6 +4,7 @@ import { config, isModelConfigured, isMemoryEnabled, buildBasePrompt, getPlanMod
|
|
|
4
4
|
import { t } from '../i18n/index.js';
|
|
5
5
|
import { listPresets, migrateCurrentToPreset } from '../config/presets.js';
|
|
6
6
|
import { runAgent } from '../agent/index.js';
|
|
7
|
+
import { defaultRuntime } from '../runtime/index.js';
|
|
7
8
|
import { getAgentMode, setAgentMode, onModeChange } from '../agent/mode.js';
|
|
8
9
|
import { sendState } from '../pet/bridge.js';
|
|
9
10
|
import { setSandboxRoot } from '../sandbox/root.js';
|
|
@@ -19,12 +20,10 @@ import '../tools/builtins/index.js';
|
|
|
19
20
|
import { initializeAllMcp, getMcpTools, closeAllMcp } from '../mcp/index.js';
|
|
20
21
|
import { refreshChatTools, classifyChatError } from '../llm/index.js';
|
|
21
22
|
import { renderChip } from '../attachments/image.js';
|
|
22
|
-
import { contextState,
|
|
23
|
-
import { listTurns, planRollback, applyRollback, persistSnapshots, loadSnapshots, rebuildFromHistory, getCurrentTurnId, } from '../rollback/index.js';
|
|
23
|
+
import { contextState, appendCurrentSessionRuntimeEvent } from '../session/index.js';
|
|
24
24
|
import { effectiveSystemPrompt } from '../skills/index.js';
|
|
25
25
|
import { clearSkillActivation } from '../skills/activation.js';
|
|
26
26
|
import { buildMemoryIndexSection, kickoffReflection, drainMemoryBackground, getLastReflectResult, clearLastReflectResult, snapshotTranscript, formatReflectResult, } from '../memory/index.js';
|
|
27
|
-
import { setCurrentSessionId } from '../session/state.js';
|
|
28
27
|
import { collectQueryHistory } from '../session/query-history.js';
|
|
29
28
|
import { ToolPolicyController } from '../tools/policy.js';
|
|
30
29
|
import { routeToolGroups } from '../tools/router.js';
|
|
@@ -165,6 +164,9 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
165
164
|
// 沙箱根:文件操作边界。优先级 --sandbox-root > SANDBOX_ROOT env > process.cwd()。
|
|
166
165
|
// 纯边界记录(不 chdir),jail.ts 内部 resolve。子 agent 同进程继承全局 root。
|
|
167
166
|
setSandboxRoot(sandboxRootOverride ?? config.sandboxRoot ?? process.cwd());
|
|
167
|
+
const runtime = defaultRuntime;
|
|
168
|
+
await runtime.start();
|
|
169
|
+
const runtimeContext = runtime.context;
|
|
168
170
|
// MCP 在工具表和 LLM schema 创建前连接;失败的单个 server 只给提示,不阻断 REPL。
|
|
169
171
|
const mcpReport = await initializeAllMcp();
|
|
170
172
|
registerToolsExtension('mcp', getMcpTools());
|
|
@@ -172,8 +174,9 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
172
174
|
// --resume:读回该会话的轮次/快照;无文件则从 history 重建 turns(无快照→旧轮次文件改动不可撤销)
|
|
173
175
|
let currentSessionId = sessionId;
|
|
174
176
|
if (!currentSessionId)
|
|
175
|
-
currentSessionId =
|
|
176
|
-
|
|
177
|
+
currentSessionId = runtime.session.create();
|
|
178
|
+
else
|
|
179
|
+
runtime.session.resume(currentSessionId);
|
|
177
180
|
// 构造系统提示:auto 用 base;plan 在 base 后追加按当前开关现拼的 plan suffix。
|
|
178
181
|
// 切模式时 applyMode 重算 history[0](history[0] 恒 system,compaction 保它,不破坏)。
|
|
179
182
|
// 活跃 plan 摘要拼在 memory 段后(systemPrompt 的尾段),todo 工具变更后 listener 重写 history[0]。
|
|
@@ -194,10 +197,6 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
194
197
|
if (initialHistory && initialHistory.length && history[0]?.role === 'system') {
|
|
195
198
|
history[0] = { role: 'system', content: buildSystemMessage(false) };
|
|
196
199
|
}
|
|
197
|
-
if (sessionId && initialHistory && initialHistory.length) {
|
|
198
|
-
if (!loadSnapshots(sessionId))
|
|
199
|
-
rebuildFromHistory(history);
|
|
200
|
-
}
|
|
201
200
|
// 反思 cadence 计数:每 reflectEveryN 轮 fire-and-forget 一次后台反思 pass。
|
|
202
201
|
let turnCount = 0;
|
|
203
202
|
// 本轮 token 累计:runAgent 返回后写入,供底栏模式 chip 右边显示。undefined=无实测
|
|
@@ -207,7 +206,11 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
207
206
|
model: config.model,
|
|
208
207
|
baseURL: config.baseURL,
|
|
209
208
|
cwd: process.cwd(),
|
|
210
|
-
tools: new ToolPolicyController({
|
|
209
|
+
tools: new ToolPolicyController({
|
|
210
|
+
groups: lastToolGroups,
|
|
211
|
+
maxExpansions: 0,
|
|
212
|
+
tools: runtimeContext.toolRuntime.tools,
|
|
213
|
+
})
|
|
211
214
|
.snapshot(getAgentMode() === 'plan')
|
|
212
215
|
.tools.map((tool) => tool.function.name)
|
|
213
216
|
.join(' · '),
|
|
@@ -325,7 +328,7 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
325
328
|
* 撤销文件 / 只撤销消息)。选轮 + 方式菜单均走 raw mode。预填经 pendingPrefill 注入下轮 INPUT。
|
|
326
329
|
*/
|
|
327
330
|
const rollbackFlow = async () => {
|
|
328
|
-
const turnList =
|
|
331
|
+
const turnList = runtime.rollback.list();
|
|
329
332
|
if (turnList.length < 1) {
|
|
330
333
|
layout.contentWrite(`${ui.dim}(没有可回滚的轮次)${ui.reset}\n`);
|
|
331
334
|
return;
|
|
@@ -348,7 +351,7 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
348
351
|
return; // Esc 取消
|
|
349
352
|
// picked(0-based)= 第 (picked+1) 轮:删该轮及之后(planRollback(picked) 保 1..picked),预填该轮 user 输入
|
|
350
353
|
const prefillText = userTexts[picked] ?? '';
|
|
351
|
-
const plan =
|
|
354
|
+
const plan = runtime.rollback.plan(picked, history);
|
|
352
355
|
// 清屏(擦选轮菜单 + /rollback 回显)+ 复位 lastView(dim 空),给文件询问一个干净、resize 安全的画面
|
|
353
356
|
layout.clearContent();
|
|
354
357
|
layout.paintInput({
|
|
@@ -379,14 +382,13 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
379
382
|
for (const c of revertable)
|
|
380
383
|
revertPaths.add(c.path);
|
|
381
384
|
}
|
|
382
|
-
const rolledBackFromTurnId = getCurrentTurnId();
|
|
383
|
-
const turnCountBeforeRollback =
|
|
384
|
-
const rollbackResult =
|
|
385
|
+
const rolledBackFromTurnId = runtimeContext.getCurrentTurnId();
|
|
386
|
+
const turnCountBeforeRollback = runtime.rollback.list().length;
|
|
387
|
+
const rollbackResult = runtime.rollback.apply(plan, history, revertPaths);
|
|
385
388
|
// 被撤销轮次的最终 route 无法再作为可靠 continuation 基线;下一真实 turn 重新由 LLM 选择。
|
|
386
389
|
lastToolGroups = [];
|
|
387
390
|
if (!currentSessionId)
|
|
388
|
-
currentSessionId =
|
|
389
|
-
setCurrentSessionId(currentSessionId, process.cwd()); // 同步到 session/state,确保 notes.md 存在
|
|
391
|
+
currentSessionId = runtime.session.create();
|
|
390
392
|
appendCurrentSessionRuntimeEvent('rollback', {
|
|
391
393
|
status: 'applied',
|
|
392
394
|
rolledBackFromTurnId,
|
|
@@ -399,12 +401,11 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
399
401
|
requestedFileCount: revertPaths.size,
|
|
400
402
|
}, plan.cutoffTurnId);
|
|
401
403
|
try {
|
|
402
|
-
|
|
404
|
+
runtime.session.save(history, currentSessionId, queryHistory);
|
|
403
405
|
}
|
|
404
406
|
catch {
|
|
405
407
|
// 落盘失败不阻断
|
|
406
408
|
}
|
|
407
|
-
persistSnapshots(currentSessionId);
|
|
408
409
|
// 复显剩余对话(无提示行),输入框预填该轮 user 输入 → 下轮 Enter 重新跑
|
|
409
410
|
layout.clearContent();
|
|
410
411
|
renderHistory(history);
|
|
@@ -428,33 +429,23 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
428
429
|
* plan 模式传 planMode=true(runAgent 用 planChatTools 只读子集)。返 ok=正常结束(未中断 / 未抛错),
|
|
429
430
|
* 供调用方决定是否弹审批面板。execute 轮的合成输入也走这里。
|
|
430
431
|
*
|
|
431
|
-
*
|
|
432
|
-
*
|
|
432
|
+
* routedCancel=true 表示 LLM 预路由阶段被 Ctrl+C 撤回(路由还没跑完、runAgent 未启动):
|
|
433
|
+
* 不报错不落盘,主循环擦气泡 + pendingPrefill 回填输入框(与 500ms 撤回窗口同语义)。
|
|
434
|
+
*
|
|
435
|
+
* 多模态:路由完成后才把 pendingAttachments flush 进 userInput(延迟到路由之后——路由阶段
|
|
436
|
+
* 撤回保留附件且 side channel 不错位);有图时构造 ContentPart[] 数组,无图时保持 string
|
|
437
|
+
* (向后兼容,且 messageTokens 走 estimateTokens 不走 IMAGE_TOKEN_COST)。
|
|
433
438
|
* side channel 记录本轮 msg 在 history 的 index → attachments,供 renderHistory 复显文件名。
|
|
434
439
|
*/
|
|
435
440
|
const runTurn = async (input, planMode, placeholder, inheritedToolPolicy) => {
|
|
436
|
-
const imgs = pendingAttachments;
|
|
437
|
-
pendingAttachments = []; // 入口即清,即使后续抛错也不留陈旧附件
|
|
438
|
-
const userInput = imgs.length === 0
|
|
439
|
-
? input
|
|
440
|
-
: [
|
|
441
|
-
{ type: 'text', text: input },
|
|
442
|
-
// detail 故意不设(留 undefined,JSON.stringify 时被丢弃):OpenAI 认 'auto'/'low'/'high',
|
|
443
|
-
// 但 MiniMax 只认 'low'/'default'/'high'——'auto' 不是合法枚举值,某些后端会 400。
|
|
444
|
-
// 不传 detail 让各 provider 用自己的默认值(OpenAI 默认视为 auto,MiniMax 默认 default),
|
|
445
|
-
// 是唯一在两边都不出错的写法。
|
|
446
|
-
...imgs.map((a) => ({
|
|
447
|
-
type: 'image_url',
|
|
448
|
-
image_url: { url: a.dataUrl },
|
|
449
|
-
})),
|
|
450
|
-
];
|
|
451
|
-
const msgIndex = history.length; // runAgent push 后 = 这个 index
|
|
452
|
-
if (imgs.length)
|
|
453
|
-
messageAttachments.set(msgIndex, imgs);
|
|
454
441
|
let ok = false;
|
|
442
|
+
let routedCancel = false;
|
|
455
443
|
let signal;
|
|
456
444
|
let toolPolicy = inheritedToolPolicy;
|
|
457
445
|
let initialToolRoute;
|
|
446
|
+
// 继承 toolPolicy 的合成执行轮没有独立路由;真实用户轮在 await routeToolGroups 完成后置 true。
|
|
447
|
+
// 路由完成前 Ctrl+C = 撤回本轮消息(routedCancel),之后才是"中断 agent 执行"(原 abort 语义)。
|
|
448
|
+
let routingDone = toolPolicy != null;
|
|
458
449
|
try {
|
|
459
450
|
signal = startRunningListener(placeholder);
|
|
460
451
|
// 入口设定本轮初始模式(合成执行轮传 false→auto;用户轮传当前 mode)。
|
|
@@ -466,19 +457,34 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
466
457
|
clearSkillActivation();
|
|
467
458
|
// 每个真实用户 turn 强制由轻量 LLM 选择最小工具簇。plan 审批后的合成执行轮
|
|
468
459
|
// 传 inheritedToolPolicy,复用同一个 controller/version,不做第二次路由。
|
|
460
|
+
// 路由 await 期间 Ctrl+C(signal.aborted=true)→ 撤回本轮。两条路径都覆盖:
|
|
461
|
+
// (a) routeToolGroups 抛 abort(流读取中途 abort)→ 下方 catch 分支;
|
|
462
|
+
// (b) OpenAI 流式在响应接近读完时 abort 会正常 resolve 而非 reject
|
|
463
|
+
// (trace 实测:Ctrl+C 后 8ms 路由仍返回)→ 返回后必须显式查 signal.aborted。
|
|
469
464
|
if (!toolPolicy) {
|
|
470
465
|
const previousGroups = [...lastToolGroups];
|
|
471
466
|
const decision = await routeToolGroups({
|
|
472
467
|
input,
|
|
473
468
|
previousGroups,
|
|
474
469
|
planMode,
|
|
475
|
-
attachmentNames:
|
|
470
|
+
attachmentNames: pendingAttachments.map((image) => image.name),
|
|
476
471
|
signal,
|
|
472
|
+
transport: runtimeContext.modelTransport,
|
|
473
|
+
tools: runtimeContext.toolRuntime.tools,
|
|
477
474
|
});
|
|
475
|
+
// 路由期间用户按了 Ctrl+C(signal 已 abort)→ 撤回,丢弃路由结果。
|
|
476
|
+
// 关键:即使 routeToolGroups 因流已读完而"成功"返回,也必须据此撤回,
|
|
477
|
+
// 否则 routingDone 被误置 true → 进 runAgent → 普通 abort(不回填)。
|
|
478
|
+
if (signal?.aborted) {
|
|
479
|
+
routedCancel = true;
|
|
480
|
+
return { ok: false, toolPolicy: undefined, routedCancel: true };
|
|
481
|
+
}
|
|
482
|
+
routingDone = true; // 路由完成且未被中断 → Ctrl+C 回归"中断执行"语义
|
|
478
483
|
toolPolicy = new ToolPolicyController({
|
|
479
484
|
groups: decision.groups,
|
|
480
485
|
reason: decision.reason,
|
|
481
486
|
confidence: decision.confidence,
|
|
487
|
+
tools: runtimeContext.toolRuntime.tools,
|
|
482
488
|
});
|
|
483
489
|
lastToolGroups = toolPolicy.groupNames;
|
|
484
490
|
initialToolRoute = {
|
|
@@ -493,12 +499,32 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
493
499
|
planMode,
|
|
494
500
|
};
|
|
495
501
|
}
|
|
502
|
+
// 路由完成后才 flush 附件:路由阶段撤回(routedCancel)保留 pendingAttachments,
|
|
503
|
+
// 回填后用户再发随消息一起带走;side channel 也不会在未 push user msg 时错写 index。
|
|
504
|
+
const imgs = pendingAttachments;
|
|
505
|
+
pendingAttachments = [];
|
|
506
|
+
const userInput = imgs.length === 0
|
|
507
|
+
? input
|
|
508
|
+
: [
|
|
509
|
+
{ type: 'text', text: input },
|
|
510
|
+
// detail 故意不设(留 undefined,JSON.stringify 时被丢弃):OpenAI 认 'auto'/'low'/'high',
|
|
511
|
+
// 但 MiniMax 只认 'low'/'default'/'high'——'auto' 不是合法枚举值,某些后端会 400。
|
|
512
|
+
// 不传 detail 让各 provider 用自己的默认值(OpenAI 默认视为 auto,MiniMax 默认 default),
|
|
513
|
+
// 是唯一在两边都不出错的写法。
|
|
514
|
+
...imgs.map((a) => ({
|
|
515
|
+
type: 'image_url',
|
|
516
|
+
image_url: { url: a.dataUrl },
|
|
517
|
+
})),
|
|
518
|
+
];
|
|
519
|
+
const msgIndex = history.length; // runAgent push 后 = 这个 index
|
|
520
|
+
if (imgs.length)
|
|
521
|
+
messageAttachments.set(msgIndex, imgs);
|
|
496
522
|
// 运行中每步 chat() 返回后刷新状态行 context 用量条(用 fresh lastUsage / 估算),
|
|
497
523
|
// 否则整轮冻结在轮首 refreshStatusBase 的值,「执行 grep」时 2k/1000k 不动。
|
|
498
524
|
const result = await runAgent(history, userInput, signal, () => {
|
|
499
525
|
refreshStatusBase(history);
|
|
500
526
|
layout.drawStatusBar();
|
|
501
|
-
}, toolPolicy, initialToolRoute);
|
|
527
|
+
}, toolPolicy, initialToolRoute, runtime);
|
|
502
528
|
if (toolPolicy)
|
|
503
529
|
lastToolGroups = toolPolicy.groupNames;
|
|
504
530
|
// 本轮 token 累计(底栏模式 chip 右边显示)。undefined = 后端不开 include_usage。
|
|
@@ -507,15 +533,13 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
507
533
|
ok = !signal.aborted; // 中断(Ctrl+C)→ runAgent 已还原 history,ok=false 不弹审批
|
|
508
534
|
// 成功轮次自动落盘(崩溃也保住上一轮);新会话首轮分配 id
|
|
509
535
|
if (!currentSessionId)
|
|
510
|
-
currentSessionId =
|
|
511
|
-
setCurrentSessionId(currentSessionId, process.cwd()); // 同步到 session/state,确保 notes.md 存在
|
|
536
|
+
currentSessionId = runtime.session.create();
|
|
512
537
|
try {
|
|
513
|
-
|
|
538
|
+
runtime.session.save(history, currentSessionId, queryHistory, lastToolGroups);
|
|
514
539
|
}
|
|
515
540
|
catch {
|
|
516
541
|
// 落盘失败不阻断 REPL
|
|
517
542
|
}
|
|
518
|
-
persistSnapshots(currentSessionId); // 随会话落盘回滚快照(/resume 后仍可撤销)
|
|
519
543
|
// 后台反思:每 reflectEveryN 轮 fire-and-forget 一次(与下一轮 agent 并发,不阻塞)。
|
|
520
544
|
// 已有在飞任务 / autoReflect 关 → kickoff 内部自守卫。快照同步取(避免下一轮 mutate history 竞态)。
|
|
521
545
|
turnCount++;
|
|
@@ -527,12 +551,18 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
527
551
|
ok = false;
|
|
528
552
|
if (toolPolicy)
|
|
529
553
|
lastToolGroups = toolPolicy.groupNames;
|
|
554
|
+
// 路由阶段 Ctrl+C = 撤回本轮消息(非"中断执行"):query 未进 queryHistory、无 user msg
|
|
555
|
+
// 进 history,故不报错不落盘;finally 结算状态栏后早退,主循环擦气泡 + pendingPrefill
|
|
556
|
+
// 回填输入框(与 500ms 撤回窗口同语义)。e 为 routeToolGroups rethrow 的 abort,不消费、不打印。
|
|
557
|
+
if (!routingDone && signal?.aborted) {
|
|
558
|
+
routedCancel = true;
|
|
559
|
+
return { ok: false, toolPolicy: undefined, routedCancel: true };
|
|
560
|
+
}
|
|
530
561
|
// 请求失败也保存已确认提交的 query,确保立即退出后仍可通过 ↑ 或 resume 找回。
|
|
531
562
|
if (!currentSessionId)
|
|
532
|
-
currentSessionId =
|
|
533
|
-
setCurrentSessionId(currentSessionId, process.cwd());
|
|
563
|
+
currentSessionId = runtime.session.create();
|
|
534
564
|
try {
|
|
535
|
-
|
|
565
|
+
runtime.session.save(history, currentSessionId, queryHistory, lastToolGroups);
|
|
536
566
|
}
|
|
537
567
|
catch {
|
|
538
568
|
// 落盘失败不覆盖原始请求错误
|
|
@@ -578,21 +608,21 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
578
608
|
refreshStatusBase(history, lastTurnUsage);
|
|
579
609
|
layout.drawStatusBar();
|
|
580
610
|
}
|
|
581
|
-
|
|
582
|
-
|
|
611
|
+
if (!routedCancel)
|
|
612
|
+
layout.contentWrite('\n'); // 轮次之间空行(路由撤回由主循环 rewindContent 擦除,不补空行)
|
|
613
|
+
return { ok, toolPolicy, routedCancel };
|
|
583
614
|
};
|
|
584
615
|
/** 把 picker 选中的会话加载进 REPL(刷 history + 重建 snapshots + 重画)。/resume / /sessions 共用。 */
|
|
585
616
|
async function resumeFromPick(pick) {
|
|
586
617
|
if (!pick)
|
|
587
618
|
return; // Esc / Ctrl+D 取消
|
|
588
|
-
const loaded =
|
|
619
|
+
const loaded = runtime.session.resume(pick.id);
|
|
589
620
|
if (!loaded || !loaded.history.length) {
|
|
590
621
|
layout.contentWrite(`${ui.yellow}${t('repl.loadFailed')}${ui.reset}\n`);
|
|
591
622
|
return;
|
|
592
623
|
}
|
|
593
624
|
// Bind before rebuilding the prompt, or it can retain the previous session's notes path.
|
|
594
625
|
currentSessionId = loaded.id;
|
|
595
|
-
setCurrentSessionId(loaded.id, process.cwd());
|
|
596
626
|
if (loaded.history[0]?.role === 'system') {
|
|
597
627
|
loaded.history[0] = { role: 'system', content: buildSystemMessage(false) };
|
|
598
628
|
}
|
|
@@ -601,9 +631,6 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
601
631
|
queryHistory = loaded.queryHistory ? [...loaded.queryHistory] : queryHistoryFromMessages(loaded.history);
|
|
602
632
|
lastToolGroups = [...(loaded.lastToolGroups ?? [])];
|
|
603
633
|
setAgentMode('auto'); // 续接重置为 auto(mode 不落盘;listener 重写 history[0] 回 auto,与 loaded 幂等)
|
|
604
|
-
// 读回该会话的轮次/快照;无文件则从 history 重建 turns(无快照→旧轮次文件改动不可撤销)
|
|
605
|
-
if (!loadSnapshots(loaded.id))
|
|
606
|
-
rebuildFromHistory(history);
|
|
607
634
|
contextState.lastUsage = undefined;
|
|
608
635
|
contextState.lifecycleStats = undefined;
|
|
609
636
|
contextState.ephemeralText = undefined;
|
|
@@ -688,6 +715,7 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
688
715
|
inputLines: input,
|
|
689
716
|
history,
|
|
690
717
|
contextState,
|
|
718
|
+
runtime,
|
|
691
719
|
// 访问器暴露闭包 `let`:命令会写它们(/clear 换 session id、/resume 换整个会话),传值会丢写回。
|
|
692
720
|
state: {
|
|
693
721
|
get currentSessionId() {
|
|
@@ -787,6 +815,17 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
787
815
|
queryHistory.push(joined);
|
|
788
816
|
const initialPlan = getAgentMode() === 'plan'; // 轮首模式(在 runTurn 之前读)
|
|
789
817
|
const turn = await runTurn(joined, initialPlan, placeholder);
|
|
818
|
+
if (turn.routedCancel) {
|
|
819
|
+
// 路由阶段 Ctrl+C 撤回:与 500ms 撤回窗口同语义——气泡擦掉 + 行回填输入框
|
|
820
|
+
// (下轮 promptWithSlashMenu 经 initialLines 消费)+ 切回 INPUT 视觉。
|
|
821
|
+
// queryHistory 在 runTurn 前已 push,撤回的 query 未真正执行,撤销;
|
|
822
|
+
// pendingAttachments 保留(路由完成前未 flush),再发时随消息一起带走。
|
|
823
|
+
queryHistory.pop();
|
|
824
|
+
layout.rewindContent(bubbleRows);
|
|
825
|
+
pendingPrefill = input;
|
|
826
|
+
layout.enterInputMode(t('repl.idle'));
|
|
827
|
+
continue;
|
|
828
|
+
}
|
|
790
829
|
// plan 轮正常结束(未中断 / 未抛错)→ 看轮末模式决定:
|
|
791
830
|
// - 仍 plan:模型只产计划就 STOP(模型已无 switch_mode 工具)→ 弹审批面板(原行为)。
|
|
792
831
|
// - 已 auto:本轮被切到 auto 模式(用户用 /auto 触发的合成执行轮)→ 跳过审批,不重复打扰。
|
|
@@ -811,6 +850,7 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
|
|
|
811
850
|
}
|
|
812
851
|
// 退出前等在飞反思收尾(Ctrl+C 走 SIGINT 直退不等;fire-and-forget 不承诺中断时完成)。
|
|
813
852
|
await drainMemoryBackground();
|
|
853
|
+
await runtime.close();
|
|
814
854
|
await closeAllMcp();
|
|
815
855
|
layout.exitAltScreen();
|
|
816
856
|
}
|