@alpaca-editor/core 1.0.4133 → 1.0.4134
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/editor/ai/AgentTerminal.js +286 -25
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +2 -0
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/package.json +1 -1
- package/src/editor/ai/AgentTerminal.tsx +377 -77
- package/src/editor/services/aiService.ts +3 -0
- package/src/revision.ts +2 -2
|
@@ -88,10 +88,116 @@ interface TodoItem {
|
|
|
88
88
|
text: string;
|
|
89
89
|
done?: boolean;
|
|
90
90
|
note?: string;
|
|
91
|
-
messageId
|
|
91
|
+
messageId?: string;
|
|
92
92
|
sourceTitle?: string;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
+
// Helper to extract todos from potentially incomplete JSON during streaming
|
|
96
|
+
const extractPartialTodos = (jsonText: string): any[] => {
|
|
97
|
+
// First try to parse complete JSON
|
|
98
|
+
try {
|
|
99
|
+
const parsed = JSON.parse(jsonText);
|
|
100
|
+
return Array.isArray(parsed) ? parsed : parsed?.items || [];
|
|
101
|
+
} catch (e) {
|
|
102
|
+
// If JSON is incomplete, try to extract whatever todo items we can find
|
|
103
|
+
const items: any[] = [];
|
|
104
|
+
|
|
105
|
+
// Look for individual todo objects in the partial JSON
|
|
106
|
+
// Match patterns like: { "text": "...", "done": false, "note": "..." }
|
|
107
|
+
// Handle various field orderings (text can be anywhere in the object)
|
|
108
|
+
const textPattern = /"text"\s*:\s*"([^"]+)"/g;
|
|
109
|
+
const textMatches: Array<{ text: string; startIdx: number }> = [];
|
|
110
|
+
let textMatch;
|
|
111
|
+
while ((textMatch = textPattern.exec(jsonText)) !== null) {
|
|
112
|
+
if (textMatch[1]) {
|
|
113
|
+
textMatches.push({
|
|
114
|
+
text: textMatch[1],
|
|
115
|
+
startIdx: textMatch.index,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// For each text field found, try to find the enclosing object
|
|
121
|
+
for (const { text, startIdx } of textMatches) {
|
|
122
|
+
// Find the opening brace before this text field
|
|
123
|
+
let openBrace = -1;
|
|
124
|
+
for (let i = startIdx - 1; i >= 0; i--) {
|
|
125
|
+
if (jsonText[i] === "{") {
|
|
126
|
+
openBrace = i;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
if (jsonText[i] === "}") break; // Hit another object's end
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (openBrace === -1) continue;
|
|
133
|
+
|
|
134
|
+
// Find the closing brace after this text field
|
|
135
|
+
let closeBrace = -1;
|
|
136
|
+
let depth = 0;
|
|
137
|
+
for (let i = openBrace; i < jsonText.length; i++) {
|
|
138
|
+
if (jsonText[i] === "{") depth++;
|
|
139
|
+
if (jsonText[i] === "}") {
|
|
140
|
+
depth--;
|
|
141
|
+
if (depth === 0) {
|
|
142
|
+
closeBrace = i;
|
|
143
|
+
break;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Extract the object and try to parse it
|
|
149
|
+
const objStr =
|
|
150
|
+
closeBrace !== -1
|
|
151
|
+
? jsonText.substring(openBrace, closeBrace + 1)
|
|
152
|
+
: jsonText.substring(openBrace) + "}"; // Try to close incomplete object
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
const obj = JSON.parse(objStr);
|
|
156
|
+
if (obj.text) {
|
|
157
|
+
items.push({
|
|
158
|
+
text: obj.text,
|
|
159
|
+
done: obj.done === true,
|
|
160
|
+
note: obj.note || undefined,
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {
|
|
164
|
+
// Skip malformed objects
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Also try to extract from partial objects at the end
|
|
169
|
+
// Look for the last opening brace and try to parse up to where we have valid content
|
|
170
|
+
const lines = jsonText.split("\n");
|
|
171
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
172
|
+
const partialJson = lines.slice(0, i + 1).join("\n");
|
|
173
|
+
// Try to close any open braces/brackets
|
|
174
|
+
let testJson = partialJson;
|
|
175
|
+
const openBraces = (testJson.match(/\{/g) || []).length;
|
|
176
|
+
const closeBraces = (testJson.match(/\}/g) || []).length;
|
|
177
|
+
const openBrackets = (testJson.match(/\[/g) || []).length;
|
|
178
|
+
const closeBrackets = (testJson.match(/\]/g) || []).length;
|
|
179
|
+
|
|
180
|
+
// Add missing closing characters
|
|
181
|
+
testJson += "]".repeat(openBrackets - closeBrackets);
|
|
182
|
+
testJson += "}".repeat(openBraces - closeBraces);
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const parsed = JSON.parse(testJson);
|
|
186
|
+
const partialItems = Array.isArray(parsed)
|
|
187
|
+
? parsed
|
|
188
|
+
: parsed?.items || [];
|
|
189
|
+
if (partialItems.length > items.length) {
|
|
190
|
+
return partialItems;
|
|
191
|
+
}
|
|
192
|
+
} catch (e) {
|
|
193
|
+
continue;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return items;
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
|
|
95
201
|
const extractTodosFromMessages = (messages: AgentChatMessage[]): TodoItem[] => {
|
|
96
202
|
const todos: TodoItem[] = [];
|
|
97
203
|
const fencedTodoToken = "```todo_list";
|
|
@@ -126,12 +232,20 @@ const extractTodosFromMessages = (messages: AgentChatMessage[]): TodoItem[] => {
|
|
|
126
232
|
|
|
127
233
|
try {
|
|
128
234
|
let jsonText = "";
|
|
235
|
+
let isComplete = true;
|
|
236
|
+
|
|
129
237
|
if (isFenced) {
|
|
130
238
|
const afterToken = todoStart + fencedTodoToken.length;
|
|
131
239
|
const closePos = content.indexOf("```", afterToken);
|
|
132
|
-
if (closePos === -1)
|
|
133
|
-
|
|
134
|
-
|
|
240
|
+
if (closePos === -1) {
|
|
241
|
+
// Incomplete fenced block - extract what we have so far
|
|
242
|
+
jsonText = content.slice(afterToken).trim();
|
|
243
|
+
isComplete = false;
|
|
244
|
+
cursor = content.length; // Process till end
|
|
245
|
+
} else {
|
|
246
|
+
jsonText = content.slice(afterToken, closePos).trim();
|
|
247
|
+
cursor = closePos + 3;
|
|
248
|
+
}
|
|
135
249
|
} else {
|
|
136
250
|
const afterToken = todoStart + plainTodoToken.length;
|
|
137
251
|
const braceStart = content.indexOf("{", afterToken);
|
|
@@ -149,24 +263,56 @@ const extractTodosFromMessages = (messages: AgentChatMessage[]): TodoItem[] => {
|
|
|
149
263
|
}
|
|
150
264
|
}
|
|
151
265
|
}
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
266
|
+
|
|
267
|
+
if (braceEnd === -1) {
|
|
268
|
+
// Incomplete JSON - extract what we have
|
|
269
|
+
jsonText = content.slice(braceStart).trim();
|
|
270
|
+
isComplete = false;
|
|
271
|
+
cursor = content.length;
|
|
272
|
+
} else {
|
|
273
|
+
jsonText = content.slice(braceStart, braceEnd + 1).trim();
|
|
274
|
+
cursor = braceEnd + 1;
|
|
275
|
+
}
|
|
155
276
|
}
|
|
156
277
|
|
|
157
|
-
|
|
158
|
-
const todoItems =
|
|
159
|
-
|
|
278
|
+
// Use the partial extraction helper for incomplete JSON
|
|
279
|
+
const todoItems = isComplete
|
|
280
|
+
? (() => {
|
|
281
|
+
try {
|
|
282
|
+
const parsed = JSON.parse(jsonText);
|
|
283
|
+
return Array.isArray(parsed) ? parsed : parsed?.items || [];
|
|
284
|
+
} catch (e) {
|
|
285
|
+
return [];
|
|
286
|
+
}
|
|
287
|
+
})()
|
|
288
|
+
: extractPartialTodos(jsonText);
|
|
289
|
+
|
|
290
|
+
const title = (() => {
|
|
291
|
+
try {
|
|
292
|
+
const parsed = JSON.parse(jsonText);
|
|
293
|
+
return Array.isArray(parsed) ? undefined : parsed?.title;
|
|
294
|
+
} catch (e) {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
})();
|
|
160
298
|
|
|
161
299
|
todoItems.forEach((item: any) => {
|
|
162
300
|
if (!item) return;
|
|
163
301
|
const text =
|
|
164
|
-
item.text ||
|
|
302
|
+
item.text ||
|
|
303
|
+
item.content ||
|
|
304
|
+
item.label ||
|
|
305
|
+
String(item.task || item.title || "");
|
|
165
306
|
if (!text) return;
|
|
166
307
|
todos.push({
|
|
167
308
|
id: item.id,
|
|
168
309
|
text,
|
|
169
|
-
done: !!(
|
|
310
|
+
done: !!(
|
|
311
|
+
item.done ??
|
|
312
|
+
item.completed ??
|
|
313
|
+
item.checked ??
|
|
314
|
+
item.status === "completed"
|
|
315
|
+
),
|
|
170
316
|
note: item.note || item.description,
|
|
171
317
|
messageId: message.id,
|
|
172
318
|
sourceTitle: title,
|
|
@@ -183,22 +329,101 @@ const extractTodosFromMessages = (messages: AgentChatMessage[]): TodoItem[] => {
|
|
|
183
329
|
};
|
|
184
330
|
|
|
185
331
|
// TodoListPanel component
|
|
186
|
-
const TodoListPanel = ({
|
|
332
|
+
const TodoListPanel = ({
|
|
333
|
+
messages,
|
|
334
|
+
agentMetadata,
|
|
335
|
+
}: {
|
|
336
|
+
messages: AgentChatMessage[];
|
|
337
|
+
agentMetadata: AgentMetadata | null;
|
|
338
|
+
}) => {
|
|
187
339
|
const [isExpanded, setIsExpanded] = useState(true);
|
|
188
|
-
const todos = useMemo(() => extractTodosFromMessages(messages), [messages]);
|
|
189
340
|
|
|
190
|
-
|
|
341
|
+
const todos = useMemo(() => {
|
|
342
|
+
// First try to get todos from agent metadata (real-time updates)
|
|
343
|
+
const metadataTodos = (() => {
|
|
344
|
+
try {
|
|
345
|
+
const context = (agentMetadata as any)?.additionalData?.context;
|
|
346
|
+
const todoList = context?.todoList;
|
|
347
|
+
if (todoList?.items && Array.isArray(todoList.items)) {
|
|
348
|
+
return todoList.items
|
|
349
|
+
.map((item: any, idx: number) => ({
|
|
350
|
+
id: item.id || `metadata-${idx}`,
|
|
351
|
+
text:
|
|
352
|
+
item.text ||
|
|
353
|
+
item.label ||
|
|
354
|
+
String(item.task || item.title || ""),
|
|
355
|
+
done: !!(item.done ?? item.completed ?? item.checked),
|
|
356
|
+
note: item.note || item.description,
|
|
357
|
+
messageId: undefined,
|
|
358
|
+
sourceTitle: todoList.title,
|
|
359
|
+
}))
|
|
360
|
+
.filter((item: any) => item.text);
|
|
361
|
+
}
|
|
362
|
+
} catch (e) {
|
|
363
|
+
// Fallback to extracting from messages
|
|
364
|
+
}
|
|
365
|
+
return null;
|
|
366
|
+
})();
|
|
367
|
+
|
|
368
|
+
// If we have metadata todos, use them; otherwise extract from messages
|
|
369
|
+
if (metadataTodos && metadataTodos.length > 0) {
|
|
370
|
+
return metadataTodos;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return extractTodosFromMessages(messages);
|
|
374
|
+
}, [messages, agentMetadata]);
|
|
375
|
+
|
|
376
|
+
// Check if there's an active streaming message with incomplete todo content
|
|
191
377
|
const isUpdating = useMemo(() => {
|
|
192
378
|
return messages.some((msg) => {
|
|
193
379
|
if (msg.role !== "assistant" || msg.isCompleted) return false;
|
|
194
380
|
const content = msg.content || "";
|
|
195
|
-
|
|
381
|
+
|
|
382
|
+
// Check for incomplete fenced todo blocks
|
|
383
|
+
const fencedStart = content.indexOf("```todo_list");
|
|
384
|
+
if (fencedStart !== -1) {
|
|
385
|
+
const afterStart = fencedStart + "```todo_list".length;
|
|
386
|
+
const closePos = content.indexOf("```", afterStart);
|
|
387
|
+
if (closePos === -1) {
|
|
388
|
+
// Incomplete fenced block
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Check for incomplete plain todo blocks
|
|
394
|
+
const plainStart = content.indexOf("todo_list");
|
|
395
|
+
if (plainStart !== -1 && plainStart !== fencedStart) {
|
|
396
|
+
const before = plainStart > 0 ? content[plainStart - 1] : "\n";
|
|
397
|
+
if (before === "\n" || before === "\r" || plainStart === 0) {
|
|
398
|
+
const braceStart = content.indexOf("{", plainStart);
|
|
399
|
+
if (braceStart !== -1) {
|
|
400
|
+
let depth = 0;
|
|
401
|
+
let braceEnd = -1;
|
|
402
|
+
for (let i = braceStart; i < content.length; i++) {
|
|
403
|
+
if (content[i] === "{") depth++;
|
|
404
|
+
if (content[i] === "}") {
|
|
405
|
+
depth--;
|
|
406
|
+
if (depth === 0) {
|
|
407
|
+
braceEnd = i;
|
|
408
|
+
break;
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
if (braceEnd === -1) {
|
|
413
|
+
// Incomplete plain block
|
|
414
|
+
return true;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return false;
|
|
196
421
|
});
|
|
197
422
|
}, [messages]);
|
|
198
423
|
|
|
199
424
|
if (todos.length === 0 && !isUpdating) return null;
|
|
200
425
|
|
|
201
|
-
const completedCount = todos.filter((t) => t.done).length;
|
|
426
|
+
const completedCount = todos.filter((t: TodoItem) => t.done).length;
|
|
202
427
|
const totalCount = todos.length;
|
|
203
428
|
|
|
204
429
|
return (
|
|
@@ -229,66 +454,69 @@ const TodoListPanel = ({ messages }: { messages: AgentChatMessage[] }) => {
|
|
|
229
454
|
</button>
|
|
230
455
|
{isExpanded && (
|
|
231
456
|
<div className="max-h-64 overflow-y-auto px-4 pb-3">
|
|
232
|
-
{
|
|
233
|
-
<div className="
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
<path
|
|
256
|
-
strokeLinecap="round"
|
|
257
|
-
strokeLinejoin="round"
|
|
258
|
-
d="M5 13l4 4L19 7"
|
|
259
|
-
/>
|
|
260
|
-
</svg>
|
|
261
|
-
</div>
|
|
262
|
-
) : (
|
|
263
|
-
<div className="h-4 w-4 rounded border-2 border-gray-300" />
|
|
264
|
-
)}
|
|
265
|
-
</div>
|
|
266
|
-
<div className="min-w-0 flex-1">
|
|
267
|
-
<div
|
|
268
|
-
className={`${
|
|
269
|
-
todo.done
|
|
270
|
-
? "text-gray-500 line-through"
|
|
271
|
-
: "text-gray-900"
|
|
272
|
-
}`}
|
|
273
|
-
>
|
|
274
|
-
{todo.text}
|
|
457
|
+
{todos.length > 0 && (
|
|
458
|
+
<div className="space-y-1.5">
|
|
459
|
+
{todos.map((todo: TodoItem, idx: number) => (
|
|
460
|
+
<div
|
|
461
|
+
key={todo.id || `${todo.messageId}-${idx}`}
|
|
462
|
+
className="flex items-start gap-2 rounded bg-white p-2 text-xs"
|
|
463
|
+
>
|
|
464
|
+
<div className="flex-shrink-0 pt-0.5">
|
|
465
|
+
{todo.done ? (
|
|
466
|
+
<div className="flex h-4 w-4 items-center justify-center rounded border-2 border-green-500 bg-green-500">
|
|
467
|
+
<svg
|
|
468
|
+
className="h-3 w-3 text-white"
|
|
469
|
+
fill="none"
|
|
470
|
+
strokeWidth={2}
|
|
471
|
+
stroke="currentColor"
|
|
472
|
+
viewBox="0 0 24 24"
|
|
473
|
+
>
|
|
474
|
+
<path
|
|
475
|
+
strokeLinecap="round"
|
|
476
|
+
strokeLinejoin="round"
|
|
477
|
+
d="M5 13l4 4L19 7"
|
|
478
|
+
/>
|
|
479
|
+
</svg>
|
|
275
480
|
</div>
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
481
|
+
) : (
|
|
482
|
+
<div className="h-4 w-4 rounded border-2 border-gray-300" />
|
|
483
|
+
)}
|
|
484
|
+
</div>
|
|
485
|
+
<div className="min-w-0 flex-1">
|
|
486
|
+
<div
|
|
487
|
+
className={`${
|
|
488
|
+
todo.done
|
|
489
|
+
? "text-gray-500 line-through"
|
|
490
|
+
: "text-gray-900"
|
|
491
|
+
}`}
|
|
492
|
+
>
|
|
493
|
+
{todo.text}
|
|
281
494
|
</div>
|
|
495
|
+
{todo.note && (
|
|
496
|
+
<div className="mt-0.5 text-xs text-gray-500">
|
|
497
|
+
{todo.note}
|
|
498
|
+
</div>
|
|
499
|
+
)}
|
|
282
500
|
</div>
|
|
283
|
-
))}
|
|
284
|
-
</div>
|
|
285
|
-
{isUpdating && todos.length > 0 && (
|
|
286
|
-
<div className="mt-2 flex items-center gap-2 rounded bg-blue-50 px-3 py-2 text-xs text-blue-700">
|
|
287
|
-
<Loader2 className="h-3 w-3 animate-spin" strokeWidth={1} />
|
|
288
|
-
<span>Updating todo list...</span>
|
|
289
501
|
</div>
|
|
290
|
-
)}
|
|
291
|
-
|
|
502
|
+
))}
|
|
503
|
+
</div>
|
|
504
|
+
)}
|
|
505
|
+
{isUpdating && (
|
|
506
|
+
<div
|
|
507
|
+
className={`flex items-center gap-2 rounded px-3 py-2 text-xs ${
|
|
508
|
+
todos.length > 0
|
|
509
|
+
? "mt-2 bg-blue-50 text-blue-700"
|
|
510
|
+
: "justify-center bg-white text-gray-500"
|
|
511
|
+
}`}
|
|
512
|
+
>
|
|
513
|
+
<Loader2 className="h-3 w-3 animate-spin" strokeWidth={1} />
|
|
514
|
+
<span>
|
|
515
|
+
{todos.length > 0
|
|
516
|
+
? "Updating todo list..."
|
|
517
|
+
: "Loading todo list..."}
|
|
518
|
+
</span>
|
|
519
|
+
</div>
|
|
292
520
|
)}
|
|
293
521
|
</div>
|
|
294
522
|
)}
|
|
@@ -423,7 +651,7 @@ export function AgentTerminal({
|
|
|
423
651
|
const [messages, setMessages] = useState<AgentChatMessage[]>([]);
|
|
424
652
|
const [prompt, setPrompt] = useState("");
|
|
425
653
|
const [inputPlaceholder, setInputPlaceholder] = useState<string>(
|
|
426
|
-
"Type your message... (Enter to send, Ctrl+Enter for new line)",
|
|
654
|
+
"Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)",
|
|
427
655
|
);
|
|
428
656
|
const [isLoading, setIsLoading] = useState(false);
|
|
429
657
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
@@ -552,6 +780,18 @@ export function AgentTerminal({
|
|
|
552
780
|
initialCostLimit: number;
|
|
553
781
|
} | null>(null);
|
|
554
782
|
|
|
783
|
+
// Live running totals from backend status updates (tokenUsage)
|
|
784
|
+
const [liveTotals, setLiveTotals] = useState<{
|
|
785
|
+
input: number;
|
|
786
|
+
output: number;
|
|
787
|
+
cached: number;
|
|
788
|
+
inputCost: number;
|
|
789
|
+
outputCost: number;
|
|
790
|
+
cachedCost: number;
|
|
791
|
+
totalCost: number;
|
|
792
|
+
currency?: string;
|
|
793
|
+
} | null>(null);
|
|
794
|
+
|
|
555
795
|
// Flag to track when we should create a new message
|
|
556
796
|
const shouldCreateNewMessage = useRef(false);
|
|
557
797
|
|
|
@@ -1102,6 +1342,26 @@ export function AgentTerminal({
|
|
|
1102
1342
|
case "statusUpdate":
|
|
1103
1343
|
try {
|
|
1104
1344
|
const kind = (message as any)?.data?.kind;
|
|
1345
|
+
// Live token usage totals update from backend
|
|
1346
|
+
if (kind === "tokenUsage") {
|
|
1347
|
+
const totals = (message as any)?.data?.totals;
|
|
1348
|
+
if (totals) {
|
|
1349
|
+
setLiveTotals({
|
|
1350
|
+
input: Number(totals.totalInputTokens) || 0,
|
|
1351
|
+
output: Number(totals.totalOutputTokens) || 0,
|
|
1352
|
+
cached: Number(totals.totalCachedInputTokens) || 0,
|
|
1353
|
+
inputCost: Number(totals.totalInputTokenCost) || 0,
|
|
1354
|
+
outputCost: Number(totals.totalOutputTokenCost) || 0,
|
|
1355
|
+
cachedCost:
|
|
1356
|
+
Number(totals.totalCachedInputTokenCost) || 0,
|
|
1357
|
+
totalCost: Number(totals.totalCost) || 0,
|
|
1358
|
+
currency: totals.currency,
|
|
1359
|
+
});
|
|
1360
|
+
// Force a re-render to update cost display immediately
|
|
1361
|
+
setMessages((prev) => [...prev]);
|
|
1362
|
+
}
|
|
1363
|
+
break;
|
|
1364
|
+
}
|
|
1105
1365
|
if (kind === "toolApprovalsRequired") {
|
|
1106
1366
|
const data = (message as any).data || {};
|
|
1107
1367
|
const msgId: string | undefined = data.messageId;
|
|
@@ -1313,6 +1573,25 @@ export function AgentTerminal({
|
|
|
1313
1573
|
(updatedMessage.inputTokens || 0) +
|
|
1314
1574
|
(updatedMessage.outputTokens || 0);
|
|
1315
1575
|
|
|
1576
|
+
// Update cost data if provided in the completed event
|
|
1577
|
+
if (data.inputTokenCost !== undefined) {
|
|
1578
|
+
updatedMessage.inputTokenCost = data.inputTokenCost;
|
|
1579
|
+
}
|
|
1580
|
+
if (data.outputTokenCost !== undefined) {
|
|
1581
|
+
updatedMessage.outputTokenCost =
|
|
1582
|
+
data.outputTokenCost;
|
|
1583
|
+
}
|
|
1584
|
+
if (
|
|
1585
|
+
data.cachedInputTokenCost !== undefined ||
|
|
1586
|
+
data.cachedTokenCost !== undefined
|
|
1587
|
+
) {
|
|
1588
|
+
updatedMessage.cachedInputTokenCost =
|
|
1589
|
+
data.cachedInputTokenCost ?? data.cachedTokenCost;
|
|
1590
|
+
}
|
|
1591
|
+
if (data.totalCost !== undefined) {
|
|
1592
|
+
updatedMessage.totalCost = data.totalCost;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1316
1595
|
// Handle content that might only be sent in the completed event
|
|
1317
1596
|
if (data.deltaContent && data.deltaContent.trim()) {
|
|
1318
1597
|
if (!data.isIncremental) {
|
|
@@ -2069,7 +2348,14 @@ export function AgentTerminal({
|
|
|
2069
2348
|
};
|
|
2070
2349
|
|
|
2071
2350
|
const handleKeyPress = (e: React.KeyboardEvent) => {
|
|
2072
|
-
|
|
2351
|
+
// Submit only on plain Enter (no Ctrl/Meta/Shift/Alt)
|
|
2352
|
+
if (
|
|
2353
|
+
e.key === "Enter" &&
|
|
2354
|
+
!e.ctrlKey &&
|
|
2355
|
+
!e.metaKey &&
|
|
2356
|
+
!e.shiftKey &&
|
|
2357
|
+
!e.altKey
|
|
2358
|
+
) {
|
|
2073
2359
|
e.preventDefault();
|
|
2074
2360
|
handleSubmit();
|
|
2075
2361
|
}
|
|
@@ -2944,7 +3230,7 @@ export function AgentTerminal({
|
|
|
2944
3230
|
{renderContextInfoBar()}
|
|
2945
3231
|
|
|
2946
3232
|
{/* Todo List Panel */}
|
|
2947
|
-
<TodoListPanel messages={messages} />
|
|
3233
|
+
<TodoListPanel messages={messages} agentMetadata={agentMetadata} />
|
|
2948
3234
|
|
|
2949
3235
|
{/* Input */}
|
|
2950
3236
|
<div className="border-t border-gray-200 p-4">
|
|
@@ -3172,7 +3458,21 @@ export function AgentTerminal({
|
|
|
3172
3458
|
</div>
|
|
3173
3459
|
</div>
|
|
3174
3460
|
<div className="mt-1 flex items-center gap-2 text-[10px] text-gray-500">
|
|
3175
|
-
<AgentCostDisplay
|
|
3461
|
+
<AgentCostDisplay
|
|
3462
|
+
totalTokens={
|
|
3463
|
+
liveTotals
|
|
3464
|
+
? {
|
|
3465
|
+
input: liveTotals.input,
|
|
3466
|
+
output: liveTotals.output,
|
|
3467
|
+
cached: liveTotals.cached,
|
|
3468
|
+
inputCost: liveTotals.inputCost,
|
|
3469
|
+
outputCost: liveTotals.outputCost,
|
|
3470
|
+
cachedCost: liveTotals.cachedCost,
|
|
3471
|
+
totalCost: liveTotals.totalCost,
|
|
3472
|
+
}
|
|
3473
|
+
: totalTokens
|
|
3474
|
+
}
|
|
3475
|
+
/>
|
|
3176
3476
|
{(() => {
|
|
3177
3477
|
try {
|
|
3178
3478
|
const s = (window as any).__agentContextWindowStatus;
|
|
@@ -14,12 +14,15 @@ export type AiProfile = {
|
|
|
14
14
|
id: string;
|
|
15
15
|
name: string;
|
|
16
16
|
instructions: string;
|
|
17
|
+
managedTodoInstructions?: string;
|
|
17
18
|
defaultModelId: string | null; // Guid as string, nullable
|
|
18
19
|
models: AiModel[]; // Array of model objects with id and name
|
|
19
20
|
prompts: { prompt: string; title: string }[];
|
|
20
21
|
errorMessage?: string;
|
|
21
22
|
// Whether a new agent should be seeded with current item/selection/field
|
|
22
23
|
includeEditorContextOnCreate?: boolean;
|
|
24
|
+
// When true, the agent should run in managed TODO list mode.
|
|
25
|
+
managedTodoMode?: boolean;
|
|
23
26
|
// Tools that are allowed when the user switches to "Ask" mode
|
|
24
27
|
askModeTools?: string[];
|
|
25
28
|
// Optional cost limit in USD, sourced from profile
|
package/src/revision.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const version = "1.0.
|
|
2
|
-
export const buildDate = "2025-10-
|
|
1
|
+
export const version = "1.0.4134";
|
|
2
|
+
export const buildDate = "2025-10-02 00:59:02";
|