farai 0.1.5 → 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -3958,7 +3958,6 @@ function atomicWriteFile(path, content, mode) {
3958
3958
  }
3959
3959
  }
3960
3960
  var DEFAULT_TRANSPARENT_PROXY_PORTS, LSP_SERVER_IDS, DEFAULT_CONFIG_TEMPLATE = `model = "big-pickle"
3961
- max_turn_seconds = 900
3962
3961
 
3963
3962
  [proxy]
3964
3963
  transparent = true
@@ -5018,14 +5017,45 @@ function sanitizeToolOutput(value) {
5018
5017
  if (http && isBinaryLike(http.body)) {
5019
5018
  return `${sanitizeText(http.head).trimEnd()}
5020
5019
 
5021
- [binary body suppressed: ${byteLength(http.body)} bytes]`;
5020
+ ${binaryPreview(http.body, "binary body")}`;
5022
5021
  }
5023
5022
  if (isBinaryLike(value)) {
5024
- return `[binary output suppressed: ${byteLength(value)} bytes]
5025
- Use a file-oriented command such as file, unzip -l, strings, or hexdump -C to inspect it.`;
5023
+ return binaryPreview(value, "binary-like output");
5026
5024
  }
5027
5025
  return sanitizeText(value);
5028
5026
  }
5027
+ function binaryPreview(value, label) {
5028
+ const bytes = Buffer.from(value, "utf8");
5029
+ const strings = printableStrings(bytes).slice(0, 24);
5030
+ const hex = [...bytes.subarray(0, 192)].map((byte) => byte.toString(16).padStart(2, "0")).reduce((lines, byte, index) => {
5031
+ const line = Math.floor(index / 16);
5032
+ lines[line] = `${lines[line] ?? ""}${lines[line] ? " " : ""}${byte}`;
5033
+ return lines;
5034
+ }, []);
5035
+ return [`[${label}: ${bytes.byteLength} bytes; showing readable strings and a hex preview]`, ...strings.length ? [strings.join(`
5036
+ `)] : [], ...hex.length ? [hex.join(`
5037
+ `)] : []].join(`
5038
+ `);
5039
+ }
5040
+ function printableStrings(bytes) {
5041
+ const result = [];
5042
+ let current = "";
5043
+ const flush = () => {
5044
+ if (current.length >= 4)
5045
+ result.push(current);
5046
+ current = "";
5047
+ };
5048
+ for (const byte of bytes) {
5049
+ if (byte === 9 || byte === 32 || byte >= 33 && byte <= 126)
5050
+ current += String.fromCharCode(byte);
5051
+ else if (byte === 10 || byte === 13)
5052
+ flush();
5053
+ else
5054
+ flush();
5055
+ }
5056
+ flush();
5057
+ return result;
5058
+ }
5029
5059
  function isBinaryLike(value) {
5030
5060
  if (!value)
5031
5061
  return false;
@@ -5093,9 +5123,6 @@ function consumeStringEscape(value, start, bellTerminates) {
5093
5123
  }
5094
5124
  return index;
5095
5125
  }
5096
- function byteLength(value) {
5097
- return Buffer.byteLength(value, "utf8");
5098
- }
5099
5126
  function splitHttpResponse(value) {
5100
5127
  if (!value.startsWith("HTTP/"))
5101
5128
  return;
@@ -6195,14 +6222,10 @@ function containerRelativePath(path) {
6195
6222
  return ".";
6196
6223
  return resolved.startsWith(prefix) ? resolved.slice(prefix.length) : resolved;
6197
6224
  }
6198
- function assertNotProtectedPath(path, intent) {
6225
+ function assertNotProtectedPath(path, _intent) {
6199
6226
  const rel = containerRelativePath(path);
6200
- if (rel === ".git" || rel.startsWith(".git/"))
6201
- throw new Error("path is protected: .git");
6202
6227
  if (rel === ".farai" || rel.startsWith(".farai/"))
6203
6228
  throw new Error("path is protected: .farai");
6204
- if (intent === "write" && rel === ".gitignore")
6205
- throw new Error("path is protected: .gitignore");
6206
6229
  }
6207
6230
  function shQuote2(value) {
6208
6231
  return `'${value.replace(/'/g, `'\\''`)}'`;
@@ -6274,7 +6297,7 @@ async function containerListFilesRecursive(context, path, limit) {
6274
6297
  import os
6275
6298
  root = ${JSON.stringify(root)}
6276
6299
  limit = ${Math.max(1, Math.floor(limit))}
6277
- exclude = {".git", ".farai", "node_modules"}
6300
+ exclude = {".farai", "node_modules"}
6278
6301
  out = []
6279
6302
  for dirpath, dirnames, filenames in os.walk(root):
6280
6303
  dirnames[:] = sorted(d for d in dirnames if d not in exclude)
@@ -6300,7 +6323,7 @@ root = ${JSON.stringify(root)}
6300
6323
  pattern = re.compile(base64.b64decode(${JSON.stringify(Buffer.from(pattern, "utf8").toString("base64"))}).decode())
6301
6324
  include = ${include === undefined ? "None" : JSON.stringify(include)}
6302
6325
  limit = ${Math.max(1, Math.floor(limit))}
6303
- exclude = {".git", ".farai", "node_modules"}
6326
+ exclude = {".farai", "node_modules"}
6304
6327
  matches = []
6305
6328
  for dirpath, dirnames, filenames in os.walk(root):
6306
6329
  dirnames[:] = sorted(d for d in dirnames if d not in exclude)
@@ -6432,12 +6455,8 @@ function safeWorkspacePath(workspace, path, intent) {
6432
6455
  return resolved;
6433
6456
  }
6434
6457
  const normalized = rel.split(/[\\/]+/).join("/");
6435
- if (normalized === ".git" || normalized.startsWith(".git/"))
6436
- throw new Error("path is protected: .git");
6437
6458
  if (normalized === ".farai" || normalized.startsWith(".farai/"))
6438
6459
  throw new Error("path is protected: .farai");
6439
- if (intent === "write" && normalized === ".gitignore")
6440
- throw new Error("path is protected: .gitignore");
6441
6460
  return resolved;
6442
6461
  }
6443
6462
  function safeExistingWorkspacePath(workspace, path, intent) {
@@ -6448,12 +6467,8 @@ function safeExistingWorkspacePath(workspace, path, intent) {
6448
6467
  if (rel.startsWith("..") || rel === "")
6449
6468
  throw new Error(`path escapes workspace${path.startsWith("/") ? ESCAPE_HINT : ""}`);
6450
6469
  const normalized = rel.split(/[\\/]+/).join("/");
6451
- if (normalized === ".git" || normalized.startsWith(".git/"))
6452
- throw new Error("path is protected: .git");
6453
6470
  if (normalized === ".farai" || normalized.startsWith(".farai/"))
6454
6471
  throw new Error("path is protected: .farai");
6455
- if (intent === "write" && normalized === ".gitignore")
6456
- throw new Error("path is protected: .gitignore");
6457
6472
  return resolved;
6458
6473
  }
6459
6474
  function page(items, offset, limit) {
@@ -10901,7 +10916,7 @@ function scopedToolName(name) {
10901
10916
  return TOOL_SCOPE_ALIASES.get(canonical) ?? canonical;
10902
10917
  }
10903
10918
  function resolveSubagentToolScope(input) {
10904
- const available = new Set(input.availableTools.map((tool) => canonicalToolName(tool.name)).filter((name) => !NON_DELEGABLE_TOOLS.has(name)));
10919
+ const available = new Set(input.availableTools.map((tool) => canonicalToolName(tool.name)));
10905
10920
  const requested = input.requestedTools?.map(scopedToolName);
10906
10921
  if (requested) {
10907
10922
  const unique = [...new Set(requested)];
@@ -10925,14 +10940,13 @@ function hasSharedWorkspaceEdits(tools) {
10925
10940
  return tools.map(canonicalToolName).some((tool) => SHARED_WORKSPACE_EDIT_TOOLS.has(tool));
10926
10941
  }
10927
10942
  function buildSubagentTaskPrompt(input) {
10928
- return ["you are a leaf subagent working for a parent farai session.", `parent session: ${input.parentSessionId}`, `task: ${input.title}`, ...input.lane ? [`lane: ${input.lane}`] : [], ...input.tools?.length ? [`tool scope: ${input.tools.join(", ")}`] : [], "work autonomously only on this bounded task. do not delegate again, ask the user questions, repeat parent work, broaden scope, or write a user-facing answer.", "preserve exact evidence and return one concise result with status, summary, claims, artifacts, changes, coverage, uncertainty, next actions, and metrics. distinguish proven, candidate, disproven, and inconclusive claims. the parent owns synthesis and the final answer.", ...input.lanePrompt ? [input.lanePrompt] : [], input.task].join(`
10943
+ return ["you are a subagent working for a parent farai session.", `parent session: ${input.parentSessionId}`, `task: ${input.title}`, ...input.lane ? [`lane: ${input.lane}`] : [], ...input.tools?.length ? [`tool scope: ${input.tools.join(", ")}`] : [], "work autonomously on the delegated task. you may delegate concrete independent subtasks when useful. avoid repeating parent work or broadening the task without evidence.", "preserve exact evidence and return one concise result with status, summary, claims, artifacts, changes, coverage, uncertainty, next actions, and metrics. distinguish proven, candidate, disproven, and inconclusive claims. the parent owns synthesis and the final answer.", ...input.lanePrompt ? [input.lanePrompt] : [], input.task].join(`
10929
10944
 
10930
10945
  `);
10931
10946
  }
10932
- var NON_DELEGABLE_TOOLS, SHARED_WORKSPACE_EDIT_TOOLS, TOOL_SCOPE_ALIASES;
10947
+ var SHARED_WORKSPACE_EDIT_TOOLS, TOOL_SCOPE_ALIASES;
10933
10948
  var init_scope = __esm(() => {
10934
10949
  init_tool_names();
10935
- NON_DELEGABLE_TOOLS = new Set(["tool_search", "tool_invoke", "request_user_input", "agent_spawn", "agent_list", "agent_wait", "agent_message", "agent_followup", "agent_interrupt", "agent_close", "campaign_dispatch"]);
10936
10950
  SHARED_WORKSPACE_EDIT_TOOLS = new Set(["fs_write", "fs_edit", "patch_apply", "code_write_script"]);
10937
10951
  TOOL_SCOPE_ALIASES = new Map([["shell", "shell_exec"]]);
10938
10952
  });
@@ -10948,7 +10962,7 @@ var init_dispatch = __esm(() => {
10948
10962
  init_renderers();
10949
10963
  campaignDispatchTool = {
10950
10964
  name: "campaign_dispatch",
10951
- description: "dispatch up to three bounded child workers with non-overlapping ownership claims. workers return evidence and candidate hypotheses, never confirmed findings. set background=true only when the parent can continue independently.",
10965
+ description: "dispatch child workers with non-overlapping ownership claims. workers return evidence and candidate hypotheses, never confirmed findings. set background=true only when the parent can continue independently.",
10952
10966
  inputSchema: {
10953
10967
  type: "object",
10954
10968
  required: ["tasks"],
@@ -10962,7 +10976,6 @@ var init_dispatch = __esm(() => {
10962
10976
  tasks: {
10963
10977
  type: "array",
10964
10978
  minItems: 1,
10965
- maxItems: 3,
10966
10979
  items: {
10967
10980
  type: "object",
10968
10981
  required: ["title", "prompt"],
@@ -10992,7 +11005,7 @@ var init_dispatch = __esm(() => {
10992
11005
  }
10993
11006
  },
10994
11007
  mutates: true,
10995
- timeoutMs: 120000,
11008
+ timeoutMs: Number.POSITIVE_INFINITY,
10996
11009
  parallel: false,
10997
11010
  concurrencyScope: "session",
10998
11011
  renderHuman: defaultHumanRenderer,
@@ -11005,8 +11018,6 @@ var init_dispatch = __esm(() => {
11005
11018
  throw new Error("delegation is unavailable in this runtime");
11006
11019
  if (!Array.isArray(args.tasks) || args.tasks.length === 0)
11007
11020
  throw new Error("tasks must contain at least one worker task");
11008
- if (args.tasks.length > 3)
11009
- throw new Error("tasks cannot contain more than three worker tasks");
11010
11021
  const background = args.background === true;
11011
11022
  const tasks = args.tasks.map((task) => {
11012
11023
  if (!task || typeof task !== "object")
@@ -11030,8 +11041,6 @@ var init_dispatch = __esm(() => {
11030
11041
  throw new Error("task title and prompt must be non-empty");
11031
11042
  if (tasks.length > 1 && tasks.some((task) => !task.claim))
11032
11043
  throw new Error("each parallel campaign worker requires an exclusive claim");
11033
- if (background && tasks.some((task) => !task.lane))
11034
- throw new Error("background campaign workers require an explicit lane");
11035
11044
  const claims = tasks.flatMap((task) => task.claim ? [normalizeClaim(task.claim)] : []);
11036
11045
  if (new Set(claims).size !== claims.length)
11037
11046
  throw new Error("parallel campaign worker claims must be unique");
@@ -11041,15 +11050,13 @@ var init_dispatch = __esm(() => {
11041
11050
  const lane = task.lane ? resolveLane(context.rootWorkspace ?? context.workspace, task.lane) : undefined;
11042
11051
  if (task.lane && !lane)
11043
11052
  throw new Error(`unknown subagent lane: ${task.lane}`);
11044
- const scope = resolveSubagentToolScope({
11053
+ resolveSubagentToolScope({
11045
11054
  parent: context.session,
11046
11055
  availableTools,
11047
11056
  ...lane?.tools ? {
11048
11057
  requestedTools: lane.tools
11049
11058
  } : {}
11050
11059
  });
11051
- if (background && hasSharedWorkspaceEdits(scope))
11052
- throw new Error(`background campaign worker ${task.title} requires a non-editing lane`);
11053
11060
  }
11054
11061
  }
11055
11062
  const results = await Promise.all(tasks.map(async (task) => {
@@ -13034,7 +13041,13 @@ function refreshSignature(input, configs) {
13034
13041
  });
13035
13042
  }
13036
13043
  function resolveMcpPort(configs, portOffset = 0) {
13037
- return DEFAULT_MITMPROXY_PORT;
13044
+ const configured = configs.find((config) => config.mitmproxy)?.mitmproxy?.port;
13045
+ const base = Number.isInteger(configured) ? configured : DEFAULT_MITMPROXY_PORT;
13046
+ const offset = Number.isFinite(portOffset) ? Math.trunc(portOffset) : 0;
13047
+ const port = base + offset;
13048
+ if (port < 1 || port > 65535)
13049
+ throw new Error(`resolved MCP proxy port is outside 1-65535: ${port}`);
13050
+ return port;
13038
13051
  }
13039
13052
  function applyMcpPortTemplate(config, port) {
13040
13053
  const replacePort = (value) => value.replaceAll("${PORT}", String(port)).replaceAll("${PROXY_PORT}", String(port)).replaceAll("{PORT}", String(port)).replaceAll("{PROXY_PORT}", String(port));
@@ -14626,20 +14639,18 @@ function parseDelegation(args, context, resumeSessionId) {
14626
14639
  throw new Error("prompt must be a non-empty string");
14627
14640
  const mode = args.mode === "detached" ? "detached" : "attached";
14628
14641
  const lane = maybeString(args.lane);
14642
+ const model = maybeString(args.model);
14629
14643
  const tools = Array.isArray(args.tools) ? [...new Set(args.tools.map((item) => asString(item, "tools[]").trim()).filter(Boolean))] : undefined;
14630
14644
  if (Array.isArray(args.tools) && !tools?.length)
14631
14645
  throw new Error("tools must contain at least one non-empty tool name");
14632
- if (resumeSessionId && (lane || tools))
14633
- throw new Error("resumed subagents preserve their original lane and tool scope");
14634
- if (mode === "detached" && !resumeSessionId && !lane && !tools?.length)
14635
- throw new Error("detached subagents require an explicit lane or tool scope");
14636
14646
  const title = normalizeSessionTitle(maybeString(args.title) ?? (resumeSessionId ? childTitle(context, resumeSessionId) : titleFromPrompt(prompt, lane ? `${lane} task` : "subagent task")));
14637
14647
  return {
14638
14648
  title,
14639
14649
  prompt,
14640
14650
  mode,
14641
14651
  lane,
14642
- tools
14652
+ tools,
14653
+ model
14643
14654
  };
14644
14655
  }
14645
14656
  async function delegate(args, context, resumeSessionId) {
@@ -14656,6 +14667,9 @@ async function delegate(args, context, resumeSessionId) {
14656
14667
  } : {},
14657
14668
  ...input.tools?.length ? {
14658
14669
  tools: input.tools
14670
+ } : {},
14671
+ ...input.model ? {
14672
+ model: input.model
14659
14673
  } : {}
14660
14674
  });
14661
14675
  return {
@@ -14704,7 +14718,7 @@ function followupTool() {
14704
14718
  additionalProperties: false
14705
14719
  },
14706
14720
  mutates: true,
14707
- timeoutMs: 900000,
14721
+ timeoutMs: Number.POSITIVE_INFINITY,
14708
14722
  parallel: true,
14709
14723
  concurrencyScope: "session",
14710
14724
  renderHuman: agentResultRenderer,
@@ -14737,6 +14751,10 @@ var init_lifecycle = __esm(() => {
14737
14751
  },
14738
14752
  description: "optional restriction that cannot exceed the parent scope"
14739
14753
  },
14754
+ model: {
14755
+ type: "string",
14756
+ description: "optional model override"
14757
+ },
14740
14758
  mode: {
14741
14759
  type: "string",
14742
14760
  enum: ["attached", "detached"]
@@ -14744,7 +14762,7 @@ var init_lifecycle = __esm(() => {
14744
14762
  };
14745
14763
  agentSpawnTool = {
14746
14764
  name: "agent_spawn",
14747
- description: "Start a bounded leaf subagent. Attached waits; detached runs independently and requires a lane or tool scope.",
14765
+ description: "Start a subagent. Attached waits for its result; detached lets it continue independently in the background.",
14748
14766
  inputSchema: {
14749
14767
  type: "object",
14750
14768
  required: ["prompt"],
@@ -14752,7 +14770,7 @@ var init_lifecycle = __esm(() => {
14752
14770
  additionalProperties: false
14753
14771
  },
14754
14772
  mutates: true,
14755
- timeoutMs: 900000,
14773
+ timeoutMs: Number.POSITIVE_INFINITY,
14756
14774
  parallel: true,
14757
14775
  concurrencyScope: "session",
14758
14776
  renderHuman: agentResultRenderer,
@@ -15643,7 +15661,7 @@ var init_request_user_input = __esm(() => {
15643
15661
  },
15644
15662
  mutates: false,
15645
15663
  timeoutMs: 86400000,
15646
- parallel: false,
15664
+ parallel: true,
15647
15665
  concurrencyScope: "session",
15648
15666
  renderHuman: (result) => result.output ?? result.summary,
15649
15667
  renderModel: (result) => result.output ?? result.summary,
@@ -17366,9 +17384,10 @@ function renderCtfNotes(input) {
17366
17384
  }
17367
17385
 
17368
17386
  // src/agent-core/default-model.ts
17369
- var DEFAULT_MODEL_PROVIDER_ID = "opencode", DEFAULT_MODEL_BASE_URL = "https://opencode.ai/zen/v1", DEFAULT_MODEL_ID = "big-pickle", DEFAULT_MODEL_PUBLIC_API_KEY = "public", DEFAULT_CONTEXT_WINDOW = 32000, DEFAULT_MAX_OUTPUT_TOKENS = 4096, DEFAULT_MAX_STEPS = 120, DEFAULT_MAX_TURN_SECONDS;
17387
+ var DEFAULT_MODEL_PROVIDER_ID = "opencode", DEFAULT_MODEL_BASE_URL = "https://opencode.ai/zen/v1", DEFAULT_MODEL_ID = "big-pickle", DEFAULT_MODEL_PUBLIC_API_KEY = "public", DEFAULT_CONTEXT_WINDOW = 32000, DEFAULT_MAX_OUTPUT_TOKENS = 4096, DEFAULT_MAX_STEPS, DEFAULT_MAX_TURN_SECONDS;
17370
17388
  var init_default_model = __esm(() => {
17371
- DEFAULT_MAX_TURN_SECONDS = 15 * 60;
17389
+ DEFAULT_MAX_STEPS = Number.POSITIVE_INFINITY;
17390
+ DEFAULT_MAX_TURN_SECONDS = Number.POSITIVE_INFINITY;
17372
17391
  });
17373
17392
 
17374
17393
  // src/agent-core/model-registry.ts
@@ -19931,11 +19950,6 @@ function activeBackgroundJobs(calls) {
19931
19950
  }
19932
19951
  return jobs;
19933
19952
  }
19934
- function findEquivalentBackgroundJob(jobs, tool, args) {
19935
- const fingerprint = stableValue(args);
19936
- const canonical = canonicalToolName(tool);
19937
- return jobs.find((job) => job.tool === canonical && stableValue(job.args) === fingerprint);
19938
- }
19939
19953
  function processIdFromArgs(args) {
19940
19954
  if (!args || typeof args !== "object" || Array.isArray(args))
19941
19955
  return;
@@ -21476,188 +21490,21 @@ var init_history_projection = __esm(() => {
21476
21490
  });
21477
21491
 
21478
21492
  // src/agent-core/capability-admission.ts
21479
- function matches(text, pattern) {
21480
- return pattern.test(text.toLowerCase());
21481
- }
21482
- function exactToolMention(text, name) {
21483
- return canonicalToolName(text.toLowerCase()).includes(name.toLowerCase());
21484
- }
21485
- function containsNetworkTarget(text) {
21486
- return /https?:\/\/|\b(?:www\.)?(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{2,63}|xn--[a-z0-9-]{2,59})(?::\d{1,5})?\b|\b\d{1,3}(?:\.\d{1,3}){3}(?::\d{1,5})?\b/i.test(text);
21487
- }
21488
- function containsHostnameTarget(text) {
21489
- return /\b(?:www\.)?(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+(?:[a-z]{2,63}|xn--[a-z0-9-]{2,59})(?::\d{1,5})?\b/i.test(text);
21490
- }
21491
- function hasAssessmentIntent(text) {
21492
- return matches(text, /\b(audit|assess(?:ment)?|security[ -]?(?:audit|test(?:ing)?)|pentest|scan|target|host|ctf|vulnerability|vuln|exploit|enumerat(?:e|ion)|recon|uji keamanan|cek keamanan|periksa keamanan)\b/);
21493
- }
21494
- function isInteractiveWebTask(session, userText = "") {
21495
- const text = userText.toLowerCase();
21496
- const assessmentPhase = ["recon", "enumeration", "hypothesis", "verification", "exploit_lab", "post_exploit_lab"].includes(session.phase);
21497
- const hostnameTarget = containsHostnameTarget(text);
21498
- const networkTarget = containsNetworkTarget(text);
21499
- const explicitWebTarget = /https?:\/\//i.test(text);
21500
- const explicitHttpService = networkTarget && matches(text, /\bhttps?\b/);
21501
- const browserOperation = matches(text, /\b(web(?:site|app)?|site|browser|playwright|camoufox|page|halaman|situs|login|sign[ -]?in|form|dashboard|cookie|redirect|javascript|dom|frontend|endpoint|api)\b/);
21502
- const interactiveOperation = explicitWebTarget || explicitHttpService || browserOperation;
21503
- const interactiveWebIntent = explicitWebTarget || interactiveOperation;
21504
- const passiveInfrastructure = isPassiveInfrastructureTask(text) && !browserOperation;
21505
- const assessmentIntent = hasAssessmentIntent(text) && (hostnameTarget || networkTarget && interactiveWebIntent) && !passiveInfrastructure;
21506
- const codingOnly = matches(text, /\b(code|coding|implement|refactor|bug|fix|unit test|typecheck|repository|repo|parser)\b|\.(ts|tsx|js|jsx|py|go|rs)\b/) && !matches(text, /https?:\/\/|\b(browser|playwright|camoufox|page|login|form|dashboard|cookie|redirect|javascript|dom|frontend)\b/);
21507
- return !passiveInfrastructure && !codingOnly && (assessmentIntent || interactiveWebIntent || assessmentPhase && interactiveWebIntent);
21508
- }
21509
- function isPassiveInfrastructureTask(userText = "") {
21510
- return matches(userText, /\b(subdomains?|passive[ -]?dns|certificate transparency|\bct logs?\b|crt\.sh|asset[ -]?(?:discovery|enumeration)|dns[ -]?(?:recon|enumeration)|enumerat(?:e|ion)\s+(?:dns|subdomains?))\b/);
21511
- }
21512
- function isExplicitRawHttpTask(userText = "") {
21513
- const text = userText.toLowerCase();
21514
- const negatedClient = /\b(?:do not|don't|never|avoid|jangan|tanpa)\b[^\n]{0,48}\b(?:http_request|curl|wget|httpie|xh)\b/.test(text);
21515
- if (!negatedClient && /\b(?:http_request|curl|wget|httpie|xh)\b/.test(text))
21516
- return true;
21517
- if (/\b(?:raw http|wire format|request smuggling|response splitting|http\/1\.[01]|http\/2|http\/3|exact protocol|protocol verification)\b/.test(text))
21518
- return true;
21519
- if (/\b(?:ffuf|fuzz(?:er|ing)?|wordlist|brute[ -]?force|load test|benchmark)\b/.test(text))
21520
- return true;
21521
- return /\b(?:script|scripting|automate|repeatable|regression|integration test|api test|testing)\b/.test(text) && /\b(?:http|https|api|request|response|endpoint)\b/.test(text);
21522
- }
21523
- function browserKernelOperation(name) {
21524
- return BROWSER_KERNEL.find((operation) => name === operation || name.endsWith(`_${operation}`));
21525
- }
21526
- function selectBrowserKernel(tools) {
21527
- const selected = [];
21528
- for (const operation of BROWSER_KERNEL) {
21529
- const candidates = tools.filter((tool) => browserKernelOperation(tool.name) === operation).sort((left, right) => Number(right.name === operation) - Number(left.name === operation) || left.name.localeCompare(right.name));
21530
- if (candidates[0])
21531
- selected.push(candidates[0]);
21532
- }
21533
- return selected;
21534
- }
21535
21493
  function selectCapabilities(input) {
21536
21494
  if (input.session.toolScope?.length) {
21537
21495
  const scope = new Set(input.session.toolScope.map(canonicalToolName));
21538
- const direct2 = input.tools.filter((tool) => scope.has(tool.name)).sort((a, b) => a.name.localeCompare(b.name));
21496
+ const direct2 = input.tools.filter((tool) => scope.has(canonicalToolName(tool.name))).sort((a, b) => a.name.localeCompare(b.name));
21539
21497
  return {
21540
21498
  direct: direct2,
21541
21499
  deferred: [],
21542
21500
  reasons: Object.fromEntries(direct2.map((tool) => [tool.name, "explicit subagent scope"]))
21543
21501
  };
21544
21502
  }
21545
- const text = input.userText ?? "";
21546
- const coding = input.session.phase === "code_assist" || matches(text, /\b(code|coding|implement|refactor|bug|fix|test|typescript|javascript|python|golang|rust|file|repository|repo|build|typecheck)\b|\.(ts|tsx|js|jsx|py|go|rs)\b/);
21547
- const recon = ["recon", "enumeration", "hypothesis", "verification", "exploit_lab", "post_exploit_lab"].includes(input.session.phase) || hasAssessmentIntent(text) || containsNetworkTarget(text) || matches(text, /\b(port|http|https|url|domain|endpoint|directory|nmap)\b/);
21548
- const callback = matches(text, /\b(reverse shell|callback|listener|lhost|oast|out.of.band|ssrf|xxe)\b/);
21549
- const campaign = Boolean(input.session.campaignId);
21550
- const interactiveWeb = isInteractiveWebTask(input.session, text);
21551
- const rawHttp = isExplicitRawHttpTask(text);
21552
- const selected = new Set(ALWAYS);
21553
- const reasons = {};
21554
- for (const name of ALWAYS)
21555
- reasons[name] = "kernel capability";
21556
- if (!input.session.parentId) {
21557
- for (const name of ["request_user_input", "agent_spawn"]) {
21558
- selected.add(name);
21559
- reasons[name] = "root session delegation";
21560
- }
21561
- }
21562
- if (coding)
21563
- for (const name of CODING) {
21564
- selected.add(name);
21565
- reasons[name] = "coding task";
21566
- }
21567
- if (recon)
21568
- for (const name of RECON) {
21569
- selected.add(name);
21570
- reasons[name] = "recon task";
21571
- }
21572
- if (interactiveWeb) {
21573
- for (const tool of selectBrowserKernel(input.tools)) {
21574
- selected.add(tool.name);
21575
- reasons[tool.name] = "interactive web task";
21576
- }
21577
- for (const name of ["proxy_scope", "proxy_flows", "proxy_flow_get", "proxy_sitemap", "proxy_replay", "proxy_intercept", "proxy_clear"]) {
21578
- selected.add(name);
21579
- reasons[name] = "managed web proxy";
21580
- }
21581
- }
21582
- if (interactiveWeb || rawHttp) {
21583
- selected.add("http_request");
21584
- reasons.http_request = rawHttp ? "explicit HTTP task" : "network assessment task";
21585
- }
21586
- if (campaign)
21587
- for (const name of CAMPAIGN) {
21588
- selected.add(name);
21589
- reasons[name] = "active campaign";
21590
- }
21591
- if (campaign && input.session.phase === "verification") {
21592
- selected.add("campaign_dispatch");
21593
- reasons["campaign_dispatch"] = "campaign verification";
21594
- }
21595
- if (!campaign && recon) {
21596
- selected.add("campaign_create");
21597
- reasons["campaign_create"] = "campaign can be initialized for assessment work";
21598
- }
21599
- if (callback)
21600
- for (const name of CALLBACK) {
21601
- selected.add(name);
21602
- reasons[name] = "callback or OOB task";
21603
- }
21604
- if (matches(text, /\b(search the web|web search|research|latest|current|internet|online|source|citation|paper|documentation)\b/)) {
21605
- selected.add("web_search");
21606
- selected.add("web_fetch");
21607
- reasons.web_search = "current web research";
21608
- reasons.web_fetch = "current web research";
21609
- }
21610
- if (matches(text, /\b(image|screenshot|photo|diagram|png|jpe?g|gif|webp)\b/)) {
21611
- selected.add("image_view");
21612
- reasons.image_view = "image inspection";
21613
- }
21614
- if (matches(text, /\bmcp\b.*\b(resource|resources)\b|\b(resource|resources)\b.*\bmcp\b/)) {
21615
- selected.add("mcp_resource_list");
21616
- selected.add("mcp_resource_read");
21617
- reasons.mcp_resource_list = "MCP resources";
21618
- reasons.mcp_resource_read = "MCP resources";
21619
- }
21620
- if (input.hasActiveJobs || input.hasOutputArtifacts) {
21621
- for (const name of BACKGROUND) {
21622
- selected.add(name);
21623
- reasons[name] = "active or retrievable tool output";
21624
- }
21625
- }
21626
- if (!input.session.parentId && input.hasActiveJobs) {
21627
- for (const name of ["agent_list", "agent_wait", "agent_message", "agent_followup", "agent_interrupt", "agent_close"]) {
21628
- selected.add(name);
21629
- reasons[name] = "active child-agent lifecycle";
21630
- }
21631
- }
21632
- for (const tool of input.tools) {
21633
- if (exactToolMention(text, tool.name)) {
21634
- selected.add(tool.name);
21635
- reasons[tool.name] = "explicit tool mention";
21636
- }
21637
- }
21638
- if (input.invokedTools?.length) {
21639
- const invoked = new Set(input.invokedTools.map(canonicalToolName));
21640
- for (const tool of input.tools) {
21641
- if (BRIDGE.has(tool.name) || selected.has(tool.name) || !invoked.has(tool.name))
21642
- continue;
21643
- selected.add(tool.name);
21644
- reasons[tool.name] = "used earlier this session";
21645
- }
21646
- }
21647
- const direct = input.tools.filter((tool) => selected.has(tool.name) && !BRIDGE.has(tool.name));
21648
- const deferred = input.tools.filter((tool) => !selected.has(tool.name) && !BRIDGE.has(tool.name));
21649
- if (deferred.length > 0) {
21650
- for (const bridge of input.tools.filter((tool) => BRIDGE.has(tool.name))) {
21651
- direct.push(bridge);
21652
- reasons[bridge.name] = `${deferred.length} capabilities deferred`;
21653
- }
21654
- }
21655
- direct.sort((a, b) => a.name.localeCompare(b.name));
21656
- deferred.sort((a, b) => a.name.localeCompare(b.name));
21503
+ const direct = [...input.tools].sort((a, b) => a.name.localeCompare(b.name));
21657
21504
  return {
21658
21505
  direct,
21659
- deferred,
21660
- reasons
21506
+ deferred: [],
21507
+ reasons: Object.fromEntries(direct.map((tool) => [tool.name, "available session capability"]))
21661
21508
  };
21662
21509
  }
21663
21510
  function toolSchemaTokens(tools) {
@@ -21668,17 +21515,8 @@ function toolSchemaTokens(tools) {
21668
21515
  }));
21669
21516
  return Math.max(0, Math.ceil(Buffer.byteLength(JSON.stringify(payload), "utf8") / 4));
21670
21517
  }
21671
- var BRIDGE, ALWAYS, CODING, RECON, BROWSER_KERNEL, CAMPAIGN, CALLBACK, BACKGROUND;
21672
21518
  var init_capability_admission = __esm(() => {
21673
21519
  init_tool_names();
21674
- BRIDGE = new Set(["tool_search", "tool_invoke"]);
21675
- ALWAYS = new Set(["shell_exec", "fs_read", "fs_grep", "skill_load", "session_rename", "todo_add", "todo_update", "todo_list"]);
21676
- CODING = new Set(["fs_list", "fs_write", "fs_edit", "patch_apply", "notebook_edit", "git_status", "git_diff", "code_write_script", "lsp_inspect", "worktree_enter", "worktree_exit"]);
21677
- RECON = new Set(["port_scan", "nmap_scan", "subdomain_enum", "dir_enum", "exploit_search", "kali_tool_search", "notes_add", "evidence_save"]);
21678
- BROWSER_KERNEL = ["browser_context", "browser_navigate", "browser_snapshot", "browser_find", "browser_click", "browser_fill_form", "browser_type", "browser_press_key", "browser_wait_for", "browser_tabs", "browser_network_requests", "browser_network_request"];
21679
- CAMPAIGN = new Set(["campaign_asset", "campaign_observe", "campaign_hypothesis", "campaign_search", "campaign_next_action", "campaign_test", "campaign_verify", "report_add_finding"]);
21680
- CALLBACK = new Set(["callback_host_info", "callback_listen", "callback_oast", "callback_stop"]);
21681
- BACKGROUND = new Set(["session_poll", "session_stop", "tool_output_read"]);
21682
21520
  });
21683
21521
 
21684
21522
  // src/agent-core/context-index.ts
@@ -23688,26 +23526,6 @@ class ToolExecutionLease {
23688
23526
  }
23689
23527
  }
23690
23528
 
23691
- class ToolGateLease {
23692
- quarantines = [];
23693
- mirrors = new Set;
23694
- mirrorTo(target) {
23695
- if (target !== this)
23696
- this.mirrors.add(target);
23697
- }
23698
- quarantineUntil(running, error) {
23699
- this.quarantines.push({
23700
- running,
23701
- error
23702
- });
23703
- for (const target of this.mirrors)
23704
- target.quarantineUntil(running, error);
23705
- }
23706
- takeQuarantines() {
23707
- return this.quarantines.splice(0);
23708
- }
23709
- }
23710
-
23711
23529
  class ToolExecutionDeadline {
23712
23530
  controller = new AbortController;
23713
23531
  constructor(tool, timeoutMs, parentSignal) {
@@ -23723,21 +23541,16 @@ class ToolExecutionDeadline {
23723
23541
  once: true
23724
23542
  });
23725
23543
  }
23726
- async run(work, gateLease) {
23727
- this.signal.throwIfAborted();
23728
- this.timer ??= setTimeout(() => {
23729
- if (!this.signal.aborted)
23730
- this.controller.abort(new ToolDeadlineError(this.tool, this.timeoutMs));
23731
- }, this.timeoutMs);
23544
+ async run(work) {
23732
23545
  this.signal.throwIfAborted();
23733
- const running = Promise.resolve().then(work);
23734
- try {
23735
- return await abortablePromise(running, this.signal);
23736
- } catch (error) {
23737
- if (this.signal.aborted)
23738
- gateLease.quarantineUntil(running, abortReason(this.signal));
23739
- throw error;
23546
+ if (Number.isFinite(this.timeoutMs)) {
23547
+ this.timer ??= setTimeout(() => {
23548
+ if (!this.signal.aborted)
23549
+ this.controller.abort(new ToolDeadlineError(this.tool, this.timeoutMs));
23550
+ }, this.timeoutMs);
23740
23551
  }
23552
+ this.signal.throwIfAborted();
23553
+ return await abortablePromise(Promise.resolve().then(work), this.signal);
23741
23554
  }
23742
23555
  dispose() {
23743
23556
  if (this.timer)
@@ -23758,12 +23571,10 @@ class ToolExecutionGate {
23758
23571
  }
23759
23572
  async run(key, parallel, fn, signal) {
23760
23573
  const release = await this.acquire(key, parallel ? "read" : "write", signal);
23761
- const lease = new ToolGateLease;
23762
23574
  try {
23763
23575
  signal?.throwIfAborted();
23764
- return await fn(lease);
23576
+ return await fn();
23765
23577
  } finally {
23766
- this.quarantine(key, lease.takeQuarantines());
23767
23578
  release();
23768
23579
  }
23769
23580
  }
@@ -23776,14 +23587,9 @@ class ToolExecutionGate {
23776
23587
  const state = this.states.get(key) ?? {
23777
23588
  activeReaders: 0,
23778
23589
  activeWriter: false,
23779
- queue: [],
23780
- quarantines: new Set
23590
+ queue: []
23781
23591
  };
23782
23592
  this.states.set(key, state);
23783
- if (state.quarantineError) {
23784
- reject(state.quarantineError);
23785
- return;
23786
- }
23787
23593
  const waiter = {
23788
23594
  mode,
23789
23595
  resolve: resolve5,
@@ -23812,7 +23618,7 @@ class ToolExecutionGate {
23812
23618
  });
23813
23619
  }
23814
23620
  drain(key, state) {
23815
- if (state.activeWriter || state.quarantineError)
23621
+ if (state.activeWriter)
23816
23622
  return;
23817
23623
  const first = state.queue[0];
23818
23624
  if (!first)
@@ -23840,7 +23646,7 @@ class ToolExecutionGate {
23840
23646
  }
23841
23647
  }
23842
23648
  cleanup(key, state) {
23843
- if (state.activeReaders === 0 && !state.activeWriter && state.queue.length === 0 && state.quarantines.size === 0 && this.states.get(key) === state) {
23649
+ if (state.activeReaders === 0 && !state.activeWriter && state.queue.length === 0 && this.states.get(key) === state) {
23844
23650
  this.states.delete(key);
23845
23651
  if (this.states.size === 0) {
23846
23652
  for (const resolve5 of this.idleResolvers)
@@ -23858,32 +23664,6 @@ class ToolExecutionGate {
23858
23664
  waiter.signal.removeEventListener("abort", waiter.onAbort);
23859
23665
  delete waiter.onAbort;
23860
23666
  }
23861
- quarantine(key, entries) {
23862
- if (entries.length === 0)
23863
- return;
23864
- const state = this.states.get(key);
23865
- if (!state)
23866
- return;
23867
- state.quarantineError ??= new ToolScopeQuarantinedError(key, entries[0].error);
23868
- for (const waiter of state.queue.splice(0)) {
23869
- this.detach(waiter);
23870
- waiter.reject(state.quarantineError);
23871
- }
23872
- for (const entry of entries) {
23873
- let tracked;
23874
- tracked = entry.running.catch(() => {
23875
- return;
23876
- }).finally(() => {
23877
- state.quarantines.delete(tracked);
23878
- if (state.quarantines.size === 0) {
23879
- delete state.quarantineError;
23880
- this.drain(key, state);
23881
- this.cleanup(key, state);
23882
- }
23883
- });
23884
- state.quarantines.add(tracked);
23885
- }
23886
- }
23887
23667
  }
23888
23668
  function leasedToolCapability(target, lease) {
23889
23669
  return new Proxy(target, {
@@ -23899,12 +23679,16 @@ function leasedToolCapability(target, lease) {
23899
23679
  });
23900
23680
  }
23901
23681
  function normalizeToolTimeout(timeoutMs) {
23682
+ if (timeoutMs === Number.POSITIVE_INFINITY)
23683
+ return timeoutMs;
23902
23684
  if (!Number.isFinite(timeoutMs))
23903
23685
  return 120000;
23904
23686
  return Math.max(1, Math.floor(timeoutMs));
23905
23687
  }
23906
23688
  function toolOperationTimeout(timeoutMs) {
23907
23689
  const deadline = normalizeToolTimeout(timeoutMs);
23690
+ if (!Number.isFinite(deadline))
23691
+ return deadline;
23908
23692
  const handoffGrace = Math.min(5000, Math.max(50, Math.floor(deadline * 0.05)));
23909
23693
  return Math.max(1, deadline - handoffGrace);
23910
23694
  }
@@ -23958,7 +23742,7 @@ function toolSchedulingDefinition(tool, args, session) {
23958
23742
  const targetName = canonicalToolName(String(args.name ?? ""));
23959
23743
  return getTool(targetName, session) ?? tool;
23960
23744
  }
23961
- var ToolDeadlineError, ToolScopeQuarantinedError;
23745
+ var ToolDeadlineError;
23962
23746
  var init_tool_execution_control = __esm(() => {
23963
23747
  init_registry4();
23964
23748
  init_tool_names();
@@ -23970,14 +23754,6 @@ var init_tool_execution_control = __esm(() => {
23970
23754
  this.name = "ToolDeadlineError";
23971
23755
  }
23972
23756
  };
23973
- ToolScopeQuarantinedError = class ToolScopeQuarantinedError extends Error {
23974
- constructor(scope, cause) {
23975
- super(`Tool concurrency scope ${scope} is quarantined after: ${cause.message}`);
23976
- this.scope = scope;
23977
- this.cause = cause;
23978
- this.name = "ToolScopeQuarantinedError";
23979
- }
23980
- };
23981
23757
  });
23982
23758
 
23983
23759
  // src/agent-core/tool-input-validation.ts
@@ -24105,7 +23881,8 @@ function normalizeToolResult(result, input) {
24105
23881
  return normalized;
24106
23882
  const rawOutput = normalized.output;
24107
23883
  const sanitizedOutput = sanitizeToolOutput(rawOutput);
24108
- if (Buffer.byteLength(sanitizedOutput, "utf8") <= TOOL_OUTPUT_LIMITS.bytes) {
23884
+ const binaryLike = isBinaryLike(rawOutput);
23885
+ if (!binaryLike && Buffer.byteLength(sanitizedOutput, "utf8") <= TOOL_OUTPUT_LIMITS.bytes) {
24109
23886
  return sanitizedOutput === rawOutput ? normalized : {
24110
23887
  ...normalized,
24111
23888
  output: sanitizedOutput
@@ -24116,6 +23893,20 @@ function normalizeToolResult(result, input) {
24116
23893
  toolCallId: input.toolCallId,
24117
23894
  content: rawOutput
24118
23895
  });
23896
+ if (binaryLike && Buffer.byteLength(sanitizedOutput, "utf8") <= TOOL_OUTPUT_LIMITS.bytes) {
23897
+ return {
23898
+ ...normalized,
23899
+ output: `${sanitizedOutput}
23900
+
23901
+ [full raw output stored as artifact ${artifact.id}; read it with tool_output_read]`,
23902
+ outputArtifactId: artifact.id,
23903
+ metadata: {
23904
+ ...normalized.metadata ?? {},
23905
+ outputArtifact: artifact,
23906
+ binaryLike: true
23907
+ }
23908
+ };
23909
+ }
24119
23910
  const head = takeBytes(sanitizedOutput, TOOL_OUTPUT_LIMITS.headBytes, "head");
24120
23911
  const tail = takeBytes(sanitizedOutput, TOOL_OUTPUT_LIMITS.tailBytes, "tail");
24121
23912
  return {
@@ -24312,6 +24103,17 @@ var init_tool_call_journal = () => {};
24312
24103
  import { createHash as createHash4 } from "crypto";
24313
24104
  import { existsSync as existsSync12, mkdirSync as mkdirSync6, realpathSync as realpathSync3, writeFileSync as writeFileSync7 } from "fs";
24314
24105
  import { isAbsolute as isAbsolute4, join as join16, relative as relative6 } from "path";
24106
+ function shouldBufferInitialTextStream(text) {
24107
+ if (isInternalMetaReasoning(text))
24108
+ return true;
24109
+ if (text.includes(`
24110
+ `))
24111
+ return false;
24112
+ const normalized = text.trimStart().toLowerCase().replace(/\s+/g, " ");
24113
+ if (!normalized)
24114
+ return false;
24115
+ return INTERNAL_META_STREAM_PREFIXES.some((prefix) => prefix.startsWith(normalized));
24116
+ }
24315
24117
 
24316
24118
  class AgentRuntime {
24317
24119
  turnControllers = new Map;
@@ -24551,7 +24353,11 @@ class AgentRuntime {
24551
24353
  });
24552
24354
  }
24553
24355
  if (!session.archivedAt) {
24554
- this.userInputs.recover(session.id, this.store.listEvents(session.id, 1e4));
24356
+ const pendingUserInput = this.userInputs.recover(session.id, this.store.listEvents(session.id, 1e4));
24357
+ const latestInterruptedTurn = [...turns].reverse().find((turn) => newlyInterruptedTurns.has(turn.id));
24358
+ if (latestInterruptedTurn && !pendingUserInput && !this.mailbox.hasQueued(session.id)) {
24359
+ this.inputQueue.enqueueFollowup(session.id, ["Continue the task that was interrupted by the runtime restart.", "Use the durable transcript and tool results as the source of truth.", "Do not blindly replay mutating calls; inspect current state first, then resume from the next useful action."].join(" "), "plain", `runtime-recovery:${latestInterruptedTurn.id}`);
24360
+ }
24555
24361
  }
24556
24362
  }
24557
24363
  for (const job of this.store.listRecoverableJobs()) {
@@ -24564,7 +24370,7 @@ class AgentRuntime {
24564
24370
  continue;
24565
24371
  }
24566
24372
  }
24567
- this.jobs.markLost(job.id, "Background execution owner was lost during runtime restart. The original work was not replayed.", job.agentMode !== "attached");
24373
+ this.jobs.markLost(job.id, "Background execution owner was lost during runtime restart. Durable session work is resumable, but the original in-memory process cannot be reattached.", job.agentMode !== "attached");
24568
24374
  }
24569
24375
  for (const job of this.store.listTerminalJobsMissingMailbox())
24570
24376
  this.jobs.repairTerminalMailbox(job.id);
@@ -25242,7 +25048,7 @@ class AgentRuntime {
25242
25048
  this.autoCompactFailures.delete(sessionId);
25243
25049
  this.fileState.clear(sessionId);
25244
25050
  for (const turn of this.store.listTurns(sessionId, 1000)) {
25245
- this.streamingParts.delete(turn.id);
25051
+ this.deleteStreamingParts(turn.id);
25246
25052
  }
25247
25053
  return this.store.clearSessionChat(sessionId);
25248
25054
  }
@@ -25551,8 +25357,8 @@ class AgentRuntime {
25551
25357
  });
25552
25358
  let autoContinueStreak = 0;
25553
25359
  let resumeAfterCompaction = false;
25554
- const maxSteps = userAuthored ? this.maxSteps : BACKGROUND_COMPLETION_MAX_STEPS;
25555
- const maxTurnMs = userAuthored ? this.maxTurnMs : Number.POSITIVE_INFINITY;
25360
+ const maxSteps = this.maxSteps;
25361
+ const maxTurnMs = this.maxTurnMs;
25556
25362
  const loopStartedAt = Date.now();
25557
25363
  let timeBudgetWarned = false;
25558
25364
  let loopError;
@@ -25590,7 +25396,7 @@ class AgentRuntime {
25590
25396
  responses.push(...await this.forceTimeLimitWrapUp(session, turn, assistantMessage, planner, maxTurnMs));
25591
25397
  break;
25592
25398
  }
25593
- if (!timeBudgetWarned && elapsedMs >= maxTurnMs * 0.75) {
25399
+ if (Number.isFinite(maxTurnMs) && !timeBudgetWarned && elapsedMs >= maxTurnMs * 0.75) {
25594
25400
  timeBudgetWarned = true;
25595
25401
  const secondsLeft = Math.max(1, Math.ceil((maxTurnMs - elapsedMs) / 1000));
25596
25402
  const queue = this.pendingSteeringContext.get(session.id) ?? [];
@@ -25601,10 +25407,7 @@ class AgentRuntime {
25601
25407
  stepCount: step + 1
25602
25408
  });
25603
25409
  if (step >= maxSteps) {
25604
- if (userAuthored)
25605
- responses.push(...await this.forceStepLimitWrapUp(session, turn, assistantMessage, planner, maxSteps));
25606
- else
25607
- this.stopTurn(turn, "completed", "no_actions");
25410
+ responses.push(...await this.forceStepLimitWrapUp(session, turn, assistantMessage, planner, maxSteps));
25608
25411
  break;
25609
25412
  }
25610
25413
  const compactResult = await this.maybeAutoCompact(session, planner);
@@ -25697,7 +25500,7 @@ This completion is already terminal and was delivered automatically. Do not call
25697
25500
  contextWindow: resolveContextWindow(planner.contextWindow),
25698
25501
  maxOutputTokens: resolveMaxOutputTokens(planner.maxOutputTokens),
25699
25502
  ...this.contextBudgetInput(),
25700
- toolsEnabled: userAuthored,
25503
+ toolsEnabled: true,
25701
25504
  extraBlocks: [...passiveCompletions ? [{
25702
25505
  title: "Completed Background Work",
25703
25506
  body: passiveCompletions,
@@ -25770,14 +25573,14 @@ This completion is already terminal and was delivered automatically. Do not call
25770
25573
  contextBlocks: context.contextBlocks,
25771
25574
  tools: context.tools,
25772
25575
  toolCatalog: context.toolCatalog,
25773
- toolChoice: userAuthored ? "auto" : "none"
25576
+ toolChoice: "auto"
25774
25577
  };
25775
25578
  resumeAfterCompaction = false;
25776
25579
  const autoContinue = {
25777
25580
  streak: autoContinueStreak
25778
25581
  };
25779
25582
  const remainingTurnMs = Number.isFinite(maxTurnMs) ? Math.max(0, maxTurnMs - (Date.now() - loopStartedAt)) : undefined;
25780
- const control = chatProvider ? await this.streamStep(chatProvider, plannerInput, session, turn, assistantMessage, planner.name, step, context.manifest, responses, autoContinue, userAuthored, remainingTurnMs) : await this.batchStep(planner, plannerInput, session, turn, assistantMessage, step, context.manifest, responses, autoContinue, userAuthored, remainingTurnMs);
25583
+ const control = chatProvider ? await this.streamStep(chatProvider, plannerInput, session, turn, assistantMessage, planner.name, step, context.manifest, responses, autoContinue, userAuthored, remainingTurnMs) : await this.batchStep(planner, plannerInput, session, turn, assistantMessage, step, context.manifest, responses, autoContinue, remainingTurnMs);
25781
25584
  autoContinueStreak = autoContinue.streak;
25782
25585
  if (control.cancelled)
25783
25586
  return responses.join(`
@@ -25900,10 +25703,10 @@ This completion is already terminal and was delivered automatically. Do not call
25900
25703
  latencyMs
25901
25704
  });
25902
25705
  }
25903
- async batchStep(planner, plannerInput, session, turn, assistantMessage, step, context, responses, autoContinue, toolsAllowed, modelTimeoutMs) {
25706
+ async batchStep(planner, plannerInput, session, turn, assistantMessage, step, context, responses, autoContinue, modelTimeoutMs) {
25904
25707
  let actions;
25905
25708
  try {
25906
- actions = await this.planWithRetry(planner, plannerInput, session, turn, assistantMessage, context, modelTimeoutMs, !toolsAllowed);
25709
+ actions = await this.planWithRetry(planner, plannerInput, session, turn, assistantMessage, context, modelTimeoutMs);
25907
25710
  } catch (error) {
25908
25711
  if (error instanceof ModelCallDeadlineError)
25909
25712
  return {
@@ -25947,10 +25750,7 @@ This completion is already terminal and was delivered automatically. Do not call
25947
25750
  };
25948
25751
  for (const action of actions) {
25949
25752
  if (action.kind === "tool") {
25950
- if (toolsAllowed)
25951
- toolBatch.push(action);
25952
- else
25953
- this.recordDisabledToolCall(session, turn, assistantMessage, step, action.toolCallId ?? action.tool, action.tool, action.args);
25753
+ toolBatch.push(action);
25954
25754
  continue;
25955
25755
  }
25956
25756
  if (await flushToolBatch())
@@ -25963,7 +25763,7 @@ This completion is already terminal and was delivered automatically. Do not call
25963
25763
  } else if (action.kind === "respond") {
25964
25764
  sawResponse = true;
25965
25765
  if (hasToolAction && isInternalMetaReasoning(action.text)) {
25966
- this.discardStreamingText(turn.id);
25766
+ this.discardStreamingText(session.id, turn.id);
25967
25767
  shouldContinue = true;
25968
25768
  continue;
25969
25769
  }
@@ -26000,7 +25800,7 @@ This completion is already terminal and was delivered automatically. Do not call
26000
25800
  });
26001
25801
  const request = buildChatRequest(plannerInput, requestController.signal);
26002
25802
  let lastError = "";
26003
- this.streamingParts.delete(turn.id);
25803
+ this.deleteStreamingParts(turn.id);
26004
25804
  try {
26005
25805
  for (let attempt = 1;; attempt += 1) {
26006
25806
  this.emitPlannerAttempt(session, turn, assistantMessage, plannerName, attempt, plannerInput, context);
@@ -26090,10 +25890,6 @@ This completion is already terminal and was delivered automatically. Do not call
26090
25890
  this.recordToolParseError(session, turn, assistantMessage, step, toolCallId ?? toolName, toolName, error instanceof Error ? error.message : String(error), event.arguments);
26091
25891
  continue;
26092
25892
  }
26093
- if (!userAuthored) {
26094
- this.recordDisabledToolCall(session, turn, assistantMessage, step, toolCallId ?? toolName, toolName, args);
26095
- continue;
26096
- }
26097
25893
  const action = {
26098
25894
  kind: "tool",
26099
25895
  tool: toolName,
@@ -26143,7 +25939,7 @@ This completion is already terminal and was delivered automatically. Do not call
26143
25939
  shouldContinue: shouldContinue2
26144
25940
  };
26145
25941
  }
26146
- this.prepareStreamingRetry(turn.id);
25942
+ this.prepareStreamingRetry(session.id, turn.id);
26147
25943
  const retry = plannerRetryState(error, attempt, dispatched.length === 0);
26148
25944
  const errorPayload = {
26149
25945
  turnId: turn.id,
@@ -26212,7 +26008,7 @@ This completion is already terminal and was delivered automatically. Do not call
26212
26008
  const rawRespondText = content.trim();
26213
26009
  const respondText = dispatched.length > 0 && isInternalMetaReasoning(rawRespondText) ? "" : rawRespondText;
26214
26010
  if (!respondText && rawRespondText && dispatched.length > 0)
26215
- this.discardStreamingText(turn.id);
26011
+ this.discardStreamingText(session.id, turn.id);
26216
26012
  const outcomes = await Promise.all(dispatched);
26217
26013
  const toolCancelled = outcomes.some((outcome) => outcome.cancelled);
26218
26014
  for (const outcome of outcomes) {
@@ -26366,9 +26162,11 @@ This completion is already terminal and was delivered automatically. Do not call
26366
26162
  rationale: ""
26367
26163
  });
26368
26164
  if (state) {
26165
+ state.reasoningAccum = "";
26166
+ this.publishStreamingReasoning(session.id, turn.id, state);
26369
26167
  delete state.reasoningPartId;
26370
26168
  delete state.reasoningAccum;
26371
- delete state.lastReasoningRender;
26169
+ delete state.lastReasoningPersist;
26372
26170
  }
26373
26171
  return;
26374
26172
  }
@@ -26377,13 +26175,15 @@ This completion is already terminal and was delivered automatically. Do not call
26377
26175
  rationale
26378
26176
  });
26379
26177
  if (state?.reasoningPartId) {
26178
+ state.reasoningAccum = rationale;
26179
+ this.publishStreamingReasoning(session.id, turn.id, state);
26380
26180
  this.store.updatePartPayload(state.reasoningPartId, {
26381
26181
  planner: plannerName,
26382
26182
  rationale
26383
26183
  });
26384
26184
  delete state.reasoningPartId;
26385
26185
  delete state.reasoningAccum;
26386
- delete state.lastReasoningRender;
26186
+ delete state.lastReasoningPersist;
26387
26187
  } else {
26388
26188
  this.store.addPart({
26389
26189
  sessionId: session.id,
@@ -26398,13 +26198,11 @@ This completion is already terminal and was delivered automatically. Do not call
26398
26198
  }
26399
26199
  }
26400
26200
  async applyRespond(session, turn, assistantMessage, plannerName, text, truncated, recoverable, responses, autoContinue) {
26401
- if (this.isRedundantAgentTaskResponse(session.id, turn.id, text)) {
26402
- this.discardStreamingText(turn.id);
26403
- return false;
26404
- }
26405
26201
  responses.push(text);
26406
26202
  const streamed = this.streamingParts.get(turn.id);
26407
26203
  if (streamed?.textPartId) {
26204
+ streamed.textAccum = text;
26205
+ this.publishStreamingText(session.id, turn.id, streamed);
26408
26206
  this.store.updatePartPayload(streamed.textPartId, {
26409
26207
  text
26410
26208
  });
@@ -26421,29 +26219,11 @@ This completion is already terminal and was delivered automatically. Do not call
26421
26219
  recoverable
26422
26220
  });
26423
26221
  if (truncated || recoverable) {
26424
- if (autoContinue.streak < MAX_RECOVERABLE_AUTO_CONTINUE) {
26425
- autoContinue.streak += 1;
26426
- return true;
26427
- }
26428
- const reason = truncated ? `kept getting cut off by its token limit ${MAX_RECOVERABLE_AUTO_CONTINUE} times in a row. Consider raising maxOutputTokens in ~/.local/pajarori/farai/config.toml or asking a smaller follow-up question` : `kept failing to produce a usable response ${MAX_RECOVERABLE_AUTO_CONTINUE} times in a row`;
26429
- const notice = `(Model ${reason} \u2014 stopping auto-continue.)`;
26430
- responses.push(notice);
26431
- this.persistTextPart(session.id, turn.id, assistantMessage.id, notice);
26432
- this.event(session.id, "text", {
26433
- role: "assistant",
26434
- text: notice,
26435
- planner: plannerName
26436
- });
26222
+ autoContinue.streak += 1;
26223
+ return true;
26437
26224
  }
26438
26225
  return false;
26439
26226
  }
26440
- isRedundantAgentTaskResponse(sessionId, turnId, text) {
26441
- const candidate2 = comparableProse(text);
26442
- if (candidate2.length < 40)
26443
- return false;
26444
- const outputs = this.store.listMessages(sessionId, 200).flatMap((message) => message.parts).filter((part) => part.turnId === turnId && part.type === "tool_result").flatMap((part) => agentTaskOutput(part.payload));
26445
- return outputs.some((output) => substantiallySameProse(candidate2, comparableProse(output)));
26446
- }
26447
26227
  recordToolParseError(session, turn, assistantMessage, step, toolCallId, tool, error, rawArguments) {
26448
26228
  const text = `Could not parse arguments for ${tool}: ${error}. Raw: ${rawArguments.slice(0, 500)}`;
26449
26229
  this.store.addPart({
@@ -26486,48 +26266,6 @@ This completion is already terminal and was delivered automatically. Do not call
26486
26266
  payload
26487
26267
  });
26488
26268
  }
26489
- recordDisabledToolCall(session, turn, assistantMessage, step, toolCallId, tool, args) {
26490
- const text = `Tool ${tool} was not executed because this is a bounded text-only completion turn.`;
26491
- this.store.addPart({
26492
- sessionId: session.id,
26493
- turnId: turn.id,
26494
- messageId: assistantMessage.id,
26495
- type: "tool_call",
26496
- payload: {
26497
- record: {
26498
- id: toolCallId,
26499
- tool,
26500
- args
26501
- }
26502
- }
26503
- });
26504
- this.store.addPart({
26505
- sessionId: session.id,
26506
- turnId: turn.id,
26507
- messageId: assistantMessage.id,
26508
- type: "tool_result",
26509
- payload: {
26510
- toolCallId,
26511
- tool,
26512
- result: text
26513
- }
26514
- });
26515
- const payload = {
26516
- turnId: turn.id,
26517
- step,
26518
- tool,
26519
- error: text,
26520
- recoverable: false
26521
- };
26522
- this.event(session.id, "planner_error", payload);
26523
- this.store.addPart({
26524
- sessionId: session.id,
26525
- turnId: turn.id,
26526
- messageId: assistantMessage.id,
26527
- type: "planner_error",
26528
- payload
26529
- });
26530
- }
26531
26269
  async forceStepLimitWrapUp(session, turn, assistantMessage, planner, maxSteps) {
26532
26270
  return this.forceTextOnlyWrapUp({
26533
26271
  session,
@@ -26680,25 +26418,6 @@ This completion is already terminal and was delivered automatically. Do not call
26680
26418
  shouldContinue: !sawResponse
26681
26419
  };
26682
26420
  }
26683
- if (action.tool === "subdomain_enum") {
26684
- const duplicate = this.store.listToolCalls(session.id, 200).find((call2) => call2.turnId === turn.id && call2.tool === action.tool && (call2.status === "done" || call2.status === "error") && stableValue(call2.args) === stableValue(action.args));
26685
- if (duplicate) {
26686
- const text = `Equivalent ${action.tool} already finished in this turn as ${duplicate.id}; reuse its source statuses and names instead of retrying.`;
26687
- this.event(session.id, "planner_error", {
26688
- turnId: turn.id,
26689
- step,
26690
- tool: action.tool,
26691
- error: text,
26692
- recoverable: true,
26693
- policy: "duplicate_terminal_tool",
26694
- duplicateSuppressed: true,
26695
- duplicateToolCallId: duplicate.id
26696
- });
26697
- return {
26698
- shouldContinue: true
26699
- };
26700
- }
26701
- }
26702
26421
  const validationError = validateToolArgs(tool.inputSchema, action.args);
26703
26422
  if (validationError) {
26704
26423
  const toolCallId = action.toolCallId ?? action.tool;
@@ -26967,6 +26686,40 @@ This completion is already terminal and was delivered automatically. Do not call
26967
26686
  };
26968
26687
  }
26969
26688
  }
26689
+ deleteStreamingParts(turnId) {
26690
+ this.streamingParts.delete(turnId);
26691
+ }
26692
+ publishStreamingText(sessionId, turnId, state) {
26693
+ if (!state.textPartId)
26694
+ return;
26695
+ this.store.publishTransientEvent({
26696
+ id: id(),
26697
+ sessionId,
26698
+ type: "stream_text",
26699
+ payload: {
26700
+ turnId,
26701
+ partId: state.textPartId,
26702
+ text: state.textAccum
26703
+ },
26704
+ createdAt: nowIso()
26705
+ });
26706
+ }
26707
+ publishStreamingReasoning(sessionId, turnId, state) {
26708
+ if (!state.reasoningPartId)
26709
+ return;
26710
+ const rationale = normalizeReasoningSummary(state.reasoningAccum ?? "");
26711
+ this.store.publishTransientEvent({
26712
+ id: id(),
26713
+ sessionId,
26714
+ type: "stream_reasoning",
26715
+ payload: {
26716
+ turnId,
26717
+ partId: state.reasoningPartId,
26718
+ rationale
26719
+ },
26720
+ createdAt: nowIso()
26721
+ });
26722
+ }
26970
26723
  applyStreamEvent(session, turn, assistantMessage, event) {
26971
26724
  const now = Date.now();
26972
26725
  const state = this.streamingParts.get(turn.id) ?? {
@@ -26991,26 +26744,39 @@ This completion is already terminal and was delivered automatically. Do not call
26991
26744
  }
26992
26745
  });
26993
26746
  state.reasoningPartId = part.id;
26994
- state.lastReasoningRender = now;
26995
- } else if (now - (state.lastReasoningRender ?? 0) >= STREAM_RENDER_INTERVAL_MS) {
26747
+ state.lastReasoningPersist = now;
26748
+ } else if (now - (state.lastReasoningPersist ?? 0) >= STREAM_PERSIST_INTERVAL_MS) {
26996
26749
  this.store.updatePartPayload(state.reasoningPartId, {
26997
26750
  rationale
26998
26751
  });
26999
- state.lastReasoningRender = now;
26752
+ state.lastReasoningPersist = now;
27000
26753
  }
26754
+ this.publishStreamingReasoning(session.id, turn.id, state);
27001
26755
  this.streamingParts.set(turn.id, state);
27002
26756
  return;
27003
26757
  }
27004
26758
  state.textAccum += event.delta;
27005
26759
  if (!state.textPartId) {
27006
- if (isInternalMetaReasoning(state.textAccum)) {
26760
+ if (shouldBufferInitialTextStream(state.textAccum)) {
27007
26761
  this.streamingParts.set(turn.id, state);
27008
26762
  return;
27009
26763
  }
27010
- if (state.textAccum.length < 80 && !state.textAccum.includes(`
27011
- `)) {
27012
- this.streamingParts.set(turn.id, state);
27013
- return;
26764
+ if (!state.reasoningPartId && state.reasoningAccum) {
26765
+ const rationale = normalizeReasoningSummary(state.reasoningAccum);
26766
+ if (rationale) {
26767
+ const reasoningPart = this.store.addPart({
26768
+ sessionId: session.id,
26769
+ turnId: turn.id,
26770
+ messageId: assistantMessage.id,
26771
+ type: "reasoning_summary",
26772
+ payload: {
26773
+ rationale
26774
+ }
26775
+ });
26776
+ state.reasoningPartId = reasoningPart.id;
26777
+ state.lastReasoningPersist = now;
26778
+ this.publishStreamingReasoning(session.id, turn.id, state);
26779
+ }
27014
26780
  }
27015
26781
  const part = this.store.addPart({
27016
26782
  sessionId: session.id,
@@ -27022,30 +26788,17 @@ This completion is already terminal and was delivered automatically. Do not call
27022
26788
  }
27023
26789
  });
27024
26790
  state.textPartId = part.id;
27025
- state.lastTextRender = now;
27026
26791
  state.lastTextPersist = now;
27027
26792
  } else if (now - (state.lastTextPersist ?? 0) >= STREAM_PERSIST_INTERVAL_MS) {
27028
26793
  this.store.updatePartPayload(state.textPartId, {
27029
26794
  text: state.textAccum
27030
26795
  });
27031
- state.lastTextRender = now;
27032
26796
  state.lastTextPersist = now;
27033
- } else if (now - (state.lastTextRender ?? 0) >= STREAM_RENDER_INTERVAL_MS) {
27034
- this.store.publishTransientEvent({
27035
- id: id(),
27036
- sessionId: session.id,
27037
- type: "stream_text",
27038
- payload: {
27039
- partId: state.textPartId,
27040
- text: state.textAccum
27041
- },
27042
- createdAt: nowIso()
27043
- });
27044
- state.lastTextRender = now;
27045
26797
  }
26798
+ this.publishStreamingText(session.id, turn.id, state);
27046
26799
  this.streamingParts.set(turn.id, state);
27047
26800
  }
27048
- discardStreamingText(turnId) {
26801
+ discardStreamingText(sessionId, turnId) {
27049
26802
  const state = this.streamingParts.get(turnId);
27050
26803
  if (!state)
27051
26804
  return;
@@ -27054,11 +26807,11 @@ This completion is already terminal and was delivered automatically. Do not call
27054
26807
  text: ""
27055
26808
  });
27056
26809
  state.textAccum = "";
26810
+ this.publishStreamingText(sessionId, turnId, state);
27057
26811
  delete state.textPartId;
27058
- delete state.lastTextRender;
27059
26812
  delete state.lastTextPersist;
27060
26813
  }
27061
- prepareStreamingRetry(turnId) {
26814
+ prepareStreamingRetry(sessionId, turnId) {
27062
26815
  const state = this.streamingParts.get(turnId);
27063
26816
  if (!state)
27064
26817
  return;
@@ -27071,11 +26824,13 @@ This completion is already terminal and was delivered automatically. Do not call
27071
26824
  rationale: ""
27072
26825
  });
27073
26826
  state.textAccum = "";
26827
+ state.reasoningAccum = "";
26828
+ this.publishStreamingText(sessionId, turnId, state);
26829
+ this.publishStreamingReasoning(sessionId, turnId, state);
27074
26830
  delete state.reasoningPartId;
27075
26831
  delete state.reasoningAccum;
27076
- delete state.lastReasoningRender;
27077
- state.lastTextRender = Date.now();
27078
- state.lastTextPersist = state.lastTextRender;
26832
+ delete state.lastReasoningPersist;
26833
+ state.lastTextPersist = Date.now();
27079
26834
  }
27080
26835
  async planWithRetry(planner, input, session, turn, assistantMessage, context, timeoutMs, allowEmpty = false) {
27081
26836
  let lastError = "";
@@ -27084,7 +26839,7 @@ This completion is already terminal and was delivered automatically. Do not call
27084
26839
  release,
27085
26840
  timedOut
27086
26841
  } = this.registerTurnController(turn.id, timeoutMs);
27087
- this.streamingParts.delete(turn.id);
26842
+ this.deleteStreamingParts(turn.id);
27088
26843
  const onStreamEvent = (event) => this.applyStreamEvent(session, turn, assistantMessage, event);
27089
26844
  try {
27090
26845
  for (let attempt = 1;; attempt += 1) {
@@ -27111,7 +26866,7 @@ This completion is already terminal and was delivered automatically. Do not call
27111
26866
  lastError = "cancelled";
27112
26867
  break;
27113
26868
  }
27114
- this.prepareStreamingRetry(turn.id);
26869
+ this.prepareStreamingRetry(session.id, turn.id);
27115
26870
  const retry = plannerRetryState(error, attempt, true);
27116
26871
  const errorPayload = {
27117
26872
  turnId: turn.id,
@@ -27233,7 +26988,7 @@ This completion is already terminal and was delivered automatically. Do not call
27233
26988
  } : {}
27234
26989
  });
27235
26990
  this.modelCallsByTurn.delete(turn.id);
27236
- this.streamingParts.delete(turn.id);
26991
+ this.deleteStreamingParts(turn.id);
27237
26992
  this.fireHooks({
27238
26993
  id: turn.sessionId
27239
26994
  }, "turn.stop", undefined, {
@@ -27256,25 +27011,18 @@ This completion is already terminal and was delivered automatically. Do not call
27256
27011
  const workspaceTransition = isWorkspaceTransitionTool(schedulingTool);
27257
27012
  const gateSignal = signal ? AbortSignal.any([signal, this.shutdownController.signal]) : this.shutdownController.signal;
27258
27013
  try {
27259
- return await this.workspaceBindingGate.run(`session-workspace:${session.id}`, !workspaceTransition, async (bindingLease) => {
27014
+ return await this.workspaceBindingGate.run(`session-workspace:${session.id}`, !workspaceTransition, async () => {
27260
27015
  session = this.store.loadSession(session.id);
27261
27016
  tool = toolForExecution(session, toolName);
27262
27017
  schedulingTool = toolSchedulingDefinition(tool, args, session);
27263
- return await this.toolExecutionGate.run(toolConcurrencyKey(schedulingTool, session, session.workspace), schedulingTool.parallel, async (gateLease) => {
27264
- gateLease.mirrorTo(bindingLease);
27018
+ return await this.toolExecutionGate.run(toolConcurrencyKey(schedulingTool, session, session.workspace), schedulingTool.parallel, async () => {
27265
27019
  gateSignal.throwIfAborted();
27266
27020
  if (owner && this.store.loadTurn(owner.turn.id).status === "cancelled")
27267
27021
  throw new Error("turn cancelled before tool start");
27268
- return await this.runToolUnderGate(session, tool, args, owner, providerToolCallId, gateLease);
27022
+ return await this.runToolUnderGate(session, tool, args, owner, providerToolCallId);
27269
27023
  }, gateSignal);
27270
27024
  }, gateSignal);
27271
27025
  } catch (error) {
27272
- if (error instanceof ToolScopeQuarantinedError) {
27273
- return this.recordRejectedToolCall(session, tool, args, error.message, {
27274
- quarantined: true,
27275
- reason: "concurrency_scope_quarantined"
27276
- }, owner, providerToolCallId);
27277
- }
27278
27026
  const ownerCancelled = owner ? this.store.loadTurn(owner.turn.id).status === "cancelled" : false;
27279
27027
  if (gateSignal.aborted || ownerCancelled) {
27280
27028
  const message = gateSignal.reason ? String(gateSignal.reason) : "turn cancelled before tool start";
@@ -27304,7 +27052,7 @@ This completion is already terminal and was delivered automatically. Do not call
27304
27052
  });
27305
27053
  return this.toolCalls.settleError(toolCall, message, state);
27306
27054
  }
27307
- async runToolUnderGate(session, tool, args, owner, providerToolCallId, gateLease) {
27055
+ async runToolUnderGate(session, tool, args, owner, providerToolCallId) {
27308
27056
  const toolCall = this.toolCalls.begin({
27309
27057
  sessionId: session.id,
27310
27058
  tool: tool.name,
@@ -27319,7 +27067,7 @@ This completion is already terminal and was delivered automatically. Do not call
27319
27067
  providerToolCallId
27320
27068
  } : {}
27321
27069
  });
27322
- await this.executeToolUnderGate(session, toolCall.id, owner, gateLease);
27070
+ await this.executeToolUnderGate(session, toolCall.id, owner);
27323
27071
  return this.store.loadToolCall(toolCall.id);
27324
27072
  }
27325
27073
  async executeTool(session, toolCallId, owner) {
@@ -27337,29 +27085,18 @@ This completion is already terminal and was delivered automatically. Do not call
27337
27085
  const gateController = turnId ? this.registerTurnController(turnId) : undefined;
27338
27086
  const gateSignal = gateController ? AbortSignal.any([gateController.signal, this.shutdownController.signal]) : this.shutdownController.signal;
27339
27087
  try {
27340
- return await this.workspaceBindingGate.run(`session-workspace:${session.id}`, !workspaceTransition, async (bindingLease) => {
27088
+ return await this.workspaceBindingGate.run(`session-workspace:${session.id}`, !workspaceTransition, async () => {
27341
27089
  session = this.store.loadSession(session.id);
27342
27090
  tool = toolForExecution(session, toolCall.tool);
27343
27091
  schedulingTool = toolSchedulingDefinition(tool, toolCall.args, session);
27344
- return await this.toolExecutionGate.run(toolConcurrencyKey(schedulingTool, session, session.workspace), schedulingTool.parallel, async (gateLease) => {
27345
- gateLease.mirrorTo(bindingLease);
27092
+ return await this.toolExecutionGate.run(toolConcurrencyKey(schedulingTool, session, session.workspace), schedulingTool.parallel, async () => {
27346
27093
  gateSignal.throwIfAborted();
27347
27094
  if (turnId && this.store.loadTurn(turnId).status === "cancelled")
27348
27095
  throw new Error("turn cancelled before tool start");
27349
- return await this.executeToolUnderGate(session, toolCallId, owner, gateLease);
27096
+ return await this.executeToolUnderGate(session, toolCallId, owner);
27350
27097
  }, gateSignal);
27351
27098
  }, gateSignal);
27352
27099
  } catch (error) {
27353
- if (error instanceof ToolScopeQuarantinedError) {
27354
- const rejected = this.store.loadToolCall(toolCallId);
27355
- if (rejected.status === "pending") {
27356
- return this.toolCalls.settleError(rejected, error.message, {
27357
- quarantined: true,
27358
- reason: "concurrency_scope_quarantined"
27359
- });
27360
- }
27361
- return rejected;
27362
- }
27363
27100
  if (!gateSignal.aborted && (!turnId || this.store.loadTurn(turnId).status !== "cancelled"))
27364
27101
  throw error;
27365
27102
  const cancelled = this.store.loadToolCall(toolCallId);
@@ -27376,7 +27113,7 @@ This completion is already terminal and was delivered automatically. Do not call
27376
27113
  gateController?.release();
27377
27114
  }
27378
27115
  }
27379
- async executeToolUnderGate(session, toolCallId, owner, gateLease = new ToolGateLease) {
27116
+ async executeToolUnderGate(session, toolCallId, owner) {
27380
27117
  let toolCall = this.store.loadToolCall(toolCallId);
27381
27118
  if (toolCall.status !== "pending")
27382
27119
  return toolCall;
@@ -27664,7 +27401,7 @@ This completion is already terminal and was delivered automatically. Do not call
27664
27401
  ...nestedContext,
27665
27402
  signal: nestedDeadline.signal,
27666
27403
  timeoutMs: toolOperationTimeout(target.timeoutMs)
27667
- }), gateLease);
27404
+ }));
27668
27405
  lease.assertActive();
27669
27406
  await this.fireHooks(session, "tool.post", canonicalName, {
27670
27407
  tool: canonicalName,
@@ -27724,22 +27461,10 @@ This completion is already terminal and was delivered automatically. Do not call
27724
27461
  linkToolCall = true
27725
27462
  }) => {
27726
27463
  lease.assertActive();
27727
- let depth = 0;
27728
- let parentId = session.parentId;
27729
- while (parentId) {
27730
- depth += 1;
27731
- parentId = this.store.loadSession(parentId).parentId;
27732
- if (depth > 4)
27733
- break;
27734
- }
27735
- if (depth >= 1)
27736
- throw new Error("nested campaign delegation is disabled; child workers are leaf agents");
27737
- if (resumeSessionId && (lane || tools || model))
27738
- throw new Error("resumed subagents preserve their original lane, model, and tool scope");
27739
27464
  const previousJob = resumeSessionId ? this.store.listJobs(session.id, 1e4).find((job2) => job2.kind === "agent" && job2.childSessionId === resumeSessionId) : undefined;
27740
27465
  const effectiveLane = lane ?? previousJob?.lane;
27741
27466
  const laneDef = effectiveLane ? resolveLane(this.workspace, effectiveLane) : undefined;
27742
- if (!resumeSessionId && effectiveLane && !laneDef)
27467
+ if (effectiveLane && !laneDef)
27743
27468
  throw new Error(`unknown subagent lane: ${effectiveLane}`);
27744
27469
  let child;
27745
27470
  let scopedTools;
@@ -27753,10 +27478,22 @@ This completion is already terminal and was delivered automatically. Do not call
27753
27478
  const active = this.store.listJobs(session.id, 1e4).some((job2) => job2.kind === "agent" && job2.childSessionId === child.id && ["created", "starting", "running", "cancelling"].includes(job2.status));
27754
27479
  if (active || this.hasRunningTurn(child.id))
27755
27480
  throw new Error(`subagent session ${child.id} is already running`);
27756
- scopedTools = child.toolScope;
27481
+ const requestedTools = tools ?? laneDef?.tools;
27482
+ scopedTools = requestedTools ? resolveSubagentToolScope({
27483
+ parent: session,
27484
+ availableTools: listToolsForSession(session),
27485
+ requestedTools
27486
+ }) : child.toolScope;
27757
27487
  editsSharedWorkspace = hasSharedWorkspaceEdits(scopedTools);
27758
- if (mode === "detached" && editsSharedWorkspace)
27759
- throw new Error("detached subagents cannot hold shared workspace edit tools; use an attached code worker or a read-only lane");
27488
+ const childModel = model ?? laneDef?.model;
27489
+ if (childModel && childModel !== child.model)
27490
+ child = this.updateSession(child.id, {
27491
+ model: childModel
27492
+ });
27493
+ if (requestedTools && scopedTools?.length)
27494
+ child = this.updateSession(child.id, {
27495
+ toolScope: scopedTools
27496
+ });
27760
27497
  } else {
27761
27498
  const requestedTools = tools ?? laneDef?.tools;
27762
27499
  scopedTools = resolveSubagentToolScope({
@@ -27767,8 +27504,6 @@ This completion is already terminal and was delivered automatically. Do not call
27767
27504
  } : {}
27768
27505
  });
27769
27506
  editsSharedWorkspace = hasSharedWorkspaceEdits(scopedTools);
27770
- if (mode === "detached" && editsSharedWorkspace)
27771
- throw new Error("detached subagents cannot hold shared workspace edit tools; use an attached code worker or a read-only lane");
27772
27507
  const childModel = model ?? laneDef?.model;
27773
27508
  child = await this.store.forkSession(session.id, title);
27774
27509
  this.recordSession(child);
@@ -27876,8 +27611,8 @@ This completion is already terminal and was delivered automatically. Do not call
27876
27611
  this.jobs.completeAgent(job.id, response, mode === "detached");
27877
27612
  return response;
27878
27613
  };
27879
- const gated = () => this.subagentGate.run(execute, agentController.signal);
27880
- return editsSharedWorkspace ? await this.subagentWorkspaceMutationGate.run(gated, agentController.signal) : await gated();
27614
+ const gated = () => session.parentId ? execute() : this.subagentGate.run(execute, agentController.signal);
27615
+ return editsSharedWorkspace && !session.parentId ? await this.subagentWorkspaceMutationGate.run(gated, agentController.signal) : await gated();
27881
27616
  } catch (error) {
27882
27617
  if (this.store.isOpen() && this.hasJob(job.id)) {
27883
27618
  const current = this.store.loadJob(job.id);
@@ -27931,7 +27666,6 @@ This completion is already terminal and was delivered automatically. Do not call
27931
27666
  this.turnControllers.delete(turnId);
27932
27667
  };
27933
27668
  let result;
27934
- const duplicate = findEquivalentBackgroundJob(activeBackgroundJobs(this.store.listToolCalls(session.id, 200)), toolCall.tool, toolCall.args);
27935
27669
  await this.fireHooks(session, "tool.pre", toolCall.tool, {
27936
27670
  tool: toolCall.tool,
27937
27671
  toolCallId: toolCall.id,
@@ -27939,16 +27673,7 @@ This completion is already terminal and was delivered automatically. Do not call
27939
27673
  });
27940
27674
  try {
27941
27675
  lease.assertActive();
27942
- result = duplicate && sessionManager.isTracked(duplicate.processId) ? {
27943
- ok: true,
27944
- summary: `Equivalent background job already running: processId=${duplicate.processId}`,
27945
- output: `Reusing ${duplicate.toolCallId}. Poll ${duplicate.processId} with session_poll instead of starting it again.`,
27946
- status: "running_background",
27947
- processId: duplicate.processId,
27948
- metadata: {
27949
- reusedBackgroundToolCallId: duplicate.toolCallId
27950
- }
27951
- } : await deadline.run(() => tool.run(toolCall.args, context), gateLease);
27676
+ result = await deadline.run(() => tool.run(toolCall.args, context));
27952
27677
  } catch (error) {
27953
27678
  settleLiveOutput();
27954
27679
  releaseController();
@@ -28609,38 +28334,6 @@ function completedToolCallStatus(result) {
28609
28334
  return "running_background";
28610
28335
  return result.ok ? "done" : "error";
28611
28336
  }
28612
- function agentTaskOutput(payload) {
28613
- if (!payload || typeof payload !== "object" || Array.isArray(payload))
28614
- return [];
28615
- const record2 = payload;
28616
- if (!["agent_task", "agent_spawn", "agent_followup"].includes(String(record2.tool)))
28617
- return [];
28618
- const toolResult = record2.toolResult;
28619
- if (!toolResult || typeof toolResult !== "object" || Array.isArray(toolResult))
28620
- return [];
28621
- const output = toolResult.output;
28622
- return typeof output === "string" && output.trim() ? [output] : [];
28623
- }
28624
- function comparableProse(value) {
28625
- return value.toLowerCase().replace(/[`*_>#|()[\]{}]/g, " ").replace(/[^a-z0-9./:_-]+/g, " ").replace(/\s+/g, " ").trim();
28626
- }
28627
- function substantiallySameProse(left, right) {
28628
- if (right.length < 40)
28629
- return false;
28630
- const shorter = Math.min(left.length, right.length);
28631
- const longer = Math.max(left.length, right.length);
28632
- if ((left.includes(right) || right.includes(left)) && shorter / longer >= 0.72)
28633
- return true;
28634
- const leftWords = new Set(left.split(" ").filter((word) => word.length > 2));
28635
- const rightWords = new Set(right.split(" ").filter((word) => word.length > 2));
28636
- if (leftWords.size < 8 || rightWords.size < 8)
28637
- return false;
28638
- let shared = 0;
28639
- for (const word of leftWords)
28640
- if (rightWords.has(word))
28641
- shared += 1;
28642
- return shared / leftWords.size >= 0.88 && shared / rightWords.size >= 0.88;
28643
- }
28644
28337
  function recoveredJobSummary(job) {
28645
28338
  if (job.result && typeof job.result === "object") {
28646
28339
  if ("response" in job.result)
@@ -28765,7 +28458,7 @@ function sameExistingPath(left, right) {
28765
28458
  return false;
28766
28459
  }
28767
28460
  }
28768
- var REASONING_MAX_BYTES2, STREAM_RENDER_INTERVAL_MS = 100, STREAM_PERSIST_INTERVAL_MS = 2000, MAX_RECOVERABLE_AUTO_CONTINUE = 3, LIVE_OUTPUT_MAX_BYTES, LIVE_OUTPUT_FLUSH_INTERVAL_MS = 150, LIVE_OUTPUT_INITIAL_DELAY_MS = 320, TOOL_HUMAN_RESULT_MAX_BYTES, BACKGROUND_COMPLETION_MAX_STEPS = 1, LOOP_SUPERVISION_NO_PROGRESS_STEPS = 12, LOOP_SUPERVISION_STEER_INTERVAL = 5, LOOP_PATTERN_MAX_PERIOD = 8, PROGRESS_ACTION_TOOLS, AUTO_COMPACTION_CONTINUATION = "[internal continuation after context compaction: Continue the active user task from the compacted prior context. Do not repeat, regenerate, or explain the summary. Resume with the exact next useful action.]", WRAPUP_MODEL_TIMEOUT_MS = 15000, DEFAULT_SHUTDOWN_GRACE_PERIOD_MS = 2000, RUNTIME_LEASE_MS = 60000, RUNTIME_HEARTBEAT_MS = 15000, RESTART_TOOL_ERROR = "Interrupted by runtime restart; tool execution was not replayed.", STEP_LIMIT_WRAPUP_DIRECTIVE = "You have reached the maximum number of steps allowed for this turn, so tools are no longer available. Do not attempt to call any tool. In a few sentences, summarize what you accomplished, the key findings or evidence so far, any blockers, and the single most useful next step. This is your final message for this turn.", TIME_LIMIT_WRAPUP_DIRECTIVE = "You have reached the interactive wall-clock budget for this turn, so tools are no longer available. Do not attempt to call any tool. Concisely summarize completed work, proven evidence, active background jobs, remaining uncertainty, and the single best next action. This is your final message for this turn.", IneffectiveCompactionError, ModelCallDeadlineError, PlannerOutputValidationError;
28461
+ var REASONING_MAX_BYTES2, STREAM_PERSIST_INTERVAL_MS = 2000, LIVE_OUTPUT_MAX_BYTES, LIVE_OUTPUT_FLUSH_INTERVAL_MS = 150, LIVE_OUTPUT_INITIAL_DELAY_MS = 320, TOOL_HUMAN_RESULT_MAX_BYTES, LOOP_SUPERVISION_NO_PROGRESS_STEPS = 12, LOOP_SUPERVISION_STEER_INTERVAL = 5, LOOP_PATTERN_MAX_PERIOD = 8, PROGRESS_ACTION_TOOLS, AUTO_COMPACTION_CONTINUATION = "[internal continuation after context compaction: Continue the active user task from the compacted prior context. Do not repeat, regenerate, or explain the summary. Resume with the exact next useful action.]", WRAPUP_MODEL_TIMEOUT_MS = 15000, DEFAULT_SHUTDOWN_GRACE_PERIOD_MS = 2000, RUNTIME_LEASE_MS = 60000, RUNTIME_HEARTBEAT_MS = 15000, RESTART_TOOL_ERROR = "Interrupted by runtime restart; tool execution was not replayed.", STEP_LIMIT_WRAPUP_DIRECTIVE = "You have reached the maximum number of steps allowed for this turn, so tools are no longer available. Do not attempt to call any tool. In a few sentences, summarize what you accomplished, the key findings or evidence so far, any blockers, and the single most useful next step. This is your final message for this turn.", TIME_LIMIT_WRAPUP_DIRECTIVE = "You have reached the interactive wall-clock budget for this turn, so tools are no longer available. Do not attempt to call any tool. Concisely summarize completed work, proven evidence, active background jobs, remaining uncertainty, and the single best next action. This is your final message for this turn.", INTERNAL_META_STREAM_PREFIXES, IneffectiveCompactionError, ModelCallDeadlineError, PlannerOutputValidationError;
28769
28462
  var init_runtime = __esm(() => {
28770
28463
  init_sqlite_store();
28771
28464
  init_registry4();
@@ -28813,6 +28506,7 @@ var init_runtime = __esm(() => {
28813
28506
  LIVE_OUTPUT_MAX_BYTES = 2 * 1024;
28814
28507
  TOOL_HUMAN_RESULT_MAX_BYTES = 24 * 1024;
28815
28508
  PROGRESS_ACTION_TOOLS = new Set(["http_request", "subdomain_enum", "dir_enum", "port_scan", "nmap_scan", "fs_edit", "fs_write", "patch_apply", "code_write_script", "campaign_verify", "campaign_test", "callback_oast", "exploit_search"]);
28509
+ INTERNAL_META_STREAM_PREFIXES = ["the user", "user asked", "user wants", "user requested", "user said", "per my", "per the", "my communication", "my instruction", "my instructions", "my rules", "my task", "according to my", "according to the", "the tool search", "tool_search", "there's no dedicated tool", "there is no dedicated tool", "there's no specific tool", "there is no specific tool", "there's no matching tool", "there is no matching tool", "i have access to", "let me inspect the available tool", "let me check the available tool", "let me search the available tool", "i should report", "i will report", "i need to report", "i must report", "i can report", "we should report", "we will report", "we need to report", "we must report", "we can report"];
28816
28510
  IneffectiveCompactionError = class IneffectiveCompactionError extends Error {
28817
28511
  constructor(preTokens, postTokens) {
28818
28512
  super(`compaction did not reduce active context: ${preTokens} -> ${postTokens} estimated tokens`);
@@ -28952,10 +28646,10 @@ function extractEntities(recordId2, text) {
28952
28646
  const found = new Set;
28953
28647
  const out = [];
28954
28648
  for (const pattern of ENTITY_PATTERNS) {
28955
- const matches2 = text.match(pattern.re);
28956
- if (!matches2)
28649
+ const matches = text.match(pattern.re);
28650
+ if (!matches)
28957
28651
  continue;
28958
- for (const raw of matches2) {
28652
+ for (const raw of matches) {
28959
28653
  const value = pattern.normalize ? pattern.normalize(raw) : raw;
28960
28654
  const key = `${pattern.type}:${value}`;
28961
28655
  if (found.has(key))
@@ -33004,6 +32698,17 @@ function createRuntimePort(runtime, options = {}) {
33004
32698
  return;
33005
32699
  if (evt.type === "snapshot.changed" && pending.some((item) => item.type === "snapshot.changed" && item.sessionId === evt.sessionId))
33006
32700
  return;
32701
+ const streamIdentity = transientStreamIdentity(evt);
32702
+ if (streamIdentity) {
32703
+ const existing = pending.length - 1;
32704
+ if (existing >= 0 && transientStreamIdentity(pending[existing]) === streamIdentity)
32705
+ pending[existing] = evt;
32706
+ else
32707
+ pending.push(evt);
32708
+ if (!flushTimer)
32709
+ flushTimer = setTimeout(flush, BATCH_DEBOUNCE_MS);
32710
+ return;
32711
+ }
33007
32712
  if (evt.type === "activity.changed") {
33008
32713
  const existing = pending.findIndex((item) => item.type === "activity.changed" && item.sessionId === evt.sessionId);
33009
32714
  if (existing >= 0)
@@ -33609,6 +33314,16 @@ function createRuntimePort(runtime, options = {}) {
33609
33314
  }
33610
33315
  };
33611
33316
  }
33317
+ function transientStreamIdentity(event) {
33318
+ if (event.type !== "event.appended")
33319
+ return;
33320
+ if (event.event.type !== "stream_text" && event.event.type !== "stream_reasoning")
33321
+ return;
33322
+ const payload = event.event.payload;
33323
+ if (typeof payload?.turnId !== "string" || typeof payload.partId !== "string")
33324
+ return;
33325
+ return `${event.sessionId}:${payload.turnId}:${event.event.type}:${payload.partId}`;
33326
+ }
33612
33327
  function storeChangeIdentity(change) {
33613
33328
  switch (change.kind) {
33614
33329
  case "part":
@@ -33856,7 +33571,8 @@ function createRequestUserInputUiState(request) {
33856
33571
  answers: {},
33857
33572
  drafts: {},
33858
33573
  textModeQuestionId: first && !first.choices?.length ? first.id : undefined,
33859
- submitting: false
33574
+ submitting: false,
33575
+ dismissed: false
33860
33576
  };
33861
33577
  }
33862
33578
  function syncRequestUserInputUiState(current, request) {
@@ -34651,19 +34367,6 @@ function createActions(store, setStore) {
34651
34367
  errorSet(message) {
34652
34368
  setStore("ui", "lastError", message);
34653
34369
  },
34654
- streamTextUpdated(partId, text) {
34655
- setStore(produce((s) => {
34656
- for (const message of s.snapshot.messages) {
34657
- const part = message.parts.find((item) => item.id === partId);
34658
- if (!part)
34659
- continue;
34660
- part.payload = {
34661
- text
34662
- };
34663
- return;
34664
- }
34665
- }));
34666
- },
34667
34370
  toolInputPreviewUpdated(preview) {
34668
34371
  setStore(produce((s) => {
34669
34372
  const index = s.snapshot.toolInputPreviews.findIndex((item) => item.id === preview.id);
@@ -34744,6 +34447,20 @@ function createActions(store, setStore) {
34744
34447
  return;
34745
34448
  s.ui.requestUserInput.submitting = submitting;
34746
34449
  }));
34450
+ },
34451
+ requestUserInputDismissedSet(dismissed) {
34452
+ setStore(produce((s) => {
34453
+ if (!s.ui.requestUserInput)
34454
+ return;
34455
+ s.ui.requestUserInput.dismissed = dismissed;
34456
+ if (dismissed)
34457
+ s.ui.requestUserInput.textModeQuestionId = undefined;
34458
+ else {
34459
+ const question = s.snapshot.pendingUserInput?.questions[s.ui.requestUserInput.questionIndex];
34460
+ if (question && !question.choices?.length)
34461
+ s.ui.requestUserInput.textModeQuestionId = question.id;
34462
+ }
34463
+ }));
34747
34464
  }
34748
34465
  };
34749
34466
  }
@@ -35091,9 +34808,11 @@ async function handleTuiEvent(evt, ctx) {
35091
34808
  }
35092
34809
  switch (evt.type) {
35093
34810
  case "turn.started":
34811
+ ctx.onTurnStarted?.(evt.turnId);
35094
34812
  ctx.actions.turnStarted(evt.turnId, evt.startedAt);
35095
34813
  break;
35096
34814
  case "turn.finished":
34815
+ ctx.onTurnFinished?.(evt.turnId);
35097
34816
  ctx.actions.turnFinished(evt.turnId);
35098
34817
  await refreshSnapshot(evt.sessionId);
35099
34818
  queueMicrotask(() => {
@@ -35124,8 +34843,14 @@ async function handleTuiEvent(evt, ctx) {
35124
34843
  }
35125
34844
  if (evt.event.type === "stream_text") {
35126
34845
  const payload = evt.event.payload;
35127
- if (typeof payload?.partId === "string" && typeof payload.text === "string") {
35128
- ctx.actions.streamTextUpdated(payload.partId, payload.text);
34846
+ if (typeof payload?.turnId === "string" && typeof payload.partId === "string" && typeof payload.text === "string") {
34847
+ ctx.onStreamText?.(payload.partId, payload.text, payload.turnId);
34848
+ }
34849
+ }
34850
+ if (evt.event.type === "stream_reasoning") {
34851
+ const payload = evt.event.payload;
34852
+ if (typeof payload?.turnId === "string" && typeof payload.partId === "string" && typeof payload.rationale === "string") {
34853
+ ctx.onStreamReasoning?.(payload.partId, payload.rationale, payload.turnId);
35129
34854
  }
35130
34855
  }
35131
34856
  const detail = statusDetailFor(evt.event);
@@ -35331,6 +35056,243 @@ var init_runtime2 = __esm(() => {
35331
35056
  RuntimeContext = createContext();
35332
35057
  });
35333
35058
 
35059
+ // src/agent-tui/transcript/stream-engine.ts
35060
+ function createTranscriptStreamEngine() {
35061
+ const [state, setState] = createStore({
35062
+ text: {},
35063
+ reasoning: {},
35064
+ diagnostics: {
35065
+ revision: 0,
35066
+ flushes: 0,
35067
+ coalescedUpdates: 0,
35068
+ lastIntervalMs: BASE_FRAME_INTERVAL_MS,
35069
+ lastFlushAt: undefined
35070
+ }
35071
+ });
35072
+ const pending = new Map;
35073
+ let activeTurnId;
35074
+ let timer;
35075
+ let timerDueAt = 0;
35076
+ let lastFlushAt = 0;
35077
+ let updatesSinceFlush = 0;
35078
+ let coalescedUpdates = 0;
35079
+ let currentInterval = BASE_FRAME_INTERVAL_MS;
35080
+ let acceptingUpdates = false;
35081
+ let disposed = false;
35082
+ function key(kind, partId) {
35083
+ return `${kind}:${partId}`;
35084
+ }
35085
+ function intervalFor(contentLength, updateCount) {
35086
+ const sizePressure = Math.log2(1 + contentLength / 4096) * 8;
35087
+ const updatePressure = Math.max(0, updateCount - 3) * 2;
35088
+ return Math.round(Math.max(MIN_FRAME_INTERVAL_MS, Math.min(MAX_FRAME_INTERVAL_MS, BASE_FRAME_INTERVAL_MS + sizePressure + updatePressure)));
35089
+ }
35090
+ function schedule() {
35091
+ if (disposed || pending.size === 0)
35092
+ return;
35093
+ const now = Date.now();
35094
+ const maxLength = Math.max(0, ...[...pending.values()].map((entry) => entry.content.length));
35095
+ const interval = intervalFor(maxLength, updatesSinceFlush);
35096
+ currentInterval = interval;
35097
+ const elapsed = now - lastFlushAt;
35098
+ if (lastFlushAt === 0 || elapsed >= interval) {
35099
+ flush();
35100
+ return;
35101
+ }
35102
+ const dueAt = lastFlushAt + interval;
35103
+ if (timer && dueAt <= timerDueAt)
35104
+ return;
35105
+ if (timer)
35106
+ clearTimeout(timer);
35107
+ timerDueAt = dueAt;
35108
+ timer = setTimeout(flush, Math.max(0, dueAt - now));
35109
+ }
35110
+ function flush() {
35111
+ if (disposed)
35112
+ return;
35113
+ if (timer) {
35114
+ clearTimeout(timer);
35115
+ timer = undefined;
35116
+ }
35117
+ timerDueAt = 0;
35118
+ if (pending.size === 0)
35119
+ return;
35120
+ const entries = [...pending.values()];
35121
+ pending.clear();
35122
+ const now = Date.now();
35123
+ lastFlushAt = now;
35124
+ updatesSinceFlush = 0;
35125
+ setState(produce((draft) => {
35126
+ draft.diagnostics.revision += 1;
35127
+ draft.diagnostics.flushes += 1;
35128
+ draft.diagnostics.coalescedUpdates = coalescedUpdates;
35129
+ draft.diagnostics.lastIntervalMs = currentInterval;
35130
+ draft.diagnostics.lastFlushAt = now;
35131
+ const revision = draft.diagnostics.revision;
35132
+ for (const entry of entries) {
35133
+ const target = entry.kind === "text" ? draft.text : draft.reasoning;
35134
+ target[entry.partId] = {
35135
+ content: entry.content,
35136
+ revision
35137
+ };
35138
+ }
35139
+ }));
35140
+ }
35141
+ function clearVisible() {
35142
+ pending.clear();
35143
+ updatesSinceFlush = 0;
35144
+ if (timer) {
35145
+ clearTimeout(timer);
35146
+ timer = undefined;
35147
+ }
35148
+ timerDueAt = 0;
35149
+ setState(produce((draft) => {
35150
+ draft.text = {};
35151
+ draft.reasoning = {};
35152
+ }));
35153
+ }
35154
+ function update(kind, partId, content, turnId) {
35155
+ if (disposed || !acceptingUpdates || turnId !== activeTurnId)
35156
+ return;
35157
+ const pendingKey = key(kind, partId);
35158
+ const existing = pending.get(pendingKey);
35159
+ const visible = kind === "text" ? state.text[partId]?.content : state.reasoning[partId]?.content;
35160
+ if (existing?.content === content || !existing && visible === content)
35161
+ return;
35162
+ if (existing)
35163
+ coalescedUpdates += 1;
35164
+ pending.set(pendingKey, {
35165
+ kind,
35166
+ partId,
35167
+ content
35168
+ });
35169
+ updatesSinceFlush += 1;
35170
+ schedule();
35171
+ }
35172
+ function durableContent(messages, partId, kind) {
35173
+ for (const message of messages) {
35174
+ const part = message.parts.find((candidate2) => candidate2.id === partId);
35175
+ if (!part)
35176
+ continue;
35177
+ if (kind === "text") {
35178
+ if (typeof part.payload === "string")
35179
+ return part.payload;
35180
+ if (part.payload && typeof part.payload === "object" && "text" in part.payload) {
35181
+ const value = part.payload.text;
35182
+ return typeof value === "string" ? value : undefined;
35183
+ }
35184
+ return;
35185
+ }
35186
+ if (part.payload && typeof part.payload === "object") {
35187
+ const payload = part.payload;
35188
+ if (typeof payload.rationale === "string")
35189
+ return payload.rationale;
35190
+ if (typeof payload.text === "string")
35191
+ return payload.text;
35192
+ }
35193
+ return typeof part.payload === "string" ? part.payload : undefined;
35194
+ }
35195
+ return;
35196
+ }
35197
+ function reconciliationRemovals(kind, visible, messages) {
35198
+ const removals = [];
35199
+ for (const [partId, entry] of Object.entries(visible)) {
35200
+ const durable = durableContent(messages, partId, kind);
35201
+ if (durable === entry.content)
35202
+ removals.push(partId);
35203
+ }
35204
+ return removals;
35205
+ }
35206
+ function reconcile2(messages, runningTurnId) {
35207
+ if (disposed)
35208
+ return;
35209
+ if (!runningTurnId) {
35210
+ clearVisible();
35211
+ activeTurnId = undefined;
35212
+ acceptingUpdates = false;
35213
+ return;
35214
+ }
35215
+ if (activeTurnId && activeTurnId !== runningTurnId) {
35216
+ clearVisible();
35217
+ lastFlushAt = 0;
35218
+ currentInterval = BASE_FRAME_INTERVAL_MS;
35219
+ }
35220
+ if (activeTurnId !== runningTurnId)
35221
+ acceptingUpdates = true;
35222
+ activeTurnId = runningTurnId;
35223
+ const textRemovals = reconciliationRemovals("text", state.text, messages);
35224
+ const reasoningRemovals = reconciliationRemovals("reasoning", state.reasoning, messages);
35225
+ if (textRemovals.length === 0 && reasoningRemovals.length === 0)
35226
+ return;
35227
+ setState(produce((draft) => {
35228
+ for (const partId of textRemovals)
35229
+ delete draft.text[partId];
35230
+ for (const partId of reasoningRemovals)
35231
+ delete draft.reasoning[partId];
35232
+ }));
35233
+ }
35234
+ function beginTurn(turnId) {
35235
+ if (disposed)
35236
+ return;
35237
+ if (activeTurnId !== turnId) {
35238
+ clearVisible();
35239
+ lastFlushAt = 0;
35240
+ currentInterval = BASE_FRAME_INTERVAL_MS;
35241
+ }
35242
+ activeTurnId = turnId;
35243
+ acceptingUpdates = true;
35244
+ }
35245
+ function finishTurn(turnId) {
35246
+ if (disposed || activeTurnId !== turnId)
35247
+ return;
35248
+ flush();
35249
+ acceptingUpdates = false;
35250
+ }
35251
+ function reset2() {
35252
+ if (disposed)
35253
+ return;
35254
+ clearVisible();
35255
+ activeTurnId = undefined;
35256
+ acceptingUpdates = false;
35257
+ lastFlushAt = 0;
35258
+ coalescedUpdates = 0;
35259
+ currentInterval = BASE_FRAME_INTERVAL_MS;
35260
+ setState("diagnostics", {
35261
+ revision: 0,
35262
+ flushes: 0,
35263
+ coalescedUpdates: 0,
35264
+ lastIntervalMs: BASE_FRAME_INTERVAL_MS,
35265
+ lastFlushAt: undefined
35266
+ });
35267
+ }
35268
+ function dispose2() {
35269
+ if (disposed)
35270
+ return;
35271
+ clearVisible();
35272
+ disposed = true;
35273
+ }
35274
+ return {
35275
+ state,
35276
+ beginTurn,
35277
+ finishTurn,
35278
+ update,
35279
+ updateText(partId, content, turnId) {
35280
+ update("text", partId, content, turnId);
35281
+ },
35282
+ updateReasoning(partId, content, turnId) {
35283
+ update("reasoning", partId, content, turnId);
35284
+ },
35285
+ reconcile: reconcile2,
35286
+ reset: reset2,
35287
+ flush,
35288
+ dispose: dispose2
35289
+ };
35290
+ }
35291
+ var MIN_FRAME_INTERVAL_MS = 24, BASE_FRAME_INTERVAL_MS = 24, MAX_FRAME_INTERVAL_MS = 96;
35292
+ var init_stream_engine = __esm(() => {
35293
+ init_store2();
35294
+ });
35295
+
35334
35296
  // src/agent-tui/reasoning.ts
35335
35297
  function parseReasoning(text) {
35336
35298
  const trimmed = normalizeReasoningSummary(text);
@@ -36820,6 +36782,34 @@ var init_renderers2 = __esm(() => {
36820
36782
  TOOL_DETAIL_MAX_BYTES = 32 * 1024;
36821
36783
  });
36822
36784
 
36785
+ // src/agent-tui/transcript/projection.ts
36786
+ function createTranscriptProjection(store, width) {
36787
+ const historyMessages = createMemo(() => {
36788
+ const runningTurnId = store.snapshot.runningTurnId;
36789
+ return runningTurnId ? store.snapshot.messages.filter((message) => message.turnId !== runningTurnId) : store.snapshot.messages;
36790
+ });
36791
+ const activeMessages = createMemo(() => {
36792
+ const runningTurnId = store.snapshot.runningTurnId;
36793
+ return runningTurnId ? store.snapshot.messages.filter((message) => message.turnId === runningTurnId) : [];
36794
+ });
36795
+ const activeMessageIds = createMemo(() => new Set(activeMessages().map((message) => message.id)));
36796
+ const historyToolCalls = createMemo(() => {
36797
+ const runningTurnId = store.snapshot.runningTurnId;
36798
+ return runningTurnId ? store.snapshot.toolCalls.filter((toolCall) => toolCall.turnId !== runningTurnId && (!toolCall.messageId || !activeMessageIds().has(toolCall.messageId))) : store.snapshot.toolCalls;
36799
+ });
36800
+ const activeToolCalls = createMemo(() => {
36801
+ const runningTurnId = store.snapshot.runningTurnId;
36802
+ return runningTurnId ? store.snapshot.toolCalls.filter((toolCall) => toolCall.turnId === runningTurnId || (toolCall.messageId ? activeMessageIds().has(toolCall.messageId) : toolCall.turnId === undefined)) : [];
36803
+ });
36804
+ const historyRows = createMemo(() => projectMessagesToRows(historyMessages(), width(), undefined, historyToolCalls()));
36805
+ const activeRows = createMemo(() => projectMessagesToRows(activeMessages(), width(), store.snapshot.runningTurnId, activeToolCalls(), store.snapshot.toolInputPreviews));
36806
+ return createMemo(() => [...historyRows(), ...activeRows()]);
36807
+ }
36808
+ var init_projection = __esm(() => {
36809
+ init_solid();
36810
+ init_renderers2();
36811
+ });
36812
+
36823
36813
  // src/agent-tui/context/store.tsx
36824
36814
  function proxyRefreshQuery() {
36825
36815
  return {
@@ -36838,6 +36828,7 @@ function TuiStoreProvider(props) {
36838
36828
  setStore: _set,
36839
36829
  actions
36840
36830
  } = createFaraiStore(workspace);
36831
+ const transcript = createTranscriptStreamEngine();
36841
36832
  let statusTimer;
36842
36833
  const proxyRefreshes = new Map;
36843
36834
  const mcpRefreshes = new Map;
@@ -36859,7 +36850,7 @@ function TuiStoreProvider(props) {
36859
36850
  actions.updateNoticeSet(notice);
36860
36851
  });
36861
36852
  }
36862
- const timelineRows = createMemo(() => projectMessagesToRows(store.snapshot.messages, Math.max(1, dims().width - 4), store.snapshot.runningTurnId, store.snapshot.toolCalls, store.snapshot.toolInputPreviews));
36853
+ const timelineRows = createTranscriptProjection(store, () => Math.max(1, dims().width - 4));
36863
36854
  function setStatusDetail(detail, timeoutMs) {
36864
36855
  if (disposed)
36865
36856
  return;
@@ -36927,6 +36918,7 @@ function TuiStoreProvider(props) {
36927
36918
  if (disposed || store.activeSessionId !== sid || snapshotGenerations.get(sid) !== generation)
36928
36919
  return;
36929
36920
  actions.snapshotApplied(snapshot);
36921
+ transcript.reconcile(snapshot.messages, snapshot.runningTurnId);
36930
36922
  for (const entry2 of promptHistoryFromMessages(snapshot.messages)) {
36931
36923
  actions.promptHistoryAdd(entry2, "session");
36932
36924
  }
@@ -36956,12 +36948,16 @@ function TuiStoreProvider(props) {
36956
36948
  return;
36957
36949
  if (sessionId === store.activeSessionId)
36958
36950
  return;
36951
+ transcript.reset();
36959
36952
  actions.activeSessionSet(sessionId);
36960
36953
  const pendingSubmission = promptSubmissions.get(sessionId);
36961
36954
  if (pendingSubmission)
36962
36955
  pendingSubmission.generation = actions.promptSubmissionStarted();
36963
36956
  props.onActiveSessionChange?.(sessionId);
36964
36957
  port.setActiveSession(sessionId);
36958
+ const runningTurnId = port.getRunningTurnId?.(sessionId);
36959
+ if (runningTurnId)
36960
+ transcript.beginTurn(runningTurnId);
36965
36961
  try {
36966
36962
  await requestSnapshotRefresh(sessionId);
36967
36963
  } catch (error) {
@@ -37238,19 +37234,6 @@ function TuiStoreProvider(props) {
37238
37234
  const sid = store.activeSessionId;
37239
37235
  if (!sid || !text.trim())
37240
37236
  return false;
37241
- if (store.snapshot.pendingUserInput) {
37242
- actions.promptHistoryAdd(text);
37243
- (async () => {
37244
- try {
37245
- await port.answerUserInput(sid, text);
37246
- await requestSnapshotRefresh(sid);
37247
- } catch (error) {
37248
- if (!disposed && store.activeSessionId === sid)
37249
- actions.errorSet(error instanceof Error ? error.message : String(error));
37250
- }
37251
- })();
37252
- return true;
37253
- }
37254
37237
  if (promptSubmissions.has(sid) || isAgentBusy(store) || port.getRunningTurnId(sid)) {
37255
37238
  if (port.steer?.(sid, text)) {
37256
37239
  actions.promptHistoryAdd(text);
@@ -37350,12 +37333,6 @@ function TuiStoreProvider(props) {
37350
37333
  actions.snapshotPatched({
37351
37334
  pendingUserInput: undefined
37352
37335
  });
37353
- const turnId = store.snapshot.runningTurnId ?? port.getRunningTurnId(sid);
37354
- if (turnId && capabilities.cancel) {
37355
- try {
37356
- await port.cancelTurn(turnId, "user input cancelled");
37357
- } catch {}
37358
- }
37359
37336
  await requestSnapshotRefresh(sid);
37360
37337
  } catch (error) {
37361
37338
  if (!disposed && store.activeSessionId === sid)
@@ -37477,9 +37454,11 @@ function TuiStoreProvider(props) {
37477
37454
  setStatusDetail,
37478
37455
  refreshSnapshot: requestSnapshotRefresh,
37479
37456
  refreshSessions,
37480
- onSnapshot: () => {
37481
- return;
37482
- }
37457
+ onSnapshot: (snapshot) => transcript.reconcile(snapshot.messages, snapshot.runningTurnId),
37458
+ onTurnStarted: (turnId) => transcript.beginTurn(turnId),
37459
+ onTurnFinished: (turnId) => transcript.finishTurn(turnId),
37460
+ onStreamText: (partId, text, turnId) => transcript.updateText(partId, text, turnId),
37461
+ onStreamReasoning: (partId, rationale, turnId) => transcript.updateReasoning(partId, rationale, turnId)
37483
37462
  }, (error) => {
37484
37463
  if (!disposed)
37485
37464
  actions.errorSet(error instanceof Error ? error.message : String(error));
@@ -37487,8 +37466,9 @@ function TuiStoreProvider(props) {
37487
37466
  const off = port.event.on((event) => {
37488
37467
  if (disposed)
37489
37468
  return;
37490
- if (event.type === "store.changed" || event.type === "store.batch")
37469
+ if (event.type === "store.changed" || event.type === "store.batch" || event.type === "turn.started") {
37491
37470
  invalidateSnapshot(event.sessionId);
37471
+ }
37492
37472
  eventDispatcher.dispatch(event);
37493
37473
  });
37494
37474
  onCleanup(() => {
@@ -37499,6 +37479,7 @@ function TuiStoreProvider(props) {
37499
37479
  mcpOverlayGeneration += 1;
37500
37480
  off();
37501
37481
  eventDispatcher.dispose();
37482
+ transcript.dispose();
37502
37483
  if (statusTimer)
37503
37484
  clearTimeout(statusTimer);
37504
37485
  });
@@ -37544,6 +37525,7 @@ function TuiStoreProvider(props) {
37544
37525
  store,
37545
37526
  actions,
37546
37527
  timelineRows,
37528
+ transcript,
37547
37529
  submitPrompt,
37548
37530
  submitUserInput,
37549
37531
  answerUserInputQuestion,
@@ -37628,7 +37610,8 @@ var init_store4 = __esm(() => {
37628
37610
  init_solid();
37629
37611
  init_store3();
37630
37612
  init_runtime2();
37631
- init_renderers2();
37613
+ init_stream_engine();
37614
+ init_projection();
37632
37615
  StoreContext = createContext();
37633
37616
  });
37634
37617
 
@@ -38009,14 +37992,14 @@ function filterOptions(options, needle) {
38009
37992
  score: score2
38010
37993
  }));
38011
37994
  }
38012
- function groupByCategory(matches2, needleActive) {
37995
+ function groupByCategory(matches, needleActive) {
38013
37996
  if (needleActive)
38014
37997
  return [{
38015
37998
  category: undefined,
38016
- matches: [...matches2]
37999
+ matches: [...matches]
38017
38000
  }];
38018
38001
  const buckets = new Map;
38019
- for (const match of matches2) {
38002
+ for (const match of matches) {
38020
38003
  const key = match.option.category;
38021
38004
  const list2 = buckets.get(key) ?? [];
38022
38005
  list2.push(match);
@@ -38437,6 +38420,10 @@ function routeRequestUserInput(key, state) {
38437
38420
  if (state.submitting)
38438
38421
  return consumed();
38439
38422
  if (key.ctrl && key.name === "c")
38423
+ return consumed({
38424
+ kind: "requestUserInput.dismiss"
38425
+ });
38426
+ if (key.ctrl && key.name === "x")
38440
38427
  return consumed({
38441
38428
  kind: "requestUserInput.cancel"
38442
38429
  });
@@ -38455,7 +38442,7 @@ function routeRequestUserInput(key, state) {
38455
38442
  if (state.textMode) {
38456
38443
  if (key.name === "escape")
38457
38444
  return consumed({
38458
- kind: state.canExitTextMode ? "requestUserInput.textModeExit" : "requestUserInput.cancel"
38445
+ kind: state.canExitTextMode ? "requestUserInput.textModeExit" : "requestUserInput.dismiss"
38459
38446
  });
38460
38447
  if (key.name === "tab" && state.canExitTextMode)
38461
38448
  return consumed({
@@ -38470,7 +38457,7 @@ function routeRequestUserInput(key, state) {
38470
38457
  switch (key.name) {
38471
38458
  case "escape":
38472
38459
  return consumed({
38473
- kind: "requestUserInput.cancel"
38460
+ kind: "requestUserInput.dismiss"
38474
38461
  });
38475
38462
  case "up":
38476
38463
  return consumed({
@@ -38795,6 +38782,10 @@ function routeBase(key, ctx) {
38795
38782
  return consumed({
38796
38783
  kind: "composer.copyLast"
38797
38784
  });
38785
+ case "q":
38786
+ return ctx.pendingUserInput ? consumed({
38787
+ kind: "requestUserInput.show"
38788
+ }) : PASSTHROUGH;
38798
38789
  case "l":
38799
38790
  return consumed({
38800
38791
  kind: "transcript.clear"
@@ -39157,7 +39148,8 @@ function KeyboardController() {
39157
39148
  historySearchActive: Boolean(tui.store.ui.historySearch),
39158
39149
  queuedCount: tui.store.snapshot.queuedPrompts.length,
39159
39150
  activeMainTab: tui.store.ui.activeMainTab,
39160
- ...pendingRequest && requestState && pendingQuestion ? {
39151
+ pendingUserInput: Boolean(pendingRequest),
39152
+ ...pendingRequest && requestState && pendingQuestion && !requestState.dismissed ? {
39161
39153
  requestUserInput: {
39162
39154
  textMode: requestState.textModeQuestionId === pendingQuestion.id,
39163
39155
  canExitTextMode: Boolean(pendingQuestion.choices?.length),
@@ -39258,6 +39250,13 @@ function KeyboardController() {
39258
39250
  await tui.answerUserInputQuestion(current.question.id, draft);
39259
39251
  return;
39260
39252
  }
39253
+ case "requestUserInput.dismiss":
39254
+ tui.actions.requestUserInputDismissedSet(true);
39255
+ composer.focus();
39256
+ return;
39257
+ case "requestUserInput.show":
39258
+ tui.actions.requestUserInputDismissedSet(false);
39259
+ return;
39261
39260
  case "requestUserInput.cancel":
39262
39261
  await tui.cancelUserInput();
39263
39262
  return;
@@ -40099,8 +40098,8 @@ function KeyboardController() {
40099
40098
  const search = tui.store.ui.historySearch;
40100
40099
  if (!search)
40101
40100
  return;
40102
- const matches2 = currentHistoryMatches();
40103
- const preview = matches2[search.index] ?? search.originalDraft;
40101
+ const matches = currentHistoryMatches();
40102
+ const preview = matches[search.index] ?? search.originalDraft;
40104
40103
  composer.setDraft(preview);
40105
40104
  }
40106
40105
  async function openExternalEditor() {
@@ -40194,7 +40193,10 @@ function KeyboardController() {
40194
40193
  for (const message of [...tui.store.snapshot.messages].reverse()) {
40195
40194
  if (message.role !== "assistant")
40196
40195
  continue;
40197
- const parts = message.parts.map((part) => {
40196
+ const parts = message.parts.filter((part) => part.type === "text").map((part) => {
40197
+ const streamed = tui.transcript.state.text[part.id]?.content;
40198
+ if (streamed !== undefined)
40199
+ return streamed;
40198
40200
  if (typeof part.payload === "string")
40199
40201
  return part.payload;
40200
40202
  if (part.payload && typeof part.payload === "object" && "text" in part.payload) {
@@ -40592,11 +40594,13 @@ function AssistantMessage(props) {
40592
40594
  insertNode(_el$, _el$2);
40593
40595
  setProp(_el$, "style", {
40594
40596
  flexDirection: "column",
40597
+ flexShrink: 0,
40595
40598
  marginBottom: 1
40596
40599
  });
40597
40600
  insertNode(_el$2, _el$3);
40598
40601
  setProp(_el$2, "style", {
40599
40602
  flexDirection: "row",
40603
+ flexShrink: 0,
40600
40604
  minWidth: 0
40601
40605
  });
40602
40606
  insert(_el$2, createComponent2(TranscriptMarker, {
@@ -40612,23 +40616,23 @@ function AssistantMessage(props) {
40612
40616
  minWidth: 0
40613
40617
  });
40614
40618
  setProp(_el$4, "width", "100%");
40619
+ setProp(_el$4, "streaming", true);
40620
+ setProp(_el$4, "internalBlockMode", "top-level");
40615
40621
  setProp(_el$4, "tableOptions", {
40616
40622
  style: "columns",
40617
40623
  widthMode: "content",
40618
40624
  cellPaddingX: 1
40619
40625
  });
40620
40626
  effect((_p$) => {
40621
- var _v$ = props.row.text, _v$2 = props.row.streaming, _v$3 = syntax(), _v$4 = COLOR.markdownText;
40627
+ var _v$ = props.streamedText ?? props.row.text, _v$2 = syntax(), _v$3 = COLOR.markdownText;
40622
40628
  _v$ !== _p$.e && (_p$.e = setProp(_el$4, "content", _v$, _p$.e));
40623
- _v$2 !== _p$.t && (_p$.t = setProp(_el$4, "streaming", _v$2, _p$.t));
40624
- _v$3 !== _p$.a && (_p$.a = setProp(_el$4, "syntaxStyle", _v$3, _p$.a));
40625
- _v$4 !== _p$.o && (_p$.o = setProp(_el$4, "fg", _v$4, _p$.o));
40629
+ _v$2 !== _p$.t && (_p$.t = setProp(_el$4, "syntaxStyle", _v$2, _p$.t));
40630
+ _v$3 !== _p$.a && (_p$.a = setProp(_el$4, "fg", _v$3, _p$.a));
40626
40631
  return _p$;
40627
40632
  }, {
40628
40633
  e: undefined,
40629
40634
  t: undefined,
40630
- a: undefined,
40631
- o: undefined
40635
+ a: undefined
40632
40636
  });
40633
40637
  return _el$;
40634
40638
  })();
@@ -40707,18 +40711,24 @@ function ReasoningRow(props) {
40707
40711
  const tui = useTuiStore();
40708
40712
  const dims = useTerminalDimensions();
40709
40713
  const expanded = () => Boolean(tui.store.ui.expandedCells[props.row.id]);
40710
- const label = () => truncateLine2(props.row.title === "reasoning" ? "thinking" : props.row.title, Math.max(1, dims().width - 4));
40714
+ const content = createMemo(() => props.streamedReasoning === undefined ? {
40715
+ title: props.row.title,
40716
+ body: props.row.body
40717
+ } : parseReasoning(props.streamedReasoning));
40718
+ const label = () => truncateLine2(content().title === "reasoning" ? "thinking" : content().title, Math.max(1, dims().width - 4));
40711
40719
  const toggleClick = createPrimaryClickGesture(() => tui.actions.cellExpandedToggle(props.row.id));
40712
40720
  return (() => {
40713
40721
  var _el$ = createElement("box"), _el$2 = createElement("box"), _el$3 = createElement("text");
40714
40722
  insertNode(_el$, _el$2);
40715
40723
  setProp(_el$, "style", {
40716
40724
  flexDirection: "column",
40725
+ flexShrink: 0,
40717
40726
  marginBottom: 1
40718
40727
  });
40719
40728
  insertNode(_el$2, _el$3);
40720
40729
  setProp(_el$2, "style", {
40721
- flexDirection: "row"
40730
+ flexDirection: "row",
40731
+ flexShrink: 0
40722
40732
  });
40723
40733
  spread(_el$2, toggleClick, true);
40724
40734
  insert(_el$2, createComponent2(TranscriptMarker, {
@@ -40732,24 +40742,24 @@ function ReasoningRow(props) {
40732
40742
  insert(_el$3, label);
40733
40743
  insert(_el$, createComponent2(Show, {
40734
40744
  get when() {
40735
- return memo2(() => !!expanded())() && props.row.body.trim();
40745
+ return memo2(() => !!expanded())() && content().body.trim();
40736
40746
  },
40737
40747
  get children() {
40738
40748
  return createComponent2(ExpandedPanel, {
40739
40749
  get children() {
40740
40750
  var _el$4 = createElement("markdown");
40751
+ setProp(_el$4, "streaming", true);
40752
+ setProp(_el$4, "internalBlockMode", "top-level");
40741
40753
  effect((_p$) => {
40742
- var _v$ = props.row.body, _v$2 = props.row.streaming, _v$3 = syntax(), _v$4 = COLOR.dim;
40754
+ var _v$ = content().body, _v$2 = syntax(), _v$3 = COLOR.dim;
40743
40755
  _v$ !== _p$.e && (_p$.e = setProp(_el$4, "content", _v$, _p$.e));
40744
- _v$2 !== _p$.t && (_p$.t = setProp(_el$4, "streaming", _v$2, _p$.t));
40745
- _v$3 !== _p$.a && (_p$.a = setProp(_el$4, "syntaxStyle", _v$3, _p$.a));
40746
- _v$4 !== _p$.o && (_p$.o = setProp(_el$4, "fg", _v$4, _p$.o));
40756
+ _v$2 !== _p$.t && (_p$.t = setProp(_el$4, "syntaxStyle", _v$2, _p$.t));
40757
+ _v$3 !== _p$.a && (_p$.a = setProp(_el$4, "fg", _v$3, _p$.a));
40747
40758
  return _p$;
40748
40759
  }, {
40749
40760
  e: undefined,
40750
40761
  t: undefined,
40751
- a: undefined,
40752
- o: undefined
40762
+ a: undefined
40753
40763
  });
40754
40764
  return _el$4;
40755
40765
  }
@@ -40778,6 +40788,7 @@ var init_reasoning_cell = __esm(() => {
40778
40788
  init_expanded_panel();
40779
40789
  init_transcript_marker();
40780
40790
  init_mouse();
40791
+ init_reasoning();
40781
40792
  });
40782
40793
 
40783
40794
  // src/agent-tui/filetype.ts
@@ -42442,12 +42453,18 @@ function FaraiRow(props) {
42442
42453
  return createComponent2(AssistantMessage, {
42443
42454
  get row() {
42444
42455
  return props.row;
42456
+ },
42457
+ get streamedText() {
42458
+ return props.streamedText;
42445
42459
  }
42446
42460
  });
42447
42461
  case "thinking":
42448
42462
  return createComponent2(ReasoningRow, {
42449
42463
  get row() {
42450
42464
  return props.row;
42465
+ },
42466
+ get streamedReasoning() {
42467
+ return props.streamedReasoning;
42451
42468
  }
42452
42469
  });
42453
42470
  case "tool":
@@ -42573,7 +42590,16 @@ function Transcript() {
42573
42590
  byId
42574
42591
  };
42575
42592
  });
42576
- const rawRows = createMemo(() => tui.store.snapshot.messages.flatMap((message) => message.parts.map((part) => `${message.role}.${part.type} ${formatPayload(part.payload, 16000)}`)));
42593
+ const rawRows = createMemo(() => tui.store.snapshot.messages.flatMap((message) => message.parts.map((part) => {
42594
+ const streamText = tui.transcript.state.text[part.id]?.content;
42595
+ const streamReasoning = tui.transcript.state.reasoning[part.id]?.content;
42596
+ const payload = streamText !== undefined ? {
42597
+ text: streamText
42598
+ } : streamReasoning !== undefined ? {
42599
+ rationale: streamReasoning
42600
+ } : part.payload;
42601
+ return `${message.role}.${part.type} ${formatPayload(payload, 16000)}`;
42602
+ })));
42577
42603
  createEffect(() => {
42578
42604
  const request = tui.store.ui.messageNavigation;
42579
42605
  const userIds = transcriptRows().filter((row) => row.kind === "user").map((row) => row.id);
@@ -42590,6 +42616,7 @@ function Transcript() {
42590
42616
  typeof _ref$ === "function" ? use(_ref$, _el$) : scrollRef = _el$;
42591
42617
  setProp(_el$, "stickyScroll", true);
42592
42618
  setProp(_el$, "stickyStart", "bottom");
42619
+ setProp(_el$, "viewportCulling", true);
42593
42620
  setProp(_el$, "scrollbarOptions", {
42594
42621
  visible: false
42595
42622
  });
@@ -42655,6 +42682,12 @@ function Transcript() {
42655
42682
  children: (id2) => createComponent2(TranscriptRow, {
42656
42683
  get row() {
42657
42684
  return indexedRows().byId.get(id2);
42685
+ },
42686
+ get streamedText() {
42687
+ return tui.transcript.state.text[id2]?.content;
42688
+ },
42689
+ get streamedReasoning() {
42690
+ return tui.transcript.state.reasoning[id2]?.content;
42658
42691
  }
42659
42692
  })
42660
42693
  });
@@ -42770,12 +42803,19 @@ function TranscriptRow(props) {
42770
42803
  var _el$1 = createElement("box");
42771
42804
  setProp(_el$1, "style", {
42772
42805
  flexDirection: "column",
42806
+ flexShrink: 0,
42773
42807
  paddingLeft: isUser ? 0 : 1,
42774
42808
  paddingRight: isUser ? 0 : 1
42775
42809
  });
42776
42810
  insert(_el$1, createComponent2(FaraiRow, {
42777
42811
  get row() {
42778
42812
  return props.row;
42813
+ },
42814
+ get streamedText() {
42815
+ return props.streamedText;
42816
+ },
42817
+ get streamedReasoning() {
42818
+ return props.streamedReasoning;
42779
42819
  }
42780
42820
  }));
42781
42821
  effect((_$p) => setProp(_el$1, "id", props.row.id, _$p));
@@ -45166,15 +45206,15 @@ var init_status_indicator = __esm(() => {
45166
45206
  });
45167
45207
 
45168
45208
  // src/agent-tui/dialog/list-selection.ts
45169
- function selectableIndex(matches2, requested) {
45170
- const enabled = matches2.filter((match) => !match.option.disabled);
45209
+ function selectableIndex(matches, requested) {
45210
+ const enabled = matches.filter((match) => !match.option.disabled);
45171
45211
  if (enabled.length === 0)
45172
45212
  return -1;
45173
45213
  return Math.max(0, Math.min(requested, enabled.length - 1));
45174
45214
  }
45175
- function selectedOptionId(matches2, requested) {
45176
- const index = selectableIndex(matches2, requested);
45177
- return index < 0 ? undefined : matches2.filter((match) => !match.option.disabled)[index]?.option.id;
45215
+ function selectedOptionId(matches, requested) {
45216
+ const index = selectableIndex(matches, requested);
45217
+ return index < 0 ? undefined : matches.filter((match) => !match.option.disabled)[index]?.option.id;
45178
45218
  }
45179
45219
  function scrollWindowStart(total, cap, selectedIndex) {
45180
45220
  if (total <= cap || selectedIndex < 0)
@@ -45183,8 +45223,8 @@ function scrollWindowStart(total, cap, selectedIndex) {
45183
45223
  return 0;
45184
45224
  return Math.min(selectedIndex - cap + 1, total - cap);
45185
45225
  }
45186
- function displayRows(matches2, selectedId) {
45187
- return matches2.map((match) => ({
45226
+ function displayRows(matches, selectedId) {
45227
+ return matches.map((match) => ({
45188
45228
  option: match.option,
45189
45229
  matched: match.score > 0,
45190
45230
  disabled: Boolean(match.option.disabled),
@@ -45275,9 +45315,9 @@ var init_selection_row = __esm(() => {
45275
45315
  function ListOverlay(props) {
45276
45316
  const tui = useTuiStore();
45277
45317
  const dims = useTerminalDimensions();
45278
- const matches2 = createMemo(() => filterOptions(props.options, props.frame.query));
45279
- const groups = createMemo(() => groupByCategory(matches2(), props.frame.query.trim() !== ""));
45280
- const selectedId = createMemo(() => selectedOptionId(matches2(), props.frame.index));
45318
+ const matches = createMemo(() => filterOptions(props.options, props.frame.query));
45319
+ const groups = createMemo(() => groupByCategory(matches(), props.frame.query.trim() !== ""));
45320
+ const selectedId = createMemo(() => selectedOptionId(matches(), props.frame.index));
45281
45321
  const rows = createMemo(() => selectionRows(groups(), selectedId()));
45282
45322
  const width = () => Math.max(30, dims().width);
45283
45323
  const maxRows = () => overlayMaxRows(props.frame.kind, dims().height);
@@ -45294,7 +45334,7 @@ function ListOverlay(props) {
45294
45334
  const descCol = () => descriptionColumn(visibleRows(), width());
45295
45335
  const subtitle = () => overlaySubtitle(props.frame.kind);
45296
45336
  const selectOption = (id2) => {
45297
- const enabled = matches2().filter((match) => !match.option.disabled);
45337
+ const enabled = matches().filter((match) => !match.option.disabled);
45298
45338
  const index = enabled.findIndex((match) => match.option.id === id2);
45299
45339
  if (index >= 0)
45300
45340
  tui.actions.overlaySetIndex(index, enabled.length);
@@ -45343,7 +45383,7 @@ function ListOverlay(props) {
45343
45383
  return dims().height;
45344
45384
  },
45345
45385
  get matches() {
45346
- return matches2();
45386
+ return matches();
45347
45387
  },
45348
45388
  get selectedId() {
45349
45389
  return selectedId();
@@ -45380,14 +45420,14 @@ function ListOverlay(props) {
45380
45420
  })());
45381
45421
  insert(_el$6, (() => {
45382
45422
  var _c$2 = memo2(() => !!props.frame.query);
45383
- return () => _c$2() ? `${matches2().length}/${props.options.length}` : memo2(() => matches2().length > 0)() ? `${Math.min(props.frame.index + 1, matches2().length)}/${matches2().length}` : "";
45423
+ return () => _c$2() ? `${matches().length}/${props.options.length}` : memo2(() => matches().length > 0)() ? `${Math.min(props.frame.index + 1, matches().length)}/${matches().length}` : "";
45384
45424
  })());
45385
45425
  setProp(_el$7, "style", {
45386
45426
  flexDirection: "column"
45387
45427
  });
45388
45428
  insert(_el$7, createComponent2(Show, {
45389
45429
  get when() {
45390
- return matches2().length > 0;
45430
+ return matches().length > 0;
45391
45431
  },
45392
45432
  get fallback() {
45393
45433
  return (() => {
@@ -45431,7 +45471,7 @@ function ListOverlay(props) {
45431
45471
  }));
45432
45472
  insert(_el$, createComponent2(SelectionMenuHint, {
45433
45473
  get text() {
45434
- return overlayHint(props.frame, matches2(), tui.store.ui.modelProviders);
45474
+ return overlayHint(props.frame, matches(), tui.store.ui.modelProviders);
45435
45475
  }
45436
45476
  }), null);
45437
45477
  effect((_p$) => {
@@ -46000,10 +46040,10 @@ function overlaySubtitle(kind) {
46000
46040
  return "inspect durable memory";
46001
46041
  return "";
46002
46042
  }
46003
- function overlayHint(frame, matches2, providers) {
46043
+ function overlayHint(frame, matches, providers) {
46004
46044
  if (frame.kind !== "model")
46005
46045
  return "press enter to confirm or esc to go back";
46006
- const enabled = matches2.filter((match) => !match.option.disabled);
46046
+ const enabled = matches.filter((match) => !match.option.disabled);
46007
46047
  const selected = enabled[frame.index]?.option.value;
46008
46048
  if (selected?.kind === "model_action")
46009
46049
  return "enter add provider \xB7 ctrl+a add \xB7 esc back";
@@ -46311,20 +46351,20 @@ function requestStatusDetail(width, progress, countdown) {
46311
46351
  function requestUserInputHint(width, textMode, hasChoices) {
46312
46352
  if (textMode) {
46313
46353
  if (width >= 76)
46314
- return `enter continue \xB7 ${hasChoices ? "tab choices \xB7 " : ""}ctrl+p/n questions \xB7 esc ${hasChoices ? "choices" : "cancel"}`;
46354
+ return `enter continue \xB7 ${hasChoices ? "tab choices \xB7 " : ""}ctrl+p/n questions \xB7 esc ${hasChoices ? "choices" : "chat"} \xB7 ctrl+x cancel`;
46315
46355
  if (width >= 52)
46316
- return `enter \xB7 ctrl+p/n questions \xB7 esc ${hasChoices ? "choices" : "cancel"}`;
46317
- return `enter \xB7 esc ${hasChoices ? "choices" : "cancel"}`;
46356
+ return `enter \xB7 ctrl+p/n questions \xB7 esc ${hasChoices ? "choices" : "chat"}`;
46357
+ return `enter \xB7 esc ${hasChoices ? "choices" : "chat"}`;
46318
46358
  }
46319
46359
  if (width >= 86)
46320
- return "\u2191\u2193 select \xB7 1-9 choose \xB7 \u2190\u2192/ctrl+p/n questions \xB7 tab other \xB7 esc cancel";
46360
+ return "\u2191\u2193 select \xB7 1-9 choose \xB7 \u2190\u2192/ctrl+p/n questions \xB7 tab other \xB7 esc chat \xB7 ctrl+x cancel";
46321
46361
  if (width >= 58)
46322
- return "\u2191\u2193 select \xB7 enter \xB7 \u2190\u2192 questions \xB7 tab other \xB7 esc cancel";
46362
+ return "\u2191\u2193 select \xB7 enter \xB7 \u2190\u2192 questions \xB7 tab other \xB7 esc chat";
46323
46363
  if (width >= 42)
46324
- return "\u2191\u2193 select \xB7 enter \xB7 tab other \xB7 esc cancel";
46364
+ return "\u2191\u2193 select \xB7 enter \xB7 tab other \xB7 esc chat";
46325
46365
  if (width >= 34)
46326
- return "\u2191\u2193 select \xB7 enter \xB7 esc cancel";
46327
- return "\u2191\u2193 \xB7 enter \xB7 esc";
46366
+ return "\u2191\u2193 select \xB7 enter \xB7 esc chat";
46367
+ return "\u2191\u2193 \xB7 enter \xB7 esc chat";
46328
46368
  }
46329
46369
  function requestOptionRows(question) {
46330
46370
  if (!question?.choices?.length)
@@ -46900,7 +46940,8 @@ function BottomPane() {
46900
46940
  const proxyTabActive = () => slot() === "proxy_tab";
46901
46941
  const providerWizardActive = () => Boolean(tui.store.ui.modelProviderWizard);
46902
46942
  const providerRemovalActive = () => Boolean(tui.store.ui.modelProviderRemoval);
46903
- const inputRequestActive = () => Boolean(tui.store.snapshot.pendingUserInput);
46943
+ const inputRequestActive = () => Boolean(tui.store.snapshot.pendingUserInput && !tui.store.ui.requestUserInput?.dismissed);
46944
+ const inputRequestPending = () => Boolean(tui.store.snapshot.pendingUserInput);
46904
46945
  const footerHidden = () => Boolean(frame()) || Boolean(centerFrame()) || slashPanelActive() || proxyTabActive() || inputRequestActive() || providerWizardActive() || providerRemovalActive();
46905
46946
  const inlineStatusDetail = () => {
46906
46947
  const detail = tui.store.ui.statusDetail;
@@ -46960,10 +47001,10 @@ function BottomPane() {
46960
47001
  return tui.store.ui.lastError;
46961
47002
  },
46962
47003
  children: (error) => (() => {
46963
- var _el$2 = createElement("text");
46964
- insert(_el$2, () => `\u2022 error \xB7 ${truncateLine2(error(), 160)}`);
46965
- effect((_$p) => setProp(_el$2, "fg", COLOR.error, _$p));
46966
- return _el$2;
47004
+ var _el$4 = createElement("text");
47005
+ insert(_el$4, () => `\u2022 error \xB7 ${truncateLine2(error(), 160)}`);
47006
+ effect((_$p) => setProp(_el$4, "fg", COLOR.error, _$p));
47007
+ return _el$4;
46967
47008
  })()
46968
47009
  }), null);
46969
47010
  insert(_el$, createComponent2(Show, {
@@ -46974,6 +47015,17 @@ function BottomPane() {
46974
47015
  return createComponent2(PendingInputPreview, {});
46975
47016
  }
46976
47017
  }), null);
47018
+ insert(_el$, createComponent2(Show, {
47019
+ get when() {
47020
+ return memo2(() => !!(!inputRequestActive() && inputRequestPending() && !providerWizardActive()))() && !providerRemovalActive();
47021
+ },
47022
+ get children() {
47023
+ var _el$2 = createElement("text");
47024
+ insertNode(_el$2, createTextNode(` question pending \xB7 ctrl+q answer \xB7 chat input remains available`));
47025
+ effect((_$p) => setProp(_el$2, "fg", COLOR.warning, _$p));
47026
+ return _el$2;
47027
+ }
47028
+ }), null);
46977
47029
  insert(_el$, createComponent2(Show, {
46978
47030
  get when() {
46979
47031
  return tui.store.ui.modelProviderRemoval;
@@ -46986,7 +47038,7 @@ function BottomPane() {
46986
47038
  get fallback() {
46987
47039
  return createComponent2(Show, {
46988
47040
  get when() {
46989
- return tui.store.snapshot.pendingUserInput;
47041
+ return memo2(() => !!inputRequestActive())() ? tui.store.snapshot.pendingUserInput : undefined;
46990
47042
  },
46991
47043
  get fallback() {
46992
47044
  return createComponent2(Show, {
@@ -47063,54 +47115,54 @@ function BottomPane() {
47063
47115
  function ProxyTabFooter() {
47064
47116
  const tui = useTuiStore();
47065
47117
  return (() => {
47066
- var _el$3 = createElement("box"), _el$4 = createElement("text"), _el$6 = createElement("text");
47067
- insertNode(_el$3, _el$4);
47068
- insertNode(_el$3, _el$6);
47069
- setProp(_el$3, "style", {
47118
+ var _el$5 = createElement("box"), _el$6 = createElement("text"), _el$8 = createElement("text");
47119
+ insertNode(_el$5, _el$6);
47120
+ insertNode(_el$5, _el$8);
47121
+ setProp(_el$5, "style", {
47070
47122
  height: 1,
47071
47123
  flexDirection: "row",
47072
47124
  justifyContent: "space-between",
47073
47125
  paddingLeft: 1,
47074
47126
  paddingRight: 1
47075
47127
  });
47076
- insertNode(_el$4, createTextNode(`\u2191\u2193 flow \xB7 tab detail \xB7 p/n ws msg \xB7 \u2190\u2192 filter \xB7 a/h/w tabs \xB7 ctrl+1 chat`));
47077
- insert(_el$6, () => `proxy \xB7 ${tui.store.ui.proxyFilter}`);
47128
+ insertNode(_el$6, createTextNode(`\u2191\u2193 flow \xB7 tab detail \xB7 p/n ws msg \xB7 \u2190\u2192 filter \xB7 a/h/w tabs \xB7 ctrl+1 chat`));
47129
+ insert(_el$8, () => `proxy \xB7 ${tui.store.ui.proxyFilter}`);
47078
47130
  effect((_p$) => {
47079
47131
  var _v$ = COLOR.dim, _v$2 = COLOR.dim;
47080
- _v$ !== _p$.e && (_p$.e = setProp(_el$4, "fg", _v$, _p$.e));
47081
- _v$2 !== _p$.t && (_p$.t = setProp(_el$6, "fg", _v$2, _p$.t));
47132
+ _v$ !== _p$.e && (_p$.e = setProp(_el$6, "fg", _v$, _p$.e));
47133
+ _v$2 !== _p$.t && (_p$.t = setProp(_el$8, "fg", _v$2, _p$.t));
47082
47134
  return _p$;
47083
47135
  }, {
47084
47136
  e: undefined,
47085
47137
  t: undefined
47086
47138
  });
47087
- return _el$3;
47139
+ return _el$5;
47088
47140
  })();
47089
47141
  }
47090
47142
  function CenterSurfaceFooter(props) {
47091
47143
  return (() => {
47092
- var _el$7 = createElement("box"), _el$8 = createElement("text"), _el$9 = createElement("text");
47093
- insertNode(_el$7, _el$8);
47094
- insertNode(_el$7, _el$9);
47095
- setProp(_el$7, "style", {
47144
+ var _el$9 = createElement("box"), _el$0 = createElement("text"), _el$1 = createElement("text");
47145
+ insertNode(_el$9, _el$0);
47146
+ insertNode(_el$9, _el$1);
47147
+ setProp(_el$9, "style", {
47096
47148
  height: 1,
47097
47149
  flexDirection: "row",
47098
47150
  justifyContent: "space-between",
47099
47151
  paddingLeft: 1,
47100
47152
  paddingRight: 1
47101
47153
  });
47102
- insert(_el$8, () => centerSurfaceFooter(props.frame).toLowerCase());
47103
- insert(_el$9, () => props.frame.kind.toLowerCase());
47154
+ insert(_el$0, () => centerSurfaceFooter(props.frame).toLowerCase());
47155
+ insert(_el$1, () => props.frame.kind.toLowerCase());
47104
47156
  effect((_p$) => {
47105
47157
  var _v$3 = COLOR.dim, _v$4 = COLOR.dim;
47106
- _v$3 !== _p$.e && (_p$.e = setProp(_el$8, "fg", _v$3, _p$.e));
47107
- _v$4 !== _p$.t && (_p$.t = setProp(_el$9, "fg", _v$4, _p$.t));
47158
+ _v$3 !== _p$.e && (_p$.e = setProp(_el$0, "fg", _v$3, _p$.e));
47159
+ _v$4 !== _p$.t && (_p$.t = setProp(_el$1, "fg", _v$4, _p$.t));
47108
47160
  return _p$;
47109
47161
  }, {
47110
47162
  e: undefined,
47111
47163
  t: undefined
47112
47164
  });
47113
- return _el$7;
47165
+ return _el$9;
47114
47166
  })();
47115
47167
  }
47116
47168
  var init_bottom_pane = __esm(() => {
@@ -47669,9 +47721,9 @@ async function ensureSession(input) {
47669
47721
  } catch {
47670
47722
  const needle = input.sessionId.trim().toLowerCase();
47671
47723
  const sessions2 = await input.runtime.listSessions();
47672
- const matches2 = sessions2.filter((session) => session.id.toLowerCase().startsWith(needle) || session.title?.trim().toLowerCase() === needle);
47673
- if (matches2.length === 1)
47674
- return matches2[0].id;
47724
+ const matches = sessions2.filter((session) => session.id.toLowerCase().startsWith(needle) || session.title?.trim().toLowerCase() === needle);
47725
+ if (matches.length === 1)
47726
+ return matches[0].id;
47675
47727
  throw new SessionResolutionError(input.sessionId, input.workspace, sessions2);
47676
47728
  }
47677
47729
  }
@@ -50303,5 +50355,5 @@ Examples:
50303
50355
  `);
50304
50356
  }
50305
50357
 
50306
- //# debugId=A6CB55D8D7667DD664756E2164756E21
50358
+ //# debugId=90702708DD95C63C64756E2164756E21
50307
50359
  //# sourceMappingURL=index.js.map