@yeaft/webchat-agent 1.0.411 → 1.0.412
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/package.json +1 -1
- package/yeaft/engine.js +46 -2
- package/yeaft/llm/adapter.js +23 -0
- package/yeaft/llm/anthropic.js +3 -0
- package/yeaft/llm/openai-responses.js +3 -0
- package/yeaft/web-bridge.js +7 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"1.0.
|
|
1
|
+
{"version":"1.0.412"}
|
package/package.json
CHANGED
package/yeaft/engine.js
CHANGED
|
@@ -22,7 +22,7 @@ import { promises as fsp } from 'fs';
|
|
|
22
22
|
import { join, resolve as resolvePath } from 'path';
|
|
23
23
|
import { buildSystemPrompt, buildWorkerPrompt } from './prompts.js';
|
|
24
24
|
import { getRuntimePlatformInfo } from './runtime-platform.js';
|
|
25
|
-
import { LLMContextError, LLMAbortError, LLMAuthError, LLMRateLimitError, LLMServerError, LLMStreamIdleTimeoutError } from './llm/adapter.js';
|
|
25
|
+
import { LLMContextError, LLMAbortError, LLMAuthError, LLMPolicyError, LLMRateLimitError, LLMServerError, LLMStreamIdleTimeoutError } from './llm/adapter.js';
|
|
26
26
|
import { runMemoryPreflow, buildRelevantScopes, memoryScopeLabel } from './sessions/pre-flow.js';
|
|
27
27
|
import {
|
|
28
28
|
readProjectDoc,
|
|
@@ -155,6 +155,8 @@ const RETRY_DEFAULTS = Object.freeze({
|
|
|
155
155
|
|
|
156
156
|
const RETRY_CONTINUATION_PROMPT =
|
|
157
157
|
'Continue from the exact point where the previous response stopped. Do not repeat text already produced.';
|
|
158
|
+
const POLICY_RECOVERY_PROMPT =
|
|
159
|
+
'Continue this authorized code review, but describe security findings abstractly. Do not repeat credential-like or exploit payloads, secrets, tokens, or step-by-step misuse instructions. Preserve the technical conclusion, evidence location, severity, and remediation.';
|
|
158
160
|
|
|
159
161
|
// Accept legacy namespaced commands and Claude Code-style bare skill commands.
|
|
160
162
|
// Project-tier skills are shown as /<skill-name>; /yeaft-skills:<name> and
|
|
@@ -2791,6 +2793,7 @@ export class Engine {
|
|
|
2791
2793
|
let retryPolicy = resolveRetryPolicy(this.#config);
|
|
2792
2794
|
let consecutiveRetryableErrors = 0;
|
|
2793
2795
|
let consecutiveForbiddenErrors = 0;
|
|
2796
|
+
let contentPolicyRecoveryAttempts = 0;
|
|
2794
2797
|
|
|
2795
2798
|
while (true) {
|
|
2796
2799
|
turnNumber++;
|
|
@@ -3442,17 +3445,42 @@ export class Engine {
|
|
|
3442
3445
|
errorName: err?.name || null,
|
|
3443
3446
|
statusCode: err?.statusCode ?? null,
|
|
3444
3447
|
retryable: err instanceof LLMRateLimitError || err instanceof LLMServerError,
|
|
3448
|
+
reasonCode: err?.reasonCode || null,
|
|
3445
3449
|
message: String(err?.message || '').slice(0, 200),
|
|
3446
3450
|
},
|
|
3447
3451
|
});
|
|
3448
3452
|
|
|
3449
3453
|
const earlyIsRateLimit = err instanceof LLMRateLimitError;
|
|
3450
3454
|
const earlyIsTransient = err instanceof LLMServerError;
|
|
3455
|
+
const earlyIsContentPolicy = err instanceof LLMPolicyError;
|
|
3451
3456
|
// A completed tool_call has already crossed the streaming boundary to
|
|
3452
3457
|
// the caller. Replaying that request would publish a duplicate call and
|
|
3453
3458
|
// leave ambiguous execution ownership, so only pre-tool failures are
|
|
3454
3459
|
// eligible for transparent retry or model fallback.
|
|
3455
3460
|
const canReplayProviderRequest = toolCalls.length === 0;
|
|
3461
|
+
if (earlyIsContentPolicy && canReplayProviderRequest && contentPolicyRecoveryAttempts === 0) {
|
|
3462
|
+
contentPolicyRecoveryAttempts = 1;
|
|
3463
|
+
endAttemptTrace('llm_retry');
|
|
3464
|
+
if (responseText) prepareRetryContinuation();
|
|
3465
|
+
retryLifecycle.pendingContinuation = {
|
|
3466
|
+
role: 'user',
|
|
3467
|
+
content: POLICY_RECOVERY_PROMPT,
|
|
3468
|
+
userAuthored: false,
|
|
3469
|
+
};
|
|
3470
|
+
yield {
|
|
3471
|
+
type: 'llm_retry',
|
|
3472
|
+
attempt: 1,
|
|
3473
|
+
maxRetries: 1,
|
|
3474
|
+
delayMs: 0,
|
|
3475
|
+
reason: 'content_policy_recovery',
|
|
3476
|
+
recoveryMode: 'continue',
|
|
3477
|
+
errorName: err.name,
|
|
3478
|
+
statusCode: err.statusCode ?? 422,
|
|
3479
|
+
message: 'Provider content-safety rejection; retrying once with sensitive examples abstracted.',
|
|
3480
|
+
};
|
|
3481
|
+
yield { type: 'turn_end', turnNumber, stopReason: 'llm_retry', threadId };
|
|
3482
|
+
continue;
|
|
3483
|
+
}
|
|
3456
3484
|
const earlyIsTemporaryForbidden = err instanceof LLMAuthError
|
|
3457
3485
|
&& err.statusCode === 403
|
|
3458
3486
|
&& err.temporary === true;
|
|
@@ -3640,9 +3668,25 @@ export class Engine {
|
|
|
3640
3668
|
&& consecutiveRetryableErrors >= retryPolicy.maxRetries;
|
|
3641
3669
|
errorEvent.retryAttempts = consecutiveRetryableErrors;
|
|
3642
3670
|
errorEvent.maxRetries = retryPolicy.maxRetries;
|
|
3671
|
+
} else if (err instanceof LLMPolicyError) {
|
|
3672
|
+
errorEvent.reason = 'content_policy_denied';
|
|
3673
|
+
errorEvent.retryExhausted = contentPolicyRecoveryAttempts >= 1;
|
|
3674
|
+
errorEvent.retryAttempts = contentPolicyRecoveryAttempts;
|
|
3675
|
+
errorEvent.maxRetries = 1;
|
|
3643
3676
|
}
|
|
3644
3677
|
yield errorEvent;
|
|
3645
|
-
yield {
|
|
3678
|
+
yield {
|
|
3679
|
+
type: 'turn_end',
|
|
3680
|
+
turnNumber,
|
|
3681
|
+
stopReason: 'error',
|
|
3682
|
+
threadId,
|
|
3683
|
+
terminal: true,
|
|
3684
|
+
detail: {
|
|
3685
|
+
errorName: err?.name || 'Error',
|
|
3686
|
+
statusCode: err?.statusCode ?? null,
|
|
3687
|
+
reason: errorEvent.reason || err?.reasonCode || null,
|
|
3688
|
+
},
|
|
3689
|
+
};
|
|
3646
3690
|
break;
|
|
3647
3691
|
}
|
|
3648
3692
|
|
package/yeaft/llm/adapter.js
CHANGED
|
@@ -140,6 +140,29 @@ export function classifyAuthError(statusCode, responseBody = '', details = {}) {
|
|
|
140
140
|
});
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/** Provider content-safety rejection (commonly 422) — eligible for one sanitized recovery. */
|
|
144
|
+
export class LLMPolicyError extends Error {
|
|
145
|
+
constructor(_providerMessage, statusCode = 422, details = {}) {
|
|
146
|
+
super('The LLM provider blocked this request under its content-safety policy. Continue and avoid repeating sensitive payloads or credential-like examples.');
|
|
147
|
+
this.name = 'LLMPolicyError';
|
|
148
|
+
this.statusCode = statusCode;
|
|
149
|
+
this.reasonCode = 'content_policy_denied';
|
|
150
|
+
this.provider = details.provider || null;
|
|
151
|
+
this.model = details.model || null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const CONTENT_POLICY_RE = /(?:content (?:was )?flagged|content[-_ ]?safety|cybersecurity risk|safety policy|safety system|content[_ -]?filter|policy[_ -]?violation)/i;
|
|
156
|
+
|
|
157
|
+
export function classifyPolicyError(statusCode, responseBody = '', details = {}) {
|
|
158
|
+
const status = Number(statusCode) || 0;
|
|
159
|
+
const signals = providerErrorSignals(responseBody);
|
|
160
|
+
if (status !== 422 || (!CONTENT_POLICY_RE.test(signals.code) && !CONTENT_POLICY_RE.test(signals.message))) {
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
return new LLMPolicyError(signals.message, status, details);
|
|
164
|
+
}
|
|
165
|
+
|
|
143
166
|
/** Context too long error (413 or API-specific) — need compaction. */
|
|
144
167
|
export class LLMContextError extends Error {
|
|
145
168
|
constructor(message) {
|
package/yeaft/llm/anthropic.js
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
retryAfterFromResponse,
|
|
14
14
|
LLMAuthError,
|
|
15
15
|
classifyAuthError,
|
|
16
|
+
classifyPolicyError,
|
|
16
17
|
LLMContextError,
|
|
17
18
|
LLMServerError,
|
|
18
19
|
LLMAbortError,
|
|
@@ -230,6 +231,8 @@ export class AnthropicAdapter extends LLMAdapter {
|
|
|
230
231
|
const retryAfter = retryAfterFromResponse(response);
|
|
231
232
|
return new LLMRateLimitError(`Anthropic overloaded (${authHint}): ${body}`, status, retryAfter);
|
|
232
233
|
}
|
|
234
|
+
const policyError = classifyPolicyError(status, body);
|
|
235
|
+
if (policyError) return policyError;
|
|
233
236
|
if (body.includes('prompt is too long') || body.includes('max_tokens')) {
|
|
234
237
|
return new LLMContextError(`Anthropic context error (${authHint}): ${body}`);
|
|
235
238
|
}
|
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
LLMRateLimitError,
|
|
31
31
|
LLMAuthError,
|
|
32
32
|
classifyAuthError,
|
|
33
|
+
classifyPolicyError,
|
|
33
34
|
LLMContextError,
|
|
34
35
|
LLMServerError,
|
|
35
36
|
LLMAbortError,
|
|
@@ -219,6 +220,8 @@ export class OpenAIResponsesAdapter extends LLMAdapter {
|
|
|
219
220
|
const retryAfter = retryAfterFromResponse(response);
|
|
220
221
|
return new LLMRateLimitError(`Overloaded: ${body}`, status, retryAfter);
|
|
221
222
|
}
|
|
223
|
+
const policyError = classifyPolicyError(status, body);
|
|
224
|
+
if (policyError) return policyError;
|
|
222
225
|
if (status === 413 || body.includes('context_length_exceeded') || body.includes('maximum context length')) {
|
|
223
226
|
return new LLMContextError(`Context too long: ${body}`);
|
|
224
227
|
}
|
package/yeaft/web-bridge.js
CHANGED
|
@@ -4464,9 +4464,13 @@ function handleEngineEvent(event, hctx) {
|
|
|
4464
4464
|
const errMsg = event.error?.message || 'Unknown error';
|
|
4465
4465
|
const retryAttempts = Number.isFinite(event.retryAttempts) ? event.retryAttempts : 0;
|
|
4466
4466
|
const exhaustedIdle = event.reason === 'stream_idle_timeout' && event.retryExhausted;
|
|
4467
|
-
const
|
|
4468
|
-
|
|
4469
|
-
|
|
4467
|
+
const contentPolicyDenied = event.reason === 'content_policy_denied'
|
|
4468
|
+
|| event.error?.reasonCode === 'content_policy_denied';
|
|
4469
|
+
const visibleErrMsg = contentPolicyDenied
|
|
4470
|
+
? 'Provider blocked this request for content-safety reasons after one safe recovery attempt. Continue and ask the VP to avoid repeating sensitive payloads, credentials, tokens, or exploit samples.'
|
|
4471
|
+
: exhaustedIdle && retryAttempts > 0
|
|
4472
|
+
? `${errMsg} after ${retryAttempts} fresh request retries`
|
|
4473
|
+
: errMsg;
|
|
4470
4474
|
hctx.lastEngineErrorDetail = {
|
|
4471
4475
|
message: visibleErrMsg,
|
|
4472
4476
|
...(event.reason ? { reason: event.reason } : {}),
|