scream-code 0.18.7 → 0.18.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/README.md +6 -3
- package/dist/{app-IU7G9gs0.mjs → app-D3QT1bx1.mjs} +502 -211
- package/dist/{dispatch-7Mas_Tvb.mjs → dispatch-BcRHd4ya.mjs} +477 -56
- package/dist/{dispatch-BqBnrIMj.mjs → dispatch-JYsyD0aN.mjs} +1 -1
- package/dist/main.mjs +1 -1
- package/dist/{markit-CVQeFGFG.mjs → markit-CV3BXOV_.mjs} +20 -13
- package/dist/{src-ChTWl9Cn.mjs → src-B1S88rK6.mjs} +1 -1
- package/dist/{src-CQwXASmd.mjs → src-DIVvoAYO.mjs} +522 -134
- package/dist/{text-input-dialog-D7dm6kak.mjs → text-input-dialog-CgAOMXv1.mjs} +1 -1
- package/dist/{text-input-dialog-gDr3i7xz.mjs → text-input-dialog-CqagQQtW.mjs} +96 -14
- package/package.json +3 -4
|
@@ -4,15 +4,15 @@ import { dirname as __cjsShimDirname } from 'node:path';
|
|
|
4
4
|
const __filename = __cjsShimFileURLToPath(import.meta.url);
|
|
5
5
|
const __dirname = __cjsShimDirname(__filename);
|
|
6
6
|
import "./suppress-sqlite-warning-C2VB0doZ.mjs";
|
|
7
|
-
import { A as sharedMemoStore } from "./src-
|
|
8
|
-
import { $ as
|
|
9
|
-
import { i as t, n as getLocale, r as setLocale } from "./text-input-dialog-
|
|
7
|
+
import { A as sharedMemoStore } from "./src-DIVvoAYO.mjs";
|
|
8
|
+
import { $ as clearGoalState, $n as DEFAULT_CATALOG_URL, $t as isStreaming, A as renderDiffLines, An as sortSlashCommands, At as createMarkdownTheme, B as SkillActivationComponent, Bn as TuiConfigParseError, Bt as DISABLE_TERMINAL_THEME_REPORTING, C as appendJsonlLine, Cn as TIP_ROTATION_INTERVAL_MS, Ct as refreshProviderBalance, Dn as getNoActiveSessionMessage, E as getDaemonInstructions, En as getLlmNotSetMessage, F as ThinkingComponent, Fn as getKnowledgeStore, Ft as contrastTextHex, G as getSharedSpeedTracker, Gn as getDataDir, Gt as OSC11_RESPONSE_PREFIX, H as ActivityGroupComponent, Hn as loadTuiConfig, Ht as ENABLE_TERMINAL_THEME_REPORTING, I as NoticeMessageComponent, In as hasEmbeddingModelCache, It as getColorPalette, J as WelcomeComponent, Jn as detectInstallSource, Jt as TERMINAL_FOCUS_IN, K as BackgroundAgentStatusComponent, Kn as getInputHistoryFile, Kt as OSC11_RESPONSE_PREFIX_NO_ESC, L as StatusMessageComponent, Ln as startManualEmbeddingDownload, Lt as detectTerminalTheme, M as highlightLines, Mn as setExperimentalFlags, Mt as isTurnElapsedEnabled, N as langFromPath, Nn as embeddingProgressLabel, Nt as toggleEmptySessionHint, O as UserMessageComponent, On as buildSkillSlashCommands, Ot as resolveThemeSync, P as CachedContainer, Pn as embeddingFailureDetail, Pt as createThemeStyles, Q as CompactionComponent, Qn as PRODUCT_NAME, Qt as isBusy, R as replaceTabs, Rn as PIXEL_PULSE_FRAMES, Rt as parseOsc11BackgroundTheme, S as readUpdateCache, Sn as SESSION_TIPS, Tn as getCtrlDHint, Tt as clearInfoPanelState, U as charsForTokenBudget, Un as saveTuiConfig, Ut as OSC11_QUERY, V as AssistantMessageComponent, Vn as TuiLikePreferencesSchema, Vt as ENABLE_TERMINAL_FOCUS_REPORTING, W as estimateTokens, Wn as detectShellEnvironment, Wt as OSC11_RESPONSE, X as getBreathingFrame, Xn as CLI_UI_MODE, Xt as TERMINAL_THEME_DARK, Y as BREATHE_CYCLE_MS, Yn as CLI_COMMAND_NAME, Yt as TERMINAL_FOCUS_OUT, Z as resetBreathingClock, Zn as CLI_USER_AGENT_PRODUCT, Zt as TERMINAL_THEME_LIGHT, _n as stringValue, _r as ErrorCodes, a as buildRoleAdditionalText, ar as appendSessionIndexEntry, b as selectUpdateTarget, bn as EXIT_CONFIRM_WINDOW_MS, cn as printableChar, ct as changeThinkingLevel, d as hasDispose, dn as appendStreamingArgsPreview, dr as resolveScreamHome, en as FooterComponent, er as fetchCatalog, et as refineGoal, f as isExpandable, fn as argsRecord, fr as flushDiagnosticLogs, gn as serializeToolResultOutput, gr as isOrphanedToolCallError, gt as handlePlanCommand, h as formatMemoryMemoForInjection, hn as parseStreamingArgs, hr as isScreamError, i as openUrl, ir as normalizeWorkDir, j as renderDiffLinesClustered, jn as isExperimentalFlagEnabled, jt as isEmptySessionHintDismissed, k as ToolCallComponent, kn as BUILTIN_SLASH_COMMANDS, kt as createEditorTheme, ln as STATUS_BULLET, lr as isRealUserPrompt, lt as getModelCycleLevel, m as MoonLoader, mn as isTodoItemShape, mr as resolveGlobalLogPath, mt as handleFusionPlanCommand, n as clearEvalPanelState, nn as lerpHex, nr as ScreamHarness, o as writeTextClipboard, or as readSessionIndex, ot as isInternalMessage, p as isPlanExpandable, pn as formatErrorMessage, pr as log, q as AgentGroupComponent, qn as getLogDir, qt as QUERY_TERMINAL_THEME, rn as SubagentSlots, rr as encodeWorkDirKey, s as syncDiagramCapabilityPrompt, sr as removeSessionIndexEntry, st as toTerminalHyperlink, t as dispatchInput, tn as lerpGradient, tr as saveCatalogCache, u as disposeChildren, un as appendSessionHeaderHint, ur as parseRlmMaxDepthArg, vn as truncateErrorMessage, vr as SCREAM_ERROR_INFO, w as readJsonlFile, wn as getCtrlCHint, wt as supportsBalance, x as refreshUpdateCache, xn as MAIN_AGENT_ID, z as sanitizeShellOutput, zn as PULSE_WAVE_FRAMES, zt as DISABLE_TERMINAL_FOCUS_REPORTING } from "./dispatch-BcRHd4ya.mjs";
|
|
9
|
+
import { i as t, n as getLocale, r as setLocale } from "./text-input-dialog-CqagQQtW.mjs";
|
|
10
10
|
import { createRequire } from "node:module";
|
|
11
11
|
import { randomBytes, randomUUID, scryptSync, timingSafeEqual } from "node:crypto";
|
|
12
12
|
import { access, appendFile, chmod, mkdir, mkdtemp, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
13
13
|
import { Writable } from "node:stream";
|
|
14
14
|
import { WebSocket, WebSocketServer } from "ws";
|
|
15
|
-
import { appendFileSync, constants, existsSync, mkdirSync, readFileSync, realpathSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
15
|
+
import { appendFileSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
16
16
|
import * as path$1 from "node:path";
|
|
17
17
|
import path, { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
18
18
|
import { homedir, networkInterfaces, tmpdir } from "node:os";
|
|
@@ -33,7 +33,7 @@ function optionalBuildString(value) {
|
|
|
33
33
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
34
34
|
}
|
|
35
35
|
const SCREAM_BUILD_INFO = {
|
|
36
|
-
version: optionalBuildString("0.18.
|
|
36
|
+
version: optionalBuildString("0.18.9"),
|
|
37
37
|
channel: optionalBuildString(""),
|
|
38
38
|
commit: optionalBuildString(""),
|
|
39
39
|
buildTarget: optionalBuildString("darwin-arm64")
|
|
@@ -948,6 +948,91 @@ function wrapText$1(text, width) {
|
|
|
948
948
|
return lines.length > 0 ? lines : [""];
|
|
949
949
|
}
|
|
950
950
|
//#endregion
|
|
951
|
+
//#region src/tui/utils/runtime-marker.ts
|
|
952
|
+
const MARKER_VERSION = 1;
|
|
953
|
+
/** Directory holding one marker per live TUI process. */
|
|
954
|
+
function runtimeMarkerDir(homeDir) {
|
|
955
|
+
return join(homeDir, "runtime", "instances");
|
|
956
|
+
}
|
|
957
|
+
function markerPath(homeDir, pid) {
|
|
958
|
+
return join(runtimeMarkerDir(homeDir), `${pid}.json`);
|
|
959
|
+
}
|
|
960
|
+
/** True when `pid` exists and we may signal it (EPERM means it is alive). */
|
|
961
|
+
function isProcessAlive(pid) {
|
|
962
|
+
if (!Number.isInteger(pid) || pid <= 0) return false;
|
|
963
|
+
try {
|
|
964
|
+
process.kill(pid, 0);
|
|
965
|
+
return true;
|
|
966
|
+
} catch (error) {
|
|
967
|
+
return error?.code === "EPERM";
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
/**
|
|
971
|
+
* Register this process as running. Written atomically (tmp + rename) so a
|
|
972
|
+
* crash mid-write cannot leave a torn JSON file behind.
|
|
973
|
+
*/
|
|
974
|
+
function writeRuntimeMarker(homeDir, info) {
|
|
975
|
+
mkdirSync(runtimeMarkerDir(homeDir), { recursive: true });
|
|
976
|
+
const payload = {
|
|
977
|
+
version: MARKER_VERSION,
|
|
978
|
+
...info
|
|
979
|
+
};
|
|
980
|
+
const target = markerPath(homeDir, info.pid);
|
|
981
|
+
const staged = `${target}.tmp`;
|
|
982
|
+
writeFileSync(staged, JSON.stringify(payload), "utf-8");
|
|
983
|
+
renameSync(staged, target);
|
|
984
|
+
}
|
|
985
|
+
/** Remove this process's marker. Best effort: never blocks shutdown. */
|
|
986
|
+
function clearRuntimeMarker(homeDir, pid = process.pid) {
|
|
987
|
+
try {
|
|
988
|
+
rmSync(markerPath(homeDir, pid), { force: true });
|
|
989
|
+
} catch {}
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
992
|
+
* Collect markers left by processes that are gone, newest first, and delete
|
|
993
|
+
* them. Unreadable files are removed too: they can only be leftovers, and
|
|
994
|
+
* keeping them would make the hint repeat forever.
|
|
995
|
+
*/
|
|
996
|
+
function collectStaleMarkers(homeDir) {
|
|
997
|
+
const dir = runtimeMarkerDir(homeDir);
|
|
998
|
+
if (!existsSync(dir)) return [];
|
|
999
|
+
const stale = [];
|
|
1000
|
+
const now = Date.now();
|
|
1001
|
+
for (const fileName of readdirSync(dir)) {
|
|
1002
|
+
if (fileName.endsWith(".json.tmp")) {
|
|
1003
|
+
const draft = join(dir, fileName);
|
|
1004
|
+
try {
|
|
1005
|
+
if (now - statSync(draft).mtimeMs > 5 * 6e4) rmSync(draft, { force: true });
|
|
1006
|
+
} catch {
|
|
1007
|
+
rmSync(draft, { force: true });
|
|
1008
|
+
}
|
|
1009
|
+
continue;
|
|
1010
|
+
}
|
|
1011
|
+
if (!fileName.endsWith(".json")) continue;
|
|
1012
|
+
const filePath = join(dir, fileName);
|
|
1013
|
+
let marker;
|
|
1014
|
+
try {
|
|
1015
|
+
marker = JSON.parse(readFileSync(filePath, "utf-8"));
|
|
1016
|
+
} catch {
|
|
1017
|
+
rmSync(filePath, { force: true });
|
|
1018
|
+
continue;
|
|
1019
|
+
}
|
|
1020
|
+
if (marker === null || typeof marker !== "object" || typeof marker.pid !== "number" || marker.version !== MARKER_VERSION) {
|
|
1021
|
+
rmSync(filePath, { force: true });
|
|
1022
|
+
continue;
|
|
1023
|
+
}
|
|
1024
|
+
if (isProcessAlive(marker.pid)) continue;
|
|
1025
|
+
stale.push({
|
|
1026
|
+
pid: marker.pid,
|
|
1027
|
+
startedAt: typeof marker.startedAt === "number" ? marker.startedAt : 0,
|
|
1028
|
+
...typeof marker.sessionId === "string" ? { sessionId: marker.sessionId } : {},
|
|
1029
|
+
...typeof marker.title === "string" ? { title: marker.title } : {}
|
|
1030
|
+
});
|
|
1031
|
+
rmSync(filePath, { force: true });
|
|
1032
|
+
}
|
|
1033
|
+
return stale.toSorted((a, b) => b.startedAt - a.startedAt);
|
|
1034
|
+
}
|
|
1035
|
+
//#endregion
|
|
951
1036
|
//#region src/tui/controllers/auth-flow.ts
|
|
952
1037
|
var AuthFlowController = class {
|
|
953
1038
|
host;
|
|
@@ -2912,6 +2997,19 @@ const INITIAL_LIVE_PANE = {
|
|
|
2912
2997
|
function sliceLeading(text, max = 48) {
|
|
2913
2998
|
return text.length > max ? `${text.slice(0, max)}…` : text;
|
|
2914
2999
|
}
|
|
3000
|
+
/**
|
|
3001
|
+
* Inbound events that prove the stream is still delivering. `TUIState.lastActivityAt`
|
|
3002
|
+
* is stamped from this set so the status-bar wave can colour a stream that went
|
|
3003
|
+
* silent mid-reasoning — before the 60s transport-level stall timeout fires.
|
|
3004
|
+
*/
|
|
3005
|
+
const ACTIVITY_EVENT_TYPES = new Set([
|
|
3006
|
+
"assistant.delta",
|
|
3007
|
+
"thinking.delta",
|
|
3008
|
+
"tool.call.started",
|
|
3009
|
+
"tool.call.delta",
|
|
3010
|
+
"tool.progress",
|
|
3011
|
+
"tool.result"
|
|
3012
|
+
]);
|
|
2915
3013
|
function addTokenUsage(a, b) {
|
|
2916
3014
|
return {
|
|
2917
3015
|
inputOther: a.inputOther + b.inputOther,
|
|
@@ -3101,6 +3199,7 @@ var SessionEventHandler = class {
|
|
|
3101
3199
|
if (hidden.length > 0) host.showStatus(formatMcpStartupStatusSummary(hidden, visible.length), host.state.theme.colors.textMuted);
|
|
3102
3200
|
}
|
|
3103
3201
|
handleEvent(event, sendQueued) {
|
|
3202
|
+
if (ACTIVITY_EVENT_TYPES.has(event.type)) this.host.state.lastActivityAt = Date.now();
|
|
3104
3203
|
if (this.routeSubagentEvent(event)) return;
|
|
3105
3204
|
if ("turnId" in event && event.turnId !== void 0) this.host.streamingUI.setTurnId(String(event.turnId));
|
|
3106
3205
|
switch (event.type) {
|
|
@@ -3972,7 +4071,10 @@ var SessionEventHandler = class {
|
|
|
3972
4071
|
if (next !== void 0) setTimeout(() => {
|
|
3973
4072
|
sendQueued(next);
|
|
3974
4073
|
}, 0);
|
|
3975
|
-
} else this.host.setAppState({
|
|
4074
|
+
} else this.host.setAppState({
|
|
4075
|
+
isCompacting: false,
|
|
4076
|
+
streamingStartTime: Date.now()
|
|
4077
|
+
});
|
|
3976
4078
|
}
|
|
3977
4079
|
handleSubagentSpawned(event) {
|
|
3978
4080
|
this.subagentSlots.onSpawned(event.subagentId, event.subagentName, event.description);
|
|
@@ -8814,6 +8916,49 @@ function installTerminalThemeTracking(state, onTheme) {
|
|
|
8814
8916
|
};
|
|
8815
8917
|
}
|
|
8816
8918
|
//#endregion
|
|
8919
|
+
//#region src/tui/utils/wave-color.ts
|
|
8920
|
+
/**
|
|
8921
|
+
* Silence ramp for the status-bar pulse wave.
|
|
8922
|
+
*
|
|
8923
|
+
* The wave reports one thing: how long the agent has been working without
|
|
8924
|
+
* receiving anything. That covers a request awaiting its first token, a
|
|
8925
|
+
* reasoning or text stream that stopped delivering, and a tool that has been
|
|
8926
|
+
* running without progress. Any inbound event resets the clock, so the wave
|
|
8927
|
+
* returns to the brand colour the moment work resumes.
|
|
8928
|
+
*
|
|
8929
|
+
* Timeline (elapsed since the last inbound event, or since the request was
|
|
8930
|
+
* sent while no token has arrived yet):
|
|
8931
|
+
* 0 – 10s brand green, nothing to report
|
|
8932
|
+
* 10 – 15s green → amber
|
|
8933
|
+
* 15 – 25s amber → red (15s is pure amber, 25s pure red)
|
|
8934
|
+
* ≥ 25s red
|
|
8935
|
+
*
|
|
8936
|
+
* Red is an early warning, not an error: retries are driven by real transport
|
|
8937
|
+
* failures (the stall timeout, disconnects, rate limits), never by this colour.
|
|
8938
|
+
*
|
|
8939
|
+
* Colours come from the active palette (primary / warning / error), so light
|
|
8940
|
+
* and dark themes each ramp between their own contrast-checked values and a
|
|
8941
|
+
* theme switch takes effect on the next frame.
|
|
8942
|
+
*/
|
|
8943
|
+
/** Green is held this long before the ramp starts. */
|
|
8944
|
+
const WAVE_GREEN_HOLD_MS = 1e4;
|
|
8945
|
+
/** Pure amber is reached here. */
|
|
8946
|
+
const WAVE_AMBER_AT_MS = 15e3;
|
|
8947
|
+
/** Pure red is reached here. */
|
|
8948
|
+
const WAVE_RED_AT_MS = 25e3;
|
|
8949
|
+
/**
|
|
8950
|
+
* Colour for a request that has been waiting `elapsedMs`.
|
|
8951
|
+
*
|
|
8952
|
+
* Non-finite or negative input falls back to green: a broken clock must never
|
|
8953
|
+
* make the indicator claim a stall.
|
|
8954
|
+
*/
|
|
8955
|
+
function resolveWaveColor(elapsedMs, stops) {
|
|
8956
|
+
if (!Number.isFinite(elapsedMs) || elapsedMs < 1e4) return stops.green;
|
|
8957
|
+
if (elapsedMs < 15e3) return lerpHex(stops.green, stops.amber, (elapsedMs - WAVE_GREEN_HOLD_MS) / (WAVE_AMBER_AT_MS - WAVE_GREEN_HOLD_MS));
|
|
8958
|
+
if (elapsedMs < 25e3) return lerpHex(stops.amber, stops.red, (elapsedMs - WAVE_AMBER_AT_MS) / (WAVE_RED_AT_MS - WAVE_AMBER_AT_MS));
|
|
8959
|
+
return stops.red;
|
|
8960
|
+
}
|
|
8961
|
+
//#endregion
|
|
8817
8962
|
//#region src/tui/components/chrome/pulse-wave-loader.ts
|
|
8818
8963
|
/** Leading/trailing glyph: U+2588 (Block Elements — guaranteed single-column
|
|
8819
8964
|
* width). The remaining stage cells render muted middle dots (U+00B7). */
|
|
@@ -8840,7 +8985,9 @@ var PulseWaveLoader = class extends Text {
|
|
|
8840
8985
|
currentFrame = 0;
|
|
8841
8986
|
intervalId = null;
|
|
8842
8987
|
ui;
|
|
8843
|
-
|
|
8988
|
+
/** Resolved on every frame so a theme switch lands immediately and the
|
|
8989
|
+
* first-token ramp can advance without rebuilding the status bar. */
|
|
8990
|
+
resolveColor;
|
|
8844
8991
|
/** Wall-clock time of the last frame advance, for drift-free scheduling. */
|
|
8845
8992
|
lastTickAt = 0;
|
|
8846
8993
|
/** Raw frame text without Text's full-width render padding, exposed so
|
|
@@ -8851,10 +8998,10 @@ var PulseWaveLoader = class extends Text {
|
|
|
8851
8998
|
getFrameText() {
|
|
8852
8999
|
return this.frameText;
|
|
8853
9000
|
}
|
|
8854
|
-
constructor(ui,
|
|
9001
|
+
constructor(ui, resolveColor) {
|
|
8855
9002
|
super("", 1, 0);
|
|
8856
9003
|
this.ui = ui;
|
|
8857
|
-
this.
|
|
9004
|
+
this.resolveColor = resolveColor;
|
|
8858
9005
|
this.start();
|
|
8859
9006
|
}
|
|
8860
9007
|
start() {
|
|
@@ -8868,6 +9015,12 @@ var PulseWaveLoader = class extends Text {
|
|
|
8868
9015
|
this.intervalId = null;
|
|
8869
9016
|
}
|
|
8870
9017
|
}
|
|
9018
|
+
/** Re-resolve the colour and repaint now. The cached frame text still carries
|
|
9019
|
+
* the previous colour, and the next scheduled tick can be up to 80ms away —
|
|
9020
|
+
* a palette swapped in place should land on the frame that follows it. */
|
|
9021
|
+
repaint() {
|
|
9022
|
+
this.updateDisplay();
|
|
9023
|
+
}
|
|
8871
9024
|
dispose() {
|
|
8872
9025
|
this.stop();
|
|
8873
9026
|
}
|
|
@@ -8896,21 +9049,18 @@ var PulseWaveLoader = class extends Text {
|
|
|
8896
9049
|
}, delayMs);
|
|
8897
9050
|
this.intervalId = timer;
|
|
8898
9051
|
}
|
|
8899
|
-
setColorHex(colorHex) {
|
|
8900
|
-
this.colorHex = colorHex;
|
|
8901
|
-
this.updateDisplay();
|
|
8902
|
-
}
|
|
8903
9052
|
updateDisplay() {
|
|
8904
9053
|
const step = PULSE_WAVE_FRAMES[this.currentFrame] ?? PULSE_WAVE_FRAMES[0];
|
|
8905
|
-
const
|
|
9054
|
+
const color = this.resolveColor();
|
|
9055
|
+
const cells = Array.from({ length: 8 }, (_, idx) => this.renderCell(idx, step.active, step.forward, color));
|
|
8906
9056
|
this.frameText = cells.join("");
|
|
8907
9057
|
this.setText(this.frameText);
|
|
8908
9058
|
this.ui.requestRender();
|
|
8909
9059
|
}
|
|
8910
|
-
renderCell(index, active, forward) {
|
|
9060
|
+
renderCell(index, active, forward, color) {
|
|
8911
9061
|
const distance = forward ? active - index : index - active;
|
|
8912
|
-
if (distance === 0) return chalk.hex(
|
|
8913
|
-
if (distance === 1) return chalk.hex(
|
|
9062
|
+
if (distance === 0) return chalk.hex(color)(LEADING_BOX);
|
|
9063
|
+
if (distance === 1) return chalk.hex(color).dim(LEADING_BOX);
|
|
8914
9064
|
return chalk.dim("·");
|
|
8915
9065
|
}
|
|
8916
9066
|
};
|
|
@@ -9312,6 +9462,7 @@ var LifecycleController = class LifecycleController {
|
|
|
9312
9462
|
state.theme.resolvedTheme = nextTheme.resolvedTheme;
|
|
9313
9463
|
state.theme.styles = nextTheme.styles;
|
|
9314
9464
|
state.theme.markdownTheme = nextTheme.markdownTheme;
|
|
9465
|
+
this.statusBarLoader?.repaint();
|
|
9315
9466
|
this.host.setAppState({ theme });
|
|
9316
9467
|
this.host.updateEditorBorderHighlight();
|
|
9317
9468
|
for (const child of state.transcriptContainer.children) child.invalidate?.();
|
|
@@ -9366,7 +9517,7 @@ var LifecycleController = class LifecycleController {
|
|
|
9366
9517
|
case "tool":
|
|
9367
9518
|
case "thinking":
|
|
9368
9519
|
{
|
|
9369
|
-
const loader = new PulseWaveLoader(state.ui,
|
|
9520
|
+
const loader = new PulseWaveLoader(state.ui, () => this.currentWaveColor());
|
|
9370
9521
|
this.statusBarLoader = loader;
|
|
9371
9522
|
state.statusBarContainer.addChild(new StatusBarPaneComponent({
|
|
9372
9523
|
mode,
|
|
@@ -9459,6 +9610,32 @@ var LifecycleController = class LifecycleController {
|
|
|
9459
9610
|
this.host.state.terminal.setProgress(active);
|
|
9460
9611
|
this.host.state.terminalState.progressActive = active;
|
|
9461
9612
|
}
|
|
9613
|
+
/**
|
|
9614
|
+
* Colour for the status-bar pulse wave.
|
|
9615
|
+
*
|
|
9616
|
+
* Measures silence in whatever the agent is doing: a request awaiting its
|
|
9617
|
+
* first token, a reasoning/text stream that stopped delivering, or a tool
|
|
9618
|
+
* that has been running without emitting progress. Red is an early warning
|
|
9619
|
+
* only — it never triggers a retry, because retries are driven by real
|
|
9620
|
+
* transport errors (the 60s stall timeout, disconnects, rate limits).
|
|
9621
|
+
*
|
|
9622
|
+
* Resolved per frame, so the ramp advances and a live theme switch applies
|
|
9623
|
+
* without rebuilding the status bar.
|
|
9624
|
+
*/
|
|
9625
|
+
currentWaveColor() {
|
|
9626
|
+
const { appState, theme, lastActivityAt } = this.host.state;
|
|
9627
|
+
const colors = theme.colors;
|
|
9628
|
+
const { streamingPhase, streamingStartTime, reconnectAttempt } = appState;
|
|
9629
|
+
if (streamingPhase === "idle") return colors.primary;
|
|
9630
|
+
const anchor = Math.max(lastActivityAt, streamingStartTime);
|
|
9631
|
+
if (anchor === 0) return colors.primary;
|
|
9632
|
+
const elapsed = Date.now() - anchor;
|
|
9633
|
+
return resolveWaveColor(reconnectAttempt > 0 ? Math.max(elapsed, WAVE_AMBER_AT_MS) : elapsed, {
|
|
9634
|
+
green: colors.primary,
|
|
9635
|
+
amber: colors.warning,
|
|
9636
|
+
red: colors.error
|
|
9637
|
+
});
|
|
9638
|
+
}
|
|
9462
9639
|
stopActivitySpinner() {
|
|
9463
9640
|
if (this.host.state.activitySpinner !== null) {
|
|
9464
9641
|
this.host.state.activitySpinner.instance.stop();
|
|
@@ -10368,116 +10545,6 @@ var QuestionController = class extends ReverseRpcController {
|
|
|
10368
10545
|
}
|
|
10369
10546
|
};
|
|
10370
10547
|
//#endregion
|
|
10371
|
-
//#region src/utils/clipboard/clipboard-text.ts
|
|
10372
|
-
/**
|
|
10373
|
-
* Write plain text to the system clipboard with platform-specific commands.
|
|
10374
|
-
*
|
|
10375
|
-
* scream-core's clipboard layer is read-only (clipboard-native / clipboard-image),
|
|
10376
|
-
* so the TUI's "copy selected text" needs its own writer. Used to back the
|
|
10377
|
-
* pi-tui `copySelection` hook — return true/false so the TUI can flash
|
|
10378
|
-
* "Copied!" or "Copy failed".
|
|
10379
|
-
*
|
|
10380
|
-
* Platform mapping:
|
|
10381
|
-
* macOS -> pbcopy
|
|
10382
|
-
* Windows-> powershell Set-Clipboard (base64 payload, avoids quoting issues)
|
|
10383
|
-
* Linux -> wl-copy (Wayland) / xclip or xsel (X11), tried in a candidate
|
|
10384
|
-
* chain so a missing tool (Ubuntu ships neither by default)
|
|
10385
|
-
* falls through to the next one instead of failing outright.
|
|
10386
|
-
*/
|
|
10387
|
-
var ClipboardTextError = class extends Error {
|
|
10388
|
-
constructor(message) {
|
|
10389
|
-
super(message);
|
|
10390
|
-
this.name = "ClipboardTextError";
|
|
10391
|
-
}
|
|
10392
|
-
};
|
|
10393
|
-
const defaultRunCommand = (command, args, input) => {
|
|
10394
|
-
const result = spawnSync(command, args, {
|
|
10395
|
-
input,
|
|
10396
|
-
encoding: "utf8",
|
|
10397
|
-
timeout: 3e3
|
|
10398
|
-
});
|
|
10399
|
-
if (result.status === 0) return { ok: true };
|
|
10400
|
-
return {
|
|
10401
|
-
ok: false,
|
|
10402
|
-
error: result.error?.message ?? result.stderr?.toString() ?? `exit ${result.status}`
|
|
10403
|
-
};
|
|
10404
|
-
};
|
|
10405
|
-
function detectPlatform() {
|
|
10406
|
-
if (process.platform === "darwin") return "macos";
|
|
10407
|
-
if (process.platform === "win32") return "windows";
|
|
10408
|
-
if (process.platform === "linux") return process.env["WAYLAND_DISPLAY"] ? "linux-wayland" : "linux-x11";
|
|
10409
|
-
return "unsupported";
|
|
10410
|
-
}
|
|
10411
|
-
function linuxCandidates(platform) {
|
|
10412
|
-
const wl = {
|
|
10413
|
-
command: "wl-copy",
|
|
10414
|
-
args: []
|
|
10415
|
-
};
|
|
10416
|
-
const xclip = {
|
|
10417
|
-
command: "xclip",
|
|
10418
|
-
args: [
|
|
10419
|
-
"-selection",
|
|
10420
|
-
"clipboard",
|
|
10421
|
-
"-i"
|
|
10422
|
-
]
|
|
10423
|
-
};
|
|
10424
|
-
const xsel = {
|
|
10425
|
-
command: "xsel",
|
|
10426
|
-
args: ["--clipboard", "--input"]
|
|
10427
|
-
};
|
|
10428
|
-
return platform === "linux-wayland" ? [
|
|
10429
|
-
wl,
|
|
10430
|
-
xclip,
|
|
10431
|
-
xsel
|
|
10432
|
-
] : [
|
|
10433
|
-
xclip,
|
|
10434
|
-
xsel,
|
|
10435
|
-
wl
|
|
10436
|
-
];
|
|
10437
|
-
}
|
|
10438
|
-
/**
|
|
10439
|
-
* Write `text` to the system clipboard. Throws ClipboardTextError when the
|
|
10440
|
-
* platform is unsupported or every candidate command fails, so callers can
|
|
10441
|
-
* surface a "Copy failed" indicator.
|
|
10442
|
-
*/
|
|
10443
|
-
async function writeTextClipboard(text, runCommand = defaultRunCommand) {
|
|
10444
|
-
const platform = detectPlatform();
|
|
10445
|
-
let command;
|
|
10446
|
-
let args;
|
|
10447
|
-
let input;
|
|
10448
|
-
switch (platform) {
|
|
10449
|
-
case "macos":
|
|
10450
|
-
command = "pbcopy";
|
|
10451
|
-
args = [];
|
|
10452
|
-
input = text;
|
|
10453
|
-
break;
|
|
10454
|
-
case "windows": {
|
|
10455
|
-
const base64 = Buffer.from(text, "utf8").toString("base64");
|
|
10456
|
-
command = "powershell";
|
|
10457
|
-
args = [
|
|
10458
|
-
"-NoProfile",
|
|
10459
|
-
"-Command",
|
|
10460
|
-
`[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${base64}')) | Set-Clipboard`
|
|
10461
|
-
];
|
|
10462
|
-
input = "";
|
|
10463
|
-
break;
|
|
10464
|
-
}
|
|
10465
|
-
case "linux-wayland":
|
|
10466
|
-
case "linux-x11": {
|
|
10467
|
-
const failures = [];
|
|
10468
|
-
for (const candidate of linuxCandidates(platform)) {
|
|
10469
|
-
const result = runCommand(candidate.command, candidate.args, text);
|
|
10470
|
-
if (result.ok) return;
|
|
10471
|
-
failures.push(`${candidate.command}: ${result.error ?? "unknown error"}`);
|
|
10472
|
-
}
|
|
10473
|
-
throw new ClipboardTextError(`failed to copy to clipboard (${failures.join(" | ")}). Install wl-clipboard (Wayland: sudo apt install wl-clipboard) or xclip/xsel (X11: sudo apt install xclip xsel)`);
|
|
10474
|
-
}
|
|
10475
|
-
default: throw new ClipboardTextError(`unsupported clipboard platform: ${process.platform}`);
|
|
10476
|
-
}
|
|
10477
|
-
const result = runCommand(command, args, input);
|
|
10478
|
-
if (!result.ok) throw new ClipboardTextError(`failed to copy to clipboard: ${command} ${result.error ?? "unknown error"}`);
|
|
10479
|
-
}
|
|
10480
|
-
//#endregion
|
|
10481
10548
|
//#region src/tui/utils/render-batcher.ts
|
|
10482
10549
|
function createRenderBatcher(doRender) {
|
|
10483
10550
|
let scheduled = false;
|
|
@@ -11694,6 +11761,7 @@ function createTUIState(options) {
|
|
|
11694
11761
|
activeDialog: null,
|
|
11695
11762
|
tasksBrowser: void 0,
|
|
11696
11763
|
externalEditorRunning: false,
|
|
11764
|
+
lastActivityAt: 0,
|
|
11697
11765
|
queuedMessages: [],
|
|
11698
11766
|
fdPath: detectFdPath(),
|
|
11699
11767
|
gitLsFilesCache: createGitLsFilesCache(initialAppState.workDir),
|
|
@@ -12499,6 +12567,11 @@ var SessionManager$1 = class {
|
|
|
12499
12567
|
resetSessionRuntime() {
|
|
12500
12568
|
this.host.state.queuedMessages = [];
|
|
12501
12569
|
this.host.harness.interactiveAgentId = MAIN_AGENT_ID;
|
|
12570
|
+
this.host.setAppState({
|
|
12571
|
+
streamingPhase: "idle",
|
|
12572
|
+
streamingStartTime: 0
|
|
12573
|
+
});
|
|
12574
|
+
this.host.state.lastActivityAt = 0;
|
|
12502
12575
|
this.host.streamingUI.discardPending();
|
|
12503
12576
|
this.host.streamingUI.resetToolCallState();
|
|
12504
12577
|
this.host.streamingUI.resetToolUi();
|
|
@@ -12998,69 +13071,86 @@ function stripLeadingSpace(s) {
|
|
|
12998
13071
|
* it as the focused component, and tears it down on the `onClose`
|
|
12999
13072
|
* callback (fired on Esc / Enter / q).
|
|
13000
13073
|
*/
|
|
13001
|
-
/**
|
|
13002
|
-
|
|
13003
|
-
|
|
13004
|
-
|
|
13005
|
-
|
|
13006
|
-
|
|
13007
|
-
|
|
13008
|
-
|
|
13009
|
-
|
|
13010
|
-
|
|
13011
|
-
|
|
13012
|
-
|
|
13013
|
-
|
|
13014
|
-
|
|
13015
|
-
|
|
13016
|
-
|
|
13017
|
-
|
|
13018
|
-
|
|
13019
|
-
|
|
13020
|
-
|
|
13021
|
-
|
|
13022
|
-
|
|
13023
|
-
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
13027
|
-
|
|
13028
|
-
|
|
13029
|
-
|
|
13030
|
-
|
|
13031
|
-
|
|
13032
|
-
|
|
13033
|
-
|
|
13034
|
-
|
|
13035
|
-
|
|
13036
|
-
|
|
13037
|
-
|
|
13038
|
-
|
|
13039
|
-
|
|
13040
|
-
|
|
13041
|
-
|
|
13042
|
-
|
|
13043
|
-
|
|
13044
|
-
|
|
13045
|
-
|
|
13046
|
-
|
|
13047
|
-
|
|
13048
|
-
|
|
13049
|
-
|
|
13050
|
-
|
|
13051
|
-
|
|
13052
|
-
|
|
13053
|
-
|
|
13054
|
-
|
|
13055
|
-
|
|
13056
|
-
|
|
13057
|
-
|
|
13058
|
-
|
|
13059
|
-
|
|
13060
|
-
|
|
13061
|
-
|
|
13062
|
-
|
|
13063
|
-
|
|
13074
|
+
/**
|
|
13075
|
+
* Keyboard shortcut reference — keep in sync with the global editor bindings.
|
|
13076
|
+
*
|
|
13077
|
+
* A function, not a constant: `t()` must run on every call. As a module-level
|
|
13078
|
+
* constant the descriptions were evaluated once at import time and froze to
|
|
13079
|
+
* whichever locale happened to be active then, so a Chinese session showed an
|
|
13080
|
+
* English shortcut list (and vice versa).
|
|
13081
|
+
*/
|
|
13082
|
+
function defaultKeyboardShortcuts() {
|
|
13083
|
+
return [
|
|
13084
|
+
{
|
|
13085
|
+
keys: "Shift-Tab",
|
|
13086
|
+
description: t("help.toggle_plan")
|
|
13087
|
+
},
|
|
13088
|
+
{
|
|
13089
|
+
keys: "Tab",
|
|
13090
|
+
description: t("help.cycle_thinking")
|
|
13091
|
+
},
|
|
13092
|
+
{
|
|
13093
|
+
keys: "Ctrl-G",
|
|
13094
|
+
description: t("help.external_editor")
|
|
13095
|
+
},
|
|
13096
|
+
{
|
|
13097
|
+
keys: "Ctrl-O",
|
|
13098
|
+
description: t("help.toggle_output")
|
|
13099
|
+
},
|
|
13100
|
+
{
|
|
13101
|
+
keys: "Ctrl-E",
|
|
13102
|
+
description: t("help.toggle_plan_view")
|
|
13103
|
+
},
|
|
13104
|
+
{
|
|
13105
|
+
keys: "Ctrl-S",
|
|
13106
|
+
description: t("help.interrupt")
|
|
13107
|
+
},
|
|
13108
|
+
{
|
|
13109
|
+
keys: "Ctrl-X",
|
|
13110
|
+
description: t("help.toggle_sidebar")
|
|
13111
|
+
},
|
|
13112
|
+
{
|
|
13113
|
+
keys: "Ctrl-B",
|
|
13114
|
+
description: t("help.toggle_hint")
|
|
13115
|
+
},
|
|
13116
|
+
{
|
|
13117
|
+
keys: "Ctrl-W",
|
|
13118
|
+
description: t("help.cancel_memory")
|
|
13119
|
+
},
|
|
13120
|
+
{
|
|
13121
|
+
keys: "↑",
|
|
13122
|
+
description: t("help.recall_queued")
|
|
13123
|
+
},
|
|
13124
|
+
{
|
|
13125
|
+
keys: "Ctrl-V / Alt-V",
|
|
13126
|
+
description: t("help.paste_image")
|
|
13127
|
+
},
|
|
13128
|
+
{
|
|
13129
|
+
keys: "Shift-Enter / Ctrl-J",
|
|
13130
|
+
description: t("help.newline")
|
|
13131
|
+
},
|
|
13132
|
+
{
|
|
13133
|
+
keys: "Ctrl-C",
|
|
13134
|
+
description: t("help.cancel_stream")
|
|
13135
|
+
},
|
|
13136
|
+
{
|
|
13137
|
+
keys: "Ctrl-D",
|
|
13138
|
+
description: t("help.exit")
|
|
13139
|
+
},
|
|
13140
|
+
{
|
|
13141
|
+
keys: "Esc",
|
|
13142
|
+
description: t("help.close_dialog")
|
|
13143
|
+
},
|
|
13144
|
+
{
|
|
13145
|
+
keys: "↑ / ↓",
|
|
13146
|
+
description: t("help.browse_history")
|
|
13147
|
+
},
|
|
13148
|
+
{
|
|
13149
|
+
keys: "Enter",
|
|
13150
|
+
description: t("help.submit")
|
|
13151
|
+
}
|
|
13152
|
+
];
|
|
13153
|
+
}
|
|
13064
13154
|
var HelpPanelComponent = class extends Container {
|
|
13065
13155
|
focused = false;
|
|
13066
13156
|
opts;
|
|
@@ -13096,33 +13186,42 @@ var HelpPanelComponent = class extends Container {
|
|
|
13096
13186
|
const muted = chalk.hex(colors.textMuted);
|
|
13097
13187
|
const kbdColor = chalk.hex(colors.warning);
|
|
13098
13188
|
const slashColor = chalk.hex(colors.primary);
|
|
13099
|
-
const shortcuts = this.opts.shortcuts ??
|
|
13100
|
-
const
|
|
13189
|
+
const shortcuts = this.opts.shortcuts ?? defaultKeyboardShortcuts();
|
|
13190
|
+
const padToWidth = (text, width) => {
|
|
13191
|
+
const pad = width - visibleWidth(text);
|
|
13192
|
+
return pad > 0 ? text + " ".repeat(pad) : text;
|
|
13193
|
+
};
|
|
13194
|
+
const kbdWidth = Math.max(8, ...shortcuts.map((s) => visibleWidth(s.keys)));
|
|
13101
13195
|
const sortedCmds = [...this.opts.commands].toSorted((a, b) => a.name.localeCompare(b.name));
|
|
13102
13196
|
const cmdLabels = sortedCmds.map((c) => {
|
|
13103
13197
|
const aliases = c.aliases.length > 0 ? ` (${c.aliases.map((a) => "/" + a).join(", ")})` : "";
|
|
13104
13198
|
return `/${c.name}${aliases}`;
|
|
13105
13199
|
});
|
|
13106
13200
|
const cmdWidth = Math.max(12, ...cmdLabels.map((l) => l.length));
|
|
13201
|
+
const hasCommands = sortedCmds.length > 0;
|
|
13107
13202
|
const lines = [
|
|
13108
13203
|
accent("─".repeat(width)),
|
|
13109
|
-
accent.bold(t("help.title")) + muted(` · ${t("help.close_hint")}`),
|
|
13110
|
-
"",
|
|
13111
|
-
` ${dim(t("help.welcome_msg"))}`,
|
|
13112
|
-
` ${muted(t("help.hidden_commands"))}`,
|
|
13204
|
+
accent.bold(this.opts.title ?? t("help.title")) + muted(` · ${t("help.close_hint")}`),
|
|
13113
13205
|
"",
|
|
13206
|
+
...hasCommands ? [
|
|
13207
|
+
` ${dim(t("help.welcome_msg"))}`,
|
|
13208
|
+
` ${muted(t("help.hidden_commands"))}`,
|
|
13209
|
+
""
|
|
13210
|
+
] : [],
|
|
13114
13211
|
` ${chalk.bold(t("help.shortcuts"))}`,
|
|
13115
|
-
...shortcuts.map((s) => ` ${kbdColor(s.keys
|
|
13116
|
-
"",
|
|
13117
|
-
` ${chalk.bold(t("help.slash_commands"))}`,
|
|
13118
|
-
...sortedCmds.map((cmd, i) => {
|
|
13119
|
-
return ` ${slashColor((cmdLabels[i] ?? `/${cmd.name}`).padEnd(cmdWidth))} ${dim(t(cmd.description))}`;
|
|
13120
|
-
}),
|
|
13212
|
+
...shortcuts.map((s) => ` ${kbdColor(padToWidth(s.keys, kbdWidth))} ${dim(s.description)}`),
|
|
13121
13213
|
"",
|
|
13214
|
+
...hasCommands ? [
|
|
13215
|
+
` ${chalk.bold(t("help.slash_commands"))}`,
|
|
13216
|
+
...sortedCmds.map((cmd, i) => {
|
|
13217
|
+
return ` ${slashColor((cmdLabels[i] ?? `/${cmd.name}`).padEnd(cmdWidth))} ${dim(t(cmd.description))}`;
|
|
13218
|
+
}),
|
|
13219
|
+
""
|
|
13220
|
+
] : [],
|
|
13122
13221
|
accent("─".repeat(width))
|
|
13123
13222
|
];
|
|
13124
13223
|
const content = lines.slice(1, lines.length - 1);
|
|
13125
|
-
const maxVisible = Math.max(5, this.opts.maxVisible ??
|
|
13224
|
+
const maxVisible = Math.max(5, this.opts.maxVisible ?? 30);
|
|
13126
13225
|
if (content.length > maxVisible) {
|
|
13127
13226
|
this.scrollTop = Math.max(0, Math.min(this.scrollTop, content.length - maxVisible));
|
|
13128
13227
|
const slice = content.slice(this.scrollTop, this.scrollTop + maxVisible);
|
|
@@ -13159,6 +13258,8 @@ var HelpPanelComponent = class extends Container {
|
|
|
13159
13258
|
* Esc Cancel / back
|
|
13160
13259
|
*/
|
|
13161
13260
|
const ELLIPSIS$1 = "…";
|
|
13261
|
+
/** One-sentence conclusion: generous for a rule, bounded for prompt injection. */
|
|
13262
|
+
const MAX_DISTILLED_CHARS = 200;
|
|
13162
13263
|
function formatRelativeTime$1(ts) {
|
|
13163
13264
|
if (!Number.isFinite(ts) || ts <= 0) return "";
|
|
13164
13265
|
const diffSec = Math.floor(Math.max(0, Date.now() - ts) / 1e3);
|
|
@@ -13169,6 +13270,13 @@ function formatRelativeTime$1(ts) {
|
|
|
13169
13270
|
if (hours < 24) return t("memory.hours_ago", { hours });
|
|
13170
13271
|
return t("memory.days_ago", { days: Math.floor(hours / 24) });
|
|
13171
13272
|
}
|
|
13273
|
+
/** Local calendar date as YYYY-MM-DD; avoids the UTC off-by-one of toISOString. */
|
|
13274
|
+
function formatDate(ts) {
|
|
13275
|
+
const date = new Date(ts);
|
|
13276
|
+
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
13277
|
+
const day = String(date.getDate()).padStart(2, "0");
|
|
13278
|
+
return `${date.getFullYear()}-${month}-${day}`;
|
|
13279
|
+
}
|
|
13172
13280
|
function singleLine$1(text) {
|
|
13173
13281
|
return text.replaceAll(/\s+/g, " ").trim();
|
|
13174
13282
|
}
|
|
@@ -13226,6 +13334,12 @@ var MemoryPickerComponent = class extends Container {
|
|
|
13226
13334
|
searchQuery = "";
|
|
13227
13335
|
isSearching = false;
|
|
13228
13336
|
searchInput = "";
|
|
13337
|
+
/** Draft text for the in-place edit of the latest distillation. */
|
|
13338
|
+
editBuffer = "";
|
|
13339
|
+
/** Set when the last save failed; keeps the draft on screen for a retry. */
|
|
13340
|
+
editError = false;
|
|
13341
|
+
/** Guards against a second Enter while the update is in flight. */
|
|
13342
|
+
saving = false;
|
|
13229
13343
|
/** Memo ids ticked for batch delete (space toggles, a selects all). */
|
|
13230
13344
|
selectedIds = /* @__PURE__ */ new Set();
|
|
13231
13345
|
memos;
|
|
@@ -13286,6 +13400,29 @@ var MemoryPickerComponent = class extends Container {
|
|
|
13286
13400
|
if (ch.length > 0) this.searchInput += ch;
|
|
13287
13401
|
return;
|
|
13288
13402
|
}
|
|
13403
|
+
if (this.mode === "editDistilled") {
|
|
13404
|
+
if (matchesKey(data, Key.escape)) {
|
|
13405
|
+
this.editBuffer = "";
|
|
13406
|
+
this.mode = "detail";
|
|
13407
|
+
this.ui?.requestRender();
|
|
13408
|
+
return;
|
|
13409
|
+
}
|
|
13410
|
+
if (matchesKey(data, Key.enter)) {
|
|
13411
|
+
if (!this.saving) this.saveDistilled();
|
|
13412
|
+
return;
|
|
13413
|
+
}
|
|
13414
|
+
if (matchesKey(data, Key.backspace) || matchesKey(data, Key.delete)) {
|
|
13415
|
+
this.editBuffer = Array.from(this.editBuffer).slice(0, -1).join("");
|
|
13416
|
+
this.ui?.requestRender();
|
|
13417
|
+
return;
|
|
13418
|
+
}
|
|
13419
|
+
const typed = printableChar(data);
|
|
13420
|
+
if (typed.length > 0) {
|
|
13421
|
+
if (Array.from(this.editBuffer).length < MAX_DISTILLED_CHARS) this.editBuffer += typed;
|
|
13422
|
+
this.ui?.requestRender();
|
|
13423
|
+
}
|
|
13424
|
+
return;
|
|
13425
|
+
}
|
|
13289
13426
|
if (this.mode === "confirmDelete") {
|
|
13290
13427
|
if (matchesKey(data, Key.enter)) {
|
|
13291
13428
|
if (this.selectedIds.size > 0) this.deleteSelectedAndReload();
|
|
@@ -13307,6 +13444,14 @@ var MemoryPickerComponent = class extends Container {
|
|
|
13307
13444
|
this.detailMemo = null;
|
|
13308
13445
|
return;
|
|
13309
13446
|
}
|
|
13447
|
+
const key = printableChar(data);
|
|
13448
|
+
if ((key === "e" || key === "E") && this.detailMemo !== null) {
|
|
13449
|
+
this.editBuffer = this.detailMemo.distilled ?? "";
|
|
13450
|
+
this.editError = false;
|
|
13451
|
+
this.mode = "editDistilled";
|
|
13452
|
+
this.ui?.requestRender();
|
|
13453
|
+
return;
|
|
13454
|
+
}
|
|
13310
13455
|
}
|
|
13311
13456
|
if (matchesKey(data, Key.escape)) {
|
|
13312
13457
|
if (this.searchQuery.length > 0) {
|
|
@@ -13375,6 +13520,52 @@ var MemoryPickerComponent = class extends Container {
|
|
|
13375
13520
|
if (this.detailMemo) this.mode = "detail";
|
|
13376
13521
|
this.ui?.requestRender();
|
|
13377
13522
|
}
|
|
13523
|
+
/**
|
|
13524
|
+
* Persist the edited distillation. A non-empty text becomes a user-authored
|
|
13525
|
+
* rule; clearing it drops back to a plain extracted memo.
|
|
13526
|
+
*/
|
|
13527
|
+
async saveDistilled() {
|
|
13528
|
+
const memo = this.detailMemo;
|
|
13529
|
+
if (memo === null) {
|
|
13530
|
+
this.mode = "list";
|
|
13531
|
+
return;
|
|
13532
|
+
}
|
|
13533
|
+
this.saving = true;
|
|
13534
|
+
const text = this.editBuffer.trim();
|
|
13535
|
+
if (text === (memo.distilled ?? "").trim()) {
|
|
13536
|
+
this.saving = false;
|
|
13537
|
+
this.editBuffer = "";
|
|
13538
|
+
this.mode = "detail";
|
|
13539
|
+
this.ui?.requestRender();
|
|
13540
|
+
return;
|
|
13541
|
+
}
|
|
13542
|
+
try {
|
|
13543
|
+
await this.store.update(memo.id, {
|
|
13544
|
+
distilled: text.length > 0 ? text : void 0,
|
|
13545
|
+
distilledFrom: text.length > 0 ? memo.distilledFrom : void 0,
|
|
13546
|
+
origin: text.length > 0 ? "user" : "session",
|
|
13547
|
+
distilledAt: text.length > 0 ? Date.now() : void 0
|
|
13548
|
+
});
|
|
13549
|
+
} catch {
|
|
13550
|
+
this.editError = true;
|
|
13551
|
+
this.saving = false;
|
|
13552
|
+
this.ui?.requestRender();
|
|
13553
|
+
return;
|
|
13554
|
+
}
|
|
13555
|
+
this.saving = false;
|
|
13556
|
+
this.editError = false;
|
|
13557
|
+
this.editBuffer = "";
|
|
13558
|
+
await this.loadMemos();
|
|
13559
|
+
const index = this.memos.findIndex((item) => item.id === memo.id);
|
|
13560
|
+
if (index < 0) {
|
|
13561
|
+
this.detailMemo = null;
|
|
13562
|
+
this.mode = "list";
|
|
13563
|
+
this.ui?.requestRender();
|
|
13564
|
+
return;
|
|
13565
|
+
}
|
|
13566
|
+
this.selectedIndex = index;
|
|
13567
|
+
await this.showDetail();
|
|
13568
|
+
}
|
|
13378
13569
|
async deleteAndReload(id) {
|
|
13379
13570
|
try {
|
|
13380
13571
|
await this.store.delete(id);
|
|
@@ -13395,8 +13586,7 @@ var MemoryPickerComponent = class extends Container {
|
|
|
13395
13586
|
}
|
|
13396
13587
|
render(width) {
|
|
13397
13588
|
const c = this.colors;
|
|
13398
|
-
const lines = [];
|
|
13399
|
-
lines.push(chalk.hex(c.primary)("─".repeat(width)));
|
|
13589
|
+
const lines = [chalk.hex(c.primary)("─".repeat(width))];
|
|
13400
13590
|
if (this.isSearching) {
|
|
13401
13591
|
const prompt = t("memory.search_label");
|
|
13402
13592
|
const input = this.searchInput.length > 0 ? this.searchInput : "█";
|
|
@@ -13425,15 +13615,18 @@ var MemoryPickerComponent = class extends Container {
|
|
|
13425
13615
|
return lines;
|
|
13426
13616
|
}
|
|
13427
13617
|
if (this.mode === "detail" && this.detailMemo) return this.renderDetail(lines, width, c);
|
|
13618
|
+
if (this.mode === "editDistilled" && this.detailMemo) return this.renderEditDistilled(lines, width, c);
|
|
13428
13619
|
if (this.mode === "confirmDelete") {
|
|
13429
13620
|
if (this.selectedIds.size > 0) {
|
|
13430
13621
|
lines.push(truncateToWidth(chalk.hex(c.warning).bold(t("memory.batch_delete_confirm", { count: String(this.selectedIds.size) })), width, ELLIPSIS$1));
|
|
13431
13622
|
lines.push(chalk.hex(c.warning)(t("memory.batch_delete_hint")));
|
|
13623
|
+
if (this.memos.some((memo) => this.selectedIds.has(memo.id) && memo.origin !== void 0 && memo.origin !== "session")) lines.push(chalk.hex(c.warning)(truncateToWidth(` ${t("memory.delete_rule_warning")}`, width, ELLIPSIS$1)));
|
|
13432
13624
|
} else {
|
|
13433
13625
|
const memo = this.memos[this.selectedIndex];
|
|
13434
13626
|
if (memo) {
|
|
13435
13627
|
lines.push(truncateToWidth(chalk.hex(c.warning).bold(` ${t("memory.deleting")}${memo.userNeed}`), width, ELLIPSIS$1));
|
|
13436
13628
|
lines.push(chalk.hex(c.warning)(t("memory.delete_confirm_hint")));
|
|
13629
|
+
if (memo.origin !== void 0 && memo.origin !== "session") lines.push(chalk.hex(c.warning)(truncateToWidth(` ${t("memory.delete_rule_warning")}`, width, ELLIPSIS$1)));
|
|
13437
13630
|
}
|
|
13438
13631
|
}
|
|
13439
13632
|
lines.push(chalk.hex(c.primary)("─".repeat(width)));
|
|
@@ -13467,7 +13660,10 @@ var MemoryPickerComponent = class extends Container {
|
|
|
13467
13660
|
const indentWidth = visibleWidth(indent);
|
|
13468
13661
|
const titleColor = isSelected ? c.primary : c.text;
|
|
13469
13662
|
const titleStyle = isSelected ? chalk.hex(titleColor).bold : chalk.hex(titleColor);
|
|
13470
|
-
const
|
|
13663
|
+
const sources = memo.distilledFrom ?? [];
|
|
13664
|
+
const isRule = memo.origin !== void 0 && memo.origin !== "session";
|
|
13665
|
+
const trailingParts = [formatRelativeTime$1(isRule ? memo.distilledAt ?? memo.recordedAt : memo.recordedAt), isRule ? memo.origin === "user" ? t("memory.origin_user") : t("memory.origin_dream") : sourceLabel(memo.extractionSource)].filter((p) => p.length > 0);
|
|
13666
|
+
if (isRule && sources.length > 0) trailingParts.push(t("memory.source_count", { count: String(sources.length) }));
|
|
13471
13667
|
const trailingText = trailingParts.length > 0 ? " " + trailingParts.join(" ") : "";
|
|
13472
13668
|
const trailingWidth = visibleWidth(trailingText);
|
|
13473
13669
|
const headerPrefixWidth = visibleWidth(pointer) + 1 + visibleWidth(check) + 1;
|
|
@@ -13493,17 +13689,22 @@ var MemoryPickerComponent = class extends Container {
|
|
|
13493
13689
|
const approachPreview = t("memory.plan_label") + singleLine$1(memo.approach);
|
|
13494
13690
|
card.push(indent + chalk.hex(c.textDim)(truncateToWidth(approachPreview, Math.max(8, width - indentWidth), ELLIPSIS$1)));
|
|
13495
13691
|
}
|
|
13692
|
+
if (memo.distilled !== void 0 && memo.distilled.length > 0) {
|
|
13693
|
+
const distilledPreview = t("memory.distilled_label") + singleLine$1(memo.distilled);
|
|
13694
|
+
card.push(indent + chalk.hex(c.success ?? c.primary)(truncateToWidth(distilledPreview, Math.max(8, width - indentWidth), ELLIPSIS$1)));
|
|
13695
|
+
}
|
|
13496
13696
|
return card;
|
|
13497
13697
|
}
|
|
13498
13698
|
renderDetail(lines, width, c) {
|
|
13499
13699
|
const memo = this.detailMemo;
|
|
13500
|
-
const time =
|
|
13700
|
+
const time = formatDate(memo.recordedAt);
|
|
13501
13701
|
const sessionLabel = memo.sourceSessionTitle && memo.sourceSessionTitle.length > 0 ? `${memo.sourceSessionTitle} (${memo.sourceSessionId.slice(0, 12)})` : memo.sourceSessionId.slice(0, 12);
|
|
13502
13702
|
const indent = " ";
|
|
13503
13703
|
const contentWidth = Math.max(8, width - visibleWidth(indent));
|
|
13504
13704
|
lines.push(truncateToWidth(chalk.hex(c.primary).bold(`${indent}${t("memory.requirement_label")}${memo.userNeed}`), width, ELLIPSIS$1));
|
|
13505
13705
|
lines.push("");
|
|
13506
|
-
|
|
13706
|
+
const sourceName = memo.origin !== void 0 && memo.origin !== "session" ? memo.origin === "user" ? t("memory.origin_user") : t("memory.origin_dream") : sourceLabel(memo.extractionSource);
|
|
13707
|
+
lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}${t("memory.result_label")}${memo.outcome} ${t("memory.source_label")}${sourceName} ${time}`, width, ELLIPSIS$1)));
|
|
13507
13708
|
lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}${t("memory.session_label")}${sessionLabel}`, width, ELLIPSIS$1)));
|
|
13508
13709
|
const project = formatProject(memo);
|
|
13509
13710
|
const tags = formatTags(memo);
|
|
@@ -13538,6 +13739,27 @@ var MemoryPickerComponent = class extends Container {
|
|
|
13538
13739
|
}
|
|
13539
13740
|
lines.push("");
|
|
13540
13741
|
}
|
|
13742
|
+
if (memo.distilled !== void 0 && memo.distilled.length > 0) {
|
|
13743
|
+
const label = t("memory.distilled_label");
|
|
13744
|
+
const wrapped = wrapText(memo.distilled, contentWidth - visibleWidth(label));
|
|
13745
|
+
for (let i = 0; i < wrapped.length; i++) {
|
|
13746
|
+
const prefix = i === 0 ? `${indent}${label}` : indent + " ".repeat(visibleWidth(label));
|
|
13747
|
+
lines.push(truncateToWidth(chalk.hex(c.success ?? c.primary)(prefix + wrapped[i]), width, ELLIPSIS$1));
|
|
13748
|
+
}
|
|
13749
|
+
lines.push("");
|
|
13750
|
+
}
|
|
13751
|
+
if (memo.origin !== void 0 && memo.origin !== "session") {
|
|
13752
|
+
const distilledAtLabel = memo.distilledAt !== void 0 ? `${t("memory.distilled_at")} ${formatDate(memo.distilledAt)}` : t("memory.distilled_at_unknown");
|
|
13753
|
+
lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}${distilledAtLabel}`, width, ELLIPSIS$1)));
|
|
13754
|
+
const sources = memo.distilledFrom ?? [];
|
|
13755
|
+
lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}${sources.length > 0 ? t("memory.source_count", { count: String(sources.length) }) : t("memory.source_unknown")}`, width, ELLIPSIS$1)));
|
|
13756
|
+
for (const source of sources.slice(0, 5)) {
|
|
13757
|
+
const date = formatDate(source.at);
|
|
13758
|
+
lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}- ${date} · ${singleLine$1(source.title)}`, width, ELLIPSIS$1)));
|
|
13759
|
+
}
|
|
13760
|
+
if (sources.length > 5) lines.push(chalk.hex(c.textDim)(truncateToWidth(`${indent}${t("memory.source_more", { count: String(sources.length - 5) })}`, width, ELLIPSIS$1)));
|
|
13761
|
+
lines.push("");
|
|
13762
|
+
}
|
|
13541
13763
|
if (memo.note !== void 0 && memo.note.length > 0) {
|
|
13542
13764
|
const label = t("memory.note_label");
|
|
13543
13765
|
const wrapped = wrapText(memo.note, contentWidth - visibleWidth(label));
|
|
@@ -13551,6 +13773,20 @@ var MemoryPickerComponent = class extends Container {
|
|
|
13551
13773
|
lines.push(chalk.hex(c.primary)("─".repeat(width)));
|
|
13552
13774
|
return lines;
|
|
13553
13775
|
}
|
|
13776
|
+
renderEditDistilled(lines, width, c) {
|
|
13777
|
+
const memo = this.detailMemo;
|
|
13778
|
+
const indent = " ";
|
|
13779
|
+
lines.push(truncateToWidth(chalk.hex(c.primary).bold(`${indent}${t("memory.requirement_label")}${memo.userNeed}`), width, ELLIPSIS$1));
|
|
13780
|
+
lines.push("");
|
|
13781
|
+
lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}${t("memory.distilled_label")}`, width, ELLIPSIS$1)));
|
|
13782
|
+
const contentWidth = Math.max(8, width - 4);
|
|
13783
|
+
for (const line of wrapText(`${this.editBuffer}█`, contentWidth)) lines.push(chalk.hex(c.success ?? c.primary)(`${indent}${line}`));
|
|
13784
|
+
lines.push("");
|
|
13785
|
+
if (this.editError) lines.push(chalk.hex(c.error)(truncateToWidth(`${indent}${t("memory.edit_save_failed")}`, width, ELLIPSIS$1)));
|
|
13786
|
+
lines.push(chalk.hex(c.textMuted)(truncateToWidth(`${indent}${t("memory.edit_distilled_hint")}`, width, ELLIPSIS$1)));
|
|
13787
|
+
lines.push(chalk.hex(c.primary)("─".repeat(width)));
|
|
13788
|
+
return lines;
|
|
13789
|
+
}
|
|
13554
13790
|
};
|
|
13555
13791
|
//#endregion
|
|
13556
13792
|
//#region src/tui/utils/session-label.ts
|
|
@@ -14477,6 +14713,22 @@ var DialogManager = class {
|
|
|
14477
14713
|
this.host.state.activeDialog = null;
|
|
14478
14714
|
this.restoreEditor();
|
|
14479
14715
|
}
|
|
14716
|
+
showHotkeysPanel() {
|
|
14717
|
+
this.host.state.activeDialog = "hotkeys";
|
|
14718
|
+
this.mountEditorReplacement(new HelpPanelComponent({
|
|
14719
|
+
commands: [],
|
|
14720
|
+
shortcuts: defaultKeyboardShortcuts(),
|
|
14721
|
+
colors: this.host.state.theme.colors,
|
|
14722
|
+
title: t("help.hotkeys_title"),
|
|
14723
|
+
onClose: () => {
|
|
14724
|
+
this.hideHotkeysPanel();
|
|
14725
|
+
}
|
|
14726
|
+
}));
|
|
14727
|
+
}
|
|
14728
|
+
hideHotkeysPanel() {
|
|
14729
|
+
this.host.state.activeDialog = null;
|
|
14730
|
+
this.restoreEditor();
|
|
14731
|
+
}
|
|
14480
14732
|
async showSessionPicker() {
|
|
14481
14733
|
await this.host.fetchSessions();
|
|
14482
14734
|
this.host.state.activeDialog = "session-picker";
|
|
@@ -14750,6 +15002,7 @@ var ScreamTUI = class {
|
|
|
14750
15002
|
deferUserMessages = false;
|
|
14751
15003
|
aborted = false;
|
|
14752
15004
|
isShuttingDown = false;
|
|
15005
|
+
startedAt = Date.now();
|
|
14753
15006
|
reverseRpcDisposers = [];
|
|
14754
15007
|
startupNotice;
|
|
14755
15008
|
/** Interval handle for the periodic provider-balance refresh. */
|
|
@@ -14856,6 +15109,8 @@ var ScreamTUI = class {
|
|
|
14856
15109
|
this.lifecycleController.startEventLoop();
|
|
14857
15110
|
try {
|
|
14858
15111
|
await this.finishStartup(shouldReplayHistory);
|
|
15112
|
+
this.refreshRuntimeMarker();
|
|
15113
|
+
this.reportStaleSessions();
|
|
14859
15114
|
this.lifecycleController.startCcConnectPolling();
|
|
14860
15115
|
this.startBalancePolling();
|
|
14861
15116
|
} catch (error) {
|
|
@@ -14940,6 +15195,34 @@ var ScreamTUI = class {
|
|
|
14940
15195
|
throw error;
|
|
14941
15196
|
}
|
|
14942
15197
|
}
|
|
15198
|
+
/** Keep this instance's crash marker in sync with the running session. */
|
|
15199
|
+
refreshRuntimeMarker() {
|
|
15200
|
+
try {
|
|
15201
|
+
writeRuntimeMarker(getDataDir(), {
|
|
15202
|
+
pid: process.pid,
|
|
15203
|
+
startedAt: this.startedAt,
|
|
15204
|
+
...this.state.appState.sessionId ? { sessionId: this.state.appState.sessionId } : {},
|
|
15205
|
+
...this.state.appState.sessionTitle ? { title: this.state.appState.sessionTitle } : {}
|
|
15206
|
+
});
|
|
15207
|
+
} catch {}
|
|
15208
|
+
}
|
|
15209
|
+
/** Report a previous instance that never shut down, exactly once. */
|
|
15210
|
+
reportStaleSessions() {
|
|
15211
|
+
let stale = [];
|
|
15212
|
+
try {
|
|
15213
|
+
stale = collectStaleMarkers(getDataDir());
|
|
15214
|
+
} catch {
|
|
15215
|
+
return;
|
|
15216
|
+
}
|
|
15217
|
+
const latest = stale[0];
|
|
15218
|
+
if (latest === void 0) return;
|
|
15219
|
+
const label = latest.title ?? (latest.sessionId !== void 0 ? latest.sessionId.slice(0, 8) : "");
|
|
15220
|
+
if (label.length === 0) {
|
|
15221
|
+
this.showNotice(t("tui.crash_recovery_title"));
|
|
15222
|
+
return;
|
|
15223
|
+
}
|
|
15224
|
+
this.showNotice(t("tui.crash_recovery_title"), t("tui.crash_recovery_detail", { title: label }));
|
|
15225
|
+
}
|
|
14943
15226
|
async stop(exitCode) {
|
|
14944
15227
|
if (this.isShuttingDown) return;
|
|
14945
15228
|
this.isShuttingDown = true;
|
|
@@ -14960,6 +15243,7 @@ var ScreamTUI = class {
|
|
|
14960
15243
|
this.inputController.dispose();
|
|
14961
15244
|
this.state.footer.dispose();
|
|
14962
15245
|
this.state.sidebarContainer.dispose();
|
|
15246
|
+
clearRuntimeMarker(getDataDir());
|
|
14963
15247
|
await this.closeSession();
|
|
14964
15248
|
await this.harness.close();
|
|
14965
15249
|
this.sessionEventHandler.stopAllMcpServerStatusSpinners();
|
|
@@ -14990,6 +15274,7 @@ var ScreamTUI = class {
|
|
|
14990
15274
|
onEmergencyExit(exitCode = 129) {
|
|
14991
15275
|
this.isShuttingDown = true;
|
|
14992
15276
|
this.lifecycleController.uninstallSignalHandlers();
|
|
15277
|
+
clearRuntimeMarker(getDataDir());
|
|
14993
15278
|
process.exit(exitCode);
|
|
14994
15279
|
}
|
|
14995
15280
|
stopMemoryIdleTimer() {
|
|
@@ -15139,6 +15424,7 @@ var ScreamTUI = class {
|
|
|
15139
15424
|
const planModeChanged = "planMode" in patch && prevPlanMode !== this.state.appState.planMode;
|
|
15140
15425
|
const sessionChanged = "sessionId" in patch && this.state.appState.sessionId !== prevSessionId;
|
|
15141
15426
|
if (sessionChanged) {
|
|
15427
|
+
this.refreshRuntimeMarker();
|
|
15142
15428
|
if (!("sessionUsage" in patch)) this.state.appState.sessionUsage = {
|
|
15143
15429
|
inputOther: 0,
|
|
15144
15430
|
output: 0,
|
|
@@ -15270,7 +15556,7 @@ var ScreamTUI = class {
|
|
|
15270
15556
|
const card = new OnboardingCardComponent({
|
|
15271
15557
|
colors: this.state.theme.colors,
|
|
15272
15558
|
onConfigure: () => {
|
|
15273
|
-
import("./dispatch-
|
|
15559
|
+
import("./dispatch-JYsyD0aN.mjs").then(({ handleConnectCommand }) => handleConnectCommand(this, "")).finally(() => {
|
|
15274
15560
|
this.restoreEditor();
|
|
15275
15561
|
onDone?.();
|
|
15276
15562
|
});
|
|
@@ -15308,7 +15594,9 @@ var ScreamTUI = class {
|
|
|
15308
15594
|
let error;
|
|
15309
15595
|
let failureKind;
|
|
15310
15596
|
try {
|
|
15311
|
-
({ok, error, failureKind} = await startManualEmbeddingDownload()
|
|
15597
|
+
({ok, error, failureKind} = await startManualEmbeddingDownload((progress) => {
|
|
15598
|
+
spinner.setLabel(embeddingProgressLabel(progress));
|
|
15599
|
+
}));
|
|
15312
15600
|
} catch (error) {
|
|
15313
15601
|
error = error instanceof Error ? error.message : String(error);
|
|
15314
15602
|
}
|
|
@@ -15396,6 +15684,9 @@ var ScreamTUI = class {
|
|
|
15396
15684
|
showHelpPanel() {
|
|
15397
15685
|
this.dialogManager.showHelpPanel(this.getSlashCommands());
|
|
15398
15686
|
}
|
|
15687
|
+
showHotkeysPanel() {
|
|
15688
|
+
this.dialogManager.showHotkeysPanel();
|
|
15689
|
+
}
|
|
15399
15690
|
hideHelpPanel() {
|
|
15400
15691
|
this.dialogManager.hideHelpPanel();
|
|
15401
15692
|
}
|