scream-code 0.15.1 → 0.15.3
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-C3aR5fdI.mjs → app-Bqc3MVGh.mjs} +641 -29
- package/dist/{dispatch-Doe-DlpL.mjs → dispatch-CweYoXh3.mjs} +1 -1
- package/dist/{dispatch-ATQMQq1p.mjs → dispatch-DGeogib-.mjs} +270 -93
- package/dist/main.mjs +1 -1
- package/dist/public/assets/{index-BfVIY6UW.css → index-BJaeSGtC.css} +1 -1
- package/dist/public/assets/index-DDVdau1l.js +93 -0
- package/dist/public/assets/index-DDVdau1l.js.map +1 -0
- package/dist/public/index.html +2 -2
- package/package.json +2 -2
- package/dist/public/assets/index-CYmwuyd8.js +0 -93
- package/dist/public/assets/index-CYmwuyd8.js.map +0 -1
|
@@ -3,5 +3,5 @@ import { fileURLToPath as __cjsShimFileURLToPath } from 'node:url';
|
|
|
3
3
|
import { dirname as __cjsShimDirname } from 'node:path';
|
|
4
4
|
const __filename = __cjsShimFileURLToPath(import.meta.url);
|
|
5
5
|
const __dirname = __cjsShimDirname(__filename);
|
|
6
|
-
import { Kt as handleConnectCommand } from "./dispatch-
|
|
6
|
+
import { Kt as handleConnectCommand } from "./dispatch-DGeogib-.mjs";
|
|
7
7
|
export { handleConnectCommand };
|
|
@@ -34205,14 +34205,27 @@ function convertMediaUrl(url, fallbackMimeType) {
|
|
|
34205
34205
|
function createAbortError() {
|
|
34206
34206
|
return new DOMException("The operation was aborted.", "AbortError");
|
|
34207
34207
|
}
|
|
34208
|
-
|
|
34209
|
-
|
|
34208
|
+
/**
|
|
34209
|
+
* Race async work against an abort signal, ALWAYS detaching the listener.
|
|
34210
|
+
* The Google GenAI SDK does not accept an AbortSignal, so callers race the
|
|
34211
|
+
* SDK call against the signal manually. A bare `addEventListener({ once })`
|
|
34212
|
+
* inside a losing promise leaks the listener on the caller's signal for the
|
|
34213
|
+
* rest of its lifetime - with a session-scoped signal that accumulates one
|
|
34214
|
+
* listener per request. This wrapper guarantees cleanup in both outcomes.
|
|
34215
|
+
*/
|
|
34216
|
+
async function abortRace(signal, work) {
|
|
34217
|
+
if (signal === void 0) return work;
|
|
34210
34218
|
if (signal.aborted) throw createAbortError();
|
|
34211
|
-
|
|
34212
|
-
|
|
34213
|
-
|
|
34214
|
-
|
|
34219
|
+
let onAbort = void 0;
|
|
34220
|
+
const abortPromise = new Promise((_, reject) => {
|
|
34221
|
+
onAbort = () => reject(createAbortError());
|
|
34222
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
34215
34223
|
});
|
|
34224
|
+
try {
|
|
34225
|
+
return await Promise.race([work, abortPromise]);
|
|
34226
|
+
} finally {
|
|
34227
|
+
if (onAbort !== void 0) signal.removeEventListener("abort", onAbort);
|
|
34228
|
+
}
|
|
34216
34229
|
}
|
|
34217
34230
|
function messageToGoogleGenAI(message) {
|
|
34218
34231
|
if (message.role === "tool") throw new ChatProviderError("Tool messages must be converted via messagesToGoogleGenAIContents.");
|
|
@@ -34582,8 +34595,8 @@ var GoogleGenAIChatProvider = class {
|
|
|
34582
34595
|
contents,
|
|
34583
34596
|
config
|
|
34584
34597
|
};
|
|
34585
|
-
if (this._stream) return new GoogleGenAIStreamedMessage(await
|
|
34586
|
-
return new GoogleGenAIStreamedMessage(await
|
|
34598
|
+
if (this._stream) return new GoogleGenAIStreamedMessage(await abortRace(options?.signal, models.generateContentStream(params)), true, options?.signal);
|
|
34599
|
+
return new GoogleGenAIStreamedMessage(await abortRace(options?.signal, models.generateContent(params)), false, options?.signal);
|
|
34587
34600
|
} catch (error) {
|
|
34588
34601
|
if (error instanceof DOMException && error.name === "AbortError") throw error;
|
|
34589
34602
|
throw convertGoogleGenAIError(error);
|
|
@@ -47619,9 +47632,41 @@ function estimateTokensForMessage(message) {
|
|
|
47619
47632
|
messageTokenCache.set(message, total);
|
|
47620
47633
|
return total;
|
|
47621
47634
|
}
|
|
47635
|
+
/**
|
|
47636
|
+
* Media parts carry no width/height metadata, and providers differ on how they
|
|
47637
|
+
* tokenize media (image token counts range from a few hundred to ~1600 for a
|
|
47638
|
+
* single image depending on provider and resolution). There is no cross-
|
|
47639
|
+
* provider formula, so these estimates are deliberately conservative — for
|
|
47640
|
+
* watermark detection an OVERestimate only compacts slightly earlier
|
|
47641
|
+
* (harmless), whereas counting media as 0 systematically UNDERESTIMATED the
|
|
47642
|
+
* watermark and delayed compaction on media-heavy sessions.
|
|
47643
|
+
*
|
|
47644
|
+
* These estimates are consumed only by watermark detection and micro-compaction
|
|
47645
|
+
* recycling decisions — the real usage reported by the provider re-anchors the
|
|
47646
|
+
* context token count at each step end, so billing/usage accounting is
|
|
47647
|
+
* unaffected.
|
|
47648
|
+
*/
|
|
47649
|
+
const IMAGE_BASE_TOKENS = 1600;
|
|
47650
|
+
const IMAGE_MAX_TOKENS = 8e3;
|
|
47651
|
+
/** data-URI payloads above this many bytes are treated as high-resolution/long captures. */
|
|
47652
|
+
const IMAGE_LARGE_PAYLOAD_BYTES = 512e3;
|
|
47653
|
+
const AUDIO_TOKENS = 2e3;
|
|
47654
|
+
const VIDEO_TOKENS = 5e3;
|
|
47655
|
+
/** Long screenshots (large data URIs) carry more visual content than a single
|
|
47656
|
+
* bounded-resolution image; grow the estimate linearly and cap it. */
|
|
47657
|
+
function estimateDataUriTokens(url, baseTokens, maxTokens) {
|
|
47658
|
+
const start = url.indexOf("base64,");
|
|
47659
|
+
if (start < 0) return baseTokens;
|
|
47660
|
+
const bytes = Math.floor((url.length - start - 7) * 3 / 4);
|
|
47661
|
+
if (bytes <= IMAGE_LARGE_PAYLOAD_BYTES) return baseTokens;
|
|
47662
|
+
return Math.min(maxTokens, baseTokens + Math.floor((bytes - IMAGE_LARGE_PAYLOAD_BYTES) / 1e3));
|
|
47663
|
+
}
|
|
47622
47664
|
function estimateTokensForContentPart(part) {
|
|
47623
47665
|
if (part.type === "text") return estimateTokens$1(part.text);
|
|
47624
47666
|
else if (part.type === "think") return estimateTokens$1(part.think);
|
|
47667
|
+
else if (part.type === "image_url") return estimateDataUriTokens(part.imageUrl.url, IMAGE_BASE_TOKENS, IMAGE_MAX_TOKENS);
|
|
47668
|
+
else if (part.type === "audio_url") return AUDIO_TOKENS;
|
|
47669
|
+
else if (part.type === "video_url") return VIDEO_TOKENS;
|
|
47625
47670
|
return 0;
|
|
47626
47671
|
}
|
|
47627
47672
|
//#endregion
|
|
@@ -53403,6 +53448,17 @@ var MemoryMemoStore = class MemoryMemoStore {
|
|
|
53403
53448
|
async append(entry) {
|
|
53404
53449
|
return this.withWriteLock(() => this.appendInternal(entry));
|
|
53405
53450
|
}
|
|
53451
|
+
/**
|
|
53452
|
+
* True when a memo with the same source session + user need + approach
|
|
53453
|
+
* already exists. Used by compaction-summary recovery to re-store memos
|
|
53454
|
+
* idempotently after a crash between compaction apply and extraction.
|
|
53455
|
+
* The approach field keeps two distinct memos that happen to share the
|
|
53456
|
+
* same user need from being collapsed into one.
|
|
53457
|
+
*/
|
|
53458
|
+
existsBySourceAndNeed(sourceSessionId, userNeed, approach) {
|
|
53459
|
+
if (this.db === void 0) return false;
|
|
53460
|
+
return this.db.prepare("SELECT 1 FROM memos WHERE source_session_id = ? AND user_need = ? AND approach = ? LIMIT 1").get(sourceSessionId, userNeed, approach) !== void 0;
|
|
53461
|
+
}
|
|
53406
53462
|
/** Delete a memo by id. */
|
|
53407
53463
|
async delete(id) {
|
|
53408
53464
|
return this.withWriteLock(() => this.deleteInternal(id));
|
|
@@ -78944,7 +79000,11 @@ var FullCompaction = class {
|
|
|
78944
79000
|
this.agent.context.applyCompaction(result);
|
|
78945
79001
|
this.lastCompactionFiles = fileLists;
|
|
78946
79002
|
this.lowWaterMark = Math.floor(this.effectiveTokenCount * 1.1);
|
|
78947
|
-
|
|
79003
|
+
try {
|
|
79004
|
+
await this.extractAndStoreMemos(processedSummary, messagesToCompactForOps);
|
|
79005
|
+
} catch (error) {
|
|
79006
|
+
this.agent.log.warn("Memory memo extraction failed (compaction itself succeeded)", { error });
|
|
79007
|
+
}
|
|
78948
79008
|
this.triggerPostCompactHook(data, result);
|
|
78949
79009
|
this.detectSkillCandidates(skillCandidateSummary);
|
|
78950
79010
|
this.consecutiveCompactionFailures = 0;
|
|
@@ -79164,6 +79224,31 @@ function extractPreviousSummary(history) {
|
|
|
79164
79224
|
const text = head.content.filter((p) => p.type === "text").map((p) => p.text).join("");
|
|
79165
79225
|
return text.length > 0 ? text : null;
|
|
79166
79226
|
}
|
|
79227
|
+
/**
|
|
79228
|
+
* Crash-recovery path for compaction memos. `context.apply_compaction` is
|
|
79229
|
+
* written to wire before the extraction step runs; if the process dies in
|
|
79230
|
+
* that window the memos are lost forever (nothing re-extracts them). On wire
|
|
79231
|
+
* replay the summary is available again, so re-parse it and store anything
|
|
79232
|
+
* missing. Idempotent: a healthy replay finds the memos already stored
|
|
79233
|
+
* (existsBySourceAndNeed) and skips them, so recovery never duplicates.
|
|
79234
|
+
*/
|
|
79235
|
+
async function recoverMemosFromCompactionSummary(agent, summary) {
|
|
79236
|
+
const memoStore = agent.memoStore;
|
|
79237
|
+
if (!memoStore || summary === void 0 || summary.trim().length === 0) return;
|
|
79238
|
+
const memos = parseMemoryMemos(summary);
|
|
79239
|
+
if (memos.length === 0) return;
|
|
79240
|
+
const sessionId = agent.homedir ? basename$1(dirname$2(dirname$2(agent.homedir))) : "unknown";
|
|
79241
|
+
const sessionTitle = await agent.getSessionTitle().catch(() => void 0);
|
|
79242
|
+
const projectDir = agent.config.cwd;
|
|
79243
|
+
const failed = (await Promise.allSettled(memos.map(async (memo) => {
|
|
79244
|
+
if (memoStore.existsBySourceAndNeed(sessionId, memo.userNeed, memo.approach)) return;
|
|
79245
|
+
memo.sourceSessionId = sessionId;
|
|
79246
|
+
memo.sourceSessionTitle = sessionTitle ?? "";
|
|
79247
|
+
memo.projectDir = projectDir;
|
|
79248
|
+
await memoStore.append(memo);
|
|
79249
|
+
}))).filter((result) => result.status === "rejected").length;
|
|
79250
|
+
if (failed > 0) agent.log.warn("Some recovered memory memos failed to store", { failed });
|
|
79251
|
+
}
|
|
79167
79252
|
//#endregion
|
|
79168
79253
|
//#region ../../packages/agent-core/src/flags/registry.ts
|
|
79169
79254
|
/**
|
|
@@ -79382,7 +79467,7 @@ function findSupersededPaths(messages, cutoff) {
|
|
|
79382
79467
|
/**
|
|
79383
79468
|
* Lightweight compaction that truncates old tool results without an LLM call.
|
|
79384
79469
|
*
|
|
79385
|
-
* When the context window is filling up (>=
|
|
79470
|
+
* When the context window is filling up (>= 30% by default), old tool result
|
|
79386
79471
|
* messages are replaced with a short placeholder. This frees up tokens for the
|
|
79387
79472
|
* model without the cost and latency of a full compaction.
|
|
79388
79473
|
*
|
|
@@ -84844,6 +84929,7 @@ var FileSystemAgentRecordPersistence = class {
|
|
|
84844
84929
|
pendingRecords = [];
|
|
84845
84930
|
shouldClear = false;
|
|
84846
84931
|
directorySynced = false;
|
|
84932
|
+
rewriteSeq = 0;
|
|
84847
84933
|
flushPromise;
|
|
84848
84934
|
error;
|
|
84849
84935
|
constructor(filePath, options = {}) {
|
|
@@ -84953,7 +85039,21 @@ var FileSystemAgentRecordPersistence = class {
|
|
|
84953
85039
|
const content = (this.options.blobStore !== void 0 ? await Promise.all(batch.map((record) => this.options.blobStore.offload(record))) : batch).map((e) => JSON.stringify(e) + "\n").join("");
|
|
84954
85040
|
const directory = dirname$2(this.filePath);
|
|
84955
85041
|
await mkdir(directory, { recursive: true });
|
|
84956
|
-
|
|
85042
|
+
if (shouldClear) {
|
|
85043
|
+
const tmpPath = `${this.filePath}.${process.pid}.${this.rewriteSeq++}.tmp`;
|
|
85044
|
+
const tmp = await open(tmpPath, "w");
|
|
85045
|
+
try {
|
|
85046
|
+
if (content.length > 0) await tmp.writeFile(content, "utf8");
|
|
85047
|
+
await tmp.sync();
|
|
85048
|
+
} finally {
|
|
85049
|
+
await tmp.close();
|
|
85050
|
+
}
|
|
85051
|
+
await rename(tmpPath, this.filePath);
|
|
85052
|
+
await syncDir(directory);
|
|
85053
|
+
this.directorySynced = true;
|
|
85054
|
+
return;
|
|
85055
|
+
}
|
|
85056
|
+
const fh = await open(this.filePath, "a");
|
|
84957
85057
|
try {
|
|
84958
85058
|
if (content.length > 0) await fh.writeFile(content, "utf8");
|
|
84959
85059
|
await fh.sync();
|
|
@@ -85310,6 +85410,7 @@ function restoreAgentRecord(agent, input) {
|
|
|
85310
85410
|
return;
|
|
85311
85411
|
case "context.apply_compaction":
|
|
85312
85412
|
agent.context.applyCompaction(input);
|
|
85413
|
+
recoverMemosFromCompactionSummary(agent, input.summary);
|
|
85313
85414
|
return;
|
|
85314
85415
|
case "context.snapshot":
|
|
85315
85416
|
agent.context.restoreJSONSnapshot(input.snapshot);
|
|
@@ -85406,11 +85507,19 @@ var AgentRecords = class {
|
|
|
85406
85507
|
snapshotIndex = i;
|
|
85407
85508
|
break;
|
|
85408
85509
|
}
|
|
85510
|
+
let foldedCompactionSummary;
|
|
85409
85511
|
for (let i = 0; i < replayedRecords.length; i++) {
|
|
85410
85512
|
const record = replayedRecords[i];
|
|
85411
85513
|
if (!record) continue;
|
|
85412
|
-
if (i < snapshotIndex && isSnapshotFoldedContextRecord(record.type))
|
|
85514
|
+
if (i < snapshotIndex && isSnapshotFoldedContextRecord(record.type)) {
|
|
85515
|
+
if (record.type === "context.apply_compaction") foldedCompactionSummary = record.summary;
|
|
85516
|
+
continue;
|
|
85517
|
+
}
|
|
85413
85518
|
this.restore(record);
|
|
85519
|
+
if (record.type === "context.snapshot" && foldedCompactionSummary !== void 0) {
|
|
85520
|
+
recoverMemosFromCompactionSummary(this.agent, foldedCompactionSummary);
|
|
85521
|
+
foldedCompactionSummary = void 0;
|
|
85522
|
+
}
|
|
85414
85523
|
}
|
|
85415
85524
|
this.agent.context.dropVacuousOpenMessages();
|
|
85416
85525
|
if (shouldRewrite) {
|
|
@@ -105566,6 +105675,9 @@ var SessionSubagentHost = class {
|
|
|
105566
105675
|
backgroundTaskTimeoutMs;
|
|
105567
105676
|
modelBindings;
|
|
105568
105677
|
activeChildren = /* @__PURE__ */ new Map();
|
|
105678
|
+
/** Per-child per-model usage already folded into the parent totals, so a
|
|
105679
|
+
* resumed child's aggregation only adds the delta. */
|
|
105680
|
+
aggregatedChildUsage = /* @__PURE__ */ new WeakMap();
|
|
105569
105681
|
constructor(session, ownerAgentId, backgroundTaskTimeoutMs, modelBindings) {
|
|
105570
105682
|
this.session = session;
|
|
105571
105683
|
this.ownerAgentId = ownerAgentId;
|
|
@@ -105709,6 +105821,21 @@ var SessionSubagentHost = class {
|
|
|
105709
105821
|
result = lastAssistantText$1(child);
|
|
105710
105822
|
}
|
|
105711
105823
|
const usage = child.usage.data().total;
|
|
105824
|
+
const childByModel = child.usage.data().byModel ?? {};
|
|
105825
|
+
const previous = this.aggregatedChildUsage.get(child) ?? {};
|
|
105826
|
+
for (const [model, childUsage] of Object.entries(childByModel)) {
|
|
105827
|
+
const delta = previous[model] === void 0 ? childUsage : subtractUsage(childUsage, previous[model]);
|
|
105828
|
+
if (isZeroUsage(delta)) continue;
|
|
105829
|
+
try {
|
|
105830
|
+
parent.usage.record(model, delta, "session");
|
|
105831
|
+
} catch (error) {
|
|
105832
|
+
parent.log.warn("Failed to aggregate subagent usage", {
|
|
105833
|
+
model,
|
|
105834
|
+
error: String(error)
|
|
105835
|
+
});
|
|
105836
|
+
}
|
|
105837
|
+
}
|
|
105838
|
+
this.aggregatedChildUsage.set(child, childByModel);
|
|
105712
105839
|
let findingsBlock = "";
|
|
105713
105840
|
if (profileName === "reviewer") {
|
|
105714
105841
|
const findings = getFindingsFromStore(child.tools.toolStore);
|
|
@@ -105813,6 +105940,18 @@ var SessionSubagentHost = class {
|
|
|
105813
105940
|
});
|
|
105814
105941
|
}
|
|
105815
105942
|
};
|
|
105943
|
+
/** Element-wise subtraction clamped at zero (usage can never go negative). */
|
|
105944
|
+
function subtractUsage(current, previous) {
|
|
105945
|
+
return {
|
|
105946
|
+
inputOther: Math.max(0, current.inputOther - previous.inputOther),
|
|
105947
|
+
output: Math.max(0, current.output - previous.output),
|
|
105948
|
+
inputCacheRead: Math.max(0, current.inputCacheRead - previous.inputCacheRead),
|
|
105949
|
+
inputCacheCreation: Math.max(0, current.inputCacheCreation - previous.inputCacheCreation)
|
|
105950
|
+
};
|
|
105951
|
+
}
|
|
105952
|
+
function isZeroUsage(usage) {
|
|
105953
|
+
return usage.inputOther === 0 && usage.output === 0 && usage.inputCacheRead === 0 && usage.inputCacheCreation === 0;
|
|
105954
|
+
}
|
|
105816
105955
|
async function runChildTurnToCompletion(child, signal) {
|
|
105817
105956
|
const completion = await child.turn.waitForCurrentTurn(signal);
|
|
105818
105957
|
const turnEnded = completion.event;
|
|
@@ -123049,28 +123188,24 @@ var ScreamCore = class {
|
|
|
123049
123188
|
return this.config;
|
|
123050
123189
|
}
|
|
123051
123190
|
async setScreamConfig(input) {
|
|
123052
|
-
|
|
123053
|
-
|
|
123054
|
-
|
|
123055
|
-
return this.config = loadRuntimeConfig(this.configPath);
|
|
123056
|
-
});
|
|
123191
|
+
const config = mergeConfigPatch(readConfigFile(this.configPath), input);
|
|
123192
|
+
await writeConfigFile(this.configPath, config);
|
|
123193
|
+
return this.config = loadRuntimeConfig(this.configPath);
|
|
123057
123194
|
}
|
|
123058
123195
|
async removeScreamProvider(input) {
|
|
123059
|
-
|
|
123060
|
-
|
|
123061
|
-
|
|
123062
|
-
|
|
123063
|
-
|
|
123064
|
-
|
|
123065
|
-
|
|
123066
|
-
|
|
123067
|
-
|
|
123068
|
-
|
|
123069
|
-
|
|
123070
|
-
|
|
123071
|
-
|
|
123072
|
-
return this.config = loadRuntimeConfig(this.configPath);
|
|
123073
|
-
});
|
|
123196
|
+
const config = readConfigFile(this.configPath);
|
|
123197
|
+
delete config.providers[input.providerId];
|
|
123198
|
+
let removedDefault = false;
|
|
123199
|
+
const existingModels = config.models ?? {};
|
|
123200
|
+
for (const [key, model] of Object.entries(existingModels)) if (typeof model === "object" && model !== null && !Array.isArray(model) && model["provider"] === input.providerId) {
|
|
123201
|
+
delete existingModels[key];
|
|
123202
|
+
if (config.defaultModel === key) removedDefault = true;
|
|
123203
|
+
}
|
|
123204
|
+
config.models = existingModels;
|
|
123205
|
+
if (removedDefault) config.defaultModel = void 0;
|
|
123206
|
+
if (config.defaultProvider === input.providerId) config.defaultProvider = void 0;
|
|
123207
|
+
await writeConfigFile(this.configPath, config);
|
|
123208
|
+
return this.config = loadRuntimeConfig(this.configPath);
|
|
123074
123209
|
}
|
|
123075
123210
|
setRuntimeSystemPrompt({ sessionId, ...payload }) {
|
|
123076
123211
|
return this.sessionApi(sessionId).setRuntimeSystemPrompt(payload);
|
|
@@ -123612,23 +123747,6 @@ var ScreamCore = class {
|
|
|
123612
123747
|
reloadProviderManager() {
|
|
123613
123748
|
return this.config = loadRuntimeConfig(this.configPath);
|
|
123614
123749
|
}
|
|
123615
|
-
/**
|
|
123616
|
-
* Serialize config-file mutations. Both setScreamConfig and
|
|
123617
|
-
* removeScreamProvider are read-modify-write cycles that read the file
|
|
123618
|
-
* synchronously and then await the write; two calls issued together (e.g.
|
|
123619
|
-
* cycling thinking levels plus confirming a model in the picker fire
|
|
123620
|
-
* back-to-back setConfig patches) would otherwise read the SAME stale
|
|
123621
|
-
* snapshot, and the later write silently reverts the earlier patch — a
|
|
123622
|
-
* lost update that makes e.g. a just-selected default model flip back to
|
|
123623
|
-
* the old one. The queue guarantees each cycle re-reads after the
|
|
123624
|
-
* previous write completed.
|
|
123625
|
-
*/
|
|
123626
|
-
configMutationQueue = Promise.resolve();
|
|
123627
|
-
enqueueConfigMutation(mutate) {
|
|
123628
|
-
const run = this.configMutationQueue.then(mutate, mutate);
|
|
123629
|
-
this.configMutationQueue = run.catch(() => void 0);
|
|
123630
|
-
return run;
|
|
123631
|
-
}
|
|
123632
123750
|
async refreshSessionRuntimeConfig(session, config) {
|
|
123633
123751
|
const api = new SessionAPIImpl(session);
|
|
123634
123752
|
const requested = (await api.getModel({ agentId: "main" })).trim();
|
|
@@ -125559,24 +125677,15 @@ const PIXEL_PULSE_FRAMES = [
|
|
|
125559
125677
|
"▒",
|
|
125560
125678
|
"▓"
|
|
125561
125679
|
];
|
|
125562
|
-
const
|
|
125563
|
-
|
|
125564
|
-
|
|
125565
|
-
|
|
125566
|
-
|
|
125567
|
-
|
|
125568
|
-
|
|
125569
|
-
|
|
125570
|
-
|
|
125571
|
-
{
|
|
125572
|
-
active: 2,
|
|
125573
|
-
forward: true
|
|
125574
|
-
},
|
|
125575
|
-
{
|
|
125576
|
-
active: 1,
|
|
125577
|
-
forward: false
|
|
125578
|
-
}
|
|
125579
|
-
];
|
|
125680
|
+
const forwardFrames = Array.from({ length: 8 }, (_, active) => ({
|
|
125681
|
+
active,
|
|
125682
|
+
forward: true
|
|
125683
|
+
}));
|
|
125684
|
+
const backwardFrames = Array.from({ length: 6 }, (_, i) => ({
|
|
125685
|
+
active: 6 - i,
|
|
125686
|
+
forward: false
|
|
125687
|
+
}));
|
|
125688
|
+
const PULSE_WAVE_FRAMES = [...forwardFrames, ...backwardFrames];
|
|
125580
125689
|
//#endregion
|
|
125581
125690
|
//#region src/tui/commands/knowledge-store.ts
|
|
125582
125691
|
let knowledgeStoreInstance;
|
|
@@ -129362,13 +129471,22 @@ var FooterComponent = class {
|
|
|
129362
129471
|
this.#stopStatusTimer();
|
|
129363
129472
|
if (phase === "idle" && !goalActive) return;
|
|
129364
129473
|
const intervalMs = 1e3 / 30;
|
|
129365
|
-
this
|
|
129474
|
+
this.#scheduleStatusTick(intervalMs, intervalMs);
|
|
129475
|
+
}
|
|
129476
|
+
#scheduleStatusTick(intervalMs, delayMs) {
|
|
129477
|
+
const timer = setTimeout(() => {
|
|
129478
|
+
if (this.statusTimer !== timer) return;
|
|
129479
|
+
const startedAt = performance.now();
|
|
129366
129480
|
this.ui.requestRender();
|
|
129367
|
-
|
|
129481
|
+
const tickCostMs = performance.now() - startedAt;
|
|
129482
|
+
if (this.statusTimer !== timer) return;
|
|
129483
|
+
this.#scheduleStatusTick(intervalMs, Math.max(0, intervalMs - tickCostMs));
|
|
129484
|
+
}, delayMs);
|
|
129485
|
+
this.statusTimer = timer;
|
|
129368
129486
|
}
|
|
129369
129487
|
#stopStatusTimer() {
|
|
129370
129488
|
if (!this.statusTimer) return;
|
|
129371
|
-
|
|
129489
|
+
clearTimeout(this.statusTimer);
|
|
129372
129490
|
this.statusTimer = null;
|
|
129373
129491
|
}
|
|
129374
129492
|
render(width) {
|
|
@@ -130817,10 +130935,11 @@ async function changeThinkingLevel(host, alias, level) {
|
|
|
130817
130935
|
}
|
|
130818
130936
|
const prevLevel = host.state.appState.thinkingLevel;
|
|
130819
130937
|
const isActiveModel = alias === host.state.appState.model;
|
|
130820
|
-
const session = host.session;
|
|
130821
130938
|
if (isActiveModel && level !== prevLevel) {
|
|
130939
|
+
const session = host.session;
|
|
130822
130940
|
try {
|
|
130823
|
-
if (session
|
|
130941
|
+
if (session === void 0) await host.authFlow.activateModelAfterLogin(alias, level);
|
|
130942
|
+
else await session.setThinking(level);
|
|
130824
130943
|
} catch (error) {
|
|
130825
130944
|
const msg = formatErrorMessage(error);
|
|
130826
130945
|
host.showError(`Failed to set thinking: ${msg}`);
|
|
@@ -130836,7 +130955,7 @@ async function changeThinkingLevel(host, alias, level) {
|
|
|
130836
130955
|
host.showError(`Failed to save thinking default: ${msg}`);
|
|
130837
130956
|
return;
|
|
130838
130957
|
}
|
|
130839
|
-
const status = isActiveModel && level !== prevLevel ?
|
|
130958
|
+
const status = isActiveModel && level !== prevLevel ? `Thinking set to ${level} for ${alias}.` : persisted ? `Saved thinking ${level} as default for ${alias}.` : `Thinking already ${level} for ${alias}.`;
|
|
130840
130959
|
host.showStatus(status, host.state.theme.colors.success);
|
|
130841
130960
|
}
|
|
130842
130961
|
async function persistThinkingDefault(host, alias, level) {
|
|
@@ -130865,16 +130984,18 @@ async function performModelSwitch(host, alias, thinkingLevel) {
|
|
|
130865
130984
|
const prevThinkingLevel = host.state.appState.thinkingLevel;
|
|
130866
130985
|
const modelChanged = alias !== prevModel;
|
|
130867
130986
|
const thinkingChanged = thinkingLevel !== prevThinkingLevel;
|
|
130868
|
-
const
|
|
130869
|
-
const overflow =
|
|
130987
|
+
const needsSessionActivation = modelChanged || thinkingChanged;
|
|
130988
|
+
const overflow = alias !== prevModel ? contextOverflowForModel(host.state.appState, alias) : null;
|
|
130870
130989
|
if (overflow !== null) {
|
|
130871
130990
|
host.showNotice("Storm Breaker(风暴守护者)", `无法切换到模型「${alias}」:当前会话上下文 ${formatTokenCount$1(overflow.currentTokens)} 已超出该模型上限 ${formatTokenCount$1(overflow.maxContextTokens)}。建议先执行 /compact 压缩上下文,或选择上下文窗口更大的模型。`);
|
|
130872
130991
|
return;
|
|
130873
130992
|
}
|
|
130993
|
+
const session = host.session;
|
|
130874
130994
|
let effectiveAlias = alias;
|
|
130875
130995
|
let effectiveThinking = thinkingLevel;
|
|
130876
130996
|
try {
|
|
130877
|
-
if (session
|
|
130997
|
+
if (session === void 0 && needsSessionActivation) await host.authFlow.activateModelAfterLogin(alias, thinkingLevel);
|
|
130998
|
+
else if (session !== void 0) {
|
|
130878
130999
|
if (modelChanged) await session.setModel(alias);
|
|
130879
131000
|
if (thinkingChanged) await session.setThinking(thinkingLevel);
|
|
130880
131001
|
const confirmed = await session.getStatus().catch(() => null);
|
|
@@ -130897,13 +131018,12 @@ async function performModelSwitch(host, alias, thinkingLevel) {
|
|
|
130897
131018
|
persisted = await persistModelSelection(host, alias, thinkingLevel);
|
|
130898
131019
|
} catch (error) {
|
|
130899
131020
|
const msg = formatErrorMessage(error);
|
|
130900
|
-
host.showError(
|
|
131021
|
+
host.showError(`Switched to ${effectiveAlias}, but failed to save default: ${msg}`);
|
|
130901
131022
|
return;
|
|
130902
131023
|
}
|
|
130903
131024
|
const hasHistory = host.state.appState.contextTokens > 0;
|
|
130904
131025
|
const cacheWarning = modelChanged && hasHistory ? " Note: switching models invalidates the existing prompt cache - use /new to avoid extra token costs." : "";
|
|
130905
131026
|
const status = (() => {
|
|
130906
|
-
if (session === void 0 && (modelChanged || thinkingChanged)) return `No active session - ${effectiveAlias} (thinking ${effectiveThinking}) saved as default. It applies when you start a new session.`;
|
|
130907
131027
|
if (modelChanged) return `Switched to ${effectiveAlias} with thinking ${effectiveThinking}.${cacheWarning}`;
|
|
130908
131028
|
if (thinkingChanged) return `Thinking set to ${effectiveThinking} for ${effectiveAlias}.`;
|
|
130909
131029
|
if (persisted) return `Saved ${effectiveAlias} with thinking ${effectiveThinking} as default.`;
|
|
@@ -133042,7 +133162,8 @@ var ThinkingComponent = class {
|
|
|
133042
133162
|
expanded = false;
|
|
133043
133163
|
ui;
|
|
133044
133164
|
spinnerFrame = 0;
|
|
133045
|
-
|
|
133165
|
+
spinnerTimer;
|
|
133166
|
+
lastSpinnerTickAt = 0;
|
|
133046
133167
|
textComponent;
|
|
133047
133168
|
cachedWidth;
|
|
133048
133169
|
cachedLines;
|
|
@@ -133126,17 +133247,41 @@ var ThinkingComponent = class {
|
|
|
133126
133247
|
}
|
|
133127
133248
|
return truncated;
|
|
133128
133249
|
}
|
|
133250
|
+
/**
|
|
133251
|
+
* setTimeout self-rescheduling chain with drift-free frame advancement and
|
|
133252
|
+
* paint-cost backpressure (same pattern as the pi-tui Loader): a slow tick
|
|
133253
|
+
* defers the next one instead of stacking intervals, keeping the live
|
|
133254
|
+
* thinking indicator ≤ ~10% CPU even under slow terminal writes.
|
|
133255
|
+
*/
|
|
133256
|
+
scheduleSpinnerTick(delayMs) {
|
|
133257
|
+
if (this.ui === void 0) return;
|
|
133258
|
+
const timer = setTimeout(() => {
|
|
133259
|
+
if (this.spinnerTimer !== timer) return;
|
|
133260
|
+
const startedAt = performance.now();
|
|
133261
|
+
const elapsed = startedAt - this.lastSpinnerTickAt;
|
|
133262
|
+
if (elapsed >= 80) {
|
|
133263
|
+
const steps = Math.floor(elapsed / 80);
|
|
133264
|
+
this.spinnerFrame = (this.spinnerFrame + steps) % BRAILLE_SPINNER_FRAMES.length;
|
|
133265
|
+
this.lastSpinnerTickAt += steps * 80;
|
|
133266
|
+
this.ui?.requestRender();
|
|
133267
|
+
}
|
|
133268
|
+
const frameCostMs = performance.now() - startedAt;
|
|
133269
|
+
if (this.spinnerTimer !== timer) return;
|
|
133270
|
+
const cadenceDelayMs = Math.max(0, 80 - frameCostMs);
|
|
133271
|
+
const backpressureDelayMs = frameCostMs * 9;
|
|
133272
|
+
this.scheduleSpinnerTick(Math.max(cadenceDelayMs, backpressureDelayMs));
|
|
133273
|
+
}, delayMs);
|
|
133274
|
+
this.spinnerTimer = timer;
|
|
133275
|
+
}
|
|
133129
133276
|
startSpinner() {
|
|
133130
|
-
if (this.ui === void 0 || this.
|
|
133131
|
-
this.
|
|
133132
|
-
|
|
133133
|
-
this.ui?.requestRender();
|
|
133134
|
-
}, 80);
|
|
133277
|
+
if (this.ui === void 0 || this.spinnerTimer !== void 0) return;
|
|
133278
|
+
this.lastSpinnerTickAt = performance.now();
|
|
133279
|
+
this.scheduleSpinnerTick(80);
|
|
133135
133280
|
}
|
|
133136
133281
|
stopSpinner() {
|
|
133137
|
-
if (this.
|
|
133138
|
-
|
|
133139
|
-
this.
|
|
133282
|
+
if (this.spinnerTimer === void 0) return;
|
|
133283
|
+
clearTimeout(this.spinnerTimer);
|
|
133284
|
+
this.spinnerTimer = void 0;
|
|
133140
133285
|
}
|
|
133141
133286
|
};
|
|
133142
133287
|
//#endregion
|
|
@@ -136028,7 +136173,11 @@ function findRevokeAnchorComponentIndex(children, count) {
|
|
|
136028
136173
|
function removeRevokeContextComponents(children, startIndex) {
|
|
136029
136174
|
for (let i = children.length - 1; i >= startIndex; i--) {
|
|
136030
136175
|
const child = children[i];
|
|
136031
|
-
if (child !== void 0 && isRevokeContextComponent(child))
|
|
136176
|
+
if (child !== void 0 && isRevokeContextComponent(child)) {
|
|
136177
|
+
const disposable = child;
|
|
136178
|
+
if (typeof disposable.dispose === "function") disposable.dispose();
|
|
136179
|
+
children.splice(i, 1);
|
|
136180
|
+
}
|
|
136032
136181
|
}
|
|
136033
136182
|
}
|
|
136034
136183
|
function isRevokeAnchorComponent(child) {
|
|
@@ -137890,6 +138039,8 @@ var MoonLoader = class extends Text {
|
|
|
137890
138039
|
ui;
|
|
137891
138040
|
colorFn;
|
|
137892
138041
|
label;
|
|
138042
|
+
/** Wall-clock time of the last frame advance, for drift-free scheduling. */
|
|
138043
|
+
lastTickAt = 0;
|
|
137893
138044
|
constructor(ui, colorFn, label = "") {
|
|
137894
138045
|
super("", 1, 0);
|
|
137895
138046
|
this.ui = ui;
|
|
@@ -137898,18 +138049,44 @@ var MoonLoader = class extends Text {
|
|
|
137898
138049
|
this.start();
|
|
137899
138050
|
}
|
|
137900
138051
|
start() {
|
|
138052
|
+
this.lastTickAt = performance.now();
|
|
137901
138053
|
this.updateDisplay();
|
|
137902
|
-
this.
|
|
137903
|
-
this.currentFrame = (this.currentFrame + 1) % BRAILLE_SPINNER_FRAMES.length;
|
|
137904
|
-
this.updateDisplay();
|
|
137905
|
-
}, 80);
|
|
138054
|
+
this.scheduleTick(80, 80);
|
|
137906
138055
|
}
|
|
137907
138056
|
stop() {
|
|
137908
138057
|
if (this.intervalId) {
|
|
137909
|
-
|
|
138058
|
+
clearTimeout(this.intervalId);
|
|
137910
138059
|
this.intervalId = null;
|
|
137911
138060
|
}
|
|
137912
138061
|
}
|
|
138062
|
+
dispose() {
|
|
138063
|
+
this.stop();
|
|
138064
|
+
}
|
|
138065
|
+
/**
|
|
138066
|
+
* setTimeout self-rescheduling chain with drift-free frame advancement and
|
|
138067
|
+
* paint-cost backpressure (same pattern as the pi-tui Loader): a slow tick
|
|
138068
|
+
* defers the next one by `max(0, interval - cost, cost × 9)` instead of
|
|
138069
|
+
* stacking intervals, keeping the spinner ≤ ~10% CPU under slow writes.
|
|
138070
|
+
*/
|
|
138071
|
+
scheduleTick(intervalMs, delayMs) {
|
|
138072
|
+
const timer = setTimeout(() => {
|
|
138073
|
+
if (this.intervalId !== timer) return;
|
|
138074
|
+
const startedAt = performance.now();
|
|
138075
|
+
const elapsed = startedAt - this.lastTickAt;
|
|
138076
|
+
if (elapsed >= intervalMs) {
|
|
138077
|
+
const steps = Math.floor(elapsed / intervalMs);
|
|
138078
|
+
this.currentFrame = (this.currentFrame + steps) % BRAILLE_SPINNER_FRAMES.length;
|
|
138079
|
+
this.lastTickAt += steps * intervalMs;
|
|
138080
|
+
this.updateDisplay();
|
|
138081
|
+
}
|
|
138082
|
+
const frameCostMs = performance.now() - startedAt;
|
|
138083
|
+
if (this.intervalId !== timer) return;
|
|
138084
|
+
const cadenceDelayMs = Math.max(0, intervalMs - frameCostMs);
|
|
138085
|
+
const backpressureDelayMs = frameCostMs * 9;
|
|
138086
|
+
this.scheduleTick(intervalMs, Math.max(cadenceDelayMs, backpressureDelayMs));
|
|
138087
|
+
}, delayMs);
|
|
138088
|
+
this.intervalId = timer;
|
|
138089
|
+
}
|
|
137913
138090
|
setLabel(label) {
|
|
137914
138091
|
this.label = label;
|
|
137915
138092
|
this.updateDisplay();
|
package/dist/main.mjs
CHANGED
|
@@ -6,7 +6,7 @@ const __dirname = __cjsShimDirname(__filename);
|
|
|
6
6
|
import "./suppress-sqlite-warning-C2VB0doZ.mjs";
|
|
7
7
|
//#region src/main.ts
|
|
8
8
|
try {
|
|
9
|
-
(await import("./app-
|
|
9
|
+
(await import("./app-Bqc3MVGh.mjs")).main();
|
|
10
10
|
} catch (error) {
|
|
11
11
|
process.stderr.write(`${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|
|
12
12
|
process.exit(1);
|