pikiclaw 0.3.83 → 0.3.84
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/drivers/claude-tui.js +81 -22
- package/dist/agent/drivers/claude.js +21 -3
- package/dist/agent/index.js +1 -1
- package/dist/agent/utils.js +31 -0
- package/dist/core/constants.js +9 -0
- package/package.json +1 -1
|
@@ -40,11 +40,11 @@ import fs from 'node:fs';
|
|
|
40
40
|
import path from 'node:path';
|
|
41
41
|
import { randomUUID } from 'node:crypto';
|
|
42
42
|
import { tmpdir } from 'node:os';
|
|
43
|
-
import { Q, agentLog, agentWarn, buildStreamPreviewMeta, computeContext, joinErrorMessages, emitSessionIdUpdate, normalizeClaudeModelId, pushRecentActivity, summarizeClaudeToolUse, summarizeClaudeToolResult, previewToolCallInput, previewToolCallResult, detectClaudeApiError, } from '../utils.js';
|
|
43
|
+
import { Q, agentLog, agentWarn, buildStreamPreviewMeta, computeContext, joinErrorMessages, emitSessionIdUpdate, normalizeClaudeModelId, pushRecentActivity, summarizeClaudeToolUse, summarizeClaudeToolResult, previewToolCallInput, previewToolCallResult, detectClaudeApiError, detectClaudeModelError, claudeModelErrorMessage, } from '../utils.js';
|
|
44
44
|
import { encodePathAsDirName, getHome, whichSync } from '../../core/platform.js';
|
|
45
45
|
import { createRetainedLogSink } from '../../core/logging.js';
|
|
46
46
|
import { stripAnsiEscapes } from '../../core/utils.js';
|
|
47
|
-
import { AGENT_STREAM_HARD_KILL_GRACE_MS, CLAUDE_TUI_STALL_QUIET_MS, CLAUDE_TUI_STALL_PENDING_TOOL_MS, CLAUDE_TUI_STALL_PTY_DEAD_MS, CLAUDE_TUI_STOP_HOLD_QUIET_TTL_MS, } from '../../core/constants.js';
|
|
47
|
+
import { AGENT_STREAM_HARD_KILL_GRACE_MS, CLAUDE_TUI_STALL_QUIET_MS, CLAUDE_TUI_STALL_PENDING_TOOL_MS, CLAUDE_TUI_STALL_PTY_DEAD_MS, CLAUDE_TUI_STOP_HOLD_QUIET_TTL_MS, CLAUDE_TUI_MODEL_ERROR_SETTLE_MS, } from '../../core/constants.js';
|
|
48
48
|
import { claudeParse, createClaudeStreamState, claudeContextWindowFromModel, claudeEffectiveContextWindow, registerClaudeBackgroundAgentLaunch, pendingClaudeBackgroundAgentCount, registerClaudeBackgroundBashLaunch, pendingClaudeBackgroundBashCount, extractClaudeBackgroundTaskId, extractClaudeWorkflowRunId, claudeEffortAndWorkflowArgs, scrubClaudeSessionContextEnv, } from './claude.js';
|
|
49
49
|
// ---------------------------------------------------------------------------
|
|
50
50
|
// Stall diagnostics (capture-only)
|
|
@@ -1080,6 +1080,7 @@ export async function doClaudeTuiStream(opts) {
|
|
|
1080
1080
|
let exitSignal = null;
|
|
1081
1081
|
let terminalLimitNotice = null;
|
|
1082
1082
|
let terminalLimitNoticeAt = 0;
|
|
1083
|
+
let terminalModelError = null;
|
|
1083
1084
|
let proc;
|
|
1084
1085
|
const emit = () => {
|
|
1085
1086
|
try {
|
|
@@ -1116,6 +1117,36 @@ export async function doClaudeTuiStream(opts) {
|
|
|
1116
1117
|
s.activity = s.recentActivity.join('\n');
|
|
1117
1118
|
emit();
|
|
1118
1119
|
};
|
|
1120
|
+
// Selected-model-unavailable notice (404 model_not_found). Unlike the limit
|
|
1121
|
+
// banner this is terminal AND invisible to every structured signal: the TUI
|
|
1122
|
+
// paints it to the PTY screen, writes nothing to the JSONL, and fires no Stop
|
|
1123
|
+
// hook — so the turn would otherwise idle at the REPL until the 3–10 min stall
|
|
1124
|
+
// watchdog kills it with a misleading "CLI freeze" message. We surface the
|
|
1125
|
+
// real reason and end the turn now. The banner is still EVIDENCE, not a bare
|
|
1126
|
+
// verdict: a short settle confirms nothing substantive followed (cross-
|
|
1127
|
+
// validating the screen scrape) before we kill — never granting a lone text
|
|
1128
|
+
// match the authority to shoot a healthy turn.
|
|
1129
|
+
const noteTerminalModelError = (notice) => {
|
|
1130
|
+
if (terminalModelError)
|
|
1131
|
+
return;
|
|
1132
|
+
terminalModelError = notice;
|
|
1133
|
+
agentWarn(`[claude-tui] model unavailable observed (settling before terminate): ${notice}`);
|
|
1134
|
+
pushRecentActivity(s.recentActivity, notice);
|
|
1135
|
+
s.activity = s.recentActivity.join('\n');
|
|
1136
|
+
emit();
|
|
1137
|
+
setTimeout(() => {
|
|
1138
|
+
if (processExited || interrupted)
|
|
1139
|
+
return;
|
|
1140
|
+
const hadOutput = !!s.text.trim()
|
|
1141
|
+
|| lastAssistantEventAt > 0 || lastSidecarEventAt > 0 || lastToolEventAt > start;
|
|
1142
|
+
if (hadOutput) {
|
|
1143
|
+
agentWarn('[claude-tui] model-unavailable banner was followed by real output — not terminating');
|
|
1144
|
+
return;
|
|
1145
|
+
}
|
|
1146
|
+
agentWarn('[claude-tui] model unavailable confirmed (no JSONL/tool/Stop activity) — terminating turn');
|
|
1147
|
+
killProc('SIGTERM');
|
|
1148
|
+
}, CLAUDE_TUI_MODEL_ERROR_SETTLE_MS);
|
|
1149
|
+
};
|
|
1119
1150
|
// Simulated streaming. See TuiStreamBuffer / applyAssistantStreaming above.
|
|
1120
1151
|
const streamBuf = makeTuiStreamBuffer();
|
|
1121
1152
|
const scheduleStreamTick = () => {
|
|
@@ -1359,6 +1390,13 @@ export async function doClaudeTuiStream(opts) {
|
|
|
1359
1390
|
if (notice)
|
|
1360
1391
|
noteTerminalLimitNotice(notice);
|
|
1361
1392
|
}
|
|
1393
|
+
// Selected-model-unavailable notice — see noteTerminalModelError. The TUI
|
|
1394
|
+
// only paints this to the screen (no JSONL, no Stop hook), so the live
|
|
1395
|
+
// screen tail is the sole signal. detectClaudeModelError is whitespace-
|
|
1396
|
+
// insensitive so it survives the TUI's char-by-char paint.
|
|
1397
|
+
if (!terminalModelError && detectClaudeModelError(screenTail)) {
|
|
1398
|
+
noteTerminalModelError(claudeModelErrorMessage(s.model || opts.claudeModel || null));
|
|
1399
|
+
}
|
|
1362
1400
|
});
|
|
1363
1401
|
// 7. Abort handling.
|
|
1364
1402
|
const abortStream = () => {
|
|
@@ -1841,28 +1879,39 @@ export async function doClaudeTuiStream(opts) {
|
|
|
1841
1879
|
screenSample: stallScreen.sample,
|
|
1842
1880
|
});
|
|
1843
1881
|
if (!s.errors) {
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
lastSubstantiveEventAt: Math.max(lastAssistantEventAt, lastToolEventAt, lastSidecarEventAt),
|
|
1853
|
-
hasOutputText: !!s.text.trim(),
|
|
1854
|
-
});
|
|
1855
|
-
if (limitOutcome === 'fatal') {
|
|
1856
|
-
s.stopReason = 'rate_limit';
|
|
1857
|
-
s.errors = [terminalLimitNotice];
|
|
1882
|
+
if (terminalModelError && !s.text.trim()) {
|
|
1883
|
+
// Model-unavailable arbitration first: if the stall watchdog beat the
|
|
1884
|
+
// settle-timer (noteTerminalModelError) to the kill, the turn didn't
|
|
1885
|
+
// freeze — the selected model is disabled / no-access. Surface the real
|
|
1886
|
+
// reason; stopReason 'model_error' is non-retryable so doClaudeWithRetry
|
|
1887
|
+
// won't auto-resume into the same dead model.
|
|
1888
|
+
s.stopReason = 'model_error';
|
|
1889
|
+
s.errors = [terminalModelError];
|
|
1858
1890
|
}
|
|
1859
1891
|
else {
|
|
1860
|
-
//
|
|
1861
|
-
//
|
|
1862
|
-
//
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1892
|
+
// Limit-notice arbitration: a turn that showed a limit banner and then
|
|
1893
|
+
// produced nothing substantive didn't freeze — the limit ate it. Label
|
|
1894
|
+
// it rate_limit with the banner's own text (which carries the reset
|
|
1895
|
+
// time) so the user gets the real reason, and so doClaudeWithRetry
|
|
1896
|
+
// doesn't auto-resume into the same wall.
|
|
1897
|
+
const limitOutcome = resolveClaudeTuiLimitOutcome({
|
|
1898
|
+
noticeText: terminalLimitNotice,
|
|
1899
|
+
noticeAt: terminalLimitNoticeAt,
|
|
1900
|
+
lastSubstantiveEventAt: Math.max(lastAssistantEventAt, lastToolEventAt, lastSidecarEventAt),
|
|
1901
|
+
hasOutputText: !!s.text.trim(),
|
|
1902
|
+
});
|
|
1903
|
+
if (limitOutcome === 'fatal') {
|
|
1904
|
+
s.stopReason = 'rate_limit';
|
|
1905
|
+
s.errors = [terminalLimitNotice];
|
|
1906
|
+
}
|
|
1907
|
+
else {
|
|
1908
|
+
// Be honest about which kind of stall this is. looksLikePrompt here
|
|
1909
|
+
// means the auto-answer (detectClaudeProceedPrompt) did NOT clear an
|
|
1910
|
+
// interactive prompt — so it's a blocking dialog, not the CLI freeze.
|
|
1911
|
+
s.errors = [stallScreen.looksLikePrompt
|
|
1912
|
+
? `Claude blocked mid-turn on an interactive prompt (PTY quiet ${ptyQuietS}s) that auto-answer couldn't clear. Terminated for auto-resume.`
|
|
1913
|
+
: `Claude process went silent mid-turn for ${quietMin}m (no JSONL, hook, or sub-agent events; PTY quiet ${ptyQuietS}s) — known claude CLI freeze. Terminated for auto-resume.`];
|
|
1914
|
+
}
|
|
1866
1915
|
}
|
|
1867
1916
|
}
|
|
1868
1917
|
agentWarn(`[claude-tui] stall detected: no progress for ${quietMin}m (pendingTools=${pendingHookToolIds.size}, ptyQuiet=${ptyQuietS}s) — terminating TUI pid=${proc.pid}${s.stopReason === 'rate_limit' ? ' (usage limit)' : ' for auto-resume'}`);
|
|
@@ -1942,6 +1991,16 @@ export async function doClaudeTuiStream(opts) {
|
|
|
1942
1991
|
if (!s.errors)
|
|
1943
1992
|
s.errors = [`Anthropic API error: ${apiErrorReason}`];
|
|
1944
1993
|
}
|
|
1994
|
+
// Model-unavailable arbitration: the TUI painted the "selected model is
|
|
1995
|
+
// unavailable" banner (noteTerminalModelError) and the turn produced nothing
|
|
1996
|
+
// — no JSONL, no Stop hook. The early settle-timer's SIGTERM (or any exit)
|
|
1997
|
+
// brought us here; surface the real reason instead of a bare "(no textual
|
|
1998
|
+
// response)". stopReason 'model_error' is non-retryable (doClaudeWithRetry
|
|
1999
|
+
// only auto-resumes 'stalled'), so we never loop on the same dead model.
|
|
2000
|
+
if (!interrupted && !s.errors && terminalModelError && !s.text.trim()) {
|
|
2001
|
+
s.stopReason = 'model_error';
|
|
2002
|
+
s.errors = [terminalModelError];
|
|
2003
|
+
}
|
|
1945
2004
|
// Limit-notice arbitration (see resolveClaudeTuiLimitOutcome). Covers the
|
|
1946
2005
|
// paths the stall watchdog never reaches: the TUI painted a limit banner,
|
|
1947
2006
|
// then Stop fired on an empty turn or the process exited — nothing
|
|
@@ -8,7 +8,7 @@ import { createInterface } from 'node:readline';
|
|
|
8
8
|
import { registerDriver } from '../driver.js';
|
|
9
9
|
import {
|
|
10
10
|
// shared helpers
|
|
11
|
-
Q, run, agentError, agentLog, agentWarn, buildStreamPreviewMeta, computeContext, pushRecentActivity, summarizeClaudeToolUse, summarizeClaudeToolResult, joinErrorMessages, parseTodoWriteAsPlan, previewToolCallInput, previewToolCallResult, detectClaudeApiError, isRetryableClaudeApiError, emitSessionIdUpdate, IMAGE_EXTS, mimeForExt, listPikiclawSessions, mergeManagedAndNativeSessions, readTailLines, stripInjectedPrompts, sanitizeSessionUserPreviewText, SESSION_PREVIEW_IMAGE_PLACEHOLDER_RE, CLAUDE_AT_MENTION_IMAGE_RE, extractClaudeAtMentionImagePaths, attachAgentImage, applyTurnWindow, shortValue, roundPercent, modelFamily, normalizeClaudeModelId, emptyUsage, normalizeUsageStatus, collapseSkillPrompt, } from '../index.js';
|
|
11
|
+
Q, run, agentError, agentLog, agentWarn, buildStreamPreviewMeta, computeContext, pushRecentActivity, summarizeClaudeToolUse, summarizeClaudeToolResult, joinErrorMessages, parseTodoWriteAsPlan, previewToolCallInput, previewToolCallResult, detectClaudeApiError, isRetryableClaudeApiError, detectClaudeModelError, claudeModelErrorMessage, emitSessionIdUpdate, IMAGE_EXTS, mimeForExt, listPikiclawSessions, mergeManagedAndNativeSessions, readTailLines, stripInjectedPrompts, sanitizeSessionUserPreviewText, SESSION_PREVIEW_IMAGE_PLACEHOLDER_RE, CLAUDE_AT_MENTION_IMAGE_RE, extractClaudeAtMentionImagePaths, attachAgentImage, applyTurnWindow, shortValue, roundPercent, modelFamily, normalizeClaudeModelId, emptyUsage, normalizeUsageStatus, collapseSkillPrompt, } from '../index.js';
|
|
12
12
|
import { AGENT_STREAM_HARD_KILL_GRACE_MS, AGENT_GRACEFUL_ABORT_GRACE_MS, SESSION_RUNNING_THRESHOLD_MS } from '../../core/constants.js';
|
|
13
13
|
import { terminateProcessTree } from '../../core/process-control.js';
|
|
14
14
|
import { getHome, IS_MAC, encodePathAsDirName } from '../../core/platform.js';
|
|
@@ -652,8 +652,22 @@ export function claudeParse(ev, s) {
|
|
|
652
652
|
// model error, …), not real Claude output. The historical jsonl reader
|
|
653
653
|
// converts them into `system_notice` blocks; on the live stream we just
|
|
654
654
|
// drop them so they don't pollute s.text / s.thinking.
|
|
655
|
-
if (msg.model === '<synthetic>')
|
|
655
|
+
if (msg.model === '<synthetic>') {
|
|
656
|
+
// …except the "selected model is unavailable" notice (404 model_not_found):
|
|
657
|
+
// a hard, non-retryable failure. The result event's text fallback below
|
|
658
|
+
// also catches it, but recording s.errors here upgrades the turn from a
|
|
659
|
+
// bare "(no textual response)" reply to a clear error + non-retryable
|
|
660
|
+
// stopReason (so doClaudeWithRetry won't loop on the same dead model).
|
|
661
|
+
if (!s.errors) {
|
|
662
|
+
const synthText = (msg.content || [])
|
|
663
|
+
.filter((b) => b?.type === 'text').map((b) => b.text || '').join(' ');
|
|
664
|
+
if (ev.error === 'model_not_found' || detectClaudeModelError(synthText)) {
|
|
665
|
+
s.stopReason = 'model_error';
|
|
666
|
+
s.errors = [claudeModelErrorMessage(s.model)];
|
|
667
|
+
}
|
|
668
|
+
}
|
|
656
669
|
return;
|
|
670
|
+
}
|
|
657
671
|
const contents = msg.content || [];
|
|
658
672
|
const th = contents.filter((b) => b?.type === 'thinking').map((b) => b.thinking || '').join('\n\n');
|
|
659
673
|
const tx = contents.filter((b) => b?.type === 'text').map((b) => b.text || '').join('\n\n');
|
|
@@ -867,7 +881,11 @@ export function claudeParse(ev, s) {
|
|
|
867
881
|
s.errors = ev.errors;
|
|
868
882
|
if (ev.result && !s.text.trim())
|
|
869
883
|
s.text = ev.result;
|
|
870
|
-
|
|
884
|
+
// A model-unavailable turn carries a normal stop_reason on its result event
|
|
885
|
+
// (e.g. 'stop_sequence') that would otherwise clobber the 'model_error' the
|
|
886
|
+
// synthetic handler set — preserve it so the failure stays diagnosable.
|
|
887
|
+
if (s.stopReason !== 'model_error')
|
|
888
|
+
s.stopReason = ev.stop_reason ?? s.stopReason;
|
|
871
889
|
const u = ev.usage;
|
|
872
890
|
if (u) {
|
|
873
891
|
// Per-call semantics: the last message_start/message_delta snapshot is
|
package/dist/agent/index.js
CHANGED
|
@@ -21,7 +21,7 @@ export { IMAGE_EXTS } from './types.js';
|
|
|
21
21
|
// ── Re-export: image pipeline ──────────────────────────────────────────────
|
|
22
22
|
export { attachAgentImage, attachInlineImage, materializeImage, rewriteImageBlocksForTransport, resolveAllowedAttachmentPath, allowedAttachmentRoots, decodeAttachmentPathParam, sessionAttachmentsDir, codexHome, } from './images.js';
|
|
23
23
|
// ── Re-export: utilities ────────────────────────────────────────────────────
|
|
24
|
-
export { Q, agentLog, agentWarn, agentError, dedupeStrings, numberOrNull, normalizeStreamPreviewPlan, parseTodoWriteAsPlan, normalizeActivityLine, pushRecentActivity, detectClaudeApiError, isRetryableClaudeApiError, firstNonEmptyLine, shortValue, normalizeErrorMessage, joinErrorMessages, appendSystemPrompt, mimeForExt, computeContext, buildStreamPreviewMeta, summarizeClaudeToolUse, summarizeClaudeToolResult, previewToolCallInput, previewToolCallResult, roundPercent, toIsoFromEpochSeconds, normalizeUsageStatus, labelFromWindowMinutes, usageWindowFromRateLimit, parseJsonTail, modelFamily, normalizeClaudeModelId, emptyUsage, readTailLines, stripInjectedPrompts, sanitizeSessionUserPreviewText, SESSION_PREVIEW_IMAGE_PLACEHOLDER_RE, CLAUDE_AT_MENTION_IMAGE_RE, extractClaudeAtMentionImagePaths, stripClaudeAtMentionImages, isPendingSessionId, emitSessionIdUpdate, sessionListDisplayTitle, } from './utils.js';
|
|
24
|
+
export { Q, agentLog, agentWarn, agentError, dedupeStrings, numberOrNull, normalizeStreamPreviewPlan, parseTodoWriteAsPlan, normalizeActivityLine, pushRecentActivity, detectClaudeApiError, isRetryableClaudeApiError, detectClaudeModelError, claudeModelErrorMessage, firstNonEmptyLine, shortValue, normalizeErrorMessage, joinErrorMessages, appendSystemPrompt, mimeForExt, computeContext, buildStreamPreviewMeta, summarizeClaudeToolUse, summarizeClaudeToolResult, previewToolCallInput, previewToolCallResult, roundPercent, toIsoFromEpochSeconds, normalizeUsageStatus, labelFromWindowMinutes, usageWindowFromRateLimit, parseJsonTail, modelFamily, normalizeClaudeModelId, emptyUsage, readTailLines, stripInjectedPrompts, sanitizeSessionUserPreviewText, SESSION_PREVIEW_IMAGE_PLACEHOLDER_RE, CLAUDE_AT_MENTION_IMAGE_RE, extractClaudeAtMentionImagePaths, stripClaudeAtMentionImages, isPendingSessionId, emitSessionIdUpdate, sessionListDisplayTitle, } from './utils.js';
|
|
25
25
|
// ── Re-export: session management ───────────────────────────────────────────
|
|
26
26
|
export { updateSessionMeta, promoteSessionId, recordFork, listPikiclawSessions, findPikiclawSession, getSessionStoredConfig, ensureManagedSession, findManagedThreadSession, stageSessionFiles, mergeManagedAndNativeSessions, getSessions, getSessionTail, getSessionMessages, applyTurnWindow, applyTurnFilter, classifySession, deriveUserStatus, exportSession, importSession, deleteAgentSession, isProcessAlive, isRunningSessionStale, reconcileOrphanedRunningSessions, } from './session.js';
|
|
27
27
|
// ── Re-export: stream & detection ───────────────────────────────────────────
|
package/dist/agent/utils.js
CHANGED
|
@@ -204,6 +204,37 @@ export function isRetryableClaudeApiError(reason) {
|
|
|
204
204
|
return false;
|
|
205
205
|
return /overloaded|overload|timeout|timed out|500|502|503|504|529|temporar|gateway|connection|network|internal (server )?error/i.test(r);
|
|
206
206
|
}
|
|
207
|
+
/**
|
|
208
|
+
* Detect Claude Code's "selected model is unavailable" notice — emitted when
|
|
209
|
+
* the requested `--model` id is disabled / not provisioned for the account (a
|
|
210
|
+
* 404 model_not_found). Its delivery differs by mode:
|
|
211
|
+
* - `-p`/stream-json: a `<synthetic>` assistant event carrying
|
|
212
|
+
* `error:"model_not_found"` plus the banner text, and a `result` event with
|
|
213
|
+
* `is_error` — both reach the parser.
|
|
214
|
+
* - TUI: the banner is *only* painted to the PTY screen. It is never written
|
|
215
|
+
* to the transcript JSONL and fires no Stop hook (verified 2026-06-13), so
|
|
216
|
+
* the screen scrape is the sole signal and the turn would otherwise hang
|
|
217
|
+
* until the stall watchdog (3–10 min).
|
|
218
|
+
*
|
|
219
|
+
* Matching is whitespace-insensitive on purpose: the TUI renders the banner
|
|
220
|
+
* character-by-character with cursor positioning, so after ANSI stripping the
|
|
221
|
+
* words lose their spaces and wrap arbitrarily ("issuewiththeselectedmodel").
|
|
222
|
+
* Collapsing whitespace on both sides makes the match survive that rendering.
|
|
223
|
+
* Callers compose the user-facing message via `claudeModelErrorMessage` with
|
|
224
|
+
* the concrete model id they hold.
|
|
225
|
+
*/
|
|
226
|
+
export function detectClaudeModelError(text) {
|
|
227
|
+
if (!text)
|
|
228
|
+
return false;
|
|
229
|
+
const collapsed = text.replace(/\s+/g, '').toLowerCase();
|
|
230
|
+
return collapsed.includes('issuewiththeselectedmodel')
|
|
231
|
+
|| collapsed.includes('maynotexistoryoumaynothaveaccess');
|
|
232
|
+
}
|
|
233
|
+
/** User-facing message for an unavailable / no-access model (see {@link detectClaudeModelError}). */
|
|
234
|
+
export function claudeModelErrorMessage(model) {
|
|
235
|
+
const id = (model || '').trim();
|
|
236
|
+
return `The selected model${id ? ` (${id})` : ''} is unavailable — it may not exist, or this account doesn't have access to it. Switch to a different model in pikiclaw settings.`;
|
|
237
|
+
}
|
|
207
238
|
export function appendSystemPrompt(base, extra) {
|
|
208
239
|
const lhs = String(base || '').trim();
|
|
209
240
|
const rhs = String(extra || '').trim();
|
package/dist/core/constants.js
CHANGED
|
@@ -318,6 +318,15 @@ export const CLAUDE_TUI_STALL_PENDING_TOOL_MS = 30 * 60_000;
|
|
|
318
318
|
* refreshes the PTY signal and defers this path to the slow thresholds.
|
|
319
319
|
*/
|
|
320
320
|
export const CLAUDE_TUI_STALL_PTY_DEAD_MS = 3 * 60_000;
|
|
321
|
+
/**
|
|
322
|
+
* Settle window after the TUI paints the "selected model is unavailable" banner
|
|
323
|
+
* (a 404 model_not_found). The notice is terminal — claude paints it then idles
|
|
324
|
+
* at the REPL forever: no JSONL is written, no Stop hook fires. We wait this
|
|
325
|
+
* brief window to cross-validate that nothing substantive followed (the banner
|
|
326
|
+
* alone is evidence, not a verdict — same discipline as resolveClaudeTuiLimitOutcome)
|
|
327
|
+
* before ending the turn, instead of waiting out the 3–10 minute stall watchdog.
|
|
328
|
+
*/
|
|
329
|
+
export const CLAUDE_TUI_MODEL_ERROR_SETTLE_MS = 2_500;
|
|
321
330
|
/**
|
|
322
331
|
* TTL for the post-Stop `hold-background` path. The hold protects
|
|
323
332
|
* run_in_background agents living inside the claude process — but a live
|
package/package.json
CHANGED