@yeaft/webchat-agent 1.0.248 → 1.0.249
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 +99 -106
- 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/yeaft/work-center/bridge.js +4 -3
- package/yeaft/work-center/projection.js +142 -115
- package/yeaft/work-center/service.js +14 -0
- package/yeaft/work-center/store.js +1 -0
- package/yeaft/work-center/watcher.js +26 -4
|
Binary file
|
package/package.json
CHANGED
|
@@ -6,7 +6,7 @@ import { scanVpLibrary } from '../vp/vp-store.js';
|
|
|
6
6
|
import { WorkCenterService } from './service.js';
|
|
7
7
|
import { WorkItemRunner } from './runner.js';
|
|
8
8
|
import { WorkItemCoordinator } from './coordinator.js';
|
|
9
|
-
import { projectWorkCenterEvent
|
|
9
|
+
import { projectWorkCenterEvent } from './projection.js';
|
|
10
10
|
import { previewWorkCenterPlan } from './planner.js';
|
|
11
11
|
import { readWorkCenterSettings, writeWorkCenterSettings } from './settings.js';
|
|
12
12
|
import { defaultWorkCenterStageInstructions } from './workflow.js';
|
|
@@ -188,6 +188,7 @@ export async function handleWorkCenterRequest(msg) {
|
|
|
188
188
|
const op = typeof msg.op === 'string' ? msg.op : '';
|
|
189
189
|
try {
|
|
190
190
|
let data;
|
|
191
|
+
let workCenter = null;
|
|
191
192
|
if (op === 'get_settings') {
|
|
192
193
|
data = await readSettingsResponse();
|
|
193
194
|
} else if (op === 'update_settings') {
|
|
@@ -210,13 +211,13 @@ export async function handleWorkCenterRequest(msg) {
|
|
|
210
211
|
await resetYeaftSession();
|
|
211
212
|
data = await readSettingsResponse();
|
|
212
213
|
} else {
|
|
213
|
-
|
|
214
|
+
workCenter = await ensureWorkCenter();
|
|
214
215
|
const payload = Object.hasOwn(BROWSER_FILE_FIELDS, op)
|
|
215
216
|
? browserFilePayload(op, msg.payload)
|
|
216
217
|
: (BROWSER_ACTION_DEBUG_OPS.has(op) ? browserFilePayload(op, msg.payload) : (msg.payload || {}));
|
|
217
218
|
data = await workCenter.handle(op, payload);
|
|
218
219
|
}
|
|
219
|
-
if (BROWSER_DETAIL_OPS.has(op)) data =
|
|
220
|
+
if (BROWSER_DETAIL_OPS.has(op)) data = workCenter.projectBrowserDetail(data);
|
|
220
221
|
send({
|
|
221
222
|
type: 'work_center_response',
|
|
222
223
|
requestId,
|
|
@@ -6,7 +6,7 @@ import {
|
|
|
6
6
|
sanitizeDebugValue,
|
|
7
7
|
sanitizeDiagnosticText,
|
|
8
8
|
} from './debug-projection.js';
|
|
9
|
-
import {
|
|
9
|
+
import { runMatchesActionIdentity } from './action-identity.js';
|
|
10
10
|
import { taskSpecificActionBrief } from './workflow.js';
|
|
11
11
|
import { buildMainlineProjection } from './mainline-projection.js';
|
|
12
12
|
|
|
@@ -175,17 +175,30 @@ function runSpecHash(run) {
|
|
|
175
175
|
: '';
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
function
|
|
179
|
-
const source = (Array.isArray(runs) ? runs : []).filter(run => run?.actionId === action?.id);
|
|
180
|
-
const selectedSpecByGeneration = new Map((Array.isArray(action?.identityHistory) ? action.identityHistory : [])
|
|
181
|
-
.filter(identity => typeof identity?.specHash === 'string' && identity.specHash)
|
|
182
|
-
.map(identity => [actionGeneration(identity.generation), identity.specHash]));
|
|
178
|
+
function conversationIdentitySelection(action) {
|
|
183
179
|
const currentGeneration = actionGeneration(action?.generation);
|
|
180
|
+
const generations = new Set([currentGeneration]);
|
|
181
|
+
const selectedSpecByGeneration = new Map();
|
|
182
|
+
for (const identity of Array.isArray(action?.identityHistory) ? action.identityHistory : []) {
|
|
183
|
+
const generation = Number(identity?.generation);
|
|
184
|
+
if (!Number.isInteger(generation) || generation <= 0 || generation > currentGeneration) continue;
|
|
185
|
+
const specHash = typeof identity?.specHash === 'string' ? identity.specHash : '';
|
|
186
|
+
if (!specHash && generation !== 1) continue;
|
|
187
|
+
generations.add(generation);
|
|
188
|
+
if (specHash) selectedSpecByGeneration.set(generation, specHash);
|
|
189
|
+
}
|
|
184
190
|
if (typeof action?.specHash === 'string' && action.specHash) {
|
|
185
191
|
selectedSpecByGeneration.set(currentGeneration, action.specHash);
|
|
186
192
|
}
|
|
193
|
+
return { generations, selectedSpecByGeneration };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function conversationRuns(action, runs) {
|
|
197
|
+
const source = (Array.isArray(runs) ? runs : []).filter(run => run?.actionId === action?.id);
|
|
198
|
+
const { generations, selectedSpecByGeneration } = conversationIdentitySelection(action);
|
|
187
199
|
return source.filter(run => {
|
|
188
200
|
const generation = runGeneration(run);
|
|
201
|
+
if (!generations.has(generation)) return false;
|
|
189
202
|
const selectedSpec = selectedSpecByGeneration.get(generation) || '';
|
|
190
203
|
const spec = runSpecHash(run);
|
|
191
204
|
return selectedSpec ? spec === selectedSpec : generation === 1 && !spec;
|
|
@@ -231,7 +244,13 @@ function compareProjectedMessages(left, right) {
|
|
|
231
244
|
if (leftEventId != null && rightEventId != null) {
|
|
232
245
|
return compareEventIds(leftEventId, rightEventId);
|
|
233
246
|
}
|
|
234
|
-
|
|
247
|
+
const leftRole = left?.role === 'user' ? 0 : 1;
|
|
248
|
+
const rightRole = right?.role === 'user' ? 0 : 1;
|
|
249
|
+
if (leftRole !== rightRole) return leftRole - rightRole;
|
|
250
|
+
const generationOrder = count(left?.generation) - count(right?.generation);
|
|
251
|
+
if (generationOrder) return generationOrder;
|
|
252
|
+
const attemptOrder = count(left?.attempt) - count(right?.attempt);
|
|
253
|
+
return attemptOrder
|
|
235
254
|
|| String(left?.id || '').localeCompare(String(right?.id || ''));
|
|
236
255
|
}
|
|
237
256
|
|
|
@@ -260,7 +279,7 @@ function normalizeProjectedMessage(message) {
|
|
|
260
279
|
};
|
|
261
280
|
}
|
|
262
281
|
|
|
263
|
-
function actionInputMessages(action, events, generation = actionGeneration(action?.generation)
|
|
282
|
+
function actionInputMessages(action, events, generation = actionGeneration(action?.generation)) {
|
|
264
283
|
return (Array.isArray(events) ? events : [])
|
|
265
284
|
.filter(event => event?.actionId === action?.id
|
|
266
285
|
&& actionGeneration(event.actionGeneration) === generation
|
|
@@ -273,29 +292,30 @@ function actionInputMessages(action, events, generation = actionGeneration(actio
|
|
|
273
292
|
text: event.data?.text || event.data?.guidance || '',
|
|
274
293
|
attachments: event.data?.attachments,
|
|
275
294
|
createdAt: event.createdAt,
|
|
276
|
-
...(includeThreadIdentity ? { generation } : {}),
|
|
277
295
|
}))
|
|
278
296
|
.filter(Boolean);
|
|
279
297
|
}
|
|
280
298
|
|
|
281
|
-
function runResponseMessage(run
|
|
299
|
+
function runResponseMessage(run) {
|
|
300
|
+
const response = typeof run?.response === 'string' ? run.response : '';
|
|
301
|
+
const text = response.trim() || (run?.status === 'failed' ? failedRunMessageText(run) : '');
|
|
282
302
|
return normalizeProjectedMessage({
|
|
283
303
|
id: `run:${run.id}`,
|
|
284
304
|
role: 'assistant',
|
|
285
305
|
kind: 'response',
|
|
286
306
|
status: run.status || 'running',
|
|
287
|
-
text
|
|
288
|
-
createdAt: count(run.startedAt),
|
|
307
|
+
text,
|
|
308
|
+
createdAt: count(run.endedAt || run.startedAt),
|
|
289
309
|
updatedAt: count(run.endedAt || run.startedAt),
|
|
290
310
|
progressRevision: count(run.progressRevision),
|
|
291
|
-
generation:
|
|
292
|
-
attempt:
|
|
311
|
+
generation: runGeneration(run),
|
|
312
|
+
attempt: Math.max(1, count(run.actionAttempt) || 1),
|
|
293
313
|
runId: run.id,
|
|
294
314
|
speaker: run.vpSnapshot,
|
|
295
315
|
});
|
|
296
316
|
}
|
|
297
317
|
|
|
298
|
-
function loopOutputMessages(action, events, matchingRuns, generation = actionGeneration(action?.generation)
|
|
318
|
+
function loopOutputMessages(action, events, matchingRuns, generation = actionGeneration(action?.generation)) {
|
|
299
319
|
const runById = new Map(matchingRuns.map(run => [run.id, run]));
|
|
300
320
|
const projected = [];
|
|
301
321
|
let previousTranscriptEvent = null;
|
|
@@ -320,9 +340,9 @@ function loopOutputMessages(action, events, matchingRuns, generation = actionGen
|
|
|
320
340
|
kind: 'response',
|
|
321
341
|
status: 'completed',
|
|
322
342
|
text: event.data?.response || '',
|
|
323
|
-
createdAt: event.createdAt,
|
|
324
|
-
generation:
|
|
325
|
-
attempt:
|
|
343
|
+
createdAt: count(run.endedAt || event.createdAt),
|
|
344
|
+
generation: runGeneration(run),
|
|
345
|
+
attempt: Math.max(1, count(run.actionAttempt) || 1),
|
|
326
346
|
runId: event.runId,
|
|
327
347
|
speaker: run.vpSnapshot,
|
|
328
348
|
});
|
|
@@ -336,66 +356,44 @@ function loopOutputMessages(action, events, matchingRuns, generation = actionGen
|
|
|
336
356
|
return projected;
|
|
337
357
|
}
|
|
338
358
|
|
|
339
|
-
function messagesForGeneration(action, runs, events, generation
|
|
340
|
-
const matchingRuns =
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
359
|
+
function messagesForGeneration(action, runs, events, generation) {
|
|
360
|
+
const matchingRuns = conversationRuns(action, runs).filter(run => (
|
|
361
|
+
runGeneration(run) === generation && run?.status !== 'running'
|
|
362
|
+
));
|
|
363
|
+
const failedWithoutResponse = new Set(matchingRuns
|
|
364
|
+
.filter(run => run?.status === 'failed' && !String(run?.response || '').trim())
|
|
365
|
+
.map(run => run.id));
|
|
366
|
+
const loopMessages = loopOutputMessages(
|
|
367
|
+
action,
|
|
368
|
+
events,
|
|
369
|
+
matchingRuns.filter(run => !failedWithoutResponse.has(run.id)),
|
|
370
|
+
generation,
|
|
371
|
+
);
|
|
372
|
+
const runsWithLoopOutput = new Set(loopMessages.map(message => message.runId).filter(Boolean));
|
|
348
373
|
return [
|
|
349
|
-
...actionInputMessages(action, events, generation
|
|
350
|
-
...
|
|
374
|
+
...actionInputMessages(action, events, generation),
|
|
375
|
+
...loopMessages,
|
|
351
376
|
...matchingRuns
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
function actionMessages(action, runs, events) {
|
|
360
|
-
return messagesForGeneration(action, runs, events, actionGeneration(action?.generation));
|
|
377
|
+
.sort((left, right) => count(left.startedAt) - count(right.startedAt))
|
|
378
|
+
.filter(run => !runsWithLoopOutput.has(run.id))
|
|
379
|
+
.map(run => runResponseMessage(run))
|
|
380
|
+
.filter(Boolean),
|
|
381
|
+
].sort(compareProjectedMessages);
|
|
361
382
|
}
|
|
362
383
|
|
|
363
|
-
function
|
|
364
|
-
const allRuns =
|
|
365
|
-
const generations =
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
.filter(event => event?.actionId === action?.id
|
|
370
|
-
&& ['action.guidance_added', 'action.input_added', 'run.loop_output'].includes(event.type))
|
|
371
|
-
.map(event => actionGeneration(event.actionGeneration ?? event.data?.actionGeneration)),
|
|
372
|
-
]);
|
|
373
|
-
return [...generations].sort((left, right) => left - right).map(generation => {
|
|
374
|
-
const canonical = generation === actionGeneration(action?.generation);
|
|
375
|
-
return {
|
|
376
|
-
generation,
|
|
377
|
-
canonical,
|
|
378
|
-
messages: canonical ? [] : messagesForGeneration(action, allRuns, events, generation, true).slice(-MAX_ACTION_MESSAGES),
|
|
379
|
-
runs: allRuns.filter(run => runGeneration(run) === generation)
|
|
380
|
-
.sort((left, right) => count(left.startedAt) - count(right.startedAt) || String(left.id).localeCompare(String(right.id)))
|
|
381
|
-
.map(run => ({
|
|
382
|
-
id: run.id,
|
|
383
|
-
attempt: Math.max(1, count(run.actionAttempt) || 1),
|
|
384
|
-
status: run.status || 'running',
|
|
385
|
-
startedAt: count(run.startedAt),
|
|
386
|
-
endedAt: count(run.endedAt),
|
|
387
|
-
progressRevision: count(run.progressRevision),
|
|
388
|
-
loopCount: count(run.loopCount),
|
|
389
|
-
toolCount: count(run.toolCount),
|
|
390
|
-
})),
|
|
391
|
-
};
|
|
392
|
-
}).filter(entry => entry.messages.length > 0 || entry.runs.length > 0 || entry.canonical);
|
|
384
|
+
function actionConversationMessages(action, runs, events) {
|
|
385
|
+
const allRuns = conversationRuns(action, runs);
|
|
386
|
+
const { generations } = conversationIdentitySelection(action);
|
|
387
|
+
return [...generations]
|
|
388
|
+
.flatMap(generation => messagesForGeneration(action, allRuns, events, generation))
|
|
389
|
+
.sort(compareProjectedMessages);
|
|
393
390
|
}
|
|
394
391
|
|
|
395
392
|
|
|
396
393
|
|
|
397
394
|
const MAX_FAILURE_REASON_LENGTH = 2_000;
|
|
398
395
|
const MAX_FAILURE_INSPECTION_LENGTH = 16_000;
|
|
396
|
+
const DEFAULT_FAILED_RUN_MESSAGE = 'The Action failed.';
|
|
399
397
|
const SAFE_FAILURE_FALLBACK = 'The Action failed. Sensitive details were omitted.';
|
|
400
398
|
const CREDENTIAL_ASSIGNMENT_PATTERN = /\b(?:[A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL)|api[_-]?key|access[_-]?token|authorization|password|secret|token)\s*[:=]/i;
|
|
401
399
|
const PROVIDER_TOKEN_PATTERN = /\b(?:sk-(?:proj-)?[A-Za-z0-9_-]{12,}|github_pat_[A-Za-z0-9_]{12,}|gh[pousr]_[A-Za-z0-9_]{12,}|xox[baprs]-[A-Za-z0-9-]{12,}|AKIA[A-Z0-9]{12,})\b/i;
|
|
@@ -429,6 +427,14 @@ function sanitizeFailureDiagnostic(value) {
|
|
|
429
427
|
return sanitizeDiagnosticText(raw, MAX_ACTION_DIAGNOSTIC_CHARS);
|
|
430
428
|
}
|
|
431
429
|
|
|
430
|
+
function failedRunMessageText(run) {
|
|
431
|
+
const diagnostics = [
|
|
432
|
+
sanitizeFailureDiagnostic(run?.summary),
|
|
433
|
+
sanitizeFailureDiagnostic(run?.error),
|
|
434
|
+
].filter(Boolean);
|
|
435
|
+
return [...new Set(diagnostics)].join('\n\n') || DEFAULT_FAILED_RUN_MESSAGE;
|
|
436
|
+
}
|
|
437
|
+
|
|
432
438
|
function sanitizeFailureReason(value) {
|
|
433
439
|
const raw = typeof value === 'string'
|
|
434
440
|
? value.trim().slice(0, MAX_FAILURE_INSPECTION_LENGTH)
|
|
@@ -446,21 +452,25 @@ function sanitizeFailureReason(value) {
|
|
|
446
452
|
|
|
447
453
|
|
|
448
454
|
function actionExecution(action, runs, events, includeBody = true) {
|
|
455
|
+
let conversationMessages = Array.isArray(runs)
|
|
456
|
+
? actionConversationMessages(action, runs, events)
|
|
457
|
+
: [];
|
|
458
|
+
if (conversationMessages.length === 0 && Array.isArray(action?.messages)) {
|
|
459
|
+
conversationMessages = action.messages.map(normalizeProjectedMessage).filter(Boolean);
|
|
460
|
+
}
|
|
461
|
+
const messageCount = Array.isArray(runs)
|
|
462
|
+
? conversationMessages.length
|
|
463
|
+
: count(action?.messageCount) || conversationMessages.length;
|
|
464
|
+
const messages = includeBody ? conversationMessages.slice(-MAX_ACTION_MESSAGES) : [];
|
|
465
|
+
const messageCursor = Array.isArray(runs)
|
|
466
|
+
? (includeBody
|
|
467
|
+
? (messageCount > messages.length ? String(messageCount - messages.length) : null)
|
|
468
|
+
: (messageCount > 0 ? String(messageCount) : null))
|
|
469
|
+
: action?.messageCursor == null ? null : String(action.messageCursor);
|
|
449
470
|
const matchingRuns = Array.isArray(runs)
|
|
450
471
|
? runs.filter(run => run?.actionId === action?.id && runMatchesActionIdentity(run, action))
|
|
451
472
|
: [];
|
|
452
473
|
if (matchingRuns.length === 0) {
|
|
453
|
-
const inputMessageCount = Array.isArray(events) ? events.filter(event => (
|
|
454
|
-
event?.actionId === action?.id
|
|
455
|
-
&& eventMatchesActionGeneration(event, action)
|
|
456
|
-
&& ['action.guidance_added', 'action.input_added'].includes(event.type)
|
|
457
|
-
)).length : 0;
|
|
458
|
-
const messages = includeBody
|
|
459
|
-
? (Array.isArray(action?.messages)
|
|
460
|
-
? action.messages.slice(-MAX_ACTION_MESSAGES).map(normalizeProjectedMessage).filter(Boolean)
|
|
461
|
-
: actionInputMessages(action, events))
|
|
462
|
-
: [];
|
|
463
|
-
const messageCount = count(action?.messageCount) || (includeBody ? messages.length : inputMessageCount);
|
|
464
474
|
return {
|
|
465
475
|
...executionStats(action?.executionStats || action),
|
|
466
476
|
response: includeBody && typeof action?.response === 'string' ? action.response : '',
|
|
@@ -479,11 +489,8 @@ function actionExecution(action, runs, events, includeBody = true) {
|
|
|
479
489
|
messages,
|
|
480
490
|
liveMessage: includeBody ? normalizeProjectedMessage(action?.liveMessage) : null,
|
|
481
491
|
messageCount,
|
|
482
|
-
messageCursor
|
|
483
|
-
? (!includeBody && messageCount > 0 ? String(messageCount) : null)
|
|
484
|
-
: String(action.messageCursor),
|
|
492
|
+
messageCursor,
|
|
485
493
|
failureReason: sanitizeFailureReason(action?.failureReason),
|
|
486
|
-
|
|
487
494
|
};
|
|
488
495
|
}
|
|
489
496
|
const stats = sumExecutionStats(matchingRuns);
|
|
@@ -496,16 +503,6 @@ function actionExecution(action, runs, events, includeBody = true) {
|
|
|
496
503
|
.filter(run => run?.status === 'failed')
|
|
497
504
|
.sort((left, right) => count(right.endedAt || right.startedAt) - count(left.endedAt || left.startedAt))[0]
|
|
498
505
|
: null;
|
|
499
|
-
const inputMessageCount = includeBody
|
|
500
|
-
? 0
|
|
501
|
-
: (Array.isArray(events) ? events : []).filter(event => (
|
|
502
|
-
event?.actionId === action?.id
|
|
503
|
-
&& eventMatchesActionGeneration(event, action)
|
|
504
|
-
&& ['action.guidance_added', 'action.input_added'].includes(event.type)
|
|
505
|
-
)).length;
|
|
506
|
-
const allMessages = includeBody ? actionMessages(action, matchingRuns, events) : [];
|
|
507
|
-
const totalMessageCount = includeBody ? allMessages.length : matchingRuns.length + inputMessageCount;
|
|
508
|
-
const messages = allMessages.slice(-MAX_ACTION_MESSAGES);
|
|
509
506
|
const liveMessage = includeBody ? runResponseMessage(latest) : null;
|
|
510
507
|
return {
|
|
511
508
|
...stats,
|
|
@@ -520,14 +517,11 @@ function actionExecution(action, runs, events, includeBody = true) {
|
|
|
520
517
|
failedAt: count(latestFailure.endedAt || latestFailure.startedAt),
|
|
521
518
|
} : null,
|
|
522
519
|
failureReason: sanitizeFailureReason(latestFailure?.error),
|
|
523
|
-
|
|
524
520
|
progressRevision: count(latest?.progressRevision),
|
|
525
521
|
messages,
|
|
526
522
|
liveMessage,
|
|
527
|
-
messageCount
|
|
528
|
-
messageCursor
|
|
529
|
-
? (totalMessageCount > messages.length ? String(totalMessageCount - messages.length) : null)
|
|
530
|
-
: (totalMessageCount > 0 ? String(totalMessageCount) : null),
|
|
523
|
+
messageCount,
|
|
524
|
+
messageCursor,
|
|
531
525
|
};
|
|
532
526
|
}
|
|
533
527
|
|
|
@@ -607,7 +601,6 @@ function projectAction(action, runs, events, includeBody = true) {
|
|
|
607
601
|
response: execution.response,
|
|
608
602
|
failure: execution.failure,
|
|
609
603
|
messages: execution.messages,
|
|
610
|
-
thread: Array.isArray(runs) ? projectActionThread(action, runs, events) : (action.thread || []),
|
|
611
604
|
liveMessage: execution.liveMessage,
|
|
612
605
|
} : {}),
|
|
613
606
|
};
|
|
@@ -641,10 +634,14 @@ function stripActionBody(action, keepFailure = false) {
|
|
|
641
634
|
return projected;
|
|
642
635
|
}
|
|
643
636
|
|
|
644
|
-
function
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
637
|
+
function canonicalActions(detail) {
|
|
638
|
+
return Array.isArray(detail?.actions)
|
|
639
|
+
? detail.actions.filter(action => !['superseded', 'cancelled'].includes(action?.status))
|
|
640
|
+
: [];
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function projectActionStats(detail, liveActionId = bodyActionId(detail)) {
|
|
644
|
+
return canonicalActions(detail).map(action => {
|
|
648
645
|
const projected = projectAction(
|
|
649
646
|
action,
|
|
650
647
|
detail.runs,
|
|
@@ -661,6 +658,7 @@ function projectActionStats(detail) {
|
|
|
661
658
|
loopCount: projected.loopCount,
|
|
662
659
|
toolCount: projected.toolCount,
|
|
663
660
|
progressRevision: projected.progressRevision,
|
|
661
|
+
attempt: Math.max(0, count(action?.attempt)),
|
|
664
662
|
};
|
|
665
663
|
if (projected.failureReason) stats.failureReason = projected.failureReason;
|
|
666
664
|
if (projected.id === liveActionId) {
|
|
@@ -676,12 +674,24 @@ function enforceWorkItemBrowserDtoBudget(value, options = {}) {
|
|
|
676
674
|
if (!value || jsonByteLength(value) <= MAX_WORK_ITEM_BROWSER_DTO_BYTES) return value;
|
|
677
675
|
const dto = value;
|
|
678
676
|
const workItem = options.event === true ? dto.workItem : dto;
|
|
679
|
-
|
|
677
|
+
let actions = Array.isArray(workItem?.actions)
|
|
680
678
|
? workItem.actions
|
|
681
679
|
: Array.isArray(workItem?.actionStats) ? workItem.actionStats : [];
|
|
682
680
|
const keepId = options.keepActionId || workItem?.currentActionId || actions.at(-1)?.id || null;
|
|
683
681
|
workItem.truncated = true;
|
|
684
682
|
|
|
683
|
+
if (!Array.isArray(workItem.actions) && Array.isArray(workItem.actionStats)) {
|
|
684
|
+
workItem.actionStats = actions.map(action => ({
|
|
685
|
+
id: action?.id,
|
|
686
|
+
generation: actionGeneration(action?.generation),
|
|
687
|
+
status: action?.status,
|
|
688
|
+
progressRevision: count(action?.progressRevision),
|
|
689
|
+
attempt: Math.max(0, count(action?.attempt)),
|
|
690
|
+
}));
|
|
691
|
+
actions = workItem.actionStats;
|
|
692
|
+
if (jsonByteLength(dto) <= MAX_WORK_ITEM_BROWSER_DTO_BYTES) return dto;
|
|
693
|
+
}
|
|
694
|
+
|
|
685
695
|
for (let index = 0; index < actions.length; index += 1) {
|
|
686
696
|
if (actions[index]?.id === keepId) continue;
|
|
687
697
|
actions[index] = stripActionBody(actions[index]);
|
|
@@ -838,9 +848,12 @@ function workItemFailureReason(detail) {
|
|
|
838
848
|
* Authenticated browser detail DTO. Raw execution records stay Agent-local;
|
|
839
849
|
* the browser receives only aggregate execution stats plus the explicit user-facing response.
|
|
840
850
|
*/
|
|
841
|
-
export function projectWorkItemDetail(detail) {
|
|
851
|
+
export function projectWorkItemDetail(detail, options = {}) {
|
|
842
852
|
if (!detail) return null;
|
|
843
853
|
const liveActionId = bodyActionId(detail);
|
|
854
|
+
const bodyActionEvents = Array.isArray(options.bodyActionEvents)
|
|
855
|
+
? options.bodyActionEvents
|
|
856
|
+
: detail.events;
|
|
844
857
|
const mainline = projectMainlineBrowser(detail);
|
|
845
858
|
const mainlineActionById = new Map((mainline?.actions || []).map(action => [action.id, action]));
|
|
846
859
|
const projected = {
|
|
@@ -899,7 +912,12 @@ export function projectWorkItemDetail(detail) {
|
|
|
899
912
|
: String(detail.actionSummary || ''),
|
|
900
913
|
actions: Array.isArray(detail.actions)
|
|
901
914
|
? detail.actions.map(action => ({
|
|
902
|
-
...projectAction(
|
|
915
|
+
...projectAction(
|
|
916
|
+
action,
|
|
917
|
+
detail.runs,
|
|
918
|
+
action?.id === liveActionId ? bodyActionEvents : detail.events,
|
|
919
|
+
action?.id === liveActionId,
|
|
920
|
+
),
|
|
903
921
|
...(mainlineActionById.get(action.id) || {}),
|
|
904
922
|
}))
|
|
905
923
|
: [],
|
|
@@ -914,7 +932,7 @@ export function projectWorkItemDetail(detail) {
|
|
|
914
932
|
export function projectWorkItemSummary(detail) {
|
|
915
933
|
if (!detail) return null;
|
|
916
934
|
if (!Array.isArray(detail.actions)) {
|
|
917
|
-
return {
|
|
935
|
+
return enforceWorkItemBrowserDtoBudget({
|
|
918
936
|
id: detail.id,
|
|
919
937
|
revision: detail.revision,
|
|
920
938
|
planRevision: count(detail.planRevision),
|
|
@@ -931,6 +949,8 @@ export function projectWorkItemSummary(detail) {
|
|
|
931
949
|
attentionActionIds: Array.isArray(detail.attentionActionIds) ? detail.attentionActionIds : undefined,
|
|
932
950
|
currentActionId: detail.currentActionId || null,
|
|
933
951
|
currentAction: projectCurrentActionSummary(detail.currentAction),
|
|
952
|
+
actionStats: Array.isArray(detail.actionStats)
|
|
953
|
+
? detail.actionStats.map(action => ({ ...action })) : [],
|
|
934
954
|
actionCount: count(detail.actionCount),
|
|
935
955
|
completedActionCount: count(detail.completedActionCount),
|
|
936
956
|
executionStats: executionStats(detail.executionStats),
|
|
@@ -944,11 +964,11 @@ export function projectWorkItemSummary(detail) {
|
|
|
944
964
|
attentionAction: detail.attentionAction || null,
|
|
945
965
|
activeAction: detail.activeAction || null,
|
|
946
966
|
executors: Array.isArray(detail.executors) ? detail.executors : [],
|
|
947
|
-
};
|
|
967
|
+
}, { keepActionId: detail.currentActionId || null });
|
|
948
968
|
}
|
|
949
969
|
const action = currentAction(detail);
|
|
950
970
|
const projectedAction = action ? projectAction(action, detail.runs, detail.events, false) : null;
|
|
951
|
-
return {
|
|
971
|
+
return enforceWorkItemBrowserDtoBudget({
|
|
952
972
|
id: detail.id,
|
|
953
973
|
revision: detail.revision,
|
|
954
974
|
planRevision: count(detail.planRevision),
|
|
@@ -973,13 +993,14 @@ export function projectWorkItemSummary(detail) {
|
|
|
973
993
|
failureReason: workItemFailureReason(detail),
|
|
974
994
|
|
|
975
995
|
currentAction: projectCurrentActionSummary(action, projectedAction),
|
|
996
|
+
actionStats: projectActionStats(detail, null),
|
|
976
997
|
origin: detail.origin?.sessionId ? { sessionId: detail.origin.sessionId } : null,
|
|
977
998
|
linkedSessionIds: Array.isArray(detail.linkedSessionIds) ? detail.linkedSessionIds : [],
|
|
978
999
|
attachmentCount: Array.isArray(detail.attachments) ? detail.attachments.length : 0,
|
|
979
1000
|
createdAt: detail.createdAt,
|
|
980
1001
|
updatedAt: detail.updatedAt,
|
|
981
1002
|
...boardFields(detail),
|
|
982
|
-
};
|
|
1003
|
+
}, { keepActionId: action?.id || null });
|
|
983
1004
|
}
|
|
984
1005
|
|
|
985
1006
|
export function projectWorkCenterEvent(event) {
|
|
@@ -993,12 +1014,18 @@ export function projectWorkCenterEvent(event) {
|
|
|
993
1014
|
},
|
|
994
1015
|
};
|
|
995
1016
|
}
|
|
996
|
-
const
|
|
1017
|
+
const eventActionId = typeof event?.actionId === 'string'
|
|
1018
|
+
&& event.workItem?.actions?.some(action => action?.id === event.actionId)
|
|
1019
|
+
? event.actionId
|
|
1020
|
+
: null;
|
|
1021
|
+
const liveActionId = eventActionId || bodyActionId(event?.workItem);
|
|
997
1022
|
return enforceWorkItemBrowserDtoBudget({
|
|
998
1023
|
type,
|
|
1024
|
+
...(eventActionId ? { actionId: eventActionId } : {}),
|
|
1025
|
+
...(typeof event?.runId === 'string' && event.runId ? { runId: event.runId } : {}),
|
|
999
1026
|
workItem: {
|
|
1000
1027
|
...projectWorkItemSummary(event?.workItem),
|
|
1001
|
-
actionStats: projectActionStats(event?.workItem),
|
|
1028
|
+
actionStats: projectActionStats(event?.workItem, liveActionId),
|
|
1002
1029
|
},
|
|
1003
1030
|
}, { event: true, keepActionId: liveActionId });
|
|
1004
1031
|
}
|
|
@@ -1015,7 +1042,7 @@ function projectDebugUsage(value) {
|
|
|
1015
1042
|
}
|
|
1016
1043
|
|
|
1017
1044
|
export function projectActionMessagePage(action, runs, events, options = {}) {
|
|
1018
|
-
const messages =
|
|
1045
|
+
const messages = actionConversationMessages(action, runs, events);
|
|
1019
1046
|
const requestedCursor = options.cursor == null ? messages.length : Number(options.cursor);
|
|
1020
1047
|
const end = Number.isFinite(requestedCursor)
|
|
1021
1048
|
? Math.max(0, Math.min(messages.length, Math.floor(requestedCursor)))
|
|
@@ -1032,12 +1059,12 @@ export function projectActionMessagePage(action, runs, events, options = {}) {
|
|
|
1032
1059
|
}
|
|
1033
1060
|
|
|
1034
1061
|
export function actionThreadIncludesRun(action, runs, runId) {
|
|
1035
|
-
return
|
|
1062
|
+
return conversationRuns(action, runs).some(run => run.id === runId);
|
|
1036
1063
|
}
|
|
1037
1064
|
|
|
1038
1065
|
export function projectActionRequestIndex(action, entries) {
|
|
1039
1066
|
const source = Array.isArray(entries) ? entries : [];
|
|
1040
|
-
const allowedRunIds = new Set(
|
|
1067
|
+
const allowedRunIds = new Set(conversationRuns(action, source.map(({ run }) => run)).map(run => run.id));
|
|
1041
1068
|
return {
|
|
1042
1069
|
actionId: action.id,
|
|
1043
1070
|
generation: Math.max(1, count(action.generation) || 1),
|
|
@@ -120,6 +120,20 @@ export class WorkCenterService {
|
|
|
120
120
|
this.store.recoverInterruptedRuns(this.ownerBootId);
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
projectBrowserDetail(detail) {
|
|
124
|
+
if (!detail) return null;
|
|
125
|
+
const actions = Array.isArray(detail.actions) ? detail.actions : [];
|
|
126
|
+
const actionId = detail.currentActionId && actions.some(action => action?.id === detail.currentActionId)
|
|
127
|
+
? detail.currentActionId
|
|
128
|
+
: [...actions].sort((left, right) => (
|
|
129
|
+
Number(right?.sequence || 0) - Number(left?.sequence || 0)
|
|
130
|
+
|| String(right?.id || '').localeCompare(String(left?.id || ''))
|
|
131
|
+
))[0]?.id || null;
|
|
132
|
+
return projectWorkItemDetail(detail, {
|
|
133
|
+
bodyActionEvents: actionId ? this.store.listActionEvents(actionId) : detail.events,
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
123
137
|
async handle(op, payload = {}, requestContext = {}) {
|
|
124
138
|
switch (op) {
|
|
125
139
|
case 'list': {
|
|
@@ -2081,6 +2081,7 @@ export class WorkItemStore {
|
|
|
2081
2081
|
const ids = workItems.map(item => item.id);
|
|
2082
2082
|
const actionsByWorkItem = new Map(ids.map(id => [id, []]));
|
|
2083
2083
|
for (const row of this.db.prepare(`SELECT * FROM actions WHERE work_item_id IN (${placeholders})
|
|
2084
|
+
AND status NOT IN ('superseded', 'cancelled')
|
|
2084
2085
|
ORDER BY work_item_id, sequence`).all(...ids)) {
|
|
2085
2086
|
actionsByWorkItem.get(row.work_item_id).push(mapAction(row));
|
|
2086
2087
|
}
|
|
@@ -166,7 +166,12 @@ export class WorkItemWatcher {
|
|
|
166
166
|
response: '', summary: '', evidence: [],
|
|
167
167
|
error: error?.message || String(error),
|
|
168
168
|
});
|
|
169
|
-
this.onEvent({
|
|
169
|
+
this.onEvent({
|
|
170
|
+
type: 'run.finished',
|
|
171
|
+
actionId: claim.action.id,
|
|
172
|
+
runId: claim.run.id,
|
|
173
|
+
workItem: this.store.getWorkItemDetail(claim.workItem.id),
|
|
174
|
+
});
|
|
170
175
|
continue;
|
|
171
176
|
}
|
|
172
177
|
this.#startClaim(claim);
|
|
@@ -212,7 +217,12 @@ export class WorkItemWatcher {
|
|
|
212
217
|
if (this.lifecycle === 'running') queueMicrotask(() => { this.tick().catch(() => {}); });
|
|
213
218
|
});
|
|
214
219
|
this.activeRuns.set(key, entry);
|
|
215
|
-
this.onEvent({
|
|
220
|
+
this.onEvent({
|
|
221
|
+
type: 'run.started',
|
|
222
|
+
actionId: claim.action.id,
|
|
223
|
+
runId: claim.run.id,
|
|
224
|
+
workItem: this.store.getWorkItemDetail(claim.workItem.id),
|
|
225
|
+
});
|
|
216
226
|
}
|
|
217
227
|
|
|
218
228
|
async #execute(claim, signal, registerProgressReader, registerInputWake) {
|
|
@@ -232,7 +242,14 @@ export class WorkItemWatcher {
|
|
|
232
242
|
claim.run.leaseEpoch,
|
|
233
243
|
progress,
|
|
234
244
|
);
|
|
235
|
-
if (detail)
|
|
245
|
+
if (detail) {
|
|
246
|
+
this.onEvent({
|
|
247
|
+
type: 'run.progress',
|
|
248
|
+
actionId: claim.action.id,
|
|
249
|
+
runId: claim.run.id,
|
|
250
|
+
workItem: detail,
|
|
251
|
+
});
|
|
252
|
+
}
|
|
236
253
|
return !!detail;
|
|
237
254
|
},
|
|
238
255
|
});
|
|
@@ -265,7 +282,12 @@ export class WorkItemWatcher {
|
|
|
265
282
|
claim.run.leaseEpoch,
|
|
266
283
|
result,
|
|
267
284
|
);
|
|
268
|
-
this.onEvent({
|
|
285
|
+
this.onEvent({
|
|
286
|
+
type: 'run.finished',
|
|
287
|
+
actionId: claim.action.id,
|
|
288
|
+
runId: claim.run.id,
|
|
289
|
+
workItem,
|
|
290
|
+
});
|
|
269
291
|
} catch (err) {
|
|
270
292
|
if (!/stale|cancelled|already finished/i.test(err?.message || '')) throw err;
|
|
271
293
|
}
|