@supatest/cli 0.0.2 → 0.0.3
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/README.md +58 -315
- package/dist/agent-runner.js +224 -52
- package/dist/commands/login.js +392 -0
- package/dist/commands/setup.js +234 -0
- package/dist/config.js +29 -0
- package/dist/core/agent.js +270 -0
- package/dist/index.js +118 -31
- package/dist/modes/headless.js +117 -0
- package/dist/modes/interactive.js +430 -0
- package/dist/presenters/composite.js +32 -0
- package/dist/presenters/console.js +163 -0
- package/dist/presenters/react.js +220 -0
- package/dist/presenters/types.js +1 -0
- package/dist/presenters/web.js +78 -0
- package/dist/prompts/builder.js +181 -0
- package/dist/prompts/fixer.js +148 -0
- package/dist/prompts/headless.md +97 -0
- package/dist/prompts/index.js +3 -0
- package/dist/prompts/interactive.md +43 -0
- package/dist/prompts/plan.md +41 -0
- package/dist/prompts/planner.js +70 -0
- package/dist/prompts/prompts/builder.md +97 -0
- package/dist/prompts/prompts/fixer.md +100 -0
- package/dist/prompts/prompts/plan.md +41 -0
- package/dist/prompts/prompts/planner.md +41 -0
- package/dist/services/api-client.js +244 -0
- package/dist/services/event-streamer.js +130 -0
- package/dist/ui/App.js +322 -0
- package/dist/ui/components/AuthBanner.js +20 -0
- package/dist/ui/components/AuthDialog.js +32 -0
- package/dist/ui/components/Banner.js +12 -0
- package/dist/ui/components/ExpandableSection.js +17 -0
- package/dist/ui/components/Header.js +49 -0
- package/dist/ui/components/HelpMenu.js +89 -0
- package/dist/ui/components/InputPrompt.js +292 -0
- package/dist/ui/components/MessageList.js +42 -0
- package/dist/ui/components/QueuedMessageDisplay.js +31 -0
- package/dist/ui/components/Scrollable.js +103 -0
- package/dist/ui/components/SessionSelector.js +196 -0
- package/dist/ui/components/StatusBar.js +45 -0
- package/dist/ui/components/messages/AssistantMessage.js +20 -0
- package/dist/ui/components/messages/ErrorMessage.js +26 -0
- package/dist/ui/components/messages/LoadingMessage.js +28 -0
- package/dist/ui/components/messages/ThinkingMessage.js +17 -0
- package/dist/ui/components/messages/TodoMessage.js +44 -0
- package/dist/ui/components/messages/ToolMessage.js +218 -0
- package/dist/ui/components/messages/UserMessage.js +14 -0
- package/dist/ui/contexts/KeypressContext.js +527 -0
- package/dist/ui/contexts/MouseContext.js +98 -0
- package/dist/ui/contexts/SessionContext.js +131 -0
- package/dist/ui/hooks/useAnimatedScrollbar.js +83 -0
- package/dist/ui/hooks/useBatchedScroll.js +22 -0
- package/dist/ui/hooks/useBracketedPaste.js +31 -0
- package/dist/ui/hooks/useFocus.js +50 -0
- package/dist/ui/hooks/useKeypress.js +26 -0
- package/dist/ui/hooks/useModeToggle.js +25 -0
- package/dist/ui/types/auth.js +13 -0
- package/dist/ui/utils/file-completion.js +56 -0
- package/dist/ui/utils/input.js +50 -0
- package/dist/ui/utils/markdown.js +376 -0
- package/dist/ui/utils/mouse.js +189 -0
- package/dist/ui/utils/theme.js +59 -0
- package/dist/utils/banner.js +7 -14
- package/dist/utils/encryption.js +71 -0
- package/dist/utils/events.js +36 -0
- package/dist/utils/keychain-storage.js +120 -0
- package/dist/utils/logger.js +103 -1
- package/dist/utils/node-version.js +1 -3
- package/dist/utils/plan-file.js +75 -0
- package/dist/utils/project-instructions.js +23 -0
- package/dist/utils/rich-logger.js +1 -1
- package/dist/utils/stdio.js +80 -0
- package/dist/utils/summary.js +1 -5
- package/dist/utils/token-storage.js +242 -0
- package/package.json +35 -15
package/dist/agent-runner.js
CHANGED
|
@@ -3,8 +3,11 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
4
4
|
import chalk from "chalk";
|
|
5
5
|
import ora from "ora";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
6
|
+
import { config as envConfig } from "./config";
|
|
7
|
+
import { ApiClient } from "./services/api-client";
|
|
8
|
+
import { EventStreamer } from "./services/event-streamer";
|
|
9
|
+
import { logger } from "./utils/logger";
|
|
10
|
+
import { generateSummary } from "./utils/summary";
|
|
8
11
|
const CLI_VERSION = "0.0.1";
|
|
9
12
|
// Fun spinner messages that rotate randomly
|
|
10
13
|
const SPINNER_MESSAGES = [
|
|
@@ -46,7 +49,7 @@ export async function runAgent(config) {
|
|
|
46
49
|
let claudeCodeStderr = "";
|
|
47
50
|
logger.setVerbose(config.verbose);
|
|
48
51
|
// Display metadata
|
|
49
|
-
|
|
52
|
+
logger.raw("");
|
|
50
53
|
// Get git branch if available
|
|
51
54
|
let gitBranch = "";
|
|
52
55
|
try {
|
|
@@ -61,16 +64,16 @@ export async function runAgent(config) {
|
|
|
61
64
|
}
|
|
62
65
|
const metadataParts = [
|
|
63
66
|
chalk.dim("Supatest AI ") + chalk.cyan(`v${CLI_VERSION}`),
|
|
64
|
-
chalk.dim("Model: ") + chalk.cyan(
|
|
67
|
+
chalk.dim("Model: ") + chalk.cyan(envConfig.anthropicModelName),
|
|
65
68
|
];
|
|
66
69
|
if (gitBranch) {
|
|
67
70
|
metadataParts.push(chalk.dim("Branch: ") + chalk.cyan(gitBranch));
|
|
68
71
|
}
|
|
69
|
-
|
|
70
|
-
|
|
72
|
+
logger.raw(metadataParts.join(chalk.dim(" • ")));
|
|
73
|
+
logger.divider();
|
|
71
74
|
// Show environment info in verbose mode
|
|
72
75
|
if (config.verbose) {
|
|
73
|
-
|
|
76
|
+
logger.raw("");
|
|
74
77
|
logger.debug("Environment & System Info:");
|
|
75
78
|
// Node.js version
|
|
76
79
|
logger.debug(` Node.js: ${process.version}`);
|
|
@@ -103,15 +106,39 @@ export async function runAgent(config) {
|
|
|
103
106
|
logger.debug(` Available Disk: unable to determine`);
|
|
104
107
|
}
|
|
105
108
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
logger.raw("");
|
|
110
|
+
logger.raw("");
|
|
111
|
+
logger.raw(chalk.white.bold("Task:") + " " + chalk.cyan(config.task));
|
|
109
112
|
if (config.logs) {
|
|
110
113
|
logger.info("Processing provided logs...");
|
|
111
114
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
+
logger.raw("");
|
|
116
|
+
// Create session on backend and initialize event streaming
|
|
117
|
+
const apiUrl = config.supatestApiUrl || "https://api.supatest.ai";
|
|
118
|
+
const apiClient = new ApiClient(apiUrl, config.supatestApiKey);
|
|
119
|
+
let sessionId;
|
|
120
|
+
let webUrl;
|
|
121
|
+
let eventStreamer;
|
|
122
|
+
try {
|
|
123
|
+
const session = await apiClient.createSession(config.task, {
|
|
124
|
+
cliVersion: CLI_VERSION,
|
|
125
|
+
cwd: process.cwd(),
|
|
126
|
+
});
|
|
127
|
+
sessionId = session.sessionId;
|
|
128
|
+
webUrl = session.webUrl;
|
|
129
|
+
eventStreamer = new EventStreamer(apiClient, sessionId);
|
|
130
|
+
logger.raw("");
|
|
131
|
+
logger.divider();
|
|
132
|
+
logger.raw(chalk.white.bold("View session live: ") +
|
|
133
|
+
chalk.cyan.underline(webUrl));
|
|
134
|
+
logger.divider();
|
|
135
|
+
logger.raw("");
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
logger.warn(`Failed to create session on backend: ${error.message}`);
|
|
139
|
+
logger.warn("Continuing without web streaming...");
|
|
140
|
+
}
|
|
141
|
+
logger.raw("");
|
|
115
142
|
// Initialize spinner variable (will be created on first agent turn)
|
|
116
143
|
let spinner = null;
|
|
117
144
|
// Resolve path to Claude Code executable
|
|
@@ -122,11 +149,26 @@ export async function runAgent(config) {
|
|
|
122
149
|
if (config.logs) {
|
|
123
150
|
prompt = `${config.task}\n\nHere are the logs to analyze:\n\`\`\`\n${config.logs}\n\`\`\``;
|
|
124
151
|
}
|
|
125
|
-
|
|
126
|
-
|
|
152
|
+
const proxyUrl = config.supatestApiUrl || "https://api.supatest.ai";
|
|
153
|
+
// Build base URL with session ID embedded in the path for message tracking
|
|
154
|
+
// The proxy will extract the session ID from the path and lookup the message ID in Redis
|
|
155
|
+
// Format: {proxyUrl}/v1/sessions/{sessionId}/anthropic
|
|
156
|
+
let baseUrl = `${proxyUrl}/public`;
|
|
157
|
+
if (sessionId) {
|
|
158
|
+
baseUrl = `${proxyUrl}/v1/sessions/${sessionId}/anthropic`;
|
|
159
|
+
if (config.verbose) {
|
|
160
|
+
logger.debug(`Using session-based proxy URL: ${baseUrl}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
process.env.ANTHROPIC_BASE_URL = baseUrl;
|
|
164
|
+
process.env.ANTHROPIC_API_KEY = config.supatestApiKey;
|
|
165
|
+
if (config.verbose) {
|
|
166
|
+
logger.debug(`Using Supatest proxy: ${proxyUrl}/public`);
|
|
167
|
+
logger.debug(`Supatest API key: ${config.supatestApiKey?.substring(0, 15)}...`);
|
|
168
|
+
}
|
|
127
169
|
// Allow override via environment variable for testing/debugging
|
|
128
|
-
if (
|
|
129
|
-
claudeCodePath =
|
|
170
|
+
if (envConfig.claudeCodeExecutablePath) {
|
|
171
|
+
claudeCodePath = envConfig.claudeCodeExecutablePath;
|
|
130
172
|
if (config.verbose) {
|
|
131
173
|
logger.debug(`Using CLAUDE_CODE_EXECUTABLE_PATH: ${claudeCodePath}`);
|
|
132
174
|
}
|
|
@@ -174,22 +216,46 @@ export async function runAgent(config) {
|
|
|
174
216
|
logger.debug(` Prompt: ${prompt.substring(0, 100)}${prompt.length > 100 ? '...' : ''}`);
|
|
175
217
|
logger.debug(` Max turns: ${config.maxIterations}`);
|
|
176
218
|
logger.debug(` Working directory: ${process.cwd()}`);
|
|
177
|
-
logger.debug(` Model: ${
|
|
219
|
+
logger.debug(` Model: ${envConfig.anthropicModelName}`);
|
|
178
220
|
logger.debug(` Claude Code executable: ${claudeCodePath}`);
|
|
179
|
-
logger.debug(` API Key: ${config.
|
|
221
|
+
logger.debug(` Supatest API Key: ${config.supatestApiKey?.substring(0, 10)}...`);
|
|
180
222
|
logger.debug(` Environment ANTHROPIC_API_KEY: ${process.env.ANTHROPIC_API_KEY ? 'set' : 'not set'}`);
|
|
181
223
|
}
|
|
224
|
+
// Stream initial user message and capture assistant message ID for usage tracking
|
|
225
|
+
let assistantMessageId;
|
|
226
|
+
if (eventStreamer && sessionId) {
|
|
227
|
+
const userMessageEvent = {
|
|
228
|
+
type: "user_message",
|
|
229
|
+
content: [{ type: "text", text: prompt }],
|
|
230
|
+
};
|
|
231
|
+
const eventResponse = await apiClient.streamEvent(sessionId, userMessageEvent);
|
|
232
|
+
assistantMessageId = eventResponse.assistantMessageId;
|
|
233
|
+
if (assistantMessageId && config.verbose) {
|
|
234
|
+
logger.debug(`Assistant message ID for tracking: ${assistantMessageId}`);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
182
237
|
// Run the agent using the SDK
|
|
238
|
+
// Note: The proxy will automatically look up the message ID via Redis using the API key
|
|
183
239
|
const queryOptions = {
|
|
184
240
|
maxTurns: config.maxIterations,
|
|
185
241
|
cwd: process.cwd(),
|
|
186
|
-
model:
|
|
242
|
+
model: envConfig.anthropicModelName,
|
|
187
243
|
permissionMode: "bypassPermissions",
|
|
188
244
|
allowDangerouslySkipPermissions: true,
|
|
189
245
|
pathToClaudeCodeExecutable: claudeCodePath,
|
|
246
|
+
// Enable streaming delta events for real-time updates
|
|
247
|
+
includePartialMessages: true,
|
|
190
248
|
// Force Node.js runtime even when running from a Bun binary
|
|
191
249
|
// The claude-code-cli.js is a large minified JS file that Bun can't parse correctly
|
|
192
250
|
executable: 'node',
|
|
251
|
+
// Explicitly pass environment variables to the subprocess
|
|
252
|
+
env: {
|
|
253
|
+
...process.env,
|
|
254
|
+
ANTHROPIC_API_KEY: process.env.ANTHROPIC_API_KEY || '',
|
|
255
|
+
ANTHROPIC_BASE_URL: process.env.ANTHROPIC_BASE_URL || '',
|
|
256
|
+
ANTHROPIC_AUTH_TOKEN: '', // Clear stored OAuth token
|
|
257
|
+
CLAUDE_CODE_AUTH_TOKEN: '', // Clear any other auth tokens
|
|
258
|
+
},
|
|
193
259
|
stderr: (msg) => {
|
|
194
260
|
claudeCodeStderr += msg + "\n";
|
|
195
261
|
if (config.verbose) {
|
|
@@ -197,14 +263,24 @@ export async function runAgent(config) {
|
|
|
197
263
|
}
|
|
198
264
|
},
|
|
199
265
|
};
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
266
|
+
// Start initial spinner while waiting for first assistant message (skip in silent mode)
|
|
267
|
+
if (!logger.isSilent()) {
|
|
268
|
+
const message = getRandomSpinnerMessage();
|
|
269
|
+
spinner = ora({
|
|
270
|
+
spinner: {
|
|
271
|
+
interval: 80,
|
|
272
|
+
frames: createShimmerFrames(message),
|
|
273
|
+
}
|
|
274
|
+
});
|
|
275
|
+
spinner.start();
|
|
203
276
|
}
|
|
204
277
|
for await (const msg of query({
|
|
205
278
|
prompt,
|
|
206
279
|
options: queryOptions,
|
|
207
280
|
})) {
|
|
281
|
+
if (config.verbose) {
|
|
282
|
+
logger.debug(`Received SDK message: ${msg.type}`);
|
|
283
|
+
}
|
|
208
284
|
// Handle different message types
|
|
209
285
|
if (msg.type === "assistant") {
|
|
210
286
|
stats.iterations++;
|
|
@@ -216,12 +292,41 @@ export async function runAgent(config) {
|
|
|
216
292
|
if (Array.isArray(content)) {
|
|
217
293
|
for (const block of content) {
|
|
218
294
|
if (block.type === "text") {
|
|
219
|
-
|
|
295
|
+
logger.raw(block.text);
|
|
220
296
|
resultText += block.text + "\n";
|
|
297
|
+
// WORKAROUND: Since SDK doesn't emit stream_event with includePartialMessages,
|
|
298
|
+
// send the complete text as an assistant_text event immediately
|
|
299
|
+
if (eventStreamer && block.text) {
|
|
300
|
+
const textEvent = {
|
|
301
|
+
type: "assistant_text",
|
|
302
|
+
delta: block.text,
|
|
303
|
+
};
|
|
304
|
+
await eventStreamer.queueEvent(textEvent);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else if (block.type === "thinking") {
|
|
308
|
+
// Send thinking blocks as well
|
|
309
|
+
if (eventStreamer && block.thinking) {
|
|
310
|
+
const thinkingEvent = {
|
|
311
|
+
type: "assistant_thinking",
|
|
312
|
+
delta: block.thinking,
|
|
313
|
+
};
|
|
314
|
+
await eventStreamer.queueEvent(thinkingEvent);
|
|
315
|
+
}
|
|
221
316
|
}
|
|
222
317
|
else if (block.type === "tool_use") {
|
|
223
318
|
const toolName = block.name;
|
|
224
319
|
const input = block.input;
|
|
320
|
+
// Stream tool use event
|
|
321
|
+
if (eventStreamer) {
|
|
322
|
+
const toolUseEvent = {
|
|
323
|
+
type: "tool_use",
|
|
324
|
+
id: block.id,
|
|
325
|
+
name: toolName,
|
|
326
|
+
input: input || {},
|
|
327
|
+
};
|
|
328
|
+
await eventStreamer.queueEvent(toolUseEvent);
|
|
329
|
+
}
|
|
225
330
|
// Display tool calls to user
|
|
226
331
|
if (toolName === "Read") {
|
|
227
332
|
const filePath = input?.file_path || 'file';
|
|
@@ -267,37 +372,60 @@ export async function runAgent(config) {
|
|
|
267
372
|
logger.info("📝 Updated todos");
|
|
268
373
|
}
|
|
269
374
|
}
|
|
270
|
-
else {
|
|
271
|
-
logger.debug(`🔧 Using tool: ${toolName}`);
|
|
272
|
-
}
|
|
273
|
-
if (config.verbose) {
|
|
274
|
-
logger.debug(` Input: ${JSON.stringify(input).substring(0, 100)}`);
|
|
275
|
-
}
|
|
276
375
|
}
|
|
277
376
|
}
|
|
278
377
|
}
|
|
279
|
-
|
|
378
|
+
// Stream message_complete event with full content
|
|
379
|
+
if (eventStreamer) {
|
|
380
|
+
// Flush any pending delta events first to ensure they arrive before message_complete
|
|
381
|
+
await eventStreamer.flush();
|
|
382
|
+
const messageCompleteEvent = {
|
|
383
|
+
type: "message_complete",
|
|
384
|
+
message: {
|
|
385
|
+
role: "assistant",
|
|
386
|
+
content: content, // Cast to avoid type mismatch between SDK and shared types
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
await eventStreamer.queueEvent(messageCompleteEvent);
|
|
390
|
+
}
|
|
391
|
+
logger.raw("");
|
|
280
392
|
// Stop and clear previous spinner if it exists
|
|
281
393
|
if (spinner) {
|
|
282
394
|
spinner.stop();
|
|
283
395
|
spinner.clear();
|
|
284
396
|
}
|
|
285
|
-
// Create a new spinner instance with a random message
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
spinner
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
397
|
+
// Create a new spinner instance with a random message (skip in silent mode)
|
|
398
|
+
if (!logger.isSilent()) {
|
|
399
|
+
const message = getRandomSpinnerMessage();
|
|
400
|
+
spinner = ora({
|
|
401
|
+
spinner: {
|
|
402
|
+
interval: 80,
|
|
403
|
+
frames: createShimmerFrames(message),
|
|
404
|
+
}
|
|
405
|
+
});
|
|
406
|
+
spinner.start();
|
|
407
|
+
}
|
|
294
408
|
}
|
|
295
409
|
else if (msg.type === "stream_event") {
|
|
296
|
-
//
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
410
|
+
// NOTE: This code path is currently not triggered due to an SDK issue with includePartialMessages
|
|
411
|
+
// We've implemented a workaround above to send text immediately when assistant messages arrive
|
|
412
|
+
// Keeping this code in case future SDK versions fix the streaming support
|
|
413
|
+
const event = msg.event;
|
|
414
|
+
if (event.type === "content_block_delta") {
|
|
415
|
+
const delta = event.delta;
|
|
416
|
+
if (delta.type === "text_delta" && eventStreamer) {
|
|
417
|
+
const textDeltaEvent = {
|
|
418
|
+
type: "assistant_text",
|
|
419
|
+
delta: delta.text,
|
|
420
|
+
};
|
|
421
|
+
await eventStreamer.queueEvent(textDeltaEvent);
|
|
422
|
+
}
|
|
423
|
+
else if (delta.type === "thinking_delta" && eventStreamer) {
|
|
424
|
+
const thinkingDeltaEvent = {
|
|
425
|
+
type: "assistant_thinking",
|
|
426
|
+
delta: delta.thinking,
|
|
427
|
+
};
|
|
428
|
+
await eventStreamer.queueEvent(thinkingDeltaEvent);
|
|
301
429
|
}
|
|
302
430
|
}
|
|
303
431
|
}
|
|
@@ -318,10 +446,6 @@ export async function runAgent(config) {
|
|
|
318
446
|
logger.error(error);
|
|
319
447
|
}
|
|
320
448
|
}
|
|
321
|
-
if (config.verbose) {
|
|
322
|
-
logger.debug("Result message details:");
|
|
323
|
-
logger.debug(JSON.stringify(msg, null, 2));
|
|
324
|
-
}
|
|
325
449
|
}
|
|
326
450
|
}
|
|
327
451
|
}
|
|
@@ -329,6 +453,16 @@ export async function runAgent(config) {
|
|
|
329
453
|
spinner.stop();
|
|
330
454
|
}
|
|
331
455
|
stats.endTime = Date.now();
|
|
456
|
+
// Complete usage tracking for this message turn
|
|
457
|
+
if (assistantMessageId && apiClient) {
|
|
458
|
+
try {
|
|
459
|
+
await apiClient.completeUsage(assistantMessageId);
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
logger.warn(`Failed to complete usage tracking: ${error.message}`);
|
|
463
|
+
// Don't fail the task if usage tracking fails
|
|
464
|
+
}
|
|
465
|
+
}
|
|
332
466
|
// Generate result
|
|
333
467
|
const result = {
|
|
334
468
|
success: !hasError && stats.errors.length === 0,
|
|
@@ -337,9 +471,31 @@ export async function runAgent(config) {
|
|
|
337
471
|
iterations: stats.iterations,
|
|
338
472
|
error: stats.errors.length > 0 ? stats.errors.join("; ") : undefined,
|
|
339
473
|
};
|
|
474
|
+
// Stream session completion or error
|
|
475
|
+
if (eventStreamer) {
|
|
476
|
+
if (result.success) {
|
|
477
|
+
await eventStreamer.queueEvent({ type: "session_complete" });
|
|
478
|
+
}
|
|
479
|
+
else {
|
|
480
|
+
await eventStreamer.queueEvent({
|
|
481
|
+
type: "session_error",
|
|
482
|
+
error: result.error || "Unknown error",
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
// Flush and shutdown event streamer
|
|
486
|
+
await eventStreamer.shutdown();
|
|
487
|
+
}
|
|
340
488
|
// Print summary
|
|
341
489
|
const summaryText = generateSummary(stats, result, config.verbose);
|
|
342
|
-
|
|
490
|
+
logger.raw(summaryText);
|
|
491
|
+
// Display web URL again at the end if available
|
|
492
|
+
if (webUrl) {
|
|
493
|
+
logger.raw("");
|
|
494
|
+
logger.divider();
|
|
495
|
+
logger.raw(chalk.white.bold("Continue on web: ") +
|
|
496
|
+
chalk.cyan.underline(webUrl));
|
|
497
|
+
logger.divider();
|
|
498
|
+
}
|
|
343
499
|
return result;
|
|
344
500
|
}
|
|
345
501
|
catch (error) {
|
|
@@ -401,7 +557,7 @@ export async function runAgent(config) {
|
|
|
401
557
|
}
|
|
402
558
|
}
|
|
403
559
|
if (config.verbose && error instanceof Error && error.stack) {
|
|
404
|
-
|
|
560
|
+
logger.error(error.stack);
|
|
405
561
|
}
|
|
406
562
|
const result = {
|
|
407
563
|
success: false,
|
|
@@ -410,8 +566,24 @@ export async function runAgent(config) {
|
|
|
410
566
|
iterations: stats.iterations,
|
|
411
567
|
error: errorMessage,
|
|
412
568
|
};
|
|
569
|
+
// Stream session error and shutdown event streamer
|
|
570
|
+
if (eventStreamer) {
|
|
571
|
+
await eventStreamer.queueEvent({
|
|
572
|
+
type: "session_error",
|
|
573
|
+
error: errorMessage,
|
|
574
|
+
});
|
|
575
|
+
await eventStreamer.shutdown();
|
|
576
|
+
}
|
|
413
577
|
const summaryText = generateSummary(stats, result, config.verbose);
|
|
414
|
-
|
|
578
|
+
logger.raw(summaryText);
|
|
579
|
+
// Display web URL at the end if available
|
|
580
|
+
if (webUrl) {
|
|
581
|
+
logger.raw("");
|
|
582
|
+
logger.divider();
|
|
583
|
+
logger.raw(chalk.white.bold("View session: ") +
|
|
584
|
+
chalk.cyan.underline(webUrl));
|
|
585
|
+
logger.divider();
|
|
586
|
+
}
|
|
415
587
|
return result;
|
|
416
588
|
}
|
|
417
589
|
}
|