scream-code 0.12.0 → 0.12.2

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 CHANGED
@@ -38,7 +38,7 @@ Scream Code is a hassle-free, locally deployable, all-in-one AI Agent assistant.
38
38
  </td>
39
39
  <td width="50%">
40
40
  <h3>🐺 Wolfpack Mode</h3>
41
- <p><strong>Unlimited intelligent batch concurrency</strong> — multiple Agents collaborate in parallel. Built-in coder/explore/plan/verify/reviewer/oracle/writer sub-agents. No item limit.</p>
41
+ <p><strong>Unlimited intelligent batch concurrency</strong> — multiple Agents collaborate in parallel. Built-in coder/explore/plan/verify/reviewer/oracle/writer/worker sub-agents. No item limit.</p>
42
42
  </td>
43
43
  </tr>
44
44
  <tr>
@@ -240,7 +240,7 @@ Scream Code 是一款省心的可在本地部署的全能 AI Agent 助手。你
240
240
  </td>
241
241
  <td width="50%">
242
242
  <h3>🐺 Wolfpack 群狼模式</h3>
243
- <p><strong>无限制智能批量并发</strong>多Agent协同,并行处理大项目任务。内置coder/explore/plan/verify/reviewer/oracle/writer 等多种子 Agent,精准识别任务类型,item 数量无上限。</p>
243
+ <p><strong>无限制智能批量并发</strong>多Agent协同,并行处理大项目任务。内置coder/explore/plan/verify/reviewer/oracle/writer/worker 等多种子 Agent,精准识别任务类型,item 数量无上限。</p>
244
244
  </td>
245
245
  </tr>
246
246
  <tr>
@@ -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-DLkaeiFf.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-DHhRrX3G.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";
@@ -54,7 +54,7 @@ import { AsyncLocalStorage } from "node:async_hooks";
54
54
  import { Command, Option } from "commander";
55
55
  import { createInterface } from "node:readline/promises";
56
56
  import chalk, { chalkStderr } from "chalk";
57
- import { CombinedAutocompleteProvider, Container, Editor, Image, Input, Key, Markdown, ProcessTerminal, Spacer, TUI, Text, decodeKittyPrintable, deleteAllKittyImages, fuzzyFilter, fuzzyMatch, getCapabilities, getImageDimensions, isKeyRelease, matchesKey, setTightMode, sliceByColumn, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@liutod-scream/pi-tui";
57
+ import { CombinedAutocompleteProvider, Container, Editor, FlexSpacer, Image, Input, Key, Markdown, ProcessTerminal, Spacer, TUI, Text, decodeKittyPrintable, deleteAllKittyImages, fuzzyFilter, fuzzyMatch, getCapabilities, getImageDimensions, isKeyRelease, matchesKey, setTightMode, sliceByColumn, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@liutod-scream/pi-tui";
58
58
  import { highlight, supportsLanguage } from "cli-highlight";
59
59
  import { diffWords } from "diff";
60
60
  import { gt, valid } from "semver";
@@ -214,7 +214,7 @@ const SCREAM_ERROR_INFO = {
214
214
  title: "Invalid permission mode",
215
215
  retryable: false,
216
216
  public: true,
217
- action: "Use one of: yolo / manual / auto."
217
+ action: "Use one of: yolo / manual / auto / ask."
218
218
  },
219
219
  "session.thinking_empty": {
220
220
  title: "Thinking value is empty",
@@ -66341,7 +66341,8 @@ const ThinkingConfigSchema = z.object({
66341
66341
  const PermissionModeSchema = z.enum([
66342
66342
  "yolo",
66343
66343
  "manual",
66344
- "auto"
66344
+ "auto",
66345
+ "ask"
66345
66346
  ]);
66346
66347
  const PermissionRuleDecisionSchema = z.enum([
66347
66348
  "allow",
@@ -79129,6 +79130,13 @@ const AUTO_MODE_ENTER_REMINDER = [
79129
79130
  " - Do NOT call AskUserQuestion while auto mode is active. Make a reasonable decision and continue without asking the user."
79130
79131
  ].join("\n");
79131
79132
  const AUTO_MODE_EXIT_REMINDER = ["Auto permission mode is no longer active. Tool approvals and permission checks are back to the current mode.", " - Continue normally, but expect approval prompts or denials when a tool requires them."].join("\n");
79133
+ const ASK_MODE_ENTER_REMINDER = [
79134
+ "Ask mode is active — read-only Q&A. You are here to discuss, not to act.",
79135
+ " - You may read files, search the codebase, and use the web to analyse.",
79136
+ " - Do NOT modify any files, run shell commands, schedule work, or call MCP tools.",
79137
+ " - Answer the user directly in conversation. If they want changes, they will ask and switch out of Ask mode."
79138
+ ].join("\n");
79139
+ const ASK_MODE_EXIT_REMINDER = ["Ask mode is no longer active. You may modify files and run commands again as the current permission mode allows.", " - Resume normal work. The previous read-only Q&A constraint is lifted."].join("\n");
79132
79140
  var PermissionModeInjector = class extends DynamicInjector {
79133
79141
  injectionVariant = "permission_mode";
79134
79142
  lastMode;
@@ -79137,8 +79145,12 @@ var PermissionModeInjector = class extends DynamicInjector {
79137
79145
  const previousMode = this.lastMode;
79138
79146
  if (mode === previousMode) return void 0;
79139
79147
  this.lastMode = mode;
79140
- if (mode === "auto") return AUTO_MODE_ENTER_REMINDER;
79141
- if (previousMode === "auto") return AUTO_MODE_EXIT_REMINDER;
79148
+ const parts = [];
79149
+ if (mode === "auto") parts.push(AUTO_MODE_ENTER_REMINDER);
79150
+ if (previousMode === "auto") parts.push(AUTO_MODE_EXIT_REMINDER);
79151
+ if (mode === "ask") parts.push(ASK_MODE_ENTER_REMINDER);
79152
+ if (previousMode === "ask") parts.push(ASK_MODE_EXIT_REMINDER);
79153
+ return parts.length > 0 ? parts.join("\n\n") : void 0;
79142
79154
  }
79143
79155
  };
79144
79156
  //#endregion
@@ -79731,6 +79743,89 @@ var InjectionManager = class {
79731
79743
  }
79732
79744
  };
79733
79745
  //#endregion
79746
+ //#region ../../packages/agent-core/src/mcp/tool-naming.ts
79747
+ const MCP_NAME_PREFIX = "mcp__";
79748
+ const MCP_NAME_SEPARATOR = "__";
79749
+ /**
79750
+ * Most LLM providers cap tool names around 64 characters. Leave headroom
79751
+ * for the prefix and a separator and truncate longer names with a stable
79752
+ * hash suffix so collisions remain extremely unlikely.
79753
+ */
79754
+ const MAX_QUALIFIED_LENGTH = 64;
79755
+ /**
79756
+ * Replace any character outside the safe ASCII set with `_`, then collapse
79757
+ * any run of `_` into a single underscore. The collapse step guarantees neither the sanitized server
79758
+ * nor tool name contains the `__` separator used by {@link qualifyMcpToolName},
79759
+ * which lets {@link isMcpToolName}-aware decoders split unambiguously on the
79760
+ * first `__` after the prefix.
79761
+ */
79762
+ function sanitizeMcpNamePart(part) {
79763
+ return part.replaceAll(/[^a-zA-Z0-9_-]/g, "_").replaceAll(/_+/g, "_");
79764
+ }
79765
+ function isMcpToolName(name) {
79766
+ return name.startsWith(MCP_NAME_PREFIX);
79767
+ }
79768
+ /**
79769
+ * Produce the qualified MCP tool name used inside the agent and on the wire.
79770
+ * If the result would exceed {@link MAX_QUALIFIED_LENGTH}, a deterministic
79771
+ * 8-char hash suffix replaces the tail so the prefix structure stays intact.
79772
+ */
79773
+ function qualifyMcpToolName(serverName, toolName) {
79774
+ const full = `${MCP_NAME_PREFIX}${sanitizeMcpNamePart(serverName)}${MCP_NAME_SEPARATOR}${sanitizeMcpNamePart(toolName)}`;
79775
+ if (full.length <= MAX_QUALIFIED_LENGTH) return full;
79776
+ const hash = stableHash8(full);
79777
+ return `${full.slice(0, MAX_QUALIFIED_LENGTH - hash.length - 1)}_${hash}`;
79778
+ }
79779
+ function stableHash8(input) {
79780
+ let hash = 2166136261;
79781
+ for (let i = 0; i < input.length; i++) {
79782
+ hash ^= input.codePointAt(i);
79783
+ hash = Math.trunc(Math.imul(hash, 16777619));
79784
+ }
79785
+ return hash.toString(16).padStart(8, "0");
79786
+ }
79787
+ //#endregion
79788
+ //#region ../../packages/agent-core/src/agent/permission/policies/ask-mode-guard-deny.ts
79789
+ /**
79790
+ * Denies every mutating tool while ask mode is active. Ask mode is a
79791
+ * read-only Q&A mode: the model may read, search, and analyse, then answer
79792
+ * in conversation — it must never modify the filesystem, run shell commands,
79793
+ * schedule work, or reach a mutating MCP server tool. The deny messages are
79794
+ * deliberately instructive: they tell the model what ask mode is for and
79795
+ * point it back to answering, so a blocked call converges instead of looping.
79796
+ */
79797
+ var AskModeGuardDenyPermissionPolicy = class {
79798
+ agent;
79799
+ name = "ask-mode-guard-deny";
79800
+ constructor(agent) {
79801
+ this.agent = agent;
79802
+ }
79803
+ evaluate(context) {
79804
+ if (this.agent.permission.mode !== "ask") return;
79805
+ const toolName = context.toolCall.name;
79806
+ if (toolName === "Bash") return {
79807
+ kind: "deny",
79808
+ message: "Bash is not available in Ask mode — it could modify the system. Ask mode is read-only Q&A: answer the user directly in conversation instead of running commands."
79809
+ };
79810
+ if (toolName === "Write" || toolName === "Edit") return {
79811
+ kind: "deny",
79812
+ message: `${toolName} is not available in Ask mode. Ask mode is read-only Q&A: answer the user directly in conversation instead of modifying files. Exit Ask mode when you are ready to make changes.`
79813
+ };
79814
+ if (toolName === "CronCreate" || toolName === "CronDelete") return {
79815
+ kind: "deny",
79816
+ message: `${toolName} is not available in Ask mode because it schedules work that mutates the system. Answer the user directly in conversation instead.`
79817
+ };
79818
+ if (toolName === "TaskStop") return {
79819
+ kind: "deny",
79820
+ message: "TaskStop is not available in Ask mode. Answer the user directly in conversation instead."
79821
+ };
79822
+ if (isMcpToolName(toolName)) return {
79823
+ kind: "deny",
79824
+ message: `${toolName} is not available in Ask mode — MCP tools may mutate external systems. Answer the user directly in conversation instead.`
79825
+ };
79826
+ }
79827
+ };
79828
+ //#endregion
79734
79829
  //#region ../../packages/agent-core/src/agent/permission/policies/auto-mode-approve.ts
79735
79830
  var AutoModeApprovePermissionPolicy = class {
79736
79831
  agent;
@@ -80324,6 +80419,7 @@ function createPermissionDecisionPolicies(agent) {
80324
80419
  new PreToolCallHookPermissionPolicy(agent),
80325
80420
  new AutoModeAskUserQuestionDenyPermissionPolicy(agent),
80326
80421
  new PlanModeGuardDenyPermissionPolicy(agent),
80422
+ new AskModeGuardDenyPermissionPolicy(agent),
80327
80423
  new UserConfiguredDenyPermissionPolicy(agent),
80328
80424
  new AutoModeApprovePermissionPolicy(agent),
80329
80425
  new SessionApprovalHistoryPermissionPolicy(agent),
@@ -89841,8 +89937,13 @@ async function recordUnexecutedToolCalls(step, response) {
89841
89937
  }
89842
89938
  }
89843
89939
  async function runToolCallBatch(step, response) {
89844
- if (response.toolCalls.length === 0) return { stopTurn: false };
89940
+ if (response.toolCalls.length === 0) return {
89941
+ stopTurn: false,
89942
+ rejectedCount: 0,
89943
+ totalCalls: 0
89944
+ };
89845
89945
  const calls = response.toolCalls.map((toolCall) => preflightToolCall(step.tools, toolCall));
89946
+ const rejectedCount = calls.filter((call) => call.kind === "rejected").length;
89846
89947
  const scheduler = new ToolScheduler();
89847
89948
  const pendingResults = [];
89848
89949
  let stopTurn = false;
@@ -89893,7 +89994,11 @@ async function runToolCallBatch(step, response) {
89893
89994
  if (steerPoll !== void 0) clearInterval(steerPoll);
89894
89995
  await Promise.allSettled(pendingResults);
89895
89996
  }
89896
- return { stopTurn };
89997
+ return {
89998
+ stopTurn,
89999
+ rejectedCount,
90000
+ totalCalls: response.toolCalls.length
90001
+ };
89897
90002
  }
89898
90003
  /**
89899
90004
  * Provider-order validation pass. It does not run hooks, spawn tools, or write
@@ -90018,7 +90123,21 @@ async function prepareToolCall(step, call) {
90018
90123
  stopBatchAfterThis: toolResultStopsTurn(coerced)
90019
90124
  };
90020
90125
  };
90021
- if (call.kind === "rejected") return settleError(call.args, call.output);
90126
+ if (call.kind === "rejected") {
90127
+ let reminder;
90128
+ try {
90129
+ reminder = await step.hooks?.onToolCallRejected?.({
90130
+ toolCallId: call.toolCall.id,
90131
+ toolName: call.toolName,
90132
+ args: call.args,
90133
+ rawArguments: call.toolCall.arguments
90134
+ });
90135
+ } catch {
90136
+ reminder = null;
90137
+ }
90138
+ const output = reminder ? `${call.output}\n${reminder}` : call.output;
90139
+ return settleError(call.args, output);
90140
+ }
90022
90141
  const decision = await runPrepareToolExecutionHook(step, call);
90023
90142
  if (decision.kind === "blocked" || decision.kind === "hookFailed") return settleError(decision.args, decision.output);
90024
90143
  if (decision.kind === "synthetic") return settleSynthetic(decision.args, decision.result);
@@ -90458,8 +90577,13 @@ async function executeLoopStep(deps) {
90458
90577
  const stopTurnAfterUsage = (await recordUsage(usage))?.stopTurn === true;
90459
90578
  const stopReason = deriveStepStopReason(response);
90460
90579
  let effectiveStopReason = stopTurnAfterUsage && stopReason === "tool_use" ? "end_turn" : stopReason;
90580
+ let rejectedCalls = 0;
90581
+ let totalCalls = 0;
90461
90582
  if (effectiveStopReason === "tool_use") {
90462
- if ((await runToolCallBatch(step, response)).stopTurn) effectiveStopReason = "end_turn";
90583
+ const toolBatch = await runToolCallBatch(step, response);
90584
+ rejectedCalls = toolBatch.rejectedCount;
90585
+ totalCalls = toolBatch.totalCalls;
90586
+ if (toolBatch.stopTurn) effectiveStopReason = "end_turn";
90463
90587
  } else if ((stopReason === "paused" || stopReason === "unknown" || stopReason === "max_tokens") && response.toolCalls.length > 0) await recordUnexecutedToolCalls(step, response);
90464
90588
  signal.throwIfAborted();
90465
90589
  await dispatchEvent({
@@ -90487,7 +90611,9 @@ async function executeLoopStep(deps) {
90487
90611
  } catch {}
90488
90612
  return {
90489
90613
  usage,
90490
- stopReason: stopTurnAfterStep && effectiveStopReason === "tool_use" ? "end_turn" : effectiveStopReason
90614
+ stopReason: stopTurnAfterStep && effectiveStopReason === "tool_use" ? "end_turn" : effectiveStopReason,
90615
+ rejectedCalls,
90616
+ totalCalls
90491
90617
  };
90492
90618
  }
90493
90619
  function deriveStepStopReason(response) {
@@ -90564,10 +90690,20 @@ function createChatStreamingCallbacks(deps) {
90564
90690
  * enforcement, usage aggregation, optional continuation after non-tool stops,
90565
90691
  * and final `TurnResult` mapping. One-step execution lives in `turn-step.ts`.
90566
90692
  */
90693
+ /**
90694
+ * Consecutive steps whose tool calls were ALL rejected during preflight
90695
+ * (unknown tool / malformed args) before the turn stops. Deliberately
90696
+ * generous — the repeat breaker's 3/5/8 reminders fire first and let a
90697
+ * healthy model self-correct, so this final breaker only trips on genuine
90698
+ * model failure after enough fully-rejected steps (8) to never hit a
90699
+ * one-off slip.
90700
+ */
90701
+ const MAX_CONSECUTIVE_REJECTED_STEPS = 8;
90567
90702
  async function runTurn(input) {
90568
90703
  const { turnId, signal, llm, buildMessages, dispatchEvent, tools, buildTools, hooks, log, maxSteps, maxRetryAttempts, recordStepUsage: hostRecordStepUsage } = input;
90569
90704
  let usage = emptyUsage();
90570
90705
  let steps = 0;
90706
+ let consecutiveRejectedSteps = 0;
90571
90707
  let stopReason = "end_turn";
90572
90708
  let activeStep;
90573
90709
  const mediaProjection = { mode: "normal" };
@@ -90598,6 +90734,15 @@ async function runTurn(input) {
90598
90734
  mediaProjection
90599
90735
  });
90600
90736
  activeStep = void 0;
90737
+ if (stepResult.totalCalls > 0) {
90738
+ if (stepResult.rejectedCalls === stepResult.totalCalls) consecutiveRejectedSteps += 1;
90739
+ else consecutiveRejectedSteps = 0;
90740
+ if (consecutiveRejectedSteps >= MAX_CONSECUTIVE_REJECTED_STEPS) {
90741
+ log?.warn(`repeated invalid tool calls — stopping turn after ${consecutiveRejectedSteps} consecutive all-rejected steps`);
90742
+ stopReason = "end_turn";
90743
+ break;
90744
+ }
90745
+ }
90601
90746
  if (stepResult.stopReason === "tool_use") continue;
90602
90747
  const terminalStopReason = stepResult.stopReason;
90603
90748
  stopReason = terminalStopReason;
@@ -94111,48 +94256,6 @@ function wrapAuthError(prefix, error) {
94111
94256
  return /* @__PURE__ */ new Error(`${prefix}: ${String(error)}`);
94112
94257
  }
94113
94258
  //#endregion
94114
- //#region ../../packages/agent-core/src/mcp/tool-naming.ts
94115
- const MCP_NAME_PREFIX = "mcp__";
94116
- const MCP_NAME_SEPARATOR = "__";
94117
- /**
94118
- * Most LLM providers cap tool names around 64 characters. Leave headroom
94119
- * for the prefix and a separator and truncate longer names with a stable
94120
- * hash suffix so collisions remain extremely unlikely.
94121
- */
94122
- const MAX_QUALIFIED_LENGTH = 64;
94123
- /**
94124
- * Replace any character outside the safe ASCII set with `_`, then collapse
94125
- * any run of `_` into a single underscore. The collapse step guarantees neither the sanitized server
94126
- * nor tool name contains the `__` separator used by {@link qualifyMcpToolName},
94127
- * which lets {@link isMcpToolName}-aware decoders split unambiguously on the
94128
- * first `__` after the prefix.
94129
- */
94130
- function sanitizeMcpNamePart(part) {
94131
- return part.replaceAll(/[^a-zA-Z0-9_-]/g, "_").replaceAll(/_+/g, "_");
94132
- }
94133
- function isMcpToolName(name) {
94134
- return name.startsWith(MCP_NAME_PREFIX);
94135
- }
94136
- /**
94137
- * Produce the qualified MCP tool name used inside the agent and on the wire.
94138
- * If the result would exceed {@link MAX_QUALIFIED_LENGTH}, a deterministic
94139
- * 8-char hash suffix replaces the tail so the prefix structure stays intact.
94140
- */
94141
- function qualifyMcpToolName(serverName, toolName) {
94142
- const full = `${MCP_NAME_PREFIX}${sanitizeMcpNamePart(serverName)}${MCP_NAME_SEPARATOR}${sanitizeMcpNamePart(toolName)}`;
94143
- if (full.length <= MAX_QUALIFIED_LENGTH) return full;
94144
- const hash = stableHash8(full);
94145
- return `${full.slice(0, MAX_QUALIFIED_LENGTH - hash.length - 1)}_${hash}`;
94146
- }
94147
- function stableHash8(input) {
94148
- let hash = 2166136261;
94149
- for (let i = 0; i < input.length; i++) {
94150
- hash ^= input.codePointAt(i);
94151
- hash = Math.trunc(Math.imul(hash, 16777619));
94152
- }
94153
- return hash.toString(16).padStart(8, "0");
94154
- }
94155
- //#endregion
94156
94259
  //#region ../../packages/agent-core/src/mcp/auth-tool.ts
94157
94260
  /**
94158
94261
  * Synthetic `mcp__<server>__authenticate` tool.
@@ -95588,6 +95691,45 @@ var ToolCallDeduplicator = class {
95588
95691
  return null;
95589
95692
  }
95590
95693
  /**
95694
+ * Projects the consecutive streak that a call at `index` in the current
95695
+ * step's call list ends, extending the cross-step streak carried in
95696
+ * `consecutiveKey`/`consecutiveCount`.
95697
+ */
95698
+ projectStreak(index) {
95699
+ let lastKey = this.consecutiveKey;
95700
+ let streak = this.consecutiveCount;
95701
+ for (let i = 0; i <= index; i += 1) {
95702
+ const k = this.stepCalls[i];
95703
+ if (k === lastKey) streak += 1;
95704
+ else {
95705
+ lastKey = k;
95706
+ streak = 1;
95707
+ }
95708
+ }
95709
+ return streak;
95710
+ }
95711
+ /**
95712
+ * Registers a tool call that never reached `prepareToolExecution` — e.g.
95713
+ * preflight rejected it (unknown tool / malformed args) — so the repeat
95714
+ * breaker can count it. Without this, re-issuing the same invalid call
95715
+ * would never fire the 3/5/8 reminders. Returns a reminder string to
95716
+ * append to the rejection output when a streak threshold is reached,
95717
+ * else null.
95718
+ *
95719
+ * `rawArguments` is the provider's raw arguments string. Args that failed
95720
+ * JSON parsing are keyed on the raw text so different malformed attempts
95721
+ * do not count as repeats of each other.
95722
+ */
95723
+ registerSkipped(toolCallId, toolName, args, rawArguments) {
95724
+ if (this.callKeyByCallId.has(toolCallId)) return null;
95725
+ const keyArgs = rawArguments !== void 0 && rawArguments !== null && !parseToolCallArguments(rawArguments).success ? rawArguments : args;
95726
+ if (this.checkSameStep(toolCallId, toolName, keyArgs) !== null) return null;
95727
+ const streak = this.projectStreak(this.stepCalls.length - 1);
95728
+ if (streak === 3) return REMINDER_TEXT_1;
95729
+ if (streak === 5 || streak === 8) return makeReminderText2(toolName, streak, args);
95730
+ return null;
95731
+ }
95732
+ /**
95591
95733
  * Called from `finalizeToolResult`, in provider order. For first-occurrence
95592
95734
  * calls, projects the consecutive streak ending at this call and, if the
95593
95735
  * threshold is reached, appends the system reminder, then resolves the
@@ -95607,16 +95749,7 @@ var ToolCallDeduplicator = class {
95607
95749
  const index = this.originalCallIndex.get(toolCallId);
95608
95750
  if (index === void 0) return result;
95609
95751
  this.originalCallIndex.delete(toolCallId);
95610
- let lastKey = this.consecutiveKey;
95611
- let streak = this.consecutiveCount;
95612
- for (let i = 0; i <= index; i += 1) {
95613
- const k = this.stepCalls[i];
95614
- if (k === lastKey) streak += 1;
95615
- else {
95616
- lastKey = k;
95617
- streak = 1;
95618
- }
95619
- }
95752
+ const streak = this.projectStreak(index);
95620
95753
  let finalResult = result;
95621
95754
  if (streak === 3) finalResult = appendReminder(result, REMINDER_TEXT_1);
95622
95755
  else if (streak === 5 || streak === 8) finalResult = appendReminder(result, makeReminderText2(toolName, streak, args));
@@ -95765,6 +95898,7 @@ var TurnFlow = class {
95765
95898
  if (this.activeTurn !== "resuming") this.activeTurn?.controller.abort(reason);
95766
95899
  this.activeTurn = null;
95767
95900
  }
95901
+ maxTokensRecoveryAttempted = false;
95768
95902
  flushSteerBuffer() {
95769
95903
  const steers = this.steerBuffer;
95770
95904
  if (steers.length === 0) return false;
@@ -96055,9 +96189,17 @@ var TurnFlow = class {
96055
96189
  await this.agent.fullCompaction.afterStep();
96056
96190
  deduper.endStep();
96057
96191
  },
96058
- shouldContinueAfterStop: async ({ signal }) => {
96192
+ shouldContinueAfterStop: async ({ signal, stopReason }) => {
96059
96193
  if (this.flushSteerBuffer()) return { continue: true };
96060
96194
  signal.throwIfAborted();
96195
+ if (stopReason === "max_tokens" && !this.maxTokensRecoveryAttempted && this.agent.config.profileName === "agent") {
96196
+ this.maxTokensRecoveryAttempted = true;
96197
+ this.agent.fullCompaction.begin({
96198
+ source: "auto",
96199
+ instruction: "The previous response was truncated by the output limit. Compact the context and continue."
96200
+ });
96201
+ return { continue: true };
96202
+ }
96061
96203
  const latestVerification = this.agent.workingSet.getLatestVerificationForTurn(this.currentTurnId);
96062
96204
  const hasPassedVerificationThisTurn = latestVerification?.passed === true;
96063
96205
  if (this.convergenceInjections < this.MAX_CONVERGENCE_INJECTIONS) {
@@ -96121,6 +96263,7 @@ var TurnFlow = class {
96121
96263
  authorizeToolExecution: async (ctx) => {
96122
96264
  return this.agent.permission.beforeToolCall(ctx);
96123
96265
  },
96266
+ onToolCallRejected: async ({ toolCallId, toolName, args, rawArguments }) => deduper.registerSkipped(toolCallId, toolName, args, rawArguments),
96124
96267
  finalizeToolResult: async (ctx) => {
96125
96268
  const finalResult = await deduper.finalizeResult(ctx.toolCall.id, ctx.toolCall.name, ctx.args, ctx.result);
96126
96269
  const { isError, output } = finalResult;
@@ -120009,7 +120152,7 @@ var Session = class {
120009
120152
  }
120010
120153
  async setPermission(mode) {
120011
120154
  this.ensureOpen();
120012
- if (!isPermissionMode(mode)) throw new ScreamError(ErrorCodes.SESSION_PERMISSION_MODE_INVALID, "Session permission mode must be yolo, manual, or auto");
120155
+ if (!isPermissionMode(mode)) throw new ScreamError(ErrorCodes.SESSION_PERMISSION_MODE_INVALID, "Session permission mode must be yolo, manual, auto, or ask");
120013
120156
  await this.rpc.setPermission({
120014
120157
  sessionId: this.id,
120015
120158
  mode
@@ -120378,7 +120521,7 @@ function normalizeOptionalString$1(value) {
120378
120521
  return normalized.length > 0 ? normalized : void 0;
120379
120522
  }
120380
120523
  function isPermissionMode(value) {
120381
- return value === "yolo" || value === "manual" || value === "auto";
120524
+ return value === "yolo" || value === "manual" || value === "auto" || value === "ask";
120382
120525
  }
120383
120526
  function resumeStateFromSummary(summary) {
120384
120527
  if (!hasResumeState(summary)) return void 0;
@@ -120755,7 +120898,7 @@ function optionalBuildString(value) {
120755
120898
  return typeof value === "string" && value.length > 0 ? value : void 0;
120756
120899
  }
120757
120900
  const SCREAM_BUILD_INFO = {
120758
- version: optionalBuildString("0.12.0"),
120901
+ version: optionalBuildString("0.12.2"),
120759
120902
  channel: optionalBuildString(""),
120760
120903
  commit: optionalBuildString(""),
120761
120904
  buildTarget: optionalBuildString("darwin-arm64")
@@ -121752,6 +121895,13 @@ const BUILTIN_SLASH_COMMANDS = [
121752
121895
  priority: 124,
121753
121896
  availability: "always"
121754
121897
  },
121898
+ {
121899
+ name: "ask",
121900
+ aliases: ["ask"],
121901
+ description: "registry.ask_desc",
121902
+ priority: 122,
121903
+ availability: "always"
121904
+ },
121755
121905
  {
121756
121906
  name: "wolfpack",
121757
121907
  aliases: ["wp"],
@@ -123290,11 +123440,16 @@ function getPermissionOptions() {
123290
123440
  value: "yolo",
123291
123441
  label: "YES",
123292
123442
  description: t("permission.yolo_desc")
123443
+ },
123444
+ {
123445
+ value: "ask",
123446
+ label: "ASK",
123447
+ description: t("permission.ask_desc")
123293
123448
  }
123294
123449
  ];
123295
123450
  }
123296
123451
  function isPermissionModeChoice(value) {
123297
- return value === "manual" || value === "auto" || value === "yolo";
123452
+ return value === "manual" || value === "auto" || value === "yolo" || value === "ask";
123298
123453
  }
123299
123454
  var PermissionSelectorComponent = class extends ChoicePickerComponent {
123300
123455
  constructor(opts) {
@@ -123950,7 +124105,7 @@ function formatTokenCount$1(n) {
123950
124105
  return String(Math.round(n));
123951
124106
  }
123952
124107
  /**
123953
- * Build a `[███░░░░░░░]` style bar. Returns a plain-ASCII string with
124108
+ * Build a `███░░░░░░░` style bar. Returns a plain-ASCII string with
123954
124109
  * `filled`/`empty` glyphs — colouring is the caller's responsibility.
123955
124110
  */
123956
124111
  function renderProgressBar(ratio, width = 20, filled = "█", empty = "░") {
@@ -123988,8 +124143,9 @@ function safeUsage(usage) {
123988
124143
  return safeUsageRatio(usage);
123989
124144
  }
123990
124145
  const CONTEXT_BAR_WIDTH = 10;
123991
- const CONTEXT_BAR_FILLED = "";
123992
- const CONTEXT_BAR_EMPTY = "";
124146
+ const CONTEXT_BAR_DEEP = "";
124147
+ const CONTEXT_BAR_FOAM = "▓▒";
124148
+ const CONTEXT_BAR_AIR = "░";
123993
124149
  const BALANCE_FLASH_MS = 1500;
123994
124150
  function currencySymbol(currency) {
123995
124151
  if (currency === "CNY") return "¥";
@@ -123997,18 +124153,22 @@ function currencySymbol(currency) {
123997
124153
  return `${currency} `;
123998
124154
  }
123999
124155
  /**
124000
- * Half-block progress bar for context usage: `▰▰▰▱▱▱▱▱▱▱` (10 cells).
124001
- * Filled cells are rounded from the clamped ratio, so 0% is all-empty and
124002
- * >=100% is all-filled; NaN/undefined coerce through safeUsageRatio first.
124156
+ * Water-level progress bar for context usage: `█████▓▒░░░` (10 cells).
124157
+ * Used cells read as water solid depth (█) with a foam transition (▓▒)
124158
+ * hugging the water line; unused cells read as air (░). Cell count is
124159
+ * rounded from the clamped ratio, so 0% is all-air and >=100% is all-water;
124160
+ * NaN/undefined coerce through safeUsageRatio first.
124003
124161
  */
124004
124162
  function formatContextBar(usage, width = CONTEXT_BAR_WIDTH) {
124005
124163
  const clamped = Math.min(1, Math.max(0, safeUsageRatio(usage)));
124006
124164
  const filled = Math.round(clamped * width);
124007
- return CONTEXT_BAR_FILLED.repeat(filled) + CONTEXT_BAR_EMPTY.repeat(width - filled);
124165
+ const foam = Math.min(2, filled);
124166
+ const deep = Math.max(0, filled - foam);
124167
+ return `${CONTEXT_BAR_DEEP.repeat(deep) + CONTEXT_BAR_FOAM.slice(0, foam)}${CONTEXT_BAR_AIR.repeat(width - filled)}`;
124008
124168
  }
124009
- function formatContextStatus(usage, tokens, maxTokens) {
124169
+ function formatContextStatus(usage, tokens, maxTokens, barWidth = CONTEXT_BAR_WIDTH) {
124010
124170
  const pct = `${(safeUsage(usage) * 100).toFixed(1)}%`;
124011
- const barAndPct = `${formatContextBar(usage)} ${pct}`;
124171
+ const barAndPct = `${barWidth > 0 ? `${formatContextBar(usage, barWidth)} ` : ""}${pct}`;
124012
124172
  if (maxTokens && maxTokens > 0 && tokens !== void 0) return t("footer.context", {
124013
124173
  pct: barAndPct,
124014
124174
  tokens: formatTokenCount(tokens),
@@ -124258,7 +124418,8 @@ var FooterComponent = class {
124258
124418
  const statusLine = buildStatusLine(state.streamingPhase, state.streamingStartTime, state.reconnectAttempt);
124259
124419
  const ccDot = state.ccConnectActive ? chalk.hex(colors.success)("●") : chalk.hex(colors.textDim)("●");
124260
124420
  const contextColor = pickContextColor(state.contextUsage, colors);
124261
- rightText = `${ccDot} ${chalk.hex(contextColor)(formatContextStatus(state.contextUsage, state.contextTokens, state.maxContextTokens))}${chalk.hex(colors.textDim)(` ${statusLine}`)}`;
124421
+ const contextBarWidth = width >= 68 ? CONTEXT_BAR_WIDTH : width >= 52 ? 6 : 0;
124422
+ rightText = `${ccDot} ${chalk.hex(contextColor)(formatContextStatus(state.contextUsage, state.contextTokens, state.maxContextTokens, contextBarWidth))}${chalk.hex(colors.textDim)(` ${statusLine}`)}`;
124262
124423
  }
124263
124424
  const rightWidth = visibleWidth(rightText);
124264
124425
  const gap = 3;
@@ -125314,6 +125475,40 @@ async function handleYoloCommand(host, args) {
125314
125475
  host.setAppState({ permissionMode: "yolo" });
125315
125476
  }
125316
125477
  }
125478
+ async function handleAskCommand(host, args) {
125479
+ const session = host.session;
125480
+ if (session === void 0) {
125481
+ host.showError(getNoActiveSessionMessage());
125482
+ return;
125483
+ }
125484
+ const subcmd = args.trim().toLowerCase();
125485
+ const currentMode = host.state.appState.permissionMode;
125486
+ if (subcmd === "on") {
125487
+ if (currentMode === "ask") {
125488
+ host.showNotice(t("config.ask_already_on"));
125489
+ return;
125490
+ }
125491
+ await session.setPermission("ask");
125492
+ host.setAppState({ permissionMode: "ask" });
125493
+ return;
125494
+ }
125495
+ if (subcmd === "off") {
125496
+ if (currentMode !== "ask") {
125497
+ host.showNotice(t("config.ask_already_off"));
125498
+ return;
125499
+ }
125500
+ await session.setPermission("manual");
125501
+ host.setAppState({ permissionMode: "manual" });
125502
+ return;
125503
+ }
125504
+ if (currentMode === "ask") {
125505
+ await session.setPermission("manual");
125506
+ host.setAppState({ permissionMode: "manual" });
125507
+ } else {
125508
+ await session.setPermission("ask");
125509
+ host.setAppState({ permissionMode: "ask" });
125510
+ }
125511
+ }
125317
125512
  async function handleAutoCommand(host, args) {
125318
125513
  const session = host.session;
125319
125514
  if (session === void 0) {
@@ -126444,7 +126639,7 @@ async function guidedGoalSetup(host) {
126444
126639
  host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
126445
126640
  return;
126446
126641
  }
126447
- const { TextInputDialogComponent } = await import("./text-input-dialog-D-IR4lxs.mjs");
126642
+ const { TextInputDialogComponent } = await import("./text-input-dialog-K1S_ir1m.mjs");
126448
126643
  const initialDesc = await promptText(host, TextInputDialogComponent, {
126449
126644
  title: t("goal.setup_title_initial"),
126450
126645
  subtitle: t("goal.setup_desc_hint"),
@@ -126465,7 +126660,7 @@ async function guidedGoalSetup(host) {
126465
126660
  await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
126466
126661
  }
126467
126662
  async function showGoalConfigWizard(host, session, objective, replace) {
126468
- const { TextInputDialogComponent } = await import("./text-input-dialog-D-IR4lxs.mjs");
126663
+ const { TextInputDialogComponent } = await import("./text-input-dialog-K1S_ir1m.mjs");
126469
126664
  const turnInput = await promptNumber(host, TextInputDialogComponent, {
126470
126665
  title: t("goal.wizard_title", { objective }),
126471
126666
  subtitle: t("goal.budget_turns_hint"),
@@ -135147,6 +135342,9 @@ async function handleBuiltInSlashCommand(host, name, args) {
135147
135342
  case "yes":
135148
135343
  await handleYoloCommand(host, args);
135149
135344
  return;
135345
+ case "ask":
135346
+ await handleAskCommand(host, args);
135347
+ return;
135150
135348
  case "auto":
135151
135349
  await handleAutoCommand(host, args);
135152
135350
  return;
@@ -140783,6 +140981,7 @@ var LifecycleController = class LifecycleController {
140783
140981
  ui.addChild(this.host.state.queueContainer);
140784
140982
  ui.addChild(this.host.state.errorBannerContainer);
140785
140983
  ui.addChild(this.host.state.planModeBannerContainer);
140984
+ ui.addChild(new FlexSpacer());
140786
140985
  ui.addChild(this.host.state.editorContainer);
140787
140986
  }
140788
140987
  mountFooter() {
@@ -143159,6 +143358,24 @@ function adaptQuestionAnswers(event, response) {
143159
143358
  //#endregion
143160
143359
  //#region src/tui/managers/session-manager.ts
143161
143360
  /**
143361
+ * How recently a session must have been touched for the empty-session pruner
143362
+ * to leave it alone. Guards against sweeping a fresh empty session that
143363
+ * another terminal is about to use.
143364
+ */
143365
+ const PRUNE_EMPTY_SESSION_GRACE_MS = 300 * 1e3;
143366
+ /**
143367
+ * A session is prunable when it never received a user prompt and never got a
143368
+ * title (auto-generated or custom) — i.e. an empty shell left behind by a
143369
+ * one-off startup. Archived sessions and sessions touched within the grace
143370
+ * window are kept.
143371
+ */
143372
+ function isPrunableEmptySession(summary, now, graceMs = PRUNE_EMPTY_SESSION_GRACE_MS) {
143373
+ if (summary.archived) return false;
143374
+ if (summary.lastPrompt !== void 0) return false;
143375
+ if (summary.title) return false;
143376
+ return now - summary.updatedAt > graceMs;
143377
+ }
143378
+ /**
143162
143379
  * Encapsulates all session lifecycle operations:
143163
143380
  * create / resume / switch / close / sync state / reset runtime.
143164
143381
  */
@@ -143212,12 +143429,34 @@ var SessionManager$1 = class {
143212
143429
  await this.syncRuntimeState(session);
143213
143430
  if (startup.wolfpack && !isResumeStartup) await session.setWolfpackMode(true);
143214
143431
  this.host.state.startupState = "ready";
143432
+ await this.pruneEmptySessions(workDir, session.id);
143215
143433
  this.host.sessionEventHandler.startSubscription();
143216
143434
  return {
143217
143435
  session,
143218
143436
  shouldReplay: shouldReplayHistory
143219
143437
  };
143220
143438
  }
143439
+ /**
143440
+ * Deletes sessions in this workdir that never received a user prompt and
143441
+ * were never renamed — repeated startups otherwise leave a growing pile of
143442
+ * one-off empty session shells. The session about to be used is skipped, as
143443
+ * are archived sessions, any session that produced a prompt or a title, and
143444
+ * any session touched within the grace window (protects a fresh empty
143445
+ * session being used from another terminal). Best-effort: cleanup failures
143446
+ * never block startup.
143447
+ */
143448
+ async pruneEmptySessions(workDir, currentSessionId) {
143449
+ try {
143450
+ const now = Date.now();
143451
+ const summaries = await this.host.harness.listSessions({ workDir });
143452
+ for (const summary of summaries) {
143453
+ if (summary.id === currentSessionId) continue;
143454
+ if (isPrunableEmptySession(summary, now)) await this.host.harness.deleteSession(summary.id);
143455
+ }
143456
+ } catch (error) {
143457
+ this.host.showStatus(`Session cleanup skipped: ${String(error)}`);
143458
+ }
143459
+ }
143221
143460
  async setSession(session) {
143222
143461
  await this.unloadCurrentSession("switching session")?.close({ extractMemories: false });
143223
143462
  this.host.session = session;
@@ -144597,11 +144836,14 @@ var SessionPickerComponent = class extends Container {
144597
144836
  return lines;
144598
144837
  }
144599
144838
  const headerLabel = t("session.picker_title");
144600
- 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");
144839
+ const headerHint = this.selectedIds.size > 0 ? t("session_picker.batch_hint", { count: String(this.selectedIds.size) }) : t("session.picker_hint");
144601
144840
  const labelWidth = visibleWidth(headerLabel);
144602
144841
  const shownHint = truncateToWidth(headerHint, Math.max(0, width - labelWidth), ELLIPSIS);
144603
- const hintColor = this.confirmingDelete ? colors.warning : colors.textMuted;
144604
- lines.push(chalk.hex(colors.primary).bold(headerLabel) + chalk.hex(hintColor)(shownHint));
144842
+ lines.push(chalk.hex(colors.primary).bold(headerLabel) + chalk.hex(colors.textMuted)(shownHint));
144843
+ if (this.confirmingDelete) if (this.selectedIds.size > 0) {
144844
+ lines.push(chalk.hex(colors.warning).bold(truncateToWidth(t("session_picker.batch_delete_confirm", { count: String(this.selectedIds.size) }), width, ELLIPSIS)));
144845
+ lines.push(chalk.hex(colors.warning)(t("session_picker.batch_delete_hint")));
144846
+ } else lines.push(chalk.hex(colors.warning)(t("session.delete_confirm")));
144605
144847
  lines.push("");
144606
144848
  const visibleStart = Math.max(0, Math.min(this.selectedIndex - Math.floor(this.maxVisibleSessions / 2), Math.max(0, this.sessions.length - this.maxVisibleSessions)));
144607
144849
  const visibleSessions = this.sessions.slice(visibleStart, visibleStart + this.maxVisibleSessions);
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-BRaJ5hGm.mjs")).main();
9
+ (await import("./app-1A238nr4.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);
@@ -474,6 +474,7 @@ const dictionaries = {
474
474
  "session_picker.empty": "未找到会话。按 Escape 关闭。",
475
475
  "session_picker.cc_restricted": "CC专属会话不支持切换或删除,请点击或复制下方文件路径进入手动管理",
476
476
  "session_picker.batch_delete_confirm": "删除选中的 {count} 个会话?",
477
+ "session_picker.batch_delete_hint": " 按 Enter 确认批量删除,Esc 取消",
477
478
  "session_picker.batch_delete_unavailable": "此处不支持批量删除。",
478
479
  "session_picker.batch_hint": "[空格] 选择 · [a] 全选 · [d] 删除选中({count})",
479
480
  "session.picker_title": "会话 ",
@@ -844,6 +845,7 @@ const dictionaries = {
844
845
  "permission.auto": "自动",
845
846
  "permission.auto_desc": "完全无交互运行。工具操作自动批准,跳过代理问题以便其自行决策。",
846
847
  "permission.yolo_desc": "自动批准工具操作和计划转换。需要您输入时代理仍会明确提问。",
848
+ "permission.ask_desc": "只读问答模式:直接对话回答,不修改文件、不运行命令、不调用 MCP 工具。",
847
849
  "permission.select_title": "选择权限模式",
848
850
  "theme.auto": "自动(跟随终端)",
849
851
  "theme.dark": "深色",
@@ -1030,6 +1032,8 @@ const dictionaries = {
1030
1032
  "config.plan_cleared": "计划已清除",
1031
1033
  "config.yolo_already_on": "YES 模式已开启",
1032
1034
  "config.yolo_already_off": "YES 模式已关闭",
1035
+ "config.ask_already_on": "ASK 模式已开启",
1036
+ "config.ask_already_off": "ASK 模式已关闭",
1033
1037
  "config.auto_already_on": "自动模式已开启",
1034
1038
  "config.auto_already_off": "自动模式已关闭",
1035
1039
  "settings.title": "设置",
@@ -1064,6 +1068,7 @@ const dictionaries = {
1064
1068
  "cc.clean_config.detail": " ~/.cc-connect(含会话记录、配置、日志)",
1065
1069
  "registry.auto_desc": "切换自动权限模式",
1066
1070
  "registry.yolo_desc": "切换至自动批准模式(yolo)",
1071
+ "registry.ask_desc": "切换至只读问答模式(ask)",
1067
1072
  "registry.wolfpack_desc": "切换群狼协作模式,自动批准+批量并发",
1068
1073
  "registry.sessions_desc": "浏览并恢复会话",
1069
1074
  "registry.goal_desc": "查看/管理自动目标",
@@ -1524,6 +1529,7 @@ const dictionaries = {
1524
1529
  "session_picker.empty": "No sessions found. Press Escape to close.",
1525
1530
  "session_picker.cc_restricted": "CC sessions cannot be switched or deleted. Use the file path below to manage manually",
1526
1531
  "session_picker.batch_delete_confirm": "Delete {count} selected sessions?",
1532
+ "session_picker.batch_delete_hint": " Press Enter to confirm batch delete, Esc to cancel",
1527
1533
  "session_picker.batch_delete_unavailable": "Batch delete is not available here.",
1528
1534
  "session_picker.batch_hint": "[space] select · [a] all · [d] delete selected ({count})",
1529
1535
  "session.picker_title": "Sessions ",
@@ -1894,6 +1900,7 @@ const dictionaries = {
1894
1900
  "permission.auto": "Auto",
1895
1901
  "permission.auto_desc": "Run fully without interaction. Tool operations are auto-approved; agent questions are skipped so it can decide on its own.",
1896
1902
  "permission.yolo_desc": "Auto-approve tool operations and plan transitions. The agent will still explicitly ask when it needs your input.",
1903
+ "permission.ask_desc": "Read-only Q&A mode: answer in conversation, never modify files, run commands, or call MCP tools.",
1897
1904
  "permission.select_title": "Select permission mode",
1898
1905
  "theme.auto": "Auto (follow terminal)",
1899
1906
  "theme.dark": "Dark",
@@ -2080,6 +2087,8 @@ const dictionaries = {
2080
2087
  "config.plan_cleared": "Plan cleared",
2081
2088
  "config.yolo_already_on": "YES mode is already on",
2082
2089
  "config.yolo_already_off": "YES mode is already off",
2090
+ "config.ask_already_on": "ASK mode is already on",
2091
+ "config.ask_already_off": "ASK mode is already off",
2083
2092
  "config.auto_already_on": "Auto mode is already on",
2084
2093
  "config.auto_already_off": "Auto mode is already off",
2085
2094
  "settings.title": "Settings",
@@ -2114,6 +2123,7 @@ const dictionaries = {
2114
2123
  "cc.clean_config.detail": " ~/.cc-connect (includes session records, config, logs)",
2115
2124
  "registry.auto_desc": "Toggle auto permission mode",
2116
2125
  "registry.yolo_desc": "Toggle auto-approve mode (yolo)",
2126
+ "registry.ask_desc": "Toggle read-only Q&A mode (ask)",
2117
2127
  "registry.wolfpack_desc": "Toggle wolfpack mode, auto-approve + batch concurrency",
2118
2128
  "registry.sessions_desc": "Browse and restore sessions",
2119
2129
  "registry.goal_desc": "View/manage auto goals",
@@ -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-DLkaeiFf.mjs";
6
+ import { t as TextInputDialogComponent } from "./text-input-dialog-DHhRrX3G.mjs";
7
7
  export { TextInputDialogComponent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "scream-code",
3
- "version": "0.12.0",
3
+ "version": "0.12.2",
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.34",
62
+ "@liutod-scream/pi-tui": "^0.80.39",
63
63
  "@mariozechner/clipboard": "^0.3.2",
64
64
  "chalk": "^5.4.1",
65
65
  "cli-highlight": "^2.1.11",