scream-code 0.10.0 → 0.10.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,8 +5,8 @@ const __filename = __cjsShimFileURLToPath(import.meta.url);
5
5
  const __dirname = __cjsShimDirname(__filename);
6
6
  import { a as __toESM, i as __require, r as __exportAll, t as __commonJSMin } from "./chunk-apG1qJts.mjs";
7
7
  import "./suppress-sqlite-warning-C2VB0doZ.mjs";
8
- import { C as join$1, D as resolve$1, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize, x as dirname$2, y as KnowledgeStore } from "./src-1LHYB0xM.mjs";
9
- import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-os5jWxGT.mjs";
8
+ import { C as join$1, D as resolve$1, E as relative$1, S as isAbsolute$1, T as parse$7, a as isSupportedFile, b as basename$1, i as ingestFile, r as ingestDirectory, t as multiSearch, w as normalize, x as dirname$2, y as KnowledgeStore } from "./src-oeUnRY3N.mjs";
9
+ import { a as setLocale, i as getLocale, n as assertScreamHostIdentity, o as t, r as createScreamDefaultHeaders, t as TextInputDialogComponent } from "./text-input-dialog-Cj7OClhs.mjs";
10
10
  import { createRequire } from "node:module";
11
11
  import { createHash, randomBytes, randomInt, randomUUID } from "node:crypto";
12
12
  import Jn, { access, appendFile, chmod, copyFile, cp, lstat, mkdir, mkdtemp, open, readFile, readdir, realpath, rename, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
@@ -22,7 +22,7 @@ import { finished, pipeline as pipeline$1 } from "node:stream/promises";
22
22
  import * as vs from "zlib";
23
23
  import Qr from "zlib";
24
24
  import * as fs$9 from "node:fs";
25
- import Vt, { appendFileSync, chmodSync, closeSync, constants, createReadStream, createWriteStream as createWriteStream$1, existsSync, fsyncSync, mkdirSync, openSync, promises, readFileSync, readSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
25
+ import Vt, { appendFileSync, chmodSync, closeSync, constants, createReadStream, createWriteStream as createWriteStream$1, existsSync, fsyncSync, mkdirSync, openSync, promises, readFileSync, readSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync, writeSync } from "node:fs";
26
26
  import * as path$8 from "node:path";
27
27
  import path, { basename, dirname as dirname$1, extname, isAbsolute, join, posix, relative, resolve, sep, win32 } from "node:path";
28
28
  import { z } from "zod";
@@ -9190,7 +9190,7 @@ var AnthropicChatProvider = class {
9190
9190
  const kwargs = {
9191
9191
  thinking: {
9192
9192
  type: "enabled",
9193
- budget_tokens: budgetTokensForEffort(effectiveEffort)
9193
+ budget_tokens: budgetTokensForEffort(effectiveEffort === "xhigh" || effectiveEffort === "max" ? "high" : effectiveEffort)
9194
9194
  },
9195
9195
  betaFeatures: newBetas
9196
9196
  };
@@ -37877,6 +37877,7 @@ function messagesToGoogleGenAIContents(messages) {
37877
37877
  toolMessages.push(toolMsg);
37878
37878
  j += 1;
37879
37879
  }
37880
+ if (toolMessages.length === 0) throw new ChatProviderError(`Missing tool responses for ids: ${expectedToolCallIds.join(", ")}`);
37880
37881
  if (toolMessages.length > 0) {
37881
37882
  const toolMsgById = /* @__PURE__ */ new Map();
37882
37883
  const seenToolCallIds = /* @__PURE__ */ new Set();
@@ -48773,6 +48774,7 @@ var OpenAILegacyChatProvider = class {
48773
48774
  _defaultHeaders;
48774
48775
  _reasoningKey;
48775
48776
  _reasoningEffort;
48777
+ _thinkingExplicitlyOff = false;
48776
48778
  _generationKwargs;
48777
48779
  _toolMessageConversion;
48778
48780
  _client;
@@ -48821,7 +48823,7 @@ var OpenAILegacyChatProvider = class {
48821
48823
  for (const msg of normalizedHistory) messages.push(convertMessage$1(msg, this._reasoningKey, this._toolMessageConversion));
48822
48824
  const kwargs = { ...this._generationKwargs };
48823
48825
  let reasoningEffort = this._reasoningEffort;
48824
- if (reasoningEffort === void 0 && kwargs["reasoning_effort"] === void 0) {
48826
+ if (!this._thinkingExplicitlyOff && reasoningEffort === void 0 && kwargs["reasoning_effort"] === void 0) {
48825
48827
  if (history.some((message) => message.content.some((part) => part.type === "think"))) reasoningEffort = "medium";
48826
48828
  }
48827
48829
  for (const key of Object.keys(kwargs)) if (kwargs[key] === void 0) delete kwargs[key];
@@ -48844,6 +48846,7 @@ var OpenAILegacyChatProvider = class {
48844
48846
  const reasoningEffort = thinkingEffortToReasoningEffort(effort);
48845
48847
  const clone = this._clone();
48846
48848
  clone._reasoningEffort = reasoningEffort;
48849
+ clone._thinkingExplicitlyOff = effort === "off";
48847
48850
  return clone;
48848
48851
  }
48849
48852
  withGenerationKwargs(kwargs) {
@@ -56837,7 +56840,10 @@ var MemoryMemoStore = class MemoryMemoStore {
56837
56840
  initPromise = null;
56838
56841
  async init() {
56839
56842
  if (this.initPromise) return this.initPromise;
56840
- this.initPromise = this._doInit();
56843
+ this.initPromise = this._doInit().catch((error) => {
56844
+ this.initPromise = null;
56845
+ throw error;
56846
+ });
56841
56847
  return this.initPromise;
56842
56848
  }
56843
56849
  async _doInit() {
@@ -56853,6 +56859,11 @@ var MemoryMemoStore = class MemoryMemoStore {
56853
56859
  }
56854
56860
  /** Close the database handle and checkpoint WAL. */
56855
56861
  close() {
56862
+ if (this.embeddingTimer !== void 0) {
56863
+ clearTimeout(this.embeddingTimer);
56864
+ this.embeddingTimer = void 0;
56865
+ }
56866
+ this.embeddingQueue.clear();
56856
56867
  if (this.db !== void 0) {
56857
56868
  this.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
56858
56869
  this.db.close();
@@ -57126,6 +57137,7 @@ var MemoryMemoStore = class MemoryMemoStore {
57126
57137
  try {
57127
57138
  for (const memo of memos) {
57128
57139
  const row = insert.get(memo.id, memo.sourceSessionId, memo.sourceSessionTitle ?? null, memo.userNeed, memo.approach, memo.outcome, memo.whatFailed, memo.whatWorked, memo.extractionSource, memo.recordedAt, memo.projectDir ?? "", JSON.stringify(memo.tags ?? []));
57140
+ if (row === void 0) continue;
57129
57141
  insertFts.run(row.rowid, toFtsText(memo.userNeed), toFtsText(memo.approach), toFtsText(memo.whatFailed), toFtsText(memo.whatWorked), toFtsText(memo.sourceSessionTitle ?? ""));
57130
57142
  }
57131
57143
  this.db.exec("COMMIT");
@@ -57188,6 +57200,7 @@ var MemoryMemoStore = class MemoryMemoStore {
57188
57200
  const updateFts = this.db.prepare(`INSERT INTO memos_fts(rowid, user_need, approach, what_failed, what_worked, source_session_title)
57189
57201
  VALUES (?, ?, ?, ?, ?, ?)`);
57190
57202
  const deleteFts = this.db.prepare("INSERT INTO memos_fts(memos_fts, rowid) VALUES ('delete', ?)");
57203
+ const deleteEmbedding = this.db.prepare("DELETE FROM memory_embeddings WHERE memory_id = ?");
57191
57204
  this.db.exec("BEGIN TRANSACTION");
57192
57205
  try {
57193
57206
  const oldRow = selectRow.get(id);
@@ -57202,7 +57215,9 @@ var MemoryMemoStore = class MemoryMemoStore {
57202
57215
  }
57203
57216
  deleteFts.run(oldRow.rowid);
57204
57217
  updateFts.run(row.rowid, toFtsText(updated.userNeed), toFtsText(updated.approach), toFtsText(updated.whatFailed), toFtsText(updated.whatWorked), toFtsText(updated.sourceSessionTitle ?? ""));
57218
+ deleteEmbedding.run(id);
57205
57219
  this.db.exec("COMMIT");
57220
+ this.scheduleEmbedding(updated);
57206
57221
  return true;
57207
57222
  } catch {
57208
57223
  this.db.exec("ROLLBACK");
@@ -57613,6 +57628,7 @@ async function applyConsolidation(store, plan) {
57613
57628
  whatWorked: worked || "none",
57614
57629
  whatFailed: failed || "none",
57615
57630
  tags: normalizeTags(plan.resolved.flatMap((m) => m.tags ?? [])),
57631
+ projectDir: plan.resolved[0].projectDir,
57616
57632
  extractionSource: "compaction"
57617
57633
  });
57618
57634
  await store.append(archive);
@@ -57631,6 +57647,7 @@ async function applyConsolidation(store, plan) {
57631
57647
  whatWorked: worked || "none",
57632
57648
  whatFailed: failed || "none",
57633
57649
  tags: normalizeTags(plan.stale.flatMap((m) => m.tags ?? [])),
57650
+ projectDir: plan.stale[0].projectDir,
57634
57651
  extractionSource: "compaction"
57635
57652
  });
57636
57653
  await store.append(archive);
@@ -57649,6 +57666,7 @@ async function applyConsolidation(store, plan) {
57649
57666
  whatFailed: group.merged.whatFailed,
57650
57667
  whatWorked: group.merged.whatWorked,
57651
57668
  tags: group.merged.tags ?? mergedTags,
57669
+ projectDir: newest.projectDir,
57652
57670
  extractionSource: "compaction"
57653
57671
  });
57654
57672
  await store.append(merged);
@@ -58334,7 +58352,7 @@ var KnowledgeLookupTool = class {
58334
58352
  const llm = { generate: async (systemPrompt, userPrompt) => {
58335
58353
  return this.agent.generateText(systemPrompt, userPrompt);
58336
58354
  } };
58337
- const { multiSearchWithTrace } = await import("./src-DA9VZFf3.mjs");
58355
+ const { multiSearchWithTrace } = await import("./src-B2kaYK-M.mjs");
58338
58356
  const { results, trace } = await multiSearchWithTrace(store, llm, query, { topK });
58339
58357
  if (results.length === 0) return {
58340
58358
  isError: false,
@@ -62525,8 +62543,8 @@ function parseSkillText(options) {
62525
62543
  if (!isRecord$6(frontmatter)) throw new SkillParseError(`Frontmatter in ${options.skillMdPath} must be a mapping at the top level`);
62526
62544
  const metadata = normalizeMetadata(frontmatter);
62527
62545
  if (!isSupportedSkillType(metadata.type)) throw new UnsupportedSkillTypeError(metadata.type ?? String(frontmatter["type"]));
62528
- const name = nonEmptyString$2(metadata.name);
62529
- const description = nonEmptyString$2(metadata.description);
62546
+ const name = nonEmptyString$1(metadata.name);
62547
+ const description = nonEmptyString$1(metadata.description);
62530
62548
  if (isDirectorySkill && (name === void 0 || description === void 0)) throw new SkillParseError(`Missing required frontmatter field ${name === void 0 ? "\"name\"" : "\"description\""} in ${options.skillMdPath}`);
62531
62549
  const skillPath = resolve$1(options.skillMdPath);
62532
62550
  const content = parsed.body.trim();
@@ -62586,11 +62604,11 @@ function normalizeMetadata(raw) {
62586
62604
  const key = METADATA_ALIASES[rawKey] ?? rawKey;
62587
62605
  out[key] = value;
62588
62606
  }
62589
- const type = nonEmptyString$2(out["type"]);
62607
+ const type = nonEmptyString$1(out["type"]);
62590
62608
  if (type !== void 0) out["type"] = type;
62591
- const name = nonEmptyString$2(out["name"]);
62609
+ const name = nonEmptyString$1(out["name"]);
62592
62610
  if (name !== void 0) out["name"] = name;
62593
- const description = nonEmptyString$2(out["description"]);
62611
+ const description = nonEmptyString$1(out["description"]);
62594
62612
  if (description !== void 0) out["description"] = description;
62595
62613
  return out;
62596
62614
  }
@@ -62632,7 +62650,7 @@ function tokenizeArgs(raw) {
62632
62650
  if (hasContent) out.push(current);
62633
62651
  return out;
62634
62652
  }
62635
- function nonEmptyString$2(value) {
62653
+ function nonEmptyString$1(value) {
62636
62654
  return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
62637
62655
  }
62638
62656
  function isRecord$6(value) {
@@ -69634,17 +69652,13 @@ const HookDefSchema = z.object({
69634
69652
  command: z.string().min(1),
69635
69653
  timeout: z.number().int().min(1).max(600).optional()
69636
69654
  }).strict();
69637
- const ScreamCliServiceConfigSchema = z.object({
69638
- baseUrl: z.string().optional(),
69639
- apiKey: z.string().optional(),
69640
- oauth: OAuthRefSchema.optional(),
69641
- customHeaders: StringRecordSchema.optional()
69642
- });
69643
69655
  const DuckDuckGoConfigSchema = z.object({ enabled: z.boolean().default(true) });
69656
+ const DomesticSearchConfigSchema = z.object({ enabled: z.boolean().default(true) });
69644
69657
  const ServicesConfigSchema = z.object({
69645
- screamCliSearch: ScreamCliServiceConfigSchema.optional(),
69646
- screamCliFetch: ScreamCliServiceConfigSchema.optional(),
69647
- duckduckgo: DuckDuckGoConfigSchema.optional()
69658
+ duckduckgo: DuckDuckGoConfigSchema.optional(),
69659
+ sogou: DomesticSearchConfigSchema.optional(),
69660
+ so360: DomesticSearchConfigSchema.optional(),
69661
+ baidu: DomesticSearchConfigSchema.optional()
69648
69662
  });
69649
69663
  const McpServerCommonFields = {
69650
69664
  enabled: z.boolean().optional(),
@@ -69718,12 +69732,13 @@ const ThinkingConfigPatchSchema = ThinkingConfigSchema.partial();
69718
69732
  const PermissionConfigPatchSchema = PermissionConfigSchema.partial();
69719
69733
  const LoopControlPatchSchema = LoopControlSchema.partial();
69720
69734
  const BackgroundConfigPatchSchema = BackgroundConfigSchema.partial();
69721
- const ScreamCliServiceConfigPatchSchema = ScreamCliServiceConfigSchema.partial();
69722
69735
  const DuckDuckGoConfigPatchSchema = DuckDuckGoConfigSchema.partial();
69736
+ const DomesticSearchConfigPatchSchema = DomesticSearchConfigSchema.partial();
69723
69737
  const ServicesConfigPatchSchema = z.object({
69724
- screamCliSearch: ScreamCliServiceConfigPatchSchema.optional(),
69725
- screamCliFetch: ScreamCliServiceConfigPatchSchema.optional(),
69726
- duckduckgo: DuckDuckGoConfigPatchSchema.optional()
69738
+ duckduckgo: DuckDuckGoConfigPatchSchema.optional(),
69739
+ sogou: DomesticSearchConfigPatchSchema.optional(),
69740
+ so360: DomesticSearchConfigPatchSchema.optional(),
69741
+ baidu: DomesticSearchConfigPatchSchema.optional()
69727
69742
  });
69728
69743
  const ScreamConfigPatchSchema = z.object({
69729
69744
  providers: z.record(z.string(), ProviderConfigPatchSchema).optional(),
@@ -76673,7 +76688,8 @@ var ReadTool = class {
76673
76688
  hasLf: false,
76674
76689
  hasLoneCr: false
76675
76690
  };
76676
- const rawBuffer = [];
76691
+ const rawHash = createHash("sha256");
76692
+ const normalizedHash = createHash("sha256");
76677
76693
  let currentLineNo = 0;
76678
76694
  let maxLinesReached = false;
76679
76695
  let collectionClosed = false;
@@ -76684,7 +76700,8 @@ var ReadTool = class {
76684
76700
  };
76685
76701
  currentLineNo += 1;
76686
76702
  updateLineEndingFlags(flags, rawLine);
76687
- rawBuffer.push(rawLine);
76703
+ rawHash.update(rawLine);
76704
+ normalizedHash.update(rawLine.replaceAll("\r\n", "\n"));
76688
76705
  if (collectionClosed) {
76689
76706
  if (effectiveLimit >= 1e3 && currentLineNo >= lineOffset) maxLinesReached = true;
76690
76707
  continue;
@@ -76702,7 +76719,7 @@ var ReadTool = class {
76702
76719
  if (selectedEntries.length >= effectiveLimit) collectionClosed = true;
76703
76720
  }
76704
76721
  const lineEndingStyle = lineEndingStyleFromFlags(flags);
76705
- const anchor = computeAnchor(toModelTextView(rawBuffer.join("")).text);
76722
+ const anchor = (lineEndingStyle === "crlf" ? normalizedHash : rawHash).digest("hex").slice(0, 8);
76706
76723
  const renderedLines = [];
76707
76724
  const truncatedLineNumbers = [];
76708
76725
  let bytes = 0;
@@ -76742,7 +76759,8 @@ var ReadTool = class {
76742
76759
  hasLf: false,
76743
76760
  hasLoneCr: false
76744
76761
  };
76745
- const rawBuffer = [];
76762
+ const rawHash = createHash("sha256");
76763
+ const normalizedHash = createHash("sha256");
76746
76764
  let currentLineNo = 0;
76747
76765
  for await (const rawLine of this.jian.readLines(safePath, { errors: "strict" })) {
76748
76766
  if (containsNulByte(rawLine)) return {
@@ -76751,7 +76769,8 @@ var ReadTool = class {
76751
76769
  };
76752
76770
  currentLineNo += 1;
76753
76771
  updateLineEndingFlags(flags, rawLine);
76754
- rawBuffer.push(rawLine);
76772
+ rawHash.update(rawLine);
76773
+ normalizedHash.update(rawLine.replaceAll("\r\n", "\n"));
76755
76774
  entries.push({
76756
76775
  lineNo: currentLineNo,
76757
76776
  rawContent: stripTrailingLf(rawLine)
@@ -76759,7 +76778,7 @@ var ReadTool = class {
76759
76778
  if (entries.length > tailCount) entries.shift();
76760
76779
  }
76761
76780
  const lineEndingStyle = lineEndingStyleFromFlags(flags);
76762
- const anchor = computeAnchor(toModelTextView(rawBuffer.join("")).text);
76781
+ const anchor = (lineEndingStyle === "crlf" ? normalizedHash : rawHash).digest("hex").slice(0, 8);
76763
76782
  let renderedCandidates = entries.slice(0, effectiveLimit).map((entry) => {
76764
76783
  return {
76765
76784
  entry,
@@ -78086,7 +78105,7 @@ var BashTool = class {
78086
78105
  const info = backgroundManager.getTask(taskId);
78087
78106
  if (info && info.status === "running") backgroundManager.stop(taskId, "Timed out");
78088
78107
  })();
78089
- }, timeoutMs);
78108
+ }, timeoutMs).unref();
78090
78109
  const status = backgroundManager.getTask(taskId).status;
78091
78110
  const builder = new ToolResultBuilder();
78092
78111
  builder.write(`task_id: ${taskId}\npid: ${String(proc.pid)}\ndescription: ${args.description.trim()}\nstatus: ${status}\nautomatic_notification: true\nnext_step: You will be automatically notified when it completes.
@@ -78330,9 +78349,12 @@ var WebSearchTool = class {
78330
78349
  execute: (ctx) => this.execution(args, ctx)
78331
78350
  };
78332
78351
  }
78333
- async execution(args, { toolCallId }) {
78352
+ async execution(args, { signal, toolCallId }) {
78334
78353
  try {
78335
- const opts = { toolCallId };
78354
+ const opts = {
78355
+ signal,
78356
+ toolCallId
78357
+ };
78336
78358
  if (args.limit !== void 0) opts.limit = args.limit;
78337
78359
  if (args.include_content !== void 0) opts.includeContent = args.include_content;
78338
78360
  const results = await this.provider.search(args.query, opts);
@@ -79152,7 +79174,10 @@ var FullCompaction = class {
79152
79174
  if (this.compactionCountInTurn > this.strategy.maxCompactionPerTurn) return;
79153
79175
  if (this.agent.records.restoring) return;
79154
79176
  const compactedCount = this.strategy.computeCompactCount(this.agent.context.history, data.source);
79155
- if (compactedCount === 0) throw new ScreamError(ErrorCodes.COMPACTION_UNABLE, "No prefix that can be compacted in current history.");
79177
+ if (compactedCount === 0) {
79178
+ if (data.source === "manual") throw new ScreamError(ErrorCodes.COMPACTION_UNABLE, "No prefix that can be compacted in current history.");
79179
+ return;
79180
+ }
79156
79181
  this.agent.records.logRecord({
79157
79182
  type: "full_compaction.begin",
79158
79183
  ...data
@@ -80881,7 +80906,8 @@ var ContextMemory = class {
80881
80906
  toolCalls: [],
80882
80907
  origin: { kind: "compaction_summary" }
80883
80908
  }, ...this._history.slice(summary.compactedCount)];
80884
- this.openSteps.clear();
80909
+ const survivingMessages = new Set(this._history);
80910
+ for (const [uuid, message] of this.openSteps) if (!survivingMessages.has(message)) this.openSteps.delete(uuid);
80885
80911
  this.flushDeferredMessagesIfToolExchangeClosed();
80886
80912
  this._tokenCount = summary.tokensAfter;
80887
80913
  this.tokenCountCoveredMessageCount = this._history.length;
@@ -92274,8 +92300,7 @@ function parseToolCallArguments(raw) {
92274
92300
  */
92275
92301
  function repairTruncatedJson(raw) {
92276
92302
  let repaired = raw.trimEnd();
92277
- let braceOpen = 0;
92278
- let bracketOpen = 0;
92303
+ const openStack = [];
92279
92304
  let inString = false;
92280
92305
  let stringHasEscape = false;
92281
92306
  let lastNonWhitespace = "";
@@ -92293,24 +92318,16 @@ function repairTruncatedJson(raw) {
92293
92318
  }
92294
92319
  stringHasEscape = false;
92295
92320
  if (inString) continue;
92296
- if (ch === "{") braceOpen++;
92297
- if (ch === "}") braceOpen--;
92298
- if (ch === "[") bracketOpen++;
92299
- if (ch === "]") bracketOpen--;
92321
+ if (ch === "{") openStack.push("}");
92322
+ else if (ch === "[") openStack.push("]");
92323
+ else if (ch === "}" || ch === "]") {
92324
+ if (openStack.length > 0 && openStack[openStack.length - 1] === ch) openStack.pop();
92325
+ }
92300
92326
  if (!/\s/.test(ch)) lastNonWhitespace = ch;
92301
92327
  }
92302
92328
  if (inString) repaired += "\"";
92303
92329
  if (lastNonWhitespace === ",") repaired += "null";
92304
- const closers = [];
92305
- while (bracketOpen > 0) {
92306
- closers.push("]");
92307
- bracketOpen--;
92308
- }
92309
- while (braceOpen > 0) {
92310
- closers.push("}");
92311
- braceOpen--;
92312
- }
92313
- if (closers.length > 0) repaired += closers.join("");
92330
+ for (let i = openStack.length - 1; i >= 0; i--) repaired += openStack[i];
92314
92331
  return repaired;
92315
92332
  }
92316
92333
  function validateExecutableToolArgs(tool, args) {
@@ -97920,7 +97937,7 @@ var TurnFlow = class {
97920
97937
  });
97921
97938
  return null;
97922
97939
  }
97923
- if (this.turnId === -1) this.agent.dreamTracker.init().then(() => this.agent.dreamTracker.recordNewSession());
97940
+ if (this.turnId === -1) this.agent.dreamTracker.init().then(() => this.agent.dreamTracker.recordNewSession()).catch(() => {});
97924
97941
  const turnId = this.allocateTurnId();
97925
97942
  const controller = new AbortController();
97926
97943
  const promise = this.turnWorker(turnId, input, origin, controller.signal);
@@ -99951,17 +99968,18 @@ function permissionRuleToToml(rule) {
99951
99968
  }
99952
99969
  function servicesToToml(services, rawServices) {
99953
99970
  const out = cloneRecord(rawServices);
99954
- if (services.screamCliSearch !== void 0) out["scream_cli_search"] = serviceToToml(services.screamCliSearch);
99955
- else delete out["scream_cli_search"];
99956
- if (services.screamCliFetch !== void 0) out["scream_cli_fetch"] = serviceToToml(services.screamCliFetch);
99957
- else delete out["scream_cli_fetch"];
99958
- return out;
99959
- }
99960
- function serviceToToml(service) {
99961
- const out = {};
99962
- for (const [key, value] of Object.entries(service)) if (key === "oauth" && value !== void 0) out[camelToSnake(key)] = oauthToToml(value);
99963
- else if (key === "customHeaders" && value !== void 0) out[camelToSnake(key)] = cloneUnknown(value);
99964
- else setDefined(out, camelToSnake(key), value);
99971
+ for (const key of [
99972
+ "duckduckgo",
99973
+ "sogou",
99974
+ "so360",
99975
+ "baidu"
99976
+ ]) {
99977
+ const toggle = services[key];
99978
+ if (toggle?.enabled !== void 0) out[key] = {
99979
+ ...isPlainObject$1(out[key]) ? out[key] : {},
99980
+ enabled: toggle.enabled
99981
+ };
99982
+ }
99965
99983
  return out;
99966
99984
  }
99967
99985
  function loopControlToToml(loopControl, rawLoopControl) {
@@ -104227,7 +104245,7 @@ var Session$1 = class {
104227
104245
  });
104228
104246
  await this.options.jian.writeText(this.metadataPath, text);
104229
104247
  };
104230
- this.writeMetadataPromise = this.writeMetadataPromise.then(() => write());
104248
+ this.writeMetadataPromise = this.writeMetadataPromise.then(() => write(), () => write());
104231
104249
  return this.writeMetadataPromise;
104232
104250
  }
104233
104251
  async readMetadata() {
@@ -117201,6 +117219,7 @@ setPrototypeOf(Document, Document$1).prototype = Document$1.prototype;
117201
117219
  */
117202
117220
  const DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36";
117203
117221
  const DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
117222
+ const FETCH_TIMEOUT_MS = 3e4;
117204
117223
  const parseHTML = parseHTML$1;
117205
117224
  /**
117206
117225
  * SSRF guard — reject non-http(s) schemes and (by default) any hostname
@@ -117263,7 +117282,7 @@ function isPrivateIp(ip) {
117263
117282
  }
117264
117283
  return lower === "::1" || lower === "::" || lower.startsWith("fe80:") || lower.startsWith("fc") || lower.startsWith("fd");
117265
117284
  }
117266
- function cacheKey$1(url, allowPrivate, maxBytes, userAgent) {
117285
+ function cacheKey(url, allowPrivate, maxBytes, userAgent) {
117267
117286
  return `local:${url}:${String(allowPrivate)}:${String(maxBytes)}:${userAgent}`;
117268
117287
  }
117269
117288
  const defaultDnsLookup = async (hostname) => {
@@ -117285,7 +117304,7 @@ var LocalFetchURLProvider = class {
117285
117304
  this.dnsLookup = options.dnsLookup ?? defaultDnsLookup;
117286
117305
  }
117287
117306
  async fetch(url, _options) {
117288
- const key = cacheKey$1(url, this.allowPrivateAddresses, this.maxBytes, this.userAgent);
117307
+ const key = cacheKey(url, this.allowPrivateAddresses, this.maxBytes, this.userAgent);
117289
117308
  const cached = this.cache.get(key);
117290
117309
  if (cached !== void 0) return cached;
117291
117310
  await assertSafeFetchTarget(url, this.allowPrivateAddresses, this.dnsLookup);
@@ -117296,7 +117315,8 @@ var LocalFetchURLProvider = class {
117296
117315
  async fetchFresh(url) {
117297
117316
  const response = await this.fetchImpl(url, {
117298
117317
  method: "GET",
117299
- headers: { "User-Agent": this.userAgent }
117318
+ headers: { "User-Agent": this.userAgent },
117319
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
117300
117320
  });
117301
117321
  if (response.status >= 400) {
117302
117322
  await response.body?.cancel().catch(() => {});
@@ -117305,7 +117325,10 @@ var LocalFetchURLProvider = class {
117305
117325
  const contentLengthRaw = response.headers.get("content-length");
117306
117326
  if (contentLengthRaw !== null) {
117307
117327
  const cl = Number(contentLengthRaw);
117308
- if (Number.isFinite(cl) && cl > this.maxBytes) throw new Error(`Response body too large: ${String(cl)} bytes exceeds maxBytes (${String(this.maxBytes)}).`);
117328
+ if (Number.isFinite(cl) && cl > this.maxBytes) {
117329
+ await response.body?.cancel().catch(() => {});
117330
+ throw new Error(`Response body too large: ${String(cl)} bytes exceeds maxBytes (${String(this.maxBytes)}).`);
117331
+ }
117309
117332
  }
117310
117333
  const body = await response.text();
117311
117334
  const actualBytes = Buffer.byteLength(body, "utf8");
@@ -117340,268 +117363,220 @@ var LocalFetchURLProvider = class {
117340
117363
  }
117341
117364
  };
117342
117365
  //#endregion
117343
- //#region ../../packages/agent-core/src/tools/providers/scream-cli-fetch-url.ts
117366
+ //#region ../../packages/agent-core/src/tools/providers/search-timeout.ts
117344
117367
  /**
117345
- * ScreamCliFetchURLProvider host-side UrlFetcher.
117368
+ * Hard timeout for outbound web-search requests (omp pattern).
117346
117369
  *
117347
- * Flow:
117348
- * 1. Try ScreamCli coding-fetch service (POST {url}, Bearer token from a
117349
- * narrow token provider, Accept: text/markdown, host-provided headers).
117350
- * 2. ScreamCli 200 return the body as `extracted` content (the
117351
- * service has already extracted the main page text on its side).
117352
- * 3. Any ScreamCli failure — non-200, network error, or token
117353
- * refresh failure delegate to `localFallback`, forwarding its
117354
- * content kind, so the LLM still gets *something* when the service
117355
- * is down.
117356
- * 4. If localFallback also throws → propagate that error.
117357
- */
117358
- function cacheKey(baseUrl, url) {
117359
- return `cli:${baseUrl}:${url}`;
117360
- }
117361
- var ScreamCliFetchURLProvider = class {
117362
- tokenProvider;
117363
- apiKey;
117364
- baseUrl;
117365
- defaultHeaders;
117366
- customHeaders;
117367
- localFallback;
117368
- fetchImpl;
117369
- cache;
117370
- constructor(options) {
117371
- this.tokenProvider = options.tokenProvider;
117372
- this.apiKey = options.apiKey;
117373
- this.baseUrl = options.baseUrl;
117374
- this.defaultHeaders = options.defaultHeaders ?? {};
117375
- this.customHeaders = options.customHeaders ?? {};
117376
- this.localFallback = options.localFallback;
117377
- this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
117378
- this.cache = options.cache ?? new FetchCache();
117379
- }
117380
- async fetch(url, options) {
117381
- const key = cacheKey(this.baseUrl, url);
117382
- const cached = this.cache.get(key);
117383
- if (cached !== void 0) return cached;
117384
- const result = await this.fetchFresh(url, options?.toolCallId);
117385
- this.cache.set(key, result);
117386
- return result;
117387
- }
117388
- async fetchFresh(url, toolCallId) {
117389
- try {
117390
- return {
117391
- content: await this.fetchViaScreamCli(url, toolCallId),
117392
- kind: "extracted"
117393
- };
117394
- } catch {
117395
- return this.localFallback.fetch(url, { toolCallId });
117396
- }
117397
- }
117398
- async fetchViaScreamCli(url, toolCallId) {
117399
- const bodyJson = JSON.stringify({ url });
117400
- const response = await this.post(bodyJson, toolCallId);
117401
- if (response.status !== 200) {
117402
- let detail = "";
117403
- try {
117404
- detail = await response.text();
117405
- } catch {}
117406
- throw new HttpFetchError(response.status, `ScreamCli fetch request failed: HTTP ${String(response.status)}. ${detail}`.trim());
117407
- }
117408
- return response.text();
117409
- }
117410
- async post(bodyJson, toolCallId) {
117411
- const accessToken = await this.resolveApiKey();
117412
- return this.fetchImpl(this.baseUrl, {
117413
- method: "POST",
117414
- headers: {
117415
- ...this.defaultHeaders,
117416
- Authorization: `Bearer ${accessToken}`,
117417
- Accept: "text/markdown",
117418
- "Content-Type": "application/json",
117419
- ...toolCallId !== void 0 && toolCallId.length > 0 ? { "X-Msh-Tool-Call-Id": toolCallId } : {},
117420
- ...this.customHeaders
117421
- },
117422
- body: bodyJson
117423
- });
117424
- }
117425
- async resolveApiKey() {
117426
- if (this.tokenProvider !== void 0) try {
117427
- const token = await this.tokenProvider.getAccessToken();
117428
- if (token.trim().length > 0) return token;
117429
- if (this.apiKey !== void 0 && this.apiKey.length > 0) return this.apiKey;
117430
- } catch (error) {
117431
- if (this.apiKey !== void 0 && this.apiKey.length > 0) return this.apiKey;
117432
- throw error;
117433
- }
117434
- if (this.apiKey !== void 0 && this.apiKey.length > 0) return this.apiKey;
117435
- throw new Error("ScreamCli fetch service is not configured: missing API key or token provider.");
117436
- }
117437
- };
117370
+ * A stalled TCP/TLS connection can keep an undici fetch pending for minutes;
117371
+ * composing the caller's signal with a fixed ceiling guarantees the request
117372
+ * settles. Fires as a `TimeoutError` DOMException, which the tool layer maps
117373
+ * to "Search timed out".
117374
+ */
117375
+ const SEARCH_HARD_TIMEOUT_MS = 6e4;
117376
+ function withHardTimeout(signal, ms = SEARCH_HARD_TIMEOUT_MS) {
117377
+ const timeout = AbortSignal.timeout(ms);
117378
+ return signal !== void 0 ? AbortSignal.any([signal, timeout]) : timeout;
117379
+ }
117438
117380
  //#endregion
117439
117381
  //#region ../../packages/agent-core/src/tools/providers/duckduckgo-search.ts
117382
+ const DUCKDUCKGO_HTML_URL = "https://html.duckduckgo.com/html/";
117383
+ /**
117384
+ * Browser-like UA so DDG serves the standard results page instead of the
117385
+ * mobile/noscript variants, and is less likely to trip bot detection.
117386
+ * DDG answers automation it suspects with HTTP 202 plus an anomaly modal;
117387
+ * the body check (not the status) is the reliable signal.
117388
+ */
117389
+ const BROWSER_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36";
117440
117390
  var DuckDuckGoSearchProvider = class {
117391
+ name = "duckduckgo";
117441
117392
  fetchImpl;
117442
117393
  constructor(options = {}) {
117443
117394
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
117444
117395
  }
117445
117396
  async search(query, options) {
117446
117397
  const limit = options?.limit ?? 5;
117447
- let results = await this.searchViaApi(query, limit);
117448
- if (results.length === 0) results = await this.searchViaLite(query, limit);
117449
- return results;
117450
- }
117451
- /**
117452
- * Query the DuckDuckGo Instant Answer API.
117453
- *
117454
- * Returns up to `limit` results built from the abstract and flattened
117455
- * RelatedTopics. Returns `[]` on any failure (network error, non-2xx,
117456
- * parse failure) so callers can fall through to the next method.
117457
- */
117458
- async searchViaApi(query, limit) {
117459
- const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1`;
117460
- let response;
117461
- try {
117462
- response = await this.fetchImpl(url);
117463
- } catch {
117464
- return [];
117465
- }
117466
- if (response.status !== 200) {
117467
- await drainBody(response);
117468
- return [];
117469
- }
117470
- let json;
117471
- try {
117472
- json = await response.json();
117473
- } catch {
117474
- return [];
117475
- }
117476
- return this.buildApiResults(json, limit);
117477
- }
117478
- buildApiResults(json, limit) {
117479
- const results = [];
117480
- if (json.AbstractText && json.AbstractURL && json.AbstractText.trim().length > 0) results.push({
117481
- title: json.Heading && json.Heading.trim().length > 0 ? json.Heading : json.AbstractSource ?? "DuckDuckGo",
117482
- url: json.AbstractURL,
117483
- snippet: json.AbstractText
117484
- });
117485
- if (json.RelatedTopics && json.RelatedTopics.length > 0) {
117486
- const flattened = flattenRelatedTopics(json.RelatedTopics);
117487
- for (const topic of flattened) {
117488
- if (results.length >= limit) break;
117489
- const url = topic.FirstURL ?? "";
117490
- const text = topic.Text ?? "";
117491
- if (url === "" || text === "") continue;
117492
- results.push({
117493
- title: text,
117494
- url,
117495
- snippet: text
117496
- });
117497
- }
117498
- }
117499
- return results.slice(0, limit);
117500
- }
117501
- /**
117502
- * Scrape DuckDuckGo Lite search results.
117503
- *
117504
- * Lite is a minimal HTML table with no JavaScript, designed for basic
117505
- * browsers. It may return a CAPTCHA page from datacenter IPs — in that
117506
- * case we return `[]` so the caller can fall through.
117507
- */
117508
- async searchViaLite(query, limit) {
117509
- const url = `https://lite.duckduckgo.com/lite/?q=${encodeURIComponent(query)}`;
117510
- let response;
117511
- try {
117512
- response = await this.fetchImpl(url);
117513
- } catch {
117514
- return [];
117515
- }
117516
- if (response.status !== 200) {
117517
- await drainBody(response);
117518
- return [];
117519
- }
117520
- let html;
117521
- try {
117522
- html = await response.text();
117523
- } catch {
117524
- return [];
117525
- }
117526
- if (isCaptchaPage(html)) return [];
117527
- return parseLiteResults(html, limit);
117398
+ const form = new URLSearchParams({
117399
+ q: query,
117400
+ kl: "us-en"
117401
+ });
117402
+ form.set("b", "");
117403
+ const response = await this.fetchImpl(DUCKDUCKGO_HTML_URL, {
117404
+ method: "POST",
117405
+ body: form.toString(),
117406
+ headers: {
117407
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
117408
+ "Accept-Language": "en,en-US;q=0.9",
117409
+ "Content-Type": "application/x-www-form-urlencoded",
117410
+ Referer: "https://html.duckduckgo.com/",
117411
+ "Upgrade-Insecure-Requests": "1",
117412
+ "User-Agent": BROWSER_USER_AGENT
117413
+ },
117414
+ signal: withHardTimeout(options?.signal)
117415
+ });
117416
+ const html = await response.text();
117417
+ if (!response.ok && response.status !== 202) throw new Error(`DuckDuckGo request failed: HTTP ${String(response.status)}`);
117418
+ if (isAnomalyResponse(html)) throw new Error("DuckDuckGo blocked the request with a bot-detection challenge (common from datacenter/shared-egress IPs)");
117419
+ return parseHtmlResults(html).slice(0, limit);
117528
117420
  }
117529
117421
  };
117530
- /**
117531
- * Recursively flatten DuckDuckGo RelatedTopics.
117532
- *
117533
- * The API nests topics in two ways:
117534
- * - A direct topic has `FirstURL` + `Text` (may lack `Topics`).
117535
- * - A category group has a `Name` and nested `Topics[]`.
117536
- * We walk both shapes, returning a flat list of leaf topics.
117537
- */
117538
- function flattenRelatedTopics(topics) {
117539
- const out = [];
117540
- for (const topic of topics) if (topic.Topics && topic.Topics.length > 0) out.push(...flattenRelatedTopics(topic.Topics));
117541
- else if (topic.FirstURL) out.push(topic);
117542
- return out;
117422
+ /** `true` when DDG returned the bot-challenge modal instead of results. */
117423
+ function isAnomalyResponse(html) {
117424
+ return html.includes("anomaly-modal") || html.includes("anomaly.js");
117543
117425
  }
117544
117426
  /**
117545
- * Return true if the HTML looks like a DuckDuckGo bot-detection / CAPTCHA page.
117427
+ * Each result lives in a `<div class="result …">` container with
117428
+ * `<a class="result__a">` for the title link and an optional
117429
+ * `<a|div|span class="result__snippet">` sibling for the preview text.
117430
+ * Sponsored rows, missing snippets, and the pagination row are tolerated.
117546
117431
  */
117547
- function isCaptchaPage(html) {
117548
- return html.includes("anomaly-modal") || html.includes("challenge-form") || html.includes("g-recaptcha") || html.includes("data-testid=\"anomaly-modal\"");
117432
+ const RESULT_BLOCK_RE = /<div\b[^>]*\bclass="[^"]*\bresult\b[^"]*"[^>]*>([\s\S]*?)(?=<div\b[^>]*\bclass="[^"]*\bresult\b|<div\b[^>]*\bclass="[^"]*\bnav-link\b|$)/g;
117433
+ const RESULT_TITLE_RE = /<a\b[^>]*\bclass="[^"]*\bresult__a\b[^"]*"[^>]*\bhref="([^"]+)"[^>]*>([\s\S]*?)<\/a>/;
117434
+ const RESULT_SNIPPET_RE = /<(?:a|div|span)\b[^>]*\bclass="[^"]*\bresult__snippet\b[^"]*"[^>]*>([\s\S]*?)<\/(?:a|div|span)>/;
117435
+ /** Strip inline tags (DDG wraps query terms in `<b>`) and decode entities. */
117436
+ function decodeHtmlText$1(value) {
117437
+ return value.replace(/<[^>]*>/g, " ").replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code))).replace(/&#x([0-9a-f]+);/gi, (_, code) => String.fromCharCode(Number.parseInt(code, 16))).replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, "\"").replace(/&#39;|&apos;/gi, "'").replace(/\s+/g, " ").trim();
117549
117438
  }
117550
117439
  /**
117551
- * DuckDuckGo Lite result rows are inside `<tr>` blocks with a predictable
117552
- * anchor shape. This regex extracts the `<a class="result-link"...>` block
117553
- * and the following snippet text from the same row.
117554
- *
117555
- * The Lite markup is intentionally minimal (no JS, no CSS classes beyond a
117556
- * handful of utility names), so a DOM parser is unnecessary overhead.
117440
+ * Resolve a DDG result href to the underlying target URL. DDG routes
117441
+ * outbound clicks through `//duckduckgo.com/l/?uddg=<encoded>`; handles
117442
+ * redirect wrappers, protocol-relative links, and plain absolute URLs.
117557
117443
  */
117558
- const LITE_RESULT_RE = /<a\s[^>]*?\bclass\s*=\s*["'][^"']*result-link[^"']*["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi;
117559
- const LITE_SNIPPET_RE = /<td\s[^>]*?\bclass\s*=\s*["'][^"']*result-snippet[^"']*["'][^>]*>([\s\S]*?)<\/td>/gi;
117560
- /** Strip HTML tags and decode common HTML entities. */
117561
- function stripHtmlAndDecode(text) {
117562
- return text.replaceAll(/<[^>]*>/g, "").replaceAll("&amp;", "&").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&quot;", "\"").replaceAll("&#x27;", "'").replaceAll("&#39;", "'").replaceAll("&nbsp;", " ").trim();
117563
- }
117564
- /** Unwrap a DuckDuckGo redirect URL to the real target if possible. */
117565
- function unwrapDdgRedirect(url) {
117566
- const uddgMatch = url.match(/[?&]uddg=([^&]+)/);
117567
- if (uddgMatch?.[1]) try {
117568
- return decodeURIComponent(uddgMatch[1]);
117444
+ function unwrapResultUrl(href) {
117445
+ if (href === "") return void 0;
117446
+ const decoded = href.replace(/&amp;/gi, "&");
117447
+ const wrapMatch = decoded.match(/[?&]uddg=([^&]+)/);
117448
+ if (wrapMatch?.[1] !== void 0) try {
117449
+ return decodeURIComponent(wrapMatch[1]);
117569
117450
  } catch {
117570
- return url;
117451
+ return;
117571
117452
  }
117572
- return url;
117453
+ if (decoded.startsWith("//")) return `https:${decoded}`;
117454
+ if (decoded.startsWith("http://") || decoded.startsWith("https://")) return decoded;
117573
117455
  }
117574
- function parseLiteResults(html, limit) {
117456
+ function parseHtmlResults(html) {
117575
117457
  const results = [];
117576
- const linkMatches = html.matchAll(LITE_RESULT_RE);
117577
- for (const match of linkMatches) {
117578
- if (results.length >= limit) break;
117579
- const href = unwrapDdgRedirect(match[1] ?? "");
117580
- const title = stripHtmlAndDecode(match[2] ?? "");
117581
- if (href === "" || title === "") continue;
117458
+ const seen = /* @__PURE__ */ new Set();
117459
+ for (const match of html.matchAll(RESULT_BLOCK_RE)) {
117460
+ const block = match[1] ?? "";
117461
+ const title = RESULT_TITLE_RE.exec(block);
117462
+ if (title === null) continue;
117463
+ const url = unwrapResultUrl(title[1] ?? "");
117464
+ if (url === void 0 || seen.has(url)) continue;
117465
+ const titleText = decodeHtmlText$1(title[2] ?? "");
117466
+ if (titleText === "") continue;
117467
+ seen.add(url);
117468
+ const snippet = RESULT_SNIPPET_RE.exec(block);
117469
+ const snippetText = snippet !== null ? decodeHtmlText$1(snippet[1] ?? "") : "";
117582
117470
  results.push({
117583
- title,
117584
- url: href,
117585
- snippet: title
117471
+ title: titleText,
117472
+ url,
117473
+ snippet: snippetText !== "" ? snippetText : titleText
117586
117474
  });
117587
117475
  }
117588
- const snippetMatches = html.matchAll(LITE_SNIPPET_RE);
117589
- let snippetIndex = 0;
117590
- for (const match of snippetMatches) {
117591
- if (snippetIndex >= results.length) break;
117592
- const snippet = stripHtmlAndDecode(match[1] ?? "");
117593
- const entry = results[snippetIndex];
117594
- if (entry && snippet.length > 0) entry.snippet = snippet;
117595
- snippetIndex++;
117596
- }
117597
117476
  return results;
117598
117477
  }
117599
- /** Drain a response body so the connection can be reused. */
117600
- async function drainBody(response) {
117601
- try {
117602
- await response.text();
117603
- } catch {}
117478
+ //#endregion
117479
+ //#region ../../packages/agent-core/src/tools/providers/domestic-search.ts
117480
+ const BROWSER_HEADERS = {
117481
+ Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8",
117482
+ "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
117483
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"
117484
+ };
117485
+ /** Strip inline tags and decode the common named/numeric entities. */
117486
+ function decodeHtmlText(value) {
117487
+ return value.replace(/<[^>]*>/g, " ").replace(/&#(\d+);/g, (_, code) => String.fromCharCode(Number(code))).replace(/&#x([0-9a-f]+);/gi, (_, code) => String.fromCharCode(Number.parseInt(code, 16))).replace(/&nbsp;/gi, " ").replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, "\"").replace(/&#39;|&apos;/gi, "'").replace(/\s+/g, " ").trim();
117604
117488
  }
117489
+ async function searchEngine(spec, fetchImpl, query, options) {
117490
+ const limit = options?.limit ?? 5;
117491
+ const response = await fetchImpl(spec.url(query), {
117492
+ headers: BROWSER_HEADERS,
117493
+ signal: withHardTimeout(options?.signal)
117494
+ });
117495
+ const html = await response.text();
117496
+ if (!response.ok) throw new Error(`${spec.label} request failed: HTTP ${String(response.status)}`);
117497
+ if (spec.botMarkers.some((m) => html.includes(m))) throw new Error(`${spec.label} blocked the request with an anti-bot challenge`);
117498
+ const results = [];
117499
+ const seen = /* @__PURE__ */ new Set();
117500
+ for (const match of html.matchAll(spec.blockRe)) {
117501
+ if (results.length >= limit) break;
117502
+ const block = match[1] ?? "";
117503
+ const title = spec.titleRe.exec(block);
117504
+ if (title === null) continue;
117505
+ const rawHref = (title[1] ?? "").replace(/&amp;/gi, "&");
117506
+ const url = spec.normalizeUrl !== void 0 ? spec.normalizeUrl(rawHref) : rawHref;
117507
+ if (url === void 0 || url === "" || seen.has(url)) continue;
117508
+ const titleText = decodeHtmlText(title[2] ?? "");
117509
+ if (titleText === "") continue;
117510
+ seen.add(url);
117511
+ const snippetMatch = spec.snippetRe === void 0 ? null : spec.snippetRe.exec(block);
117512
+ const snippetText = snippetMatch !== null ? decodeHtmlText(snippetMatch[1] ?? "") : "";
117513
+ results.push({
117514
+ title: titleText,
117515
+ url,
117516
+ snippet: snippetText !== "" ? snippetText : titleText
117517
+ });
117518
+ }
117519
+ return results;
117520
+ }
117521
+ const SOGOU = {
117522
+ label: "Sogou",
117523
+ url: (q) => `https://www.sogou.com/web?query=${encodeURIComponent(q)}`,
117524
+ blockRe: /<div\b[^>]*\bclass="[^"]*\b(?:vrwrap|rb)\b[^"]*"[^>]*>([\s\S]*?)(?=<div\b[^>]*\bclass="[^"]*\b(?:vrwrap|rb)\b|$)/g,
117525
+ titleRe: /<h3[^>]*>[\s\S]*?<a[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/,
117526
+ snippetRe: /<(?:p|div)\b[^>]*\bclass="[^"]*\b(?:str-text|str_info|text-layout)\b[^"]*"[^>]*>([\s\S]*?)<\/(?:p|div)>/,
117527
+ normalizeUrl: (href) => {
117528
+ if (href.startsWith("/link?")) return `https://www.sogou.com${href}`;
117529
+ if (href.includes("sogou.com/sogou?")) return void 0;
117530
+ if (href.startsWith("http://") || href.startsWith("https://")) return href;
117531
+ },
117532
+ botMarkers: ["antispider", "异常访问请求"]
117533
+ };
117534
+ var SogouSearchProvider = class {
117535
+ name = "sogou";
117536
+ fetchImpl;
117537
+ constructor(options = {}) {
117538
+ this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
117539
+ }
117540
+ search(query, options) {
117541
+ return searchEngine(SOGOU, this.fetchImpl, query, options);
117542
+ }
117543
+ };
117544
+ const SO360 = {
117545
+ label: "360 Search",
117546
+ url: (q) => `https://www.so.com/s?q=${encodeURIComponent(q)}`,
117547
+ blockRe: /<li\b[^>]*\bclass="[^"]*\bres-list\b[^"]*"[^>]*>([\s\S]*?)(?=<li\b[^>]*\bclass="[^"]*\bres-list\b|$)/g,
117548
+ titleRe: /<h3[^>]*class="[^"]*\bres-title\b[^"]*"[^>]*>[\s\S]*?<a[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/,
117549
+ snippetRe: /<p\b[^>]*\bclass="[^"]*\bres-desc\b[^"]*"[^>]*>([\s\S]*?)<\/p>/,
117550
+ botMarkers: ["安全验证"]
117551
+ };
117552
+ var So360SearchProvider = class {
117553
+ name = "360";
117554
+ fetchImpl;
117555
+ constructor(options = {}) {
117556
+ this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
117557
+ }
117558
+ search(query, options) {
117559
+ return searchEngine(SO360, this.fetchImpl, query, options);
117560
+ }
117561
+ };
117562
+ const BAIDU = {
117563
+ label: "Baidu",
117564
+ url: (q) => `https://www.baidu.com/s?wd=${encodeURIComponent(q)}`,
117565
+ blockRe: /<div\b[^>]*\bclass="[^"]*\bc-container\b[^"]*"[^>]*>([\s\S]*?)(?=<div\b[^>]*\bclass="[^"]*\bc-container\b|$)/g,
117566
+ titleRe: /<h3[^>]*class="[^"]*\bt\b[^"]*"[^>]*>[\s\S]*?<a[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/,
117567
+ snippetRe: /<(?:span|div)\b[^>]*\bclass="[^"]*\b(?:c-abstract|content-right)[^"]*\b[^"]*"[^>]*>([\s\S]*?)<\/(?:span|div)>/,
117568
+ botMarkers: ["百度安全验证", "wappass.baidu.com"]
117569
+ };
117570
+ var BaiduSearchProvider = class {
117571
+ name = "baidu";
117572
+ fetchImpl;
117573
+ constructor(options = {}) {
117574
+ this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
117575
+ }
117576
+ search(query, options) {
117577
+ return searchEngine(BAIDU, this.fetchImpl, query, options);
117578
+ }
117579
+ };
117605
117580
  //#endregion
117606
117581
  //#region ../../packages/agent-core/src/tools/providers/fallback-search.ts
117607
117582
  var FallbackSearchProvider = class {
@@ -117611,94 +117586,31 @@ var FallbackSearchProvider = class {
117611
117586
  this.providers = providers;
117612
117587
  }
117613
117588
  async search(query, options) {
117614
- for (const provider of this.providers) try {
117615
- const results = await provider.search(query, options);
117616
- if (results.length > 0) return results;
117617
- } catch {
117618
- continue;
117619
- }
117620
- return [];
117621
- }
117622
- };
117623
- //#endregion
117624
- //#region ../../packages/agent-core/src/tools/providers/scream-cli-web-search.ts
117625
- var ScreamCliWebSearchProvider = class {
117626
- tokenProvider;
117627
- apiKey;
117628
- baseUrl;
117629
- defaultHeaders;
117630
- customHeaders;
117631
- fetchImpl;
117632
- constructor(options) {
117633
- this.tokenProvider = options.tokenProvider;
117634
- this.apiKey = options.apiKey;
117635
- this.baseUrl = options.baseUrl;
117636
- this.defaultHeaders = options.defaultHeaders ?? {};
117637
- this.customHeaders = options.customHeaders ?? {};
117638
- this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
117639
- }
117640
- async search(query, options) {
117641
- const body = {
117642
- text_query: query,
117643
- limit: options?.limit ?? 5,
117644
- enable_page_crawling: options?.includeContent ?? false,
117645
- timeout_seconds: 30
117646
- };
117647
- const bodyJson = JSON.stringify(body);
117648
- const toolCallId = options?.toolCallId;
117649
- const response = await this.post(bodyJson, toolCallId);
117650
- if (response.status === 401) {
117651
- const detail = await safeReadText(response);
117652
- throw new Error(`ScreamCli search request failed: HTTP 401 (auth/unauthorized). ${detail}`.trim());
117653
- }
117654
- if (response.status !== 200) {
117655
- const detail = await safeReadText(response);
117656
- throw new Error(`ScreamCli search request failed: HTTP ${String(response.status)}. ${detail}`.trim());
117657
- }
117658
- const json = await response.json();
117659
- return (Array.isArray(json.search_results) ? json.search_results : []).map((r) => {
117660
- const out = {
117661
- title: r.title ?? "",
117662
- url: r.url ?? "",
117663
- snippet: r.snippet ?? ""
117664
- };
117665
- if (typeof r.date === "string" && r.date.length > 0) out.date = r.date;
117666
- if (typeof r.content === "string" && r.content.length > 0) out.content = r.content;
117667
- return out;
117668
- });
117669
- }
117670
- async post(bodyJson, toolCallId) {
117671
- const accessToken = await this.resolveApiKey();
117672
- return this.fetchImpl(this.baseUrl, {
117673
- method: "POST",
117674
- headers: {
117675
- ...this.defaultHeaders,
117676
- Authorization: `Bearer ${accessToken}`,
117677
- "Content-Type": "application/json",
117678
- ...toolCallId !== void 0 && toolCallId.length > 0 ? { "X-Msh-Tool-Call-Id": toolCallId } : {},
117679
- ...this.customHeaders
117680
- },
117681
- body: bodyJson
117682
- });
117683
- }
117684
- async resolveApiKey() {
117685
- if (this.tokenProvider !== void 0) try {
117686
- return await this.tokenProvider.getAccessToken();
117687
- } catch (error) {
117688
- if (this.apiKey !== void 0 && this.apiKey.length > 0) return this.apiKey;
117689
- throw error;
117589
+ const failures = [];
117590
+ for (const [index, provider] of this.providers.entries()) {
117591
+ options?.signal?.throwIfAborted();
117592
+ try {
117593
+ const results = await provider.search(query, options);
117594
+ if (results.length > 0) return results;
117595
+ failures.push({
117596
+ provider: provider.name ?? `provider ${String(index + 1)}`,
117597
+ reason: "no results"
117598
+ });
117599
+ } catch (error) {
117600
+ options?.signal?.throwIfAborted();
117601
+ failures.push({
117602
+ provider: provider.name ?? `provider ${String(index + 1)}`,
117603
+ reason: error instanceof Error ? error.message : String(error)
117604
+ });
117605
+ }
117690
117606
  }
117691
- if (this.apiKey !== void 0 && this.apiKey.length > 0) return this.apiKey;
117692
- throw new Error("ScreamCli search service is not configured: missing API key or token provider.");
117607
+ if (failures.every((f) => f.reason === "no results")) return [];
117608
+ const last = failures[failures.length - 1];
117609
+ if (this.providers.length === 1 && last !== void 0) throw new Error(last.reason);
117610
+ const summary = failures.map((f) => `${f.provider}: ${f.reason}`).join("; ");
117611
+ throw new Error(`All web search providers failed — ${summary}`);
117693
117612
  }
117694
117613
  };
117695
- async function safeReadText(response) {
117696
- try {
117697
- return await response.text();
117698
- } catch {
117699
- return "";
117700
- }
117701
- }
117702
117614
  //#endregion
117703
117615
  //#region ../../packages/agent-core/src/session/export/manifest.ts
117704
117616
  const WIRE_PROTOCOL_VERSION = "1.3";
@@ -118888,7 +118800,7 @@ function providerApiKey(provider) {
118888
118800
  case "openai_responses": return providerValue(provider.apiKey, provider.env, "OPENAI_API_KEY");
118889
118801
  case "scream": return providerValue(provider.apiKey, provider.env, "SCREAM_API_KEY");
118890
118802
  case "google-genai": return providerValue(provider.apiKey, provider.env, "GOOGLE_API_KEY");
118891
- case "vertexai": return nonEmptyString$1(provider.apiKey) ?? envValue(provider.env, "VERTEXAI_API_KEY") ?? envValue(provider.env, "GOOGLE_API_KEY");
118803
+ case "vertexai": return nonEmptyString(provider.apiKey) ?? envValue(provider.env, "VERTEXAI_API_KEY") ?? envValue(provider.env, "GOOGLE_API_KEY");
118892
118804
  default: {
118893
118805
  const exhaustive = provider.type;
118894
118806
  throw new ScreamError(ErrorCodes.MODEL_CONFIG_INVALID, `Unsupported provider type: ${String(exhaustive)}`);
@@ -118905,21 +118817,21 @@ function vertexAILocation(provider) {
118905
118817
  return envValue(provider.env, "GOOGLE_CLOUD_LOCATION") ?? locationFromVertexAIBaseUrl(provider.baseUrl);
118906
118818
  }
118907
118819
  function providerValue(configured, env, envKey) {
118908
- return nonEmptyString$1(configured) ?? envValue(env, envKey);
118820
+ return nonEmptyString(configured) ?? envValue(env, envKey);
118909
118821
  }
118910
118822
  function envValue(env, key) {
118911
- return nonEmptyString$1(env?.[key]);
118823
+ return nonEmptyString(env?.[key]);
118912
118824
  }
118913
- function nonEmptyString$1(value) {
118825
+ function nonEmptyString(value) {
118914
118826
  const trimmed = value?.trim();
118915
118827
  return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
118916
118828
  }
118917
118829
  function locationFromVertexAIBaseUrl(baseUrl) {
118918
- const url = nonEmptyString$1(baseUrl);
118830
+ const url = nonEmptyString(baseUrl);
118919
118831
  if (url === void 0) return void 0;
118920
118832
  try {
118921
118833
  const host = new URL(url).hostname;
118922
- return host.endsWith("-aiplatform.googleapis.com") ? nonEmptyString$1(host.slice(0, -26)) : void 0;
118834
+ return host.endsWith("-aiplatform.googleapis.com") ? nonEmptyString(host.slice(0, -26)) : void 0;
118923
118835
  } catch {
118924
118836
  return;
118925
118837
  }
@@ -120464,7 +120376,6 @@ function waitForSpawn(child) {
120464
120376
  new AsyncLocalStorage();
120465
120377
  //#endregion
120466
120378
  //#region ../../packages/agent-core/src/rpc/core-impl.ts
120467
- const SCREAM_CODE_PROVIDER_NAME = "managed:scream-code";
120468
120379
  var ScreamCore = class {
120469
120380
  rpcClient;
120470
120381
  sdk;
@@ -120945,11 +120856,7 @@ var ScreamCore = class {
120945
120856
  }
120946
120857
  async resolveRuntime(config) {
120947
120858
  if (this.runtime !== void 0) return this.runtime;
120948
- const runtime = await createRuntimeConfig({
120949
- config,
120950
- screamRequestHeaders: this.screamRequestHeaders,
120951
- resolveOAuthTokenProvider: this.resolveOAuthTokenProvider
120952
- });
120859
+ const runtime = await createRuntimeConfig({ config });
120953
120860
  this.runtime = runtime;
120954
120861
  return runtime;
120955
120862
  }
@@ -121006,42 +120913,20 @@ var ScreamCore = class {
121006
120913
  }
121007
120914
  };
121008
120915
  async function createRuntimeConfig(input) {
121009
- const fetchCache = new FetchCache();
121010
- const localFetcher = new LocalFetchURLProvider({ cache: fetchCache });
121011
- const fetchService = input.config.services?.screamCliFetch;
121012
120916
  return {
121013
- urlFetcher: fetchService?.baseUrl === void 0 ? localFetcher : new ScreamCliFetchURLProvider({
121014
- baseUrl: fetchService.baseUrl,
121015
- localFallback: localFetcher,
121016
- defaultHeaders: input.screamRequestHeaders,
121017
- cache: fetchCache,
121018
- ...serviceCredentials(fetchService, input.resolveOAuthTokenProvider)
121019
- }),
120917
+ urlFetcher: new LocalFetchURLProvider({ cache: new FetchCache() }),
121020
120918
  webSearcher: buildWebSearcher(input)
121021
120919
  };
121022
120920
  }
121023
120921
  function buildWebSearcher(input) {
121024
- const searchService = input.config.services?.screamCliSearch;
121025
- const ddgEnabled = input.config.services?.duckduckgo?.enabled !== false;
121026
- const screamProvider = searchService?.baseUrl !== void 0 ? new ScreamCliWebSearchProvider({
121027
- baseUrl: searchService.baseUrl,
121028
- defaultHeaders: input.screamRequestHeaders,
121029
- ...serviceCredentials(searchService, input.resolveOAuthTokenProvider)
121030
- }) : void 0;
121031
- const ddgProvider = ddgEnabled ? new DuckDuckGoSearchProvider() : void 0;
121032
- if (screamProvider && ddgProvider) return new FallbackSearchProvider([screamProvider, ddgProvider]);
121033
- return screamProvider ?? ddgProvider;
121034
- }
121035
- function serviceCredentials(service, resolveOAuthTokenProvider) {
121036
- return {
121037
- apiKey: nonEmptyString(service.apiKey),
121038
- tokenProvider: service.oauth !== void 0 ? resolveOAuthTokenProvider?.(SCREAM_CODE_PROVIDER_NAME, service.oauth) : void 0,
121039
- customHeaders: service.customHeaders
121040
- };
121041
- }
121042
- function nonEmptyString(value) {
121043
- const trimmed = value?.trim();
121044
- return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
120922
+ const services = input.config.services;
120923
+ const providers = [];
120924
+ if (services?.duckduckgo?.enabled !== false) providers.push(new DuckDuckGoSearchProvider());
120925
+ if (services?.sogou?.enabled !== false) providers.push(new SogouSearchProvider());
120926
+ if (services?.so360?.enabled !== false) providers.push(new So360SearchProvider());
120927
+ if (services?.baidu?.enabled !== false) providers.push(new BaiduSearchProvider());
120928
+ if (providers.length === 0) return void 0;
120929
+ return providers.length === 1 ? providers[0] : new FallbackSearchProvider(providers);
121045
120930
  }
121046
120931
  function requiredWorkDir(operation, value) {
121047
120932
  if (typeof value !== "string" || value.trim() === "") throw new ScreamError(ErrorCodes.REQUEST_WORK_DIR_REQUIRED, `${operation} requires workDir`);
@@ -122505,7 +122390,7 @@ function optionalBuildString(value) {
122505
122390
  return typeof value === "string" && value.length > 0 ? value : void 0;
122506
122391
  }
122507
122392
  const SCREAM_BUILD_INFO = {
122508
- version: optionalBuildString("0.10.0"),
122393
+ version: optionalBuildString("0.10.2"),
122509
122394
  channel: optionalBuildString(""),
122510
122395
  commit: optionalBuildString(""),
122511
122396
  buildTarget: optionalBuildString("darwin-arm64")
@@ -122991,7 +122876,7 @@ async function resolvePromptSession(harness, opts, workDir, defaultModel, stderr
122991
122876
  workDir
122992
122877
  }))[0];
122993
122878
  if (target === void 0) throw new Error(`未找到会话 "${opts.session}"。`);
122994
- if (target.workDir !== workDir) {
122879
+ if (!sameWorkDir(target.workDir, workDir)) {
122995
122880
  stderr.write(`${chalk.yellow(`会话 "${opts.session}" 是在其他目录下创建的。\n cd "${target.workDir}" && scream -r ${opts.session}`)}\n\n`);
122996
122881
  throw new Error(`会话 "${opts.session}" 是在其他目录下创建的。`);
122997
122882
  }
@@ -123380,6 +123265,23 @@ function formatTurnEndedFailure(event) {
123380
123265
  if (event.error !== void 0) return `${event.error.code}: ${event.error.message}`;
123381
123266
  return `提示回合结束,原因:${event.reason}`;
123382
123267
  }
123268
+ /**
123269
+ * Compare two working directories tolerating symlink aliases (/tmp vs
123270
+ * /private/tmp on macOS), trailing separators, and Windows case
123271
+ * differences. Falls back to resolved-string compare when either path
123272
+ * doesn't exist on disk.
123273
+ */
123274
+ function sameWorkDir(a, b) {
123275
+ const normalize = (p) => {
123276
+ let out = resolve(p);
123277
+ try {
123278
+ out = realpathSync(out);
123279
+ } catch {}
123280
+ out = out.replace(/[/\\]+$/, "");
123281
+ return process.platform === "win32" ? out.toLowerCase() : out;
123282
+ };
123283
+ return normalize(a) === normalize(b);
123284
+ }
123383
123285
  //#endregion
123384
123286
  //#region src/tui/constant/rendering.ts
123385
123287
  const MAX_SHELL_OUTPUT_BYTES = 128 * 1024;
@@ -123838,12 +123740,13 @@ function isManagedUsageProvider(providerKey) {
123838
123740
  //#region src/tui/constant/streaming.ts
123839
123741
  const STREAMING_ARGS_FIELD_RE = /"(path|file_path|command|pattern|query|url|description|title|name)"\s*:\s*"((?:\\.|[^"\\])*)"/g;
123840
123742
  const STREAMING_ARGS_PREVIEW_MAX_CHARS = 8 * 1024;
123743
+ const STREAMING_ARGS_BUFFER_MAX_CHARS = 1024 * 1024;
123841
123744
  //#endregion
123842
123745
  //#region src/tui/utils/event-payload.ts
123843
123746
  function appendStreamingArgsPreview(current, next) {
123844
- const existing = (current ?? "").slice(0, STREAMING_ARGS_PREVIEW_MAX_CHARS);
123747
+ const existing = (current ?? "").slice(0, STREAMING_ARGS_BUFFER_MAX_CHARS);
123845
123748
  if (next === null || next === void 0 || next.length === 0) return existing;
123846
- const remaining = STREAMING_ARGS_PREVIEW_MAX_CHARS - existing.length;
123749
+ const remaining = STREAMING_ARGS_BUFFER_MAX_CHARS - existing.length;
123847
123750
  if (remaining <= 0) return existing;
123848
123751
  return `${existing}${next.slice(0, remaining)}`;
123849
123752
  }
@@ -125769,7 +125672,7 @@ var FooterComponent = class {
125769
125672
  gitCache;
125770
125673
  gitCacheWorkDir;
125771
125674
  transientHint = null;
125772
- shimmerTimer = null;
125675
+ statusTimer = null;
125773
125676
  /**
125774
125677
  * Non-terminal background-task counts split by kind so the footer can
125775
125678
  * render two distinct badges. `bashTasks` covers `bash-*` BPM tasks
@@ -125788,15 +125691,13 @@ var FooterComponent = class {
125788
125691
  this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
125789
125692
  }
125790
125693
  setState(state) {
125791
- const wasThinking = this.state?.streamingPhase === "thinking";
125694
+ const prevPhase = this.state?.streamingPhase;
125792
125695
  if (state.workDir !== this.gitCacheWorkDir) {
125793
125696
  this.gitCacheWorkDir = state.workDir;
125794
125697
  this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
125795
125698
  }
125796
125699
  this.state = state;
125797
- const isThinking = state.streamingPhase === "thinking";
125798
- if (isThinking && !wasThinking) this.#startShimmer();
125799
- else if (!isThinking && wasThinking) this.#stopShimmer();
125700
+ if (state.streamingPhase !== prevPhase) this.#restartStatusTimer(state.streamingPhase);
125800
125701
  }
125801
125702
  setColors(colors) {
125802
125703
  this.colors = colors;
@@ -125821,29 +125722,29 @@ var FooterComponent = class {
125821
125722
  }
125822
125723
  invalidate() {}
125823
125724
  /**
125824
- * Stop the shimmer animation timer. Idempotent — safe to call even when
125725
+ * Stop the status timer. Idempotent — safe to call even when
125825
125726
  * the timer isn't running. Call this when the component is disposed.
125826
125727
  */
125827
125728
  dispose() {
125828
- this.#stopShimmer();
125729
+ this.#stopStatusTimer();
125829
125730
  }
125830
- #startShimmer() {
125831
- if (this.shimmerTimer) return;
125832
- this.shimmerTimer = setInterval(() => {
125833
- this.ui.requestRender();
125834
- }, 1e3 / 30);
125731
+ #restartStatusTimer(phase) {
125732
+ this.#stopStatusTimer();
125733
+ if (phase === "idle") return;
125734
+ const intervalMs = phase === "thinking" ? 1e3 / 30 : SPINNER_TICK_MS;
125735
+ this.statusTimer = setInterval(() => {
125736
+ this.ui.requestComponentRender(this);
125737
+ }, intervalMs);
125835
125738
  }
125836
- #stopShimmer() {
125837
- if (!this.shimmerTimer) return;
125838
- clearInterval(this.shimmerTimer);
125839
- this.shimmerTimer = null;
125739
+ #stopStatusTimer() {
125740
+ if (!this.statusTimer) return;
125741
+ clearInterval(this.statusTimer);
125742
+ this.statusTimer = null;
125840
125743
  }
125841
125744
  render(width) {
125842
125745
  const colors = this.colors;
125843
125746
  const state = this.state;
125844
125747
  const left = [];
125845
- if (state.permissionMode === "auto") left.push(chalk.hex(colors.warning).bold(t("badge.auto")));
125846
- if (state.permissionMode === "yolo") left.push(chalk.hex(colors.warning).bold(t("badge.yes")));
125847
125748
  if (state.planMode !== "off") {
125848
125749
  const isFusion = state.planMode === "fusionplan";
125849
125750
  left.push(chalk.hex(isFusion ? colors.fusionPlanMode : colors.planMode).bold(isFusion ? t("badge.fusion") : t("badge.plan")));
@@ -126727,7 +126628,6 @@ async function handleYoloCommand(host, args) {
126727
126628
  }
126728
126629
  await session.setPermission("yolo");
126729
126630
  host.setAppState({ permissionMode: "yolo" });
126730
- host.showNotice(t("config.yolo_on"), t("config.yolo_on_desc"));
126731
126631
  return;
126732
126632
  }
126733
126633
  if (subcmd === "off") {
@@ -126737,17 +126637,14 @@ async function handleYoloCommand(host, args) {
126737
126637
  }
126738
126638
  await session.setPermission("manual");
126739
126639
  host.setAppState({ permissionMode: "manual" });
126740
- host.showNotice(t("config.yolo_off"));
126741
126640
  return;
126742
126641
  }
126743
126642
  if (currentMode === "yolo") {
126744
126643
  await session.setPermission("manual");
126745
126644
  host.setAppState({ permissionMode: "manual" });
126746
- host.showNotice(t("config.yolo_off"));
126747
126645
  } else {
126748
126646
  await session.setPermission("yolo");
126749
126647
  host.setAppState({ permissionMode: "yolo" });
126750
- host.showNotice(t("config.yolo_toggle_on"), t("config.yolo_toggle_on_desc"));
126751
126648
  }
126752
126649
  }
126753
126650
  async function handleAutoCommand(host, args) {
@@ -126765,7 +126662,6 @@ async function handleAutoCommand(host, args) {
126765
126662
  }
126766
126663
  await session.setPermission("auto");
126767
126664
  host.setAppState({ permissionMode: "auto" });
126768
- host.showNotice(t("config.auto_on"), t("config.auto_on_desc"));
126769
126665
  return;
126770
126666
  }
126771
126667
  if (subcmd === "off") {
@@ -126775,17 +126671,14 @@ async function handleAutoCommand(host, args) {
126775
126671
  }
126776
126672
  await session.setPermission("manual");
126777
126673
  host.setAppState({ permissionMode: "manual" });
126778
- host.showNotice(t("config.auto_off"));
126779
126674
  return;
126780
126675
  }
126781
126676
  if (currentMode === "auto") {
126782
126677
  await session.setPermission("manual");
126783
126678
  host.setAppState({ permissionMode: "manual" });
126784
- host.showNotice(t("config.auto_off"));
126785
126679
  } else {
126786
126680
  await session.setPermission("auto");
126787
126681
  host.setAppState({ permissionMode: "auto" });
126788
- host.showNotice(t("config.auto_toggle_on"), t("config.auto_toggle_on_desc"));
126789
126682
  }
126790
126683
  }
126791
126684
  async function handleWolfpackCommand(host, args) {
@@ -127788,7 +127681,7 @@ async function createGoal(host, parsed) {
127788
127681
  await showGoalConfigWizard(host, session, parsed.objective, parsed.replace);
127789
127682
  }
127790
127683
  async function showGoalConfigWizard(host, session, objective, replace) {
127791
- const { TextInputDialogComponent } = await import("./text-input-dialog-Yyssn7t3.mjs");
127684
+ const { TextInputDialogComponent } = await import("./text-input-dialog-C8_8qYYi.mjs");
127792
127685
  const turnInput = await promptNumber(host, TextInputDialogComponent, {
127793
127686
  title: t("goal.wizard_title", { objective }),
127794
127687
  subtitle: t("goal.budget_turns_hint"),
@@ -128537,7 +128430,7 @@ var AssistantMessageComponent = class {
128537
128430
  const age = Date.now() - (this.fadeStartMs ?? 0);
128538
128431
  this.cachedWidth = void 0;
128539
128432
  this.cachedLines = void 0;
128540
- this.ui?.requestRender();
128433
+ this.ui?.requestComponentRender(this);
128541
128434
  if (age >= 1200) this.stopFade();
128542
128435
  }, FADE_TICK_MS);
128543
128436
  this.ui.requestRender();
@@ -129018,7 +128911,7 @@ var ThinkingComponent = class {
129018
128911
  if (this.ui === void 0 || this.spinnerInterval !== void 0) return;
129019
128912
  this.spinnerInterval = setInterval(() => {
129020
128913
  this.spinnerFrame = (this.spinnerFrame + 1) % BRAILLE_SPINNER_FRAMES.length;
129021
- this.ui?.requestRender();
128914
+ this.ui?.requestComponentRender(this);
129022
128915
  }, 80);
129023
128916
  }
129024
128917
  stopSpinner() {
@@ -130205,23 +130098,20 @@ function unescapeJsonString(s) {
130205
130098
  });
130206
130099
  }
130207
130100
  /**
130208
- * Pull the live value of a JSON string field out of partially-streamed
130209
- * arguments, even if the closing quote hasn't arrived yet. Handles the
130210
- * common JSON string escapes so `\n` in a streamed `content` becomes a
130211
- * real newline we can highlight. Returns `undefined` if the field hasn't
130212
- * started streaming yet.
130101
+ * Scan a JSON string value starting at `start` (just after the opening
130102
+ * quote), unescaping into plain text. Stops at the closing quote or at
130103
+ * `end`, tolerating truncation (an incomplete escape at the boundary
130104
+ * simply ends the scan). Shared by the partial-args extractor and the
130105
+ * Write streaming tail preview.
130213
130106
  */
130214
- function extractPartialStringField(text, key) {
130215
- const match = new RegExp(`"${key}"\\s*:\\s*"`).exec(text);
130216
- if (match === null) return void 0;
130217
- const start = match.index + match[0].length;
130107
+ function unescapeJsonStringValue(text, start, end = text.length) {
130218
130108
  let out = "";
130219
130109
  let i = start;
130220
- while (i < text.length) {
130110
+ while (i < end) {
130221
130111
  const ch = text[i];
130222
130112
  if (ch === "\\") {
130223
130113
  const next = text[i + 1];
130224
- if (next === void 0) return out;
130114
+ if (next === void 0 || i + 1 >= end) return out;
130225
130115
  switch (next) {
130226
130116
  case "n":
130227
130117
  out += "\n";
@@ -130248,7 +130138,7 @@ function extractPartialStringField(text, key) {
130248
130138
  out += "/";
130249
130139
  break;
130250
130140
  case "u": {
130251
- if (i + 5 >= text.length) return out;
130141
+ if (i + 5 >= end) return out;
130252
130142
  const hex = text.slice(i + 2, i + 6);
130253
130143
  const code = Number.parseInt(hex, 16);
130254
130144
  if (Number.isNaN(code)) return out;
@@ -130267,6 +130157,16 @@ function extractPartialStringField(text, key) {
130267
130157
  }
130268
130158
  return out;
130269
130159
  }
130160
+ /**
130161
+ * Pull the live value of a JSON string field out of partially-streamed
130162
+ * arguments, even if the closing quote hasn't arrived yet. Returns
130163
+ * `undefined` if the field hasn't started streaming yet.
130164
+ */
130165
+ function extractPartialStringField(text, key) {
130166
+ const match = new RegExp(`"${key}"\\s*:\\s*"`).exec(text);
130167
+ if (match === null) return void 0;
130168
+ return unescapeJsonStringValue(text, match.index + match[0].length);
130169
+ }
130270
130170
  function parseArgsPreview(value) {
130271
130171
  const previewText = value.slice(0, STREAMING_ARGS_PREVIEW_MAX_CHARS);
130272
130172
  if (previewText.trim().length === 0) return {};
@@ -130409,6 +130309,11 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
130409
130309
  subagentEndedAtMs;
130410
130310
  progressLines = [];
130411
130311
  static MAX_PROGRESS_LINES = 24;
130312
+ writeStreamContentStart = -1;
130313
+ writeStreamNlScanOffset = 0;
130314
+ writeStreamNlCount = 0;
130315
+ writeStreamLang;
130316
+ static WRITE_STREAM_TAIL_RAW_CHARS = 4096;
130412
130317
  /**
130413
130318
  * Registered by a group container (`AgentGroupComponent` or
130414
130319
  * `ReadGroupComponent`) when this component is borrowed as a hidden state
@@ -131178,21 +131083,12 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
131178
131083
  const name = this.toolCall.name;
131179
131084
  const previewText = streamText.slice(0, STREAMING_ARGS_PREVIEW_MAX_CHARS);
131180
131085
  if (name === "Write") {
131181
- const content = extractPartialStringField(previewText, "content");
131182
- if (content === void 0 || content.length === 0) return;
131183
- const allLines = highlightLines(content, langFromPath(extractPartialStringField(previewText, "file_path") ?? extractPartialStringField(previewText, "path") ?? ""));
131184
- const maxLines = 10;
131185
- const scrollLines = allLines.length > maxLines ? allLines.slice(allLines.length - maxLines) : allLines;
131186
- for (const [i, line] of scrollLines.entries()) {
131187
- const originalLineNumber = allLines.length > maxLines ? allLines.length - maxLines + i : i;
131188
- const lineNum = chalk.dim(String(originalLineNumber + 1).padStart(4) + " ");
131189
- this.addChild(new Text(lineNum + line, 2, 0));
131190
- }
131086
+ this.buildWriteStreamingPreview(streamText);
131191
131087
  return;
131192
131088
  }
131193
131089
  if (name === "Edit") {
131194
131090
  const filePath = extractPartialStringField(previewText, "file_path") ?? extractPartialStringField(previewText, "path") ?? "";
131195
- const bytes = Buffer.byteLength(previewText, "utf8");
131091
+ const bytes = Buffer.byteLength(streamText, "utf8");
131196
131092
  const startedAtMs = this.toolCall.streamingStartedAtMs;
131197
131093
  const elapsedSeconds = startedAtMs === void 0 ? 0 : Math.max(0, Math.floor((Date.now() - startedAtMs) / 1e3));
131198
131094
  const progress = t("toolcall.preparing_changes", {
@@ -131214,6 +131110,54 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
131214
131110
  }));
131215
131111
  }
131216
131112
  }
131113
+ /**
131114
+ * Live Write preview: renders the last COMMAND_PREVIEW_LINES lines of the
131115
+ * file being written, reading from the TAIL of the accumulating args JSON
131116
+ * so the window keeps scrolling for arbitrarily large files (the old
131117
+ * head-bounded preview visibly froze once args passed 8KB). Line numbers
131118
+ * stay exact via an incremental newline counter. Per-frame cost is
131119
+ * O(delta + tail), independent of total file size.
131120
+ */
131121
+ buildWriteStreamingPreview(streamText) {
131122
+ if (streamText.length < this.writeStreamNlScanOffset) {
131123
+ this.writeStreamContentStart = -1;
131124
+ this.writeStreamLang = void 0;
131125
+ }
131126
+ if (this.writeStreamContentStart < 0) {
131127
+ const opener = /"content"\s*:\s*"/.exec(streamText);
131128
+ if (opener === null) return;
131129
+ this.writeStreamContentStart = opener.index + opener[0].length;
131130
+ this.writeStreamNlScanOffset = this.writeStreamContentStart;
131131
+ this.writeStreamNlCount = 0;
131132
+ const filePath = extractPartialStringField(streamText, "file_path") ?? extractPartialStringField(streamText, "path") ?? "";
131133
+ this.writeStreamLang = langFromPath(filePath);
131134
+ }
131135
+ const contentStart = this.writeStreamContentStart;
131136
+ const tailStart = Math.max(contentStart, streamText.length - ToolCallComponent.WRITE_STREAM_TAIL_RAW_CHARS);
131137
+ while (this.writeStreamNlScanOffset < tailStart) {
131138
+ const idx = streamText.indexOf("\\n", this.writeStreamNlScanOffset);
131139
+ if (idx === -1 || idx >= tailStart) break;
131140
+ this.writeStreamNlCount++;
131141
+ this.writeStreamNlScanOffset = idx + 2;
131142
+ }
131143
+ let fragmentStart = tailStart;
131144
+ if (tailStart > contentStart) {
131145
+ const firstNl = streamText.indexOf("\\n", tailStart);
131146
+ if (firstNl === -1) return;
131147
+ fragmentStart = firstNl + 2;
131148
+ }
131149
+ const fragmentEnd = Math.min(streamText.length, fragmentStart + ToolCallComponent.WRITE_STREAM_TAIL_RAW_CHARS);
131150
+ const fragment = unescapeJsonStringValue(streamText, fragmentStart, fragmentEnd);
131151
+ if (fragment.length === 0) return;
131152
+ const lines = highlightLines(fragment, this.writeStreamLang);
131153
+ const displayLines = lines.slice(-10);
131154
+ const firstFragmentLineNo = this.writeStreamNlCount + (tailStart > contentStart ? 2 : 1);
131155
+ const skipped = lines.length - displayLines.length;
131156
+ for (const [i, line] of displayLines.entries()) {
131157
+ const lineNum = chalk.dim(String(firstFragmentLineNo + skipped + i).padStart(4) + " ");
131158
+ this.addChild(new Text(lineNum + line, 2, 0));
131159
+ }
131160
+ }
131217
131161
  buildPlanPreview() {
131218
131162
  const plan = this.resolvePlanForPreview();
131219
131163
  if (plan.length === 0) return;
@@ -132341,35 +132285,39 @@ async function handleUpdateCommand(host) {
132341
132285
  * /mcp — MCP 服务器管理面板。
132342
132286
  *
132343
132287
  * 查看已安装的 MCP 服务器状态,一键安装推荐服务器。 */
132344
- const RECOMMENDED = [{
132345
- name: "peekaboo",
132346
- displayName: "Peekaboo",
132347
- description: t("mcp.desktop_desc"),
132348
- command: "npx",
132349
- args: [
132350
- "-y",
132351
- "@steipete/peekaboo",
132352
- "mcp"
132353
- ],
132354
- macOnly: true
132355
- }, {
132356
- name: "chrome-devtools",
132357
- displayName: "Chrome DevTools",
132358
- description: t("mcp.browser_desc"),
132359
- command: "npx",
132360
- args: [
132361
- "-y",
132362
- "chrome-devtools-mcp@latest",
132363
- "--no-usage-statistics"
132364
- ]
132365
- }];
132366
- const STATUS_LABELS = {
132367
- pending: t("mcp.connecting"),
132368
- connected: t("mcp.connected"),
132369
- failed: t("mcp.failed"),
132370
- disabled: t("mcp.disabled"),
132371
- "needs-auth": t("mcp.auth_required")
132372
- };
132288
+ function getRecommended() {
132289
+ return [{
132290
+ name: "peekaboo",
132291
+ displayName: "Peekaboo",
132292
+ description: t("mcp.desktop_desc"),
132293
+ command: "npx",
132294
+ args: [
132295
+ "-y",
132296
+ "@steipete/peekaboo",
132297
+ "mcp"
132298
+ ],
132299
+ macOnly: true
132300
+ }, {
132301
+ name: "chrome-devtools",
132302
+ displayName: "Chrome DevTools",
132303
+ description: t("mcp.browser_desc"),
132304
+ command: "npx",
132305
+ args: [
132306
+ "-y",
132307
+ "chrome-devtools-mcp@latest",
132308
+ "--no-usage-statistics"
132309
+ ]
132310
+ }];
132311
+ }
132312
+ function getStatusLabels() {
132313
+ return {
132314
+ pending: t("mcp.connecting"),
132315
+ connected: t("mcp.connected"),
132316
+ failed: t("mcp.failed"),
132317
+ disabled: t("mcp.disabled"),
132318
+ "needs-auth": t("mcp.auth_required")
132319
+ };
132320
+ }
132373
132321
  async function handleMcpCommand(host, _args) {
132374
132322
  if (!host.session) {
132375
132323
  host.showError(t("mcp.no_session"));
@@ -132438,7 +132386,7 @@ function buildRows(servers) {
132438
132386
  status: "__section"
132439
132387
  });
132440
132388
  for (const s of servers) {
132441
- const statusLabel = STATUS_LABELS[s.status] ?? s.status;
132389
+ const statusLabel = getStatusLabels()[s.status] ?? s.status;
132442
132390
  const toolInfo = s.status === "connected" ? `${s.toolCount} tools` : "";
132443
132391
  const errorInfo = s.error ? ` — ${sanitizeDesc(s.error)}` : "";
132444
132392
  rows.push({
@@ -132467,7 +132415,7 @@ function buildRows(servers) {
132467
132415
  label: "── " + t("mcp.recommended") + " ──",
132468
132416
  status: "__section"
132469
132417
  });
132470
- for (const rec of RECOMMENDED) {
132418
+ for (const rec of getRecommended()) {
132471
132419
  const alreadyInstalled = installedNames.has(rec.name);
132472
132420
  const platformUnavailable = rec.macOnly && process.platform !== "darwin";
132473
132421
  rows.push({
@@ -132486,7 +132434,7 @@ async function handleEnter(host, row) {
132486
132434
  host.showStatus(t("mcp.already_installed", { name: row.label }));
132487
132435
  return;
132488
132436
  }
132489
- const rec = RECOMMENDED.find((r) => r.name === row.name);
132437
+ const rec = getRecommended().find((r) => r.name === row.name);
132490
132438
  if (!rec) return;
132491
132439
  await installMcp(host, rec);
132492
132440
  } else if (row.kind === "installed" && row.status && row.status !== "__section" && row.status !== "__empty") if (row.status === "connected") await disableMcp(host, row.name);
@@ -133189,7 +133137,7 @@ var MoonLoader = class extends Text {
133189
133137
  const frame = this.frames[this.currentFrame];
133190
133138
  const coloredFrame = this.colorFn ? this.colorFn(frame) : frame;
133191
133139
  this.setText(this.label ? `${coloredFrame} ${this.label}` : coloredFrame);
133192
- this.ui.requestRender();
133140
+ this.ui.requestComponentRender(this);
133193
133141
  }
133194
133142
  };
133195
133143
  //#endregion
@@ -136492,6 +136440,9 @@ out.join('\n');
136492
136440
  function isWaylandSession(env) {
136493
136441
  return Boolean(env["WAYLAND_DISPLAY"]) || env["XDG_SESSION_TYPE"] === "wayland";
136494
136442
  }
136443
+ function isX11Session(env) {
136444
+ return Boolean(env["DISPLAY"]);
136445
+ }
136495
136446
  function isWSL(env) {
136496
136447
  if (env["WSL_DISTRO_NAME"] !== void 0 || env["WSLENV"] !== void 0) return true;
136497
136448
  try {
@@ -136829,7 +136780,7 @@ async function readClipboardMedia(options) {
136829
136780
  if (platform === "linux") {
136830
136781
  const wayland = isWaylandSession(env);
136831
136782
  const wsl = isWSL(env);
136832
- if (wayland || wsl) {
136783
+ if (wayland || wsl || isX11Session(env)) {
136833
136784
  const fileMedia = readClipboardFileMediaViaWlPaste() ?? readClipboardFileMediaViaXclip();
136834
136785
  if (fileMedia !== null) return fileMedia;
136835
136786
  image = readClipboardImageViaWlPaste() ?? readClipboardImageViaXclip();
@@ -138122,9 +138073,9 @@ var SessionEventHandler = class {
138122
138073
  const { streamingUI } = this.host;
138123
138074
  const backgroundMeta = this.backgroundAgentMetadata.get(event.subagentId);
138124
138075
  if (backgroundMeta !== void 0) {
138076
+ const taskId = this.findAgentTaskId(event.subagentId);
138125
138077
  this.backgroundAgentMetadata.delete(event.subagentId);
138126
138078
  this.syncBackgroundAgentBadge();
138127
- const taskId = this.findAgentTaskId(event.subagentId);
138128
138079
  if (taskId !== void 0 && this.backgroundTaskTranscriptedTerminal.has(taskId)) return;
138129
138080
  if (taskId !== void 0) this.backgroundTaskTranscriptedTerminal.add(taskId);
138130
138081
  const extras = event.resultSummary === void 0 ? void 0 : { resultSummary: event.resultSummary };
@@ -138145,6 +138096,7 @@ var SessionEventHandler = class {
138145
138096
  const { streamingUI } = this.host;
138146
138097
  const backgroundMeta = this.backgroundAgentMetadata.get(event.subagentId);
138147
138098
  if (backgroundMeta !== void 0) {
138099
+ const taskId = this.findAgentTaskId(event.subagentId);
138148
138100
  this.backgroundAgentMetadata.delete(event.subagentId);
138149
138101
  this.syncBackgroundAgentBadge();
138150
138102
  streamingUI.applyBackgroundTaskTerminalStatus({
@@ -138153,7 +138105,6 @@ var SessionEventHandler = class {
138153
138105
  status: "failed",
138154
138106
  errorText: event.error
138155
138107
  });
138156
- const taskId = this.findAgentTaskId(event.subagentId);
138157
138108
  if (taskId !== void 0 && this.backgroundTaskTranscriptedTerminal.has(taskId)) return;
138158
138109
  if (taskId !== void 0) this.backgroundTaskTranscriptedTerminal.add(taskId);
138159
138110
  this.appendBackgroundAgentEntry("failed", backgroundMeta, { error: event.error });
@@ -138297,18 +138248,21 @@ var SessionEventHandler = class {
138297
138248
  };
138298
138249
  //#endregion
138299
138250
  //#region src/tui/utils/media-url.ts
138300
- const MEDIA_KIND_LABELS = {
138301
- audio: t("mediaurl.audio"),
138302
- image: t("mediaurl.image"),
138303
- video: t("mediaurl.video")
138304
- };
138251
+ function getMediaKindLabels() {
138252
+ return {
138253
+ audio: t("mediaurl.audio"),
138254
+ image: t("mediaurl.image"),
138255
+ video: t("mediaurl.video")
138256
+ };
138257
+ }
138305
138258
  function mediaUrlPartToText(kind, url) {
138259
+ const labels = getMediaKindLabels();
138306
138260
  const summary = summarizeDataUrl(url);
138307
138261
  if (summary !== void 0) {
138308
138262
  const size = summary.bytes !== void 0 ? `, ${formatByteSize(summary.bytes)}` : "";
138309
- return `[${MEDIA_KIND_LABELS[kind]} ${summary.mime}${size}]`;
138263
+ return `[${labels[kind]} ${summary.mime}${size}]`;
138310
138264
  }
138311
- return `<${MEDIA_KIND_LABELS[kind]} url="${escapeAttribute$1(url)}">`;
138265
+ return `<${labels[kind]} url="${escapeAttribute$1(url)}">`;
138312
138266
  }
138313
138267
  function summarizeDataUrl(url) {
138314
138268
  if (!url.startsWith("data:")) return void 0;
@@ -141457,7 +141411,7 @@ var PulseWaveLoader = class extends Text {
141457
141411
  2
141458
141412
  ].map((idx) => this.renderCell(idx, step.active, step.forward));
141459
141413
  this.setText(cells.join(" "));
141460
- this.ui.requestRender();
141414
+ this.ui.requestComponentRender(this);
141461
141415
  }
141462
141416
  renderCell(index, active, forward) {
141463
141417
  const distance = forward ? active - index : index - active;
@@ -142194,7 +142148,7 @@ function hslToHex(h, s, l) {
142194
142148
  const toHex = (v) => Math.round((v + m) * 255).toString(16).padStart(2, "0");
142195
142149
  return `#${toHex(rf)}${toHex(gf)}${toHex(bf)}`;
142196
142150
  }
142197
- var InputController = class {
142151
+ var InputController = class InputController {
142198
142152
  host;
142199
142153
  lastHistoryContent;
142200
142154
  breatheTimer = null;
@@ -142215,12 +142169,38 @@ var InputController = class {
142215
142169
  };
142216
142170
  if (this.host.state.appState.planMode === "off") this.#startBreathing();
142217
142171
  }
142172
+ pendingLargeInput = null;
142173
+ static LARGE_INPUT_THRESHOLD = 5e3;
142218
142174
  handleInput(text) {
142175
+ if (this.pendingLargeInput !== null) {
142176
+ const answer = text.trim().toLowerCase();
142177
+ if (answer === "y" || answer === "yes") {
142178
+ const pending = this.pendingLargeInput;
142179
+ this.pendingLargeInput = null;
142180
+ this.persistInputHistory(pending);
142181
+ dispatchInput(this.host, pending);
142182
+ this.host.stopMemoryIdleTimer();
142183
+ } else {
142184
+ const pending = this.pendingLargeInput;
142185
+ this.pendingLargeInput = null;
142186
+ this.host.showStatus(t("input.large_cancelled"), this.host.state.theme.colors.textDim);
142187
+ if (answer !== "n" && answer !== "no" && text.trim().length > 0) {
142188
+ this.host.state.editor.setText(pending);
142189
+ this.handleInput(text);
142190
+ } else this.host.state.editor.setText(pending);
142191
+ }
142192
+ return;
142193
+ }
142219
142194
  if (text.trim().length === 0) return;
142220
142195
  if (this.host.state.appState.isReplaying) {
142221
142196
  this.host.showError(t("input.replay_blocked"));
142222
142197
  return;
142223
142198
  }
142199
+ if (text.length > InputController.LARGE_INPUT_THRESHOLD) {
142200
+ this.pendingLargeInput = text;
142201
+ this.host.showStatus(t("input.large_confirm", { count: text.length }), this.host.state.theme.colors.warning);
142202
+ return;
142203
+ }
142224
142204
  this.persistInputHistory(text);
142225
142205
  dispatchInput(this.host, text);
142226
142206
  this.host.stopMemoryIdleTimer();
@@ -143030,6 +143010,8 @@ var CustomEditor = class extends Editor {
143030
143010
  thinking = false;
143031
143011
  /** Current thinking effort level (e.g. low, medium, high). Used to annotate the think label. */
143032
143012
  thinkingLevel = "off";
143013
+ /** Current permission mode — always shown as a badge at the top-left of the input box border. */
143014
+ permissionMode = "manual";
143033
143015
  /** Current border colour hex — kept in sync with borderColor by the host. */
143034
143016
  borderHex = "";
143035
143017
  consumingPaste = false;
@@ -143090,7 +143072,13 @@ var CustomEditor = class extends Editor {
143090
143072
  const withPrompt = injectPromptSymbol(firstContent);
143091
143073
  if (withPrompt !== void 0) lines[firstContentIdx] = withPrompt;
143092
143074
  }
143093
- if (this.thinking) injectThinkLabel(lines, width, this.thinkingLevel, this.borderColor ?? ((s) => s), this.borderHex);
143075
+ injectBorderBadges(lines, width, {
143076
+ mode: this.permissionMode,
143077
+ thinking: this.thinking,
143078
+ thinkingLevel: this.thinkingLevel,
143079
+ paint: this.borderColor ?? ((s) => s),
143080
+ borderHex: this.borderHex
143081
+ });
143094
143082
  return lines;
143095
143083
  }
143096
143084
  handleInput(data) {
@@ -143223,34 +143211,47 @@ function injectPromptSymbol(line) {
143223
143211
  return "> " + line.slice(2);
143224
143212
  }
143225
143213
  const THINK_LABEL_MIN_WIDTH = 14;
143214
+ const MODE_BADGE_MIN_WIDTH = 10;
143226
143215
  function isLightBg(hex) {
143227
143216
  const r = parseInt(hex.slice(1, 3), 16);
143228
143217
  const g = parseInt(hex.slice(3, 5), 16);
143229
143218
  const b = parseInt(hex.slice(5, 7), 16);
143230
143219
  return (.299 * r + .587 * g + .114 * b) / 255 > .5;
143231
143220
  }
143221
+ function makeBadge(label, bgHex) {
143222
+ return bgHex ? chalk.bgHex(bgHex).hex(isLightBg(bgHex) ? "#000000" : "#FFFFFF")(label) : chalk.bgBlack.white(label);
143223
+ }
143232
143224
  /**
143233
- * Embed a small "think" badge into the top border line of the editor.
143234
- * The label only appears when the active model has thinking enabled.
143235
- * Works with pi-tui's flat two-line design (no side borders).
143236
- *
143237
- * The badge uses the border colour as background with black text for
143238
- * maximum contrast against the bright border colours (yellow-green,
143239
- * cyan, amber) used across themes.
143225
+ * Compose the editor's top border line with up to two badges: the
143226
+ * permission mode badge at the left (always shown) and the think badge at
143227
+ * the right (only when thinking is enabled). Each badge is a solid colour
143228
+ * block with black/white text picked by background luminance. When the
143229
+ * editor is scrolled, pi-tui's `↑ N more` indicator keeps the line — it
143230
+ * signals hidden content, which matters more than the badges. On narrow
143231
+ * terminals the think badge drops first, then the mode badge.
143240
143232
  */
143241
- function injectThinkLabel(lines, width, thinkingLevel, paint, borderHex) {
143242
- if (width < THINK_LABEL_MIN_WIDTH) return;
143233
+ function injectBorderBadges(lines, width, opts) {
143243
143234
  const topIdx = lines.findIndex((line) => {
143244
143235
  const plain = stripSgr(line);
143245
143236
  return plain.length > 0 && plain[0] === "─";
143246
143237
  });
143247
143238
  if (topIdx === -1) return;
143248
- const label = thinkingLevel !== "off" ? ` Think ${thinkingLevel} ` : " Think ";
143249
- const badge = borderHex ? chalk.bgHex(borderHex).hex(isLightBg(borderHex) ? "#000000" : "#FFFFFF")(label) : chalk.bgBlack.white(label);
143250
- const badgeVis = visibleWidth(badge);
143251
- const leftDashCount = width - 1 - badgeVis;
143252
- if (leftDashCount < 1) return;
143253
- lines[topIdx] = paint("─".repeat(leftDashCount)) + badge + paint("─");
143239
+ if (stripSgr(lines[topIdx]).includes("")) return;
143240
+ const { paint } = opts;
143241
+ let left = "";
143242
+ let right = paint("─");
143243
+ if (width >= MODE_BADGE_MIN_WIDTH) {
143244
+ const badgeText = ` ${opts.mode} `;
143245
+ left = paint("──") + (opts.borderHex ? chalk.hex(opts.borderHex).bold(badgeText) : paint(badgeText));
143246
+ }
143247
+ if (opts.thinking && width >= THINK_LABEL_MIN_WIDTH) right = makeBadge(opts.thinkingLevel !== "off" ? ` Think ${opts.thinkingLevel} ` : " Think ", opts.borderHex) + paint("─");
143248
+ const fill = width - visibleWidth(left) - visibleWidth(right);
143249
+ if (fill < 0) {
143250
+ const modeOnlyFill = width - visibleWidth(left) - 1;
143251
+ lines[topIdx] = modeOnlyFill >= 0 ? left + paint("─".repeat(modeOnlyFill)) + paint("─") : paint("─".repeat(width));
143252
+ return;
143253
+ }
143254
+ lines[topIdx] = left + paint("─".repeat(fill)) + right;
143254
143255
  }
143255
143256
  //#endregion
143256
143257
  //#region src/tui/utils/terminal-state.ts
@@ -143491,6 +143492,7 @@ function createTUIState(options) {
143491
143492
  const editor = new CustomEditor(ui, theme.colors);
143492
143493
  editor.thinking = initialAppState.thinkingLevel !== "off";
143493
143494
  editor.thinkingLevel = initialAppState.thinkingLevel;
143495
+ editor.permissionMode = initialAppState.permissionMode ?? "manual";
143494
143496
  return {
143495
143497
  ui,
143496
143498
  terminal,
@@ -144110,11 +144112,17 @@ var SessionManager = class {
144110
144112
  }
144111
144113
  if (isBusy(this.host.state.appState)) {
144112
144114
  this.host.showError(t("session.switch_streaming"));
144113
- return { switched: false };
144115
+ return {
144116
+ switched: false,
144117
+ blocked: true
144118
+ };
144114
144119
  }
144115
144120
  if (this.host.state.appState.isReplaying) {
144116
144121
  this.host.showError(t("session.switch_replaying"));
144117
- return { switched: false };
144122
+ return {
144123
+ switched: false,
144124
+ blocked: true
144125
+ };
144118
144126
  }
144119
144127
  let session;
144120
144128
  try {
@@ -146025,12 +146033,12 @@ var DialogManager = class {
146025
146033
  const row = this.host.getSessions().find((s) => s.id === pickerId);
146026
146034
  const isCc = row?.metadata?.["source"] === "cc-connect";
146027
146035
  const realId = isCc ? row.metadata["agentSessionId"] : pickerId;
146028
- this.host.resumeSession(realId).then(async (switched) => {
146036
+ this.host.resumeSession(realId).then(async ({ switched, blocked }) => {
146029
146037
  if (switched) {
146030
146038
  this.hideSessionPicker();
146031
146039
  return;
146032
146040
  }
146033
- if (isCc) try {
146041
+ if (isCc && blocked !== true) try {
146034
146042
  const session = await this.host.harness.createSession({
146035
146043
  id: realId,
146036
146044
  workDir: this.host.getCurrentWorkDir(),
@@ -146044,6 +146052,8 @@ var DialogManager = class {
146044
146052
  } catch {
146045
146053
  this.host.showError(t("dialog.create_session_failed"));
146046
146054
  }
146055
+ }).catch((error) => {
146056
+ this.host.showError(error instanceof Error ? error.message : String(error));
146047
146057
  });
146048
146058
  },
146049
146059
  onCancel,
@@ -146056,6 +146066,8 @@ var DialogManager = class {
146056
146066
  await this.host.fetchSessions();
146057
146067
  if (this.host.getSessions().length === 0) this.hideSessionPicker();
146058
146068
  else if (this.host.state.activeDialog === "session-picker") this.mountSessionPicker(onCancel);
146069
+ }).catch((error) => {
146070
+ this.host.showError(error instanceof Error ? error.message : String(error));
146059
146071
  });
146060
146072
  }
146061
146073
  }));
@@ -146576,6 +146588,7 @@ var ScreamTUI = class {
146576
146588
  this.state.editor.thinking = patch.thinkingLevel !== "off";
146577
146589
  this.state.editor.thinkingLevel = patch.thinkingLevel ?? "off";
146578
146590
  }
146591
+ if ("permissionMode" in patch) this.state.editor.permissionMode = patch.permissionMode ?? "manual";
146579
146592
  if ("streamingPhase" in patch && patch.streamingPhase !== "idle") {
146580
146593
  this.transcriptController.stopWelcomeBreathing();
146581
146594
  this.inputController.stopBreathingForStreaming();
@@ -146630,7 +146643,11 @@ var ScreamTUI = class {
146630
146643
  this.sessionManager.resetSessionRuntime();
146631
146644
  }
146632
146645
  async resumeSession(targetSessionId) {
146633
- return (await this.sessionManager.resumeSession(targetSessionId)).switched;
146646
+ const result = await this.sessionManager.resumeSession(targetSessionId);
146647
+ return {
146648
+ switched: result.switched,
146649
+ blocked: result.blocked
146650
+ };
146634
146651
  }
146635
146652
  async switchToSession(session, statusMessage) {
146636
146653
  await this.sessionManager.switchToSession(session, statusMessage);
@@ -146775,6 +146792,8 @@ const SHADOW_CHARS = new Set([
146775
146792
  const SHEEN_STEP = 4;
146776
146793
  const SHEEN_INTERVAL_MS = 60;
146777
146794
  const LOADING_DURATION_MS = 1500;
146795
+ const FULL_LOGO_MIN_COLS = 87;
146796
+ const COMPACT_LOGO = ["██▄▄▄██", "▐█▄▀▄█▌"];
146778
146797
  const THEME_PRIMARY = {
146779
146798
  dark: [
146780
146799
  204,
@@ -146963,7 +146982,9 @@ function supportsAnsi() {
146963
146982
  }
146964
146983
  function runLoadingAnimation(theme = "dark") {
146965
146984
  if (!supportsAnsi()) {
146966
- for (const line of LOGO) stdout.write(`${fg(...LOGO_RGB)}${line}${RESET}\n`);
146985
+ const { cols } = getTerminalSize();
146986
+ if (cols >= FULL_LOGO_MIN_COLS) for (const line of LOGO) stdout.write(`${fg(...LOGO_RGB)}${line}${RESET}\n`);
146987
+ else for (const line of COMPACT_LOGO) stdout.write(`${fg(...BLOCK_RGB)}${line}${RESET}\n`);
146967
146988
  stdout.write(`${BOLD}${fg(...THEME_PRIMARY[theme])}${t("loading.waking")}${RESET}\n`);
146968
146989
  return Promise.resolve();
146969
146990
  }
@@ -146981,15 +147002,20 @@ function runLoadingAnimation(theme = "dark") {
146981
147002
  function render() {
146982
147003
  const { cols, rows } = getTerminalSize();
146983
147004
  const lines = [];
146984
- const contentHeight = LOGO.length + 5;
147005
+ const useFullLogo = cols >= FULL_LOGO_MIN_COLS;
147006
+ const contentHeight = useFullLogo ? LOGO.length + 5 : COMPACT_LOGO.length + 5;
146985
147007
  const topPad = Math.max(0, Math.floor((rows - contentHeight) / 2));
146986
147008
  for (let i = 0; i < topPad; i++) lines.push("");
146987
147009
  const breatheColor = breathePalette[breatheFrame] ?? primary;
146988
- for (const line of LOGO) {
147010
+ if (useFullLogo) for (const line of LOGO) {
146989
147011
  let colored = "";
146990
147012
  for (let ci = 0; ci < line.length; ci++) colored += renderSheen(line[ci], ci, sheenPos, isReversing, breatheColor);
146991
147013
  lines.push(centerPad(colored, cols));
146992
147014
  }
147015
+ else for (const line of COMPACT_LOGO) {
147016
+ const colored = `${fg(...BLOCK_RGB)}${line}${RESET}`;
147017
+ lines.push(centerPad(colored, cols));
147018
+ }
146993
147019
  lines.push("");
146994
147020
  if (phase === "loading") lines.push(centerPad(renderShimmer(shimmerPulse, breatheColor), cols));
146995
147021
  else lines.push(centerPad(`${BOLD}${fg(...breatheColor)}${t("loading.press_enter")}${RESET}`, cols));
@@ -147046,7 +147072,9 @@ function runLoadingAnimation(theme = "dark") {
147046
147072
  process$1.off("SIGTERM", interrupt);
147047
147073
  stdout.write("\x1B[?25h");
147048
147074
  if (process$1.platform !== "win32") stdout.write("\x1B[?1049l");
147049
- for (const line of LOGO) stdout.write(`${fg(...LOGO_RGB)}${line}${RESET}\n`);
147075
+ const { cols: fallbackCols } = getTerminalSize();
147076
+ if (fallbackCols >= FULL_LOGO_MIN_COLS) for (const line of LOGO) stdout.write(`${fg(...LOGO_RGB)}${line}${RESET}\n`);
147077
+ else for (const line of COMPACT_LOGO) stdout.write(`${fg(...BLOCK_RGB)}${line}${RESET}\n`);
147050
147078
  stdout.write(`${BOLD}${fg(...primary)}${t("loading.waking")}${RESET}\n`);
147051
147079
  resolve();
147052
147080
  return;
@@ -147700,6 +147728,27 @@ async function runStreamJson(opts) {
147700
147728
  hasAppendPrompt: appendPrompt.length > 0
147701
147729
  });
147702
147730
  }
147731
+ let cleaned = false;
147732
+ const runCleanup = async () => {
147733
+ if (cleaned) return;
147734
+ cleaned = true;
147735
+ for (const [, pending] of pendingApprovals) pending.resolve({
147736
+ decision: "rejected",
147737
+ feedback: "会话已结束"
147738
+ });
147739
+ pendingApprovals.clear();
147740
+ if (currentSessionId) writer.emitResumeHint(sessionKey);
147741
+ try {
147742
+ if (session) await session.close();
147743
+ await harness.close();
147744
+ } catch {}
147745
+ if (injectedAgentsMd) try {
147746
+ if (originalAgentsMd === void 0) {
147747
+ if (existsSync(agentsMdPath)) unlinkSync(agentsMdPath);
147748
+ } else writeFileSync(agentsMdPath, originalAgentsMd, "utf-8");
147749
+ } catch {}
147750
+ };
147751
+ const uninstallTerminationHandlers = installStreamJsonTerminationHandlers(runCleanup);
147703
147752
  try {
147704
147753
  await harness.ensureConfigFile();
147705
147754
  const config = await harness.getConfig();
@@ -147934,7 +147983,7 @@ async function runStreamJson(opts) {
147934
147983
  });
147935
147984
  session.prompt(userText).catch((error) => {
147936
147985
  const msg = error instanceof Error ? error.message : String(error);
147937
- if (msg.includes("insufficient tool messages") || msg.includes("tool_calls")) {
147986
+ if (msg.includes("insufficient tool messages") || msg.includes("tool_calls") && msg.includes("followed by tool messages")) {
147938
147987
  log.warn("stream-json: resetting session after tool call mismatch", {
147939
147988
  sessionId: session?.id,
147940
147989
  error: msg
@@ -147952,7 +148001,9 @@ async function runStreamJson(opts) {
147952
148001
  }
147953
148002
  finish(error instanceof Error ? error : new Error(msg));
147954
148003
  });
147955
- await turnPromise;
148004
+ try {
148005
+ await turnPromise;
148006
+ } catch {}
147956
148007
  }
147957
148008
  } catch (error) {
147958
148009
  if (process.exitCode === void 0) {
@@ -147961,23 +148012,35 @@ async function runStreamJson(opts) {
147961
148012
  process.exitCode = 1;
147962
148013
  } else log.error("stream-json: turn error (exitCode already set)", { error });
147963
148014
  } finally {
147964
- for (const [, pending] of pendingApprovals) pending.resolve({
147965
- decision: "rejected",
147966
- feedback: "会话已结束"
147967
- });
147968
- pendingApprovals.clear();
147969
- if (currentSessionId) writer.emitResumeHint(sessionKey);
147970
- try {
147971
- if (session) await session.close();
147972
- await harness.close();
147973
- } catch {}
147974
- if (injectedAgentsMd) try {
147975
- if (originalAgentsMd === void 0) {
147976
- if (existsSync(agentsMdPath)) unlinkSync(agentsMdPath);
147977
- } else writeFileSync(agentsMdPath, originalAgentsMd, "utf-8");
147978
- } catch {}
148015
+ uninstallTerminationHandlers();
148016
+ await runCleanup();
147979
148017
  }
147980
148018
  }
148019
+ /** Install termination handlers so we can clean up on SIGINT / SIGTERM. */
148020
+ function installStreamJsonTerminationHandlers(cleanup) {
148021
+ let terminating = false;
148022
+ const handler = async (signal) => {
148023
+ if (terminating) return;
148024
+ terminating = true;
148025
+ try {
148026
+ await cleanup();
148027
+ } finally {
148028
+ process.exit(signal === "SIGINT" ? 130 : 143);
148029
+ }
148030
+ };
148031
+ const onSigint = () => {
148032
+ handler("SIGINT");
148033
+ };
148034
+ const onSigterm = () => {
148035
+ handler("SIGTERM");
148036
+ };
148037
+ process.once("SIGINT", onSigint);
148038
+ process.once("SIGTERM", onSigterm);
148039
+ return () => {
148040
+ process.off("SIGINT", onSigint);
148041
+ process.off("SIGTERM", onSigterm);
148042
+ };
148043
+ }
147981
148044
  //#endregion
147982
148045
  //#region src/cli/startup-error.ts
147983
148046
  function formatUnknownErrorMessage(error) {