vigthoria-cli 1.10.47 → 1.10.49
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/commands/agent-session-menu.js +2 -8
- package/dist/commands/auth.js +51 -68
- package/dist/commands/bridge.js +42 -22
- package/dist/commands/cancel.js +15 -22
- package/dist/commands/chat.d.ts +3 -0
- package/dist/commands/chat.js +326 -295
- package/dist/commands/config.js +33 -73
- package/dist/commands/deploy.js +83 -123
- package/dist/commands/device.js +21 -61
- package/dist/commands/edit.js +32 -39
- package/dist/commands/explain.js +18 -25
- package/dist/commands/fork.d.ts +17 -0
- package/dist/commands/fork.js +164 -0
- package/dist/commands/generate.js +37 -44
- package/dist/commands/history.d.ts +17 -0
- package/dist/commands/history.js +113 -0
- package/dist/commands/hub.js +95 -102
- package/dist/commands/index.js +41 -46
- package/dist/commands/legion.js +146 -186
- package/dist/commands/preview.d.ts +55 -0
- package/dist/commands/preview.js +467 -0
- package/dist/commands/replay.d.ts +18 -0
- package/dist/commands/replay.js +156 -0
- package/dist/commands/repo.d.ts +97 -0
- package/dist/commands/repo.js +773 -0
- package/dist/commands/review.js +29 -36
- package/dist/commands/security.js +5 -12
- package/dist/commands/update.d.ts +9 -0
- package/dist/commands/update.js +201 -0
- package/dist/commands/wallet.js +28 -35
- package/dist/commands/workflow.js +13 -20
- package/dist/index.d.ts +21 -0
- package/dist/index.js +1652 -0
- package/dist/utils/api.d.ts +544 -0
- package/dist/utils/api.js +5486 -0
- package/dist/utils/brain-hub-client.js +1 -5
- package/dist/utils/bridge-client.js +11 -52
- package/dist/utils/cli-state.d.ts +54 -0
- package/dist/utils/cli-state.js +185 -0
- package/dist/utils/codebase-indexer.js +4 -41
- package/dist/utils/config.d.ts +82 -0
- package/dist/utils/config.js +269 -0
- package/dist/utils/context-ranker.js +15 -21
- package/dist/utils/desktop-bridge-client.d.ts +12 -0
- package/dist/utils/desktop-bridge-client.js +30 -0
- package/dist/utils/files.js +5 -42
- package/dist/utils/logger.js +42 -50
- package/dist/utils/persona.js +3 -8
- package/dist/utils/post-write-validator.js +26 -33
- package/dist/utils/project-memory.js +16 -23
- package/dist/utils/session.d.ts +118 -0
- package/dist/utils/session.js +423 -0
- package/dist/utils/task-display.js +13 -20
- package/dist/utils/tools.d.ts +269 -0
- package/dist/utils/tools.js +3450 -0
- package/dist/utils/workspace-brain-service.js +8 -45
- package/dist/utils/workspace-cache.js +18 -26
- package/dist/utils/workspace-stream.js +21 -63
- package/package.json +2 -1
- package/scripts/release/validate-no-go-gates.sh +7 -4
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session Manager - Persist and resume conversations
|
|
3
|
+
* Similar to Vigthoria's session persistence
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import * as os from 'os';
|
|
8
|
+
import { atomicWriteJson, secureFileMode } from './cli-state.js';
|
|
9
|
+
function createSessionLoadError(message, details) {
|
|
10
|
+
return {
|
|
11
|
+
code: 'SESSION_LOAD_FAILED',
|
|
12
|
+
message,
|
|
13
|
+
details,
|
|
14
|
+
isCritical: true,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function normalizeAuthState(raw) {
|
|
18
|
+
if (!raw || typeof raw !== 'object') {
|
|
19
|
+
return { token: null, expiresAt: null, isValid: false };
|
|
20
|
+
}
|
|
21
|
+
const source = raw.auth && typeof raw.auth === 'object' ? raw.auth : raw;
|
|
22
|
+
const rawToken = source.token ?? source.accessToken ?? source.jwt ?? null;
|
|
23
|
+
const rawExpiresAt = source.expiresAt ?? null;
|
|
24
|
+
const token = typeof rawToken === 'string' && rawToken.trim().length > 0 ? rawToken.trim() : null;
|
|
25
|
+
const expiresAt = typeof rawExpiresAt === 'number' && Number.isFinite(rawExpiresAt) ? rawExpiresAt : null;
|
|
26
|
+
const session = { token, expiresAt, isValid: false };
|
|
27
|
+
session.isValid = validateSession(session, { silent: true });
|
|
28
|
+
return session;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validate persisted authentication state without assuming any field is present.
|
|
32
|
+
*
|
|
33
|
+
* Production hygiene: by default this helper is SILENT. Earlier revisions
|
|
34
|
+
* wrote ``console.warn`` for every missing-token branch, which polluted
|
|
35
|
+
* stderr on PowerShell (NativeCommandError styling) and surfaced as noise
|
|
36
|
+
* on first-run installs. Pass ``{ silent: false }`` only when the caller
|
|
37
|
+
* actually wants the warning visible.
|
|
38
|
+
*/
|
|
39
|
+
export function validateSession(session, options = {}) {
|
|
40
|
+
const silent = options.silent !== false;
|
|
41
|
+
if (!session || typeof session !== 'object') {
|
|
42
|
+
if (!silent)
|
|
43
|
+
console.warn('Invalid session: session state is missing.');
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (typeof session.token !== 'string' || session.token.trim().length === 0) {
|
|
47
|
+
if (!silent)
|
|
48
|
+
console.warn('Invalid session: authentication token is missing.');
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
if (typeof session.expiresAt !== 'number' || !Number.isFinite(session.expiresAt)) {
|
|
52
|
+
if (!silent)
|
|
53
|
+
console.warn('Invalid session: expiration timestamp is missing.');
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
if (session.expiresAt <= Date.now()) {
|
|
57
|
+
if (!silent)
|
|
58
|
+
console.warn('Invalid session: authentication token has expired.');
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Load persisted authentication state and normalize nullable token fields safely.
|
|
65
|
+
*
|
|
66
|
+
* Production behaviour: first-run users (no session file yet) get a clean
|
|
67
|
+
* ``{ token: null, expiresAt: null, isValid: false }`` instead of an
|
|
68
|
+
* exception. Callers that need a hard failure can inspect ``isValid``.
|
|
69
|
+
*/
|
|
70
|
+
export async function loadSession() {
|
|
71
|
+
const configDir = path.join(os.homedir(), '.vigthoria');
|
|
72
|
+
const candidateFiles = [
|
|
73
|
+
path.join(configDir, 'auth.json'),
|
|
74
|
+
path.join(configDir, 'session.json'),
|
|
75
|
+
path.join(configDir, 'config.json'),
|
|
76
|
+
];
|
|
77
|
+
const sessionFile = candidateFiles.find((filePath) => {
|
|
78
|
+
try {
|
|
79
|
+
return fs.existsSync(filePath);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
if (!sessionFile) {
|
|
86
|
+
return { token: null, expiresAt: null, isValid: false };
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const content = fs.readFileSync(sessionFile, 'utf-8');
|
|
90
|
+
const parsed = JSON.parse(content);
|
|
91
|
+
return normalizeAuthState(parsed);
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
if (process.env.VIGTHORIA_DEBUG === '1') {
|
|
95
|
+
console.error('[vigthoria] Could not read session file:', error?.message ?? error);
|
|
96
|
+
}
|
|
97
|
+
return { token: null, expiresAt: null, isValid: false };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export class SessionManager {
|
|
101
|
+
sessionsDir;
|
|
102
|
+
compactThreshold = 40;
|
|
103
|
+
retainRecentMessages = 18;
|
|
104
|
+
/** Keep at most this many session files on disk per CLI install. */
|
|
105
|
+
maxRetainedSessions = 200;
|
|
106
|
+
/** Sessions older than this are pruned (90 days). */
|
|
107
|
+
retentionWindowMs = 90 * 24 * 60 * 60 * 1000;
|
|
108
|
+
/** Avoid running prune on every save — at most once per CLI process. */
|
|
109
|
+
prunedThisProcess = false;
|
|
110
|
+
constructor() {
|
|
111
|
+
this.sessionsDir = path.join(os.homedir(), '.vigthoria', 'sessions');
|
|
112
|
+
this.ensureDir();
|
|
113
|
+
}
|
|
114
|
+
ensureDir() {
|
|
115
|
+
try {
|
|
116
|
+
if (!fs.existsSync(this.sessionsDir)) {
|
|
117
|
+
fs.mkdirSync(this.sessionsDir, { recursive: true, mode: 0o700 });
|
|
118
|
+
}
|
|
119
|
+
if (process.platform !== 'win32') {
|
|
120
|
+
try {
|
|
121
|
+
fs.chmodSync(this.sessionsDir, 0o700);
|
|
122
|
+
}
|
|
123
|
+
catch { /* best-effort */ }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
catch (error) {
|
|
127
|
+
if (error?.code === 'EACCES' || error?.code === 'EPERM') {
|
|
128
|
+
this.sessionsDir = path.join(os.tmpdir(), 'vigthoria-sessions');
|
|
129
|
+
try {
|
|
130
|
+
if (!fs.existsSync(this.sessionsDir)) {
|
|
131
|
+
fs.mkdirSync(this.sessionsDir, { recursive: true });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
// If even tmpdir is unwritable we'll fail at write time with a useful error.
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Prune sessions older than the retention window AND cap the number of
|
|
142
|
+
* retained files. This is best-effort: any unreadable file is treated
|
|
143
|
+
* as stale and removed. Pruning is bounded to once per process.
|
|
144
|
+
*/
|
|
145
|
+
pruneIfNeeded() {
|
|
146
|
+
if (this.prunedThisProcess)
|
|
147
|
+
return;
|
|
148
|
+
this.prunedThisProcess = true;
|
|
149
|
+
try {
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
const files = fs.readdirSync(this.sessionsDir)
|
|
152
|
+
.filter((f) => f.endsWith('.json'))
|
|
153
|
+
.map((f) => {
|
|
154
|
+
const full = path.join(this.sessionsDir, f);
|
|
155
|
+
let mtimeMs = 0;
|
|
156
|
+
try {
|
|
157
|
+
mtimeMs = fs.statSync(full).mtimeMs;
|
|
158
|
+
}
|
|
159
|
+
catch { /* treat as stale */ }
|
|
160
|
+
return { file: f, full, mtimeMs };
|
|
161
|
+
});
|
|
162
|
+
// Drop everything older than the retention window.
|
|
163
|
+
for (const entry of files) {
|
|
164
|
+
if (entry.mtimeMs === 0 || now - entry.mtimeMs > this.retentionWindowMs) {
|
|
165
|
+
try {
|
|
166
|
+
fs.unlinkSync(entry.full);
|
|
167
|
+
}
|
|
168
|
+
catch { /* ignore */ }
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
// Cap retained-session count (keep most recent).
|
|
172
|
+
const remaining = files
|
|
173
|
+
.filter((e) => e.mtimeMs > 0 && now - e.mtimeMs <= this.retentionWindowMs)
|
|
174
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
175
|
+
if (remaining.length > this.maxRetainedSessions) {
|
|
176
|
+
for (const entry of remaining.slice(this.maxRetainedSessions)) {
|
|
177
|
+
try {
|
|
178
|
+
fs.unlinkSync(entry.full);
|
|
179
|
+
}
|
|
180
|
+
catch { /* ignore */ }
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
// Pruning is purely a hygiene step — never block on failure.
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Generate unique session ID
|
|
190
|
+
*/
|
|
191
|
+
generateId() {
|
|
192
|
+
const timestamp = Date.now().toString(36);
|
|
193
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
194
|
+
return `${timestamp}-${random}`;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Create a new session
|
|
198
|
+
*/
|
|
199
|
+
create(project, model, agentMode = false, operatorMode = false) {
|
|
200
|
+
const session = {
|
|
201
|
+
id: this.generateId(),
|
|
202
|
+
name: `Session ${new Date().toLocaleString()}`,
|
|
203
|
+
project,
|
|
204
|
+
model,
|
|
205
|
+
messages: [],
|
|
206
|
+
memorySummary: '',
|
|
207
|
+
compactedAt: null,
|
|
208
|
+
createdAt: new Date().toISOString(),
|
|
209
|
+
updatedAt: new Date().toISOString(),
|
|
210
|
+
agentMode,
|
|
211
|
+
operatorMode,
|
|
212
|
+
};
|
|
213
|
+
this.save(session);
|
|
214
|
+
return session;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Save session to disk — atomic + 0600 mode so transcripts cannot be
|
|
218
|
+
* read by other local users on a shared POSIX host.
|
|
219
|
+
*/
|
|
220
|
+
save(session) {
|
|
221
|
+
const compacted = this.compactSession(session);
|
|
222
|
+
const targetSession = compacted.session;
|
|
223
|
+
targetSession.updatedAt = new Date().toISOString();
|
|
224
|
+
const filePath = path.join(this.sessionsDir, `${session.id}.json`);
|
|
225
|
+
try {
|
|
226
|
+
atomicWriteJson(filePath, targetSession, 0o600);
|
|
227
|
+
secureFileMode(filePath);
|
|
228
|
+
}
|
|
229
|
+
catch (error) {
|
|
230
|
+
// Surface a best-effort hint rather than crashing the active chat.
|
|
231
|
+
if (process.env.VIGTHORIA_DEBUG === '1') {
|
|
232
|
+
console.error('[vigthoria] session save failed:', error?.message ?? error);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
this.pruneIfNeeded();
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Load session by ID
|
|
239
|
+
*/
|
|
240
|
+
load(id) {
|
|
241
|
+
const filePath = path.join(this.sessionsDir, `${id}.json`);
|
|
242
|
+
if (!fs.existsSync(filePath)) {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
247
|
+
return JSON.parse(content);
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
console.warn(`Failed to load session ${id}:`, error);
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
/**
|
|
255
|
+
* Get the most recent session for a project
|
|
256
|
+
*/
|
|
257
|
+
getLatest(project) {
|
|
258
|
+
const sessions = this.list();
|
|
259
|
+
const projectSessions = sessions
|
|
260
|
+
.filter(s => s.project === project)
|
|
261
|
+
.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime());
|
|
262
|
+
return projectSessions.length > 0 ? this.load(projectSessions[0].id) : null;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* List all sessions (metadata only) — corrupt or unreadable files are
|
|
266
|
+
* skipped silently unless VIGTHORIA_DEBUG=1.
|
|
267
|
+
*/
|
|
268
|
+
list() {
|
|
269
|
+
let files = [];
|
|
270
|
+
try {
|
|
271
|
+
files = fs.readdirSync(this.sessionsDir).filter(f => f.endsWith('.json'));
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
return files.map(f => {
|
|
277
|
+
try {
|
|
278
|
+
const content = fs.readFileSync(path.join(this.sessionsDir, f), 'utf-8');
|
|
279
|
+
const session = JSON.parse(content);
|
|
280
|
+
const { messages, ...metadata } = session;
|
|
281
|
+
return { ...metadata, messages: [] };
|
|
282
|
+
}
|
|
283
|
+
catch (error) {
|
|
284
|
+
if (process.env.VIGTHORIA_DEBUG === '1') {
|
|
285
|
+
console.warn(`Failed to read session metadata from ${f}:`, error);
|
|
286
|
+
}
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
}).filter(Boolean);
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Delete a session
|
|
293
|
+
*/
|
|
294
|
+
delete(id) {
|
|
295
|
+
const filePath = path.join(this.sessionsDir, `${id}.json`);
|
|
296
|
+
if (fs.existsSync(filePath)) {
|
|
297
|
+
fs.unlinkSync(filePath);
|
|
298
|
+
return true;
|
|
299
|
+
}
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Clear all sessions
|
|
304
|
+
*/
|
|
305
|
+
clearAll() {
|
|
306
|
+
const files = fs.readdirSync(this.sessionsDir)
|
|
307
|
+
.filter(f => f.endsWith('.json'));
|
|
308
|
+
files.forEach(f => fs.unlinkSync(path.join(this.sessionsDir, f)));
|
|
309
|
+
return files.length;
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Add message to session
|
|
313
|
+
*/
|
|
314
|
+
addMessage(session, message) {
|
|
315
|
+
session.messages.push(message);
|
|
316
|
+
this.save(session);
|
|
317
|
+
}
|
|
318
|
+
compactInMemory(session) {
|
|
319
|
+
return this.compactSession(session).session;
|
|
320
|
+
}
|
|
321
|
+
buildMemoryContext(session) {
|
|
322
|
+
if (!session?.memorySummary || !session.memorySummary.trim()) {
|
|
323
|
+
return '';
|
|
324
|
+
}
|
|
325
|
+
return [
|
|
326
|
+
'Session memory summary from earlier conversation turns.',
|
|
327
|
+
'Use this as compressed context for follow-up questions and resume behavior.',
|
|
328
|
+
session.memorySummary.trim(),
|
|
329
|
+
].join('\n');
|
|
330
|
+
}
|
|
331
|
+
compactSession(session) {
|
|
332
|
+
if (session.messages.length <= this.compactThreshold) {
|
|
333
|
+
return { session, compacted: false };
|
|
334
|
+
}
|
|
335
|
+
const systemMessages = session.messages.filter((message) => message.role === 'system' && this.isPersistentSystemMessage(message.content));
|
|
336
|
+
const recentMessages = session.messages.slice(-this.retainRecentMessages);
|
|
337
|
+
const olderMessages = session.messages.slice(0, -this.retainRecentMessages);
|
|
338
|
+
const nextSummary = this.mergeSummaries(session.memorySummary || '', this.summarizeMessages(olderMessages));
|
|
339
|
+
session.memorySummary = nextSummary;
|
|
340
|
+
session.compactedAt = new Date().toISOString();
|
|
341
|
+
session.messages = this.mergePersistentAndRecent(systemMessages, recentMessages);
|
|
342
|
+
return { session, compacted: true };
|
|
343
|
+
}
|
|
344
|
+
isPersistentSystemMessage(content) {
|
|
345
|
+
return content.includes('Vigthoria CLI agent operating contract')
|
|
346
|
+
|| content.includes('Session memory summary from earlier conversation turns.');
|
|
347
|
+
}
|
|
348
|
+
mergePersistentAndRecent(systemMessages, recentMessages) {
|
|
349
|
+
const merged = [];
|
|
350
|
+
const seen = new Set();
|
|
351
|
+
for (const message of [...systemMessages, ...recentMessages]) {
|
|
352
|
+
const key = `${message.role}:${message.content}`;
|
|
353
|
+
if (seen.has(key)) {
|
|
354
|
+
continue;
|
|
355
|
+
}
|
|
356
|
+
seen.add(key);
|
|
357
|
+
merged.push(message);
|
|
358
|
+
}
|
|
359
|
+
return merged;
|
|
360
|
+
}
|
|
361
|
+
mergeSummaries(existingSummary, newSummary) {
|
|
362
|
+
const blocks = [existingSummary.trim(), newSummary.trim()].filter(Boolean);
|
|
363
|
+
if (blocks.length === 0) {
|
|
364
|
+
return '';
|
|
365
|
+
}
|
|
366
|
+
const mergedLines = new Set();
|
|
367
|
+
for (const block of blocks) {
|
|
368
|
+
for (const line of block.split('\n')) {
|
|
369
|
+
const trimmed = line.trim();
|
|
370
|
+
if (trimmed) {
|
|
371
|
+
mergedLines.add(trimmed);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
return Array.from(mergedLines).slice(-18).join('\n');
|
|
376
|
+
}
|
|
377
|
+
summarizeMessages(messages) {
|
|
378
|
+
const summaryLines = [];
|
|
379
|
+
let userCount = 0;
|
|
380
|
+
let assistantCount = 0;
|
|
381
|
+
for (const message of messages) {
|
|
382
|
+
const normalized = this.normalizeMessageContent(message.content);
|
|
383
|
+
if (!normalized) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
if (message.role === 'user' && userCount < 8) {
|
|
387
|
+
summaryLines.push(`- User intent: ${normalized}`);
|
|
388
|
+
userCount += 1;
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
if (message.role === 'assistant' && assistantCount < 6) {
|
|
392
|
+
summaryLines.push(`- Assistant outcome: ${normalized}`);
|
|
393
|
+
assistantCount += 1;
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
if (message.role === 'system' && /Tool .* (succeeded|failed)\./.test(normalized) && assistantCount < 10) {
|
|
397
|
+
summaryLines.push(`- Tool evidence: ${normalized}`);
|
|
398
|
+
assistantCount += 1;
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return summaryLines.slice(-14).join('\n');
|
|
402
|
+
}
|
|
403
|
+
normalizeMessageContent(content) {
|
|
404
|
+
const collapsed = String(content || '')
|
|
405
|
+
.replace(/<tool_call>[\s\S]*?<\/tool_call>/g, ' ')
|
|
406
|
+
.replace(/\s+/g, ' ')
|
|
407
|
+
.trim();
|
|
408
|
+
if (!collapsed) {
|
|
409
|
+
return '';
|
|
410
|
+
}
|
|
411
|
+
return collapsed.length > 220 ? `${collapsed.slice(0, 220)}...` : collapsed;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Get session summary for display
|
|
415
|
+
*/
|
|
416
|
+
getSummary(session) {
|
|
417
|
+
const userMessages = session.messages.filter(m => m.role === 'user');
|
|
418
|
+
const preview = userMessages.length > 0
|
|
419
|
+
? userMessages[0].content.substring(0, 50) + '...'
|
|
420
|
+
: 'Empty session';
|
|
421
|
+
return `[${session.id}] ${preview} (${userMessages.length} messages)`;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
"use strict";
|
|
2
1
|
/**
|
|
3
2
|
* Vigthoria CLI — Multi-Step Terminal Task Display
|
|
4
3
|
*
|
|
@@ -7,27 +6,22 @@
|
|
|
7
6
|
*
|
|
8
7
|
* Only activates when stderr is a real TTY; JSON mode and piped output stay clean.
|
|
9
8
|
*/
|
|
10
|
-
|
|
11
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
12
|
-
};
|
|
13
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
-
exports.TaskDisplay = void 0;
|
|
15
|
-
const chalk_1 = __importDefault(require("chalk"));
|
|
9
|
+
import chalk from 'chalk';
|
|
16
10
|
const ICONS = {
|
|
17
|
-
pending:
|
|
18
|
-
running:
|
|
19
|
-
done:
|
|
20
|
-
error:
|
|
21
|
-
skipped:
|
|
11
|
+
pending: chalk.gray('○'),
|
|
12
|
+
running: chalk.cyan('⟳'),
|
|
13
|
+
done: chalk.green('✓'),
|
|
14
|
+
error: chalk.red('✗'),
|
|
15
|
+
skipped: chalk.gray('\u2013'),
|
|
22
16
|
};
|
|
23
17
|
const LABEL_FN = {
|
|
24
|
-
pending: (s) =>
|
|
25
|
-
running: (s) =>
|
|
26
|
-
done: (s) =>
|
|
27
|
-
error: (s) =>
|
|
28
|
-
skipped: (s) =>
|
|
18
|
+
pending: (s) => chalk.gray(s),
|
|
19
|
+
running: (s) => chalk.cyan.bold(s),
|
|
20
|
+
done: (s) => chalk.gray(s),
|
|
21
|
+
error: (s) => chalk.red(s),
|
|
22
|
+
skipped: (s) => chalk.gray(s),
|
|
29
23
|
};
|
|
30
|
-
class TaskDisplay {
|
|
24
|
+
export class TaskDisplay {
|
|
31
25
|
tasks = [];
|
|
32
26
|
linesRendered = 0;
|
|
33
27
|
enabled;
|
|
@@ -44,7 +38,7 @@ class TaskDisplay {
|
|
|
44
38
|
return this.tasks.map((t) => {
|
|
45
39
|
const icon = ICONS[t.status];
|
|
46
40
|
const label = LABEL_FN[t.status](t.label);
|
|
47
|
-
const detail = t.detail ?
|
|
41
|
+
const detail = t.detail ? chalk.gray(` \u2014 ${t.detail.slice(0, 60)}`) : '';
|
|
48
42
|
return ` ${icon} ${label}${detail}`;
|
|
49
43
|
});
|
|
50
44
|
}
|
|
@@ -112,4 +106,3 @@ class TaskDisplay {
|
|
|
112
106
|
return this.enabled;
|
|
113
107
|
}
|
|
114
108
|
}
|
|
115
|
-
exports.TaskDisplay = TaskDisplay;
|