mocode-ai 1.3.9 → 1.4.0
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 +78 -70
- package/README.zh-CN.md +81 -73
- package/bin/mocode-agent-host.js +1 -1
- package/dist/agent/core.js +226 -293
- package/dist/agent/index.js +14 -10
- package/dist/agent/mode.js +3 -3
- package/dist/agent/runtime-context.js +45 -0
- package/dist/agent/spawn.js +101 -86
- package/dist/agent/tool-helpers.js +134 -0
- package/dist/agent/trace-state.js +118 -0
- package/dist/agents/coordinator.js +6 -1
- package/dist/changeset/index.js +9 -8
- package/dist/config/index.js +109 -77
- package/dist/config/presets.js +3 -1
- package/dist/config/profiles.js +125 -0
- package/dist/context/age-aware.js +2 -4
- package/dist/context/artifacts.js +6 -13
- package/dist/context/budget.js +6 -8
- package/dist/context/classifier.js +1 -2
- package/dist/context/encoders/code.js +2 -6
- package/dist/context/encoders/command.js +1 -3
- package/dist/context/encoders/search.js +1 -3
- package/dist/context/index.js +1 -1
- package/dist/context/lifecycle.js +5 -3
- package/dist/context/pipeline.js +1 -1
- package/dist/context/relevance.js +2 -4
- package/dist/context/token-calibration.js +13 -18
- package/dist/host/stdio.js +73 -12
- package/dist/i18n/index.js +96 -44
- package/dist/index.js +1 -1
- package/dist/llm/index.js +39 -23
- package/dist/llm/provider.js +23 -0
- package/dist/llm/providers/anthropic.js +12 -18
- package/dist/llm/tool-schema.js +48 -0
- package/dist/mcp/client.js +70 -25
- package/dist/mcp/config.js +1 -3
- package/dist/mcp/index.js +3 -1
- package/dist/memory/reflect.js +3 -6
- package/dist/memory/store.js +10 -12
- package/dist/permissions/index.js +66 -22
- package/dist/pet/bridge.js +2 -2
- package/dist/repl/commands/appearance.js +72 -0
- package/dist/repl/commands/compact.js +114 -0
- package/dist/repl/commands/context.js +17 -0
- package/dist/repl/commands/image.js +71 -0
- package/dist/repl/commands/memory.js +51 -0
- package/dist/repl/commands/mode.js +47 -0
- package/dist/repl/commands/model.js +435 -0
- package/dist/repl/commands/pet.js +70 -0
- package/dist/repl/commands/registry.js +46 -0
- package/dist/repl/commands/session.js +102 -0
- package/dist/repl/commands/skill.js +95 -0
- package/dist/repl/commands/system.js +202 -0
- package/dist/repl/commands/tool-group.js +233 -0
- package/dist/repl/commands/types.js +4 -0
- package/dist/repl/commands.js +324 -0
- package/dist/repl/index.js +5 -2577
- package/dist/repl/message-format.js +151 -0
- package/dist/repl/running-input.js +147 -0
- package/dist/repl/runtime.js +816 -0
- package/dist/repl/status-bar.js +161 -0
- package/dist/rollback/index.js +44 -14
- package/dist/runtime/browser-manager.js +1 -1
- package/dist/runtime/dev-server-manager.js +1 -1
- package/dist/runtime/input-injector.js +313 -0
- package/dist/runtime/screen-capture.js +89 -0
- package/dist/runtime/screen-pipeline.js +223 -0
- package/dist/sandbox/index.js +1 -1
- package/dist/sandbox/policy.js +8 -2
- package/dist/session/compact.js +22 -34
- package/dist/session/index.js +2 -2
- package/dist/session/notes.js +39 -5
- package/dist/session/persist.js +14 -8
- package/dist/session/scheduler.js +3 -4
- package/dist/session/trace-metrics.js +6 -3
- package/dist/skills/activation.js +10 -5
- package/dist/skills/discover.js +5 -17
- package/dist/skills/index.js +1 -1
- package/dist/skills/runner.js +28 -44
- package/dist/skills/toolmap.js +5 -2
- package/dist/tools/builtins/browser.js +17 -3
- package/dist/tools/builtins/computer.js +322 -0
- package/dist/tools/builtins/dev-server.js +1 -3
- package/dist/tools/builtins/edit-file.js +21 -7
- package/dist/tools/builtins/grep.js +1 -1
- package/dist/tools/builtins/index.js +28 -25
- package/dist/tools/builtins/memory-forget.js +1 -1
- package/dist/tools/builtins/memory-graph.js +4 -7
- package/dist/tools/builtins/memory-list.js +2 -6
- package/dist/tools/builtins/memory-save.js +8 -2
- package/dist/tools/builtins/memory-search.js +4 -4
- package/dist/tools/builtins/note-append.js +14 -3
- package/dist/tools/builtins/plan-update.js +40 -9
- package/dist/tools/builtins/run-command.js +8 -8
- package/dist/tools/builtins/screenshot.js +11 -87
- package/dist/tools/builtins/task.js +29 -40
- package/dist/tools/builtins/use-skill.js +1 -1
- package/dist/tools/builtins/view-image.js +5 -3
- package/dist/tools/builtins/web-fetch.js +1 -3
- package/dist/tools/builtins/web-search.js +3 -7
- package/dist/tools/builtins/write-file.js +4 -2
- package/dist/tools/constants.js +50 -37
- package/dist/tools/policy.js +221 -0
- package/dist/tools/registry.js +40 -19
- package/dist/tools/resource-lock.js +4 -2
- package/dist/tools/router.js +143 -0
- package/dist/tools/validation.js +6 -5
- package/dist/ui/batch.js +63 -23
- package/dist/ui/clipboard.js +17 -5
- package/dist/ui/content.js +166 -13
- package/dist/ui/diff.js +61 -19
- package/dist/ui/fuzzy.js +35 -2
- package/dist/ui/intervention.js +2 -4
- package/dist/ui/layout-internal/content-write.js +587 -0
- package/dist/ui/layout-internal/core.js +590 -0
- package/dist/ui/layout-internal/input-paint.js +365 -0
- package/dist/ui/layout-internal/screen.js +128 -0
- package/dist/ui/layout-internal/scroll.js +152 -0
- package/dist/ui/layout-internal/selection.js +229 -0
- package/dist/ui/layout-internal/state.js +82 -0
- package/dist/ui/layout-internal/statusbar.js +351 -0
- package/dist/ui/layout-types.js +1 -0
- package/dist/ui/layout.js +3 -2288
- package/dist/ui/markdown.js +10 -2
- package/dist/ui/prompt-internal/editor.js +945 -0
- package/dist/ui/prompt-internal/paste.js +52 -0
- package/dist/ui/prompt-internal/pickers.js +519 -0
- package/dist/ui/prompt-internal/types.js +1 -0
- package/dist/ui/prompt.js +4 -1506
- package/dist/ui/render.js +87 -2
- package/dist/verification/affected.js +11 -5
- package/dist/verification/discovery.js +1 -3
- package/dist/verification/profile.js +11 -3
- package/package.json +11 -2
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { ui } from '../ui/theme.js';
|
|
2
|
+
import * as layout from '../ui/layout.js';
|
|
3
|
+
import * as batch from '../ui/batch.js';
|
|
4
|
+
import { displayWidth, padEndDisplay, summarizeToolCall, summarizeToolResult } from '../ui/render.js';
|
|
5
|
+
import { renderChip } from '../attachments/image.js';
|
|
6
|
+
import { PROMPT } from './commands.js';
|
|
7
|
+
/** side channel:history 中 user 消息的 index → 附件列表,供 renderHistory 复显文件名。runTurn 在 push 前设。 */
|
|
8
|
+
export const messageAttachments = new Map();
|
|
9
|
+
export function formatUserMessage(lines, trailingBlank = true) {
|
|
10
|
+
const cols = layout.getGeo().cols;
|
|
11
|
+
const promptW = displayWidth(PROMPT);
|
|
12
|
+
const indent = ' '.repeat(promptW);
|
|
13
|
+
const { userBg, reset } = ui;
|
|
14
|
+
return (lines
|
|
15
|
+
.map((l, i) => {
|
|
16
|
+
const prefix = i === 0 ? PROMPT : indent;
|
|
17
|
+
const full = prefix + l;
|
|
18
|
+
const padded = padEndDisplay(full, cols);
|
|
19
|
+
return `${userBg}${padded}${reset}`;
|
|
20
|
+
})
|
|
21
|
+
.join('\n') + (trailingBlank ? '\n\n' : '\n'));
|
|
22
|
+
}
|
|
23
|
+
/** 把任意消息 content 拍平成字符串(OpenAI 可能 string / null / 多模态数组)。 */
|
|
24
|
+
export function textOf(c) {
|
|
25
|
+
if (typeof c === 'string')
|
|
26
|
+
return c;
|
|
27
|
+
if (c == null)
|
|
28
|
+
return '';
|
|
29
|
+
if (Array.isArray(c)) {
|
|
30
|
+
return c.map((p) => (typeof p === 'string' ? p : (p?.text ?? ''))).join('');
|
|
31
|
+
}
|
|
32
|
+
return String(c);
|
|
33
|
+
}
|
|
34
|
+
/** 从旧 session 的消息历史回填输入历史;新 session 使用独立 queryHistory,避免混入合成 user 消息。 */
|
|
35
|
+
export function queryHistoryFromMessages(messages) {
|
|
36
|
+
return messages
|
|
37
|
+
.filter((message) => message.role === 'user')
|
|
38
|
+
.map((message) => textOf(message.content))
|
|
39
|
+
.filter((query) => query.trim().length > 0);
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 把会话历史渲染成静态文本进内容区(回滚 / 续接 / --resume 后复显上下文):
|
|
43
|
+
* user→❯ 回显、assistant→正文(+ tool_calls 折叠成 ● 摘要行)、tool→↳ 结果预览;system 跳过。
|
|
44
|
+
* 思考段不持久(history 只存正文),故无思考折叠。渲染后续写位在末尾,紧接 enterInputMode 画输入框。
|
|
45
|
+
* 内容长于屏时 viewport 显尾(最近轮次),PgUp 可看更早——与流式态一致。
|
|
46
|
+
* user 多模态:用 textOf 取 text parts;若侧 channel messageAttachments 有原文件名则追加 chip 行
|
|
47
|
+
* (避免 base64 解码不可逆,旧 session 没侧 channel 时只显文本,文件名 fallback 到 image/* mime)。
|
|
48
|
+
*
|
|
49
|
+
* 折叠策略:遇到 assistant + tool_calls 不立即打 ● 行,而是累积到 batchEntries;
|
|
50
|
+
* 跟随的连续 tool 消息按 tool_call_id 反查填 resultSummary;遇下一个非 tool 消息(或末尾)时,
|
|
51
|
+
* 用 batch.writeSummaryOnly 出单行摘要(与实时 runAgent 同一渲染器,UI 一致)。
|
|
52
|
+
* 回放默认全折叠;用户可鼠标点击摘要行展开(由 BatchRenderer 接管,见 ui/batch.ts)。
|
|
53
|
+
*/
|
|
54
|
+
export function renderHistory(history) {
|
|
55
|
+
const idToEntry = new Map();
|
|
56
|
+
// 普通工具可跨 assistant 步聚合;mutation 各自占一个 group,并切断前后普通工具。
|
|
57
|
+
let pendingBatches = [];
|
|
58
|
+
let normalBatch = null;
|
|
59
|
+
const flushBatch = () => {
|
|
60
|
+
for (const entries of pendingBatches)
|
|
61
|
+
batch.writeSummaryOnly(entries, layout);
|
|
62
|
+
pendingBatches = [];
|
|
63
|
+
normalBatch = null;
|
|
64
|
+
};
|
|
65
|
+
for (let idx = 0; idx < history.length; idx++) {
|
|
66
|
+
const m = history[idx];
|
|
67
|
+
if (m.role === 'system')
|
|
68
|
+
continue;
|
|
69
|
+
if (m.role === 'user') {
|
|
70
|
+
flushBatch(); // 上一轮 batch(若有)收尾
|
|
71
|
+
const lines = textOf(m.content).split('\n');
|
|
72
|
+
layout.contentWrite(formatUserMessage(lines));
|
|
73
|
+
const atts = messageAttachments.get(idx);
|
|
74
|
+
if (atts && atts.length > 0) {
|
|
75
|
+
for (const a of atts) {
|
|
76
|
+
layout.contentWrite(` ${ui.dim}${renderChip(a)}${ui.reset}\n`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (Array.isArray(m.content)) {
|
|
80
|
+
// 旧 session 没侧 channel:从 data URL 头抽 mime,显一个通用 chip
|
|
81
|
+
const c = m.content;
|
|
82
|
+
for (const p of c) {
|
|
83
|
+
if (p && typeof p === 'object' && p.type === 'image_url') {
|
|
84
|
+
const url = p.image_url?.url ?? '';
|
|
85
|
+
const mime = url.startsWith('data:') ? url.slice(5, url.indexOf(';')) : 'image';
|
|
86
|
+
layout.contentWrite(` ${ui.dim}📷 ${mime}${ui.reset}\n`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (m.role === 'assistant') {
|
|
93
|
+
const text = textOf(m.content);
|
|
94
|
+
if (text) {
|
|
95
|
+
flushBatch(); // 文本前若有累积 batch 先收尾(罕见:连续两个 assistant tool_calls 文本间)
|
|
96
|
+
layout.contentWriteMdOnce(text);
|
|
97
|
+
}
|
|
98
|
+
const tcs = m.tool_calls;
|
|
99
|
+
if (Array.isArray(tcs) && tcs.length > 0) {
|
|
100
|
+
if (text) {
|
|
101
|
+
// contentWriteMdOnce 会裁掉 markdown 尾部空行,而 history 中的原始 text 是否以 \n
|
|
102
|
+
// 结尾并不能代表当前物理布局。按缓冲中的视觉行归一化,和实时“正文 → 工具”
|
|
103
|
+
// 边界一致地保留一条空行,避免 /resume 回放时工具摘要紧贴正文。
|
|
104
|
+
layout.normalizeMutationBoundary();
|
|
105
|
+
}
|
|
106
|
+
// 累积到 pendingBatch,顺序 = tool_calls 序
|
|
107
|
+
for (const tc of tcs) {
|
|
108
|
+
const name = tc?.function?.name ?? '';
|
|
109
|
+
const args = tc?.function?.arguments ?? '';
|
|
110
|
+
const entry = {
|
|
111
|
+
name,
|
|
112
|
+
callSummary: summarizeToolCall(name, args),
|
|
113
|
+
resultSummary: '',
|
|
114
|
+
diffBlock: null,
|
|
115
|
+
};
|
|
116
|
+
if (batch.isMutationToolName(name)) {
|
|
117
|
+
normalBatch = null;
|
|
118
|
+
pendingBatches.push([entry]);
|
|
119
|
+
}
|
|
120
|
+
else {
|
|
121
|
+
if (!normalBatch) {
|
|
122
|
+
normalBatch = [];
|
|
123
|
+
pendingBatches.push(normalBatch);
|
|
124
|
+
}
|
|
125
|
+
normalBatch.push(entry);
|
|
126
|
+
}
|
|
127
|
+
if (tc?.id)
|
|
128
|
+
idToEntry.set(tc.id, entry);
|
|
129
|
+
}
|
|
130
|
+
continue; // 跳过后续 tool 消息处理循环(由下一分支填 result)
|
|
131
|
+
}
|
|
132
|
+
// 无 tool_calls:若有 pending batch(文本+无 tool_calls 的 assistant),不常见,先收尾
|
|
133
|
+
flushBatch();
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (m.role === 'tool') {
|
|
137
|
+
const id = m.tool_call_id ?? '';
|
|
138
|
+
const target = idToEntry.get(id);
|
|
139
|
+
const name = target?.name ?? '';
|
|
140
|
+
const output = textOf(m.content);
|
|
141
|
+
const preview = summarizeToolResult(name, output);
|
|
142
|
+
if (target) {
|
|
143
|
+
target.resultSummary = preview;
|
|
144
|
+
target.fullOutput = output;
|
|
145
|
+
}
|
|
146
|
+
// 不直接写屏——等 flushBatch 时出单行摘要
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
flushBatch(); // 末尾兜底
|
|
151
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { emitKeypressEvents } from 'node:readline';
|
|
2
|
+
import { stdin } from 'node:process';
|
|
3
|
+
import * as layout from '../ui/layout.js';
|
|
4
|
+
import * as mouse from '../ui/mouse.js';
|
|
5
|
+
import { appendCurrentSessionRuntimeEvent } from '../session/index.js';
|
|
6
|
+
const emitter = stdin;
|
|
7
|
+
// ── 运行态交互(typeahead 输入 + 滚动回看 + Ctrl+C 中断)──
|
|
8
|
+
// 只在 await runAgent() 期间挂载;/resume /rollback /compact 等走 askLine(cooked readline)的分支不挂(避免抢 stdin)。
|
|
9
|
+
let runningInput = ''; // 运行中已打字缓冲(单行;agent 结束后预填下一轮 INPUT 态)
|
|
10
|
+
let runningCursor = 0; // 缓冲内光标字符索引(0..len);运行态支持任意位置编辑,与空闲态一致
|
|
11
|
+
let runningPlaceholder = '';
|
|
12
|
+
let currentAbort = null;
|
|
13
|
+
/** 获取运行中输入缓冲(agent 结束后预填下一轮 INPUT 态)。 */
|
|
14
|
+
export function getRunningInput() {
|
|
15
|
+
return runningInput;
|
|
16
|
+
}
|
|
17
|
+
/** 清空运行中输入缓冲(预填已消费后调用)。 */
|
|
18
|
+
export function clearRunningInput() {
|
|
19
|
+
runningInput = '';
|
|
20
|
+
runningCursor = 0;
|
|
21
|
+
}
|
|
22
|
+
/** 运行态按键:滚动优先,再 Ctrl+C 中断,再 typeahead 编辑(单行,Enter=无操作)。 */
|
|
23
|
+
function onRunningKey(_str, key) {
|
|
24
|
+
if (!key)
|
|
25
|
+
return;
|
|
26
|
+
// 鼠标 fragment:重组 + 派发给 layout.handleMouseEvent(滚轮/框选/复制)。
|
|
27
|
+
if (mouse.swallow(key.sequence ?? ''))
|
|
28
|
+
return;
|
|
29
|
+
// 滚动回看键(优先;不触发回尾):PgUp/PgDn 翻页,↑/↓ 每次 5 行(键盘)。
|
|
30
|
+
// 运行态无输入光标,↑/↓ 无其他用途,直接作滚动。
|
|
31
|
+
if (key.name === 'pageup' || key.name === 'pagedown' || key.name === 'up' || key.name === 'down') {
|
|
32
|
+
const pageH = layout.getGeo().contentBottom;
|
|
33
|
+
if (key.name === 'pageup')
|
|
34
|
+
layout.scrollBy(pageH);
|
|
35
|
+
else if (key.name === 'pagedown')
|
|
36
|
+
layout.scrollBy(-pageH);
|
|
37
|
+
else if (key.name === 'up')
|
|
38
|
+
layout.scrollBy(5);
|
|
39
|
+
else
|
|
40
|
+
layout.scrollBy(-5);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
// 用户在交互(非滚动键)→ 暂停流式物理写,避免光标去 contentRow 扰动 IME 候选窗(停手后自动 flush)
|
|
44
|
+
layout.setUserActive();
|
|
45
|
+
// 滚动回看时打字 / 编辑(typeahead)不回尾——保持历史视图,便于运行中边看历史边预输入;
|
|
46
|
+
// 回尾时机:Enter 在运行态是 no-op,真正回尾发生在 agent 结束后 INPUT 态按 Enter 提交(见 prompt.ts submit 前)。
|
|
47
|
+
// Ctrl+C 4 层语义(RUNNING 态):有 typeahead → 清空(层 1,不中断);空 → abort(层 2,中断 agent)。
|
|
48
|
+
// 两次 Ctrl+C 才中断(先清 typeahead 再 abort),与 INPUT 态 onCtrlC 的 fish 式一致。
|
|
49
|
+
// raw 模式下 Ctrl+C 是按键不触发 SIGINT;signal 经 executeTool 串进工具,run_command/web_fetch 即时被杀。
|
|
50
|
+
if (key.ctrl && key.name === 'c') {
|
|
51
|
+
if (runningInput.length > 0) {
|
|
52
|
+
runningInput = '';
|
|
53
|
+
runningCursor = 0;
|
|
54
|
+
layout.paintRunningInput(runningInput, runningCursor, runningPlaceholder);
|
|
55
|
+
}
|
|
56
|
+
else if (currentAbort && !currentAbort.signal.aborted) {
|
|
57
|
+
appendCurrentSessionRuntimeEvent('abort', { phase: 'requested', source: 'keyboard' });
|
|
58
|
+
currentAbort.abort();
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const s = key.sequence ?? '';
|
|
63
|
+
// 光标移动(单行 typeahead,光标可任意位置,与空闲态一致)
|
|
64
|
+
if (key.name === 'left') {
|
|
65
|
+
runningCursor = Math.max(0, runningCursor - 1);
|
|
66
|
+
layout.paintRunningInput(runningInput, runningCursor, runningPlaceholder);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (key.name === 'right') {
|
|
70
|
+
runningCursor = Math.min(runningInput.length, runningCursor + 1);
|
|
71
|
+
layout.paintRunningInput(runningInput, runningCursor, runningPlaceholder);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (key.name === 'home' || (key.ctrl && key.name === 'a')) {
|
|
75
|
+
runningCursor = 0;
|
|
76
|
+
layout.paintRunningInput(runningInput, runningCursor, runningPlaceholder);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (key.name === 'end' || (key.ctrl && key.name === 'e')) {
|
|
80
|
+
runningCursor = runningInput.length;
|
|
81
|
+
layout.paintRunningInput(runningInput, runningCursor, runningPlaceholder);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
if (key.name === 'backspace') {
|
|
85
|
+
if (runningCursor > 0) {
|
|
86
|
+
runningInput = runningInput.slice(0, runningCursor - 1) + runningInput.slice(runningCursor);
|
|
87
|
+
runningCursor--;
|
|
88
|
+
layout.paintRunningInput(runningInput, runningCursor, runningPlaceholder);
|
|
89
|
+
}
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (key.name === 'delete') {
|
|
93
|
+
if (runningCursor < runningInput.length) {
|
|
94
|
+
runningInput = runningInput.slice(0, runningCursor) + runningInput.slice(runningCursor + 1);
|
|
95
|
+
layout.paintRunningInput(runningInput, runningCursor, runningPlaceholder);
|
|
96
|
+
}
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (key.name === 'escape') {
|
|
100
|
+
runningInput = '';
|
|
101
|
+
runningCursor = 0;
|
|
102
|
+
layout.paintRunningInput(runningInput, runningCursor, runningPlaceholder);
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// Enter / Ctrl+J:运行中 no-op(单行 typeahead;agent 结束后预填,用户在 INPUT 态按 Enter 提交)
|
|
106
|
+
if (key.name === 'return' || key.name === 'enter' || (key.ctrl && key.name === 'j')) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
// 可打印字符(>= 空格,非 ctrl/meta)→ 光标处插入 + 非 dim 回显(与空闲态一致)
|
|
110
|
+
if (s && s >= ' ' && !key.ctrl && !key.meta) {
|
|
111
|
+
runningInput = runningInput.slice(0, runningCursor) + s + runningInput.slice(runningCursor);
|
|
112
|
+
runningCursor += s.length;
|
|
113
|
+
layout.paintRunningInput(runningInput, runningCursor, runningPlaceholder);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** 鼠标右键单击输入框(未拖动)时 layout 读剪贴板后回调:在光标处插入 typeahead 缓冲(单行,换行折为空格)。 */
|
|
117
|
+
function onRunningMousePaste(text) {
|
|
118
|
+
const flat = text.replace(/[\r\n]+/g, ' ');
|
|
119
|
+
runningInput = runningInput.slice(0, runningCursor) + flat + runningInput.slice(runningCursor);
|
|
120
|
+
runningCursor += flat.length;
|
|
121
|
+
layout.paintRunningInput(runningInput, runningCursor, runningPlaceholder);
|
|
122
|
+
}
|
|
123
|
+
/** 进入运行态:挂 keypress 监听 + raw mode + 新建 abort 控制器,返回其 signal。在 await runAgent 前、enterRunningMode 后调。 */
|
|
124
|
+
export function startRunningListener(placeholder) {
|
|
125
|
+
runningPlaceholder = placeholder;
|
|
126
|
+
runningInput = '';
|
|
127
|
+
runningCursor = 0;
|
|
128
|
+
emitKeypressEvents(stdin); // 幂等:首轮 prompt 已永久挂解析器,这里防御性再调
|
|
129
|
+
try {
|
|
130
|
+
stdin.setRawMode(true);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// 非 TTY / 不支持 raw:监听器仍挂(按键可能不来,不影响 agent)
|
|
134
|
+
}
|
|
135
|
+
stdin.resume();
|
|
136
|
+
emitter.on('keypress', onRunningKey);
|
|
137
|
+
layout.setPasteHandler(onRunningMousePaste); // 鼠标右键单击输入框(未拖动)→ 读剪贴板贴入
|
|
138
|
+
const ac = new AbortController();
|
|
139
|
+
currentAbort = ac;
|
|
140
|
+
return ac.signal;
|
|
141
|
+
}
|
|
142
|
+
/** 退出运行态:摘监听 + 清 abort。不 pause / 不 setRawMode(false)——紧接着 promptWithSlashMenu 自己接管 raw。 */
|
|
143
|
+
export function stopRunningListener() {
|
|
144
|
+
emitter.off('keypress', onRunningKey);
|
|
145
|
+
layout.setPasteHandler(null);
|
|
146
|
+
currentAbort = null;
|
|
147
|
+
}
|