@yeaft/webchat-agent 1.0.173 → 1.0.174
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/package.json +1 -1
- package/yeaft/work-center/bridge.js +8 -2
- package/yeaft/work-center/controller.js +48 -4
- package/yeaft/work-center/debug-projection.js +541 -0
- package/yeaft/work-center/projection.js +441 -63
- package/yeaft/work-center/runner.js +8 -2
- package/yeaft/work-center/service.js +95 -9
- package/yeaft/work-center/store.js +31 -5
|
@@ -1,5 +1,31 @@
|
|
|
1
|
+
import { reconstructDebugRawRequest } from '../debug-trace.js';
|
|
2
|
+
import {
|
|
3
|
+
enforceActionRequestDetailBudget,
|
|
4
|
+
limitActionRequestDebugInput,
|
|
5
|
+
sanitizeDebugValue,
|
|
6
|
+
sanitizeDiagnosticText,
|
|
7
|
+
} from './debug-projection.js';
|
|
1
8
|
import { normalizeActionBrief } from './workflow.js';
|
|
2
9
|
|
|
10
|
+
const MAX_ACTION_MESSAGE_CHARS = 16_000;
|
|
11
|
+
const MAX_ACTION_DIAGNOSTIC_CHARS = 8_000;
|
|
12
|
+
const MAX_ACTION_MESSAGES = 20;
|
|
13
|
+
const MAX_ACTION_REQUEST_TOOL_CALLS = 128;
|
|
14
|
+
const MAX_HISTORICAL_BRIEF_CHARS = 256;
|
|
15
|
+
export const MAX_WORK_ITEM_BROWSER_DTO_BYTES = 512 * 1024;
|
|
16
|
+
|
|
17
|
+
function jsonByteLength(value) {
|
|
18
|
+
return Buffer.byteLength(JSON.stringify(value), 'utf8');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function truncateUtf8(value, maxBytes) {
|
|
22
|
+
const bytes = Buffer.from(String(value || ''), 'utf8');
|
|
23
|
+
if (bytes.length <= maxBytes) return bytes.toString('utf8');
|
|
24
|
+
let end = Math.min(maxBytes, bytes.length);
|
|
25
|
+
while (end > 0 && (bytes[end] & 0xc0) === 0x80) end -= 1;
|
|
26
|
+
return bytes.subarray(0, end).toString('utf8');
|
|
27
|
+
}
|
|
28
|
+
|
|
3
29
|
function currentAction(detail) {
|
|
4
30
|
if (!detail?.currentActionId || !Array.isArray(detail.actions)) return null;
|
|
5
31
|
return detail.actions.find(action => action.id === detail.currentActionId) || null;
|
|
@@ -36,6 +62,70 @@ function sumExecutionStats(values) {
|
|
|
36
62
|
}, emptyExecutionStats());
|
|
37
63
|
}
|
|
38
64
|
|
|
65
|
+
function normalizeProjectedMessage(message) {
|
|
66
|
+
if (!message || typeof message !== 'object') return null;
|
|
67
|
+
const text = typeof message.text === 'string'
|
|
68
|
+
? message.text.trim().slice(0, MAX_ACTION_MESSAGE_CHARS)
|
|
69
|
+
: '';
|
|
70
|
+
const attachments = projectAttachments(message.attachments);
|
|
71
|
+
if (!text && attachments.length === 0) return null;
|
|
72
|
+
return {
|
|
73
|
+
id: String(message.id || ''),
|
|
74
|
+
role: message.role === 'user' ? 'user' : 'assistant',
|
|
75
|
+
kind: message.kind === 'input' ? 'input' : 'response',
|
|
76
|
+
status: message.status || null,
|
|
77
|
+
text,
|
|
78
|
+
attachments,
|
|
79
|
+
createdAt: count(message.createdAt),
|
|
80
|
+
updatedAt: count(message.updatedAt || message.createdAt),
|
|
81
|
+
...(message.progressRevision == null ? {} : { progressRevision: count(message.progressRevision) }),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function actionInputMessages(action, events) {
|
|
86
|
+
return (Array.isArray(events) ? events : [])
|
|
87
|
+
.filter(event => event?.actionId === action?.id
|
|
88
|
+
&& ['action.guidance_added', 'action.input_added'].includes(event.type))
|
|
89
|
+
.map(event => normalizeProjectedMessage({
|
|
90
|
+
id: `event:${event.id}`,
|
|
91
|
+
role: 'user',
|
|
92
|
+
kind: 'input',
|
|
93
|
+
status: 'sent',
|
|
94
|
+
text: event.data?.text || event.data?.guidance || '',
|
|
95
|
+
attachments: event.data?.attachments,
|
|
96
|
+
createdAt: event.createdAt,
|
|
97
|
+
}))
|
|
98
|
+
.filter(Boolean);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function runResponseMessage(run) {
|
|
102
|
+
return normalizeProjectedMessage({
|
|
103
|
+
id: `run:${run.id}`,
|
|
104
|
+
role: 'assistant',
|
|
105
|
+
kind: 'response',
|
|
106
|
+
status: run.status || 'running',
|
|
107
|
+
text: typeof run.response === 'string' ? run.response : '',
|
|
108
|
+
createdAt: count(run.startedAt),
|
|
109
|
+
updatedAt: count(run.endedAt || run.startedAt),
|
|
110
|
+
progressRevision: count(run.progressRevision),
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function actionMessages(action, runs, events) {
|
|
115
|
+
const matchingRuns = Array.isArray(runs)
|
|
116
|
+
? runs.filter(run => run?.actionId === action?.id)
|
|
117
|
+
: [];
|
|
118
|
+
return [...actionInputMessages(action, events), ...matchingRuns
|
|
119
|
+
.sort((left, right) => count(left.startedAt) - count(right.startedAt))
|
|
120
|
+
.map(run => runResponseMessage(run))
|
|
121
|
+
.filter(Boolean)]
|
|
122
|
+
.sort((left, right) => left.createdAt - right.createdAt
|
|
123
|
+
|| (left.role === 'user' ? -1 : 1)
|
|
124
|
+
|| left.id.localeCompare(right.id));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
39
129
|
const MAX_FAILURE_REASON_LENGTH = 2_000;
|
|
40
130
|
const MAX_FAILURE_INSPECTION_LENGTH = 16_000;
|
|
41
131
|
const SAFE_FAILURE_FALLBACK = 'The Action failed. Sensitive details were omitted.';
|
|
@@ -56,35 +146,71 @@ function sanitizedUrl(raw) {
|
|
|
56
146
|
}
|
|
57
147
|
}
|
|
58
148
|
|
|
149
|
+
function unsafeFailureText(raw) {
|
|
150
|
+
return PROVIDER_TOKEN_PATTERN.test(raw) || HIGH_ENTROPY_SECRET_PATTERN.test(raw)
|
|
151
|
+
|| LOCAL_PATH_ASSIGNMENT_PATTERN.test(raw) || POSIX_ABSOLUTE_PATH_PATTERN.test(raw)
|
|
152
|
+
|| WINDOWS_ABSOLUTE_PATH_PATTERN.test(raw);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function sanitizeFailureDiagnostic(value) {
|
|
156
|
+
const raw = typeof value === 'string'
|
|
157
|
+
? value.trim().slice(0, MAX_FAILURE_INSPECTION_LENGTH)
|
|
158
|
+
: '';
|
|
159
|
+
if (!raw) return '';
|
|
160
|
+
if (unsafeFailureText(raw)) return SAFE_FAILURE_FALLBACK;
|
|
161
|
+
return sanitizeDiagnosticText(raw, MAX_ACTION_DIAGNOSTIC_CHARS);
|
|
162
|
+
}
|
|
163
|
+
|
|
59
164
|
function sanitizeFailureReason(value) {
|
|
60
165
|
const raw = typeof value === 'string'
|
|
61
166
|
? value.trim().slice(0, MAX_FAILURE_INSPECTION_LENGTH)
|
|
62
167
|
: '';
|
|
63
168
|
if (!raw) return '';
|
|
169
|
+
if (unsafeFailureText(raw)) return SAFE_FAILURE_FALLBACK;
|
|
64
170
|
const text = raw.replace(/https?:\/\/[^\s<>'"`]+/gi, sanitizedUrl);
|
|
65
|
-
if (CREDENTIAL_ASSIGNMENT_PATTERN.test(text)
|
|
66
|
-
|| HIGH_ENTROPY_SECRET_PATTERN.test(text)) return SAFE_FAILURE_FALLBACK;
|
|
67
|
-
if (LOCAL_PATH_ASSIGNMENT_PATTERN.test(text) || POSIX_ABSOLUTE_PATH_PATTERN.test(text)
|
|
68
|
-
|| WINDOWS_ABSOLUTE_PATH_PATTERN.test(text)) {
|
|
69
|
-
return SAFE_FAILURE_FALLBACK;
|
|
70
|
-
}
|
|
171
|
+
if (CREDENTIAL_ASSIGNMENT_PATTERN.test(text)) return SAFE_FAILURE_FALLBACK;
|
|
71
172
|
return text
|
|
72
173
|
.replace(/\b(?:Bearer|Basic)\s+[^\s,;]+/gi, '[redacted credential]')
|
|
73
174
|
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '')
|
|
74
175
|
.slice(0, MAX_FAILURE_REASON_LENGTH);
|
|
75
176
|
}
|
|
76
177
|
|
|
77
|
-
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
function actionExecution(action, runs, events, includeBody = true) {
|
|
78
181
|
const matchingRuns = Array.isArray(runs)
|
|
79
182
|
? runs.filter(run => run?.actionId === action?.id)
|
|
80
183
|
: [];
|
|
81
184
|
if (matchingRuns.length === 0) {
|
|
185
|
+
const inputMessageCount = Array.isArray(events) ? events.filter(event => (
|
|
186
|
+
event?.actionId === action?.id
|
|
187
|
+
&& ['action.guidance_added', 'action.input_added'].includes(event.type)
|
|
188
|
+
)).length : 0;
|
|
189
|
+
const messages = includeBody
|
|
190
|
+
? (Array.isArray(action?.messages)
|
|
191
|
+
? action.messages.slice(-MAX_ACTION_MESSAGES).map(normalizeProjectedMessage).filter(Boolean)
|
|
192
|
+
: actionInputMessages(action, events))
|
|
193
|
+
: [];
|
|
194
|
+
const messageCount = count(action?.messageCount) || (includeBody ? messages.length : inputMessageCount);
|
|
82
195
|
return {
|
|
83
|
-
...executionStats(action),
|
|
84
|
-
response: '',
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
196
|
+
...executionStats(action?.executionStats || action),
|
|
197
|
+
response: includeBody && typeof action?.response === 'string' ? action.response : '',
|
|
198
|
+
failure: includeBody && action?.failure && typeof action.failure === 'object'
|
|
199
|
+
? {
|
|
200
|
+
error: sanitizeFailureDiagnostic(action.failure.error),
|
|
201
|
+
summary: sanitizeFailureDiagnostic(action.failure.summary),
|
|
202
|
+
failedAt: count(action.failure.failedAt),
|
|
203
|
+
}
|
|
204
|
+
: null,
|
|
205
|
+
progressRevision: count(action?.progressRevision),
|
|
206
|
+
messages,
|
|
207
|
+
liveMessage: includeBody ? normalizeProjectedMessage(action?.liveMessage) : null,
|
|
208
|
+
messageCount,
|
|
209
|
+
messageCursor: action?.messageCursor == null
|
|
210
|
+
? (!includeBody && messageCount > 0 ? String(messageCount) : null)
|
|
211
|
+
: String(action.messageCursor),
|
|
212
|
+
failureReason: sanitizeFailureReason(action?.failureReason),
|
|
213
|
+
|
|
88
214
|
};
|
|
89
215
|
}
|
|
90
216
|
const stats = sumExecutionStats(matchingRuns);
|
|
@@ -92,31 +218,38 @@ function actionExecution(action, runs) {
|
|
|
92
218
|
count(right.progressRevision) - count(left.progressRevision)
|
|
93
219
|
|| count(right.startedAt) - count(left.startedAt)
|
|
94
220
|
))[0];
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|| ['failed', 'retryable', 'interrupted'].includes(latestRun?.status);
|
|
100
|
-
const latestFailure = showCurrentFailure
|
|
101
|
-
? runsByEnd.find(run => sanitizeFailureReason(run?.error))
|
|
221
|
+
const latestFailure = action?.status === 'failed'
|
|
222
|
+
? [...matchingRuns]
|
|
223
|
+
.filter(run => run?.status === 'failed')
|
|
224
|
+
.sort((left, right) => count(right.endedAt || right.startedAt) - count(left.endedAt || left.startedAt))[0]
|
|
102
225
|
: null;
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
.filter(message => message.text || message.failureReason);
|
|
226
|
+
const inputMessageCount = includeBody
|
|
227
|
+
? 0
|
|
228
|
+
: (Array.isArray(events) ? events : []).filter(event => (
|
|
229
|
+
event?.actionId === action?.id
|
|
230
|
+
&& ['action.guidance_added', 'action.input_added'].includes(event.type)
|
|
231
|
+
)).length;
|
|
232
|
+
const allMessages = includeBody ? actionMessages(action, matchingRuns, events) : [];
|
|
233
|
+
const totalMessageCount = includeBody ? allMessages.length : matchingRuns.length + inputMessageCount;
|
|
234
|
+
const messages = allMessages.slice(-MAX_ACTION_MESSAGES);
|
|
235
|
+
const liveMessage = includeBody ? runResponseMessage(latest) : null;
|
|
114
236
|
return {
|
|
115
237
|
...stats,
|
|
116
|
-
response: typeof latest?.response === 'string' ? latest.response : '',
|
|
238
|
+
response: includeBody && typeof latest?.response === 'string' ? latest.response : '',
|
|
239
|
+
failure: includeBody && latestFailure ? {
|
|
240
|
+
error: sanitizeFailureDiagnostic(latestFailure.error),
|
|
241
|
+
summary: sanitizeFailureDiagnostic(latestFailure.summary),
|
|
242
|
+
failedAt: count(latestFailure.endedAt || latestFailure.startedAt),
|
|
243
|
+
} : null,
|
|
117
244
|
failureReason: sanitizeFailureReason(latestFailure?.error),
|
|
245
|
+
|
|
118
246
|
progressRevision: count(latest?.progressRevision),
|
|
119
247
|
messages,
|
|
248
|
+
liveMessage,
|
|
249
|
+
messageCount: totalMessageCount,
|
|
250
|
+
messageCursor: includeBody
|
|
251
|
+
? (totalMessageCount > messages.length ? String(totalMessageCount - messages.length) : null)
|
|
252
|
+
: (totalMessageCount > 0 ? String(totalMessageCount) : null),
|
|
120
253
|
};
|
|
121
254
|
}
|
|
122
255
|
|
|
@@ -140,50 +273,169 @@ function projectAssignmentPolicy(policy) {
|
|
|
140
273
|
};
|
|
141
274
|
}
|
|
142
275
|
|
|
143
|
-
function projectAction(action, runs) {
|
|
276
|
+
function projectAction(action, runs, events, includeBody = true) {
|
|
144
277
|
if (!action) return null;
|
|
145
|
-
const execution = actionExecution(action, runs);
|
|
278
|
+
const execution = actionExecution(action, runs, events, includeBody);
|
|
279
|
+
const alreadyProjected = !Array.isArray(runs) && Array.isArray(action.messages);
|
|
280
|
+
const brief = alreadyProjected ? (action.brief || null) : normalizeActionBrief(action.brief, action.type);
|
|
281
|
+
const projectedBrief = includeBody || !brief
|
|
282
|
+
? brief
|
|
283
|
+
: Object.fromEntries(Object.entries(brief).map(([key, value]) => [
|
|
284
|
+
key,
|
|
285
|
+
truncateUtf8(value, MAX_HISTORICAL_BRIEF_CHARS),
|
|
286
|
+
]));
|
|
146
287
|
return {
|
|
147
288
|
id: action.id,
|
|
148
289
|
sequence: action.sequence,
|
|
149
290
|
type: action.type,
|
|
150
291
|
stageId: action.stageId || action.type,
|
|
151
|
-
assignmentPolicy:
|
|
292
|
+
assignmentPolicy: alreadyProjected
|
|
293
|
+
? (action.assignmentPolicy || null)
|
|
294
|
+
: projectAssignmentPolicy(action.assignmentPolicy),
|
|
152
295
|
dependsOnStageIds: Array.isArray(action.dependsOnStageIds) ? action.dependsOnStageIds : [],
|
|
153
296
|
workspaceMode: action.workspaceMode || 'shared',
|
|
154
297
|
requiredRole: action.requiredRole || '',
|
|
155
|
-
brief:
|
|
298
|
+
brief: projectedBrief,
|
|
156
299
|
status: action.status,
|
|
157
300
|
executionStats: executionStats(execution),
|
|
158
301
|
loopCount: execution.loopCount,
|
|
159
302
|
toolCount: execution.toolCount,
|
|
160
|
-
|
|
161
|
-
|
|
303
|
+
...(includeBody ? {
|
|
304
|
+
response: execution.response,
|
|
305
|
+
failureReason: execution.failureReason,
|
|
306
|
+
} : {}),
|
|
162
307
|
progressRevision: execution.progressRevision,
|
|
163
|
-
|
|
308
|
+
messageCount: execution.messageCount,
|
|
309
|
+
messageCursor: execution.messageCursor,
|
|
310
|
+
...(includeBody ? {
|
|
311
|
+
response: execution.response,
|
|
312
|
+
failure: execution.failure,
|
|
313
|
+
messages: execution.messages,
|
|
314
|
+
liveMessage: execution.liveMessage,
|
|
315
|
+
} : {}),
|
|
164
316
|
};
|
|
165
317
|
}
|
|
166
318
|
|
|
319
|
+
function bodyActionId(detail) {
|
|
320
|
+
const actions = Array.isArray(detail?.actions) ? detail.actions : [];
|
|
321
|
+
if (detail?.currentActionId && actions.some(action => action?.id === detail.currentActionId)) {
|
|
322
|
+
return detail.currentActionId;
|
|
323
|
+
}
|
|
324
|
+
return [...actions].sort((left, right) => (
|
|
325
|
+
count(right?.sequence) - count(left?.sequence)
|
|
326
|
+
|| String(right?.id || '').localeCompare(String(left?.id || ''))
|
|
327
|
+
))[0]?.id || null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function stripActionBody(action, keepFailure = false) {
|
|
331
|
+
if (!action) return action;
|
|
332
|
+
const projected = { ...action };
|
|
333
|
+
delete projected.response;
|
|
334
|
+
delete projected.messages;
|
|
335
|
+
delete projected.liveMessage;
|
|
336
|
+
if (!keepFailure) delete projected.failure;
|
|
337
|
+
if (projected.brief) {
|
|
338
|
+
projected.brief = Object.fromEntries(Object.entries(projected.brief).map(([key, value]) => [
|
|
339
|
+
key,
|
|
340
|
+
truncateUtf8(value, MAX_HISTORICAL_BRIEF_CHARS),
|
|
341
|
+
]));
|
|
342
|
+
}
|
|
343
|
+
return projected;
|
|
344
|
+
}
|
|
345
|
+
|
|
167
346
|
function projectActionStats(detail) {
|
|
168
347
|
if (!Array.isArray(detail?.actions)) return [];
|
|
348
|
+
const liveActionId = bodyActionId(detail);
|
|
169
349
|
return detail.actions.map(action => {
|
|
170
|
-
const projected = projectAction(
|
|
171
|
-
|
|
350
|
+
const projected = projectAction(
|
|
351
|
+
action,
|
|
352
|
+
detail.runs,
|
|
353
|
+
detail.events,
|
|
354
|
+
action?.id === liveActionId,
|
|
355
|
+
);
|
|
356
|
+
const stats = {
|
|
172
357
|
id: projected.id,
|
|
173
358
|
status: projected.status,
|
|
174
359
|
executionStats: projected.executionStats,
|
|
175
360
|
loopCount: projected.loopCount,
|
|
176
361
|
toolCount: projected.toolCount,
|
|
177
|
-
response: projected.response,
|
|
178
|
-
failureReason: projected.failureReason,
|
|
179
362
|
progressRevision: projected.progressRevision,
|
|
180
|
-
messages: projected.messages
|
|
181
|
-
.map(({ failureReason, ...message }) => message)
|
|
182
|
-
.filter(message => message.text),
|
|
183
363
|
};
|
|
364
|
+
if (projected.failureReason) stats.failureReason = projected.failureReason;
|
|
365
|
+
if (projected.id === liveActionId) {
|
|
366
|
+
stats.response = projected.response;
|
|
367
|
+
stats.failure = projected.failure;
|
|
368
|
+
if (projected.liveMessage) stats.liveMessage = projected.liveMessage;
|
|
369
|
+
}
|
|
370
|
+
return stats;
|
|
184
371
|
});
|
|
185
372
|
}
|
|
186
373
|
|
|
374
|
+
function enforceWorkItemBrowserDtoBudget(value, options = {}) {
|
|
375
|
+
if (!value || jsonByteLength(value) <= MAX_WORK_ITEM_BROWSER_DTO_BYTES) return value;
|
|
376
|
+
const dto = value;
|
|
377
|
+
const workItem = options.event === true ? dto.workItem : dto;
|
|
378
|
+
const actions = Array.isArray(workItem?.actions)
|
|
379
|
+
? workItem.actions
|
|
380
|
+
: Array.isArray(workItem?.actionStats) ? workItem.actionStats : [];
|
|
381
|
+
const keepId = options.keepActionId || workItem?.currentActionId || actions.at(-1)?.id || null;
|
|
382
|
+
workItem.truncated = true;
|
|
383
|
+
|
|
384
|
+
for (let index = 0; index < actions.length; index += 1) {
|
|
385
|
+
if (actions[index]?.id === keepId) continue;
|
|
386
|
+
actions[index] = stripActionBody(actions[index]);
|
|
387
|
+
}
|
|
388
|
+
if (jsonByteLength(dto) <= MAX_WORK_ITEM_BROWSER_DTO_BYTES) return dto;
|
|
389
|
+
|
|
390
|
+
const keep = actions.find(action => action?.id === keepId);
|
|
391
|
+
if (keep) {
|
|
392
|
+
delete keep.response;
|
|
393
|
+
delete keep.messages;
|
|
394
|
+
delete keep.liveMessage;
|
|
395
|
+
}
|
|
396
|
+
if (jsonByteLength(dto) <= MAX_WORK_ITEM_BROWSER_DTO_BYTES) return dto;
|
|
397
|
+
|
|
398
|
+
const originalCount = actions.length;
|
|
399
|
+
const retained = keep ? [stripActionBody(keep, true)] : [];
|
|
400
|
+
if (Array.isArray(workItem.actions)) workItem.actions = retained;
|
|
401
|
+
else workItem.actionStats = retained;
|
|
402
|
+
workItem.omittedActionCount = originalCount - retained.length;
|
|
403
|
+
if (jsonByteLength(dto) <= MAX_WORK_ITEM_BROWSER_DTO_BYTES) return dto;
|
|
404
|
+
|
|
405
|
+
if (retained[0]) {
|
|
406
|
+
delete retained[0].brief;
|
|
407
|
+
delete retained[0].failure;
|
|
408
|
+
}
|
|
409
|
+
workItem.title = truncateUtf8(workItem.title, 4 * 1024);
|
|
410
|
+
workItem.goal = truncateUtf8(workItem.goal, 4 * 1024);
|
|
411
|
+
workItem.waitingReason = truncateUtf8(workItem.waitingReason, 4 * 1024);
|
|
412
|
+
workItem.actionSummary = truncateUtf8(workItem.actionSummary, 4 * 1024);
|
|
413
|
+
if (Array.isArray(workItem.acceptanceCriteria)) workItem.acceptanceCriteria = [];
|
|
414
|
+
if (Array.isArray(workItem.linkedSessionIds)) workItem.linkedSessionIds = [];
|
|
415
|
+
if (Array.isArray(workItem.attachments)) workItem.attachments = [];
|
|
416
|
+
if (jsonByteLength(dto) <= MAX_WORK_ITEM_BROWSER_DTO_BYTES) return dto;
|
|
417
|
+
|
|
418
|
+
const minimalWorkItem = {
|
|
419
|
+
id: truncateUtf8(workItem.id, 4 * 1024),
|
|
420
|
+
revision: count(workItem.revision),
|
|
421
|
+
title: truncateUtf8(workItem.title, 4 * 1024),
|
|
422
|
+
goal: truncateUtf8(workItem.goal, 4 * 1024),
|
|
423
|
+
status: truncateUtf8(workItem.status, 256),
|
|
424
|
+
currentActionId: truncateUtf8(workItem.currentActionId, 4 * 1024) || null,
|
|
425
|
+
executionStats: workItem.executionStats,
|
|
426
|
+
actionCount: count(workItem.actionCount),
|
|
427
|
+
actions: Array.isArray(workItem.actions) ? [] : undefined,
|
|
428
|
+
actionStats: Array.isArray(workItem.actionStats) ? [] : undefined,
|
|
429
|
+
truncated: true,
|
|
430
|
+
omittedActionCount: originalCount,
|
|
431
|
+
createdAt: count(workItem.createdAt),
|
|
432
|
+
updatedAt: count(workItem.updatedAt),
|
|
433
|
+
};
|
|
434
|
+
if (options.event === true) dto.workItem = minimalWorkItem;
|
|
435
|
+
else return minimalWorkItem;
|
|
436
|
+
return dto;
|
|
437
|
+
}
|
|
438
|
+
|
|
187
439
|
function waitingReason(detail) {
|
|
188
440
|
if (typeof detail?.waitingReason === 'string') return detail.waitingReason;
|
|
189
441
|
if (detail?.status !== 'waiting' || !Array.isArray(detail.runs)) return '';
|
|
@@ -207,35 +459,45 @@ function workItemFailureReason(detail) {
|
|
|
207
459
|
*/
|
|
208
460
|
export function projectWorkItemDetail(detail) {
|
|
209
461
|
if (!detail) return null;
|
|
210
|
-
|
|
462
|
+
const liveActionId = bodyActionId(detail);
|
|
463
|
+
const projected = {
|
|
211
464
|
id: detail.id,
|
|
212
465
|
revision: detail.revision,
|
|
213
466
|
title: detail.title,
|
|
214
467
|
goal: detail.goal,
|
|
215
468
|
acceptanceCriteria: Array.isArray(detail.acceptanceCriteria) ? detail.acceptanceCriteria : [],
|
|
216
469
|
workflowTemplate: detail.workflowTemplate,
|
|
217
|
-
workItemType: detail.workflowSnapshot?.workItemType || null,
|
|
218
|
-
planningMode: detail.workflowSnapshot?.planningMode || 'static',
|
|
219
|
-
executionMode: detail.workflowSnapshot?.executionMode || 'linear',
|
|
470
|
+
workItemType: detail.workflowSnapshot?.workItemType || detail.workItemType || null,
|
|
471
|
+
planningMode: detail.workflowSnapshot?.planningMode || detail.planningMode || 'static',
|
|
472
|
+
executionMode: detail.workflowSnapshot?.executionMode || detail.executionMode || 'linear',
|
|
220
473
|
status: detail.status,
|
|
221
474
|
currentActionId: detail.currentActionId || null,
|
|
222
|
-
executionStats:
|
|
475
|
+
executionStats: Array.isArray(detail.runs)
|
|
476
|
+
? sumExecutionStats(detail.runs)
|
|
477
|
+
: executionStats(detail.executionStats),
|
|
223
478
|
reuseMemory: detail.reuseMemory !== false,
|
|
224
|
-
waitingReason: waitingReason(detail),
|
|
479
|
+
waitingReason: sanitizeDiagnosticText(waitingReason(detail), MAX_ACTION_DIAGNOSTIC_CHARS),
|
|
225
480
|
failureReason: workItemFailureReason(detail),
|
|
481
|
+
|
|
226
482
|
origin: detail.origin?.sessionId ? { sessionId: detail.origin.sessionId } : null,
|
|
227
483
|
linkedSessionIds: Array.isArray(detail.linkedSessionIds) ? detail.linkedSessionIds : [],
|
|
228
484
|
attachments: projectAttachments(detail.attachments),
|
|
229
485
|
createdAt: detail.createdAt,
|
|
230
486
|
updatedAt: detail.updatedAt,
|
|
231
|
-
actionCount: Array.isArray(detail.actions) ? detail.actions.length :
|
|
487
|
+
actionCount: Array.isArray(detail.actions) ? detail.actions.length : count(detail.actionCount),
|
|
232
488
|
actionSummary: Array.isArray(detail.actions)
|
|
233
489
|
? detail.actions.map(action => action.type).filter(Boolean).join(' → ')
|
|
234
|
-
: '',
|
|
490
|
+
: String(detail.actionSummary || ''),
|
|
235
491
|
actions: Array.isArray(detail.actions)
|
|
236
|
-
? detail.actions.map(action => projectAction(
|
|
492
|
+
? detail.actions.map(action => projectAction(
|
|
493
|
+
action,
|
|
494
|
+
detail.runs,
|
|
495
|
+
detail.events,
|
|
496
|
+
action?.id === liveActionId,
|
|
497
|
+
))
|
|
237
498
|
: [],
|
|
238
499
|
};
|
|
500
|
+
return enforceWorkItemBrowserDtoBudget(projected, { keepActionId: liveActionId });
|
|
239
501
|
}
|
|
240
502
|
|
|
241
503
|
/**
|
|
@@ -250,8 +512,8 @@ export function projectWorkItemSummary(detail) {
|
|
|
250
512
|
revision: detail.revision,
|
|
251
513
|
title: detail.title,
|
|
252
514
|
goal: detail.goal,
|
|
253
|
-
workItemType: detail.workflowSnapshot?.workItemType || null,
|
|
254
|
-
planningMode: detail.workflowSnapshot?.planningMode || 'static',
|
|
515
|
+
workItemType: detail.workflowSnapshot?.workItemType || detail.workItemType || null,
|
|
516
|
+
planningMode: detail.workflowSnapshot?.planningMode || detail.planningMode || 'static',
|
|
255
517
|
status: detail.status,
|
|
256
518
|
currentActionId: detail.currentActionId || null,
|
|
257
519
|
currentAction: null,
|
|
@@ -264,19 +526,22 @@ export function projectWorkItemSummary(detail) {
|
|
|
264
526
|
};
|
|
265
527
|
}
|
|
266
528
|
const action = currentAction(detail);
|
|
267
|
-
const projectedAction = action ? projectAction(action, detail.runs) : null;
|
|
529
|
+
const projectedAction = action ? projectAction(action, detail.runs, detail.events, false) : null;
|
|
268
530
|
return {
|
|
269
531
|
id: detail.id,
|
|
270
532
|
revision: detail.revision,
|
|
271
533
|
title: detail.title,
|
|
272
534
|
goal: detail.goal,
|
|
273
|
-
workItemType: detail.workflowSnapshot?.workItemType || null,
|
|
274
|
-
planningMode: detail.workflowSnapshot?.planningMode || 'static',
|
|
275
|
-
executionMode: detail.workflowSnapshot?.executionMode || 'linear',
|
|
535
|
+
workItemType: detail.workflowSnapshot?.workItemType || detail.workItemType || null,
|
|
536
|
+
planningMode: detail.workflowSnapshot?.planningMode || detail.planningMode || 'static',
|
|
537
|
+
executionMode: detail.workflowSnapshot?.executionMode || detail.executionMode || 'linear',
|
|
276
538
|
status: detail.status,
|
|
277
539
|
currentActionId: detail.currentActionId || null,
|
|
278
|
-
executionStats:
|
|
540
|
+
executionStats: Array.isArray(detail.runs)
|
|
541
|
+
? sumExecutionStats(detail.runs)
|
|
542
|
+
: executionStats(detail.executionStats),
|
|
279
543
|
failureReason: workItemFailureReason(detail),
|
|
544
|
+
|
|
280
545
|
currentAction: projectedAction ? {
|
|
281
546
|
id: projectedAction.id,
|
|
282
547
|
type: projectedAction.type,
|
|
@@ -293,11 +558,124 @@ export function projectWorkItemSummary(detail) {
|
|
|
293
558
|
}
|
|
294
559
|
|
|
295
560
|
export function projectWorkCenterEvent(event) {
|
|
296
|
-
|
|
297
|
-
|
|
561
|
+
const liveActionId = bodyActionId(event?.workItem);
|
|
562
|
+
return enforceWorkItemBrowserDtoBudget({
|
|
563
|
+
type: truncateUtf8(event?.type || 'work_item.updated', 256),
|
|
298
564
|
workItem: {
|
|
299
565
|
...projectWorkItemSummary(event?.workItem),
|
|
300
566
|
actionStats: projectActionStats(event?.workItem),
|
|
301
567
|
},
|
|
568
|
+
}, { event: true, keepActionId: liveActionId });
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
function projectDebugUsage(value) {
|
|
572
|
+
return {
|
|
573
|
+
inputTokens: count(value?.inputTokens),
|
|
574
|
+
outputTokens: count(value?.outputTokens),
|
|
575
|
+
cacheReadTokens: count(value?.cacheReadTokens),
|
|
576
|
+
cacheWriteTokens: count(value?.cacheWriteTokens),
|
|
577
|
+
totalInputTokens: count(value?.totalInputTokens),
|
|
578
|
+
totalTokens: count(value?.totalTokens),
|
|
579
|
+
};
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export function projectActionMessagePage(action, runs, events, options = {}) {
|
|
583
|
+
const messages = actionMessages(action, runs, events);
|
|
584
|
+
const requestedCursor = options.cursor == null ? messages.length : Number(options.cursor);
|
|
585
|
+
const end = Number.isFinite(requestedCursor)
|
|
586
|
+
? Math.max(0, Math.min(messages.length, Math.floor(requestedCursor)))
|
|
587
|
+
: messages.length;
|
|
588
|
+
const limit = Math.max(1, Math.min(50, Number(options.limit) || MAX_ACTION_MESSAGES));
|
|
589
|
+
const start = Math.max(0, end - limit);
|
|
590
|
+
return {
|
|
591
|
+
actionId: action.id,
|
|
592
|
+
messages: messages.slice(start, end),
|
|
593
|
+
nextCursor: start > 0 ? String(start) : null,
|
|
594
|
+
total: messages.length,
|
|
302
595
|
};
|
|
303
596
|
}
|
|
597
|
+
|
|
598
|
+
export function projectActionRequestIndex(action, entries) {
|
|
599
|
+
return {
|
|
600
|
+
actionId: action.id,
|
|
601
|
+
requests: (Array.isArray(entries) ? entries : []).map(({ run, turn }) => ({
|
|
602
|
+
id: turn.turnId,
|
|
603
|
+
runId: run.id,
|
|
604
|
+
status: run.status || 'running',
|
|
605
|
+
model: run.modelSnapshot?.id || null,
|
|
606
|
+
vp: run.vpSnapshot ? {
|
|
607
|
+
id: run.vpSnapshot.id || null,
|
|
608
|
+
name: run.vpSnapshot.name || run.vpSnapshot.id || null,
|
|
609
|
+
} : null,
|
|
610
|
+
openedAt: count(turn.openedAt || run.startedAt),
|
|
611
|
+
closedAt: count(turn.closedAt || run.endedAt),
|
|
612
|
+
loopCount: count(turn.loopCount),
|
|
613
|
+
totalMs: count(turn.totalMs),
|
|
614
|
+
inputTokens: count(turn.summaryInputTokens),
|
|
615
|
+
outputTokens: count(turn.summaryOutputTokens),
|
|
616
|
+
totalTokens: count(turn.totalTokens),
|
|
617
|
+
})).sort((left, right) => right.openedAt - left.openedAt || right.id.localeCompare(left.id)),
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
export function projectActionRequestDetail(action, run, history) {
|
|
622
|
+
const turn = Array.isArray(history?.turns) ? history.turns[0] : null;
|
|
623
|
+
if (!turn) return null;
|
|
624
|
+
const sourceLoops = Array.isArray(history?.loops) ? history.loops : [];
|
|
625
|
+
const limited = limitActionRequestDebugInput(sourceLoops, turn.tools);
|
|
626
|
+
const tools = limited.tools;
|
|
627
|
+
const toolsByCall = new Map(tools.map(tool => [
|
|
628
|
+
`${count(tool.loopNumber)}:${tool.callId}`,
|
|
629
|
+
tool,
|
|
630
|
+
]));
|
|
631
|
+
const loops = limited.loops.map(loop => ({
|
|
632
|
+
id: loop.loopInstanceId || `${turn.turnId}:${loop.loopNumber}`,
|
|
633
|
+
loopNumber: count(loop.loopNumber),
|
|
634
|
+
model: loop.model || run.modelSnapshot?.id || null,
|
|
635
|
+
systemPrompt: sanitizeDebugValue(typeof loop.systemPrompt === 'string' ? loop.systemPrompt : ''),
|
|
636
|
+
messages: sanitizeDebugValue(Array.isArray(loop.messages) ? loop.messages : []),
|
|
637
|
+
response: sanitizeDebugValue(typeof loop.response === 'string' ? loop.response : ''),
|
|
638
|
+
usage: projectDebugUsage(loop.usage),
|
|
639
|
+
latencyMs: count(loop.latencyMs),
|
|
640
|
+
ttfbMs: loop.ttfbMs == null ? null : count(loop.ttfbMs),
|
|
641
|
+
stopReason: loop.stopReason || null,
|
|
642
|
+
at: count(loop.at),
|
|
643
|
+
tools: (Array.isArray(loop.toolCalls) ? loop.toolCalls : [])
|
|
644
|
+
.slice(0, MAX_ACTION_REQUEST_TOOL_CALLS)
|
|
645
|
+
.map(call => {
|
|
646
|
+
const result = toolsByCall.get(`${count(loop.loopNumber)}:${call.id}`);
|
|
647
|
+
return {
|
|
648
|
+
id: call.id || null,
|
|
649
|
+
name: call.name || result?.name || '?',
|
|
650
|
+
input: sanitizeDebugValue(call.input),
|
|
651
|
+
output: sanitizeDebugValue(result?.toolOutput ?? null),
|
|
652
|
+
durationMs: count(result?.durationMs),
|
|
653
|
+
isError: result?.isError === true,
|
|
654
|
+
};
|
|
655
|
+
}),
|
|
656
|
+
rawRequest: sanitizeDebugValue(reconstructDebugRawRequest(
|
|
657
|
+
loop.rawRequestBase ?? loop.requestBase?.rawRequest ?? null,
|
|
658
|
+
loop.requestDelta || null,
|
|
659
|
+
)),
|
|
660
|
+
rawResponse: sanitizeDebugValue(loop.rawResponse ?? null),
|
|
661
|
+
}));
|
|
662
|
+
return enforceActionRequestDetailBudget({
|
|
663
|
+
actionId: action.id,
|
|
664
|
+
request: {
|
|
665
|
+
id: turn.turnId,
|
|
666
|
+
runId: run.id,
|
|
667
|
+
status: run.status || 'running',
|
|
668
|
+
model: run.modelSnapshot?.id || loops[0]?.model || null,
|
|
669
|
+
vp: run.vpSnapshot ? {
|
|
670
|
+
id: run.vpSnapshot.id || null,
|
|
671
|
+
name: run.vpSnapshot.name || run.vpSnapshot.id || null,
|
|
672
|
+
} : null,
|
|
673
|
+
openedAt: count(turn.openedAt || run.startedAt),
|
|
674
|
+
closedAt: count(turn.closedAt || run.endedAt),
|
|
675
|
+
loopCount: sourceLoops.length,
|
|
676
|
+
totalMs: count(turn.totalMs),
|
|
677
|
+
totalTokens: count(turn.totalTokens),
|
|
678
|
+
loops,
|
|
679
|
+
},
|
|
680
|
+
}, limited.omittedLoopCount);
|
|
681
|
+
}
|
|
@@ -3,7 +3,7 @@ import { ToolRegistry } from '../tools/registry.js';
|
|
|
3
3
|
import { allTools } from '../tools/index.js';
|
|
4
4
|
import { parsePatch } from '../tools/apply-patch.js';
|
|
5
5
|
import { defaultRegistry } from '../vp/registry.js';
|
|
6
|
-
import {
|
|
6
|
+
import { createTrace } from '../debug-trace.js';
|
|
7
7
|
import { isPathInsideOrEqual } from '../tools/path-safety.js';
|
|
8
8
|
import { resolveWorkItemModel, selectWorkItemVp } from './assignment.js';
|
|
9
9
|
import { approxTokens } from '../memory/budget.js';
|
|
@@ -490,6 +490,10 @@ export class WorkItemRunner {
|
|
|
490
490
|
this.runtimeProvider = options.runtimeProvider;
|
|
491
491
|
this.policyProvider = typeof options.policyProvider === 'function' ? options.policyProvider : null;
|
|
492
492
|
this.attachmentRoot = options.attachmentRoot || null;
|
|
493
|
+
this.trace = options.trace || createTrace({
|
|
494
|
+
enabled: Boolean(options.yeaftDir),
|
|
495
|
+
dirPath: options.yeaftDir || null,
|
|
496
|
+
});
|
|
493
497
|
this.actionWorktreeRoot = options.actionWorktreeRoot || null;
|
|
494
498
|
this.store = options.store;
|
|
495
499
|
this.registry = options.registry || defaultRegistry;
|
|
@@ -699,7 +703,7 @@ export class WorkItemRunner {
|
|
|
699
703
|
});
|
|
700
704
|
const engine = new Engine({
|
|
701
705
|
adapter,
|
|
702
|
-
trace:
|
|
706
|
+
trace: this.trace,
|
|
703
707
|
config,
|
|
704
708
|
conversationStore: null,
|
|
705
709
|
memoryIndex: null,
|
|
@@ -725,6 +729,8 @@ export class WorkItemRunner {
|
|
|
725
729
|
messages: [],
|
|
726
730
|
signal,
|
|
727
731
|
scenario: 'work-item',
|
|
732
|
+
sessionId: `work-item-${workItem.id}`,
|
|
733
|
+
threadId: run.id,
|
|
728
734
|
vpPersona: personaFor(vp),
|
|
729
735
|
workCenterInstructions: workItem?.workflowSnapshot?.globalInstructions || '',
|
|
730
736
|
workDir,
|