scream-code 0.12.4 → 0.12.6
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 +20 -0
- package/dist/{app-DSS4PdWO.mjs → app-NZLMIJND.mjs} +1055 -159
- package/dist/main.mjs +1 -1
- package/dist/{text-input-dialog-DHhRrX3G.mjs → text-input-dialog-BI6Q02oM.mjs} +16 -0
- package/dist/{text-input-dialog-K1S_ir1m.mjs → text-input-dialog-BViNsv5f.mjs} +1 -1
- package/package.json +2 -2
|
@@ -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-BI6Q02oM.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,
|
|
57
|
+
import { CombinedAutocompleteProvider, Container, Editor, HStack, Image, Input, Key, Markdown, ProcessTerminal, ScrollView, Spacer, Text, TuiAltScreen, VStack, decodeKittyPrintable, deleteAllKittyImages, fuzzyFilter, fuzzyMatch, getCapabilities, getImageDimensions, isKeyRelease, matchesKey, 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";
|
|
@@ -53309,7 +53309,8 @@ function createMemoryMemo(partial) {
|
|
|
53309
53309
|
extractionSource: partial.extractionSource,
|
|
53310
53310
|
recordedAt: partial.recordedAt ?? Date.now(),
|
|
53311
53311
|
projectDir: partial.projectDir ?? "",
|
|
53312
|
-
tags: normalizedTags(partial.tags)
|
|
53312
|
+
tags: normalizedTags(partial.tags),
|
|
53313
|
+
note: partial.note
|
|
53313
53314
|
};
|
|
53314
53315
|
}
|
|
53315
53316
|
function normalizedTags(value) {
|
|
@@ -53329,7 +53330,8 @@ function toSummary(memo) {
|
|
|
53329
53330
|
extractionSource: memo.extractionSource,
|
|
53330
53331
|
recordedAt: memo.recordedAt,
|
|
53331
53332
|
projectDir: memo.projectDir,
|
|
53332
|
-
tags: memo.tags
|
|
53333
|
+
tags: memo.tags,
|
|
53334
|
+
note: memo.note
|
|
53333
53335
|
};
|
|
53334
53336
|
}
|
|
53335
53337
|
//#endregion
|
|
@@ -54214,6 +54216,7 @@ function parseMemoryMemos(text) {
|
|
|
54214
54216
|
if (userNeed.length === 0) continue;
|
|
54215
54217
|
const rawTags = parsed["tags"];
|
|
54216
54218
|
const tags = Array.isArray(rawTags) ? normalizeTags(rawTags) : void 0;
|
|
54219
|
+
const note = typeof parsed["note"] === "string" ? parsed["note"].trim() : void 0;
|
|
54217
54220
|
memos.push(createMemoryMemo({
|
|
54218
54221
|
userNeed,
|
|
54219
54222
|
approach: typeof parsed["approach"] === "string" ? parsed["approach"].trim() : "",
|
|
@@ -54221,6 +54224,7 @@ function parseMemoryMemos(text) {
|
|
|
54221
54224
|
whatFailed: typeof parsed["whatFailed"] === "string" ? parsed["whatFailed"].trim() : "none",
|
|
54222
54225
|
whatWorked: typeof parsed["whatWorked"] === "string" ? parsed["whatWorked"].trim() : "none",
|
|
54223
54226
|
tags,
|
|
54227
|
+
note: note && note.length > 0 ? note : void 0,
|
|
54224
54228
|
extractionSource: "compaction",
|
|
54225
54229
|
sourceSessionId: "",
|
|
54226
54230
|
sourceSessionTitle: ""
|
|
@@ -54259,7 +54263,8 @@ function buildExitExtractionPrompt(sessionId, messageCount, sampleText) {
|
|
|
54259
54263
|
"outcome": "<最终结果,如'完成'、'部分完成'、'失败:原因'>",
|
|
54260
54264
|
"whatFailed": "<踩坑记录:试了但不行的路,无则填 'none'>",
|
|
54261
54265
|
"whatWorked": "<成功经验:最终奏效的关键动作,无则填 'none'>",
|
|
54262
|
-
"tags": ["<标签1>", "<标签2>", "<标签3>"]
|
|
54266
|
+
"tags": ["<标签1>", "<标签2>", "<标签3>"],
|
|
54267
|
+
"note": "<可选:给 AI 的补充说明/建议——让未来理解这条经验更容易,如'这类任务先查接口可用性再写代码'。无则省略>"
|
|
54263
54268
|
}
|
|
54264
54269
|
\`\`\`
|
|
54265
54270
|
|
|
@@ -54267,9 +54272,10 @@ function buildExitExtractionPrompt(sessionId, messageCount, sampleText) {
|
|
|
54267
54272
|
- tags 是 3-5 个语义标签,概括任务领域/技术栈/动作类型,例如 ["react", "auth", "部署"]
|
|
54268
54273
|
- whatFailed 记录重要的错误尝试,帮助未来避免重蹈覆辙
|
|
54269
54274
|
- whatWorked 记录最终成功的关键动作,帮助未来复用经验
|
|
54275
|
+
- note 是可选字段,写一句能让未来 AI 更快理解/复用这条经验的说明或建议(软性提示,非强制规则)
|
|
54270
54276
|
- 跳过未完成的工作,除非其中包含有价值的踩坑经验
|
|
54271
54277
|
- 将紧密相关的子任务合并为一条记录
|
|
54272
|
-
- 严格遵守字段名和 JSON
|
|
54278
|
+
- 严格遵守字段名和 JSON 格式(除 note 外不要添加额外字段)
|
|
54273
54279
|
|
|
54274
54280
|
如果没有已完成的任务闭环,输出:
|
|
54275
54281
|
\`\`\`memory-memo
|
|
@@ -54964,7 +54970,8 @@ const MemoryWriteInputSchema = z.object({
|
|
|
54964
54970
|
outcome: z.string().min(1).describe("Final outcome, e.g. \"完成\", \"部分完成\", \"失败\"."),
|
|
54965
54971
|
whatFailed: z.string().optional().describe("Dead ends tried — things that did not work. Use \"none\" if nothing notable."),
|
|
54966
54972
|
whatWorked: z.string().optional().describe("What ultimately worked — key actions that led to success. Use \"none\" if nothing notable."),
|
|
54967
|
-
tags: z.array(z.string()).optional().describe("3-5 semantic tags summarizing the task domain, tech stack, or action type (e.g. [\"react\", \"auth\", \"部署\"]).")
|
|
54973
|
+
tags: z.array(z.string()).optional().describe("3-5 semantic tags summarizing the task domain, tech stack, or action type (e.g. [\"react\", \"auth\", \"部署\"])."),
|
|
54974
|
+
note: z.string().optional().describe("Optional advisory note that helps future AI understand/reuse this memo faster — a soft suggestion, not a user-enforced rule.")
|
|
54968
54975
|
});
|
|
54969
54976
|
/**
|
|
54970
54977
|
* Lets the model actively write a new memory memo to the global store.
|
|
@@ -54997,6 +55004,7 @@ var MemoryWriteTool = class {
|
|
|
54997
55004
|
const whatFailed = stripMemoryTags(args.whatFailed ?? "");
|
|
54998
55005
|
const whatWorked = stripMemoryTags(args.whatWorked ?? "");
|
|
54999
55006
|
const tags = normalizeTags(args.tags !== void 0 && args.tags.length > 0 ? args.tags : generateTags(`${userNeed} ${approach}`));
|
|
55007
|
+
const note = args.note ? stripMemoryTags(args.note).trim() : void 0;
|
|
55000
55008
|
const memo = createMemoryMemo({
|
|
55001
55009
|
sourceSessionId: sessionId,
|
|
55002
55010
|
sourceSessionTitle,
|
|
@@ -55006,6 +55014,7 @@ var MemoryWriteTool = class {
|
|
|
55006
55014
|
whatFailed: whatFailed.length === 0 ? "none" : whatFailed,
|
|
55007
55015
|
whatWorked: whatWorked.length === 0 ? "none" : whatWorked,
|
|
55008
55016
|
tags,
|
|
55017
|
+
note: note && note.length > 0 ? note : void 0,
|
|
55009
55018
|
extractionSource: "manual",
|
|
55010
55019
|
projectDir: this.agent.config.cwd
|
|
55011
55020
|
});
|
|
@@ -72330,7 +72339,7 @@ z.object({
|
|
|
72330
72339
|
numMatches: z.number().int().nonnegative().optional(),
|
|
72331
72340
|
appliedLimit: z.number().int().nonnegative().optional()
|
|
72332
72341
|
});
|
|
72333
|
-
const DEFAULT_TIMEOUT_MS = 2e4;
|
|
72342
|
+
const DEFAULT_TIMEOUT_MS$1 = 2e4;
|
|
72334
72343
|
const SIGTERM_GRACE_MS$1 = 5e3;
|
|
72335
72344
|
const MAX_OUTPUT_BYTES = 10 * 1024 * 1024;
|
|
72336
72345
|
const RG_MAX_COLUMNS = 500;
|
|
@@ -72443,7 +72452,7 @@ var GrepTool = class {
|
|
|
72443
72452
|
if (bufferTruncated || timedOut) stdoutText = omitIncompleteTrailingRecord(stdoutText, mode);
|
|
72444
72453
|
if (timedOut && stdoutText.trim() === "") return {
|
|
72445
72454
|
isError: true,
|
|
72446
|
-
output: `Grep timed out after ${String(DEFAULT_TIMEOUT_MS / 1e3)}s. Try a more specific path or pattern.`
|
|
72455
|
+
output: `Grep timed out after ${String(DEFAULT_TIMEOUT_MS$1 / 1e3)}s. Try a more specific path or pattern.`
|
|
72447
72456
|
};
|
|
72448
72457
|
if (signal.aborted) return {
|
|
72449
72458
|
isError: true,
|
|
@@ -72483,7 +72492,7 @@ var GrepTool = class {
|
|
|
72483
72492
|
else messages.push(paginationNotice);
|
|
72484
72493
|
}
|
|
72485
72494
|
if (bufferTruncated) messages.push(`[stdout truncated at ${String(MAX_OUTPUT_BYTES)} bytes; incomplete trailing line omitted]`);
|
|
72486
|
-
if (timedOut) messages.push(`Grep timed out after ${String(DEFAULT_TIMEOUT_MS / 1e3)}s; partial results returned`);
|
|
72495
|
+
if (timedOut) messages.push(`Grep timed out after ${String(DEFAULT_TIMEOUT_MS$1 / 1e3)}s; partial results returned`);
|
|
72487
72496
|
const contentIncludesLineNumbers = mode === "content" && args["-n"] !== false;
|
|
72488
72497
|
const contentBody = limited.map((line) => formatDisplayLine(line, mode, this.workspace.workspaceDir, pathClass, contentIncludesLineNumbers)).join("\n");
|
|
72489
72498
|
const visibleBody = orderedLines.length === 0 && filteredSensitive.size > 0 ? "No non-sensitive matches found" : contentBody;
|
|
@@ -72600,7 +72609,7 @@ async function runRipgrepOnce(jian, rgArgs, signal) {
|
|
|
72600
72609
|
const timeoutHandle = setTimeout(() => {
|
|
72601
72610
|
timedOut = true;
|
|
72602
72611
|
killProc();
|
|
72603
|
-
}, DEFAULT_TIMEOUT_MS);
|
|
72612
|
+
}, DEFAULT_TIMEOUT_MS$1);
|
|
72604
72613
|
let exitCode = 0;
|
|
72605
72614
|
let stdoutText = "";
|
|
72606
72615
|
let stderrText = "";
|
|
@@ -74711,6 +74720,466 @@ function truncateUtf8$1(input, maxBytes) {
|
|
|
74711
74720
|
return `${input.slice(0, low)}${suffix}`;
|
|
74712
74721
|
}
|
|
74713
74722
|
//#endregion
|
|
74723
|
+
//#region ../../packages/agent-core/src/tools/builtin/python/python.ts
|
|
74724
|
+
const PY_DONE_MARKER = "__SCREAM_PY_DONE__";
|
|
74725
|
+
const DEFAULT_TIMEOUT_MS = 6e4;
|
|
74726
|
+
const MAX_TIMEOUT_MS = 5 * 6e4;
|
|
74727
|
+
const KERNEL_START_TIMEOUT_MS = 3e4;
|
|
74728
|
+
const PythonInputSchema = z.object({
|
|
74729
|
+
code: z.string().min(1).describe("Python code to execute in the persistent kernel. Variables, imports, and loaded data persist across calls, unlike Bash. State is kept for the whole session while the /rlm mode is enabled."),
|
|
74730
|
+
timeout: z.number().int().positive().max(MAX_TIMEOUT_MS / 1e3).optional().describe("Execution timeout in seconds (default 60, max 300).")
|
|
74731
|
+
});
|
|
74732
|
+
const RLM_BOOTSTRAP_PY = `import json, os, sys, tempfile, time, itertools, pickle
|
|
74733
|
+
_RLM_ID = itertools.count(1)
|
|
74734
|
+
_SNAP = __SNAP_PATH__
|
|
74735
|
+
|
|
74736
|
+
def _host_request(method, payload, timeout=120):
|
|
74737
|
+
rid = next(_RLM_ID)
|
|
74738
|
+
sys.stdout.write(json.dumps({"type": "host_request", "id": rid, "method": method, "payload": payload}) + "\\n")
|
|
74739
|
+
sys.stdout.flush()
|
|
74740
|
+
reply_file = os.path.join(tempfile.gettempdir(), "scream-rlm-" + str(os.getpid()) + "-" + str(rid) + ".json")
|
|
74741
|
+
deadline = time.time() + timeout
|
|
74742
|
+
while time.time() < deadline:
|
|
74743
|
+
if os.path.exists(reply_file):
|
|
74744
|
+
try:
|
|
74745
|
+
with open(reply_file, "r", encoding="utf-8") as f:
|
|
74746
|
+
reply = json.load(f)
|
|
74747
|
+
os.remove(reply_file)
|
|
74748
|
+
except Exception:
|
|
74749
|
+
time.sleep(0.1)
|
|
74750
|
+
continue
|
|
74751
|
+
if "error" in reply:
|
|
74752
|
+
raise RuntimeError(reply["error"])
|
|
74753
|
+
return reply.get("result")
|
|
74754
|
+
time.sleep(0.1)
|
|
74755
|
+
raise RuntimeError("host bridge timeout")
|
|
74756
|
+
|
|
74757
|
+
def rlm(task, name="subagent", **meta):
|
|
74758
|
+
payload = {"task": task, "name": name}
|
|
74759
|
+
payload.update(meta)
|
|
74760
|
+
return _host_request("rlm.run", payload)
|
|
74761
|
+
|
|
74762
|
+
def rlm_wait(handle, timeout=120):
|
|
74763
|
+
return _host_request("rlm.result", {"id": handle, "timeout": timeout}, timeout=timeout)
|
|
74764
|
+
|
|
74765
|
+
def _snapshot(path=_SNAP):
|
|
74766
|
+
saved = {}
|
|
74767
|
+
for k, v in list(globals().items()):
|
|
74768
|
+
if k.startswith("_") or callable(v) or isinstance(v, type(sys)):
|
|
74769
|
+
continue
|
|
74770
|
+
try:
|
|
74771
|
+
pickle.dumps(v)
|
|
74772
|
+
saved[k] = v
|
|
74773
|
+
except Exception:
|
|
74774
|
+
pass
|
|
74775
|
+
try:
|
|
74776
|
+
with open(path, "wb") as f:
|
|
74777
|
+
pickle.dump(saved, f)
|
|
74778
|
+
return len(saved)
|
|
74779
|
+
except Exception:
|
|
74780
|
+
return 0
|
|
74781
|
+
|
|
74782
|
+
def _restore(path=_SNAP):
|
|
74783
|
+
if not os.path.exists(path):
|
|
74784
|
+
return 0
|
|
74785
|
+
try:
|
|
74786
|
+
with open(path, "rb") as f:
|
|
74787
|
+
saved = pickle.load(f)
|
|
74788
|
+
except Exception:
|
|
74789
|
+
return 0
|
|
74790
|
+
for k, v in saved.items():
|
|
74791
|
+
globals()[k] = v
|
|
74792
|
+
return len(saved)
|
|
74793
|
+
|
|
74794
|
+
_restore()
|
|
74795
|
+
`;
|
|
74796
|
+
/** Builds the single-line bootstrap exec for this tool instance, embedding
|
|
74797
|
+
* the snapshot path so each PythonTool has its own persistent state file. */
|
|
74798
|
+
function buildRlmBootstrap(snapshotPath) {
|
|
74799
|
+
const py = RLM_BOOTSTRAP_PY.replace("__SNAP_PATH__", JSON.stringify(snapshotPath));
|
|
74800
|
+
return `exec(__import__('base64').b64decode('${Buffer.from(py).toString("base64")}').decode())\nprint("__SCREAM_BOOT_DONE__")`;
|
|
74801
|
+
}
|
|
74802
|
+
/**
|
|
74803
|
+
* Executes Python code in a persistent interactive kernel (`python3 -u -i`
|
|
74804
|
+
* over a pipe). The kernel process is lazily started on first use and lives
|
|
74805
|
+
* for the lifetime of this tool instance (the /rlm session), so variables,
|
|
74806
|
+
* imports, and loaded data survive across calls — unlike the stateless Bash
|
|
74807
|
+
* tool. Runs under the normal permission mode like any other tool; the code
|
|
74808
|
+
* can read/write files, so it is gated exactly like a mutating tool.
|
|
74809
|
+
*/
|
|
74810
|
+
var PythonTool = class PythonTool {
|
|
74811
|
+
cwd;
|
|
74812
|
+
options;
|
|
74813
|
+
name = "python";
|
|
74814
|
+
description;
|
|
74815
|
+
parameters = toInputJsonSchema(PythonInputSchema);
|
|
74816
|
+
kernel;
|
|
74817
|
+
kernelBusy = false;
|
|
74818
|
+
/** Accumulated stderr from the kernel (tracebacks land here). */
|
|
74819
|
+
kernelStderr = "";
|
|
74820
|
+
/** Bytes of kernelStderr consumed by the last execution (race-safe drain). */
|
|
74821
|
+
kernelStderrOffset = 0;
|
|
74822
|
+
/** Per-instance snapshot file so RLM state survives kernel restarts. */
|
|
74823
|
+
snapshotPath;
|
|
74824
|
+
hostHandlers;
|
|
74825
|
+
constructor(cwd, options = {}) {
|
|
74826
|
+
this.cwd = cwd;
|
|
74827
|
+
this.options = options;
|
|
74828
|
+
this.hostHandlers = options.hostHandlers;
|
|
74829
|
+
this.snapshotPath = options.snapshotPath ?? join(tmpdir(), `scream-rlm-state-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}.pkl`);
|
|
74830
|
+
this.description = "Execute Python code in a persistent kernel. Variables, imports, and loaded data persist across calls (unlike Bash) — ideal for data analysis and multi-step processing. Run shell commands with the Bash tool instead. In RLM mode the kernel also provides `rlm(task, name=\"subagent\")` to spawn a subagent (returns a handle immediately) and `rlm_wait(handle, timeout)` to await its final summary. Multi-line code (def/for/if) is fully supported. Code runs under the current permission mode; mutating operations follow the same approval rules as other tools.";
|
|
74831
|
+
}
|
|
74832
|
+
dispose() {
|
|
74833
|
+
if (this.hostHandlers !== void 0) this.hostHandlers["__dispose__"]?.({}).catch(() => {});
|
|
74834
|
+
this.kernel?.kill("SIGKILL");
|
|
74835
|
+
this.kernel = void 0;
|
|
74836
|
+
this.kernelBusy = false;
|
|
74837
|
+
this.kernelStderr = "";
|
|
74838
|
+
this.kernelStderrOffset = 0;
|
|
74839
|
+
unlink(this.snapshotPath).catch(() => {});
|
|
74840
|
+
}
|
|
74841
|
+
/** Lazily starts the persistent kernel. The banner and REPL prompts are
|
|
74842
|
+
* emitted on stderr (not stdout), so we drain stderr continuously and drop
|
|
74843
|
+
* the startup banner; stdout only carries print() output. */
|
|
74844
|
+
async ensureKernel() {
|
|
74845
|
+
if (this.kernel !== void 0 && this.kernel.exitCode === null) return this.kernel;
|
|
74846
|
+
this.kernelBusy = false;
|
|
74847
|
+
this.kernelStderr = "";
|
|
74848
|
+
this.kernelStderrOffset = 0;
|
|
74849
|
+
const proc = await this.spawnKernel();
|
|
74850
|
+
this.kernel = proc;
|
|
74851
|
+
let spawnError;
|
|
74852
|
+
proc.on("error", (error) => {
|
|
74853
|
+
spawnError = error;
|
|
74854
|
+
});
|
|
74855
|
+
proc.stdin?.on("error", () => {});
|
|
74856
|
+
proc.stdout?.on("error", () => {});
|
|
74857
|
+
proc.stderr?.on("error", () => {});
|
|
74858
|
+
this.drainStderr(proc);
|
|
74859
|
+
if (this.hostHandlers !== void 0) {
|
|
74860
|
+
proc.stdin.write(`${buildRlmBootstrap(this.snapshotPath)}\n`);
|
|
74861
|
+
if (!(await this.readUntilMarker(proc.stdout, "__SCREAM_BOOT_DONE__", KERNEL_START_TIMEOUT_MS)).found) {
|
|
74862
|
+
proc.kill("SIGKILL");
|
|
74863
|
+
this.kernel = void 0;
|
|
74864
|
+
throw new Error(`Python kernel failed to start: ${spawnError?.message ?? "bootstrap did not complete"}`);
|
|
74865
|
+
}
|
|
74866
|
+
}
|
|
74867
|
+
return proc;
|
|
74868
|
+
}
|
|
74869
|
+
/** Candidate python commands, platform-first order. Exposed for tests:
|
|
74870
|
+
* POSIX prefers `python3`, Windows prefers `python`. Both fall back to the
|
|
74871
|
+
* other on ENOENT so a machine with either interpreter works. */
|
|
74872
|
+
static pythonCandidates(platform = process.platform) {
|
|
74873
|
+
return platform === "win32" ? ["python", "python3"] : ["python3", "python"];
|
|
74874
|
+
}
|
|
74875
|
+
/**
|
|
74876
|
+
* Spawns the kernel, resolving the python command per platform.
|
|
74877
|
+
*
|
|
74878
|
+
* The command name differs across platforms: POSIX ships `python3`; Windows
|
|
74879
|
+
* commonly exposes only `python` (python3 may or may not exist as an alias).
|
|
74880
|
+
* We prefer the platform-default name first and fall back to the other on
|
|
74881
|
+
* ENOENT, so a Windows install with either `python` or `python3` works.
|
|
74882
|
+
*/
|
|
74883
|
+
async spawnKernel() {
|
|
74884
|
+
const candidates = PythonTool.pythonCandidates();
|
|
74885
|
+
let lastError;
|
|
74886
|
+
for (const command of candidates) try {
|
|
74887
|
+
return await this.trySpawn(command);
|
|
74888
|
+
} catch (error) {
|
|
74889
|
+
lastError = error;
|
|
74890
|
+
if (error.code !== "ENOENT") throw error;
|
|
74891
|
+
}
|
|
74892
|
+
throw lastError ?? /* @__PURE__ */ new Error(`No python interpreter found (tried: ${candidates.join(", ")})`);
|
|
74893
|
+
}
|
|
74894
|
+
trySpawn(command) {
|
|
74895
|
+
return new Promise((resolve, reject) => {
|
|
74896
|
+
const proc = spawn(command, ["-u", "-i"], {
|
|
74897
|
+
cwd: this.cwd,
|
|
74898
|
+
stdio: [
|
|
74899
|
+
"pipe",
|
|
74900
|
+
"pipe",
|
|
74901
|
+
"pipe"
|
|
74902
|
+
]
|
|
74903
|
+
});
|
|
74904
|
+
proc.once("error", (error) => {
|
|
74905
|
+
reject(error);
|
|
74906
|
+
});
|
|
74907
|
+
proc.once("spawn", () => {
|
|
74908
|
+
proc.off("error", reject);
|
|
74909
|
+
resolve(proc);
|
|
74910
|
+
});
|
|
74911
|
+
});
|
|
74912
|
+
}
|
|
74913
|
+
drainStderr(proc) {
|
|
74914
|
+
const decoder = new StringDecoder("utf8");
|
|
74915
|
+
proc.stderr?.on("data", (chunk) => {
|
|
74916
|
+
this.kernelStderr += decoder.write(chunk);
|
|
74917
|
+
});
|
|
74918
|
+
}
|
|
74919
|
+
/**
|
|
74920
|
+
* Reads stdout until `marker` appears (or the deadline passes). Uses the
|
|
74921
|
+
* 'data' event instead of an async iterator: a `for await` that returns
|
|
74922
|
+
* early destroys the stream, which breaks the next call on the same
|
|
74923
|
+
* persistent kernel. `'data'` keeps the stream alive across calls.
|
|
74924
|
+
*/
|
|
74925
|
+
async readUntilMarker(stream, marker, timeoutMs) {
|
|
74926
|
+
if (stream === null) return {
|
|
74927
|
+
lines: [],
|
|
74928
|
+
found: false
|
|
74929
|
+
};
|
|
74930
|
+
return new Promise((resolve) => {
|
|
74931
|
+
const lines = [];
|
|
74932
|
+
let buffer = "";
|
|
74933
|
+
const decoder = new StringDecoder("utf8");
|
|
74934
|
+
const cleanup = () => {
|
|
74935
|
+
clearTimeout(timer);
|
|
74936
|
+
stream.off("data", onData);
|
|
74937
|
+
stream.off("end", onEnd);
|
|
74938
|
+
stream.off("close", onEnd);
|
|
74939
|
+
stream.off("error", onEnd);
|
|
74940
|
+
};
|
|
74941
|
+
const finish = (found) => {
|
|
74942
|
+
cleanup();
|
|
74943
|
+
resolve({
|
|
74944
|
+
lines,
|
|
74945
|
+
found
|
|
74946
|
+
});
|
|
74947
|
+
};
|
|
74948
|
+
const onEnd = () => finish(false);
|
|
74949
|
+
const onData = (chunk) => {
|
|
74950
|
+
buffer += decoder.write(chunk);
|
|
74951
|
+
const parts = buffer.split("\n");
|
|
74952
|
+
buffer = parts.pop() ?? "";
|
|
74953
|
+
for (const line of parts) {
|
|
74954
|
+
if (line.includes(marker)) return finish(true);
|
|
74955
|
+
if (line.includes("\"host_request\"")) {
|
|
74956
|
+
this.handleHostRequest(line, stream).catch(() => {});
|
|
74957
|
+
continue;
|
|
74958
|
+
}
|
|
74959
|
+
lines.push(line);
|
|
74960
|
+
}
|
|
74961
|
+
if (buffer.includes(marker)) {
|
|
74962
|
+
lines.push(buffer);
|
|
74963
|
+
buffer = "";
|
|
74964
|
+
return finish(true);
|
|
74965
|
+
}
|
|
74966
|
+
};
|
|
74967
|
+
const timer = setTimeout(() => finish(false), timeoutMs);
|
|
74968
|
+
stream.on("data", onData);
|
|
74969
|
+
stream.on("end", onEnd);
|
|
74970
|
+
stream.on("close", onEnd);
|
|
74971
|
+
stream.on("error", onEnd);
|
|
74972
|
+
});
|
|
74973
|
+
}
|
|
74974
|
+
/**
|
|
74975
|
+
* Handles a kernel `host_request` line: invokes the registered handler and
|
|
74976
|
+
* writes the reply to the kernel's expected reply file (the kernel polls
|
|
74977
|
+
* that file — replies must not go over stdin, which is used for code input
|
|
74978
|
+
* and would race with the REPL). Fire-and-forget from the read loop.
|
|
74979
|
+
*/
|
|
74980
|
+
/** Writes a bridge reply to the kernel's expected reply file. Uses the
|
|
74981
|
+
* pid captured when the request was received: if the kernel is restarted
|
|
74982
|
+
* (timeout kill) while a bridge handler is still running, the late reply
|
|
74983
|
+
* must still land in the file the originating kernel is polling. */
|
|
74984
|
+
writeHostReply(pid, id, body) {
|
|
74985
|
+
if (pid === void 0) return;
|
|
74986
|
+
writeFile(join(tmpdir(), `scream-rlm-${pid}-${id}.json`), JSON.stringify({
|
|
74987
|
+
type: "host_reply",
|
|
74988
|
+
id,
|
|
74989
|
+
...body
|
|
74990
|
+
}), "utf8").catch(() => {});
|
|
74991
|
+
}
|
|
74992
|
+
async handleHostRequest(line, stream) {
|
|
74993
|
+
const replyPid = this.kernel?.pid;
|
|
74994
|
+
if (replyPid === void 0) return;
|
|
74995
|
+
let parsed;
|
|
74996
|
+
try {
|
|
74997
|
+
parsed = JSON.parse(line);
|
|
74998
|
+
} catch {
|
|
74999
|
+
return;
|
|
75000
|
+
}
|
|
75001
|
+
const { id, method, payload } = parsed;
|
|
75002
|
+
if (typeof id !== "number" || typeof method !== "string" || payload === void 0) return;
|
|
75003
|
+
const handler = this.hostHandlers?.[method];
|
|
75004
|
+
if (handler === void 0) {
|
|
75005
|
+
this.writeHostReply(replyPid, id, { error: `no handler for ${method}` });
|
|
75006
|
+
return;
|
|
75007
|
+
}
|
|
75008
|
+
try {
|
|
75009
|
+
const result = await handler(payload);
|
|
75010
|
+
this.writeHostReply(replyPid, id, { result });
|
|
75011
|
+
} catch (error) {
|
|
75012
|
+
this.writeHostReply(replyPid, id, { error: error instanceof Error ? error.message : String(error) });
|
|
75013
|
+
}
|
|
75014
|
+
}
|
|
75015
|
+
resolveExecution(args) {
|
|
75016
|
+
return {
|
|
75017
|
+
description: `Python: ${args.code.length > 50 ? `${args.code.slice(0, 50)}…` : args.code}`,
|
|
75018
|
+
display: {
|
|
75019
|
+
kind: "command",
|
|
75020
|
+
command: args.code,
|
|
75021
|
+
cwd: this.cwd
|
|
75022
|
+
},
|
|
75023
|
+
approvalRule: this.name,
|
|
75024
|
+
execute: (ctx) => this.execution(args, ctx)
|
|
75025
|
+
};
|
|
75026
|
+
}
|
|
75027
|
+
async execution(args, _ctx) {
|
|
75028
|
+
if (this.kernelBusy) return { output: "The Python kernel is busy executing a previous call. Wait for it to finish, or stop it with TaskStop." };
|
|
75029
|
+
this.kernelBusy = true;
|
|
75030
|
+
try {
|
|
75031
|
+
const proc = await this.ensureKernel();
|
|
75032
|
+
const timeoutMs = (args.timeout ?? DEFAULT_TIMEOUT_MS / 1e3) * 1e3;
|
|
75033
|
+
const wrapperPy = `import ast as _ast, base64 as _b
|
|
75034
|
+
__c = _b.b64decode('${Buffer.from(args.code, "utf8").toString("base64")}').decode()\ntry:
|
|
75035
|
+
try:
|
|
75036
|
+
__r = eval(__c, globals())
|
|
75037
|
+
if __r is not None:
|
|
75038
|
+
print(repr(__r))
|
|
75039
|
+
except SyntaxError:
|
|
75040
|
+
__tree = _ast.parse(__c)
|
|
75041
|
+
__tail = __tree.body[-1] if __tree.body else None
|
|
75042
|
+
if isinstance(__tail, _ast.Expr) and not any(
|
|
75043
|
+
isinstance(n, (_ast.ClassDef, _ast.FunctionDef, _ast.AsyncFunctionDef))
|
|
75044
|
+
for n in _ast.walk(__tail)):
|
|
75045
|
+
__lines = __c.split("\\n")
|
|
75046
|
+
__head = "\\n".join(__lines[: __tail.lineno - 1] + [__lines[__tail.lineno - 1][: __tail.col_offset]])
|
|
75047
|
+
if __head.strip():
|
|
75048
|
+
exec(__head, globals())
|
|
75049
|
+
__r = eval(_ast.unparse(__tail.value), globals())
|
|
75050
|
+
if __r is not None:
|
|
75051
|
+
print(repr(__r))
|
|
75052
|
+
else:
|
|
75053
|
+
exec(__c, globals())
|
|
75054
|
+
except BaseException as __e:
|
|
75055
|
+
# Show the user’s own source lines (the raw REPL traceback
|
|
75056
|
+
# only exposes the base64 wrapper line, which is useless for
|
|
75057
|
+
# debugging), then re-raise so the kernel prints the real stack
|
|
75058
|
+
# and the tool flags the call as an error.
|
|
75059
|
+
print("Traceback (most recent call last):")
|
|
75060
|
+
for __i, __ln in enumerate(__c.split("\\n"), 1):
|
|
75061
|
+
print(f" File \\"<user_code>\\", line {__i}")
|
|
75062
|
+
print(f" {__ln}")
|
|
75063
|
+
print(f"{type(__e).__name__}: {__e}")
|
|
75064
|
+
raise`;
|
|
75065
|
+
const wrapperB64 = Buffer.from(wrapperPy, "utf8").toString("base64");
|
|
75066
|
+
const codeWithDone = this.hostHandlers !== void 0 ? `exec(__import__('base64').b64decode('${wrapperB64}').decode(), globals())\nprint('${PY_DONE_MARKER}')\n_snapshot()\n` : `exec(__import__('base64').b64decode('${wrapperB64}').decode(), globals())\nprint('${PY_DONE_MARKER}')\n`;
|
|
75067
|
+
if (!proc.stdin.write(codeWithDone)) await new Promise((resolve) => {
|
|
75068
|
+
const onError = () => resolve();
|
|
75069
|
+
proc.stdin.once("close", onError);
|
|
75070
|
+
proc.stdin.once("error", onError);
|
|
75071
|
+
proc.stdin.once("drain", () => {
|
|
75072
|
+
proc.stdin.off("close", onError);
|
|
75073
|
+
proc.stdin.off("error", onError);
|
|
75074
|
+
resolve();
|
|
75075
|
+
});
|
|
75076
|
+
});
|
|
75077
|
+
const { lines, found } = await this.readUntilMarker(proc.stdout, PY_DONE_MARKER, timeoutMs);
|
|
75078
|
+
const output = lines.filter((line) => !line.includes(PY_DONE_MARKER) && !line.trimStart().startsWith(">>> ") && !line.trimStart().startsWith("... ")).join("\n").trim();
|
|
75079
|
+
await this.drainStderrQuiet(proc);
|
|
75080
|
+
const stderr = this.kernelStderr.slice(this.kernelStderrOffset).split("\n").filter((line) => !line.trimStart().startsWith(">>> ") && !line.trimStart().startsWith("... ") && !line.trimStart().startsWith("Python ") && !line.trimStart().startsWith("Type \"help\"")).join("\n").trim();
|
|
75081
|
+
this.kernelStderrOffset = this.kernelStderr.length;
|
|
75082
|
+
const merged = [output, stderr].filter((part) => part.length > 0).join("\n");
|
|
75083
|
+
if (!found) {
|
|
75084
|
+
const preInterruptOffset = this.kernelStderrOffset;
|
|
75085
|
+
if (process.platform === "win32" ? true : await this.interruptKernel(proc, 1500)) {
|
|
75086
|
+
proc.kill("SIGKILL");
|
|
75087
|
+
this.kernel = void 0;
|
|
75088
|
+
return {
|
|
75089
|
+
isError: true,
|
|
75090
|
+
output: `Python execution timed out after ${Math.round(timeoutMs / 1e3)}s (kernel restarted).\n${merged}`
|
|
75091
|
+
};
|
|
75092
|
+
}
|
|
75093
|
+
if (!await this.syncKernel(proc)) {
|
|
75094
|
+
proc.kill("SIGKILL");
|
|
75095
|
+
this.kernel = void 0;
|
|
75096
|
+
return {
|
|
75097
|
+
isError: true,
|
|
75098
|
+
output: `Python execution timed out after ${Math.round(timeoutMs / 1e3)}s (kernel restarted).\n${merged}`
|
|
75099
|
+
};
|
|
75100
|
+
}
|
|
75101
|
+
await this.drainStderrQuiet(proc);
|
|
75102
|
+
const interruptStderr = this.kernelStderr.slice(preInterruptOffset).split("\n").filter((line) => !line.trimStart().startsWith(">>> ") && !line.trimStart().startsWith("... ") && !line.trimStart().startsWith("Python ") && !line.trimStart().startsWith("Type \"help\"")).join("\n").trim();
|
|
75103
|
+
this.kernelStderrOffset = this.kernelStderr.length;
|
|
75104
|
+
const timedOutMerged = [merged, interruptStderr].filter((part) => part.length > 0).join("\n");
|
|
75105
|
+
return {
|
|
75106
|
+
isError: true,
|
|
75107
|
+
output: `Python execution timed out after ${Math.round(timeoutMs / 1e3)}s (kernel interrupted; state preserved).\n${timedOutMerged}`
|
|
75108
|
+
};
|
|
75109
|
+
}
|
|
75110
|
+
return {
|
|
75111
|
+
isError: merged.includes("Traceback") || merged.toLowerCase().includes("error:"),
|
|
75112
|
+
output: merged.length > 0 ? merged : "(no output)"
|
|
75113
|
+
};
|
|
75114
|
+
} finally {
|
|
75115
|
+
this.kernelBusy = false;
|
|
75116
|
+
}
|
|
75117
|
+
}
|
|
75118
|
+
/**
|
|
75119
|
+
* Sends SIGINT to the kernel and waits (up to `graceMs`) for it to exit.
|
|
75120
|
+
* Returns true when the process exited — meaning the kernel is gone and a
|
|
75121
|
+
* fresh one must be started; false when it is still alive after the signal,
|
|
75122
|
+
* which for `python3 -i` means the REPL interrupted the running statement
|
|
75123
|
+
* and is ready again with state intact.
|
|
75124
|
+
*
|
|
75125
|
+
* `proc.killed` is NOT used as an exit check: it flips true the first time
|
|
75126
|
+
* `kill()` is called, so on a second timeout it would falsely report the
|
|
75127
|
+
* process as exited. Only `exitCode`/`signalCode` reflect a real exit.
|
|
75128
|
+
*/
|
|
75129
|
+
interruptKernel(proc, graceMs) {
|
|
75130
|
+
if (proc.exitCode !== null || proc.signalCode !== null) return Promise.resolve(true);
|
|
75131
|
+
return new Promise((resolve) => {
|
|
75132
|
+
const exited = () => resolve(true);
|
|
75133
|
+
const onExit = () => {
|
|
75134
|
+
clearTimeout(timer);
|
|
75135
|
+
exited();
|
|
75136
|
+
};
|
|
75137
|
+
const timer = setTimeout(() => {
|
|
75138
|
+
proc.off("exit", onExit);
|
|
75139
|
+
resolve(false);
|
|
75140
|
+
}, graceMs);
|
|
75141
|
+
proc.once("exit", onExit);
|
|
75142
|
+
proc.kill("SIGINT");
|
|
75143
|
+
});
|
|
75144
|
+
}
|
|
75145
|
+
/**
|
|
75146
|
+
* Round-trips a sync marker through the (interrupted) kernel: writes
|
|
75147
|
+
* `print('__SCREAM_SYNC__')` and waits for it to appear on stdout. Any
|
|
75148
|
+
* residual queued output (a DONE marker written before the timeout, a
|
|
75149
|
+
* partially-flushed traceback) is consumed by the same read loop, so the
|
|
75150
|
+
* next execution starts from a clean marker state. Returns true when the
|
|
75151
|
+
* marker came back — the REPL is idle and reusable.
|
|
75152
|
+
*/
|
|
75153
|
+
async syncKernel(proc, timeoutMs = 3e3) {
|
|
75154
|
+
const SYNC_MARKER = "__SCREAM_SYNC__";
|
|
75155
|
+
if (!proc.stdin.write(`print('${SYNC_MARKER}')\n`)) await new Promise((resolve) => {
|
|
75156
|
+
const onError = () => resolve();
|
|
75157
|
+
proc.stdin.once("close", onError);
|
|
75158
|
+
proc.stdin.once("error", onError);
|
|
75159
|
+
proc.stdin.once("drain", () => {
|
|
75160
|
+
proc.stdin.off("close", onError);
|
|
75161
|
+
proc.stdin.off("error", onError);
|
|
75162
|
+
resolve();
|
|
75163
|
+
});
|
|
75164
|
+
});
|
|
75165
|
+
const { found } = await this.readUntilMarker(proc.stdout, SYNC_MARKER, timeoutMs);
|
|
75166
|
+
return found;
|
|
75167
|
+
}
|
|
75168
|
+
/** Waits (bounded) for stderr to go quiet so tracebacks settle before the
|
|
75169
|
+
* execution snapshot is committed. */
|
|
75170
|
+
async drainStderrQuiet(proc, quietMs = 40, capMs = 400) {
|
|
75171
|
+
const start = Date.now();
|
|
75172
|
+
let quietSince = Date.now();
|
|
75173
|
+
while (Date.now() - start < capMs) {
|
|
75174
|
+
const before = this.kernelStderr.length;
|
|
75175
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
75176
|
+
if (this.kernelStderr.length === before) {
|
|
75177
|
+
if (Date.now() - quietSince >= quietMs) return;
|
|
75178
|
+
} else quietSince = Date.now();
|
|
75179
|
+
}
|
|
75180
|
+
}
|
|
75181
|
+
};
|
|
75182
|
+
//#endregion
|
|
74714
75183
|
//#region ../../packages/agent-core/src/tools/builtin/shell/bash.md
|
|
74715
75184
|
var bash_default = "Execute a `{{ SHELL_NAME }}` command. Use this for shell semantics — pipes, env, processes, git, package managers, build/test runners, anything genuinely interactive or multi-step.\n\n**Translate these to a dedicated tool instead:**\n- `cat` / `head` / `tail` (known path) → `Read`\n- `sed` / `awk` (in-place edit) → `Edit`\n- `echo > file` / `cat <<EOF` → `Write`\n- `find` / recursive `ls` to locate files by name pattern → `Glob` (plain `ls <known-directory>` is fine for listing a directory)\n- `grep` / `rg` (search file contents) → `Grep`\n- `echo` / `printf` (talk to the user) → just output text directly\n\nThe dedicated tools render in the per-tool permission UI and keep raw stdout out of the conversation; that is why they are worth reaching for whenever one fits.\n\n**Output:**\nThe stdout and stderr will be combined and returned as a string. The output may be truncated if it is too long. If the command failed, the output will end with a `Command failed with exit code: N` line stating the non-zero exit code.\n\nIf `run_in_background=true`, the command will be started as a background task and this tool will return a task ID instead of waiting for command completion. When doing that, you must provide a short `description`. Background commands default to a {{ DEFAULT_BACKGROUND_TIMEOUT_S }}s timeout and `timeout` is capped at {{ MAX_BACKGROUND_TIMEOUT_S }}s; set `disable_timeout=true` only when the task should run without a timeout. You will be automatically notified when the task completes. Use `TaskOutput` for a non-blocking status/output snapshot, and only set `block=true` when you explicitly want to wait for completion. Use `TaskStop` only if the task must be cancelled. If a human user wants to inspect background tasks themselves, point them to the `/tasks` command, which opens an interactive panel; it has no subcommands.\n\n**Guidelines for safety and security:**\n- Each shell tool call will be executed in a fresh shell environment. The shell variables, current working directory changes, and the shell history is not preserved between calls.\n- The tool call will return after the command is finished. You shall not use this tool to execute an interactive command or a command that may run forever. For possibly long-running foreground commands, set the `timeout` argument in seconds. Foreground commands default to {{ DEFAULT_TIMEOUT_S }}s and allow up to {{ MAX_TIMEOUT_S }}s.\n- Avoid using `..` to access files or directories outside of the working directory.\n- Avoid modifying files outside of the working directory unless explicitly instructed to do so.\n- Never run commands that require superuser privileges unless explicitly instructed to do so.\n\n**Guidelines for efficiency:**\n- For multiple related commands, use `&&` to chain them in a single call, e.g. `cd /path && ls -la`\n- Use `;` to run commands sequentially regardless of success/failure\n- Use `||` for conditional execution (run second command only if first fails)\n- Use pipe operations (`|`) and redirections (`>`, `>>`) to chain input and output between commands\n- Always quote file paths containing spaces with double quotes (e.g., cd \"/path with spaces/\")\n- Compose multi-step logic in a single call with `if` / `case` / `for` / `while` control flows.\n- Prefer `run_in_background=true` for long-running builds, tests, watchers, or servers when you need the conversation to continue before the command finishes.\n\n**Commands available:**\nThe following common command categories are usually available. Availability still depends on the host, so when in doubt run `which <command>` first to confirm a command exists before relying on it.\n- Navigation and inspection: `ls`, `pwd`, `cd`, `stat`, `file`, `du`, `df`, `tree`\n- File and directory management: `cp`, `mv`, `rm`, `mkdir`, `touch`, `ln`, `chmod`, `chown`\n- Text and data processing: `wc`, `sort`, `uniq`, `cut`, `tr`, `diff`, `xargs`\n- Archives and compression: `tar`, `gzip`, `gunzip`, `zip`, `unzip`\n- Networking and transfer: `curl`, `wget`, `ping`, `ssh`, `scp`\n- Version control: `git`\n- Process and system: `ps`, `kill`, `top`, `env`, `date`, `uname`, `whoami`\n- Language and package toolchains: `node`, `npm`, `pnpm`, `yarn`, `python`, `pip` (use whichever the project actually relies on)\n";
|
|
74716
75185
|
//#endregion
|
|
@@ -75926,7 +76395,7 @@ function stripContextMetadata(message) {
|
|
|
75926
76395
|
}
|
|
75927
76396
|
//#endregion
|
|
75928
76397
|
//#region ../../packages/agent-core/src/agent/compaction/compaction-instruction.md
|
|
75929
|
-
var compaction_instruction_default = "\n--- This message is a direct task, not part of the above conversation ---\n\nYou are now given a task to compact this conversation context according to specific priorities and output requirements.\n\nOutput text only. DO NOT CALL ANY TOOLS. Calling tools will be rejected and fails the task. You already have all the information you need in the conversation history. You have only one chance.\n\nThe goal of compaction is to keep essential code patterns, technical details, and architectural decisions for continuing development without losing context after the above messages are cleared work.\n\n{{ customInstruction }}\n\n<!-- Memory Memo Extraction (PRIORITY — do not skip) -->\n\n## 任务经验提取\n\nAFTER completing the compaction summary below, scan the messages being compacted for **completed task loops**. A task loop is \"completed\" when:\n- The user made a clear request or asked a specific question\n- You provided a solution or answer\n- The outcome is clear (success, partial success, or failure)\n\nFor each completed task loop found, output a structured experience record **at the very end of your response**:\n\n```memory-memo\n{\n \"userNeed\": \"<the user's need or goal, one sentence>\",\n \"approach\": \"<what was done — the approach taken, 2-4 sentences>\",\n \"outcome\": \"<final result, e.g. '完成', '部分完成', '失败: reason'>\",\n \"whatFailed\": \"<dead ends tried — things that didn't work, or 'none'>\",\n \"whatWorked\": \"<key actions that ultimately worked, or 'none'>\",\n \"tags\": [\"<tag1>\", \"<tag2>\", \"<tag3>\"]\n}\n```\n\nGuidelines:\n- Record important failed attempts in \"whatFailed\" to help avoid repeating mistakes.\n- Record key successful actions in \"whatWorked\" to help reuse effective approaches.\n- Include 3-5 semantic \"tags\" summarizing the task domain, tech stack, or action type (e.g. [\"react\", \"auth\", \"部署\"]).\n- Skip in-progress work unless it contains a valuable error+fix experience.\n- Merge closely related sub-tasks into a single record.\n- Use the exact field names and JSON format shown above.\n\nIf no completed task loops are found in the compacted messages, output:\n```memory-memo\n{\"none\": true}\n```\n\n<!-- Compression Priorities (in order) -->\n\n1. **Current Task State**: What is being worked on RIGHT NOW\n2. **Errors & Solutions**: All encountered errors and their resolutions\n3. **Code Evolution**: Final working versions only (remove intermediate attempts)\n4. **System Context**: Project structure, dependencies, environment setup\n5. **Design Decisions**: Architectural choices and their rationale\n6. **TODO Items**: Unfinished tasks and known issues\n\n<!-- Required Output Structure -->\n\n## Current Focus\n\n[What we're working on now]\n\n## Environment\n\n- [Key setup/config points]\n- ...\n\n## Completed Tasks\n\n- [Task]: [Brief outcome]\n- ...\n\n## Active Issues\n\n- [Issue]: [Status/Next steps]\n- ...\n\n## Code State\n\n### [Critical file name]\n\n[Brief description of the file's purpose and current state]\n\n```\n[The latest version of critical code snippets in this file, <20 lines]\n```\n\n### [Critical file name]\n\n- [Useful classes/methods/functions]: [Brief description/usage]\n- ...\n\n<!-- Omit non-critical code, intermediate attempts, and resolved errors -->\n\n## Important Context\n\n- [Any crucial information not covered above]\n- ...\n\n## All User Messages\n\n- [Detailed non tool use user message]\n- ...\n";
|
|
76398
|
+
var compaction_instruction_default = "\n--- This message is a direct task, not part of the above conversation ---\n\nYou are now given a task to compact this conversation context according to specific priorities and output requirements.\n\nOutput text only. DO NOT CALL ANY TOOLS. Calling tools will be rejected and fails the task. You already have all the information you need in the conversation history. You have only one chance.\n\nThe goal of compaction is to keep essential code patterns, technical details, and architectural decisions for continuing development without losing context after the above messages are cleared work.\n\n{{ customInstruction }}\n\n<!-- Memory Memo Extraction (PRIORITY — do not skip) -->\n\n## 任务经验提取\n\nAFTER completing the compaction summary below, scan the messages being compacted for **completed task loops**. A task loop is \"completed\" when:\n- The user made a clear request or asked a specific question\n- You provided a solution or answer\n- The outcome is clear (success, partial success, or failure)\n\nFor each completed task loop found, output a structured experience record **at the very end of your response**:\n\n```memory-memo\n{\n \"userNeed\": \"<the user's need or goal, one sentence>\",\n \"approach\": \"<what was done — the approach taken, 2-4 sentences>\",\n \"outcome\": \"<final result, e.g. '完成', '部分完成', '失败: reason'>\",\n \"whatFailed\": \"<dead ends tried — things that didn't work, or 'none'>\",\n \"whatWorked\": \"<key actions that ultimately worked, or 'none'>\",\n \"tags\": [\"<tag1>\", \"<tag2>\", \"<tag3>\"],\n \"note\": \"<optional: a soft, AI-readable note/suggestion that makes the record easier for future turns to understand — e.g. '这类任务先查接口可用性再写代码'. Omit if none>\"\n}\n```\n\nGuidelines:\n- Record important failed attempts in \"whatFailed\" to help avoid repeating mistakes.\n- Record key successful actions in \"whatWorked\" to help reuse effective approaches.\n- Include 3-5 semantic \"tags\" summarizing the task domain, tech stack, or action type (e.g. [\"react\", \"auth\", \"部署\"]).\n- \"note\" is optional: a one-sentence advisory note that helps future AI understand/reuse the record faster. It is a soft suggestion, not a user-enforced rule.\n- Skip in-progress work unless it contains a valuable error+fix experience.\n- Merge closely related sub-tasks into a single record.\n- Use the exact field names and JSON format shown above (no extra fields beyond \"note\").\n\nIf no completed task loops are found in the compacted messages, output:\n```memory-memo\n{\"none\": true}\n```\n\n<!-- Compression Priorities (in order) -->\n\n1. **Current Task State**: What is being worked on RIGHT NOW\n2. **Errors & Solutions**: All encountered errors and their resolutions\n3. **Code Evolution**: Final working versions only (remove intermediate attempts)\n4. **System Context**: Project structure, dependencies, environment setup\n5. **Design Decisions**: Architectural choices and their rationale\n6. **TODO Items**: Unfinished tasks and known issues\n\n<!-- Required Output Structure -->\n\n## Current Focus\n\n[What we're working on now]\n\n## Environment\n\n- [Key setup/config points]\n- ...\n\n## Completed Tasks\n\n- [Task]: [Brief outcome]\n- ...\n\n## Active Issues\n\n- [Issue]: [Status/Next steps]\n- ...\n\n## Code State\n\n### [Critical file name]\n\n[Brief description of the file's purpose and current state]\n\n```\n[The latest version of critical code snippets in this file, <20 lines]\n```\n\n### [Critical file name]\n\n- [Useful classes/methods/functions]: [Brief description/usage]\n- ...\n\n<!-- Omit non-critical code, intermediate attempts, and resolved errors -->\n\n## Important Context\n\n- [Any crucial information not covered above]\n- ...\n\n## All User Messages\n\n- [Detailed non tool use user message]\n- ...\n\n## Skill candidates\n\nScan the compressed messages for reusable processes (project-specific build\nsteps, recurring debugging patterns, or tool workflows). If any is genuinely\nworth capturing as a reusable skill, output at the very end, at most one:\n\n[[skill-candidate: <name>|<one-line purpose>|<evidence>]]\n\nOmit if none.\n";
|
|
75930
76399
|
//#endregion
|
|
75931
76400
|
//#region ../../packages/agent-core/src/agent/compaction/compaction-update-instruction.md
|
|
75932
76401
|
var compaction_update_instruction_default = "\n--- This message is a direct task, not part of the above conversation ---\n\nYou are now given a task to UPDATE an existing compaction summary with new messages that came in after the last compaction.\n\nA previous compaction summary already exists at the top of the conversation (the first assistant message). You must NOT discard it. Instead, merge the new information into the existing structure, keeping all previously captured state intact unless explicitly contradicted or resolved by the new messages.\n\nOutput text only. DO NOT CALL ANY TOOLS. Calling tools will be rejected and fails the task. You have only one chance.\n\n{{ customInstruction }}\n\n<!-- Previous Summary Reference -->\n\nThe first assistant message above is the PREVIOUS summary. Treat it as the source of truth for everything that happened before the new messages. Do not restate it verbatim — produce an updated, merged summary.\n\n<!-- Memory Memo Extraction (PRIORITY — do not skip) -->\n\n## 任务经验提取\n\nAFTER completing the updated summary below, scan ONLY the new messages being compacted (not those already covered by the previous summary) for **completed task loops**. A task loop is \"completed\" when:\n- The user made a clear request or asked a specific question\n- You provided a solution or answer\n- The outcome is clear (success, partial success, or failure)\n\nFor each completed task loop found, output a structured experience record **at the very end of your response**:\n\n```memory-memo\n{\n \"userNeed\": \"<the user's need or goal, one sentence>\",\n \"approach\": \"<what was done — the approach taken, 2-4 sentences>\",\n \"outcome\": \"<final result, e.g. '完成', '部分完成', '失败: reason'>\",\n \"whatFailed\": \"<dead ends tried — things that didn't work, or 'none'>\",\n \"whatWorked\": \"<key actions that ultimately worked, or 'none'>\",\n \"tags\": [\"<tag1>\", \"<tag2>\", \"<tag3>\"]\n}\n```\n\nGuidelines:\n- Record important failed attempts in \"whatFailed\" to help avoid repeating mistakes.\n- Record key successful actions in \"whatWorked\" to help reuse effective approaches.\n- Include 3-5 semantic \"tags\" summarizing the task domain, tech stack, or action type.\n- Skip in-progress work unless it contains a valuable error+fix experience.\n- Merge closely related sub-tasks into a single record.\n- Use the exact field names and JSON format shown above.\n\nIf no completed task loops are found in the new compacted messages, output:\n```memory-memo\n{\"none\": true}\n```\n\n<!-- Update Rules -->\n\n1. Preserve the section structure of the previous summary (Current Focus, Environment, Completed Tasks, Active Issues, Code State, Important Context, All User Messages).\n2. Move newly-completed tasks from \"Current Focus\" / \"Active Issues\" into \"Completed Tasks\".\n3. Update \"Current Focus\" to reflect what is being worked on RIGHT NOW.\n4. Append new user messages to \"All User Messages\" — do not repeat those already captured.\n5. Refresh \"Code State\" code snippets only if newer versions exist in the new messages.\n6. Drop resolved issues from \"Active Issues\"; add newly-discovered ones.\n7. Do not invent new information. If the new messages say nothing about a section, carry the previous content forward unchanged.\n\n<!-- Required Output Structure -->\n\n## Current Focus\n\n[What we're working on now]\n\n## Environment\n\n- [Key setup/config points]\n- ...\n\n## Completed Tasks\n\n- [Task]: [Brief outcome]\n- ...\n\n## Active Issues\n\n- [Issue]: [Status/Next steps]\n- ...\n\n## Code State\n\n### [Critical file name]\n\n[Brief description of the file's purpose and current state]\n\n```\n[The latest version of critical code snippets in this file, <20 lines]\n```\n\n## Important Context\n\n- [Any crucial information not covered above]\n- ...\n\n## All User Messages\n\n- [Detailed non tool use user message]\n- ...\n";
|
|
@@ -76551,6 +77020,7 @@ var FullCompaction = class {
|
|
|
76551
77020
|
this.lowWaterMark = Math.floor(this.effectiveTokenCount * 1.1);
|
|
76552
77021
|
await this.extractAndStoreMemos(processedSummary);
|
|
76553
77022
|
this.triggerPostCompactHook(data, result);
|
|
77023
|
+
this.detectSkillCandidates(processedSummary);
|
|
76554
77024
|
this.consecutiveCompactionFailures = 0;
|
|
76555
77025
|
this._shouldInjectSessionSummary = true;
|
|
76556
77026
|
} catch (error) {
|
|
@@ -76641,6 +77111,33 @@ var FullCompaction = class {
|
|
|
76641
77111
|
});
|
|
76642
77112
|
}
|
|
76643
77113
|
/**
|
|
77114
|
+
* Detects [[skill-candidate: <name>|<purpose>|<evidence>]] markers in the
|
|
77115
|
+
* compaction summary and emits a `skill_candidate` event for each one (at
|
|
77116
|
+
* most one per compact). Best-effort and fully isolated: a parse failure or
|
|
77117
|
+
* a missing marker never affects compaction or memory extraction — this
|
|
77118
|
+
* method only reads the summary and emits an event.
|
|
77119
|
+
*/
|
|
77120
|
+
detectSkillCandidates(summary) {
|
|
77121
|
+
try {
|
|
77122
|
+
const matches = summary.matchAll(/\[\[skill-candidate:\s*([^|\]]+)\|([^|\]]+)\|([^|\]]+)\]\]/g);
|
|
77123
|
+
for (const m of matches) {
|
|
77124
|
+
const name = m[1].trim();
|
|
77125
|
+
if (name.length === 0) continue;
|
|
77126
|
+
this.agent.emitEvent({
|
|
77127
|
+
type: "skill_candidate",
|
|
77128
|
+
candidate: {
|
|
77129
|
+
name,
|
|
77130
|
+
purpose: m[2].trim(),
|
|
77131
|
+
evidence: m[3].trim()
|
|
77132
|
+
}
|
|
77133
|
+
});
|
|
77134
|
+
break;
|
|
77135
|
+
}
|
|
77136
|
+
} catch (error) {
|
|
77137
|
+
this.agent.log.warn("Skill candidate detection failed", { error: String(error) });
|
|
77138
|
+
}
|
|
77139
|
+
}
|
|
77140
|
+
/**
|
|
76644
77141
|
* Append the current todo list and file operations as markdown sections to
|
|
76645
77142
|
* the compaction summary so active tasks and file context survive
|
|
76646
77143
|
* compression. Without this, both are lost after compaction because the
|
|
@@ -82111,6 +82608,12 @@ function restoreAgentRecord(agent, input) {
|
|
|
82111
82608
|
case "wolfpack.exit":
|
|
82112
82609
|
agent.wolfpackMode.exit();
|
|
82113
82610
|
return;
|
|
82611
|
+
case "rlm.enter":
|
|
82612
|
+
agent.restoreRlm(true);
|
|
82613
|
+
return;
|
|
82614
|
+
case "rlm.exit":
|
|
82615
|
+
agent.restoreRlm(false);
|
|
82616
|
+
return;
|
|
82114
82617
|
case "goal.create":
|
|
82115
82618
|
agent.goal.restoreCreate(input);
|
|
82116
82619
|
return;
|
|
@@ -95160,6 +95663,70 @@ function createGoalGrader(agent) {
|
|
|
95160
95663
|
};
|
|
95161
95664
|
};
|
|
95162
95665
|
}
|
|
95666
|
+
/**
|
|
95667
|
+
* Host bridge handlers for the /rlm python kernel: `rlm.run` spawns a
|
|
95668
|
+
* subagent (reusing the subagent host) and returns its handle id;
|
|
95669
|
+
* `rlm.result` waits for that subagent's final summary. Handles are kept in
|
|
95670
|
+
* a closure map for the lifetime of the ToolManager (one per session).
|
|
95671
|
+
*
|
|
95672
|
+
* Recursion guard: a subagent may only spawn its own rlm() children if its
|
|
95673
|
+
* depth is below the cap. Depth is carried on each agent instance (root = 0,
|
|
95674
|
+
* every spawned subagent = parent + 1). The cap is per-agent
|
|
95675
|
+
* (setRlmMaxDepth / /rlm-max-depth); it is `Infinity` by default — unlimited
|
|
95676
|
+
* recursion — and a positive integer when the user opts into a limit.
|
|
95677
|
+
*/
|
|
95678
|
+
function createRlmHostHandlers(agent) {
|
|
95679
|
+
const handles = /* @__PURE__ */ new Map();
|
|
95680
|
+
return {
|
|
95681
|
+
"rlm.run": async (payload) => {
|
|
95682
|
+
const host = agent.subagentHost;
|
|
95683
|
+
if (host === void 0) throw new Error("subagent host unavailable");
|
|
95684
|
+
if (agent.getRlmDepth() >= agent.getRlmMaxDepth()) throw new Error(`RLM recursion depth limit reached (depth ${agent.getRlmDepth()}, max ${agent.getRlmMaxDepth()}). Nested rlm() subagents are not allowed.`);
|
|
95685
|
+
const task = String(payload["task"] ?? "");
|
|
95686
|
+
const name = String(payload["name"] ?? "subagent").slice(0, 64);
|
|
95687
|
+
const controller = new AbortController();
|
|
95688
|
+
const handle = await host.spawn("coder", {
|
|
95689
|
+
parentToolCallId: `rlm_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
95690
|
+
prompt: task,
|
|
95691
|
+
description: `rlm subagent: ${name}`,
|
|
95692
|
+
runInBackground: false,
|
|
95693
|
+
signal: controller.signal
|
|
95694
|
+
});
|
|
95695
|
+
const entry = {
|
|
95696
|
+
completion: handle.completion,
|
|
95697
|
+
name,
|
|
95698
|
+
controller
|
|
95699
|
+
};
|
|
95700
|
+
handles.set(handle.agentId, entry);
|
|
95701
|
+
handle.completion.then((result) => {
|
|
95702
|
+
entry.result = result;
|
|
95703
|
+
}, () => {
|
|
95704
|
+
entry.result = void 0;
|
|
95705
|
+
});
|
|
95706
|
+
return {
|
|
95707
|
+
id: handle.agentId,
|
|
95708
|
+
name
|
|
95709
|
+
};
|
|
95710
|
+
},
|
|
95711
|
+
"rlm.result": async (payload) => {
|
|
95712
|
+
const id = String(payload["id"]);
|
|
95713
|
+
const entry = handles.get(id);
|
|
95714
|
+
if (entry === void 0) throw new Error(`unknown rlm handle: ${id}`);
|
|
95715
|
+
if (entry.result !== void 0) {
|
|
95716
|
+
handles.delete(id);
|
|
95717
|
+
return { result: entry.result };
|
|
95718
|
+
}
|
|
95719
|
+
const result = (await entry.completion).result;
|
|
95720
|
+
handles.delete(id);
|
|
95721
|
+
return { result };
|
|
95722
|
+
},
|
|
95723
|
+
__dispose__: async () => {
|
|
95724
|
+
for (const { controller } of handles.values()) controller.abort(/* @__PURE__ */ new Error("rlm kernel disposed"));
|
|
95725
|
+
handles.clear();
|
|
95726
|
+
return {};
|
|
95727
|
+
}
|
|
95728
|
+
};
|
|
95729
|
+
}
|
|
95163
95730
|
var ToolManager = class {
|
|
95164
95731
|
agent;
|
|
95165
95732
|
builtinTools = /* @__PURE__ */ new Map();
|
|
@@ -95417,6 +95984,12 @@ var ToolManager = class {
|
|
|
95417
95984
|
} })
|
|
95418
95985
|
});
|
|
95419
95986
|
}
|
|
95987
|
+
getActiveTools() {
|
|
95988
|
+
return [...this.enabledTools];
|
|
95989
|
+
}
|
|
95990
|
+
getBuiltinTool(name) {
|
|
95991
|
+
return this.builtinTools.get(name);
|
|
95992
|
+
}
|
|
95420
95993
|
setActiveTools(names) {
|
|
95421
95994
|
this.agent.records.logRecord({
|
|
95422
95995
|
type: "tools.set_active_tools",
|
|
@@ -95477,6 +96050,7 @@ var ToolManager = class {
|
|
|
95477
96050
|
allowBackground,
|
|
95478
96051
|
availableTools: this.enabledTools
|
|
95479
96052
|
}),
|
|
96053
|
+
new PythonTool(cwd, { hostHandlers: createRlmHostHandlers(this.agent) }),
|
|
95480
96054
|
(modelCapabilities.image_in || modelCapabilities.video_in) && new ReadMediaFileTool(jian, workspace, modelCapabilities, videoUploader),
|
|
95481
96055
|
new EnterPlanModeTool(this.agent),
|
|
95482
96056
|
new ExitPlanModeTool(this.agent),
|
|
@@ -97095,6 +97669,96 @@ var Agent = class {
|
|
|
97095
97669
|
}
|
|
97096
97670
|
})();
|
|
97097
97671
|
}
|
|
97672
|
+
rlmEnabled = false;
|
|
97673
|
+
/** RLM recursion depth: 0 for the root agent, +1 per spawned subagent. */
|
|
97674
|
+
rlmDepth = 0;
|
|
97675
|
+
/** Maximum allowed RLM recursion depth. `Infinity` (the default) means
|
|
97676
|
+
* unlimited recursion. Set to a positive integer via /rlm-max-depth to
|
|
97677
|
+
* cap the chain (e.g. 1 = root may spawn children, children may not spawn
|
|
97678
|
+
* grandchildren); 0 is treated as unlimited as well. */
|
|
97679
|
+
rlmMaxDepth = Infinity;
|
|
97680
|
+
getRlmDepth() {
|
|
97681
|
+
return this.rlmDepth;
|
|
97682
|
+
}
|
|
97683
|
+
setRlmDepth(depth) {
|
|
97684
|
+
this.rlmDepth = Math.max(0, depth);
|
|
97685
|
+
}
|
|
97686
|
+
getRlmMaxDepth() {
|
|
97687
|
+
return this.rlmMaxDepth;
|
|
97688
|
+
}
|
|
97689
|
+
setRlmMaxDepth(maxDepth) {
|
|
97690
|
+
if (!Number.isFinite(maxDepth) || maxDepth <= 0) {
|
|
97691
|
+
this.rlmMaxDepth = Infinity;
|
|
97692
|
+
return;
|
|
97693
|
+
}
|
|
97694
|
+
this.rlmMaxDepth = Math.trunc(maxDepth);
|
|
97695
|
+
}
|
|
97696
|
+
/** Disposes the persistent python kernel (if any) and resets RLM mode.
|
|
97697
|
+
* Called on session close so no orphaned kernel process is left behind.
|
|
97698
|
+
* Only touches state when RLM was actually enabled — a session that never
|
|
97699
|
+
* used /rlm must not emit a spurious setActiveTools record (which would
|
|
97700
|
+
* pollute the wire log and, on a subagent that does not re-apply a profile
|
|
97701
|
+
* on resume, could strip MCP access patterns). */
|
|
97702
|
+
disposeRlm() {
|
|
97703
|
+
if (!this.rlmEnabled) return;
|
|
97704
|
+
this.rlmEnabled = false;
|
|
97705
|
+
const withoutPython = this.tools.getActiveTools().filter((name) => name !== "python");
|
|
97706
|
+
this.tools.setActiveTools(withoutPython);
|
|
97707
|
+
this.tools.getBuiltinTool("python")?.dispose?.();
|
|
97708
|
+
this.records.logRecord({ type: "rlm.exit" });
|
|
97709
|
+
}
|
|
97710
|
+
/**
|
|
97711
|
+
* Enables or disables the /rlm persistent-python mode. On enable, the
|
|
97712
|
+
* `python` tool is added to the active tools; on disable it is removed and
|
|
97713
|
+
* its kernel is disposed. Default state (disabled) leaves the tool list and
|
|
97714
|
+
* default behaviour untouched.
|
|
97715
|
+
*/
|
|
97716
|
+
setRlmEnabled(enabled) {
|
|
97717
|
+
this.rlmEnabled = enabled;
|
|
97718
|
+
const current = this.tools.getActiveTools();
|
|
97719
|
+
if (enabled) {
|
|
97720
|
+
if (!current.includes("python")) this.tools.setActiveTools([...current, "python"]);
|
|
97721
|
+
} else {
|
|
97722
|
+
const withoutPython = current.filter((name) => name !== "python");
|
|
97723
|
+
if (withoutPython.length !== current.length) this.tools.setActiveTools(withoutPython);
|
|
97724
|
+
this.tools.getBuiltinTool("python")?.dispose?.();
|
|
97725
|
+
}
|
|
97726
|
+
this.records.logRecord({ type: enabled ? "rlm.enter" : "rlm.exit" });
|
|
97727
|
+
this.emitStatusUpdated();
|
|
97728
|
+
}
|
|
97729
|
+
/** Returns whether RLM (persistent python mode) is currently enabled. */
|
|
97730
|
+
getRlmEnabled() {
|
|
97731
|
+
return this.rlmEnabled;
|
|
97732
|
+
}
|
|
97733
|
+
/**
|
|
97734
|
+
* Lets a subagent inherit RLM mode from its parent at spawn time: mounts
|
|
97735
|
+
* the python tool (so the child can keep recursing) and records rlm.enter,
|
|
97736
|
+
* mirroring setRlmEnabled(true). The rlm.enter record keeps enter/exit
|
|
97737
|
+
* symmetric: the child's disposeRlm (triggered by session close) emits
|
|
97738
|
+
* rlm.exit, so a subagent's records must contain the matching enter or the
|
|
97739
|
+
* pair is unbalanced. A status event is NOT emitted here — the child's own
|
|
97740
|
+
* turn is about to start and the parent's record already represents this
|
|
97741
|
+
* recursion chain. Non-RLM parents never call this.
|
|
97742
|
+
*/
|
|
97743
|
+
inheritRlm() {
|
|
97744
|
+
this.rlmEnabled = true;
|
|
97745
|
+
const current = this.tools.getActiveTools();
|
|
97746
|
+
if (!current.includes("python")) this.tools.setActiveTools([...current, "python"]);
|
|
97747
|
+
this.records.logRecord({ type: "rlm.enter" });
|
|
97748
|
+
}
|
|
97749
|
+
/** Restores RLM mode from a persisted record during replay. Does not log
|
|
97750
|
+
* a new record and does not emit a status update (both are suppressed while
|
|
97751
|
+
* records are restoring). */
|
|
97752
|
+
restoreRlm(enabled) {
|
|
97753
|
+
this.rlmEnabled = enabled;
|
|
97754
|
+
const current = this.tools.getActiveTools();
|
|
97755
|
+
if (enabled) {
|
|
97756
|
+
if (!current.includes("python")) this.tools.setActiveTools([...current, "python"]);
|
|
97757
|
+
} else {
|
|
97758
|
+
const withoutPython = current.filter((name) => name !== "python");
|
|
97759
|
+
if (withoutPython.length !== current.length) this.tools.setActiveTools(withoutPython);
|
|
97760
|
+
}
|
|
97761
|
+
}
|
|
97098
97762
|
initKnowledgeStore(store) {
|
|
97099
97763
|
if (store === void 0) return Promise.resolve();
|
|
97100
97764
|
return (async () => {
|
|
@@ -97262,6 +97926,12 @@ var Agent = class {
|
|
|
97262
97926
|
setActiveTools: (payload) => {
|
|
97263
97927
|
this.tools.setActiveTools(payload.names);
|
|
97264
97928
|
},
|
|
97929
|
+
setRlmEnabled: (payload) => {
|
|
97930
|
+
this.setRlmEnabled(payload.enabled);
|
|
97931
|
+
},
|
|
97932
|
+
setRlmMaxDepth: (payload) => {
|
|
97933
|
+
this.setRlmMaxDepth(payload.maxDepth);
|
|
97934
|
+
},
|
|
97265
97935
|
stopBackground: (payload) => {
|
|
97266
97936
|
this.background.stop(payload.taskId, payload.reason);
|
|
97267
97937
|
},
|
|
@@ -97335,6 +98005,9 @@ var Agent = class {
|
|
|
97335
98005
|
},
|
|
97336
98006
|
getWolfpackMode: () => {
|
|
97337
98007
|
return this.wolfpackMode.isActive;
|
|
98008
|
+
},
|
|
98009
|
+
getRlmEnabled: () => {
|
|
98010
|
+
return this.rlmEnabled;
|
|
97338
98011
|
}
|
|
97339
98012
|
};
|
|
97340
98013
|
}
|
|
@@ -97459,6 +98132,7 @@ var Agent = class {
|
|
|
97459
98132
|
planMode: this.planMode.isActive,
|
|
97460
98133
|
planStrategy: this.planMode.isActive ? this.planMode.strategy : void 0,
|
|
97461
98134
|
wolfpackMode: this.wolfpackMode.isActive,
|
|
98135
|
+
rlmEnabled: this.rlmEnabled,
|
|
97462
98136
|
permission: this.permission.mode,
|
|
97463
98137
|
usage
|
|
97464
98138
|
});
|
|
@@ -101779,6 +102453,8 @@ var SessionSubagentHost = class {
|
|
|
101779
102453
|
type: "sub",
|
|
101780
102454
|
generate: parent.rawGenerate
|
|
101781
102455
|
}, void 0, this.ownerAgentId);
|
|
102456
|
+
agent.setRlmDepth(parent.getRlmDepth() + 1);
|
|
102457
|
+
agent.setRlmMaxDepth(parent.getRlmMaxDepth());
|
|
101782
102458
|
const controller = new AbortController();
|
|
101783
102459
|
const unlinkAbortSignal = linkAbortSignal(options.signal, controller);
|
|
101784
102460
|
this.activeChildren.set(id, {
|
|
@@ -101946,6 +102622,7 @@ var SessionSubagentHost = class {
|
|
|
101946
102622
|
});
|
|
101947
102623
|
const context = await prepareSystemPromptContext(child.jian);
|
|
101948
102624
|
child.useProfile(profile, context);
|
|
102625
|
+
if (parent.getRlmEnabled()) child.inheritRlm();
|
|
101949
102626
|
}
|
|
101950
102627
|
resolveModelBinding(profileName) {
|
|
101951
102628
|
const bindings = this.modelBindings?.();
|
|
@@ -102127,6 +102804,7 @@ var Session$1 = class {
|
|
|
102127
102804
|
try {
|
|
102128
102805
|
for (const agent of this.agents.values()) if (agent.turn.hasActiveTurn) agent.turn.cancel();
|
|
102129
102806
|
await Promise.allSettled(Array.from(this.agents.values(), async (agent) => agent.cron?.stop()));
|
|
102807
|
+
for (const agent of this.agents.values()) agent.disposeRlm?.();
|
|
102130
102808
|
await this.stopBackgroundTasksOnExit();
|
|
102131
102809
|
await this.flushMetadata();
|
|
102132
102810
|
await this.triggerSessionEnd("exit");
|
|
@@ -117314,6 +117992,12 @@ var SessionAPIImpl = class {
|
|
|
117314
117992
|
setActiveTools({ agentId, ...payload }) {
|
|
117315
117993
|
return this.getAgent(agentId).setActiveTools(payload);
|
|
117316
117994
|
}
|
|
117995
|
+
setRlmEnabled({ agentId, ...payload }) {
|
|
117996
|
+
return this.getAgent(agentId).setRlmEnabled(payload);
|
|
117997
|
+
}
|
|
117998
|
+
setRlmMaxDepth({ agentId, ...payload }) {
|
|
117999
|
+
return this.getAgent(agentId).setRlmMaxDepth(payload);
|
|
118000
|
+
}
|
|
117317
118001
|
stopBackground({ agentId, ...payload }) {
|
|
117318
118002
|
return this.getAgent(agentId).stopBackground(payload);
|
|
117319
118003
|
}
|
|
@@ -117387,6 +118071,9 @@ var SessionAPIImpl = class {
|
|
|
117387
118071
|
getWolfpackMode({ agentId }) {
|
|
117388
118072
|
return this.getAgent(agentId).getWolfpackMode({});
|
|
117389
118073
|
}
|
|
118074
|
+
getRlmEnabled({ agentId }) {
|
|
118075
|
+
return this.getAgent(agentId).getRlmEnabled({});
|
|
118076
|
+
}
|
|
117390
118077
|
getAgent(agentId) {
|
|
117391
118078
|
const agent = this.session.agents.get(agentId);
|
|
117392
118079
|
if (agent === void 0) throw new ScreamError(ErrorCodes.AGENT_NOT_FOUND, `Agent "${agentId}" was not found`);
|
|
@@ -119063,6 +119750,12 @@ var ScreamCore = class {
|
|
|
119063
119750
|
setActiveTools({ sessionId, ...payload }) {
|
|
119064
119751
|
return this.sessionApi(sessionId).setActiveTools(payload);
|
|
119065
119752
|
}
|
|
119753
|
+
setRlmEnabled({ sessionId, ...payload }) {
|
|
119754
|
+
return this.sessionApi(sessionId).setRlmEnabled(payload);
|
|
119755
|
+
}
|
|
119756
|
+
setRlmMaxDepth({ sessionId, ...payload }) {
|
|
119757
|
+
return this.sessionApi(sessionId).setRlmMaxDepth(payload);
|
|
119758
|
+
}
|
|
119066
119759
|
stopBackground({ sessionId, ...payload }) {
|
|
119067
119760
|
return this.sessionApi(sessionId).stopBackground(payload);
|
|
119068
119761
|
}
|
|
@@ -119135,6 +119828,9 @@ var ScreamCore = class {
|
|
|
119135
119828
|
getWolfpackMode({ sessionId, ...payload }) {
|
|
119136
119829
|
return this.sessionApi(sessionId).getWolfpackMode(payload);
|
|
119137
119830
|
}
|
|
119831
|
+
getRlmEnabled({ sessionId, ...payload }) {
|
|
119832
|
+
return this.sessionApi(sessionId).getRlmEnabled(payload);
|
|
119833
|
+
}
|
|
119138
119834
|
updateSessionMetadata({ sessionId, ...payload }) {
|
|
119139
119835
|
return this.sessionApi(sessionId).updateSessionMetadata(payload);
|
|
119140
119836
|
}
|
|
@@ -119773,7 +120469,7 @@ var SDKRpcClient = class {
|
|
|
119773
120469
|
async getStatus(input) {
|
|
119774
120470
|
const rpc = await this.getRpc();
|
|
119775
120471
|
const agentId = this.interactiveAgentId;
|
|
119776
|
-
const [config, context, permission, plan, usage, wolfpackMode] = await Promise.all([
|
|
120472
|
+
const [config, context, permission, plan, usage, wolfpackMode, rlmEnabled] = await Promise.all([
|
|
119777
120473
|
rpc.getConfig({
|
|
119778
120474
|
sessionId: input.sessionId,
|
|
119779
120475
|
agentId
|
|
@@ -119797,6 +120493,10 @@ var SDKRpcClient = class {
|
|
|
119797
120493
|
rpc.getWolfpackMode({
|
|
119798
120494
|
sessionId: input.sessionId,
|
|
119799
120495
|
agentId
|
|
120496
|
+
}),
|
|
120497
|
+
rpc.getRlmEnabled({
|
|
120498
|
+
sessionId: input.sessionId,
|
|
120499
|
+
agentId
|
|
119800
120500
|
})
|
|
119801
120501
|
]);
|
|
119802
120502
|
const maxContextTokens = config.modelCapabilities?.max_context_tokens ?? 0;
|
|
@@ -119810,6 +120510,7 @@ var SDKRpcClient = class {
|
|
|
119810
120510
|
planMode: plan !== null,
|
|
119811
120511
|
planStrategy: plan?.strategy,
|
|
119812
120512
|
wolfpackMode,
|
|
120513
|
+
rlmEnabled,
|
|
119813
120514
|
contextTokens,
|
|
119814
120515
|
maxContextTokens,
|
|
119815
120516
|
contextUsage,
|
|
@@ -119854,6 +120555,20 @@ var SDKRpcClient = class {
|
|
|
119854
120555
|
taskId: input.taskId
|
|
119855
120556
|
});
|
|
119856
120557
|
}
|
|
120558
|
+
async setRlmEnabled(input) {
|
|
120559
|
+
return (await this.getRpc()).setRlmEnabled({
|
|
120560
|
+
sessionId: input.sessionId,
|
|
120561
|
+
agentId: this.interactiveAgentId,
|
|
120562
|
+
enabled: input.enabled
|
|
120563
|
+
});
|
|
120564
|
+
}
|
|
120565
|
+
async setRlmMaxDepth(input) {
|
|
120566
|
+
return (await this.getRpc()).setRlmMaxDepth({
|
|
120567
|
+
sessionId: input.sessionId,
|
|
120568
|
+
agentId: this.interactiveAgentId,
|
|
120569
|
+
maxDepth: input.maxDepth
|
|
120570
|
+
});
|
|
120571
|
+
}
|
|
119857
120572
|
async stopBackgroundTask(input) {
|
|
119858
120573
|
return (await this.getRpc()).stopBackground({
|
|
119859
120574
|
sessionId: input.sessionId,
|
|
@@ -120279,6 +120994,27 @@ var Session = class {
|
|
|
120279
120994
|
* for unknown or already-terminal task ids are no-ops at the core
|
|
120280
120995
|
* level — this method does not throw in those cases.
|
|
120281
120996
|
*/
|
|
120997
|
+
/**
|
|
120998
|
+
* Enables or disables the /rlm persistent-python mode. When enabled, the
|
|
120999
|
+
* `python` tool (persistent kernel) is added to the active tools; when
|
|
121000
|
+
* disabled it is removed and its kernel is disposed. Default is disabled.
|
|
121001
|
+
*/
|
|
121002
|
+
async setRlmEnabled(enabled) {
|
|
121003
|
+
this.ensureOpen();
|
|
121004
|
+
await this.rpc.setRlmEnabled({
|
|
121005
|
+
sessionId: this.id,
|
|
121006
|
+
enabled
|
|
121007
|
+
});
|
|
121008
|
+
}
|
|
121009
|
+
/** Sets the maximum RLM recursion depth (default 1). Depth 1 means the root
|
|
121010
|
+
* kernel may spawn rlm() children but they cannot spawn grandchildren. */
|
|
121011
|
+
async setRlmMaxDepth(maxDepth) {
|
|
121012
|
+
this.ensureOpen();
|
|
121013
|
+
await this.rpc.setRlmMaxDepth({
|
|
121014
|
+
sessionId: this.id,
|
|
121015
|
+
maxDepth
|
|
121016
|
+
});
|
|
121017
|
+
}
|
|
120282
121018
|
async stopBackgroundTask(taskId, options = {}) {
|
|
120283
121019
|
this.ensureOpen();
|
|
120284
121020
|
const trimmedTaskId = normalizeRequiredString(taskId, "Task id cannot be empty", ErrorCodes.BACKGROUND_TASK_ID_EMPTY);
|
|
@@ -120898,7 +121634,7 @@ function optionalBuildString(value) {
|
|
|
120898
121634
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
120899
121635
|
}
|
|
120900
121636
|
const SCREAM_BUILD_INFO = {
|
|
120901
|
-
version: optionalBuildString("0.12.
|
|
121637
|
+
version: optionalBuildString("0.12.6"),
|
|
120902
121638
|
channel: optionalBuildString(""),
|
|
120903
121639
|
commit: optionalBuildString(""),
|
|
120904
121640
|
buildTarget: optionalBuildString("darwin-arm64")
|
|
@@ -121899,28 +122635,43 @@ const BUILTIN_SLASH_COMMANDS = [
|
|
|
121899
122635
|
name: "ask",
|
|
121900
122636
|
aliases: ["ask"],
|
|
121901
122637
|
description: "registry.ask_desc",
|
|
121902
|
-
priority:
|
|
122638
|
+
priority: 123,
|
|
122639
|
+
availability: "always"
|
|
122640
|
+
},
|
|
122641
|
+
{
|
|
122642
|
+
name: "rlm",
|
|
122643
|
+
aliases: ["rlm"],
|
|
122644
|
+
description: "registry.rlm_desc",
|
|
122645
|
+
priority: 121,
|
|
122646
|
+
availability: "always"
|
|
122647
|
+
},
|
|
122648
|
+
{
|
|
122649
|
+
name: "rlm-max-depth",
|
|
122650
|
+
aliases: [],
|
|
122651
|
+
description: "registry.rlm_max_depth_desc",
|
|
122652
|
+
argumentHint: "[N]",
|
|
122653
|
+
priority: 119,
|
|
121903
122654
|
availability: "always"
|
|
121904
122655
|
},
|
|
121905
122656
|
{
|
|
121906
122657
|
name: "wolfpack",
|
|
121907
122658
|
aliases: ["wp"],
|
|
121908
122659
|
description: "registry.wolfpack_desc",
|
|
121909
|
-
priority:
|
|
122660
|
+
priority: 122,
|
|
121910
122661
|
availability: "always"
|
|
121911
122662
|
},
|
|
121912
122663
|
{
|
|
121913
122664
|
name: "sessions",
|
|
121914
122665
|
aliases: ["resume"],
|
|
121915
122666
|
description: "registry.sessions_desc",
|
|
121916
|
-
priority:
|
|
122667
|
+
priority: 119
|
|
121917
122668
|
},
|
|
121918
122669
|
{
|
|
121919
122670
|
name: "goal",
|
|
121920
122671
|
aliases: ["goaloff"],
|
|
121921
122672
|
description: "registry.goal_desc",
|
|
121922
122673
|
argumentHint: "[objective]",
|
|
121923
|
-
priority:
|
|
122674
|
+
priority: 120,
|
|
121924
122675
|
availability: (args) => {
|
|
121925
122676
|
const trimmed = args.trim();
|
|
121926
122677
|
return trimmed === "" || trimmed === "status" || trimmed === "pause" || trimmed === "off" ? "always" : "idle-only";
|
|
@@ -123242,7 +123993,7 @@ async function handleConnectCommand(host, args) {
|
|
|
123242
123993
|
ok: false,
|
|
123243
123994
|
label: "Failed to load catalog."
|
|
123244
123995
|
});
|
|
123245
|
-
host.showError(
|
|
123996
|
+
host.showError(t("auth.catalog_fetch_failed", { error: formatErrorMessage(error) }));
|
|
123246
123997
|
}
|
|
123247
123998
|
}
|
|
123248
123999
|
}
|
|
@@ -124391,6 +125142,7 @@ var FooterComponent = class {
|
|
|
124391
125142
|
left.push(chalk.hex(isFusion ? colors.fusionPlanMode : colors.planMode).bold(isFusion ? t("badge.fusion") : t("badge.plan")));
|
|
124392
125143
|
}
|
|
124393
125144
|
if (state.wolfpackMode) left.push(chalk.hex(colors.wolfpackMode).bold(t("badge.wolfpack")));
|
|
125145
|
+
if (state.rlmEnabled) left.push(chalk.hex(colors.rlmMode).bold(t("badge.rlm")));
|
|
124394
125146
|
if (state.goalActive && state.goal) {
|
|
124395
125147
|
const goalLabel = formatGoalBadge(state.goal);
|
|
124396
125148
|
left.push(chalk.hex(colors.primary).bold(goalLabel));
|
|
@@ -124679,6 +125431,7 @@ const darkColors = {
|
|
|
124679
125431
|
planMode: dark.cyan400,
|
|
124680
125432
|
fusionPlanMode: dark.amber500,
|
|
124681
125433
|
wolfpackMode: "#C084FC",
|
|
125434
|
+
rlmMode: "#FFD700",
|
|
124682
125435
|
text: dark.gray100,
|
|
124683
125436
|
textStrong: dark.gray50,
|
|
124684
125437
|
textDim: dark.gray500,
|
|
@@ -124710,6 +125463,7 @@ const lightColors = {
|
|
|
124710
125463
|
planMode: light.cyan800,
|
|
124711
125464
|
fusionPlanMode: light.amber700,
|
|
124712
125465
|
wolfpackMode: "#7C3AED",
|
|
125466
|
+
rlmMode: "#A16207",
|
|
124713
125467
|
text: light.gray900,
|
|
124714
125468
|
textStrong: light.gray900,
|
|
124715
125469
|
textDim: light.gray700,
|
|
@@ -125509,6 +126263,61 @@ async function handleAskCommand(host, args) {
|
|
|
125509
126263
|
host.setAppState({ permissionMode: "ask" });
|
|
125510
126264
|
}
|
|
125511
126265
|
}
|
|
126266
|
+
async function handleRlmCommand(host, args) {
|
|
126267
|
+
const session = host.session;
|
|
126268
|
+
if (session === void 0) {
|
|
126269
|
+
host.showError(getNoActiveSessionMessage());
|
|
126270
|
+
return;
|
|
126271
|
+
}
|
|
126272
|
+
const subcmd = args.trim().toLowerCase();
|
|
126273
|
+
let enabled;
|
|
126274
|
+
if (subcmd.length === 0) enabled = !host.state.appState.rlmEnabled;
|
|
126275
|
+
else if (subcmd === "on") enabled = true;
|
|
126276
|
+
else if (subcmd === "off") enabled = false;
|
|
126277
|
+
else {
|
|
126278
|
+
host.showError(`Unknown rlm subcommand: ${subcmd}`);
|
|
126279
|
+
return;
|
|
126280
|
+
}
|
|
126281
|
+
try {
|
|
126282
|
+
await session.setRlmEnabled(enabled);
|
|
126283
|
+
host.setAppState({ rlmEnabled: enabled });
|
|
126284
|
+
if (enabled) {
|
|
126285
|
+
host.showNotice(t("config.rlm_on"));
|
|
126286
|
+
return;
|
|
126287
|
+
}
|
|
126288
|
+
host.showNotice(t("config.rlm_off"));
|
|
126289
|
+
} catch (error) {
|
|
126290
|
+
const msg = formatErrorMessage(error);
|
|
126291
|
+
host.showError(`Failed to set rlm mode: ${msg}`);
|
|
126292
|
+
}
|
|
126293
|
+
}
|
|
126294
|
+
/** /rlm-max-depth [N] — query or set the maximum RLM recursion depth.
|
|
126295
|
+
* N=0 or no limit means unlimited (the default). */
|
|
126296
|
+
async function handleRlmMaxDepthCommand(host, args) {
|
|
126297
|
+
const session = host.session;
|
|
126298
|
+
if (session === void 0) {
|
|
126299
|
+
host.showError(getNoActiveSessionMessage());
|
|
126300
|
+
return;
|
|
126301
|
+
}
|
|
126302
|
+
const arg = args.trim();
|
|
126303
|
+
if (arg.length === 0) {
|
|
126304
|
+
host.showStatus("RLM max depth: run /rlm-max-depth <N> (0 or blank = unlimited) to set it.");
|
|
126305
|
+
return;
|
|
126306
|
+
}
|
|
126307
|
+
const parsed = Number(arg);
|
|
126308
|
+
if (!Number.isInteger(parsed) || parsed < 0) {
|
|
126309
|
+
host.showError(`Invalid rlm-max-depth: ${arg} (expected a non-negative integer, 0 = unlimited).`);
|
|
126310
|
+
return;
|
|
126311
|
+
}
|
|
126312
|
+
try {
|
|
126313
|
+
await session.setRlmMaxDepth(parsed);
|
|
126314
|
+
if (parsed === 0) host.showStatus("RLM recursion depth set to unlimited.", host.state.theme.colors.success);
|
|
126315
|
+
else host.showStatus(`RLM max depth set to ${parsed}.`, host.state.theme.colors.success);
|
|
126316
|
+
} catch (error) {
|
|
126317
|
+
const msg = formatErrorMessage(error);
|
|
126318
|
+
host.showError(`Failed to set rlm max depth: ${msg}`);
|
|
126319
|
+
}
|
|
126320
|
+
}
|
|
125512
126321
|
async function handleAutoCommand(host, args) {
|
|
125513
126322
|
const session = host.session;
|
|
125514
126323
|
if (session === void 0) {
|
|
@@ -126639,7 +127448,7 @@ async function guidedGoalSetup(host) {
|
|
|
126639
127448
|
host.showNotice(t("goal.storm_breaker"), t("goal.conflict_loop"));
|
|
126640
127449
|
return;
|
|
126641
127450
|
}
|
|
126642
|
-
const { TextInputDialogComponent } = await import("./text-input-dialog-
|
|
127451
|
+
const { TextInputDialogComponent } = await import("./text-input-dialog-BViNsv5f.mjs");
|
|
126643
127452
|
const initialDesc = await promptText(host, TextInputDialogComponent, {
|
|
126644
127453
|
title: t("goal.setup_title_initial"),
|
|
126645
127454
|
subtitle: t("goal.setup_desc_hint"),
|
|
@@ -126660,7 +127469,7 @@ async function guidedGoalSetup(host) {
|
|
|
126660
127469
|
await showGoalConfigWizard(host, session, confirmed.trim() || objective, false);
|
|
126661
127470
|
}
|
|
126662
127471
|
async function showGoalConfigWizard(host, session, objective, replace) {
|
|
126663
|
-
const { TextInputDialogComponent } = await import("./text-input-dialog-
|
|
127472
|
+
const { TextInputDialogComponent } = await import("./text-input-dialog-BViNsv5f.mjs");
|
|
126664
127473
|
const turnInput = await promptNumber(host, TextInputDialogComponent, {
|
|
126665
127474
|
title: t("goal.wizard_title", { objective }),
|
|
126666
127475
|
subtitle: t("goal.budget_turns_hint"),
|
|
@@ -127472,7 +128281,7 @@ var AssistantMessageComponent = class {
|
|
|
127472
128281
|
const age = Date.now() - (this.fadeStartMs ?? 0);
|
|
127473
128282
|
this.cachedWidth = void 0;
|
|
127474
128283
|
this.cachedLines = void 0;
|
|
127475
|
-
this.ui?.
|
|
128284
|
+
this.ui?.requestRender();
|
|
127476
128285
|
if (age >= 1200) this.stopFade();
|
|
127477
128286
|
}, FADE_TICK_MS);
|
|
127478
128287
|
this.ui.requestRender();
|
|
@@ -127966,7 +128775,7 @@ var ThinkingComponent = class {
|
|
|
127966
128775
|
if (this.ui === void 0 || this.spinnerInterval !== void 0) return;
|
|
127967
128776
|
this.spinnerInterval = setInterval(() => {
|
|
127968
128777
|
this.spinnerFrame = (this.spinnerFrame + 1) % BRAILLE_SPINNER_FRAMES.length;
|
|
127969
|
-
this.ui?.
|
|
128778
|
+
this.ui?.requestRender();
|
|
127970
128779
|
}, 80);
|
|
127971
128780
|
}
|
|
127972
128781
|
stopSpinner() {
|
|
@@ -132408,7 +133217,7 @@ var MoonLoader = class extends Text {
|
|
|
132408
133217
|
const frame = BRAILLE_SPINNER_FRAMES[this.currentFrame];
|
|
132409
133218
|
const coloredFrame = this.colorFn ? this.colorFn(frame) : frame;
|
|
132410
133219
|
this.setText(this.label ? `${coloredFrame} ${this.label}` : coloredFrame);
|
|
132411
|
-
this.ui.
|
|
133220
|
+
this.ui.requestRender();
|
|
132412
133221
|
}
|
|
132413
133222
|
};
|
|
132414
133223
|
//#endregion
|
|
@@ -132544,9 +133353,6 @@ function getFallbackSkillMarketplace() {
|
|
|
132544
133353
|
}
|
|
132545
133354
|
//#endregion
|
|
132546
133355
|
//#region src/tui/utils/component-capabilities.ts
|
|
132547
|
-
function isExpandable(obj) {
|
|
132548
|
-
return typeof obj === "object" && obj !== null && "setExpanded" in obj && typeof obj.setExpanded === "function";
|
|
132549
|
-
}
|
|
132550
133356
|
function isPlanExpandable(obj) {
|
|
132551
133357
|
return typeof obj === "object" && obj !== null && "setPlanExpanded" in obj && typeof obj.setPlanExpanded === "function";
|
|
132552
133358
|
}
|
|
@@ -135345,6 +136151,12 @@ async function handleBuiltInSlashCommand(host, name, args) {
|
|
|
135345
136151
|
case "ask":
|
|
135346
136152
|
await handleAskCommand(host, args);
|
|
135347
136153
|
return;
|
|
136154
|
+
case "rlm":
|
|
136155
|
+
await handleRlmCommand(host, args);
|
|
136156
|
+
return;
|
|
136157
|
+
case "rlm-max-depth":
|
|
136158
|
+
await handleRlmMaxDepthCommand(host, args);
|
|
136159
|
+
return;
|
|
135348
136160
|
case "auto":
|
|
135349
136161
|
await handleAutoCommand(host, args);
|
|
135350
136162
|
return;
|
|
@@ -135420,6 +136232,17 @@ var AuthFlowController = class {
|
|
|
135420
136232
|
availableModels: config.models ?? {},
|
|
135421
136233
|
availableProviders: config.providers ?? {}
|
|
135422
136234
|
});
|
|
136235
|
+
this.prefetchCatalogCache();
|
|
136236
|
+
}
|
|
136237
|
+
/**
|
|
136238
|
+
* Best-effort background catalog refresh. Fetches the remote catalog and,
|
|
136239
|
+
* on success, updates the on-disk cache. On failure it stays silent: the
|
|
136240
|
+
* existing cache (or built-in) keeps working. Never throws.
|
|
136241
|
+
*/
|
|
136242
|
+
async prefetchCatalogCache() {
|
|
136243
|
+
try {
|
|
136244
|
+
saveCatalogCache(await fetchCatalog(DEFAULT_CATALOG_URL), resolveScreamHome());
|
|
136245
|
+
} catch {}
|
|
135423
136246
|
}
|
|
135424
136247
|
async activateModelAfterLogin(model, thinkingLevel) {
|
|
135425
136248
|
const { host } = this;
|
|
@@ -136885,6 +137708,9 @@ var SessionEventHandler = class {
|
|
|
136885
137708
|
case "compaction.cancelled":
|
|
136886
137709
|
this.handleCompactionCancel(event, sendQueued);
|
|
136887
137710
|
break;
|
|
137711
|
+
case "skill_candidate":
|
|
137712
|
+
this.handleSkillCandidate(event);
|
|
137713
|
+
break;
|
|
136888
137714
|
case "subagent.spawned":
|
|
136889
137715
|
this.handleSubagentSpawned(event);
|
|
136890
137716
|
break;
|
|
@@ -137219,6 +138045,7 @@ var SessionEventHandler = class {
|
|
|
137219
138045
|
if (event.maxContextTokens !== void 0) patch.maxContextTokens = event.maxContextTokens;
|
|
137220
138046
|
if (event.planMode !== void 0) patch.planMode = event.planMode ? event.planStrategy === "fusion" ? "fusionplan" : "plan" : "off";
|
|
137221
138047
|
if (event.wolfpackMode !== void 0) patch.wolfpackMode = event.wolfpackMode;
|
|
138048
|
+
if (event.rlmEnabled !== void 0) patch.rlmEnabled = event.rlmEnabled;
|
|
137222
138049
|
if (event.permission !== void 0) patch.permissionMode = event.permission;
|
|
137223
138050
|
if (Object.keys(patch).length > 0) this.host.setAppState(patch);
|
|
137224
138051
|
}
|
|
@@ -137391,6 +138218,28 @@ var SessionEventHandler = class {
|
|
|
137391
138218
|
this.lastCompactionTrigger = void 0;
|
|
137392
138219
|
this.finishCompaction(sendQueued);
|
|
137393
138220
|
}
|
|
138221
|
+
/** Candidates already prompted this session, to avoid repeating the request. */
|
|
138222
|
+
promptedSkillCandidates = /* @__PURE__ */ new Set();
|
|
138223
|
+
/**
|
|
138224
|
+
* Handles a `skill_candidate` event emitted after compaction detected a
|
|
138225
|
+
* reusable process in the summary. Best-effort and non-fatal: if the session
|
|
138226
|
+
* is unavailable or the prompt fails, nothing happens — the user simply sees
|
|
138227
|
+
* no confirmation. When it works, the model runs an AskUserQuestion dialog
|
|
138228
|
+
* (确定/忽略) so the user can confirm or dismiss with a single keystroke,
|
|
138229
|
+
* then generates the skill via MakeSkillPlanTool on confirmation.
|
|
138230
|
+
*/
|
|
138231
|
+
handleSkillCandidate(event) {
|
|
138232
|
+
try {
|
|
138233
|
+
const { name, purpose } = event.candidate;
|
|
138234
|
+
if (name.length === 0) return;
|
|
138235
|
+
if (this.promptedSkillCandidates.has(name)) return;
|
|
138236
|
+
this.promptedSkillCandidates.add(name);
|
|
138237
|
+
const session = this.host.session;
|
|
138238
|
+
if (session === void 0) return;
|
|
138239
|
+
const request = `检测到可复用过程「${name}」${purpose ? `(${purpose})` : ""}。请用 AskUserQuestion 工具询问用户是否将其保存为可复用技能(选项:生成 / 忽略)。用户选择「生成」后,用 MakeSkillPlanTool 生成该技能;选择「忽略」则直接说明已跳过。`;
|
|
138240
|
+
session.prompt(request).catch(() => {});
|
|
138241
|
+
} catch {}
|
|
138242
|
+
}
|
|
137394
138243
|
finishCompaction(sendQueued) {
|
|
137395
138244
|
if (!this.host.streamingUI.hasActiveTurn()) {
|
|
137396
138245
|
this.host.setAppState({
|
|
@@ -139749,9 +140598,8 @@ var TasksBrowserController = class {
|
|
|
139749
140598
|
colors: state.theme.colors,
|
|
139750
140599
|
...this.buildCallbacks()
|
|
139751
140600
|
}, state.terminal);
|
|
139752
|
-
const
|
|
139753
|
-
state.ui.
|
|
139754
|
-
state.ui.addChild(component);
|
|
140601
|
+
const savedLayoutRoot = state.layoutRoot;
|
|
140602
|
+
state.ui.setLayoutRoot(component);
|
|
139755
140603
|
state.ui.setFocus(component);
|
|
139756
140604
|
state.ui.requestRender(true);
|
|
139757
140605
|
const pollTimer = setInterval(() => {
|
|
@@ -139759,7 +140607,7 @@ var TasksBrowserController = class {
|
|
|
139759
140607
|
}, 1e3);
|
|
139760
140608
|
this.host.setTasksBrowser({
|
|
139761
140609
|
component,
|
|
139762
|
-
|
|
140610
|
+
savedLayoutRoot,
|
|
139763
140611
|
filter,
|
|
139764
140612
|
selectedTaskId,
|
|
139765
140613
|
tailOutput: void 0,
|
|
@@ -139779,8 +140627,7 @@ var TasksBrowserController = class {
|
|
|
139779
140627
|
if (browser.viewer !== void 0) this.closeOutputViewer();
|
|
139780
140628
|
if (browser.pollTimer !== void 0) clearInterval(browser.pollTimer);
|
|
139781
140629
|
if (browser.flashTimer !== void 0) clearTimeout(browser.flashTimer);
|
|
139782
|
-
state.ui.
|
|
139783
|
-
for (const child of browser.savedChildren) state.ui.addChild(child);
|
|
140630
|
+
state.ui.setLayoutRoot(browser.savedLayoutRoot);
|
|
139784
140631
|
this.host.setTasksBrowser(void 0);
|
|
139785
140632
|
state.ui.setFocus(state.editor);
|
|
139786
140633
|
state.ui.requestRender(true);
|
|
@@ -139950,9 +140797,8 @@ var TasksBrowserController = class {
|
|
|
139950
140797
|
this.closeOutputViewer();
|
|
139951
140798
|
}
|
|
139952
140799
|
}, state.terminal);
|
|
139953
|
-
const
|
|
139954
|
-
state.ui.
|
|
139955
|
-
state.ui.addChild(viewer);
|
|
140800
|
+
const savedBrowserLayout = state.layoutRoot;
|
|
140801
|
+
state.ui.setLayoutRoot(viewer);
|
|
139956
140802
|
state.ui.setFocus(viewer);
|
|
139957
140803
|
state.ui.requestRender(true);
|
|
139958
140804
|
const pollTimer = setInterval(() => {
|
|
@@ -139960,7 +140806,7 @@ var TasksBrowserController = class {
|
|
|
139960
140806
|
}, 1e3);
|
|
139961
140807
|
browser.viewer = {
|
|
139962
140808
|
component: viewer,
|
|
139963
|
-
|
|
140809
|
+
savedLayoutRoot: savedBrowserLayout,
|
|
139964
140810
|
taskId,
|
|
139965
140811
|
output,
|
|
139966
140812
|
refreshId: 0,
|
|
@@ -140016,8 +140862,7 @@ var TasksBrowserController = class {
|
|
|
140016
140862
|
const viewer = browser.viewer;
|
|
140017
140863
|
clearInterval(viewer.pollTimer);
|
|
140018
140864
|
browser.viewer = void 0;
|
|
140019
|
-
this.host.state.ui.
|
|
140020
|
-
for (const child of viewer.savedChildren) this.host.state.ui.addChild(child);
|
|
140865
|
+
this.host.state.ui.setLayoutRoot(viewer.savedLayoutRoot);
|
|
140021
140866
|
this.host.state.ui.setFocus(browser.component);
|
|
140022
140867
|
this.host.state.ui.requestRender(true);
|
|
140023
140868
|
}
|
|
@@ -140453,13 +141298,6 @@ var TranscriptController = class TranscriptController {
|
|
|
140453
141298
|
toggleToolOutputExpansion() {
|
|
140454
141299
|
const { state } = this.host;
|
|
140455
141300
|
state.toolOutputExpanded = !state.toolOutputExpanded;
|
|
140456
|
-
const walk = (children) => {
|
|
140457
|
-
for (const child of children) {
|
|
140458
|
-
if (isExpandable(child)) child.setExpanded(state.toolOutputExpanded);
|
|
140459
|
-
if ("children" in child && Array.isArray(child.children)) walk(child.children);
|
|
140460
|
-
}
|
|
140461
|
-
};
|
|
140462
|
-
walk(state.transcriptContainer.children);
|
|
140463
141301
|
state.ui.requestRender();
|
|
140464
141302
|
}
|
|
140465
141303
|
togglePlanExpansion() {
|
|
@@ -140508,6 +141346,44 @@ var GutterContainer = class extends Container {
|
|
|
140508
141346
|
}
|
|
140509
141347
|
};
|
|
140510
141348
|
//#endregion
|
|
141349
|
+
//#region src/tui/components/panes/status-bar-pane.ts
|
|
141350
|
+
/**
|
|
141351
|
+
* A fixed one-line status bar shown directly above the input editor: it
|
|
141352
|
+
* displays the current work phase (idle / waiting / thinking / composing /
|
|
141353
|
+
* tool) with its spinner, pinned to the bottom of the terminal via the dock
|
|
141354
|
+
* VStack. Designed to hold more status widgets later — add children to the
|
|
141355
|
+
* container when needed.
|
|
141356
|
+
*/
|
|
141357
|
+
var StatusBarPaneComponent = class extends Container {
|
|
141358
|
+
constructor(options) {
|
|
141359
|
+
super();
|
|
141360
|
+
this.update(options);
|
|
141361
|
+
}
|
|
141362
|
+
/** Re-renders the bar for a new mode/label. The spinner/pulse are the
|
|
141363
|
+
* caller's shared instances (kept alive across mode changes), so this only
|
|
141364
|
+
* swaps which children are visible. */
|
|
141365
|
+
update(options) {
|
|
141366
|
+
this.clear();
|
|
141367
|
+
if (options.mode === "idle") return;
|
|
141368
|
+
if (options.mode === "waiting" || options.mode === "tool" || options.mode === "composing" || options.mode === "thinking") {
|
|
141369
|
+
if (options.pulseWave !== void 0) {
|
|
141370
|
+
if (options.label.length > 0) {
|
|
141371
|
+
const row = new HStack();
|
|
141372
|
+
row.addChild(options.pulseWave);
|
|
141373
|
+
row.addChild(new Text(` ${options.label}`, 1, 0));
|
|
141374
|
+
this.addChild(row);
|
|
141375
|
+
} else this.addChild(options.pulseWave);
|
|
141376
|
+
return;
|
|
141377
|
+
}
|
|
141378
|
+
if (options.spinner !== void 0) {
|
|
141379
|
+
this.addChild(options.spinner);
|
|
141380
|
+
return;
|
|
141381
|
+
}
|
|
141382
|
+
if (options.label.length > 0) this.addChild(new Text(options.label, 1, 0));
|
|
141383
|
+
}
|
|
141384
|
+
}
|
|
141385
|
+
};
|
|
141386
|
+
//#endregion
|
|
140511
141387
|
//#region src/tui/theme/bundle.ts
|
|
140512
141388
|
function createScreamTUIThemeBundle(theme, resolvedTheme) {
|
|
140513
141389
|
const actualTheme = resolvedTheme ?? resolveThemeSync(theme);
|
|
@@ -140799,36 +141675,11 @@ var PulseWaveLoader = class extends Text {
|
|
|
140799
141675
|
}
|
|
140800
141676
|
};
|
|
140801
141677
|
//#endregion
|
|
140802
|
-
//#region src/tui/components/panes/activity-pane.ts
|
|
140803
|
-
var ActivityPaneComponent = class extends Container {
|
|
140804
|
-
constructor(options) {
|
|
140805
|
-
super();
|
|
140806
|
-
if (options.mode === "waiting" || options.mode === "tool") {
|
|
140807
|
-
if (options.spinner !== void 0) {
|
|
140808
|
-
this.addChild(new Spacer(1));
|
|
140809
|
-
this.addChild(options.spinner);
|
|
140810
|
-
}
|
|
140811
|
-
if (options.pulseWave !== void 0) {
|
|
140812
|
-
this.addChild(new Spacer(1));
|
|
140813
|
-
this.addChild(options.pulseWave);
|
|
140814
|
-
}
|
|
140815
|
-
return;
|
|
140816
|
-
}
|
|
140817
|
-
if (options.mode === "composing" && options.spinner !== void 0) {
|
|
140818
|
-
this.addChild(new Spacer(1));
|
|
140819
|
-
this.addChild(options.spinner);
|
|
140820
|
-
if (options.pulseWave !== void 0) {
|
|
140821
|
-
this.addChild(new Spacer(1));
|
|
140822
|
-
this.addChild(options.pulseWave);
|
|
140823
|
-
}
|
|
140824
|
-
}
|
|
140825
|
-
}
|
|
140826
|
-
};
|
|
140827
|
-
//#endregion
|
|
140828
141678
|
//#region src/tui/controllers/lifecycle-controller.ts
|
|
140829
141679
|
var LifecycleController = class LifecycleController {
|
|
140830
141680
|
host;
|
|
140831
141681
|
signalCleanupHandlers = [];
|
|
141682
|
+
footerWrap;
|
|
140832
141683
|
ccConnectPollTimer;
|
|
140833
141684
|
memoryIdleTimer;
|
|
140834
141685
|
memoryCountdownTimer;
|
|
@@ -140836,6 +141687,9 @@ var LifecycleController = class LifecycleController {
|
|
|
140836
141687
|
terminalFocusTrackingDispose;
|
|
140837
141688
|
terminalThemeTrackingDispose;
|
|
140838
141689
|
lastActivityMode;
|
|
141690
|
+
/** The active status-bar loader (PulseWaveLoader or MoonLoader). Owned by
|
|
141691
|
+
* this controller; stopped before replacement to avoid leaking timers. */
|
|
141692
|
+
statusBarLoader;
|
|
140839
141693
|
static MEMORY_IDLE_MS = 900 * 1e3;
|
|
140840
141694
|
static MEMORY_COUNTDOWN_MS = 15 * 1e3;
|
|
140841
141695
|
static MEMORY_EXTRACT_COOLDOWN_MS = 600 * 1e3;
|
|
@@ -140975,19 +141829,72 @@ var LifecycleController = class LifecycleController {
|
|
|
140975
141829
|
buildLayout() {
|
|
140976
141830
|
const { ui } = this.host.state;
|
|
140977
141831
|
ui.clear();
|
|
140978
|
-
|
|
140979
|
-
|
|
140980
|
-
|
|
140981
|
-
|
|
140982
|
-
|
|
140983
|
-
|
|
140984
|
-
|
|
140985
|
-
|
|
141832
|
+
const scrollContent = new Container();
|
|
141833
|
+
scrollContent.addChild(this.host.state.transcriptContainer);
|
|
141834
|
+
scrollContent.addChild(this.host.state.activityContainer);
|
|
141835
|
+
scrollContent.addChild(this.host.state.queueContainer);
|
|
141836
|
+
const transcriptScrollView = new ScrollView(scrollContent, {
|
|
141837
|
+
follow: "end",
|
|
141838
|
+
primary: true,
|
|
141839
|
+
scrollbar: "auto"
|
|
141840
|
+
});
|
|
141841
|
+
const dock = new VStack([
|
|
141842
|
+
{
|
|
141843
|
+
component: this.host.state.todoPanelContainer,
|
|
141844
|
+
shrink: 1,
|
|
141845
|
+
minSize: 0,
|
|
141846
|
+
visible: (viewport) => viewport.width >= 90
|
|
141847
|
+
},
|
|
141848
|
+
{
|
|
141849
|
+
component: this.host.state.errorBannerContainer,
|
|
141850
|
+
shrink: 1,
|
|
141851
|
+
minSize: 0
|
|
141852
|
+
},
|
|
141853
|
+
{
|
|
141854
|
+
component: this.host.state.planModeBannerContainer,
|
|
141855
|
+
shrink: 1,
|
|
141856
|
+
minSize: 0
|
|
141857
|
+
},
|
|
141858
|
+
{
|
|
141859
|
+
component: this.host.state.statusBarContainer,
|
|
141860
|
+
shrink: 1,
|
|
141861
|
+
minSize: 1,
|
|
141862
|
+
visible: (viewport) => viewport.width >= 60
|
|
141863
|
+
},
|
|
141864
|
+
{
|
|
141865
|
+
component: this.host.state.editorContainer,
|
|
141866
|
+
shrink: 1,
|
|
141867
|
+
minSize: 3
|
|
141868
|
+
},
|
|
141869
|
+
{
|
|
141870
|
+
component: this.ensureFooterWrap(),
|
|
141871
|
+
shrink: 1,
|
|
141872
|
+
minSize: 1
|
|
141873
|
+
}
|
|
141874
|
+
]);
|
|
141875
|
+
const layoutRoot = new VStack([{
|
|
141876
|
+
component: transcriptScrollView,
|
|
141877
|
+
basis: 0,
|
|
141878
|
+
grow: 1,
|
|
141879
|
+
shrink: 1,
|
|
141880
|
+
minSize: 1
|
|
141881
|
+
}, {
|
|
141882
|
+
component: dock,
|
|
141883
|
+
basis: "auto",
|
|
141884
|
+
grow: 0,
|
|
141885
|
+
shrink: 1,
|
|
141886
|
+
minSize: 1
|
|
141887
|
+
}]);
|
|
141888
|
+
ui.setLayoutRoot(layoutRoot);
|
|
141889
|
+
this.host.state.layoutRoot = layoutRoot;
|
|
140986
141890
|
}
|
|
140987
141891
|
mountFooter() {
|
|
140988
|
-
|
|
140989
|
-
footerWrap.addChild(this.host.state.footer);
|
|
140990
|
-
|
|
141892
|
+
this.ensureFooterWrap();
|
|
141893
|
+
this.footerWrap.addChild(this.host.state.footer);
|
|
141894
|
+
}
|
|
141895
|
+
ensureFooterWrap() {
|
|
141896
|
+
if (this.footerWrap === void 0) this.footerWrap = new GutterContainer(1, 1);
|
|
141897
|
+
return this.footerWrap;
|
|
140991
141898
|
}
|
|
140992
141899
|
refreshTerminalThemeTracking() {
|
|
140993
141900
|
this.stopTerminalThemeTracking();
|
|
@@ -141022,48 +141929,57 @@ var LifecycleController = class LifecycleController {
|
|
|
141022
141929
|
this.syncTerminalProgress(this.shouldShowTerminalProgress(effectiveMode));
|
|
141023
141930
|
if (effectiveMode === this.lastActivityMode) return;
|
|
141024
141931
|
this.lastActivityMode = effectiveMode;
|
|
141932
|
+
this.updateStatusBar(effectiveMode);
|
|
141025
141933
|
const { state } = this.host;
|
|
141026
141934
|
state.activityContainer.clear();
|
|
141027
141935
|
switch (effectiveMode) {
|
|
141028
141936
|
case "hidden":
|
|
141937
|
+
case "idle":
|
|
141029
141938
|
this.stopActivitySpinner();
|
|
141030
141939
|
this.stopPulseWave();
|
|
141031
|
-
state.ui.requestRender();
|
|
141032
|
-
return;
|
|
141033
|
-
case "waiting": {
|
|
141034
|
-
this.stopActivitySpinner();
|
|
141035
|
-
const pulseWave = this.ensurePulseWave();
|
|
141036
|
-
state.activityContainer.addChild(new ActivityPaneComponent({
|
|
141037
|
-
mode: "waiting",
|
|
141038
|
-
pulseWave
|
|
141039
|
-
}));
|
|
141040
141940
|
break;
|
|
141041
|
-
|
|
141941
|
+
case "waiting":
|
|
141042
141942
|
case "thinking":
|
|
141943
|
+
case "composing":
|
|
141944
|
+
case "tool":
|
|
141043
141945
|
this.stopActivitySpinner();
|
|
141044
141946
|
this.stopPulseWave();
|
|
141045
141947
|
break;
|
|
141046
|
-
|
|
141047
|
-
|
|
141048
|
-
|
|
141049
|
-
|
|
141050
|
-
|
|
141051
|
-
|
|
141052
|
-
|
|
141053
|
-
|
|
141054
|
-
|
|
141055
|
-
|
|
141056
|
-
|
|
141057
|
-
|
|
141058
|
-
|
|
141059
|
-
|
|
141060
|
-
|
|
141948
|
+
}
|
|
141949
|
+
state.ui.requestRender();
|
|
141950
|
+
}
|
|
141951
|
+
/**
|
|
141952
|
+
* Updates the fixed status bar above the editor to reflect the current
|
|
141953
|
+
* work phase. Called only when the phase actually changes. Uses its OWN
|
|
141954
|
+
* spinner/pulse instances (a component can only have one parent, so the
|
|
141955
|
+
* activity pane's instances must not be shared). Recreating a loader on
|
|
141956
|
+
* every call would leak setInterval timers (each PulseWaveLoader.start()
|
|
141957
|
+
* registers one and the old instance is never stopped), so the previous
|
|
141958
|
+
* instance is explicitly stopped before a new one is mounted. Idle leaves
|
|
141959
|
+
* the bar empty.
|
|
141960
|
+
*/
|
|
141961
|
+
updateStatusBar(mode) {
|
|
141962
|
+
const { state } = this.host;
|
|
141963
|
+
state.statusBarContainer.clear();
|
|
141964
|
+
this.statusBarLoader?.stop();
|
|
141965
|
+
this.statusBarLoader = void 0;
|
|
141966
|
+
switch (mode) {
|
|
141967
|
+
case "waiting":
|
|
141968
|
+
case "composing":
|
|
141969
|
+
case "tool":
|
|
141970
|
+
{
|
|
141971
|
+
const loader = new PulseWaveLoader(state.ui, state.theme.colors.primary);
|
|
141972
|
+
this.statusBarLoader = loader;
|
|
141973
|
+
state.statusBarContainer.addChild(new StatusBarPaneComponent({
|
|
141974
|
+
mode,
|
|
141975
|
+
label: "",
|
|
141976
|
+
pulseWave: loader
|
|
141977
|
+
}));
|
|
141978
|
+
}
|
|
141061
141979
|
break;
|
|
141062
|
-
|
|
141980
|
+
case "thinking": break;
|
|
141063
141981
|
case "idle":
|
|
141064
|
-
|
|
141065
|
-
this.stopPulseWave();
|
|
141066
|
-
break;
|
|
141982
|
+
case "hidden": break;
|
|
141067
141983
|
}
|
|
141068
141984
|
state.ui.requestRender();
|
|
141069
141985
|
}
|
|
@@ -141086,28 +142002,12 @@ var LifecycleController = class LifecycleController {
|
|
|
141086
142002
|
this.host.state.terminal.setProgress(active);
|
|
141087
142003
|
this.host.state.terminalState.progressActive = active;
|
|
141088
142004
|
}
|
|
141089
|
-
ensureActivitySpinner(label = "", colorFn) {
|
|
141090
|
-
if (this.host.state.activitySpinner === null) {
|
|
141091
|
-
const instance = new MoonLoader(this.host.state.ui, colorFn, label);
|
|
141092
|
-
this.host.state.activitySpinner = { instance };
|
|
141093
|
-
return instance;
|
|
141094
|
-
}
|
|
141095
|
-
this.host.state.activitySpinner.instance.setLabel(label);
|
|
141096
|
-
if (colorFn !== void 0) this.host.state.activitySpinner.instance.setColorFn(colorFn);
|
|
141097
|
-
return this.host.state.activitySpinner.instance;
|
|
141098
|
-
}
|
|
141099
142005
|
stopActivitySpinner() {
|
|
141100
142006
|
if (this.host.state.activitySpinner !== null) {
|
|
141101
142007
|
this.host.state.activitySpinner.instance.stop();
|
|
141102
142008
|
this.host.state.activitySpinner = null;
|
|
141103
142009
|
}
|
|
141104
142010
|
}
|
|
141105
|
-
ensurePulseWave() {
|
|
141106
|
-
if (this.host.state.pulseWave !== null) return this.host.state.pulseWave;
|
|
141107
|
-
const instance = new PulseWaveLoader(this.host.state.ui, this.host.state.theme.colors.primary);
|
|
141108
|
-
this.host.state.pulseWave = instance;
|
|
141109
|
-
return instance;
|
|
141110
|
-
}
|
|
141111
142011
|
stopPulseWave() {
|
|
141112
142012
|
if (this.host.state.pulseWave !== null) {
|
|
141113
142013
|
this.host.state.pulseWave.stop();
|
|
@@ -141534,18 +142434,10 @@ var InputController = class InputController {
|
|
|
141534
142434
|
this.host = host;
|
|
141535
142435
|
}
|
|
141536
142436
|
setupAutocomplete() {
|
|
141537
|
-
const
|
|
141538
|
-
const slashCommands = visible.map((cmd) => cmd);
|
|
142437
|
+
const slashCommands = this.host.getSlashCommands().filter((cmd) => !(cmd.name.startsWith("skill:") && cmd.source === "builtin")).map((cmd) => cmd);
|
|
141539
142438
|
const { state } = this.host;
|
|
141540
142439
|
const provider = new FileMentionProvider(slashCommands, state.appState.workDir, state.fdPath, state.gitLsFilesCache);
|
|
141541
142440
|
state.editor.setAutocompleteProvider(provider);
|
|
141542
|
-
const argumentHints = /* @__PURE__ */ new Map();
|
|
141543
|
-
for (const cmd of visible) {
|
|
141544
|
-
if (cmd.argumentHint === void 0) continue;
|
|
141545
|
-
argumentHints.set(cmd.name, cmd.argumentHint);
|
|
141546
|
-
for (const alias of cmd.aliases) argumentHints.set(alias, cmd.argumentHint);
|
|
141547
|
-
}
|
|
141548
|
-
state.editor.setArgumentHints(argumentHints);
|
|
141549
142441
|
state.editor.onFirstInput = () => {
|
|
141550
142442
|
this.host.stopWelcomeBreathing();
|
|
141551
142443
|
this.#permanentlyStopBreathing();
|
|
@@ -142840,8 +143732,7 @@ function createTUIState(options) {
|
|
|
142840
143732
|
const initialAppState = options.initialAppState;
|
|
142841
143733
|
const theme = createScreamTUIThemeBundle(initialAppState.theme, options.resolvedTheme);
|
|
142842
143734
|
const terminal = new ProcessTerminal();
|
|
142843
|
-
const ui = new
|
|
142844
|
-
ui.setClearOnShrink(true);
|
|
143735
|
+
const ui = new TuiAltScreen(terminal);
|
|
142845
143736
|
const uiAny = ui;
|
|
142846
143737
|
const originalDoRender = uiAny["doRender"].bind(ui);
|
|
142847
143738
|
let recovering = false;
|
|
@@ -142867,6 +143758,7 @@ function createTUIState(options) {
|
|
|
142867
143758
|
};
|
|
142868
143759
|
const transcriptContainer = new GutterContainer(1, 1);
|
|
142869
143760
|
const activityContainer = new GutterContainer(1, 1);
|
|
143761
|
+
const statusBarContainer = new GutterContainer(1, 1);
|
|
142870
143762
|
const todoPanelContainer = new GutterContainer(1, 1);
|
|
142871
143763
|
const todoPanel = new TodoPanelComponent(theme.colors);
|
|
142872
143764
|
const queueContainer = new GutterContainer(1, 1);
|
|
@@ -142884,8 +143776,10 @@ function createTUIState(options) {
|
|
|
142884
143776
|
return {
|
|
142885
143777
|
ui,
|
|
142886
143778
|
terminal,
|
|
143779
|
+
layoutRoot: void 0,
|
|
142887
143780
|
transcriptContainer,
|
|
142888
143781
|
activityContainer,
|
|
143782
|
+
statusBarContainer,
|
|
142889
143783
|
todoPanelContainer,
|
|
142890
143784
|
todoPanel,
|
|
142891
143785
|
queueContainer,
|
|
@@ -143372,9 +144266,12 @@ const PRUNE_EMPTY_SESSION_GRACE_MS = 300 * 1e3;
|
|
|
143372
144266
|
function isPrunableEmptySession(summary, now, graceMs = PRUNE_EMPTY_SESSION_GRACE_MS) {
|
|
143373
144267
|
if (summary.archived) return false;
|
|
143374
144268
|
if (summary.lastPrompt !== void 0) return false;
|
|
143375
|
-
if (summary.title) return false;
|
|
144269
|
+
if (isUntitledTitle(summary.title)) return false;
|
|
143376
144270
|
return now - summary.updatedAt > graceMs;
|
|
143377
144271
|
}
|
|
144272
|
+
function isUntitledTitle(title) {
|
|
144273
|
+
return typeof title === "string" && title.trim().length > 0 && title !== "New Session";
|
|
144274
|
+
}
|
|
143378
144275
|
/**
|
|
143379
144276
|
* Encapsulates all session lifecycle operations:
|
|
143380
144277
|
* create / resume / switch / close / sync state / reset runtime.
|
|
@@ -143471,6 +144368,7 @@ var SessionManager$1 = class {
|
|
|
143471
144368
|
thinkingLevel: status.thinkingLevel,
|
|
143472
144369
|
planMode: status.planMode ? status.planStrategy === "fusion" ? "fusionplan" : "plan" : "off",
|
|
143473
144370
|
wolfpackMode: status.wolfpackMode,
|
|
144371
|
+
rlmEnabled: status.rlmEnabled,
|
|
143474
144372
|
contextTokens: status.contextTokens,
|
|
143475
144373
|
maxContextTokens: status.maxContextTokens,
|
|
143476
144374
|
contextUsage: status.contextUsage,
|
|
@@ -143789,7 +144687,7 @@ var ApprovalPanelComponent = class extends Container {
|
|
|
143789
144687
|
};
|
|
143790
144688
|
if (this.ui !== void 0) this.intervalId = setInterval(() => {
|
|
143791
144689
|
this.animFrame = (this.animFrame + 1) % PIXEL_PULSE_FRAMES.length;
|
|
143792
|
-
this.ui?.
|
|
144690
|
+
this.ui?.requestRender();
|
|
143793
144691
|
}, 100);
|
|
143794
144692
|
}
|
|
143795
144693
|
/** Stop the selection pulse. Idempotent; called on submit and on hide. */
|
|
@@ -144644,6 +145542,15 @@ var MemoryPickerComponent = class extends Container {
|
|
|
144644
145542
|
}
|
|
144645
145543
|
lines.push("");
|
|
144646
145544
|
}
|
|
145545
|
+
if (memo.note !== void 0 && memo.note.length > 0) {
|
|
145546
|
+
const label = t("memory.note_label");
|
|
145547
|
+
const wrapped = wrapText(memo.note, contentWidth - visibleWidth(label));
|
|
145548
|
+
for (let i = 0; i < wrapped.length; i++) {
|
|
145549
|
+
const prefix = i === 0 ? `${indent}${label}` : indent + " ".repeat(visibleWidth(label));
|
|
145550
|
+
lines.push(truncateToWidth(chalk.hex(c.textMuted)(prefix + wrapped[i]), width, ELLIPSIS$1));
|
|
145551
|
+
}
|
|
145552
|
+
lines.push("");
|
|
145553
|
+
}
|
|
144647
145554
|
lines.push(chalk.hex(c.textMuted)(truncateToWidth(t("memory.detail_nav_hint"), width, ELLIPSIS$1)));
|
|
144648
145555
|
lines.push(chalk.hex(c.primary)("─".repeat(width)));
|
|
144649
145556
|
return lines;
|
|
@@ -145009,7 +145916,7 @@ var QuestionDialogComponent = class extends Container {
|
|
|
145009
145916
|
this.answers = Array.from({ length: total }, () => void 0);
|
|
145010
145917
|
if (this.ui !== void 0) this.intervalId = setInterval(() => {
|
|
145011
145918
|
this.animFrame = (this.animFrame + 1) % PIXEL_PULSE_FRAMES.length;
|
|
145012
|
-
this.ui?.
|
|
145919
|
+
this.ui?.requestRender();
|
|
145013
145920
|
}, 100);
|
|
145014
145921
|
}
|
|
145015
145922
|
/** Stop the wizard pulse. Idempotent; called on respond and on hide. */
|
|
@@ -145721,7 +146628,7 @@ var DialogManager = class {
|
|
|
145721
146628
|
}
|
|
145722
146629
|
openApprovalPreview(panel, block) {
|
|
145723
146630
|
if (this.approvalPreview !== void 0) return;
|
|
145724
|
-
const
|
|
146631
|
+
const savedLayoutRoot = this.host.state.layoutRoot;
|
|
145725
146632
|
const viewer = new ApprovalPreviewViewer({
|
|
145726
146633
|
block,
|
|
145727
146634
|
colors: this.host.state.theme.colors,
|
|
@@ -145729,13 +146636,12 @@ var DialogManager = class {
|
|
|
145729
146636
|
this.closeApprovalPreview();
|
|
145730
146637
|
}
|
|
145731
146638
|
}, this.host.state.terminal);
|
|
145732
|
-
this.host.state.ui.
|
|
145733
|
-
this.host.state.ui.addChild(viewer);
|
|
146639
|
+
this.host.state.ui.setLayoutRoot(viewer);
|
|
145734
146640
|
this.host.state.ui.setFocus(viewer);
|
|
145735
146641
|
this.host.state.ui.requestRender(true);
|
|
145736
146642
|
this.approvalPreview = {
|
|
145737
146643
|
component: viewer,
|
|
145738
|
-
|
|
146644
|
+
savedLayoutRoot,
|
|
145739
146645
|
panel
|
|
145740
146646
|
};
|
|
145741
146647
|
}
|
|
@@ -145743,8 +146649,7 @@ var DialogManager = class {
|
|
|
145743
146649
|
const preview = this.approvalPreview;
|
|
145744
146650
|
if (preview === void 0) return;
|
|
145745
146651
|
this.approvalPreview = void 0;
|
|
145746
|
-
this.host.state.ui.
|
|
145747
|
-
for (const child of preview.savedChildren) this.host.state.ui.addChild(child);
|
|
146652
|
+
this.host.state.ui.setLayoutRoot(preview.savedLayoutRoot);
|
|
145748
146653
|
this.host.state.ui.setFocus(preview.panel);
|
|
145749
146654
|
this.host.state.ui.requestRender(true);
|
|
145750
146655
|
}
|
|
@@ -145814,6 +146719,7 @@ function createInitialAppState(input) {
|
|
|
145814
146719
|
goalContinuationCount: 0,
|
|
145815
146720
|
ccConnectActive: false,
|
|
145816
146721
|
wolfpackMode: input.cliOptions.wolfpack === true,
|
|
146722
|
+
rlmEnabled: false,
|
|
145817
146723
|
reconnectAttempt: 0,
|
|
145818
146724
|
recentSessions: [],
|
|
145819
146725
|
subagentUsage: {}
|
|
@@ -145834,7 +146740,6 @@ var ScreamTUI = class {
|
|
|
145834
146740
|
deferUserMessages = false;
|
|
145835
146741
|
aborted = false;
|
|
145836
146742
|
isShuttingDown = false;
|
|
145837
|
-
tightModeHandler = null;
|
|
145838
146743
|
reverseRpcDisposers = [];
|
|
145839
146744
|
startupNotice;
|
|
145840
146745
|
/** Interval handle for the periodic provider-balance refresh. */
|
|
@@ -145933,11 +146838,6 @@ var ScreamTUI = class {
|
|
|
145933
146838
|
this.inputController.setupAutocomplete();
|
|
145934
146839
|
}
|
|
145935
146840
|
async start() {
|
|
145936
|
-
this.tightModeHandler = () => {
|
|
145937
|
-
setTightMode((process.stdout.columns ?? 80) < 60);
|
|
145938
|
-
};
|
|
145939
|
-
this.tightModeHandler();
|
|
145940
|
-
process.stdout.on("resize", this.tightModeHandler);
|
|
145941
146841
|
this.lifecycleController.installSignalHandlers();
|
|
145942
146842
|
try {
|
|
145943
146843
|
const shouldReplayHistory = await this.initMainTui();
|
|
@@ -146028,11 +146928,6 @@ var ScreamTUI = class {
|
|
|
146028
146928
|
async stop(exitCode) {
|
|
146029
146929
|
if (this.isShuttingDown) return;
|
|
146030
146930
|
this.isShuttingDown = true;
|
|
146031
|
-
if (this.tightModeHandler !== null) {
|
|
146032
|
-
process.stdout.off("resize", this.tightModeHandler);
|
|
146033
|
-
this.tightModeHandler = null;
|
|
146034
|
-
}
|
|
146035
|
-
setTightMode(false);
|
|
146036
146931
|
this.lifecycleController.stopCcConnectPolling();
|
|
146037
146932
|
if (this.balancePollTimer !== void 0) {
|
|
146038
146933
|
clearInterval(this.balancePollTimer);
|
|
@@ -148641,6 +149536,7 @@ var WebSession = class {
|
|
|
148641
149536
|
permission: this.permission,
|
|
148642
149537
|
planMode: false,
|
|
148643
149538
|
wolfpackMode: false,
|
|
149539
|
+
rlmEnabled: false,
|
|
148644
149540
|
contextTokens: 0,
|
|
148645
149541
|
maxContextTokens: 0,
|
|
148646
149542
|
contextUsage: 0
|