@cryer/star-cli 0.3.10 → 0.3.12
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/{chunk-PDFC7QVW.js → chunk-2GAUAYCU.js} +40 -8
- package/dist/chunk-2GAUAYCU.js.map +1 -0
- package/dist/{chunk-MNVCCOI2.js → chunk-F5RWZMQY.js} +224 -12
- package/dist/chunk-F5RWZMQY.js.map +1 -0
- package/dist/{chunk-A5PXPWHB.js → chunk-LSYGHLRU.js} +13 -1
- package/dist/chunk-LSYGHLRU.js.map +1 -0
- package/dist/{chunk-IJGUW4PM.js → chunk-MYHD3SZB.js} +6 -5
- package/dist/chunk-MYHD3SZB.js.map +1 -0
- package/dist/{chunk-URC7JXKX.js → chunk-Q5FJN4LN.js} +7 -3
- package/dist/{chunk-URC7JXKX.js.map → chunk-Q5FJN4LN.js.map} +1 -1
- package/dist/{loop-TW2FHL7J.js → loop-BYXBNFFY.js} +5 -5
- package/dist/main.js +8 -9
- package/dist/main.js.map +1 -1
- package/dist/{repl-GBVIW5QR.js → repl-SSCURTAH.js} +322 -57
- package/dist/repl-SSCURTAH.js.map +1 -0
- package/dist/{tools-7EBGCYYE.js → tools-BH55BVIS.js} +3 -3
- package/package.json +25 -16
- package/dist/chunk-A5PXPWHB.js.map +0 -1
- package/dist/chunk-IJGUW4PM.js.map +0 -1
- package/dist/chunk-MNVCCOI2.js.map +0 -1
- package/dist/chunk-PDFC7QVW.js.map +0 -1
- package/dist/repl-GBVIW5QR.js.map +0 -1
- /package/dist/{loop-TW2FHL7J.js.map → loop-BYXBNFFY.js.map} +0 -0
- /package/dist/{tools-7EBGCYYE.js.map → tools-BH55BVIS.js.map} +0 -0
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
toCoreMessages,
|
|
13
13
|
toStarMessage,
|
|
14
14
|
trackTree
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-MYHD3SZB.js";
|
|
16
16
|
import {
|
|
17
17
|
createModel,
|
|
18
18
|
streamActivity
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
ToolRegistry,
|
|
25
25
|
createDefaultRegistry,
|
|
26
26
|
debugStreamLog
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-Q5FJN4LN.js";
|
|
28
28
|
import {
|
|
29
29
|
TodoStore,
|
|
30
30
|
beginTurn,
|
|
@@ -43,7 +43,7 @@ import {
|
|
|
43
43
|
stripOversizedImages,
|
|
44
44
|
undoTurnSnapshots,
|
|
45
45
|
userMemoryPath
|
|
46
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-LSYGHLRU.js";
|
|
47
47
|
|
|
48
48
|
// src/agent/loop.ts
|
|
49
49
|
import { tool as aiTool, generateText as generateText3 } from "ai";
|
|
@@ -76,6 +76,9 @@ function textWeight(text) {
|
|
|
76
76
|
}
|
|
77
77
|
return text.length + cjk * (CHARS_PER_TOKEN - 1);
|
|
78
78
|
}
|
|
79
|
+
function roughTextTokens(text) {
|
|
80
|
+
return Math.ceil(textWeight(text) / CHARS_PER_TOKEN);
|
|
81
|
+
}
|
|
79
82
|
function contentTokenLength(message) {
|
|
80
83
|
if (message.role === "tool") {
|
|
81
84
|
let tokens2 = 0;
|
|
@@ -241,6 +244,7 @@ ${transcript}`,
|
|
|
241
244
|
}
|
|
242
245
|
function compactMessages(messages, maxTokens, opts = {}) {
|
|
243
246
|
const limit = opts.force ? 0 : maxTokens;
|
|
247
|
+
const minKept = opts.force ? 1 : MIN_KEPT_MESSAGES;
|
|
244
248
|
const overhead = opts.overheadTokens ?? 0;
|
|
245
249
|
const tokens = messages.map((star) => estimateMessageTokens(star.message));
|
|
246
250
|
let total = overhead;
|
|
@@ -269,7 +273,7 @@ function compactMessages(messages, maxTokens, opts = {}) {
|
|
|
269
273
|
let droppedCount = 0;
|
|
270
274
|
let droppedTokens = 0;
|
|
271
275
|
for (const turn of turns) {
|
|
272
|
-
if (restCount - droppedCount - turn.length <
|
|
276
|
+
if (restCount - droppedCount - turn.length < minKept) {
|
|
273
277
|
break;
|
|
274
278
|
}
|
|
275
279
|
droppedCount += turn.length;
|
|
@@ -299,6 +303,7 @@ var KEEP_RECENT_MESSAGES = 24;
|
|
|
299
303
|
var MIN_ELISION_TOKENS = 250;
|
|
300
304
|
var ELIDED_TOOL_RESULT_TEXT = "[stale tool output elided to free context \u2014 it was shown in full earlier; re-run the tool if you need it again]";
|
|
301
305
|
var ELIDED_IMAGE_TEXT = "[image elided to free context \u2014 re-attach it if it is still needed]";
|
|
306
|
+
var ELIDED_TOOL_CALL_ARGS_TEXT = "arguments elided to free context \u2014 the call already ran; this placeholder is not valid tool input, re-read the file if you need the old content";
|
|
302
307
|
function elideStaleContent(messages, thresholdTokens, overheadTokens = 0) {
|
|
303
308
|
let total = overheadTokens;
|
|
304
309
|
for (const star of messages) total += estimateMessageTokens(star.message);
|
|
@@ -324,6 +329,19 @@ function elideStaleContent(messages, thresholdTokens, overheadTokens = 0) {
|
|
|
324
329
|
meta: { ...star.meta, elided: true }
|
|
325
330
|
};
|
|
326
331
|
elidedCount++;
|
|
332
|
+
} else if (message.role === "assistant" && Array.isArray(message.content)) {
|
|
333
|
+
if (!message.content.some((part) => part.type === "tool-call")) continue;
|
|
334
|
+
if (estimateMessageTokens(message) < MIN_ELISION_TOKENS) continue;
|
|
335
|
+
out[i] = {
|
|
336
|
+
message: {
|
|
337
|
+
...message,
|
|
338
|
+
content: message.content.map(
|
|
339
|
+
(part) => part.type === "tool-call" ? { ...part, args: { elided: ELIDED_TOOL_CALL_ARGS_TEXT } } : part
|
|
340
|
+
)
|
|
341
|
+
},
|
|
342
|
+
meta: { ...star.meta, elided: true }
|
|
343
|
+
};
|
|
344
|
+
elidedCount++;
|
|
327
345
|
} else if (message.role === "user" && Array.isArray(message.content)) {
|
|
328
346
|
if (!message.content.some((part) => part.type === "image" || part.type === "file")) continue;
|
|
329
347
|
out[i] = {
|
|
@@ -348,6 +366,16 @@ function usageTotalTokens(usage) {
|
|
|
348
366
|
return summed > 0 ? summed : usage.totalTokens;
|
|
349
367
|
}
|
|
350
368
|
function toStreamErrorInfo(error) {
|
|
369
|
+
if (error !== null && typeof error === "object" && !(error instanceof Error)) {
|
|
370
|
+
const raw = error;
|
|
371
|
+
const message = typeof raw.message === "string" && raw.message.length > 0 ? raw.message : JSON.stringify(raw);
|
|
372
|
+
const name = typeof raw.name === "string" ? raw.name : typeof raw.type === "string" ? raw.type : "Error";
|
|
373
|
+
const info2 = { name, message };
|
|
374
|
+
if (typeof raw.statusCode === "number") info2.statusCode = raw.statusCode;
|
|
375
|
+
if (typeof raw.status === "number") info2.statusCode = raw.status;
|
|
376
|
+
if (typeof raw.code === "string") info2.code = raw.code;
|
|
377
|
+
return info2;
|
|
378
|
+
}
|
|
351
379
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
352
380
|
const info = { name: err.name, message: err.message };
|
|
353
381
|
const statusCode = err.statusCode;
|
|
@@ -657,6 +685,10 @@ function isNetworkFailure(error) {
|
|
|
657
685
|
function matchesRetryablePattern(text) {
|
|
658
686
|
return typeof text === "string" && RETRYABLE_MESSAGE_PATTERNS.some((p) => p.test(text));
|
|
659
687
|
}
|
|
688
|
+
var STATUSLESS_RETRYABLE_PATTERNS = [
|
|
689
|
+
/overloaded|rate.?limit|too many requests|resource.?exhausted|at capacity|service.?unavailable|try (?:your request )?again/i,
|
|
690
|
+
/\b(429|500|502|503|504|529)\b/
|
|
691
|
+
];
|
|
660
692
|
function isRetryableStreamError(error) {
|
|
661
693
|
if (NON_RETRYABLE_ERROR_NAMES.has(error.name)) return false;
|
|
662
694
|
if (error.isRetryable === true) return true;
|
|
@@ -666,7 +698,7 @@ function isRetryableStreamError(error) {
|
|
|
666
698
|
if (NON_RETRYABLE_4XX.has(status)) return false;
|
|
667
699
|
return matchesRetryablePattern(error.message) || matchesRetryablePattern(responseBodyOf(error));
|
|
668
700
|
}
|
|
669
|
-
return isNetworkFailure(error);
|
|
701
|
+
return isNetworkFailure(error) || STATUSLESS_RETRYABLE_PATTERNS.some((p) => p.test(error.message));
|
|
670
702
|
}
|
|
671
703
|
var RETRY_JITTER_FACTOR = 0.25;
|
|
672
704
|
var MAX_RETRY_DELAY_MS = 6e4;
|
|
@@ -709,7 +741,7 @@ function summarizeStreamError(error) {
|
|
|
709
741
|
}
|
|
710
742
|
|
|
711
743
|
// src/llm/stream.ts
|
|
712
|
-
import { streamText } from "ai";
|
|
744
|
+
import { InvalidToolArgumentsError, streamText } from "ai";
|
|
713
745
|
var DEFAULT_IDLE_TIMEOUT_MS = 6e4;
|
|
714
746
|
var DEFAULT_FIRST_PART_TIMEOUT_MS = 12e4;
|
|
715
747
|
var CONTENT_PART_TYPES = /* @__PURE__ */ new Set(["text-delta", "tool-call", "tool-call-delta"]);
|
|
@@ -791,6 +823,7 @@ async function* streamChat(opts) {
|
|
|
791
823
|
let deltas = 0;
|
|
792
824
|
let hasContent = false;
|
|
793
825
|
const argsProgress = /* @__PURE__ */ new Map();
|
|
826
|
+
let invalidArgsCounter = 0;
|
|
794
827
|
let lastPartAt = Date.now();
|
|
795
828
|
debugStreamLog("request", {
|
|
796
829
|
messages: opts.messages.length,
|
|
@@ -863,6 +896,7 @@ async function* streamChat(opts) {
|
|
|
863
896
|
break;
|
|
864
897
|
case "tool-call":
|
|
865
898
|
hasContent = true;
|
|
899
|
+
argsProgress.delete(part.toolCallId);
|
|
866
900
|
yield {
|
|
867
901
|
type: "tool-call",
|
|
868
902
|
id: part.toolCallId,
|
|
@@ -902,6 +936,22 @@ async function* streamChat(opts) {
|
|
|
902
936
|
usage: part.usage,
|
|
903
937
|
deltas
|
|
904
938
|
});
|
|
939
|
+
for (const [id, progress] of argsProgress) {
|
|
940
|
+
debugStreamLog("truncated-tool-call", {
|
|
941
|
+
toolCallId: id,
|
|
942
|
+
toolName: progress.name,
|
|
943
|
+
chars: progress.chars,
|
|
944
|
+
finishReason: part.finishReason
|
|
945
|
+
});
|
|
946
|
+
yield {
|
|
947
|
+
type: "tool-call",
|
|
948
|
+
id,
|
|
949
|
+
name: progress.name,
|
|
950
|
+
args: {},
|
|
951
|
+
invalidArgs: `the arguments were cut off mid-stream after ${progress.chars} characters (finish reason: ${part.finishReason}) \u2014 the reply hit the output or context limit before the JSON completed. Reissue ${progress.name} with complete arguments; for large content, split it into several smaller calls.`
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
argsProgress.clear();
|
|
905
955
|
yield {
|
|
906
956
|
type: "finish",
|
|
907
957
|
finishReason: part.finishReason,
|
|
@@ -916,6 +966,29 @@ async function* streamChat(opts) {
|
|
|
916
966
|
}
|
|
917
967
|
case "error": {
|
|
918
968
|
if (opts.abortSignal?.aborted) return;
|
|
969
|
+
if (InvalidToolArgumentsError.isInstance(part.error)) {
|
|
970
|
+
const invalid = part.error;
|
|
971
|
+
let args = invalid.toolArgs;
|
|
972
|
+
try {
|
|
973
|
+
args = JSON.parse(invalid.toolArgs);
|
|
974
|
+
} catch {
|
|
975
|
+
}
|
|
976
|
+
const started = [...argsProgress.entries()].reverse().find(([, progress]) => progress.name === invalid.toolName);
|
|
977
|
+
debugStreamLog("invalid-tool-args", {
|
|
978
|
+
toolName: invalid.toolName,
|
|
979
|
+
recoveredId: started?.[0] ?? null
|
|
980
|
+
});
|
|
981
|
+
yield {
|
|
982
|
+
type: "tool-call",
|
|
983
|
+
id: started?.[0] ?? `invalid-args-${++invalidArgsCounter}`,
|
|
984
|
+
name: invalid.toolName,
|
|
985
|
+
args,
|
|
986
|
+
// The zod detail tells the model exactly which field to fix;
|
|
987
|
+
// capped because the message embeds the full rejected value.
|
|
988
|
+
invalidArgs: invalid.message.length > 2e3 ? `${invalid.message.slice(0, 2e3)}\u2026` : invalid.message
|
|
989
|
+
};
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
919
992
|
const error = toStreamErrorInfo(part.error);
|
|
920
993
|
debugStreamLog("error-part", { seenContent, deltas, error: summarizeStreamError(error) });
|
|
921
994
|
yield { type: "error", error };
|
|
@@ -2087,7 +2160,7 @@ function findModelPricing(config, name, modelId) {
|
|
|
2087
2160
|
return config.models.find((m) => m.model === modelId);
|
|
2088
2161
|
}
|
|
2089
2162
|
async function runSubagent(deps, args, cwd, signal, agentIdRef) {
|
|
2090
|
-
const { AgentLoop: AgentLoop2 } = await import("./loop-
|
|
2163
|
+
const { AgentLoop: AgentLoop2 } = await import("./loop-BYXBNFFY.js");
|
|
2091
2164
|
const child = new AgentLoop2({
|
|
2092
2165
|
model: deps.model,
|
|
2093
2166
|
registry: createDefaultRegistry(),
|
|
@@ -2284,6 +2357,7 @@ function finalNudgeWarning(autoContinues, maxAutoContinues) {
|
|
|
2284
2357
|
return autoContinues >= maxAutoContinues ? " This is the final automatic continuation \u2014 if your next reply again has no tool calls, nudging stops and the turn is handed back to the user. Finish the work now or summarize what remains." : "";
|
|
2285
2358
|
}
|
|
2286
2359
|
var COMPLETION_CHECK_TIMEOUT_MS = 3e4;
|
|
2360
|
+
var TODO_STALE_NUDGE_STEPS = 20;
|
|
2287
2361
|
function attachTurnContext(messages, context) {
|
|
2288
2362
|
if (!context) return messages;
|
|
2289
2363
|
for (let i = messages.length - 1; i >= 0; i--) {
|
|
@@ -2422,6 +2496,29 @@ var AgentLoop = class {
|
|
|
2422
2496
|
// (the tool map's share of the request window) is computed alongside since
|
|
2423
2497
|
// it depends on the same inputs.
|
|
2424
2498
|
aiToolsCache = null;
|
|
2499
|
+
// Server-calibration baseline for the context estimate: the latest finish
|
|
2500
|
+
// event's provider-reported promptTokens paired with the local estimate of
|
|
2501
|
+
// the exact request that produced it. Non-OpenAI tokenizers can count the
|
|
2502
|
+
// same history far higher than the local cl100k estimate (a qwen endpoint
|
|
2503
|
+
// counted ~30% more on a Chinese-heavy code session), which let the ctx%
|
|
2504
|
+
// display and the auto-compaction trigger lag the real window fill until
|
|
2505
|
+
// the provider itself started truncating replies. The delta refreshes on
|
|
2506
|
+
// every step's finish, so drift is bounded to one step's new content.
|
|
2507
|
+
serverTokenBaseline = null;
|
|
2508
|
+
// Text streamed by the in-flight reply (visible text + reasoning +
|
|
2509
|
+
// completed tool-call args). estimateContextTokens folds a rough token
|
|
2510
|
+
// estimate of it into the status-bar ctx% so the display tracks the
|
|
2511
|
+
// server's real-time occupancy — prompt PLUS generated-so-far, which is
|
|
2512
|
+
// what llama.cpp-style monitors show — instead of lagging it by the whole
|
|
2513
|
+
// in-flight generation (10k reasoning tokens ≈ 8% of a 128k window).
|
|
2514
|
+
// Reset when each stream attempt ends: the persisted reply is then counted
|
|
2515
|
+
// through the history estimate, and reasoning — never resent — drops out.
|
|
2516
|
+
// User steering prompts submitted while a turn is running (the REPL's
|
|
2517
|
+
// "insert now" choice). Drained into the history as synthetic user
|
|
2518
|
+
// messages at the next step boundary — the in-flight model reply and its
|
|
2519
|
+
// tool calls always finish first, so a steer never interrupts mid-step.
|
|
2520
|
+
pendingSteers = [];
|
|
2521
|
+
inflightStreamText = "";
|
|
2425
2522
|
// Lazily resolved auxiliary model (config.smallModel) for cheap side calls
|
|
2426
2523
|
// — compaction summaries, session titles, completion checks. Undefined =
|
|
2427
2524
|
// use the main model; a resolution failure is surfaced once as a notice.
|
|
@@ -2487,12 +2584,56 @@ var AgentLoop = class {
|
|
|
2487
2584
|
getStarMessages() {
|
|
2488
2585
|
return this.messages;
|
|
2489
2586
|
}
|
|
2587
|
+
// Queue a user steering prompt for the running turn. Delivery happens at
|
|
2588
|
+
// the next step boundary (see drainSteers); if the turn ends first the
|
|
2589
|
+
// text stays queued and takeUndeliveredSteers hands it back to the caller.
|
|
2590
|
+
injectUserMessage(text) {
|
|
2591
|
+
const trimmed = text.trim();
|
|
2592
|
+
if (trimmed.length > 0) this.pendingSteers.push(trimmed);
|
|
2593
|
+
}
|
|
2594
|
+
// Steers that never reached a step boundary (turn ended, aborted or
|
|
2595
|
+
// errored first). The caller decides their fate — the REPL re-queues them
|
|
2596
|
+
// as ordinary prompts so user input is never dropped.
|
|
2597
|
+
takeUndeliveredSteers() {
|
|
2598
|
+
return this.pendingSteers.splice(0);
|
|
2599
|
+
}
|
|
2600
|
+
// Append queued steers to the history as synthetic user messages: they
|
|
2601
|
+
// belong to the turn in progress, so /undo, /rewind and compaction must
|
|
2602
|
+
// not treat them as turn boundaries. Returns the number delivered.
|
|
2603
|
+
async drainSteers() {
|
|
2604
|
+
const steers = this.pendingSteers.splice(0);
|
|
2605
|
+
for (const text of steers) {
|
|
2606
|
+
const message = { role: "user", content: text };
|
|
2607
|
+
const meta = { synthetic: "steer" };
|
|
2608
|
+
this.messages.push({ message, meta });
|
|
2609
|
+
await this.persist(message, meta);
|
|
2610
|
+
}
|
|
2611
|
+
return steers.length;
|
|
2612
|
+
}
|
|
2490
2613
|
// Wire-schema token overhead of the active tool set (memoized by
|
|
2491
2614
|
// buildAiTools). The auto-compact budget includes it; surfaced here so the
|
|
2492
2615
|
// status bar ctx% can show the same total the compaction decision uses.
|
|
2493
2616
|
getToolSchemaTokens() {
|
|
2494
2617
|
return this.buildAiTools().schemaTokens;
|
|
2495
2618
|
}
|
|
2619
|
+
// Additive correction from serverTokenBaseline, clamped ≥ 0: an
|
|
2620
|
+
// under-reading provider estimate must pull compaction earlier (the bug
|
|
2621
|
+
// this fixes), but an over-reading one must never push it later than the
|
|
2622
|
+
// local estimate alone would — compacting early is safe, late is fatal.
|
|
2623
|
+
calibrationDelta() {
|
|
2624
|
+
const baseline = this.serverTokenBaseline;
|
|
2625
|
+
if (!baseline) return 0;
|
|
2626
|
+
return Math.max(0, baseline.serverTokens - baseline.localTokens);
|
|
2627
|
+
}
|
|
2628
|
+
// Effective context-window usage in tokens: local BPE estimate + tool
|
|
2629
|
+
// schemas + the server-calibration delta + the in-flight reply's streamed
|
|
2630
|
+
// output. This is the status bar's live ctx% view; the elision/compaction
|
|
2631
|
+
// budgets deliberately use only the first three (history + schemas +
|
|
2632
|
+
// calibration), since in-flight reasoning is never resent and must not
|
|
2633
|
+
// trigger a compaction the next request won't need.
|
|
2634
|
+
estimateContextTokens() {
|
|
2635
|
+
return estimateTokens(this.getMessages()) + this.getToolSchemaTokens() + this.calibrationDelta() + (this.inflightStreamText.length > 0 ? roughTextTokens(this.inflightStreamText) : 0);
|
|
2636
|
+
}
|
|
2496
2637
|
// Auxiliary model for cheap side calls: config.smallModel, resolved once.
|
|
2497
2638
|
// An unresolvable name/key degrades to the main model instead of breaking
|
|
2498
2639
|
// turns; the failure rides out once as a notice at the next turn start.
|
|
@@ -2832,6 +2973,9 @@ var AgentLoop = class {
|
|
|
2832
2973
|
let usedToolsThisTurn = false;
|
|
2833
2974
|
let streamCutContinues = 0;
|
|
2834
2975
|
let openTodos = [];
|
|
2976
|
+
let todoRevision = this.opts.registry.todos.revision;
|
|
2977
|
+
let stepsSinceTodoChange = 0;
|
|
2978
|
+
const todoStaleNudgeSteps = this.opts.todoStaleNudgeSteps ?? TODO_STALE_NUDGE_STEPS;
|
|
2835
2979
|
const maxSteps = config.maxSteps;
|
|
2836
2980
|
let step = 0;
|
|
2837
2981
|
let toolExecutedSinceCheckpoint = false;
|
|
@@ -2868,16 +3012,47 @@ ${finished.result}`
|
|
|
2868
3012
|
await this.persist(note, meta);
|
|
2869
3013
|
}
|
|
2870
3014
|
}
|
|
3015
|
+
const steered = await this.drainSteers();
|
|
3016
|
+
if (steered > 0) {
|
|
3017
|
+
yield {
|
|
3018
|
+
type: "notice",
|
|
3019
|
+
message: `${steered} steering message(s) delivered to the model; taking effect from this step.`
|
|
3020
|
+
};
|
|
3021
|
+
}
|
|
3022
|
+
const currentTodoRevision = registry.todos.revision;
|
|
3023
|
+
if (currentTodoRevision !== todoRevision) {
|
|
3024
|
+
todoRevision = currentTodoRevision;
|
|
3025
|
+
stepsSinceTodoChange = 0;
|
|
3026
|
+
} else if (stepsSinceTodoChange >= todoStaleNudgeSteps) {
|
|
3027
|
+
const open = registry.todos.list().filter((t) => t.status !== "done");
|
|
3028
|
+
if (open.length > 0) {
|
|
3029
|
+
stepsSinceTodoChange = 0;
|
|
3030
|
+
yield {
|
|
3031
|
+
type: "notice",
|
|
3032
|
+
message: `todo list unchanged for ${todoStaleNudgeSteps} steps with ${open.length} open item(s); reminding the model to update it.`
|
|
3033
|
+
};
|
|
3034
|
+
const reminder = {
|
|
3035
|
+
role: "user",
|
|
3036
|
+
content: `[todo reminder] Your todo list still has ${open.length} open item(s), unchanged for ${todoStaleNudgeSteps} steps: ${open.slice(0, 5).map((t) => `"${t.title}"`).join(
|
|
3037
|
+
", "
|
|
3038
|
+
)}${open.length > 5 ? ", \u2026" : ""}. Update the list with todo_write so it reflects what is done and what you are working on now, then continue working.`
|
|
3039
|
+
};
|
|
3040
|
+
const meta = { synthetic: "nudge" };
|
|
3041
|
+
this.messages.push({ message: reminder, meta });
|
|
3042
|
+
await this.persist(reminder, meta);
|
|
3043
|
+
}
|
|
3044
|
+
}
|
|
2871
3045
|
const maxTokens = this.opts.contextMaxTokens ?? config.contextMaxTokens;
|
|
2872
3046
|
const threshold = resolveCompactThreshold(config, maxTokens);
|
|
2873
|
-
const
|
|
3047
|
+
const calibration = this.calibrationDelta();
|
|
3048
|
+
const elided = elideStaleContent(this.messages, threshold, toolSchemaTokens + calibration);
|
|
2874
3049
|
if (elided) {
|
|
2875
3050
|
this.messages = elided.messages;
|
|
2876
3051
|
await this.opts.sessionStore?.replaceMessages([...this.messages]);
|
|
2877
3052
|
this.opts.registry?.resetVolatileState();
|
|
2878
3053
|
}
|
|
2879
3054
|
const compacted = compactMessages(this.messages, threshold, {
|
|
2880
|
-
overheadTokens: toolSchemaTokens
|
|
3055
|
+
overheadTokens: toolSchemaTokens + calibration
|
|
2881
3056
|
});
|
|
2882
3057
|
if (compacted.compacted) {
|
|
2883
3058
|
this.messages = await this.applyCompactionSummary(compacted, signal);
|
|
@@ -2885,6 +3060,7 @@ ${finished.result}`
|
|
|
2885
3060
|
this.turnMarkers = [];
|
|
2886
3061
|
this.opts.registry?.resetVolatileState();
|
|
2887
3062
|
}
|
|
3063
|
+
const requestLocalTokens = estimateTokens(this.getMessages()) + toolSchemaTokens;
|
|
2888
3064
|
const maxRetries = Math.max(0, config.streamMaxRetries);
|
|
2889
3065
|
const baseDelay = this.opts.retryDelayMs ?? STREAM_RETRY_BASE_DELAY_MS;
|
|
2890
3066
|
let text = "";
|
|
@@ -2905,22 +3081,37 @@ ${finished.result}`
|
|
|
2905
3081
|
lastUsage = void 0;
|
|
2906
3082
|
sawReasoning = false;
|
|
2907
3083
|
wasTruncated = false;
|
|
3084
|
+
this.inflightStreamText = "";
|
|
2908
3085
|
const timeoutScale = Math.min(attempt + 1, 3);
|
|
2909
3086
|
try {
|
|
2910
3087
|
for await (const event of this.streamOnce(aiTools, signal, timeoutScale)) {
|
|
2911
3088
|
if (event.type === "text-delta") {
|
|
2912
3089
|
text += event.text;
|
|
3090
|
+
this.inflightStreamText += event.text;
|
|
2913
3091
|
yield event;
|
|
2914
3092
|
} else if (event.type === "reasoning") {
|
|
2915
3093
|
sawReasoning = true;
|
|
3094
|
+
this.inflightStreamText += event.text;
|
|
2916
3095
|
yield event;
|
|
2917
3096
|
} else if (event.type === "tool-call") {
|
|
2918
|
-
|
|
3097
|
+
this.inflightStreamText += JSON.stringify(event.args ?? null);
|
|
3098
|
+
toolCalls.push({
|
|
3099
|
+
id: event.id,
|
|
3100
|
+
name: event.name,
|
|
3101
|
+
args: event.args,
|
|
3102
|
+
...event.invalidArgs !== void 0 ? { invalidArgs: event.invalidArgs } : {}
|
|
3103
|
+
});
|
|
2919
3104
|
yield event;
|
|
2920
3105
|
} else if (event.type === "finish") {
|
|
2921
3106
|
lastFinishReason = event.finishReason;
|
|
2922
3107
|
if (event.truncated) wasTruncated = true;
|
|
2923
3108
|
lastUsage = event.usage;
|
|
3109
|
+
if (event.usage && event.usage.promptTokens > 0) {
|
|
3110
|
+
this.serverTokenBaseline = {
|
|
3111
|
+
localTokens: requestLocalTokens,
|
|
3112
|
+
serverTokens: event.usage.promptTokens
|
|
3113
|
+
};
|
|
3114
|
+
}
|
|
2924
3115
|
yield this.foldSubagentUsage(event);
|
|
2925
3116
|
} else if (event.type === "error") {
|
|
2926
3117
|
failure = event.error;
|
|
@@ -2933,6 +3124,7 @@ ${finished.result}`
|
|
|
2933
3124
|
failure = toStreamErrorInfo(error);
|
|
2934
3125
|
}
|
|
2935
3126
|
}
|
|
3127
|
+
this.inflightStreamText = "";
|
|
2936
3128
|
if (signal.aborted) {
|
|
2937
3129
|
yield* this.persistInterrupted(text, toolCalls);
|
|
2938
3130
|
await this.runStopHooks("aborted");
|
|
@@ -2985,6 +3177,14 @@ ${finished.result}`
|
|
|
2985
3177
|
return;
|
|
2986
3178
|
}
|
|
2987
3179
|
if (!hasOutput()) {
|
|
3180
|
+
const steered2 = await this.drainSteers();
|
|
3181
|
+
if (steered2 > 0) {
|
|
3182
|
+
yield {
|
|
3183
|
+
type: "notice",
|
|
3184
|
+
message: `${steered2} steering message(s) delivered to the model after an empty reply.`
|
|
3185
|
+
};
|
|
3186
|
+
continue;
|
|
3187
|
+
}
|
|
2988
3188
|
if (isSteerableEmptyFinish(lastFinishReason) && config.permissionMode !== "plan" && autoContinues < config.maxAutoContinues) {
|
|
2989
3189
|
autoContinues++;
|
|
2990
3190
|
lastWasNudge = true;
|
|
@@ -3026,6 +3226,16 @@ ${finished.result}`
|
|
|
3026
3226
|
this.messages.push({ message: assistantMessage });
|
|
3027
3227
|
await this.persist(assistantMessage);
|
|
3028
3228
|
if (toolCalls.length === 0) {
|
|
3229
|
+
const steered2 = await this.drainSteers();
|
|
3230
|
+
if (steered2 > 0) {
|
|
3231
|
+
autoContinues = 0;
|
|
3232
|
+
lastWasNudge = false;
|
|
3233
|
+
yield {
|
|
3234
|
+
type: "notice",
|
|
3235
|
+
message: `${steered2} steering message(s) delivered to the model; the turn continues.`
|
|
3236
|
+
};
|
|
3237
|
+
continue;
|
|
3238
|
+
}
|
|
3029
3239
|
if (wasTruncated) {
|
|
3030
3240
|
if (config.permissionMode !== "plan" && streamCutContinues < config.streamMaxRetries) {
|
|
3031
3241
|
streamCutContinues++;
|
|
@@ -3114,7 +3324,8 @@ ${finished.result}`
|
|
|
3114
3324
|
try {
|
|
3115
3325
|
for (const call of toolCalls) {
|
|
3116
3326
|
if (signal.aborted) break;
|
|
3117
|
-
const refusal =
|
|
3327
|
+
const refusal = call.invalidArgs !== void 0 ? `Tool call not executed \u2014 ${call.invalidArgs}
|
|
3328
|
+
Reissue ${call.name} with valid arguments.` : this.doomLoopRefusal(call);
|
|
3118
3329
|
if (refusal !== null) {
|
|
3119
3330
|
yield { type: "notice", message: refusal };
|
|
3120
3331
|
} else {
|
|
@@ -3194,6 +3405,7 @@ ${finished.result}`
|
|
|
3194
3405
|
return;
|
|
3195
3406
|
}
|
|
3196
3407
|
step++;
|
|
3408
|
+
stepsSinceTodoChange++;
|
|
3197
3409
|
}
|
|
3198
3410
|
}
|
|
3199
3411
|
// The permission mode can change at runtime, so the system message is
|
|
@@ -3507,4 +3719,4 @@ export {
|
|
|
3507
3719
|
attachTurnContext,
|
|
3508
3720
|
AgentLoop
|
|
3509
3721
|
};
|
|
3510
|
-
//# sourceMappingURL=chunk-
|
|
3722
|
+
//# sourceMappingURL=chunk-F5RWZMQY.js.map
|