@yeaft/webchat-agent 1.0.171 → 1.0.172
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
CHANGED
|
@@ -36,6 +36,44 @@ function sumExecutionStats(values) {
|
|
|
36
36
|
}, emptyExecutionStats());
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
const MAX_FAILURE_REASON_LENGTH = 2_000;
|
|
40
|
+
const MAX_FAILURE_INSPECTION_LENGTH = 16_000;
|
|
41
|
+
const SAFE_FAILURE_FALLBACK = 'The Action failed. Sensitive details were omitted.';
|
|
42
|
+
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;
|
|
43
|
+
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;
|
|
44
|
+
const HIGH_ENTROPY_SECRET_PATTERN = /\b(?=[A-Za-z0-9_./+=-]{32,}\b)(?=[A-Za-z0-9_./+=-]*[A-Za-z])(?=[A-Za-z0-9_./+=-]*\d)[A-Za-z0-9_./+=-]+\b/;
|
|
45
|
+
const LOCAL_PATH_ASSIGNMENT_PATTERN = /\b(?:path|cwd|file|filename|directory)\s*[:=]\s*(?:\/(?!\/)|[A-Za-z]:\\|\\\\)/i;
|
|
46
|
+
const POSIX_ABSOLUTE_PATH_PATTERN = /(?:^|[\s("'`])\/(?!\/)[^\r\n]*/m;
|
|
47
|
+
const WINDOWS_ABSOLUTE_PATH_PATTERN = /(?:^|[\s("'`])(?:[A-Za-z]:\\|\\\\)[^\r\n]*/m;
|
|
48
|
+
|
|
49
|
+
function sanitizedUrl(raw) {
|
|
50
|
+
try {
|
|
51
|
+
const url = new URL(raw);
|
|
52
|
+
if (!['http:', 'https:'].includes(url.protocol)) return '[redacted URL]';
|
|
53
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
54
|
+
} catch {
|
|
55
|
+
return '[redacted URL]';
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function sanitizeFailureReason(value) {
|
|
60
|
+
const raw = typeof value === 'string'
|
|
61
|
+
? value.trim().slice(0, MAX_FAILURE_INSPECTION_LENGTH)
|
|
62
|
+
: '';
|
|
63
|
+
if (!raw) return '';
|
|
64
|
+
const text = raw.replace(/https?:\/\/[^\s<>'"`]+/gi, sanitizedUrl);
|
|
65
|
+
if (CREDENTIAL_ASSIGNMENT_PATTERN.test(text) || PROVIDER_TOKEN_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
|
+
}
|
|
71
|
+
return text
|
|
72
|
+
.replace(/\b(?:Bearer|Basic)\s+[^\s,;]+/gi, '[redacted credential]')
|
|
73
|
+
.replace(/[\u0000-\u0008\u000B\u000C\u000E-\u001F\u007F]/g, '')
|
|
74
|
+
.slice(0, MAX_FAILURE_REASON_LENGTH);
|
|
75
|
+
}
|
|
76
|
+
|
|
39
77
|
function actionExecution(action, runs) {
|
|
40
78
|
const matchingRuns = Array.isArray(runs)
|
|
41
79
|
? runs.filter(run => run?.actionId === action?.id)
|
|
@@ -44,6 +82,7 @@ function actionExecution(action, runs) {
|
|
|
44
82
|
return {
|
|
45
83
|
...executionStats(action),
|
|
46
84
|
response: '',
|
|
85
|
+
failureReason: '',
|
|
47
86
|
progressRevision: 0,
|
|
48
87
|
messages: [],
|
|
49
88
|
};
|
|
@@ -53,19 +92,29 @@ function actionExecution(action, runs) {
|
|
|
53
92
|
count(right.progressRevision) - count(left.progressRevision)
|
|
54
93
|
|| count(right.startedAt) - count(left.startedAt)
|
|
55
94
|
))[0];
|
|
95
|
+
const runsByEnd = [...matchingRuns]
|
|
96
|
+
.sort((left, right) => count(right.endedAt || right.startedAt) - count(left.endedAt || left.startedAt));
|
|
97
|
+
const latestRun = runsByEnd[0];
|
|
98
|
+
const showCurrentFailure = action?.status === 'failed'
|
|
99
|
+
|| ['failed', 'retryable', 'interrupted'].includes(latestRun?.status);
|
|
100
|
+
const latestFailure = showCurrentFailure
|
|
101
|
+
? runsByEnd.find(run => sanitizeFailureReason(run?.error))
|
|
102
|
+
: null;
|
|
56
103
|
const messages = [...matchingRuns]
|
|
57
104
|
.sort((left, right) => count(left.startedAt) - count(right.startedAt))
|
|
58
105
|
.map((run, index) => ({
|
|
59
106
|
id: `${action.id}:${index + 1}`,
|
|
60
107
|
status: run.status || 'running',
|
|
61
108
|
text: typeof run.response === 'string' ? run.response.trim().slice(0, 16_000) : '',
|
|
109
|
+
failureReason: sanitizeFailureReason(run.error),
|
|
62
110
|
createdAt: count(run.startedAt),
|
|
63
111
|
updatedAt: count(run.endedAt || run.startedAt),
|
|
64
112
|
}))
|
|
65
|
-
.filter(message => message.text);
|
|
113
|
+
.filter(message => message.text || message.failureReason);
|
|
66
114
|
return {
|
|
67
115
|
...stats,
|
|
68
116
|
response: typeof latest?.response === 'string' ? latest.response : '',
|
|
117
|
+
failureReason: sanitizeFailureReason(latestFailure?.error),
|
|
69
118
|
progressRevision: count(latest?.progressRevision),
|
|
70
119
|
messages,
|
|
71
120
|
};
|
|
@@ -109,6 +158,7 @@ function projectAction(action, runs) {
|
|
|
109
158
|
loopCount: execution.loopCount,
|
|
110
159
|
toolCount: execution.toolCount,
|
|
111
160
|
response: execution.response,
|
|
161
|
+
failureReason: execution.failureReason,
|
|
112
162
|
progressRevision: execution.progressRevision,
|
|
113
163
|
messages: execution.messages,
|
|
114
164
|
};
|
|
@@ -125,8 +175,11 @@ function projectActionStats(detail) {
|
|
|
125
175
|
loopCount: projected.loopCount,
|
|
126
176
|
toolCount: projected.toolCount,
|
|
127
177
|
response: projected.response,
|
|
178
|
+
failureReason: projected.failureReason,
|
|
128
179
|
progressRevision: projected.progressRevision,
|
|
129
|
-
messages: projected.messages
|
|
180
|
+
messages: projected.messages
|
|
181
|
+
.map(({ failureReason, ...message }) => message)
|
|
182
|
+
.filter(message => message.text),
|
|
130
183
|
};
|
|
131
184
|
});
|
|
132
185
|
}
|
|
@@ -139,6 +192,15 @@ function waitingReason(detail) {
|
|
|
139
192
|
))?.waitingReason || '';
|
|
140
193
|
}
|
|
141
194
|
|
|
195
|
+
function workItemFailureReason(detail) {
|
|
196
|
+
if (!['needs_attention', 'cancelled'].includes(detail?.status) || !Array.isArray(detail?.runs)) return '';
|
|
197
|
+
const ordered = [...detail.runs].sort((left, right) => (
|
|
198
|
+
count(right.endedAt || right.startedAt) - count(left.endedAt || left.startedAt)
|
|
199
|
+
));
|
|
200
|
+
const current = ordered.find(run => run?.actionId === detail.currentActionId && sanitizeFailureReason(run.error));
|
|
201
|
+
return sanitizeFailureReason(current?.error || ordered.find(run => sanitizeFailureReason(run?.error))?.error);
|
|
202
|
+
}
|
|
203
|
+
|
|
142
204
|
/**
|
|
143
205
|
* Authenticated browser detail DTO. Raw execution records stay Agent-local;
|
|
144
206
|
* the browser receives only aggregate execution stats plus the explicit user-facing response.
|
|
@@ -160,6 +222,7 @@ export function projectWorkItemDetail(detail) {
|
|
|
160
222
|
executionStats: sumExecutionStats(Array.isArray(detail.runs) ? detail.runs : []),
|
|
161
223
|
reuseMemory: detail.reuseMemory !== false,
|
|
162
224
|
waitingReason: waitingReason(detail),
|
|
225
|
+
failureReason: workItemFailureReason(detail),
|
|
163
226
|
origin: detail.origin?.sessionId ? { sessionId: detail.origin.sessionId } : null,
|
|
164
227
|
linkedSessionIds: Array.isArray(detail.linkedSessionIds) ? detail.linkedSessionIds : [],
|
|
165
228
|
attachments: projectAttachments(detail.attachments),
|
|
@@ -213,6 +276,7 @@ export function projectWorkItemSummary(detail) {
|
|
|
213
276
|
status: detail.status,
|
|
214
277
|
currentActionId: detail.currentActionId || null,
|
|
215
278
|
executionStats: sumExecutionStats(Array.isArray(detail.runs) ? detail.runs : []),
|
|
279
|
+
failureReason: workItemFailureReason(detail),
|
|
216
280
|
currentAction: projectedAction ? {
|
|
217
281
|
id: projectedAction.id,
|
|
218
282
|
type: projectedAction.type,
|
|
@@ -327,7 +327,7 @@ function completionContract(action, workItem) {
|
|
|
327
327
|
? ',\n "contractPatch": { "goal": "optional refined goal", "acceptanceCriteria": ["optional refined criterion"] }'
|
|
328
328
|
: '';
|
|
329
329
|
const planField = action.type === 'triage' && workItem?.workflowSnapshot?.planningMode === 'ai'
|
|
330
|
-
? ',\n "plan": { "workItemType": "specific-lowercase-slug", "actions": [{ "id": "stable-id", "name": "User-facing name", "type": "extensible-lowercase-slug (built-ins include research|design|diagnose|implement|migrate|test|review|document|operate|deliver|integrate|write|custom)", "capability": "specific executor capability", "objective": "
|
|
330
|
+
? ',\n "plan": { "workItemType": "specific-lowercase-slug", "actions": [{ "id": "stable-id", "name": "User-facing name", "type": "extensible-lowercase-slug (built-ins include research|design|diagnose|implement|migrate|test|review|document|operate|deliver|integrate|write|custom)", "capability": "specific executor capability", "objective": "task-specific concrete work this Action must do", "approach": "task-specific repository-aware method the executor must follow", "expectedOutcome": "task-specific verifiable result this Action must produce", "dependsOnActionIds": ["earlier Action id; [] means concurrent root"], "workspaceMode": "read|isolated-write|integrate|shared", "separateFromActionTypes": ["optional prior Action type"], "changesRequestedActionId": "for review: optional earlier editable Action id; omit to use nearest", "maxAttempts": 2 }] }'
|
|
331
331
|
: '';
|
|
332
332
|
const acceptanceChecks = (workItem?.acceptanceCriteria || []).map(criterion => ({
|
|
333
333
|
criterion,
|
|
@@ -341,7 +341,7 @@ export function resolvePlanningWorkflowSnapshot(settings, requestedWorkItemType
|
|
|
341
341
|
const typeInstruction = requestedType
|
|
342
342
|
? `The user explicitly selected workItemType "${requestedType}". Keep that exact type.`
|
|
343
343
|
: 'Infer one specific workItemType from the contract.';
|
|
344
|
-
const triageInstruction = `${normalized.actionInstructions.triage}\n\n${typeInstruction}\nReusable Action templates:\n${catalog || '(none)'}\nIf the final type matches a reusable template, return its workItemType and omit actions so Work Center can apply that frozen template. Otherwise generate the smallest reliable graph of 1 to 8 Actions. Split independent work into separate Actions and declare dependsOnActionIds. Use workspaceMode read for analysis, isolated-write for independent Git changes, integrate for an integrate Action that combines isolated-write dependencies, and shared for serial side effects. Non-Git or dirty workspaces are serialized automatically. Every generated Action must state objective, approach, expectedOutcome, capability, dependencies, and workspaceMode. Add only Actions required by this task. Do not copy a generic workflow.`;
|
|
344
|
+
const triageInstruction = `${normalized.actionInstructions.triage}\n\n${typeInstruction}\nReusable Action templates:\n${catalog || '(none)'}\nIf the final type matches a reusable template, return its workItemType and omit actions so Work Center can apply that frozen template. Otherwise generate the smallest reliable graph of 1 to 8 Actions. Split independent work into separate Actions and declare dependsOnActionIds. Use workspaceMode read for analysis, isolated-write for independent Git changes, integrate for an integrate Action that combines isolated-write dependencies, and shared for serial side effects. Non-Git or dirty workspaces are serialized automatically. Every generated Action must state objective, approach, expectedOutcome, capability, dependencies, and workspaceMode. The objective, approach, and expectedOutcome must be specific to this WorkItem and that Action: describe the concrete work, the repository-aware execution method, and the verifiable result that will guide the executor. Generic Action-type boilerplate is invalid. Add only Actions required by this task. Do not copy a generic workflow.`;
|
|
345
345
|
return normalizeWorkflowDefinition({
|
|
346
346
|
id: 'ai-planned',
|
|
347
347
|
name: 'AI planned',
|
|
@@ -434,14 +434,20 @@ export function applyGeneratedPlan(workItem, rawPlan) {
|
|
|
434
434
|
if (seen.has(id)) throw new Error(`Duplicate AI-planned Action id: ${id}`);
|
|
435
435
|
seen.add(id);
|
|
436
436
|
const objective = typeof input.objective === 'string' ? input.objective.trim().slice(0, 2_000) : '';
|
|
437
|
-
if (!objective) throw new Error(`AI-planned Action "${id}" requires
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
: defaults.approach;
|
|
442
|
-
const expectedOutcome = typeof input.expectedOutcome === 'string' && input.expectedOutcome.trim()
|
|
437
|
+
if (!objective) throw new Error(`AI-planned Action "${id}" requires a task-specific objective`);
|
|
438
|
+
const approach = typeof input.approach === 'string' ? input.approach.trim().slice(0, 2_000) : '';
|
|
439
|
+
if (!approach) throw new Error(`AI-planned Action "${id}" requires a task-specific approach`);
|
|
440
|
+
const expectedOutcome = typeof input.expectedOutcome === 'string'
|
|
443
441
|
? input.expectedOutcome.trim().slice(0, 2_000)
|
|
444
|
-
:
|
|
442
|
+
: '';
|
|
443
|
+
if (!expectedOutcome) {
|
|
444
|
+
throw new Error(`AI-planned Action "${id}" requires a task-specific expectedOutcome`);
|
|
445
|
+
}
|
|
446
|
+
const defaults = defaultActionBrief(type);
|
|
447
|
+
if (objective === defaults.objective || approach === defaults.approach
|
|
448
|
+
|| expectedOutcome === defaults.expectedOutcome) {
|
|
449
|
+
throw new Error(`AI-planned Action "${id}" must not reuse generic Action-type brief text`);
|
|
450
|
+
}
|
|
445
451
|
const actionInstruction = Object.hasOwn(source.actionInstructions, type)
|
|
446
452
|
? source.actionInstructions[type]
|
|
447
453
|
: source.actionInstructions.custom;
|