scream-code 0.11.7 → 0.11.8

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.
@@ -7,7 +7,7 @@ import { i as __require, o as __toESM, r as __exportAll, t as __commonJSMin } fr
7
7
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
8
8
  import { C as join$1, D as resolve$1, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize, x as dirname$2, y as KnowledgeStore } from "./src-BH9W5k24.mjs";
9
9
  import { t as require_base64_js } from "./base64-js-DzVmk6Nb.mjs";
10
- import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-BdUmo73M.mjs";
10
+ import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-DYHGTG6m.mjs";
11
11
  import { createRequire } from "node:module";
12
12
  import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
13
13
  import * as fs$1 from "node:fs/promises";
@@ -119233,6 +119233,77 @@ function parsePositiveInt(value) {
119233
119233
  return n;
119234
119234
  }
119235
119235
  //#endregion
119236
+ //#region ../../packages/agent-core/src/session/provider-balance.ts
119237
+ const fetchers = [{
119238
+ matches: (baseUrl) => {
119239
+ try {
119240
+ return new URL(baseUrl).hostname === "api.deepseek.com";
119241
+ } catch {
119242
+ return false;
119243
+ }
119244
+ },
119245
+ fetch: async (baseUrl, apiKey) => {
119246
+ try {
119247
+ const root = new URL(baseUrl).origin;
119248
+ const res = await fetch(`${root}/user/balance`, {
119249
+ headers: {
119250
+ Accept: "application/json",
119251
+ Authorization: `Bearer ${apiKey}`
119252
+ },
119253
+ signal: AbortSignal.timeout(5e3)
119254
+ });
119255
+ if (!res.ok) return null;
119256
+ const info = (await res.json()).balance_infos?.[0];
119257
+ if (info?.total_balance === void 0) return null;
119258
+ return {
119259
+ currency: info.currency ?? "",
119260
+ totalBalance: info.total_balance
119261
+ };
119262
+ } catch {
119263
+ return null;
119264
+ }
119265
+ }
119266
+ }, {
119267
+ matches: (baseUrl) => {
119268
+ try {
119269
+ return new URL(baseUrl).hostname === "api.moonshot.cn";
119270
+ } catch {
119271
+ return false;
119272
+ }
119273
+ },
119274
+ fetch: async (baseUrl, apiKey) => {
119275
+ try {
119276
+ const root = new URL(baseUrl).origin;
119277
+ const res = await fetch(`${root}/v1/users/me/balance`, {
119278
+ headers: {
119279
+ Accept: "application/json",
119280
+ Authorization: `Bearer ${apiKey}`
119281
+ },
119282
+ signal: AbortSignal.timeout(5e3)
119283
+ });
119284
+ if (!res.ok) return null;
119285
+ const data = await res.json();
119286
+ if (typeof data.data?.available_balance !== "number") return null;
119287
+ return {
119288
+ currency: "CNY",
119289
+ totalBalance: data.data.available_balance.toFixed(2)
119290
+ };
119291
+ } catch {
119292
+ return null;
119293
+ }
119294
+ }
119295
+ }];
119296
+ /**
119297
+ * Look up the balance for the given provider endpoint.
119298
+ * Returns null when the endpoint is not a recognized official vendor or
119299
+ * when the lookup fails — callers render nothing in that case.
119300
+ */
119301
+ async function fetchProviderBalance(baseUrl, apiKey) {
119302
+ const fetcher = fetchers.find((f) => f.matches(baseUrl));
119303
+ if (!fetcher) return null;
119304
+ return fetcher.fetch(baseUrl, apiKey);
119305
+ }
119306
+ //#endregion
119236
119307
  //#region ../../packages/node-sdk/src/auth.ts
119237
119308
  var ScreamAuthFacade = class {
119238
119309
  options;
@@ -120653,7 +120724,7 @@ function optionalBuildString(value) {
120653
120724
  return typeof value === "string" && value.length > 0 ? value : void 0;
120654
120725
  }
120655
120726
  const SCREAM_BUILD_INFO = {
120656
- version: optionalBuildString("0.11.7"),
120727
+ version: optionalBuildString("0.11.8"),
120657
120728
  channel: optionalBuildString(""),
120658
120729
  commit: optionalBuildString(""),
120659
120730
  buildTarget: optionalBuildString("darwin-arm64")
@@ -120918,7 +120989,9 @@ const NotificationsConfigSchema = z.object({
120918
120989
  const TuiLikePreferencesSchema = z.object({
120919
120990
  nickname: z.string().optional(),
120920
120991
  tone: z.string().optional(),
120921
- other: z.string().optional()
120992
+ other: z.string().optional(),
120993
+ /** Explicit prohibitions: things the user does NOT want done. */
120994
+ doNot: z.string().optional()
120922
120995
  });
120923
120996
  const TuiConfigFileSchema = z.object({
120924
120997
  theme: TuiThemeSchema.optional(),
@@ -123307,7 +123380,7 @@ function mountProfileList(host) {
123307
123380
  const { subagentModels: bindings, availableModels } = host.state.appState;
123308
123381
  const options = getSubagentProfiles().map((profile) => {
123309
123382
  const alias = bindings[profile.name];
123310
- const bindingLabel = alias === void 0 ? t("subagent.follow_main") : modelDisplayName$1(alias, availableModels[alias]);
123383
+ const bindingLabel = alias === void 0 ? t("subagent.follow_main") : availableModels[alias] !== void 0 ? modelDisplayName$1(alias, availableModels[alias]) : t("subagent.stale_binding", { alias });
123311
123384
  return {
123312
123385
  value: profile.name,
123313
123386
  label: `${profile.name} → ${bindingLabel}`,
@@ -123361,6 +123434,10 @@ async function applyBinding(host, profileName, value) {
123361
123434
  const configPath = getTuiConfigPath();
123362
123435
  try {
123363
123436
  const current = await loadTuiConfig(configPath);
123437
+ if (value !== FOLLOW_MAIN && host.state.appState.availableModels[value] === void 0) {
123438
+ host.showError(t("subagent.invalid_alias", { alias: value }));
123439
+ return;
123440
+ }
123364
123441
  const updated = { ...current.subagentModels };
123365
123442
  if (value === FOLLOW_MAIN) delete updated[profileName];
123366
123443
  else updated[profileName] = value;
@@ -123873,6 +123950,11 @@ function safeUsage(usage) {
123873
123950
  const CONTEXT_BAR_WIDTH = 10;
123874
123951
  const CONTEXT_BAR_FILLED = "▰";
123875
123952
  const CONTEXT_BAR_EMPTY = "▱";
123953
+ function currencySymbol(currency) {
123954
+ if (currency === "CNY") return "¥";
123955
+ if (currency === "USD") return "$";
123956
+ return `${currency} `;
123957
+ }
123876
123958
  /**
123877
123959
  * Half-block progress bar for context usage: `▰▰▰▱▱▱▱▱▱▱` (10 cells).
123878
123960
  * Filled cells are rounded from the clamped ratio, so 0% is all-empty and
@@ -124094,8 +124176,12 @@ var FooterComponent = class {
124094
124176
  left.push(chalk.hex(colors.primary).bold(goalLabel));
124095
124177
  }
124096
124178
  const model = shortenModel(modelDisplayName(state));
124097
- if (model) if (state.streamingPhase === "thinking") left.push(shimmerText(model, colors));
124098
- else left.push(chalk.hex(colors.textDim)(model));
124179
+ if (model) {
124180
+ if (state.streamingPhase === "thinking") left.push(shimmerText(model, colors));
124181
+ else left.push(chalk.hex(colors.textDim)(model));
124182
+ const balance = state.providerBalance;
124183
+ if (balance !== null && balance !== void 0) left.push(chalk.hex(colors.textDim)(`${currencySymbol(balance.currency)}${balance.totalBalance}`));
124184
+ }
124099
124185
  if (this.backgroundBashTaskCount > 0) left.push(chalk.hex(colors.primary)(`[${t("footer.tasks_running", { count: String(this.backgroundBashTaskCount) })}]`));
124100
124186
  if (this.backgroundAgentCount > 0) left.push(chalk.hex(colors.primary)(`[${t("footer.agents_running", { count: String(this.backgroundAgentCount) })}]`));
124101
124187
  const git = this.gitCache.getStatus();
@@ -124542,6 +124628,7 @@ function createMarkdownTheme(colors) {
124542
124628
  quote: (text) => chalk.hex(colors.mdQuote)(text),
124543
124629
  quoteBorder: (text) => chalk.hex(colors.mdQuote)(text),
124544
124630
  hr: (text) => border(text),
124631
+ tableHeader: (text) => chalk.bold.hex(colors.accent)(text),
124545
124632
  listBullet: (text) => chalk.hex(colors.roleAssistant)(text.replace(/^-/, "•")),
124546
124633
  bold: (text) => chalk.bold(text),
124547
124634
  italic: (text) => chalk.italic(text),
@@ -124933,6 +125020,84 @@ async function loadManagedUsageReport(host) {
124933
125020
  } };
124934
125021
  }
124935
125022
  //#endregion
125023
+ //#region src/tui/api-balance.ts
125024
+ /**
125025
+ * TUI-side provider balance lookup.
125026
+ *
125027
+ * Reads the runtime config.toml (same file the engine consumes), resolves
125028
+ * the plaintext API key (direct value or the provider's ENV_VAR env
125029
+ * reference), and delegates the actual query to the engine's
125030
+ * fetchProviderBalance. Results are cached briefly so footer renders do
125031
+ * not hammer the vendor endpoint.
125032
+ */
125033
+ const BALANCE_CACHE_MS = 6e4;
125034
+ const cache = /* @__PURE__ */ new Map();
125035
+ /** Monotonic id so an out-of-order lookup can never overwrite a newer one. */
125036
+ let latestRequestId = 0;
125037
+ function resolveApiKey(providerName) {
125038
+ const provider = readConfigFile(join(getDataDir(), "config.toml")).providers?.[providerName];
125039
+ if (!provider) return void 0;
125040
+ const direct = provider.apiKey?.trim();
125041
+ if (direct !== void 0 && direct.length > 0) return direct;
125042
+ const envKey = envKeyForProviderType(provider.type);
125043
+ const stored = envKey !== void 0 ? provider.env?.[envKey]?.trim() : void 0;
125044
+ return stored !== void 0 && stored.length > 0 ? stored : void 0;
125045
+ }
125046
+ function envKeyForProviderType(type) {
125047
+ switch (type) {
125048
+ case "anthropic": return "ANTHROPIC_API_KEY";
125049
+ case "openai":
125050
+ case "openai_responses": return "OPENAI_API_KEY";
125051
+ case "scream": return "SCREAM_API_KEY";
125052
+ case "google-genai": return "GOOGLE_API_KEY";
125053
+ case "vertexai": return "VERTEXAI_API_KEY";
125054
+ default: return;
125055
+ }
125056
+ }
125057
+ async function loadBalance(providerName) {
125058
+ try {
125059
+ const provider = readConfigFile(join(getDataDir(), "config.toml")).providers?.[providerName];
125060
+ if (!provider?.baseUrl) return null;
125061
+ const apiKey = resolveApiKey(providerName);
125062
+ if (apiKey === void 0) return null;
125063
+ return await fetchProviderBalance(provider.baseUrl, apiKey);
125064
+ } catch {
125065
+ return null;
125066
+ }
125067
+ }
125068
+ /**
125069
+ * Look up the balance for the provider that serves the given model
125070
+ * (model names follow the "provider/model" convention). Returns null when
125071
+ * the provider is unknown, unofficial, or the lookup failed — callers
125072
+ * render nothing in that case. Cached for BALANCE_CACHE_MS per provider.
125073
+ */
125074
+ async function getProviderBalanceForModel(model) {
125075
+ const providerName = model.split("/")[0] ?? "";
125076
+ if (providerName.length === 0) return null;
125077
+ const cached = cache.get(providerName);
125078
+ if (cached !== void 0 && Date.now() - cached.at < BALANCE_CACHE_MS) return cached.balance;
125079
+ const balance = await loadBalance(providerName);
125080
+ cache.set(providerName, {
125081
+ balance,
125082
+ at: Date.now()
125083
+ });
125084
+ return balance;
125085
+ }
125086
+ /**
125087
+ * Fetch the balance for a model and push it into app state. Fire-and-forget:
125088
+ * failures resolve to null inside getProviderBalanceForModel, so callers
125089
+ * (startup sync and model switches) never await it. A request id guards
125090
+ * against out-of-order resolutions: when the user switches models quickly,
125091
+ * only the latest lookup's result is committed.
125092
+ */
125093
+ function refreshProviderBalance(model, setAppState) {
125094
+ const requestId = ++latestRequestId;
125095
+ getProviderBalanceForModel(model).then((balance) => {
125096
+ if (requestId !== latestRequestId) return;
125097
+ setAppState({ providerBalance: balance });
125098
+ });
125099
+ }
125100
+ //#endregion
124936
125101
  //#region src/tui/commands/config.ts
124937
125102
  /**
124938
125103
  * Storm Breaker guard for model switches. Returns the (currentTokens,
@@ -125359,8 +125524,10 @@ async function performModelSwitch(host, alias, thinkingLevel) {
125359
125524
  }
125360
125525
  host.setAppState({
125361
125526
  model: effectiveAlias,
125362
- thinkingLevel: effectiveThinking
125527
+ thinkingLevel: effectiveThinking,
125528
+ providerBalance: null
125363
125529
  });
125530
+ refreshProviderBalance(effectiveAlias, (patch) => host.setAppState(patch));
125364
125531
  let persisted = false;
125365
125532
  try {
125366
125533
  persisted = await persistModelSelection(host, alias, thinkingLevel);
@@ -126195,7 +126362,7 @@ async function guidedGoalSetup(host) {
126195
126362
  host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
126196
126363
  return;
126197
126364
  }
126198
- const { TextInputDialogComponent } = await import("./text-input-dialog-Ct-Yn67a.mjs");
126365
+ const { TextInputDialogComponent } = await import("./text-input-dialog-DX3hKoBF.mjs");
126199
126366
  const initialDesc = await promptText(host, TextInputDialogComponent, {
126200
126367
  title: t("goal.setup_title_initial"),
126201
126368
  subtitle: t("goal.setup_desc_hint"),
@@ -126216,7 +126383,7 @@ async function guidedGoalSetup(host) {
126216
126383
  await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
126217
126384
  }
126218
126385
  async function showGoalConfigWizard(host, session, objective, replace) {
126219
- const { TextInputDialogComponent } = await import("./text-input-dialog-Ct-Yn67a.mjs");
126386
+ const { TextInputDialogComponent } = await import("./text-input-dialog-DX3hKoBF.mjs");
126220
126387
  const turnInput = await promptNumber(host, TextInputDialogComponent, {
126221
126388
  title: t("goal.wizard_title", { objective }),
126222
126389
  subtitle: t("goal.budget_turns_hint"),
@@ -132617,8 +132784,11 @@ function buildRoleAdditionalText(prefs) {
132617
132784
  if (prefs.nickname !== void 0 && prefs.nickname.trim().length > 0) items.push(`- Nickname: address the user as "${prefs.nickname.trim()}".`);
132618
132785
  if (prefs.tone !== void 0 && prefs.tone.trim().length > 0) items.push(`- Tone: respond in ${prefs.tone.trim()} tone.`);
132619
132786
  if (prefs.other !== void 0 && prefs.other.trim().length > 0) items.push(`- Other: ${prefs.other.trim()}`);
132620
- if (items.length === 0) return "";
132621
- lines.push("", ...items, "", t("like.priority"));
132787
+ const doNot = prefs.doNot?.trim();
132788
+ if (items.length === 0 && (doNot === void 0 || doNot.length === 0)) return "";
132789
+ lines.push("", ...items);
132790
+ if (doNot !== void 0 && doNot.length > 0) lines.push("", "## Do NOT (explicit prohibitions — NEVER do these)", doNot);
132791
+ lines.push("", t("like.priority"));
132622
132792
  return lines.join("\n");
132623
132793
  }
132624
132794
  async function getUserPrefsPath() {
@@ -132626,12 +132796,23 @@ async function getUserPrefsPath() {
132626
132796
  }
132627
132797
  async function persistLikePreferences(host, prefs) {
132628
132798
  const configPath = getTuiConfigPath();
132629
- await saveTuiConfig({
132630
- ...await loadTuiConfig(configPath),
132799
+ const current = await loadTuiConfig(configPath);
132800
+ const updated = {
132801
+ ...current,
132631
132802
  like: prefs
132632
- }, configPath);
132633
- const roleAdditional = buildRoleAdditionalText(prefs);
132634
- await writeFile(await getUserPrefsPath(), roleAdditional, "utf-8");
132803
+ };
132804
+ try {
132805
+ await saveTuiConfig(updated, configPath);
132806
+ await writeFile(await getUserPrefsPath(), buildRoleAdditionalText(prefs), "utf-8");
132807
+ } catch (error) {
132808
+ try {
132809
+ await saveTuiConfig(current, configPath);
132810
+ } catch {}
132811
+ try {
132812
+ await writeFile(await getUserPrefsPath(), buildRoleAdditionalText(current.like ?? {}), "utf-8");
132813
+ } catch {}
132814
+ throw error;
132815
+ }
132635
132816
  host.setAppState({ like: prefs });
132636
132817
  }
132637
132818
  async function handleLikeCommand(host) {
@@ -132666,10 +132847,21 @@ async function handleLikeCommand(host) {
132666
132847
  host.showStatus(t("like.cancelled"), host.state.theme.colors.textDim);
132667
132848
  return;
132668
132849
  }
132850
+ const doNot = await promptTextInput$1(host, t("like.do_not"), {
132851
+ subtitle: t("like.do_not_hint"),
132852
+ placeholder: t("like.do_not_example"),
132853
+ initialValue: current.doNot,
132854
+ allowEmpty: true
132855
+ });
132856
+ if (doNot === void 0) {
132857
+ host.showStatus(t("like.cancelled"), host.state.theme.colors.textDim);
132858
+ return;
132859
+ }
132669
132860
  await persistLikePreferences(host, {
132670
132861
  nickname: nickname.trim().length > 0 ? nickname.trim() : void 0,
132671
132862
  tone: tone.trim().length > 0 ? tone.trim() : void 0,
132672
- other: other.trim().length > 0 ? other.trim() : void 0
132863
+ other: other.trim().length > 0 ? other.trim() : void 0,
132864
+ doNot: doNot.trim().length > 0 ? doNot.trim() : void 0
132673
132865
  });
132674
132866
  host.showStatus(t("like.saved"), host.state.theme.colors.success);
132675
132867
  }
@@ -142967,8 +143159,10 @@ var SessionManager$1 = class {
142967
143159
  wallClockBaseAt: Date.now()
142968
143160
  } : null,
142969
143161
  goalActive: goal?.status === "active",
142970
- goalContinuationCount: 0
143162
+ goalContinuationCount: 0,
143163
+ providerBalance: null
142971
143164
  });
143165
+ refreshProviderBalance(status.model ?? "", (patch) => this.host.setAppState(patch));
142972
143166
  }
142973
143167
  async activateRuntime() {
142974
143168
  const session = this.requireSession();
@@ -143815,6 +144009,8 @@ var MemoryPickerComponent = class extends Container {
143815
144009
  searchQuery = "";
143816
144010
  isSearching = false;
143817
144011
  searchInput = "";
144012
+ /** Memo ids ticked for batch delete (space toggles, a selects all). */
144013
+ selectedIds = /* @__PURE__ */ new Set();
143818
144014
  memos;
143819
144015
  total;
143820
144016
  loading;
@@ -143843,6 +144039,7 @@ var MemoryPickerComponent = class extends Container {
143843
144039
  this.memos = result.memos;
143844
144040
  this.total = result.total;
143845
144041
  this.selectedIndex = 0;
144042
+ this.selectedIds.clear();
143846
144043
  } catch {
143847
144044
  this.memos = [];
143848
144045
  this.total = 0;
@@ -143874,8 +144071,11 @@ var MemoryPickerComponent = class extends Container {
143874
144071
  }
143875
144072
  if (this.mode === "confirmDelete") {
143876
144073
  if (matchesKey(data, Key.enter)) {
143877
- const memo = this.memos[this.selectedIndex];
143878
- if (memo) this.deleteAndReload(memo.id);
144074
+ if (this.selectedIds.size > 0) this.deleteSelectedAndReload();
144075
+ else {
144076
+ const memo = this.memos[this.selectedIndex];
144077
+ if (memo) this.deleteAndReload(memo.id);
144078
+ }
143879
144079
  return;
143880
144080
  }
143881
144081
  if (matchesKey(data, Key.escape)) {
@@ -143922,7 +144122,23 @@ var MemoryPickerComponent = class extends Container {
143922
144122
  return;
143923
144123
  }
143924
144124
  if (ch === "d" || ch === "D") {
143925
- if (this.memos.length > 0) this.mode = "confirmDelete";
144125
+ if (this.selectedIds.size > 0 || this.memos.length > 0) this.mode = "confirmDelete";
144126
+ return;
144127
+ }
144128
+ if (ch === " ") {
144129
+ const memo = this.memos[this.selectedIndex];
144130
+ if (memo) {
144131
+ if (this.selectedIds.has(memo.id)) this.selectedIds.delete(memo.id);
144132
+ else this.selectedIds.add(memo.id);
144133
+ this.ui?.requestRender();
144134
+ }
144135
+ return;
144136
+ }
144137
+ if (ch === "a" || ch === "A") {
144138
+ if (this.memos.length === 0) return;
144139
+ if (this.memos.every((memo) => this.selectedIds.has(memo.id))) this.selectedIds.clear();
144140
+ else for (const memo of this.memos) this.selectedIds.add(memo.id);
144141
+ this.ui?.requestRender();
143926
144142
  return;
143927
144143
  }
143928
144144
  if (ch === "/") {
@@ -143951,6 +144167,15 @@ var MemoryPickerComponent = class extends Container {
143951
144167
  if (this.selectedIndex >= this.memos.length) this.selectedIndex = Math.max(0, this.memos.length - 1);
143952
144168
  this.ui?.requestRender();
143953
144169
  }
144170
+ async deleteSelectedAndReload() {
144171
+ const ids = [...this.selectedIds];
144172
+ for (const id of ids) try {
144173
+ await this.store.delete(id);
144174
+ } catch {}
144175
+ await this.loadMemos();
144176
+ this.mode = "list";
144177
+ this.ui?.requestRender();
144178
+ }
143954
144179
  render(width) {
143955
144180
  const c = this.colors;
143956
144181
  const lines = [];
@@ -143963,7 +144188,7 @@ var MemoryPickerComponent = class extends Container {
143963
144188
  return lines;
143964
144189
  }
143965
144190
  const headerLabel = t("memory.notebook_title");
143966
- const headerHint = this.searchQuery.length > 0 ? t("memory.esc_clear_search") : t("memory.nav_hint");
144191
+ const headerHint = this.searchQuery.length > 0 ? t("memory.esc_clear_search") : this.selectedIds.size > 0 ? t("memory.batch_hint", { count: String(this.selectedIds.size) }) : t("memory.nav_hint");
143967
144192
  const labelWidth = visibleWidth(headerLabel);
143968
144193
  const shownHint = truncateToWidth(headerHint, Math.max(0, width - labelWidth), ELLIPSIS$1);
143969
144194
  lines.push(chalk.hex(c.primary).bold(headerLabel) + chalk.hex(c.textMuted)(shownHint));
@@ -143984,10 +144209,15 @@ var MemoryPickerComponent = class extends Container {
143984
144209
  }
143985
144210
  if (this.mode === "detail" && this.detailMemo) return this.renderDetail(lines, width, c);
143986
144211
  if (this.mode === "confirmDelete") {
143987
- const memo = this.memos[this.selectedIndex];
143988
- if (memo) {
143989
- lines.push(truncateToWidth(chalk.hex(c.warning).bold(` ${t("memory.deleting")}${memo.userNeed}`), width, ELLIPSIS$1));
143990
- lines.push(chalk.hex(c.warning)(t("memory.delete_confirm_hint")));
144212
+ if (this.selectedIds.size > 0) {
144213
+ lines.push(truncateToWidth(chalk.hex(c.warning).bold(t("memory.batch_delete_confirm", { count: String(this.selectedIds.size) })), width, ELLIPSIS$1));
144214
+ lines.push(chalk.hex(c.warning)(t("memory.batch_delete_hint")));
144215
+ } else {
144216
+ const memo = this.memos[this.selectedIndex];
144217
+ if (memo) {
144218
+ lines.push(truncateToWidth(chalk.hex(c.warning).bold(` ${t("memory.deleting")}${memo.userNeed}`), width, ELLIPSIS$1));
144219
+ lines.push(chalk.hex(c.warning)(t("memory.delete_confirm_hint")));
144220
+ }
143991
144221
  }
143992
144222
  lines.push(chalk.hex(c.primary)("─".repeat(width)));
143993
144223
  return lines;
@@ -144015,6 +144245,7 @@ var MemoryPickerComponent = class extends Container {
144015
144245
  renderMemoCard(width, memo, isSelected) {
144016
144246
  const c = this.colors;
144017
144247
  const pointer = isSelected ? "❯" : " ";
144248
+ const check = this.selectedIds.has(memo.id) ? "☑" : "□";
144018
144249
  const indent = " ";
144019
144250
  const indentWidth = visibleWidth(indent);
144020
144251
  const titleColor = isSelected ? c.primary : c.text;
@@ -144022,10 +144253,12 @@ var MemoryPickerComponent = class extends Container {
144022
144253
  const trailingParts = [formatRelativeTime$1(memo.recordedAt), sourceLabel(memo.extractionSource)].filter((p) => p.length > 0);
144023
144254
  const trailingText = trailingParts.length > 0 ? " " + trailingParts.join(" ") : "";
144024
144255
  const trailingWidth = visibleWidth(trailingText);
144025
- const headerPrefixWidth = visibleWidth(pointer) + 1;
144256
+ const headerPrefixWidth = visibleWidth(pointer) + 1 + visibleWidth(check) + 1;
144026
144257
  const titleBudget = Math.max(8, width - headerPrefixWidth - trailingWidth);
144027
144258
  const shownTitle = truncateToWidth(singleLine$1(memo.userNeed), titleBudget, ELLIPSIS$1);
144259
+ const checkColor = this.selectedIds.has(memo.id) ? c.primary : c.textDim;
144028
144260
  let header = chalk.hex(isSelected ? c.primary : c.textDim)(pointer + " ");
144261
+ header += chalk.hex(checkColor)(check + " ");
144029
144262
  header += titleStyle(shownTitle);
144030
144263
  if (trailingText.length > 0) header += chalk.hex(c.textDim)(trailingText);
144031
144264
  const card = [truncateToWidth(header, width, ELLIPSIS$1)];
@@ -144162,12 +144395,15 @@ var SessionPickerComponent = class extends Container {
144162
144395
  onSelect;
144163
144396
  onCancel;
144164
144397
  onDelete;
144398
+ onDeleteMany;
144165
144399
  onStatus;
144166
144400
  maxVisibleSessions;
144167
144401
  loading;
144168
144402
  focused = false;
144169
144403
  selectedIndex = 0;
144170
144404
  confirmingDelete = false;
144405
+ /** Session ids ticked for batch delete (space toggles, a selects all). */
144406
+ selectedIds = /* @__PURE__ */ new Set();
144171
144407
  constructor(opts) {
144172
144408
  super();
144173
144409
  this.sessions = opts.sessions;
@@ -144177,6 +144413,7 @@ var SessionPickerComponent = class extends Container {
144177
144413
  this.onSelect = opts.onSelect;
144178
144414
  this.onCancel = opts.onCancel;
144179
144415
  this.onDelete = opts.onDelete;
144416
+ this.onDeleteMany = opts.onDeleteMany;
144180
144417
  this.onStatus = opts.onStatus;
144181
144418
  this.maxVisibleSessions = opts.maxVisibleSessions ?? 4;
144182
144419
  }
@@ -144194,6 +144431,15 @@ var SessionPickerComponent = class extends Container {
144194
144431
  if (!session) return;
144195
144432
  if (this.confirmingDelete) {
144196
144433
  this.confirmingDelete = false;
144434
+ if (this.selectedIds.size > 0) {
144435
+ if (this.onDeleteMany === void 0) {
144436
+ this.confirmingDelete = true;
144437
+ this.onStatus(t("session_picker.batch_delete_unavailable"));
144438
+ return;
144439
+ }
144440
+ this.onDeleteMany?.([...this.selectedIds]);
144441
+ return;
144442
+ }
144197
144443
  if (session.metadata?.["source"] === "cc-connect") {
144198
144444
  this.onStatus(t("session_picker.cc_restricted"));
144199
144445
  return;
@@ -144209,11 +144455,13 @@ var SessionPickerComponent = class extends Container {
144209
144455
  return;
144210
144456
  }
144211
144457
  if (matchesKey(data, Key.up)) {
144458
+ if (this.confirmingDelete) return;
144212
144459
  this.selectedIndex = Math.max(0, this.selectedIndex - 1);
144213
144460
  this.confirmingDelete = false;
144214
144461
  return;
144215
144462
  }
144216
144463
  if (matchesKey(data, Key.down)) {
144464
+ if (this.confirmingDelete) return;
144217
144465
  this.selectedIndex = Math.min(this.sessions.length - 1, this.selectedIndex + 1);
144218
144466
  this.confirmingDelete = false;
144219
144467
  return;
@@ -144228,6 +144476,26 @@ var SessionPickerComponent = class extends Container {
144228
144476
  }
144229
144477
  if (session.id !== this.currentSessionId) this.confirmingDelete = true;
144230
144478
  }
144479
+ if (k === " " && !this.confirmingDelete) {
144480
+ const session = this.sessions[this.selectedIndex];
144481
+ if (!session || session.id === this.currentSessionId) return;
144482
+ if (session.metadata?.["source"] === "cc-connect") {
144483
+ this.onStatus(t("session_picker.cc_restricted"));
144484
+ return;
144485
+ }
144486
+ if (this.selectedIds.has(session.id)) this.selectedIds.delete(session.id);
144487
+ else this.selectedIds.add(session.id);
144488
+ return;
144489
+ }
144490
+ if (k === "a" || k === "A") {
144491
+ if (this.confirmingDelete) return;
144492
+ if (this.sessions.length === 0) return;
144493
+ const selectable = this.sessions.filter((s) => s.id !== this.currentSessionId && s.metadata?.["source"] !== "cc-connect");
144494
+ if (selectable.length === 0) return;
144495
+ if (selectable.every((s) => this.selectedIds.has(s.id))) this.selectedIds.clear();
144496
+ else for (const s of selectable) this.selectedIds.add(s.id);
144497
+ return;
144498
+ }
144231
144499
  }
144232
144500
  render(width) {
144233
144501
  const colors = this.colors;
@@ -144245,7 +144513,7 @@ var SessionPickerComponent = class extends Container {
144245
144513
  return lines;
144246
144514
  }
144247
144515
  const headerLabel = t("session.picker_title");
144248
- const headerHint = this.confirmingDelete ? t("session.delete_confirm") : t("session.picker_hint");
144516
+ const headerHint = this.confirmingDelete ? this.selectedIds.size > 0 ? t("session_picker.batch_delete_confirm", { count: String(this.selectedIds.size) }) : t("session.delete_confirm") : this.selectedIds.size > 0 ? t("session_picker.batch_hint", { count: String(this.selectedIds.size) }) : t("session.picker_hint");
144249
144517
  const labelWidth = visibleWidth(headerLabel);
144250
144518
  const shownHint = truncateToWidth(headerHint, Math.max(0, width - labelWidth), ELLIPSIS);
144251
144519
  const hintColor = this.confirmingDelete ? colors.warning : colors.textMuted;
@@ -144271,6 +144539,7 @@ var SessionPickerComponent = class extends Container {
144271
144539
  renderSessionCard(width, session, isSelected, isCurrent) {
144272
144540
  const colors = this.colors;
144273
144541
  const pointer = isSelected ? "❯" : " ";
144542
+ const check = this.selectedIds.has(session.id) ? "☑" : "□";
144274
144543
  const indent = " ";
144275
144544
  const indentWidth = visibleWidth(indent);
144276
144545
  const titleColor = isSelected ? colors.primary : colors.text;
@@ -144284,14 +144553,16 @@ var SessionPickerComponent = class extends Container {
144284
144553
  });
144285
144554
  const trailingParts = [time, badge].filter((p) => p.length > 0);
144286
144555
  const trailingWidth = visibleWidth(trailingParts.length > 0 ? " " + trailingParts.join(" ") : "");
144287
- const headerPrefixWidth = visibleWidth(pointer) + 1;
144556
+ const headerPrefixWidth = visibleWidth(pointer) + 1 + visibleWidth(check) + 1;
144288
144557
  const titleBudget = Math.max(8, width - headerPrefixWidth - trailingWidth);
144289
144558
  const shownTitle = truncateToWidth(singleLine(titleSource), titleBudget, ELLIPSIS);
144559
+ const checkColor = this.selectedIds.has(session.id) ? colors.primary : colors.textDim;
144290
144560
  let header = chalk.hex(isSelected ? colors.primary : colors.textDim)(pointer + " ");
144561
+ header += chalk.hex(checkColor)(check + " ");
144291
144562
  header += titleStyle(shownTitle);
144292
144563
  if (time.length > 0) header += " " + chalk.hex(colors.textDim)(time);
144293
144564
  if (badge.length > 0) header += " " + chalk.hex(colors.success)(badge);
144294
- const card = [header];
144565
+ const card = [truncateToWidth(header, width)];
144295
144566
  const fullId = session.id;
144296
144567
  const idWidth = visibleWidth(fullId);
144297
144568
  const metaGap = " ";
@@ -145036,6 +145307,27 @@ var DialogManager = class {
145036
145307
  }).catch((error) => {
145037
145308
  this.host.showError(error instanceof Error ? error.message : String(error));
145038
145309
  });
145310
+ },
145311
+ onDeleteMany: (sessionIds) => {
145312
+ const ccIds = new Set(this.host.getSessions().filter((s) => s.metadata?.["source"] === "cc-connect").map((s) => s.id));
145313
+ const deletable = sessionIds.filter((id) => !ccIds.has(id));
145314
+ if (deletable.length === 0) {
145315
+ this.host.showStatus(t("dialog.cc_managed"));
145316
+ return;
145317
+ }
145318
+ (async () => {
145319
+ for (const id of deletable) try {
145320
+ await this.host.deleteSession(id);
145321
+ } catch (error) {
145322
+ this.host.showError(error instanceof Error ? error.message : String(error));
145323
+ break;
145324
+ }
145325
+ await this.host.fetchSessions();
145326
+ if (this.host.getSessions().length === 0) this.hideSessionPicker();
145327
+ else if (this.host.state.activeDialog === "session-picker") this.mountSessionPicker(onCancel);
145328
+ })().catch((error) => {
145329
+ this.host.showError(error instanceof Error ? error.message : String(error));
145330
+ });
145039
145331
  }
145040
145332
  }));
145041
145333
  }
@@ -145170,6 +145462,7 @@ function createInitialAppState(input) {
145170
145462
  contextUsage: 0,
145171
145463
  contextTokens: 0,
145172
145464
  maxContextTokens: 0,
145465
+ providerBalance: null,
145173
145466
  isCompacting: false,
145174
145467
  lastCompactionFinishedAt: void 0,
145175
145468
  autoCompactionCount: 0,
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-D95_TREY.mjs")).main();
9
+ (await import("./app-zSNn5qdo.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);
@@ -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 { t as TextInputDialogComponent } from "./text-input-dialog-BdUmo73M.mjs";
6
+ import { t as TextInputDialogComponent } from "./text-input-dialog-DYHGTG6m.mjs";
7
7
  export { TextInputDialogComponent };
@@ -431,6 +431,9 @@ const dictionaries = {
431
431
  "like.other": "其他偏好",
432
432
  "like.other_hint": "例如:多说例子、先给结论再展开、避免术语等(留空表示不设置)",
433
433
  "like.other_example": "例如:请用中文回答,避免缩写",
434
+ "like.do_not": "用户禁止",
435
+ "like.do_not_hint": "用户明确不让做的事。Agent 绝不能做这些。",
436
+ "like.do_not_example": "例如:未经询问绝不修改用户的配置文件",
434
437
  "like.cancelled": "已取消 /like 设置",
435
438
  "like.saved": "偏好已保存(下次新会话生效)",
436
439
  "revoke.streaming": "无法在 streaming 中撤回 — 请先按 Esc 或 Ctrl-C 取消。",
@@ -470,9 +473,12 @@ const dictionaries = {
470
473
  "session.loading": "正在加载会话...",
471
474
  "session_picker.empty": "未找到会话。按 Escape 关闭。",
472
475
  "session_picker.cc_restricted": "CC专属会话不支持切换或删除,请点击或复制下方文件路径进入手动管理",
476
+ "session_picker.batch_delete_confirm": "删除选中的 {count} 个会话?",
477
+ "session_picker.batch_delete_unavailable": "此处不支持批量删除。",
478
+ "session_picker.batch_hint": "[空格] 选择 · [a] 全选 · [d] 删除选中({count})",
473
479
  "session.picker_title": "会话 ",
474
480
  "session.delete_confirm": "⚠️ 按 Enter 确认删除,Esc 取消",
475
- "session.picker_hint": "(↑↓ 导航,Enter 选择,d 删除,Esc 取消)",
481
+ "session.picker_hint": "(↑↓ 导航,Enter 打开,空格 标记,a 全选,d 删除,Esc 取消)",
476
482
  "model.nav_model": "↑↓ 模型",
477
483
  "model.nav_thinking": "←→ 思考等级",
478
484
  "model.nav_page": "PgUp/PgDn 翻页",
@@ -535,13 +541,16 @@ const dictionaries = {
535
541
  "memory.search_label": "搜索: ",
536
542
  "memory.notebook_title": "记忆备忘录 ",
537
543
  "memory.esc_clear_search": "(Esc 清除搜索)",
538
- "memory.nav_hint": "(↑↓ 导航,Enter 查看,i 注入,d 删除,/ 搜索,Esc 关闭)",
544
+ "memory.nav_hint": "(↑↓ 导航,Enter 查看,空格 标记,a 全选,i 注入,d 删除,/ 搜索,Esc 关闭)",
539
545
  "memory.loading": "正在加载...",
540
546
  "memory.no_match": "未找到匹配 \"{query}\" 的记忆。",
541
547
  "memory.empty": "暂无记忆备忘录。",
542
548
  "memory.auto_extract_hint": " 压缩对话或退出会话时,系统会自动提取并保存。",
543
549
  "memory.deleting": "删除: ",
544
550
  "memory.delete_confirm_hint": " 按 Enter 确认删除,Esc 取消",
551
+ "memory.batch_delete_confirm": "删除选中的 {count} 条记忆?",
552
+ "memory.batch_delete_hint": " 按 Enter 确认批量删除,Esc 取消",
553
+ "memory.batch_hint": "已选 {count} 条 · [空格] 选择 · [a] 全选 · [d] 删除选中",
545
554
  "memory.showing_range": "{start}-{end} / {total} 条",
546
555
  "memory.id_label": "ID: ",
547
556
  "memory.source_label": "来源: ",
@@ -724,6 +733,8 @@ const dictionaries = {
724
733
  "subagent.bind_title": "绑定 {profile}",
725
734
  "subagent.model_hint": "↑↓ 选择模型 · Enter 确认 · Esc 返回",
726
735
  "subagent.save_failed": "保存失败:{msg}",
736
+ "subagent.stale_binding": "{alias}(已失效)",
737
+ "subagent.invalid_alias": "模型别名 {alias} 不存在",
727
738
  "kdoctree.no_title": "(无标题)",
728
739
  "kdoctree.empty": "(空)",
729
740
  "kdoctree.move": "移动",
@@ -1468,6 +1479,9 @@ const dictionaries = {
1468
1479
  "like.other": "Other preferences",
1469
1480
  "like.other_hint": "e.g. more examples, conclusion first, avoid jargon (leave empty to skip)",
1470
1481
  "like.other_example": "e.g. Answer in English, avoid abbreviations",
1482
+ "like.do_not": "User Prohibitions",
1483
+ "like.do_not_hint": "Things the user explicitly does NOT want done. The agent must NEVER do these.",
1484
+ "like.do_not_example": "e.g. Never modify user config files without asking",
1471
1485
  "like.cancelled": "/like setup cancelled",
1472
1486
  "like.saved": "Preferences saved (takes effect in next session)",
1473
1487
  "revoke.streaming": "Cannot revoke during streaming — press Esc or Ctrl-C to cancel first.",
@@ -1507,9 +1521,12 @@ const dictionaries = {
1507
1521
  "session.loading": "Loading sessions...",
1508
1522
  "session_picker.empty": "No sessions found. Press Escape to close.",
1509
1523
  "session_picker.cc_restricted": "CC sessions cannot be switched or deleted. Use the file path below to manage manually",
1524
+ "session_picker.batch_delete_confirm": "Delete {count} selected sessions?",
1525
+ "session_picker.batch_delete_unavailable": "Batch delete is not available here.",
1526
+ "session_picker.batch_hint": "[space] select · [a] all · [d] delete selected ({count})",
1510
1527
  "session.picker_title": "Sessions ",
1511
1528
  "session.delete_confirm": "⚠️ Press Enter to confirm delete, Esc to cancel",
1512
- "session.picker_hint": "(↑↓ Navigate, Enter Select, d Delete, Esc Cancel)",
1529
+ "session.picker_hint": "(↑↓ Navigate, Enter Open, Space Mark, a All, d Delete, Esc Cancel)",
1513
1530
  "model.nav_model": "↑↓ Model",
1514
1531
  "model.nav_thinking": "←→ Thinking",
1515
1532
  "model.nav_page": "PgUp/PgDn Page",
@@ -1572,13 +1589,16 @@ const dictionaries = {
1572
1589
  "memory.search_label": "Search: ",
1573
1590
  "memory.notebook_title": "Memory Notebook ",
1574
1591
  "memory.esc_clear_search": "(Esc clear search)",
1575
- "memory.nav_hint": "(↑↓ Navigate, Enter View, i Inject, d Delete, / Search, Esc Close)",
1592
+ "memory.nav_hint": "(↑↓ Navigate, Enter View, Space Mark, a All, i Inject, d Delete, / Search, Esc Close)",
1576
1593
  "memory.loading": "Loading...",
1577
1594
  "memory.no_match": "No memories matching \"{query}\".",
1578
1595
  "memory.empty": "No memories yet.",
1579
1596
  "memory.auto_extract_hint": " Memories are extracted automatically when compacting or exiting a session.",
1580
1597
  "memory.deleting": "Delete: ",
1581
1598
  "memory.delete_confirm_hint": " Press Enter to confirm delete, Esc to cancel",
1599
+ "memory.batch_delete_confirm": "Delete {count} selected memos?",
1600
+ "memory.batch_delete_hint": " Press Enter to confirm batch delete, Esc to cancel",
1601
+ "memory.batch_hint": "{count} selected · [space] select · [a] all · [d] delete selected",
1582
1602
  "memory.showing_range": "{start}-{end} / {total} items",
1583
1603
  "memory.id_label": "ID: ",
1584
1604
  "memory.source_label": "Source: ",
@@ -1761,6 +1781,8 @@ const dictionaries = {
1761
1781
  "subagent.bind_title": "Bind {profile}",
1762
1782
  "subagent.model_hint": "↑↓ Select model · Enter confirm · Esc back",
1763
1783
  "subagent.save_failed": "Save failed: {msg}",
1784
+ "subagent.stale_binding": "{alias} (stale)",
1785
+ "subagent.invalid_alias": "Model alias {alias} does not exist",
1764
1786
  "kdoctree.no_title": "(untitled)",
1765
1787
  "kdoctree.empty": "(empty)",
1766
1788
  "kdoctree.move": "Move",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.11.7",
3
+ "version": "0.11.8",
4
4
  "description": "A terminal-native AI agent for builders",
5
5
  "license": "MIT",
6
6
  "author": "ScreamCli",
@@ -59,7 +59,7 @@
59
59
  "smoke": "node dist/main.mjs --version"
60
60
  },
61
61
  "dependencies": {
62
- "@liutod-scream/pi-tui": "^0.80.31",
62
+ "@liutod-scream/pi-tui": "^0.80.32",
63
63
  "@mariozechner/clipboard": "^0.3.2",
64
64
  "chalk": "^5.4.1",
65
65
  "cli-highlight": "^2.1.11",