scream-code 0.10.1 → 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.
@@ -6,7 +6,7 @@ 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
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-D_xK0DJH.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";
@@ -62543,8 +62543,8 @@ function parseSkillText(options) {
62543
62543
  if (!isRecord$6(frontmatter)) throw new SkillParseError(`Frontmatter in ${options.skillMdPath} must be a mapping at the top level`);
62544
62544
  const metadata = normalizeMetadata(frontmatter);
62545
62545
  if (!isSupportedSkillType(metadata.type)) throw new UnsupportedSkillTypeError(metadata.type ?? String(frontmatter["type"]));
62546
- const name = nonEmptyString$2(metadata.name);
62547
- const description = nonEmptyString$2(metadata.description);
62546
+ const name = nonEmptyString$1(metadata.name);
62547
+ const description = nonEmptyString$1(metadata.description);
62548
62548
  if (isDirectorySkill && (name === void 0 || description === void 0)) throw new SkillParseError(`Missing required frontmatter field ${name === void 0 ? "\"name\"" : "\"description\""} in ${options.skillMdPath}`);
62549
62549
  const skillPath = resolve$1(options.skillMdPath);
62550
62550
  const content = parsed.body.trim();
@@ -62604,11 +62604,11 @@ function normalizeMetadata(raw) {
62604
62604
  const key = METADATA_ALIASES[rawKey] ?? rawKey;
62605
62605
  out[key] = value;
62606
62606
  }
62607
- const type = nonEmptyString$2(out["type"]);
62607
+ const type = nonEmptyString$1(out["type"]);
62608
62608
  if (type !== void 0) out["type"] = type;
62609
- const name = nonEmptyString$2(out["name"]);
62609
+ const name = nonEmptyString$1(out["name"]);
62610
62610
  if (name !== void 0) out["name"] = name;
62611
- const description = nonEmptyString$2(out["description"]);
62611
+ const description = nonEmptyString$1(out["description"]);
62612
62612
  if (description !== void 0) out["description"] = description;
62613
62613
  return out;
62614
62614
  }
@@ -62650,7 +62650,7 @@ function tokenizeArgs(raw) {
62650
62650
  if (hasContent) out.push(current);
62651
62651
  return out;
62652
62652
  }
62653
- function nonEmptyString$2(value) {
62653
+ function nonEmptyString$1(value) {
62654
62654
  return typeof value === "string" && value.trim() !== "" ? value.trim() : void 0;
62655
62655
  }
62656
62656
  function isRecord$6(value) {
@@ -69652,17 +69652,13 @@ const HookDefSchema = z.object({
69652
69652
  command: z.string().min(1),
69653
69653
  timeout: z.number().int().min(1).max(600).optional()
69654
69654
  }).strict();
69655
- const ScreamCliServiceConfigSchema = z.object({
69656
- baseUrl: z.string().optional(),
69657
- apiKey: z.string().optional(),
69658
- oauth: OAuthRefSchema.optional(),
69659
- customHeaders: StringRecordSchema.optional()
69660
- });
69661
69655
  const DuckDuckGoConfigSchema = z.object({ enabled: z.boolean().default(true) });
69656
+ const DomesticSearchConfigSchema = z.object({ enabled: z.boolean().default(true) });
69662
69657
  const ServicesConfigSchema = z.object({
69663
- screamCliSearch: ScreamCliServiceConfigSchema.optional(),
69664
- screamCliFetch: ScreamCliServiceConfigSchema.optional(),
69665
- duckduckgo: DuckDuckGoConfigSchema.optional()
69658
+ duckduckgo: DuckDuckGoConfigSchema.optional(),
69659
+ sogou: DomesticSearchConfigSchema.optional(),
69660
+ so360: DomesticSearchConfigSchema.optional(),
69661
+ baidu: DomesticSearchConfigSchema.optional()
69666
69662
  });
69667
69663
  const McpServerCommonFields = {
69668
69664
  enabled: z.boolean().optional(),
@@ -69736,12 +69732,13 @@ const ThinkingConfigPatchSchema = ThinkingConfigSchema.partial();
69736
69732
  const PermissionConfigPatchSchema = PermissionConfigSchema.partial();
69737
69733
  const LoopControlPatchSchema = LoopControlSchema.partial();
69738
69734
  const BackgroundConfigPatchSchema = BackgroundConfigSchema.partial();
69739
- const ScreamCliServiceConfigPatchSchema = ScreamCliServiceConfigSchema.partial();
69740
69735
  const DuckDuckGoConfigPatchSchema = DuckDuckGoConfigSchema.partial();
69736
+ const DomesticSearchConfigPatchSchema = DomesticSearchConfigSchema.partial();
69741
69737
  const ServicesConfigPatchSchema = z.object({
69742
- screamCliSearch: ScreamCliServiceConfigPatchSchema.optional(),
69743
- screamCliFetch: ScreamCliServiceConfigPatchSchema.optional(),
69744
- duckduckgo: DuckDuckGoConfigPatchSchema.optional()
69738
+ duckduckgo: DuckDuckGoConfigPatchSchema.optional(),
69739
+ sogou: DomesticSearchConfigPatchSchema.optional(),
69740
+ so360: DomesticSearchConfigPatchSchema.optional(),
69741
+ baidu: DomesticSearchConfigPatchSchema.optional()
69745
69742
  });
69746
69743
  const ScreamConfigPatchSchema = z.object({
69747
69744
  providers: z.record(z.string(), ProviderConfigPatchSchema).optional(),
@@ -78352,9 +78349,12 @@ var WebSearchTool = class {
78352
78349
  execute: (ctx) => this.execution(args, ctx)
78353
78350
  };
78354
78351
  }
78355
- async execution(args, { toolCallId }) {
78352
+ async execution(args, { signal, toolCallId }) {
78356
78353
  try {
78357
- const opts = { toolCallId };
78354
+ const opts = {
78355
+ signal,
78356
+ toolCallId
78357
+ };
78358
78358
  if (args.limit !== void 0) opts.limit = args.limit;
78359
78359
  if (args.include_content !== void 0) opts.includeContent = args.include_content;
78360
78360
  const results = await this.provider.search(args.query, opts);
@@ -99968,17 +99968,18 @@ function permissionRuleToToml(rule) {
99968
99968
  }
99969
99969
  function servicesToToml(services, rawServices) {
99970
99970
  const out = cloneRecord(rawServices);
99971
- if (services.screamCliSearch !== void 0) out["scream_cli_search"] = serviceToToml(services.screamCliSearch);
99972
- else delete out["scream_cli_search"];
99973
- if (services.screamCliFetch !== void 0) out["scream_cli_fetch"] = serviceToToml(services.screamCliFetch);
99974
- else delete out["scream_cli_fetch"];
99975
- return out;
99976
- }
99977
- function serviceToToml(service) {
99978
- const out = {};
99979
- for (const [key, value] of Object.entries(service)) if (key === "oauth" && value !== void 0) out[camelToSnake(key)] = oauthToToml(value);
99980
- else if (key === "customHeaders" && value !== void 0) out[camelToSnake(key)] = cloneUnknown(value);
99981
- 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
+ }
99982
99983
  return out;
99983
99984
  }
99984
99985
  function loopControlToToml(loopControl, rawLoopControl) {
@@ -117281,7 +117282,7 @@ function isPrivateIp(ip) {
117281
117282
  }
117282
117283
  return lower === "::1" || lower === "::" || lower.startsWith("fe80:") || lower.startsWith("fc") || lower.startsWith("fd");
117283
117284
  }
117284
- function cacheKey$1(url, allowPrivate, maxBytes, userAgent) {
117285
+ function cacheKey(url, allowPrivate, maxBytes, userAgent) {
117285
117286
  return `local:${url}:${String(allowPrivate)}:${String(maxBytes)}:${userAgent}`;
117286
117287
  }
117287
117288
  const defaultDnsLookup = async (hostname) => {
@@ -117303,7 +117304,7 @@ var LocalFetchURLProvider = class {
117303
117304
  this.dnsLookup = options.dnsLookup ?? defaultDnsLookup;
117304
117305
  }
117305
117306
  async fetch(url, _options) {
117306
- const key = cacheKey$1(url, this.allowPrivateAddresses, this.maxBytes, this.userAgent);
117307
+ const key = cacheKey(url, this.allowPrivateAddresses, this.maxBytes, this.userAgent);
117307
117308
  const cached = this.cache.get(key);
117308
117309
  if (cached !== void 0) return cached;
117309
117310
  await assertSafeFetchTarget(url, this.allowPrivateAddresses, this.dnsLookup);
@@ -117362,268 +117363,220 @@ var LocalFetchURLProvider = class {
117362
117363
  }
117363
117364
  };
117364
117365
  //#endregion
117365
- //#region ../../packages/agent-core/src/tools/providers/scream-cli-fetch-url.ts
117366
+ //#region ../../packages/agent-core/src/tools/providers/search-timeout.ts
117366
117367
  /**
117367
- * ScreamCliFetchURLProvider host-side UrlFetcher.
117368
+ * Hard timeout for outbound web-search requests (omp pattern).
117368
117369
  *
117369
- * Flow:
117370
- * 1. Try ScreamCli coding-fetch service (POST {url}, Bearer token from a
117371
- * narrow token provider, Accept: text/markdown, host-provided headers).
117372
- * 2. ScreamCli 200 return the body as `extracted` content (the
117373
- * service has already extracted the main page text on its side).
117374
- * 3. Any ScreamCli failure — non-200, network error, or token
117375
- * refresh failure delegate to `localFallback`, forwarding its
117376
- * content kind, so the LLM still gets *something* when the service
117377
- * is down.
117378
- * 4. If localFallback also throws → propagate that error.
117379
- */
117380
- function cacheKey(baseUrl, url) {
117381
- return `cli:${baseUrl}:${url}`;
117382
- }
117383
- var ScreamCliFetchURLProvider = class {
117384
- tokenProvider;
117385
- apiKey;
117386
- baseUrl;
117387
- defaultHeaders;
117388
- customHeaders;
117389
- localFallback;
117390
- fetchImpl;
117391
- cache;
117392
- constructor(options) {
117393
- this.tokenProvider = options.tokenProvider;
117394
- this.apiKey = options.apiKey;
117395
- this.baseUrl = options.baseUrl;
117396
- this.defaultHeaders = options.defaultHeaders ?? {};
117397
- this.customHeaders = options.customHeaders ?? {};
117398
- this.localFallback = options.localFallback;
117399
- this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
117400
- this.cache = options.cache ?? new FetchCache();
117401
- }
117402
- async fetch(url, options) {
117403
- const key = cacheKey(this.baseUrl, url);
117404
- const cached = this.cache.get(key);
117405
- if (cached !== void 0) return cached;
117406
- const result = await this.fetchFresh(url, options?.toolCallId);
117407
- this.cache.set(key, result);
117408
- return result;
117409
- }
117410
- async fetchFresh(url, toolCallId) {
117411
- try {
117412
- return {
117413
- content: await this.fetchViaScreamCli(url, toolCallId),
117414
- kind: "extracted"
117415
- };
117416
- } catch {
117417
- return this.localFallback.fetch(url, { toolCallId });
117418
- }
117419
- }
117420
- async fetchViaScreamCli(url, toolCallId) {
117421
- const bodyJson = JSON.stringify({ url });
117422
- const response = await this.post(bodyJson, toolCallId);
117423
- if (response.status !== 200) {
117424
- let detail = "";
117425
- try {
117426
- detail = await response.text();
117427
- } catch {}
117428
- throw new HttpFetchError(response.status, `ScreamCli fetch request failed: HTTP ${String(response.status)}. ${detail}`.trim());
117429
- }
117430
- return response.text();
117431
- }
117432
- async post(bodyJson, toolCallId) {
117433
- const accessToken = await this.resolveApiKey();
117434
- return this.fetchImpl(this.baseUrl, {
117435
- method: "POST",
117436
- headers: {
117437
- ...this.defaultHeaders,
117438
- Authorization: `Bearer ${accessToken}`,
117439
- Accept: "text/markdown",
117440
- "Content-Type": "application/json",
117441
- ...toolCallId !== void 0 && toolCallId.length > 0 ? { "X-Msh-Tool-Call-Id": toolCallId } : {},
117442
- ...this.customHeaders
117443
- },
117444
- body: bodyJson
117445
- });
117446
- }
117447
- async resolveApiKey() {
117448
- if (this.tokenProvider !== void 0) try {
117449
- const token = await this.tokenProvider.getAccessToken();
117450
- if (token.trim().length > 0) return token;
117451
- if (this.apiKey !== void 0 && this.apiKey.length > 0) return this.apiKey;
117452
- } catch (error) {
117453
- if (this.apiKey !== void 0 && this.apiKey.length > 0) return this.apiKey;
117454
- throw error;
117455
- }
117456
- if (this.apiKey !== void 0 && this.apiKey.length > 0) return this.apiKey;
117457
- throw new Error("ScreamCli fetch service is not configured: missing API key or token provider.");
117458
- }
117459
- };
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
+ }
117460
117380
  //#endregion
117461
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";
117462
117390
  var DuckDuckGoSearchProvider = class {
117391
+ name = "duckduckgo";
117463
117392
  fetchImpl;
117464
117393
  constructor(options = {}) {
117465
117394
  this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
117466
117395
  }
117467
117396
  async search(query, options) {
117468
117397
  const limit = options?.limit ?? 5;
117469
- let results = await this.searchViaApi(query, limit);
117470
- if (results.length === 0) results = await this.searchViaLite(query, limit);
117471
- return results;
117472
- }
117473
- /**
117474
- * Query the DuckDuckGo Instant Answer API.
117475
- *
117476
- * Returns up to `limit` results built from the abstract and flattened
117477
- * RelatedTopics. Returns `[]` on any failure (network error, non-2xx,
117478
- * parse failure) so callers can fall through to the next method.
117479
- */
117480
- async searchViaApi(query, limit) {
117481
- const url = `https://api.duckduckgo.com/?q=${encodeURIComponent(query)}&format=json&no_html=1&skip_disambig=1`;
117482
- let response;
117483
- try {
117484
- response = await this.fetchImpl(url);
117485
- } catch {
117486
- return [];
117487
- }
117488
- if (response.status !== 200) {
117489
- await drainBody(response);
117490
- return [];
117491
- }
117492
- let json;
117493
- try {
117494
- json = await response.json();
117495
- } catch {
117496
- return [];
117497
- }
117498
- return this.buildApiResults(json, limit);
117499
- }
117500
- buildApiResults(json, limit) {
117501
- const results = [];
117502
- if (json.AbstractText && json.AbstractURL && json.AbstractText.trim().length > 0) results.push({
117503
- title: json.Heading && json.Heading.trim().length > 0 ? json.Heading : json.AbstractSource ?? "DuckDuckGo",
117504
- url: json.AbstractURL,
117505
- snippet: json.AbstractText
117506
- });
117507
- if (json.RelatedTopics && json.RelatedTopics.length > 0) {
117508
- const flattened = flattenRelatedTopics(json.RelatedTopics);
117509
- for (const topic of flattened) {
117510
- if (results.length >= limit) break;
117511
- const url = topic.FirstURL ?? "";
117512
- const text = topic.Text ?? "";
117513
- if (url === "" || text === "") continue;
117514
- results.push({
117515
- title: text,
117516
- url,
117517
- snippet: text
117518
- });
117519
- }
117520
- }
117521
- return results.slice(0, limit);
117522
- }
117523
- /**
117524
- * Scrape DuckDuckGo Lite search results.
117525
- *
117526
- * Lite is a minimal HTML table with no JavaScript, designed for basic
117527
- * browsers. It may return a CAPTCHA page from datacenter IPs — in that
117528
- * case we return `[]` so the caller can fall through.
117529
- */
117530
- async searchViaLite(query, limit) {
117531
- const url = `https://lite.duckduckgo.com/lite/?q=${encodeURIComponent(query)}`;
117532
- let response;
117533
- try {
117534
- response = await this.fetchImpl(url);
117535
- } catch {
117536
- return [];
117537
- }
117538
- if (response.status !== 200) {
117539
- await drainBody(response);
117540
- return [];
117541
- }
117542
- let html;
117543
- try {
117544
- html = await response.text();
117545
- } catch {
117546
- return [];
117547
- }
117548
- if (isCaptchaPage(html)) return [];
117549
- 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);
117550
117420
  }
117551
117421
  };
117552
- /**
117553
- * Recursively flatten DuckDuckGo RelatedTopics.
117554
- *
117555
- * The API nests topics in two ways:
117556
- * - A direct topic has `FirstURL` + `Text` (may lack `Topics`).
117557
- * - A category group has a `Name` and nested `Topics[]`.
117558
- * We walk both shapes, returning a flat list of leaf topics.
117559
- */
117560
- function flattenRelatedTopics(topics) {
117561
- const out = [];
117562
- for (const topic of topics) if (topic.Topics && topic.Topics.length > 0) out.push(...flattenRelatedTopics(topic.Topics));
117563
- else if (topic.FirstURL) out.push(topic);
117564
- 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");
117565
117425
  }
117566
117426
  /**
117567
- * 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.
117568
117431
  */
117569
- function isCaptchaPage(html) {
117570
- 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();
117571
117438
  }
117572
117439
  /**
117573
- * DuckDuckGo Lite result rows are inside `<tr>` blocks with a predictable
117574
- * anchor shape. This regex extracts the `<a class="result-link"...>` block
117575
- * and the following snippet text from the same row.
117576
- *
117577
- * The Lite markup is intentionally minimal (no JS, no CSS classes beyond a
117578
- * 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.
117579
117443
  */
117580
- const LITE_RESULT_RE = /<a\s[^>]*?\bclass\s*=\s*["'][^"']*result-link[^"']*["'][^>]*?\bhref\s*=\s*["']([^"']+)["'][^>]*>([\s\S]*?)<\/a>/gi;
117581
- const LITE_SNIPPET_RE = /<td\s[^>]*?\bclass\s*=\s*["'][^"']*result-snippet[^"']*["'][^>]*>([\s\S]*?)<\/td>/gi;
117582
- /** Strip HTML tags and decode common HTML entities. */
117583
- function stripHtmlAndDecode(text) {
117584
- return text.replaceAll(/<[^>]*>/g, "").replaceAll("&amp;", "&").replaceAll("&lt;", "<").replaceAll("&gt;", ">").replaceAll("&quot;", "\"").replaceAll("&#x27;", "'").replaceAll("&#39;", "'").replaceAll("&nbsp;", " ").trim();
117585
- }
117586
- /** Unwrap a DuckDuckGo redirect URL to the real target if possible. */
117587
- function unwrapDdgRedirect(url) {
117588
- const uddgMatch = url.match(/[?&]uddg=([^&]+)/);
117589
- if (uddgMatch?.[1]) try {
117590
- 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]);
117591
117450
  } catch {
117592
- return url;
117451
+ return;
117593
117452
  }
117594
- return url;
117453
+ if (decoded.startsWith("//")) return `https:${decoded}`;
117454
+ if (decoded.startsWith("http://") || decoded.startsWith("https://")) return decoded;
117595
117455
  }
117596
- function parseLiteResults(html, limit) {
117456
+ function parseHtmlResults(html) {
117597
117457
  const results = [];
117598
- const linkMatches = html.matchAll(LITE_RESULT_RE);
117599
- for (const match of linkMatches) {
117600
- if (results.length >= limit) break;
117601
- const href = unwrapDdgRedirect(match[1] ?? "");
117602
- const title = stripHtmlAndDecode(match[2] ?? "");
117603
- 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] ?? "") : "";
117604
117470
  results.push({
117605
- title,
117606
- url: href,
117607
- snippet: title
117471
+ title: titleText,
117472
+ url,
117473
+ snippet: snippetText !== "" ? snippetText : titleText
117608
117474
  });
117609
117475
  }
117610
- const snippetMatches = html.matchAll(LITE_SNIPPET_RE);
117611
- let snippetIndex = 0;
117612
- for (const match of snippetMatches) {
117613
- if (snippetIndex >= results.length) break;
117614
- const snippet = stripHtmlAndDecode(match[1] ?? "");
117615
- const entry = results[snippetIndex];
117616
- if (entry && snippet.length > 0) entry.snippet = snippet;
117617
- snippetIndex++;
117618
- }
117619
117476
  return results;
117620
117477
  }
117621
- /** Drain a response body so the connection can be reused. */
117622
- async function drainBody(response) {
117623
- try {
117624
- await response.text();
117625
- } 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();
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;
117626
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
+ };
117627
117580
  //#endregion
117628
117581
  //#region ../../packages/agent-core/src/tools/providers/fallback-search.ts
117629
117582
  var FallbackSearchProvider = class {
@@ -117633,94 +117586,31 @@ var FallbackSearchProvider = class {
117633
117586
  this.providers = providers;
117634
117587
  }
117635
117588
  async search(query, options) {
117636
- for (const provider of this.providers) try {
117637
- const results = await provider.search(query, options);
117638
- if (results.length > 0) return results;
117639
- } catch {
117640
- continue;
117641
- }
117642
- return [];
117643
- }
117644
- };
117645
- //#endregion
117646
- //#region ../../packages/agent-core/src/tools/providers/scream-cli-web-search.ts
117647
- var ScreamCliWebSearchProvider = class {
117648
- tokenProvider;
117649
- apiKey;
117650
- baseUrl;
117651
- defaultHeaders;
117652
- customHeaders;
117653
- fetchImpl;
117654
- constructor(options) {
117655
- this.tokenProvider = options.tokenProvider;
117656
- this.apiKey = options.apiKey;
117657
- this.baseUrl = options.baseUrl;
117658
- this.defaultHeaders = options.defaultHeaders ?? {};
117659
- this.customHeaders = options.customHeaders ?? {};
117660
- this.fetchImpl = options.fetchImpl ?? globalThis.fetch.bind(globalThis);
117661
- }
117662
- async search(query, options) {
117663
- const body = {
117664
- text_query: query,
117665
- limit: options?.limit ?? 5,
117666
- enable_page_crawling: options?.includeContent ?? false,
117667
- timeout_seconds: 30
117668
- };
117669
- const bodyJson = JSON.stringify(body);
117670
- const toolCallId = options?.toolCallId;
117671
- const response = await this.post(bodyJson, toolCallId);
117672
- if (response.status === 401) {
117673
- const detail = await safeReadText(response);
117674
- throw new Error(`ScreamCli search request failed: HTTP 401 (auth/unauthorized). ${detail}`.trim());
117675
- }
117676
- if (response.status !== 200) {
117677
- const detail = await safeReadText(response);
117678
- throw new Error(`ScreamCli search request failed: HTTP ${String(response.status)}. ${detail}`.trim());
117679
- }
117680
- const json = await response.json();
117681
- return (Array.isArray(json.search_results) ? json.search_results : []).map((r) => {
117682
- const out = {
117683
- title: r.title ?? "",
117684
- url: r.url ?? "",
117685
- snippet: r.snippet ?? ""
117686
- };
117687
- if (typeof r.date === "string" && r.date.length > 0) out.date = r.date;
117688
- if (typeof r.content === "string" && r.content.length > 0) out.content = r.content;
117689
- return out;
117690
- });
117691
- }
117692
- async post(bodyJson, toolCallId) {
117693
- const accessToken = await this.resolveApiKey();
117694
- return this.fetchImpl(this.baseUrl, {
117695
- method: "POST",
117696
- headers: {
117697
- ...this.defaultHeaders,
117698
- Authorization: `Bearer ${accessToken}`,
117699
- "Content-Type": "application/json",
117700
- ...toolCallId !== void 0 && toolCallId.length > 0 ? { "X-Msh-Tool-Call-Id": toolCallId } : {},
117701
- ...this.customHeaders
117702
- },
117703
- body: bodyJson
117704
- });
117705
- }
117706
- async resolveApiKey() {
117707
- if (this.tokenProvider !== void 0) try {
117708
- return await this.tokenProvider.getAccessToken();
117709
- } catch (error) {
117710
- if (this.apiKey !== void 0 && this.apiKey.length > 0) return this.apiKey;
117711
- 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
+ }
117712
117606
  }
117713
- if (this.apiKey !== void 0 && this.apiKey.length > 0) return this.apiKey;
117714
- 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}`);
117715
117612
  }
117716
117613
  };
117717
- async function safeReadText(response) {
117718
- try {
117719
- return await response.text();
117720
- } catch {
117721
- return "";
117722
- }
117723
- }
117724
117614
  //#endregion
117725
117615
  //#region ../../packages/agent-core/src/session/export/manifest.ts
117726
117616
  const WIRE_PROTOCOL_VERSION = "1.3";
@@ -118910,7 +118800,7 @@ function providerApiKey(provider) {
118910
118800
  case "openai_responses": return providerValue(provider.apiKey, provider.env, "OPENAI_API_KEY");
118911
118801
  case "scream": return providerValue(provider.apiKey, provider.env, "SCREAM_API_KEY");
118912
118802
  case "google-genai": return providerValue(provider.apiKey, provider.env, "GOOGLE_API_KEY");
118913
- 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");
118914
118804
  default: {
118915
118805
  const exhaustive = provider.type;
118916
118806
  throw new ScreamError(ErrorCodes.MODEL_CONFIG_INVALID, `Unsupported provider type: ${String(exhaustive)}`);
@@ -118927,21 +118817,21 @@ function vertexAILocation(provider) {
118927
118817
  return envValue(provider.env, "GOOGLE_CLOUD_LOCATION") ?? locationFromVertexAIBaseUrl(provider.baseUrl);
118928
118818
  }
118929
118819
  function providerValue(configured, env, envKey) {
118930
- return nonEmptyString$1(configured) ?? envValue(env, envKey);
118820
+ return nonEmptyString(configured) ?? envValue(env, envKey);
118931
118821
  }
118932
118822
  function envValue(env, key) {
118933
- return nonEmptyString$1(env?.[key]);
118823
+ return nonEmptyString(env?.[key]);
118934
118824
  }
118935
- function nonEmptyString$1(value) {
118825
+ function nonEmptyString(value) {
118936
118826
  const trimmed = value?.trim();
118937
118827
  return trimmed === void 0 || trimmed.length === 0 ? void 0 : trimmed;
118938
118828
  }
118939
118829
  function locationFromVertexAIBaseUrl(baseUrl) {
118940
- const url = nonEmptyString$1(baseUrl);
118830
+ const url = nonEmptyString(baseUrl);
118941
118831
  if (url === void 0) return void 0;
118942
118832
  try {
118943
118833
  const host = new URL(url).hostname;
118944
- 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;
118945
118835
  } catch {
118946
118836
  return;
118947
118837
  }
@@ -120486,7 +120376,6 @@ function waitForSpawn(child) {
120486
120376
  new AsyncLocalStorage();
120487
120377
  //#endregion
120488
120378
  //#region ../../packages/agent-core/src/rpc/core-impl.ts
120489
- const SCREAM_CODE_PROVIDER_NAME = "managed:scream-code";
120490
120379
  var ScreamCore = class {
120491
120380
  rpcClient;
120492
120381
  sdk;
@@ -120967,11 +120856,7 @@ var ScreamCore = class {
120967
120856
  }
120968
120857
  async resolveRuntime(config) {
120969
120858
  if (this.runtime !== void 0) return this.runtime;
120970
- const runtime = await createRuntimeConfig({
120971
- config,
120972
- screamRequestHeaders: this.screamRequestHeaders,
120973
- resolveOAuthTokenProvider: this.resolveOAuthTokenProvider
120974
- });
120859
+ const runtime = await createRuntimeConfig({ config });
120975
120860
  this.runtime = runtime;
120976
120861
  return runtime;
120977
120862
  }
@@ -121028,42 +120913,20 @@ var ScreamCore = class {
121028
120913
  }
121029
120914
  };
121030
120915
  async function createRuntimeConfig(input) {
121031
- const fetchCache = new FetchCache();
121032
- const localFetcher = new LocalFetchURLProvider({ cache: fetchCache });
121033
- const fetchService = input.config.services?.screamCliFetch;
121034
120916
  return {
121035
- urlFetcher: fetchService?.baseUrl === void 0 ? localFetcher : new ScreamCliFetchURLProvider({
121036
- baseUrl: fetchService.baseUrl,
121037
- localFallback: localFetcher,
121038
- defaultHeaders: input.screamRequestHeaders,
121039
- cache: fetchCache,
121040
- ...serviceCredentials(fetchService, input.resolveOAuthTokenProvider)
121041
- }),
120917
+ urlFetcher: new LocalFetchURLProvider({ cache: new FetchCache() }),
121042
120918
  webSearcher: buildWebSearcher(input)
121043
120919
  };
121044
120920
  }
121045
120921
  function buildWebSearcher(input) {
121046
- const searchService = input.config.services?.screamCliSearch;
121047
- const ddgEnabled = input.config.services?.duckduckgo?.enabled !== false;
121048
- const screamProvider = searchService?.baseUrl !== void 0 ? new ScreamCliWebSearchProvider({
121049
- baseUrl: searchService.baseUrl,
121050
- defaultHeaders: input.screamRequestHeaders,
121051
- ...serviceCredentials(searchService, input.resolveOAuthTokenProvider)
121052
- }) : void 0;
121053
- const ddgProvider = ddgEnabled ? new DuckDuckGoSearchProvider() : void 0;
121054
- if (screamProvider && ddgProvider) return new FallbackSearchProvider([screamProvider, ddgProvider]);
121055
- return screamProvider ?? ddgProvider;
121056
- }
121057
- function serviceCredentials(service, resolveOAuthTokenProvider) {
121058
- return {
121059
- apiKey: nonEmptyString(service.apiKey),
121060
- tokenProvider: service.oauth !== void 0 ? resolveOAuthTokenProvider?.(SCREAM_CODE_PROVIDER_NAME, service.oauth) : void 0,
121061
- customHeaders: service.customHeaders
121062
- };
121063
- }
121064
- function nonEmptyString(value) {
121065
- const trimmed = value?.trim();
121066
- 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);
121067
120930
  }
121068
120931
  function requiredWorkDir(operation, value) {
121069
120932
  if (typeof value !== "string" || value.trim() === "") throw new ScreamError(ErrorCodes.REQUEST_WORK_DIR_REQUIRED, `${operation} requires workDir`);
@@ -122527,7 +122390,7 @@ function optionalBuildString(value) {
122527
122390
  return typeof value === "string" && value.length > 0 ? value : void 0;
122528
122391
  }
122529
122392
  const SCREAM_BUILD_INFO = {
122530
- version: optionalBuildString("0.10.1"),
122393
+ version: optionalBuildString("0.10.2"),
122531
122394
  channel: optionalBuildString(""),
122532
122395
  commit: optionalBuildString(""),
122533
122396
  buildTarget: optionalBuildString("darwin-arm64")
@@ -123877,12 +123740,13 @@ function isManagedUsageProvider(providerKey) {
123877
123740
  //#region src/tui/constant/streaming.ts
123878
123741
  const STREAMING_ARGS_FIELD_RE = /"(path|file_path|command|pattern|query|url|description|title|name)"\s*:\s*"((?:\\.|[^"\\])*)"/g;
123879
123742
  const STREAMING_ARGS_PREVIEW_MAX_CHARS = 8 * 1024;
123743
+ const STREAMING_ARGS_BUFFER_MAX_CHARS = 1024 * 1024;
123880
123744
  //#endregion
123881
123745
  //#region src/tui/utils/event-payload.ts
123882
123746
  function appendStreamingArgsPreview(current, next) {
123883
- const existing = (current ?? "").slice(0, STREAMING_ARGS_PREVIEW_MAX_CHARS);
123747
+ const existing = (current ?? "").slice(0, STREAMING_ARGS_BUFFER_MAX_CHARS);
123884
123748
  if (next === null || next === void 0 || next.length === 0) return existing;
123885
- const remaining = STREAMING_ARGS_PREVIEW_MAX_CHARS - existing.length;
123749
+ const remaining = STREAMING_ARGS_BUFFER_MAX_CHARS - existing.length;
123886
123750
  if (remaining <= 0) return existing;
123887
123751
  return `${existing}${next.slice(0, remaining)}`;
123888
123752
  }
@@ -125808,7 +125672,7 @@ var FooterComponent = class {
125808
125672
  gitCache;
125809
125673
  gitCacheWorkDir;
125810
125674
  transientHint = null;
125811
- shimmerTimer = null;
125675
+ statusTimer = null;
125812
125676
  /**
125813
125677
  * Non-terminal background-task counts split by kind so the footer can
125814
125678
  * render two distinct badges. `bashTasks` covers `bash-*` BPM tasks
@@ -125827,15 +125691,13 @@ var FooterComponent = class {
125827
125691
  this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
125828
125692
  }
125829
125693
  setState(state) {
125830
- const wasThinking = this.state?.streamingPhase === "thinking";
125694
+ const prevPhase = this.state?.streamingPhase;
125831
125695
  if (state.workDir !== this.gitCacheWorkDir) {
125832
125696
  this.gitCacheWorkDir = state.workDir;
125833
125697
  this.gitCache = createGitStatusCache(state.workDir, { onChange: this.onGitStatusChange });
125834
125698
  }
125835
125699
  this.state = state;
125836
- const isThinking = state.streamingPhase === "thinking";
125837
- if (isThinking && !wasThinking) this.#startShimmer();
125838
- else if (!isThinking && wasThinking) this.#stopShimmer();
125700
+ if (state.streamingPhase !== prevPhase) this.#restartStatusTimer(state.streamingPhase);
125839
125701
  }
125840
125702
  setColors(colors) {
125841
125703
  this.colors = colors;
@@ -125860,29 +125722,29 @@ var FooterComponent = class {
125860
125722
  }
125861
125723
  invalidate() {}
125862
125724
  /**
125863
- * Stop the shimmer animation timer. Idempotent — safe to call even when
125725
+ * Stop the status timer. Idempotent — safe to call even when
125864
125726
  * the timer isn't running. Call this when the component is disposed.
125865
125727
  */
125866
125728
  dispose() {
125867
- this.#stopShimmer();
125729
+ this.#stopStatusTimer();
125868
125730
  }
125869
- #startShimmer() {
125870
- if (this.shimmerTimer) return;
125871
- this.shimmerTimer = setInterval(() => {
125872
- this.ui.requestRender();
125873
- }, 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);
125874
125738
  }
125875
- #stopShimmer() {
125876
- if (!this.shimmerTimer) return;
125877
- clearInterval(this.shimmerTimer);
125878
- this.shimmerTimer = null;
125739
+ #stopStatusTimer() {
125740
+ if (!this.statusTimer) return;
125741
+ clearInterval(this.statusTimer);
125742
+ this.statusTimer = null;
125879
125743
  }
125880
125744
  render(width) {
125881
125745
  const colors = this.colors;
125882
125746
  const state = this.state;
125883
125747
  const left = [];
125884
- if (state.permissionMode === "auto") left.push(chalk.hex(colors.warning).bold(t("badge.auto")));
125885
- if (state.permissionMode === "yolo") left.push(chalk.hex(colors.warning).bold(t("badge.yes")));
125886
125748
  if (state.planMode !== "off") {
125887
125749
  const isFusion = state.planMode === "fusionplan";
125888
125750
  left.push(chalk.hex(isFusion ? colors.fusionPlanMode : colors.planMode).bold(isFusion ? t("badge.fusion") : t("badge.plan")));
@@ -126766,7 +126628,6 @@ async function handleYoloCommand(host, args) {
126766
126628
  }
126767
126629
  await session.setPermission("yolo");
126768
126630
  host.setAppState({ permissionMode: "yolo" });
126769
- host.showNotice(t("config.yolo_on"), t("config.yolo_on_desc"));
126770
126631
  return;
126771
126632
  }
126772
126633
  if (subcmd === "off") {
@@ -126776,17 +126637,14 @@ async function handleYoloCommand(host, args) {
126776
126637
  }
126777
126638
  await session.setPermission("manual");
126778
126639
  host.setAppState({ permissionMode: "manual" });
126779
- host.showNotice(t("config.yolo_off"));
126780
126640
  return;
126781
126641
  }
126782
126642
  if (currentMode === "yolo") {
126783
126643
  await session.setPermission("manual");
126784
126644
  host.setAppState({ permissionMode: "manual" });
126785
- host.showNotice(t("config.yolo_off"));
126786
126645
  } else {
126787
126646
  await session.setPermission("yolo");
126788
126647
  host.setAppState({ permissionMode: "yolo" });
126789
- host.showNotice(t("config.yolo_toggle_on"), t("config.yolo_toggle_on_desc"));
126790
126648
  }
126791
126649
  }
126792
126650
  async function handleAutoCommand(host, args) {
@@ -126804,7 +126662,6 @@ async function handleAutoCommand(host, args) {
126804
126662
  }
126805
126663
  await session.setPermission("auto");
126806
126664
  host.setAppState({ permissionMode: "auto" });
126807
- host.showNotice(t("config.auto_on"), t("config.auto_on_desc"));
126808
126665
  return;
126809
126666
  }
126810
126667
  if (subcmd === "off") {
@@ -126814,17 +126671,14 @@ async function handleAutoCommand(host, args) {
126814
126671
  }
126815
126672
  await session.setPermission("manual");
126816
126673
  host.setAppState({ permissionMode: "manual" });
126817
- host.showNotice(t("config.auto_off"));
126818
126674
  return;
126819
126675
  }
126820
126676
  if (currentMode === "auto") {
126821
126677
  await session.setPermission("manual");
126822
126678
  host.setAppState({ permissionMode: "manual" });
126823
- host.showNotice(t("config.auto_off"));
126824
126679
  } else {
126825
126680
  await session.setPermission("auto");
126826
126681
  host.setAppState({ permissionMode: "auto" });
126827
- host.showNotice(t("config.auto_toggle_on"), t("config.auto_toggle_on_desc"));
126828
126682
  }
126829
126683
  }
126830
126684
  async function handleWolfpackCommand(host, args) {
@@ -127827,7 +127681,7 @@ async function createGoal(host, parsed) {
127827
127681
  await showGoalConfigWizard(host, session, parsed.objective, parsed.replace);
127828
127682
  }
127829
127683
  async function showGoalConfigWizard(host, session, objective, replace) {
127830
- const { TextInputDialogComponent } = await import("./text-input-dialog-Dk5xzj4F.mjs");
127684
+ const { TextInputDialogComponent } = await import("./text-input-dialog-C8_8qYYi.mjs");
127831
127685
  const turnInput = await promptNumber(host, TextInputDialogComponent, {
127832
127686
  title: t("goal.wizard_title", { objective }),
127833
127687
  subtitle: t("goal.budget_turns_hint"),
@@ -128576,7 +128430,7 @@ var AssistantMessageComponent = class {
128576
128430
  const age = Date.now() - (this.fadeStartMs ?? 0);
128577
128431
  this.cachedWidth = void 0;
128578
128432
  this.cachedLines = void 0;
128579
- this.ui?.requestRender();
128433
+ this.ui?.requestComponentRender(this);
128580
128434
  if (age >= 1200) this.stopFade();
128581
128435
  }, FADE_TICK_MS);
128582
128436
  this.ui.requestRender();
@@ -129057,7 +128911,7 @@ var ThinkingComponent = class {
129057
128911
  if (this.ui === void 0 || this.spinnerInterval !== void 0) return;
129058
128912
  this.spinnerInterval = setInterval(() => {
129059
128913
  this.spinnerFrame = (this.spinnerFrame + 1) % BRAILLE_SPINNER_FRAMES.length;
129060
- this.ui?.requestRender();
128914
+ this.ui?.requestComponentRender(this);
129061
128915
  }, 80);
129062
128916
  }
129063
128917
  stopSpinner() {
@@ -130244,23 +130098,20 @@ function unescapeJsonString(s) {
130244
130098
  });
130245
130099
  }
130246
130100
  /**
130247
- * Pull the live value of a JSON string field out of partially-streamed
130248
- * arguments, even if the closing quote hasn't arrived yet. Handles the
130249
- * common JSON string escapes so `\n` in a streamed `content` becomes a
130250
- * real newline we can highlight. Returns `undefined` if the field hasn't
130251
- * 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.
130252
130106
  */
130253
- function extractPartialStringField(text, key) {
130254
- const match = new RegExp(`"${key}"\\s*:\\s*"`).exec(text);
130255
- if (match === null) return void 0;
130256
- const start = match.index + match[0].length;
130107
+ function unescapeJsonStringValue(text, start, end = text.length) {
130257
130108
  let out = "";
130258
130109
  let i = start;
130259
- while (i < text.length) {
130110
+ while (i < end) {
130260
130111
  const ch = text[i];
130261
130112
  if (ch === "\\") {
130262
130113
  const next = text[i + 1];
130263
- if (next === void 0) return out;
130114
+ if (next === void 0 || i + 1 >= end) return out;
130264
130115
  switch (next) {
130265
130116
  case "n":
130266
130117
  out += "\n";
@@ -130287,7 +130138,7 @@ function extractPartialStringField(text, key) {
130287
130138
  out += "/";
130288
130139
  break;
130289
130140
  case "u": {
130290
- if (i + 5 >= text.length) return out;
130141
+ if (i + 5 >= end) return out;
130291
130142
  const hex = text.slice(i + 2, i + 6);
130292
130143
  const code = Number.parseInt(hex, 16);
130293
130144
  if (Number.isNaN(code)) return out;
@@ -130306,6 +130157,16 @@ function extractPartialStringField(text, key) {
130306
130157
  }
130307
130158
  return out;
130308
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
+ }
130309
130170
  function parseArgsPreview(value) {
130310
130171
  const previewText = value.slice(0, STREAMING_ARGS_PREVIEW_MAX_CHARS);
130311
130172
  if (previewText.trim().length === 0) return {};
@@ -130448,6 +130309,11 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
130448
130309
  subagentEndedAtMs;
130449
130310
  progressLines = [];
130450
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;
130451
130317
  /**
130452
130318
  * Registered by a group container (`AgentGroupComponent` or
130453
130319
  * `ReadGroupComponent`) when this component is borrowed as a hidden state
@@ -131217,21 +131083,12 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
131217
131083
  const name = this.toolCall.name;
131218
131084
  const previewText = streamText.slice(0, STREAMING_ARGS_PREVIEW_MAX_CHARS);
131219
131085
  if (name === "Write") {
131220
- const content = extractPartialStringField(previewText, "content");
131221
- if (content === void 0 || content.length === 0) return;
131222
- const allLines = highlightLines(content, langFromPath(extractPartialStringField(previewText, "file_path") ?? extractPartialStringField(previewText, "path") ?? ""));
131223
- const maxLines = 10;
131224
- const scrollLines = allLines.length > maxLines ? allLines.slice(allLines.length - maxLines) : allLines;
131225
- for (const [i, line] of scrollLines.entries()) {
131226
- const originalLineNumber = allLines.length > maxLines ? allLines.length - maxLines + i : i;
131227
- const lineNum = chalk.dim(String(originalLineNumber + 1).padStart(4) + " ");
131228
- this.addChild(new Text(lineNum + line, 2, 0));
131229
- }
131086
+ this.buildWriteStreamingPreview(streamText);
131230
131087
  return;
131231
131088
  }
131232
131089
  if (name === "Edit") {
131233
131090
  const filePath = extractPartialStringField(previewText, "file_path") ?? extractPartialStringField(previewText, "path") ?? "";
131234
- const bytes = Buffer.byteLength(previewText, "utf8");
131091
+ const bytes = Buffer.byteLength(streamText, "utf8");
131235
131092
  const startedAtMs = this.toolCall.streamingStartedAtMs;
131236
131093
  const elapsedSeconds = startedAtMs === void 0 ? 0 : Math.max(0, Math.floor((Date.now() - startedAtMs) / 1e3));
131237
131094
  const progress = t("toolcall.preparing_changes", {
@@ -131253,6 +131110,54 @@ var ToolCallComponent = class ToolCallComponent extends CachedContainer {
131253
131110
  }));
131254
131111
  }
131255
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
+ }
131256
131161
  buildPlanPreview() {
131257
131162
  const plan = this.resolvePlanForPreview();
131258
131163
  if (plan.length === 0) return;
@@ -133232,7 +133137,7 @@ var MoonLoader = class extends Text {
133232
133137
  const frame = this.frames[this.currentFrame];
133233
133138
  const coloredFrame = this.colorFn ? this.colorFn(frame) : frame;
133234
133139
  this.setText(this.label ? `${coloredFrame} ${this.label}` : coloredFrame);
133235
- this.ui.requestRender();
133140
+ this.ui.requestComponentRender(this);
133236
133141
  }
133237
133142
  };
133238
133143
  //#endregion
@@ -141506,7 +141411,7 @@ var PulseWaveLoader = class extends Text {
141506
141411
  2
141507
141412
  ].map((idx) => this.renderCell(idx, step.active, step.forward));
141508
141413
  this.setText(cells.join(" "));
141509
- this.ui.requestRender();
141414
+ this.ui.requestComponentRender(this);
141510
141415
  }
141511
141416
  renderCell(index, active, forward) {
141512
141417
  const distance = forward ? active - index : index - active;
@@ -143105,6 +143010,8 @@ var CustomEditor = class extends Editor {
143105
143010
  thinking = false;
143106
143011
  /** Current thinking effort level (e.g. low, medium, high). Used to annotate the think label. */
143107
143012
  thinkingLevel = "off";
143013
+ /** Current permission mode — always shown as a badge at the top-left of the input box border. */
143014
+ permissionMode = "manual";
143108
143015
  /** Current border colour hex — kept in sync with borderColor by the host. */
143109
143016
  borderHex = "";
143110
143017
  consumingPaste = false;
@@ -143165,7 +143072,13 @@ var CustomEditor = class extends Editor {
143165
143072
  const withPrompt = injectPromptSymbol(firstContent);
143166
143073
  if (withPrompt !== void 0) lines[firstContentIdx] = withPrompt;
143167
143074
  }
143168
- 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
+ });
143169
143082
  return lines;
143170
143083
  }
143171
143084
  handleInput(data) {
@@ -143298,34 +143211,47 @@ function injectPromptSymbol(line) {
143298
143211
  return "> " + line.slice(2);
143299
143212
  }
143300
143213
  const THINK_LABEL_MIN_WIDTH = 14;
143214
+ const MODE_BADGE_MIN_WIDTH = 10;
143301
143215
  function isLightBg(hex) {
143302
143216
  const r = parseInt(hex.slice(1, 3), 16);
143303
143217
  const g = parseInt(hex.slice(3, 5), 16);
143304
143218
  const b = parseInt(hex.slice(5, 7), 16);
143305
143219
  return (.299 * r + .587 * g + .114 * b) / 255 > .5;
143306
143220
  }
143221
+ function makeBadge(label, bgHex) {
143222
+ return bgHex ? chalk.bgHex(bgHex).hex(isLightBg(bgHex) ? "#000000" : "#FFFFFF")(label) : chalk.bgBlack.white(label);
143223
+ }
143307
143224
  /**
143308
- * Embed a small "think" badge into the top border line of the editor.
143309
- * The label only appears when the active model has thinking enabled.
143310
- * Works with pi-tui's flat two-line design (no side borders).
143311
- *
143312
- * The badge uses the border colour as background with black text for
143313
- * maximum contrast against the bright border colours (yellow-green,
143314
- * 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.
143315
143232
  */
143316
- function injectThinkLabel(lines, width, thinkingLevel, paint, borderHex) {
143317
- if (width < THINK_LABEL_MIN_WIDTH) return;
143233
+ function injectBorderBadges(lines, width, opts) {
143318
143234
  const topIdx = lines.findIndex((line) => {
143319
143235
  const plain = stripSgr(line);
143320
143236
  return plain.length > 0 && plain[0] === "─";
143321
143237
  });
143322
143238
  if (topIdx === -1) return;
143323
- const label = thinkingLevel !== "off" ? ` Think ${thinkingLevel} ` : " Think ";
143324
- const badge = borderHex ? chalk.bgHex(borderHex).hex(isLightBg(borderHex) ? "#000000" : "#FFFFFF")(label) : chalk.bgBlack.white(label);
143325
- const badgeVis = visibleWidth(badge);
143326
- const leftDashCount = width - 1 - badgeVis;
143327
- if (leftDashCount < 1) return;
143328
- 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;
143329
143255
  }
143330
143256
  //#endregion
143331
143257
  //#region src/tui/utils/terminal-state.ts
@@ -143566,6 +143492,7 @@ function createTUIState(options) {
143566
143492
  const editor = new CustomEditor(ui, theme.colors);
143567
143493
  editor.thinking = initialAppState.thinkingLevel !== "off";
143568
143494
  editor.thinkingLevel = initialAppState.thinkingLevel;
143495
+ editor.permissionMode = initialAppState.permissionMode ?? "manual";
143569
143496
  return {
143570
143497
  ui,
143571
143498
  terminal,
@@ -146661,6 +146588,7 @@ var ScreamTUI = class {
146661
146588
  this.state.editor.thinking = patch.thinkingLevel !== "off";
146662
146589
  this.state.editor.thinkingLevel = patch.thinkingLevel ?? "off";
146663
146590
  }
146591
+ if ("permissionMode" in patch) this.state.editor.permissionMode = patch.permissionMode ?? "manual";
146664
146592
  if ("streamingPhase" in patch && patch.streamingPhase !== "idle") {
146665
146593
  this.transcriptController.stopWelcomeBreathing();
146666
146594
  this.inputController.stopBreathingForStreaming();