minimal-agent 0.6.8 → 0.6.9
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/package.json +1 -1
- package/src/cli/streamJson.js +25 -16
- package/src/tools/ask/ask.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "minimal-agent",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.9",
|
|
4
4
|
"description": "最小化 Agent 系统 —— 10 工具 + 插件系统 + workflow DSL + 自动压缩 + OpenAI 兼容 + Ink TUI;NodeNext + tsc 原地编译,dev 用 Bun .ts、install 用 Node .js(学习/教学用)",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE",
|
|
6
6
|
"author": "Bill Wang <leiwang0359@gmail.com>",
|
package/src/cli/streamJson.js
CHANGED
|
@@ -49,24 +49,28 @@ function firstUserTitle(history) {
|
|
|
49
49
|
* 整段 stdin 当裸 prompt 读到 EOF(保 `echo "x" | minimal-agent -p --output-format stream-json` 仍可用,
|
|
50
50
|
* legacy 下天然无交互、一次性)。argv 已给 prompt 时,stdin 全部行都按 ask_answer 解析。
|
|
51
51
|
*
|
|
52
|
-
* 返回 prompt
|
|
52
|
+
* 返回 `{ prompt, control }`:`control=true` **仅**当 prompt 来自 `{type:'prompt'}` 控制行
|
|
53
|
+
* (= broker 在管理本子进程、会回灌 ask_answer);argv / 裸管道 / TTY / EOF 兜底均为 `false`
|
|
54
|
+
* (无人回灌 → 调用方据此**不注册** wire ask handler,让 askBridge 内置「自动选推荐项」兜底,
|
|
55
|
+
* 避免模型提问后永等挂死)。同时把 ask_answer 路由给 onAskAnswer。stdin 全程保持打开,
|
|
53
56
|
* 直到子进程结束(broker 在 turn 末 end stdin)。
|
|
54
57
|
*/
|
|
55
58
|
export function attachStdinChannel(opts) {
|
|
56
59
|
const input = opts.input ?? process.stdin;
|
|
57
60
|
return new Promise((resolvePrompt) => {
|
|
58
61
|
let promptResolved = false;
|
|
59
|
-
const resolveOnce = (text) => {
|
|
62
|
+
const resolveOnce = (text, control) => {
|
|
60
63
|
if (promptResolved)
|
|
61
64
|
return;
|
|
62
65
|
promptResolved = true;
|
|
63
|
-
resolvePrompt(text);
|
|
66
|
+
resolvePrompt({ prompt: text, control });
|
|
64
67
|
};
|
|
68
|
+
// argv 给的 prompt:直连 CLI、无控制通道(broker 走 stdin 的 {type:'prompt'},不用 argv)。
|
|
65
69
|
if (opts.argvPrompt !== undefined)
|
|
66
|
-
resolveOnce(opts.argvPrompt);
|
|
70
|
+
resolveOnce(opts.argvPrompt, false);
|
|
67
71
|
// 无 stdin 管道(TTY):argv 没给 prompt 也只能返回空(调用方据空报错)。
|
|
68
72
|
if (input.isTTY) {
|
|
69
|
-
resolveOnce('');
|
|
73
|
+
resolveOnce('', false);
|
|
70
74
|
return;
|
|
71
75
|
}
|
|
72
76
|
let buf = '';
|
|
@@ -91,7 +95,7 @@ export function attachStdinChannel(opts) {
|
|
|
91
95
|
}
|
|
92
96
|
if (!promptResolved) {
|
|
93
97
|
if (msg && msg.type === 'prompt' && typeof msg.text === 'string') {
|
|
94
|
-
resolveOnce(msg.text);
|
|
98
|
+
resolveOnce(msg.text, true); // 控制行 → 存在控制通道(broker 会回灌 ask_answer)
|
|
95
99
|
}
|
|
96
100
|
else {
|
|
97
101
|
// 首行不是 prompt 控制消息 → legacy:把它放回去,整段当裸 prompt 读到 EOF
|
|
@@ -106,9 +110,9 @@ export function attachStdinChannel(opts) {
|
|
|
106
110
|
}
|
|
107
111
|
});
|
|
108
112
|
input.on('end', () => {
|
|
109
|
-
// legacy 一次性:EOF 时整段 stdin = 裸 prompt
|
|
113
|
+
// legacy 一次性:EOF 时整段 stdin = 裸 prompt,无控制通道
|
|
110
114
|
if (!promptResolved)
|
|
111
|
-
resolveOnce(buf.trim());
|
|
115
|
+
resolveOnce(buf.trim(), false);
|
|
112
116
|
});
|
|
113
117
|
});
|
|
114
118
|
}
|
|
@@ -133,6 +137,7 @@ export async function runStreamJsonMode(provider, args, initialHistory, options)
|
|
|
133
137
|
catch {
|
|
134
138
|
/* 某些 stdin 类型无 unref,忽略 */
|
|
135
139
|
}
|
|
140
|
+
setAskHandler(null); // 注销 handler,避免全局污染(测试复用 / 同进程多次调用)
|
|
136
141
|
await saveContext(history);
|
|
137
142
|
};
|
|
138
143
|
// --- 压缩动作(/compact):不需要 prompt ---
|
|
@@ -159,15 +164,8 @@ export async function runStreamJsonMode(provider, args, initialHistory, options)
|
|
|
159
164
|
p.reject(new Error(reason));
|
|
160
165
|
pendingAsks.clear();
|
|
161
166
|
};
|
|
162
|
-
// 注册 askBridge handler:工具调 requestUserChoice → emit `ask` 到 stdout、挂起等 stdin 回灌的 ask_answer。
|
|
163
|
-
// 同一时刻可有多个未决问题(按 requestId 区分),但工具 isConcurrencySafe:false → 实际串行。
|
|
164
|
-
setAskHandler((questions) => new Promise((resolve, reject) => {
|
|
165
|
-
const requestId = crypto.randomUUID();
|
|
166
|
-
pendingAsks.set(requestId, { resolve, reject });
|
|
167
|
-
emitLine({ type: 'ask', requestId, questions });
|
|
168
|
-
}));
|
|
169
167
|
const promptArgs = extractPromptArgs(args);
|
|
170
|
-
const prompt = await attachStdinChannel({
|
|
168
|
+
const { prompt, control } = await attachStdinChannel({
|
|
171
169
|
argvPrompt: promptArgs.length > 0 ? promptArgs.join(' ') : undefined,
|
|
172
170
|
onAskAnswer: (requestId, answers) => {
|
|
173
171
|
const p = pendingAsks.get(requestId);
|
|
@@ -177,6 +175,17 @@ export async function runStreamJsonMode(provider, args, initialHistory, options)
|
|
|
177
175
|
}
|
|
178
176
|
},
|
|
179
177
|
});
|
|
178
|
+
// 仅当存在控制通道(broker 经 `{type:'prompt'}` 启动、会回灌 ask_answer)才注册 wire handler:
|
|
179
|
+
// 工具调 requestUserChoice → emit `ask` 到 stdout、挂起等 stdin 回灌。无控制通道(argv / 裸管道 /
|
|
180
|
+
// TTY)时**不注册** → 落 askBridge 内置「自动选推荐项」兜底,避免模型提问后永等挂死。
|
|
181
|
+
// loop 在 prompt resolve 后才启动 → 此处注册时机安全(注册前不会有 ask 触发)。
|
|
182
|
+
if (control) {
|
|
183
|
+
setAskHandler((questions) => new Promise((resolve, reject) => {
|
|
184
|
+
const requestId = crypto.randomUUID();
|
|
185
|
+
pendingAsks.set(requestId, { resolve, reject });
|
|
186
|
+
emitLine({ type: 'ask', requestId, questions });
|
|
187
|
+
}));
|
|
188
|
+
}
|
|
180
189
|
if (!prompt.trim()) {
|
|
181
190
|
emitLine({ type: 'error', error: '未提供 prompt(argv / stdin 都为空)', code: 'llm_error' });
|
|
182
191
|
process.exit(1);
|