opc-agent 4.2.0 → 4.2.1

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.
Files changed (119) hide show
  1. package/.opc/memory.db +0 -0
  2. package/COMPETITIVE-GAP.md +92 -92
  3. package/CONTRIBUTING.md +36 -36
  4. package/README.md +290 -290
  5. package/README.zh-CN.md +269 -269
  6. package/STUDIO-REWRITE-TASK.md +76 -0
  7. package/dist/channels/telegram.d.ts +5 -0
  8. package/dist/channels/telegram.d.ts.map +1 -1
  9. package/dist/channels/telegram.js +108 -0
  10. package/dist/channels/telegram.js.map +1 -1
  11. package/dist/channels/voice.d.ts +71 -97
  12. package/dist/channels/voice.d.ts.map +1 -1
  13. package/dist/channels/voice.js +369 -347
  14. package/dist/channels/voice.js.map +1 -1
  15. package/dist/channels/web.d.ts.map +1 -1
  16. package/dist/channels/web.js +8 -2
  17. package/dist/channels/web.js.map +1 -1
  18. package/dist/channels/wechat.js +6 -6
  19. package/dist/cli/chat.d.ts +4 -1
  20. package/dist/cli/chat.d.ts.map +1 -1
  21. package/dist/cli/chat.js +680 -73
  22. package/dist/cli/chat.js.map +1 -1
  23. package/dist/cli/setup.js +1 -1
  24. package/dist/cli/setup.js.map +1 -1
  25. package/dist/cli.js +373 -280
  26. package/dist/cli.js.map +1 -1
  27. package/dist/core/a2a-http.d.ts +75 -0
  28. package/dist/core/a2a-http.d.ts.map +1 -0
  29. package/dist/core/a2a-http.js +217 -0
  30. package/dist/core/a2a-http.js.map +1 -0
  31. package/dist/core/a2a.d.ts +2 -0
  32. package/dist/core/a2a.d.ts.map +1 -1
  33. package/dist/core/a2a.js +6 -1
  34. package/dist/core/a2a.js.map +1 -1
  35. package/dist/core/agent.d.ts +1 -0
  36. package/dist/core/agent.d.ts.map +1 -1
  37. package/dist/core/agent.js +3 -0
  38. package/dist/core/agent.js.map +1 -1
  39. package/dist/core/gateway-registry.d.ts +116 -0
  40. package/dist/core/gateway-registry.d.ts.map +1 -0
  41. package/dist/core/gateway-registry.js +280 -0
  42. package/dist/core/gateway-registry.js.map +1 -0
  43. package/dist/core/model-recommender.d.ts +40 -0
  44. package/dist/core/model-recommender.d.ts.map +1 -0
  45. package/dist/core/model-recommender.js +186 -0
  46. package/dist/core/model-recommender.js.map +1 -0
  47. package/dist/core/priority-queue.d.ts +100 -0
  48. package/dist/core/priority-queue.d.ts.map +1 -0
  49. package/dist/core/priority-queue.js +181 -0
  50. package/dist/core/priority-queue.js.map +1 -0
  51. package/dist/core/runtime.d.ts.map +1 -1
  52. package/dist/core/runtime.js +192 -22
  53. package/dist/core/runtime.js.map +1 -1
  54. package/dist/deploy/index.js +56 -56
  55. package/dist/doctor.d.ts +1 -0
  56. package/dist/doctor.d.ts.map +1 -1
  57. package/dist/doctor.js +155 -10
  58. package/dist/doctor.js.map +1 -1
  59. package/dist/index.d.ts +10 -3
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +24 -13
  62. package/dist/index.js.map +1 -1
  63. package/dist/memory/deepbrain.d.ts +1 -1
  64. package/dist/memory/deepbrain.d.ts.map +1 -1
  65. package/dist/memory/deepbrain.js +95 -4
  66. package/dist/memory/deepbrain.js.map +1 -1
  67. package/dist/memory/evolve-engine.d.ts +113 -0
  68. package/dist/memory/evolve-engine.d.ts.map +1 -0
  69. package/dist/memory/evolve-engine.js +549 -0
  70. package/dist/memory/evolve-engine.js.map +1 -0
  71. package/dist/memory/index.d.ts +2 -0
  72. package/dist/memory/index.d.ts.map +1 -1
  73. package/dist/memory/index.js +3 -1
  74. package/dist/memory/index.js.map +1 -1
  75. package/dist/memory/sqlite-store.d.ts +40 -0
  76. package/dist/memory/sqlite-store.d.ts.map +1 -0
  77. package/dist/memory/sqlite-store.js +269 -0
  78. package/dist/memory/sqlite-store.js.map +1 -0
  79. package/dist/memory/user-profiler.d.ts +8 -0
  80. package/dist/memory/user-profiler.d.ts.map +1 -1
  81. package/dist/memory/user-profiler.js +89 -0
  82. package/dist/memory/user-profiler.js.map +1 -1
  83. package/dist/scheduler/cron-engine.d.ts.map +1 -1
  84. package/dist/scheduler/cron-engine.js +3 -36
  85. package/dist/scheduler/cron-engine.js.map +1 -1
  86. package/dist/scheduler/proactive.d.ts +62 -0
  87. package/dist/scheduler/proactive.d.ts.map +1 -0
  88. package/dist/scheduler/proactive.js +185 -0
  89. package/dist/scheduler/proactive.js.map +1 -0
  90. package/dist/skills/auto-learn.d.ts.map +1 -1
  91. package/dist/skills/auto-learn.js +65 -11
  92. package/dist/skills/auto-learn.js.map +1 -1
  93. package/dist/skills/builtin/index.d.ts.map +1 -1
  94. package/dist/skills/builtin/index.js +163 -30
  95. package/dist/skills/builtin/index.js.map +1 -1
  96. package/dist/skills/types.d.ts +1 -1
  97. package/dist/skills/types.d.ts.map +1 -1
  98. package/dist/skills/types.js +1 -0
  99. package/dist/skills/types.js.map +1 -1
  100. package/dist/studio/server.d.ts +1 -0
  101. package/dist/studio/server.d.ts.map +1 -1
  102. package/dist/studio/server.js +148 -17
  103. package/dist/studio/server.js.map +1 -1
  104. package/dist/studio-ui/index.html +867 -2630
  105. package/dist/ui/components.js +105 -105
  106. package/examples/README.md +22 -22
  107. package/examples/basic-agent.ts +90 -90
  108. package/examples/brain-integration.ts +71 -71
  109. package/examples/multi-channel.ts +74 -74
  110. package/install.ps1 +127 -127
  111. package/install.sh +154 -154
  112. package/models.json +164 -164
  113. package/package.json +5 -2
  114. package/scripts/install.ps1 +31 -31
  115. package/scripts/install.sh +40 -40
  116. package/templates/ecommerce-assistant/README.md +45 -45
  117. package/templates/ecommerce-assistant/oad.yaml +47 -47
  118. package/templates/tech-support/README.md +43 -43
  119. package/templates/tech-support/oad.yaml +45 -45
package/dist/cli/chat.js CHANGED
@@ -37,98 +37,705 @@ exports.runChat = runChat;
37
37
  const readline = __importStar(require("readline"));
38
38
  const fs = __importStar(require("fs"));
39
39
  const path = __importStar(require("path"));
40
- const os = __importStar(require("os"));
40
+ const yaml = __importStar(require("js-yaml"));
41
41
  const providers_1 = require("../providers");
42
- const OPC_HOME = path.join(os.homedir(), '.opc');
43
- const CONFIG_PATH = path.join(OPC_HOME, 'config.json');
44
- const c = {
45
- green: (s) => `\x1b[32m${s}\x1b[0m`,
46
- red: (s) => `\x1b[31m${s}\x1b[0m`,
47
- cyan: (s) => `\x1b[36m${s}\x1b[0m`,
48
- bold: (s) => `\x1b[1m${s}\x1b[0m`,
49
- dim: (s) => `\x1b[2m${s}\x1b[0m`,
42
+ // ── ANSI helpers ────────────────────────────────────────────
43
+ const ESC = '\x1b';
44
+ const CSI = `${ESC}[`;
45
+ const ansi = {
46
+ reset: `${CSI}0m`,
47
+ bold: `${CSI}1m`,
48
+ dim: `${CSI}2m`,
49
+ italic: `${CSI}3m`,
50
+ underline: `${CSI}4m`,
51
+ inverse: `${CSI}7m`,
52
+ // foreground
53
+ black: `${CSI}30m`,
54
+ red: `${CSI}31m`,
55
+ green: `${CSI}32m`,
56
+ yellow: `${CSI}33m`,
57
+ blue: `${CSI}34m`,
58
+ magenta: `${CSI}35m`,
59
+ cyan: `${CSI}36m`,
60
+ white: `${CSI}37m`,
61
+ gray: `${CSI}90m`,
62
+ // background
63
+ bgBlack: `${CSI}40m`,
64
+ bgRed: `${CSI}41m`,
65
+ bgGreen: `${CSI}42m`,
66
+ bgYellow: `${CSI}43m`,
67
+ bgBlue: `${CSI}44m`,
68
+ bgWhite: `${CSI}47m`,
69
+ bgGray: `${CSI}100m`,
70
+ // cursor / screen
71
+ clearScreen: `${CSI}2J`,
72
+ clearLine: `${CSI}2K`,
73
+ cursorHome: `${CSI}H`,
74
+ cursorTo: (row, col) => `${CSI}${row};${col}H`,
75
+ cursorUp: (n) => `${CSI}${n}A`,
76
+ cursorDown: (n) => `${CSI}${n}B`,
77
+ cursorSave: `${ESC}7`,
78
+ cursorRestore: `${ESC}8`,
79
+ scrollRegion: (top, bottom) => `${CSI}${top};${bottom}r`,
80
+ hideCursor: `${CSI}?25l`,
81
+ showCursor: `${CSI}?25h`,
50
82
  };
51
- function loadConfig() {
52
- if (!fs.existsSync(CONFIG_PATH))
53
- return {};
54
- try {
55
- return JSON.parse(fs.readFileSync(CONFIG_PATH, 'utf-8'));
56
- }
57
- catch {
58
- return {};
83
+ function c(style, text) {
84
+ return `${style}${text}${ansi.reset}`;
85
+ }
86
+ function stripAnsi(s) {
87
+ return s.replace(/\x1b\[[0-9;]*[a-zA-Z]/g, '');
88
+ }
89
+ // ── Markdown → ANSI renderer ────────────────────────────────
90
+ function renderMarkdown(text) {
91
+ const lines = text.split('\n');
92
+ const out = [];
93
+ let inCodeBlock = false;
94
+ let codeLang = '';
95
+ for (const line of lines) {
96
+ // Code block toggle
97
+ if (line.trimStart().startsWith('```')) {
98
+ if (!inCodeBlock) {
99
+ inCodeBlock = true;
100
+ codeLang = line.trimStart().slice(3).trim();
101
+ const label = codeLang ? ` ${codeLang} ` : '';
102
+ out.push(`${ansi.bgGray}${ansi.white}${label}${'─'.repeat(Math.max(0, 50 - label.length))}${ansi.reset}`);
103
+ }
104
+ else {
105
+ inCodeBlock = false;
106
+ out.push(`${ansi.bgGray}${'─'.repeat(50)}${ansi.reset}`);
107
+ }
108
+ continue;
109
+ }
110
+ if (inCodeBlock) {
111
+ out.push(`${ansi.bgGray}${ansi.white} ${line}${ansi.reset}`);
112
+ continue;
113
+ }
114
+ let rendered = line;
115
+ // Headers
116
+ const hMatch = rendered.match(/^(#{1,3})\s+(.*)/);
117
+ if (hMatch) {
118
+ out.push(c(ansi.bold + ansi.cyan, hMatch[2]));
119
+ continue;
120
+ }
121
+ // Bullet lists
122
+ if (/^\s*[-*]\s/.test(rendered)) {
123
+ const indent = rendered.match(/^(\s*)/)?.[1] || '';
124
+ rendered = rendered.replace(/^(\s*)[-*]\s/, '');
125
+ rendered = renderInline(rendered);
126
+ out.push(`${indent} ${ansi.cyan}•${ansi.reset} ${rendered}`);
127
+ continue;
128
+ }
129
+ // Numbered lists
130
+ if (/^\s*\d+\.\s/.test(rendered)) {
131
+ const match = rendered.match(/^(\s*)(\d+)\.\s(.*)/);
132
+ if (match) {
133
+ out.push(`${match[1]} ${ansi.cyan}${match[2]}.${ansi.reset} ${renderInline(match[3])}`);
134
+ continue;
135
+ }
136
+ }
137
+ out.push(renderInline(rendered));
59
138
  }
139
+ return out.join('\n');
60
140
  }
61
- async function runChat() {
62
- const config = loadConfig();
63
- if (!config.provider || !config.model) {
64
- console.log(`${c.red('✘')} 未找到配置。请先运行 ${c.cyan('opc setup')} 完成初始设置。`);
65
- process.exit(1);
141
+ function renderInline(text) {
142
+ // Bold **text** or __text__
143
+ text = text.replace(/\*\*(.+?)\*\*/g, `${ansi.bold}$1${ansi.reset}`);
144
+ text = text.replace(/__(.+?)__/g, `${ansi.bold}$1${ansi.reset}`);
145
+ // Italic *text* or _text_
146
+ text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, `${ansi.italic}$1${ansi.reset}`);
147
+ // Inline code `text`
148
+ text = text.replace(/`([^`]+)`/g, `${ansi.bgGray}${ansi.white} $1 ${ansi.reset}`);
149
+ return text;
150
+ }
151
+ function loadChatConfig(oadFile = 'oad.yaml') {
152
+ let agentName = 'Agent';
153
+ let agentVersion = '1.0.0';
154
+ let providerName = 'auto';
155
+ let model = 'auto';
156
+ let systemPrompt = 'You are a helpful AI agent.';
157
+ let skillNames = [];
158
+ // Load SOUL.md + CONTEXT.md
159
+ const soulPath = path.resolve('SOUL.md');
160
+ const contextPath = path.resolve('CONTEXT.md');
161
+ const soul = fs.existsSync(soulPath) ? fs.readFileSync(soulPath, 'utf-8') : '';
162
+ const context = fs.existsSync(contextPath) ? fs.readFileSync(contextPath, 'utf-8') : '';
163
+ try {
164
+ const raw = fs.readFileSync(oadFile, 'utf-8');
165
+ const config = yaml.load(raw);
166
+ if (config?.spec?.systemPrompt)
167
+ systemPrompt = config.spec.systemPrompt;
168
+ if (config?.spec?.model)
169
+ model = config.spec.model;
170
+ if (config?.metadata?.name)
171
+ agentName = config.metadata.name;
172
+ if (config?.metadata?.version)
173
+ agentVersion = config.metadata.version;
174
+ if (config?.spec?.provider?.default)
175
+ providerName = config.spec.provider.default;
176
+ if (config?.spec?.skills)
177
+ skillNames = config.spec.skills.map((s) => s.name);
66
178
  }
67
- const agentId = config.defaultAgent;
68
- let agentConfig = {};
69
- if (agentId) {
70
- const agentPath = path.join(OPC_HOME, 'agents', agentId, 'config.json');
71
- if (fs.existsSync(agentPath)) {
72
- try {
73
- agentConfig = JSON.parse(fs.readFileSync(agentPath, 'utf-8'));
74
- }
75
- catch { /* ignore */ }
76
- }
77
- }
78
- const agentName = agentConfig.name || 'AI 助手';
79
- const systemPrompt = agentConfig.description
80
- ? `你是「${agentName}」,${agentConfig.description}。请用简洁友好的方式回答。`
81
- : `你是「${agentName}」,一个智能 AI 助手。请用简洁友好的方式回答。`;
82
- console.log('');
83
- console.log(c.bold(`💬 与「${agentName}」对话`));
84
- console.log(c.dim(' 输入 /quit 退出'));
85
- console.log('');
179
+ catch { /* no config */ }
180
+ systemPrompt = [soul, context, systemPrompt].filter(Boolean).join('\n\n');
181
+ return { agentName, agentVersion, providerName, model, systemPrompt, skillNames };
182
+ }
183
+ // ── Slash commands ──────────────────────────────────────────
184
+ const COMMANDS = ['/help', '/clear', '/model', '/tools', '/skills', '/history', '/status', '/quit'];
185
+ async function runChat(options) {
186
+ const oadFile = options?.file || 'oad.yaml';
187
+ const config = loadChatConfig(oadFile);
188
+ const startTime = Date.now();
189
+ // Create provider
86
190
  let provider;
87
191
  try {
88
- provider = (0, providers_1.createProvider)({
89
- provider: config.provider,
90
- model: config.model,
91
- apiKey: config.apiKey,
92
- baseUrl: config.baseUrl,
93
- });
192
+ provider = (0, providers_1.createProvider)(config.providerName, config.model);
94
193
  }
95
194
  catch (err) {
96
- console.log(`${c.red('')} 无法初始化模型: ${err.message}`);
97
- console.log(c.dim(` Provider: ${config.provider}, Model: ${config.model}`));
195
+ console.error(`${ansi.red}Failed to create provider: ${err.message}${ansi.reset}`);
98
196
  process.exit(1);
99
197
  }
100
- const messages = [
101
- { role: 'system', content: systemPrompt },
102
- ];
103
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
104
- const askQuestion = () => {
105
- rl.question(c.cyan('你: '), async (input) => {
106
- const trimmed = input.trim();
107
- if (!trimmed) {
108
- askQuestion();
109
- return;
198
+ const messages = [];
199
+ const inputHistory = [];
200
+ let inputHistoryIdx = -1;
201
+ let currentInput = '';
202
+ let isStreaming = false;
203
+ let abortStream = false;
204
+ const recentTools = [];
205
+ const cols = () => process.stdout.columns || 80;
206
+ const rows = () => process.stdout.rows || 24;
207
+ // ── Render status bar ───────────────────────────────────
208
+ function renderStatusBar() {
209
+ const w = cols();
210
+ const uptimeSec = Math.floor((Date.now() - startTime) / 1000);
211
+ const h = Math.floor(uptimeSec / 3600);
212
+ const m = Math.floor((uptimeSec % 3600) / 60);
213
+ const s = uptimeSec % 60;
214
+ const uptimeStr = h > 0 ? `${h}h${m}m` : `${m}m${s}s`;
215
+ const left = ` 🤖 ${config.agentName} `;
216
+ const mid = ` ${config.providerName}/${config.model} `;
217
+ const right = ` ⏱${uptimeStr} | 🔧${config.skillNames.length} skills `;
218
+ const padding = Math.max(0, w - stripAnsi(left).length - stripAnsi(mid).length - stripAnsi(right).length);
219
+ const bar = left + mid + ' '.repeat(padding) + right;
220
+ process.stdout.write(ansi.cursorSave);
221
+ process.stdout.write(ansi.cursorTo(1, 1));
222
+ process.stdout.write(`${ansi.bgBlue}${ansi.white}${ansi.bold}${bar.padEnd(w)}${ansi.reset}`);
223
+ process.stdout.write(ansi.cursorRestore);
224
+ }
225
+ // ── Render input bar ────────────────────────────────────
226
+ function renderInputBar(text) {
227
+ const w = cols();
228
+ const r = rows();
229
+ process.stdout.write(ansi.cursorSave);
230
+ // Separator line
231
+ process.stdout.write(ansi.cursorTo(r - 1, 1));
232
+ process.stdout.write(`${ansi.gray}${'─'.repeat(w)}${ansi.reset}`);
233
+ // Input line
234
+ process.stdout.write(ansi.cursorTo(r, 1));
235
+ process.stdout.write(ansi.clearLine);
236
+ const prompt = `${ansi.cyan}${ansi.bold}❯ ${ansi.reset}`;
237
+ const maxLen = w - 4;
238
+ const displayText = text.length > maxLen ? '…' + text.slice(-(maxLen - 1)) : text;
239
+ process.stdout.write(`${prompt}${displayText}`);
240
+ process.stdout.write(ansi.cursorRestore);
241
+ }
242
+ // ── Print message in chat area ──────────────────────────
243
+ let chatLine = 3; // start below status bar
244
+ function printToChat(text) {
245
+ const r = rows();
246
+ const maxChatLine = r - 2; // leave 2 lines for input area
247
+ const lines = text.split('\n');
248
+ for (const line of lines) {
249
+ if (chatLine >= maxChatLine) {
250
+ // Scroll: move everything up
251
+ process.stdout.write(ansi.cursorTo(3, 1));
252
+ process.stdout.write(`${CSI}1S`); // scroll up 1 line in region
253
+ chatLine = maxChatLine - 1;
254
+ }
255
+ process.stdout.write(ansi.cursorTo(chatLine, 1));
256
+ process.stdout.write(ansi.clearLine);
257
+ process.stdout.write(line);
258
+ chatLine++;
259
+ }
260
+ }
261
+ function printSystemMsg(text) {
262
+ printToChat(`${ansi.gray} ${text}${ansi.reset}`);
263
+ }
264
+ function printUserMsg(text) {
265
+ printToChat(`${ansi.cyan}${ansi.bold} You: ${ansi.reset}${text}`);
266
+ printToChat('');
267
+ }
268
+ function printAssistantPrefix() {
269
+ printToChat(`${ansi.green}${ansi.bold} ${config.agentName}: ${ansi.reset}`);
270
+ }
271
+ // ── Clear and redraw ───────────────────────────────────
272
+ function fullRedraw() {
273
+ const r = rows();
274
+ process.stdout.write(ansi.clearScreen + ansi.cursorHome);
275
+ // Set scroll region (between status bar and input)
276
+ process.stdout.write(ansi.scrollRegion(3, r - 2));
277
+ chatLine = 3;
278
+ renderStatusBar();
279
+ // Re-render recent messages (last N that fit)
280
+ const maxLines = r - 5;
281
+ let lineCount = 0;
282
+ const toShow = [];
283
+ for (let i = messages.length - 1; i >= 0 && lineCount < maxLines; i--) {
284
+ const msg = messages[i];
285
+ const rendered = msg.role === 'user' ? msg.content : renderMarkdown(msg.content);
286
+ const lines = rendered.split('\n').length + 2;
287
+ lineCount += lines;
288
+ toShow.unshift(msg);
289
+ }
290
+ for (const msg of toShow) {
291
+ if (msg.role === 'user') {
292
+ printUserMsg(msg.content);
293
+ }
294
+ else if (msg.role === 'assistant') {
295
+ printToChat(`${ansi.green}${ansi.bold} ${config.agentName}: ${ansi.reset}`);
296
+ const rendered = renderMarkdown(msg.content);
297
+ for (const line of rendered.split('\n')) {
298
+ printToChat(` ${line}`);
299
+ }
300
+ printToChat('');
301
+ }
302
+ else {
303
+ printSystemMsg(msg.content);
304
+ }
305
+ }
306
+ renderInputBar(currentInput);
307
+ }
308
+ // ── Handle slash commands ──────────────────────────────
309
+ function handleCommand(cmd) {
310
+ const lower = cmd.toLowerCase().trim();
311
+ const parts = lower.split(/\s+/);
312
+ switch (parts[0]) {
313
+ case '/help':
314
+ printToChat('');
315
+ printSystemMsg('╭─── Commands ───────────────────────╮');
316
+ printSystemMsg('│ /help Show this help │');
317
+ printSystemMsg('│ /clear Clear screen & history │');
318
+ printSystemMsg('│ /model Show/switch model │');
319
+ printSystemMsg('│ /tools List available tools │');
320
+ printSystemMsg('│ /skills List skills │');
321
+ printSystemMsg('│ /history Export chat history │');
322
+ printSystemMsg('│ /status Show agent status │');
323
+ printSystemMsg('│ /quit Exit │');
324
+ printSystemMsg('╰────────────────────────────────────╯');
325
+ printSystemMsg('Shortcuts: Ctrl+C abort | Ctrl+L clear | ↑↓ history');
326
+ printToChat('');
327
+ return true;
328
+ case '/clear':
329
+ messages.length = 0;
330
+ fullRedraw();
331
+ printSystemMsg('Chat cleared.');
332
+ printToChat('');
333
+ return true;
334
+ case '/model':
335
+ printSystemMsg(`Current model: ${config.providerName}/${config.model}`);
336
+ printToChat('');
337
+ return true;
338
+ case '/tools':
339
+ printSystemMsg('Tools: (managed by agent runtime)');
340
+ if (recentTools.length > 0) {
341
+ printSystemMsg(`Recent: ${recentTools.join(', ')}`);
342
+ }
343
+ printToChat('');
344
+ return true;
345
+ case '/skills':
346
+ if (config.skillNames.length === 0) {
347
+ printSystemMsg('No skills registered.');
348
+ }
349
+ else {
350
+ printSystemMsg(`Skills (${config.skillNames.length}):`);
351
+ for (const s of config.skillNames) {
352
+ printSystemMsg(` • ${s}`);
353
+ }
354
+ }
355
+ printToChat('');
356
+ return true;
357
+ case '/history': {
358
+ const histFile = path.resolve(`chat-history-${Date.now()}.md`);
359
+ const content = messages.map(m => {
360
+ const prefix = m.role === 'user' ? '**You**' : m.role === 'assistant' ? `**${config.agentName}**` : '*System*';
361
+ return `${prefix}: ${m.content}`;
362
+ }).join('\n\n');
363
+ fs.writeFileSync(histFile, content);
364
+ printSystemMsg(`History exported to ${histFile}`);
365
+ printToChat('');
366
+ return true;
367
+ }
368
+ case '/status': {
369
+ const uptimeSec = Math.floor((Date.now() - startTime) / 1000);
370
+ const msgCount = messages.length;
371
+ const userMsgs = messages.filter(m => m.role === 'user').length;
372
+ printSystemMsg(`Agent: ${config.agentName} v${config.agentVersion}`);
373
+ printSystemMsg(`Model: ${config.providerName}/${config.model}`);
374
+ printSystemMsg(`Uptime: ${uptimeSec}s | Messages: ${msgCount} (${userMsgs} from you)`);
375
+ printSystemMsg(`Skills: ${config.skillNames.length}`);
376
+ printToChat('');
377
+ return true;
378
+ }
379
+ case '/quit':
380
+ case '/exit':
381
+ case '/q':
382
+ cleanup();
383
+ process.stdout.write(ansi.clearScreen + ansi.cursorHome + ansi.showCursor);
384
+ // Reset scroll region
385
+ process.stdout.write(`${CSI}r`);
386
+ console.log(`${ansi.dim}👋 Goodbye!${ansi.reset}`);
387
+ process.exit(0);
388
+ return true;
389
+ default:
390
+ printSystemMsg(`Unknown command: ${parts[0]}. Type /help for commands.`);
391
+ printToChat('');
392
+ return true;
393
+ }
394
+ }
395
+ // ── Stream AI response ─────────────────────────────────
396
+ async function sendMessage(text) {
397
+ messages.push({ role: 'user', content: text, timestamp: Date.now() });
398
+ printUserMsg(text);
399
+ // Build message array for provider
400
+ const history = messages.map(m => ({
401
+ id: `msg_${m.timestamp}`,
402
+ role: m.role,
403
+ content: m.content,
404
+ timestamp: m.timestamp,
405
+ }));
406
+ printAssistantPrefix();
407
+ isStreaming = true;
408
+ abortStream = false;
409
+ let fullResponse = '';
410
+ try {
411
+ let lineBuffer = '';
412
+ for await (const chunk of provider.chatStream(history, config.systemPrompt)) {
413
+ if (abortStream) {
414
+ fullResponse += '\n[interrupted]';
415
+ break;
416
+ }
417
+ fullResponse += chunk;
418
+ lineBuffer += chunk;
419
+ // Flush complete lines for markdown rendering
420
+ const nlIdx = lineBuffer.lastIndexOf('\n');
421
+ if (nlIdx >= 0) {
422
+ const complete = lineBuffer.slice(0, nlIdx);
423
+ lineBuffer = lineBuffer.slice(nlIdx + 1);
424
+ const rendered = renderMarkdown(complete);
425
+ for (const line of rendered.split('\n')) {
426
+ printToChat(` ${line}`);
427
+ }
428
+ }
429
+ }
430
+ // Flush remaining
431
+ if (lineBuffer.length > 0) {
432
+ const rendered = renderMarkdown(lineBuffer);
433
+ for (const line of rendered.split('\n')) {
434
+ printToChat(` ${line}`);
435
+ }
110
436
  }
111
- if (trimmed === '/quit' || trimmed === '/exit' || trimmed === '/q') {
112
- console.log(c.dim('\n👋 再见!'));
437
+ }
438
+ catch (err) {
439
+ printToChat(` ${ansi.red}Error: ${err.message}${ansi.reset}`);
440
+ fullResponse = `[Error: ${err.message}]`;
441
+ }
442
+ isStreaming = false;
443
+ printToChat('');
444
+ messages.push({ role: 'assistant', content: fullResponse, timestamp: Date.now() });
445
+ // Trim history
446
+ if (messages.length > 60) {
447
+ messages.splice(0, messages.length - 60);
448
+ }
449
+ // Update status bar (uptime changed)
450
+ renderStatusBar();
451
+ }
452
+ // ── Tab completion ─────────────────────────────────────
453
+ function tabComplete(text) {
454
+ if (!text.startsWith('/'))
455
+ return text;
456
+ const matches = COMMANDS.filter(c => c.startsWith(text));
457
+ if (matches.length === 1)
458
+ return matches[0];
459
+ if (matches.length > 1) {
460
+ printSystemMsg(`Completions: ${matches.join(' ')}`);
461
+ }
462
+ return text;
463
+ }
464
+ // ── Cleanup ────────────────────────────────────────────
465
+ function cleanup() {
466
+ process.stdin.setRawMode?.(false);
467
+ process.stdout.write(ansi.showCursor);
468
+ process.stdout.write(`${CSI}r`); // reset scroll region
469
+ }
470
+ // ── Setup raw mode input ──────────────────────────────
471
+ process.stdout.write(ansi.hideCursor);
472
+ fullRedraw();
473
+ printSystemMsg(`💬 Chat with ${config.agentName} — type /help for commands`);
474
+ printToChat('');
475
+ renderInputBar('');
476
+ // Use readline for input handling — simpler and more compatible
477
+ const rl = readline.createInterface({
478
+ input: process.stdin,
479
+ output: process.stdout,
480
+ terminal: true,
481
+ historySize: 100,
482
+ prompt: '',
483
+ completer: (line) => {
484
+ if (line.startsWith('/')) {
485
+ const matches = COMMANDS.filter(c => c.startsWith(line));
486
+ return [matches, line];
487
+ }
488
+ return [[], line];
489
+ },
490
+ });
491
+ // Override output to avoid readline messing with our TUI
492
+ // We'll use a simpler approach: standard readline but render our TUI around it
493
+ // Reset to simpler mode — full raw TUI is complex with readline
494
+ // Instead, use a hybrid: ANSI decorations + readline for input
495
+ process.stdout.write(ansi.showCursor);
496
+ process.stdout.write(`${CSI}r`); // reset scroll region
497
+ // Simpler but polished approach
498
+ process.stdout.write(ansi.clearScreen + ansi.cursorHome);
499
+ // Print banner
500
+ const w = cols();
501
+ const bannerBg = `${ansi.bgBlue}${ansi.white}${ansi.bold}`;
502
+ const uptimeStr = '0m0s';
503
+ const bannerLeft = ` 🤖 ${config.agentName} v${config.agentVersion}`;
504
+ const bannerMid = ` │ ${config.providerName}/${config.model}`;
505
+ const bannerRight = `⏱${uptimeStr} │ 🔧${config.skillNames.length} skills `;
506
+ const bannerPad = Math.max(0, w - bannerLeft.length - bannerMid.length - bannerRight.length);
507
+ process.stdout.write(`${bannerBg}${bannerLeft}${bannerMid}${' '.repeat(bannerPad)}${bannerRight}${ansi.reset}\n`);
508
+ process.stdout.write(`${ansi.gray}${'─'.repeat(w)}${ansi.reset}\n`);
509
+ // Welcome
510
+ const soulLoaded = fs.existsSync(path.resolve('SOUL.md'));
511
+ const ctxLoaded = fs.existsSync(path.resolve('CONTEXT.md'));
512
+ if (soulLoaded)
513
+ process.stdout.write(`${ansi.gray} ✦ SOUL.md loaded${ansi.reset}\n`);
514
+ if (ctxLoaded)
515
+ process.stdout.write(`${ansi.gray} ✦ CONTEXT.md loaded${ansi.reset}\n`);
516
+ process.stdout.write(`${ansi.gray} Type /help for commands, /quit to exit${ansi.reset}\n`);
517
+ process.stdout.write(`${ansi.gray}${'─'.repeat(w)}${ansi.reset}\n\n`);
518
+ // Now use readline for interactive input
519
+ const prompt = `${ansi.cyan}${ansi.bold}❯ ${ansi.reset}`;
520
+ rl.setPrompt(prompt);
521
+ rl.prompt();
522
+ rl.on('line', async (input) => {
523
+ const text = input.trim();
524
+ if (!text) {
525
+ rl.prompt();
526
+ return;
527
+ }
528
+ // Save to input history
529
+ inputHistory.push(text);
530
+ // Handle slash commands
531
+ if (text.startsWith('/')) {
532
+ if (text.toLowerCase() === '/quit' || text.toLowerCase() === '/exit' || text.toLowerCase() === '/q') {
533
+ process.stdout.write(`\n${ansi.dim}👋 Goodbye!${ansi.reset}\n`);
113
534
  rl.close();
535
+ process.exit(0);
536
+ }
537
+ if (text.toLowerCase() === '/help') {
538
+ console.log('');
539
+ console.log(`${ansi.gray} ╭─── Commands ─────────────────────────╮${ansi.reset}`);
540
+ console.log(`${ansi.gray} │ /help Show this help │${ansi.reset}`);
541
+ console.log(`${ansi.gray} │ /clear Clear screen │${ansi.reset}`);
542
+ console.log(`${ansi.gray} │ /model Show current model │${ansi.reset}`);
543
+ console.log(`${ansi.gray} │ /tools List available tools │${ansi.reset}`);
544
+ console.log(`${ansi.gray} │ /skills List skills │${ansi.reset}`);
545
+ console.log(`${ansi.gray} │ /history Export chat history │${ansi.reset}`);
546
+ console.log(`${ansi.gray} │ /status Show agent status │${ansi.reset}`);
547
+ console.log(`${ansi.gray} │ /quit Exit (/q, /exit) │${ansi.reset}`);
548
+ console.log(`${ansi.gray} ╰────────────────────────────────────────╯${ansi.reset}`);
549
+ console.log(`${ansi.gray} Shortcuts: Ctrl+C interrupt | Ctrl+L clear | ↑↓ history${ansi.reset}`);
550
+ console.log('');
551
+ rl.prompt();
552
+ return;
553
+ }
554
+ if (text.toLowerCase() === '/clear') {
555
+ messages.length = 0;
556
+ process.stdout.write(ansi.clearScreen + ansi.cursorHome);
557
+ const w2 = cols();
558
+ process.stdout.write(`${bannerBg} 🤖 ${config.agentName} v${config.agentVersion} │ ${config.providerName}/${config.model}${' '.repeat(Math.max(0, w2 - 60))}${ansi.reset}\n`);
559
+ process.stdout.write(`${ansi.gray}${'─'.repeat(w2)}${ansi.reset}\n`);
560
+ console.log(`${ansi.gray} ✦ Chat cleared${ansi.reset}\n`);
561
+ rl.prompt();
114
562
  return;
115
563
  }
116
- messages.push({ role: 'user', content: trimmed });
117
- try {
118
- process.stdout.write(c.green(`${agentName}: `));
119
- const response = await provider.chat(messages);
120
- const reply = typeof response === 'string' ? response : response?.content || response?.message?.content || JSON.stringify(response);
121
- console.log(reply);
564
+ if (text.toLowerCase() === '/model') {
565
+ console.log(`\n${ansi.gray} Model: ${ansi.cyan}${config.providerName}/${config.model}${ansi.reset}\n`);
566
+ rl.prompt();
567
+ return;
568
+ }
569
+ if (text.toLowerCase() === '/tools') {
570
+ console.log(`\n${ansi.gray} Tools are managed by agent runtime.${ansi.reset}`);
571
+ if (recentTools.length > 0) {
572
+ console.log(`${ansi.gray} Recent: ${recentTools.join(', ')}${ansi.reset}`);
573
+ }
122
574
  console.log('');
123
- messages.push({ role: 'assistant', content: reply });
575
+ rl.prompt();
576
+ return;
577
+ }
578
+ if (text.toLowerCase() === '/skills') {
579
+ if (config.skillNames.length === 0) {
580
+ console.log(`\n${ansi.gray} No skills registered.${ansi.reset}\n`);
581
+ }
582
+ else {
583
+ console.log(`\n${ansi.bold} Skills (${config.skillNames.length}):${ansi.reset}`);
584
+ for (const s of config.skillNames) {
585
+ console.log(`${ansi.gray} • ${ansi.cyan}${s}${ansi.reset}`);
586
+ }
587
+ console.log('');
588
+ }
589
+ rl.prompt();
590
+ return;
591
+ }
592
+ if (text.toLowerCase() === '/history') {
593
+ const histFile = path.resolve(`chat-history-${Date.now()}.md`);
594
+ const content = messages.map(m => {
595
+ const prefix = m.role === 'user' ? '**You**' : m.role === 'assistant' ? `**${config.agentName}**` : '*System*';
596
+ return `${prefix}: ${m.content}`;
597
+ }).join('\n\n');
598
+ fs.writeFileSync(histFile, content);
599
+ console.log(`\n${ansi.gray} ✦ History exported to ${histFile}${ansi.reset}\n`);
600
+ rl.prompt();
601
+ return;
124
602
  }
125
- catch (err) {
126
- console.log(c.red(`\n 错误: ${err.message}`));
603
+ if (text.toLowerCase() === '/status') {
604
+ const uptimeSec = Math.floor((Date.now() - startTime) / 1000);
605
+ const h = Math.floor(uptimeSec / 3600);
606
+ const m = Math.floor((uptimeSec % 3600) / 60);
607
+ const s = uptimeSec % 60;
608
+ const upStr = h > 0 ? `${h}h ${m}m ${s}s` : `${m}m ${s}s`;
609
+ const userMsgs = messages.filter(m => m.role === 'user').length;
127
610
  console.log('');
611
+ console.log(`${ansi.gray} ╭─── Agent Status ─────────────────────╮${ansi.reset}`);
612
+ console.log(`${ansi.gray} │ Agent: ${ansi.cyan}${config.agentName} v${config.agentVersion}${ansi.reset}${ansi.gray}${' '.repeat(Math.max(0, 28 - config.agentName.length - config.agentVersion.length))}│${ansi.reset}`);
613
+ console.log(`${ansi.gray} │ Model: ${ansi.cyan}${config.providerName}/${config.model}${ansi.reset}${ansi.gray}${' '.repeat(Math.max(0, 28 - config.providerName.length - config.model.length))}│${ansi.reset}`);
614
+ console.log(`${ansi.gray} │ Uptime: ${upStr}${' '.repeat(Math.max(0, 28 - upStr.length))}│${ansi.reset}`);
615
+ console.log(`${ansi.gray} │ Messages: ${userMsgs} sent, ${messages.length} total${' '.repeat(Math.max(0, 14 - String(userMsgs).length - String(messages.length).length))}│${ansi.reset}`);
616
+ console.log(`${ansi.gray} │ Skills: ${config.skillNames.length}${' '.repeat(Math.max(0, 27 - String(config.skillNames.length).length))}│${ansi.reset}`);
617
+ console.log(`${ansi.gray} ╰────────────────────────────────────────╯${ansi.reset}`);
618
+ console.log('');
619
+ rl.prompt();
620
+ return;
621
+ }
622
+ console.log(`\n${ansi.gray} Unknown command: ${text}. Type /help${ansi.reset}\n`);
623
+ rl.prompt();
624
+ return;
625
+ }
626
+ // Regular message
627
+ messages.push({ role: 'user', content: text, timestamp: Date.now() });
628
+ console.log('');
629
+ // Build message array for provider
630
+ const history = messages.map(m => ({
631
+ id: `msg_${m.timestamp}`,
632
+ role: m.role,
633
+ content: m.content,
634
+ timestamp: m.timestamp,
635
+ }));
636
+ process.stdout.write(`${ansi.green}${ansi.bold} ${config.agentName}: ${ansi.reset}`);
637
+ isStreaming = true;
638
+ abortStream = false;
639
+ let fullResponse = '';
640
+ // Handle Ctrl+C during streaming
641
+ const sigintHandler = () => {
642
+ if (isStreaming) {
643
+ abortStream = true;
644
+ }
645
+ };
646
+ process.on('SIGINT', sigintHandler);
647
+ try {
648
+ let lineBuffer = '';
649
+ let firstChunk = true;
650
+ for await (const chunk of provider.chatStream(history, config.systemPrompt)) {
651
+ if (abortStream) {
652
+ process.stdout.write(`\n${ansi.yellow} [interrupted]${ansi.reset}`);
653
+ fullResponse += '\n[interrupted]';
654
+ break;
655
+ }
656
+ fullResponse += chunk;
657
+ // Stream output with markdown rendering for complete lines
658
+ lineBuffer += chunk;
659
+ const nlIdx = lineBuffer.lastIndexOf('\n');
660
+ if (nlIdx >= 0) {
661
+ const complete = lineBuffer.slice(0, nlIdx);
662
+ lineBuffer = lineBuffer.slice(nlIdx + 1);
663
+ if (firstChunk) {
664
+ // First line continues after prefix
665
+ const lines = complete.split('\n');
666
+ const renderedFirst = renderMarkdown(lines[0]);
667
+ process.stdout.write(renderedFirst);
668
+ for (let i = 1; i < lines.length; i++) {
669
+ const rendered = renderMarkdown(lines[i]);
670
+ process.stdout.write(`\n ${rendered}`);
671
+ }
672
+ process.stdout.write('\n');
673
+ firstChunk = false;
674
+ }
675
+ else {
676
+ const rendered = renderMarkdown(complete);
677
+ for (const line of rendered.split('\n')) {
678
+ process.stdout.write(` ${line}\n`);
679
+ }
680
+ }
681
+ }
682
+ }
683
+ // Flush remaining
684
+ if (lineBuffer.length > 0) {
685
+ const rendered = renderMarkdown(lineBuffer);
686
+ if (firstChunk) {
687
+ process.stdout.write(rendered);
688
+ }
689
+ else {
690
+ for (const line of rendered.split('\n')) {
691
+ process.stdout.write(` ${line}\n`);
692
+ }
693
+ }
694
+ }
695
+ }
696
+ catch (err) {
697
+ process.stdout.write(`\n${ansi.red} Error: ${err.message}${ansi.reset}`);
698
+ fullResponse = `[Error: ${err.message}]`;
699
+ }
700
+ process.removeListener('SIGINT', sigintHandler);
701
+ isStreaming = false;
702
+ console.log('\n');
703
+ messages.push({ role: 'assistant', content: fullResponse, timestamp: Date.now() });
704
+ // Trim history
705
+ if (messages.length > 60) {
706
+ messages.splice(0, messages.length - 60);
707
+ }
708
+ rl.prompt();
709
+ });
710
+ // Ctrl+L to clear
711
+ rl.on('SIGCONT', () => {
712
+ // not standard, handled below
713
+ });
714
+ // Handle Ctrl+C when not streaming
715
+ rl.on('SIGINT', () => {
716
+ if (isStreaming) {
717
+ abortStream = true;
718
+ }
719
+ else {
720
+ console.log(`\n${ansi.dim} (Ctrl+C again or /quit to exit)${ansi.reset}\n`);
721
+ rl.prompt();
722
+ }
723
+ });
724
+ rl.on('close', () => {
725
+ process.stdout.write(`\n${ansi.dim}👋 Goodbye!${ansi.reset}\n`);
726
+ process.exit(0);
727
+ });
728
+ // Ctrl+L clear screen
729
+ if (process.stdin.isTTY) {
730
+ process.stdin.on('keypress', (_ch, key) => {
731
+ if (key && key.ctrl && key.name === 'l') {
732
+ process.stdout.write(ansi.clearScreen + ansi.cursorHome);
733
+ const w2 = cols();
734
+ process.stdout.write(`${bannerBg} 🤖 ${config.agentName} v${config.agentVersion} │ ${config.providerName}/${config.model}${' '.repeat(Math.max(0, w2 - 60))}${ansi.reset}\n`);
735
+ process.stdout.write(`${ansi.gray}${'─'.repeat(w2)}${ansi.reset}\n\n`);
736
+ rl.prompt();
128
737
  }
129
- askQuestion();
130
738
  });
131
- };
132
- askQuestion();
739
+ }
133
740
  }
134
741
  //# sourceMappingURL=chat.js.map