clementine-agent 1.18.24 → 1.18.25
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/dist/agent/assistant.js
CHANGED
|
@@ -5863,6 +5863,21 @@ You have a cost budget per message — not a hard turn limit. Work until the tas
|
|
|
5863
5863
|
}
|
|
5864
5864
|
logger.error({ err, jobName, phase }, `Unleashed task phase ${phase} error`);
|
|
5865
5865
|
appendProgress({ event: 'phase_error', phase, error: String(err), terminalReason });
|
|
5866
|
+
if (isCreditBalanceError(err)) {
|
|
5867
|
+
markBackgroundCreditBlocked(err);
|
|
5868
|
+
appendProgress({ event: 'aborted', phase, reason: 'account_usage_limit' });
|
|
5869
|
+
writeStatus({
|
|
5870
|
+
jobName,
|
|
5871
|
+
status: 'error',
|
|
5872
|
+
phase,
|
|
5873
|
+
startedAt,
|
|
5874
|
+
finishedAt: new Date().toISOString(),
|
|
5875
|
+
});
|
|
5876
|
+
const message = (`Task "${jobName}" stopped because Claude account usage or billing is blocked: ${String(err).slice(0, 500)}. ` +
|
|
5877
|
+
'Background jobs have been paused so Clementine does not keep retrying against the same account limit.');
|
|
5878
|
+
logger.error({ jobName, phase }, 'Unleashed task aborted on Claude account usage limit');
|
|
5879
|
+
throw new UnleashedTaskFailedError(message, this._lastTerminalReason);
|
|
5880
|
+
}
|
|
5866
5881
|
if (terminalReason === 'rapid_refill_breaker' || terminalReason === 'prompt_too_long') {
|
|
5867
5882
|
appendProgress({ event: 'aborted', phase, reason: terminalReason });
|
|
5868
5883
|
writeStatus({
|
package/dist/cli/dashboard.js
CHANGED
|
@@ -17625,18 +17625,27 @@ async function apiFetch(url, opts) {
|
|
|
17625
17625
|
}
|
|
17626
17626
|
// 401 means the dashboard was restarted and regenerated its token.
|
|
17627
17627
|
// The page still in your browser has a stale one baked into <meta>,
|
|
17628
|
-
// so every API call silently fails. Reload
|
|
17629
|
-
//
|
|
17628
|
+
// so every API call silently fails. Reload to pick up the new HTML
|
|
17629
|
+
// (and new token), but throttle by token so a tab cannot get stuck
|
|
17630
|
+
// forever after one earlier restart.
|
|
17630
17631
|
if (resp.status === 401) {
|
|
17631
|
-
var key = '
|
|
17632
|
-
|
|
17633
|
-
|
|
17632
|
+
var key = '_dashReloadedForToken:' + (_dashToken || 'missing').slice(0, 12);
|
|
17633
|
+
var lastReload = Number(sessionStorage.getItem(key) || '0');
|
|
17634
|
+
var now = Date.now();
|
|
17635
|
+
if (!lastReload || now - lastReload > 5000) {
|
|
17636
|
+
sessionStorage.setItem(key, String(now));
|
|
17634
17637
|
console.warn('Dashboard token expired — reloading to refresh.');
|
|
17635
17638
|
location.reload();
|
|
17636
17639
|
// Let the reload kick in; return the 401 so any caller that
|
|
17637
17640
|
// handles it sees a consistent result until the page unloads.
|
|
17638
17641
|
return resp;
|
|
17639
17642
|
}
|
|
17643
|
+
} else if (resp.ok) {
|
|
17644
|
+
try {
|
|
17645
|
+
Object.keys(sessionStorage).forEach(function(k) {
|
|
17646
|
+
if (k.indexOf('_dashReloadedForToken:') === 0) sessionStorage.removeItem(k);
|
|
17647
|
+
});
|
|
17648
|
+
} catch (_) { /* ignore */ }
|
|
17640
17649
|
}
|
|
17641
17650
|
return resp;
|
|
17642
17651
|
}
|
|
@@ -5,7 +5,7 @@ const CREDIT_BLOCK_FILE = path.join(BASE_DIR, 'cron', 'credit-block.json');
|
|
|
5
5
|
const DEFAULT_BLOCK_MS = 6 * 60 * 60 * 1000;
|
|
6
6
|
export function isCreditBalanceError(err) {
|
|
7
7
|
const msg = String(err ?? '');
|
|
8
|
-
return /credit balance is too low|credit balance.*too low|insufficient credits?|billing.*credits?|account.*credits?.*low/i.test(msg);
|
|
8
|
+
return /credit balance is too low|credit balance.*too low|insufficient credits?|billing.*credits?|account.*credits?.*low|monthly usage limit|org'?s monthly usage limit|organization'?s monthly usage limit|hit your .*usage limit|usage limit.*(?:reached|exceeded|hit|active)|usage or credit limit|credit limit is active|spending limit|billing limit/i.test(msg);
|
|
9
9
|
}
|
|
10
10
|
export function getBackgroundCreditBlock(nowMs = Date.now()) {
|
|
11
11
|
try {
|
|
@@ -41,6 +41,6 @@ export function markBackgroundCreditBlocked(err, nowMs = Date.now()) {
|
|
|
41
41
|
return { block, created: true };
|
|
42
42
|
}
|
|
43
43
|
export function formatCreditBlock(block) {
|
|
44
|
-
return `Claude credit
|
|
44
|
+
return `Claude account usage or credit limit is active. Background jobs are paused until ${block.until}.`;
|
|
45
45
|
}
|
|
46
46
|
//# sourceMappingURL=credit-guard.js.map
|
|
@@ -16,6 +16,7 @@ import path from 'node:path';
|
|
|
16
16
|
import pino from 'pino';
|
|
17
17
|
import { AGENTS_DIR, BASE_DIR, CRON_FILE } from '../config.js';
|
|
18
18
|
import { loadPromptOverrides, loadPromptOverridesForJob } from '../agent/prompt-overrides/loader.js';
|
|
19
|
+
import { isCreditBalanceError } from './credit-guard.js';
|
|
19
20
|
const logger = pino({ name: 'clementine.failure-diagnostics' });
|
|
20
21
|
const CACHE_FILE = path.join(BASE_DIR, 'cron', 'failure-diagnostics.json');
|
|
21
22
|
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
@@ -297,6 +298,18 @@ export function diagnoseKnownFailurePattern(broken, jobDef, recentRuns, opts) {
|
|
|
297
298
|
...broken.lastErrors,
|
|
298
299
|
recentRuns,
|
|
299
300
|
].join('\n').toLowerCase();
|
|
301
|
+
if (isCreditBalanceError(haystack)) {
|
|
302
|
+
return {
|
|
303
|
+
rootCause: 'Claude is blocked by an org usage or billing limit, so this is not a job-definition failure.',
|
|
304
|
+
confidence: 'high',
|
|
305
|
+
proposedFix: {
|
|
306
|
+
type: 'escalate_to_owner',
|
|
307
|
+
details: 'Keep background jobs paused until the Claude org usage limit resets or billing capacity is restored. Do not retry or auto-apply job fixes for this error.',
|
|
308
|
+
},
|
|
309
|
+
riskLevel: 'low',
|
|
310
|
+
generatedAt: new Date().toISOString(),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
300
313
|
if (/rapid_refill_breaker|autocompact.*thrash|context refilled|prompt is too long|prompt too long|context.?length|maximum context|input is too long/.test(haystack)) {
|
|
301
314
|
if (hasContextOverflowPromptOverride(broken.jobName, broken.agentSlug, opts)) {
|
|
302
315
|
return {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { CronRunEntry } from '../types.js';
|
|
2
|
-
export type JobHealthKind = 'healthy' | 'recovered' | 'partial' | 'context_overflow' | 'auth' | 'rate_limited' | 'tool_scope' | 'prompt_too_large' | 'failed' | 'unknown';
|
|
2
|
+
export type JobHealthKind = 'healthy' | 'recovered' | 'partial' | 'context_overflow' | 'usage_blocked' | 'auth' | 'rate_limited' | 'tool_scope' | 'prompt_too_large' | 'failed' | 'unknown';
|
|
3
3
|
export interface JobHealthStatus {
|
|
4
4
|
status: JobHealthKind;
|
|
5
5
|
jobName?: string;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isCreditBalanceError } from './credit-guard.js';
|
|
1
2
|
function compactEvidence(...items) {
|
|
2
3
|
const seen = new Set();
|
|
3
4
|
const result = [];
|
|
@@ -21,6 +22,15 @@ export function classifyRunHealth(entry) {
|
|
|
21
22
|
lastRunAt: entry.startedAt,
|
|
22
23
|
terminalReason,
|
|
23
24
|
};
|
|
25
|
+
if (isCreditBalanceError(blob)) {
|
|
26
|
+
return {
|
|
27
|
+
...base,
|
|
28
|
+
status: 'usage_blocked',
|
|
29
|
+
evidence: compactEvidence(entry.error, entry.outputPreview, terminalReason ? `terminalReason=${terminalReason}` : undefined),
|
|
30
|
+
recommendedAction: 'Resolve the Claude org usage or billing limit, or switch this job to an available provider/model before retrying.',
|
|
31
|
+
requiresApproval: false,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
24
34
|
if (terminalReason === 'rapid_refill_breaker' || /rapid_refill_breaker|autocompact|context refilled|maximum context|context.?length/.test(blob)) {
|
|
25
35
|
return {
|
|
26
36
|
...base,
|
|
@@ -112,6 +122,6 @@ export function classifyRunHealth(entry) {
|
|
|
112
122
|
}
|
|
113
123
|
export function isRunHealthFailure(entry) {
|
|
114
124
|
const health = classifyRunHealth(entry);
|
|
115
|
-
return !['healthy', 'recovered', 'unknown'].includes(health.status);
|
|
125
|
+
return !['healthy', 'recovered', 'unknown', 'usage_blocked'].includes(health.status);
|
|
116
126
|
}
|
|
117
127
|
//# sourceMappingURL=job-health.js.map
|