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 +2 -2
- package/dist/{app-BRaJ5hGm.mjs → app-1A238nr4.mjs} +325 -83
- package/dist/main.mjs +1 -1
- package/dist/{text-input-dialog-DLkaeiFf.mjs → text-input-dialog-DHhRrX3G.mjs} +10 -0
- package/dist/{text-input-dialog-D-IR4lxs.mjs → text-input-dialog-K1S_ir1m.mjs} +1 -1
- package/package.json +2 -2
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-
|
|
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
|
-
|
|
79141
|
-
if (
|
|
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 {
|
|
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 {
|
|
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")
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
123992
|
-
const
|
|
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
|
-
*
|
|
124001
|
-
*
|
|
124002
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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.
|
|
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
|
-
|
|
144604
|
-
|
|
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-
|
|
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-
|
|
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.
|
|
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.
|
|
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",
|