protoagent 0.1.6 → 0.1.7
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/dist/App.js
CHANGED
|
@@ -6,10 +6,10 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
* and cost/usage info. All heavy logic lives in `agentic-loop.ts`;
|
|
7
7
|
* this file is purely presentation + state wiring.
|
|
8
8
|
*/
|
|
9
|
-
import { useState, useEffect, useCallback, useRef
|
|
10
|
-
import { Box, Text, useApp, useInput, useStdout } from 'ink';
|
|
9
|
+
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
10
|
+
import { Box, Text, Static, useApp, useInput, useStdout } from 'ink';
|
|
11
|
+
import { LeftBar } from './components/LeftBar.js';
|
|
11
12
|
import { TextInput, Select, PasswordInput } from '@inkjs/ui';
|
|
12
|
-
import BigText from 'ink-big-text';
|
|
13
13
|
import { OpenAI } from 'openai';
|
|
14
14
|
import { readConfig, writeConfig, resolveApiKey } from './config.js';
|
|
15
15
|
import { loadRuntimeConfig } from './runtime-config.js';
|
|
@@ -21,117 +21,103 @@ import { createSession, ensureSystemPromptAtTop, saveSession, loadSession, gener
|
|
|
21
21
|
import { clearTodos, getTodosForSession, setTodosForSession } from './tools/todo.js';
|
|
22
22
|
import { initializeMcp, closeMcp } from './mcp.js';
|
|
23
23
|
import { generateSystemPrompt } from './system-prompt.js';
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
rendered.push(_jsx(Text, { children: " " }, `spacer-${index}`));
|
|
55
|
-
}
|
|
56
|
-
if (msg.role === 'user') {
|
|
57
|
-
rendered.push(_jsx(Box, { flexDirection: "column", children: _jsxs(Text, { children: [_jsx(Text, { color: "green", bold: true, children: '> ' }), _jsx(Text, { children: displayContent })] }) }, index));
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
if (msg.role === 'system') {
|
|
61
|
-
rendered.push(_jsx(CollapsibleBox, { title: "System Prompt", content: displayContent || '', titleColor: "green", dimColor: false, maxPreviewLines: 3, expanded: expandedMessages.has(index) }, index));
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
if (isToolCall) {
|
|
65
|
-
if (normalizedContent.length > 0) {
|
|
66
|
-
rendered.push(_jsx(Box, { flexDirection: "column", children: _jsx(FormattedMessage, { content: normalizedContent, deferTables: deferTables }) }, `${index}-text`));
|
|
67
|
-
}
|
|
68
|
-
const toolCalls = msgAny.tool_calls.map((tc) => ({
|
|
69
|
-
id: tc.id,
|
|
70
|
-
name: tc.function?.name || 'tool',
|
|
71
|
-
}));
|
|
72
|
-
const toolResults = new Map();
|
|
73
|
-
let nextLocalIndex = localIndex + 1;
|
|
74
|
-
for (const toolCall of toolCalls) {
|
|
75
|
-
if (nextLocalIndex < messagesToRender.length) {
|
|
76
|
-
const nextMsg = messagesToRender[nextLocalIndex];
|
|
77
|
-
if (nextMsg.role === 'tool' && nextMsg.tool_call_id === toolCall.id) {
|
|
78
|
-
toolResults.set(toolCall.id, {
|
|
79
|
-
content: normalizeMessageSpacing(nextMsg.content || ''),
|
|
80
|
-
name: nextMsg.name || toolCall.name,
|
|
81
|
-
});
|
|
82
|
-
skippedIndices.add(nextLocalIndex);
|
|
83
|
-
nextLocalIndex++;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
rendered.push(_jsx(ConsolidatedToolMessage, { toolCalls: toolCalls, toolResults: toolResults, expanded: expandedMessages.has(index) }, index));
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
if (msg.role === 'tool') {
|
|
91
|
-
rendered.push(_jsx(CollapsibleBox, { title: `${msgAny.name || 'tool'} result`, content: normalizedContent, dimColor: true, maxPreviewLines: 3, expanded: expandedMessages.has(index) }, index));
|
|
92
|
-
return;
|
|
93
|
-
}
|
|
94
|
-
rendered.push(_jsx(Box, { flexDirection: "column", children: _jsx(FormattedMessage, { content: normalizedContent, deferTables: deferTables }) }, index));
|
|
95
|
-
});
|
|
96
|
-
return rendered;
|
|
24
|
+
// ─── Scrollback helpers ───
|
|
25
|
+
// These functions append text to the permanent scrollback buffer via the
|
|
26
|
+
// <Static> component. Ink flushes new Static items within its own render
|
|
27
|
+
// cycle, so there are no timing issues with write()/log-update.
|
|
28
|
+
let _staticCounter = 0;
|
|
29
|
+
function makeStaticId() {
|
|
30
|
+
return `s${++_staticCounter}`;
|
|
31
|
+
}
|
|
32
|
+
function printBanner(addStatic) {
|
|
33
|
+
const green = '\x1b[38;2;9;164;105m';
|
|
34
|
+
const reset = '\x1b[0m';
|
|
35
|
+
addStatic([
|
|
36
|
+
`${green}█▀█ █▀█ █▀█ ▀█▀ █▀█ ▄▀█ █▀▀ █▀▀ █▄ █ ▀█▀${reset}`,
|
|
37
|
+
`${green}█▀▀ █▀▄ █▄█ █ █▄█ █▀█ █▄█ ██▄ █ ▀█ █${reset}`,
|
|
38
|
+
'',
|
|
39
|
+
].join('\n'));
|
|
40
|
+
}
|
|
41
|
+
function printRuntimeHeader(addStatic, config, session, logFilePath, dangerouslyAcceptAll) {
|
|
42
|
+
const provider = getProvider(config.provider);
|
|
43
|
+
let line = `Model: ${provider?.name || config.provider} / ${config.model}`;
|
|
44
|
+
if (dangerouslyAcceptAll)
|
|
45
|
+
line += ' (auto-approve all)';
|
|
46
|
+
if (session)
|
|
47
|
+
line += ` | Session: ${session.id.slice(0, 8)}`;
|
|
48
|
+
let text = `${line}\n`;
|
|
49
|
+
if (logFilePath) {
|
|
50
|
+
text += `Debug logs: ${logFilePath}\n`;
|
|
51
|
+
}
|
|
52
|
+
text += '\n';
|
|
53
|
+
addStatic(text);
|
|
97
54
|
}
|
|
98
|
-
function
|
|
99
|
-
const normalized =
|
|
55
|
+
function normalizeTranscriptText(text) {
|
|
56
|
+
const normalized = text.replace(/\r\n/g, '\n');
|
|
100
57
|
const lines = normalized.split('\n');
|
|
101
|
-
while (lines.length > 0 && lines[0].trim() === '')
|
|
58
|
+
while (lines.length > 0 && lines[0].trim() === '')
|
|
102
59
|
lines.shift();
|
|
103
|
-
|
|
104
|
-
while (lines.length > 0 && lines[lines.length - 1].trim() === '') {
|
|
60
|
+
while (lines.length > 0 && lines[lines.length - 1].trim() === '')
|
|
105
61
|
lines.pop();
|
|
106
|
-
}
|
|
107
62
|
return lines.join('\n');
|
|
108
63
|
}
|
|
109
|
-
function
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
return
|
|
114
|
-
|
|
115
|
-
|
|
64
|
+
function printMessageToScrollback(addStatic, role, text) {
|
|
65
|
+
const normalized = normalizeTranscriptText(text);
|
|
66
|
+
if (!normalized) {
|
|
67
|
+
addStatic('\n');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (role === 'user') {
|
|
71
|
+
addStatic(`\x1b[32m>\x1b[0m ${normalized}\n`);
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
addStatic(`${normalized}\n\n`);
|
|
75
|
+
}
|
|
76
|
+
function replayMessagesToScrollback(addStatic, messages) {
|
|
77
|
+
for (const message of messages) {
|
|
78
|
+
const msgAny = message;
|
|
79
|
+
if (message.role === 'system')
|
|
80
|
+
continue;
|
|
81
|
+
if (message.role === 'user' && typeof message.content === 'string') {
|
|
82
|
+
printMessageToScrollback(addStatic, 'user', message.content);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (message.role === 'assistant' && typeof message.content === 'string' && message.content.trim().length > 0) {
|
|
86
|
+
printMessageToScrollback(addStatic, 'assistant', message.content);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
if (message.role === 'tool') {
|
|
90
|
+
const toolName = msgAny.name || 'tool';
|
|
91
|
+
const compact = String(msgAny.content || '').replace(/\s+/g, ' ').trim().slice(0, 180);
|
|
92
|
+
addStatic(`\x1b[2m▶ ${toolName}: ${compact}\x1b[0m\n`);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (messages.length > 0) {
|
|
96
|
+
addStatic('\n');
|
|
116
97
|
}
|
|
117
|
-
|
|
98
|
+
}
|
|
99
|
+
// Returns only the last N displayable lines of text so the live streaming box
|
|
100
|
+
// never grows taller than the terminal, preventing Ink's clearTerminal wipe.
|
|
101
|
+
const STREAMING_RESERVED_ROWS = 3; // usage bar + spinner + input line
|
|
102
|
+
function clipToRows(text, terminalRows) {
|
|
103
|
+
const maxLines = Math.max(1, terminalRows - STREAMING_RESERVED_ROWS);
|
|
104
|
+
const lines = text.split('\n');
|
|
105
|
+
if (lines.length <= maxLines)
|
|
106
|
+
return text;
|
|
107
|
+
return lines.slice(lines.length - maxLines).join('\n');
|
|
118
108
|
}
|
|
119
109
|
// ─── Available slash commands ───
|
|
120
110
|
const SLASH_COMMANDS = [
|
|
121
111
|
{ name: '/clear', description: 'Clear conversation and start fresh' },
|
|
122
|
-
{ name: '/collapse', description: 'Collapse all long messages' },
|
|
123
|
-
{ name: '/expand', description: 'Expand all collapsed messages' },
|
|
124
112
|
{ name: '/help', description: 'Show all available commands' },
|
|
125
113
|
{ name: '/quit', description: 'Exit ProtoAgent' },
|
|
126
114
|
];
|
|
127
115
|
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
128
116
|
const HELP_TEXT = [
|
|
129
117
|
'Commands:',
|
|
130
|
-
' /clear
|
|
131
|
-
' /
|
|
132
|
-
' /
|
|
133
|
-
' /help - Show this help',
|
|
134
|
-
' /quit - Exit ProtoAgent',
|
|
118
|
+
' /clear - Clear conversation and start fresh',
|
|
119
|
+
' /help - Show this help',
|
|
120
|
+
' /quit - Exit ProtoAgent',
|
|
135
121
|
].join('\n');
|
|
136
122
|
function buildClient(config) {
|
|
137
123
|
const provider = getProvider(config.provider);
|
|
@@ -194,13 +180,13 @@ const ApprovalPrompt = ({ request, onRespond }) => {
|
|
|
194
180
|
{ label: sessionApprovalLabel, value: 'approve_session' },
|
|
195
181
|
{ label: 'Reject', value: 'reject' },
|
|
196
182
|
];
|
|
197
|
-
return (_jsxs(
|
|
183
|
+
return (_jsxs(LeftBar, { color: "green", marginTop: 1, marginBottom: 1, children: [_jsx(Text, { color: "green", bold: true, children: "Approval Required" }), _jsx(Text, { children: request.description }), request.detail && (_jsx(Text, { dimColor: true, children: request.detail.length > 200 ? request.detail.slice(0, 200) + '...' : request.detail })), _jsx(Box, { marginTop: 1, children: _jsx(Select, { options: items.map((item) => ({ value: item.value, label: item.label })), onChange: (value) => onRespond(value) }) })] }));
|
|
198
184
|
};
|
|
199
185
|
/** Cost/usage display in the status bar. */
|
|
200
186
|
const UsageDisplay = ({ usage, totalCost }) => {
|
|
201
187
|
if (!usage && totalCost === 0)
|
|
202
188
|
return null;
|
|
203
|
-
return (_jsxs(Box, {
|
|
189
|
+
return (_jsxs(Box, { children: [usage && (_jsxs(Text, { dimColor: true, children: ["tokens: ", usage.inputTokens, "\u2193 ", usage.outputTokens, "\u2191 | ctx: ", usage.contextPercent.toFixed(0), "%"] })), totalCost > 0 && (_jsxs(Text, { dimColor: true, children: [" | cost: $", totalCost.toFixed(4)] }))] }));
|
|
204
190
|
};
|
|
205
191
|
/** Inline setup wizard — shown when no config exists. */
|
|
206
192
|
const InlineSetup = ({ onComplete }) => {
|
|
@@ -240,10 +226,27 @@ const InlineSetup = ({ onComplete }) => {
|
|
|
240
226
|
export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
241
227
|
const { exit } = useApp();
|
|
242
228
|
const { stdout } = useStdout();
|
|
229
|
+
const terminalRows = stdout?.rows ?? 24;
|
|
230
|
+
// ─── Static scrollback state ───
|
|
231
|
+
// Each item appended here is rendered once by <Static> and permanently
|
|
232
|
+
// flushed to the terminal scrollback by Ink, within its own render cycle.
|
|
233
|
+
// This eliminates all write()/log-update timing issues.
|
|
234
|
+
const [staticItems, setStaticItems] = useState([]);
|
|
235
|
+
const addStatic = useCallback((text) => {
|
|
236
|
+
setStaticItems((prev) => [...prev, { id: makeStaticId(), text }]);
|
|
237
|
+
}, []);
|
|
243
238
|
// Core state
|
|
244
239
|
const [config, setConfig] = useState(null);
|
|
245
240
|
const [completionMessages, setCompletionMessages] = useState([]);
|
|
246
241
|
const [inputText, setInputText] = useState('');
|
|
242
|
+
// isStreaming: true while the assistant is producing tokens.
|
|
243
|
+
// streamingText: the live in-progress token buffer shown in the dynamic Ink
|
|
244
|
+
// frame while the response streams. Cleared to '' at done and flushed to
|
|
245
|
+
// <Static> as a permanent scrollback item. Keeping it in React state (not a
|
|
246
|
+
// ref) is safe because the Ink frame height does NOT change as tokens arrive —
|
|
247
|
+
// the streaming box is always 1+ lines tall while loading=true.
|
|
248
|
+
const [isStreaming, setIsStreaming] = useState(false);
|
|
249
|
+
const [streamingText, setStreamingText] = useState('');
|
|
247
250
|
const [loading, setLoading] = useState(false);
|
|
248
251
|
const [error, setError] = useState(null);
|
|
249
252
|
const [helpMessage, setHelpMessage] = useState(null);
|
|
@@ -253,18 +256,6 @@ export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
|
253
256
|
const [logFilePath, setLogFilePath] = useState(null);
|
|
254
257
|
// Input reset key — incremented on submit to force TextInput remount and clear
|
|
255
258
|
const [inputResetKey, setInputResetKey] = useState(0);
|
|
256
|
-
const [inputWidthKey, setInputWidthKey] = useState(stdout?.columns ?? 80);
|
|
257
|
-
// Collapsible state — only applies to live (current turn) messages
|
|
258
|
-
const [expandedMessages, setExpandedMessages] = useState(new Set());
|
|
259
|
-
const expandLatestMessage = useCallback((index) => {
|
|
260
|
-
setExpandedMessages((prev) => {
|
|
261
|
-
if (prev.has(index))
|
|
262
|
-
return prev;
|
|
263
|
-
const next = new Set(prev);
|
|
264
|
-
next.add(index);
|
|
265
|
-
return next;
|
|
266
|
-
});
|
|
267
|
-
}, []);
|
|
268
259
|
// Approval state
|
|
269
260
|
const [pendingApproval, setPendingApproval] = useState(null);
|
|
270
261
|
// Usage state
|
|
@@ -279,12 +270,12 @@ export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
|
279
270
|
const [quittingSession, setQuittingSession] = useState(null);
|
|
280
271
|
// OpenAI client ref (stable across renders)
|
|
281
272
|
const clientRef = useRef(null);
|
|
282
|
-
// Track current assistant message being built in the event handler
|
|
283
273
|
const assistantMessageRef = useRef(null);
|
|
284
274
|
// Abort controller for cancelling the current completion
|
|
285
275
|
const abortControllerRef = useRef(null);
|
|
286
|
-
|
|
287
|
-
const
|
|
276
|
+
const didPrintIntroRef = useRef(false);
|
|
277
|
+
const printedThreadErrorIdsRef = useRef(new Set());
|
|
278
|
+
const printedLogPathRef = useRef(null);
|
|
288
279
|
// ─── Post-config initialization (reused after inline setup) ───
|
|
289
280
|
const initializeWithConfig = useCallback(async (loadedConfig) => {
|
|
290
281
|
setConfig(loadedConfig);
|
|
@@ -301,6 +292,12 @@ export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
|
301
292
|
setTodosForSession(loadedSession.id, loadedSession.todos);
|
|
302
293
|
setSession(loadedSession);
|
|
303
294
|
setCompletionMessages(loadedSession.completionMessages);
|
|
295
|
+
if (!didPrintIntroRef.current) {
|
|
296
|
+
printBanner(addStatic);
|
|
297
|
+
printRuntimeHeader(addStatic, loadedConfig, loadedSession, logFilePath, dangerouslyAcceptAll);
|
|
298
|
+
replayMessagesToScrollback(addStatic, loadedSession.completionMessages);
|
|
299
|
+
didPrintIntroRef.current = true;
|
|
300
|
+
}
|
|
304
301
|
}
|
|
305
302
|
else {
|
|
306
303
|
setError(`Session "${sessionId}" not found. Starting a new session.`);
|
|
@@ -314,10 +311,15 @@ export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
|
314
311
|
clearTodos(newSession.id);
|
|
315
312
|
newSession.completionMessages = initialCompletionMessages;
|
|
316
313
|
setSession(newSession);
|
|
314
|
+
if (!didPrintIntroRef.current) {
|
|
315
|
+
printBanner(addStatic);
|
|
316
|
+
printRuntimeHeader(addStatic, loadedConfig, newSession, logFilePath, dangerouslyAcceptAll);
|
|
317
|
+
didPrintIntroRef.current = true;
|
|
318
|
+
}
|
|
317
319
|
}
|
|
318
320
|
setNeedsSetup(false);
|
|
319
321
|
setInitialized(true);
|
|
320
|
-
}, [sessionId]);
|
|
322
|
+
}, [dangerouslyAcceptAll, logFilePath, sessionId, addStatic]);
|
|
321
323
|
// ─── Initialization ───
|
|
322
324
|
useEffect(() => {
|
|
323
325
|
if (!loading) {
|
|
@@ -330,17 +332,26 @@ export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
|
330
332
|
return () => clearInterval(interval);
|
|
331
333
|
}, [loading]);
|
|
332
334
|
useEffect(() => {
|
|
333
|
-
if (
|
|
335
|
+
if (error) {
|
|
336
|
+
addStatic(`\x1b[31mError: ${error}\x1b[0m\n\n`);
|
|
337
|
+
}
|
|
338
|
+
}, [error, addStatic]);
|
|
339
|
+
useEffect(() => {
|
|
340
|
+
if (!didPrintIntroRef.current || !logFilePath || printedLogPathRef.current === logFilePath) {
|
|
334
341
|
return;
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
};
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
342
|
+
}
|
|
343
|
+
printedLogPathRef.current = logFilePath;
|
|
344
|
+
addStatic(`Debug logs: ${logFilePath}\n\n`);
|
|
345
|
+
}, [logFilePath, addStatic]);
|
|
346
|
+
useEffect(() => {
|
|
347
|
+
for (const threadError of threadErrors) {
|
|
348
|
+
if (threadError.transient || printedThreadErrorIdsRef.current.has(threadError.id)) {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
printedThreadErrorIdsRef.current.add(threadError.id);
|
|
352
|
+
addStatic(`\x1b[31mError: ${threadError.message}\x1b[0m\n\n`);
|
|
353
|
+
}
|
|
354
|
+
}, [threadErrors, addStatic]);
|
|
344
355
|
useEffect(() => {
|
|
345
356
|
const init = async () => {
|
|
346
357
|
// Set log level and initialize log file
|
|
@@ -418,7 +429,6 @@ export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
|
418
429
|
setLastUsage(null);
|
|
419
430
|
setTotalCost(0);
|
|
420
431
|
setThreadErrors([]);
|
|
421
|
-
setExpandedMessages(new Set());
|
|
422
432
|
if (session) {
|
|
423
433
|
const newSession = createSession(config.model, config.provider);
|
|
424
434
|
clearTodos(session.id);
|
|
@@ -429,13 +439,8 @@ export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
|
429
439
|
});
|
|
430
440
|
return true;
|
|
431
441
|
case '/expand':
|
|
432
|
-
// Expand all collapsed messages
|
|
433
|
-
const allIndices = new Set(completionMessages.map((_, i) => i));
|
|
434
|
-
setExpandedMessages(allIndices);
|
|
435
|
-
return true;
|
|
436
442
|
case '/collapse':
|
|
437
|
-
//
|
|
438
|
-
setExpandedMessages(new Set());
|
|
443
|
+
// expand/collapse removed — transcript lives in scrollback
|
|
439
444
|
return true;
|
|
440
445
|
case '/help':
|
|
441
446
|
setHelpMessage(HELP_TEXT);
|
|
@@ -461,13 +466,19 @@ export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
|
461
466
|
setInputText('');
|
|
462
467
|
setInputResetKey((prev) => prev + 1); // Force TextInput to remount and clear
|
|
463
468
|
setLoading(true);
|
|
469
|
+
setIsStreaming(false);
|
|
470
|
+
setStreamingText('');
|
|
464
471
|
setError(null);
|
|
465
472
|
setHelpMessage(null);
|
|
466
473
|
setThreadErrors([]);
|
|
467
|
-
//
|
|
474
|
+
// Reset turn tracking
|
|
475
|
+
assistantMessageRef.current = null;
|
|
476
|
+
// Print the user message directly to scrollback so it is selectable/copyable.
|
|
477
|
+
// We still push it into completionMessages for session saving.
|
|
468
478
|
const userMessage = { role: 'user', content: trimmed };
|
|
479
|
+
printMessageToScrollback(addStatic, 'user', trimmed);
|
|
469
480
|
setCompletionMessages((prev) => [...prev, userMessage]);
|
|
470
|
-
// Reset assistant message tracker
|
|
481
|
+
// Reset assistant message tracker (streamed indices were reset above)
|
|
471
482
|
assistantMessageRef.current = null;
|
|
472
483
|
try {
|
|
473
484
|
const pricing = getModelPricing(config.provider, config.model);
|
|
@@ -476,33 +487,27 @@ export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
|
476
487
|
abortControllerRef.current = new AbortController();
|
|
477
488
|
const updatedMessages = await runAgenticLoop(clientRef.current, config.model, [...completionMessages, userMessage], trimmed, (event) => {
|
|
478
489
|
switch (event.type) {
|
|
479
|
-
case 'text_delta':
|
|
480
|
-
//
|
|
490
|
+
case 'text_delta': {
|
|
491
|
+
// Accumulate tokens into streamingText React state — shown live in
|
|
492
|
+
// the dynamic Ink frame. The frame height stays constant (spinner +
|
|
493
|
+
// streaming box + input) so setState here does NOT trigger
|
|
494
|
+
// clearTerminal. At 'done' the full text is flushed to <Static>.
|
|
481
495
|
if (!assistantMessageRef.current || assistantMessageRef.current.kind !== 'streaming_text') {
|
|
482
|
-
// First text delta
|
|
496
|
+
// First text delta of this turn: initialise ref, show streaming indicator.
|
|
483
497
|
const assistantMsg = { role: 'assistant', content: event.content || '', tool_calls: [] };
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
498
|
+
const idx = completionMessages.length + 1;
|
|
499
|
+
assistantMessageRef.current = { message: assistantMsg, index: idx, kind: 'streaming_text' };
|
|
500
|
+
setIsStreaming(true);
|
|
501
|
+
setStreamingText(event.content || '');
|
|
502
|
+
setCompletionMessages((prev) => [...prev, assistantMsg]);
|
|
488
503
|
}
|
|
489
504
|
else {
|
|
490
|
-
// Subsequent deltas —
|
|
505
|
+
// Subsequent deltas — append to ref AND to React state for live display.
|
|
491
506
|
assistantMessageRef.current.message.content += event.content || '';
|
|
492
|
-
|
|
493
|
-
textFlushTimerRef.current = setTimeout(() => {
|
|
494
|
-
textFlushTimerRef.current = null;
|
|
495
|
-
setCompletionMessages((prev) => {
|
|
496
|
-
if (!assistantMessageRef.current)
|
|
497
|
-
return prev;
|
|
498
|
-
const updated = [...prev];
|
|
499
|
-
updated[assistantMessageRef.current.index] = { ...assistantMessageRef.current.message };
|
|
500
|
-
return updated;
|
|
501
|
-
});
|
|
502
|
-
}, 50);
|
|
503
|
-
}
|
|
507
|
+
setStreamingText((prev) => prev + (event.content || ''));
|
|
504
508
|
}
|
|
505
509
|
break;
|
|
510
|
+
}
|
|
506
511
|
case 'sub_agent_iteration':
|
|
507
512
|
if (event.subAgentTool) {
|
|
508
513
|
const { tool, status } = event.subAgentTool;
|
|
@@ -564,16 +569,20 @@ export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
|
564
569
|
if (event.toolCall) {
|
|
565
570
|
const toolCall = event.toolCall;
|
|
566
571
|
setActiveTool(null);
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
572
|
+
// Write the tool summary immediately — at this point loading is
|
|
573
|
+
// still true but the frame height is stable (spinner + input box).
|
|
574
|
+
// The next state change (setActiveTool(null)) doesn't affect
|
|
575
|
+
// frame height so write() restores the correct frame.
|
|
576
|
+
const compactResult = (toolCall.result || '')
|
|
577
|
+
.replace(/\s+/g, ' ')
|
|
578
|
+
.trim()
|
|
579
|
+
.slice(0, 180);
|
|
580
|
+
addStatic(`\x1b[2m▶ ${toolCall.name}: ${compactResult}\x1b[0m\n`);
|
|
581
|
+
// Flush the assistant message + tool result into completionMessages
|
|
582
|
+
// for session saving.
|
|
574
583
|
setCompletionMessages((prev) => {
|
|
575
584
|
const updated = [...prev];
|
|
576
|
-
// Sync assistant message
|
|
585
|
+
// Sync assistant message
|
|
577
586
|
if (assistantMessageRef.current) {
|
|
578
587
|
updated[assistantMessageRef.current.index] = {
|
|
579
588
|
...assistantMessageRef.current.message,
|
|
@@ -628,13 +637,27 @@ export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
|
628
637
|
}
|
|
629
638
|
break;
|
|
630
639
|
case 'iteration_done':
|
|
631
|
-
assistantMessageRef.current
|
|
640
|
+
if (assistantMessageRef.current?.kind === 'tool_call_assistant') {
|
|
641
|
+
assistantMessageRef.current = null;
|
|
642
|
+
}
|
|
632
643
|
break;
|
|
633
644
|
case 'done':
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
645
|
+
if (assistantMessageRef.current?.kind === 'streaming_text') {
|
|
646
|
+
const finalRef = assistantMessageRef.current;
|
|
647
|
+
// Flush the complete streamed text to <Static> (permanent scrollback),
|
|
648
|
+
// then clear the live streaming state from the dynamic Ink frame.
|
|
649
|
+
const normalized = normalizeTranscriptText(finalRef.message.content || '');
|
|
650
|
+
if (normalized) {
|
|
651
|
+
addStatic(`${normalized}\n\n`);
|
|
652
|
+
}
|
|
653
|
+
setIsStreaming(false);
|
|
654
|
+
setStreamingText('');
|
|
655
|
+
setCompletionMessages((prev) => {
|
|
656
|
+
const updated = [...prev];
|
|
657
|
+
updated[finalRef.index] = { ...finalRef.message };
|
|
658
|
+
return updated;
|
|
659
|
+
});
|
|
660
|
+
assistantMessageRef.current = null;
|
|
638
661
|
}
|
|
639
662
|
setActiveTool(null);
|
|
640
663
|
setThreadErrors((prev) => prev.filter((threadError) => !threadError.transient));
|
|
@@ -662,7 +685,7 @@ export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
|
662
685
|
finally {
|
|
663
686
|
setLoading(false);
|
|
664
687
|
}
|
|
665
|
-
}, [loading, config, completionMessages, session, handleSlashCommand,
|
|
688
|
+
}, [loading, config, completionMessages, session, handleSlashCommand, addStatic]);
|
|
666
689
|
// ─── Keyboard shortcuts ───
|
|
667
690
|
useInput((input, key) => {
|
|
668
691
|
if (key.ctrl && input === 'c') {
|
|
@@ -674,25 +697,12 @@ export const App = ({ dangerouslyAcceptAll = false, logLevel, sessionId, }) => {
|
|
|
674
697
|
}
|
|
675
698
|
});
|
|
676
699
|
// ─── Render ───
|
|
677
|
-
|
|
678
|
-
const liveStartIndex = loading
|
|
679
|
-
? (typeof assistantMessageRef.current?.index === 'number'
|
|
680
|
-
? assistantMessageRef.current.index
|
|
681
|
-
: Math.max(completionMessages.length - 1, 0))
|
|
682
|
-
: completionMessages.length;
|
|
683
|
-
const archivedMessages = completionMessages.slice(0, liveStartIndex);
|
|
684
|
-
const liveMessages = completionMessages.slice(liveStartIndex);
|
|
685
|
-
const archivedMessageNodes = useMemo(() => renderMessageList(archivedMessages, completionMessages, expandedMessages), [archivedMessages, completionMessages, expandedMessages]);
|
|
686
|
-
const liveMessageNodes = useMemo(() => renderMessageList(liveMessages, completionMessages, expandedMessages, liveStartIndex, loading), [liveMessages, completionMessages, expandedMessages, liveStartIndex, loading]);
|
|
687
|
-
return (_jsxs(Box, { flexDirection: "column", height: "100%", children: [_jsx(BigText, { text: "ProtoAgent", font: "tiny", colors: ["#09A469"] }), config && (_jsxs(Text, { dimColor: true, children: ["Model: ", providerInfo?.name || config.provider, " / ", config.model, dangerouslyAcceptAll && _jsx(Text, { color: "red", children: " (auto-approve all)" }), session && _jsxs(Text, { dimColor: true, children: [" | Session: ", session.id.slice(0, 8)] })] })), logFilePath && _jsxs(Text, { dimColor: true, children: ["Debug logs: ", logFilePath] }), error && _jsx(Text, { color: "red", children: error }), helpMessage && (_jsx(CollapsibleBox, { title: "Help", content: helpMessage, titleColor: "green", dimColor: false, maxPreviewLines: 10, expanded: true })), !initialized && !error && !needsSetup && _jsx(Text, { children: "Initializing..." }), needsSetup && (_jsx(InlineSetup, { onComplete: (newConfig) => {
|
|
700
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Static, { items: staticItems, children: (item) => (_jsx(Text, { children: item.text }, item.id)) }), helpMessage && (_jsx(LeftBar, { color: "green", marginTop: 1, marginBottom: 1, children: _jsx(Text, { children: helpMessage }) })), !initialized && !error && !needsSetup && _jsx(Text, { children: "Initializing..." }), needsSetup && (_jsx(InlineSetup, { onComplete: (newConfig) => {
|
|
688
701
|
initializeWithConfig(newConfig).catch((err) => {
|
|
689
702
|
setError(`Initialization failed: ${err.message}`);
|
|
690
703
|
});
|
|
691
|
-
} })), _jsxs(
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
pendingApproval.resolve(response);
|
|
696
|
-
setPendingApproval(null);
|
|
697
|
-
} }))] }), _jsx(UsageDisplay, { usage: lastUsage ?? null, totalCost: totalCost }), initialized && !pendingApproval && inputText.startsWith('/') && (_jsx(CommandFilter, { inputText: inputText })), initialized && !pendingApproval && loading && (_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { color: "green", bold: true, children: [SPINNER_FRAMES[spinnerFrame], ' ', activeTool ? `Running ${activeTool}...` : 'Working...'] }) })), initialized && !pendingApproval && (_jsx(Box, { borderStyle: "round", borderColor: "green", paddingX: 1, flexDirection: "column", children: _jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 2, flexShrink: 0, children: _jsx(Text, { color: "green", bold: true, children: '>' }) }), _jsx(Box, { flexGrow: 1, minWidth: 10, children: _jsx(TextInput, { defaultValue: inputText, onChange: setInputText, placeholder: "Type your message... (/help for commands)", onSubmit: handleSubmit }, `${inputResetKey}-${inputWidthKey}`) })] }) }, `input-shell-${inputWidthKey}`)), quittingSession && (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingX: 1, marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Session saved. Resume with:" }), _jsxs(Text, { color: "green", children: ["protoagent --session ", quittingSession.id] })] }))] }));
|
|
704
|
+
} })), isStreaming && (_jsxs(Text, { wrap: "wrap", children: [clipToRows(streamingText, terminalRows), _jsx(Text, { dimColor: true, children: "\u258D" })] })), threadErrors.filter((threadError) => threadError.transient).map((threadError) => (_jsx(LeftBar, { color: "red", marginBottom: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", threadError.message] }) }, `thread-error-${threadError.id}`))), pendingApproval && (_jsx(ApprovalPrompt, { request: pendingApproval.request, onRespond: (response) => {
|
|
705
|
+
pendingApproval.resolve(response);
|
|
706
|
+
setPendingApproval(null);
|
|
707
|
+
} })), _jsx(UsageDisplay, { usage: lastUsage ?? null, totalCost: totalCost }), initialized && !pendingApproval && inputText.startsWith('/') && (_jsx(CommandFilter, { inputText: inputText })), initialized && !pendingApproval && loading && !isStreaming && (_jsx(Box, { children: _jsxs(Text, { color: "green", bold: true, children: [SPINNER_FRAMES[spinnerFrame], ' ', activeTool ? `Running ${activeTool}...` : 'Working...'] }) })), initialized && !pendingApproval && (_jsx(Box, { children: _jsxs(Box, { flexDirection: "row", children: [_jsx(Box, { width: 2, flexShrink: 0, children: _jsx(Text, { color: "green", bold: true, children: '>' }) }), _jsx(Box, { flexGrow: 1, minWidth: 10, children: _jsx(TextInput, { defaultValue: inputText, onChange: setInputText, placeholder: "Type your message... (/help for commands)", onSubmit: handleSubmit }, inputResetKey) })] }) })), quittingSession && (_jsxs(Box, { flexDirection: "column", marginTop: 1, paddingX: 1, marginBottom: 1, children: [_jsx(Text, { dimColor: true, children: "Session saved. Resume with:" }), _jsxs(Text, { color: "green", children: ["protoagent --session ", quittingSession.id] })] }))] }));
|
|
698
708
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { Text } from 'ink';
|
|
3
|
+
import { LeftBar } from './LeftBar.js';
|
|
3
4
|
export const CollapsibleBox = ({ title, content, titleColor, dimColor = false, maxPreviewLines = 3, maxPreviewChars = 500, expanded = false, marginBottom = 0, }) => {
|
|
4
5
|
const lines = content.split('\n');
|
|
5
6
|
const isTooManyLines = lines.length > maxPreviewLines;
|
|
@@ -7,7 +8,7 @@ export const CollapsibleBox = ({ title, content, titleColor, dimColor = false, m
|
|
|
7
8
|
const isLong = isTooManyLines || isTooManyChars;
|
|
8
9
|
// If content is short, always show it
|
|
9
10
|
if (!isLong) {
|
|
10
|
-
return (_jsxs(
|
|
11
|
+
return (_jsxs(LeftBar, { color: titleColor ?? 'white', marginBottom: marginBottom, children: [_jsx(Text, { color: titleColor, dimColor: dimColor, bold: true, children: title }), _jsx(Text, { dimColor: dimColor, children: content })] }));
|
|
11
12
|
}
|
|
12
13
|
// For long content, show preview or full content
|
|
13
14
|
let preview;
|
|
@@ -22,5 +23,5 @@ export const CollapsibleBox = ({ title, content, titleColor, dimColor = false, m
|
|
|
22
23
|
: linesTruncated;
|
|
23
24
|
}
|
|
24
25
|
const hasMore = !expanded;
|
|
25
|
-
return (_jsxs(
|
|
26
|
+
return (_jsxs(LeftBar, { color: titleColor ?? 'white', marginBottom: marginBottom, children: [_jsxs(Text, { color: titleColor, dimColor: dimColor, bold: true, children: [expanded ? '▼' : '▶', " ", title] }), _jsx(Text, { dimColor: dimColor, children: preview }), hasMore && _jsx(Text, { dimColor: true, children: "... (use /expand to see all)" })] }));
|
|
26
27
|
};
|
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { FormattedMessage } from './FormattedMessage.js';
|
|
4
|
+
import { LeftBar } from './LeftBar.js';
|
|
4
5
|
export const ConsolidatedToolMessage = ({ toolCalls, toolResults, expanded = false, }) => {
|
|
5
6
|
const toolNames = toolCalls.map((toolCall) => toolCall.name);
|
|
6
7
|
const title = `Called: ${toolNames.join(', ')}`;
|
|
7
8
|
const containsTodoTool = toolCalls.some((toolCall) => toolCall.name === 'todo_read' || toolCall.name === 'todo_write');
|
|
8
|
-
const titleColor = containsTodoTool ? 'green' : '
|
|
9
|
+
const titleColor = containsTodoTool ? 'green' : 'cyan';
|
|
9
10
|
const isExpanded = expanded || containsTodoTool;
|
|
10
11
|
if (isExpanded) {
|
|
11
|
-
return (_jsxs(
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
return (_jsxs(LeftBar, { color: titleColor, children: [_jsxs(Text, { color: titleColor, bold: true, children: ["\u25BC ", title] }), toolCalls.map((toolCall, idx) => {
|
|
13
|
+
const result = toolResults.get(toolCall.id);
|
|
14
|
+
if (!result)
|
|
15
|
+
return null;
|
|
16
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "cyan", bold: true, children: ["[", result.name, "]:"] }), _jsx(FormattedMessage, { content: result.content })] }, idx));
|
|
17
|
+
})] }));
|
|
17
18
|
}
|
|
18
19
|
const compactLines = toolCalls.flatMap((toolCall) => {
|
|
19
20
|
const result = toolResults.get(toolCall.id);
|
|
@@ -29,5 +30,5 @@ export const ConsolidatedToolMessage = ({ toolCalls, toolResults, expanded = fal
|
|
|
29
30
|
const preview = compactPreview.length > previewLimit
|
|
30
31
|
? `${compactPreview.slice(0, previewLimit).trimEnd()}... (use /expand)`
|
|
31
32
|
: compactPreview;
|
|
32
|
-
return (_jsxs(
|
|
33
|
+
return (_jsxs(LeftBar, { color: "white", children: [_jsxs(Text, { color: titleColor, dimColor: true, bold: true, children: ["\u25B6 ", title] }), _jsx(Text, { dimColor: true, children: preview })] }));
|
|
33
34
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
3
|
import { formatMessage } from '../utils/format-message.js';
|
|
4
|
+
import { LeftBar } from './LeftBar.js';
|
|
4
5
|
export const DEFERRED_TABLE_PLACEHOLDER = 'table loading';
|
|
5
6
|
const graphemeSegmenter = typeof Intl !== 'undefined' && 'Segmenter' in Intl
|
|
6
7
|
? new Intl.Segmenter(undefined, { granularity: 'grapheme' })
|
|
@@ -156,10 +157,10 @@ export const FormattedMessage = ({ content, deferTables = false }) => {
|
|
|
156
157
|
if (deferTables) {
|
|
157
158
|
return (_jsx(Box, { marginY: 1, children: _jsx(Text, { dimColor: true, children: DEFERRED_TABLE_PLACEHOLDER }) }, index));
|
|
158
159
|
}
|
|
159
|
-
return (_jsx(
|
|
160
|
+
return (_jsx(LeftBar, { color: "gray", marginTop: 1, marginBottom: 1, children: _jsx(Text, { children: renderPreformattedTable(block.content) }) }, index));
|
|
160
161
|
}
|
|
161
162
|
if (block.type === 'code') {
|
|
162
|
-
return (_jsx(
|
|
163
|
+
return (_jsx(LeftBar, { color: "gray", marginTop: 1, marginBottom: 1, children: _jsx(Text, { dimColor: true, children: block.content }) }, index));
|
|
163
164
|
}
|
|
164
165
|
// Text Block
|
|
165
166
|
if (!block.content.trim())
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* LeftBar — renders a bold green vertical bar (│) on the left side of
|
|
4
|
+
* content, like a GitHub "note" callout. The bar stretches to match the
|
|
5
|
+
* full height of the content by measuring the content box after each render
|
|
6
|
+
* and repeating the │ character once per row.
|
|
7
|
+
*
|
|
8
|
+
* Uses Ink's measureElement (available in stock Ink) rather than a Box
|
|
9
|
+
* border, so it adds zero extra border lines and avoids ghosting on resize.
|
|
10
|
+
*/
|
|
11
|
+
import { useRef, useState, useLayoutEffect } from 'react';
|
|
12
|
+
import { Box, Text, measureElement } from 'ink';
|
|
13
|
+
export const LeftBar = ({ color = 'green', children, marginTop = 0, marginBottom = 0, }) => {
|
|
14
|
+
const contentRef = useRef(null);
|
|
15
|
+
const [height, setHeight] = useState(1);
|
|
16
|
+
useLayoutEffect(() => {
|
|
17
|
+
if (contentRef.current) {
|
|
18
|
+
try {
|
|
19
|
+
const { height: h } = measureElement(contentRef.current);
|
|
20
|
+
if (h > 0)
|
|
21
|
+
setHeight(h);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
// measureElement can throw before layout is complete; keep previous height
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
const bar = Array.from({ length: height }, () => '│').join('\n');
|
|
29
|
+
return (_jsxs(Box, { flexDirection: "row", marginTop: marginTop, marginBottom: marginBottom, children: [_jsx(Box, { flexDirection: "column", marginRight: 1, children: _jsx(Text, { color: color, bold: true, children: bar }) }), _jsx(Box, { ref: contentRef, flexDirection: "column", flexGrow: 1, children: children })] }));
|
|
30
|
+
};
|
package/dist/system-prompt.js
CHANGED
|
@@ -85,13 +85,13 @@ GUIDELINES
|
|
|
85
85
|
|
|
86
86
|
OUTPUT FORMAT:
|
|
87
87
|
- You are running in a terminal. Be concise. Optimise for scannability.
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
- Use flat
|
|
88
|
+
- Do NOT use Markdown formatting. No **bold**, no *italic*, no # headers, no --- dividers.
|
|
89
|
+
- Do NOT use Markdown code fences (backticks) unless the content is actual code or a command.
|
|
90
|
+
- For structured data, use plain text with aligned columns (spaces, not pipes/dashes).
|
|
91
|
+
- Keep tables compact: narrower columns, minimal padding. Wrap cell content rather than making very wide tables.
|
|
92
|
+
- Use flat plain-text lists with a simple dash or symbol prefix (e.g. - item, or ✅ done, ❌ failed).
|
|
93
93
|
- NEVER use nested indentation. Keep all lists flat — one level only.
|
|
94
|
-
- Markdown links [text](url)
|
|
94
|
+
- Do NOT use Markdown links [text](url) — just write URLs inline.
|
|
95
95
|
|
|
96
96
|
SUBAGENT STRATEGY:
|
|
97
97
|
Delegate work to specialized subagents aggressively. They excel at focused, parallel tasks.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "protoagent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"commander": "^14.0.1",
|
|
29
29
|
"he": "^1.2.0",
|
|
30
30
|
"html-to-text": "^9.0.5",
|
|
31
|
-
"ink": "^6.
|
|
31
|
+
"ink": "^6.8.0",
|
|
32
32
|
"ink-big-text": "^2.0.0",
|
|
33
33
|
"jsonc-parser": "^3.3.1",
|
|
34
34
|
"openai": "^5.23.1",
|