scream-code 0.5.5 → 0.5.7

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.
Files changed (2) hide show
  1. package/dist/main.mjs +1265 -739
  2. package/package.json +2 -2
package/dist/main.mjs CHANGED
@@ -5,7 +5,7 @@ const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
6
  import { createRequire } from "node:module";
7
7
  import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
8
- import Jn, { access, appendFile, chmod, copyFile, cp, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
8
+ import Jn, { access, appendFile, chmod, copyFile, cp, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
9
9
  import I, { createWriteStream } from "fs";
10
10
  import Vr, { EventEmitter } from "events";
11
11
  import * as path$1$1 from "path";
@@ -33,10 +33,10 @@ import * as posixPath from "node:path/posix";
33
33
  import * as win32Path from "node:path/win32";
34
34
  import { createServer } from "node:http";
35
35
  import * as z$1 from "zod/v4";
36
+ import { fileURLToPath, pathToFileURL } from "node:url";
36
37
  import { parse as parse$1, stringify } from "smol-toml";
37
38
  import "zod/v3";
38
39
  import * as z4mini from "zod/v4-mini";
39
- import { fileURLToPath, pathToFileURL } from "node:url";
40
40
  import process$1 from "node:process";
41
41
  import { AsyncLocalStorage } from "node:async_hooks";
42
42
  import { Command, Option } from "commander";
@@ -50331,7 +50331,7 @@ function redactCtx(ctx) {
50331
50331
  };
50332
50332
  return walk(ctx, 0);
50333
50333
  }
50334
- function truncate$2(value, max) {
50334
+ function truncate$3(value, max) {
50335
50335
  return value.length <= max ? value : value.slice(0, max - 1) + ELLIPSIS$6;
50336
50336
  }
50337
50337
  function serializeValue(raw) {
@@ -50355,7 +50355,7 @@ function quote(value) {
50355
50355
  return `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"").replaceAll("\n", "\\n")}"`;
50356
50356
  }
50357
50357
  function formatPair(key, raw) {
50358
- const limited = truncate$2(serializeValue(raw), CTX_VALUE_MAX_CHARS);
50358
+ const limited = truncate$3(serializeValue(raw), CTX_VALUE_MAX_CHARS);
50359
50359
  return `${SAFE_KEY_RE.test(key) ? key : quote(key)}=${/[\s="\\]/.test(limited) || limited.length === 0 ? quote(limited) : limited}`;
50360
50360
  }
50361
50361
  function clipBytes(text, maxBytes) {
@@ -50379,7 +50379,7 @@ function indentStack(stack) {
50379
50379
  function formatEntry(entry, options = {}) {
50380
50380
  const ctx = entry.ctx ? redactCtx(entry.ctx) : void 0;
50381
50381
  const omitContextKeys = new Set(options.omitContextKeys ?? []);
50382
- const msg = truncate$2(entry.msg, 200);
50382
+ const msg = truncate$3(entry.msg, 200);
50383
50383
  const pairs = [];
50384
50384
  if (ctx) for (const [k, v] of Object.entries(ctx)) {
50385
50385
  if (omitContextKeys.has(k)) continue;
@@ -56403,6 +56403,881 @@ var WriteGoalNoteTool = class {
56403
56403
  }
56404
56404
  };
56405
56405
  //#endregion
56406
+ //#region ../../packages/memory/src/models.ts
56407
+ function generateId() {
56408
+ return `memo-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
56409
+ }
56410
+ function createMemoryMemo(partial) {
56411
+ return {
56412
+ id: partial.id ?? generateId(),
56413
+ sourceSessionId: partial.sourceSessionId,
56414
+ sourceSessionTitle: partial.sourceSessionTitle,
56415
+ userNeed: partial.userNeed,
56416
+ approach: partial.approach,
56417
+ outcome: partial.outcome,
56418
+ whatFailed: partial.whatFailed,
56419
+ whatWorked: partial.whatWorked,
56420
+ extractionSource: partial.extractionSource,
56421
+ recordedAt: partial.recordedAt ?? Date.now()
56422
+ };
56423
+ }
56424
+ function toSummary(memo) {
56425
+ return {
56426
+ id: memo.id,
56427
+ sourceSessionTitle: memo.sourceSessionTitle,
56428
+ sourceSessionId: memo.sourceSessionId,
56429
+ userNeed: memo.userNeed,
56430
+ approach: memo.approach,
56431
+ outcome: memo.outcome,
56432
+ whatFailed: memo.whatFailed,
56433
+ whatWorked: memo.whatWorked,
56434
+ extractionSource: memo.extractionSource,
56435
+ recordedAt: memo.recordedAt
56436
+ };
56437
+ }
56438
+ //#endregion
56439
+ //#region ../../packages/memory/src/store.ts
56440
+ const FILE_NAME = "entries.jsonl";
56441
+ const TMP_SUFFIX = ".tmp";
56442
+ const MIGRATION_MARKER = ".migrated";
56443
+ var MemoryMemoStore = class MemoryMemoStore {
56444
+ filePath;
56445
+ constructor(projectDir) {
56446
+ this.filePath = join$1(projectDir, "memory", FILE_NAME);
56447
+ }
56448
+ /** Iterate all memo records from the JSONL file. */
56449
+ async *read() {
56450
+ let stream;
56451
+ try {
56452
+ stream = createReadStream(this.filePath, { encoding: "utf8" });
56453
+ } catch {
56454
+ return;
56455
+ }
56456
+ let line = "";
56457
+ let lineNumber = 0;
56458
+ try {
56459
+ for await (const chunk of stream) {
56460
+ line += chunk;
56461
+ let newlineIndex = line.indexOf("\n");
56462
+ while (newlineIndex !== -1) {
56463
+ const rawLine = line.slice(0, newlineIndex).replace(/\r$/, "");
56464
+ line = line.slice(newlineIndex + 1);
56465
+ lineNumber++;
56466
+ const memo = this.parseLine(rawLine, lineNumber);
56467
+ if (memo !== void 0) yield memo;
56468
+ newlineIndex = line.indexOf("\n");
56469
+ }
56470
+ }
56471
+ } catch (error) {
56472
+ if (error.code === "ENOENT") return;
56473
+ throw error;
56474
+ }
56475
+ }
56476
+ /** Append a memo. */
56477
+ async append(entry) {
56478
+ const record = {
56479
+ type: "memory_memo",
56480
+ version: 2,
56481
+ entry
56482
+ };
56483
+ await this.ensureDir();
56484
+ const fh = await open(this.filePath, "a");
56485
+ try {
56486
+ await fh.writeFile(JSON.stringify(record) + "\n", "utf8");
56487
+ await fh.sync();
56488
+ } finally {
56489
+ await fh.close();
56490
+ }
56491
+ }
56492
+ /** Delete a memo by id (rewrites the file without it). */
56493
+ async delete(id) {
56494
+ const entries = [];
56495
+ for await (const memo of this.read()) if (memo.id !== id) entries.push(memo);
56496
+ if (entries.length === 0) {
56497
+ try {
56498
+ await unlink(this.filePath);
56499
+ } catch (error) {
56500
+ if (error.code !== "ENOENT") throw error;
56501
+ }
56502
+ return true;
56503
+ }
56504
+ try {
56505
+ const tmpPath = this.filePath + TMP_SUFFIX;
56506
+ const fh = await open(tmpPath, "w");
56507
+ try {
56508
+ for (const entry of entries) {
56509
+ const record = {
56510
+ type: "memory_memo",
56511
+ version: 2,
56512
+ entry
56513
+ };
56514
+ await fh.writeFile(JSON.stringify(record) + "\n", "utf8");
56515
+ }
56516
+ await fh.sync();
56517
+ } finally {
56518
+ await fh.close();
56519
+ }
56520
+ await rename(tmpPath, this.filePath);
56521
+ return true;
56522
+ } catch {
56523
+ return false;
56524
+ }
56525
+ }
56526
+ /** Get a single memo by ID. */
56527
+ async get(id) {
56528
+ for await (const memo of this.read()) if (memo.id === id) return memo;
56529
+ }
56530
+ /** List memos with optional search and limit. */
56531
+ async list(options) {
56532
+ const search = options?.search?.toLowerCase();
56533
+ const limit = options?.limit ?? 50;
56534
+ const offset = options?.offset ?? 0;
56535
+ const all = [];
56536
+ for await (const memo of this.read()) all.push(memo);
56537
+ all.sort((a, b) => b.recordedAt - a.recordedAt);
56538
+ let filtered = all;
56539
+ if (search) filtered = all.filter((m) => m.userNeed.toLowerCase().includes(search) || m.approach.toLowerCase().includes(search) || m.whatFailed.toLowerCase().includes(search) || m.whatWorked.toLowerCase().includes(search) || (m.sourceSessionTitle ?? "").toLowerCase().includes(search));
56540
+ const total = filtered.length;
56541
+ return {
56542
+ memos: filtered.slice(offset, offset + limit).map(toSummary),
56543
+ total
56544
+ };
56545
+ }
56546
+ async ensureDir() {
56547
+ await mkdir(dirname$2(this.filePath), { recursive: true });
56548
+ }
56549
+ /** @internal */
56550
+ parseLine(rawLine, _lineNumber) {
56551
+ if (rawLine.length === 0) return void 0;
56552
+ try {
56553
+ const record = JSON.parse(rawLine);
56554
+ if (record["type"] !== "memory_memo" || !record["entry"]) return void 0;
56555
+ const entry = record["entry"];
56556
+ if (record["version"] === 1 || entry["userRequirement"] !== void 0 && entry["userNeed"] === void 0) {
56557
+ const str = (v, fallback = "") => typeof v === "string" ? v : fallback;
56558
+ return {
56559
+ id: str(entry["id"]),
56560
+ sourceSessionId: str(entry["sourceSessionId"]),
56561
+ sourceSessionTitle: str(entry["sourceSessionTitle"], void 0),
56562
+ userNeed: str(entry["userRequirement"]),
56563
+ approach: str(entry["solution"]),
56564
+ outcome: str(entry["completionStatus"]),
56565
+ whatFailed: str(entry["problemsEncountered"], "none"),
56566
+ whatWorked: "none",
56567
+ extractionSource: entry["extractionSource"] === "exit" ? "exit" : "compaction",
56568
+ recordedAt: typeof entry["recordedAt"] === "number" ? entry["recordedAt"] : 0
56569
+ };
56570
+ }
56571
+ return entry;
56572
+ } catch {
56573
+ return;
56574
+ }
56575
+ }
56576
+ /**
56577
+ * One-time migration from per-workDir memory stores to a global store.
56578
+ * Reads `<screamHomeDir>/sessions/<workDirKey>/memory/entries.jsonl`
56579
+ * and appends valid entries to `<screamHomeDir>/memory/entries.jsonl`.
56580
+ * Deletes the legacy per-session memory files afterwards and writes a marker
56581
+ * file so the migration only runs once.
56582
+ */
56583
+ static async migrateLegacyStores(screamHomeDir) {
56584
+ const target = new MemoryMemoStore(screamHomeDir);
56585
+ const markerPath = join$1(screamHomeDir, "memory", MIGRATION_MARKER);
56586
+ try {
56587
+ await stat(markerPath);
56588
+ return;
56589
+ } catch {}
56590
+ const sessionsDir = join$1(screamHomeDir, "sessions");
56591
+ let sessionEntries;
56592
+ try {
56593
+ sessionEntries = await readdir(sessionsDir, { withFileTypes: true }).then((entries) => entries.filter((e) => e.isDirectory()).map((e) => e.name));
56594
+ } catch {
56595
+ await writeFile(markerPath, "", "utf8").catch(() => {});
56596
+ return;
56597
+ }
56598
+ const migratedIds = /* @__PURE__ */ new Set();
56599
+ for await (const memo of target.read()) migratedIds.add(memo.id);
56600
+ let migratedCount = 0;
56601
+ const legacyPaths = [];
56602
+ for (const sessionKey of sessionEntries) {
56603
+ const legacyPath = join$1(sessionsDir, sessionKey, "memory", FILE_NAME);
56604
+ let stream;
56605
+ try {
56606
+ stream = createReadStream(legacyPath, { encoding: "utf8" });
56607
+ } catch {
56608
+ continue;
56609
+ }
56610
+ stream.on("error", () => {});
56611
+ let line = "";
56612
+ try {
56613
+ for await (const chunk of stream) {
56614
+ line += chunk;
56615
+ let newlineIndex = line.indexOf("\n");
56616
+ while (newlineIndex !== -1) {
56617
+ const rawLine = line.slice(0, newlineIndex).replace(/\r$/, "");
56618
+ line = line.slice(newlineIndex + 1);
56619
+ newlineIndex = line.indexOf("\n");
56620
+ const memo = target.parseLine(rawLine, 0);
56621
+ if (memo === void 0 || migratedIds.has(memo.id)) continue;
56622
+ await target.append(memo);
56623
+ migratedIds.add(memo.id);
56624
+ migratedCount++;
56625
+ }
56626
+ }
56627
+ } catch {
56628
+ continue;
56629
+ }
56630
+ legacyPaths.push(legacyPath);
56631
+ }
56632
+ for (const legacyPath of legacyPaths) {
56633
+ await unlink(legacyPath).catch(() => {});
56634
+ await rmdir(dirname$2(legacyPath)).catch(() => {});
56635
+ }
56636
+ await writeFile(markerPath, `${migratedCount}\n`, "utf8").catch(() => {});
56637
+ }
56638
+ };
56639
+ //#endregion
56640
+ //#region ../../packages/memory/src/extractor.ts
56641
+ /**
56642
+ * Parse memory-memo blocks from LLM compaction output.
56643
+ */
56644
+ function parseMemoryMemos(text) {
56645
+ const memos = [];
56646
+ const regex = /```memory-memo[\s\S]*?\n([\s\S]*?)```/g;
56647
+ let match;
56648
+ while ((match = regex.exec(text)) !== null) {
56649
+ const jsonStr = match[1]?.trim();
56650
+ if (!jsonStr) continue;
56651
+ try {
56652
+ const parsed = JSON.parse(jsonStr);
56653
+ if (parsed["none"] === true) continue;
56654
+ const userNeed = typeof parsed["userNeed"] === "string" ? parsed["userNeed"].trim() : "";
56655
+ if (userNeed.length === 0) {
56656
+ console.warn("[memory] Skipping memory-memo with empty userNeed:", jsonStr.slice(0, 200));
56657
+ continue;
56658
+ }
56659
+ memos.push(createMemoryMemo({
56660
+ userNeed,
56661
+ approach: typeof parsed["approach"] === "string" ? parsed["approach"].trim() : "",
56662
+ outcome: typeof parsed["outcome"] === "string" ? parsed["outcome"].trim() : "",
56663
+ whatFailed: typeof parsed["whatFailed"] === "string" ? parsed["whatFailed"].trim() : "none",
56664
+ whatWorked: typeof parsed["whatWorked"] === "string" ? parsed["whatWorked"].trim() : "none",
56665
+ extractionSource: "compaction",
56666
+ sourceSessionId: "",
56667
+ sourceSessionTitle: ""
56668
+ }));
56669
+ } catch (err) {
56670
+ console.warn("[memory] Malformed JSON in memory-memo block:", jsonStr.slice(0, 200), err);
56671
+ }
56672
+ }
56673
+ return memos;
56674
+ }
56675
+ /** System prompt for exit-time extraction — instructs the LLM how to extract. */
56676
+ const EXIT_EXTRACTION_SYSTEM_PROMPT = "你是一个任务经验提取助手。任务是从对话记录中识别已完成的任务闭环,提炼出任务经验记录。用对话的主要语言输出(中文对话用中文,英文对话用英文)。只输出指定的 JSON 格式,不要调用任何工具。";
56677
+ /** Build the user prompt for exit-time extraction, including a conversation sample. */
56678
+ function buildExitExtractionPrompt(sessionId, messageCount, sampleText) {
56679
+ return `以下是会话 "${sessionId}"(共 ${messageCount} 条消息)的对话记录。请提取其中所有**已完成的任务闭环**:
56680
+
56681
+ 判断标准:
56682
+ - 用户提出了明确的需求或问题
56683
+ - 给出了解决方案或回答
56684
+ - 结果明确(成功、部分完成、失败)
56685
+
56686
+ 对每个已完成的任务闭环,输出一个结构化经验记录。**必须用对话的主要语言书写**:
56687
+
56688
+ \`\`\`memory-memo
56689
+ {
56690
+ "userNeed": "<用户需求/目标,一句话概括>",
56691
+ "approach": "<执行方案,做了什么,2-4 句话>",
56692
+ "outcome": "<最终结果,如'完成'、'部分完成'、'失败:原因'>",
56693
+ "whatFailed": "<踩坑记录:试了但不行的路,无则填 'none'>",
56694
+ "whatWorked": "<成功经验:最终奏效的关键动作,无则填 'none'>"
56695
+ }
56696
+ \`\`\`
56697
+
56698
+ 注意:
56699
+ - whatFailed 记录重要的错误尝试,帮助未来避免重蹈覆辙
56700
+ - whatWorked 记录最终成功的关键动作,帮助未来复用经验
56701
+ - 跳过未完成的工作,除非其中包含有价值的踩坑经验
56702
+ - 将紧密相关的子任务合并为一条记录
56703
+ - 严格遵守字段名和 JSON 格式,不要添加额外字段
56704
+
56705
+ 如果没有已完成的任务闭环,输出:
56706
+ \`\`\`memory-memo
56707
+ {"none": true}
56708
+ \`\`\`
56709
+
56710
+ --- 对话记录(最近 30 条消息)---
56711
+
56712
+ ${sampleText}
56713
+
56714
+ --- 对话记录结束 ---`;
56715
+ }
56716
+ //#endregion
56717
+ //#region ../../packages/memory/src/scoring.ts
56718
+ /**
56719
+ * Multi-factor relevance score for a memory memo against a query.
56720
+ * Pure deterministic scoring — no LLM call, no network.
56721
+ */
56722
+ function computeRelevanceScore(memo, query, usageCount = 0) {
56723
+ const factors = {
56724
+ keywordOverlap: computeKeywordSimilarity(memo, query),
56725
+ recency: computeRecency(memo.recordedAt),
56726
+ usageBoost: Math.min(.3, usageCount * .1)
56727
+ };
56728
+ return factors.keywordOverlap * .5 + factors.recency * .25 + factors.usageBoost * .25;
56729
+ }
56730
+ /**
56731
+ * Score multiple memos against a query, returning sorted results.
56732
+ */
56733
+ function rankMemos(memos, query, minScore = .3, maxResults = 3) {
56734
+ return memos.map((memo) => ({
56735
+ memo,
56736
+ score: computeRelevanceScore(memo, query)
56737
+ })).filter((s) => s.score >= minScore).sort((a, b) => b.score - a.score).slice(0, maxResults);
56738
+ }
56739
+ const STOP_WORDS = new Set([
56740
+ "的",
56741
+ "了",
56742
+ "在",
56743
+ "是",
56744
+ "我",
56745
+ "有",
56746
+ "和",
56747
+ "就",
56748
+ "不",
56749
+ "人",
56750
+ "都",
56751
+ "一",
56752
+ "一个",
56753
+ "上",
56754
+ "也",
56755
+ "很",
56756
+ "到",
56757
+ "说",
56758
+ "要",
56759
+ "去",
56760
+ "你",
56761
+ "会",
56762
+ "着",
56763
+ "没有",
56764
+ "看",
56765
+ "好",
56766
+ "自己",
56767
+ "这",
56768
+ "他",
56769
+ "她",
56770
+ "它",
56771
+ "们",
56772
+ "那",
56773
+ "些",
56774
+ "the",
56775
+ "a",
56776
+ "an",
56777
+ "is",
56778
+ "are",
56779
+ "was",
56780
+ "were",
56781
+ "be",
56782
+ "been",
56783
+ "being",
56784
+ "have",
56785
+ "has",
56786
+ "had",
56787
+ "do",
56788
+ "does",
56789
+ "did",
56790
+ "will",
56791
+ "would",
56792
+ "could",
56793
+ "should",
56794
+ "may",
56795
+ "might",
56796
+ "can",
56797
+ "shall",
56798
+ "to",
56799
+ "of",
56800
+ "in",
56801
+ "for",
56802
+ "on",
56803
+ "with",
56804
+ "at",
56805
+ "by",
56806
+ "from",
56807
+ "as",
56808
+ "into",
56809
+ "through",
56810
+ "during",
56811
+ "before",
56812
+ "after",
56813
+ "above",
56814
+ "below",
56815
+ "between",
56816
+ "and",
56817
+ "but",
56818
+ "or",
56819
+ "nor",
56820
+ "not",
56821
+ "so",
56822
+ "yet",
56823
+ "both",
56824
+ "either",
56825
+ "neither",
56826
+ "each",
56827
+ "every",
56828
+ "all",
56829
+ "any",
56830
+ "few",
56831
+ "more",
56832
+ "most",
56833
+ "other",
56834
+ "some",
56835
+ "such",
56836
+ "no",
56837
+ "only",
56838
+ "own",
56839
+ "same",
56840
+ "than",
56841
+ "too",
56842
+ "very",
56843
+ "just",
56844
+ "because",
56845
+ "about",
56846
+ "what",
56847
+ "which",
56848
+ "who",
56849
+ "whom",
56850
+ "this",
56851
+ "that",
56852
+ "these",
56853
+ "those",
56854
+ "it",
56855
+ "its",
56856
+ "he",
56857
+ "she",
56858
+ "they",
56859
+ "we",
56860
+ "you",
56861
+ "how"
56862
+ ]);
56863
+ function extractKeywords(text) {
56864
+ const lower = text.toLowerCase();
56865
+ const tokens = [];
56866
+ const parts = lower.split(/[^a-z0-9一-鿿㐀-䶿]+/);
56867
+ for (const part of parts) {
56868
+ if (part.length === 0) continue;
56869
+ if (/[一-鿿㐀-䶿]/.test(part)) {
56870
+ for (const ch of part) if (ch.length >= 1 && !STOP_WORDS.has(ch)) tokens.push(ch);
56871
+ }
56872
+ if (/[a-z0-9]/.test(part) && part.length >= 2 && !STOP_WORDS.has(part)) tokens.push(part);
56873
+ }
56874
+ return [...new Set(tokens)];
56875
+ }
56876
+ function computeKeywordSimilarity(memo, query) {
56877
+ const memoWords = extractKeywords(`${memo.userNeed} ${memo.approach} ${memo.whatFailed} ${memo.whatWorked}`);
56878
+ const queryWords = extractKeywords(query);
56879
+ if (memoWords.length === 0 || queryWords.length === 0) return 0;
56880
+ const intersection = memoWords.filter((w) => queryWords.includes(w)).length;
56881
+ const union = new Set([...memoWords, ...queryWords]).size;
56882
+ return union === 0 ? 0 : intersection / union;
56883
+ }
56884
+ function computeRecency(recordedAt) {
56885
+ const daysSince = (Date.now() - recordedAt) / (1e3 * 60 * 60 * 24);
56886
+ return Math.max(0, 1 - daysSince / 90);
56887
+ }
56888
+ //#endregion
56889
+ //#region ../../packages/memory/src/consolidator.ts
56890
+ const SIMILARITY_THRESHOLD = .45;
56891
+ const STALE_DAYS = 30;
56892
+ /**
56893
+ * Analyze all memos and produce a consolidation plan.
56894
+ *
56895
+ * Pure logic — no LLM call. Uses keyword similarity to find near-duplicate
56896
+ * memos, flags resolved/stale entries.
56897
+ */
56898
+ async function buildConsolidationPlan(store) {
56899
+ const allMemos = [];
56900
+ for await (const memo of store.read()) allMemos.push(memo);
56901
+ const summaries = allMemos.map(toSummary);
56902
+ const duplicateGroups = findDuplicateGroups(summaries);
56903
+ const resolved = findResolved(summaries);
56904
+ const stale = findStale(summaries, STALE_DAYS);
56905
+ const dedupedCount = duplicateGroups.reduce((acc, g) => acc + g.memos.length - 1, 0);
56906
+ return {
56907
+ duplicateGroups,
56908
+ resolved,
56909
+ stale,
56910
+ summary: {
56911
+ totalMemos: allMemos.length,
56912
+ duplicatesFound: dedupedCount,
56913
+ resolvedFound: resolved.length,
56914
+ staleFound: stale.length,
56915
+ memosAfterConsolidation: allMemos.length - dedupedCount - resolved.length - stale.length
56916
+ }
56917
+ };
56918
+ }
56919
+ /**
56920
+ * Apply a consolidation plan: delete duplicates, resolved, and stale memos,
56921
+ * appending merged replacements for duplicates.
56922
+ */
56923
+ async function applyConsolidation(store, plan) {
56924
+ let deleted = 0;
56925
+ let created = 0;
56926
+ for (const memo of plan.resolved) {
56927
+ await store.delete(memo.id);
56928
+ deleted++;
56929
+ }
56930
+ for (const memo of plan.stale) {
56931
+ await store.delete(memo.id);
56932
+ deleted++;
56933
+ }
56934
+ for (const group of plan.duplicateGroups) {
56935
+ const newest = group.memos.reduce((a, b) => a.recordedAt > b.recordedAt ? a : b);
56936
+ const merged = createMemoryMemo({
56937
+ sourceSessionId: newest.sourceSessionId,
56938
+ sourceSessionTitle: newest.sourceSessionTitle,
56939
+ userNeed: group.merged.userNeed,
56940
+ approach: group.merged.approach,
56941
+ outcome: group.merged.outcome,
56942
+ whatFailed: group.merged.whatFailed,
56943
+ whatWorked: group.merged.whatWorked,
56944
+ extractionSource: "compaction"
56945
+ });
56946
+ for (const memo of group.memos) {
56947
+ await store.delete(memo.id);
56948
+ deleted++;
56949
+ }
56950
+ await store.append(merged);
56951
+ created++;
56952
+ }
56953
+ return {
56954
+ deleted,
56955
+ created
56956
+ };
56957
+ }
56958
+ function findDuplicateGroups(memos) {
56959
+ const groups = [];
56960
+ const used = /* @__PURE__ */ new Set();
56961
+ for (let i = 0; i < memos.length; i++) {
56962
+ const first = memos[i];
56963
+ if (!first || used.has(first.id)) continue;
56964
+ const cluster = [first];
56965
+ for (let j = i + 1; j < memos.length; j++) {
56966
+ const candidate = memos[j];
56967
+ if (!candidate || used.has(candidate.id)) continue;
56968
+ if (cluster.some((m) => {
56969
+ return computeRelevanceScore(candidate, `${m.userNeed} ${m.approach}`) >= SIMILARITY_THRESHOLD;
56970
+ })) cluster.push(candidate);
56971
+ }
56972
+ if (cluster.length > 1) {
56973
+ for (const m of cluster) used.add(m.id);
56974
+ groups.push(buildDuplicateGroup(cluster));
56975
+ }
56976
+ }
56977
+ return groups;
56978
+ }
56979
+ function buildDuplicateGroup(cluster) {
56980
+ const newest = [...cluster].sort((a, b) => b.recordedAt - a.recordedAt)[0];
56981
+ const failures = new Set(cluster.map((m) => m.whatFailed).filter((p) => p && p !== "none" && p !== "无"));
56982
+ const successes = new Set(cluster.map((m) => m.whatWorked).filter((w) => w && w !== "none" && w !== "无"));
56983
+ const bestOutcome = cluster.map((m) => m.outcome).some((o) => o.includes("完成") || o.toLowerCase().includes("done")) ? "完成" : newest.outcome;
56984
+ return {
56985
+ memos: cluster,
56986
+ merged: {
56987
+ userNeed: newest.userNeed,
56988
+ approach: `合并 ${cluster.length} 条相关记录。最新方案: ${newest.approach}`,
56989
+ outcome: bestOutcome,
56990
+ whatFailed: failures.size > 0 ? [...failures].join("; ") : "none",
56991
+ whatWorked: successes.size > 0 ? [...successes].join("; ") : "none"
56992
+ },
56993
+ reason: `发现 ${cluster.length} 条相似记录(关键词重叠 > ${Math.round(SIMILARITY_THRESHOLD * 100)}%)`
56994
+ };
56995
+ }
56996
+ function isOutcomeCompleted(outcome) {
56997
+ const lower = outcome.toLowerCase();
56998
+ return lower.includes("完成") || lower.includes("done") || lower.includes("completed") || lower.includes("成功") || lower.includes("success");
56999
+ }
57000
+ function findResolved(memos) {
57001
+ return memos.filter((m) => isOutcomeCompleted(m.outcome) && Date.now() - m.recordedAt > 10080 * 60 * 1e3);
57002
+ }
57003
+ function findStale(memos, staleDays) {
57004
+ const threshold = Date.now() - staleDays * 24 * 60 * 60 * 1e3;
57005
+ return memos.filter((m) => m.recordedAt < threshold && !isOutcomeCompleted(m.outcome) && !m.outcome.includes("blocked"));
57006
+ }
57007
+ //#endregion
57008
+ //#region ../../packages/memory/src/dream.ts
57009
+ const LOCK_FILE = "dream-lock.json";
57010
+ const MIN_HOURS_BETWEEN_DREAMS = 24;
57011
+ const MIN_SESSIONS_BETWEEN_DREAMS = 5;
57012
+ /**
57013
+ * Tracks dream consolidation state and decides when to suggest running
57014
+ * another dream. Persisted to `<screamHomeDir>/dream-lock.json`.
57015
+ */
57016
+ var DreamTracker = class {
57017
+ state;
57018
+ lockPath;
57019
+ screamHomeDir;
57020
+ initialized = false;
57021
+ constructor(screamHomeDir) {
57022
+ this.screamHomeDir = screamHomeDir;
57023
+ this.lockPath = join$1(screamHomeDir, LOCK_FILE);
57024
+ this.state = {
57025
+ lastDreamAt: (/* @__PURE__ */ new Date()).toISOString(),
57026
+ sessionsSinceLastDream: 0
57027
+ };
57028
+ }
57029
+ /** Load persisted state (call once at startup). */
57030
+ async init() {
57031
+ if (this.initialized) return;
57032
+ this.initialized = true;
57033
+ try {
57034
+ const raw = await readFile(this.lockPath, "utf8");
57035
+ const parsed = JSON.parse(raw);
57036
+ if (parsed.version === 1 && parsed.state) {
57037
+ this.state = parsed.state;
57038
+ return;
57039
+ }
57040
+ } catch {}
57041
+ await this.migrateLegacyLockFiles();
57042
+ }
57043
+ /** Record that a dream completed successfully. */
57044
+ async recordDream() {
57045
+ this.state = {
57046
+ lastDreamAt: (/* @__PURE__ */ new Date()).toISOString(),
57047
+ sessionsSinceLastDream: 0
57048
+ };
57049
+ await this.persist();
57050
+ }
57051
+ /** Call on each new session to bump the session counter. */
57052
+ async recordNewSession() {
57053
+ if (!this.initialized) await this.init();
57054
+ this.state.sessionsSinceLastDream += 1;
57055
+ await this.persist();
57056
+ }
57057
+ /** Check whether it's time to suggest another dream. */
57058
+ shouldSuggest() {
57059
+ return (Date.now() - new Date(this.state.lastDreamAt).getTime()) / (1e3 * 60 * 60) >= MIN_HOURS_BETWEEN_DREAMS && this.state.sessionsSinceLastDream >= MIN_SESSIONS_BETWEEN_DREAMS;
57060
+ }
57061
+ /** Get a human-readable suggestion message when conditions are met. */
57062
+ getSuggestionMessage() {
57063
+ const hoursSince = (Date.now() - new Date(this.state.lastDreamAt).getTime()) / (1e3 * 60 * 60);
57064
+ return `距离上次记忆整理已过去 ${Math.floor(hoursSince / 24)} 天、${this.state.sessionsSinceLastDream} 个会话。建议运行 /dream 来合并重复记忆、清理过期条目、解决矛盾信息。`;
57065
+ }
57066
+ async persist() {
57067
+ const data = {
57068
+ version: 1,
57069
+ state: this.state
57070
+ };
57071
+ try {
57072
+ await mkdir(dirname$2(this.lockPath), { recursive: true });
57073
+ await writeFile(this.lockPath, JSON.stringify(data, null, 2), "utf8");
57074
+ } catch {}
57075
+ }
57076
+ /**
57077
+ * One-time migration for legacy dream-lock.json locations:
57078
+ * - <screamHomeDir>/.scream-code/dream-lock.json (buggy double directory)
57079
+ * - <screamHomeDir>/sessions/<sessionKey>/.scream-code/dream-lock.json (old per-session)
57080
+ *
57081
+ * Picks the most recent state, writes it to the new global location, and
57082
+ * deletes the legacy files/directories.
57083
+ */
57084
+ async migrateLegacyLockFiles() {
57085
+ const candidates = [join$1(this.screamHomeDir, ".scream-code", LOCK_FILE)];
57086
+ const sessionsDir = join$1(this.screamHomeDir, "sessions");
57087
+ try {
57088
+ const entries = await readdir(sessionsDir, { withFileTypes: true });
57089
+ for (const entry of entries) if (entry.isDirectory()) candidates.push(join$1(sessionsDir, entry.name, ".scream-code", LOCK_FILE));
57090
+ } catch {}
57091
+ let bestState;
57092
+ for (const candidate of candidates) try {
57093
+ await stat(candidate);
57094
+ const raw = await readFile(candidate, "utf8");
57095
+ const parsed = JSON.parse(raw);
57096
+ if (parsed.version === 1 && parsed.state) {
57097
+ if (bestState === void 0 || new Date(parsed.state.lastDreamAt).getTime() > new Date(bestState.lastDreamAt).getTime()) bestState = parsed.state;
57098
+ }
57099
+ } catch {}
57100
+ if (bestState !== void 0) {
57101
+ this.state = bestState;
57102
+ await this.persist();
57103
+ }
57104
+ for (const candidate of candidates) try {
57105
+ await rm(candidate);
57106
+ await rmdir(dirname$2(candidate)).catch(() => {});
57107
+ } catch {}
57108
+ }
57109
+ };
57110
+ //#endregion
57111
+ //#region ../../packages/agent-core/src/tools/builtin/memory/memory-consolidate.ts
57112
+ const MemoryMemoSummarySchema = z.object({
57113
+ id: z.string(),
57114
+ sourceSessionId: z.string(),
57115
+ sourceSessionTitle: z.string().optional(),
57116
+ userNeed: z.string(),
57117
+ approach: z.string(),
57118
+ outcome: z.string(),
57119
+ whatFailed: z.string(),
57120
+ whatWorked: z.string(),
57121
+ extractionSource: z.string(),
57122
+ recordedAt: z.number()
57123
+ });
57124
+ const DuplicateGroupSchema = z.object({
57125
+ memos: z.array(MemoryMemoSummarySchema),
57126
+ merged: z.object({
57127
+ userNeed: z.string(),
57128
+ approach: z.string(),
57129
+ outcome: z.string(),
57130
+ whatFailed: z.string(),
57131
+ whatWorked: z.string()
57132
+ }),
57133
+ reason: z.string()
57134
+ });
57135
+ const ConsolidationPlanSchema = z.object({
57136
+ duplicateGroups: z.array(DuplicateGroupSchema),
57137
+ resolved: z.array(MemoryMemoSummarySchema),
57138
+ stale: z.array(MemoryMemoSummarySchema),
57139
+ summary: z.object({
57140
+ totalMemos: z.number(),
57141
+ duplicatesFound: z.number(),
57142
+ resolvedFound: z.number(),
57143
+ staleFound: z.number(),
57144
+ memosAfterConsolidation: z.number()
57145
+ })
57146
+ });
57147
+ /**
57148
+ * Produces a consolidation plan for the global memory memo store: near-duplicate
57149
+ * groups, resolved entries, and stale entries. The model should present this plan
57150
+ * to the user and ask for confirmation before applying it.
57151
+ */
57152
+ var MemoryConsolidatePlanTool = class {
57153
+ agent;
57154
+ name = "MemoryConsolidatePlan";
57155
+ description = "Analyze the global memory memo store and produce a consolidation plan. The plan includes groups of near-duplicate memos to merge, resolved (completed) memos to remove, and stale memos to prune. Only call this when the user has invoked /dream. Present the returned plan to the user and ask for confirmation before calling MemoryConsolidateApply.";
57156
+ parameters = toInputJsonSchema(z.object({}));
57157
+ constructor(agent) {
57158
+ this.agent = agent;
57159
+ }
57160
+ resolveExecution() {
57161
+ return {
57162
+ description: "Building memory consolidation plan",
57163
+ approvalRule: this.name,
57164
+ execute: async () => {
57165
+ const store = this.agent.memoStore;
57166
+ if (!store) return {
57167
+ isError: true,
57168
+ output: "Memory memo store is not available."
57169
+ };
57170
+ const plan = await buildConsolidationPlan(store);
57171
+ if (plan.summary.totalMemos === 0) return {
57172
+ isError: false,
57173
+ output: "The memory memo store is empty; nothing to consolidate."
57174
+ };
57175
+ return {
57176
+ isError: false,
57177
+ output: JSON.stringify(plan)
57178
+ };
57179
+ }
57180
+ };
57181
+ }
57182
+ };
57183
+ /**
57184
+ * Applies a consolidation plan produced by MemoryConsolidatePlan. Deletes the
57185
+ * original memos in duplicate groups and appends merged replacements, deletes
57186
+ * resolved and stale memos, and returns the count of deleted/created entries.
57187
+ */
57188
+ var MemoryConsolidateApplyTool = class {
57189
+ agent;
57190
+ name = "MemoryConsolidateApply";
57191
+ description = "Apply a memory consolidation plan produced by MemoryConsolidatePlan. This deletes the original memos in duplicate groups and appends merged replacements, deletes resolved and stale memos, and returns the count of deleted and created entries. Only call this after the user has explicitly confirmed the plan shown from MemoryConsolidatePlan.";
57192
+ parameters = toInputJsonSchema(ConsolidationPlanSchema);
57193
+ constructor(agent) {
57194
+ this.agent = agent;
57195
+ }
57196
+ resolveExecution(args) {
57197
+ return {
57198
+ description: "Applying memory consolidation plan",
57199
+ approvalRule: this.name,
57200
+ execute: async () => {
57201
+ const store = this.agent.memoStore;
57202
+ if (!store) return {
57203
+ isError: true,
57204
+ output: "Memory memo store is not available."
57205
+ };
57206
+ const result = await applyConsolidation(store, args);
57207
+ await this.agent.dreamTracker.recordDream();
57208
+ return {
57209
+ isError: false,
57210
+ output: `Consolidation complete. Deleted ${result.deleted} memo(s) and created ${result.created} merged memo(s).`
57211
+ };
57212
+ }
57213
+ };
57214
+ }
57215
+ };
57216
+ //#endregion
57217
+ //#region ../../packages/agent-core/src/tools/builtin/memory/memory-lookup.ts
57218
+ const DEFAULT_LIMIT = 5;
57219
+ const MAX_LIMIT = 20;
57220
+ const DEFAULT_MIN_SCORE = .2;
57221
+ const MemoryLookupInputSchema = z.object({
57222
+ query: z.string().min(1).describe("Search query describing the current task, error, approach, or keywords to look up in the global memory memo store."),
57223
+ limit: z.number().int().min(1).max(MAX_LIMIT).optional().describe(`Maximum number of memos to return (default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}).`),
57224
+ min_score: z.number().min(0).max(1).optional().describe(`Minimum relevance score threshold from 0 to 1 (default ${DEFAULT_MIN_SCORE}).`)
57225
+ });
57226
+ /**
57227
+ * Lets the model actively search the global memory memo store for historical
57228
+ * task experiences. Returns ranked memos with what failed and what worked so
57229
+ * the model can avoid repeating past mistakes or rediscovering known solutions.
57230
+ */
57231
+ var MemoryLookupTool = class {
57232
+ agent;
57233
+ name = "MemoryLookup";
57234
+ description = "Search the global memory memo store for historical experiences from past user tasks. Call this when the current task may benefit from prior work, when you encounter a repeating error or pattern, or when you are unsure of the best approach. Returns memos ranked by relevance, including the approach taken, the outcome, what failed, and what worked.";
57235
+ parameters = toInputJsonSchema(MemoryLookupInputSchema);
57236
+ constructor(agent) {
57237
+ this.agent = agent;
57238
+ }
57239
+ resolveExecution(args) {
57240
+ return {
57241
+ description: "Searching memory memos",
57242
+ approvalRule: this.name,
57243
+ execute: async () => {
57244
+ const store = this.agent.memoStore;
57245
+ if (!store) return {
57246
+ isError: true,
57247
+ output: "Memory memo store is not available."
57248
+ };
57249
+ const query = args.query.trim();
57250
+ if (query.length === 0) return {
57251
+ isError: true,
57252
+ output: "Query cannot be empty."
57253
+ };
57254
+ const limit = Math.min(args.limit ?? DEFAULT_LIMIT, MAX_LIMIT);
57255
+ const minScore = args.min_score ?? DEFAULT_MIN_SCORE;
57256
+ const all = [];
57257
+ for await (const memo of store.read()) all.push(toSummary(memo));
57258
+ if (all.length === 0) return {
57259
+ isError: false,
57260
+ output: "No memory memos found. The experience store is empty."
57261
+ };
57262
+ const ranked = rankMemos(all, query, minScore, limit);
57263
+ if (ranked.length === 0) return {
57264
+ isError: false,
57265
+ output: `No relevant memory memos found for query "${query}".`
57266
+ };
57267
+ const lines = [`Found ${ranked.length} relevant memory memo${ranked.length === 1 ? "" : "s"} for query "${query}":`, ""];
57268
+ for (const [i, { memo, score }] of ranked.entries()) {
57269
+ const source = memo.sourceSessionTitle?.length ? ` (from: ${memo.sourceSessionTitle})` : "";
57270
+ lines.push(`**${i + 1}. ${memo.userNeed}${source}**`, ` Score: ${score.toFixed(3)}`, ` Approach: ${memo.approach}`, ` Outcome: ${memo.outcome}`, ...memo.whatFailed !== "none" ? [` What failed: ${memo.whatFailed}`] : [], ...memo.whatWorked !== "none" ? [` What worked: ${memo.whatWorked}`] : [], "");
57271
+ }
57272
+ return {
57273
+ isError: false,
57274
+ output: lines.join("\n")
57275
+ };
57276
+ }
57277
+ };
57278
+ }
57279
+ };
57280
+ //#endregion
56406
57281
  //#region ../../node_modules/.pnpm/js-yaml@4.1.1/node_modules/js-yaml/dist/js-yaml.mjs
56407
57282
  /*! js-yaml 4.1.1 https://github.com/nodeca/js-yaml @license MIT */
56408
57283
  function isNothing(subject) {
@@ -60076,7 +60951,7 @@ function isRecord$5(value) {
60076
60951
  }
60077
60952
  //#endregion
60078
60953
  //#region ../../packages/agent-core/src/skill/builtin/dream.md
60079
- var dream_default = "---\nname: dream\ndescription: 整理记忆库 — 合并重复、解决矛盾、清理过时条目\n---\n\n# Dream: 记忆合并整理\n\n用户调用了 `/dream`。你要对记忆库进行一次完整的整理和清理。\n\n## 前置检查\n\n1. 确定记忆文件位置。用以下 Node.js 命令查找(跨平台兼容 Windows / macOS / Linux):\n ```bash\n node -e \"const p=require('path'),f=require('fs'),h=require('os').homedir();const d=p.join(h,'.scream-code','sessions');const r=[];for(const e of f.readdirSync(d)){const m=p.join(d,e,'memory','entries.jsonl');if(f.existsSync(m))r.push({p:m,t:f.statSync(m).mtimeMs})}r.sort((a,b)=>b.t-a.t);console.log(r[0]?.p||'')\"\n ```\n 如果输出为空,告知用户\"记忆库为空,无需整理\"并停止。\n 记住这个路径,后续所有读写都用它。\n\n## 四阶段流程\n\n### 阶段一:Orient(定向)\n\n读取全部记忆(逐行 JSONL),了解现有记忆的全貌。统计:\n\n- 总条数\n- outcome 分布(完成 / 部分完成 / 失败等)\n- 时间范围(最早 → 最新)\n\n向用户报告概况。\n\n### 阶段二:Gather(收集信号)\n\n扫描所有记忆,找出以下信号:\n\n**重复信号**(仅限高度确定的重复):\n- 两条或多条记忆描述的是完全相同的问题和相同的解决方案(如\"修复登录页 token 刷新\"出现了 3 次)\n- 用你的理解判断,但必须非常保守。\"登录页 bug\"和\"token 过期问题\"可能是同一件事,也可能不是——如果不确定,**不要标记为重复**\n- 仅仅主题相关不算重复。两个不同的 bug 修复、两个不同的优化、同一个模块的不同改动,都应保留为独立记录\n\n**矛盾信号**:\n- 两条记忆给出相反的信息(如一条说\"用方案 A\",另一条说\"方案 A 不行要用方案 B\")\n- 一条完成而另一条失败但描述的是同一件事\n\n**过时信号**:\n- outcome 包含\"完成\"且 >= 7 天前的记忆(已完成,无需保留详情)\n- outcome 包含\"放弃\"且 >= 30 天前的记忆(放弃已久,可以清理)\n- 内容指向已不存在的文件或旧架构\n\n### 阶段三:Consolidate(合并方案)\n\n对每组信号,产出合并建议:\n\n**对于重复组**:\n- 列出组内所有记忆 ID\n- 给出合并后的一条新记忆(用最新的 userNeed 为底,融合所有 approach)\n- 选择最准确的 outcome\n- 汇总所有 whatFailed 和 whatWorked\n\n**对于矛盾组**:\n- 列出冲突的记忆\n- 给出你的判断:哪个是对的(或两者都保留并标记矛盾)\n- 建议:保留更新的一条 + 在 approach 中注明\"之前认为 X,后确认 Y\"\n\n**对于过时条目**:\n- 建议直接删除(标注原因)\n\n### 阶段四:Prune(裁剪确认)\n\n输出完整的合并计划:\n\n```\n## Dream 整理计划\n\n### 概况\n- 当前共 X 条记忆\n- 整理后预计 Y 条\n\n### 重复合并(N 组)\n**组 1: 修复登录 token 刷新**\n- memo-abc123 (2026-05-01, 完成) — \"登录页 token 过期需要手动刷新\"\n- memo-def456 (2026-05-10, 完成) — \"登录 token 过期问题修复\"\n→ 合并为: \"修复登录页 token 过期问题。方案: 在 axios 拦截器中添加自动 refresh 逻辑...\"\n 结果: 完成\n\n### 矛盾解决(N 组)\n**组 2: ...**\n\n### 建议删除(N 条)\n- memo-xyz789: \"添加暗色模式\" (完成, 2026-04-01) — 已完成超过 2 个月\n\n### 总结\n- 合并: N 组 → 减少 X 条\n- 删除: N 条\n- 整理后: 共 Y 条记忆\n```\n\n用 AskUserQuestion 让用户选择:\n- \"执行整理\" — 按计划执行\n- \"仅显示计划\" — 不做修改\n- \"取消\"\n\n## 执行\n\n如果用户确认\"执行整理\":\n\n1. 删除所有被合并的原记忆(用 Bash 读写 JSONL + 临时文件替换)\n2. 为每个合并组追加一条新记忆(createMemoryMemo 字段)\n3. 删除过时记忆\n4. 更新 dream-lock.json:从 entries.jsonl 路径推导——取其父目录的父目录(即 `sessions/wd_<hash>/`),在此目录下找 `.scream-code/dream-lock.json` 并写入当前时间戳,sessionsSinceLastDream 归零\n5. 报告结果:\"已删除 X 条,创建 Y 条合并记忆。当前共 Z 条记忆。记忆库整理完成。\"\n\n## 重要规则\n\n- **仅合并高度重复的记忆**。只有当两条记忆描述的是同一件事、同一个问题、同一个修复方案时才可合并。如果只是主题相关但细节不同(如两个不同的 bug、两个不同的优化方向),**绝对不能合并**。宁可多保留十条,不可误删一条。误合并导致的记忆丢失是不可逆的。\n- 不确定时保留原文,不要猜测删除\n- 重复判断用语义理解,不要纯关键词匹配\n- 矛盾组默认保留更新一条,旧的那条在 approach 中加注\"已过时\"\n- 操作前必须用户确认\n- dream-lock.json 格式:`{ \"version\": 1, \"state\": { \"lastDreamAt\": \"ISO时间\", \"sessionsSinceLastDream\": 0 } }`\n";
60954
+ var dream_default = "---\nname: dream\ndescription: 整理记忆库 — 合并重复、解决矛盾、清理过时条目\n---\n\n# Dream: 记忆合并整理\n\n用户调用了 `/dream`。你要对全局记忆库进行一次完整的整理和清理。\n\n记忆库是**全局**的,所有会话的记忆都保存在同一个文件里(`<screamHomeDir>/memory/entries.jsonl`),不是按会话分散存放。\n\n## 前置检查\n\n1. 调用 `MemoryConsolidatePlan` 工具获取整理计划。\n - 如果返回\"记忆库为空\",告知用户\"记忆库为空,无需整理\"并停止。\n - 否则你会得到一个 JSON 计划,包含:duplicateGroups(重复组)、resolved(已完成条目)、stale(过时条目)、summary(统计)。\n\n2. 不要直接修改记忆文件。所有删除和写入都通过 `MemoryConsolidateApply` 工具完成。\n\n## 整理计划展示\n\n把计划转换成用户可读的格式:\n\n```\n## Dream 整理计划\n\n### 概况\n- 当前共 X 条记忆\n- 重复组:N 组\n- 建议删除:M 条(已完成 + 过时)\n- 整理后预计:Y 条\n\n### 重复合并(N 组)\n**组 1: 修复登录 token 刷新**\n- memo-abc123 (2026-05-01, 完成) — \"登录页 token 过期需要手动刷新\"\n- memo-def456 (2026-05-10, 完成) — \"登录 token 过期问题修复\"\n→ 合并为: \"修复登录页 token 过期问题。方案: 在 axios 拦截器中添加自动 refresh 逻辑...\"\n 结果: 完成\n\n### 建议删除(M 条)\n- memo-xyz789: \"添加暗色模式\" (完成, 2026-04-01) — 已完成超过 2 个月\n- memo-old001: \"尝试某方案\" (放弃, 2026-03-01) — 过时\n\n### 总结\n- 合并: N 组 → 减少 X 条\n- 删除: M 条\n- 整理后: 共 Y 条记忆\n```\n\n用 AskUserQuestion 让用户选择:\n- \"执行整理\" — 按上述计划执行\n- \"仅显示计划\" — 不做修改,直接结束\n- \"取消\"\n\n## 执行整理\n\n如果用户选择\"执行整理\":\n\n1. 直接把 `MemoryConsolidatePlan` 返回的完整 JSON 计划作为参数,调用 `MemoryConsolidateApply`。\n2. 工具会自动完成以下操作:\n - 删除重复组中的原记忆\n - 为每组重复记忆追加一条合并后的新记忆(字段格式由工具保证正确)\n - 删除已完成和过时的记忆\n - 更新 `dream-lock.json`,重置建议计数器\n3. 向用户报告工具返回的结果:\"已删除 X 条,创建 Y 条合并记忆。记忆库整理完成。\"\n\n## 重要规则\n\n- **仅合并高度重复的记忆**。只有当两条记忆描述的是同一件事、同一个问题、同一个修复方案时才可合并。如果只是主题相关但细节不同(如两个不同的 bug、两个不同的优化方向),**绝对不能合并**。宁可多保留十条,不可误删一条。\n- 如果 `MemoryConsolidatePlan` 标记的某组重复实际上并不是同一件事,在展示计划时把它从\"重复合并\"里去掉,只保留你确信重复的组,再把精简后的计划传给 `MemoryConsolidateApply`。\n- 不确定时保留原文,不要猜测删除。\n- 操作前必须得到用户确认,不能擅自执行。\n- 不要手动写 Bash 去修改 `entries.jsonl`,统一通过 `MemoryConsolidateApply` 工具执行。\n";
60080
60955
  //#endregion
60081
60956
  //#region ../../packages/agent-core/src/skill/builtin/dream.ts
60082
60957
  const PSEUDO_PATH = "builtin://dream";
@@ -60353,7 +61228,7 @@ var SkillRegistry = class {
60353
61228
  this.indexPluginSkill(skill);
60354
61229
  }
60355
61230
  });
60356
- for (const skill of skills) this.byName.set(normalizeSkillName(skill.name), skill);
61231
+ for (const skill of skills) this.register(skill);
60357
61232
  }
60358
61233
  registerBuiltinSkill(skill) {
60359
61234
  this.register(skill.source === "builtin" ? skill : {
@@ -60362,9 +61237,20 @@ var SkillRegistry = class {
60362
61237
  });
60363
61238
  }
60364
61239
  register(skill, options = {}) {
60365
- const key = normalizeSkillName(skill.name);
60366
- if (options.replace === true || !this.byName.has(key)) this.byName.set(key, skill);
61240
+ const pluginId = skill.plugin?.id;
61241
+ const resolved = resolveUniqueSkillName(skill, this.byName, pluginId);
61242
+ if (resolved.shadowed) {
61243
+ this.onWarning(`Skill "${skill.name}" from ${skill.source}${pluginId !== void 0 ? ` (plugin "${pluginId}")` : ""} is shadowed by an existing skill with the same name.`);
61244
+ return;
61245
+ }
61246
+ const normalizedSkill = resolved.name === skill.name ? skill : {
61247
+ ...skill,
61248
+ name: resolved.name
61249
+ };
61250
+ const key = normalizeSkillName(resolved.name);
61251
+ if (options.replace === true || !this.byName.has(key)) this.byName.set(key, normalizedSkill);
60367
61252
  this.indexPluginSkill(skill, options);
61253
+ if (resolved.renamed) this.onWarning(`Skill "${skill.name}" from plugin "${pluginId}" was renamed to "${resolved.name}" because a skill with the same name is already registered.`);
60368
61254
  }
60369
61255
  getSkill(name) {
60370
61256
  return this.byName.get(normalizeSkillName(name));
@@ -60416,6 +61302,46 @@ var SkillRegistry = class {
60416
61302
  function pluginSkillKey(pluginId, skillName) {
60417
61303
  return `${pluginId}\0${normalizeSkillName(skillName)}`;
60418
61304
  }
61305
+ /**
61306
+ * Resolve a unique skill name when registering across multiple sources.
61307
+ *
61308
+ * Non-plugin skills are never renamed; if their name collides with an existing
61309
+ * skill they are considered shadowed and should be reported to the caller.
61310
+ * Plugin skills get a "plugin-id:skill-name" prefix on collision so users can
61311
+ * still invoke them and distinguish them in listings. If even the prefixed
61312
+ * form collides, a numeric suffix is appended.
61313
+ */
61314
+ function resolveUniqueSkillName(skill, byName, pluginId) {
61315
+ const originalKey = normalizeSkillName(skill.name);
61316
+ if (!byName.has(originalKey)) return {
61317
+ name: skill.name,
61318
+ renamed: false,
61319
+ shadowed: false
61320
+ };
61321
+ if (pluginId === void 0) return {
61322
+ name: skill.name,
61323
+ renamed: false,
61324
+ shadowed: true
61325
+ };
61326
+ const prefixed = `${pluginId}:${skill.name}`;
61327
+ const prefixedKey = normalizeSkillName(prefixed);
61328
+ if (!byName.has(prefixedKey)) return {
61329
+ name: prefixed,
61330
+ renamed: true,
61331
+ shadowed: false
61332
+ };
61333
+ let index = 2;
61334
+ while (true) {
61335
+ const candidate = `${pluginId}:${skill.name}-${String(index)}`;
61336
+ const candidateKey = normalizeSkillName(candidate);
61337
+ if (!byName.has(candidateKey)) return {
61338
+ name: candidate,
61339
+ renamed: true,
61340
+ shadowed: false
61341
+ };
61342
+ index++;
61343
+ }
61344
+ }
60419
61345
  const SOURCE_GROUPS = [
60420
61346
  {
60421
61347
  source: "project",
@@ -60452,12 +61378,12 @@ function formatFullSkill(skill) {
60452
61378
  ];
60453
61379
  }
60454
61380
  function formatModelSkill(skill) {
60455
- const lines = [`- ${skill.name}: ${truncate$1(skill.description, LISTING_DESC_MAX)}`];
61381
+ const lines = [`- ${skill.name}: ${truncate$2(skill.description, LISTING_DESC_MAX)}`];
60456
61382
  if (typeof skill.metadata.whenToUse === "string" && skill.metadata.whenToUse.length > 0) lines.push(` When to use: ${skill.metadata.whenToUse}`);
60457
61383
  lines.push(` Path: ${skill.path}`);
60458
61384
  return lines;
60459
61385
  }
60460
- function truncate$1(value, max) {
61386
+ function truncate$2(value, max) {
60461
61387
  return value.length > max ? value.slice(0, max) : value;
60462
61388
  }
60463
61389
  function escapeAttr$1(value) {
@@ -73260,509 +74186,6 @@ function canSplitAfter(messages, index) {
73260
74186
  if (messages[index + 1]?.role === "tool") return false;
73261
74187
  return true;
73262
74188
  }
73263
- //#endregion
73264
- //#region ../../packages/memory/src/models.ts
73265
- function generateId() {
73266
- return `memo-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
73267
- }
73268
- function createMemoryMemo(partial) {
73269
- return {
73270
- id: partial.id ?? generateId(),
73271
- sourceSessionId: partial.sourceSessionId,
73272
- sourceSessionTitle: partial.sourceSessionTitle,
73273
- userNeed: partial.userNeed,
73274
- approach: partial.approach,
73275
- outcome: partial.outcome,
73276
- whatFailed: partial.whatFailed,
73277
- whatWorked: partial.whatWorked,
73278
- extractionSource: partial.extractionSource,
73279
- recordedAt: partial.recordedAt ?? Date.now()
73280
- };
73281
- }
73282
- function toSummary(memo) {
73283
- return {
73284
- id: memo.id,
73285
- sourceSessionTitle: memo.sourceSessionTitle,
73286
- sourceSessionId: memo.sourceSessionId,
73287
- userNeed: memo.userNeed,
73288
- approach: memo.approach,
73289
- outcome: memo.outcome,
73290
- whatFailed: memo.whatFailed,
73291
- whatWorked: memo.whatWorked,
73292
- extractionSource: memo.extractionSource,
73293
- recordedAt: memo.recordedAt
73294
- };
73295
- }
73296
- //#endregion
73297
- //#region ../../packages/memory/src/store.ts
73298
- const FILE_NAME = "entries.jsonl";
73299
- const TMP_SUFFIX = ".tmp";
73300
- var MemoryMemoStore = class {
73301
- filePath;
73302
- constructor(projectDir) {
73303
- this.filePath = join$1(projectDir, "memory", FILE_NAME);
73304
- }
73305
- /** Iterate all memo records from the JSONL file. */
73306
- async *read() {
73307
- let stream;
73308
- try {
73309
- stream = createReadStream(this.filePath, { encoding: "utf8" });
73310
- } catch {
73311
- return;
73312
- }
73313
- let line = "";
73314
- let lineNumber = 0;
73315
- try {
73316
- for await (const chunk of stream) {
73317
- line += chunk;
73318
- let newlineIndex = line.indexOf("\n");
73319
- while (newlineIndex !== -1) {
73320
- const rawLine = line.slice(0, newlineIndex).replace(/\r$/, "");
73321
- line = line.slice(newlineIndex + 1);
73322
- lineNumber++;
73323
- const memo = this.parseLine(rawLine, lineNumber);
73324
- if (memo !== void 0) yield memo;
73325
- newlineIndex = line.indexOf("\n");
73326
- }
73327
- }
73328
- } catch (error) {
73329
- if (error.code === "ENOENT") return;
73330
- throw error;
73331
- }
73332
- }
73333
- /** Append a memo. */
73334
- async append(entry) {
73335
- const record = {
73336
- type: "memory_memo",
73337
- version: 2,
73338
- entry
73339
- };
73340
- await this.ensureDir();
73341
- const fh = await open(this.filePath, "a");
73342
- try {
73343
- await fh.writeFile(JSON.stringify(record) + "\n", "utf8");
73344
- await fh.sync();
73345
- } finally {
73346
- await fh.close();
73347
- }
73348
- }
73349
- /** Delete a memo by id (rewrites the file without it). */
73350
- async delete(id) {
73351
- const entries = [];
73352
- for await (const memo of this.read()) if (memo.id !== id) entries.push(memo);
73353
- if (entries.length === 0) {
73354
- try {
73355
- await unlink(this.filePath);
73356
- } catch (error) {
73357
- if (error.code !== "ENOENT") throw error;
73358
- }
73359
- return true;
73360
- }
73361
- try {
73362
- const tmpPath = this.filePath + TMP_SUFFIX;
73363
- const fh = await open(tmpPath, "w");
73364
- try {
73365
- for (const entry of entries) {
73366
- const record = {
73367
- type: "memory_memo",
73368
- version: 2,
73369
- entry
73370
- };
73371
- await fh.writeFile(JSON.stringify(record) + "\n", "utf8");
73372
- }
73373
- await fh.sync();
73374
- } finally {
73375
- await fh.close();
73376
- }
73377
- await rename(tmpPath, this.filePath);
73378
- return true;
73379
- } catch {
73380
- return false;
73381
- }
73382
- }
73383
- /** Get a single memo by ID. */
73384
- async get(id) {
73385
- for await (const memo of this.read()) if (memo.id === id) return memo;
73386
- }
73387
- /** List memos with optional search and limit. */
73388
- async list(options) {
73389
- const search = options?.search?.toLowerCase();
73390
- const limit = options?.limit ?? 50;
73391
- const offset = options?.offset ?? 0;
73392
- const all = [];
73393
- for await (const memo of this.read()) all.push(memo);
73394
- all.sort((a, b) => b.recordedAt - a.recordedAt);
73395
- let filtered = all;
73396
- if (search) filtered = all.filter((m) => m.userNeed.toLowerCase().includes(search) || m.approach.toLowerCase().includes(search) || m.whatFailed.toLowerCase().includes(search) || m.whatWorked.toLowerCase().includes(search) || (m.sourceSessionTitle ?? "").toLowerCase().includes(search));
73397
- const total = filtered.length;
73398
- return {
73399
- memos: filtered.slice(offset, offset + limit).map(toSummary),
73400
- total
73401
- };
73402
- }
73403
- async ensureDir() {
73404
- await mkdir(dirname$2(this.filePath), { recursive: true });
73405
- }
73406
- parseLine(rawLine, _lineNumber) {
73407
- if (rawLine.length === 0) return void 0;
73408
- try {
73409
- const record = JSON.parse(rawLine);
73410
- if (record["type"] !== "memory_memo" || !record["entry"]) return void 0;
73411
- const entry = record["entry"];
73412
- if (record["version"] === 1 || entry["userRequirement"] !== void 0 && entry["userNeed"] === void 0) {
73413
- const str = (v, fallback = "") => typeof v === "string" ? v : fallback;
73414
- return {
73415
- id: str(entry["id"]),
73416
- sourceSessionId: str(entry["sourceSessionId"]),
73417
- sourceSessionTitle: str(entry["sourceSessionTitle"], void 0),
73418
- userNeed: str(entry["userRequirement"]),
73419
- approach: str(entry["solution"]),
73420
- outcome: str(entry["completionStatus"]),
73421
- whatFailed: str(entry["problemsEncountered"], "none"),
73422
- whatWorked: "none",
73423
- extractionSource: entry["extractionSource"] === "exit" ? "exit" : "compaction",
73424
- recordedAt: typeof entry["recordedAt"] === "number" ? entry["recordedAt"] : 0
73425
- };
73426
- }
73427
- return entry;
73428
- } catch {
73429
- return;
73430
- }
73431
- }
73432
- };
73433
- //#endregion
73434
- //#region ../../packages/memory/src/extractor.ts
73435
- /**
73436
- * Parse memory-memo blocks from LLM compaction output.
73437
- */
73438
- function parseMemoryMemos(text) {
73439
- const memos = [];
73440
- const regex = /```memory-memo[\s\S]*?\n([\s\S]*?)```/g;
73441
- let match;
73442
- while ((match = regex.exec(text)) !== null) {
73443
- const jsonStr = match[1]?.trim();
73444
- if (!jsonStr) continue;
73445
- try {
73446
- const parsed = JSON.parse(jsonStr);
73447
- if (parsed["none"] === true) continue;
73448
- const userNeed = typeof parsed["userNeed"] === "string" ? parsed["userNeed"].trim() : "";
73449
- if (userNeed.length === 0) {
73450
- console.warn("[memory] Skipping memory-memo with empty userNeed:", jsonStr.slice(0, 200));
73451
- continue;
73452
- }
73453
- memos.push(createMemoryMemo({
73454
- userNeed,
73455
- approach: typeof parsed["approach"] === "string" ? parsed["approach"].trim() : "",
73456
- outcome: typeof parsed["outcome"] === "string" ? parsed["outcome"].trim() : "",
73457
- whatFailed: typeof parsed["whatFailed"] === "string" ? parsed["whatFailed"].trim() : "none",
73458
- whatWorked: typeof parsed["whatWorked"] === "string" ? parsed["whatWorked"].trim() : "none",
73459
- extractionSource: "compaction",
73460
- sourceSessionId: "",
73461
- sourceSessionTitle: ""
73462
- }));
73463
- } catch (err) {
73464
- console.warn("[memory] Malformed JSON in memory-memo block:", jsonStr.slice(0, 200), err);
73465
- }
73466
- }
73467
- return memos;
73468
- }
73469
- /** System prompt for exit-time extraction — instructs the LLM how to extract. */
73470
- const EXIT_EXTRACTION_SYSTEM_PROMPT = "你是一个任务经验提取助手。任务是从对话记录中识别已完成的任务闭环,提炼出任务经验记录。用对话的主要语言输出(中文对话用中文,英文对话用英文)。只输出指定的 JSON 格式,不要调用任何工具。";
73471
- /** Build the user prompt for exit-time extraction, including a conversation sample. */
73472
- function buildExitExtractionPrompt(sessionId, messageCount, sampleText) {
73473
- return `以下是会话 "${sessionId}"(共 ${messageCount} 条消息)的对话记录。请提取其中所有**已完成的任务闭环**:
73474
-
73475
- 判断标准:
73476
- - 用户提出了明确的需求或问题
73477
- - 给出了解决方案或回答
73478
- - 结果明确(成功、部分完成、失败)
73479
-
73480
- 对每个已完成的任务闭环,输出一个结构化经验记录。**必须用对话的主要语言书写**:
73481
-
73482
- \`\`\`memory-memo
73483
- {
73484
- "userNeed": "<用户需求/目标,一句话概括>",
73485
- "approach": "<执行方案,做了什么,2-4 句话>",
73486
- "outcome": "<最终结果,如'完成'、'部分完成'、'失败:原因'>",
73487
- "whatFailed": "<踩坑记录:试了但不行的路,无则填 'none'>",
73488
- "whatWorked": "<成功经验:最终奏效的关键动作,无则填 'none'>"
73489
- }
73490
- \`\`\`
73491
-
73492
- 注意:
73493
- - whatFailed 记录重要的错误尝试,帮助未来避免重蹈覆辙
73494
- - whatWorked 记录最终成功的关键动作,帮助未来复用经验
73495
- - 跳过未完成的工作,除非其中包含有价值的踩坑经验
73496
- - 将紧密相关的子任务合并为一条记录
73497
- - 严格遵守字段名和 JSON 格式,不要添加额外字段
73498
-
73499
- 如果没有已完成的任务闭环,输出:
73500
- \`\`\`memory-memo
73501
- {"none": true}
73502
- \`\`\`
73503
-
73504
- --- 对话记录(最近 30 条消息)---
73505
-
73506
- ${sampleText}
73507
-
73508
- --- 对话记录结束 ---`;
73509
- }
73510
- //#endregion
73511
- //#region ../../packages/memory/src/paths.ts
73512
- const WORKDIR_KEY_PREFIX$1 = "wd_";
73513
- const HASH_LENGTH$1 = 12;
73514
- function slugifyWorkDirName$1(name) {
73515
- return name.toLowerCase().replaceAll(/[^a-z0-9]/g, "-").replaceAll(/-+/g, "-").replaceAll(/^-|-$/g, "").slice(0, 60);
73516
- }
73517
- function encodeWorkDirKey$1(workDir) {
73518
- const normalized = resolve$1(workDir);
73519
- return `${WORKDIR_KEY_PREFIX$1}${slugifyWorkDirName$1(basename$1(normalized))}_${createHash("sha256").update(normalized).digest("hex").slice(0, HASH_LENGTH$1)}`;
73520
- }
73521
- /**
73522
- * Resolve the project-level directory for memory storage.
73523
- *
73524
- * Matches the session-store layout:
73525
- * <dataDir>/sessions/<workDirKey>/memory/
73526
- */
73527
- function resolveProjectDir(dataDir, workDir) {
73528
- return join$1(dataDir, "sessions", encodeWorkDirKey$1(workDir));
73529
- }
73530
- //#endregion
73531
- //#region ../../packages/memory/src/scoring.ts
73532
- /**
73533
- * Multi-factor relevance score for a memory memo against a query.
73534
- * Pure deterministic scoring — no LLM call, no network.
73535
- */
73536
- function computeRelevanceScore(memo, query, usageCount = 0) {
73537
- const factors = {
73538
- keywordOverlap: computeKeywordSimilarity(memo, query),
73539
- recency: computeRecency(memo.recordedAt),
73540
- usageBoost: Math.min(.3, usageCount * .1)
73541
- };
73542
- return factors.keywordOverlap * .5 + factors.recency * .25 + factors.usageBoost * .25;
73543
- }
73544
- /**
73545
- * Score multiple memos against a query, returning sorted results.
73546
- */
73547
- function rankMemos(memos, query, minScore = .3, maxResults = 3) {
73548
- return memos.map((memo) => ({
73549
- memo,
73550
- score: computeRelevanceScore(memo, query)
73551
- })).filter((s) => s.score >= minScore).sort((a, b) => b.score - a.score).slice(0, maxResults);
73552
- }
73553
- const STOP_WORDS = new Set([
73554
- "的",
73555
- "了",
73556
- "在",
73557
- "是",
73558
- "我",
73559
- "有",
73560
- "和",
73561
- "就",
73562
- "不",
73563
- "人",
73564
- "都",
73565
- "一",
73566
- "一个",
73567
- "上",
73568
- "也",
73569
- "很",
73570
- "到",
73571
- "说",
73572
- "要",
73573
- "去",
73574
- "你",
73575
- "会",
73576
- "着",
73577
- "没有",
73578
- "看",
73579
- "好",
73580
- "自己",
73581
- "这",
73582
- "他",
73583
- "她",
73584
- "它",
73585
- "们",
73586
- "那",
73587
- "些",
73588
- "the",
73589
- "a",
73590
- "an",
73591
- "is",
73592
- "are",
73593
- "was",
73594
- "were",
73595
- "be",
73596
- "been",
73597
- "being",
73598
- "have",
73599
- "has",
73600
- "had",
73601
- "do",
73602
- "does",
73603
- "did",
73604
- "will",
73605
- "would",
73606
- "could",
73607
- "should",
73608
- "may",
73609
- "might",
73610
- "can",
73611
- "shall",
73612
- "to",
73613
- "of",
73614
- "in",
73615
- "for",
73616
- "on",
73617
- "with",
73618
- "at",
73619
- "by",
73620
- "from",
73621
- "as",
73622
- "into",
73623
- "through",
73624
- "during",
73625
- "before",
73626
- "after",
73627
- "above",
73628
- "below",
73629
- "between",
73630
- "and",
73631
- "but",
73632
- "or",
73633
- "nor",
73634
- "not",
73635
- "so",
73636
- "yet",
73637
- "both",
73638
- "either",
73639
- "neither",
73640
- "each",
73641
- "every",
73642
- "all",
73643
- "any",
73644
- "few",
73645
- "more",
73646
- "most",
73647
- "other",
73648
- "some",
73649
- "such",
73650
- "no",
73651
- "only",
73652
- "own",
73653
- "same",
73654
- "than",
73655
- "too",
73656
- "very",
73657
- "just",
73658
- "because",
73659
- "about",
73660
- "what",
73661
- "which",
73662
- "who",
73663
- "whom",
73664
- "this",
73665
- "that",
73666
- "these",
73667
- "those",
73668
- "it",
73669
- "its",
73670
- "he",
73671
- "she",
73672
- "they",
73673
- "we",
73674
- "you",
73675
- "how"
73676
- ]);
73677
- function extractKeywords(text) {
73678
- const lower = text.toLowerCase();
73679
- const tokens = [];
73680
- const parts = lower.split(/[^a-z0-9一-鿿㐀-䶿]+/);
73681
- for (const part of parts) {
73682
- if (part.length === 0) continue;
73683
- if (/[一-鿿㐀-䶿]/.test(part)) {
73684
- for (const ch of part) if (ch.length >= 1 && !STOP_WORDS.has(ch)) tokens.push(ch);
73685
- }
73686
- if (/[a-z0-9]/.test(part) && part.length >= 2 && !STOP_WORDS.has(part)) tokens.push(part);
73687
- }
73688
- return [...new Set(tokens)];
73689
- }
73690
- function computeKeywordSimilarity(memo, query) {
73691
- const memoWords = extractKeywords(`${memo.userNeed} ${memo.approach} ${memo.whatFailed} ${memo.whatWorked}`);
73692
- const queryWords = extractKeywords(query);
73693
- if (memoWords.length === 0 || queryWords.length === 0) return 0;
73694
- const intersection = memoWords.filter((w) => queryWords.includes(w)).length;
73695
- const union = new Set([...memoWords, ...queryWords]).size;
73696
- return union === 0 ? 0 : intersection / union;
73697
- }
73698
- function computeRecency(recordedAt) {
73699
- const daysSince = (Date.now() - recordedAt) / (1e3 * 60 * 60 * 24);
73700
- return Math.max(0, 1 - daysSince / 90);
73701
- }
73702
- //#endregion
73703
- //#region ../../packages/memory/src/dream.ts
73704
- const LOCK_FILE = "dream-lock.json";
73705
- const MIN_HOURS_BETWEEN_DREAMS = 24;
73706
- const MIN_SESSIONS_BETWEEN_DREAMS = 5;
73707
- /**
73708
- * Tracks dream consolidation state and decides when to suggest running
73709
- * another dream. Persisted to `<project>/.scream-code/dream-lock.json`.
73710
- */
73711
- var DreamTracker = class {
73712
- state;
73713
- lockPath;
73714
- initialized = false;
73715
- constructor(projectDir) {
73716
- this.lockPath = join$1(projectDir, ".scream-code", LOCK_FILE);
73717
- this.state = {
73718
- lastDreamAt: (/* @__PURE__ */ new Date()).toISOString(),
73719
- sessionsSinceLastDream: 0
73720
- };
73721
- }
73722
- /** Load persisted state (call once at startup). */
73723
- async init() {
73724
- if (this.initialized) return;
73725
- this.initialized = true;
73726
- try {
73727
- const raw = await readFile(this.lockPath, "utf8");
73728
- const parsed = JSON.parse(raw);
73729
- if (parsed.version === 1 && parsed.state) this.state = parsed.state;
73730
- } catch {}
73731
- }
73732
- /** Record that a dream completed successfully. */
73733
- async recordDream() {
73734
- this.state = {
73735
- lastDreamAt: (/* @__PURE__ */ new Date()).toISOString(),
73736
- sessionsSinceLastDream: 0
73737
- };
73738
- await this.persist();
73739
- }
73740
- /** Call on each new session to bump the session counter. */
73741
- async recordNewSession() {
73742
- if (!this.initialized) await this.init();
73743
- this.state.sessionsSinceLastDream += 1;
73744
- await this.persist();
73745
- }
73746
- /** Check whether it's time to suggest another dream. */
73747
- shouldSuggest() {
73748
- return (Date.now() - new Date(this.state.lastDreamAt).getTime()) / (1e3 * 60 * 60) >= MIN_HOURS_BETWEEN_DREAMS && this.state.sessionsSinceLastDream >= MIN_SESSIONS_BETWEEN_DREAMS;
73749
- }
73750
- /** Get a human-readable suggestion message when conditions are met. */
73751
- getSuggestionMessage() {
73752
- const hoursSince = (Date.now() - new Date(this.state.lastDreamAt).getTime()) / (1e3 * 60 * 60);
73753
- return `距离上次记忆整理已过去 ${Math.floor(hoursSince / 24)} 天、${this.state.sessionsSinceLastDream} 个会话。建议运行 /dream 来合并重复记忆、清理过期条目、解决矛盾信息。`;
73754
- }
73755
- async persist() {
73756
- const data = {
73757
- version: 1,
73758
- state: this.state
73759
- };
73760
- try {
73761
- await mkdir(dirname$2(this.lockPath), { recursive: true });
73762
- await writeFile(this.lockPath, JSON.stringify(data, null, 2), "utf8");
73763
- } catch {}
73764
- }
73765
- };
73766
74189
  /** Max consecutive compaction failures before auto-compaction is
73767
74190
  * disabled for the remainder of the turn. Resets each turn. */
73768
74191
  const MAX_CONSECUTIVE_FAILURES = 3;
@@ -74102,7 +74525,7 @@ function compactionTelemetryTrigger(trigger, instruction) {
74102
74525
  }
74103
74526
  //#endregion
74104
74527
  //#region ../../packages/agent-core/src/agent/compaction/micro.ts
74105
- const DEFAULT_CONFIG$1 = {
74528
+ const DEFAULT_CONFIG = {
74106
74529
  keepRecentMessages: 20,
74107
74530
  minContentTokens: 100,
74108
74531
  minContextUsageRatio: .5,
@@ -74124,7 +74547,7 @@ var MicroCompaction = class {
74124
74547
  constructor(agent, config) {
74125
74548
  this.agent = agent;
74126
74549
  this.config = {
74127
- ...DEFAULT_CONFIG$1,
74550
+ ...DEFAULT_CONFIG,
74128
74551
  ...config
74129
74552
  };
74130
74553
  }
@@ -76063,64 +76486,6 @@ function formatElapsed$1(ms) {
76063
76486
  return `${Math.floor(totalSeconds / 60)}m${(totalSeconds % 60).toString().padStart(2, "0")}s`;
76064
76487
  }
76065
76488
  //#endregion
76066
- //#region ../../packages/agent-core/src/agent/injection/memory-recall.ts
76067
- const DEFAULT_CONFIG = {
76068
- maxMemos: 3,
76069
- maxChars: 2e3,
76070
- minScore: .3
76071
- };
76072
- /**
76073
- * Injects relevant memory memos at the start of each turn.
76074
- *
76075
- * Uses the pure-keyword {@link rankMemos} scorer (no LLM cost) to find
76076
- * memos relevant to the current user query and injects them as a
76077
- * {@code <system-reminder>} block so the model can reference past work.
76078
- */
76079
- var MemoryRecallInjector = class extends DynamicInjector {
76080
- injectionVariant = "memory_recall";
76081
- injectedForTurn = false;
76082
- constructor(agent) {
76083
- super(agent);
76084
- }
76085
- /** Reset per-turn state so the injector fires again next turn. */
76086
- resetForTurn() {
76087
- this.injectedForTurn = false;
76088
- }
76089
- async getInjection() {
76090
- if (this.injectedForTurn) return void 0;
76091
- this.injectedForTurn = true;
76092
- const store = this.agent.memoStore;
76093
- if (!store) return void 0;
76094
- const query = this.getLastUserQuery();
76095
- if (query.length === 0) return void 0;
76096
- const all = await store.list({ limit: 100 });
76097
- if (all.memos.length === 0) return void 0;
76098
- const ranked = rankMemos(all.memos, query, DEFAULT_CONFIG.minScore, DEFAULT_CONFIG.maxMemos);
76099
- if (ranked.length === 0) return void 0;
76100
- return this.formatInjection(ranked);
76101
- }
76102
- /** Walk the context history backwards to find the last user message. */
76103
- getLastUserQuery() {
76104
- const history = this.agent.context.history;
76105
- for (let i = history.length - 1; i >= 0; i--) {
76106
- const msg = history[i];
76107
- if (!msg || msg.role !== "user") continue;
76108
- const text = msg.content.filter((p) => p.type === "text").map((p) => p.text).join(" ");
76109
- if (text.length > 0) return text.slice(0, 500);
76110
- }
76111
- return "";
76112
- }
76113
- formatInjection(ranked) {
76114
- const lines = ["以下是与当前任务相关的历史经验记录(来自之前的会话):", ""];
76115
- for (const [i, { memo, score }] of ranked.entries()) {
76116
- const level = score >= .6 ? "高" : score >= .4 ? "中" : "低";
76117
- lines.push(`**记录 ${i + 1}** (相关性: ${level})`, `- 需求: ${memo.userNeed}`, `- 方案: ${memo.approach}`, `- 结果: ${memo.outcome}`, memo.whatFailed !== "none" ? `- 踩坑: ${memo.whatFailed}` : "", memo.whatWorked !== "none" ? `- 经验: ${memo.whatWorked}` : "", "");
76118
- }
76119
- const joined = lines.join("\n");
76120
- return joined.length > DEFAULT_CONFIG.maxChars ? joined.slice(0, DEFAULT_CONFIG.maxChars - 3) + "..." : joined;
76121
- }
76122
- };
76123
- //#endregion
76124
76489
  //#region ../../packages/agent-core/src/agent/injection/permission-mode.ts
76125
76490
  const AUTO_MODE_ENTER_REMINDER = [
76126
76491
  "Auto permission mode is active. Tool approvals will be handled automatically while this mode remains enabled.",
@@ -76450,28 +76815,22 @@ var WolfPackModeInjector = class extends DynamicInjector {
76450
76815
  var InjectionManager = class {
76451
76816
  agent;
76452
76817
  injectors;
76453
- memoryRecall;
76454
76818
  constructor(agent) {
76455
76819
  this.agent = agent;
76456
- const autoRecallEnabled = agent.memoStore !== void 0;
76457
- this.memoryRecall = autoRecallEnabled ? new MemoryRecallInjector(agent) : null;
76458
76820
  this.injectors = [
76459
76821
  new PluginSessionStartInjector(agent),
76460
76822
  new WolfPackModeInjector(agent),
76461
76823
  new PlanModeInjector(agent),
76462
76824
  new PermissionModeInjector(agent),
76463
76825
  new TodoListReminderInjector(agent),
76464
- new GoalInjector(agent),
76465
- ...this.memoryRecall ? [this.memoryRecall] : []
76826
+ new GoalInjector(agent)
76466
76827
  ];
76467
76828
  }
76468
76829
  async inject() {
76469
76830
  for (const injector of this.injectors) await injector.inject();
76470
76831
  }
76471
- /** Reset per-turn state on all injectors (e.g. memory recall flag). */
76472
- resetForTurn() {
76473
- this.memoryRecall?.resetForTurn();
76474
- }
76832
+ /** Reset per-turn state on all injectors. */
76833
+ resetForTurn() {}
76475
76834
  onContextClear() {
76476
76835
  for (const injector of this.injectors) injector.onContextClear();
76477
76836
  }
@@ -90398,6 +90757,88 @@ function errorResult(serverName, error, authorizationUrl) {
90398
90757
  };
90399
90758
  }
90400
90759
  //#endregion
90760
+ //#region ../../packages/agent-core/src/version.ts
90761
+ function getCoreVersion() {
90762
+ try {
90763
+ const raw = readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf-8");
90764
+ const pkg = JSON.parse(raw);
90765
+ return typeof pkg.version === "string" ? pkg.version : "0.0.0";
90766
+ } catch {
90767
+ return "0.0.0";
90768
+ }
90769
+ }
90770
+ const SCREAM_MCP_CLIENT_VERSION = getCoreVersion();
90771
+ /**
90772
+ * Build the `RequestOptions` object accepted by the MCP SDK's `callTool`,
90773
+ * including either the configured tool-call timeout, an in-flight abort
90774
+ * signal, both, or neither. Returns `undefined` when nothing needs to be
90775
+ * passed so the SDK falls back to its defaults.
90776
+ */
90777
+ function buildRequestOptions(toolCallTimeoutMs, signal) {
90778
+ if (toolCallTimeoutMs === void 0 && signal === void 0) return void 0;
90779
+ return {
90780
+ timeout: toolCallTimeoutMs,
90781
+ signal
90782
+ };
90783
+ }
90784
+ function toMcpToolDefinition(tool) {
90785
+ return {
90786
+ name: tool.name,
90787
+ description: tool.description ?? "",
90788
+ inputSchema: tool.inputSchema
90789
+ };
90790
+ }
90791
+ /**
90792
+ * Normalise the SDK's `callTool` return into ltod's {@link MCPToolResult}.
90793
+ * The SDK can return either the modern `{ content, isError }` shape or a
90794
+ * legacy `{ toolResult }` shape; we collapse the legacy shape to a single
90795
+ * text content block.
90796
+ */
90797
+ function toMcpToolResult(result) {
90798
+ if (typeof result === "object" && result !== null && "content" in result) {
90799
+ const typed = result;
90800
+ if (Array.isArray(typed.content)) return {
90801
+ content: typed.content,
90802
+ isError: typed.isError === true
90803
+ };
90804
+ }
90805
+ if (typeof result === "object" && result !== null && "toolResult" in result) {
90806
+ const legacy = result.toolResult;
90807
+ return {
90808
+ content: [{
90809
+ type: "text",
90810
+ text: typeof legacy === "string" ? legacy : JSON.stringify(legacy)
90811
+ }],
90812
+ isError: false
90813
+ };
90814
+ }
90815
+ return {
90816
+ content: [],
90817
+ isError: false
90818
+ };
90819
+ }
90820
+ /**
90821
+ * Returns true when an MCP tool-call failure looks like a dead transport rather
90822
+ * than an application-level error. These are the cases where tearing down and
90823
+ * reconnecting the server is likely to help.
90824
+ */
90825
+ function isRetriableMcpCallError(error) {
90826
+ if (!(error instanceof Error)) return false;
90827
+ const message = error.message.toLowerCase();
90828
+ return [
90829
+ /\be?connrefused\b/,
90830
+ /\be?connreset\b/,
90831
+ /\bepipe\b/,
90832
+ /\be?netunreach\b/,
90833
+ /\be?hostunreach\b/,
90834
+ /fetch failed/,
90835
+ /transport not connected/,
90836
+ /transport closed/,
90837
+ /network error/,
90838
+ /maximum reconnection attempts/
90839
+ ].some((pattern) => pattern.test(message));
90840
+ }
90841
+ //#endregion
90401
90842
  //#region ../../packages/agent-core/src/mcp/output.ts
90402
90843
  const MCP_MAX_OUTPUT_CHARS = 1e5;
90403
90844
  const MCP_OUTPUT_TRUNCATED_TEXT = `\n\n[Output truncated: exceeded ${String(MCP_MAX_OUTPUT_CHARS)} character limit. Use pagination or more specific queries to get remaining content.]`;
@@ -90894,13 +91335,13 @@ function normalizeSourcePath(path) {
90894
91335
  }
90895
91336
  //#endregion
90896
91337
  //#region ../../packages/agent-core/src/profile/default/agent.yaml
90897
- var agent_default = "name: agent\ndescription: Default Scream Code agent\n\nsystemPromptPath: ./system.md\npromptVars:\n roleAdditional: ''\n\ntools:\n - Read\n - Write\n - Edit\n - Grep\n - Glob\n - Bash\n - TaskList\n - TaskOutput\n - TaskStop\n - CronCreate\n - CronList\n - CronDelete\n - CreateGoal\n - GetGoal\n - SetGoalBudget\n - UpdateGoal\n - ReadMediaFile\n - TodoList\n - Skill\n - WebSearch\n - Agent\n - WolfPack\n\n - FetchURL\n - AskUserQuestion\n - EnterPlanMode\n - ExitPlanMode\n - mcp__*\n\nsubagents:\n coder:\n description: Good at general software engineering tasks.\n explore:\n description: Fast codebase exploration with prompt-enforced read-only behavior.\n plan:\n description: Read-only implementation planning and architecture design.\n verify:\n description: Verification specialist. Runs build, test, and lint commands to validate code changes.\n writer:\n description: Content production and research specialist. Produces structured, data-driven reports, analyses, and Markdown documents.\n";
91338
+ var agent_default = "name: agent\ndescription: Default Scream Code agent\n\nsystemPromptPath: ./system.md\npromptVars:\n roleAdditional: ''\n\ntools:\n - Read\n - Write\n - Edit\n - Grep\n - Glob\n - Bash\n - TaskList\n - TaskOutput\n - TaskStop\n - CronCreate\n - CronList\n - CronDelete\n - CreateGoal\n - GetGoal\n - SetGoalBudget\n - UpdateGoal\n - ReadMediaFile\n - TodoList\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n - Skill\n - WebSearch\n - Agent\n - WolfPack\n\n - FetchURL\n - AskUserQuestion\n - EnterPlanMode\n - ExitPlanMode\n - mcp__*\n\nsubagents:\n coder:\n description: Good at general software engineering tasks.\n explore:\n description: Fast codebase exploration with prompt-enforced read-only behavior.\n plan:\n description: Read-only implementation planning and architecture design.\n verify:\n description: Verification specialist. Runs build, test, and lint commands to validate code changes.\n writer:\n description: Content production and research specialist. Produces structured, data-driven reports, analyses, and Markdown documents.\n";
90898
91339
  //#endregion
90899
91340
  //#region ../../packages/agent-core/src/profile/default/coder.yaml
90900
- var coder_default = "extends: agent\nname: coder\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\nwhenToUse: |\n Use this agent for non-trivial software engineering work that may require reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - Write\n - Edit\n - WebSearch\n - FetchURL\n - mcp__*\n";
91341
+ var coder_default = "extends: agent\nname: coder\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\nwhenToUse: |\n Use this agent for non-trivial software engineering work that may require reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - Write\n - Edit\n - WebSearch\n - FetchURL\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n - mcp__*\n";
90901
91342
  //#endregion
90902
91343
  //#region ../../packages/agent-core/src/profile/default/explore.yaml
90903
- var explore_default = "extends: agent\nname: explore\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n You are a codebase exploration specialist. Your role is EXCLUSIVELY to search, read, and analyze existing code and resources. You do NOT have access to file editing tools.\n\n Your strengths:\n - Rapidly finding files using glob patterns\n - Searching code and text with powerful regex patterns\n - Reading and analyzing file contents\n - Running read-only shell commands (git log, git diff, ls, find, etc.)\n\n Guidelines:\n - Use Glob for broad file pattern matching. Patterns MUST contain a literal anchor (extension or subdirectory); pure wildcards like `*` or `**/*` are rejected by the tool.\n - Use Grep for searching file contents with regex\n - Use Read when you know the specific file path\n - Use Bash ONLY for read-only operations (ls, git status, git log, git diff, find)\n - NEVER use Bash for any file creation or modification commands\n - Adapt your search depth based on the thoroughness level specified by the caller\n - Wherever possible, spawn multiple parallel tool calls for grepping and reading files to maximize speed\n\n If the prompt includes a <git-context> block, use it to orient yourself about the repository state before starting your investigation.\n\n You are meant to be a fast agent. Complete the search request efficiently and report your findings clearly in a structured format.\nwhenToUse: |\n Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (e.g. \"src/**/*.yaml\"), search code for keywords (e.g. \"database connection\"), or answer questions about the codebase (e.g. \"how does the auth module work?\"). When calling this agent, specify the desired thoroughness level: \"quick\" for basic searches, \"medium\" for moderate exploration, or \"thorough\" for comprehensive analysis across multiple locations and naming conventions. Use this agent for any read-only exploration that will clearly require more than 3 search queries. Prefer launching multiple explore agents concurrently when investigating independent questions.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - WebSearch\n - FetchURL\n";
91344
+ var explore_default = "extends: agent\nname: explore\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n You are a codebase exploration specialist. Your role is EXCLUSIVELY to search, read, and analyze existing code and resources. You do NOT have access to file editing tools.\n\n Your strengths:\n - Rapidly finding files using glob patterns\n - Searching code and text with powerful regex patterns\n - Reading and analyzing file contents\n - Running read-only shell commands (git log, git diff, ls, find, etc.)\n\n Guidelines:\n - Use Glob for broad file pattern matching. Patterns MUST contain a literal anchor (extension or subdirectory); pure wildcards like `*` or `**/*` are rejected by the tool.\n - Use Grep for searching file contents with regex\n - Use Read when you know the specific file path\n - Use Bash ONLY for read-only operations (ls, git status, git log, git diff, find)\n - NEVER use Bash for any file creation or modification commands\n - Adapt your search depth based on the thoroughness level specified by the caller\n - Wherever possible, spawn multiple parallel tool calls for grepping and reading files to maximize speed\n\n If the prompt includes a <git-context> block, use it to orient yourself about the repository state before starting your investigation.\n\n You are meant to be a fast agent. Complete the search request efficiently and report your findings clearly in a structured format.\nwhenToUse: |\n Fast agent specialized for exploring codebases. Use this when you need to quickly find files by patterns (e.g. \"src/**/*.yaml\"), search code for keywords (e.g. \"database connection\"), or answer questions about the codebase (e.g. \"how does the auth module work?\"). When calling this agent, specify the desired thoroughness level: \"quick\" for basic searches, \"medium\" for moderate exploration, or \"thorough\" for comprehensive analysis across multiple locations and naming conventions. Use this agent for any read-only exploration that will clearly require more than 3 search queries. Prefer launching multiple explore agents concurrently when investigating independent questions.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - WebSearch\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n - FetchURL\n";
90904
91345
  //#endregion
90905
91346
  //#region ../../packages/agent-core/src/profile/default/init.md
90906
91347
  var init_default = "You are a software engineering expert with many years of programming experience. Please explore the current project directory to understand the project's architecture and main details.\n\nTask requirements:\n1. Analyze the project structure and identify key configuration files (such as pyproject.toml, package.json, Cargo.toml, etc.).\n2. Understand the project's technology stack, build process and runtime architecture.\n3. Identify how the code is organized and main module divisions.\n4. Discover project-specific development conventions, testing strategies, and deployment processes.\n\nAfter the exploration, you should do a thorough summary of your findings and overwrite it into `AGENTS.md` file in the project root. You need to refer to what is already in the file when you do so.\n\nFor your information, `AGENTS.md` is a file intended to be read by AI coding agents. Expect the reader of this file know nothing about the project.\n\nYou should compose this file according to the actual project content. Do not make any assumptions or generalizations. Ensure the information is accurate and useful. You must use the natural language that is mainly used in the project's comments and documentation.\n\nPopular sections that people usually write in `AGENTS.md` are:\n\n- Project overview\n- Build and test commands\n- Code style guidelines\n- Testing instructions\n- Security considerations\n";
@@ -90910,10 +91351,10 @@ const PROFILE_SOURCES = {
90910
91351
  "profile/default/agent.yaml": agent_default,
90911
91352
  "profile/default/coder.yaml": coder_default,
90912
91353
  "profile/default/explore.yaml": explore_default,
90913
- "profile/default/plan.yaml": "extends: agent\nname: plan\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n Before designing your implementation plan, consider whether you fully understand the codebase areas relevant to the task. If not, recommend the parent agent to use the explore agent (subagent_type=\"explore\") to investigate key questions first. In your response, clearly state:\n 1. What you already know from the information provided\n 2. What questions remain unanswered that would benefit from explore agent investigation\n 3. Your implementation plan (either preliminary if questions remain, or final if sufficient context exists)\nwhenToUse: |\n Use this agent when the parent agent needs a step-by-step implementation plan, key file identification, and architectural trade-off analysis before code changes are made.\ntools:\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - WebSearch\n - FetchURL\n",
90914
- "profile/default/system.md": "You are Scream Code CLI, an interactive general AI agent running on a user's computer.\n\nYour primary goal is to help users with software engineering tasks by taking action — use the tools available to you to make real changes on the user's system. You should also answer questions when asked. Always adhere strictly to the following system instructions and the user's requirements.\n\n{{ ROLE_ADDITIONAL }}\n\n# Prompt and Tool Use\n\nThe user's messages may contain questions and/or task descriptions in natural language, code snippets, logs, file paths, or other forms of information. Read them, understand them and do what the user requested. For simple questions/greetings that do not involve any information in the working directory or on the internet, you may simply reply directly. For anything else, default to taking action with tools. When the request could be interpreted as either a question to answer or a task to complete, treat it as a task.\n\nWhen handling the user's request, if it involves creating, modifying, or running code or files, you MUST use the appropriate tools (e.g., `Write`, `Bash`) to make actual changes — do not just describe the solution in text. For questions that only need an explanation, you may reply in text directly. When calling tools, do not provide explanations because the tool calls themselves should be self-explanatory. You MUST follow the description of each tool and its parameters when calling tools.\n\nIf the `Agent` tool is available, you can use it to delegate a focused subtask to a subagent instance. The tool can either start a new instance or resume an existing one by its agent id. Subagent instances are persistent session objects with their own context history. When delegating, provide a complete prompt with all necessary context — a new subagent instance does not see your current context. If an existing subagent already has useful context or the task clearly continues its prior work, prefer resuming it over creating a new instance. Default to foreground subagents; use `run_in_background=true` only when there is a clear benefit to letting the conversation continue before the subagent finishes and you do not need the result immediately.\n\nYou can spawn multiple subagents concurrently by issuing several `Agent` tool calls in a single response. The system executes all tool calls in parallel automatically. Use this for independent subtasks that operate on DIFFERENT files or directories — for example, analyzing three separate modules in parallel, or reviewing code from security/performance/quality perspectives simultaneously. Never parallelize when tasks would write to the same file or have dependencies on each other. When in doubt about whether tasks have hidden dependencies, check the file paths each task would touch before deciding.\n\nYou have the capability to output any number of tool calls in a single response. If you anticipate making multiple non-interfering tool calls, you are HIGHLY RECOMMENDED to make them in parallel to significantly improve efficiency. This is very important to your performance.\n\nThe results of the tool calls will be returned to you in a tool message. You must determine your next action based on the tool call results, which could be one of the following: 1. Continue working on the task, 2. Inform the user that the task is completed or has failed, or 3. Ask the user for more information.\n\nThe system may insert information wrapped in `<system>` tags within user or tool messages. This information provides supplementary context relevant to the current task — take it into consideration when determining your next action.\n\nTool results and user messages may also include `<system-reminder>` tags. Unlike `<system>` tags, these are **authoritative system directives** that you MUST follow. They bear no direct relation to the specific tool results or user messages in which they appear. Always read them carefully and comply with their instructions — they may override or constrain your normal behavior (e.g., restricting you to read-only actions during plan mode).\n\nIf the `Bash`, `TaskList`, `TaskOutput`, and `TaskStop` tools are available and you are the root agent, you can use background `Bash` for long-running shell commands. Launch it via `Bash` with `run_in_background=true` and a short `description`. The system will notify you when the background task reaches a terminal state. Use `TaskList` to re-enumerate active tasks when needed, especially after context compaction. Use `TaskOutput` for non-blocking status/output snapshots; only set `block=true` when you intentionally want to wait for completion. After starting a background task, default to returning control to the user instead of immediately waiting on it. Use `TaskStop` only when you need to cancel the task. For human users in the interactive shell, the only task-management slash command is `/tasks`. Do not tell users to run `/task`, `/tasks list`, `/tasks output`, `/tasks stop`, or any other invented slash subcommands. If you are a subagent or these tools are not available, do not assume you can create or control background tasks.\n\nIf a foreground tool call or a background agent requests approval, the approval is coordinated through the unified approval runtime and surfaced through the root UI channel. Do not assume approvals are local to a single subagent turn.\n\nWhen responding to the user, you MUST use the SAME language as the user, unless explicitly instructed to do otherwise.\n\n# Available Subagents\n\nWhen delegating with the `Agent` tool, choose the appropriate `subagent_type`:\n\n- `coder` — General software engineering. Use for reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.\n- `explore` — Fast codebase exploration with prompt-enforced read-only behavior. Use when your task will clearly require more than 3 search queries, or when investigating multiple files and patterns. Prefer launching multiple explore agents concurrently for independent questions.\n- `plan` — Read-only implementation planning and architecture design. Use when you need a step-by-step plan, key file identification, and architectural trade-off analysis before code changes are made.\n- `verify` — Verification specialist. Runs build, test, and lint commands. Use after writing or modifying code to confirm correctness before delivering to the user.\n- `writer` — Content production and research specialist. Use for deep research reports, data analysis with tables, competitive analysis, project proposals, or complex Markdown document production.\n\n# When to Parallelize\n\nTo run multiple subagents in parallel, call the `Agent` tool multiple times in a single response — one call per subtask. All calls execute concurrently.\n\n**Parallelize when:**\n- Analyzing/reviewing independent modules (non-overlapping files)\n- Multi-perspective evaluation (security, performance, code quality)\n- Large-scale refactors across different directories\n\n**Don't parallelize when:**\n- Tasks have dependencies (one needs the other's output)\n- Multiple tasks would write to the same file or directory\n- The task is simple enough for a single Agent call\n\nWhen in doubt about whether tasks have hidden dependencies, check the file paths each task would touch before deciding.\n\n# General Guidelines for Coding\n\nWhen building something from scratch, you should:\n\n- Understand the user's requirements.\n- Ask the user for clarification if there is anything unclear.\n- Design the architecture and make a plan for the implementation.\n- Write the code in a modular and maintainable way.\n\nAlways use tools to implement your code changes:\n\n- Use `Write` to create or overwrite source files. Code that only appears in your text response is NOT saved to the file system and will not take effect.\n- Use `Bash` to run and test your code after writing it.\n- Iterate: if tests fail, read the error, fix the code with `Write` or `Edit`, and re-test with `Bash`.\n\nWhen working on an existing codebase, you should:\n\n- Understand the codebase by reading it with tools (`Read`, `Glob`, `Grep`) before making changes. Identify the ultimate goal and the most important criteria to achieve the goal.\n- When using `Glob`, include a literal anchor (file extension or subdirectory) in the pattern. Pure wildcards like `*` or `**/*` are rejected by the tool.\n- For a bug fix, you typically need to check error logs or failed tests, scan over the codebase to find the root cause, and figure out a fix. If user mentioned any failed tests, you should make sure they pass after the changes.\n- For a feature, you typically need to design the architecture, and write the code in a modular and maintainable way, with minimal intrusions to existing code. Add new tests if the project already has tests.\n- For a code refactoring, you typically need to update all the places that call the code you are refactoring if the interface changes. DO NOT change any existing logic especially in tests, focus only on fixing any errors caused by the interface changes.\n- Make MINIMAL changes to achieve the goal. This is very important to your performance.\n- Follow the coding style of existing code in the project.\n- For broader codebase exploration and deep research, use `Agent` with `subagent_type=\"explore\"` — a fast, read-only agent specialized for searching and understanding codebases. Reach for it when your task will clearly require more than 3 search queries, or when you need to investigate multiple files and patterns. Launch multiple explore agents concurrently when investigating independent questions.\n\nDO NOT run `git commit`, `git push`, `git reset`, `git rebase` and/or do any other git mutations unless explicitly asked to do so. Ask for confirmation each time when you need to do git mutations, even if the user has confirmed in earlier conversations.\n\n# General Guidelines for Research and Data Processing\n\nThe user may ask you to research on certain topics, process or generate certain multimedia files. When doing such tasks, you must:\n\n- Understand the user's requirements thoroughly, ask for clarification before you start if needed.\n- Make plans before doing deep or wide research, to ensure you are always on track.\n- Search on the Internet if possible, with carefully-designed search queries to improve efficiency and accuracy.\n- Use proper tools or shell commands or Python packages to process or generate images, videos, PDFs, docs, spreadsheets, presentations, or other multimedia files. Detect if there are already such tools in the environment. If you have to install third-party tools/packages, you MUST ensure that they are installed in a virtual/isolated environment.\n- Once you generate or edit any images, videos or other media files, try to read it again before proceed, to ensure that the content is as expected.\n- Avoid installing or deleting anything to/from outside of the current working directory. If you have to do so, ask the user for confirmation.\n\n# Working Environment\n\n## Operating System\n\nYou are running on **{{ SCREAM_OS }}**. The Bash tool executes commands using **{{ SCREAM_SHELL }}**.\n{% if SCREAM_OS == \"Windows\" %}\n\nIMPORTANT: You are on Windows. The Bash tool runs through Git Bash, so use Unix shell syntax inside Bash commands — `/dev/null` not `NUL`, and forward slashes in paths. For file operations, always prefer the built-in tools (Read, Write, Edit, Glob, Grep) over Bash commands — they work reliably across all platforms.\n{% endif %}\n\nThe operating environment is not in a sandbox. Any actions you do will immediately affect the user's system. So you MUST be extremely cautious. Unless being explicitly instructed to do so, you should never access (read/write/execute) files outside of the working directory.\n\n## Date and Time\n\nThe current date and time in ISO format is `{{ SCREAM_NOW }}`. This is only a reference for you when searching the web, or checking file modification time, etc. If you need the exact time, use Bash tool with proper command.\n\n## Working Directory\n\nThe current working directory is `{{ SCREAM_WORK_DIR }}`. This should be considered as the project root if you are instructed to perform tasks on the project. Every file system operation will be relative to the working directory if you do not explicitly specify the absolute path. Tools may require absolute paths for some parameters, IF SO, YOU MUST use absolute paths for these parameters.\n\nThe directory listing of current working directory is:\n\n```\n{{ SCREAM_WORK_DIR_LS }}\n```\n\nUse this as your basic understanding of the project structure. The tree only shows the first two levels; entries marked \"... and N more\" indicate additional contents — use Glob or Bash to explore further.\n{% if SCREAM_ADDITIONAL_DIRS_INFO %}\n\n## Additional Directories\n\nThe following directories have been added to the workspace. You can read, write, search, and glob files in these directories as part of your workspace scope.\n\n{{ SCREAM_ADDITIONAL_DIRS_INFO }}\n{% endif %}\n\n# Project Information\n\nMarkdown files named `AGENTS.md` usually contain the background, structure, coding styles, user preferences and other relevant information about the project. You should use this information to understand the project and the user's preferences. `AGENTS.md` files may exist at different locations in the project, but typically there is one in the project root.\n\n> Why `AGENTS.md`?\n>\n> `README.md` files are for humans: quick starts, project descriptions, and contribution guidelines. `AGENTS.md` complements this by containing the extra, sometimes detailed context coding agents need: build steps, tests, and conventions that might clutter a README or aren’t relevant to human contributors.\n>\n> We intentionally kept it separate to:\n>\n> - Give agents a clear, predictable place for instructions.\n> - Keep `README`s concise and focused on human contributors.\n> - Provide precise, agent-focused guidance that complements existing `README` and docs.\n\nThe `AGENTS.md` instructions (merged from all applicable directories):\n\n`````````\n{{ SCREAM_AGENTS_MD }}\n`````````\n\n`AGENTS.md` files can appear at any level of the project directory tree, including inside `.scream-code/` directories. Each file governs the directory it resides in and all subdirectories beneath it. When multiple `AGENTS.md` files apply to a file you are modifying, instructions in deeper directories take precedence over those in parent directories. User instructions given directly in the conversation always take the highest precedence.\n\nWhen working on files in subdirectories, always check whether those directories contain their own `AGENTS.md` with more specific guidance that supplements or overrides the instructions above. You may also check `README`/`README.md` files for more information about the project.\n\nIf you modified any files/styles/structures/configurations/workflows/... mentioned in `AGENTS.md` files, you MUST update the corresponding `AGENTS.md` files to keep them up-to-date.\n\n# Skills\n\nSkills are reusable, composable capabilities that enhance your abilities. Each skill is either a self-contained directory with a `SKILL.md` file or a standalone `.md` file that contains instructions, examples, and/or reference material.\n\n## What are skills?\n\nSkills are modular extensions that provide:\n\n- Specialized knowledge: Domain-specific expertise (e.g., PDF processing, data analysis)\n- Workflow patterns: Best practices for common tasks\n- Tool integrations: Pre-configured tool chains for specific operations\n- Reference material: Documentation, templates, and examples\n\n## Available skills\n\nSkills are grouped by scope (`Project`, `User`, `Extra`, `Built-in`) so you can tell where each came from. When the user refers to \"the skill in this project\" or \"the user-scope skill\", use the scope heading to disambiguate. When multiple scopes define a skill with the same name, the more specific scope takes precedence: **Project overrides User overrides Extra overrides Built-in**.\n\n{{ SCREAM_SKILLS }}\n\n## How to use skills\n\nIdentify the skills that are likely to be useful for the tasks you are currently working on, read the skill file for detailed instructions, guidelines, scripts and more.\n\nOnly read skill details when needed to conserve the context window.\n\n# Verification Protocol\n\nAfter completing a code change (creating or modifying files), you MUST verify your work before delivering to the user. Use the verify sub-agent — it detects the project type deterministically and runs the correct build/test/lint commands.\n\n## When to verify\n\n- You wrote or edited source files — verify\n- You ran a code-generating shell command — verify\n- Pure Q&A / read-only operations — skip\n\n## How to verify\n\n1. Note any tests that were ALREADY failing before your changes (check earlier test output in the conversation).\n\n2. Call:\n `spawn_agent(type=\"verify\", prompt=\"Verify the current changes. <list pre-existing failures if any>\")`\n\n3. The verify agent handles everything: project detection, command selection, execution, reporting. You do NOT need to detect the project type yourself.\n\n4. On pass: deliver to user.\n5. On fail: fix the issues, re-verify. Maximum 2 rounds.\n6. Pre-existing failures: mark and report, but do NOT block delivery.\n\n# Ultimate Reminders\n\nAt any time, you should be HELPFUL, CONCISE, and ACCURATE. Be thorough in your actions — test what you build, verify what you change — not in your explanations.\n\n- Never diverge from the requirements and the goals of the task you work on. Stay on track.\n- Never give the user more than what they want.\n- Try your best to avoid any hallucination. Do fact checking before providing any factual information.\n- Think about the best approach, then take action decisively.\n- Do not give up too early.\n- ALWAYS, keep it stupidly simple. Do not overcomplicate things.\n- When the task requires creating or modifying files, always use tools to do so. Never treat displaying code in your response as a substitute for actually writing it to the file system.\n",
90915
- "profile/default/verify.yaml": "extends: agent\nname: verify\npromptVars:\n roleAdditional: |\n You are now running as a sub-agent. All `user` messages are sent by the main agent.\n You are the Verify sub-agent. Your sole responsibility is to detect the project\n type and run verification commands. Do NOT try to fix anything.\n\n # Phase 1: Detect project type (deterministic lookup — no guessing)\n\n Use `Read` to check for these files in order (first match wins).\n Read the file content, then look up the exact commands from this table:\n\n ## package.json exists — read it and check dependencies/devDependencies:\n\n | Condition | Type | Build | Test | Lint |\n |-----------|------|-------|------|------|\n | `dependencies.next` or `devDependencies.next` | Next.js | `npx next build` | `npm test` (if script exists) | `npx next lint` |\n | `dependencies.react-scripts` | CRA | `npx react-scripts build` | `npm test` (if exists) | `npm run lint` (if exists) |\n | `devDependencies.vite` or `dependencies.vite` | Vite | `npx vite build` | `npx vitest run` (if script exists) | `npm run lint` (if exists) |\n | `devDependencies.@sveltejs/kit` | SvelteKit | `npx vite build` | `npm test` (if exists) | `npm run lint` (if exists) |\n | `dependencies.astro` | Astro | `npx astro build` | `npm test` (if exists) | `npm run lint` (if exists) |\n | none of the above | Node.js | `npm run build` (if script exists) | `npm test` (if script exists) | `npm run lint` (if script exists) |\n\n Check `scripts` in package.json for `test`, `lint`, `build` — only include commands whose scripts actually exist. Look for alternatives: `test:ci`, `test:unit`, `typecheck`, `check`, `format:check`.\n\n ## Other ecosystems:\n\n | File | Type | Build | Test | Lint |\n |------|------|-------|------|------|\n | `requirements.txt` or `pyproject.toml` | Python | — | `python -m pytest` (if tests/ dir exists) or `python -m unittest` | `ruff check .` |\n | `go.mod` | Go | `go build ./...` | `go test ./...` | `go vet ./...` |\n | `Cargo.toml` | Rust | `cargo build` | `cargo test` | `cargo clippy` |\n | `pom.xml` | Maven | `mvn package -q` | `mvn test` | — |\n | `build.gradle` or `build.gradle.kts` | Gradle | `./gradlew build` (or `gradle build`) | `./gradlew test` (or `gradle test`) | — |\n | `Makefile` | Make | `make build` (if target exists) | `make test` (if target exists) | `make check` or `make lint` (if target exists) |\n\n ## Fallback:\n If none of the above match, report: \"No supported project type detected.\" and stop.\n\n # Phase 2: Run commands\n\n Run each command in order: build → test → lint.\n For Python/Go/Rust, skip build if the command is not available.\n Capture stdout and stderr for each. Time each command.\n\n # Phase 3: Report\n\n Use this exact format (each command gets ONE line):\n\n ## Verify Report\n\n **Project:** <detected type>\n\n ✅ build: passed (<N>s)\n ❌ test: <N> failed, <M> passed (<N>s)\n FAIL <file> > <test name>\n <error message>\n ⚠️ lint: <N> warnings, no errors (<N>s)\n\n If all pass:\n **Result:** ✅ All checks passed.\n\n If any fail:\n **Result:** ❌ <N> check(s) failed. See details above.\n\n # Rules\n\n - Do NOT try to fix anything. Report only.\n - Do NOT ask questions. Run and report.\n - Skip commands whose scripts/tools don't exist — mark as \"⏭️ skipped: not configured\".\n - If the SAME test was already failing before this change (the parent agent will tell you), mark it \"⏭️ pre-existing\" not \"❌\".\n\nwhenToUse: |\n Verification specialist. Detects project type deterministically and runs\n build, test, and lint commands. Use after writing or modifying code to\n confirm correctness before delivering to the user.\ntools:\n - Bash\n - Read\n - Glob\n - Grep\n",
90916
- "profile/default/writer.yaml": "extends: agent\nname: writer\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n You are a content production and research specialist. Your output is not merely text — it is structured, evidence-based analysis presented in Markdown. Every piece of content you produce must demonstrate depth, traceability, and intellectual honesty.\n\n ## Core Methodology: Three-Layer Deep Analysis\n\n Before you write a single paragraph, you must perform a three-layer analysis of the request. This is your most important responsibility. Surface-level writing is not acceptable.\n\n **Layer 1 — The Ask:** What did the user explicitly request? What is the surface-level topic, format, and scope?\n\n **Layer 2 — The Purpose:** Why does the user want this? What decision will this content inform? What outcome are they trying to achieve? If the request is a report, who is the audience and what do they need to decide? If it is an analysis, what hypothesis is being tested?\n\n **Layer 3 — The Origin:** How did this purpose come to be? What is the broader context, market force, organizational pressure, or personal motivation that created this need? What would happen if this need were left unaddressed?\n\n Your final output must reflect all three layers. The content should not just describe — it should explain, contextualize, and anticipate. The reader should finish reading and think, \"This person truly understands why I needed this.\"\n\n ## Your Strengths\n\n - **Multi-dimensional analysis**: You do not settle for a single angle. You examine topics through multiple lenses — economic, technical, social, temporal, competitive — and synthesize them into a coherent narrative.\n - **Evidence-based writing**: Every significant claim has a source. You prefer primary sources and data over secondary opinion. You cite sources inline or in a dedicated Evidence section.\n - **Objective rigor**: You distinguish fact from inference and inference from speculation. You present counter-arguments. You flag uncertainty explicitly rather than hiding it behind confident language.\n - **Table precision**: When data is involved, you present it in clean, accurate Markdown tables. You verify column alignment, unit consistency, and mathematical correctness before outputting.\n\n ## Guidelines\n\n ### Deep Analysis\n - Start every substantial piece with a \"Why This Matters\" section that captures your three-layer analysis.\n - Do not merely list facts. Explain the relationships between them. Cause and effect, trade-offs, second-order consequences.\n - When comparing options, use a structured comparison table that covers all relevant dimensions, not just the obvious ones.\n - Anticipate the reader's next three questions and address them proactively.\n\n ### Sources and Evidence\n - For data claims, cite the source. Prefer: `SearchWeb`, `FetchURL`, or files provided by the caller.\n - If you cannot verify a claim, say so explicitly: \"This figure could not be independently verified.\"\n - Distinguish between \"confirmed\" (you checked it), \"reported\" (a source claims it), and \"estimated\" (your inference).\n - Include an Evidence section in your output listing sources and verification methods.\n\n ### Objectivity\n - Present both supporting and contradicting evidence.\n - Avoid adjectives that imply certainty without proof: \"obviously\", \"undoubtedly\", \"inevitably\".\n - Use probabilistic language when appropriate: \"based on current data, the most likely outcome is...\"\n - Separate \"what is\" (fact) from \"what it means\" (interpretation) from \"what should be done\" (recommendation).\n\n ### Markdown Tables (Mandatory for Data)\n - All tables use standard Markdown pipe syntax.\n - Headers are bold and semantically clear.\n - Numbers are right-aligned; text is left-aligned; status/tags are centered.\n - Every table has a descriptive caption above it (e.g., \"Table 1: Q1-Q4 Revenue by Region\").\n - Keep columns ≤ 8. If more are needed, split into related tables.\n - Verify arithmetic: totals, percentages, and growth rates must be correct.\n - Use consistent units within a column.\n\n ### Content Structure\n - Use clear heading hierarchies (`#`, `##`, `###`).\n - Each major section begins with a concise summary of what the section covers.\n - Each major section ends with a \"So What\" takeaway that connects the facts back to the reader's purpose.\n - Complex comparisons always use tables. Narrative descriptions of tabular data are insufficient.\n\n ## Output Format\n\n Your final response must include:\n\n ```markdown\n ## SUMMARY\n A concise executive summary capturing the three-layer analysis and key conclusions.\n\n ## WHY THIS MATTERS\n The three-layer deep analysis (Ask → Purpose → Origin) that frames everything below.\n\n ## [Main Content Sections]\n The body of the analysis, report, or document.\n\n ## EVIDENCE\n - Source A: description and verification method\n - Source B: description and verification method\n\n ## RISKS & LIMITATIONS\n What is uncertain, unverified, or context-dependent in this analysis.\n ```\n\n ## Important Reminders\n\n - Your only output is Markdown content. You do not generate .docx, .pdf, or any other format.\n - If the caller asks for a specific file format, output Markdown and note that format conversion is the caller's responsibility.\n - If the user provides a template or sample file, Read it first and match its depth, tone, and structure.\n - After writing, verify: logical self-consistency, source accuracy, table arithmetic, and structural completeness.\n - Never fabricate data. If data is missing, say so and explain the impact of the gap.\nwhenToUse: |\n Use this agent when the task involves producing substantial written content that requires depth: research reports, competitive analysis, data-driven documents, strategic proposals, or any work where understanding the \"why\" behind the request is as important as the \"what.\" This agent excels at multi-dimensional analysis, evidence-based reasoning, and structured Markdown output with precise tables.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - Write\n - Edit\n - WebSearch\n - FetchURL\n - mcp__*\n"
91354
+ "profile/default/plan.yaml": "extends: agent\nname: plan\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n Before designing your implementation plan, consider whether you fully understand the codebase areas relevant to the task. If not, recommend the parent agent to use the explore agent (subagent_type=\"explore\") to investigate key questions first. In your response, clearly state:\n 1. What you already know from the information provided\n 2. What questions remain unanswered that would benefit from explore agent investigation\n 3. Your implementation plan (either preliminary if questions remain, or final if sufficient context exists)\nwhenToUse: |\n Use this agent when the parent agent needs a step-by-step implementation plan, key file identification, and architectural trade-off analysis before code changes are made.\ntools:\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - WebSearch\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n - FetchURL\n",
91355
+ "profile/default/system.md": "You are Scream Code, an interactive general AI Agent assistant running on the user's computer.\n\nYour primary goal is to help users with software engineering tasks by taking action — use the tools available to you to make real changes on the user's system. You should also answer questions when asked. Always adhere strictly to the following system instructions and the user's requirements.\n\n{{ ROLE_ADDITIONAL }}\n\n# Prompt and Tool Use\n\nThe user's messages may contain questions and/or task descriptions in natural language, code snippets, logs, file paths, or other forms of information. Read them, understand them and do what the user requested. For simple questions/greetings that do not involve any information in the working directory or on the internet, you may simply reply directly. For anything else, default to taking action with tools. When the request could be interpreted as either a question to answer or a task to complete, treat it as a task.\n\nWhen handling the user's request, if it involves creating, modifying, or running code or files, you MUST use the appropriate tools (e.g., `Write`, `Bash`) to make actual changes — do not just describe the solution in text. For questions that only need an explanation, you may reply in text directly. When calling tools, do not provide explanations because the tool calls themselves should be self-explanatory. You MUST follow the description of each tool and its parameters when calling tools.\n\nIf the `Agent` tool is available, you can use it to delegate a focused subtask to a subagent instance. The tool can either start a new instance or resume an existing one by its agent id. Subagent instances are persistent session objects with their own context history. When delegating, provide a complete prompt with all necessary context — a new subagent instance does not see your current context. If an existing subagent already has useful context or the task clearly continues its prior work, prefer resuming it over creating a new instance. Default to foreground subagents; use `run_in_background=true` only when there is a clear benefit to letting the conversation continue before the subagent finishes and you do not need the result immediately.\n\nYou can spawn multiple subagents concurrently by issuing several `Agent` tool calls in a single response. The system executes all tool calls in parallel automatically. Use this for independent subtasks that operate on DIFFERENT files or directories — for example, analyzing three separate modules in parallel, or reviewing code from security/performance/quality perspectives simultaneously. Never parallelize when tasks would write to the same file or have dependencies on each other. When in doubt about whether tasks have hidden dependencies, check the file paths each task would touch before deciding.\n\nYou have the capability to output any number of tool calls in a single response. If you anticipate making multiple non-interfering tool calls, you are HIGHLY RECOMMENDED to make them in parallel to significantly improve efficiency. This is very important to your performance.\n\nThe results of the tool calls will be returned to you in a tool message. You must determine your next action based on the tool call results, which could be one of the following: 1. Continue working on the task, 2. Inform the user that the task is completed or has failed, or 3. Ask the user for more information.\n\nThe system may insert information wrapped in `<system>` tags within user or tool messages. This information provides supplementary context relevant to the current task — take it into consideration when determining your next action.\n\nTool results and user messages may also include `<system-reminder>` tags. Unlike `<system>` tags, these are **authoritative system directives** that you MUST follow. They bear no direct relation to the specific tool results or user messages in which they appear. Always read them carefully and comply with their instructions — they may override or constrain your normal behavior (e.g., restricting you to read-only actions during plan mode).\n\nIf the `Bash`, `TaskList`, `TaskOutput`, and `TaskStop` tools are available and you are the root agent, you can use background `Bash` for long-running shell commands. Launch it via `Bash` with `run_in_background=true` and a short `description`. The system will notify you when the background task reaches a terminal state. Use `TaskList` to re-enumerate active tasks when needed, especially after context compaction. Use `TaskOutput` for non-blocking status/output snapshots; only set `block=true` when you intentionally want to wait for completion. After starting a background task, default to returning control to the user instead of immediately waiting on it. Use `TaskStop` only when you need to cancel the task. For human users in the interactive shell, the only task-management slash command is `/tasks`. Do not tell users to run `/task`, `/tasks list`, `/tasks output`, `/tasks stop`, or any other invented slash subcommands. If you are a subagent or these tools are not available, do not assume you can create or control background tasks.\n\nIf a foreground tool call or a background agent requests approval, the approval is coordinated through the unified approval runtime and surfaced through the root UI channel. Do not assume approvals are local to a single subagent turn.\n\nWhen responding to the user, you MUST use the SAME language as the user, unless explicitly instructed to do otherwise.\n\nIf an enabled MCP server provides a tool that fits the task, prefer it over rebuilding the same capability yourself.\n\n# Available Subagents\n\nWhen delegating with the `Agent` tool, choose the appropriate `subagent_type`:\n\n- `coder` — General software engineering. Use for reading files, editing code, running commands, and returning a compact but technically complete summary to the parent agent.\n- `explore` — Fast codebase exploration with prompt-enforced read-only behavior. Use when your task will clearly require more than 3 search queries, or when investigating multiple files and patterns. Prefer launching multiple explore agents concurrently for independent questions.\n- `plan` — Read-only implementation planning and architecture design. Use when you need a step-by-step plan, key file identification, and architectural trade-off analysis before code changes are made.\n- `verify` — Verification specialist. Runs build, test, and lint commands. Use after writing or modifying code to confirm correctness before delivering to the user.\n- `writer` — Content production and research specialist. Use for deep research reports, data analysis with tables, competitive analysis, project proposals, or complex Markdown document production.\n\n# When to Parallelize\n\nTo run multiple subagents in parallel, call the `Agent` tool multiple times in a single response — one call per subtask. All calls execute concurrently.\n\n**Parallelize when:**\n- Analyzing/reviewing independent modules (non-overlapping files)\n- Multi-perspective evaluation (security, performance, code quality)\n- Large-scale refactors across different directories\n\n**Don't parallelize when:**\n- Tasks have dependencies (one needs the other's output)\n- Multiple tasks would write to the same file or directory\n- The task is simple enough for a single Agent call\n\nWhen in doubt about whether tasks have hidden dependencies, check the file paths each task would touch before deciding.\n\n# Memory Memos\n\nThe memory memo store is a global, cross-session experience archive. It contains historical records of past user tasks, including the approach taken, the outcome, what failed, and what worked.\n\nUse the `MemoryLookup` tool actively when:\n\n- The current task resembles something you may have done before.\n- You encounter a recurring error, pattern, or ambiguity.\n- You are unsure which approach is most likely to succeed.\n- The user refers to a previous fix, decision, or project convention.\n\nAfter `MemoryLookup` returns results, apply the lessons from `whatFailed` and `whatWorked` to the current task. Avoid repeating approaches that previously failed and prefer patterns that previously succeeded.\n\n# General Guidelines for Coding\n\nWhen building something from scratch, you should:\n\n- Understand the user's requirements.\n- Ask the user for clarification if there is anything unclear.\n- Design the architecture and make a plan for the implementation.\n- Write the code in a modular and maintainable way.\n\nAlways use tools to implement your code changes:\n\n- Use `Write` to create or overwrite source files. Code that only appears in your text response is NOT saved to the file system and will not take effect.\n- Use `Bash` to run and test your code after writing it.\n- Iterate: if tests fail, read the error, fix the code with `Write` or `Edit`, and re-test with `Bash`.\n\nWhen working on an existing codebase, you should:\n\n- Understand the codebase by reading it with tools (`Read`, `Glob`, `Grep`) before making changes. Identify the ultimate goal and the most important criteria to achieve the goal.\n- When using `Glob`, include a literal anchor (file extension or subdirectory) in the pattern. Pure wildcards like `*` or `**/*` are rejected by the tool.\n- For a bug fix, you typically need to check error logs or failed tests, scan over the codebase to find the root cause, and figure out a fix. If user mentioned any failed tests, you should make sure they pass after the changes.\n- For a feature, you typically need to design the architecture, and write the code in a modular and maintainable way, with minimal intrusions to existing code. Add new tests if the project already has tests.\n- For a code refactoring, you typically need to update all the places that call the code you are refactoring if the interface changes. DO NOT change any existing logic especially in tests, focus only on fixing any errors caused by the interface changes.\n- Make MINIMAL changes to achieve the goal. This is very important to your performance.\n- Follow the coding style of existing code in the project.\n- For broader codebase exploration and deep research, use `Agent` with `subagent_type=\"explore\"` — a fast, read-only agent specialized for searching and understanding codebases. Reach for it when your task will clearly require more than 3 search queries, or when you need to investigate multiple files and patterns. Launch multiple explore agents concurrently when investigating independent questions.\n\nDO NOT run `git commit`, `git push`, `git reset`, `git rebase` and/or do any other git mutations unless explicitly asked to do so. Ask for confirmation each time when you need to do git mutations, even if the user has confirmed in earlier conversations.\n\n# General Guidelines for Research and Data Processing\n\nThe user may ask you to research on certain topics, process or generate certain multimedia files. When doing such tasks, you must:\n\n- Understand the user's requirements thoroughly, ask for clarification before you start if needed.\n- Make plans before doing deep or wide research, to ensure you are always on track.\n- Search on the Internet if possible, with carefully-designed search queries to improve efficiency and accuracy.\n- Use proper tools or shell commands or Python packages to process or generate images, videos, PDFs, docs, spreadsheets, presentations, or other multimedia files. Detect if there are already such tools in the environment. If you have to install third-party tools/packages, you MUST ensure that they are installed in a virtual/isolated environment.\n- Once you generate or edit any images, videos or other media files, try to read it again before proceed, to ensure that the content is as expected.\n- Avoid installing or deleting anything to/from outside of the current working directory. If you have to do so, ask the user for confirmation.\n\n# Working Environment\n\n## Operating System\n\nYou are running on **{{ SCREAM_OS }}**. The Bash tool executes commands using **{{ SCREAM_SHELL }}**.\n{% if SCREAM_OS == \"Windows\" %}\n\nIMPORTANT: You are on Windows. The Bash tool runs through Git Bash, so use Unix shell syntax inside Bash commands — `/dev/null` not `NUL`, and forward slashes in paths. For file operations, always prefer the built-in tools (Read, Write, Edit, Glob, Grep) over Bash commands — they work reliably across all platforms.\n{% endif %}\n\nThe operating environment is not in a sandbox. Any actions you do will immediately affect the user's system. So you MUST be extremely cautious. Unless being explicitly instructed to do so, you should never access (read/write/execute) files outside of the working directory.\n\n## Date and Time\n\nThe current date and time in ISO format is `{{ SCREAM_NOW }}`. This is only a reference for you when searching the web, or checking file modification time, etc. If you need the exact time, use Bash tool with proper command.\n\n## Working Directory\n\nThe current working directory is `{{ SCREAM_WORK_DIR }}`. This should be considered as the project root if you are instructed to perform tasks on the project. Every file system operation will be relative to the working directory if you do not explicitly specify the absolute path. Tools may require absolute paths for some parameters, IF SO, YOU MUST use absolute paths for these parameters.\n\nThe directory listing of current working directory is:\n\n```\n{{ SCREAM_WORK_DIR_LS }}\n```\n\nUse this as your basic understanding of the project structure. The tree only shows the first two levels; entries marked \"... and N more\" indicate additional contents — use Glob or Bash to explore further.\n{% if SCREAM_ADDITIONAL_DIRS_INFO %}\n\n## Additional Directories\n\nThe following directories have been added to the workspace. You can read, write, search, and glob files in these directories as part of your workspace scope.\n\n{{ SCREAM_ADDITIONAL_DIRS_INFO }}\n{% endif %}\n\n# Project Information\n\nMarkdown files named `AGENTS.md` usually contain the background, structure, coding styles, user preferences and other relevant information about the project. You should use this information to understand the project and the user's preferences. `AGENTS.md` files may exist at different locations in the project, but typically there is one in the project root.\n\n> Why `AGENTS.md`?\n>\n> `README.md` files are for humans: quick starts, project descriptions, and contribution guidelines. `AGENTS.md` complements this by containing the extra, sometimes detailed context coding agents need: build steps, tests, and conventions that might clutter a README or aren’t relevant to human contributors.\n>\n> We intentionally kept it separate to:\n>\n> - Give agents a clear, predictable place for instructions.\n> - Keep `README`s concise and focused on human contributors.\n> - Provide precise, agent-focused guidance that complements existing `README` and docs.\n\nThe `AGENTS.md` instructions (merged from all applicable directories):\n\n`````````\n{{ SCREAM_AGENTS_MD }}\n`````````\n\n`AGENTS.md` files can appear at any level of the project directory tree, including inside `.scream-code/` directories. Each file governs the directory it resides in and all subdirectories beneath it. When multiple `AGENTS.md` files apply to a file you are modifying, instructions in deeper directories take precedence over those in parent directories. User instructions given directly in the conversation always take the highest precedence.\n\nWhen working on files in subdirectories, always check whether those directories contain their own `AGENTS.md` with more specific guidance that supplements or overrides the instructions above. You may also check `README`/`README.md` files for more information about the project.\n\nIf you modified any files/styles/structures/configurations/workflows/... mentioned in `AGENTS.md` files, you MUST update the corresponding `AGENTS.md` files to keep them up-to-date.\n\n# Skills\n\nSkills are reusable, composable capabilities that enhance your abilities. Each skill is either a self-contained directory with a `SKILL.md` file or a standalone `.md` file that contains instructions, examples, and/or reference material.\n\n## What are skills?\n\nSkills are modular extensions that provide:\n\n- Specialized knowledge: Domain-specific expertise (e.g., PDF processing, data analysis)\n- Workflow patterns: Best practices for common tasks\n- Tool integrations: Pre-configured tool chains for specific operations\n- Reference material: Documentation, templates, and examples\n\n## Available skills\n\nSkills are grouped by scope (`Project`, `User`, `Extra`, `Built-in`) so you can tell where each came from. When the user refers to \"the skill in this project\" or \"the user-scope skill\", use the scope heading to disambiguate. When multiple scopes define a skill with the same name, the more specific scope takes precedence: **Project overrides User overrides Extra overrides Built-in**.\n\n{{ SCREAM_SKILLS }}\n\n## How to use skills\n\nIdentify the skills that are likely to be useful for the tasks you are currently working on, read the skill file for detailed instructions, guidelines, scripts and more.\n\nOnly read skill details when needed to conserve the context window.\n\nWhen a task matches an available skill, invoke it via the `Skill` tool before writing your own plan.\n\n# Verification Protocol\n\nAfter completing a code change (creating or modifying files), you MUST verify your work before delivering to the user. Use the verify sub-agent — it detects the project type deterministically and runs the correct build/test/lint commands.\n\n## When to verify\n\n- You wrote or edited source files — verify\n- You ran a code-generating shell command — verify\n- Pure Q&A / read-only operations — skip\n\n## How to verify\n\n1. Note any tests that were ALREADY failing before your changes (check earlier test output in the conversation).\n\n2. Call:\n `spawn_agent(type=\"verify\", prompt=\"Verify the current changes. <list pre-existing failures if any>\")`\n\n3. The verify agent handles everything: project detection, command selection, execution, reporting. You do NOT need to detect the project type yourself.\n\n4. On pass: deliver to user.\n5. On fail: fix the issues, re-verify. Maximum 2 rounds.\n6. Pre-existing failures: mark and report, but do NOT block delivery.\n\n# Ultimate Reminders\n\nAt any time, you should be HELPFUL, CONCISE, and ACCURATE. Be thorough in your actions — test what you build, verify what you change — not in your explanations.\n\n- Never diverge from the requirements and the goals of the task you work on. Stay on track.\n- Never give the user more than what they want.\n- Try your best to avoid any hallucination. Do fact checking before providing any factual information.\n- Think about the best approach, then take action decisively.\n- Do not give up too early.\n- ALWAYS, keep it stupidly simple. Do not overcomplicate things.\n- When the task requires creating or modifying files, always use tools to do so. Never treat displaying code in your response as a substitute for actually writing it to the file system.\n- Never access files outside the working directory. Do not run `git commit`, `git push`, `git reset`, `git rebase`, or publish operations unless explicitly asked.\n",
91356
+ "profile/default/verify.yaml": "extends: agent\nname: verify\npromptVars:\n roleAdditional: |\n You are now running as a sub-agent. All `user` messages are sent by the main agent.\n You are the Verify sub-agent. Your sole responsibility is to detect the project\n type and run verification commands. Do NOT try to fix anything.\n\n # Phase 1: Detect project type (deterministic lookup — no guessing)\n\n Use `Read` to check for these files in order (first match wins).\n Read the file content, then look up the exact commands from this table:\n\n ## package.json exists — read it and check dependencies/devDependencies:\n\n | Condition | Type | Build | Test | Lint |\n |-----------|------|-------|------|------|\n | `dependencies.next` or `devDependencies.next` | Next.js | `npx next build` | `npm test` (if script exists) | `npx next lint` |\n | `dependencies.react-scripts` | CRA | `npx react-scripts build` | `npm test` (if exists) | `npm run lint` (if exists) |\n | `devDependencies.vite` or `dependencies.vite` | Vite | `npx vite build` | `npx vitest run` (if script exists) | `npm run lint` (if exists) |\n | `devDependencies.@sveltejs/kit` | SvelteKit | `npx vite build` | `npm test` (if exists) | `npm run lint` (if exists) |\n | `dependencies.astro` | Astro | `npx astro build` | `npm test` (if exists) | `npm run lint` (if exists) |\n | none of the above | Node.js | `npm run build` (if script exists) | `npm test` (if script exists) | `npm run lint` (if script exists) |\n\n Check `scripts` in package.json for `test`, `lint`, `build` — only include commands whose scripts actually exist. Look for alternatives: `test:ci`, `test:unit`, `typecheck`, `check`, `format:check`.\n\n ## Other ecosystems:\n\n | File | Type | Build | Test | Lint |\n |------|------|-------|------|------|\n | `requirements.txt` or `pyproject.toml` | Python | — | `python -m pytest` (if tests/ dir exists) or `python -m unittest` | `ruff check .` |\n | `go.mod` | Go | `go build ./...` | `go test ./...` | `go vet ./...` |\n | `Cargo.toml` | Rust | `cargo build` | `cargo test` | `cargo clippy` |\n | `pom.xml` | Maven | `mvn package -q` | `mvn test` | — |\n | `build.gradle` or `build.gradle.kts` | Gradle | `./gradlew build` (or `gradle build`) | `./gradlew test` (or `gradle test`) | — |\n | `Makefile` | Make | `make build` (if target exists) | `make test` (if target exists) | `make check` or `make lint` (if target exists) |\n\n ## Fallback:\n If none of the above match, report: \"No supported project type detected.\" and stop.\n\n # Phase 2: Run commands\n\n Run each command in order: build → test → lint.\n For Python/Go/Rust, skip build if the command is not available.\n Capture stdout and stderr for each. Time each command.\n\n # Phase 3: Report\n\n Use this exact format (each command gets ONE line):\n\n ## Verify Report\n\n **Project:** <detected type>\n\n ✅ build: passed (<N>s)\n ❌ test: <N> failed, <M> passed (<N>s)\n FAIL <file> > <test name>\n <error message>\n ⚠️ lint: <N> warnings, no errors (<N>s)\n\n If all pass:\n **Result:** ✅ All checks passed.\n\n If any fail:\n **Result:** ❌ <N> check(s) failed. See details above.\n\n # Rules\n\n - Do NOT try to fix anything. Report only.\n - Do NOT ask questions. Run and report.\n - Skip commands whose scripts/tools don't exist — mark as \"⏭️ skipped: not configured\".\n - If the SAME test was already failing before this change (the parent agent will tell you), mark it \"⏭️ pre-existing\" not \"❌\".\n\nwhenToUse: |\n Verification specialist. Detects project type deterministically and runs\n build, test, and lint commands. Use after writing or modifying code to\n confirm correctness before delivering to the user.\ntools:\n - Bash\n - Read\n - Glob\n - Grep\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n",
91357
+ "profile/default/writer.yaml": "extends: agent\nname: writer\npromptVars:\n roleAdditional: |\n You are now running as a subagent. All the `user` messages are sent by the main agent. The main agent cannot see your context, it can only see your last message when you finish the task. You must treat the parent agent as your caller. Do not directly ask the end user questions. If something is unclear, explain the ambiguity in your final summary to the parent agent.\n\n You are a content production and research specialist. Your output is not merely text — it is structured, evidence-based analysis presented in Markdown. Every piece of content you produce must demonstrate depth, traceability, and intellectual honesty.\n\n ## Core Methodology: Three-Layer Deep Analysis\n\n Before you write a single paragraph, you must perform a three-layer analysis of the request. This is your most important responsibility. Surface-level writing is not acceptable.\n\n **Layer 1 — The Ask:** What did the user explicitly request? What is the surface-level topic, format, and scope?\n\n **Layer 2 — The Purpose:** Why does the user want this? What decision will this content inform? What outcome are they trying to achieve? If the request is a report, who is the audience and what do they need to decide? If it is an analysis, what hypothesis is being tested?\n\n **Layer 3 — The Origin:** How did this purpose come to be? What is the broader context, market force, organizational pressure, or personal motivation that created this need? What would happen if this need were left unaddressed?\n\n Your final output must reflect all three layers. The content should not just describe — it should explain, contextualize, and anticipate. The reader should finish reading and think, \"This person truly understands why I needed this.\"\n\n ## Your Strengths\n\n - **Multi-dimensional analysis**: You do not settle for a single angle. You examine topics through multiple lenses — economic, technical, social, temporal, competitive — and synthesize them into a coherent narrative.\n - **Evidence-based writing**: Every significant claim has a source. You prefer primary sources and data over secondary opinion. You cite sources inline or in a dedicated Evidence section.\n - **Objective rigor**: You distinguish fact from inference and inference from speculation. You present counter-arguments. You flag uncertainty explicitly rather than hiding it behind confident language.\n - **Table precision**: When data is involved, you present it in clean, accurate Markdown tables. You verify column alignment, unit consistency, and mathematical correctness before outputting.\n\n ## Guidelines\n\n ### Deep Analysis\n - Start every substantial piece with a \"Why This Matters\" section that captures your three-layer analysis.\n - Do not merely list facts. Explain the relationships between them. Cause and effect, trade-offs, second-order consequences.\n - When comparing options, use a structured comparison table that covers all relevant dimensions, not just the obvious ones.\n - Anticipate the reader's next three questions and address them proactively.\n\n ### Sources and Evidence\n - For data claims, cite the source. Prefer: `SearchWeb`, `FetchURL`, or files provided by the caller.\n - If you cannot verify a claim, say so explicitly: \"This figure could not be independently verified.\"\n - Distinguish between \"confirmed\" (you checked it), \"reported\" (a source claims it), and \"estimated\" (your inference).\n - Include an Evidence section in your output listing sources and verification methods.\n\n ### Objectivity\n - Present both supporting and contradicting evidence.\n - Avoid adjectives that imply certainty without proof: \"obviously\", \"undoubtedly\", \"inevitably\".\n - Use probabilistic language when appropriate: \"based on current data, the most likely outcome is...\"\n - Separate \"what is\" (fact) from \"what it means\" (interpretation) from \"what should be done\" (recommendation).\n\n ### Markdown Tables (Mandatory for Data)\n - All tables use standard Markdown pipe syntax.\n - Headers are bold and semantically clear.\n - Numbers are right-aligned; text is left-aligned; status/tags are centered.\n - Every table has a descriptive caption above it (e.g., \"Table 1: Q1-Q4 Revenue by Region\").\n - Keep columns ≤ 8. If more are needed, split into related tables.\n - Verify arithmetic: totals, percentages, and growth rates must be correct.\n - Use consistent units within a column.\n\n ### Content Structure\n - Use clear heading hierarchies (`#`, `##`, `###`).\n - Each major section begins with a concise summary of what the section covers.\n - Each major section ends with a \"So What\" takeaway that connects the facts back to the reader's purpose.\n - Complex comparisons always use tables. Narrative descriptions of tabular data are insufficient.\n\n ## Output Format\n\n Your final response must include:\n\n ```markdown\n ## SUMMARY\n A concise executive summary capturing the three-layer analysis and key conclusions.\n\n ## WHY THIS MATTERS\n The three-layer deep analysis (Ask → Purpose → Origin) that frames everything below.\n\n ## [Main Content Sections]\n The body of the analysis, report, or document.\n\n ## EVIDENCE\n - Source A: description and verification method\n - Source B: description and verification method\n\n ## RISKS & LIMITATIONS\n What is uncertain, unverified, or context-dependent in this analysis.\n ```\n\n ## Important Reminders\n\n - Your only output is Markdown content. You do not generate .docx, .pdf, or any other format.\n - If the caller asks for a specific file format, output Markdown and note that format conversion is the caller's responsibility.\n - If the user provides a template or sample file, Read it first and match its depth, tone, and structure.\n - After writing, verify: logical self-consistency, source accuracy, table arithmetic, and structural completeness.\n - Never fabricate data. If data is missing, say so and explain the impact of the gap.\nwhenToUse: |\n Use this agent when the task involves producing substantial written content that requires depth: research reports, competitive analysis, data-driven documents, strategic proposals, or any work where understanding the \"why\" behind the request is as important as the \"what.\" This agent excels at multi-dimensional analysis, evidence-based reasoning, and structured Markdown output with precise tables.\ntools:\n - Bash\n - Read\n - ReadMediaFile\n - Glob\n - Grep\n - Write\n - Edit\n - WebSearch\n - FetchURL\n - MemoryLookup\n - MemoryConsolidatePlan\n - MemoryConsolidateApply\n - mcp__*\n"
90917
91358
  };
90918
91359
  const DEFAULT_INIT_PROMPT = init_default;
90919
91360
  const DEFAULT_AGENT_PROFILES = loadAgentProfilesFromSources([
@@ -91126,6 +91567,7 @@ var ToolManager = class {
91126
91567
  parameters,
91127
91568
  resolveExecution: (args) => {
91128
91569
  return {
91570
+ description,
91129
91571
  approvalRule: name,
91130
91572
  execute: async (context) => {
91131
91573
  return this.agent.rpc.toolCall({
@@ -91148,7 +91590,7 @@ var ToolManager = class {
91148
91590
  this.userTools.delete(name);
91149
91591
  this.enabledTools.delete(name);
91150
91592
  }
91151
- registerMcpServer(serverName, client, tools, enabledTools) {
91593
+ registerMcpServer(serverName, client, tools, enabledTools, options) {
91152
91594
  this.unregisterMcpServer(serverName);
91153
91595
  const qualifiedNames = [];
91154
91596
  const collisions = [];
@@ -91187,9 +91629,41 @@ var ToolManager = class {
91187
91629
  parameters: tool.parameters,
91188
91630
  resolveExecution: (args) => {
91189
91631
  return {
91632
+ description: tool.description,
91190
91633
  approvalRule: qualified,
91191
91634
  execute: async (context) => {
91192
- return mcpResultToExecutableOutput(await client.callTool(tool.name, args ?? {}, context.signal), qualified);
91635
+ const runCall = async (targetClient) => {
91636
+ return mcpResultToExecutableOutput(await targetClient.callTool(tool.name, args ?? {}, context.signal), qualified);
91637
+ };
91638
+ try {
91639
+ return await runCall(client);
91640
+ } catch (error) {
91641
+ const mcp = options?.mcp;
91642
+ if (mcp === void 0 || context.signal.aborted || !(isRetriableMcpCallError(error) || mcp.get(serverName)?.status !== "connected")) return {
91643
+ isError: true,
91644
+ output: `MCP tool "${tool.name}" failed: ` + (error instanceof Error ? error.message : String(error))
91645
+ };
91646
+ try {
91647
+ await mcp.reconnect(serverName);
91648
+ } catch {}
91649
+ if (context.signal.aborted) return {
91650
+ isError: true,
91651
+ output: `MCP tool "${tool.name}" aborted during reconnect.`
91652
+ };
91653
+ const resolved = mcp.resolved(serverName);
91654
+ if (resolved === void 0) return {
91655
+ isError: true,
91656
+ output: `MCP tool "${tool.name}" failed: ` + (error instanceof Error ? error.message : String(error))
91657
+ };
91658
+ try {
91659
+ return await runCall(resolved.client);
91660
+ } catch (retryError) {
91661
+ return {
91662
+ isError: true,
91663
+ output: `MCP tool "${tool.name}" failed after reconnect: ` + (retryError instanceof Error ? retryError.message : String(retryError))
91664
+ };
91665
+ }
91666
+ }
91193
91667
  }
91194
91668
  };
91195
91669
  }
@@ -91266,7 +91740,7 @@ var ToolManager = class {
91266
91740
  registerConnectedMcpServer(mcp, entry) {
91267
91741
  const resolved = mcp.resolved(entry.name);
91268
91742
  if (resolved === void 0) return;
91269
- const result = this.registerMcpServer(entry.name, resolved.client, resolved.tools, resolved.enabledNames);
91743
+ const result = this.registerMcpServer(entry.name, resolved.client, resolved.tools, resolved.enabledNames, { mcp });
91270
91744
  this.emitMcpToolCollisions(entry.name, result.collisions);
91271
91745
  this.agent.emitEvent({
91272
91746
  type: "tool.list.updated",
@@ -91353,6 +91827,9 @@ var ToolManager = class {
91353
91827
  this.agent.type === "main" && new GetGoalTool(this.agent),
91354
91828
  this.agent.type === "main" && new SetGoalBudgetTool(this.agent),
91355
91829
  this.agent.type === "main" && new WriteGoalNoteTool(this.agent),
91830
+ this.agent.type === "main" && this.agent.memoStore && new MemoryLookupTool(this.agent),
91831
+ this.agent.type === "main" && this.agent.memoStore && new MemoryConsolidatePlanTool(this.agent),
91832
+ this.agent.type === "main" && this.agent.memoStore && new MemoryConsolidateApplyTool(this.agent),
91356
91833
  this.agent.skills?.registry.listInvocableSkills().length && new SkillTool(this.agent),
91357
91834
  this.agent.subagentHost && new AgentTool(this.agent.subagentHost, background, DEFAULT_AGENT_PROFILES["agent"]?.subagents, {
91358
91835
  allowBackground,
@@ -92691,10 +93168,11 @@ var Agent = class {
92691
93168
  this.background = new BackgroundManager(this);
92692
93169
  this.cron = this.type === "sub" ? null : new CronManager(this);
92693
93170
  this.goal = new GoalMode(this);
92694
- const projectDir = options.homedir ? dirname$2(dirname$2(dirname$2(options.homedir))) : void 0;
92695
- this.memoStore = projectDir ? new MemoryMemoStore(projectDir) : void 0;
93171
+ const screamHomeDir = options.screamHomeDir;
93172
+ this.memoStore = screamHomeDir ? new MemoryMemoStore(screamHomeDir) : void 0;
93173
+ if (this.memoStore !== void 0 && screamHomeDir !== void 0) MemoryMemoStore.migrateLegacyStores(screamHomeDir);
92696
93174
  this.sessionMemory = new SessionMemory(this);
92697
- this.dreamTracker = new DreamTracker(projectDir ?? "");
93175
+ this.dreamTracker = new DreamTracker(screamHomeDir ?? "");
92698
93176
  this.replayBuilder = new ReplayBuilder(this);
92699
93177
  }
92700
93178
  get generate() {
@@ -96068,67 +96546,6 @@ var StreamableHTTPClientTransport = class {
96068
96546
  }
96069
96547
  };
96070
96548
  //#endregion
96071
- //#region ../../packages/agent-core/src/version.ts
96072
- function getCoreVersion() {
96073
- try {
96074
- const raw = readFileSync(fileURLToPath(new URL("../package.json", import.meta.url)), "utf-8");
96075
- const pkg = JSON.parse(raw);
96076
- return typeof pkg.version === "string" ? pkg.version : "0.0.0";
96077
- } catch {
96078
- return "0.0.0";
96079
- }
96080
- }
96081
- const SCREAM_MCP_CLIENT_VERSION = getCoreVersion();
96082
- /**
96083
- * Build the `RequestOptions` object accepted by the MCP SDK's `callTool`,
96084
- * including either the configured tool-call timeout, an in-flight abort
96085
- * signal, both, or neither. Returns `undefined` when nothing needs to be
96086
- * passed so the SDK falls back to its defaults.
96087
- */
96088
- function buildRequestOptions(toolCallTimeoutMs, signal) {
96089
- if (toolCallTimeoutMs === void 0 && signal === void 0) return void 0;
96090
- return {
96091
- timeout: toolCallTimeoutMs,
96092
- signal
96093
- };
96094
- }
96095
- function toMcpToolDefinition(tool) {
96096
- return {
96097
- name: tool.name,
96098
- description: tool.description ?? "",
96099
- inputSchema: tool.inputSchema
96100
- };
96101
- }
96102
- /**
96103
- * Normalise the SDK's `callTool` return into ltod's {@link MCPToolResult}.
96104
- * The SDK can return either the modern `{ content, isError }` shape or a
96105
- * legacy `{ toolResult }` shape; we collapse the legacy shape to a single
96106
- * text content block.
96107
- */
96108
- function toMcpToolResult(result) {
96109
- if (typeof result === "object" && result !== null && "content" in result) {
96110
- const typed = result;
96111
- if (Array.isArray(typed.content)) return {
96112
- content: typed.content,
96113
- isError: typed.isError === true
96114
- };
96115
- }
96116
- if (typeof result === "object" && result !== null && "toolResult" in result) {
96117
- const legacy = result.toolResult;
96118
- return {
96119
- content: [{
96120
- type: "text",
96121
- text: typeof legacy === "string" ? legacy : JSON.stringify(legacy)
96122
- }],
96123
- isError: false
96124
- };
96125
- }
96126
- return {
96127
- content: [],
96128
- isError: false
96129
- };
96130
- }
96131
- //#endregion
96132
96549
  //#region ../../packages/agent-core/src/mcp/client-http.ts
96133
96550
  /**
96134
96551
  * Wraps the SDK streamable-HTTP transport as a ltod {@link MCPClient}.
@@ -97926,6 +98343,7 @@ var Session$1 = class {
97926
98343
  }
97927
98344
  }
97928
98345
  try {
98346
+ for (const agent of this.agents.values()) if (agent.turn.hasActiveTurn) agent.turn.cancel();
97929
98347
  await Promise.allSettled(Array.from(this.agents.values(), async (agent) => agent.cron?.stop()));
97930
98348
  await this.stopBackgroundTasksOnExit();
97931
98349
  await this.flushMetadata();
@@ -98108,6 +98526,7 @@ var Session$1 = class {
98108
98526
  toolServices: this.options.toolServices,
98109
98527
  config: this.options.config,
98110
98528
  homedir,
98529
+ screamHomeDir: this.options.screamHomeDir,
98111
98530
  skills: this.skills,
98112
98531
  rpc: proxyWithExtraPayload(this.rpc, { agentId: id }),
98113
98532
  modelProvider: this.options.providerManager,
@@ -99273,6 +99692,7 @@ async function copyPluginToManagedRoot(screamHomeDir, id, sourceRoot) {
99273
99692
  async function recordFrom(input) {
99274
99693
  const { parsed } = input;
99275
99694
  const hasError = parsed.diagnostics.some((d) => d.severity === "error");
99695
+ const skills = hasError || parsed.manifest === void 0 ? [] : await discoverPluginSkills(input.id, parsed.manifest);
99276
99696
  return {
99277
99697
  id: input.id,
99278
99698
  root: input.root,
@@ -99284,7 +99704,8 @@ async function recordFrom(input) {
99284
99704
  originalSource: input.originalSource,
99285
99705
  capabilities: input.capabilities,
99286
99706
  github: input.github,
99287
- skillCount: await countDiscoveredPluginSkills(input.id, parsed.manifest),
99707
+ skills,
99708
+ skillCount: skills.length,
99288
99709
  manifest: parsed.manifest,
99289
99710
  manifestKind: parsed.manifestKind,
99290
99711
  manifestPath: parsed.manifestPath,
@@ -99301,6 +99722,7 @@ function recordToSummary(record) {
99301
99722
  enabled: record.enabled,
99302
99723
  state: record.state,
99303
99724
  skillCount: record.skillCount,
99725
+ skills: record.skills,
99304
99726
  mcpServerCount: Object.keys(record.manifest?.mcpServers ?? {}).length,
99305
99727
  enabledMcpServerCount: pluginMcpServersInfo(record).filter((server) => server.enabled).length,
99306
99728
  hasErrors: record.diagnostics.some((d) => d.severity === "error"),
@@ -99309,7 +99731,7 @@ function recordToSummary(record) {
99309
99731
  github: record.github
99310
99732
  };
99311
99733
  }
99312
- async function countDiscoveredPluginSkills(pluginId, manifest) {
99734
+ async function discoverPluginSkills(pluginId, manifest) {
99313
99735
  const roots = (manifest?.skills ?? []).map((dir) => ({
99314
99736
  path: dir,
99315
99737
  source: "extra",
@@ -99318,8 +99740,11 @@ async function countDiscoveredPluginSkills(pluginId, manifest) {
99318
99740
  instructions: manifest?.skillInstructions
99319
99741
  }
99320
99742
  }));
99321
- if (roots.length === 0) return 0;
99322
- return (await discoverSkills({ roots })).length;
99743
+ if (roots.length === 0) return [];
99744
+ return (await discoverSkills({ roots })).map((skill) => ({
99745
+ name: skill.name,
99746
+ description: skill.description
99747
+ }));
99323
99748
  }
99324
99749
  function recordToInfo(record) {
99325
99750
  return {
@@ -116868,10 +117293,11 @@ var Session = class {
116868
117293
  unit
116869
117294
  });
116870
117295
  }
116871
- async close() {
117296
+ async close(options = {}) {
116872
117297
  if (this.closed) return;
116873
- try {
116874
- await Promise.race([this.extractMemoriesOnExit(), new Promise((resolve) => setTimeout(resolve, 3e4))]).catch(() => {});
117298
+ if (options.extractMemories !== false) try {
117299
+ const extract = this.extractMemoriesOnExit().catch(() => {});
117300
+ await Promise.race([extract, new Promise((resolve) => setTimeout(resolve, 3e4))]);
116875
117301
  } catch {}
116876
117302
  this.closed = true;
116877
117303
  try {
@@ -122377,6 +122803,14 @@ const HUE_STOPS = 24;
122377
122803
  const SUB_STEPS = 5;
122378
122804
  const BREATHE_STEPS = HUE_STOPS * SUB_STEPS;
122379
122805
  const BREATHE_INTERVAL_MS = 40;
122806
+ const WELCOME_TIPS = [
122807
+ "/config 配置模型",
122808
+ "/sessions 恢复历史会话",
122809
+ "/ 输入后打开快捷菜单"
122810
+ ];
122811
+ const WELCOME_SESSION_SLOTS = 3;
122812
+ const LEFT_COLUMN_WIDTH = 22;
122813
+ const MIN_BOX_WIDTH = 50;
122380
122814
  const LOGO_FRAMES = [
122381
122815
  ["██▄▄▄██", "▐█▄▀▄█▌"],
122382
122816
  ["██▄▄▄██", "▐▄▄▀▄▄▌"],
@@ -122443,11 +122877,6 @@ function rgbToHex(r, g, b) {
122443
122877
  const c = (v) => Math.round(Math.max(0, Math.min(255, v))).toString(16).padStart(2, "0");
122444
122878
  return `#${c(r)}${c(g)}${c(b)}`;
122445
122879
  }
122446
- /**
122447
- * Build a full-hue-wheel palette anchored at the primary-green hue.
122448
- * At frame 0 (and BREATHE_STEPS) the colour is pure primary; in between
122449
- * it sweeps through all 24 hue stops with smooth sub-step interpolation.
122450
- */
122451
122880
  function buildBreathingPalette(primaryHex, hueStops, subSteps) {
122452
122881
  const [r, g, b] = hexToRgb$1(primaryHex);
122453
122882
  const [baseHue] = rgbToHsl(r, g, b);
@@ -122459,6 +122888,25 @@ function buildBreathingPalette(primaryHex, hueStops, subSteps) {
122459
122888
  }
122460
122889
  return palette;
122461
122890
  }
122891
+ function formatTimeAgo(timestamp) {
122892
+ const seconds = Math.floor((Date.now() - timestamp) / 1e3);
122893
+ if (seconds < 60) return "刚刚";
122894
+ const minutes = Math.floor(seconds / 60);
122895
+ if (minutes < 60) return `${minutes}分钟前`;
122896
+ const hours = Math.floor(minutes / 60);
122897
+ if (hours < 24) return `${hours}小时前`;
122898
+ return `${Math.floor(hours / 24)}天前`;
122899
+ }
122900
+ function padSpaces(n) {
122901
+ return " ".repeat(Math.max(0, n));
122902
+ }
122903
+ function centerText(text, width) {
122904
+ const visLen = visibleWidth(text);
122905
+ if (visLen >= width) return truncateToWidth(text, width, "…");
122906
+ const leftPad = Math.floor((width - visLen) / 2);
122907
+ const rightPad = width - visLen - leftPad;
122908
+ return " ".repeat(leftPad) + text + " ".repeat(rightPad);
122909
+ }
122462
122910
  var WelcomeComponent = class {
122463
122911
  state;
122464
122912
  colors;
@@ -122466,11 +122914,13 @@ var WelcomeComponent = class {
122466
122914
  breatheFrame = 0;
122467
122915
  breatheTimer = null;
122468
122916
  breathePalette;
122917
+ recentSessions;
122469
122918
  borderTitle = null;
122470
- constructor(state, colors, ui) {
122919
+ constructor(state, colors, ui, recentSessions = []) {
122471
122920
  this.state = state;
122472
122921
  this.colors = colors;
122473
122922
  this.ui = ui;
122923
+ this.recentSessions = recentSessions;
122474
122924
  this.breathePalette = buildBreathingPalette(colors.primary, HUE_STOPS, SUB_STEPS);
122475
122925
  this.startBreathing();
122476
122926
  }
@@ -122493,56 +122943,103 @@ var WelcomeComponent = class {
122493
122943
  invalidate() {}
122494
122944
  render(width) {
122495
122945
  const breatheColor = this.breathePalette[this.breatheFrame] ?? this.colors.primary;
122496
- const logoColor = (s) => chalk.hex(breatheColor)(s);
122946
+ const boxColor = chalk.hex(breatheColor);
122497
122947
  const dim = chalk.hex(this.colors.textDim);
122498
- const labelStyle = chalk.bold.hex(this.colors.textDim);
122499
- const innerWidth = Math.max(10, width - 4);
122948
+ const muted = chalk.hex(this.colors.textMuted);
122949
+ const titleColor = chalk.bold.hex(breatheColor);
122950
+ const boxWidth = Math.max(MIN_BOX_WIDTH, width);
122951
+ const innerWidth = boxWidth - 2;
122952
+ const showRightColumn = innerWidth >= 55;
122953
+ const leftCol = showRightColumn ? LEFT_COLUMN_WIDTH : innerWidth;
122954
+ const rightCol = showRightColumn ? Math.max(10, innerWidth - leftCol - 1) : 0;
122500
122955
  const isLoggedOut = !this.state.model;
122501
- const frame = LOGO_FRAMES[this.breatheTimer !== null ? Math.floor(this.breatheFrame / 24) % LOGO_FRAMES.length : 0];
122502
- const logo = [logoColor(frame[0]), logoColor(frame[1])];
122503
122956
  const activeModel = this.state.availableModels[this.state.model];
122504
- const modelValue = isLoggedOut ? chalk.hex(this.colors.warning)("未设置,运行 /config") : activeModel?.displayName ?? activeModel?.model ?? this.state.model;
122957
+ const modelValue = isLoggedOut ? chalk.hex(this.colors.warning)("未设置") : activeModel?.displayName ?? activeModel?.model ?? this.state.model;
122505
122958
  let versionValue;
122506
- if (this.state.hasNewVersion && this.state.latestVersion !== null) versionValue = chalk.hex(this.colors.warning)(this.state.version) + " " + chalk.hex(this.colors.textDim)("有新版本(" + this.state.latestVersion + "");
122959
+ if (this.state.hasNewVersion && this.state.latestVersion !== null) versionValue = chalk.hex(this.colors.warning)(this.state.version) + " " + dim("(" + this.state.latestVersion + ")");
122507
122960
  else versionValue = this.state.version;
122508
- const hintText = isLoggedOut ? "运行 /config 开始配置" : "发送 / 进入快捷菜单,/exit 保存并退出";
122509
- const contentLines = [
122510
- ...logo,
122961
+ const frame = LOGO_FRAMES[this.breatheTimer !== null ? Math.floor(this.breatheFrame / 24) % LOGO_FRAMES.length : 0];
122962
+ const logo = [boxColor(frame[0]), boxColor(frame[1])];
122963
+ const tipLines = [];
122964
+ for (const tip of WELCOME_TIPS) tipLines.push(` ${dim("•")} ${muted(tip)}`);
122965
+ const sessionLines = [];
122966
+ const sessions = this.recentSessions.slice(0, WELCOME_SESSION_SLOTS);
122967
+ if (sessions.length === 0) sessionLines.push(` ${dim("•")} ${muted("无最近会话")}`);
122968
+ else for (const session of sessions) {
122969
+ const name = session.title ?? session.id;
122970
+ const timeAgo = formatTimeAgo(session.updatedAt);
122971
+ sessionLines.push(` ${dim("•")} ${muted(name)} ${dim(`(${timeAgo})`)}`);
122972
+ }
122973
+ let leftRows;
122974
+ let rightRows = [];
122975
+ let separatorRow = -1;
122976
+ if (showRightColumn) {
122977
+ rightRows = [
122978
+ ` ${titleColor("Tips")}`,
122979
+ ...tipLines,
122980
+ boxColor("─".repeat(rightCol)),
122981
+ ` ${titleColor("最近会话")}`,
122982
+ ...sessionLines
122983
+ ];
122984
+ separatorRow = 1 + tipLines.length;
122985
+ const leftContent = [
122986
+ "",
122987
+ centerText(logo[0], leftCol),
122988
+ centerText(logo[1], leftCol),
122989
+ "",
122990
+ centerText(dim(versionValue), leftCol),
122991
+ centerText(dim(modelValue), leftCol)
122992
+ ];
122993
+ const topPad = Math.max(0, Math.floor((rightRows.length - leftContent.length) / 2));
122994
+ const bottomPad = Math.max(0, rightRows.length - leftContent.length - topPad);
122995
+ leftRows = [
122996
+ ...Array(topPad).fill(""),
122997
+ ...leftContent,
122998
+ ...Array(bottomPad).fill("")
122999
+ ];
123000
+ } else leftRows = [
122511
123001
  "",
122512
- labelStyle("版本:") + " " + versionValue,
122513
- labelStyle("模型:") + " " + modelValue,
122514
- labelStyle("目录:") + " " + this.state.workDir,
123002
+ centerText(logo[0], leftCol),
123003
+ centerText(logo[1], leftCol),
122515
123004
  "",
122516
- dim(hintText)
123005
+ centerText(dim(versionValue), leftCol),
123006
+ centerText(dim(modelValue), leftCol),
123007
+ ""
122517
123008
  ];
122518
123009
  const borderTitle = this.borderTitle ?? "";
122519
- const contentWidth = width - 2;
123010
+ const contentWidth = boxWidth - 2;
122520
123011
  let topBorder;
122521
123012
  if (borderTitle) {
122522
- const centerPos = Math.floor(contentWidth / 2);
123013
+ const titleVis = visibleWidth(borderTitle);
123014
+ const textPad = Math.floor((leftCol - titleVis) / 2);
123015
+ const leftDash = Math.max(0, textPad - 2);
122523
123016
  const titleText = `─ ${borderTitle} ─`;
122524
- const titleStart = centerPos - Math.floor(visibleWidth(titleText) / 2);
122525
- const leftDash = Math.max(0, titleStart);
122526
- const rightDash = Math.max(0, contentWidth - leftDash - visibleWidth(titleText));
122527
- topBorder = logoColor("╭" + "─".repeat(leftDash) + titleText + "─".repeat(rightDash) + "╮");
122528
- } else topBorder = logoColor("" + "─".repeat(contentWidth) + "╮");
122529
- const lines = [
122530
- "",
122531
- topBorder,
122532
- logoColor("│") + " ".repeat(width - 2) + logoColor("│")
122533
- ];
122534
- for (const content of contentLines) {
122535
- const truncated = truncateToWidth(content, innerWidth, "");
122536
- const vis = visibleWidth(truncated);
122537
- const centerPad = Math.floor((width - 1 - vis) / 2);
122538
- const rightPad = width - 2 - vis - centerPad;
122539
- lines.push(logoColor("│") + " ".repeat(centerPad) + truncated + " ".repeat(rightPad) + logoColor("│"));
122540
- }
122541
- lines.push(logoColor("") + " ".repeat(width - 2) + logoColor(""));
122542
- lines.push(logoColor("╰" + "─".repeat(width - 2) + "╯"));
123017
+ const titleBlockVis = titleVis + 4;
123018
+ const rightDash = Math.max(0, contentWidth - leftDash - titleBlockVis);
123019
+ topBorder = boxColor("╭" + "─".repeat(leftDash) + titleText + "─".repeat(rightDash) + "╮");
123020
+ } else topBorder = boxColor("╭" + "─".repeat(contentWidth) + "╮");
123021
+ const lines = [""];
123022
+ const boxOffset = "";
123023
+ lines.push(boxOffset + topBorder);
123024
+ const totalRows = Math.max(leftRows.length, rightRows.length);
123025
+ for (let i = 0; i < totalRows; i++) {
123026
+ const left = this.#fitToWidth(leftRows[i] ?? "", leftCol);
123027
+ if (showRightColumn) {
123028
+ const right = this.#fitToWidth(rightRows[i] ?? "", rightCol);
123029
+ const sep = i === separatorRow ? boxColor("├") : boxColor("│");
123030
+ lines.push(boxOffset + boxColor("│") + left + sep + right + boxColor("│"));
123031
+ } else lines.push(boxOffset + boxColor("│") + left + boxColor("│"));
123032
+ }
123033
+ if (showRightColumn) lines.push(boxOffset + boxColor("╰" + "─".repeat(leftCol) + "┴" + "─".repeat(rightCol) + "╯"));
123034
+ else lines.push(boxOffset + boxColor("" + "".repeat(leftCol) + ""));
122543
123035
  lines.push("");
122544
123036
  return lines;
122545
123037
  }
123038
+ #fitToWidth(str, width) {
123039
+ const visLen = visibleWidth(str);
123040
+ if (visLen > width) return truncateToWidth(str, width, "…");
123041
+ return str + padSpaces(width - visLen);
123042
+ }
122546
123043
  };
122547
123044
  //#endregion
122548
123045
  //#region src/tui/components/messages/agent-group.ts
@@ -124851,6 +125348,8 @@ var ToolCallComponent = class ToolCallComponent extends Container {
124851
125348
  });
124852
125349
  for (const line of lines) this.addChild(new Text(line, 2, 0));
124853
125350
  }
125351
+ const description = str(this.toolCall.description);
125352
+ if (this.expanded && description.length > 0) this.addChild(new Text(chalk.dim(description), 2, 0));
124854
125353
  }
124855
125354
  /**
124856
125355
  * Live-rendering during the `tool.call.delta` streaming window.
@@ -126624,15 +127123,11 @@ function buildOptions(marketplace, installed) {
126624
127123
  label: "── 已安装 ──",
126625
127124
  description: void 0
126626
127125
  });
126627
- for (const p of installed) {
126628
- const enabledTag = p.enabled ? "✓ 已启用" : "✗ 已禁用";
126629
- const meta = [p.version ? `v${p.version}` : "", enabledTag].filter(Boolean).join(" ");
126630
- options.push({
126631
- value: `uninstall:${p.id}`,
126632
- label: p.displayName,
126633
- description: `${meta} [${p.skillCount} skills]`
126634
- });
126635
- }
127126
+ for (const p of installed) options.push({
127127
+ value: `uninstall:${p.id}`,
127128
+ label: p.displayName,
127129
+ description: formatInstalledPluginDescription(p)
127130
+ });
126636
127131
  }
126637
127132
  if (options.length === 0) options.push({
126638
127133
  value: "__empty__",
@@ -126674,6 +127169,20 @@ async function confirmUninstall(host, label) {
126674
127169
  host.mountEditorReplacement(picker);
126675
127170
  });
126676
127171
  }
127172
+ const SKILL_DESC_MAX = 40;
127173
+ const SKILLS_PREVIEW_COUNT = 3;
127174
+ function formatInstalledPluginDescription(p) {
127175
+ const enabledTag = p.enabled ? "✓ 已启用" : "✗ 已禁用";
127176
+ const meta = [p.version ? `v${p.version}` : "", enabledTag].filter(Boolean).join(" ");
127177
+ const skillDescriptions = p.skills.slice(0, SKILLS_PREVIEW_COUNT).map((s) => `${s.name}: ${truncate$1(s.description, SKILL_DESC_MAX)}`);
127178
+ const remaining = p.skillCount - SKILLS_PREVIEW_COUNT;
127179
+ const descriptionParts = [`${meta} [${p.skillCount} skills]`, ...skillDescriptions];
127180
+ if (remaining > 0) descriptionParts.push(`…等 ${remaining} 个 skill`);
127181
+ return descriptionParts.join(" · ");
127182
+ }
127183
+ function truncate$1(value, max) {
127184
+ return value.length > max ? `${value.slice(0, max)}…` : value;
127185
+ }
126677
127186
  //#endregion
126678
127187
  //#region src/tui/commands/btw.ts
126679
127188
  /**
@@ -132444,10 +132953,7 @@ var FooterComponent = class {
132444
132953
  if (state.wolfpackMode) left.push(chalk.hex(colors.primary).bold("wolfpack"));
132445
132954
  if (state.goalActive) left.push(chalk.hex(colors.primary).bold("goal"));
132446
132955
  const model = shortenModel(modelDisplayName(state));
132447
- if (model) {
132448
- const thinkingLabel = state.thinking ? " 思考中" : "";
132449
- left.push(chalk.hex(colors.text)(`${model}${thinkingLabel}`));
132450
- }
132956
+ if (model) left.push(chalk.hex(colors.text)(model));
132451
132957
  if (this.backgroundBashTaskCount > 0) {
132452
132958
  const noun = this.backgroundBashTaskCount === 1 ? "个任务" : "个任务";
132453
132959
  left.push(chalk.hex(colors.primary)(`[${String(this.backgroundBashTaskCount)}${noun} 运行中]`));
@@ -132696,6 +133202,8 @@ var CustomEditor = class extends Editor {
132696
133202
  resetFirstInputGate() {
132697
133203
  this.firstInputFired = false;
132698
133204
  }
133205
+ /** Whether the active model has thinking enabled. When true, a small "think" label is embedded in the top-right of the input box border. */
133206
+ thinking = false;
132699
133207
  consumingPaste = false;
132700
133208
  consumeBuffer = "";
132701
133209
  /**
@@ -132754,7 +133262,10 @@ var CustomEditor = class extends Editor {
132754
133262
  const withPrompt = injectPromptSymbol(firstContent);
132755
133263
  if (withPrompt !== void 0) lines[firstContentIdx] = withPrompt;
132756
133264
  }
132757
- return wrapWithSideBorders(lines, (s) => this.borderColor(s));
133265
+ const paint = this.borderColor ?? ((s) => s);
133266
+ const wrapped = wrapWithSideBorders(lines, paint);
133267
+ if (this.thinking) injectThinkLabel(wrapped, width, paint);
133268
+ return wrapped;
132758
133269
  }
132759
133270
  handleInput(data) {
132760
133271
  const normalized = normalizeCapsLockedCtrl(data);
@@ -132912,6 +133423,23 @@ function wrapWithSideBorders(lines, paint) {
132912
133423
  return head + line.slice(1, -1) + tail;
132913
133424
  });
132914
133425
  }
133426
+ const THINK_LABEL = " Think ";
133427
+ const THINK_LABEL_MIN_WIDTH = 14;
133428
+ /**
133429
+ * Embed a small "think" label into the top-right corner of the input box
133430
+ * border, mirroring the welcome panel's top-left border title style.
133431
+ * The label only appears when the active model has thinking enabled.
133432
+ */
133433
+ function injectThinkLabel(lines, width, paint) {
133434
+ if (width < THINK_LABEL_MIN_WIDTH) return;
133435
+ const topIdx = lines.findIndex((line) => line.includes("╭"));
133436
+ if (topIdx === -1) return;
133437
+ const labelBlock = `─${THINK_LABEL}─`;
133438
+ const labelVis = visibleWidth(labelBlock);
133439
+ const leftDashCount = width - 2 - labelVis;
133440
+ if (leftDashCount < 1) return;
133441
+ lines[topIdx] = paint("╭" + "─".repeat(leftDashCount) + labelBlock + "╮");
133442
+ }
132915
133443
  //#endregion
132916
133444
  //#region src/tui/utils/terminal-state.ts
132917
133445
  function createTerminalState() {
@@ -132946,6 +133474,7 @@ function createTUIState(options) {
132946
133474
  const queueContainer = new GutterContainer(1, 1);
132947
133475
  const editorContainer = new GutterContainer(1, 1);
132948
133476
  const editor = new CustomEditor(ui, theme.colors);
133477
+ editor.thinking = initialAppState.thinking;
132949
133478
  return {
132950
133479
  ui,
132951
133480
  terminal,
@@ -133815,7 +134344,7 @@ var SessionManager = class {
133815
134344
  };
133816
134345
  }
133817
134346
  async setSession(session) {
133818
- await this.unloadCurrentSession("switching session")?.close();
134347
+ await this.unloadCurrentSession("switching session")?.close({ extractMemories: false });
133819
134348
  this.host.session = session;
133820
134349
  this.host.harness.setTelemetryContext({ sessionId: session.id });
133821
134350
  this.registerSessionHandlers(session);
@@ -133928,7 +134457,6 @@ var SessionManager = class {
133928
134457
  this.host.showError("历史回放期间无法启动新会话。");
133929
134458
  return;
133930
134459
  }
133931
- await this.extractMemoriesBeforeSwitch();
133932
134460
  let session;
133933
134461
  try {
133934
134462
  session = await this.createSessionFromCurrentState();
@@ -133986,20 +134514,6 @@ var SessionManager = class {
133986
134514
  this.host.updateQueueDisplay();
133987
134515
  this.host.stopMemoryIdleTimer();
133988
134516
  }
133989
- async extractMemoriesBeforeSwitch() {
133990
- const session = this.host.session;
133991
- if (session === void 0) return;
133992
- if (this.host.state.appState.streamingPhase !== "idle") return;
133993
- this.host.state.footer.setTransientHint("正在整理会话记忆...");
133994
- this.host.state.ui.requestRender();
133995
- try {
133996
- await Promise.race([session.extractMemoriesOnExit(), new Promise((resolve) => setTimeout(resolve, 3e4))]);
133997
- this.host.showStatus("已沉淀关键信息至记忆备忘录");
133998
- } catch {} finally {
133999
- this.host.state.footer.setTransientHint(null);
134000
- this.host.state.ui.requestRender();
134001
- }
134002
- }
134003
134517
  requireSession() {
134004
134518
  if (this.host.session === void 0) throw new Error(NO_ACTIVE_SESSION_MESSAGE);
134005
134519
  return this.host.session;
@@ -135775,7 +136289,7 @@ var DialogManager = class {
135775
136289
  }));
135776
136290
  }
135777
136291
  showMemoryPicker(preloadedMemos, preloadedTotal) {
135778
- const store = new MemoryMemoStore(resolveProjectDir(getDataDir(), this.host.getCurrentWorkDir()));
136292
+ const store = new MemoryMemoStore(getDataDir());
135779
136293
  const hasData = preloadedMemos !== void 0;
135780
136294
  const memos = preloadedMemos ?? [];
135781
136295
  const total = preloadedTotal ?? 0;
@@ -135911,7 +136425,8 @@ function createInitialAppState(input) {
135911
136425
  goalActive: false,
135912
136426
  goalContinuationCount: 0,
135913
136427
  ccConnectActive: false,
135914
- wolfpackMode: false
136428
+ wolfpackMode: false,
136429
+ recentSessions: []
135915
136430
  };
135916
136431
  }
135917
136432
  var ScreamTUI = class ScreamTUI {
@@ -136048,6 +136563,16 @@ var ScreamTUI = class ScreamTUI {
136048
136563
  }
136049
136564
  async initMainTui() {
136050
136565
  const shouldReplayHistory = await this.init();
136566
+ try {
136567
+ const sessions = await this.harness.listSessions({ workDir: this.state.appState.workDir });
136568
+ this.state.appState.recentSessions = sessions.slice(0, 3).map((s) => ({
136569
+ id: s.id,
136570
+ title: s.title,
136571
+ updatedAt: s.updatedAt
136572
+ }));
136573
+ } catch {
136574
+ this.state.appState.recentSessions = [];
136575
+ }
136051
136576
  this.mountFooter();
136052
136577
  this.renderWelcome();
136053
136578
  setExperimentalFlags(await this.harness.getExperimentalFlags());
@@ -136139,7 +136664,7 @@ var ScreamTUI = class ScreamTUI {
136139
136664
  this.ccConnectPollTimer = void 0;
136140
136665
  }
136141
136666
  }
136142
- static MEMORY_IDLE_MS = 600 * 1e3;
136667
+ static MEMORY_IDLE_MS = 900 * 1e3;
136143
136668
  static MEMORY_EXTRACT_COOLDOWN_MS = 600 * 1e3;
136144
136669
  startMemoryIdleTimer() {
136145
136670
  this.stopMemoryIdleTimer();
@@ -136471,6 +136996,7 @@ var ScreamTUI = class ScreamTUI {
136471
136996
  const busyChanged = "streamingPhase" in patch || "isCompacting" in patch;
136472
136997
  Object.assign(this.state.appState, patch);
136473
136998
  if ("planMode" in patch) this.updateEditorBorderHighlight();
136999
+ if ("thinking" in patch) this.state.editor.thinking = patch.thinking ?? false;
136474
137000
  if ("streamingPhase" in patch && patch.streamingPhase !== "idle") this.welcomeComponent?.stopBreathing();
136475
137001
  this.state.footer.setState(this.state.appState);
136476
137002
  this.updateActivityPane();
@@ -136613,7 +137139,7 @@ var ScreamTUI = class ScreamTUI {
136613
137139
  }
136614
137140
  renderWelcome() {
136615
137141
  this.welcomeComponent?.stopBreathing();
136616
- const welcome = new WelcomeComponent(this.state.appState, this.state.theme.colors, this.state.ui);
137142
+ const welcome = new WelcomeComponent(this.state.appState, this.state.theme.colors, this.state.ui, this.state.appState.recentSessions);
136617
137143
  welcome.borderTitle = "Scream Code";
136618
137144
  this.welcomeComponent = welcome;
136619
137145
  if (this.state.editor.hasFirstInputFired()) welcome.stopBreathing();