@yeaft/webchat-agent 1.0.247 → 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 +144 -114
- 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/action-identity.js +23 -0
- package/yeaft/work-center/bridge.js +21 -5
- package/yeaft/work-center/completion-contract.js +25 -3
- package/yeaft/work-center/controller.js +52 -75
- package/yeaft/work-center/coordinator.js +323 -0
- package/yeaft/work-center/mainline-projection.js +86 -36
- package/yeaft/work-center/plan-mutation.js +99 -2
- package/yeaft/work-center/projection.js +165 -115
- package/yeaft/work-center/runner.js +1 -1
- package/yeaft/work-center/service.js +33 -5
- package/yeaft/work-center/store.js +1161 -147
- package/yeaft/work-center/watcher.js +26 -4
- package/yeaft/work-center/workflow.js +75 -5
|
@@ -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
|
-
|
|
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);
|
|
357
382
|
}
|
|
358
383
|
|
|
359
|
-
function
|
|
360
|
-
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
const generations = new Set([
|
|
366
|
-
actionGeneration(action?.generation),
|
|
367
|
-
...allRuns.map(runGeneration),
|
|
368
|
-
...(Array.isArray(events) ? events : [])
|
|
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]);
|
|
@@ -732,6 +742,7 @@ function enforceWorkItemBrowserDtoBudget(value, options = {}) {
|
|
|
732
742
|
omittedActionCount: originalCount,
|
|
733
743
|
createdAt: count(workItem.createdAt),
|
|
734
744
|
updatedAt: count(workItem.updatedAt),
|
|
745
|
+
coordinatorRevision: count(workItem.coordinatorRevision),
|
|
735
746
|
};
|
|
736
747
|
if (options.event === true) dto.workItem = minimalWorkItem;
|
|
737
748
|
else return minimalWorkItem;
|
|
@@ -837,14 +848,20 @@ function workItemFailureReason(detail) {
|
|
|
837
848
|
* Authenticated browser detail DTO. Raw execution records stay Agent-local;
|
|
838
849
|
* the browser receives only aggregate execution stats plus the explicit user-facing response.
|
|
839
850
|
*/
|
|
840
|
-
export function projectWorkItemDetail(detail) {
|
|
851
|
+
export function projectWorkItemDetail(detail, options = {}) {
|
|
841
852
|
if (!detail) return null;
|
|
842
853
|
const liveActionId = bodyActionId(detail);
|
|
854
|
+
const bodyActionEvents = Array.isArray(options.bodyActionEvents)
|
|
855
|
+
? options.bodyActionEvents
|
|
856
|
+
: detail.events;
|
|
843
857
|
const mainline = projectMainlineBrowser(detail);
|
|
844
858
|
const mainlineActionById = new Map((mainline?.actions || []).map(action => [action.id, action]));
|
|
845
859
|
const projected = {
|
|
846
860
|
id: detail.id,
|
|
847
861
|
revision: detail.revision,
|
|
862
|
+
planRevision: count(detail.planRevision),
|
|
863
|
+
ledgerRevision: count(detail.ledgerRevision),
|
|
864
|
+
coordinatorRevision: count(detail.coordinatorRevision),
|
|
848
865
|
title: detail.title,
|
|
849
866
|
goal: detail.goal,
|
|
850
867
|
acceptanceCriteria: Array.isArray(detail.acceptanceCriteria) ? detail.acceptanceCriteria : [],
|
|
@@ -870,8 +887,21 @@ export function projectWorkItemDetail(detail) {
|
|
|
870
887
|
linkedSessionIds: Array.isArray(detail.linkedSessionIds) ? detail.linkedSessionIds : [],
|
|
871
888
|
messages: (Array.isArray(detail.messages) ? detail.messages : []).slice(-100).map(message => ({
|
|
872
889
|
id: String(message.id || ''),
|
|
890
|
+
turnId: String(message.turnId || message.id || ''),
|
|
891
|
+
role: message.role === 'assistant' ? 'assistant' : message.role === 'legacy_instruction' ? 'legacy_instruction' : 'user',
|
|
873
892
|
text: truncateUtf8(message.text || '', MAX_ACTION_MESSAGE_CHARS),
|
|
893
|
+
status: ['thinking', 'completed', 'failed'].includes(message.status) ? message.status : 'completed',
|
|
894
|
+
error: truncateUtf8(message.error || '', MAX_ACTION_DIAGNOSTIC_CHARS) || null,
|
|
895
|
+
decision: message.decision && typeof message.decision === 'object' ? {
|
|
896
|
+
kind: ['answer', 'guide_actions', 'replan'].includes(message.decision.kind)
|
|
897
|
+
? message.decision.kind : null,
|
|
898
|
+
reason: truncateUtf8(message.decision.reason || '', MAX_ACTION_DIAGNOSTIC_CHARS),
|
|
899
|
+
changedContract: message.decision.changedContract === true,
|
|
900
|
+
affectedActionIds: Array.isArray(message.decision.affectedActionIds)
|
|
901
|
+
? message.decision.affectedActionIds.map(id => String(id)).slice(0, 8) : [],
|
|
902
|
+
} : null,
|
|
874
903
|
createdAt: count(message.createdAt),
|
|
904
|
+
updatedAt: count(message.updatedAt || message.createdAt),
|
|
875
905
|
})),
|
|
876
906
|
attachments: projectAttachments(detail.attachments),
|
|
877
907
|
createdAt: detail.createdAt,
|
|
@@ -882,7 +912,12 @@ export function projectWorkItemDetail(detail) {
|
|
|
882
912
|
: String(detail.actionSummary || ''),
|
|
883
913
|
actions: Array.isArray(detail.actions)
|
|
884
914
|
? detail.actions.map(action => ({
|
|
885
|
-
...projectAction(
|
|
915
|
+
...projectAction(
|
|
916
|
+
action,
|
|
917
|
+
detail.runs,
|
|
918
|
+
action?.id === liveActionId ? bodyActionEvents : detail.events,
|
|
919
|
+
action?.id === liveActionId,
|
|
920
|
+
),
|
|
886
921
|
...(mainlineActionById.get(action.id) || {}),
|
|
887
922
|
}))
|
|
888
923
|
: [],
|
|
@@ -897,9 +932,12 @@ export function projectWorkItemDetail(detail) {
|
|
|
897
932
|
export function projectWorkItemSummary(detail) {
|
|
898
933
|
if (!detail) return null;
|
|
899
934
|
if (!Array.isArray(detail.actions)) {
|
|
900
|
-
return {
|
|
935
|
+
return enforceWorkItemBrowserDtoBudget({
|
|
901
936
|
id: detail.id,
|
|
902
937
|
revision: detail.revision,
|
|
938
|
+
planRevision: count(detail.planRevision),
|
|
939
|
+
ledgerRevision: count(detail.ledgerRevision),
|
|
940
|
+
coordinatorRevision: count(detail.coordinatorRevision),
|
|
903
941
|
title: detail.title,
|
|
904
942
|
goal: detail.goal,
|
|
905
943
|
workItemType: detail.workflowSnapshot?.workItemType || detail.workItemType || null,
|
|
@@ -911,6 +949,8 @@ export function projectWorkItemSummary(detail) {
|
|
|
911
949
|
attentionActionIds: Array.isArray(detail.attentionActionIds) ? detail.attentionActionIds : undefined,
|
|
912
950
|
currentActionId: detail.currentActionId || null,
|
|
913
951
|
currentAction: projectCurrentActionSummary(detail.currentAction),
|
|
952
|
+
actionStats: Array.isArray(detail.actionStats)
|
|
953
|
+
? detail.actionStats.map(action => ({ ...action })) : [],
|
|
914
954
|
actionCount: count(detail.actionCount),
|
|
915
955
|
completedActionCount: count(detail.completedActionCount),
|
|
916
956
|
executionStats: executionStats(detail.executionStats),
|
|
@@ -924,13 +964,16 @@ export function projectWorkItemSummary(detail) {
|
|
|
924
964
|
attentionAction: detail.attentionAction || null,
|
|
925
965
|
activeAction: detail.activeAction || null,
|
|
926
966
|
executors: Array.isArray(detail.executors) ? detail.executors : [],
|
|
927
|
-
};
|
|
967
|
+
}, { keepActionId: detail.currentActionId || null });
|
|
928
968
|
}
|
|
929
969
|
const action = currentAction(detail);
|
|
930
970
|
const projectedAction = action ? projectAction(action, detail.runs, detail.events, false) : null;
|
|
931
|
-
return {
|
|
971
|
+
return enforceWorkItemBrowserDtoBudget({
|
|
932
972
|
id: detail.id,
|
|
933
973
|
revision: detail.revision,
|
|
974
|
+
planRevision: count(detail.planRevision),
|
|
975
|
+
ledgerRevision: count(detail.ledgerRevision),
|
|
976
|
+
coordinatorRevision: count(detail.coordinatorRevision),
|
|
934
977
|
title: detail.title,
|
|
935
978
|
goal: detail.goal,
|
|
936
979
|
workItemType: detail.workflowSnapshot?.workItemType || detail.workItemType || null,
|
|
@@ -950,13 +993,14 @@ export function projectWorkItemSummary(detail) {
|
|
|
950
993
|
failureReason: workItemFailureReason(detail),
|
|
951
994
|
|
|
952
995
|
currentAction: projectCurrentActionSummary(action, projectedAction),
|
|
996
|
+
actionStats: projectActionStats(detail, null),
|
|
953
997
|
origin: detail.origin?.sessionId ? { sessionId: detail.origin.sessionId } : null,
|
|
954
998
|
linkedSessionIds: Array.isArray(detail.linkedSessionIds) ? detail.linkedSessionIds : [],
|
|
955
999
|
attachmentCount: Array.isArray(detail.attachments) ? detail.attachments.length : 0,
|
|
956
1000
|
createdAt: detail.createdAt,
|
|
957
1001
|
updatedAt: detail.updatedAt,
|
|
958
1002
|
...boardFields(detail),
|
|
959
|
-
};
|
|
1003
|
+
}, { keepActionId: action?.id || null });
|
|
960
1004
|
}
|
|
961
1005
|
|
|
962
1006
|
export function projectWorkCenterEvent(event) {
|
|
@@ -970,12 +1014,18 @@ export function projectWorkCenterEvent(event) {
|
|
|
970
1014
|
},
|
|
971
1015
|
};
|
|
972
1016
|
}
|
|
973
|
-
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);
|
|
974
1022
|
return enforceWorkItemBrowserDtoBudget({
|
|
975
1023
|
type,
|
|
1024
|
+
...(eventActionId ? { actionId: eventActionId } : {}),
|
|
1025
|
+
...(typeof event?.runId === 'string' && event.runId ? { runId: event.runId } : {}),
|
|
976
1026
|
workItem: {
|
|
977
1027
|
...projectWorkItemSummary(event?.workItem),
|
|
978
|
-
actionStats: projectActionStats(event?.workItem),
|
|
1028
|
+
actionStats: projectActionStats(event?.workItem, liveActionId),
|
|
979
1029
|
},
|
|
980
1030
|
}, { event: true, keepActionId: liveActionId });
|
|
981
1031
|
}
|
|
@@ -992,7 +1042,7 @@ function projectDebugUsage(value) {
|
|
|
992
1042
|
}
|
|
993
1043
|
|
|
994
1044
|
export function projectActionMessagePage(action, runs, events, options = {}) {
|
|
995
|
-
const messages =
|
|
1045
|
+
const messages = actionConversationMessages(action, runs, events);
|
|
996
1046
|
const requestedCursor = options.cursor == null ? messages.length : Number(options.cursor);
|
|
997
1047
|
const end = Number.isFinite(requestedCursor)
|
|
998
1048
|
? Math.max(0, Math.min(messages.length, Math.floor(requestedCursor)))
|
|
@@ -1009,12 +1059,12 @@ export function projectActionMessagePage(action, runs, events, options = {}) {
|
|
|
1009
1059
|
}
|
|
1010
1060
|
|
|
1011
1061
|
export function actionThreadIncludesRun(action, runs, runId) {
|
|
1012
|
-
return
|
|
1062
|
+
return conversationRuns(action, runs).some(run => run.id === runId);
|
|
1013
1063
|
}
|
|
1014
1064
|
|
|
1015
1065
|
export function projectActionRequestIndex(action, entries) {
|
|
1016
1066
|
const source = Array.isArray(entries) ? entries : [];
|
|
1017
|
-
const allowedRunIds = new Set(
|
|
1067
|
+
const allowedRunIds = new Set(conversationRuns(action, source.map(({ run }) => run)).map(run => run.id));
|
|
1018
1068
|
return {
|
|
1019
1069
|
actionId: action.id,
|
|
1020
1070
|
generation: Math.max(1, count(action.generation) || 1),
|
|
@@ -578,7 +578,7 @@ function completionContract(action, workItem) {
|
|
|
578
578
|
"acceptanceChecks": ${JSON.stringify(acceptanceChecks)},
|
|
579
579
|
"waitingReason": null,
|
|
580
580
|
"error": null${reviewField}${triageField}${planField}
|
|
581
|
-
}\nFor completed, provide at least one concrete evidence item and exactly one acceptanceChecks entry for every current acceptance criterion, in the same order, with status passed, deferred, or not_applicable and a non-empty evidence reference. Triage must use its proposed criteria when submitting a contractPatch.
|
|
581
|
+
}\nFor completed, provide at least one concrete evidence item and exactly one acceptanceChecks entry for every current acceptance criterion, in the same order, with status passed, deferred, or not_applicable and a non-empty evidence reference. Triage must use its proposed criteria when submitting a contractPatch. An intermediate Action may defer criteria outside its task-specific expected result; the final deliver Action, and an approved review with no downstream work, require every criterion to pass. If a criterion is no longer applicable, ask the WorkItem Coordinator to revise the contract instead of pretending it passed. This is a deterministic submission gate, not independent proof: later verification and delivery Actions must verify the claims. A model turn ending is not completion. Use waiting when user or external input is required. Use retryable only for a transient failure. Do not start background jobs or delegate this Action.`;
|
|
582
582
|
}
|
|
583
583
|
|
|
584
584
|
function safeCheckpointUrl(value) {
|
|
@@ -105,6 +105,7 @@ export class WorkCenterService {
|
|
|
105
105
|
this.controller = options.controller || new WorkflowController(this.store, {
|
|
106
106
|
listAvailableVpIds: options.listAvailableVpIds,
|
|
107
107
|
});
|
|
108
|
+
this.coordinator = options.coordinator || null;
|
|
108
109
|
this.onEvent = typeof options.onEvent === 'function' ? options.onEvent : () => {};
|
|
109
110
|
this.watcher = new WorkItemWatcher({
|
|
110
111
|
store: this.store,
|
|
@@ -119,6 +120,20 @@ export class WorkCenterService {
|
|
|
119
120
|
this.store.recoverInterruptedRuns(this.ownerBootId);
|
|
120
121
|
}
|
|
121
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
|
+
|
|
122
137
|
async handle(op, payload = {}, requestContext = {}) {
|
|
123
138
|
switch (op) {
|
|
124
139
|
case 'list': {
|
|
@@ -271,14 +286,22 @@ export class WorkCenterService {
|
|
|
271
286
|
return { id, deleted: true, cleanupWarning };
|
|
272
287
|
}
|
|
273
288
|
case 'work_item_message': {
|
|
289
|
+
if (!this.coordinator) throw new Error('Work Center Coordinator is unavailable');
|
|
274
290
|
const id = requiredString(payload.id, 'id');
|
|
275
|
-
const
|
|
291
|
+
const turn = this.coordinator.message(id, {
|
|
276
292
|
text: typeof payload.text === 'string' ? payload.text : '',
|
|
277
293
|
revision: payload.revision,
|
|
294
|
+
planRevision: payload.planRevision,
|
|
295
|
+
ledgerRevision: payload.ledgerRevision,
|
|
296
|
+
coordinatorRevision: payload.coordinatorRevision,
|
|
297
|
+
}, {
|
|
298
|
+
onUpdate: (type, workItem) => {
|
|
299
|
+
this.watcher.abortInvalidWorkItemRuns(id);
|
|
300
|
+
this.#emit({ type, workItem });
|
|
301
|
+
},
|
|
278
302
|
});
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
return detail;
|
|
303
|
+
turn.task.catch(() => {});
|
|
304
|
+
return { accepted: true, turnId: turn.detail.messages?.at(-1)?.turnId || null };
|
|
282
305
|
}
|
|
283
306
|
case 'retry_action': {
|
|
284
307
|
const id = requiredString(payload.id, 'id');
|
|
@@ -296,6 +319,10 @@ export class WorkCenterService {
|
|
|
296
319
|
}
|
|
297
320
|
case 'action_input': {
|
|
298
321
|
const id = requiredString(payload.id, 'id');
|
|
322
|
+
const generation = Number(payload.generation);
|
|
323
|
+
if (!Number.isInteger(generation) || generation < 1) {
|
|
324
|
+
throw new Error('generation must be a positive integer');
|
|
325
|
+
}
|
|
299
326
|
const workItem = this.#requiredItem(id);
|
|
300
327
|
let addedAttachments = [];
|
|
301
328
|
let detail;
|
|
@@ -308,7 +335,7 @@ export class WorkCenterService {
|
|
|
308
335
|
text: typeof payload.text === 'string' ? payload.text : '',
|
|
309
336
|
actionId: typeof payload.actionId === 'string' ? payload.actionId : '',
|
|
310
337
|
revision: payload.revision,
|
|
311
|
-
generation
|
|
338
|
+
generation,
|
|
312
339
|
addedAttachmentCount: addedAttachments.length,
|
|
313
340
|
addedAttachments,
|
|
314
341
|
attachments: [...(workItem.attachments || []), ...addedAttachments],
|
|
@@ -429,6 +456,7 @@ export class WorkCenterService {
|
|
|
429
456
|
}
|
|
430
457
|
|
|
431
458
|
async shutdown() {
|
|
459
|
+
await this.coordinator?.shutdown?.();
|
|
432
460
|
await this.watcher.stop();
|
|
433
461
|
try { await this.watcher.runner?.shutdown?.(); } catch {}
|
|
434
462
|
try { await this.watcher.runner?.trace?.close?.(); } catch {}
|