@yeaft/webchat-agent 0.1.399 → 0.1.408
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/crew/role-query.js +10 -6
- package/package.json +3 -1
- package/sdk/query.js +3 -1
- package/unify/cli.js +537 -0
- package/unify/config.js +256 -0
- package/unify/debug-trace.js +398 -0
- package/unify/engine.js +319 -0
- package/unify/index.js +21 -0
- package/unify/init.js +147 -0
- package/unify/llm/adapter.js +186 -0
- package/unify/llm/anthropic.js +322 -0
- package/unify/llm/chat-completions.js +315 -0
- package/unify/models.js +167 -0
- package/unify/prompts.js +61 -0
package/crew/role-query.js
CHANGED
|
@@ -149,12 +149,13 @@ async function _createRoleQueryInner(session, roleName) {
|
|
|
149
149
|
|
|
150
150
|
// 继承全局 MCP disallowedTools,避免不必要的 tool schema token 消耗
|
|
151
151
|
const globalDisallowed = ctx.CONFIG?.disallowedTools || [];
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
//
|
|
156
|
-
//
|
|
157
|
-
|
|
152
|
+
// Developer 角色保留 skills(tdd、commit 等对开发有帮助),
|
|
153
|
+
// 其他角色(pm、reviewer、tester)禁用 skills 以避免和 ROUTE 冲突
|
|
154
|
+
const isDeveloper = role.roleType === 'developer';
|
|
155
|
+
// Crew 角色禁用 Agent 工具:角色间协作必须通过 ROUTE 块,不能自行启动 sub-agent
|
|
156
|
+
// 非 developer 角色额外禁用 Skill 工具:skills 的 trigger 词会和角色职能冲突
|
|
157
|
+
// (如 review-code、commit),导致 Claude 调用 Skill 而不是输出 ROUTE 块
|
|
158
|
+
const crewDisallowed = isDeveloper ? ['Agent'] : ['Agent', 'Skill'];
|
|
158
159
|
const effectiveDisallowed = [...globalDisallowed, ...crewDisallowed];
|
|
159
160
|
|
|
160
161
|
const queryOptions = {
|
|
@@ -163,6 +164,9 @@ async function _createRoleQueryInner(session, roleName) {
|
|
|
163
164
|
abort: abortController.signal,
|
|
164
165
|
model: role.model || undefined,
|
|
165
166
|
appendSystemPrompt: systemPrompt,
|
|
167
|
+
// 非 developer 角色禁用 skills(--disable-slash-commands),
|
|
168
|
+
// 从 CLI 层面阻止 skills 系统 prompt 注入,避免和 ROUTE 冲突
|
|
169
|
+
...(!isDeveloper && { disableSlashCommands: true }),
|
|
166
170
|
...(effectiveDisallowed.length > 0 && { disallowedTools: effectiveDisallowed })
|
|
167
171
|
};
|
|
168
172
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yeaft/webchat-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.408",
|
|
4
4
|
"description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -52,9 +52,11 @@
|
|
|
52
52
|
"ext": "js"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
+
"better-sqlite3": "^11.0.0",
|
|
55
56
|
"dotenv": "^16.3.1",
|
|
56
57
|
"tweetnacl": "^1.0.3",
|
|
57
58
|
"tweetnacl-util": "^0.15.1",
|
|
59
|
+
"uuid": "^11.1.0",
|
|
58
60
|
"ws": "^8.16.0"
|
|
59
61
|
},
|
|
60
62
|
"optionalDependencies": {
|
package/sdk/query.js
CHANGED
|
@@ -350,7 +350,8 @@ export function query(config) {
|
|
|
350
350
|
model,
|
|
351
351
|
canCallTool,
|
|
352
352
|
abort,
|
|
353
|
-
noSessionPersistence
|
|
353
|
+
noSessionPersistence,
|
|
354
|
+
disableSlashCommands
|
|
354
355
|
} = {}
|
|
355
356
|
} = config;
|
|
356
357
|
|
|
@@ -374,6 +375,7 @@ export function query(config) {
|
|
|
374
375
|
if (disallowedTools.length > 0) args.push('--disallowedTools', ...disallowedTools);
|
|
375
376
|
if (permissionMode) args.push('--permission-mode', permissionMode);
|
|
376
377
|
if (noSessionPersistence) args.push('--no-session-persistence');
|
|
378
|
+
if (disableSlashCommands) args.push('--disable-slash-commands');
|
|
377
379
|
|
|
378
380
|
// Handle prompt input
|
|
379
381
|
if (typeof prompt === 'string') {
|
package/unify/cli.js
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* cli.js — Yeaft Unify CLI entry point
|
|
4
|
+
*
|
|
5
|
+
* Features:
|
|
6
|
+
* --dry-run "prompt" — Assemble system prompt + messages, don't call LLM
|
|
7
|
+
* --trace stats|recent|search <keyword> — Query debug.db
|
|
8
|
+
* -i / --interactive — REPL mode with / commands
|
|
9
|
+
* <prompt> — One-shot query (Phase 1: engine.query)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createInterface } from 'readline';
|
|
13
|
+
import { join } from 'path';
|
|
14
|
+
import { initYeaftDir } from './init.js';
|
|
15
|
+
import { loadConfig } from './config.js';
|
|
16
|
+
import { DebugTrace, NullTrace, createTrace } from './debug-trace.js';
|
|
17
|
+
import { createLLMAdapter } from './llm/adapter.js';
|
|
18
|
+
import { Engine } from './engine.js';
|
|
19
|
+
import { listModels, resolveModel } from './models.js';
|
|
20
|
+
import { buildSystemPrompt } from './prompts.js';
|
|
21
|
+
|
|
22
|
+
// ─── Argument parsing ──────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
function parseArgs(argv) {
|
|
25
|
+
const args = {
|
|
26
|
+
mode: 'chat',
|
|
27
|
+
debug: false,
|
|
28
|
+
interactive: false,
|
|
29
|
+
verbose: false,
|
|
30
|
+
model: null,
|
|
31
|
+
language: null,
|
|
32
|
+
trace: null, // 'stats' | 'recent' | 'search' | 'tools' | null
|
|
33
|
+
traceArg: null, // search keyword or tool name
|
|
34
|
+
dryRun: false,
|
|
35
|
+
prompt: null,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const rest = argv.slice(2);
|
|
39
|
+
let i = 0;
|
|
40
|
+
|
|
41
|
+
while (i < rest.length) {
|
|
42
|
+
const arg = rest[i];
|
|
43
|
+
switch (arg) {
|
|
44
|
+
case '-m':
|
|
45
|
+
case '--mode':
|
|
46
|
+
args.mode = rest[++i] || 'chat';
|
|
47
|
+
break;
|
|
48
|
+
case '-d':
|
|
49
|
+
case '--debug':
|
|
50
|
+
args.debug = true;
|
|
51
|
+
break;
|
|
52
|
+
case '-i':
|
|
53
|
+
case '--interactive':
|
|
54
|
+
args.interactive = true;
|
|
55
|
+
break;
|
|
56
|
+
case '-v':
|
|
57
|
+
case '--verbose':
|
|
58
|
+
args.verbose = true;
|
|
59
|
+
break;
|
|
60
|
+
case '--model':
|
|
61
|
+
args.model = rest[++i] || null;
|
|
62
|
+
break;
|
|
63
|
+
case '--language':
|
|
64
|
+
args.language = rest[++i] || null;
|
|
65
|
+
break;
|
|
66
|
+
case '--trace':
|
|
67
|
+
args.trace = rest[++i] || 'stats';
|
|
68
|
+
if (['search', 'tools'].includes(args.trace) && i + 1 < rest.length && !rest[i + 1].startsWith('-')) {
|
|
69
|
+
args.traceArg = rest[++i];
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
case '--dry-run':
|
|
73
|
+
args.dryRun = true;
|
|
74
|
+
break;
|
|
75
|
+
default:
|
|
76
|
+
if (!arg.startsWith('-') && !args.prompt) {
|
|
77
|
+
args.prompt = arg;
|
|
78
|
+
}
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
i++;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return args;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// ─── Trace query handler ───────────────────────────────────────
|
|
88
|
+
|
|
89
|
+
function handleTraceQuery(args, config) {
|
|
90
|
+
const dbPath = join(config.dir, 'debug.db');
|
|
91
|
+
let trace;
|
|
92
|
+
try {
|
|
93
|
+
trace = new DebugTrace(dbPath);
|
|
94
|
+
} catch (e) {
|
|
95
|
+
throw new Error(`Cannot open debug database at ${dbPath}: ${e.message}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
switch (args.trace) {
|
|
100
|
+
case 'stats': {
|
|
101
|
+
const s = trace.stats();
|
|
102
|
+
console.log('Debug Trace Statistics:');
|
|
103
|
+
console.log(` Turns: ${s.turnCount}`);
|
|
104
|
+
console.log(` Tools: ${s.toolCount}`);
|
|
105
|
+
console.log(` Events: ${s.eventCount}`);
|
|
106
|
+
console.log(` DB Size: ${(s.dbSizeBytes / 1024).toFixed(1)} KB`);
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
case 'recent': {
|
|
110
|
+
const turns = trace.queryRecent(20);
|
|
111
|
+
if (turns.length === 0) {
|
|
112
|
+
console.log('No recent turns.');
|
|
113
|
+
} else {
|
|
114
|
+
for (const t of turns) {
|
|
115
|
+
const time = new Date(t.started_at).toLocaleString();
|
|
116
|
+
const tokens = t.input_tokens != null ? `${t.input_tokens}+${t.output_tokens} tokens` : 'pending';
|
|
117
|
+
const model = t.model || 'unknown';
|
|
118
|
+
console.log(` [${time}] ${model} | ${tokens} | ${t.stop_reason || 'running'}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
123
|
+
case 'search': {
|
|
124
|
+
if (!args.traceArg) {
|
|
125
|
+
throw new Error('Usage: --trace search <keyword>');
|
|
126
|
+
}
|
|
127
|
+
const results = trace.search(args.traceArg);
|
|
128
|
+
console.log(`Found ${results.length} turns matching "${args.traceArg}":`);
|
|
129
|
+
for (const t of results) {
|
|
130
|
+
const time = new Date(t.started_at).toLocaleString();
|
|
131
|
+
const preview = (t.response_text || '').slice(0, 80);
|
|
132
|
+
console.log(` [${time}] ${preview}...`);
|
|
133
|
+
}
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
case 'tools': {
|
|
137
|
+
const tools = trace.queryTools({ name: args.traceArg });
|
|
138
|
+
console.log(`Found ${tools.length} tool calls${args.traceArg ? ` for "${args.traceArg}"` : ''}:`);
|
|
139
|
+
for (const t of tools.slice(0, 20)) {
|
|
140
|
+
const time = new Date(t.created_at).toLocaleString();
|
|
141
|
+
const status = t.is_error ? 'ERROR' : 'OK';
|
|
142
|
+
console.log(` [${time}] ${t.tool_name} | ${t.duration_ms || '?'}ms | ${status}`);
|
|
143
|
+
}
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
default:
|
|
147
|
+
throw new Error(`Unknown trace command: ${args.trace}. Available: stats, recent, search <keyword>, tools [name]`);
|
|
148
|
+
}
|
|
149
|
+
} finally {
|
|
150
|
+
trace.close();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ─── Dry-run handler ───────────────────────────────────────────
|
|
155
|
+
|
|
156
|
+
function handleDryRun(args, config) {
|
|
157
|
+
const systemPrompt = buildSystemPrompt({ language: config.language, mode: args.mode });
|
|
158
|
+
const messages = [];
|
|
159
|
+
|
|
160
|
+
if (args.prompt) {
|
|
161
|
+
messages.push({ role: 'user', content: args.prompt });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
console.log('=== DRY RUN ===');
|
|
165
|
+
console.log();
|
|
166
|
+
console.log('--- Config ---');
|
|
167
|
+
console.log(` Model: ${config.model}`);
|
|
168
|
+
console.log(` Adapter: ${config.adapter || 'auto'}`);
|
|
169
|
+
console.log(` Mode: ${args.mode}`);
|
|
170
|
+
console.log(` Debug: ${config.debug}`);
|
|
171
|
+
console.log();
|
|
172
|
+
console.log('--- System Prompt ---');
|
|
173
|
+
console.log(systemPrompt);
|
|
174
|
+
console.log();
|
|
175
|
+
console.log('--- Messages ---');
|
|
176
|
+
for (const msg of messages) {
|
|
177
|
+
console.log(` [${msg.role}] ${msg.content}`);
|
|
178
|
+
}
|
|
179
|
+
if (messages.length === 0) {
|
|
180
|
+
console.log(' (no messages)');
|
|
181
|
+
}
|
|
182
|
+
console.log();
|
|
183
|
+
console.log('=== END DRY RUN ===');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ─── REPL ──────────────────────────────────────────────────────
|
|
187
|
+
|
|
188
|
+
async function runREPL(config, args) {
|
|
189
|
+
const trace = createTrace({
|
|
190
|
+
enabled: config.debug,
|
|
191
|
+
dbPath: join(config.dir, 'debug.db'),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
let adapter;
|
|
195
|
+
let engine;
|
|
196
|
+
let currentMode = args.mode;
|
|
197
|
+
let conversationMessages = []; // persistent conversation for REPL
|
|
198
|
+
|
|
199
|
+
// Lazy adapter creation (don't fail on start if no API key for --trace-only usage)
|
|
200
|
+
async function ensureEngine() {
|
|
201
|
+
if (!engine) {
|
|
202
|
+
adapter = await createLLMAdapter(config);
|
|
203
|
+
engine = new Engine({ adapter, trace, config });
|
|
204
|
+
}
|
|
205
|
+
return engine;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log(`Yeaft Unify REPL (model: ${config.model}, mode: ${currentMode})`);
|
|
209
|
+
console.log('Type /help for commands, /quit to exit.');
|
|
210
|
+
console.log();
|
|
211
|
+
|
|
212
|
+
const rl = createInterface({
|
|
213
|
+
input: process.stdin,
|
|
214
|
+
output: process.stdout,
|
|
215
|
+
prompt: `yeaft:${currentMode}> `,
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
rl.prompt();
|
|
219
|
+
|
|
220
|
+
rl.on('line', async (line) => {
|
|
221
|
+
const input = line.trim();
|
|
222
|
+
if (!input) {
|
|
223
|
+
rl.prompt();
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Handle / commands
|
|
228
|
+
if (input.startsWith('/')) {
|
|
229
|
+
const [cmd, ...cmdArgs] = input.slice(1).split(/\s+/);
|
|
230
|
+
switch (cmd) {
|
|
231
|
+
case 'help':
|
|
232
|
+
console.log('Commands:');
|
|
233
|
+
console.log(' /mode <chat|work|dream> — Switch mode');
|
|
234
|
+
console.log(' /debug — Toggle debug mode');
|
|
235
|
+
console.log(' /trace <stats|recent> — Query debug trace');
|
|
236
|
+
console.log(' /memory — Show memory status');
|
|
237
|
+
console.log(' /context — Show context info');
|
|
238
|
+
console.log(' /dry-run — Toggle dry-run mode');
|
|
239
|
+
console.log(' /stats — Show session stats');
|
|
240
|
+
console.log(' /model <name> — Switch model');
|
|
241
|
+
console.log(' /models — List available models');
|
|
242
|
+
console.log(' /language <en|zh> — Switch language');
|
|
243
|
+
console.log(' /clear — Clear conversation history');
|
|
244
|
+
console.log(' /quit — Exit');
|
|
245
|
+
break;
|
|
246
|
+
|
|
247
|
+
case 'mode':
|
|
248
|
+
if (cmdArgs[0]) {
|
|
249
|
+
currentMode = cmdArgs[0];
|
|
250
|
+
rl.setPrompt(`yeaft:${currentMode}> `);
|
|
251
|
+
console.log(`Mode switched to: ${currentMode}`);
|
|
252
|
+
} else {
|
|
253
|
+
console.log(`Current mode: ${currentMode}`);
|
|
254
|
+
}
|
|
255
|
+
break;
|
|
256
|
+
|
|
257
|
+
case 'debug':
|
|
258
|
+
config.debug = !config.debug;
|
|
259
|
+
console.log(`Debug mode: ${config.debug ? 'ON' : 'OFF'}`);
|
|
260
|
+
break;
|
|
261
|
+
|
|
262
|
+
case 'trace': {
|
|
263
|
+
const subcmd = cmdArgs[0] || 'stats';
|
|
264
|
+
try {
|
|
265
|
+
handleTraceQuery({ trace: subcmd, traceArg: cmdArgs[1] }, config);
|
|
266
|
+
} catch (e) {
|
|
267
|
+
console.error(`Trace error: ${e.message}`);
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
case 'memory':
|
|
273
|
+
console.log('Memory status: (not yet implemented — Phase 2)');
|
|
274
|
+
break;
|
|
275
|
+
|
|
276
|
+
case 'context':
|
|
277
|
+
console.log(`Context info:`);
|
|
278
|
+
console.log(` Model: ${config.model}`);
|
|
279
|
+
console.log(` Mode: ${currentMode}`);
|
|
280
|
+
console.log(` Language: ${config.language}`);
|
|
281
|
+
console.log(` Max context: ${config.maxContextTokens} tokens`);
|
|
282
|
+
console.log(` System prompt: ${buildSystemPrompt({ language: config.language, mode: currentMode }).length} chars`);
|
|
283
|
+
break;
|
|
284
|
+
|
|
285
|
+
case 'dry-run':
|
|
286
|
+
handleDryRun({ ...args, mode: currentMode, prompt: cmdArgs.join(' ') || null }, config);
|
|
287
|
+
break;
|
|
288
|
+
|
|
289
|
+
case 'stats': {
|
|
290
|
+
const s = trace.stats();
|
|
291
|
+
console.log(`Session stats:`);
|
|
292
|
+
console.log(` Mode: ${currentMode}`);
|
|
293
|
+
console.log(` Debug: ${config.debug}`);
|
|
294
|
+
console.log(` Turns: ${s.turnCount}`);
|
|
295
|
+
console.log(` Tools: ${s.toolCount}`);
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
case 'model':
|
|
300
|
+
if (cmdArgs[0]) {
|
|
301
|
+
try {
|
|
302
|
+
config.model = cmdArgs[0];
|
|
303
|
+
// Re-resolve adapter and baseUrl from model registry
|
|
304
|
+
const newModelInfo = resolveModel(config.model);
|
|
305
|
+
if (newModelInfo) {
|
|
306
|
+
config.adapter = newModelInfo.adapter === 'anthropic' ? 'anthropic' : 'openai';
|
|
307
|
+
config.baseUrl = newModelInfo.baseUrl;
|
|
308
|
+
config.maxContextTokens = newModelInfo.contextWindow;
|
|
309
|
+
config.maxOutputTokens = newModelInfo.maxOutputTokens;
|
|
310
|
+
config.modelInfo = newModelInfo;
|
|
311
|
+
}
|
|
312
|
+
engine = null; // Force re-creation with new model + adapter
|
|
313
|
+
console.log(`Model switched to: ${config.model} (adapter: ${config.adapter})`);
|
|
314
|
+
} catch (e) {
|
|
315
|
+
console.error(`Error switching model: ${e.message}`);
|
|
316
|
+
}
|
|
317
|
+
} else {
|
|
318
|
+
console.log(`Current model: ${config.model} (adapter: ${config.adapter})`);
|
|
319
|
+
}
|
|
320
|
+
break;
|
|
321
|
+
|
|
322
|
+
case 'models': {
|
|
323
|
+
const models = listModels();
|
|
324
|
+
console.log('Available models:');
|
|
325
|
+
for (const m of models) {
|
|
326
|
+
const current = m.name === config.model ? ' ← current' : '';
|
|
327
|
+
console.log(` ${m.name} (${m.displayName}) — ${m.adapter}, ${(m.contextWindow / 1000).toFixed(0)}K ctx${current}`);
|
|
328
|
+
}
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
case 'language':
|
|
333
|
+
case 'lang':
|
|
334
|
+
if (cmdArgs[0]) {
|
|
335
|
+
config.language = cmdArgs[0];
|
|
336
|
+
console.log(`Language switched to: ${config.language}`);
|
|
337
|
+
} else {
|
|
338
|
+
console.log(`Current language: ${config.language}`);
|
|
339
|
+
}
|
|
340
|
+
break;
|
|
341
|
+
|
|
342
|
+
case 'clear':
|
|
343
|
+
conversationMessages = [];
|
|
344
|
+
console.log('Conversation history cleared.');
|
|
345
|
+
break;
|
|
346
|
+
|
|
347
|
+
case 'quit':
|
|
348
|
+
case 'exit':
|
|
349
|
+
case 'q':
|
|
350
|
+
rl.close(); // close handler does trace.close() + process.exit()
|
|
351
|
+
return; // don't call rl.prompt() below
|
|
352
|
+
|
|
353
|
+
default:
|
|
354
|
+
console.log(`Unknown command: /${cmd}. Type /help for commands.`);
|
|
355
|
+
}
|
|
356
|
+
rl.prompt();
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Regular input → engine.query
|
|
361
|
+
try {
|
|
362
|
+
const eng = await ensureEngine();
|
|
363
|
+
let responseText = '';
|
|
364
|
+
|
|
365
|
+
for await (const event of eng.query({
|
|
366
|
+
prompt: input,
|
|
367
|
+
mode: currentMode,
|
|
368
|
+
messages: conversationMessages,
|
|
369
|
+
})) {
|
|
370
|
+
switch (event.type) {
|
|
371
|
+
case 'text_delta':
|
|
372
|
+
responseText += event.text;
|
|
373
|
+
process.stdout.write(event.text);
|
|
374
|
+
break;
|
|
375
|
+
case 'tool_start':
|
|
376
|
+
if (config.debug) {
|
|
377
|
+
process.stderr.write(`\n[tool] ${event.name}(${JSON.stringify(event.input)})\n`);
|
|
378
|
+
}
|
|
379
|
+
break;
|
|
380
|
+
case 'tool_end':
|
|
381
|
+
if (config.debug) {
|
|
382
|
+
const status = event.isError ? 'ERROR' : 'OK';
|
|
383
|
+
process.stderr.write(`[tool] ${event.name} → ${status}\n`);
|
|
384
|
+
}
|
|
385
|
+
break;
|
|
386
|
+
case 'error':
|
|
387
|
+
process.stderr.write(`\nError: ${event.error.message}\n`);
|
|
388
|
+
break;
|
|
389
|
+
case 'turn_start':
|
|
390
|
+
if (config.debug && event.turnNumber > 1) {
|
|
391
|
+
process.stderr.write(`\n--- Turn ${event.turnNumber} ---\n`);
|
|
392
|
+
}
|
|
393
|
+
break;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
console.log(); // newline after response
|
|
397
|
+
|
|
398
|
+
// Save both user and assistant messages for multi-turn context
|
|
399
|
+
conversationMessages.push({ role: 'user', content: input });
|
|
400
|
+
if (responseText) {
|
|
401
|
+
conversationMessages.push({ role: 'assistant', content: responseText });
|
|
402
|
+
}
|
|
403
|
+
} catch (err) {
|
|
404
|
+
console.error(`Error: ${err.message}`);
|
|
405
|
+
}
|
|
406
|
+
rl.prompt();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
rl.on('close', () => {
|
|
410
|
+
trace.close();
|
|
411
|
+
console.log('\nBye!');
|
|
412
|
+
process.exit(0);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// ─── One-shot handler ──────────────────────────────────────────
|
|
417
|
+
|
|
418
|
+
async function runOnce(config, args) {
|
|
419
|
+
const trace = createTrace({
|
|
420
|
+
enabled: config.debug,
|
|
421
|
+
dbPath: join(config.dir, 'debug.db'),
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
try {
|
|
425
|
+
if (args.dryRun) {
|
|
426
|
+
handleDryRun(args, config);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const adapter = await createLLMAdapter(config);
|
|
431
|
+
const engine = new Engine({ adapter, trace, config });
|
|
432
|
+
|
|
433
|
+
for await (const event of engine.query({ prompt: args.prompt, mode: args.mode })) {
|
|
434
|
+
switch (event.type) {
|
|
435
|
+
case 'text_delta':
|
|
436
|
+
process.stdout.write(event.text);
|
|
437
|
+
break;
|
|
438
|
+
case 'tool_start':
|
|
439
|
+
if (args.verbose) {
|
|
440
|
+
process.stderr.write(`\n[tool] ${event.name}(${JSON.stringify(event.input)})\n`);
|
|
441
|
+
}
|
|
442
|
+
break;
|
|
443
|
+
case 'tool_end':
|
|
444
|
+
if (args.verbose) {
|
|
445
|
+
const status = event.isError ? 'ERROR' : 'OK';
|
|
446
|
+
process.stderr.write(`[tool] ${event.name} → ${status}\n`);
|
|
447
|
+
}
|
|
448
|
+
break;
|
|
449
|
+
case 'error':
|
|
450
|
+
process.stderr.write(`\nError: ${event.error.message}\n`);
|
|
451
|
+
break;
|
|
452
|
+
case 'turn_start':
|
|
453
|
+
if (args.verbose && event.turnNumber > 1) {
|
|
454
|
+
process.stderr.write(`\n--- Turn ${event.turnNumber} ---\n`);
|
|
455
|
+
}
|
|
456
|
+
break;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
// Final newline after streaming text
|
|
460
|
+
console.log();
|
|
461
|
+
} finally {
|
|
462
|
+
trace.close();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// ─── Main ──────────────────────────────────────────────────────
|
|
467
|
+
|
|
468
|
+
async function main() {
|
|
469
|
+
const args = parseArgs(process.argv);
|
|
470
|
+
|
|
471
|
+
// Load config with CLI overrides
|
|
472
|
+
const config = loadConfig({
|
|
473
|
+
model: args.model,
|
|
474
|
+
language: args.language,
|
|
475
|
+
debug: args.debug || undefined,
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// Initialize directory structure
|
|
479
|
+
initYeaftDir(config.dir);
|
|
480
|
+
|
|
481
|
+
// Handle --trace queries (no LLM needed)
|
|
482
|
+
if (args.trace) {
|
|
483
|
+
handleTraceQuery(args, config);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Handle interactive mode
|
|
488
|
+
if (args.interactive) {
|
|
489
|
+
await runREPL(config, args);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Handle prompt (from args or stdin)
|
|
494
|
+
if (args.prompt) {
|
|
495
|
+
await runOnce(config, args);
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// Read from stdin if piped
|
|
500
|
+
if (!process.stdin.isTTY) {
|
|
501
|
+
let input = '';
|
|
502
|
+
for await (const chunk of process.stdin) {
|
|
503
|
+
input += chunk;
|
|
504
|
+
}
|
|
505
|
+
args.prompt = input.trim();
|
|
506
|
+
if (args.prompt) {
|
|
507
|
+
await runOnce(config, args);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// No input and not interactive — show help
|
|
513
|
+
console.log('Yeaft Unify CLI');
|
|
514
|
+
console.log();
|
|
515
|
+
console.log('Usage:');
|
|
516
|
+
console.log(' node cli.js "your prompt" — One-shot query');
|
|
517
|
+
console.log(' node cli.js -i — Interactive REPL');
|
|
518
|
+
console.log(' node cli.js --dry-run "prompt" — Show what would be sent');
|
|
519
|
+
console.log(' node cli.js --trace stats — Debug trace statistics');
|
|
520
|
+
console.log(' node cli.js --trace recent — Recent turns');
|
|
521
|
+
console.log(' node cli.js --trace search "keyword" — Search traces');
|
|
522
|
+
console.log();
|
|
523
|
+
console.log('Options:');
|
|
524
|
+
console.log(' -m, --mode <mode> Mode: chat, work, dream (default: chat)');
|
|
525
|
+
console.log(' -d, --debug Enable debug tracing');
|
|
526
|
+
console.log(' -i, --interactive Start REPL');
|
|
527
|
+
console.log(' -v, --verbose Verbose output');
|
|
528
|
+
console.log(' --model <name> Override model');
|
|
529
|
+
console.log(' --language <code> Language: en, zh (default: en)');
|
|
530
|
+
console.log(' --trace <cmd> Query debug trace');
|
|
531
|
+
console.log(' --dry-run Show prompt without calling LLM');
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
main().catch(err => {
|
|
535
|
+
console.error(err);
|
|
536
|
+
process.exit(1);
|
|
537
|
+
});
|