@yeaft/webchat-agent 1.0.546 → 1.0.548
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/local-runtime/version.json +1 -1
- package/local-runtime/web/app.bundle.js +51 -48
- package/local-runtime/web/app.bundle.js.gz +0 -0
- package/local-runtime/web/index.html +2 -2
- package/local-runtime/web/style.bundle.css +1 -1
- package/local-runtime/web/style.bundle.css.gz +0 -0
- package/package.json +1 -1
- package/workbench/file-ops.js +16 -3
- package/yeaft/work-center/action-checkpoint.js +33 -10
- package/yeaft/work-center/projection.js +9 -1
- package/yeaft/work-center/runner.js +30 -1
|
Binary file
|
package/package.json
CHANGED
package/workbench/file-ops.js
CHANGED
|
@@ -10,14 +10,27 @@ export const MAX_WORKBENCH_PREVIEW_BYTES = 20 * 1024 * 1024;
|
|
|
10
10
|
export const WORKBENCH_FILE_CHUNK_BYTES = 1024 * 1024;
|
|
11
11
|
export const WORKBENCH_VIDEO_CHUNK_BYTES = 1024 * 1024;
|
|
12
12
|
|
|
13
|
-
async function
|
|
13
|
+
async function canonicalWorkspaceReadPath(filePath, workDir, errorMessage = 'File is outside the active workspace.') {
|
|
14
14
|
const canonicalRoot = await realpath(resolve(workDir));
|
|
15
15
|
const canonicalFile = await realpath(resolveAndValidatePath(filePath, canonicalRoot));
|
|
16
16
|
const relativePath = relative(canonicalRoot, canonicalFile);
|
|
17
17
|
const outside = relativePath === '..'
|
|
18
18
|
|| relativePath.startsWith(`..${platform() === 'win32' ? '\\' : '/'}`)
|
|
19
19
|
|| isAbsolute(relativePath);
|
|
20
|
-
if (outside)
|
|
20
|
+
if (outside) {
|
|
21
|
+
const error = new Error(errorMessage);
|
|
22
|
+
error.code = 'FILE_OUTSIDE_WORKSPACE';
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
return canonicalFile;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function validateResponseImagePath(filePath, workDir) {
|
|
29
|
+
const canonicalFile = await canonicalWorkspaceReadPath(
|
|
30
|
+
filePath,
|
|
31
|
+
workDir,
|
|
32
|
+
'Response image is outside the active workspace.',
|
|
33
|
+
);
|
|
21
34
|
const mimeType = BINARY_EXTENSIONS[extname(canonicalFile).toLowerCase()];
|
|
22
35
|
if (!mimeType?.startsWith('image/')) throw new Error('Response preview only supports image files.');
|
|
23
36
|
return canonicalFile;
|
|
@@ -169,7 +182,7 @@ export async function handleReadFile(msg) {
|
|
|
169
182
|
try {
|
|
170
183
|
const resolved = msg.responseImagePreview
|
|
171
184
|
? await validateResponseImagePath(filePath, workDir)
|
|
172
|
-
:
|
|
185
|
+
: await canonicalWorkspaceReadPath(filePath, workDir);
|
|
173
186
|
const ext = extname(resolved).toLowerCase();
|
|
174
187
|
if (VIDEO_EXTENSIONS[ext]) {
|
|
175
188
|
const error = new Error('Video files require the Workbench streaming protocol.');
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const MAX_TOOL_EVENTS = 16;
|
|
2
|
+
const MAX_TOOL_ID_LENGTH = 256;
|
|
2
3
|
const MAX_TOOL_NAME_LENGTH = 80;
|
|
3
4
|
const MAX_RESOURCE_LENGTH = 240;
|
|
4
5
|
const MAX_RESPONSE_LENGTH = 4_000;
|
|
@@ -9,27 +10,33 @@ function boundedString(value, maxLength) {
|
|
|
9
10
|
}
|
|
10
11
|
|
|
11
12
|
function safeResource(value) {
|
|
12
|
-
const
|
|
13
|
-
if (!
|
|
13
|
+
const raw = typeof value === 'string' ? value.trim() : '';
|
|
14
|
+
if (!raw) return '';
|
|
15
|
+
const looksLikeHttpUrl = /^https?:/i.test(raw);
|
|
14
16
|
try {
|
|
15
|
-
const url = new URL(
|
|
17
|
+
const url = new URL(raw);
|
|
16
18
|
if (['http:', 'https:'].includes(url.protocol)) {
|
|
17
19
|
return `${url.protocol}//${url.host}${url.pathname}`.slice(0, MAX_RESOURCE_LENGTH);
|
|
18
20
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
if (looksLikeHttpUrl) return '';
|
|
22
|
+
} catch {
|
|
23
|
+
if (looksLikeHttpUrl) return '';
|
|
24
|
+
}
|
|
25
|
+
return raw.slice(0, MAX_RESOURCE_LENGTH);
|
|
21
26
|
}
|
|
22
27
|
|
|
23
28
|
function normalizeToolEvent(value) {
|
|
24
29
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return null;
|
|
25
30
|
const name = boundedString(value.name, MAX_TOOL_NAME_LENGTH);
|
|
26
31
|
if (!name) return null;
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
const status = ['running', 'error'].includes(value.status) ? value.status : 'completed';
|
|
33
|
+
const event = { name, status };
|
|
34
|
+
const id = boundedString(value.id, MAX_TOOL_ID_LENGTH);
|
|
35
|
+
if (id) event.id = id;
|
|
31
36
|
const resource = safeResource(value.resource);
|
|
32
37
|
if (resource) event.resource = resource;
|
|
38
|
+
const startedAt = Number(value.startedAt);
|
|
39
|
+
if (Number.isFinite(startedAt) && startedAt > 0) event.startedAt = startedAt;
|
|
33
40
|
return event;
|
|
34
41
|
}
|
|
35
42
|
|
|
@@ -48,9 +55,25 @@ export function normalizeActionCheckpoint(value) {
|
|
|
48
55
|
|
|
49
56
|
export function appendCheckpointToolEvent(checkpoint, event) {
|
|
50
57
|
const current = normalizeActionCheckpoint(checkpoint) || { version: 1, toolEvents: [] };
|
|
58
|
+
const normalized = normalizeToolEvent(event);
|
|
59
|
+
if (!normalized) return current;
|
|
60
|
+
const toolEvents = normalized.id
|
|
61
|
+
? current.toolEvents.filter(item => item.id !== normalized.id)
|
|
62
|
+
: current.toolEvents;
|
|
63
|
+
return normalizeActionCheckpoint({
|
|
64
|
+
...current,
|
|
65
|
+
toolEvents: [...toolEvents, normalized],
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function settleCheckpointToolEvents(checkpoint, status = 'error') {
|
|
70
|
+
const current = normalizeActionCheckpoint(checkpoint);
|
|
71
|
+
if (!current?.toolEvents.some(event => event.status === 'running')) return current;
|
|
51
72
|
return normalizeActionCheckpoint({
|
|
52
73
|
...current,
|
|
53
|
-
toolEvents:
|
|
74
|
+
toolEvents: current.toolEvents.map(event => (
|
|
75
|
+
event.status === 'running' ? { ...event, status } : event
|
|
76
|
+
)),
|
|
54
77
|
});
|
|
55
78
|
}
|
|
56
79
|
|
|
@@ -10,6 +10,7 @@ import { runMatchesActionIdentity } from './action-identity.js';
|
|
|
10
10
|
import { normalizeOutputs } from './evidence.js';
|
|
11
11
|
import { taskSpecificActionBrief } from './workflow.js';
|
|
12
12
|
import { buildMainlineProjection } from './mainline-projection.js';
|
|
13
|
+
import { normalizeActionCheckpoint } from './action-checkpoint.js';
|
|
13
14
|
|
|
14
15
|
const MAX_ACTION_MESSAGE_CHARS = 16_000;
|
|
15
16
|
const MAX_ACTION_DIAGNOSTIC_CHARS = 8_000;
|
|
@@ -293,8 +294,13 @@ function normalizeProjectedMessage(message) {
|
|
|
293
294
|
const text = typeof message.text === 'string'
|
|
294
295
|
? message.text.trim().slice(0, MAX_ACTION_MESSAGE_CHARS)
|
|
295
296
|
: '';
|
|
297
|
+
const checkpoint = normalizeActionCheckpoint({ toolEvents: message.toolEvents });
|
|
298
|
+
const toolEvents = (checkpoint?.toolEvents || []).map((event, index) => ({
|
|
299
|
+
...event,
|
|
300
|
+
id: event.id || `${index}`,
|
|
301
|
+
}));
|
|
296
302
|
const attachments = projectAttachments(message.attachments);
|
|
297
|
-
if (!text && attachments.length === 0) return null;
|
|
303
|
+
if (!text && attachments.length === 0 && toolEvents.length === 0) return null;
|
|
298
304
|
const speaker = message.role === 'user' ? null : projectVpSpeaker(message.speaker);
|
|
299
305
|
return {
|
|
300
306
|
id: String(message.id || ''),
|
|
@@ -302,6 +308,7 @@ function normalizeProjectedMessage(message) {
|
|
|
302
308
|
kind: message.kind === 'input' ? 'input' : 'response',
|
|
303
309
|
status: message.status || null,
|
|
304
310
|
text,
|
|
311
|
+
toolEvents,
|
|
305
312
|
attachments,
|
|
306
313
|
...(projectedMessageQuote(message.quote) ? { quote: projectedMessageQuote(message.quote) } : {}),
|
|
307
314
|
createdAt: count(message.createdAt),
|
|
@@ -341,6 +348,7 @@ function runResponseMessage(run) {
|
|
|
341
348
|
kind: 'response',
|
|
342
349
|
status: run.status || 'running',
|
|
343
350
|
text,
|
|
351
|
+
toolEvents: run.checkpoint?.toolEvents,
|
|
344
352
|
createdAt: count(run.endedAt || run.startedAt),
|
|
345
353
|
updatedAt: count(run.endedAt || run.startedAt),
|
|
346
354
|
progressRevision: count(run.progressRevision),
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
import {
|
|
31
31
|
appendCheckpointToolEvent,
|
|
32
32
|
renderActionResumeBlock,
|
|
33
|
+
settleCheckpointToolEvents,
|
|
33
34
|
} from './action-checkpoint.js';
|
|
34
35
|
import { createSkillManager } from '../skills.js';
|
|
35
36
|
import { loadMCPConfig } from '../config.js';
|
|
@@ -1303,6 +1304,7 @@ export class WorkItemRunner {
|
|
|
1303
1304
|
let checkpoint = null;
|
|
1304
1305
|
let terminalEngineError = null;
|
|
1305
1306
|
const toolInputs = new Map();
|
|
1307
|
+
const toolStartedAt = new Map();
|
|
1306
1308
|
const usageStats = {
|
|
1307
1309
|
llmRequestCount: 0,
|
|
1308
1310
|
inputTokens: 0,
|
|
@@ -1318,6 +1320,14 @@ export class WorkItemRunner {
|
|
|
1318
1320
|
...executionStats(),
|
|
1319
1321
|
checkpoint,
|
|
1320
1322
|
});
|
|
1323
|
+
const settleOpenTools = (status = 'error') => {
|
|
1324
|
+
const settled = settleCheckpointToolEvents(checkpoint, status);
|
|
1325
|
+
const changed = settled !== checkpoint;
|
|
1326
|
+
checkpoint = settled;
|
|
1327
|
+
toolInputs.clear();
|
|
1328
|
+
toolStartedAt.clear();
|
|
1329
|
+
return changed;
|
|
1330
|
+
};
|
|
1321
1331
|
const reportProgress = (force = false) => {
|
|
1322
1332
|
if (typeof onProgress !== 'function') return;
|
|
1323
1333
|
const now = Date.now();
|
|
@@ -1472,16 +1482,33 @@ export class WorkItemRunner {
|
|
|
1472
1482
|
response: publicWorkItemResponse(event.response),
|
|
1473
1483
|
});
|
|
1474
1484
|
}
|
|
1475
|
-
else if (event?.type === 'tool_start')
|
|
1485
|
+
else if (event?.type === 'tool_start') {
|
|
1486
|
+
const startedAt = Date.now();
|
|
1487
|
+
toolInputs.set(event.id, event.input);
|
|
1488
|
+
toolStartedAt.set(event.id, startedAt);
|
|
1489
|
+
checkpoint = appendCheckpointToolEvent(checkpoint, {
|
|
1490
|
+
id: event.id,
|
|
1491
|
+
name: event.name,
|
|
1492
|
+
status: 'running',
|
|
1493
|
+
resource: checkpointResource(event.name, event.input, workDir),
|
|
1494
|
+
startedAt,
|
|
1495
|
+
});
|
|
1496
|
+
reportProgress(true);
|
|
1497
|
+
}
|
|
1476
1498
|
else if (event?.type === 'tool_end') {
|
|
1477
1499
|
toolCount += 1;
|
|
1478
1500
|
const input = toolInputs.get(event.id);
|
|
1479
1501
|
toolInputs.delete(event.id);
|
|
1502
|
+
const startedAt = toolStartedAt.get(event.id);
|
|
1503
|
+
toolStartedAt.delete(event.id);
|
|
1480
1504
|
checkpoint = appendCheckpointToolEvent(checkpoint, {
|
|
1505
|
+
id: event.id,
|
|
1481
1506
|
name: event.name,
|
|
1482
1507
|
status: event.isError ? 'error' : 'completed',
|
|
1483
1508
|
resource: checkpointResource(event.name, input, workDir),
|
|
1509
|
+
startedAt,
|
|
1484
1510
|
});
|
|
1511
|
+
reportProgress(true);
|
|
1485
1512
|
}
|
|
1486
1513
|
else if (event?.type === 'error' && !terminalEngineError) {
|
|
1487
1514
|
terminalEngineError = event.error instanceof Error
|
|
@@ -1523,11 +1550,13 @@ export class WorkItemRunner {
|
|
|
1523
1550
|
throw stopped;
|
|
1524
1551
|
}
|
|
1525
1552
|
} catch (error) {
|
|
1553
|
+
if (settleOpenTools('error')) reportProgress(true);
|
|
1526
1554
|
error.workItemExecutionStats = currentProgress();
|
|
1527
1555
|
throw error;
|
|
1528
1556
|
} finally {
|
|
1529
1557
|
try { engine.abort?.('work_item_run_finished'); } catch {}
|
|
1530
1558
|
}
|
|
1559
|
+
settleOpenTools('error');
|
|
1531
1560
|
const response = publicWorkItemResponse(text);
|
|
1532
1561
|
reportProgress(true);
|
|
1533
1562
|
const submittedPlan = !replanToolEnabled ? planCollector.value : null;
|