agent-state-machine 2.0.4 → 2.0.6
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/lib/remote/client.js +12 -0
- package/lib/runtime/agent.js +49 -12
- package/lib/runtime/runtime.js +54 -0
- package/package.json +1 -1
- package/vercel-server/api/events/[token].js +21 -35
- package/vercel-server/api/submit/[token].js +3 -12
- package/vercel-server/api/ws/cli.js +23 -30
- package/vercel-server/ui/index.html +73 -24
package/lib/remote/client.js
CHANGED
|
@@ -164,6 +164,18 @@ export class RemoteClient {
|
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
166
|
|
|
167
|
+
/**
|
|
168
|
+
* Sync history to remote (for manual edits to history.jsonl)
|
|
169
|
+
* @param {Array} history - Array of history entries
|
|
170
|
+
*/
|
|
171
|
+
async sendHistorySync(history = []) {
|
|
172
|
+
await this.send({
|
|
173
|
+
type: 'history_sync',
|
|
174
|
+
sessionToken: this.sessionToken,
|
|
175
|
+
history,
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
167
179
|
/**
|
|
168
180
|
* Send an event to the server
|
|
169
181
|
* @param {object} event - Event object with timestamp, event type, etc.
|
package/lib/runtime/agent.js
CHANGED
|
@@ -26,26 +26,24 @@ export async function agent(name, params = {}) {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
console.log(` [Agent: ${name}] Starting...`);
|
|
29
|
-
runtime.prependHistory({
|
|
30
|
-
event: 'AGENT_STARTED',
|
|
31
|
-
agent: name
|
|
32
|
-
});
|
|
33
29
|
|
|
34
30
|
try {
|
|
35
31
|
const result = await executeAgent(runtime, name, params);
|
|
36
32
|
|
|
37
|
-
let prompt = undefined;
|
|
38
33
|
if (result && typeof result === 'object' && result._debug_prompt) {
|
|
39
|
-
prompt = result._debug_prompt;
|
|
40
34
|
delete result._debug_prompt;
|
|
41
35
|
}
|
|
42
36
|
|
|
43
37
|
console.log(` [Agent: ${name}] Completed`);
|
|
38
|
+
if (runtime._agentSuppressCompletion?.has(name)) {
|
|
39
|
+
runtime._agentSuppressCompletion.delete(name);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
|
|
44
43
|
runtime.prependHistory({
|
|
45
44
|
event: 'AGENT_COMPLETED',
|
|
46
45
|
agent: name,
|
|
47
|
-
output: result
|
|
48
|
-
prompt: prompt
|
|
46
|
+
output: result
|
|
49
47
|
});
|
|
50
48
|
|
|
51
49
|
return result;
|
|
@@ -119,6 +117,8 @@ async function executeJSAgent(runtime, agentPath, name, params) {
|
|
|
119
117
|
throw new Error(`Agent ${name} does not export a function`);
|
|
120
118
|
}
|
|
121
119
|
|
|
120
|
+
logAgentStart(runtime, name);
|
|
121
|
+
|
|
122
122
|
// Build context
|
|
123
123
|
const context = {
|
|
124
124
|
...runtime._rawMemory,
|
|
@@ -135,7 +135,7 @@ async function executeJSAgent(runtime, agentPath, name, params) {
|
|
|
135
135
|
|
|
136
136
|
// Handle interaction response from JS agent
|
|
137
137
|
if (result && result._interaction) {
|
|
138
|
-
const interactionResponse = await handleInteraction(runtime, result._interaction);
|
|
138
|
+
const interactionResponse = await handleInteraction(runtime, result._interaction, name);
|
|
139
139
|
|
|
140
140
|
// Use the interaction response as the primary output if it exists
|
|
141
141
|
// This allows the workflow to receive the user's input directly
|
|
@@ -176,7 +176,7 @@ async function executeJSAgent(runtime, agentPath, name, params) {
|
|
|
176
176
|
* Execute a Markdown agent (prompt-based)
|
|
177
177
|
*/
|
|
178
178
|
async function executeMDAgent(runtime, agentPath, name, params) {
|
|
179
|
-
const { llm, parseJSON, parseInteractionRequest } = await import('../llm.js');
|
|
179
|
+
const { llm, buildPrompt, parseJSON, parseInteractionRequest } = await import('../llm.js');
|
|
180
180
|
|
|
181
181
|
const content = fs.readFileSync(agentPath, 'utf-8');
|
|
182
182
|
const { config, prompt } = parseMarkdownAgent(content);
|
|
@@ -201,6 +201,14 @@ async function executeMDAgent(runtime, agentPath, name, params) {
|
|
|
201
201
|
|
|
202
202
|
const model = config.model || 'fast';
|
|
203
203
|
|
|
204
|
+
const fullPrompt = buildPrompt(context, {
|
|
205
|
+
model,
|
|
206
|
+
prompt: interpolatedPrompt,
|
|
207
|
+
includeContext: config.includeContext !== 'false'
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
logAgentStart(runtime, name, fullPrompt);
|
|
211
|
+
|
|
204
212
|
console.log(` Using model: ${model}`);
|
|
205
213
|
|
|
206
214
|
const response = await llm(context, {
|
|
@@ -247,7 +255,7 @@ async function executeMDAgent(runtime, agentPath, name, params) {
|
|
|
247
255
|
slug,
|
|
248
256
|
targetKey,
|
|
249
257
|
content: interactionContent
|
|
250
|
-
});
|
|
258
|
+
}, name);
|
|
251
259
|
|
|
252
260
|
// Return the user's response as the agent result
|
|
253
261
|
return { [outputKey]: userResponse, _debug_prompt: response.fullPrompt };
|
|
@@ -334,7 +342,9 @@ function sanitizeSlug(input) {
|
|
|
334
342
|
/**
|
|
335
343
|
* Handle interaction (create file, wait for user, return response)
|
|
336
344
|
*/
|
|
337
|
-
async function handleInteraction(runtime, interaction) {
|
|
345
|
+
async function handleInteraction(runtime, interaction, agentName) {
|
|
346
|
+
const effectiveAgentName = typeof agentName === 'string' ? agentName : null;
|
|
347
|
+
|
|
338
348
|
const slug = sanitizeSlug(interaction.slug);
|
|
339
349
|
const targetKey = String(interaction.targetKey || slug);
|
|
340
350
|
const content = String(interaction.content || '').trim();
|
|
@@ -357,8 +367,35 @@ ${content}
|
|
|
357
367
|
question: content
|
|
358
368
|
});
|
|
359
369
|
|
|
370
|
+
if (effectiveAgentName) {
|
|
371
|
+
runtime._agentSuppressCompletion?.add(effectiveAgentName);
|
|
372
|
+
runtime._agentResumeFlags?.add(effectiveAgentName);
|
|
373
|
+
}
|
|
374
|
+
|
|
360
375
|
// Block and wait for user input (instead of throwing)
|
|
361
376
|
const response = await runtime.waitForInteraction(filePath, slug, targetKey);
|
|
362
377
|
|
|
363
378
|
return response;
|
|
364
379
|
}
|
|
380
|
+
|
|
381
|
+
function logAgentStart(runtime, name, prompt) {
|
|
382
|
+
if (runtime._agentResumeFlags?.has(name)) {
|
|
383
|
+
runtime._agentResumeFlags.delete(name);
|
|
384
|
+
runtime.prependHistory({
|
|
385
|
+
event: 'AGENT_RESUMED',
|
|
386
|
+
agent: name
|
|
387
|
+
});
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
const entry = {
|
|
392
|
+
event: 'AGENT_STARTED',
|
|
393
|
+
agent: name
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
if (prompt) {
|
|
397
|
+
entry.prompt = prompt;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
runtime.prependHistory(entry);
|
|
401
|
+
}
|
package/lib/runtime/runtime.js
CHANGED
|
@@ -84,6 +84,10 @@ export class WorkflowRuntime {
|
|
|
84
84
|
this.remoteEnabled = false;
|
|
85
85
|
this.remoteUrl = null;
|
|
86
86
|
this.pendingRemoteInteraction = null; // { slug, targetKey, resolve, reject }
|
|
87
|
+
|
|
88
|
+
// Agent interaction tracking for history logging
|
|
89
|
+
this._agentResumeFlags = new Set();
|
|
90
|
+
this._agentSuppressCompletion = new Set();
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
ensureDirectories() {
|
|
@@ -164,6 +168,9 @@ export class WorkflowRuntime {
|
|
|
164
168
|
|
|
165
169
|
const line = JSON.stringify(entry) + '\n';
|
|
166
170
|
|
|
171
|
+
// Track when we're writing to avoid triggering the file watcher
|
|
172
|
+
this._lastHistoryWrite = Date.now();
|
|
173
|
+
|
|
167
174
|
// Prepend to file (read existing, write new + existing)
|
|
168
175
|
let existing = '';
|
|
169
176
|
if (fs.existsSync(this.historyFile)) {
|
|
@@ -539,16 +546,63 @@ export class WorkflowRuntime {
|
|
|
539
546
|
this.remoteEnabled = true;
|
|
540
547
|
this.remoteUrl = this.remoteClient.getRemoteUrl();
|
|
541
548
|
|
|
549
|
+
// Watch history.jsonl for manual edits and sync to remote
|
|
550
|
+
this.startHistoryWatcher();
|
|
551
|
+
|
|
542
552
|
console.log(`\n${C.cyan}${C.bold}Remote follow enabled${C.reset}`);
|
|
543
553
|
console.log(` ${C.dim}URL:${C.reset} ${this.remoteUrl}\n`);
|
|
544
554
|
|
|
545
555
|
return this.remoteUrl;
|
|
546
556
|
}
|
|
547
557
|
|
|
558
|
+
/**
|
|
559
|
+
* Start watching history.jsonl for manual edits
|
|
560
|
+
*/
|
|
561
|
+
startHistoryWatcher() {
|
|
562
|
+
if (this.historyWatcher) return;
|
|
563
|
+
|
|
564
|
+
// Debounce to avoid multiple syncs for rapid changes
|
|
565
|
+
let debounceTimer = null;
|
|
566
|
+
|
|
567
|
+
try {
|
|
568
|
+
this.historyWatcher = fs.watch(this.historyFile, (eventType) => {
|
|
569
|
+
if (eventType !== 'change') return;
|
|
570
|
+
|
|
571
|
+
// Debounce: wait 300ms after last change before syncing
|
|
572
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
573
|
+
|
|
574
|
+
debounceTimer = setTimeout(async () => {
|
|
575
|
+
// Don't sync if we just wrote to the file ourselves (within 500ms)
|
|
576
|
+
if (this._lastHistoryWrite && Date.now() - this._lastHistoryWrite < 500) return;
|
|
577
|
+
|
|
578
|
+
if (this.remoteClient && this.remoteEnabled) {
|
|
579
|
+
const history = this.loadHistory();
|
|
580
|
+
await this.remoteClient.sendHistorySync(history);
|
|
581
|
+
console.log(`${C.dim}Remote: History synced from file${C.reset}`);
|
|
582
|
+
}
|
|
583
|
+
}, 300);
|
|
584
|
+
});
|
|
585
|
+
} catch (err) {
|
|
586
|
+
// File might not exist yet, that's ok
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Stop watching history.jsonl
|
|
592
|
+
*/
|
|
593
|
+
stopHistoryWatcher() {
|
|
594
|
+
if (this.historyWatcher) {
|
|
595
|
+
this.historyWatcher.close();
|
|
596
|
+
this.historyWatcher = null;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
548
600
|
/**
|
|
549
601
|
* Disable remote follow mode and disconnect
|
|
550
602
|
*/
|
|
551
603
|
async disableRemote() {
|
|
604
|
+
this.stopHistoryWatcher();
|
|
605
|
+
|
|
552
606
|
if (this.remoteClient) {
|
|
553
607
|
await this.remoteClient.disconnect();
|
|
554
608
|
this.remoteClient = null;
|
package/package.json
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
* File: /vercel-server/api/events/[token].js
|
|
3
3
|
*
|
|
4
4
|
* SSE endpoint for browser connections
|
|
5
|
-
* Streams
|
|
5
|
+
* Streams events to connected browsers from a single source of truth (events list)
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import {
|
|
9
9
|
getSession,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
getEvents,
|
|
11
|
+
getEventsLength,
|
|
12
|
+
getEventsRange,
|
|
13
13
|
refreshSession,
|
|
14
14
|
} from '../../lib/redis.js';
|
|
15
15
|
|
|
@@ -48,32 +48,15 @@ export default async function handler(req, res) {
|
|
|
48
48
|
workflowName: session.workflowName,
|
|
49
49
|
})}\n\n`);
|
|
50
50
|
|
|
51
|
-
// Send existing
|
|
52
|
-
const
|
|
51
|
+
// Send all existing events (single source of truth)
|
|
52
|
+
const events = await getEvents(token);
|
|
53
53
|
res.write(`data: ${JSON.stringify({
|
|
54
54
|
type: 'history',
|
|
55
|
-
entries:
|
|
55
|
+
entries: events,
|
|
56
56
|
})}\n\n`);
|
|
57
57
|
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
let lastEventIndex = 0;
|
|
61
|
-
|
|
62
|
-
// If history is empty, seed from the event list to catch early events.
|
|
63
|
-
const currentLength = await redis.llen(eventsListKey);
|
|
64
|
-
// if (history.length === 0 && currentLength > 0) {
|
|
65
|
-
// const existingEvents = await redis.lrange(eventsListKey, 0, -1);
|
|
66
|
-
// const entries = existingEvents
|
|
67
|
-
// .map((event) => (typeof event === 'object' ? event : JSON.parse(event)))
|
|
68
|
-
// .filter(Boolean);
|
|
69
|
-
// res.write(`data: ${JSON.stringify({
|
|
70
|
-
// type: 'history',
|
|
71
|
-
// entries,
|
|
72
|
-
// })}\n\n`);
|
|
73
|
-
// lastEventIndex = currentLength;
|
|
74
|
-
// } else {
|
|
75
|
-
// lastEventIndex = currentLength;
|
|
76
|
-
// }
|
|
58
|
+
// Track current position for polling new events
|
|
59
|
+
let lastEventIndex = await getEventsLength(token);
|
|
77
60
|
|
|
78
61
|
const pollInterval = setInterval(async () => {
|
|
79
62
|
try {
|
|
@@ -81,15 +64,19 @@ export default async function handler(req, res) {
|
|
|
81
64
|
await refreshSession(token);
|
|
82
65
|
|
|
83
66
|
// Check for new events
|
|
84
|
-
const newLength = await
|
|
67
|
+
const newLength = await getEventsLength(token);
|
|
85
68
|
|
|
86
69
|
if (newLength > lastEventIndex) {
|
|
87
|
-
// Get new events (newest
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
70
|
+
// Get new events (they're prepended, so newest are at the start)
|
|
71
|
+
const newCount = newLength - lastEventIndex;
|
|
72
|
+
const newEvents = await getEventsRange(token, 0, newCount - 1);
|
|
73
|
+
|
|
74
|
+
// Send in chronological order (oldest first of the new batch)
|
|
75
|
+
for (const eventData of newEvents.reverse()) {
|
|
76
|
+
res.write(`data: ${JSON.stringify({
|
|
77
|
+
type: 'event',
|
|
78
|
+
...eventData,
|
|
79
|
+
})}\n\n`);
|
|
93
80
|
}
|
|
94
81
|
|
|
95
82
|
lastEventIndex = newLength;
|
|
@@ -106,14 +93,13 @@ export default async function handler(req, res) {
|
|
|
106
93
|
} catch (err) {
|
|
107
94
|
console.error('Error polling events:', err);
|
|
108
95
|
}
|
|
109
|
-
},
|
|
96
|
+
}, 1000); // Poll every 1 second for faster updates
|
|
110
97
|
|
|
111
98
|
// Clean up on client disconnect
|
|
112
99
|
req.on('close', () => {
|
|
113
100
|
clearInterval(pollInterval);
|
|
114
101
|
});
|
|
115
102
|
|
|
116
|
-
// For Vercel, we need to keep the connection alive but also respect function timeout
|
|
117
103
|
// Send keepalive pings
|
|
118
104
|
const keepaliveInterval = setInterval(() => {
|
|
119
105
|
res.write(': keepalive\n\n');
|
|
@@ -6,8 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
8
|
getSession,
|
|
9
|
-
|
|
10
|
-
publishEvent,
|
|
9
|
+
addEvent,
|
|
11
10
|
redis,
|
|
12
11
|
KEYS,
|
|
13
12
|
} from '../../lib/redis.js';
|
|
@@ -68,22 +67,14 @@ export default async function handler(req, res) {
|
|
|
68
67
|
// Set TTL on pending list
|
|
69
68
|
await redis.expire(pendingKey, 300); // 5 minutes
|
|
70
69
|
|
|
71
|
-
// Log event to
|
|
72
|
-
|
|
70
|
+
// Log event to events list (single source of truth for UI)
|
|
71
|
+
await addEvent(token, {
|
|
73
72
|
timestamp: new Date().toISOString(),
|
|
74
73
|
event: 'INTERACTION_SUBMITTED',
|
|
75
74
|
slug,
|
|
76
75
|
targetKey: targetKey || `_interaction_${slug}`,
|
|
77
76
|
answer: response.substring(0, 200) + (response.length > 200 ? '...' : ''),
|
|
78
77
|
source: 'remote',
|
|
79
|
-
};
|
|
80
|
-
|
|
81
|
-
await addHistoryEvent(token, event);
|
|
82
|
-
|
|
83
|
-
// Notify other browsers
|
|
84
|
-
await publishEvent(token, {
|
|
85
|
-
type: 'event',
|
|
86
|
-
...event,
|
|
87
78
|
});
|
|
88
79
|
|
|
89
80
|
return res.status(200).json({ success: true });
|
|
@@ -12,9 +12,8 @@ import {
|
|
|
12
12
|
getSession,
|
|
13
13
|
updateSession,
|
|
14
14
|
setCLIConnected,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
publishEvent,
|
|
15
|
+
addEvent,
|
|
16
|
+
setEvents,
|
|
18
17
|
redis,
|
|
19
18
|
KEYS,
|
|
20
19
|
} from '../../lib/redis.js';
|
|
@@ -59,19 +58,8 @@ async function handlePost(req, res) {
|
|
|
59
58
|
// Create session
|
|
60
59
|
await createSession(sessionToken, { workflowName, cliConnected: true });
|
|
61
60
|
|
|
62
|
-
// Replace
|
|
63
|
-
await
|
|
64
|
-
|
|
65
|
-
// Store initial history
|
|
66
|
-
if (history && history.length > 0) {
|
|
67
|
-
await addHistoryEvents(sessionToken, history);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Notify browsers
|
|
71
|
-
await publishEvent(sessionToken, {
|
|
72
|
-
type: 'cli_connected',
|
|
73
|
-
workflowName,
|
|
74
|
-
});
|
|
61
|
+
// Replace events with the provided history snapshot (single source of truth)
|
|
62
|
+
await setEvents(sessionToken, history || []);
|
|
75
63
|
|
|
76
64
|
return res.status(200).json({ success: true });
|
|
77
65
|
}
|
|
@@ -82,9 +70,10 @@ async function handlePost(req, res) {
|
|
|
82
70
|
// Update session as connected
|
|
83
71
|
await setCLIConnected(sessionToken, true);
|
|
84
72
|
|
|
85
|
-
//
|
|
86
|
-
await
|
|
87
|
-
|
|
73
|
+
// Add reconnect event to events list
|
|
74
|
+
await addEvent(sessionToken, {
|
|
75
|
+
timestamp: new Date().toISOString(),
|
|
76
|
+
event: 'CLI_RECONNECTED',
|
|
88
77
|
workflowName,
|
|
89
78
|
});
|
|
90
79
|
|
|
@@ -104,14 +93,8 @@ async function handlePost(req, res) {
|
|
|
104
93
|
delete historyEvent.sessionToken;
|
|
105
94
|
delete historyEvent.type;
|
|
106
95
|
|
|
107
|
-
// Add to
|
|
108
|
-
await
|
|
109
|
-
|
|
110
|
-
// Notify browsers
|
|
111
|
-
await publishEvent(sessionToken, {
|
|
112
|
-
type: 'event',
|
|
113
|
-
...historyEvent,
|
|
114
|
-
});
|
|
96
|
+
// Add to events list (single source of truth)
|
|
97
|
+
await addEvent(sessionToken, historyEvent);
|
|
115
98
|
|
|
116
99
|
return res.status(200).json({ success: true });
|
|
117
100
|
}
|
|
@@ -122,15 +105,25 @@ async function handlePost(req, res) {
|
|
|
122
105
|
// Mark CLI as disconnected
|
|
123
106
|
await setCLIConnected(sessionToken, false);
|
|
124
107
|
|
|
125
|
-
//
|
|
126
|
-
await
|
|
127
|
-
|
|
108
|
+
// Add disconnect event to events list
|
|
109
|
+
await addEvent(sessionToken, {
|
|
110
|
+
timestamp: new Date().toISOString(),
|
|
111
|
+
event: 'CLI_DISCONNECTED',
|
|
128
112
|
reason,
|
|
129
113
|
});
|
|
130
114
|
|
|
131
115
|
return res.status(200).json({ success: true });
|
|
132
116
|
}
|
|
133
117
|
|
|
118
|
+
case 'history_sync': {
|
|
119
|
+
const { history } = body;
|
|
120
|
+
|
|
121
|
+
// Replace events with synced history (for manual edits to history.jsonl)
|
|
122
|
+
await setEvents(sessionToken, history || []);
|
|
123
|
+
|
|
124
|
+
return res.status(200).json({ success: true });
|
|
125
|
+
}
|
|
126
|
+
|
|
134
127
|
default:
|
|
135
128
|
return res.status(400).json({ error: `Unknown message type: ${type}` });
|
|
136
129
|
}
|
|
@@ -674,11 +674,54 @@
|
|
|
674
674
|
|
|
675
675
|
// CENTERED inside card
|
|
676
676
|
if (item.event === "AGENT_STARTED") {
|
|
677
|
+
if (item.prompt) {
|
|
678
|
+
return wrapIO(
|
|
679
|
+
"left",
|
|
680
|
+
<section className="hairline rounded-2xl rounded-tl-none overflow-hidden io-in">
|
|
681
|
+
<div className="rtl-safe px-5 py-4 divider flex items-center justify-between">
|
|
682
|
+
<div>
|
|
683
|
+
<div className="text-[11px] tracking-[0.22em] font-semibold" style={{ color: "var(--muted)" }}>
|
|
684
|
+
AGENT STARTED
|
|
685
|
+
</div>
|
|
686
|
+
<div className="mt-1 text-[11px] tracking-[0.14em]" style={{ color: "var(--muted)" }}>
|
|
687
|
+
{(item.agent || "").toString()} • {time}
|
|
688
|
+
</div>
|
|
689
|
+
</div>
|
|
690
|
+
<CopyButton text={item.prompt} />
|
|
691
|
+
</div>
|
|
692
|
+
<div className="px-5 py-4">
|
|
693
|
+
<div className="markdown-body text-[13px] leading-relaxed overflow-x-auto">{item.prompt}</div>
|
|
694
|
+
</div>
|
|
695
|
+
</section>,
|
|
696
|
+
idx
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
return wrapIO(
|
|
701
|
+
"left",
|
|
702
|
+
<section className="hairline rounded-2xl rounded-tl-none px-6 py-5 io-in">
|
|
703
|
+
<div className="rtl-safe flex items-center justify-between gap-4">
|
|
704
|
+
<div className="text-[11px] tracking-[0.22em] font-semibold" style={{ color: "var(--muted)" }}>
|
|
705
|
+
AGENT STARTED
|
|
706
|
+
</div>
|
|
707
|
+
<span className="text-[11px] tracking-[0.14em]" style={{ color: "var(--muted)" }}>
|
|
708
|
+
{time}
|
|
709
|
+
</span>
|
|
710
|
+
</div>
|
|
711
|
+
<div className="mt-3 text-[13px] leading-relaxed">
|
|
712
|
+
<span className="font-semibold">{item.agent}</span>
|
|
713
|
+
</div>
|
|
714
|
+
</section>,
|
|
715
|
+
idx
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (item.event === "AGENT_RESUMED") {
|
|
677
720
|
return wrapIO(
|
|
678
721
|
"center",
|
|
679
722
|
<section className="meta-center">
|
|
680
723
|
<div className="text-[11px] tracking-[0.22em] font-semibold" style={{ color: "var(--muted)" }}>
|
|
681
|
-
AGENT
|
|
724
|
+
AGENT RESUMED
|
|
682
725
|
</div>
|
|
683
726
|
<div className="mt-2 text-[13px] leading-relaxed">
|
|
684
727
|
<span className="font-semibold">{item.agent}</span>
|
|
@@ -714,8 +757,8 @@
|
|
|
714
757
|
|
|
715
758
|
if (item.event === "INTERACTION_REQUESTED" || item.event === "PROMPT_REQUESTED") {
|
|
716
759
|
return wrapIO(
|
|
717
|
-
"
|
|
718
|
-
<section className="hairline rounded-2xl px-6 py-5
|
|
760
|
+
"center",
|
|
761
|
+
<section className="hairline rounded-2xl px-6 py-5">
|
|
719
762
|
<div className="rtl-safe flex items-center justify-between gap-4">
|
|
720
763
|
<div className="text-[11px] tracking-[0.22em] font-semibold" style={{ color: "var(--muted)" }}>
|
|
721
764
|
INTERVENTION REQUIRED
|
|
@@ -735,8 +778,8 @@
|
|
|
735
778
|
if (item.event === "PROMPT_ANSWERED" || item.event === "INTERACTION_SUBMITTED") {
|
|
736
779
|
const isManual = item.source === "remote";
|
|
737
780
|
return wrapIO(
|
|
738
|
-
"
|
|
739
|
-
<section className="hairline rounded-2xl px-6 py-5
|
|
781
|
+
"center",
|
|
782
|
+
<section className="hairline rounded-2xl px-6 py-5">
|
|
740
783
|
<div className="rtl-safe flex items-center justify-between gap-4">
|
|
741
784
|
<div className="text-[11px] tracking-[0.22em] font-semibold" style={{ color: "var(--muted)" }}>
|
|
742
785
|
{isManual ? "RESOLVED VIA BROWSER" : "USER ANSWERED"}
|
|
@@ -752,31 +795,37 @@
|
|
|
752
795
|
}
|
|
753
796
|
|
|
754
797
|
if (item.event === "AGENT_COMPLETED" || item.event === "INTERACTION_RESOLVED") {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
<section className="meta-center">
|
|
798
|
+
if (item.event === "AGENT_COMPLETED") {
|
|
799
|
+
return wrapIO(
|
|
800
|
+
"right",
|
|
801
|
+
<section className="io-out">
|
|
802
|
+
<section className="meta-center mb-4">
|
|
760
803
|
<div className="text-[11px] tracking-[0.22em] font-semibold" style={{ color: "var(--muted)" }}>
|
|
761
804
|
DONE
|
|
762
805
|
</div>
|
|
763
806
|
<div className="mt-1 text-[11px] tracking-[0.14em]" style={{ color: "var(--muted)" }}>
|
|
764
|
-
{(item.agent ||
|
|
807
|
+
{(item.agent || "").toString()} • {time}
|
|
765
808
|
</div>
|
|
766
|
-
</section
|
|
767
|
-
|
|
768
|
-
)}
|
|
769
|
-
|
|
770
|
-
{(item.output || item.result) && wrapIO(
|
|
771
|
-
"right",
|
|
772
|
-
<section className="io-out">
|
|
809
|
+
</section>
|
|
810
|
+
{(item.output || item.result) && (
|
|
773
811
|
<JsonView data={item.output || item.result} label="OUTPUT / RESPONSE" align="right" />
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
812
|
+
)}
|
|
813
|
+
</section>,
|
|
814
|
+
idx
|
|
815
|
+
);
|
|
816
|
+
}
|
|
777
817
|
|
|
778
|
-
|
|
779
|
-
|
|
818
|
+
return wrapIO(
|
|
819
|
+
"center",
|
|
820
|
+
<section className="meta-center">
|
|
821
|
+
<div className="text-[11px] tracking-[0.22em] font-semibold" style={{ color: "var(--muted)" }}>
|
|
822
|
+
DONE
|
|
823
|
+
</div>
|
|
824
|
+
<div className="mt-1 text-[11px] tracking-[0.14em]" style={{ color: "var(--muted)" }}>
|
|
825
|
+
{(item.slug || "").toString()} • {time}
|
|
826
|
+
</div>
|
|
827
|
+
</section>,
|
|
828
|
+
idx
|
|
780
829
|
);
|
|
781
830
|
}
|
|
782
831
|
|
|
@@ -936,4 +985,4 @@
|
|
|
936
985
|
</script>
|
|
937
986
|
</body>
|
|
938
987
|
|
|
939
|
-
</html>
|
|
988
|
+
</html>
|