zerg-ztc 0.1.11 → 0.1.12
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/bin/ztc-audio-darwin-arm64 +0 -0
- package/dist/utils/dictation_native.d.ts.map +1 -1
- package/dist/utils/dictation_native.js +43 -23
- package/dist/utils/dictation_native.js.map +1 -1
- package/package.json +5 -4
- package/packages/ztc-dictation/Cargo.toml +0 -43
- package/packages/ztc-dictation/README.md +0 -65
- package/packages/ztc-dictation/index.d.ts +0 -16
- package/packages/ztc-dictation/index.js +0 -74
- package/packages/ztc-dictation/package.json +0 -41
- package/packages/ztc-dictation/src/main.rs +0 -430
- package/src/App.tsx +0 -910
- package/src/agent/agent.ts +0 -534
- package/src/agent/backends/anthropic.ts +0 -86
- package/src/agent/backends/gemini.ts +0 -119
- package/src/agent/backends/inception.ts +0 -23
- package/src/agent/backends/index.ts +0 -17
- package/src/agent/backends/openai.ts +0 -23
- package/src/agent/backends/openai_compatible.ts +0 -143
- package/src/agent/backends/types.ts +0 -83
- package/src/agent/commands/clipboard.ts +0 -77
- package/src/agent/commands/config.ts +0 -204
- package/src/agent/commands/debug.ts +0 -23
- package/src/agent/commands/dictation.ts +0 -11
- package/src/agent/commands/emulation.ts +0 -80
- package/src/agent/commands/execution.ts +0 -9
- package/src/agent/commands/help.ts +0 -20
- package/src/agent/commands/history.ts +0 -13
- package/src/agent/commands/index.ts +0 -48
- package/src/agent/commands/input_mode.ts +0 -22
- package/src/agent/commands/keybindings.ts +0 -40
- package/src/agent/commands/model.ts +0 -11
- package/src/agent/commands/models.ts +0 -116
- package/src/agent/commands/permissions.ts +0 -64
- package/src/agent/commands/retry.ts +0 -9
- package/src/agent/commands/shell.ts +0 -68
- package/src/agent/commands/skills.ts +0 -54
- package/src/agent/commands/status.ts +0 -19
- package/src/agent/commands/types.ts +0 -88
- package/src/agent/commands/update.ts +0 -32
- package/src/agent/factory.ts +0 -60
- package/src/agent/index.ts +0 -20
- package/src/agent/runtime/capabilities.ts +0 -7
- package/src/agent/runtime/memory.ts +0 -23
- package/src/agent/runtime/policy.ts +0 -48
- package/src/agent/runtime/session.ts +0 -18
- package/src/agent/runtime/tracing.ts +0 -23
- package/src/agent/tools/file.ts +0 -178
- package/src/agent/tools/index.ts +0 -52
- package/src/agent/tools/screenshot.ts +0 -821
- package/src/agent/tools/search.ts +0 -138
- package/src/agent/tools/shell.ts +0 -69
- package/src/agent/tools/skills.ts +0 -28
- package/src/agent/tools/types.ts +0 -14
- package/src/agent/tools/zerg.ts +0 -50
- package/src/cli.tsx +0 -163
- package/src/components/ActivityLine.tsx +0 -23
- package/src/components/FullScreen.tsx +0 -79
- package/src/components/Header.tsx +0 -27
- package/src/components/InputArea.tsx +0 -1660
- package/src/components/MessageList.tsx +0 -71
- package/src/components/SingleMessage.tsx +0 -298
- package/src/components/StatusBar.tsx +0 -55
- package/src/components/index.tsx +0 -8
- package/src/config/types.ts +0 -19
- package/src/config.ts +0 -186
- package/src/debug/logger.ts +0 -14
- package/src/emulation/README.md +0 -24
- package/src/emulation/catalog.ts +0 -82
- package/src/emulation/trace_style.ts +0 -8
- package/src/emulation/types.ts +0 -7
- package/src/skills/index.ts +0 -36
- package/src/skills/loader.ts +0 -135
- package/src/skills/registry.ts +0 -6
- package/src/skills/types.ts +0 -10
- package/src/types.ts +0 -84
- package/src/ui/README.md +0 -44
- package/src/ui/core/factory.ts +0 -9
- package/src/ui/core/index.ts +0 -4
- package/src/ui/core/input.ts +0 -38
- package/src/ui/core/input_segments.ts +0 -410
- package/src/ui/core/input_state.ts +0 -17
- package/src/ui/core/layout_yoga.ts +0 -122
- package/src/ui/core/style.ts +0 -38
- package/src/ui/core/types.ts +0 -54
- package/src/ui/ink/index.tsx +0 -1
- package/src/ui/ink/render.tsx +0 -60
- package/src/ui/views/activity_line.ts +0 -33
- package/src/ui/views/app.ts +0 -111
- package/src/ui/views/header.ts +0 -44
- package/src/ui/views/input_area.ts +0 -255
- package/src/ui/views/message_list.ts +0 -443
- package/src/ui/views/status_bar.ts +0 -114
- package/src/ui/vue/index.ts +0 -53
- package/src/ui/web/frame_render.tsx +0 -148
- package/src/ui/web/index.tsx +0 -1
- package/src/ui/web/render.tsx +0 -41
- package/src/utils/clipboard.ts +0 -39
- package/src/utils/clipboard_image.ts +0 -40
- package/src/utils/dictation.ts +0 -467
- package/src/utils/dictation_native.ts +0 -258
- package/src/utils/diff.ts +0 -52
- package/src/utils/image_preview.ts +0 -36
- package/src/utils/models.ts +0 -98
- package/src/utils/path_complete.ts +0 -173
- package/src/utils/path_format.ts +0 -99
- package/src/utils/shell.ts +0 -72
- package/src/utils/spinner_frames.ts +0 -1
- package/src/utils/spinner_verbs.ts +0 -23
- package/src/utils/table.ts +0 -171
- package/src/utils/tool_summary.ts +0 -56
- package/src/utils/tool_trace.ts +0 -346
- package/src/utils/update.ts +0 -44
- package/src/utils/version.ts +0 -15
- package/src/web/index.html +0 -352
- package/src/web/mirror-favicon.svg +0 -4
- package/src/web/mirror.html +0 -641
- package/src/web/mirror_hook.ts +0 -25
- package/src/web/mirror_server.ts +0 -204
- package/tsconfig.json +0 -22
- package/vite.config.ts +0 -363
- /package/{packages/ztc-dictation/bin → bin}/.gitkeep +0 -0
|
@@ -1,443 +0,0 @@
|
|
|
1
|
-
import { Message, MessageRole, ToolCall } from '../../types.js';
|
|
2
|
-
import { box, text, LayoutNode } from '../core/index.js';
|
|
3
|
-
|
|
4
|
-
function formatTime(date: Date): string {
|
|
5
|
-
return date.toLocaleTimeString('en-US', {
|
|
6
|
-
hour: '2-digit',
|
|
7
|
-
minute: '2-digit',
|
|
8
|
-
hour12: false
|
|
9
|
-
});
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
function toDate(value?: Date | string): Date | null {
|
|
13
|
-
if (!value) return null;
|
|
14
|
-
if (value instanceof Date) return value;
|
|
15
|
-
const parsed = new Date(value);
|
|
16
|
-
if (Number.isNaN(parsed.getTime())) return null;
|
|
17
|
-
return parsed;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function truncate(str: string, maxLen: number): string {
|
|
21
|
-
if (str.length <= maxLen) return str;
|
|
22
|
-
return str.slice(0, maxLen - 3) + '...';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
interface RoleConfig {
|
|
26
|
-
icon: string;
|
|
27
|
-
color: string;
|
|
28
|
-
label: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const roleConfigs: Record<MessageRole, RoleConfig> = {
|
|
32
|
-
user: { icon: '❯', color: 'blue', label: 'You' },
|
|
33
|
-
assistant: { icon: '◆', color: 'magenta', label: 'Zerg' },
|
|
34
|
-
tool: { icon: '⏺', color: 'yellow', label: 'Trace' },
|
|
35
|
-
system: { icon: '●', color: 'gray', label: 'System' }
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
function toolCallView(tool: ToolCall): LayoutNode {
|
|
39
|
-
const statusIcons = {
|
|
40
|
-
pending: '○',
|
|
41
|
-
running: '◐',
|
|
42
|
-
complete: '●',
|
|
43
|
-
error: '✗'
|
|
44
|
-
};
|
|
45
|
-
|
|
46
|
-
const statusColors = {
|
|
47
|
-
pending: 'gray',
|
|
48
|
-
running: 'yellow',
|
|
49
|
-
complete: 'green',
|
|
50
|
-
error: 'red'
|
|
51
|
-
} as const;
|
|
52
|
-
|
|
53
|
-
const icon = statusIcons[tool.status];
|
|
54
|
-
const color = statusColors[tool.status];
|
|
55
|
-
|
|
56
|
-
let duration = '';
|
|
57
|
-
const started = toDate(tool.startedAt);
|
|
58
|
-
const completed = toDate(tool.completedAt);
|
|
59
|
-
if (started && completed) {
|
|
60
|
-
const ms = completed.getTime() - started.getTime();
|
|
61
|
-
duration = ms < 1000 ? `${ms}ms` : `${(ms / 1000).toFixed(1)}s`;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const extra = tool.result ? renderToolResult(tool) : null;
|
|
65
|
-
|
|
66
|
-
return box([
|
|
67
|
-
box([
|
|
68
|
-
text(`${icon} `, { color }),
|
|
69
|
-
text(tool.name, { color: 'cyan', bold: true }),
|
|
70
|
-
text(` (${tool.id.slice(0, 8)})`, { color: 'gray', dimColor: true }),
|
|
71
|
-
duration ? text(` [${duration}]`, { color: 'gray', dimColor: true }) : text('', {})
|
|
72
|
-
], { flexDirection: 'row' }),
|
|
73
|
-
tool.result
|
|
74
|
-
? extra || box([
|
|
75
|
-
text('→ ', { color: 'green' }),
|
|
76
|
-
text(truncate(tool.result, 80))
|
|
77
|
-
], { flexDirection: 'row', marginLeft: 2 })
|
|
78
|
-
: box([], {}),
|
|
79
|
-
tool.error
|
|
80
|
-
? box([
|
|
81
|
-
text('✗ ', { color: 'red' }),
|
|
82
|
-
text(truncate(tool.error, 80), { color: 'red' })
|
|
83
|
-
], { flexDirection: 'row', marginLeft: 2 })
|
|
84
|
-
: box([], {})
|
|
85
|
-
], { flexDirection: 'column', marginLeft: 2 });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function parseJson(raw?: string): Record<string, unknown> | null {
|
|
89
|
-
if (!raw) return null;
|
|
90
|
-
try {
|
|
91
|
-
const data = JSON.parse(raw) as Record<string, unknown>;
|
|
92
|
-
if (data && typeof data === 'object') return data;
|
|
93
|
-
} catch {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function renderDiffLines(diff: string, maxLines = 12): LayoutNode {
|
|
100
|
-
const lines = diff.split('\n').slice(0, maxLines);
|
|
101
|
-
return box(lines.map(line => {
|
|
102
|
-
if (line.startsWith('+')) {
|
|
103
|
-
return text(line, { color: 'green' });
|
|
104
|
-
}
|
|
105
|
-
if (line.startsWith('-')) {
|
|
106
|
-
return text(line, { color: 'red' });
|
|
107
|
-
}
|
|
108
|
-
return text(line, { color: 'gray', dimColor: true });
|
|
109
|
-
}), { flexDirection: 'column', marginLeft: 2 });
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function renderToolResult(tool: ToolCall): LayoutNode | null {
|
|
113
|
-
const parsed = parseJson(tool.result);
|
|
114
|
-
if (!parsed) return null;
|
|
115
|
-
|
|
116
|
-
if (tool.name === 'read_file') {
|
|
117
|
-
const path = String(parsed.path || '');
|
|
118
|
-
const size = parsed.size ? `${parsed.size} bytes` : 'unknown size';
|
|
119
|
-
const content = String(parsed.content || '');
|
|
120
|
-
const truncatedFlag = parsed.truncated ? ' (truncated)' : '';
|
|
121
|
-
const previewLines = content.split('\n').slice(0, 6);
|
|
122
|
-
return box([
|
|
123
|
-
box([
|
|
124
|
-
text('→ ', { color: 'green' }),
|
|
125
|
-
text(`Read ${path} (${size})${truncatedFlag}`, { color: 'green' })
|
|
126
|
-
], { flexDirection: 'row', marginLeft: 2 }),
|
|
127
|
-
box(previewLines.map(line => text(line, { color: 'gray' })), { flexDirection: 'column', marginLeft: 4 })
|
|
128
|
-
], { flexDirection: 'column' });
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (tool.name === 'write_file') {
|
|
132
|
-
const path = String(parsed.path || '');
|
|
133
|
-
const action = String(parsed.action || 'written');
|
|
134
|
-
const diff = typeof parsed.diff === 'string' ? parsed.diff : '';
|
|
135
|
-
return box([
|
|
136
|
-
box([
|
|
137
|
-
text('→ ', { color: 'green' }),
|
|
138
|
-
text(`Wrote ${path} (${action})`, { color: 'green' })
|
|
139
|
-
], { flexDirection: 'row', marginLeft: 2 }),
|
|
140
|
-
diff ? renderDiffLines(diff) : box([text('(no diff)', { color: 'gray', dimColor: true })], { marginLeft: 4 })
|
|
141
|
-
], { flexDirection: 'column' });
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
if (tool.name === 'search') {
|
|
145
|
-
const path = String(parsed.path || '');
|
|
146
|
-
const count = Number(parsed.count || 0);
|
|
147
|
-
const matches = Array.isArray(parsed.matches) ? parsed.matches : [];
|
|
148
|
-
const preview = matches.slice(0, 6).map((entry: unknown) => {
|
|
149
|
-
if (entry && typeof entry === 'object') {
|
|
150
|
-
const row = entry as Record<string, unknown>;
|
|
151
|
-
const file = String(row.path || '');
|
|
152
|
-
const line = row.line ? `:${row.line}` : '';
|
|
153
|
-
const textValue = String(row.text || '');
|
|
154
|
-
return `${file}${line} ${textValue}`.trim();
|
|
155
|
-
}
|
|
156
|
-
return '';
|
|
157
|
-
}).filter(Boolean);
|
|
158
|
-
return box([
|
|
159
|
-
box([
|
|
160
|
-
text('→ ', { color: 'green' }),
|
|
161
|
-
text(`Found ${count} matches${path ? ` in ${path}` : ''}`, { color: 'green' })
|
|
162
|
-
], { flexDirection: 'row', marginLeft: 2 }),
|
|
163
|
-
box(preview.map(line => text(line, { color: 'gray' })), { flexDirection: 'column', marginLeft: 4 })
|
|
164
|
-
], { flexDirection: 'column' });
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
if (tool.name === 'list_directory') {
|
|
168
|
-
const path = String(parsed.path || '');
|
|
169
|
-
const entries = Array.isArray(parsed.entries) ? parsed.entries : [];
|
|
170
|
-
const names = entries
|
|
171
|
-
.map(entry => entry && typeof entry === 'object' && 'name' in entry ? String((entry as Record<string, unknown>).name) : '')
|
|
172
|
-
.filter(Boolean)
|
|
173
|
-
.slice(0, 8);
|
|
174
|
-
return box([
|
|
175
|
-
box([
|
|
176
|
-
text('→ ', { color: 'green' }),
|
|
177
|
-
text(`Listed ${path} (${entries.length} items)`, { color: 'green' })
|
|
178
|
-
], { flexDirection: 'row', marginLeft: 2 }),
|
|
179
|
-
box(names.map(name => text(`- ${name}`, { color: 'gray' })), { flexDirection: 'column', marginLeft: 4 })
|
|
180
|
-
], { flexDirection: 'column' });
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function renderInlineMarkdown(line: string): LayoutNode {
|
|
187
|
-
const parts: Array<{ text: string; bold?: boolean; dim?: boolean }> = [];
|
|
188
|
-
let rest = line;
|
|
189
|
-
let matchedBold = false;
|
|
190
|
-
|
|
191
|
-
const pushPlain = (text: string) => {
|
|
192
|
-
if (text) parts.push({ text });
|
|
193
|
-
};
|
|
194
|
-
|
|
195
|
-
while (rest.length > 0) {
|
|
196
|
-
const boldStart = rest.indexOf('**');
|
|
197
|
-
if (boldStart === -1) {
|
|
198
|
-
pushPlain(rest);
|
|
199
|
-
break;
|
|
200
|
-
}
|
|
201
|
-
pushPlain(rest.slice(0, boldStart));
|
|
202
|
-
const afterStart = rest.slice(boldStart + 2);
|
|
203
|
-
const boldEnd = afterStart.indexOf('**');
|
|
204
|
-
if (boldEnd === -1) {
|
|
205
|
-
pushPlain(rest);
|
|
206
|
-
break;
|
|
207
|
-
}
|
|
208
|
-
const boldText = afterStart.slice(0, boldEnd);
|
|
209
|
-
if (boldText) {
|
|
210
|
-
matchedBold = true;
|
|
211
|
-
parts.push({ text: boldText, bold: true });
|
|
212
|
-
}
|
|
213
|
-
rest = afterStart.slice(boldEnd + 2);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (!matchedBold && line.includes('**')) {
|
|
217
|
-
return text(line.replace(/\*\*/g, ''), {});
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return box(
|
|
221
|
-
parts.map(part => text(part.text, { bold: part.bold, dimColor: part.dim })),
|
|
222
|
-
{ flexDirection: 'row' }
|
|
223
|
-
);
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function renderMarkdownLine(line: string): LayoutNode {
|
|
227
|
-
const trimmed = line.trimStart();
|
|
228
|
-
if (trimmed.startsWith('### ')) {
|
|
229
|
-
return text(trimmed.slice(4), { bold: true, color: 'cyan' });
|
|
230
|
-
}
|
|
231
|
-
if (trimmed.startsWith('## ')) {
|
|
232
|
-
return text(trimmed.slice(3), { bold: true, color: 'cyan' });
|
|
233
|
-
}
|
|
234
|
-
if (trimmed.startsWith('# ')) {
|
|
235
|
-
return text(trimmed.slice(2), { bold: true, color: 'cyan' });
|
|
236
|
-
}
|
|
237
|
-
return renderInlineMarkdown(line);
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
function isTableSeparator(line: string): boolean {
|
|
241
|
-
const trimmed = line.trim();
|
|
242
|
-
return trimmed.startsWith('|') && trimmed.endsWith('|') && /^[|\-\s:]+$/.test(trimmed);
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
function splitTableRow(line: string): string[] {
|
|
246
|
-
const trimmed = line.trim();
|
|
247
|
-
const parts = trimmed.replace(/^\|/, '').replace(/\|$/, '').split('|');
|
|
248
|
-
return parts.map(part => part.replace(/\*\*/g, '').trim());
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
function renderTable(lines: string[]): LayoutNode[] {
|
|
252
|
-
if (lines.length < 2) return [];
|
|
253
|
-
const header = splitTableRow(lines[0]);
|
|
254
|
-
const rows = lines.slice(2).map(splitTableRow);
|
|
255
|
-
const allRows = [header, ...rows];
|
|
256
|
-
const widths: number[] = [];
|
|
257
|
-
allRows.forEach(row => {
|
|
258
|
-
row.forEach((cell, idx) => {
|
|
259
|
-
const len = cell.length;
|
|
260
|
-
widths[idx] = Math.max(widths[idx] || 0, len);
|
|
261
|
-
});
|
|
262
|
-
});
|
|
263
|
-
|
|
264
|
-
const formatRow = (row: string[]) => {
|
|
265
|
-
const cells = row.map((cell, idx) => cell.padEnd(widths[idx] || 0));
|
|
266
|
-
return `| ${cells.join(' | ')} |`;
|
|
267
|
-
};
|
|
268
|
-
|
|
269
|
-
const rendered: LayoutNode[] = [];
|
|
270
|
-
rendered.push(text(formatRow(header), { bold: true }));
|
|
271
|
-
const sep = `| ${widths.map(w => '-'.repeat(Math.max(3, w))).join(' | ')} |`;
|
|
272
|
-
rendered.push(text(sep, { color: 'gray', dimColor: true }));
|
|
273
|
-
rows.forEach(row => {
|
|
274
|
-
rendered.push(text(formatRow(row), { color: 'gray' }));
|
|
275
|
-
});
|
|
276
|
-
return rendered;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function renderMarkdownLines(lines: string[]): LayoutNode[] {
|
|
280
|
-
const normalized: string[] = [];
|
|
281
|
-
let inCodeBlock = false;
|
|
282
|
-
lines.forEach(line => {
|
|
283
|
-
if (line.trim().startsWith('```')) {
|
|
284
|
-
inCodeBlock = !inCodeBlock;
|
|
285
|
-
normalized.push(line);
|
|
286
|
-
return;
|
|
287
|
-
}
|
|
288
|
-
if (inCodeBlock) {
|
|
289
|
-
normalized.push(line);
|
|
290
|
-
return;
|
|
291
|
-
}
|
|
292
|
-
if (line.includes('||') && line.includes('|')) {
|
|
293
|
-
const segments = line.split('||').map(segment => segment.trim()).filter(Boolean);
|
|
294
|
-
segments.forEach(segment => {
|
|
295
|
-
let out = segment;
|
|
296
|
-
if (!out.startsWith('|')) out = `| ${out}`;
|
|
297
|
-
if (!out.endsWith('|')) out = `${out} |`;
|
|
298
|
-
normalized.push(out);
|
|
299
|
-
});
|
|
300
|
-
} else {
|
|
301
|
-
// Preserve the original line - don't collapse whitespace as it breaks formatting
|
|
302
|
-
normalized.push(line);
|
|
303
|
-
}
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
const nodes: LayoutNode[] = [];
|
|
307
|
-
let i = 0;
|
|
308
|
-
while (i < normalized.length) {
|
|
309
|
-
const line = normalized[i];
|
|
310
|
-
const next = normalized[i + 1];
|
|
311
|
-
if (line && next && line.includes('|') && isTableSeparator(next)) {
|
|
312
|
-
const tableLines: string[] = [line, next];
|
|
313
|
-
let j = i + 2;
|
|
314
|
-
while (j < normalized.length && normalized[j].includes('|')) {
|
|
315
|
-
tableLines.push(normalized[j]);
|
|
316
|
-
j += 1;
|
|
317
|
-
}
|
|
318
|
-
nodes.push(...renderTable(tableLines));
|
|
319
|
-
i = j;
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
nodes.push(renderMarkdownLine(line));
|
|
323
|
-
i += 1;
|
|
324
|
-
}
|
|
325
|
-
return nodes;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
function messageView(message: Message, expandToolOutputs = false): LayoutNode {
|
|
329
|
-
const config = roleConfigs[message.role];
|
|
330
|
-
const toolOutput = message.metadata && (message.metadata as Record<string, any>).toolOutput;
|
|
331
|
-
const content = toolOutput && expandToolOutputs && toolOutput.full
|
|
332
|
-
? toolOutput.full
|
|
333
|
-
: toolOutput && toolOutput.preview
|
|
334
|
-
? toolOutput.preview
|
|
335
|
-
: message.content;
|
|
336
|
-
const lines = content.split('\n');
|
|
337
|
-
|
|
338
|
-
return box([
|
|
339
|
-
box([
|
|
340
|
-
text(`${config.icon} ${config.label}`, { color: config.color, bold: true }),
|
|
341
|
-
text(` • ${formatTime(message.timestamp)}`, { color: 'gray', dimColor: true }),
|
|
342
|
-
message.isStreaming ? text(' ▌', { color: 'yellow', bold: true }) : text('', {})
|
|
343
|
-
], { flexDirection: 'row' }),
|
|
344
|
-
box(renderMarkdownLines(lines), { flexDirection: 'column', marginLeft: 2 }),
|
|
345
|
-
message.toolCalls && message.toolCalls.length > 0
|
|
346
|
-
? box(message.toolCalls.map(tool => toolCallView(tool)), { flexDirection: 'column' })
|
|
347
|
-
: box([], {})
|
|
348
|
-
], { flexDirection: 'column', marginY: 1 });
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
function estimateMessageLines(message: Message): number {
|
|
352
|
-
const toolOutput = message.metadata && (message.metadata as Record<string, any>).toolOutput;
|
|
353
|
-
const content = toolOutput && toolOutput.preview ? toolOutput.preview : message.content;
|
|
354
|
-
const contentLines = content.split('\n').length;
|
|
355
|
-
const toolLines = message.toolCalls ? message.toolCalls.length * 3 : 0;
|
|
356
|
-
const headerLines = 1;
|
|
357
|
-
const margins = 2;
|
|
358
|
-
return headerLines + contentLines + toolLines + margins;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function clipMessage(message: Message, available: number): Message | null {
|
|
362
|
-
if (available <= 0) return null;
|
|
363
|
-
const headerLines = 1;
|
|
364
|
-
const margins = 2;
|
|
365
|
-
const remaining = available - headerLines - margins;
|
|
366
|
-
if (remaining <= 0) return null;
|
|
367
|
-
|
|
368
|
-
const toolOutput = message.metadata && (message.metadata as Record<string, any>).toolOutput;
|
|
369
|
-
const content = toolOutput && toolOutput.preview ? toolOutput.preview : message.content;
|
|
370
|
-
const lines = content.split('\n');
|
|
371
|
-
if (lines.length <= remaining) {
|
|
372
|
-
return message;
|
|
373
|
-
}
|
|
374
|
-
const clippedLines = lines.slice(-remaining);
|
|
375
|
-
if (clippedLines.length > 0) {
|
|
376
|
-
clippedLines[0] = `... ${clippedLines[0]}`;
|
|
377
|
-
}
|
|
378
|
-
return {
|
|
379
|
-
...message,
|
|
380
|
-
content: clippedLines.join('\n'),
|
|
381
|
-
toolCalls: []
|
|
382
|
-
};
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
interface MessageListProps {
|
|
386
|
-
messages: Message[];
|
|
387
|
-
height?: number;
|
|
388
|
-
maxMessages?: number;
|
|
389
|
-
debug?: boolean;
|
|
390
|
-
expandToolOutputs?: boolean;
|
|
391
|
-
scrollback?: boolean;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
export function buildMessageListView({
|
|
395
|
-
messages,
|
|
396
|
-
height,
|
|
397
|
-
maxMessages = 50,
|
|
398
|
-
debug = false,
|
|
399
|
-
expandToolOutputs = false,
|
|
400
|
-
scrollback = false
|
|
401
|
-
}: MessageListProps): LayoutNode {
|
|
402
|
-
const recentMessages = messages.slice(-maxMessages);
|
|
403
|
-
let visibleMessages: Message[] = recentMessages;
|
|
404
|
-
if (!scrollback && typeof height === 'number') {
|
|
405
|
-
visibleMessages = [];
|
|
406
|
-
let remaining = height;
|
|
407
|
-
for (let i = recentMessages.length - 1; i >= 0; i -= 1) {
|
|
408
|
-
const msg = recentMessages[i];
|
|
409
|
-
const estimate = estimateMessageLines(msg);
|
|
410
|
-
if (estimate <= remaining) {
|
|
411
|
-
visibleMessages.unshift(msg);
|
|
412
|
-
remaining -= estimate;
|
|
413
|
-
continue;
|
|
414
|
-
}
|
|
415
|
-
if (visibleMessages.length === 0) {
|
|
416
|
-
const clipped = clipMessage(msg, remaining);
|
|
417
|
-
if (clipped) {
|
|
418
|
-
visibleMessages.unshift(clipped);
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
break;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
return box([
|
|
426
|
-
!scrollback ? box([], { flexGrow: 1 }) : box([], {}),
|
|
427
|
-
box(visibleMessages.map(msg => messageView(msg, expandToolOutputs)), { flexDirection: 'column' }),
|
|
428
|
-
messages.length === 0
|
|
429
|
-
? box([text('No messages yet. Type something to begin.', { color: 'gray', dimColor: true })], {
|
|
430
|
-
flexGrow: 1,
|
|
431
|
-
alignItems: 'center',
|
|
432
|
-
justifyContent: 'center'
|
|
433
|
-
})
|
|
434
|
-
: box([], {})
|
|
435
|
-
], {
|
|
436
|
-
flexDirection: 'column',
|
|
437
|
-
height: scrollback ? undefined : height,
|
|
438
|
-
paddingX: 1,
|
|
439
|
-
flexGrow: scrollback ? undefined : 1,
|
|
440
|
-
borderStyle: debug ? 'single' : undefined,
|
|
441
|
-
borderColor: debug ? 'gray' : undefined
|
|
442
|
-
});
|
|
443
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import { AgentState, AgentStatus } from '../../types.js';
|
|
2
|
-
import { box, text, LayoutNode } from '../core/index.js';
|
|
3
|
-
|
|
4
|
-
interface StatusBarProps {
|
|
5
|
-
state: AgentState;
|
|
6
|
-
sessionId?: string;
|
|
7
|
-
version?: string;
|
|
8
|
-
connectionStatus?: 'connected' | 'disconnected' | 'connecting';
|
|
9
|
-
contextLength?: number;
|
|
10
|
-
contextEstimated?: boolean;
|
|
11
|
-
provider?: string;
|
|
12
|
-
model?: string;
|
|
13
|
-
emulationId?: string;
|
|
14
|
-
inputMode?: 'queue' | 'interrupt';
|
|
15
|
-
toast?: string | null;
|
|
16
|
-
spinnerLabel?: string | null;
|
|
17
|
-
debug?: boolean;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function getStatusConfig(state: AgentState) {
|
|
21
|
-
const configs: Record<AgentStatus, { icon: string; color: string; label: string }> = {
|
|
22
|
-
idle: { icon: '●', color: 'green', label: 'Ready' },
|
|
23
|
-
thinking: { icon: '◐', color: 'yellow', label: 'Thinking' },
|
|
24
|
-
tool_use: { icon: '⚙', color: 'cyan', label: `Tool: ${state.currentTool || 'unknown'}` },
|
|
25
|
-
streaming: { icon: '▸', color: 'magenta', label: 'Streaming' },
|
|
26
|
-
error: { icon: '✗', color: 'red', label: 'Error' }
|
|
27
|
-
};
|
|
28
|
-
return configs[state.status];
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export function buildStatusBarView({
|
|
32
|
-
state,
|
|
33
|
-
sessionId,
|
|
34
|
-
version = '0.1.0',
|
|
35
|
-
connectionStatus = 'connected',
|
|
36
|
-
contextLength,
|
|
37
|
-
contextEstimated,
|
|
38
|
-
provider,
|
|
39
|
-
model,
|
|
40
|
-
emulationId,
|
|
41
|
-
inputMode,
|
|
42
|
-
toast,
|
|
43
|
-
spinnerLabel,
|
|
44
|
-
debug = false
|
|
45
|
-
}: StatusBarProps): LayoutNode {
|
|
46
|
-
const config = getStatusConfig(state);
|
|
47
|
-
const isActive = state.status !== 'idle' && state.status !== 'error';
|
|
48
|
-
const useSpinnerLabel = spinnerLabel && (state.status === 'thinking' || state.status === 'streaming');
|
|
49
|
-
const label = useSpinnerLabel ? spinnerLabel : config.label;
|
|
50
|
-
|
|
51
|
-
const connectionColors = {
|
|
52
|
-
connected: 'green',
|
|
53
|
-
disconnected: 'red',
|
|
54
|
-
connecting: 'yellow'
|
|
55
|
-
} as const;
|
|
56
|
-
|
|
57
|
-
const connectionIcons = {
|
|
58
|
-
connected: '◉',
|
|
59
|
-
disconnected: '◯',
|
|
60
|
-
connecting: '◐'
|
|
61
|
-
};
|
|
62
|
-
|
|
63
|
-
const contextLabel = contextLength !== undefined
|
|
64
|
-
? `${contextEstimated ? '~' : ''}ctx ${contextLength.toLocaleString()}`
|
|
65
|
-
: '';
|
|
66
|
-
const toastLabel = toast ? (toast.length > 60 ? `${toast.slice(0, 59)}…` : toast) : '';
|
|
67
|
-
|
|
68
|
-
return box([
|
|
69
|
-
box([
|
|
70
|
-
text(isActive ? '⠋' : config.icon, { color: config.color }),
|
|
71
|
-
text(` ${label}`, { color: config.color }),
|
|
72
|
-
state.error ? text(` - ${state.error}`, { color: 'red' }) : text('', {}),
|
|
73
|
-
toastLabel ? text(' • ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
74
|
-
toastLabel ? text(toastLabel, { color: 'yellow' }) : text('', {})
|
|
75
|
-
], { flexDirection: 'row' }),
|
|
76
|
-
box([
|
|
77
|
-
text(connectionIcons[connectionStatus], { color: connectionColors[connectionStatus] }),
|
|
78
|
-
text(' ', { color: 'gray', dimColor: true }),
|
|
79
|
-
state.authError ? text('auth error', { color: 'red' }) : text('', {}),
|
|
80
|
-
state.authError ? text(' • ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
81
|
-
provider ? text(provider, { color: 'gray', dimColor: true }) : text('', {}),
|
|
82
|
-
provider && model ? text(' / ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
83
|
-
model ? text(model, { color: 'gray', dimColor: true }) : text('', {}),
|
|
84
|
-
(provider || model) ? text(' • ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
85
|
-
emulationId ? text(`emu:${emulationId}`, { color: 'gray', dimColor: true }) : text('', {}),
|
|
86
|
-
emulationId ? text(' • ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
87
|
-
inputMode ? text(`mode:${inputMode}`, { color: 'gray', dimColor: true }) : text('', {}),
|
|
88
|
-
inputMode ? text(' • ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
89
|
-
state.tokensUsed !== undefined && state.tokensUsed > 0
|
|
90
|
-
? text(`${state.tokensUsed.toLocaleString()} tok`, { color: 'gray', dimColor: true })
|
|
91
|
-
: text('', {}),
|
|
92
|
-
state.tokensUsed !== undefined && state.tokensUsed > 0
|
|
93
|
-
? text(' • ', { color: 'gray', dimColor: true })
|
|
94
|
-
: text('', {}),
|
|
95
|
-
contextLength !== undefined
|
|
96
|
-
? text(contextLabel, { color: 'gray', dimColor: true })
|
|
97
|
-
: text('', {}),
|
|
98
|
-
contextLength !== undefined
|
|
99
|
-
? text(' • ', { color: 'gray', dimColor: true })
|
|
100
|
-
: text('', {}),
|
|
101
|
-
text(`ZTC v${version}`, { color: 'gray', dimColor: true }),
|
|
102
|
-
sessionId ? text(' • ', { color: 'gray', dimColor: true }) : text('', {}),
|
|
103
|
-
sessionId ? text(sessionId.slice(0, 8), { color: 'gray', dimColor: true }) : text('', {})
|
|
104
|
-
], { flexDirection: 'row' })
|
|
105
|
-
], {
|
|
106
|
-
flexDirection: 'row',
|
|
107
|
-
justifyContent: 'space-between',
|
|
108
|
-
paddingX: 1,
|
|
109
|
-
height: 1,
|
|
110
|
-
flexShrink: 0,
|
|
111
|
-
borderStyle: debug ? 'single' : undefined,
|
|
112
|
-
borderColor: debug ? 'gray' : undefined
|
|
113
|
-
});
|
|
114
|
-
}
|
package/src/ui/vue/index.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { LayoutNode, TextNode } from '../core/types.js';
|
|
2
|
-
|
|
3
|
-
type VNodeFactory = (type: string, props: Record<string, unknown> | null, children?: unknown) => unknown;
|
|
4
|
-
|
|
5
|
-
function toVueStyle(style?: Record<string, unknown>): Record<string, unknown> {
|
|
6
|
-
return style ? { ...style } : {};
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function renderText(h: VNodeFactory, node: TextNode): unknown {
|
|
10
|
-
const style: Record<string, unknown> = {
|
|
11
|
-
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
|
|
12
|
-
fontSize: '14px',
|
|
13
|
-
lineHeight: '16px',
|
|
14
|
-
whiteSpace: 'pre'
|
|
15
|
-
};
|
|
16
|
-
if (node.style?.color) style.color = node.style.color;
|
|
17
|
-
if (node.style?.bold) style.fontWeight = 600;
|
|
18
|
-
if (node.style?.dimColor) style.opacity = 0.6;
|
|
19
|
-
if (node.style?.inverse) style.background = '#111';
|
|
20
|
-
|
|
21
|
-
return h('span', { style }, node.text);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function renderToVue(h: VNodeFactory, node: LayoutNode): unknown {
|
|
25
|
-
if (node.type === 'text') {
|
|
26
|
-
return renderText(h, node);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const style = toVueStyle({
|
|
30
|
-
display: 'flex',
|
|
31
|
-
flexDirection: node.style?.flexDirection || 'column',
|
|
32
|
-
flexGrow: node.style?.flexGrow ?? 0,
|
|
33
|
-
flexShrink: node.style?.flexShrink ?? 0,
|
|
34
|
-
paddingLeft: node.style?.paddingX ?? node.style?.padding ?? 0,
|
|
35
|
-
paddingRight: node.style?.paddingX ?? node.style?.padding ?? 0,
|
|
36
|
-
paddingTop: node.style?.paddingY ?? node.style?.padding ?? 0,
|
|
37
|
-
paddingBottom: node.style?.paddingY ?? node.style?.padding ?? 0,
|
|
38
|
-
marginLeft: node.style?.marginX ?? node.style?.margin ?? 0,
|
|
39
|
-
marginRight: node.style?.marginX ?? node.style?.margin ?? 0,
|
|
40
|
-
marginTop: node.style?.marginY ?? node.style?.margin ?? 0,
|
|
41
|
-
marginBottom: node.style?.marginY ?? node.style?.margin ?? 0
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
if (node.style?.width !== undefined) style.width = `${node.style.width}px`;
|
|
45
|
-
if (node.style?.height !== undefined) style.height = `${node.style.height}px`;
|
|
46
|
-
if (node.style?.borderStyle) {
|
|
47
|
-
style.borderStyle = node.style.borderStyle;
|
|
48
|
-
style.borderWidth = '1px';
|
|
49
|
-
style.borderColor = node.style.borderColor || 'currentColor';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return h('div', { style }, node.children.map(child => renderToVue(h, child)));
|
|
53
|
-
}
|