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.
- package/.opc/memory.db +0 -0
- package/COMPETITIVE-GAP.md +92 -92
- package/CONTRIBUTING.md +36 -36
- package/README.md +290 -290
- package/README.zh-CN.md +269 -269
- package/STUDIO-REWRITE-TASK.md +76 -0
- package/dist/channels/telegram.d.ts +5 -0
- package/dist/channels/telegram.d.ts.map +1 -1
- package/dist/channels/telegram.js +108 -0
- package/dist/channels/telegram.js.map +1 -1
- package/dist/channels/voice.d.ts +71 -97
- package/dist/channels/voice.d.ts.map +1 -1
- package/dist/channels/voice.js +369 -347
- package/dist/channels/voice.js.map +1 -1
- package/dist/channels/web.d.ts.map +1 -1
- package/dist/channels/web.js +8 -2
- package/dist/channels/web.js.map +1 -1
- package/dist/channels/wechat.js +6 -6
- package/dist/cli/chat.d.ts +4 -1
- package/dist/cli/chat.d.ts.map +1 -1
- package/dist/cli/chat.js +680 -73
- package/dist/cli/chat.js.map +1 -1
- package/dist/cli/setup.js +1 -1
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli.js +373 -280
- package/dist/cli.js.map +1 -1
- package/dist/core/a2a-http.d.ts +75 -0
- package/dist/core/a2a-http.d.ts.map +1 -0
- package/dist/core/a2a-http.js +217 -0
- package/dist/core/a2a-http.js.map +1 -0
- package/dist/core/a2a.d.ts +2 -0
- package/dist/core/a2a.d.ts.map +1 -1
- package/dist/core/a2a.js +6 -1
- package/dist/core/a2a.js.map +1 -1
- package/dist/core/agent.d.ts +1 -0
- package/dist/core/agent.d.ts.map +1 -1
- package/dist/core/agent.js +3 -0
- package/dist/core/agent.js.map +1 -1
- package/dist/core/gateway-registry.d.ts +116 -0
- package/dist/core/gateway-registry.d.ts.map +1 -0
- package/dist/core/gateway-registry.js +280 -0
- package/dist/core/gateway-registry.js.map +1 -0
- package/dist/core/model-recommender.d.ts +40 -0
- package/dist/core/model-recommender.d.ts.map +1 -0
- package/dist/core/model-recommender.js +186 -0
- package/dist/core/model-recommender.js.map +1 -0
- package/dist/core/priority-queue.d.ts +100 -0
- package/dist/core/priority-queue.d.ts.map +1 -0
- package/dist/core/priority-queue.js +181 -0
- package/dist/core/priority-queue.js.map +1 -0
- package/dist/core/runtime.d.ts.map +1 -1
- package/dist/core/runtime.js +192 -22
- package/dist/core/runtime.js.map +1 -1
- package/dist/deploy/index.js +56 -56
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +155 -10
- package/dist/doctor.js.map +1 -1
- package/dist/index.d.ts +10 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +24 -13
- package/dist/index.js.map +1 -1
- package/dist/memory/deepbrain.d.ts +1 -1
- package/dist/memory/deepbrain.d.ts.map +1 -1
- package/dist/memory/deepbrain.js +95 -4
- package/dist/memory/deepbrain.js.map +1 -1
- package/dist/memory/evolve-engine.d.ts +113 -0
- package/dist/memory/evolve-engine.d.ts.map +1 -0
- package/dist/memory/evolve-engine.js +549 -0
- package/dist/memory/evolve-engine.js.map +1 -0
- package/dist/memory/index.d.ts +2 -0
- package/dist/memory/index.d.ts.map +1 -1
- package/dist/memory/index.js +3 -1
- package/dist/memory/index.js.map +1 -1
- package/dist/memory/sqlite-store.d.ts +40 -0
- package/dist/memory/sqlite-store.d.ts.map +1 -0
- package/dist/memory/sqlite-store.js +269 -0
- package/dist/memory/sqlite-store.js.map +1 -0
- package/dist/memory/user-profiler.d.ts +8 -0
- package/dist/memory/user-profiler.d.ts.map +1 -1
- package/dist/memory/user-profiler.js +89 -0
- package/dist/memory/user-profiler.js.map +1 -1
- package/dist/scheduler/cron-engine.d.ts.map +1 -1
- package/dist/scheduler/cron-engine.js +3 -36
- package/dist/scheduler/cron-engine.js.map +1 -1
- package/dist/scheduler/proactive.d.ts +62 -0
- package/dist/scheduler/proactive.d.ts.map +1 -0
- package/dist/scheduler/proactive.js +185 -0
- package/dist/scheduler/proactive.js.map +1 -0
- package/dist/skills/auto-learn.d.ts.map +1 -1
- package/dist/skills/auto-learn.js +65 -11
- package/dist/skills/auto-learn.js.map +1 -1
- package/dist/skills/builtin/index.d.ts.map +1 -1
- package/dist/skills/builtin/index.js +163 -30
- package/dist/skills/builtin/index.js.map +1 -1
- package/dist/skills/types.d.ts +1 -1
- package/dist/skills/types.d.ts.map +1 -1
- package/dist/skills/types.js +1 -0
- package/dist/skills/types.js.map +1 -1
- package/dist/studio/server.d.ts +1 -0
- package/dist/studio/server.d.ts.map +1 -1
- package/dist/studio/server.js +148 -17
- package/dist/studio/server.js.map +1 -1
- package/dist/studio-ui/index.html +867 -2630
- package/dist/ui/components.js +105 -105
- package/examples/README.md +22 -22
- package/examples/basic-agent.ts +90 -90
- package/examples/brain-integration.ts +71 -71
- package/examples/multi-channel.ts +74 -74
- package/install.ps1 +127 -127
- package/install.sh +154 -154
- package/models.json +164 -164
- package/package.json +5 -2
- package/scripts/install.ps1 +31 -31
- package/scripts/install.sh +40 -40
- package/templates/ecommerce-assistant/README.md +45 -45
- package/templates/ecommerce-assistant/oad.yaml +47 -47
- package/templates/tech-support/README.md +43 -43
- 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
|
|
40
|
+
const yaml = __importStar(require("js-yaml"));
|
|
41
41
|
const providers_1 = require("../providers");
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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.
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
|
|
112
|
-
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
126
|
-
|
|
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
|