scream-code 0.6.8 → 0.6.9
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/{app-w3Sui-3y.mjs → app-BRE6Pm0i.mjs} +1243 -265
- package/dist/main.mjs +1 -1
- package/package.json +6 -5
- package/scripts/postinstall/reach.mjs +2 -2
- package/scripts/postinstall/ui.mjs +4 -4
- package/scripts/postinstall.mjs +2 -2
- package/dist/assets/tokenizers.darwin-universal-BJ7w7c9- 2.node +0 -0
- package/dist/chunk-apG1qJts 2.mjs +0 -41
- package/dist/dist-0bMQWc-B 2.mjs +0 -1258
- package/dist/esm-Du2MwXei 2.mjs +0 -8590
- package/dist/from-CKE2n10i 2.mjs +0 -3849
- package/dist/main 2.mjs +0 -15
- package/dist/main.d 2.mts +0 -2
- package/dist/multipart-parser-BxHsVgPe 2.mjs +0 -299
- package/dist/src-BMbLXrAA 3.mjs +0 -1182
- package/dist/suppress-sqlite-warning-C2VB0doZ 2.mjs +0 -52
|
@@ -43,10 +43,11 @@ import * as z4mini from "zod/v4-mini";
|
|
|
43
43
|
import process$1 from "node:process";
|
|
44
44
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
45
45
|
import { Command, Option } from "commander";
|
|
46
|
-
import { CombinedAutocompleteProvider, Container, Editor, Image, Input, Key, Markdown, ProcessTerminal, Spacer, TUI, Text, decodeKittyPrintable, deleteAllKittyImages, fuzzyFilter, fuzzyMatch, getCapabilities, isKeyRelease, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
46
|
+
import { CombinedAutocompleteProvider, Container, Editor, Image, Input, Key, Markdown, ProcessTerminal, Spacer, TUI, Text, decodeKittyPrintable, deleteAllKittyImages, fuzzyFilter, fuzzyMatch, getCapabilities, getImageDimensions, isKeyRelease, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
47
47
|
import chalk, { chalkStderr } from "chalk";
|
|
48
48
|
import { createInterface } from "node:readline/promises";
|
|
49
49
|
import { highlight, supportsLanguage } from "cli-highlight";
|
|
50
|
+
import { diffWords } from "diff";
|
|
50
51
|
import { gt, valid } from "semver";
|
|
51
52
|
import { createInterface as createInterface$1 } from "node:readline";
|
|
52
53
|
//#region ../../node_modules/.pnpm/pathe@2.0.3/node_modules/pathe/dist/shared/pathe.M-eThtNZ.mjs
|
|
@@ -731,6 +732,49 @@ function createToolMessage(toolCallId, output) {
|
|
|
731
732
|
};
|
|
732
733
|
}
|
|
733
734
|
//#endregion
|
|
735
|
+
//#region ../../packages/ltod/src/rate-limit-utils.ts
|
|
736
|
+
const QUOTA_EXHAUSTED_BACKOFF_MS = 1800 * 1e3;
|
|
737
|
+
const RATE_LIMIT_EXCEEDED_BACKOFF_MS = 30 * 1e3;
|
|
738
|
+
const MODEL_CAPACITY_BASE_MS = 45 * 1e3;
|
|
739
|
+
const MODEL_CAPACITY_JITTER_MS = 30 * 1e3;
|
|
740
|
+
const SERVER_ERROR_BACKOFF_MS = 20 * 1e3;
|
|
741
|
+
const ACCOUNT_RATE_LIMIT_PATTERN = /\baccount(?:'s)?\b[^\n]{0,80}\brate.?limit\b|\brate.?limit\b[^\n]{0,80}\baccount\b/i;
|
|
742
|
+
const INSUFFICIENT_BALANCE_PATTERN = /insufficient.?balance/i;
|
|
743
|
+
/**
|
|
744
|
+
* Classify a rate-limit error message into a reason category.
|
|
745
|
+
*
|
|
746
|
+
* Priority: QUOTA (explicit "quota will reset") > MODEL_CAPACITY > QUOTA (account)
|
|
747
|
+
* > RATE_LIMIT > QUOTA (generic) > SERVER_ERROR > UNKNOWN.
|
|
748
|
+
*
|
|
749
|
+
* "quota will reset" / "exhausted your capacity" short-circuits to QUOTA before
|
|
750
|
+
* the MODEL_CAPACITY fallthrough — the word "capacity" appears in both but the
|
|
751
|
+
* long-wait signal means credential rotation, not a 60s backoff.
|
|
752
|
+
*/
|
|
753
|
+
function parseRateLimitReason(errorMessage) {
|
|
754
|
+
const lower = errorMessage.toLowerCase();
|
|
755
|
+
if (lower.includes("quota will reset") || lower.includes("exhausted your capacity")) return "QUOTA_EXHAUSTED";
|
|
756
|
+
if (lower.includes("capacity") || lower.includes("overloaded") || lower.includes("529") || lower.includes("503") || lower.includes("resource exhausted")) return "MODEL_CAPACITY_EXHAUSTED";
|
|
757
|
+
if (ACCOUNT_RATE_LIMIT_PATTERN.test(errorMessage)) return "QUOTA_EXHAUSTED";
|
|
758
|
+
if (lower.includes("per minute") || lower.includes("rate limit") || lower.includes("too many requests")) return "RATE_LIMIT_EXCEEDED";
|
|
759
|
+
if (lower.includes("exhausted") || lower.includes("quota") || lower.includes("usage limit") || INSUFFICIENT_BALANCE_PATTERN.test(errorMessage)) return "QUOTA_EXHAUSTED";
|
|
760
|
+
if (lower.includes("500") || lower.includes("internal error") || lower.includes("internal server error")) return "SERVER_ERROR";
|
|
761
|
+
return "UNKNOWN";
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Backoff in ms for a given reason. MODEL_CAPACITY gets jitter to avoid
|
|
765
|
+
* thundering herd. UNKNOWN falls back to the conservative QUOTA backoff
|
|
766
|
+
* — safer to wait too long than to hammer a rate-limited endpoint.
|
|
767
|
+
*/
|
|
768
|
+
function calculateRateLimitBackoffMs(reason) {
|
|
769
|
+
switch (reason) {
|
|
770
|
+
case "QUOTA_EXHAUSTED": return QUOTA_EXHAUSTED_BACKOFF_MS;
|
|
771
|
+
case "RATE_LIMIT_EXCEEDED": return RATE_LIMIT_EXCEEDED_BACKOFF_MS;
|
|
772
|
+
case "MODEL_CAPACITY_EXHAUSTED": return MODEL_CAPACITY_BASE_MS + Math.random() * MODEL_CAPACITY_JITTER_MS;
|
|
773
|
+
case "SERVER_ERROR": return SERVER_ERROR_BACKOFF_MS;
|
|
774
|
+
default: return QUOTA_EXHAUSTED_BACKOFF_MS;
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
//#endregion
|
|
734
778
|
//#region ../../packages/ltod/src/errors.ts
|
|
735
779
|
/**
|
|
736
780
|
* Base error for all chat provider errors.
|
|
@@ -784,12 +828,16 @@ var APIContextOverflowError = class extends APIStatusError {
|
|
|
784
828
|
};
|
|
785
829
|
/**
|
|
786
830
|
* HTTP status error that specifically means the provider rate-limited the
|
|
787
|
-
* request.
|
|
831
|
+
* request. Carries a `reason` so retry logic can pick the right backoff
|
|
832
|
+
* (quota exhaustion needs 30min or a credential switch; a transient 529
|
|
833
|
+
* just needs 45s) instead of treating every 429 the same.
|
|
788
834
|
*/
|
|
789
835
|
var APIProviderRateLimitError = class extends APIStatusError {
|
|
790
|
-
|
|
836
|
+
reason;
|
|
837
|
+
constructor(message, requestId, reason) {
|
|
791
838
|
super(429, message, requestId);
|
|
792
839
|
this.name = "APIProviderRateLimitError";
|
|
840
|
+
this.reason = reason ?? "UNKNOWN";
|
|
793
841
|
}
|
|
794
842
|
};
|
|
795
843
|
/**
|
|
@@ -804,6 +852,7 @@ var APIEmptyResponseError = class extends ChatProviderError {
|
|
|
804
852
|
function isRetryableGenerateError(error) {
|
|
805
853
|
if (error instanceof APIConnectionError$3 || error instanceof APITimeoutError) return true;
|
|
806
854
|
if (error instanceof APIEmptyResponseError) return true;
|
|
855
|
+
if (error instanceof APIProviderRateLimitError) return error.reason !== "QUOTA_EXHAUSTED";
|
|
807
856
|
return error instanceof APIStatusError && [
|
|
808
857
|
429,
|
|
809
858
|
500,
|
|
@@ -826,7 +875,7 @@ function isContextOverflowErrorCode(code) {
|
|
|
826
875
|
return code === "context_length_exceeded";
|
|
827
876
|
}
|
|
828
877
|
function normalizeAPIStatusError(statusCode, message, requestId) {
|
|
829
|
-
if (statusCode === 429) return new APIProviderRateLimitError(message, requestId);
|
|
878
|
+
if (statusCode === 429) return new APIProviderRateLimitError(message, requestId, parseRateLimitReason(message));
|
|
830
879
|
if (isContextOverflowStatusError(statusCode, message)) return new APIContextOverflowError(statusCode, message, requestId);
|
|
831
880
|
return new APIStatusError(statusCode, message, requestId);
|
|
832
881
|
}
|
|
@@ -8951,6 +9000,7 @@ function convertAnthropicError(error) {
|
|
|
8951
9000
|
const reqId = error.requestID ?? null;
|
|
8952
9001
|
return normalizeAPIStatusError(error.status, error.message, reqId);
|
|
8953
9002
|
}
|
|
9003
|
+
if (error instanceof APIUserAbortError$2) throw new DOMException("The operation was aborted.", "AbortError");
|
|
8954
9004
|
if (error instanceof AnthropicError) return new ChatProviderError(`Anthropic error: ${error.message}`);
|
|
8955
9005
|
if (error instanceof Error) return new ChatProviderError(`Error: ${error.message}`);
|
|
8956
9006
|
return new ChatProviderError(`Error: ${String(error)}`);
|
|
@@ -9079,7 +9129,7 @@ var AnthropicStreamedMessage = class {
|
|
|
9079
9129
|
type: "function",
|
|
9080
9130
|
id: block.id,
|
|
9081
9131
|
name: block.name,
|
|
9082
|
-
arguments:
|
|
9132
|
+
arguments: null,
|
|
9083
9133
|
_streamIndex: blockIndex
|
|
9084
9134
|
};
|
|
9085
9135
|
break;
|
|
@@ -48130,6 +48180,7 @@ function convertOpenAIError(error) {
|
|
|
48130
48180
|
return normalizeAPIStatusError(error.status, error.message, reqId);
|
|
48131
48181
|
}
|
|
48132
48182
|
if (error instanceof APIError && error.constructor === APIError && error.error === void 0) return classifyBaseApiError(error.message);
|
|
48183
|
+
if (error instanceof APIUserAbortError) throw new DOMException("The operation was aborted.", "AbortError");
|
|
48133
48184
|
if (error instanceof OpenAIError) return new ChatProviderError(`Error: ${error.message}`);
|
|
48134
48185
|
if (error instanceof Error) return classifyBaseApiError(error.message);
|
|
48135
48186
|
return new ChatProviderError(`Error: ${String(error)}`);
|
|
@@ -48187,7 +48238,7 @@ function extractUsage(usage) {
|
|
|
48187
48238
|
if (typeof details["cached_tokens"] === "number") cached = details["cached_tokens"];
|
|
48188
48239
|
}
|
|
48189
48240
|
return {
|
|
48190
|
-
inputOther: promptTokens - cached,
|
|
48241
|
+
inputOther: Math.max(0, promptTokens - cached),
|
|
48191
48242
|
output: completionTokens,
|
|
48192
48243
|
inputCacheRead: cached,
|
|
48193
48244
|
inputCacheCreation: 0
|
|
@@ -48426,6 +48477,10 @@ function convertChatCompletionStreamToolCall(toolCall, bufferedByIndex) {
|
|
|
48426
48477
|
}
|
|
48427
48478
|
//#endregion
|
|
48428
48479
|
//#region ../../packages/ltod/src/providers/scream.ts
|
|
48480
|
+
const OPENAI_CHAT_TOOL_CALL_ID_POLICY$1 = {
|
|
48481
|
+
normalize: (id) => sanitizeToolCallId(id, 64),
|
|
48482
|
+
maxLength: 64
|
|
48483
|
+
};
|
|
48429
48484
|
function isEffectivelyEmptyContent(parts) {
|
|
48430
48485
|
for (const part of parts) {
|
|
48431
48486
|
if (part.type !== "text") return false;
|
|
@@ -48632,12 +48687,13 @@ var ScreamChatProvider = class {
|
|
|
48632
48687
|
};
|
|
48633
48688
|
}
|
|
48634
48689
|
async generate(systemPrompt, tools, history, options) {
|
|
48690
|
+
const normalizedHistory = normalizeToolCallIdsForProvider(history, OPENAI_CHAT_TOOL_CALL_ID_POLICY$1);
|
|
48635
48691
|
const messages = [];
|
|
48636
48692
|
if (systemPrompt) messages.push({
|
|
48637
48693
|
role: "system",
|
|
48638
48694
|
content: systemPrompt
|
|
48639
48695
|
});
|
|
48640
|
-
for (const msg of
|
|
48696
|
+
for (const msg of normalizedHistory) messages.push(convertMessage$2(msg));
|
|
48641
48697
|
const kwargs = { ...this._generationKwargs };
|
|
48642
48698
|
for (const key of Object.keys(kwargs)) if (kwargs[key] === void 0) delete kwargs[key];
|
|
48643
48699
|
if (kwargs["max_completion_tokens"] === void 0 && kwargs["max_tokens"] !== void 0) kwargs["max_completion_tokens"] = kwargs["max_tokens"];
|
|
@@ -49324,7 +49380,7 @@ var OpenAIResponsesStreamedMessage = class {
|
|
|
49324
49380
|
const details = readObjectField(usage, "input_tokens_details");
|
|
49325
49381
|
const cached = details ? readNumberField(details, "cached_tokens") ?? 0 : 0;
|
|
49326
49382
|
this._usage = {
|
|
49327
|
-
inputOther: inputTokens - cached,
|
|
49383
|
+
inputOther: Math.max(0, inputTokens - cached),
|
|
49328
49384
|
output: outputTokens,
|
|
49329
49385
|
inputCacheRead: cached,
|
|
49330
49386
|
inputCacheCreation: 0
|
|
@@ -49718,6 +49774,227 @@ function catalogProviderModels(entry) {
|
|
|
49718
49774
|
return Object.values(models).map((model) => catalogModelToCapability(model)).filter((model) => model !== void 0);
|
|
49719
49775
|
}
|
|
49720
49776
|
//#endregion
|
|
49777
|
+
//#region ../../packages/ltod/src/idle-iterator.ts
|
|
49778
|
+
/**
|
|
49779
|
+
* Idle-stream watchdog.
|
|
49780
|
+
*
|
|
49781
|
+
* Ported from oh-my-pi `packages/ai/src/utils/idle-iterator.ts:154-382`.
|
|
49782
|
+
* Reasoning models (deepseek-reasoner, mimo thinking) can sit silent for
|
|
49783
|
+
* 30s+ between tokens; without a watchdog the stream hangs forever and the
|
|
49784
|
+
* user thinks the agent died. This wraps any async iterable with a per-item
|
|
49785
|
+
* idle deadline, turning a stalled stream into a retryable `APITimeoutError`.
|
|
49786
|
+
*
|
|
49787
|
+
* Simplified from the upstream version: no `armPreResponseTimeout`, no
|
|
49788
|
+
* `iterateWithTerminalGrace`, no per-provider env-var aliases. The core
|
|
49789
|
+
* racer-reuse and single-timer-self-rearm design is preserved verbatim.
|
|
49790
|
+
*/
|
|
49791
|
+
const DEFAULT_STREAM_IDLE_TIMEOUT_MS = 6e4;
|
|
49792
|
+
const DEFAULT_STREAM_FIRST_ITEM_TIMEOUT_MS = 12e4;
|
|
49793
|
+
const RACER_REMINT_INTERVAL = 1024;
|
|
49794
|
+
function withResolvers() {
|
|
49795
|
+
let resolve;
|
|
49796
|
+
let reject;
|
|
49797
|
+
return {
|
|
49798
|
+
promise: new Promise((res, rej) => {
|
|
49799
|
+
resolve = res;
|
|
49800
|
+
reject = rej;
|
|
49801
|
+
}),
|
|
49802
|
+
resolve,
|
|
49803
|
+
reject
|
|
49804
|
+
};
|
|
49805
|
+
}
|
|
49806
|
+
function normalizeTimeoutMs$1(value, fallback) {
|
|
49807
|
+
if (value === void 0) return fallback;
|
|
49808
|
+
const parsed = Number(value);
|
|
49809
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
49810
|
+
if (parsed <= 0) return void 0;
|
|
49811
|
+
return Math.trunc(parsed);
|
|
49812
|
+
}
|
|
49813
|
+
function getStreamIdleTimeoutMs(fallback = DEFAULT_STREAM_IDLE_TIMEOUT_MS) {
|
|
49814
|
+
return normalizeTimeoutMs$1(process.env["SCREAM_STREAM_IDLE_TIMEOUT_MS"], fallback);
|
|
49815
|
+
}
|
|
49816
|
+
function getStreamFirstItemTimeoutMs(idleTimeoutMs, fallback = DEFAULT_STREAM_FIRST_ITEM_TIMEOUT_MS) {
|
|
49817
|
+
const floor = idleTimeoutMs === void 0 ? fallback : Math.max(fallback, idleTimeoutMs);
|
|
49818
|
+
return normalizeTimeoutMs$1(process.env["SCREAM_STREAM_FIRST_ITEM_TIMEOUT_MS"], floor);
|
|
49819
|
+
}
|
|
49820
|
+
/**
|
|
49821
|
+
* Thrown when the stream stalls longer than the configured idle deadline.
|
|
49822
|
+
* Extends `APITimeoutError` so `isRetryableGenerateError` picks it up
|
|
49823
|
+
* automatically — the agent retries instead of dying.
|
|
49824
|
+
*/
|
|
49825
|
+
var StreamIdleTimeoutError = class extends APITimeoutError {
|
|
49826
|
+
phase;
|
|
49827
|
+
constructor(phase, message) {
|
|
49828
|
+
super(message);
|
|
49829
|
+
this.name = "StreamIdleTimeoutError";
|
|
49830
|
+
this.phase = phase;
|
|
49831
|
+
}
|
|
49832
|
+
};
|
|
49833
|
+
/**
|
|
49834
|
+
* Yields items from an async iterable while enforcing a maximum idle gap.
|
|
49835
|
+
*
|
|
49836
|
+
* The first item may use a longer timeout (reasoning models can take 60s+ to
|
|
49837
|
+
* emit the first token); subsequent gaps use the shorter idle timeout.
|
|
49838
|
+
*/
|
|
49839
|
+
async function* iterateWithIdleTimeout(iterable, options) {
|
|
49840
|
+
const firstItemTimeoutMs = options.firstItemTimeoutMs ?? options.idleTimeoutMs;
|
|
49841
|
+
const firstItemDeadlineMs = firstItemTimeoutMs !== void 0 && firstItemTimeoutMs > 0 ? Date.now() + firstItemTimeoutMs : void 0;
|
|
49842
|
+
const abortSignal = options.abortSignal;
|
|
49843
|
+
const iterator = iterable[Symbol.asyncIterator]();
|
|
49844
|
+
let iteratorClosed = false;
|
|
49845
|
+
const closeIterator = () => {
|
|
49846
|
+
if (iteratorClosed) return;
|
|
49847
|
+
iteratorClosed = true;
|
|
49848
|
+
const returnPromise = iterator.return?.();
|
|
49849
|
+
if (returnPromise) returnPromise.catch(() => {});
|
|
49850
|
+
};
|
|
49851
|
+
if (abortSignal?.aborted) {
|
|
49852
|
+
closeIterator();
|
|
49853
|
+
throw abortReason(abortSignal);
|
|
49854
|
+
}
|
|
49855
|
+
const withRacy = (promise) => promise.then((result) => ({
|
|
49856
|
+
kind: "next",
|
|
49857
|
+
result
|
|
49858
|
+
}), (error) => ({
|
|
49859
|
+
kind: "error",
|
|
49860
|
+
error
|
|
49861
|
+
}));
|
|
49862
|
+
let awaitingFirstItem = true;
|
|
49863
|
+
const isProgressItem = (item) => {
|
|
49864
|
+
if (!options.isProgressItem) return true;
|
|
49865
|
+
try {
|
|
49866
|
+
return options.isProgressItem(item);
|
|
49867
|
+
} catch {
|
|
49868
|
+
return true;
|
|
49869
|
+
}
|
|
49870
|
+
};
|
|
49871
|
+
let lastProgressAt = Date.now();
|
|
49872
|
+
const noTimeoutEnforced = (firstItemTimeoutMs === void 0 || firstItemTimeoutMs <= 0) && (options.idleTimeoutMs === void 0 || options.idleTimeoutMs <= 0);
|
|
49873
|
+
let abortPromise;
|
|
49874
|
+
let abortListener;
|
|
49875
|
+
let resolveAbort;
|
|
49876
|
+
if (abortSignal) {
|
|
49877
|
+
const { promise, resolve } = withResolvers();
|
|
49878
|
+
resolveAbort = resolve;
|
|
49879
|
+
abortListener = () => resolveAbort?.({ kind: "abort" });
|
|
49880
|
+
abortSignal.addEventListener("abort", abortListener, { once: true });
|
|
49881
|
+
abortPromise = promise;
|
|
49882
|
+
}
|
|
49883
|
+
let timeoutPromise;
|
|
49884
|
+
let resolveTimeout;
|
|
49885
|
+
let timeoutFired = false;
|
|
49886
|
+
let timer;
|
|
49887
|
+
let timerFireAtMs = Infinity;
|
|
49888
|
+
const currentDeadlineMs = () => {
|
|
49889
|
+
if (awaitingFirstItem) return firstItemDeadlineMs;
|
|
49890
|
+
if (options.idleTimeoutMs !== void 0 && options.idleTimeoutMs > 0) return lastProgressAt + options.idleTimeoutMs;
|
|
49891
|
+
};
|
|
49892
|
+
const onTimerFire = () => {
|
|
49893
|
+
timer = void 0;
|
|
49894
|
+
timerFireAtMs = Infinity;
|
|
49895
|
+
const deadlineMs = currentDeadlineMs();
|
|
49896
|
+
if (deadlineMs === void 0) return;
|
|
49897
|
+
const remainingMs = deadlineMs - Date.now();
|
|
49898
|
+
if (remainingMs > 0) {
|
|
49899
|
+
timerFireAtMs = deadlineMs;
|
|
49900
|
+
timer = setTimeout(onTimerFire, remainingMs);
|
|
49901
|
+
return;
|
|
49902
|
+
}
|
|
49903
|
+
timeoutFired = true;
|
|
49904
|
+
resolveTimeout?.({ kind: "timeout" });
|
|
49905
|
+
};
|
|
49906
|
+
const armTimer = (deadlineMs) => {
|
|
49907
|
+
if (timeoutPromise === void 0 || timeoutFired) {
|
|
49908
|
+
const { promise, resolve } = withResolvers();
|
|
49909
|
+
timeoutPromise = promise;
|
|
49910
|
+
resolveTimeout = resolve;
|
|
49911
|
+
timeoutFired = false;
|
|
49912
|
+
}
|
|
49913
|
+
if (timer !== void 0) {
|
|
49914
|
+
if (timerFireAtMs <= deadlineMs) return;
|
|
49915
|
+
clearTimeout(timer);
|
|
49916
|
+
}
|
|
49917
|
+
timerFireAtMs = deadlineMs;
|
|
49918
|
+
timer = setTimeout(onTimerFire, Math.max(0, deadlineMs - Date.now()));
|
|
49919
|
+
};
|
|
49920
|
+
try {
|
|
49921
|
+
let raceCount = 0;
|
|
49922
|
+
while (true) {
|
|
49923
|
+
if (++raceCount % RACER_REMINT_INTERVAL === 0) {
|
|
49924
|
+
if (abortPromise !== void 0 && !abortSignal.aborted) {
|
|
49925
|
+
const { promise, resolve } = withResolvers();
|
|
49926
|
+
resolveAbort = resolve;
|
|
49927
|
+
abortPromise = promise;
|
|
49928
|
+
}
|
|
49929
|
+
if (timeoutPromise !== void 0 && !timeoutFired) {
|
|
49930
|
+
const { promise, resolve } = withResolvers();
|
|
49931
|
+
resolveTimeout = resolve;
|
|
49932
|
+
timeoutPromise = promise;
|
|
49933
|
+
}
|
|
49934
|
+
}
|
|
49935
|
+
let activeTimeoutMs;
|
|
49936
|
+
if (awaitingFirstItem) {
|
|
49937
|
+
if (firstItemDeadlineMs !== void 0) {
|
|
49938
|
+
activeTimeoutMs = firstItemDeadlineMs - Date.now();
|
|
49939
|
+
if (activeTimeoutMs <= 0) {
|
|
49940
|
+
closeIterator();
|
|
49941
|
+
throw new StreamIdleTimeoutError("first-item", options.firstItemErrorMessage ?? options.errorMessage);
|
|
49942
|
+
}
|
|
49943
|
+
}
|
|
49944
|
+
} else if (options.idleTimeoutMs !== void 0 && options.idleTimeoutMs > 0) {
|
|
49945
|
+
activeTimeoutMs = options.idleTimeoutMs - (Date.now() - lastProgressAt);
|
|
49946
|
+
if (activeTimeoutMs <= 0) {
|
|
49947
|
+
closeIterator();
|
|
49948
|
+
throw new StreamIdleTimeoutError("idle", options.errorMessage);
|
|
49949
|
+
}
|
|
49950
|
+
}
|
|
49951
|
+
const racers = [withRacy(iterator.next())];
|
|
49952
|
+
if (!noTimeoutEnforced && activeTimeoutMs !== void 0 && activeTimeoutMs > 0 && activeTimeoutMs !== void 0) {
|
|
49953
|
+
armTimer(Date.now() + activeTimeoutMs);
|
|
49954
|
+
racers.push(timeoutPromise);
|
|
49955
|
+
}
|
|
49956
|
+
if (abortPromise) racers.push(abortPromise);
|
|
49957
|
+
let continuing = false;
|
|
49958
|
+
try {
|
|
49959
|
+
const outcome = await Promise.race(racers);
|
|
49960
|
+
if (outcome.kind === "abort") {
|
|
49961
|
+
closeIterator();
|
|
49962
|
+
throw abortReason(abortSignal);
|
|
49963
|
+
}
|
|
49964
|
+
if (outcome.kind === "timeout") {
|
|
49965
|
+
closeIterator();
|
|
49966
|
+
throw new StreamIdleTimeoutError(!awaitingFirstItem ? "idle" : "first-item", !awaitingFirstItem ? options.errorMessage : options.firstItemErrorMessage ?? options.errorMessage);
|
|
49967
|
+
}
|
|
49968
|
+
if (outcome.kind === "error") throw outcome.error;
|
|
49969
|
+
if (outcome.result.done) {
|
|
49970
|
+
awaitingFirstItem = false;
|
|
49971
|
+
return;
|
|
49972
|
+
}
|
|
49973
|
+
const item = outcome.result.value;
|
|
49974
|
+
if (isProgressItem(item)) {
|
|
49975
|
+
awaitingFirstItem = false;
|
|
49976
|
+
lastProgressAt = Date.now();
|
|
49977
|
+
}
|
|
49978
|
+
yield item;
|
|
49979
|
+
continuing = true;
|
|
49980
|
+
} finally {
|
|
49981
|
+
if (!continuing) closeIterator();
|
|
49982
|
+
}
|
|
49983
|
+
}
|
|
49984
|
+
} finally {
|
|
49985
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
49986
|
+
resolveTimeout?.({ kind: "timeout" });
|
|
49987
|
+
if (abortListener && abortSignal) abortSignal.removeEventListener("abort", abortListener);
|
|
49988
|
+
resolveAbort?.({ kind: "abort" });
|
|
49989
|
+
}
|
|
49990
|
+
}
|
|
49991
|
+
function abortReason(signal) {
|
|
49992
|
+
const reason = signal.reason;
|
|
49993
|
+
if (reason instanceof Error) return reason;
|
|
49994
|
+
if (typeof reason === "string") return new Error(reason);
|
|
49995
|
+
return /* @__PURE__ */ new Error("Request was aborted");
|
|
49996
|
+
}
|
|
49997
|
+
//#endregion
|
|
49721
49998
|
//#region ../../packages/ltod/src/generate.ts
|
|
49722
49999
|
/**
|
|
49723
50000
|
* Generate one assistant message by streaming from the given provider.
|
|
@@ -49755,7 +50032,16 @@ async function generate(provider, systemPrompt, tools, history, callbacks, optio
|
|
|
49755
50032
|
options?.onRequestStart?.();
|
|
49756
50033
|
const stream = await provider.generate(systemPrompt, tools, history, options);
|
|
49757
50034
|
await throwIfAborted(options?.signal, stream);
|
|
49758
|
-
|
|
50035
|
+
const idleTimeoutMs = getStreamIdleTimeoutMs();
|
|
50036
|
+
const firstItemTimeoutMs = getStreamFirstItemTimeoutMs(idleTimeoutMs);
|
|
50037
|
+
const watchedStream = iterateWithIdleTimeout(stream, {
|
|
50038
|
+
idleTimeoutMs,
|
|
50039
|
+
firstItemTimeoutMs,
|
|
50040
|
+
abortSignal: options?.signal,
|
|
50041
|
+
errorMessage: `Stream stalled — no data for ${idleTimeoutMs ?? 0}ms. Provider: ${provider.name}, model: ${provider.modelName}`,
|
|
50042
|
+
firstItemErrorMessage: `Stream stalled — no first token within ${firstItemTimeoutMs ?? 0}ms. Provider: ${provider.name}, model: ${provider.modelName}`
|
|
50043
|
+
});
|
|
50044
|
+
for await (const part of watchedStream) {
|
|
49759
50045
|
await throwIfAborted(options?.signal, stream);
|
|
49760
50046
|
if (callbacks?.onMessagePart !== void 0) {
|
|
49761
50047
|
await callbacks.onMessagePart(deepCopyPart(part));
|
|
@@ -49799,13 +50085,7 @@ function throwAbortError() {
|
|
|
49799
50085
|
throw new DOMException("The operation was aborted.", "AbortError");
|
|
49800
50086
|
}
|
|
49801
50087
|
async function cancelStream(stream) {
|
|
49802
|
-
|
|
49803
|
-
try {
|
|
49804
|
-
await cancelable.cancel?.();
|
|
49805
|
-
} catch {}
|
|
49806
|
-
try {
|
|
49807
|
-
await cancelable.return?.();
|
|
49808
|
-
} catch {}
|
|
50088
|
+
await stream[Symbol.asyncIterator]().return?.();
|
|
49809
50089
|
}
|
|
49810
50090
|
async function throwIfAborted(signal, stream) {
|
|
49811
50091
|
if (!signal?.aborted) return;
|
|
@@ -49893,8 +50173,8 @@ function addUsage(a, b) {
|
|
|
49893
50173
|
/**
|
|
49894
50174
|
* Concrete provider adapters stay off the root barrel because their SDK type
|
|
49895
50175
|
* graphs pollute downstream declaration bundles. Import them from subpaths:
|
|
49896
|
-
* `@scream-
|
|
49897
|
-
* `@scream-
|
|
50176
|
+
* `@scream-code/ltod/providers/scream`,
|
|
50177
|
+
* `@scream-code/ltod/providers/openai-legacy`, etc.
|
|
49898
50178
|
*/
|
|
49899
50179
|
//#endregion
|
|
49900
50180
|
//#region ../../packages/agent-core/src/errors/serialize.ts
|
|
@@ -50727,7 +51007,7 @@ function resolveGlobalLogPath(homeDir) {
|
|
|
50727
51007
|
* and supersedes this value. Used to keep `tokenCountWithPending`
|
|
50728
51008
|
* monotonic between LLM round-trips without paying for a tokenizer.
|
|
50729
51009
|
*/
|
|
50730
|
-
function estimateTokens(text) {
|
|
51010
|
+
function estimateTokens$1(text) {
|
|
50731
51011
|
let asciiCount = 0;
|
|
50732
51012
|
let nonAsciiCount = 0;
|
|
50733
51013
|
for (const char of text) if (char.codePointAt(0) <= 127) asciiCount++;
|
|
@@ -50742,24 +51022,24 @@ function estimateTokensForMessages(messages) {
|
|
|
50742
51022
|
function estimateTokensForTools(tools) {
|
|
50743
51023
|
let total = 0;
|
|
50744
51024
|
for (const tool of tools) {
|
|
50745
|
-
total += estimateTokens(tool.name);
|
|
50746
|
-
total += estimateTokens(tool.description);
|
|
50747
|
-
total += estimateTokens(JSON.stringify(tool.parameters));
|
|
51025
|
+
total += estimateTokens$1(tool.name);
|
|
51026
|
+
total += estimateTokens$1(tool.description);
|
|
51027
|
+
total += estimateTokens$1(JSON.stringify(tool.parameters));
|
|
50748
51028
|
}
|
|
50749
51029
|
return total;
|
|
50750
51030
|
}
|
|
50751
51031
|
function estimateTokensForMessage(message) {
|
|
50752
|
-
let total = estimateTokens(message.role);
|
|
51032
|
+
let total = estimateTokens$1(message.role);
|
|
50753
51033
|
for (const part of message.content) total += estimateTokensForContentPart(part);
|
|
50754
51034
|
if (message.toolCalls !== void 0) for (const call of message.toolCalls) {
|
|
50755
|
-
total += estimateTokens(call.name);
|
|
50756
|
-
total += estimateTokens(JSON.stringify(call.arguments));
|
|
51035
|
+
total += estimateTokens$1(call.name);
|
|
51036
|
+
total += estimateTokens$1(JSON.stringify(call.arguments));
|
|
50757
51037
|
}
|
|
50758
51038
|
return total;
|
|
50759
51039
|
}
|
|
50760
51040
|
function estimateTokensForContentPart(part) {
|
|
50761
|
-
if (part.type === "text") return estimateTokens(part.text);
|
|
50762
|
-
else if (part.type === "think") return estimateTokens(part.think);
|
|
51041
|
+
if (part.type === "text") return estimateTokens$1(part.text);
|
|
51042
|
+
else if (part.type === "think") return estimateTokens$1(part.think);
|
|
50763
51043
|
return 0;
|
|
50764
51044
|
}
|
|
50765
51045
|
//#endregion
|
|
@@ -54586,7 +54866,7 @@ function fractionFromId(id) {
|
|
|
54586
54866
|
if (Number.isFinite(n)) return n / 4294967296;
|
|
54587
54867
|
}
|
|
54588
54868
|
let hash = 5381;
|
|
54589
|
-
for (let i = 0; i < id.length; i++) hash = (hash << 5) + hash + id.
|
|
54869
|
+
for (let i = 0; i < id.length; i++) hash = (hash << 5) + hash + id.codePointAt(i) | 0;
|
|
54590
54870
|
return (hash >>> 0) / 4294967296;
|
|
54591
54871
|
}
|
|
54592
54872
|
function jitterDisabledByEnv() {
|
|
@@ -54691,10 +54971,10 @@ var CronCreateTool = class {
|
|
|
54691
54971
|
let parsed;
|
|
54692
54972
|
try {
|
|
54693
54973
|
parsed = parseCronExpression(normalizedCron);
|
|
54694
|
-
} catch (
|
|
54974
|
+
} catch (error) {
|
|
54695
54975
|
return {
|
|
54696
54976
|
isError: true,
|
|
54697
|
-
output: `Invalid cron expression: ${
|
|
54977
|
+
output: `Invalid cron expression: ${error instanceof Error ? error.message : String(error)}`
|
|
54698
54978
|
};
|
|
54699
54979
|
}
|
|
54700
54980
|
const nowAtPrepare = this.manager.clocks.wallNow();
|
|
@@ -54711,7 +54991,7 @@ var CronCreateTool = class {
|
|
|
54711
54991
|
isError: true,
|
|
54712
54992
|
output: `Prompt exceeds ${String(MAX_PROMPT_BYTES)} bytes (got ${String(byteLen)}).`
|
|
54713
54993
|
};
|
|
54714
|
-
const recurring = args.recurring
|
|
54994
|
+
const recurring = args.recurring;
|
|
54715
54995
|
if (!recurring) {
|
|
54716
54996
|
const firstFire = computeNextCronRun(parsed, nowAtPrepare);
|
|
54717
54997
|
if (firstFire !== null && firstFire - nowAtPrepare > ONE_SHOT_MAX_FUTURE_MS) return {
|
|
@@ -56518,7 +56798,7 @@ function createFastEmbedEngine() {
|
|
|
56518
56798
|
if (texts.length === 0) return [];
|
|
56519
56799
|
try {
|
|
56520
56800
|
if (embedder === null) {
|
|
56521
|
-
|
|
56801
|
+
initPromise ??= loadEmbedder();
|
|
56522
56802
|
embedder = await initPromise;
|
|
56523
56803
|
if (embedder === null) {
|
|
56524
56804
|
loadFailed = true;
|
|
@@ -56594,16 +56874,33 @@ var MemoryMemoStore = class MemoryMemoStore {
|
|
|
56594
56874
|
* the types catch up, the synchronous calls here should be migrated to the
|
|
56595
56875
|
* async API to avoid blocking the event loop on large operations.
|
|
56596
56876
|
*/
|
|
56877
|
+
initPromise = null;
|
|
56597
56878
|
async init() {
|
|
56879
|
+
if (this.initPromise) return this.initPromise;
|
|
56880
|
+
this.initPromise = this._doInit();
|
|
56881
|
+
return this.initPromise;
|
|
56882
|
+
}
|
|
56883
|
+
async _doInit() {
|
|
56598
56884
|
if (this.initialized) return;
|
|
56599
56885
|
await this.ensureDir();
|
|
56600
56886
|
this.db = new DatabaseSync(this.dbPath);
|
|
56601
56887
|
this.db.exec("PRAGMA journal_mode = WAL;");
|
|
56602
56888
|
this.db.exec("PRAGMA foreign_keys = ON;");
|
|
56889
|
+
this.db.exec("PRAGMA busy_timeout = 5000;");
|
|
56603
56890
|
this.createSchema();
|
|
56604
56891
|
await this.migrateFromJsonl();
|
|
56605
56892
|
this.initialized = true;
|
|
56606
56893
|
}
|
|
56894
|
+
/** Close the database handle and checkpoint WAL. */
|
|
56895
|
+
close() {
|
|
56896
|
+
if (this.db !== void 0) {
|
|
56897
|
+
this.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
56898
|
+
this.db.close();
|
|
56899
|
+
this.db = void 0;
|
|
56900
|
+
}
|
|
56901
|
+
this.initialized = false;
|
|
56902
|
+
this.initPromise = null;
|
|
56903
|
+
}
|
|
56607
56904
|
/** Iterate all memos from the database, newest first. Optionally filter by project directory. */
|
|
56608
56905
|
async *read(options) {
|
|
56609
56906
|
await this.init();
|
|
@@ -56709,9 +57006,7 @@ var MemoryMemoStore = class MemoryMemoStore {
|
|
|
56709
57006
|
let stream;
|
|
56710
57007
|
try {
|
|
56711
57008
|
stream = createReadStream(legacyPath, { encoding: "utf8" });
|
|
56712
|
-
} catch {
|
|
56713
|
-
continue;
|
|
56714
|
-
}
|
|
57009
|
+
} catch {}
|
|
56715
57010
|
stream.on("error", () => {});
|
|
56716
57011
|
let line = "";
|
|
56717
57012
|
try {
|
|
@@ -56763,6 +57058,12 @@ var MemoryMemoStore = class MemoryMemoStore {
|
|
|
56763
57058
|
projectDir: str(entry["projectDir"])
|
|
56764
57059
|
};
|
|
56765
57060
|
}
|
|
57061
|
+
const src = entry["extractionSource"];
|
|
57062
|
+
if (typeof src === "string" && ![
|
|
57063
|
+
"compaction",
|
|
57064
|
+
"exit",
|
|
57065
|
+
"manual"
|
|
57066
|
+
].includes(src)) return;
|
|
56766
57067
|
return entry;
|
|
56767
57068
|
} catch {
|
|
56768
57069
|
return;
|
|
@@ -56854,7 +57155,7 @@ var MemoryMemoStore = class MemoryMemoStore {
|
|
|
56854
57155
|
}
|
|
56855
57156
|
insertMany(memos) {
|
|
56856
57157
|
if (this.db === void 0 || memos.length === 0) return;
|
|
56857
|
-
const insert = this.db.prepare(`INSERT INTO memos (
|
|
57158
|
+
const insert = this.db.prepare(`INSERT OR IGNORE INTO memos (
|
|
56858
57159
|
id, source_session_id, source_session_title, user_need, approach,
|
|
56859
57160
|
outcome, what_failed, what_worked, extraction_source, recorded_at, project_dir, tags
|
|
56860
57161
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
@@ -56868,9 +57169,9 @@ var MemoryMemoStore = class MemoryMemoStore {
|
|
|
56868
57169
|
insertFts.run(row.rowid, toFtsText(memo.userNeed), toFtsText(memo.approach), toFtsText(memo.whatFailed), toFtsText(memo.whatWorked), toFtsText(memo.sourceSessionTitle ?? ""));
|
|
56869
57170
|
}
|
|
56870
57171
|
this.db.exec("COMMIT");
|
|
56871
|
-
} catch (
|
|
57172
|
+
} catch (error) {
|
|
56872
57173
|
this.db.exec("ROLLBACK");
|
|
56873
|
-
throw new Error(`Failed to migrate memos to SQLite: ${
|
|
57174
|
+
throw new Error(`Failed to migrate memos to SQLite: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
56874
57175
|
}
|
|
56875
57176
|
}
|
|
56876
57177
|
async appendInternal(entry) {
|
|
@@ -56983,6 +57284,10 @@ var MemoryMemoStore = class MemoryMemoStore {
|
|
|
56983
57284
|
* Search memos by vector similarity. Returns memos sorted by cosine
|
|
56984
57285
|
* similarity (highest first). Falls back to empty if no embeddings exist.
|
|
56985
57286
|
*
|
|
57287
|
+
* NOTE: candidates are ordered by created_at DESC, so only the newest
|
|
57288
|
+
* N embeddings are considered. Older but highly relevant memos may be
|
|
57289
|
+
* omitted when the store has more than candidateLimit embeddings.
|
|
57290
|
+
* This is a known limitation — see M-M5 in project review notes.
|
|
56986
57291
|
* Performance notes:
|
|
56987
57292
|
* - candidateLimit bounds the SQL query so we never load every embedding.
|
|
56988
57293
|
* - recencyCutoffDays lets callers ignore very old memos.
|
|
@@ -57088,12 +57393,12 @@ var MemoryMemoStore = class MemoryMemoStore {
|
|
|
57088
57393
|
try {
|
|
57089
57394
|
for (let i = 0; i < pending.length; i++) insert.run(pending[i].id, JSON.stringify([...vectors[i]]), "bge-small-zh-v1.5", now);
|
|
57090
57395
|
this.db.exec("COMMIT");
|
|
57091
|
-
} catch (
|
|
57396
|
+
} catch (error) {
|
|
57092
57397
|
this.db.exec("ROLLBACK");
|
|
57093
|
-
this.markEmbeddingFailure(
|
|
57398
|
+
this.markEmbeddingFailure(error instanceof Error ? error : new Error(String(error)));
|
|
57094
57399
|
}
|
|
57095
|
-
} catch (
|
|
57096
|
-
this.markEmbeddingFailure(
|
|
57400
|
+
} catch (error) {
|
|
57401
|
+
this.markEmbeddingFailure(error instanceof Error ? error : new Error(String(error)));
|
|
57097
57402
|
} finally {
|
|
57098
57403
|
this.embeddingFlushing = false;
|
|
57099
57404
|
}
|
|
@@ -57195,7 +57500,7 @@ function memoMatchesSearch(memo, search) {
|
|
|
57195
57500
|
memo.whatWorked,
|
|
57196
57501
|
memo.sourceSessionTitle ?? "",
|
|
57197
57502
|
...memo.tags ?? []
|
|
57198
|
-
].join(" ").toLowerCase().includes(search);
|
|
57503
|
+
].join(" ").toLowerCase().replaceAll(/\s+/g, "").includes(search.replaceAll(/\s+/g, ""));
|
|
57199
57504
|
}
|
|
57200
57505
|
/**
|
|
57201
57506
|
* Tokenize text so FTS5's unicode61 tokenizer can index mixed CJK/ASCII text.
|
|
@@ -57336,13 +57641,41 @@ async function buildConsolidationPlan(store, options) {
|
|
|
57336
57641
|
async function applyConsolidation(store, plan) {
|
|
57337
57642
|
let deleted = 0;
|
|
57338
57643
|
let created = 0;
|
|
57339
|
-
|
|
57340
|
-
|
|
57341
|
-
|
|
57342
|
-
|
|
57343
|
-
|
|
57344
|
-
|
|
57345
|
-
|
|
57644
|
+
if (plan.resolved.length > 0) {
|
|
57645
|
+
const worked = plan.resolved.filter((m) => m.whatWorked).map((m) => m.whatWorked).join("; ");
|
|
57646
|
+
const failed = plan.resolved.filter((m) => m.whatFailed).map((m) => m.whatFailed).join("; ");
|
|
57647
|
+
if (worked || failed) {
|
|
57648
|
+
const archive = createMemoryMemo({
|
|
57649
|
+
sourceSessionId: plan.resolved[0].sourceSessionId,
|
|
57650
|
+
userNeed: "已解决任务的经验归档(dream 整理)",
|
|
57651
|
+
approach: "多次尝试后解决",
|
|
57652
|
+
outcome: "已完成",
|
|
57653
|
+
whatWorked: worked || "none",
|
|
57654
|
+
whatFailed: failed || "none",
|
|
57655
|
+
tags: normalizeTags(plan.resolved.flatMap((m) => m.tags ?? [])),
|
|
57656
|
+
extractionSource: "compaction"
|
|
57657
|
+
});
|
|
57658
|
+
await store.append(archive);
|
|
57659
|
+
created++;
|
|
57660
|
+
}
|
|
57661
|
+
}
|
|
57662
|
+
if (plan.stale.length > 0) {
|
|
57663
|
+
const worked = plan.stale.filter((m) => m.whatWorked).map((m) => m.whatWorked).join("; ");
|
|
57664
|
+
const failed = plan.stale.filter((m) => m.whatFailed).map((m) => m.whatFailed).join("; ");
|
|
57665
|
+
if (worked || failed) {
|
|
57666
|
+
const archive = createMemoryMemo({
|
|
57667
|
+
sourceSessionId: plan.stale[0].sourceSessionId,
|
|
57668
|
+
userNeed: "过期记忆的经验归档(dream 整理)",
|
|
57669
|
+
approach: "距今超过30天未使用",
|
|
57670
|
+
outcome: "已归档",
|
|
57671
|
+
whatWorked: worked || "none",
|
|
57672
|
+
whatFailed: failed || "none",
|
|
57673
|
+
tags: normalizeTags(plan.stale.flatMap((m) => m.tags ?? [])),
|
|
57674
|
+
extractionSource: "compaction"
|
|
57675
|
+
});
|
|
57676
|
+
await store.append(archive);
|
|
57677
|
+
created++;
|
|
57678
|
+
}
|
|
57346
57679
|
}
|
|
57347
57680
|
for (const group of plan.duplicateGroups) {
|
|
57348
57681
|
const newest = group.memos.reduce((a, b) => a.recordedAt > b.recordedAt ? a : b);
|
|
@@ -57358,13 +57691,21 @@ async function applyConsolidation(store, plan) {
|
|
|
57358
57691
|
tags: group.merged.tags ?? mergedTags,
|
|
57359
57692
|
extractionSource: "compaction"
|
|
57360
57693
|
});
|
|
57361
|
-
for (const memo of group.memos) {
|
|
57362
|
-
await store.delete(memo.id);
|
|
57363
|
-
deleted++;
|
|
57364
|
-
}
|
|
57365
57694
|
await store.append(merged);
|
|
57366
57695
|
created++;
|
|
57367
57696
|
}
|
|
57697
|
+
for (const group of plan.duplicateGroups) for (const memo of group.memos) {
|
|
57698
|
+
await store.delete(memo.id);
|
|
57699
|
+
deleted++;
|
|
57700
|
+
}
|
|
57701
|
+
for (const memo of plan.resolved) {
|
|
57702
|
+
await store.delete(memo.id);
|
|
57703
|
+
deleted++;
|
|
57704
|
+
}
|
|
57705
|
+
for (const memo of plan.stale) {
|
|
57706
|
+
await store.delete(memo.id);
|
|
57707
|
+
deleted++;
|
|
57708
|
+
}
|
|
57368
57709
|
return {
|
|
57369
57710
|
deleted,
|
|
57370
57711
|
created
|
|
@@ -57381,7 +57722,7 @@ function findDuplicateGroups(memos) {
|
|
|
57381
57722
|
const candidate = memos[j];
|
|
57382
57723
|
if (!candidate || used.has(candidate.id)) continue;
|
|
57383
57724
|
if (cluster.some((m) => {
|
|
57384
|
-
return
|
|
57725
|
+
return computeKeywordSimilarity(candidate, `${m.userNeed} ${m.approach}`) >= SIMILARITY_THRESHOLD;
|
|
57385
57726
|
})) cluster.push(candidate);
|
|
57386
57727
|
}
|
|
57387
57728
|
if (cluster.length > 1) {
|
|
@@ -57967,7 +58308,7 @@ var MemoryWriteTool = class {
|
|
|
57967
58308
|
const sourceSessionTitle = await this.agent.getSessionTitle();
|
|
57968
58309
|
const whatFailed = args.whatFailed?.trim();
|
|
57969
58310
|
const whatWorked = args.whatWorked?.trim();
|
|
57970
|
-
const tags = args.tags !== void 0 && args.tags.length > 0 ? args.tags : generateTags(`${args.userNeed} ${args.approach}`);
|
|
58311
|
+
const tags = normalizeTags(args.tags !== void 0 && args.tags.length > 0 ? args.tags : generateTags(`${args.userNeed} ${args.approach}`));
|
|
57971
58312
|
const memo = createMemoryMemo({
|
|
57972
58313
|
sourceSessionId: sessionId,
|
|
57973
58314
|
sourceSessionTitle,
|
|
@@ -58032,7 +58373,14 @@ var LspClient = class {
|
|
|
58032
58373
|
await this.request("initialize", {
|
|
58033
58374
|
processId: process.pid,
|
|
58034
58375
|
rootUri: pathToUri(this.workspaceRoot),
|
|
58035
|
-
capabilities: {
|
|
58376
|
+
capabilities: { textDocument: {
|
|
58377
|
+
synchronization: {
|
|
58378
|
+
willSave: false,
|
|
58379
|
+
willSaveWaitUntil: false,
|
|
58380
|
+
didSave: false
|
|
58381
|
+
},
|
|
58382
|
+
rename: { prepareSupport: false }
|
|
58383
|
+
} }
|
|
58036
58384
|
});
|
|
58037
58385
|
this.notify("initialized", {});
|
|
58038
58386
|
}
|
|
@@ -58079,6 +58427,16 @@ var LspClient = class {
|
|
|
58079
58427
|
if (result === null) return [];
|
|
58080
58428
|
return Array.isArray(result) ? result : [result];
|
|
58081
58429
|
}
|
|
58430
|
+
async rename(path, line, character, newName) {
|
|
58431
|
+
return await this.request("textDocument/rename", {
|
|
58432
|
+
textDocument: { uri: pathToUri(path) },
|
|
58433
|
+
position: {
|
|
58434
|
+
line,
|
|
58435
|
+
character
|
|
58436
|
+
},
|
|
58437
|
+
newName
|
|
58438
|
+
});
|
|
58439
|
+
}
|
|
58082
58440
|
async diagnostics(path, timeoutMs = 5e3) {
|
|
58083
58441
|
const uri = pathToUri(path);
|
|
58084
58442
|
this.collectedDiagnostics.delete(uri);
|
|
@@ -58174,6 +58532,19 @@ function pathToUri(path) {
|
|
|
58174
58532
|
}
|
|
58175
58533
|
return `file://${path.startsWith("/") ? path : `/${path}`}`;
|
|
58176
58534
|
}
|
|
58535
|
+
/**
|
|
58536
|
+
* Convert a `file://` URI back to a filesystem path. Tolerates
|
|
58537
|
+
* percent-encoded characters and lax servers that send raw paths.
|
|
58538
|
+
*/
|
|
58539
|
+
function uriToPath(uri) {
|
|
58540
|
+
if (!uri.startsWith("file://")) return uri;
|
|
58541
|
+
let filePath = uri.slice(7);
|
|
58542
|
+
try {
|
|
58543
|
+
filePath = decodeURIComponent(filePath);
|
|
58544
|
+
} catch {}
|
|
58545
|
+
if (process.platform === "win32" && filePath.startsWith("/") && /^[A-Za-z]:/.test(filePath.slice(1))) filePath = filePath.slice(1);
|
|
58546
|
+
return filePath;
|
|
58547
|
+
}
|
|
58177
58548
|
function sleep$1(ms) {
|
|
58178
58549
|
return new Promise((resolve) => {
|
|
58179
58550
|
setTimeout(resolve, ms);
|
|
@@ -58189,24 +58560,141 @@ function formatDiagnostic(diagnostic) {
|
|
|
58189
58560
|
return `- ${diagnostic.severity !== void 0 ? SEVERITY_LABELS[diagnostic.severity - 1] ?? "Unknown" : "Diagnostic"} at ${start.line + 1}:${start.character + 1}: ${diagnostic.message}`;
|
|
58190
58561
|
}
|
|
58191
58562
|
//#endregion
|
|
58563
|
+
//#region ../../packages/agent-core/src/lsp/edits.ts
|
|
58564
|
+
function comparePosition(a, b) {
|
|
58565
|
+
return a.line === b.line ? a.character - b.character : a.line - b.line;
|
|
58566
|
+
}
|
|
58567
|
+
/**
|
|
58568
|
+
* Sort edits bottom-to-top for in-place application and reject overlaps.
|
|
58569
|
+
* Equal start positions tiebreak by original array index descending so that,
|
|
58570
|
+
* applied bottom-up, inserts at the same position land in array order
|
|
58571
|
+
* (LSP spec: the order of edits in the array defines the order in the result).
|
|
58572
|
+
*/
|
|
58573
|
+
function sortAndValidateTextEdits(edits) {
|
|
58574
|
+
const sorted = edits.map((edit, index) => ({
|
|
58575
|
+
edit,
|
|
58576
|
+
index
|
|
58577
|
+
})).sort((a, b) => {
|
|
58578
|
+
const sa = a.edit.range.start;
|
|
58579
|
+
const sb = b.edit.range.start;
|
|
58580
|
+
if (sa.line !== sb.line) return sb.line - sa.line;
|
|
58581
|
+
if (sa.character !== sb.character) return sb.character - sa.character;
|
|
58582
|
+
return b.index - a.index;
|
|
58583
|
+
}).map((entry) => entry.edit);
|
|
58584
|
+
for (let i = 0; i < sorted.length - 1; i++) {
|
|
58585
|
+
const later = sorted[i].range;
|
|
58586
|
+
const earlier = sorted[i + 1].range;
|
|
58587
|
+
if (comparePosition(earlier.end, later.start) > 0) throw new Error(`overlapping LSP edits at ${earlier.start.line + 1}:${earlier.start.character + 1} conflict with ${later.start.line + 1}:${later.start.character + 1}; multi-server rename produced inconsistent edits`);
|
|
58588
|
+
}
|
|
58589
|
+
return sorted;
|
|
58590
|
+
}
|
|
58591
|
+
/**
|
|
58592
|
+
* Apply text edits to a string in-memory. Edits are applied in reverse
|
|
58593
|
+
* order (bottom-to-top) to preserve line/character indices.
|
|
58594
|
+
*/
|
|
58595
|
+
function applyTextEditsToString(content, edits) {
|
|
58596
|
+
const lines = content.split("\n");
|
|
58597
|
+
const sortedEdits = sortAndValidateTextEdits(edits);
|
|
58598
|
+
for (const edit of sortedEdits) {
|
|
58599
|
+
const { start, end } = edit.range;
|
|
58600
|
+
if (start.line === end.line) {
|
|
58601
|
+
const line = lines[start.line] ?? "";
|
|
58602
|
+
lines[start.line] = line.slice(0, start.character) + edit.newText + line.slice(end.character);
|
|
58603
|
+
} else {
|
|
58604
|
+
const startLine = lines[start.line] ?? "";
|
|
58605
|
+
const endLine = lines[end.line] ?? "";
|
|
58606
|
+
const newContent = startLine.slice(0, start.character) + edit.newText + endLine.slice(end.character);
|
|
58607
|
+
lines.splice(start.line, end.line - start.line + 1, ...newContent.split("\n"));
|
|
58608
|
+
}
|
|
58609
|
+
}
|
|
58610
|
+
return lines.join("\n");
|
|
58611
|
+
}
|
|
58612
|
+
/**
|
|
58613
|
+
* Flatten a WorkspaceEdit's text edits into a Map<path, TextEdit[]>.
|
|
58614
|
+
* Resource operations (create/rename/delete) are ignored — callers handle
|
|
58615
|
+
* them separately.
|
|
58616
|
+
*/
|
|
58617
|
+
function flattenWorkspaceTextEdits(edit) {
|
|
58618
|
+
const out = /* @__PURE__ */ new Map();
|
|
58619
|
+
const push = (uri, edits) => {
|
|
58620
|
+
if (edits.length === 0) return;
|
|
58621
|
+
const path = uriToPath(uri);
|
|
58622
|
+
const prev = out.get(path);
|
|
58623
|
+
if (prev) prev.push(...edits);
|
|
58624
|
+
else out.set(path, [...edits]);
|
|
58625
|
+
};
|
|
58626
|
+
if (edit.changes) for (const uri of Object.keys(edit.changes)) push(uri, edit.changes[uri]);
|
|
58627
|
+
if (edit.documentChanges) {
|
|
58628
|
+
for (const change of edit.documentChanges) if (change && change.textDocument && change.edits) push(change.textDocument.uri, change.edits);
|
|
58629
|
+
}
|
|
58630
|
+
return out;
|
|
58631
|
+
}
|
|
58632
|
+
/**
|
|
58633
|
+
* Apply a workspace edit (collection of file changes) to disk via Jian.
|
|
58634
|
+
*
|
|
58635
|
+
* All text-edit batches are overlap-validated before anything is written
|
|
58636
|
+
* so a conflict throws without leaving the workspace half-applied. Every
|
|
58637
|
+
* target path is passed through `validatePath` first — throws
|
|
58638
|
+
* `LspEditPathValidationError` if any path is outside the allowed root.
|
|
58639
|
+
*/
|
|
58640
|
+
async function applyWorkspaceEdit(edit, jian, validatePath) {
|
|
58641
|
+
const flattened = flattenWorkspaceTextEdits(edit);
|
|
58642
|
+
for (const edits of flattened.values()) sortAndValidateTextEdits(edits);
|
|
58643
|
+
if (validatePath !== void 0) for (const filePath of flattened.keys()) validatePath(filePath);
|
|
58644
|
+
const applied = [];
|
|
58645
|
+
for (const [filePath, edits] of flattened) {
|
|
58646
|
+
const result = applyTextEditsToString(await jian.readText(filePath), edits);
|
|
58647
|
+
await jian.writeText(filePath, result);
|
|
58648
|
+
applied.push({
|
|
58649
|
+
filePath,
|
|
58650
|
+
editCount: edits.length
|
|
58651
|
+
});
|
|
58652
|
+
}
|
|
58653
|
+
return applied;
|
|
58654
|
+
}
|
|
58655
|
+
/**
|
|
58656
|
+
* Format a workspace edit as a summary list (one entry per affected file).
|
|
58657
|
+
* Used for the preview mode (`apply: false`) where we show what *would*
|
|
58658
|
+
* change without writing to disk.
|
|
58659
|
+
*/
|
|
58660
|
+
function formatWorkspaceEditPreview(edit) {
|
|
58661
|
+
const lines = [];
|
|
58662
|
+
if (edit.changes) for (const uri of Object.keys(edit.changes)) {
|
|
58663
|
+
const edits = edit.changes[uri];
|
|
58664
|
+
const path = uriToPath(uri);
|
|
58665
|
+
lines.push(`${path}: ${String(edits.length)} edit${edits.length > 1 ? "s" : ""}`);
|
|
58666
|
+
}
|
|
58667
|
+
if (edit.documentChanges) {
|
|
58668
|
+
for (const change of edit.documentChanges) if (change && change.textDocument && change.edits) {
|
|
58669
|
+
const path = uriToPath(change.textDocument.uri);
|
|
58670
|
+
lines.push(`${path}: ${String(change.edits.length)} edit${change.edits.length > 1 ? "s" : ""}`);
|
|
58671
|
+
}
|
|
58672
|
+
}
|
|
58673
|
+
return lines;
|
|
58674
|
+
}
|
|
58675
|
+
//#endregion
|
|
58192
58676
|
//#region ../../packages/agent-core/src/tools/builtin/lsp-tool.ts
|
|
58193
58677
|
const LspInputSchema = z.object({
|
|
58194
58678
|
path: z.string().describe("Path to the source file. Relative paths resolve against the working directory; a path outside the working directory must be absolute."),
|
|
58195
58679
|
operation: z.enum([
|
|
58196
58680
|
"references",
|
|
58197
58681
|
"definition",
|
|
58198
|
-
"diagnostics"
|
|
58199
|
-
|
|
58200
|
-
|
|
58201
|
-
|
|
58202
|
-
|
|
58682
|
+
"diagnostics",
|
|
58683
|
+
"rename"
|
|
58684
|
+
]).describe("LSP operation to perform: 'references', 'definition', 'diagnostics', or 'rename'."),
|
|
58685
|
+
line: z.number().int().min(1).optional().describe("1-based line number for references/definition/rename."),
|
|
58686
|
+
character: z.number().int().min(0).optional().describe("0-based column/character offset for references/definition/rename."),
|
|
58687
|
+
include_declaration: z.boolean().optional().describe("For 'references': whether to include the declaration site in the results."),
|
|
58688
|
+
new_name: z.string().optional().describe("For 'rename': the new symbol name. Required for rename. The rename is applied to disk unless `apply` is false."),
|
|
58689
|
+
apply: z.boolean().optional().describe("For 'rename': when true, apply the rename to disk across all affected files. When false (default), return a preview of the changes without writing. Pass true only after previewing.")
|
|
58203
58690
|
});
|
|
58204
58691
|
/**
|
|
58205
|
-
* LSP tool —
|
|
58692
|
+
* LSP tool — code intelligence via language servers.
|
|
58206
58693
|
*
|
|
58207
|
-
* Supports references, go-to-definition, and
|
|
58208
|
-
* opened in the language server so results reflect the current
|
|
58209
|
-
* even when the file has not been saved to disk.
|
|
58694
|
+
* Supports references, go-to-definition, diagnostics, and rename. The file
|
|
58695
|
+
* content is opened in the language server so results reflect the current
|
|
58696
|
+
* editor state even when the file has not been saved to disk. Rename is the
|
|
58697
|
+
* only write op; pass `apply: false` to preview without writing.
|
|
58210
58698
|
*/
|
|
58211
58699
|
var LspTool = class {
|
|
58212
58700
|
agent;
|
|
@@ -58214,10 +58702,10 @@ var LspTool = class {
|
|
|
58214
58702
|
lspRegistry;
|
|
58215
58703
|
name = "LSP";
|
|
58216
58704
|
description = [
|
|
58217
|
-
"Query a language server for
|
|
58218
|
-
"Use 'references' to find all usages of a symbol, 'definition' to jump to where a symbol is defined,
|
|
58705
|
+
"Query a language server for code intelligence.",
|
|
58706
|
+
"Use 'references' to find all usages of a symbol, 'definition' to jump to where a symbol is defined, 'diagnostics' to get type errors and warnings for a file, and 'rename' to rename a symbol across all its references.",
|
|
58219
58707
|
"The language server is started automatically for supported file types (TypeScript/JavaScript, Python, Rust, Go).",
|
|
58220
|
-
"
|
|
58708
|
+
"Rename requires the typescript-language-server (or equivalent) binary on PATH for the file type."
|
|
58221
58709
|
].join(" ");
|
|
58222
58710
|
parameters = toInputJsonSchema(LspInputSchema);
|
|
58223
58711
|
constructor(agent, workspace, lspRegistry) {
|
|
@@ -58226,13 +58714,14 @@ var LspTool = class {
|
|
|
58226
58714
|
this.lspRegistry = lspRegistry;
|
|
58227
58715
|
}
|
|
58228
58716
|
resolveExecution(args) {
|
|
58717
|
+
const isWrite = args.operation === "rename" && args.apply === true;
|
|
58229
58718
|
const path = resolvePathAccessPath(args.path, {
|
|
58230
58719
|
jian: this.agent.jian,
|
|
58231
58720
|
workspace: this.workspace,
|
|
58232
|
-
operation: "read"
|
|
58721
|
+
operation: isWrite ? "write" : "read"
|
|
58233
58722
|
});
|
|
58234
58723
|
return {
|
|
58235
|
-
accesses: ToolAccesses.readFile(path),
|
|
58724
|
+
accesses: isWrite ? ToolAccesses.writeFile(path) : ToolAccesses.readFile(path),
|
|
58236
58725
|
description: `LSP ${args.operation} ${args.path}`,
|
|
58237
58726
|
approvalRule: this.name,
|
|
58238
58727
|
execute: () => this.execution(args, path)
|
|
@@ -58315,6 +58804,44 @@ var LspTool = class {
|
|
|
58315
58804
|
].join("\n")
|
|
58316
58805
|
};
|
|
58317
58806
|
}
|
|
58807
|
+
case "rename": {
|
|
58808
|
+
if (args.line === void 0 || args.character === void 0) return {
|
|
58809
|
+
isError: true,
|
|
58810
|
+
output: "'rename' requires both 'line' and 'character'."
|
|
58811
|
+
};
|
|
58812
|
+
if (args.new_name === void 0 || args.new_name === "") return {
|
|
58813
|
+
isError: true,
|
|
58814
|
+
output: "'rename' requires 'new_name'."
|
|
58815
|
+
};
|
|
58816
|
+
const workspaceEdit = await client.rename(safePath, args.line - 1, args.character, args.new_name);
|
|
58817
|
+
if (workspaceEdit === null) return {
|
|
58818
|
+
isError: false,
|
|
58819
|
+
output: "Rename returned no edits."
|
|
58820
|
+
};
|
|
58821
|
+
if (args.apply === true) {
|
|
58822
|
+
const applied = await applyWorkspaceEdit(workspaceEdit, this.agent.jian, (p) => {
|
|
58823
|
+
if (!isWithinWorkspace(p, this.workspace, this.agent.jian.pathClass())) throw new Error(`Refusing to apply rename: LSP returned edits for a file outside the workspace: ${p}`);
|
|
58824
|
+
});
|
|
58825
|
+
if (applied.length === 0) return {
|
|
58826
|
+
isError: false,
|
|
58827
|
+
output: "Rename produced no file changes."
|
|
58828
|
+
};
|
|
58829
|
+
const lines = applied.map((a) => ` Applied ${String(a.editCount)} edit(s) to ${a.filePath}`);
|
|
58830
|
+
return {
|
|
58831
|
+
isError: false,
|
|
58832
|
+
output: [`Applied rename to ${String(applied.length)} file(s):`, ...lines].join("\n")
|
|
58833
|
+
};
|
|
58834
|
+
}
|
|
58835
|
+
const preview = formatWorkspaceEditPreview(workspaceEdit);
|
|
58836
|
+
if (preview.length === 0) return {
|
|
58837
|
+
isError: false,
|
|
58838
|
+
output: "Rename preview is empty."
|
|
58839
|
+
};
|
|
58840
|
+
return {
|
|
58841
|
+
isError: false,
|
|
58842
|
+
output: [`Rename preview (${String(preview.length)} file(s)):`, ...preview].join("\n")
|
|
58843
|
+
};
|
|
58844
|
+
}
|
|
58318
58845
|
default: return {
|
|
58319
58846
|
isError: true,
|
|
58320
58847
|
output: `Unsupported operation: ${String(args.operation)}`
|
|
@@ -68759,7 +69286,9 @@ async function resolveGithubSource(input) {
|
|
|
68759
69286
|
}
|
|
68760
69287
|
};
|
|
68761
69288
|
const headController = new AbortController();
|
|
68762
|
-
const headTimeout = setTimeout(() =>
|
|
69289
|
+
const headTimeout = setTimeout(() => {
|
|
69290
|
+
headController.abort();
|
|
69291
|
+
}, 3e4);
|
|
68763
69292
|
let headProbe;
|
|
68764
69293
|
try {
|
|
68765
69294
|
headProbe = await fetch(`https://codeload.github.com/${owner}/${repo}/zip/HEAD`, {
|
|
@@ -68795,7 +69324,9 @@ async function resolveGithubSource(input) {
|
|
|
68795
69324
|
async function tryResolveLatestReleaseTag(owner, repo) {
|
|
68796
69325
|
const url = `https://github.com/${owner}/${repo}/releases/latest`;
|
|
68797
69326
|
const controller = new AbortController();
|
|
68798
|
-
const timeoutHandle = setTimeout(() =>
|
|
69327
|
+
const timeoutHandle = setTimeout(() => {
|
|
69328
|
+
controller.abort();
|
|
69329
|
+
}, 3e4);
|
|
68799
69330
|
let resp;
|
|
68800
69331
|
try {
|
|
68801
69332
|
resp = await fetch(url, {
|
|
@@ -69487,7 +70018,7 @@ async function discoverSkillDirs(root, maxDepth) {
|
|
|
69487
70018
|
}
|
|
69488
70019
|
}
|
|
69489
70020
|
await walk(root, 1);
|
|
69490
|
-
const sorted = found.
|
|
70021
|
+
const sorted = found.toSorted((a, b) => a.length - b.length);
|
|
69491
70022
|
const result = [];
|
|
69492
70023
|
for (const dir of sorted) if (!result.some((parent) => dir.startsWith(parent + path$1.sep))) result.push(dir);
|
|
69493
70024
|
return result;
|
|
@@ -70442,7 +70973,7 @@ var wolfpack_default = "Use WolfPack to spawn multiple subagents in parallel for
|
|
|
70442
70973
|
*
|
|
70443
70974
|
* Spawns multiple subagents in parallel using a template + items pattern.
|
|
70444
70975
|
* Each item gets its own subagent; results are batched together.
|
|
70445
|
-
*
|
|
70976
|
+
* Concurrency capped at WOLFPACK_CONCURRENCY to avoid provider rate-limit exhaustion.
|
|
70446
70977
|
*/
|
|
70447
70978
|
const MAX_ITEMS = 20;
|
|
70448
70979
|
const WolfPackToolInputSchema = z.object({
|
|
@@ -70489,27 +71020,51 @@ var WolfPackTool = class {
|
|
|
70489
71020
|
};
|
|
70490
71021
|
const profileName = args.subagent_type ?? "coder";
|
|
70491
71022
|
const template = args.prompt_template;
|
|
71023
|
+
const WOLFPACK_CONCURRENCY = 8;
|
|
70492
71024
|
const handlePromises = args.items.map(async (item) => {
|
|
70493
71025
|
ctx.signal.throwIfAborted();
|
|
70494
|
-
|
|
70495
|
-
|
|
70496
|
-
|
|
70497
|
-
|
|
70498
|
-
|
|
70499
|
-
|
|
70500
|
-
|
|
70501
|
-
|
|
70502
|
-
|
|
70503
|
-
|
|
70504
|
-
|
|
71026
|
+
try {
|
|
71027
|
+
const prompt = template.replaceAll("{{item}}", () => item);
|
|
71028
|
+
return {
|
|
71029
|
+
item,
|
|
71030
|
+
handle: await this.subagentHost.spawn(profileName, {
|
|
71031
|
+
parentToolCallId: ctx.toolCallId,
|
|
71032
|
+
prompt,
|
|
71033
|
+
description: `${args.description}: ${item}`,
|
|
71034
|
+
runInBackground: false,
|
|
71035
|
+
signal: ctx.signal
|
|
71036
|
+
})
|
|
71037
|
+
};
|
|
71038
|
+
} catch (error) {
|
|
71039
|
+
return {
|
|
71040
|
+
item,
|
|
71041
|
+
error
|
|
71042
|
+
};
|
|
71043
|
+
}
|
|
70505
71044
|
});
|
|
70506
|
-
const
|
|
71045
|
+
const handleResults = [];
|
|
71046
|
+
for (let i = 0; i < handlePromises.length; i += WOLFPACK_CONCURRENCY) {
|
|
71047
|
+
ctx.signal.throwIfAborted();
|
|
71048
|
+
const batch = handlePromises.slice(i, i + WOLFPACK_CONCURRENCY);
|
|
71049
|
+
const batchResults = await Promise.allSettled(batch);
|
|
71050
|
+
handleResults.push(...batchResults);
|
|
71051
|
+
}
|
|
71052
|
+
const completionPromises = handleResults.map(async (settled) => {
|
|
70507
71053
|
if (settled.status === "rejected") return {
|
|
70508
71054
|
item: "unknown",
|
|
70509
71055
|
result: `Spawn failed: ${settled.reason instanceof Error ? settled.reason.message : String(settled.reason)}`,
|
|
70510
71056
|
success: false
|
|
70511
71057
|
};
|
|
70512
|
-
const
|
|
71058
|
+
const value = settled.value;
|
|
71059
|
+
if ("error" in value) {
|
|
71060
|
+
const msg = value.error instanceof Error ? value.error.message : String(value.error);
|
|
71061
|
+
return {
|
|
71062
|
+
item: value.item,
|
|
71063
|
+
result: `Spawn failed: ${msg}`,
|
|
71064
|
+
success: false
|
|
71065
|
+
};
|
|
71066
|
+
}
|
|
71067
|
+
const { item, handle } = value;
|
|
70513
71068
|
try {
|
|
70514
71069
|
return {
|
|
70515
71070
|
item,
|
|
@@ -75655,7 +76210,7 @@ var ReadGroupTool = class {
|
|
|
75655
76210
|
});
|
|
75656
76211
|
}
|
|
75657
76212
|
const accesses = executions.filter((e) => "exec" in e).flatMap((e) => e.exec.accesses ?? ToolAccesses.none());
|
|
75658
|
-
const sortedPaths = [...paths].
|
|
76213
|
+
const sortedPaths = [...paths].toSorted();
|
|
75659
76214
|
const approvalRule = literalRulePattern(this.name, sortedPaths.join("\n"));
|
|
75660
76215
|
const deniedCount = executions.filter((e) => "error" in e).length;
|
|
75661
76216
|
return {
|
|
@@ -76267,7 +76822,7 @@ function commandNotFoundHint(command) {
|
|
|
76267
76822
|
return "Hint: The command binary was not found. Check that the required toolchain is installed and use the project-specific script when available.";
|
|
76268
76823
|
}
|
|
76269
76824
|
function validateCommand(command, isWindows) {
|
|
76270
|
-
const cmd = command;
|
|
76825
|
+
const cmd = command.replace(/^(?:command\s+|builtin\s+)+/, "");
|
|
76271
76826
|
if (/\bkill\s+-9\s+-1\b/.test(cmd) || /\bkill\s+-KILL\s+-1\b/.test(cmd)) return rejectDangerousCommand("kill -9 -1", "Use 'kill <pid>' with a specific PID to terminate the target process.");
|
|
76272
76827
|
if (/\b(killall|pkill)\b.*\b(node|scream)/i.test(cmd)) return rejectDangerousCommand("killall/pkill node", "Use 'kill <pid>' with a specific PID, or use the server's own stop command.");
|
|
76273
76828
|
if (isWindows) {
|
|
@@ -76555,7 +77110,7 @@ const TodoItemSchema = z.object({
|
|
|
76555
77110
|
]).describe("Current status of the todo."),
|
|
76556
77111
|
phase: z.string().optional().describe("Optional phase/group for the todo. Items in the same phase are rendered together. Complete one phase before starting the next.")
|
|
76557
77112
|
});
|
|
76558
|
-
const TodoListInputSchema = z.object({ todos: z.array(TodoItemSchema).optional().describe("The updated todo list. Omit to read the current todo list without making changes. Pass an empty array to clear the list.") });
|
|
77113
|
+
const TodoListInputSchema = z.object({ todos: z.array(TodoItemSchema).max(200).optional().describe("The updated todo list. Omit to read the current todo list without making changes. Pass an empty array to clear the list.") });
|
|
76559
77114
|
function renderTodoList(todos) {
|
|
76560
77115
|
if (todos.length === 0) return "Todo list is empty.";
|
|
76561
77116
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -77070,11 +77625,15 @@ async function chatWithRetry(input) {
|
|
|
77070
77625
|
logRequestFailure(input, error, attempt, maxAttempts);
|
|
77071
77626
|
throw error;
|
|
77072
77627
|
}
|
|
77628
|
+
if (error instanceof APIProviderRateLimitError && error.reason === "QUOTA_EXHAUSTED") {
|
|
77629
|
+
logRequestFailure(input, error, attempt, maxAttempts);
|
|
77630
|
+
throw error;
|
|
77631
|
+
}
|
|
77073
77632
|
if (attempt >= maxAttempts || !input.llm.isRetryableError(error)) {
|
|
77074
77633
|
logRequestFailure(input, error, attempt, maxAttempts);
|
|
77075
77634
|
throw error;
|
|
77076
77635
|
}
|
|
77077
|
-
const delayMs = delays
|
|
77636
|
+
const delayMs = computeDelayMs(error, delays, attempt);
|
|
77078
77637
|
input.params.signal.throwIfAborted();
|
|
77079
77638
|
input.dispatchEvent({
|
|
77080
77639
|
type: "step.retrying",
|
|
@@ -77090,6 +77649,10 @@ async function chatWithRetry(input) {
|
|
|
77090
77649
|
await sleepForRetry(delayMs, input.params.signal);
|
|
77091
77650
|
}
|
|
77092
77651
|
}
|
|
77652
|
+
function computeDelayMs(error, delays, attempt) {
|
|
77653
|
+
if (error instanceof APIProviderRateLimitError) return calculateRateLimitBackoffMs(error.reason);
|
|
77654
|
+
return delays[attempt - 1] ?? 0;
|
|
77655
|
+
}
|
|
77093
77656
|
function logRequestFailure(input, error, attempt, maxAttempts) {
|
|
77094
77657
|
if (isAbortError$1(error) || input.params.signal.aborted) return;
|
|
77095
77658
|
input.log?.warn("llm request failed", {
|
|
@@ -77334,6 +77897,7 @@ var DefaultCompactionStrategy = class {
|
|
|
77334
77897
|
}
|
|
77335
77898
|
shouldCompactProactively(usedSize, maxOutputTokens) {
|
|
77336
77899
|
if (this.maxSize <= 0) return false;
|
|
77900
|
+
if (this.config.triggerRatio >= 1) return false;
|
|
77337
77901
|
return usedSize + this.estimateTurnGrowth(maxOutputTokens) >= this.maxSize;
|
|
77338
77902
|
}
|
|
77339
77903
|
get checkAfterStep() {
|
|
@@ -77377,6 +77941,8 @@ var FullCompaction = class {
|
|
|
77377
77941
|
agent;
|
|
77378
77942
|
compactionCountInTurn = 0;
|
|
77379
77943
|
consecutiveCompactionFailures = 0;
|
|
77944
|
+
_shouldInjectSessionSummary = false;
|
|
77945
|
+
compactionTimedOut = false;
|
|
77380
77946
|
compacting = null;
|
|
77381
77947
|
_compactedHistory = [];
|
|
77382
77948
|
strategy;
|
|
@@ -77394,6 +77960,14 @@ var FullCompaction = class {
|
|
|
77394
77960
|
get compactedHistory() {
|
|
77395
77961
|
return this._compactedHistory;
|
|
77396
77962
|
}
|
|
77963
|
+
/** One-shot: true if session memory summary should be injected at the next step. */
|
|
77964
|
+
shouldInjectSessionSummary() {
|
|
77965
|
+
if (this._shouldInjectSessionSummary) {
|
|
77966
|
+
this._shouldInjectSessionSummary = false;
|
|
77967
|
+
return true;
|
|
77968
|
+
}
|
|
77969
|
+
return false;
|
|
77970
|
+
}
|
|
77397
77971
|
begin(data) {
|
|
77398
77972
|
if (this.compacting) return;
|
|
77399
77973
|
if (data.source === "manual") this.compactionCountInTurn = 0;
|
|
@@ -77449,13 +78023,22 @@ var FullCompaction = class {
|
|
|
77449
78023
|
this.consecutiveCompactionFailures = 0;
|
|
77450
78024
|
}
|
|
77451
78025
|
async handleOverflowError(signal, error) {
|
|
77452
|
-
if (!this.beginAutoCompaction() && !this.compacting)
|
|
77453
|
-
|
|
78026
|
+
if (!this.beginAutoCompaction(false) && !this.compacting) {
|
|
78027
|
+
if (this.consecutiveCompactionFailures >= MAX_CONSECUTIVE_FAILURES) this.agent.emitEvent({
|
|
78028
|
+
type: "warning",
|
|
78029
|
+
message: "压缩熔断器已打开,使用 /compact 手动重试。",
|
|
78030
|
+
code: "compaction_circuit_open_overflow"
|
|
78031
|
+
});
|
|
78032
|
+
throw error;
|
|
78033
|
+
}
|
|
77454
78034
|
}
|
|
77455
78035
|
async beforeStep(signal) {
|
|
77456
78036
|
this.agent.microCompaction.detect();
|
|
77457
78037
|
const effectiveTokens = this.effectiveTokenCount;
|
|
77458
|
-
|
|
78038
|
+
const isReactiveTrigger = this.strategy.shouldCompact(effectiveTokens);
|
|
78039
|
+
const isProactiveTrigger = !isReactiveTrigger && this.strategy.shouldCompactProactively(effectiveTokens, this.estimatedMaxOutputTokens);
|
|
78040
|
+
if (isReactiveTrigger) this.checkAutoCompaction();
|
|
78041
|
+
else if (isProactiveTrigger) this.beginAutoCompaction();
|
|
77459
78042
|
if (this.strategy.shouldBlock(effectiveTokens)) await this.block(signal);
|
|
77460
78043
|
}
|
|
77461
78044
|
/** Conservative estimate of max output tokens for one API call. */
|
|
@@ -77497,7 +78080,10 @@ var FullCompaction = class {
|
|
|
77497
78080
|
if (!active) return;
|
|
77498
78081
|
active.blockedByTurn = true;
|
|
77499
78082
|
const timeoutId = setTimeout(() => {
|
|
77500
|
-
if (this.compacting === active)
|
|
78083
|
+
if (this.compacting === active) {
|
|
78084
|
+
this.compactionTimedOut = true;
|
|
78085
|
+
this.markCanceled("压缩超时(60秒),已取消。请使用 /compact 手动重试。");
|
|
78086
|
+
}
|
|
77501
78087
|
}, 6e4);
|
|
77502
78088
|
const onAbort = () => {
|
|
77503
78089
|
clearTimeout(timeoutId);
|
|
@@ -77558,7 +78144,7 @@ var FullCompaction = class {
|
|
|
77558
78144
|
return;
|
|
77559
78145
|
}
|
|
77560
78146
|
const recent = originalHistory.slice(compactedCount);
|
|
77561
|
-
const tokensAfter = estimateTokens(summary) + estimateTokensForMessages(recent);
|
|
78147
|
+
const tokensAfter = estimateTokens$1(summary) + estimateTokensForMessages(recent);
|
|
77562
78148
|
const result = {
|
|
77563
78149
|
summary,
|
|
77564
78150
|
compactedCount,
|
|
@@ -77574,8 +78160,11 @@ var FullCompaction = class {
|
|
|
77574
78160
|
await this.extractAndStoreMemos(summary);
|
|
77575
78161
|
this.triggerPostCompactHook(data, result);
|
|
77576
78162
|
this.consecutiveCompactionFailures = 0;
|
|
78163
|
+
this._shouldInjectSessionSummary = true;
|
|
77577
78164
|
} catch (error) {
|
|
77578
|
-
|
|
78165
|
+
const wasTimedOut = this.compactionTimedOut;
|
|
78166
|
+
this.compactionTimedOut = false;
|
|
78167
|
+
if (!isAbortError$1(error) || wasTimedOut) {
|
|
77579
78168
|
const blockedByTurn = this.compacting?.blockedByTurn === true;
|
|
77580
78169
|
this.consecutiveCompactionFailures += 1;
|
|
77581
78170
|
if (this.consecutiveCompactionFailures >= MAX_CONSECUTIVE_FAILURES) this.agent.emitEvent({
|
|
@@ -77751,7 +78340,7 @@ var MicroCompaction = class {
|
|
|
77751
78340
|
const { history } = this.agent.context;
|
|
77752
78341
|
const maxContextTokens = this.agent.config.modelCapabilities.max_context_tokens;
|
|
77753
78342
|
const contextTokens = this.agent.context.tokenCountWithPending;
|
|
77754
|
-
if ((maxContextTokens !== void 0 && maxContextTokens > 0 ? contextTokens / maxContextTokens :
|
|
78343
|
+
if ((maxContextTokens !== void 0 && maxContextTokens > 0 ? contextTokens / maxContextTokens : 0) < config.minContextUsageRatio) return;
|
|
77755
78344
|
const nextCutoff = Math.max(0, history.length - config.keepRecentMessages);
|
|
77756
78345
|
this.apply(nextCutoff);
|
|
77757
78346
|
}
|
|
@@ -77800,7 +78389,7 @@ var MicroCompaction = class {
|
|
|
77800
78389
|
if (message?.role !== "tool" || message.toolCallId === void 0) continue;
|
|
77801
78390
|
const contentTokens = estimateTokensForMessages([message]);
|
|
77802
78391
|
if (contentTokens < this.config.minContentTokens) continue;
|
|
77803
|
-
markerTokenCount ??= estimateTokens(this.config.truncatedMarker);
|
|
78392
|
+
markerTokenCount ??= estimateTokens$1(this.config.truncatedMarker);
|
|
77804
78393
|
truncatedToolResultCount += 1;
|
|
77805
78394
|
beforeTokens += contentTokens;
|
|
77806
78395
|
afterTokens += markerTokenCount;
|
|
@@ -78275,7 +78864,7 @@ function createCronScheduler(opts) {
|
|
|
78275
78864
|
const parsed = getParsed(task.cron);
|
|
78276
78865
|
const seen = lastSeenAt.get(task.id);
|
|
78277
78866
|
const persistedCursor = task.lastFiredAt !== void 0 && Number.isFinite(task.lastFiredAt) && task.lastFiredAt <= clocks.wallNow() ? task.lastFiredAt : void 0;
|
|
78278
|
-
const cursor = seen
|
|
78867
|
+
const cursor = seen ?? persistedCursor ?? void 0;
|
|
78279
78868
|
return computeJitteredNext(task, parsed, cursor !== void 0 && cursor > task.createdAt ? cursor : task.createdAt);
|
|
78280
78869
|
} catch (error) {
|
|
78281
78870
|
debugLog(`getNextFireFor skipping task ${task.id}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -78371,7 +78960,9 @@ var CronManager = class {
|
|
|
78371
78960
|
source: () => this.store.list(),
|
|
78372
78961
|
isIdle: () => !agent.turn.hasActiveTurn,
|
|
78373
78962
|
isKilled: () => process.env["SCREAM_DISABLE_CRON"] === "1",
|
|
78374
|
-
onFire: (task, ctx) =>
|
|
78963
|
+
onFire: (task, ctx) => {
|
|
78964
|
+
this.handleFire(task, ctx);
|
|
78965
|
+
},
|
|
78375
78966
|
removeOneShot: (id) => {
|
|
78376
78967
|
this.removeTasks([id]);
|
|
78377
78968
|
},
|
|
@@ -78464,8 +79055,8 @@ var CronManager = class {
|
|
|
78464
79055
|
*/
|
|
78465
79056
|
persistEnqueue(id, work) {
|
|
78466
79057
|
if (this.persistStore === void 0) return;
|
|
78467
|
-
const next = (this.persistQueues.get(id) ?? Promise.resolve()).catch(() => {}).then(() => work()).catch((
|
|
78468
|
-
this.agent.log?.warn?.("cron persist failed",
|
|
79058
|
+
const next = (this.persistQueues.get(id) ?? Promise.resolve()).catch(() => {}).then(() => work()).catch((error) => {
|
|
79059
|
+
this.agent.log?.warn?.("cron persist failed", error);
|
|
78469
79060
|
}).finally(() => {
|
|
78470
79061
|
if (this.persistQueues.get(id) === next) this.persistQueues.delete(id);
|
|
78471
79062
|
});
|
|
@@ -78911,10 +79502,12 @@ var ContextMemory = class {
|
|
|
78911
79502
|
if (removedUserCount >= count) break;
|
|
78912
79503
|
}
|
|
78913
79504
|
}
|
|
78914
|
-
this.
|
|
78915
|
-
|
|
78916
|
-
|
|
78917
|
-
|
|
79505
|
+
for (let i = this._history.length - 1; i >= 0; i--) {
|
|
79506
|
+
if (this._history[i]?.origin?.kind !== "injection") continue;
|
|
79507
|
+
const prev = this._history[i - 1];
|
|
79508
|
+
const next = this._history[i + 1];
|
|
79509
|
+
if (prev?.origin?.kind === "injection" || next?.origin?.kind === "injection" || i === 0) this._history.splice(i, 1);
|
|
79510
|
+
}
|
|
78918
79511
|
if (!this.agent.records.restoring && (stoppedAtBoundary || removedUserCount < count)) {}
|
|
78919
79512
|
}
|
|
78920
79513
|
applyCompaction(summary) {
|
|
@@ -78937,6 +79530,7 @@ var ContextMemory = class {
|
|
|
78937
79530
|
this.tokenCountCoveredMessageCount = this._history.length;
|
|
78938
79531
|
this.agent.injection.onContextCompacted(summary.compactedCount);
|
|
78939
79532
|
this.agent.emitStatusUpdated();
|
|
79533
|
+
this.agent.microCompaction.reset();
|
|
78940
79534
|
}
|
|
78941
79535
|
data() {
|
|
78942
79536
|
return {
|
|
@@ -79068,8 +79662,8 @@ function toolResultOutputForModel(result) {
|
|
|
79068
79662
|
}
|
|
79069
79663
|
/** Truncate a plain-text tool output that exceeds MAX_TOOL_RESULT_TOKENS. */
|
|
79070
79664
|
function truncateToolOutput(text) {
|
|
79071
|
-
if (estimateTokens(text) <= MAX_TOOL_RESULT_TOKENS) return text;
|
|
79072
|
-
const budget = MAX_TOOL_RESULT_TOKENS - estimateTokens(TOOL_TRUNCATION_NOTICE);
|
|
79665
|
+
if (estimateTokens$1(text) <= MAX_TOOL_RESULT_TOKENS) return text;
|
|
79666
|
+
const budget = MAX_TOOL_RESULT_TOKENS - estimateTokens$1(TOOL_TRUNCATION_NOTICE);
|
|
79073
79667
|
if (budget <= 0) return TOOL_TRUNCATION_NOTICE.trim();
|
|
79074
79668
|
let kept = "";
|
|
79075
79669
|
let tokens = 0;
|
|
@@ -79084,9 +79678,9 @@ function truncateToolOutput(text) {
|
|
|
79084
79678
|
/** Truncate oversized text parts in a ContentPart array. */
|
|
79085
79679
|
function truncateContentParts(parts) {
|
|
79086
79680
|
let totalTokens = 0;
|
|
79087
|
-
for (const p of parts) if (p.type === "text") totalTokens += estimateTokens(p.text);
|
|
79681
|
+
for (const p of parts) if (p.type === "text") totalTokens += estimateTokens$1(p.text);
|
|
79088
79682
|
if (totalTokens <= MAX_TOOL_RESULT_TOKENS) return [...parts];
|
|
79089
|
-
const budget = MAX_TOOL_RESULT_TOKENS - estimateTokens(TOOL_TRUNCATION_NOTICE);
|
|
79683
|
+
const budget = MAX_TOOL_RESULT_TOKENS - estimateTokens$1(TOOL_TRUNCATION_NOTICE);
|
|
79090
79684
|
if (budget <= 0) return [{
|
|
79091
79685
|
type: "text",
|
|
79092
79686
|
text: TOOL_TRUNCATION_NOTICE.trim()
|
|
@@ -79098,7 +79692,7 @@ function truncateContentParts(parts) {
|
|
|
79098
79692
|
result.push(p);
|
|
79099
79693
|
continue;
|
|
79100
79694
|
}
|
|
79101
|
-
const partTokens = estimateTokens(p.text);
|
|
79695
|
+
const partTokens = estimateTokens$1(p.text);
|
|
79102
79696
|
if (used + partTokens <= budget) {
|
|
79103
79697
|
result.push(p);
|
|
79104
79698
|
used += partTokens;
|
|
@@ -79520,15 +80114,25 @@ var DynamicInjector = class {
|
|
|
79520
80114
|
};
|
|
79521
80115
|
//#endregion
|
|
79522
80116
|
//#region ../../packages/agent-core/src/agent/injection/goal.ts
|
|
80117
|
+
const GOAL_REMINDER_INTERVAL = 5;
|
|
79523
80118
|
var GoalInjector = class extends DynamicInjector {
|
|
79524
80119
|
injectionVariant = "goal";
|
|
79525
80120
|
getInjection() {
|
|
79526
80121
|
const goal = this.agent.goal.getGoal().goal;
|
|
79527
80122
|
if (goal === null) return void 0;
|
|
80123
|
+
if (this.injectedAt !== null) {
|
|
80124
|
+
if (this.countAssistantTurnsSince(this.injectedAt) < GOAL_REMINDER_INTERVAL) return void 0;
|
|
80125
|
+
}
|
|
79528
80126
|
if (goal.status === "active") return buildGoalReminder(goal);
|
|
79529
80127
|
if (goal.status === "blocked") return buildBlockedNote(goal);
|
|
79530
80128
|
if (goal.status === "paused") return buildPausedNote(goal);
|
|
79531
80129
|
}
|
|
80130
|
+
countAssistantTurnsSince(from) {
|
|
80131
|
+
let count = 0;
|
|
80132
|
+
const history = this.agent.context.history;
|
|
80133
|
+
for (let i = from; i < history.length; i++) if (history[i]?.role === "assistant") count++;
|
|
80134
|
+
return count;
|
|
80135
|
+
}
|
|
79532
80136
|
};
|
|
79533
80137
|
function buildBlockedNote(goal) {
|
|
79534
80138
|
const reason = goal.terminalReason;
|
|
@@ -79932,11 +80536,15 @@ var WolfPackModeInjector = class extends DynamicInjector {
|
|
|
79932
80536
|
};
|
|
79933
80537
|
//#endregion
|
|
79934
80538
|
//#region ../../packages/agent-core/src/agent/injection/working-set.ts
|
|
80539
|
+
const WORKING_SET_REMINDER_INTERVAL = 3;
|
|
79935
80540
|
var WorkingSetInjector = class extends DynamicInjector {
|
|
79936
80541
|
injectionVariant = "working-set";
|
|
79937
80542
|
getInjection() {
|
|
79938
80543
|
const paths = this.agent.workingSet.getPaths();
|
|
79939
80544
|
if (paths.length === 0) return void 0;
|
|
80545
|
+
if (this.injectedAt !== null) {
|
|
80546
|
+
if (this.countAssistantTurnsSince(this.injectedAt) < WORKING_SET_REMINDER_INTERVAL) return void 0;
|
|
80547
|
+
}
|
|
79940
80548
|
return [
|
|
79941
80549
|
"## Working Set",
|
|
79942
80550
|
"",
|
|
@@ -79946,6 +80554,12 @@ var WorkingSetInjector = class extends DynamicInjector {
|
|
|
79946
80554
|
"优先检查这些文件,避免重复读取未修改文件。"
|
|
79947
80555
|
].join("\n");
|
|
79948
80556
|
}
|
|
80557
|
+
countAssistantTurnsSince(from) {
|
|
80558
|
+
let count = 0;
|
|
80559
|
+
const history = this.agent.context.history;
|
|
80560
|
+
for (let i = from; i < history.length; i++) if (history[i]?.role === "assistant") count++;
|
|
80561
|
+
return count;
|
|
80562
|
+
}
|
|
79949
80563
|
};
|
|
79950
80564
|
//#endregion
|
|
79951
80565
|
//#region ../../packages/agent-core/src/agent/injection/manager.ts
|
|
@@ -81203,21 +81817,14 @@ var WolfPackMode = class {
|
|
|
81203
81817
|
//#region ../../packages/agent-core/src/agent/session-memory.ts
|
|
81204
81818
|
const MAX_EVENTS = 50;
|
|
81205
81819
|
const MAX_SUMMARY_LENGTH = 1500;
|
|
81206
|
-
/**
|
|
81207
|
-
* In-memory session running notes.
|
|
81208
|
-
*
|
|
81209
|
-
* Tracks tool executions and errors during the current session so a brief
|
|
81210
|
-
* summary can be injected after compaction, preventing the model from losing
|
|
81211
|
-
* its bearings when detailed conversation history is compressed away.
|
|
81212
|
-
*/
|
|
81213
81820
|
var SessionMemory = class {
|
|
81214
81821
|
agent;
|
|
81215
81822
|
events = [];
|
|
81216
|
-
|
|
81823
|
+
lastInjectedSeq = -1;
|
|
81824
|
+
nextSeq = 1;
|
|
81217
81825
|
constructor(agent) {
|
|
81218
81826
|
this.agent = agent;
|
|
81219
81827
|
}
|
|
81220
|
-
/** Record a tool execution (success or failure). */
|
|
81221
81828
|
recordToolExecution(toolName, argsSummary, isError, step) {
|
|
81222
81829
|
this.events.push({
|
|
81223
81830
|
type: "tool_execution",
|
|
@@ -81225,26 +81832,26 @@ var SessionMemory = class {
|
|
|
81225
81832
|
argsSummary,
|
|
81226
81833
|
isError,
|
|
81227
81834
|
timestamp: Date.now(),
|
|
81228
|
-
step
|
|
81835
|
+
step,
|
|
81836
|
+
seq: this.nextSeq++
|
|
81229
81837
|
});
|
|
81230
81838
|
if (this.events.length > MAX_EVENTS) this.events = this.events.slice(-50);
|
|
81231
81839
|
}
|
|
81232
|
-
/** Record a session-level error. */
|
|
81233
81840
|
recordError(message, step) {
|
|
81234
81841
|
this.events.push({
|
|
81235
81842
|
type: "error",
|
|
81236
81843
|
message,
|
|
81237
81844
|
isError: true,
|
|
81238
81845
|
timestamp: Date.now(),
|
|
81239
|
-
step
|
|
81846
|
+
step,
|
|
81847
|
+
seq: this.nextSeq++
|
|
81240
81848
|
});
|
|
81241
81849
|
if (this.events.length > MAX_EVENTS) this.events = this.events.slice(-50);
|
|
81242
81850
|
}
|
|
81243
|
-
/** Build a markdown summary of recent activity since the last injection. */
|
|
81244
81851
|
getSessionSummary() {
|
|
81245
|
-
const newEvents = this.events.filter((e) => e.
|
|
81852
|
+
const newEvents = this.events.filter((e) => e.seq > this.lastInjectedSeq);
|
|
81246
81853
|
if (newEvents.length === 0) return "";
|
|
81247
|
-
this.
|
|
81854
|
+
this.lastInjectedSeq = this.events.at(-1)?.seq ?? this.lastInjectedSeq;
|
|
81248
81855
|
const recent = newEvents.slice(-15);
|
|
81249
81856
|
const toolExecs = recent.filter((e) => e.type === "tool_execution");
|
|
81250
81857
|
const errors = recent.filter((e) => e.type === "error");
|
|
@@ -81267,10 +81874,10 @@ var SessionMemory = class {
|
|
|
81267
81874
|
const joined = lines.join("\n");
|
|
81268
81875
|
return joined.length > MAX_SUMMARY_LENGTH ? joined.slice(0, MAX_SUMMARY_LENGTH - 3) + "..." : joined;
|
|
81269
81876
|
}
|
|
81270
|
-
/** Reset for a new session. */
|
|
81271
81877
|
clear() {
|
|
81272
81878
|
this.events.length = 0;
|
|
81273
|
-
this.
|
|
81879
|
+
this.lastInjectedSeq = -1;
|
|
81880
|
+
this.nextSeq = 1;
|
|
81274
81881
|
}
|
|
81275
81882
|
};
|
|
81276
81883
|
const VERIFICATION_DEDUP_MS = 6e4;
|
|
@@ -81812,7 +82419,7 @@ var BlobStore = class {
|
|
|
81812
82419
|
if (typeof url !== "string") continue;
|
|
81813
82420
|
const newUrl = await this.maybeOffloadString(url);
|
|
81814
82421
|
if (newUrl === url) continue;
|
|
81815
|
-
|
|
82422
|
+
updated ??= { ...part };
|
|
81816
82423
|
updated[key] = {
|
|
81817
82424
|
...value,
|
|
81818
82425
|
url: newUrl
|
|
@@ -95505,6 +96112,7 @@ var TurnFlow = class {
|
|
|
95505
96112
|
});
|
|
95506
96113
|
if (turnId !== void 0 && turnId !== this.currentId) return;
|
|
95507
96114
|
const cancelReason = reason ?? userCancellationReason();
|
|
96115
|
+
this.agent.usage.endTurn();
|
|
95508
96116
|
this.abortTurn(cancelReason);
|
|
95509
96117
|
this.agent.subagentHost?.cancelAll(cancelReason);
|
|
95510
96118
|
}
|
|
@@ -95613,7 +96221,6 @@ var TurnFlow = class {
|
|
|
95613
96221
|
turnId,
|
|
95614
96222
|
origin
|
|
95615
96223
|
});
|
|
95616
|
-
this.agent.context.appendUserMessage(input, origin);
|
|
95617
96224
|
const ended = {
|
|
95618
96225
|
type: "turn.ended",
|
|
95619
96226
|
turnId,
|
|
@@ -95645,6 +96252,7 @@ var TurnFlow = class {
|
|
|
95645
96252
|
this.turnStartWorkingSetPathCount = this.agent.workingSet.getPaths().length;
|
|
95646
96253
|
this.turnStartVerificationCount = this.agent.workingSet.getVerificationCount();
|
|
95647
96254
|
this.currentStepByTurn.set(turnId, 0);
|
|
96255
|
+
this.currentStep = 0;
|
|
95648
96256
|
this.agent.fullCompaction.resetForTurn();
|
|
95649
96257
|
this.agent.injection.resetForTurn();
|
|
95650
96258
|
this.agent.usage.beginTurn();
|
|
@@ -95793,11 +96401,13 @@ var TurnFlow = class {
|
|
|
95793
96401
|
kind: "system_trigger",
|
|
95794
96402
|
name: "todo_suggested"
|
|
95795
96403
|
});
|
|
95796
|
-
|
|
95797
|
-
|
|
95798
|
-
|
|
95799
|
-
|
|
95800
|
-
|
|
96404
|
+
if (stepNumber === 1 || this.agent.fullCompaction.shouldInjectSessionSummary()) {
|
|
96405
|
+
const sessionSummary = this.agent.sessionMemory.getSessionSummary();
|
|
96406
|
+
if (sessionSummary.length > 0) this.agent.context.appendSystemReminder(sessionSummary, {
|
|
96407
|
+
kind: "injection",
|
|
96408
|
+
variant: "session_memory"
|
|
96409
|
+
});
|
|
96410
|
+
}
|
|
95801
96411
|
if (stepNumber === 1 && this.agent.dreamTracker.shouldSuggest()) this.agent.context.appendSystemReminder(this.agent.dreamTracker.getSuggestionMessage(), {
|
|
95802
96412
|
kind: "injection",
|
|
95803
96413
|
variant: "dream_suggestion"
|
|
@@ -95955,6 +96565,7 @@ var TurnFlow = class {
|
|
|
95955
96565
|
}
|
|
95956
96566
|
}
|
|
95957
96567
|
}
|
|
96568
|
+
/** Track files touched by builtin tools. Bash-modified files are NOT tracked. */
|
|
95958
96569
|
recordWorkingSetPaths(toolName, args, turnId) {
|
|
95959
96570
|
const workingSet = this.agent.workingSet;
|
|
95960
96571
|
if (toolName === "Read" || toolName === "ReadGroup" || toolName === "ReadMediaFile") {
|
|
@@ -96040,7 +96651,23 @@ var TurnFlow = class {
|
|
|
96040
96651
|
/(^|;\s*|&&\s*)\s*pnp[ms]\s+add\s+(--global\s+)?typescript/,
|
|
96041
96652
|
/(^|;\s*|&&\s*)\s*pnp[ms]\s+add\s+(--global\s+)?tsx/,
|
|
96042
96653
|
/(^|;\s*|&&\s*)\s*yarn\s+add\s+(--dev\s+)?typescript/,
|
|
96043
|
-
/(^|;\s*|&&\s*)\s*yarn\s+add\s+(--dev\s+)?tsx
|
|
96654
|
+
/(^|;\s*|&&\s*)\s*yarn\s+add\s+(--dev\s+)?tsx/,
|
|
96655
|
+
/\bgit\s+status\b/,
|
|
96656
|
+
/\bgit\s+diff\b/,
|
|
96657
|
+
/\bgit\s+log\b/,
|
|
96658
|
+
/\bgrep\s+/,
|
|
96659
|
+
/\brg\s+/,
|
|
96660
|
+
/\bnode\s+-e\s+/,
|
|
96661
|
+
/\bpython\b/,
|
|
96662
|
+
/\bpython3\b/,
|
|
96663
|
+
/\bwc\s+/,
|
|
96664
|
+
/\bsort\s+/,
|
|
96665
|
+
/\buniq\b/,
|
|
96666
|
+
/\bdiff\s+/,
|
|
96667
|
+
/\bfile\s+/,
|
|
96668
|
+
/\bstat\s+/,
|
|
96669
|
+
/\bdf\s+/,
|
|
96670
|
+
/\bdu\s+/
|
|
96044
96671
|
].some((pattern) => pattern.test(normalized));
|
|
96045
96672
|
}
|
|
96046
96673
|
};
|
|
@@ -96289,7 +96916,7 @@ var LtodLLM = class {
|
|
|
96289
96916
|
streamEndedAt = Date.now();
|
|
96290
96917
|
};
|
|
96291
96918
|
const markStreamOutput = () => {
|
|
96292
|
-
|
|
96919
|
+
firstChunkAt ??= Date.now();
|
|
96293
96920
|
};
|
|
96294
96921
|
const callbacks = buildLtodCallbacks(params, markStreamOutput);
|
|
96295
96922
|
const effectiveProvider = applyCompletionBudget({
|
|
@@ -96613,7 +97240,7 @@ var Agent = class {
|
|
|
96613
97240
|
this.logLlmConfigIfChanged(context, configMetadata, buildLlmConfigSignature(configMetadata, systemPrompt, tools));
|
|
96614
97241
|
let partialMessageCount = 0;
|
|
96615
97242
|
for (const message of history) if (message.partial === true) partialMessageCount += 1;
|
|
96616
|
-
const requestMetadata = { estimatedInputTokens: estimateTokens(systemPrompt) + estimateTokensForMessages(history) + estimateTokensForTools(tools) };
|
|
97243
|
+
const requestMetadata = { estimatedInputTokens: estimateTokens$1(systemPrompt) + estimateTokensForMessages(history) + estimateTokensForTools(tools) };
|
|
96617
97244
|
if (partialMessageCount > 0) requestMetadata.partialMessageCount = partialMessageCount;
|
|
96618
97245
|
this.log.info("llm request", {
|
|
96619
97246
|
...context,
|
|
@@ -96844,6 +97471,7 @@ var Agent = class {
|
|
|
96844
97471
|
}
|
|
96845
97472
|
const conversationContext = contextParts.join("\n\n");
|
|
96846
97473
|
const system = conversationContext ? `${SIDE_QUESTION_SYSTEM}\n\n<conversation_context>\n${conversationContext}\n</conversation_context>` : SIDE_QUESTION_SYSTEM;
|
|
97474
|
+
if (!this.config.hasModel) return "No model configured. Run `scream config` or use `/model` to set a default model.";
|
|
96847
97475
|
return (await this.generate(this.config.provider, system, [], [{
|
|
96848
97476
|
role: "user",
|
|
96849
97477
|
content: [{
|
|
@@ -100985,7 +101613,7 @@ function findParentMcpJsonPaths(cwd) {
|
|
|
100985
101613
|
paths.push(join$1(dir, ".scream-code", "mcp.json"));
|
|
100986
101614
|
dir = dirname$2(dir);
|
|
100987
101615
|
}
|
|
100988
|
-
return paths.
|
|
101616
|
+
return paths.toReversed();
|
|
100989
101617
|
}
|
|
100990
101618
|
/**
|
|
100991
101619
|
* Load MCP server declarations from:
|
|
@@ -114909,7 +115537,7 @@ const LITE_RESULT_RE = /<a\s[^>]*?\bclass\s*=\s*["'][^"']*result-link[^"']*["'][
|
|
|
114909
115537
|
const LITE_SNIPPET_RE = /<td\s[^>]*?\bclass\s*=\s*["'][^"']*result-snippet[^"']*["'][^>]*>([\s\S]*?)<\/td>/gi;
|
|
114910
115538
|
/** Strip HTML tags and decode common HTML entities. */
|
|
114911
115539
|
function stripHtmlAndDecode(text) {
|
|
114912
|
-
return text.
|
|
115540
|
+
return text.replaceAll(/<[^>]*>/g, "").replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll(""", "\"").replaceAll("'", "'").replaceAll("'", "'").replaceAll(" ", " ").trim();
|
|
114913
115541
|
}
|
|
114914
115542
|
/** Unwrap a DuckDuckGo redirect URL to the real target if possible. */
|
|
114915
115543
|
function unwrapDdgRedirect(url) {
|
|
@@ -117095,7 +117723,7 @@ function ccSessionToSummary(cc, agentSessionId, projectName) {
|
|
|
117095
117723
|
const createdAt = parseCcTimestamp(cc.created_at);
|
|
117096
117724
|
const updatedAt = parseCcTimestamp(cc.updated_at) ?? createdAt;
|
|
117097
117725
|
if (createdAt === void 0 && updatedAt === void 0) return void 0;
|
|
117098
|
-
const title = cc.name?.trim()
|
|
117726
|
+
const title = cc.name?.trim() ?? cc.id;
|
|
117099
117727
|
const lastPrompt = lastPromptFromCcHistory(cc.history);
|
|
117100
117728
|
const metadata = {
|
|
117101
117729
|
source: "cc-connect",
|
|
@@ -117167,6 +117795,17 @@ var JianPathOutsideRootError = class extends JianError {
|
|
|
117167
117795
|
this.name = "JianPathOutsideRootError";
|
|
117168
117796
|
}
|
|
117169
117797
|
};
|
|
117798
|
+
/** Thrown when process execution fails (spawn error, ENOENT, EACCES). */
|
|
117799
|
+
var JianExecError = class extends JianError {
|
|
117800
|
+
command;
|
|
117801
|
+
code;
|
|
117802
|
+
constructor(message, command, code) {
|
|
117803
|
+
super(message);
|
|
117804
|
+
this.command = command;
|
|
117805
|
+
this.code = code;
|
|
117806
|
+
this.name = "JianExecError";
|
|
117807
|
+
}
|
|
117808
|
+
};
|
|
117170
117809
|
//#endregion
|
|
117171
117810
|
//#region ../../packages/jian/src/environment.ts
|
|
117172
117811
|
/**
|
|
@@ -117651,16 +118290,19 @@ var LocalProcess = class {
|
|
|
117651
118290
|
"/PID",
|
|
117652
118291
|
String(this.pid)
|
|
117653
118292
|
];
|
|
117654
|
-
return new Promise((resolve) => {
|
|
118293
|
+
return new Promise((resolve, reject) => {
|
|
117655
118294
|
const killer = spawn("taskkill", taskkillArgs, {
|
|
117656
118295
|
stdio: "ignore",
|
|
117657
118296
|
windowsHide: true
|
|
117658
118297
|
});
|
|
117659
|
-
|
|
117660
|
-
|
|
117661
|
-
};
|
|
117662
|
-
killer.once("
|
|
117663
|
-
|
|
118298
|
+
killer.once("error", (err) => {
|
|
118299
|
+
reject(/* @__PURE__ */ new Error(`taskkill failed to start: ${err.message}`));
|
|
118300
|
+
});
|
|
118301
|
+
killer.once("close", (code) => {
|
|
118302
|
+
if (code === 0) resolve();
|
|
118303
|
+
else reject(/* @__PURE__ */ new Error(`taskkill exited with code ${code ?? "null"}`));
|
|
118304
|
+
});
|
|
118305
|
+
killer.unref();
|
|
117664
118306
|
});
|
|
117665
118307
|
}
|
|
117666
118308
|
try {
|
|
@@ -117720,6 +118362,14 @@ var LocalJian = class LocalJian {
|
|
|
117720
118362
|
if (isWithinDirectory(resolvedPath, this._rootDir)) return;
|
|
117721
118363
|
throw new JianPathOutsideRootError(`Path outside allowed root directory: ${resolvedPath}`, resolvedPath, this._rootDir);
|
|
117722
118364
|
}
|
|
118365
|
+
/** Resolve path for sandboxed operations — lexical check + realpath. */
|
|
118366
|
+
async _resolveSandboxedPath(path) {
|
|
118367
|
+
const lexical = isAbsolute$1(path) ? normalize(path) : join$1(this._cwd, path);
|
|
118368
|
+
if (!isWithinDirectory(lexical, this._rootDir)) throw new JianPathOutsideRootError(`Path outside allowed root directory: ${lexical}`, lexical, this._rootDir);
|
|
118369
|
+
const realPath = await realpath(lexical);
|
|
118370
|
+
if (!isWithinDirectory(realPath, await realpath(this._rootDir))) throw new JianPathOutsideRootError(`Path outside allowed root directory (via symlink): ${lexical}`, lexical, this._rootDir);
|
|
118371
|
+
return realPath;
|
|
118372
|
+
}
|
|
117723
118373
|
pathClass() {
|
|
117724
118374
|
return isWindows ? "win32" : "posix";
|
|
117725
118375
|
}
|
|
@@ -117742,13 +118392,14 @@ var LocalJian = class LocalJian {
|
|
|
117742
118392
|
* call `process.chdir(x)` directly.
|
|
117743
118393
|
*/
|
|
117744
118394
|
async chdir(path) {
|
|
117745
|
-
const resolved = this._resolvePath(path);
|
|
118395
|
+
const resolved = this._rootDir !== void 0 ? await this._resolveSandboxedPath(path) : this._resolvePath(path);
|
|
117746
118396
|
if (!(await stat(resolved)).isDirectory()) throw new Error(`Not a directory: ${resolved}`);
|
|
117747
118397
|
this._cwd = resolved;
|
|
117748
118398
|
}
|
|
117749
118399
|
async stat(path, options) {
|
|
117750
|
-
const
|
|
117751
|
-
const
|
|
118400
|
+
const followSymlinks = options?.followSymlinks ?? true;
|
|
118401
|
+
const resolved = this._rootDir !== void 0 && followSymlinks ? await this._resolveSandboxedPath(path) : this._resolvePath(path);
|
|
118402
|
+
const s = followSymlinks ? await stat(resolved) : await lstat(resolved);
|
|
117752
118403
|
return {
|
|
117753
118404
|
stMode: s.mode,
|
|
117754
118405
|
stIno: s.ino,
|
|
@@ -117792,6 +118443,7 @@ var LocalJian = class LocalJian {
|
|
|
117792
118443
|
}
|
|
117793
118444
|
for (const entry of entries) {
|
|
117794
118445
|
const fullPath = join$1(basePath, entry);
|
|
118446
|
+
if (this._rootDir && !isWithinDirectory(fullPath, this._rootDir)) continue;
|
|
117795
118447
|
let entryStat;
|
|
117796
118448
|
try {
|
|
117797
118449
|
entryStat = await stat(fullPath);
|
|
@@ -117815,6 +118467,7 @@ var LocalJian = class LocalJian {
|
|
|
117815
118467
|
for (const entry of entries) {
|
|
117816
118468
|
if (!regex.test(entry)) continue;
|
|
117817
118469
|
const fullPath = join$1(basePath, entry);
|
|
118470
|
+
if (this._rootDir && !isWithinDirectory(fullPath, this._rootDir)) continue;
|
|
117818
118471
|
if (remainingParts.length === 0) yield fullPath;
|
|
117819
118472
|
else {
|
|
117820
118473
|
let entryStat;
|
|
@@ -117851,16 +118504,21 @@ var LocalJian = class LocalJian {
|
|
|
117851
118504
|
return decodeTextWithErrors(await readFile(resolved), encoding, errors);
|
|
117852
118505
|
}
|
|
117853
118506
|
async *readLines(path, options) {
|
|
117854
|
-
const
|
|
117855
|
-
|
|
117856
|
-
|
|
117857
|
-
|
|
117858
|
-
|
|
117859
|
-
|
|
117860
|
-
|
|
117861
|
-
|
|
117862
|
-
|
|
118507
|
+
const stream = createReadStream(this._resolvePath(path), {
|
|
118508
|
+
encoding: options?.encoding ?? "utf-8",
|
|
118509
|
+
highWaterMark: 64 * 1024
|
|
118510
|
+
});
|
|
118511
|
+
let remainder = "";
|
|
118512
|
+
try {
|
|
118513
|
+
for await (const chunk of stream) {
|
|
118514
|
+
const lines = (remainder + chunk).split("\n");
|
|
118515
|
+
remainder = lines.pop() ?? "";
|
|
118516
|
+
for (const line of lines) yield line + "\n";
|
|
118517
|
+
}
|
|
118518
|
+
} finally {
|
|
118519
|
+
stream.destroy();
|
|
117863
118520
|
}
|
|
118521
|
+
if (remainder !== "") yield remainder;
|
|
117864
118522
|
}
|
|
117865
118523
|
async writeBytes(path, data) {
|
|
117866
118524
|
await writeFile(this._resolvePath(path), data);
|
|
@@ -117913,7 +118571,12 @@ var LocalJian = class LocalJian {
|
|
|
117913
118571
|
detached: !isWindows,
|
|
117914
118572
|
env: buildSafeEnv(env)
|
|
117915
118573
|
});
|
|
117916
|
-
|
|
118574
|
+
try {
|
|
118575
|
+
await waitForSpawn(child);
|
|
118576
|
+
} catch (error) {
|
|
118577
|
+
if (error instanceof Error) throw new JianExecError(`Failed to spawn ${command}: ${error.message}`, command, error.code);
|
|
118578
|
+
throw error;
|
|
118579
|
+
}
|
|
117917
118580
|
return new LocalProcess(child);
|
|
117918
118581
|
}
|
|
117919
118582
|
};
|
|
@@ -118923,26 +119586,28 @@ var SDKRpcClient = class {
|
|
|
118923
119586
|
async getStatus(input) {
|
|
118924
119587
|
const rpc = await this.getRpc();
|
|
118925
119588
|
const agentId = this.interactiveAgentId;
|
|
118926
|
-
const config = await
|
|
118927
|
-
|
|
118928
|
-
|
|
118929
|
-
|
|
118930
|
-
|
|
118931
|
-
|
|
118932
|
-
|
|
118933
|
-
|
|
118934
|
-
|
|
118935
|
-
|
|
118936
|
-
|
|
118937
|
-
|
|
118938
|
-
|
|
118939
|
-
|
|
118940
|
-
|
|
118941
|
-
|
|
118942
|
-
|
|
118943
|
-
|
|
118944
|
-
|
|
118945
|
-
|
|
119589
|
+
const [config, context, permission, plan, usage] = await Promise.all([
|
|
119590
|
+
rpc.getConfig({
|
|
119591
|
+
sessionId: input.sessionId,
|
|
119592
|
+
agentId
|
|
119593
|
+
}),
|
|
119594
|
+
rpc.getContext({
|
|
119595
|
+
sessionId: input.sessionId,
|
|
119596
|
+
agentId
|
|
119597
|
+
}),
|
|
119598
|
+
rpc.getPermission({
|
|
119599
|
+
sessionId: input.sessionId,
|
|
119600
|
+
agentId
|
|
119601
|
+
}),
|
|
119602
|
+
rpc.getPlan({
|
|
119603
|
+
sessionId: input.sessionId,
|
|
119604
|
+
agentId
|
|
119605
|
+
}),
|
|
119606
|
+
rpc.getUsage({
|
|
119607
|
+
sessionId: input.sessionId,
|
|
119608
|
+
agentId
|
|
119609
|
+
})
|
|
119610
|
+
]);
|
|
118946
119611
|
const maxContextTokens = config.modelCapabilities?.max_context_tokens ?? 0;
|
|
118947
119612
|
const contextTokens = context.tokenCount;
|
|
118948
119613
|
const contextUsage = maxContextTokens > 0 ? contextTokens / maxContextTokens : 0;
|
|
@@ -119085,7 +119750,9 @@ var SDKRpcClient = class {
|
|
|
119085
119750
|
};
|
|
119086
119751
|
}
|
|
119087
119752
|
receiveEvent(event) {
|
|
119088
|
-
for (const listener of this.eventListeners)
|
|
119753
|
+
for (const listener of this.eventListeners) try {
|
|
119754
|
+
listener(event);
|
|
119755
|
+
} catch {}
|
|
119089
119756
|
}
|
|
119090
119757
|
setApprovalHandler(sessionId, handler) {
|
|
119091
119758
|
if (handler === void 0) {
|
|
@@ -119206,19 +119873,24 @@ var Session = class {
|
|
|
119206
119873
|
this.resumeState = options.resumeState ?? resumeStateFromSummary(options.summary);
|
|
119207
119874
|
this.rpc = options.rpc;
|
|
119208
119875
|
this.onClose = options.onClose;
|
|
119209
|
-
const
|
|
119210
|
-
this.metadata =
|
|
119211
|
-
if (!this.metadata["custom"]) this.metadata["custom"] = {};
|
|
119876
|
+
const customBag = (this.resumeState?.sessionMetadata)?.custom ?? options.summary?.metadata ?? {};
|
|
119877
|
+
this.metadata = customBag;
|
|
119212
119878
|
}
|
|
119213
119879
|
getResumeState() {
|
|
119214
119880
|
this.ensureOpen();
|
|
119215
119881
|
return this.resumeState;
|
|
119216
119882
|
}
|
|
119883
|
+
eventUnsubscribers = /* @__PURE__ */ new Set();
|
|
119217
119884
|
onEvent(listener) {
|
|
119218
119885
|
this.ensureOpen();
|
|
119219
|
-
|
|
119886
|
+
const unsub = this.rpc.onEvent((event) => {
|
|
119220
119887
|
if (event.sessionId === this.id) listener(event);
|
|
119221
119888
|
});
|
|
119889
|
+
this.eventUnsubscribers.add(unsub);
|
|
119890
|
+
return () => {
|
|
119891
|
+
unsub();
|
|
119892
|
+
this.eventUnsubscribers.delete(unsub);
|
|
119893
|
+
};
|
|
119222
119894
|
}
|
|
119223
119895
|
setApprovalHandler(handler) {
|
|
119224
119896
|
this.ensureOpen();
|
|
@@ -119545,11 +120217,13 @@ var Session = class {
|
|
|
119545
120217
|
}
|
|
119546
120218
|
async close(options = {}) {
|
|
119547
120219
|
if (this.closed) return;
|
|
119548
|
-
if (options.extractMemories !== false)
|
|
119549
|
-
const extract = this.extractMemoriesOnExit()
|
|
119550
|
-
await Promise.race([extract, new Promise((
|
|
119551
|
-
}
|
|
120220
|
+
if (options.extractMemories !== false) {
|
|
120221
|
+
const extract = this.extractMemoriesOnExit();
|
|
120222
|
+
await Promise.race([extract.catch(() => {}), new Promise((r) => setTimeout(r, 3e4))]);
|
|
120223
|
+
}
|
|
119552
120224
|
this.closed = true;
|
|
120225
|
+
for (const unsub of this.eventUnsubscribers) unsub();
|
|
120226
|
+
this.eventUnsubscribers.clear();
|
|
119553
120227
|
try {
|
|
119554
120228
|
await this.rpc.closeSession({ sessionId: this.id });
|
|
119555
120229
|
} finally {
|
|
@@ -120067,11 +120741,11 @@ function detectMultiplexer() {
|
|
|
120067
120741
|
}
|
|
120068
120742
|
function detectShellEnvironment() {
|
|
120069
120743
|
return {
|
|
120070
|
-
term: process.env["TERM"]
|
|
120071
|
-
termProgram: process.env["TERM_PROGRAM"]
|
|
120072
|
-
termProgramVersion: process.env["TERM_PROGRAM_VERSION"]
|
|
120744
|
+
term: process.env["TERM"] ?? void 0,
|
|
120745
|
+
termProgram: process.env["TERM_PROGRAM"] ?? void 0,
|
|
120746
|
+
termProgramVersion: process.env["TERM_PROGRAM_VERSION"] ?? void 0,
|
|
120073
120747
|
multiplexer: detectMultiplexer(),
|
|
120074
|
-
shell: process.env["SHELL"]
|
|
120748
|
+
shell: process.env["SHELL"] ?? void 0
|
|
120075
120749
|
};
|
|
120076
120750
|
}
|
|
120077
120751
|
//#endregion
|
|
@@ -121283,6 +121957,19 @@ function formatErrorMessage(error) {
|
|
|
121283
121957
|
if (isScreamError(error)) return `[${error.code}] ${error.message}`;
|
|
121284
121958
|
return error instanceof Error ? error.message : String(error);
|
|
121285
121959
|
}
|
|
121960
|
+
/**
|
|
121961
|
+
* Cap an error message body for inline transcript display. Drops blank lines,
|
|
121962
|
+
* keeps the first `maxLines` non-blank lines, and appends a `… (N more lines)`
|
|
121963
|
+
* hint when truncated. Full text is preserved in the persisted session — this
|
|
121964
|
+
* only bounds the live transcript render.
|
|
121965
|
+
*/
|
|
121966
|
+
function truncateErrorMessage(message, maxLines = 8) {
|
|
121967
|
+
const lines = message.split("\n").filter((line) => line.trim().length > 0);
|
|
121968
|
+
if (lines.length <= maxLines) return lines.join("\n");
|
|
121969
|
+
const kept = lines.slice(0, maxLines);
|
|
121970
|
+
const remaining = lines.length - maxLines;
|
|
121971
|
+
return `${kept.join("\n")}\n… (${String(remaining)} more lines)`;
|
|
121972
|
+
}
|
|
121286
121973
|
function stringValue(value) {
|
|
121287
121974
|
return typeof value === "string" ? value : void 0;
|
|
121288
121975
|
}
|
|
@@ -122197,7 +122884,7 @@ async function handleDiyConfig(host) {
|
|
|
122197
122884
|
const maxContextTokens = parseInt(maxContextStr, 10) || 131072;
|
|
122198
122885
|
const thinking = await promptThinkingMode(host);
|
|
122199
122886
|
if (thinking === void 0) return;
|
|
122200
|
-
const providerId = `custom-${modelId.
|
|
122887
|
+
const providerId = `custom-${modelId.replaceAll(/[^A-Za-z0-9._-]/g, "-")}`;
|
|
122201
122888
|
const catalogModel = {
|
|
122202
122889
|
id: modelId,
|
|
122203
122890
|
name: modelId,
|
|
@@ -122588,7 +123275,7 @@ const darkColors = {
|
|
|
122588
123275
|
const lightColors = {
|
|
122589
123276
|
primary: light.green,
|
|
122590
123277
|
accent: light.tangerine700,
|
|
122591
|
-
planMode: "#
|
|
123278
|
+
planMode: "#00838F",
|
|
122592
123279
|
text: light.gray900,
|
|
122593
123280
|
textStrong: light.gray900,
|
|
122594
123281
|
textDim: light.gray700,
|
|
@@ -124027,7 +124714,9 @@ async function showGoalStatus(host) {
|
|
|
124027
124714
|
const panel = new GoalStatusMessageComponent(result.goal, host.state.theme.colors);
|
|
124028
124715
|
host.state.transcriptContainer.addChild(panel);
|
|
124029
124716
|
activeGoalPanel = panel;
|
|
124030
|
-
activeGoalTimer = setTimeout(() =>
|
|
124717
|
+
activeGoalTimer = setTimeout(() => {
|
|
124718
|
+
dismissGoalPanel(host);
|
|
124719
|
+
}, GOAL_STATUS_DISMISS_MS);
|
|
124031
124720
|
host.state.ui.requestRender();
|
|
124032
124721
|
} catch (error) {
|
|
124033
124722
|
const message = error instanceof Error ? error.message : String(error);
|
|
@@ -124787,10 +125476,109 @@ var SkillActivationComponent = class extends Container {
|
|
|
124787
125476
|
}
|
|
124788
125477
|
};
|
|
124789
125478
|
//#endregion
|
|
125479
|
+
//#region src/tui/utils/speed-tracker.ts
|
|
125480
|
+
/**
|
|
125481
|
+
* Streaming-speed gauge for the live thinking indicator.
|
|
125482
|
+
*
|
|
125483
|
+
* Ported from oh-my-pi `packages/coding-agent/src/modes/components/assistant-message.ts:120-178`.
|
|
125484
|
+
* scream-code adaptation: the provider does not report cumulative token counts
|
|
125485
|
+
* during streaming (only character deltas), so instantaneous tok/s is estimated
|
|
125486
|
+
* from delta length via {@link CHARS_PER_TOKEN_ESTIMATE}. The badge is a progress
|
|
125487
|
+
* indicator, not a precise meter.
|
|
125488
|
+
*/
|
|
125489
|
+
/** Rolling window (ms) over which streaming-rate observations are averaged. */
|
|
125490
|
+
const SPEED_WINDOW_MS = 3e3;
|
|
125491
|
+
/**
|
|
125492
|
+
* Chars-per-token estimate for converting character deltas to an approximate
|
|
125493
|
+
* token count. 2.5 is a middle ground between English (~4 chars/token) and
|
|
125494
|
+
* Chinese (~1 char/token). Pure Chinese underestimates ~2.5x, pure English
|
|
125495
|
+
* overestimates ~1.6x — acceptable for a progress indicator.
|
|
125496
|
+
*/
|
|
125497
|
+
const CHARS_PER_TOKEN_ESTIMATE = 2.5;
|
|
125498
|
+
var SpeedTracker = class {
|
|
125499
|
+
observations = [];
|
|
125500
|
+
prune(now) {
|
|
125501
|
+
const threshold = now - SPEED_WINDOW_MS;
|
|
125502
|
+
while (this.observations.length > 0 && this.observations[0].time < threshold) this.observations.shift();
|
|
125503
|
+
}
|
|
125504
|
+
/**
|
|
125505
|
+
* Record one instantaneous tok/s reading, clamped to {@link SPEED_MAX} so a
|
|
125506
|
+
* single oversized delta can't poison the windowed average. Non-finite or
|
|
125507
|
+
* negative rates are ignored.
|
|
125508
|
+
*/
|
|
125509
|
+
observe(rate, now = performance.now()) {
|
|
125510
|
+
if (!Number.isFinite(rate) || rate < 0) return;
|
|
125511
|
+
this.observations.push({
|
|
125512
|
+
time: now,
|
|
125513
|
+
rate: Math.min(rate, 200)
|
|
125514
|
+
});
|
|
125515
|
+
this.prune(now);
|
|
125516
|
+
}
|
|
125517
|
+
/** Windowed-average tok/s; 0 once observations age out of the window. */
|
|
125518
|
+
getSpeed(now = performance.now()) {
|
|
125519
|
+
this.prune(now);
|
|
125520
|
+
if (this.observations.length === 0) return 0;
|
|
125521
|
+
let sum = 0;
|
|
125522
|
+
for (const o of this.observations) sum += o.rate;
|
|
125523
|
+
return sum / this.observations.length;
|
|
125524
|
+
}
|
|
125525
|
+
reset() {
|
|
125526
|
+
this.observations = [];
|
|
125527
|
+
}
|
|
125528
|
+
};
|
|
125529
|
+
/**
|
|
125530
|
+
* One gauge for the whole session. Only the single live thinking block feeds it
|
|
125531
|
+
* (via {@link StreamingUIController.appendThinkingDelta} / {@link appendAssistantDelta}),
|
|
125532
|
+
* and only the live {@link ThinkingComponent} reads it. Reset on turn boundaries
|
|
125533
|
+
* so a previous turn's trailing rate doesn't leak onto a fresh block.
|
|
125534
|
+
*/
|
|
125535
|
+
const sharedSpeedTracker = new SpeedTracker();
|
|
125536
|
+
function getSharedSpeedTracker() {
|
|
125537
|
+
return sharedSpeedTracker;
|
|
125538
|
+
}
|
|
125539
|
+
/**
|
|
125540
|
+
* Linear-interpolate two `#rrggbb` colors in sRGB space. `t` clamps to [0,1]:
|
|
125541
|
+
* `t = 0` → `from`, `t = 1` → `to`. Drives the streaming speed badge, fading
|
|
125542
|
+
* from a dim gray toward the theme accent as tok/s rises.
|
|
125543
|
+
*/
|
|
125544
|
+
function lerpHex(from, to, t) {
|
|
125545
|
+
const k = t < 0 ? 0 : t > 1 ? 1 : t;
|
|
125546
|
+
const fr = Number.parseInt(from.slice(1, 3), 16);
|
|
125547
|
+
const fg = Number.parseInt(from.slice(3, 5), 16);
|
|
125548
|
+
const fb = Number.parseInt(from.slice(5, 7), 16);
|
|
125549
|
+
const tr = Number.parseInt(to.slice(1, 3), 16);
|
|
125550
|
+
const tg = Number.parseInt(to.slice(3, 5), 16);
|
|
125551
|
+
const tb = Number.parseInt(to.slice(5, 7), 16);
|
|
125552
|
+
const r = Math.round(fr + (tr - fr) * k);
|
|
125553
|
+
const g = Math.round(fg + (tg - fg) * k);
|
|
125554
|
+
const b = Math.round(fb + (tb - fb) * k);
|
|
125555
|
+
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
125556
|
+
}
|
|
125557
|
+
/**
|
|
125558
|
+
* Estimate token count from a character delta. At least 1 to avoid zero-rate
|
|
125559
|
+
* observations when a tiny delta arrives.
|
|
125560
|
+
*/
|
|
125561
|
+
function estimateTokens(delta) {
|
|
125562
|
+
return Math.max(1, Math.round(delta.length / CHARS_PER_TOKEN_ESTIMATE));
|
|
125563
|
+
}
|
|
125564
|
+
/**
|
|
125565
|
+
* Ease the normalized speed ratio [0,1] for color interpolation. Uses smoothstep
|
|
125566
|
+
* (zero derivative at both endpoints) so the badge stays mostly gray at low
|
|
125567
|
+
* rates — subtle rather than distracting — and only reaches the full accent
|
|
125568
|
+
* color at high rates. Smoother than sqrt, whose infinite derivative at t=0
|
|
125569
|
+
* makes the color jump toward accent as soon as any tokens flow.
|
|
125570
|
+
*/
|
|
125571
|
+
function easeSpeedRatio(ratio) {
|
|
125572
|
+
const t = ratio < 0 ? 0 : ratio > 1 ? 1 : ratio;
|
|
125573
|
+
return t * t * (3 - 2 * t);
|
|
125574
|
+
}
|
|
125575
|
+
//#endregion
|
|
124790
125576
|
//#region src/tui/components/messages/thinking.ts
|
|
124791
125577
|
var ThinkingComponent = class {
|
|
124792
125578
|
text;
|
|
124793
125579
|
color;
|
|
125580
|
+
dimColor;
|
|
125581
|
+
accentColor;
|
|
124794
125582
|
showMarker;
|
|
124795
125583
|
mode;
|
|
124796
125584
|
expanded = false;
|
|
@@ -124803,6 +125591,8 @@ var ThinkingComponent = class {
|
|
|
124803
125591
|
constructor(text, colors, showMarker = true, mode = "finalized", ui) {
|
|
124804
125592
|
this.text = text;
|
|
124805
125593
|
this.color = colors.roleThinking;
|
|
125594
|
+
this.dimColor = colors.textDim;
|
|
125595
|
+
this.accentColor = colors.primary;
|
|
124806
125596
|
this.showMarker = showMarker;
|
|
124807
125597
|
this.mode = mode;
|
|
124808
125598
|
this.ui = ui;
|
|
@@ -124845,9 +125635,12 @@ var ThinkingComponent = class {
|
|
|
124845
125635
|
const contentLines = this.text.length > 0 ? this.textComponent.render(contentWidth) : [""];
|
|
124846
125636
|
if (this.mode === "live") {
|
|
124847
125637
|
const visibleLines = contentLines.length > 2 ? contentLines.slice(contentLines.length - 2) : contentLines;
|
|
125638
|
+
const spinner = chalk.hex(this.color)(`${BRAILLE_SPINNER_FRAMES[this.spinnerFrame] ?? BRAILLE_SPINNER_FRAMES[0]} `);
|
|
125639
|
+
const rate = Math.min(200, getSharedSpeedTracker().getSpeed());
|
|
125640
|
+
const rateSuffix = rate > .05 ? chalk.hex(lerpHex(this.dimColor, this.accentColor, easeSpeedRatio(rate / 200)))(` · ${rate.toFixed(1)} toks/s`) : "";
|
|
124848
125641
|
return [
|
|
124849
125642
|
"",
|
|
124850
|
-
|
|
125643
|
+
spinner + chalk.hex(this.color)("思考中...") + rateSuffix,
|
|
124851
125644
|
...visibleLines.map((line) => " " + line)
|
|
124852
125645
|
];
|
|
124853
125646
|
}
|
|
@@ -124949,6 +125742,66 @@ function makeDiffStyles(colors) {
|
|
|
124949
125742
|
meta: (s) => chalk.hex(colors.diffMeta)(s)
|
|
124950
125743
|
};
|
|
124951
125744
|
}
|
|
125745
|
+
/**
|
|
125746
|
+
* Compute word-level diff between two single lines and highlight the changed
|
|
125747
|
+
* words with `chalk.inverse()`. Only the first removed/added part has its
|
|
125748
|
+
* leading whitespace stripped, so indentation isn't highlighted.
|
|
125749
|
+
*
|
|
125750
|
+
* Ported from oh-my-pi `packages/coding-agent/src/modes/components/diff.ts:55-95`.
|
|
125751
|
+
* Returns the two lines with inline inverse highlighting applied; caller wraps
|
|
125752
|
+
* them in the usual add/del gutter + line color.
|
|
125753
|
+
*/
|
|
125754
|
+
function renderIntraLineDiff(oldContent, newContent) {
|
|
125755
|
+
const wordDiff = diffWords(oldContent, newContent);
|
|
125756
|
+
let removedLine = "";
|
|
125757
|
+
let addedLine = "";
|
|
125758
|
+
let isFirstRemoved = true;
|
|
125759
|
+
let isFirstAdded = true;
|
|
125760
|
+
for (const part of wordDiff) if (part.removed) {
|
|
125761
|
+
let value = part.value;
|
|
125762
|
+
if (isFirstRemoved) {
|
|
125763
|
+
const leadingWs = value.match(/^(\s*)/)?.[1] ?? "";
|
|
125764
|
+
value = value.slice(leadingWs.length);
|
|
125765
|
+
removedLine += leadingWs;
|
|
125766
|
+
isFirstRemoved = false;
|
|
125767
|
+
}
|
|
125768
|
+
if (value) removedLine += chalk.inverse(value);
|
|
125769
|
+
} else if (part.added) {
|
|
125770
|
+
let value = part.value;
|
|
125771
|
+
if (isFirstAdded) {
|
|
125772
|
+
const leadingWs = value.match(/^(\s*)/)?.[1] ?? "";
|
|
125773
|
+
value = value.slice(leadingWs.length);
|
|
125774
|
+
addedLine += leadingWs;
|
|
125775
|
+
isFirstAdded = false;
|
|
125776
|
+
}
|
|
125777
|
+
if (value) addedLine += chalk.inverse(value);
|
|
125778
|
+
} else {
|
|
125779
|
+
removedLine += part.value;
|
|
125780
|
+
addedLine += part.value;
|
|
125781
|
+
}
|
|
125782
|
+
return {
|
|
125783
|
+
removedLine,
|
|
125784
|
+
addedLine
|
|
125785
|
+
};
|
|
125786
|
+
}
|
|
125787
|
+
/**
|
|
125788
|
+
* Check whether a delete line at index `i` and an add line at index `i+1`
|
|
125789
|
+
* form an isolated single-line replacement — the only case where intra-line
|
|
125790
|
+
* word diff is meaningful. We require both sides to be single-line blocks
|
|
125791
|
+
* (prev of delete is not delete; next of add is not add) so multi-line
|
|
125792
|
+
* delete/add blocks stay as whole-line coloring.
|
|
125793
|
+
*/
|
|
125794
|
+
function isIsolatedSingleLinePair(lines, i) {
|
|
125795
|
+
const curr = lines[i];
|
|
125796
|
+
const next = lines[i + 1];
|
|
125797
|
+
if (curr === void 0 || next === void 0) return false;
|
|
125798
|
+
if (curr.kind !== "delete" || next.kind !== "add") return false;
|
|
125799
|
+
const prev = lines[i - 1];
|
|
125800
|
+
if (prev !== void 0 && prev.kind === "delete") return false;
|
|
125801
|
+
const after = lines[i + 2];
|
|
125802
|
+
if (after !== void 0 && after.kind === "add") return false;
|
|
125803
|
+
return true;
|
|
125804
|
+
}
|
|
124952
125805
|
function computeDiffLines(oldText, newText, oldStart = 1, newStart = 1, isIncomplete = false) {
|
|
124953
125806
|
const oldLines = oldText ? oldText.split("\n") : [];
|
|
124954
125807
|
const newLines = newText ? newText.split("\n") : [];
|
|
@@ -125005,10 +125858,22 @@ function renderDiffLines(oldText, newText, path, colors, isIncomplete = false, o
|
|
|
125005
125858
|
header += path;
|
|
125006
125859
|
output.push(header);
|
|
125007
125860
|
const shown = maxLines !== void 0 && maxLines >= 0 && changedLines.length > maxLines ? changedLines.slice(0, maxLines) : changedLines;
|
|
125008
|
-
|
|
125861
|
+
let i = 0;
|
|
125862
|
+
while (i < shown.length) {
|
|
125863
|
+
if (isIsolatedSingleLinePair(shown, i)) {
|
|
125864
|
+
const line = shown[i];
|
|
125865
|
+
const next = shown[i + 1];
|
|
125866
|
+
const { removedLine, addedLine } = renderIntraLineDiff(line.code, next.code);
|
|
125867
|
+
output.push(s.gutter(String(line.lineNum).padStart(4) + " ") + s.del("- " + removedLine));
|
|
125868
|
+
output.push(s.gutter(String(next.lineNum).padStart(4) + " ") + s.add("+ " + addedLine));
|
|
125869
|
+
i += 2;
|
|
125870
|
+
continue;
|
|
125871
|
+
}
|
|
125872
|
+
const line = shown[i];
|
|
125009
125873
|
const marker = line.kind === "add" ? "+" : "-";
|
|
125010
125874
|
const color = line.kind === "add" ? s.add : s.del;
|
|
125011
125875
|
output.push(s.gutter(String(line.lineNum).padStart(4) + " ") + color(marker + " " + line.code));
|
|
125876
|
+
i += 1;
|
|
125012
125877
|
}
|
|
125013
125878
|
const hidden = changedLines.length - shown.length;
|
|
125014
125879
|
if (hidden > 0) output.push(s.meta(` … ${String(hidden)} more change${hidden > 1 ? "s" : ""} hidden (ctrl+o to expand)`));
|
|
@@ -125107,16 +125972,35 @@ function renderDiffLinesClustered(oldText, newText, path, colors, opts = {}) {
|
|
|
125107
125972
|
body++;
|
|
125108
125973
|
}
|
|
125109
125974
|
}
|
|
125110
|
-
|
|
125975
|
+
let i = cluster.start;
|
|
125976
|
+
while (i <= cluster.end) {
|
|
125111
125977
|
if (body >= cap) {
|
|
125112
125978
|
truncated = true;
|
|
125113
125979
|
break outer;
|
|
125114
125980
|
}
|
|
125115
125981
|
const line = diffLines[i];
|
|
125982
|
+
if (i + 1 <= cluster.end && isIsolatedSingleLinePair(diffLines, i)) {
|
|
125983
|
+
const next = diffLines[i + 1];
|
|
125984
|
+
const { removedLine, addedLine } = renderIntraLineDiff(line.code, next.code);
|
|
125985
|
+
output.push(s.gutter(String(line.lineNum).padStart(4) + " ") + s.del("- " + removedLine));
|
|
125986
|
+
body++;
|
|
125987
|
+
if (body >= cap) {
|
|
125988
|
+
truncated = true;
|
|
125989
|
+
prevEnd = i;
|
|
125990
|
+
break outer;
|
|
125991
|
+
}
|
|
125992
|
+
output.push(s.gutter(String(next.lineNum).padStart(4) + " ") + s.add("+ " + addedLine));
|
|
125993
|
+
body++;
|
|
125994
|
+
shownChanges += 2;
|
|
125995
|
+
prevEnd = i + 1;
|
|
125996
|
+
i += 2;
|
|
125997
|
+
continue;
|
|
125998
|
+
}
|
|
125116
125999
|
output.push(formatDiffRow(line, s));
|
|
125117
126000
|
body++;
|
|
125118
126001
|
if (line.kind !== "context") shownChanges++;
|
|
125119
126002
|
prevEnd = i;
|
|
126003
|
+
i += 1;
|
|
125120
126004
|
}
|
|
125121
126005
|
}
|
|
125122
126006
|
if (truncated) {
|
|
@@ -125351,7 +126235,66 @@ const shellExecutionResultRenderer = (toolCall, result, ctx) => [new ShellExecut
|
|
|
125351
126235
|
commandPreviewLines: void 0
|
|
125352
126236
|
})];
|
|
125353
126237
|
//#endregion
|
|
126238
|
+
//#region src/tui/components/media/image-thumbnail.ts
|
|
126239
|
+
/**
|
|
126240
|
+
* Transcript-side rendering of a pasted image.
|
|
126241
|
+
*
|
|
126242
|
+
* On terminals that speak the Kitty graphics protocol or iTerm2 inline
|
|
126243
|
+
* image protocol (detected by pi-tui's `getCapabilities()`), we show
|
|
126244
|
+
* the actual image. Everywhere else we fall back to a one-line text
|
|
126245
|
+
* marker matching the placeholder the user sees in the input box —
|
|
126246
|
+
* this keeps the transcript readable on Terminal.app / Linux default
|
|
126247
|
+
* terminals / `script` recordings without extra chrome.
|
|
126248
|
+
*
|
|
126249
|
+
* Height is capped at ~12 rows so a single screenshot can't monopolize
|
|
126250
|
+
* the viewport; pi-tui handles proportional scaling internally.
|
|
126251
|
+
*/
|
|
126252
|
+
const MAX_IMAGE_ROWS = 12;
|
|
126253
|
+
const MAX_IMAGE_WIDTH = 40;
|
|
126254
|
+
function createImageComponent(opts, colors) {
|
|
126255
|
+
const caps = getCapabilities();
|
|
126256
|
+
if (!(caps.images === "kitty" || caps.images === "iterm2")) {
|
|
126257
|
+
const label = opts.filename ?? `[${opts.mime}]`;
|
|
126258
|
+
return new Text(chalk.hex(colors.accent)(label), 0, 0);
|
|
126259
|
+
}
|
|
126260
|
+
return new Image(opts.base64, opts.mime, { fallbackColor: (s) => chalk.hex(colors.textDim)(s) }, {
|
|
126261
|
+
maxHeightCells: MAX_IMAGE_ROWS,
|
|
126262
|
+
maxWidthCells: MAX_IMAGE_WIDTH,
|
|
126263
|
+
filename: opts.filename
|
|
126264
|
+
}, {
|
|
126265
|
+
widthPx: opts.width,
|
|
126266
|
+
heightPx: opts.height
|
|
126267
|
+
});
|
|
126268
|
+
}
|
|
126269
|
+
var ImageThumbnail = class extends Container {
|
|
126270
|
+
constructor(attachment, colors) {
|
|
126271
|
+
super();
|
|
126272
|
+
const base64 = Buffer.from(attachment.bytes).toString("base64");
|
|
126273
|
+
this.addChild(createImageComponent({
|
|
126274
|
+
base64,
|
|
126275
|
+
mime: attachment.mime,
|
|
126276
|
+
width: attachment.width,
|
|
126277
|
+
height: attachment.height,
|
|
126278
|
+
filename: attachment.placeholder
|
|
126279
|
+
}, colors));
|
|
126280
|
+
}
|
|
126281
|
+
};
|
|
126282
|
+
//#endregion
|
|
125354
126283
|
//#region src/tui/components/messages/tool-renderers/media.ts
|
|
126284
|
+
/**
|
|
126285
|
+
* ReadMediaFile renderer.
|
|
126286
|
+
*
|
|
126287
|
+
* The ReadMediaFile tool `output` is the JSON-serialized array of
|
|
126288
|
+
* content parts the tool returned — which includes the full base64 of
|
|
126289
|
+
* the image/video. Dumping that string into the transcript blasts a
|
|
126290
|
+
* multi-screen blob of base64. This renderer parses the envelope and
|
|
126291
|
+
* surfaces just the human-readable bits (kind, path, mime, size) via
|
|
126292
|
+
* a header chip + a tiny expanded body. It never emits the base64.
|
|
126293
|
+
*
|
|
126294
|
+
* On error, or when the output isn't the expected media envelope, we
|
|
126295
|
+
* fall back to the truncated renderer so the user still sees the raw
|
|
126296
|
+
* message.
|
|
126297
|
+
*/
|
|
125355
126298
|
const PATH_TAG_RE = /^<(image|video)\s+path="([^"]+)">$/;
|
|
125356
126299
|
const ORIGINAL_SIZE_RE = /original size\s+(\d+x\d+px)/;
|
|
125357
126300
|
const DATA_URL_RE = /^data:([^;]+);base64,(.*)$/s;
|
|
@@ -125373,6 +126316,7 @@ function parseReadMediaOutput(output) {
|
|
|
125373
126316
|
let path;
|
|
125374
126317
|
let mimeType;
|
|
125375
126318
|
let bytes;
|
|
126319
|
+
let base64;
|
|
125376
126320
|
let url;
|
|
125377
126321
|
let originalSize;
|
|
125378
126322
|
let foundMedia = false;
|
|
@@ -125403,6 +126347,7 @@ function parseReadMediaOutput(output) {
|
|
|
125403
126347
|
if (data && data[1] !== void 0 && data[2] !== void 0) {
|
|
125404
126348
|
mimeType = data[1];
|
|
125405
126349
|
bytes = bytesFromBase64(data[2]);
|
|
126350
|
+
base64 = data[2];
|
|
125406
126351
|
} else url = u;
|
|
125407
126352
|
}
|
|
125408
126353
|
}
|
|
@@ -125413,6 +126358,7 @@ function parseReadMediaOutput(output) {
|
|
|
125413
126358
|
if (path !== void 0) summary.path = path;
|
|
125414
126359
|
if (mimeType !== void 0) summary.mimeType = mimeType;
|
|
125415
126360
|
if (bytes !== void 0) summary.bytes = bytes;
|
|
126361
|
+
if (base64 !== void 0) summary.base64 = base64;
|
|
125416
126362
|
if (url !== void 0) summary.url = url;
|
|
125417
126363
|
if (originalSize !== void 0) summary.originalSize = originalSize;
|
|
125418
126364
|
return summary;
|
|
@@ -125441,15 +126387,33 @@ const readMediaSummary = (toolCall, result, ctx) => {
|
|
|
125441
126387
|
if (result.is_error) return renderTruncated(toolCall, result, ctx);
|
|
125442
126388
|
const summary = parseReadMediaOutput(result.output);
|
|
125443
126389
|
if (summary === null) return renderTruncated(toolCall, result, ctx);
|
|
125444
|
-
if (!ctx.expanded) return [];
|
|
125445
126390
|
const dim = chalk.dim;
|
|
125446
126391
|
const out = [];
|
|
125447
|
-
if (
|
|
125448
|
-
|
|
125449
|
-
|
|
125450
|
-
|
|
125451
|
-
|
|
125452
|
-
|
|
126392
|
+
if (ctx.expanded) {
|
|
126393
|
+
if (summary.path !== void 0) out.push(new Text(` ${dim(summary.path)}`, 0, 0));
|
|
126394
|
+
const meta = metaSegments(summary);
|
|
126395
|
+
const tail = [summary.kind];
|
|
126396
|
+
if (meta.length > 0) tail.push(meta.join(", "));
|
|
126397
|
+
if (summary.url !== void 0) tail.push(summary.url);
|
|
126398
|
+
out.push(new Text(` ${dim(tail.join(" · "))}`, 0, 0));
|
|
126399
|
+
}
|
|
126400
|
+
if (summary.kind === "image" && summary.base64 !== void 0 && summary.mimeType !== void 0) {
|
|
126401
|
+
const caps = getCapabilities();
|
|
126402
|
+
const supportsInline = caps.images === "kitty" || caps.images === "iterm2";
|
|
126403
|
+
if (ctx.expanded || supportsInline) {
|
|
126404
|
+
const dims = getImageDimensions(summary.base64, summary.mimeType) ?? {
|
|
126405
|
+
widthPx: 800,
|
|
126406
|
+
heightPx: 600
|
|
126407
|
+
};
|
|
126408
|
+
out.push(createImageComponent({
|
|
126409
|
+
base64: summary.base64,
|
|
126410
|
+
mime: summary.mimeType,
|
|
126411
|
+
width: dims.widthPx,
|
|
126412
|
+
height: dims.heightPx,
|
|
126413
|
+
filename: summary.path
|
|
126414
|
+
}, ctx.colors));
|
|
126415
|
+
}
|
|
126416
|
+
}
|
|
125453
126417
|
return out;
|
|
125454
126418
|
};
|
|
125455
126419
|
//#endregion
|
|
@@ -126868,42 +127832,6 @@ function formatActivityLine(verb, toolName, args, workspaceDir) {
|
|
|
126868
127832
|
return keyArg ? `${verb} ${toolName} (${keyArg})` : `${verb} ${toolName}`;
|
|
126869
127833
|
}
|
|
126870
127834
|
//#endregion
|
|
126871
|
-
//#region src/tui/components/media/image-thumbnail.ts
|
|
126872
|
-
/**
|
|
126873
|
-
* Transcript-side rendering of a pasted image.
|
|
126874
|
-
*
|
|
126875
|
-
* On terminals that speak the Kitty graphics protocol or iTerm2 inline
|
|
126876
|
-
* image protocol (detected by pi-tui's `getCapabilities()`), we show
|
|
126877
|
-
* the actual image. Everywhere else we fall back to a one-line text
|
|
126878
|
-
* marker matching the placeholder the user sees in the input box —
|
|
126879
|
-
* this keeps the transcript readable on Terminal.app / Linux default
|
|
126880
|
-
* terminals / `script` recordings without extra chrome.
|
|
126881
|
-
*
|
|
126882
|
-
* Height is capped at ~12 rows so a single screenshot can't monopolize
|
|
126883
|
-
* the viewport; pi-tui handles proportional scaling internally.
|
|
126884
|
-
*/
|
|
126885
|
-
const MAX_IMAGE_ROWS = 12;
|
|
126886
|
-
const MAX_IMAGE_WIDTH = 40;
|
|
126887
|
-
var ImageThumbnail = class extends Container {
|
|
126888
|
-
constructor(attachment, colors) {
|
|
126889
|
-
super();
|
|
126890
|
-
const caps = getCapabilities();
|
|
126891
|
-
if (!(caps.images === "kitty" || caps.images === "iterm2")) {
|
|
126892
|
-
this.addChild(new Text(chalk.hex(colors.accent)(attachment.placeholder), 0, 0));
|
|
126893
|
-
return;
|
|
126894
|
-
}
|
|
126895
|
-
const image = new Image(Buffer.from(attachment.bytes).toString("base64"), attachment.mime, { fallbackColor: (s) => chalk.hex(colors.textDim)(s) }, {
|
|
126896
|
-
maxHeightCells: MAX_IMAGE_ROWS,
|
|
126897
|
-
maxWidthCells: MAX_IMAGE_WIDTH,
|
|
126898
|
-
filename: attachment.placeholder
|
|
126899
|
-
}, {
|
|
126900
|
-
widthPx: attachment.width,
|
|
126901
|
-
heightPx: attachment.height
|
|
126902
|
-
});
|
|
126903
|
-
this.addChild(image);
|
|
126904
|
-
}
|
|
126905
|
-
};
|
|
126906
|
-
//#endregion
|
|
126907
127835
|
//#region src/tui/components/messages/user-message.ts
|
|
126908
127836
|
var UserMessageComponent = class {
|
|
126909
127837
|
color;
|
|
@@ -127695,7 +128623,7 @@ async function loadServers(host) {
|
|
|
127695
128623
|
}
|
|
127696
128624
|
/** Replace newlines so error messages don't break single-line terminal rendering. */
|
|
127697
128625
|
function sanitizeDesc(s) {
|
|
127698
|
-
return s.
|
|
128626
|
+
return s.replaceAll(/\r?\n/g, " ").replaceAll(/\s+/g, " ").trim();
|
|
127699
128627
|
}
|
|
127700
128628
|
function buildRows(servers) {
|
|
127701
128629
|
const rows = [];
|
|
@@ -127933,7 +128861,8 @@ var McpPickerComponent = class extends Container {
|
|
|
127933
128861
|
this.selectedIndex = this.nextSelectable(this.selectedIndex);
|
|
127934
128862
|
return;
|
|
127935
128863
|
}
|
|
127936
|
-
|
|
128864
|
+
const key = printableChar(data);
|
|
128865
|
+
if (key === "d" || key === "D") {
|
|
127937
128866
|
const row = this.rows[this.selectedIndex];
|
|
127938
128867
|
if (row && row.kind === "installed" && row.status && row.status !== "__section" && row.status !== "__empty") this.onDelete(row);
|
|
127939
128868
|
return;
|
|
@@ -128098,7 +129027,7 @@ function readConfiguredType() {
|
|
|
128098
129027
|
}
|
|
128099
129028
|
}
|
|
128100
129029
|
function escapeSingleQuotes(str) {
|
|
128101
|
-
return str.
|
|
129030
|
+
return str.replaceAll("'", "\\'");
|
|
128102
129031
|
}
|
|
128103
129032
|
function generateConfig$1(platform) {
|
|
128104
129033
|
const dir = dirname$1(CONFIG_PATH);
|
|
@@ -130137,11 +131066,18 @@ var EditorKeyboardController = class {
|
|
|
130137
131066
|
}
|
|
130138
131067
|
};
|
|
130139
131068
|
//#endregion
|
|
131069
|
+
//#region src/tui/utils/sanitize.ts
|
|
131070
|
+
/** Replace tabs with spaces, keeping column alignment. Default tab width 4. */
|
|
131071
|
+
function replaceTabs(text, tabWidth = 4) {
|
|
131072
|
+
return text.replaceAll(" ", " ".repeat(tabWidth));
|
|
131073
|
+
}
|
|
131074
|
+
//#endregion
|
|
130140
131075
|
//#region src/tui/components/messages/status-message.ts
|
|
130141
131076
|
var StatusMessageComponent = class extends Container {
|
|
130142
131077
|
constructor(content, colors, color) {
|
|
130143
131078
|
super();
|
|
130144
|
-
const
|
|
131079
|
+
const sanitized = replaceTabs(content);
|
|
131080
|
+
const text = color === void 0 ? chalk.hex(colors.textDim)(sanitized) : chalk.hex(color)(sanitized);
|
|
130145
131081
|
this.addChild(new Text(` ${text}`, 0, 0));
|
|
130146
131082
|
}
|
|
130147
131083
|
};
|
|
@@ -130149,8 +131085,8 @@ var NoticeMessageComponent = class extends Container {
|
|
|
130149
131085
|
constructor(title, detail, colors) {
|
|
130150
131086
|
super();
|
|
130151
131087
|
this.addChild(new Spacer(1));
|
|
130152
|
-
this.addChild(new Text(` ${chalk.hex(colors.textStrong)(title)}`, 0, 0));
|
|
130153
|
-
if (detail !== void 0 && detail.length > 0) this.addChild(new Text(` ${chalk.hex(colors.textDim)(detail)}`, 0, 0));
|
|
131088
|
+
this.addChild(new Text(` ${chalk.hex(colors.textStrong)(replaceTabs(title))}`, 0, 0));
|
|
131089
|
+
if (detail !== void 0 && detail.length > 0) this.addChild(new Text(` ${chalk.hex(colors.textDim)(replaceTabs(detail))}`, 0, 0));
|
|
130154
131090
|
}
|
|
130155
131091
|
};
|
|
130156
131092
|
//#endregion
|
|
@@ -131964,6 +132900,7 @@ var StreamingUIController = class {
|
|
|
131964
132900
|
_currentStep = 0;
|
|
131965
132901
|
_assistantDraft = "";
|
|
131966
132902
|
_thinkingDraft = "";
|
|
132903
|
+
lastDeltaAt;
|
|
131967
132904
|
_thinkingEntry = void 0;
|
|
131968
132905
|
_compactionEntry = void 0;
|
|
131969
132906
|
_streamingBlock = null;
|
|
@@ -131996,11 +132933,30 @@ var StreamingUIController = class {
|
|
|
131996
132933
|
appendThinkingDelta(delta) {
|
|
131997
132934
|
this._thinkingDraft += delta;
|
|
131998
132935
|
this.pendingThinkingFlush = true;
|
|
132936
|
+
this.recordSpeedObservation(delta);
|
|
131999
132937
|
}
|
|
132000
132938
|
appendAssistantDelta(delta) {
|
|
132001
132939
|
if (this._streamingBlock === null) this.onStreamingTextStart();
|
|
132002
132940
|
this._assistantDraft += delta;
|
|
132003
132941
|
this.pendingAssistantFlush = true;
|
|
132942
|
+
this.recordSpeedObservation(delta);
|
|
132943
|
+
}
|
|
132944
|
+
/**
|
|
132945
|
+
* Feed an estimated tok/s observation into the shared speed gauge. Uses the
|
|
132946
|
+
* inter-delta elapsed time so a burst of small deltas produces an accurate
|
|
132947
|
+
* instantaneous rate. The gauge's 3s rolling window smooths jitter.
|
|
132948
|
+
*/
|
|
132949
|
+
recordSpeedObservation(delta) {
|
|
132950
|
+
const now = performance.now();
|
|
132951
|
+
const tokens = estimateTokens(delta);
|
|
132952
|
+
if (this.lastDeltaAt !== void 0) {
|
|
132953
|
+
const elapsedMs = now - this.lastDeltaAt;
|
|
132954
|
+
if (elapsedMs > 0) {
|
|
132955
|
+
const rate = tokens / elapsedMs * 1e3;
|
|
132956
|
+
getSharedSpeedTracker().observe(rate, now);
|
|
132957
|
+
}
|
|
132958
|
+
}
|
|
132959
|
+
this.lastDeltaAt = now;
|
|
132004
132960
|
}
|
|
132005
132961
|
hasThinkingDraft() {
|
|
132006
132962
|
return this._thinkingDraft.length > 0;
|
|
@@ -132267,6 +133223,8 @@ var StreamingUIController = class {
|
|
|
132267
133223
|
this._assistantDraft = "";
|
|
132268
133224
|
this._streamingBlock = null;
|
|
132269
133225
|
this._thinkingDraft = "";
|
|
133226
|
+
this.lastDeltaAt = void 0;
|
|
133227
|
+
getSharedSpeedTracker().reset();
|
|
132270
133228
|
this.disposeActiveThinkingComponent();
|
|
132271
133229
|
}
|
|
132272
133230
|
resetToolUi() {
|
|
@@ -133984,7 +134942,7 @@ var TranscriptController = class TranscriptController {
|
|
|
133984
134942
|
this.host.state.ui.requestRender();
|
|
133985
134943
|
}
|
|
133986
134944
|
showError(message) {
|
|
133987
|
-
this.showStatus(`错误:${message}`, this.host.state.theme.colors.error);
|
|
134945
|
+
this.showStatus(`错误:${truncateErrorMessage(replaceTabs(message))}`, this.host.state.theme.colors.error);
|
|
133988
134946
|
}
|
|
133989
134947
|
showProgressSpinner(label) {
|
|
133990
134948
|
const tint = (s) => chalk.hex(this.host.state.theme.colors.primary)(s);
|
|
@@ -134106,7 +135064,7 @@ function pgrep(pattern) {
|
|
|
134106
135064
|
});
|
|
134107
135065
|
}
|
|
134108
135066
|
function escapeShell(pattern) {
|
|
134109
|
-
return `'${pattern.
|
|
135067
|
+
return `'${pattern.replaceAll("'", "'\\''")}'`;
|
|
134110
135068
|
}
|
|
134111
135069
|
async function checkWindows() {
|
|
134112
135070
|
const pm2Active = await checkPm2();
|
|
@@ -135476,7 +136434,7 @@ const BOLD_OPEN = "\x1B[1m";
|
|
|
135476
136434
|
const BOLD_CLOSE = "\x1B[22m";
|
|
135477
136435
|
function hexToAnsi(hex) {
|
|
135478
136436
|
const v = parseInt(hex.slice(1), 16);
|
|
135479
|
-
return `\
|
|
136437
|
+
return `\u001B[38;2;${v >> 16 & 255};${v >> 8 & 255};${v & 255}m`;
|
|
135480
136438
|
}
|
|
135481
136439
|
function compilePalette(palette) {
|
|
135482
136440
|
const lowOpen = hexToAnsi(palette.low);
|
|
@@ -135971,6 +136929,22 @@ function formatContextStatus(usage, tokens, maxTokens) {
|
|
|
135971
136929
|
if (maxTokens && maxTokens > 0 && tokens !== void 0) return `上下文:${pct} (${formatTokenCount(tokens)}/${formatTokenCount(maxTokens)})`;
|
|
135972
136930
|
return `上下文:${pct}`;
|
|
135973
136931
|
}
|
|
136932
|
+
const CONTEXT_WARNING_PERCENT_THRESHOLD = 70;
|
|
136933
|
+
const CONTEXT_WARNING_TOKEN_THRESHOLD = 14e4;
|
|
136934
|
+
const CONTEXT_ERROR_PERCENT_THRESHOLD = 90;
|
|
136935
|
+
const CONTEXT_ERROR_TOKEN_THRESHOLD = 18e4;
|
|
136936
|
+
function reachesThreshold(percent, maxTokens, percentThreshold, tokenThreshold) {
|
|
136937
|
+
if (!Number.isFinite(percent) || percent <= 0) return false;
|
|
136938
|
+
if (maxTokens === void 0 || !Number.isFinite(maxTokens) || maxTokens <= 0) return percent >= percentThreshold;
|
|
136939
|
+
const tokenPercentThreshold = tokenThreshold / maxTokens * 100;
|
|
136940
|
+
return percent >= Math.min(percentThreshold, tokenPercentThreshold);
|
|
136941
|
+
}
|
|
136942
|
+
function pickContextColor(usage, maxTokens, colors) {
|
|
136943
|
+
const percent = safeUsage(usage) * 100;
|
|
136944
|
+
if (reachesThreshold(percent, maxTokens, CONTEXT_ERROR_PERCENT_THRESHOLD, CONTEXT_ERROR_TOKEN_THRESHOLD)) return colors.error;
|
|
136945
|
+
if (reachesThreshold(percent, maxTokens, CONTEXT_WARNING_PERCENT_THRESHOLD, CONTEXT_WARNING_TOKEN_THRESHOLD)) return colors.warning;
|
|
136946
|
+
return colors.textDim;
|
|
136947
|
+
}
|
|
135974
136948
|
const BRAND_COLORS = [
|
|
135975
136949
|
"#72A4E9",
|
|
135976
136950
|
"#A78BFA",
|
|
@@ -136138,7 +137112,8 @@ var FooterComponent = class {
|
|
|
136138
137112
|
else {
|
|
136139
137113
|
const statusLine = buildStatusLine(state.streamingPhase, state.livePaneMode, state.streamingStartTime);
|
|
136140
137114
|
const ccDot = state.ccConnectActive ? chalk.hex(colors.success)("●") : chalk.hex(colors.textDim)("●");
|
|
136141
|
-
|
|
137115
|
+
const contextColor = pickContextColor(state.contextUsage, state.maxContextTokens, colors);
|
|
137116
|
+
rightText = `${ccDot} ${chalk.hex(contextColor)(formatContextStatus(state.contextUsage, state.contextTokens, state.maxContextTokens))}${chalk.hex(colors.textDim)(` ${statusLine}`)}`;
|
|
136142
137117
|
}
|
|
136143
137118
|
const rightWidth = visibleWidth(rightText);
|
|
136144
137119
|
const gap = 3;
|
|
@@ -137487,14 +138462,14 @@ function makeBlockStyles(colors) {
|
|
|
137487
138462
|
}
|
|
137488
138463
|
function renderDisplayBlock(block, s, colors) {
|
|
137489
138464
|
switch (block.type) {
|
|
137490
|
-
case "diff": return renderDiffLinesClustered(block.old_text, block.new_text, block.path, colors, {
|
|
138465
|
+
case "diff": return renderDiffLinesClustered(replaceTabs(block.old_text), replaceTabs(block.new_text), block.path, colors, {
|
|
137491
138466
|
contextLines: 3,
|
|
137492
138467
|
expandKeyHint: "ctrl+e to preview",
|
|
137493
138468
|
maxLines: DIFF_SUMMARY_MAX_LINES
|
|
137494
138469
|
});
|
|
137495
138470
|
case "file_content": {
|
|
137496
138471
|
const lang = block.language ?? langFromPath(block.path);
|
|
137497
|
-
const allLines = highlightLines(block.content, lang);
|
|
138472
|
+
const allLines = highlightLines(replaceTabs(block.content), lang);
|
|
137498
138473
|
const shown = allLines.slice(0, CONTENT_SUMMARY_MAX_LINES);
|
|
137499
138474
|
const lines = [s.strong(block.path)];
|
|
137500
138475
|
for (const [i, line] of shown.entries()) lines.push(s.gutter(String(i + 1).padStart(4) + " ") + line);
|
|
@@ -137506,11 +138481,12 @@ function renderDisplayBlock(block, s, colors) {
|
|
|
137506
138481
|
const lines = [];
|
|
137507
138482
|
if (block.cwd !== void 0 && block.cwd.length > 0) lines.push(s.dim(`cwd: ${block.cwd}`));
|
|
137508
138483
|
if (block.danger !== void 0) lines.push(s.errorBold(`Dangerous: ${block.danger}`));
|
|
137509
|
-
|
|
138484
|
+
const command = replaceTabs(block.command);
|
|
138485
|
+
(command.length > 0 ? command.split("\n") : [""]).forEach((cmdLine, idx) => {
|
|
137510
138486
|
const prefix = idx === 0 ? s.accent("$") : s.dim("·");
|
|
137511
138487
|
lines.push(`${prefix} ${s.strong(cmdLine)}`);
|
|
137512
138488
|
});
|
|
137513
|
-
if (block.description !== void 0 && block.description.length > 0) lines.push(` ${s.dim(block.description)}`);
|
|
138489
|
+
if (block.description !== void 0 && block.description.length > 0) lines.push(` ${s.dim(replaceTabs(block.description))}`);
|
|
137514
138490
|
return lines;
|
|
137515
138491
|
}
|
|
137516
138492
|
case "file_op": {
|
|
@@ -137529,14 +138505,14 @@ function renderDisplayBlock(block, s, colors) {
|
|
|
137529
138505
|
if (block.description !== void 0 && block.description.length > 0) lines.push(s.dim(truncateOneLine(block.description, 200)));
|
|
137530
138506
|
return lines;
|
|
137531
138507
|
}
|
|
137532
|
-
case "brief": return block.text ? block.text.split("\n").map((line) => line.length > 0 ? s.strong(line) : "") : [];
|
|
138508
|
+
case "brief": return block.text ? replaceTabs(block.text).split("\n").map((line) => line.length > 0 ? s.strong(line) : "") : [];
|
|
137533
138509
|
case "background_task": return [s.strong(`${block.status} ${block.kind} task ${block.task_id}: ${block.description}`)];
|
|
137534
138510
|
case "todo": return block.items.map((item) => s.strong(`- [${item.status}] ${item.title}`));
|
|
137535
138511
|
default: return [];
|
|
137536
138512
|
}
|
|
137537
138513
|
}
|
|
137538
138514
|
function normalizeApprovalText(text) {
|
|
137539
|
-
return text.replaceAll("\r\n", "\n").trim();
|
|
138515
|
+
return replaceTabs(text.replaceAll("\r\n", "\n").trim());
|
|
137540
138516
|
}
|
|
137541
138517
|
function isDuplicateBriefBlock(block, description) {
|
|
137542
138518
|
if (block.type !== "brief" || block.text.trim().length === 0) return false;
|
|
@@ -139962,7 +140938,7 @@ const DIM_RGB = [
|
|
|
139962
140938
|
85
|
|
139963
140939
|
];
|
|
139964
140940
|
function fg(r, g, b) {
|
|
139965
|
-
return `\
|
|
140941
|
+
return `\u001B[38;2;${r};${g};${b}m`;
|
|
139966
140942
|
}
|
|
139967
140943
|
const RESET = "\x1B[0m";
|
|
139968
140944
|
const BOLD = "\x1B[1m";
|
|
@@ -140011,7 +140987,7 @@ function getTerminalSize() {
|
|
|
140011
140987
|
}
|
|
140012
140988
|
function visualWidth(s) {
|
|
140013
140989
|
let w = 0;
|
|
140014
|
-
for (const ch of s.
|
|
140990
|
+
for (const ch of s.replaceAll(/\u001B\[[0-9;]*[a-zA-Z]/g, "")) w += /[一-鿿 -〿-]/.test(ch) ? 2 : 1;
|
|
140015
140991
|
return w;
|
|
140016
140992
|
}
|
|
140017
140993
|
function centerPad(text, width) {
|
|
@@ -140297,7 +141273,9 @@ const PLATFORMS = [
|
|
|
140297
141273
|
];
|
|
140298
141274
|
async function question(rl, prompt) {
|
|
140299
141275
|
return new Promise((resolve) => {
|
|
140300
|
-
rl.question(prompt, (answer) =>
|
|
141276
|
+
rl.question(prompt, (answer) => {
|
|
141277
|
+
resolve(answer.trim());
|
|
141278
|
+
});
|
|
140301
141279
|
});
|
|
140302
141280
|
}
|
|
140303
141281
|
function checkCcConnect() {
|
|
@@ -140774,10 +141752,10 @@ async function runStreamJson(opts) {
|
|
|
140774
141752
|
if (existing.length > 0) try {
|
|
140775
141753
|
session = await harness.resumeSession({ id: sessionKey });
|
|
140776
141754
|
log.info("stream-json: resumed session", { sessionId: session.id });
|
|
140777
|
-
} catch (
|
|
141755
|
+
} catch (error) {
|
|
140778
141756
|
log.warn("stream-json: resume failed, repairing session", {
|
|
140779
141757
|
sessionKey,
|
|
140780
|
-
error: String(
|
|
141758
|
+
error: String(error)
|
|
140781
141759
|
});
|
|
140782
141760
|
await harness.deleteSession(sessionKey).catch(() => {});
|
|
140783
141761
|
const orphanDir = existing[0]?.sessionDir;
|