@pentoshi/clai 3.11.5 → 3.11.7
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/interrupted-reasoning.d.ts +6 -0
- package/dist/agent/interrupted-reasoning.js +38 -0
- package/dist/agent/interrupted-reasoning.js.map +1 -0
- package/dist/agent/runner.js +90 -15
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/stream-recovery.d.ts +4 -1
- package/dist/agent/stream-recovery.js +20 -9
- package/dist/agent/stream-recovery.js.map +1 -1
- package/dist/llm/anthropic.js +16 -1
- package/dist/llm/anthropic.js.map +1 -1
- package/dist/llm/aws-mantle.js +14 -1
- package/dist/llm/aws-mantle.js.map +1 -1
- package/dist/llm/gemini.js +3 -1
- package/dist/llm/gemini.js.map +1 -1
- package/dist/llm/http.d.ts +8 -2
- package/dist/llm/http.js +62 -21
- package/dist/llm/http.js.map +1 -1
- package/dist/llm/modal.js +3 -3
- package/dist/llm/modal.js.map +1 -1
- package/dist/llm/nvidia.js +2 -3
- package/dist/llm/nvidia.js.map +1 -1
- package/dist/llm/ollama.js +3 -1
- package/dist/llm/ollama.js.map +1 -1
- package/dist/tui-v2/components/pager/pager-line.js +7 -4
- package/dist/tui-v2/components/pager/pager-line.js.map +1 -1
- package/dist/tui-v2/components/pager/pager.js +2 -2
- package/dist/tui-v2/components/pager/pager.js.map +1 -1
- package/dist/tui-v2/components/transcript/assistant-message.js +5 -3
- package/dist/tui-v2/components/transcript/assistant-message.js.map +1 -1
- package/dist/tui-v2/components/transcript/file-diff-card.js +3 -0
- package/dist/tui-v2/components/transcript/file-diff-card.js.map +1 -1
- package/dist/tui-v2/components/transcript/selectable-line.d.ts +13 -0
- package/dist/tui-v2/components/transcript/selectable-line.js +11 -0
- package/dist/tui-v2/components/transcript/selectable-line.js.map +1 -1
- package/dist/tui-v2/components/transcript/thinking-block.js +7 -4
- package/dist/tui-v2/components/transcript/thinking-block.js.map +1 -1
- package/dist/tui-v2/components/transcript/tool-card.js +3 -3
- package/dist/tui-v2/components/transcript/tool-card.js.map +1 -1
- package/dist/tui-v2/rendering/pager-chrome.d.ts +14 -3
- package/dist/tui-v2/rendering/pager-chrome.js +86 -23
- package/dist/tui-v2/rendering/pager-chrome.js.map +1 -1
- package/dist/tui-v2/rendering/pager-markdown.js +5 -0
- package/dist/tui-v2/rendering/pager-markdown.js.map +1 -1
- package/dist/ui/code-block.d.ts +8 -0
- package/dist/ui/code-block.js +33 -6
- package/dist/ui/code-block.js.map +1 -1
- package/dist/ui/markdown.js +37 -3
- package/dist/ui/markdown.js.map +1 -1
- package/dist/ui/text-width.d.ts +27 -0
- package/dist/ui/text-width.js +36 -0
- package/dist/ui/text-width.js.map +1 -0
- package/dist/version.generated.d.ts +2 -2
- package/dist/version.generated.js +2 -2
- package/package.json +12 -12
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const INTERRUPTED_REASONING_LIMIT = 4000;
|
|
2
|
+
export declare const INTERRUPTED_REASONING_MIN = 160;
|
|
3
|
+
export declare const MIN_RESUMPTION_YIELD = 240;
|
|
4
|
+
export declare function isMeaningfulResumptionYield(producedChars: number): boolean;
|
|
5
|
+
export declare function appendInterruptedReasoning(previous: string, next: string): string;
|
|
6
|
+
export declare function interruptedReasoningBrief(reasoning: string): string | undefined;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export const INTERRUPTED_REASONING_LIMIT = 4_000;
|
|
2
|
+
export const INTERRUPTED_REASONING_MIN = 160;
|
|
3
|
+
export const MIN_RESUMPTION_YIELD = 240;
|
|
4
|
+
export function isMeaningfulResumptionYield(producedChars) {
|
|
5
|
+
return producedChars >= MIN_RESUMPTION_YIELD;
|
|
6
|
+
}
|
|
7
|
+
function normalize(reasoning) {
|
|
8
|
+
return reasoning.replace(/\r/g, "").replace(/[ \t]+$/gm, "").trim();
|
|
9
|
+
}
|
|
10
|
+
function clampReasoning(reasoning) {
|
|
11
|
+
if (reasoning.length <= INTERRUPTED_REASONING_LIMIT)
|
|
12
|
+
return reasoning;
|
|
13
|
+
const tail = reasoning.slice(reasoning.length - INTERRUPTED_REASONING_LIMIT);
|
|
14
|
+
const breakAt = tail.indexOf("\n");
|
|
15
|
+
return breakAt > 0 && breakAt < 400 ? tail.slice(breakAt + 1) : tail;
|
|
16
|
+
}
|
|
17
|
+
export function appendInterruptedReasoning(previous, next) {
|
|
18
|
+
const addition = normalize(next);
|
|
19
|
+
if (!addition)
|
|
20
|
+
return previous;
|
|
21
|
+
const earlier = normalize(previous);
|
|
22
|
+
if (!earlier)
|
|
23
|
+
return clampReasoning(addition);
|
|
24
|
+
if (earlier.includes(addition))
|
|
25
|
+
return clampReasoning(earlier);
|
|
26
|
+
if (addition.includes(earlier))
|
|
27
|
+
return clampReasoning(addition);
|
|
28
|
+
return clampReasoning(`${earlier}\n\n${addition}`);
|
|
29
|
+
}
|
|
30
|
+
export function interruptedReasoningBrief(reasoning) {
|
|
31
|
+
const body = normalize(reasoning);
|
|
32
|
+
if (body.length < INTERRUPTED_REASONING_MIN)
|
|
33
|
+
return undefined;
|
|
34
|
+
return ("Your own reasoning from the interrupted attempt is preserved below. " +
|
|
35
|
+
"Build on these conclusions instead of re-deriving them, and go straight to the next concrete action.\n\n" +
|
|
36
|
+
`<preserved_reasoning>\n${body}\n</preserved_reasoning>`);
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=interrupted-reasoning.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interrupted-reasoning.js","sourceRoot":"","sources":["../../src/agent/interrupted-reasoning.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,CAAC;AAEjD,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAE7C,MAAM,CAAC,MAAM,oBAAoB,GAAG,GAAG,CAAC;AAExC,MAAM,UAAU,2BAA2B,CAAC,aAAqB;IAC/D,OAAO,aAAa,IAAI,oBAAoB,CAAC;AAC/C,CAAC;AAED,SAAS,SAAS,CAAC,SAAiB;IAClC,OAAO,SAAS,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;AACtE,CAAC;AAED,SAAS,cAAc,CAAC,SAAiB;IACvC,IAAI,SAAS,CAAC,MAAM,IAAI,2BAA2B;QAAE,OAAO,SAAS,CAAC;IACtE,MAAM,IAAI,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,2BAA2B,CAAC,CAAC;IAC7E,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,OAAO,OAAO,GAAG,CAAC,IAAI,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACvE,CAAC;AAED,MAAM,UAAU,0BAA0B,CACxC,QAAgB,EAChB,IAAY;IAEZ,MAAM,QAAQ,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,CAAC,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC/B,MAAM,OAAO,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;IACpC,IAAI,CAAC,OAAO;QAAE,OAAO,cAAc,CAAC,QAAQ,CAAC,CAAC;IAC9C,IAAI,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAAE,OAAO,cAAc,CAAC,OAAO,CAAC,CAAC;IAC/D,IAAI,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;QAAE,OAAO,cAAc,CAAC,QAAQ,CAAC,CAAC;IAChE,OAAO,cAAc,CAAC,GAAG,OAAO,OAAO,QAAQ,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,yBAAyB,CACvC,SAAiB;IAEjB,MAAM,IAAI,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,IAAI,CAAC,MAAM,GAAG,yBAAyB;QAAE,OAAO,SAAS,CAAC;IAC9D,OAAO,CACL,sEAAsE;QACtE,0GAA0G;QAC1G,0BAA0B,IAAI,0BAA0B,CACzD,CAAC;AACJ,CAAC"}
|
package/dist/agent/runner.js
CHANGED
|
@@ -58,6 +58,7 @@ import { buildDurableEnvelope, WorkLedger, } from "./durable-envelope.js";
|
|
|
58
58
|
import { COMPACTION_SYSTEM_PROMPT, } from "./compaction-summary.js";
|
|
59
59
|
import { maybeAppendPlanModeReminder, PLAN_REMINDER_TOAST, } from "./plan-mode-reminders.js";
|
|
60
60
|
import { LoopGuard } from "./loop-guard.js";
|
|
61
|
+
import { appendInterruptedReasoning, interruptedReasoningBrief, isMeaningfulResumptionYield, } from "./interrupted-reasoning.js";
|
|
61
62
|
import { CompactionAttemptLedger, compactionAttemptKey, } from "./compaction-attempt.js";
|
|
62
63
|
import { resolveRequestBudget } from "./request-budget.js";
|
|
63
64
|
import { loadPlan, mutatePlan, markTask, appendPlanTask, readyPlanTasks, foregroundRemaining, responderOpenTasks, isPlanTerminal, isPlanSuccessful, } from "../store/plan.js";
|
|
@@ -169,6 +170,8 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
169
170
|
// at the top of every loop iteration.
|
|
170
171
|
let visibleCommitted = false;
|
|
171
172
|
let interruptedVisible = "";
|
|
173
|
+
let interruptedReasoning = "";
|
|
174
|
+
let lowYieldResumptions = 0;
|
|
172
175
|
const trimExactContinuationOverlap = (previous, current, minLength = 32) => {
|
|
173
176
|
if (previous.length > 0 && current.startsWith(previous)) {
|
|
174
177
|
return current.slice(previous.length);
|
|
@@ -3095,6 +3098,30 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3095
3098
|
let accumulatedText = "";
|
|
3096
3099
|
const callIds = [];
|
|
3097
3100
|
let streamedCallsCount = 0;
|
|
3101
|
+
// A model can think silently for minutes. Without a heartbeat the UI
|
|
3102
|
+
// shows a frozen label and the turn looks hung, so surface elapsed time
|
|
3103
|
+
// and the current phase on a timer rather than only on token arrival.
|
|
3104
|
+
const streamStartedAt = Date.now();
|
|
3105
|
+
const streamPhase = () => {
|
|
3106
|
+
const seconds = Math.round((Date.now() - streamStartedAt) / 1000);
|
|
3107
|
+
const elapsed = seconds < 60
|
|
3108
|
+
? `${seconds}s`
|
|
3109
|
+
: `${Math.floor(seconds / 60)}m${String(seconds % 60).padStart(2, "0")}s`;
|
|
3110
|
+
if (generatedTokens > 0 && !inThinking) {
|
|
3111
|
+
return `generating response · ${generatedTokens} tokens · ${elapsed}`;
|
|
3112
|
+
}
|
|
3113
|
+
if (sawReasoning)
|
|
3114
|
+
return `thinking · ${elapsed}`;
|
|
3115
|
+
return `waiting for model · ${elapsed}`;
|
|
3116
|
+
};
|
|
3117
|
+
const heartbeat = setInterval(() => {
|
|
3118
|
+
const text = streamPhase();
|
|
3119
|
+
if (writesDirectly)
|
|
3120
|
+
spinner.setLabel(text);
|
|
3121
|
+
else
|
|
3122
|
+
emit({ type: "status", text });
|
|
3123
|
+
}, 10_000);
|
|
3124
|
+
heartbeat.unref?.();
|
|
3098
3125
|
const deferredToolCalls = [];
|
|
3099
3126
|
const deltaParser = writesDirectly
|
|
3100
3127
|
? undefined
|
|
@@ -3336,6 +3363,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3336
3363
|
resetStreamRecoveryState(recoveryState);
|
|
3337
3364
|
allowModelFallback = false;
|
|
3338
3365
|
preferModelFallback = false;
|
|
3366
|
+
lowYieldResumptions = 0;
|
|
3339
3367
|
}
|
|
3340
3368
|
catch (streamError) {
|
|
3341
3369
|
// User cancelled (double-Esc) — never try to recover, just stop.
|
|
@@ -3363,47 +3391,89 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3363
3391
|
// We only rethrow (stop the turn) in the worst case: every approach
|
|
3364
3392
|
// for that failure class is exhausted or the total budget is spent.
|
|
3365
3393
|
const failureKind = classifyStreamFailure(streamError);
|
|
3394
|
+
const partialStream = streamAlreadyEmitted(streamError) || accumulatedText.length > 0;
|
|
3395
|
+
const partial = rememberThinkingFromText(accumulatedText);
|
|
3396
|
+
const rawPartialVisible = partialStream
|
|
3397
|
+
? textBeforeToolCall(stripThinking(collapseRepeatedText(accumulatedText)).visible)
|
|
3398
|
+
: "";
|
|
3399
|
+
const normalizedPartialVisible = trimExactContinuationOverlap(interruptedVisible, rawPartialVisible);
|
|
3400
|
+
const partialVisible = normalizedPartialVisible.trim();
|
|
3401
|
+
// A route that drops after a handful of characters is not making
|
|
3402
|
+
// progress, however many times it is retried. Only a substantial
|
|
3403
|
+
// yield unlocks the generous resumption budget; anything less is
|
|
3404
|
+
// charged to the failure class and escalates to another route.
|
|
3405
|
+
const meaningfulProgress = partialStream &&
|
|
3406
|
+
isMeaningfulResumptionYield(normalizedPartialVisible.length + partial.thinkContent.length);
|
|
3407
|
+
if (partialStream) {
|
|
3408
|
+
lowYieldResumptions = meaningfulProgress
|
|
3409
|
+
? 0
|
|
3410
|
+
: lowYieldResumptions + 1;
|
|
3411
|
+
}
|
|
3366
3412
|
const plan = planStreamRecovery({
|
|
3367
3413
|
kind: failureKind,
|
|
3368
3414
|
state: recoveryState,
|
|
3415
|
+
progressed: meaningfulProgress,
|
|
3369
3416
|
});
|
|
3370
|
-
const
|
|
3417
|
+
const terminalFailure = plan.action === "give-up";
|
|
3371
3418
|
let continuationNudge = "";
|
|
3372
3419
|
if (partialStream) {
|
|
3373
3420
|
spinner.stop();
|
|
3374
3421
|
deltaParser?.finish();
|
|
3375
|
-
const partial = rememberThinkingFromText(accumulatedText);
|
|
3376
3422
|
const hasShownToolCall = deferredToolCalls.some((entry) => entry.shown);
|
|
3377
|
-
const rawPartialVisible = textBeforeToolCall(stripThinking(collapseRepeatedText(accumulatedText)).visible);
|
|
3378
|
-
const normalizedPartialVisible = trimExactContinuationOverlap(interruptedVisible, rawPartialVisible);
|
|
3379
|
-
const partialVisible = normalizedPartialVisible.trim();
|
|
3380
3423
|
if (partialVisible) {
|
|
3381
|
-
|
|
3382
|
-
|
|
3424
|
+
// Finalizing here would close the streaming card and split one
|
|
3425
|
+
// answer across a card per interruption. Keep it open and let
|
|
3426
|
+
// the single commit below paint the stitched text; only a
|
|
3427
|
+
// terminal failure has to flush it now.
|
|
3428
|
+
if (terminalFailure) {
|
|
3429
|
+
writeAssistantMessage(interruptedVisible + normalizedPartialVisible);
|
|
3430
|
+
}
|
|
3431
|
+
else {
|
|
3432
|
+
visibleCommitted = true;
|
|
3433
|
+
}
|
|
3434
|
+
messages.push({
|
|
3435
|
+
role: "assistant",
|
|
3436
|
+
content: sanitizeAssistantText(partialVisible),
|
|
3437
|
+
});
|
|
3383
3438
|
interruptedVisible += normalizedPartialVisible;
|
|
3384
3439
|
}
|
|
3385
|
-
else if (!writesDirectly) {
|
|
3440
|
+
else if (terminalFailure && !writesDirectly) {
|
|
3386
3441
|
emit({ type: "assistant-message", text: "" });
|
|
3387
3442
|
}
|
|
3388
3443
|
if (partial.hasThinking && !hasShownToolCall) {
|
|
3389
3444
|
writeThinkingBlock(partial.thinkContent);
|
|
3390
3445
|
}
|
|
3446
|
+
if (partial.hasThinking) {
|
|
3447
|
+
interruptedReasoning = appendInterruptedReasoning(interruptedReasoning, partial.thinkContent);
|
|
3448
|
+
}
|
|
3391
3449
|
for (const deferred of deferredToolCalls) {
|
|
3392
3450
|
if (!deferred.shown || deferred.call.name === "…")
|
|
3393
3451
|
continue;
|
|
3394
3452
|
writeToolBlocked(deferred.eventId, deferred.call.name, "Incomplete tool call discarded after the provider stream was interrupted.", chalk.yellow(" ⚠ incomplete tool call discarded after stream interruption\n"));
|
|
3395
3453
|
}
|
|
3396
|
-
continuationNudge =
|
|
3397
|
-
|
|
3398
|
-
|
|
3454
|
+
continuationNudge = [
|
|
3455
|
+
partialVisible
|
|
3456
|
+
? "The provider stream was interrupted after partial output. Continue from the exact stopping point without repeating prior text. Any incomplete tool call was discarded and must be reissued in full."
|
|
3457
|
+
: "The provider stream was interrupted before any answer was produced. Any incomplete tool call was discarded and must be reissued in full. Do not restart your analysis from the beginning.",
|
|
3458
|
+
interruptedReasoningBrief(interruptedReasoning),
|
|
3459
|
+
]
|
|
3460
|
+
.filter((part) => Boolean(part))
|
|
3461
|
+
.join("\n\n");
|
|
3462
|
+
const restartNotice = terminalFailure
|
|
3399
3463
|
? "partial response preserved before terminal provider failure"
|
|
3400
|
-
:
|
|
3464
|
+
: lowYieldResumptions > 1
|
|
3465
|
+
? `route is dropping after almost no output (${lowYieldResumptions} in a row) — switching model`
|
|
3466
|
+
: "partial response preserved — resuming from the interruption";
|
|
3401
3467
|
writeNotice("warn", restartNotice, chalk.yellow(` ⚠ ${restartNotice}\n`));
|
|
3402
3468
|
}
|
|
3403
|
-
if (
|
|
3469
|
+
if (terminalFailure) {
|
|
3404
3470
|
throw streamError;
|
|
3405
3471
|
}
|
|
3406
|
-
recordRecoveryAttempt(recoveryState, failureKind);
|
|
3472
|
+
recordRecoveryAttempt(recoveryState, failureKind, meaningfulProgress);
|
|
3473
|
+
if (lowYieldResumptions > 1) {
|
|
3474
|
+
allowModelFallback = true;
|
|
3475
|
+
preferModelFallback = true;
|
|
3476
|
+
}
|
|
3407
3477
|
if (plan.notice) {
|
|
3408
3478
|
writeNotice("warn", plan.notice, chalk.yellow(` ⚠ ${plan.notice}\n`));
|
|
3409
3479
|
}
|
|
@@ -3434,6 +3504,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3434
3504
|
}
|
|
3435
3505
|
finally {
|
|
3436
3506
|
// Always clear the spinner — abort, network error, or success.
|
|
3507
|
+
clearInterval(heartbeat);
|
|
3437
3508
|
spinner.stop();
|
|
3438
3509
|
}
|
|
3439
3510
|
if (responderDelivery) {
|
|
@@ -3487,7 +3558,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3487
3558
|
const commitAssistantRetry = (historyText) => {
|
|
3488
3559
|
const hasShownToolCall = deferredToolCalls.some((entry) => entry.shown);
|
|
3489
3560
|
if (!hasShownToolCall) {
|
|
3490
|
-
const displayText = textBeforeToolCall(collapseRepeatedText(
|
|
3561
|
+
const displayText = textBeforeToolCall(collapseRepeatedText(canonicalAssistantVisible)).trim();
|
|
3491
3562
|
if (displayText) {
|
|
3492
3563
|
writeAssistantMessage(displayText);
|
|
3493
3564
|
}
|
|
@@ -3503,6 +3574,8 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3503
3574
|
}
|
|
3504
3575
|
pushAssistantHistory(historyText);
|
|
3505
3576
|
interruptedVisible = "";
|
|
3577
|
+
interruptedReasoning = "";
|
|
3578
|
+
lowYieldResumptions = 0;
|
|
3506
3579
|
};
|
|
3507
3580
|
// Only emit a thinking-block event when the classic renderer is
|
|
3508
3581
|
// active (writesDirectly / no deltaParser). In TUI v2 the
|
|
@@ -4186,6 +4259,8 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
4186
4259
|
emit({ type: "assistant-message", text: "" });
|
|
4187
4260
|
}
|
|
4188
4261
|
interruptedVisible = "";
|
|
4262
|
+
interruptedReasoning = "";
|
|
4263
|
+
lowYieldResumptions = 0;
|
|
4189
4264
|
let bound = [];
|
|
4190
4265
|
if (nativeToolCalls.length) {
|
|
4191
4266
|
bound = nativeToolCalls.map((tc, index) => {
|