farai 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -3635,7 +3635,7 @@ function proxyConfig(value) {
3635
3635
  }
3636
3636
  function resolveProxyConfig(config) {
3637
3637
  return {
3638
- transparent: config.proxy?.transparent !== false,
3638
+ transparent: config.proxy?.transparent === true,
3639
3639
  ports: config.proxy?.ports?.length ? config.proxy.ports : DEFAULT_TRANSPARENT_PROXY_PORTS
3640
3640
  };
3641
3641
  }
@@ -3958,10 +3958,9 @@ 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
- transparent = true
3963
+ transparent = false
3965
3964
 
3966
3965
  [mcp_servers.mitmproxy-mcp]
3967
3966
  command = "mitmproxy-mcp"
@@ -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) {
23544
+ async run(work) {
23727
23545
  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);
23732
- 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 {
@@ -24551,7 +24342,11 @@ class AgentRuntime {
24551
24342
  });
24552
24343
  }
24553
24344
  if (!session.archivedAt) {
24554
- this.userInputs.recover(session.id, this.store.listEvents(session.id, 1e4));
24345
+ const pendingUserInput = this.userInputs.recover(session.id, this.store.listEvents(session.id, 1e4));
24346
+ const latestInterruptedTurn = [...turns].reverse().find((turn) => newlyInterruptedTurns.has(turn.id));
24347
+ if (latestInterruptedTurn && !pendingUserInput && !this.mailbox.hasQueued(session.id)) {
24348
+ 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}`);
24349
+ }
24555
24350
  }
24556
24351
  }
24557
24352
  for (const job of this.store.listRecoverableJobs()) {
@@ -24564,7 +24359,7 @@ class AgentRuntime {
24564
24359
  continue;
24565
24360
  }
24566
24361
  }
24567
- this.jobs.markLost(job.id, "Background execution owner was lost during runtime restart. The original work was not replayed.", job.agentMode !== "attached");
24362
+ 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
24363
  }
24569
24364
  for (const job of this.store.listTerminalJobsMissingMailbox())
24570
24365
  this.jobs.repairTerminalMailbox(job.id);
@@ -25551,8 +25346,8 @@ class AgentRuntime {
25551
25346
  });
25552
25347
  let autoContinueStreak = 0;
25553
25348
  let resumeAfterCompaction = false;
25554
- const maxSteps = userAuthored ? this.maxSteps : BACKGROUND_COMPLETION_MAX_STEPS;
25555
- const maxTurnMs = userAuthored ? this.maxTurnMs : Number.POSITIVE_INFINITY;
25349
+ const maxSteps = this.maxSteps;
25350
+ const maxTurnMs = this.maxTurnMs;
25556
25351
  const loopStartedAt = Date.now();
25557
25352
  let timeBudgetWarned = false;
25558
25353
  let loopError;
@@ -25590,7 +25385,7 @@ class AgentRuntime {
25590
25385
  responses.push(...await this.forceTimeLimitWrapUp(session, turn, assistantMessage, planner, maxTurnMs));
25591
25386
  break;
25592
25387
  }
25593
- if (!timeBudgetWarned && elapsedMs >= maxTurnMs * 0.75) {
25388
+ if (Number.isFinite(maxTurnMs) && !timeBudgetWarned && elapsedMs >= maxTurnMs * 0.75) {
25594
25389
  timeBudgetWarned = true;
25595
25390
  const secondsLeft = Math.max(1, Math.ceil((maxTurnMs - elapsedMs) / 1000));
25596
25391
  const queue = this.pendingSteeringContext.get(session.id) ?? [];
@@ -25601,10 +25396,7 @@ class AgentRuntime {
25601
25396
  stepCount: step + 1
25602
25397
  });
25603
25398
  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");
25399
+ responses.push(...await this.forceStepLimitWrapUp(session, turn, assistantMessage, planner, maxSteps));
25608
25400
  break;
25609
25401
  }
25610
25402
  const compactResult = await this.maybeAutoCompact(session, planner);
@@ -25697,7 +25489,7 @@ This completion is already terminal and was delivered automatically. Do not call
25697
25489
  contextWindow: resolveContextWindow(planner.contextWindow),
25698
25490
  maxOutputTokens: resolveMaxOutputTokens(planner.maxOutputTokens),
25699
25491
  ...this.contextBudgetInput(),
25700
- toolsEnabled: userAuthored,
25492
+ toolsEnabled: true,
25701
25493
  extraBlocks: [...passiveCompletions ? [{
25702
25494
  title: "Completed Background Work",
25703
25495
  body: passiveCompletions,
@@ -25770,14 +25562,14 @@ This completion is already terminal and was delivered automatically. Do not call
25770
25562
  contextBlocks: context.contextBlocks,
25771
25563
  tools: context.tools,
25772
25564
  toolCatalog: context.toolCatalog,
25773
- toolChoice: userAuthored ? "auto" : "none"
25565
+ toolChoice: "auto"
25774
25566
  };
25775
25567
  resumeAfterCompaction = false;
25776
25568
  const autoContinue = {
25777
25569
  streak: autoContinueStreak
25778
25570
  };
25779
25571
  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);
25572
+ 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
25573
  autoContinueStreak = autoContinue.streak;
25782
25574
  if (control.cancelled)
25783
25575
  return responses.join(`
@@ -25900,10 +25692,10 @@ This completion is already terminal and was delivered automatically. Do not call
25900
25692
  latencyMs
25901
25693
  });
25902
25694
  }
25903
- async batchStep(planner, plannerInput, session, turn, assistantMessage, step, context, responses, autoContinue, toolsAllowed, modelTimeoutMs) {
25695
+ async batchStep(planner, plannerInput, session, turn, assistantMessage, step, context, responses, autoContinue, modelTimeoutMs) {
25904
25696
  let actions;
25905
25697
  try {
25906
- actions = await this.planWithRetry(planner, plannerInput, session, turn, assistantMessage, context, modelTimeoutMs, !toolsAllowed);
25698
+ actions = await this.planWithRetry(planner, plannerInput, session, turn, assistantMessage, context, modelTimeoutMs);
25907
25699
  } catch (error) {
25908
25700
  if (error instanceof ModelCallDeadlineError)
25909
25701
  return {
@@ -25947,10 +25739,7 @@ This completion is already terminal and was delivered automatically. Do not call
25947
25739
  };
25948
25740
  for (const action of actions) {
25949
25741
  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);
25742
+ toolBatch.push(action);
25954
25743
  continue;
25955
25744
  }
25956
25745
  if (await flushToolBatch())
@@ -26090,10 +25879,6 @@ This completion is already terminal and was delivered automatically. Do not call
26090
25879
  this.recordToolParseError(session, turn, assistantMessage, step, toolCallId ?? toolName, toolName, error instanceof Error ? error.message : String(error), event.arguments);
26091
25880
  continue;
26092
25881
  }
26093
- if (!userAuthored) {
26094
- this.recordDisabledToolCall(session, turn, assistantMessage, step, toolCallId ?? toolName, toolName, args);
26095
- continue;
26096
- }
26097
25882
  const action = {
26098
25883
  kind: "tool",
26099
25884
  tool: toolName,
@@ -26398,10 +26183,6 @@ This completion is already terminal and was delivered automatically. Do not call
26398
26183
  }
26399
26184
  }
26400
26185
  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
26186
  responses.push(text);
26406
26187
  const streamed = this.streamingParts.get(turn.id);
26407
26188
  if (streamed?.textPartId) {
@@ -26421,29 +26202,11 @@ This completion is already terminal and was delivered automatically. Do not call
26421
26202
  recoverable
26422
26203
  });
26423
26204
  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
- });
26205
+ autoContinue.streak += 1;
26206
+ return true;
26437
26207
  }
26438
26208
  return false;
26439
26209
  }
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
26210
  recordToolParseError(session, turn, assistantMessage, step, toolCallId, tool, error, rawArguments) {
26448
26211
  const text = `Could not parse arguments for ${tool}: ${error}. Raw: ${rawArguments.slice(0, 500)}`;
26449
26212
  this.store.addPart({
@@ -26486,48 +26249,6 @@ This completion is already terminal and was delivered automatically. Do not call
26486
26249
  payload
26487
26250
  });
26488
26251
  }
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
26252
  async forceStepLimitWrapUp(session, turn, assistantMessage, planner, maxSteps) {
26532
26253
  return this.forceTextOnlyWrapUp({
26533
26254
  session,
@@ -26680,25 +26401,6 @@ This completion is already terminal and was delivered automatically. Do not call
26680
26401
  shouldContinue: !sawResponse
26681
26402
  };
26682
26403
  }
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
26404
  const validationError = validateToolArgs(tool.inputSchema, action.args);
26703
26405
  if (validationError) {
26704
26406
  const toolCallId = action.toolCallId ?? action.tool;
@@ -27256,25 +26958,18 @@ This completion is already terminal and was delivered automatically. Do not call
27256
26958
  const workspaceTransition = isWorkspaceTransitionTool(schedulingTool);
27257
26959
  const gateSignal = signal ? AbortSignal.any([signal, this.shutdownController.signal]) : this.shutdownController.signal;
27258
26960
  try {
27259
- return await this.workspaceBindingGate.run(`session-workspace:${session.id}`, !workspaceTransition, async (bindingLease) => {
26961
+ return await this.workspaceBindingGate.run(`session-workspace:${session.id}`, !workspaceTransition, async () => {
27260
26962
  session = this.store.loadSession(session.id);
27261
26963
  tool = toolForExecution(session, toolName);
27262
26964
  schedulingTool = toolSchedulingDefinition(tool, args, session);
27263
- return await this.toolExecutionGate.run(toolConcurrencyKey(schedulingTool, session, session.workspace), schedulingTool.parallel, async (gateLease) => {
27264
- gateLease.mirrorTo(bindingLease);
26965
+ return await this.toolExecutionGate.run(toolConcurrencyKey(schedulingTool, session, session.workspace), schedulingTool.parallel, async () => {
27265
26966
  gateSignal.throwIfAborted();
27266
26967
  if (owner && this.store.loadTurn(owner.turn.id).status === "cancelled")
27267
26968
  throw new Error("turn cancelled before tool start");
27268
- return await this.runToolUnderGate(session, tool, args, owner, providerToolCallId, gateLease);
26969
+ return await this.runToolUnderGate(session, tool, args, owner, providerToolCallId);
27269
26970
  }, gateSignal);
27270
26971
  }, gateSignal);
27271
26972
  } 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
26973
  const ownerCancelled = owner ? this.store.loadTurn(owner.turn.id).status === "cancelled" : false;
27279
26974
  if (gateSignal.aborted || ownerCancelled) {
27280
26975
  const message = gateSignal.reason ? String(gateSignal.reason) : "turn cancelled before tool start";
@@ -27304,7 +26999,7 @@ This completion is already terminal and was delivered automatically. Do not call
27304
26999
  });
27305
27000
  return this.toolCalls.settleError(toolCall, message, state);
27306
27001
  }
27307
- async runToolUnderGate(session, tool, args, owner, providerToolCallId, gateLease) {
27002
+ async runToolUnderGate(session, tool, args, owner, providerToolCallId) {
27308
27003
  const toolCall = this.toolCalls.begin({
27309
27004
  sessionId: session.id,
27310
27005
  tool: tool.name,
@@ -27319,7 +27014,7 @@ This completion is already terminal and was delivered automatically. Do not call
27319
27014
  providerToolCallId
27320
27015
  } : {}
27321
27016
  });
27322
- await this.executeToolUnderGate(session, toolCall.id, owner, gateLease);
27017
+ await this.executeToolUnderGate(session, toolCall.id, owner);
27323
27018
  return this.store.loadToolCall(toolCall.id);
27324
27019
  }
27325
27020
  async executeTool(session, toolCallId, owner) {
@@ -27337,29 +27032,18 @@ This completion is already terminal and was delivered automatically. Do not call
27337
27032
  const gateController = turnId ? this.registerTurnController(turnId) : undefined;
27338
27033
  const gateSignal = gateController ? AbortSignal.any([gateController.signal, this.shutdownController.signal]) : this.shutdownController.signal;
27339
27034
  try {
27340
- return await this.workspaceBindingGate.run(`session-workspace:${session.id}`, !workspaceTransition, async (bindingLease) => {
27035
+ return await this.workspaceBindingGate.run(`session-workspace:${session.id}`, !workspaceTransition, async () => {
27341
27036
  session = this.store.loadSession(session.id);
27342
27037
  tool = toolForExecution(session, toolCall.tool);
27343
27038
  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);
27039
+ return await this.toolExecutionGate.run(toolConcurrencyKey(schedulingTool, session, session.workspace), schedulingTool.parallel, async () => {
27346
27040
  gateSignal.throwIfAborted();
27347
27041
  if (turnId && this.store.loadTurn(turnId).status === "cancelled")
27348
27042
  throw new Error("turn cancelled before tool start");
27349
- return await this.executeToolUnderGate(session, toolCallId, owner, gateLease);
27043
+ return await this.executeToolUnderGate(session, toolCallId, owner);
27350
27044
  }, gateSignal);
27351
27045
  }, gateSignal);
27352
27046
  } 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
27047
  if (!gateSignal.aborted && (!turnId || this.store.loadTurn(turnId).status !== "cancelled"))
27364
27048
  throw error;
27365
27049
  const cancelled = this.store.loadToolCall(toolCallId);
@@ -27376,7 +27060,7 @@ This completion is already terminal and was delivered automatically. Do not call
27376
27060
  gateController?.release();
27377
27061
  }
27378
27062
  }
27379
- async executeToolUnderGate(session, toolCallId, owner, gateLease = new ToolGateLease) {
27063
+ async executeToolUnderGate(session, toolCallId, owner) {
27380
27064
  let toolCall = this.store.loadToolCall(toolCallId);
27381
27065
  if (toolCall.status !== "pending")
27382
27066
  return toolCall;
@@ -27664,7 +27348,7 @@ This completion is already terminal and was delivered automatically. Do not call
27664
27348
  ...nestedContext,
27665
27349
  signal: nestedDeadline.signal,
27666
27350
  timeoutMs: toolOperationTimeout(target.timeoutMs)
27667
- }), gateLease);
27351
+ }));
27668
27352
  lease.assertActive();
27669
27353
  await this.fireHooks(session, "tool.post", canonicalName, {
27670
27354
  tool: canonicalName,
@@ -27724,22 +27408,10 @@ This completion is already terminal and was delivered automatically. Do not call
27724
27408
  linkToolCall = true
27725
27409
  }) => {
27726
27410
  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
27411
  const previousJob = resumeSessionId ? this.store.listJobs(session.id, 1e4).find((job2) => job2.kind === "agent" && job2.childSessionId === resumeSessionId) : undefined;
27740
27412
  const effectiveLane = lane ?? previousJob?.lane;
27741
27413
  const laneDef = effectiveLane ? resolveLane(this.workspace, effectiveLane) : undefined;
27742
- if (!resumeSessionId && effectiveLane && !laneDef)
27414
+ if (effectiveLane && !laneDef)
27743
27415
  throw new Error(`unknown subagent lane: ${effectiveLane}`);
27744
27416
  let child;
27745
27417
  let scopedTools;
@@ -27753,10 +27425,22 @@ This completion is already terminal and was delivered automatically. Do not call
27753
27425
  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
27426
  if (active || this.hasRunningTurn(child.id))
27755
27427
  throw new Error(`subagent session ${child.id} is already running`);
27756
- scopedTools = child.toolScope;
27428
+ const requestedTools = tools ?? laneDef?.tools;
27429
+ scopedTools = requestedTools ? resolveSubagentToolScope({
27430
+ parent: session,
27431
+ availableTools: listToolsForSession(session),
27432
+ requestedTools
27433
+ }) : child.toolScope;
27757
27434
  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");
27435
+ const childModel = model ?? laneDef?.model;
27436
+ if (childModel && childModel !== child.model)
27437
+ child = this.updateSession(child.id, {
27438
+ model: childModel
27439
+ });
27440
+ if (requestedTools && scopedTools?.length)
27441
+ child = this.updateSession(child.id, {
27442
+ toolScope: scopedTools
27443
+ });
27760
27444
  } else {
27761
27445
  const requestedTools = tools ?? laneDef?.tools;
27762
27446
  scopedTools = resolveSubagentToolScope({
@@ -27767,8 +27451,6 @@ This completion is already terminal and was delivered automatically. Do not call
27767
27451
  } : {}
27768
27452
  });
27769
27453
  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
27454
  const childModel = model ?? laneDef?.model;
27773
27455
  child = await this.store.forkSession(session.id, title);
27774
27456
  this.recordSession(child);
@@ -27876,8 +27558,8 @@ This completion is already terminal and was delivered automatically. Do not call
27876
27558
  this.jobs.completeAgent(job.id, response, mode === "detached");
27877
27559
  return response;
27878
27560
  };
27879
- const gated = () => this.subagentGate.run(execute, agentController.signal);
27880
- return editsSharedWorkspace ? await this.subagentWorkspaceMutationGate.run(gated, agentController.signal) : await gated();
27561
+ const gated = () => session.parentId ? execute() : this.subagentGate.run(execute, agentController.signal);
27562
+ return editsSharedWorkspace && !session.parentId ? await this.subagentWorkspaceMutationGate.run(gated, agentController.signal) : await gated();
27881
27563
  } catch (error) {
27882
27564
  if (this.store.isOpen() && this.hasJob(job.id)) {
27883
27565
  const current = this.store.loadJob(job.id);
@@ -27931,7 +27613,6 @@ This completion is already terminal and was delivered automatically. Do not call
27931
27613
  this.turnControllers.delete(turnId);
27932
27614
  };
27933
27615
  let result;
27934
- const duplicate = findEquivalentBackgroundJob(activeBackgroundJobs(this.store.listToolCalls(session.id, 200)), toolCall.tool, toolCall.args);
27935
27616
  await this.fireHooks(session, "tool.pre", toolCall.tool, {
27936
27617
  tool: toolCall.tool,
27937
27618
  toolCallId: toolCall.id,
@@ -27939,16 +27620,7 @@ This completion is already terminal and was delivered automatically. Do not call
27939
27620
  });
27940
27621
  try {
27941
27622
  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);
27623
+ result = await deadline.run(() => tool.run(toolCall.args, context));
27952
27624
  } catch (error) {
27953
27625
  settleLiveOutput();
27954
27626
  releaseController();
@@ -28609,38 +28281,6 @@ function completedToolCallStatus(result) {
28609
28281
  return "running_background";
28610
28282
  return result.ok ? "done" : "error";
28611
28283
  }
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
28284
  function recoveredJobSummary(job) {
28645
28285
  if (job.result && typeof job.result === "object") {
28646
28286
  if ("response" in job.result)
@@ -28765,7 +28405,7 @@ function sameExistingPath(left, right) {
28765
28405
  return false;
28766
28406
  }
28767
28407
  }
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;
28408
+ var REASONING_MAX_BYTES2, STREAM_RENDER_INTERVAL_MS = 100, 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.", IneffectiveCompactionError, ModelCallDeadlineError, PlannerOutputValidationError;
28769
28409
  var init_runtime = __esm(() => {
28770
28410
  init_sqlite_store();
28771
28411
  init_registry4();
@@ -28832,6 +28472,24 @@ var init_runtime = __esm(() => {
28832
28472
  };
28833
28473
  });
28834
28474
 
28475
+ // src/branding.ts
28476
+ import figlet from "figlet";
28477
+ function renderFaraiBanner() {
28478
+ try {
28479
+ return figlet.textSync("farai", {
28480
+ font: "Ogre"
28481
+ }).trimEnd();
28482
+ } catch {
28483
+ return "farai";
28484
+ }
28485
+ }
28486
+ var FARAI_BANNER, FARAI_BANNER_LINES;
28487
+ var init_branding = __esm(() => {
28488
+ FARAI_BANNER = renderFaraiBanner();
28489
+ FARAI_BANNER_LINES = FARAI_BANNER.split(`
28490
+ `);
28491
+ });
28492
+
28835
28493
  // src/agent-knowledge/pack.ts
28836
28494
  import { existsSync as existsSync13, mkdirSync as mkdirSync7, readdirSync as readdirSync4, readFileSync as readFileSync12, renameSync as renameSync4, rmSync, writeFileSync as writeFileSync8 } from "fs";
28837
28495
  import { createHash as createHash5 } from "crypto";
@@ -28934,10 +28592,10 @@ function extractEntities(recordId2, text) {
28934
28592
  const found = new Set;
28935
28593
  const out = [];
28936
28594
  for (const pattern of ENTITY_PATTERNS) {
28937
- const matches2 = text.match(pattern.re);
28938
- if (!matches2)
28595
+ const matches = text.match(pattern.re);
28596
+ if (!matches)
28939
28597
  continue;
28940
- for (const raw of matches2) {
28598
+ for (const raw of matches) {
28941
28599
  const value = pattern.normalize ? pattern.normalize(raw) : raw;
28942
28600
  const key = `${pattern.type}:${value}`;
28943
28601
  if (found.has(key))
@@ -33838,7 +33496,8 @@ function createRequestUserInputUiState(request) {
33838
33496
  answers: {},
33839
33497
  drafts: {},
33840
33498
  textModeQuestionId: first && !first.choices?.length ? first.id : undefined,
33841
- submitting: false
33499
+ submitting: false,
33500
+ dismissed: false
33842
33501
  };
33843
33502
  }
33844
33503
  function syncRequestUserInputUiState(current, request) {
@@ -34043,7 +33702,8 @@ function initialStore(workspace) {
34043
33702
  sessionStats: {},
34044
33703
  agentThreads: [],
34045
33704
  lastError: undefined,
34046
- requestUserInput: undefined
33705
+ requestUserInput: undefined,
33706
+ updateNotice: undefined
34047
33707
  }
34048
33708
  };
34049
33709
  }
@@ -34465,6 +34125,9 @@ function createActions(store, setStore) {
34465
34125
  statusDetailSet(detail) {
34466
34126
  setStore("ui", "statusDetail", detail);
34467
34127
  },
34128
+ updateNoticeSet(notice) {
34129
+ setStore("ui", "updateNotice", notice);
34130
+ },
34468
34131
  contextUsageUpdated(usage2) {
34469
34132
  if (!usage2) {
34470
34133
  setStore("ui", "contextUsage", undefined);
@@ -34722,6 +34385,20 @@ function createActions(store, setStore) {
34722
34385
  return;
34723
34386
  s.ui.requestUserInput.submitting = submitting;
34724
34387
  }));
34388
+ },
34389
+ requestUserInputDismissedSet(dismissed) {
34390
+ setStore(produce((s) => {
34391
+ if (!s.ui.requestUserInput)
34392
+ return;
34393
+ s.ui.requestUserInput.dismissed = dismissed;
34394
+ if (dismissed)
34395
+ s.ui.requestUserInput.textModeQuestionId = undefined;
34396
+ else {
34397
+ const question = s.snapshot.pendingUserInput?.questions[s.ui.requestUserInput.questionIndex];
34398
+ if (question && !question.choices?.length)
34399
+ s.ui.requestUserInput.textModeQuestionId = question.id;
34400
+ }
34401
+ }));
34725
34402
  }
34726
34403
  };
34727
34404
  }
@@ -36829,6 +36506,14 @@ function TuiStoreProvider(props) {
36829
36506
  let sessionSelectionIntent = 0;
36830
36507
  let mcpOverlayGeneration = 0;
36831
36508
  let disposed = false;
36509
+ if (props.updateCheck?.cachedNotice)
36510
+ actions.updateNoticeSet(props.updateCheck.cachedNotice);
36511
+ if (props.updateCheck?.refresh) {
36512
+ props.updateCheck.refresh.then((notice) => {
36513
+ if (!disposed)
36514
+ actions.updateNoticeSet(notice);
36515
+ });
36516
+ }
36832
36517
  const timelineRows = createMemo(() => projectMessagesToRows(store.snapshot.messages, Math.max(1, dims().width - 4), store.snapshot.runningTurnId, store.snapshot.toolCalls, store.snapshot.toolInputPreviews));
36833
36518
  function setStatusDetail(detail, timeoutMs) {
36834
36519
  if (disposed)
@@ -37208,19 +36893,6 @@ function TuiStoreProvider(props) {
37208
36893
  const sid = store.activeSessionId;
37209
36894
  if (!sid || !text.trim())
37210
36895
  return false;
37211
- if (store.snapshot.pendingUserInput) {
37212
- actions.promptHistoryAdd(text);
37213
- (async () => {
37214
- try {
37215
- await port.answerUserInput(sid, text);
37216
- await requestSnapshotRefresh(sid);
37217
- } catch (error) {
37218
- if (!disposed && store.activeSessionId === sid)
37219
- actions.errorSet(error instanceof Error ? error.message : String(error));
37220
- }
37221
- })();
37222
- return true;
37223
- }
37224
36896
  if (promptSubmissions.has(sid) || isAgentBusy(store) || port.getRunningTurnId(sid)) {
37225
36897
  if (port.steer?.(sid, text)) {
37226
36898
  actions.promptHistoryAdd(text);
@@ -37320,12 +36992,6 @@ function TuiStoreProvider(props) {
37320
36992
  actions.snapshotPatched({
37321
36993
  pendingUserInput: undefined
37322
36994
  });
37323
- const turnId = store.snapshot.runningTurnId ?? port.getRunningTurnId(sid);
37324
- if (turnId && capabilities.cancel) {
37325
- try {
37326
- await port.cancelTurn(turnId, "user input cancelled");
37327
- } catch {}
37328
- }
37329
36995
  await requestSnapshotRefresh(sid);
37330
36996
  } catch (error) {
37331
36997
  if (!disposed && store.activeSessionId === sid)
@@ -37979,14 +37645,14 @@ function filterOptions(options, needle) {
37979
37645
  score: score2
37980
37646
  }));
37981
37647
  }
37982
- function groupByCategory(matches2, needleActive) {
37648
+ function groupByCategory(matches, needleActive) {
37983
37649
  if (needleActive)
37984
37650
  return [{
37985
37651
  category: undefined,
37986
- matches: [...matches2]
37652
+ matches: [...matches]
37987
37653
  }];
37988
37654
  const buckets = new Map;
37989
- for (const match of matches2) {
37655
+ for (const match of matches) {
37990
37656
  const key = match.option.category;
37991
37657
  const list2 = buckets.get(key) ?? [];
37992
37658
  list2.push(match);
@@ -38407,6 +38073,10 @@ function routeRequestUserInput(key, state) {
38407
38073
  if (state.submitting)
38408
38074
  return consumed();
38409
38075
  if (key.ctrl && key.name === "c")
38076
+ return consumed({
38077
+ kind: "requestUserInput.dismiss"
38078
+ });
38079
+ if (key.ctrl && key.name === "x")
38410
38080
  return consumed({
38411
38081
  kind: "requestUserInput.cancel"
38412
38082
  });
@@ -38425,7 +38095,7 @@ function routeRequestUserInput(key, state) {
38425
38095
  if (state.textMode) {
38426
38096
  if (key.name === "escape")
38427
38097
  return consumed({
38428
- kind: state.canExitTextMode ? "requestUserInput.textModeExit" : "requestUserInput.cancel"
38098
+ kind: state.canExitTextMode ? "requestUserInput.textModeExit" : "requestUserInput.dismiss"
38429
38099
  });
38430
38100
  if (key.name === "tab" && state.canExitTextMode)
38431
38101
  return consumed({
@@ -38440,7 +38110,7 @@ function routeRequestUserInput(key, state) {
38440
38110
  switch (key.name) {
38441
38111
  case "escape":
38442
38112
  return consumed({
38443
- kind: "requestUserInput.cancel"
38113
+ kind: "requestUserInput.dismiss"
38444
38114
  });
38445
38115
  case "up":
38446
38116
  return consumed({
@@ -38765,6 +38435,10 @@ function routeBase(key, ctx) {
38765
38435
  return consumed({
38766
38436
  kind: "composer.copyLast"
38767
38437
  });
38438
+ case "q":
38439
+ return ctx.pendingUserInput ? consumed({
38440
+ kind: "requestUserInput.show"
38441
+ }) : PASSTHROUGH;
38768
38442
  case "l":
38769
38443
  return consumed({
38770
38444
  kind: "transcript.clear"
@@ -39127,7 +38801,8 @@ function KeyboardController() {
39127
38801
  historySearchActive: Boolean(tui.store.ui.historySearch),
39128
38802
  queuedCount: tui.store.snapshot.queuedPrompts.length,
39129
38803
  activeMainTab: tui.store.ui.activeMainTab,
39130
- ...pendingRequest && requestState && pendingQuestion ? {
38804
+ pendingUserInput: Boolean(pendingRequest),
38805
+ ...pendingRequest && requestState && pendingQuestion && !requestState.dismissed ? {
39131
38806
  requestUserInput: {
39132
38807
  textMode: requestState.textModeQuestionId === pendingQuestion.id,
39133
38808
  canExitTextMode: Boolean(pendingQuestion.choices?.length),
@@ -39228,6 +38903,13 @@ function KeyboardController() {
39228
38903
  await tui.answerUserInputQuestion(current.question.id, draft);
39229
38904
  return;
39230
38905
  }
38906
+ case "requestUserInput.dismiss":
38907
+ tui.actions.requestUserInputDismissedSet(true);
38908
+ composer.focus();
38909
+ return;
38910
+ case "requestUserInput.show":
38911
+ tui.actions.requestUserInputDismissedSet(false);
38912
+ return;
39231
38913
  case "requestUserInput.cancel":
39232
38914
  await tui.cancelUserInput();
39233
38915
  return;
@@ -40069,8 +39751,8 @@ function KeyboardController() {
40069
39751
  const search = tui.store.ui.historySearch;
40070
39752
  if (!search)
40071
39753
  return;
40072
- const matches2 = currentHistoryMatches();
40073
- const preview = matches2[search.index] ?? search.originalDraft;
39754
+ const matches = currentHistoryMatches();
39755
+ const preview = matches[search.index] ?? search.originalDraft;
40074
39756
  composer.setDraft(preview);
40075
39757
  }
40076
39758
  async function openExternalEditor() {
@@ -42575,21 +42257,35 @@ function Transcript() {
42575
42257
  },
42576
42258
  get fallback() {
42577
42259
  return (() => {
42578
- var _el$2 = createElement("box"), _el$3 = createElement("text"), _el$5 = createElement("text");
42260
+ var _el$2 = createElement("box"), _el$3 = createElement("box"), _el$4 = createElement("text"), _el$6 = createElement("text");
42579
42261
  insertNode(_el$2, _el$3);
42580
- insertNode(_el$2, _el$5);
42262
+ insertNode(_el$2, _el$4);
42263
+ insertNode(_el$2, _el$6);
42581
42264
  setProp(_el$2, "style", {
42582
42265
  flexDirection: "column",
42583
42266
  marginTop: 1,
42584
42267
  paddingLeft: 1,
42585
42268
  paddingRight: 1
42586
42269
  });
42587
- insertNode(_el$3, createTextNode(`\u203A message farai to get started`));
42588
- insertNode(_el$5, createTextNode(` / opens commands \xB7 ? shows shortcuts`));
42270
+ setProp(_el$3, "style", {
42271
+ flexDirection: "column",
42272
+ marginBottom: 1
42273
+ });
42274
+ insert(_el$3, createComponent2(For, {
42275
+ each: FARAI_BANNER_LINES,
42276
+ children: (line) => (() => {
42277
+ var _el$8 = createElement("text");
42278
+ insert(_el$8, () => truncateLine2(line, Math.max(1, dims().width - 4)));
42279
+ effect((_$p) => setProp(_el$8, "fg", COLOR.dim, _$p));
42280
+ return _el$8;
42281
+ })()
42282
+ }));
42283
+ insertNode(_el$4, createTextNode(`\u203A message farai to get started`));
42284
+ insertNode(_el$6, createTextNode(` / opens commands \xB7 ? shows shortcuts`));
42589
42285
  effect((_p$) => {
42590
42286
  var _v$ = COLOR.dim, _v$2 = COLOR.dim;
42591
- _v$ !== _p$.e && (_p$.e = setProp(_el$3, "fg", _v$, _p$.e));
42592
- _v$2 !== _p$.t && (_p$.t = setProp(_el$5, "fg", _v$2, _p$.t));
42287
+ _v$ !== _p$.e && (_p$.e = setProp(_el$4, "fg", _v$, _p$.e));
42288
+ _v$2 !== _p$.t && (_p$.t = setProp(_el$6, "fg", _v$2, _p$.t));
42593
42289
  return _p$;
42594
42290
  }, {
42595
42291
  e: undefined,
@@ -42621,26 +42317,26 @@ function Transcript() {
42621
42317
  return rawRows();
42622
42318
  },
42623
42319
  children: (row) => (() => {
42624
- var _el$7 = createElement("box");
42625
- setProp(_el$7, "style", {
42320
+ var _el$9 = createElement("box");
42321
+ setProp(_el$9, "style", {
42626
42322
  flexDirection: "column",
42627
42323
  marginBottom: 1,
42628
42324
  paddingLeft: 1,
42629
42325
  paddingRight: 1
42630
42326
  });
42631
- insert(_el$7, createComponent2(For, {
42327
+ insert(_el$9, createComponent2(For, {
42632
42328
  get each() {
42633
42329
  return row.split(`
42634
42330
  `);
42635
42331
  },
42636
42332
  children: (line) => (() => {
42637
- var _el$8 = createElement("text");
42638
- insert(_el$8, () => truncateLine2(line, Math.max(1, dims().width - 4)));
42639
- effect((_$p) => setProp(_el$8, "fg", COLOR.dim, _$p));
42640
- return _el$8;
42333
+ var _el$0 = createElement("text");
42334
+ insert(_el$0, () => truncateLine2(line, Math.max(1, dims().width - 4)));
42335
+ effect((_$p) => setProp(_el$0, "fg", COLOR.dim, _$p));
42336
+ return _el$0;
42641
42337
  })()
42642
42338
  }));
42643
- return _el$7;
42339
+ return _el$9;
42644
42340
  })()
42645
42341
  });
42646
42342
  }
@@ -42723,19 +42419,19 @@ function valuesEqual(left, right, seen) {
42723
42419
  function TranscriptRow(props) {
42724
42420
  const isUser = props.row.kind === "user";
42725
42421
  return (() => {
42726
- var _el$9 = createElement("box");
42727
- setProp(_el$9, "style", {
42422
+ var _el$1 = createElement("box");
42423
+ setProp(_el$1, "style", {
42728
42424
  flexDirection: "column",
42729
42425
  paddingLeft: isUser ? 0 : 1,
42730
42426
  paddingRight: isUser ? 0 : 1
42731
42427
  });
42732
- insert(_el$9, createComponent2(FaraiRow, {
42428
+ insert(_el$1, createComponent2(FaraiRow, {
42733
42429
  get row() {
42734
42430
  return props.row;
42735
42431
  }
42736
42432
  }));
42737
- effect((_$p) => setProp(_el$9, "id", props.row.id, _$p));
42738
- return _el$9;
42433
+ effect((_$p) => setProp(_el$1, "id", props.row.id, _$p));
42434
+ return _el$1;
42739
42435
  })();
42740
42436
  }
42741
42437
  var init_transcript = __esm(() => {
@@ -42754,6 +42450,7 @@ var init_transcript = __esm(() => {
42754
42450
  init_compaction();
42755
42451
  init_theme();
42756
42452
  init_cells();
42453
+ init_branding();
42757
42454
  });
42758
42455
 
42759
42456
  // src/agent-tui/surfaces/center-surface.tsx
@@ -44685,8 +44382,15 @@ function instructionalFooterLines(state) {
44685
44382
  function contextualFooter(state) {
44686
44383
  return state.context;
44687
44384
  }
44688
- function footerRightItems(backgroundActivities, subagents, browserContexts, queueSize, statusDetail, contextUsage) {
44385
+ function footerRightItems(backgroundActivities, subagents, browserContexts, queueSize, statusDetail, contextUsage, updateNotice) {
44689
44386
  const items = [];
44387
+ if (updateNotice) {
44388
+ items.push({
44389
+ id: "update",
44390
+ kind: "update",
44391
+ text: `update ${updateNotice.latestVersion}`
44392
+ });
44393
+ }
44690
44394
  if (contextUsage && contextUsage.tokens >= 0) {
44691
44395
  items.push({
44692
44396
  id: "context",
@@ -44880,10 +44584,12 @@ function Footer(props) {
44880
44584
  budget
44881
44585
  };
44882
44586
  };
44883
- const rightItems = createMemo(() => footerRightItems(tui.store.snapshot.backgroundActivities, tui.store.snapshot.subagents, tui.store.snapshot.browserContexts, tui.store.snapshot.queuedPrompts.length, tui.store.ui.statusDetail, contextUsage()));
44587
+ const rightItems = createMemo(() => footerRightItems(tui.store.snapshot.backgroundActivities, tui.store.snapshot.subagents, tui.store.snapshot.browserContexts, tui.store.snapshot.queuedPrompts.length, tui.store.ui.statusDetail, contextUsage(), tui.store.ui.updateNotice));
44884
44588
  const firstLine = () => fitFooterLine(left(), rightItems(), Math.max(0, dims().width - 4));
44589
+ const updateText = () => tui.store.ui.updateNotice ? `update ${tui.store.ui.updateNotice.latestVersion}` : undefined;
44590
+ const rightLine = createMemo(() => splitUpdateWarning(firstLine().right, updateText()));
44885
44591
  return (() => {
44886
- var _el$ = createElement("box"), _el$2 = createElement("box"), _el$3 = createElement("text"), _el$4 = createElement("text");
44592
+ var _el$ = createElement("box"), _el$2 = createElement("box"), _el$3 = createElement("text"), _el$4 = createElement("text"), _el$5 = createElement("span"), _el$6 = createElement("span");
44887
44593
  insertNode(_el$, _el$2);
44888
44594
  setProp(_el$, "style", {
44889
44595
  flexShrink: 0,
@@ -44897,33 +44603,64 @@ function Footer(props) {
44897
44603
  justifyContent: "space-between"
44898
44604
  });
44899
44605
  insert(_el$3, () => firstLine().left);
44900
- insert(_el$4, () => firstLine().right);
44606
+ insertNode(_el$4, _el$5);
44607
+ insertNode(_el$4, _el$6);
44608
+ insert(_el$5, () => rightLine().warning);
44609
+ insert(_el$6, () => rightLine().rest);
44901
44610
  insert(_el$, createComponent2(ShowShortcutLines, {
44902
44611
  get lines() {
44903
44612
  return lines().slice(1).map((line) => line.toLowerCase());
44904
44613
  }
44905
44614
  }), null);
44906
44615
  effect((_p$) => {
44907
- var _v$ = historySearch() ? COLOR.accent : COLOR.dim, _v$2 = COLOR.dim;
44616
+ var _v$ = historySearch() ? COLOR.accent : COLOR.dim, _v$2 = {
44617
+ fg: COLOR.warning
44618
+ }, _v$3 = {
44619
+ fg: COLOR.dim
44620
+ };
44908
44621
  _v$ !== _p$.e && (_p$.e = setProp(_el$3, "fg", _v$, _p$.e));
44909
- _v$2 !== _p$.t && (_p$.t = setProp(_el$4, "fg", _v$2, _p$.t));
44622
+ _v$2 !== _p$.t && (_p$.t = setProp(_el$5, "style", _v$2, _p$.t));
44623
+ _v$3 !== _p$.a && (_p$.a = setProp(_el$6, "style", _v$3, _p$.a));
44910
44624
  return _p$;
44911
44625
  }, {
44912
44626
  e: undefined,
44913
- t: undefined
44627
+ t: undefined,
44628
+ a: undefined
44914
44629
  });
44915
44630
  return _el$;
44916
44631
  })();
44917
44632
  }
44633
+ function splitUpdateWarning(line, updateText) {
44634
+ if (!line || !updateText)
44635
+ return {
44636
+ warning: "",
44637
+ rest: line
44638
+ };
44639
+ if (line.startsWith(updateText))
44640
+ return {
44641
+ warning: updateText,
44642
+ rest: line.slice(updateText.length)
44643
+ };
44644
+ const visible = line.endsWith("\u2026") ? line.slice(0, -1) : line;
44645
+ if (visible && updateText.startsWith(visible))
44646
+ return {
44647
+ warning: line,
44648
+ rest: ""
44649
+ };
44650
+ return {
44651
+ warning: "",
44652
+ rest: line
44653
+ };
44654
+ }
44918
44655
  function displayModel(sessionModel, workspace) {
44919
44656
  return displayModelSelection(workspace, sessionModel);
44920
44657
  }
44921
44658
  function ShowShortcutLines(props) {
44922
44659
  return memo2(() => props.lines.map((line) => (() => {
44923
- var _el$5 = createElement("text");
44924
- insert(_el$5, line);
44925
- effect((_$p) => setProp(_el$5, "fg", COLOR.dim, _$p));
44926
- return _el$5;
44660
+ var _el$7 = createElement("text");
44661
+ insert(_el$7, line);
44662
+ effect((_$p) => setProp(_el$7, "fg", COLOR.dim, _$p));
44663
+ return _el$7;
44927
44664
  })()));
44928
44665
  }
44929
44666
  var init_footer = __esm(() => {
@@ -45081,15 +44818,15 @@ var init_status_indicator = __esm(() => {
45081
44818
  });
45082
44819
 
45083
44820
  // src/agent-tui/dialog/list-selection.ts
45084
- function selectableIndex(matches2, requested) {
45085
- const enabled = matches2.filter((match) => !match.option.disabled);
44821
+ function selectableIndex(matches, requested) {
44822
+ const enabled = matches.filter((match) => !match.option.disabled);
45086
44823
  if (enabled.length === 0)
45087
44824
  return -1;
45088
44825
  return Math.max(0, Math.min(requested, enabled.length - 1));
45089
44826
  }
45090
- function selectedOptionId(matches2, requested) {
45091
- const index = selectableIndex(matches2, requested);
45092
- return index < 0 ? undefined : matches2.filter((match) => !match.option.disabled)[index]?.option.id;
44827
+ function selectedOptionId(matches, requested) {
44828
+ const index = selectableIndex(matches, requested);
44829
+ return index < 0 ? undefined : matches.filter((match) => !match.option.disabled)[index]?.option.id;
45093
44830
  }
45094
44831
  function scrollWindowStart(total, cap, selectedIndex) {
45095
44832
  if (total <= cap || selectedIndex < 0)
@@ -45098,8 +44835,8 @@ function scrollWindowStart(total, cap, selectedIndex) {
45098
44835
  return 0;
45099
44836
  return Math.min(selectedIndex - cap + 1, total - cap);
45100
44837
  }
45101
- function displayRows(matches2, selectedId) {
45102
- return matches2.map((match) => ({
44838
+ function displayRows(matches, selectedId) {
44839
+ return matches.map((match) => ({
45103
44840
  option: match.option,
45104
44841
  matched: match.score > 0,
45105
44842
  disabled: Boolean(match.option.disabled),
@@ -45190,9 +44927,9 @@ var init_selection_row = __esm(() => {
45190
44927
  function ListOverlay(props) {
45191
44928
  const tui = useTuiStore();
45192
44929
  const dims = useTerminalDimensions();
45193
- const matches2 = createMemo(() => filterOptions(props.options, props.frame.query));
45194
- const groups = createMemo(() => groupByCategory(matches2(), props.frame.query.trim() !== ""));
45195
- const selectedId = createMemo(() => selectedOptionId(matches2(), props.frame.index));
44930
+ const matches = createMemo(() => filterOptions(props.options, props.frame.query));
44931
+ const groups = createMemo(() => groupByCategory(matches(), props.frame.query.trim() !== ""));
44932
+ const selectedId = createMemo(() => selectedOptionId(matches(), props.frame.index));
45196
44933
  const rows = createMemo(() => selectionRows(groups(), selectedId()));
45197
44934
  const width = () => Math.max(30, dims().width);
45198
44935
  const maxRows = () => overlayMaxRows(props.frame.kind, dims().height);
@@ -45209,7 +44946,7 @@ function ListOverlay(props) {
45209
44946
  const descCol = () => descriptionColumn(visibleRows(), width());
45210
44947
  const subtitle = () => overlaySubtitle(props.frame.kind);
45211
44948
  const selectOption = (id2) => {
45212
- const enabled = matches2().filter((match) => !match.option.disabled);
44949
+ const enabled = matches().filter((match) => !match.option.disabled);
45213
44950
  const index = enabled.findIndex((match) => match.option.id === id2);
45214
44951
  if (index >= 0)
45215
44952
  tui.actions.overlaySetIndex(index, enabled.length);
@@ -45258,7 +44995,7 @@ function ListOverlay(props) {
45258
44995
  return dims().height;
45259
44996
  },
45260
44997
  get matches() {
45261
- return matches2();
44998
+ return matches();
45262
44999
  },
45263
45000
  get selectedId() {
45264
45001
  return selectedId();
@@ -45295,14 +45032,14 @@ function ListOverlay(props) {
45295
45032
  })());
45296
45033
  insert(_el$6, (() => {
45297
45034
  var _c$2 = memo2(() => !!props.frame.query);
45298
- return () => _c$2() ? `${matches2().length}/${props.options.length}` : memo2(() => matches2().length > 0)() ? `${Math.min(props.frame.index + 1, matches2().length)}/${matches2().length}` : "";
45035
+ return () => _c$2() ? `${matches().length}/${props.options.length}` : memo2(() => matches().length > 0)() ? `${Math.min(props.frame.index + 1, matches().length)}/${matches().length}` : "";
45299
45036
  })());
45300
45037
  setProp(_el$7, "style", {
45301
45038
  flexDirection: "column"
45302
45039
  });
45303
45040
  insert(_el$7, createComponent2(Show, {
45304
45041
  get when() {
45305
- return matches2().length > 0;
45042
+ return matches().length > 0;
45306
45043
  },
45307
45044
  get fallback() {
45308
45045
  return (() => {
@@ -45346,7 +45083,7 @@ function ListOverlay(props) {
45346
45083
  }));
45347
45084
  insert(_el$, createComponent2(SelectionMenuHint, {
45348
45085
  get text() {
45349
- return overlayHint(props.frame, matches2(), tui.store.ui.modelProviders);
45086
+ return overlayHint(props.frame, matches(), tui.store.ui.modelProviders);
45350
45087
  }
45351
45088
  }), null);
45352
45089
  effect((_p$) => {
@@ -45915,10 +45652,10 @@ function overlaySubtitle(kind) {
45915
45652
  return "inspect durable memory";
45916
45653
  return "";
45917
45654
  }
45918
- function overlayHint(frame, matches2, providers) {
45655
+ function overlayHint(frame, matches, providers) {
45919
45656
  if (frame.kind !== "model")
45920
45657
  return "press enter to confirm or esc to go back";
45921
- const enabled = matches2.filter((match) => !match.option.disabled);
45658
+ const enabled = matches.filter((match) => !match.option.disabled);
45922
45659
  const selected = enabled[frame.index]?.option.value;
45923
45660
  if (selected?.kind === "model_action")
45924
45661
  return "enter add provider \xB7 ctrl+a add \xB7 esc back";
@@ -46226,20 +45963,20 @@ function requestStatusDetail(width, progress, countdown) {
46226
45963
  function requestUserInputHint(width, textMode, hasChoices) {
46227
45964
  if (textMode) {
46228
45965
  if (width >= 76)
46229
- return `enter continue \xB7 ${hasChoices ? "tab choices \xB7 " : ""}ctrl+p/n questions \xB7 esc ${hasChoices ? "choices" : "cancel"}`;
45966
+ return `enter continue \xB7 ${hasChoices ? "tab choices \xB7 " : ""}ctrl+p/n questions \xB7 esc ${hasChoices ? "choices" : "chat"} \xB7 ctrl+x cancel`;
46230
45967
  if (width >= 52)
46231
- return `enter \xB7 ctrl+p/n questions \xB7 esc ${hasChoices ? "choices" : "cancel"}`;
46232
- return `enter \xB7 esc ${hasChoices ? "choices" : "cancel"}`;
45968
+ return `enter \xB7 ctrl+p/n questions \xB7 esc ${hasChoices ? "choices" : "chat"}`;
45969
+ return `enter \xB7 esc ${hasChoices ? "choices" : "chat"}`;
46233
45970
  }
46234
45971
  if (width >= 86)
46235
- return "\u2191\u2193 select \xB7 1-9 choose \xB7 \u2190\u2192/ctrl+p/n questions \xB7 tab other \xB7 esc cancel";
45972
+ 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";
46236
45973
  if (width >= 58)
46237
- return "\u2191\u2193 select \xB7 enter \xB7 \u2190\u2192 questions \xB7 tab other \xB7 esc cancel";
45974
+ return "\u2191\u2193 select \xB7 enter \xB7 \u2190\u2192 questions \xB7 tab other \xB7 esc chat";
46238
45975
  if (width >= 42)
46239
- return "\u2191\u2193 select \xB7 enter \xB7 tab other \xB7 esc cancel";
45976
+ return "\u2191\u2193 select \xB7 enter \xB7 tab other \xB7 esc chat";
46240
45977
  if (width >= 34)
46241
- return "\u2191\u2193 select \xB7 enter \xB7 esc cancel";
46242
- return "\u2191\u2193 \xB7 enter \xB7 esc";
45978
+ return "\u2191\u2193 select \xB7 enter \xB7 esc chat";
45979
+ return "\u2191\u2193 \xB7 enter \xB7 esc chat";
46243
45980
  }
46244
45981
  function requestOptionRows(question) {
46245
45982
  if (!question?.choices?.length)
@@ -46815,7 +46552,8 @@ function BottomPane() {
46815
46552
  const proxyTabActive = () => slot() === "proxy_tab";
46816
46553
  const providerWizardActive = () => Boolean(tui.store.ui.modelProviderWizard);
46817
46554
  const providerRemovalActive = () => Boolean(tui.store.ui.modelProviderRemoval);
46818
- const inputRequestActive = () => Boolean(tui.store.snapshot.pendingUserInput);
46555
+ const inputRequestActive = () => Boolean(tui.store.snapshot.pendingUserInput && !tui.store.ui.requestUserInput?.dismissed);
46556
+ const inputRequestPending = () => Boolean(tui.store.snapshot.pendingUserInput);
46819
46557
  const footerHidden = () => Boolean(frame()) || Boolean(centerFrame()) || slashPanelActive() || proxyTabActive() || inputRequestActive() || providerWizardActive() || providerRemovalActive();
46820
46558
  const inlineStatusDetail = () => {
46821
46559
  const detail = tui.store.ui.statusDetail;
@@ -46875,10 +46613,10 @@ function BottomPane() {
46875
46613
  return tui.store.ui.lastError;
46876
46614
  },
46877
46615
  children: (error) => (() => {
46878
- var _el$2 = createElement("text");
46879
- insert(_el$2, () => `\u2022 error \xB7 ${truncateLine2(error(), 160)}`);
46880
- effect((_$p) => setProp(_el$2, "fg", COLOR.error, _$p));
46881
- return _el$2;
46616
+ var _el$4 = createElement("text");
46617
+ insert(_el$4, () => `\u2022 error \xB7 ${truncateLine2(error(), 160)}`);
46618
+ effect((_$p) => setProp(_el$4, "fg", COLOR.error, _$p));
46619
+ return _el$4;
46882
46620
  })()
46883
46621
  }), null);
46884
46622
  insert(_el$, createComponent2(Show, {
@@ -46889,6 +46627,17 @@ function BottomPane() {
46889
46627
  return createComponent2(PendingInputPreview, {});
46890
46628
  }
46891
46629
  }), null);
46630
+ insert(_el$, createComponent2(Show, {
46631
+ get when() {
46632
+ return memo2(() => !!(!inputRequestActive() && inputRequestPending() && !providerWizardActive()))() && !providerRemovalActive();
46633
+ },
46634
+ get children() {
46635
+ var _el$2 = createElement("text");
46636
+ insertNode(_el$2, createTextNode(` question pending \xB7 ctrl+q answer \xB7 chat input remains available`));
46637
+ effect((_$p) => setProp(_el$2, "fg", COLOR.warning, _$p));
46638
+ return _el$2;
46639
+ }
46640
+ }), null);
46892
46641
  insert(_el$, createComponent2(Show, {
46893
46642
  get when() {
46894
46643
  return tui.store.ui.modelProviderRemoval;
@@ -46901,7 +46650,7 @@ function BottomPane() {
46901
46650
  get fallback() {
46902
46651
  return createComponent2(Show, {
46903
46652
  get when() {
46904
- return tui.store.snapshot.pendingUserInput;
46653
+ return memo2(() => !!inputRequestActive())() ? tui.store.snapshot.pendingUserInput : undefined;
46905
46654
  },
46906
46655
  get fallback() {
46907
46656
  return createComponent2(Show, {
@@ -46978,54 +46727,54 @@ function BottomPane() {
46978
46727
  function ProxyTabFooter() {
46979
46728
  const tui = useTuiStore();
46980
46729
  return (() => {
46981
- var _el$3 = createElement("box"), _el$4 = createElement("text"), _el$6 = createElement("text");
46982
- insertNode(_el$3, _el$4);
46983
- insertNode(_el$3, _el$6);
46984
- setProp(_el$3, "style", {
46730
+ var _el$5 = createElement("box"), _el$6 = createElement("text"), _el$8 = createElement("text");
46731
+ insertNode(_el$5, _el$6);
46732
+ insertNode(_el$5, _el$8);
46733
+ setProp(_el$5, "style", {
46985
46734
  height: 1,
46986
46735
  flexDirection: "row",
46987
46736
  justifyContent: "space-between",
46988
46737
  paddingLeft: 1,
46989
46738
  paddingRight: 1
46990
46739
  });
46991
- 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`));
46992
- insert(_el$6, () => `proxy \xB7 ${tui.store.ui.proxyFilter}`);
46740
+ 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`));
46741
+ insert(_el$8, () => `proxy \xB7 ${tui.store.ui.proxyFilter}`);
46993
46742
  effect((_p$) => {
46994
46743
  var _v$ = COLOR.dim, _v$2 = COLOR.dim;
46995
- _v$ !== _p$.e && (_p$.e = setProp(_el$4, "fg", _v$, _p$.e));
46996
- _v$2 !== _p$.t && (_p$.t = setProp(_el$6, "fg", _v$2, _p$.t));
46744
+ _v$ !== _p$.e && (_p$.e = setProp(_el$6, "fg", _v$, _p$.e));
46745
+ _v$2 !== _p$.t && (_p$.t = setProp(_el$8, "fg", _v$2, _p$.t));
46997
46746
  return _p$;
46998
46747
  }, {
46999
46748
  e: undefined,
47000
46749
  t: undefined
47001
46750
  });
47002
- return _el$3;
46751
+ return _el$5;
47003
46752
  })();
47004
46753
  }
47005
46754
  function CenterSurfaceFooter(props) {
47006
46755
  return (() => {
47007
- var _el$7 = createElement("box"), _el$8 = createElement("text"), _el$9 = createElement("text");
47008
- insertNode(_el$7, _el$8);
47009
- insertNode(_el$7, _el$9);
47010
- setProp(_el$7, "style", {
46756
+ var _el$9 = createElement("box"), _el$0 = createElement("text"), _el$1 = createElement("text");
46757
+ insertNode(_el$9, _el$0);
46758
+ insertNode(_el$9, _el$1);
46759
+ setProp(_el$9, "style", {
47011
46760
  height: 1,
47012
46761
  flexDirection: "row",
47013
46762
  justifyContent: "space-between",
47014
46763
  paddingLeft: 1,
47015
46764
  paddingRight: 1
47016
46765
  });
47017
- insert(_el$8, () => centerSurfaceFooter(props.frame).toLowerCase());
47018
- insert(_el$9, () => props.frame.kind.toLowerCase());
46766
+ insert(_el$0, () => centerSurfaceFooter(props.frame).toLowerCase());
46767
+ insert(_el$1, () => props.frame.kind.toLowerCase());
47019
46768
  effect((_p$) => {
47020
46769
  var _v$3 = COLOR.dim, _v$4 = COLOR.dim;
47021
- _v$3 !== _p$.e && (_p$.e = setProp(_el$8, "fg", _v$3, _p$.e));
47022
- _v$4 !== _p$.t && (_p$.t = setProp(_el$9, "fg", _v$4, _p$.t));
46770
+ _v$3 !== _p$.e && (_p$.e = setProp(_el$0, "fg", _v$3, _p$.e));
46771
+ _v$4 !== _p$.t && (_p$.t = setProp(_el$1, "fg", _v$4, _p$.t));
47023
46772
  return _p$;
47024
46773
  }, {
47025
46774
  e: undefined,
47026
46775
  t: undefined
47027
46776
  });
47028
- return _el$7;
46777
+ return _el$9;
47029
46778
  })();
47030
46779
  }
47031
46780
  var init_bottom_pane = __esm(() => {
@@ -47242,6 +46991,191 @@ var init_app = __esm(() => {
47242
46991
  init_app_shell();
47243
46992
  });
47244
46993
 
46994
+ // src/agent-tui/update-check.ts
46995
+ import { mkdirSync as mkdirSync13, readFileSync as readFileSync19, renameSync as renameSync7, writeFileSync as writeFileSync14 } from "fs";
46996
+ import { dirname as dirname8, join as join26 } from "path";
46997
+ function prepareUpdateCheck(options = {}) {
46998
+ if (updateCheckDisabled())
46999
+ return {
47000
+ cachedNotice: undefined,
47001
+ refresh: undefined
47002
+ };
47003
+ const currentVersion = options.currentVersion ?? readCurrentVersion();
47004
+ if (!currentVersion)
47005
+ return {
47006
+ cachedNotice: undefined,
47007
+ refresh: undefined
47008
+ };
47009
+ const now = options.now ?? Date.now();
47010
+ const cachePath = options.cachePath ?? updateCachePath();
47011
+ const cache = readUpdateCache(cachePath);
47012
+ const cachedNotice = cache ? createUpdateNotice(currentVersion, cache.latestVersion) : undefined;
47013
+ if (cache && isFreshCache(cache, now))
47014
+ return {
47015
+ cachedNotice,
47016
+ refresh: undefined
47017
+ };
47018
+ return {
47019
+ cachedNotice,
47020
+ refresh: refreshUpdateNotice({
47021
+ cachePath,
47022
+ currentVersion,
47023
+ fetcher: options.fetcher ?? fetch,
47024
+ now,
47025
+ timeoutMs: options.timeoutMs ?? UPDATE_CHECK_TIMEOUT_MS,
47026
+ fallback: cachedNotice
47027
+ })
47028
+ };
47029
+ }
47030
+ function createUpdateNotice(currentVersion, latestVersion) {
47031
+ if (compareSemver(latestVersion, currentVersion) <= 0)
47032
+ return;
47033
+ return {
47034
+ currentVersion,
47035
+ latestVersion,
47036
+ updateCommand: "npm install -g farai@latest"
47037
+ };
47038
+ }
47039
+ function compareSemver(left, right) {
47040
+ const a = parseSemver(left);
47041
+ const b = parseSemver(right);
47042
+ if (!a || !b)
47043
+ return 0;
47044
+ for (let index = 0;index < 3; index += 1) {
47045
+ const delta = a.core[index] - b.core[index];
47046
+ if (delta !== 0)
47047
+ return delta < 0 ? -1 : 1;
47048
+ }
47049
+ if (a.prerelease.length === 0 || b.prerelease.length === 0) {
47050
+ if (a.prerelease.length === b.prerelease.length)
47051
+ return 0;
47052
+ return a.prerelease.length === 0 ? 1 : -1;
47053
+ }
47054
+ const length = Math.max(a.prerelease.length, b.prerelease.length);
47055
+ for (let index = 0;index < length; index += 1) {
47056
+ const aPart = a.prerelease[index];
47057
+ const bPart = b.prerelease[index];
47058
+ if (aPart === undefined || bPart === undefined)
47059
+ return aPart === undefined ? -1 : 1;
47060
+ if (aPart === bPart)
47061
+ continue;
47062
+ const aNumber = numericIdentifier(aPart);
47063
+ const bNumber = numericIdentifier(bPart);
47064
+ if (aNumber !== undefined && bNumber !== undefined)
47065
+ return aNumber < bNumber ? -1 : 1;
47066
+ if (aNumber !== undefined || bNumber !== undefined)
47067
+ return aNumber !== undefined ? -1 : 1;
47068
+ return aPart < bPart ? -1 : 1;
47069
+ }
47070
+ return 0;
47071
+ }
47072
+ function readUpdateCache(path = updateCachePath()) {
47073
+ try {
47074
+ const parsed = JSON.parse(readFileSync19(path, "utf8"));
47075
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
47076
+ return;
47077
+ const value = parsed;
47078
+ if (typeof value.checkedAt !== "number" || !Number.isFinite(value.checkedAt))
47079
+ return;
47080
+ if (typeof value.latestVersion !== "string" || !parseSemver(value.latestVersion))
47081
+ return;
47082
+ return {
47083
+ checkedAt: value.checkedAt,
47084
+ latestVersion: value.latestVersion
47085
+ };
47086
+ } catch {
47087
+ return;
47088
+ }
47089
+ }
47090
+ function updateCachePath() {
47091
+ return join26(globalDataDir(), "update.json");
47092
+ }
47093
+ function readCurrentVersion() {
47094
+ try {
47095
+ const packagePath = join26(import.meta.dir, "..", "..", "package.json");
47096
+ const parsed = JSON.parse(readFileSync19(packagePath, "utf8"));
47097
+ return typeof parsed.version === "string" && parseSemver(parsed.version) ? parsed.version : undefined;
47098
+ } catch {
47099
+ return;
47100
+ }
47101
+ }
47102
+ async function refreshUpdateNotice(input) {
47103
+ try {
47104
+ const latestVersion = await fetchLatestVersion(input.fetcher, input.timeoutMs);
47105
+ writeUpdateCache(input.cachePath, {
47106
+ checkedAt: input.now,
47107
+ latestVersion
47108
+ });
47109
+ return createUpdateNotice(input.currentVersion, latestVersion);
47110
+ } catch {
47111
+ return input.fallback;
47112
+ }
47113
+ }
47114
+ async function fetchLatestVersion(fetcher, timeoutMs) {
47115
+ const controller = new AbortController;
47116
+ const timer = setTimeout(() => controller.abort(), Math.max(1, timeoutMs));
47117
+ timer.unref?.();
47118
+ try {
47119
+ const response = await fetcher(UPDATE_REGISTRY_URL, {
47120
+ headers: {
47121
+ accept: "application/json"
47122
+ },
47123
+ signal: controller.signal
47124
+ });
47125
+ if (!response.ok)
47126
+ throw new Error(`npm registry returned ${response.status}`);
47127
+ const parsed = await response.json();
47128
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
47129
+ throw new Error("invalid npm registry response");
47130
+ const version = parsed.version;
47131
+ if (typeof version !== "string" || !parseSemver(version))
47132
+ throw new Error("invalid npm package version");
47133
+ return version;
47134
+ } finally {
47135
+ clearTimeout(timer);
47136
+ }
47137
+ }
47138
+ function writeUpdateCache(path, cache) {
47139
+ try {
47140
+ mkdirSync13(dirname8(path), {
47141
+ recursive: true
47142
+ });
47143
+ const temporary = `${path}.${process.pid}.${Date.now()}.tmp`;
47144
+ writeFileSync14(temporary, `${JSON.stringify(cache)}
47145
+ `, "utf8");
47146
+ renameSync7(temporary, path);
47147
+ } catch {}
47148
+ }
47149
+ function isFreshCache(cache, now) {
47150
+ const age = now - cache.checkedAt;
47151
+ return age >= 0 && age < UPDATE_CACHE_TTL_MS;
47152
+ }
47153
+ function updateCheckDisabled() {
47154
+ return envEnabled(process.env.FARAI_DISABLE_UPDATE_CHECK) || envEnabled(process.env.NO_UPDATE_NOTIFIER);
47155
+ }
47156
+ function envEnabled(value) {
47157
+ return value === "1" || value?.toLowerCase() === "true" || value?.toLowerCase() === "yes";
47158
+ }
47159
+ function parseSemver(value) {
47160
+ const match = value.trim().match(/^(?:v)?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/);
47161
+ if (!match)
47162
+ return;
47163
+ return {
47164
+ core: [Number(match[1]), Number(match[2]), Number(match[3])],
47165
+ prerelease: match[4]?.split(".") ?? []
47166
+ };
47167
+ }
47168
+ function numericIdentifier(value) {
47169
+ if (!/^(0|[1-9]\d*)$/.test(value))
47170
+ return;
47171
+ return Number(value);
47172
+ }
47173
+ var UPDATE_CACHE_TTL_MS, UPDATE_CHECK_TIMEOUT_MS = 4000, UPDATE_REGISTRY_URL = "https://registry.npmjs.org/farai/latest";
47174
+ var init_update_check = __esm(() => {
47175
+ init_config();
47176
+ UPDATE_CACHE_TTL_MS = 20 * 60 * 60 * 1000;
47177
+ });
47178
+
47245
47179
  // src/agent-tui/index.tsx
47246
47180
  var exports_agent_tui = {};
47247
47181
  __export(exports_agent_tui, {
@@ -47265,6 +47199,7 @@ async function runOpenTui(input) {
47265
47199
  let handleRendererDestroy;
47266
47200
  const managedRenderer = await createManagedRenderer(() => handleRendererDestroy?.());
47267
47201
  const renderer = managedRenderer.renderer;
47202
+ const updateCheck = prepareUpdateCheck();
47268
47203
  let done;
47269
47204
  const finished = new Promise((resolve5) => {
47270
47205
  done = resolve5;
@@ -47326,6 +47261,7 @@ async function runOpenTui(input) {
47326
47261
  get children() {
47327
47262
  return createComponent2(TuiStoreProvider, {
47328
47263
  initialSessionId,
47264
+ updateCheck,
47329
47265
  onActiveSessionChange: (sessionId, title) => {
47330
47266
  activeSessionId = sessionId;
47331
47267
  renderer.setTerminalTitle(`farai \xB7 ${title?.trim() || DEFAULT_SESSION_TITLE}`);
@@ -47397,9 +47333,9 @@ async function ensureSession(input) {
47397
47333
  } catch {
47398
47334
  const needle = input.sessionId.trim().toLowerCase();
47399
47335
  const sessions2 = await input.runtime.listSessions();
47400
- const matches2 = sessions2.filter((session) => session.id.toLowerCase().startsWith(needle) || session.title?.trim().toLowerCase() === needle);
47401
- if (matches2.length === 1)
47402
- return matches2[0].id;
47336
+ const matches = sessions2.filter((session) => session.id.toLowerCase().startsWith(needle) || session.title?.trim().toLowerCase() === needle);
47337
+ if (matches.length === 1)
47338
+ return matches[0].id;
47403
47339
  throw new SessionResolutionError(input.sessionId, input.workspace, sessions2);
47404
47340
  }
47405
47341
  }
@@ -47450,6 +47386,7 @@ var init_agent_tui = __esm(() => {
47450
47386
  init_runtime_port();
47451
47387
  init_session_title();
47452
47388
  init_session_catalog();
47389
+ init_update_check();
47453
47390
  SessionResolutionError = class SessionResolutionError extends Error {
47454
47391
  constructor(query, workspace, sessions2) {
47455
47392
  const recent = sessions2.slice(0, 5).map((session) => ` ${session.id} ${session.title?.trim() || DEFAULT_SESSION_TITLE}`).join(`
@@ -47726,8 +47663,8 @@ var init_csi_cybench_33 = __esm(() => {
47726
47663
 
47727
47664
  // src/agent-benchmark/hash.ts
47728
47665
  import { createHash as createHash7 } from "crypto";
47729
- import { readFileSync as readFileSync19, readdirSync as readdirSync9, statSync as statSync7 } from "fs";
47730
- import { join as join26, relative as relative9 } from "path";
47666
+ import { readFileSync as readFileSync20, readdirSync as readdirSync9, statSync as statSync7 } from "fs";
47667
+ import { join as join27, relative as relative9 } from "path";
47731
47668
  function stableStringify(value) {
47732
47669
  return JSON.stringify(sortValue(value));
47733
47670
  }
@@ -47737,10 +47674,10 @@ function sha256(value) {
47737
47674
  function hashPath(path) {
47738
47675
  const stat = statSync7(path);
47739
47676
  if (stat.isFile())
47740
- return sha256(readFileSync19(path));
47677
+ return sha256(readFileSync20(path));
47741
47678
  if (!stat.isDirectory())
47742
47679
  throw new Error(`unsupported benchmark input type: ${path}`);
47743
- const entries = walk(path).map((entry) => `${relative9(path, entry).replace(/\\/g, "/")}\x00${sha256(readFileSync19(entry))}`);
47680
+ const entries = walk(path).map((entry) => `${relative9(path, entry).replace(/\\/g, "/")}\x00${sha256(readFileSync20(entry))}`);
47744
47681
  return sha256(entries.join(`
47745
47682
  `));
47746
47683
  }
@@ -47798,7 +47735,7 @@ function sortValue(value) {
47798
47735
  function walk(root) {
47799
47736
  const out = [];
47800
47737
  for (const name of readdirSync9(root).sort()) {
47801
- const path = join26(root, name);
47738
+ const path = join27(root, name);
47802
47739
  const stat = statSync7(path);
47803
47740
  if (stat.isDirectory())
47804
47741
  out.push(...walk(path));
@@ -48144,8 +48081,8 @@ __export(exports_csi_suite, {
48144
48081
  loadCsiCampaignConfig: () => loadCsiCampaignConfig,
48145
48082
  generateCsiBenchmarkSuite: () => generateCsiBenchmarkSuite
48146
48083
  });
48147
- import { existsSync as existsSync20, readFileSync as readFileSync20, readdirSync as readdirSync10, statSync as statSync8, writeFileSync as writeFileSync14 } from "fs";
48148
- import { dirname as dirname8, isAbsolute as isAbsolute6, join as join27, relative as relative10, resolve as resolve5 } from "path";
48084
+ import { existsSync as existsSync20, readFileSync as readFileSync21, readdirSync as readdirSync10, statSync as statSync8, writeFileSync as writeFileSync15 } from "fs";
48085
+ import { dirname as dirname9, isAbsolute as isAbsolute6, join as join28, relative as relative10, resolve as resolve5 } from "path";
48149
48086
  async function loadCsiCampaignConfig(path) {
48150
48087
  return normalizeCsiCampaignConfig(JSON.parse(await Bun.file(path).text()));
48151
48088
  }
@@ -48172,7 +48109,7 @@ async function generateCsiBenchmarkSuite(configInput, materialRoot) {
48172
48109
  const promptPath = protectedPath(root, material.promptFile, `${challenge.id}.promptFile`);
48173
48110
  if (!existsSync20(promptPath) || !statSync8(promptPath).isFile())
48174
48111
  throw new Error(`missing prompt file for csi challenge: ${challenge.id}`);
48175
- const prompt = readFileSync20(promptPath, "utf8").trim();
48112
+ const prompt = readFileSync21(promptPath, "utf8").trim();
48176
48113
  if (!prompt)
48177
48114
  throw new Error(`empty prompt file for csi challenge: ${challenge.id}`);
48178
48115
  const files = material.files?.map((file, index) => {
@@ -48279,10 +48216,10 @@ async function generateCsiBenchmarkSuite(configInput, materialRoot) {
48279
48216
  });
48280
48217
  }
48281
48218
  function writeCsiBenchmarkSuite(suite, path) {
48282
- const directory = dirname8(resolve5(path));
48219
+ const directory = dirname9(resolve5(path));
48283
48220
  if (!existsSync20(directory))
48284
48221
  throw new Error(`suite output directory does not exist: ${directory}`);
48285
- writeFileSync14(path, `${JSON.stringify(suite, null, 2)}
48222
+ writeFileSync15(path, `${JSON.stringify(suite, null, 2)}
48286
48223
  `);
48287
48224
  }
48288
48225
  function normalizeCsiCampaignConfig(value) {
@@ -48417,7 +48354,7 @@ function protectedPath(root, path, name) {
48417
48354
  function listFiles(rootPath) {
48418
48355
  if (!statSync8(rootPath).isDirectory())
48419
48356
  return [rootPath];
48420
- return readdirSync10(rootPath).flatMap((name) => listFiles(join27(rootPath, name)));
48357
+ return readdirSync10(rootPath).flatMap((name) => listFiles(join28(rootPath, name)));
48421
48358
  }
48422
48359
  function object2(value, name) {
48423
48360
  if (!value || typeof value !== "object" || Array.isArray(value))
@@ -48475,30 +48412,30 @@ var init_csi_suite = __esm(() => {
48475
48412
 
48476
48413
  // src/agent-benchmark/bundle.ts
48477
48414
  import { createHash as createHash8 } from "crypto";
48478
- import { chmodSync as chmodSync3, mkdirSync as mkdirSync13, readFileSync as readFileSync21, writeFileSync as writeFileSync15 } from "fs";
48479
- import { join as join28 } from "path";
48415
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync14, readFileSync as readFileSync22, writeFileSync as writeFileSync16 } from "fs";
48416
+ import { join as join29 } from "path";
48480
48417
  function writeBenchmarkBundle(bundle, directory) {
48481
- mkdirSync13(directory, {
48418
+ mkdirSync14(directory, {
48482
48419
  recursive: true
48483
48420
  });
48484
48421
  const files = new Map([["manifest.json", json(redactManifest(bundle.manifest))], ["result.json", json(bundle.result)], ["environment.json", json(bundle.result.frozen)], ["sessions.jsonl", jsonl(bundle.sessions)], ["turns.jsonl", jsonl(bundle.turns)], ["messages.jsonl", jsonl(bundle.messages)], ["events.jsonl", jsonl(bundle.events)], ["tool-calls.jsonl", jsonl(bundle.toolCalls)], ["jobs.jsonl", jsonl(bundle.jobs)], ["usage.jsonl", jsonl(bundle.usage)], ["compactions.jsonl", jsonl(bundle.compactions)], ["evidence.jsonl", jsonl(bundle.evidence)]]);
48485
48422
  for (const [name, content] of files)
48486
- writeFileSync15(join28(directory, name), content);
48487
- const checksums = [...files.keys()].sort().map((name) => `${sha2562(readFileSync21(join28(directory, name)))} ${name}`).join(`
48423
+ writeFileSync16(join29(directory, name), content);
48424
+ const checksums = [...files.keys()].sort().map((name) => `${sha2562(readFileSync22(join29(directory, name)))} ${name}`).join(`
48488
48425
  `);
48489
- writeFileSync15(join28(directory, "checksums.sha256"), `${checksums}
48426
+ writeFileSync16(join29(directory, "checksums.sha256"), `${checksums}
48490
48427
  `);
48491
48428
  for (const name of [...files.keys(), "checksums.sha256"])
48492
- chmodSync3(join28(directory, name), 292);
48429
+ chmodSync3(join29(directory, name), 292);
48493
48430
  return directory;
48494
48431
  }
48495
48432
  function writeBenchmarkResult(result, path) {
48496
48433
  const directory = path.slice(0, Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\")));
48497
48434
  if (directory)
48498
- mkdirSync13(directory, {
48435
+ mkdirSync14(directory, {
48499
48436
  recursive: true
48500
48437
  });
48501
- writeFileSync15(path, json(result));
48438
+ writeFileSync16(path, json(result));
48502
48439
  }
48503
48440
  function redactManifest(manifest) {
48504
48441
  return canonicalBenchmarkManifest(manifest);
@@ -48720,26 +48657,26 @@ __export(exports_runner, {
48720
48657
  normalizeBenchmarkManifest: () => normalizeBenchmarkManifest,
48721
48658
  loadBenchmarkManifest: () => loadBenchmarkManifest
48722
48659
  });
48723
- import { cpSync, existsSync as existsSync22, mkdirSync as mkdirSync14, mkdtempSync as mkdtempSync3, readFileSync as readFileSync22, readdirSync as readdirSync11, statSync as statSync9 } from "fs";
48660
+ import { cpSync, existsSync as existsSync22, mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync3, readFileSync as readFileSync23, readdirSync as readdirSync11, statSync as statSync9 } from "fs";
48724
48661
  import { arch, platform, tmpdir as tmpdir4 } from "os";
48725
- import { dirname as dirname9, join as join29, relative as relative11, resolve as resolve7 } from "path";
48662
+ import { dirname as dirname10, join as join30, relative as relative11, resolve as resolve7 } from "path";
48726
48663
  async function runBenchmark(input, options = {}) {
48727
48664
  const manifest = normalizeBenchmarkManifest(input);
48728
48665
  assertExecutableIsolation(manifest);
48729
- const workspace = options.workspace ?? mkdtempSync3(join29(tmpdir4(), "farai-benchmark-"));
48730
- const artifactsRoot = options.artifactsDir ?? mkdtempSync3(join29(tmpdir4(), "farai-benchmark-artifacts-"));
48666
+ const workspace = options.workspace ?? mkdtempSync3(join30(tmpdir4(), "farai-benchmark-"));
48667
+ const artifactsRoot = options.artifactsDir ?? mkdtempSync3(join30(tmpdir4(), "farai-benchmark-artifacts-"));
48731
48668
  const repetition = options.repetition ?? 1;
48732
- mkdirSync14(workspace, {
48669
+ mkdirSync15(workspace, {
48733
48670
  recursive: true
48734
48671
  });
48735
48672
  assertCleanWorkspace(workspace);
48736
48673
  assertArtifactsOutsideWorkspace(workspace, artifactsRoot);
48737
- mkdirSync14(artifactsRoot, {
48674
+ mkdirSync15(artifactsRoot, {
48738
48675
  recursive: true
48739
48676
  });
48740
48677
  stageFiles(manifest, workspace);
48741
48678
  const runId = id();
48742
- const bundlePath = join29(artifactsRoot, `${safeName2(manifest.challenge.id)}-r${repetition}-${runId}`);
48679
+ const bundlePath = join30(artifactsRoot, `${safeName2(manifest.challenge.id)}-r${repetition}-${runId}`);
48743
48680
  const provider = options.provider ?? await createChatProviderForSession(syntheticSession(workspace, manifest));
48744
48681
  assertProvider(manifest, provider);
48745
48682
  const dockerLifecycle = manifest.isolation.backend === "docker" ? new BenchmarkDockerLifecycle(manifest, workspace, runId, options.dockerProcessRunner) : undefined;
@@ -49026,7 +48963,7 @@ function stageFiles(manifest, workspace) {
49026
48963
  const target = resolve7(workspace, file.destination);
49027
48964
  if (relative11(workspace, target).startsWith(".."))
49028
48965
  throw new Error(`benchmark destination escapes scratch workspace: ${file.destination}`);
49029
- mkdirSync14(dirname9(target), {
48966
+ mkdirSync15(dirname10(target), {
49030
48967
  recursive: true
49031
48968
  });
49032
48969
  cpSync(source, target, {
@@ -49043,7 +48980,7 @@ function listFiles2(rootPath) {
49043
48980
  return [];
49044
48981
  if (!statSync9(rootPath).isDirectory())
49045
48982
  return [rootPath];
49046
- return readdirSync11(rootPath).flatMap((name) => listFiles2(join29(rootPath, name)));
48983
+ return readdirSync11(rootPath).flatMap((name) => listFiles2(join30(rootPath, name)));
49047
48984
  }
49048
48985
 
49049
48986
  class BenchmarkHostBackend {
@@ -49087,7 +49024,7 @@ class BenchmarkHostBackend {
49087
49024
  hostPath(path) {
49088
49025
  if (path === "/workspace")
49089
49026
  return this.workspace;
49090
- return join29(this.workspace, path.slice("/workspace/".length));
49027
+ return join30(this.workspace, path.slice("/workspace/".length));
49091
49028
  }
49092
49029
  }
49093
49030
  function freezeRun(manifest, session, tools, faraiRoot, provider, kaliImageId) {
@@ -49141,7 +49078,7 @@ function freezeRun(manifest, session, tools, faraiRoot, provider, kaliImageId) {
49141
49078
  })),
49142
49079
  kaliImage: kaliImageId ?? DEFAULT_KALI_IMAGE,
49143
49080
  kaliContract: KALI_IMAGE_CONTRACT,
49144
- kaliToolManifestHash: sha256(readFileSync22(KALI_TOOL_MANIFEST_PATH)),
49081
+ kaliToolManifestHash: sha256(readFileSync23(KALI_TOOL_MANIFEST_PATH)),
49145
49082
  ...manifest.challenge.targetImage ? {
49146
49083
  targetImage: manifest.challenge.targetImage
49147
49084
  } : {},
@@ -49355,16 +49292,16 @@ __export(exports_suite, {
49355
49292
  normalizeBenchmarkSuiteManifest: () => normalizeBenchmarkSuiteManifest,
49356
49293
  loadBenchmarkSuiteManifest: () => loadBenchmarkSuiteManifest
49357
49294
  });
49358
- import { mkdirSync as mkdirSync15, mkdtempSync as mkdtempSync4, writeFileSync as writeFileSync16 } from "fs";
49295
+ import { mkdirSync as mkdirSync16, mkdtempSync as mkdtempSync4, writeFileSync as writeFileSync17 } from "fs";
49359
49296
  import { tmpdir as tmpdir5 } from "os";
49360
- import { join as join30 } from "path";
49297
+ import { join as join31 } from "path";
49361
49298
  async function runBenchmarkSuite(input, options = {}) {
49362
49299
  const manifest = normalizeBenchmarkSuiteManifest(input);
49363
49300
  const campaignId = id();
49364
- const root = options.artifactsDir ?? mkdtempSync4(join30(tmpdir5(), "farai-benchmark-campaign-"));
49365
- const bundlePath = join30(root, `${safeName3(manifest.id)}-${campaignId}`);
49366
- const runsPath = join30(bundlePath, "runs");
49367
- mkdirSync15(runsPath, {
49301
+ const root = options.artifactsDir ?? mkdtempSync4(join31(tmpdir5(), "farai-benchmark-campaign-"));
49302
+ const bundlePath = join31(root, `${safeName3(manifest.id)}-${campaignId}`);
49303
+ const runsPath = join31(bundlePath, "runs");
49304
+ mkdirSync16(runsPath, {
49368
49305
  recursive: true
49369
49306
  });
49370
49307
  const attempts = [];
@@ -49457,9 +49394,9 @@ async function runBenchmarkSuite(input, options = {}) {
49457
49394
  error: outcome.error
49458
49395
  })
49459
49396
  };
49460
- writeFileSync16(join30(bundlePath, "campaign.json"), `${JSON.stringify(result, null, 2)}
49397
+ writeFileSync17(join31(bundlePath, "campaign.json"), `${JSON.stringify(result, null, 2)}
49461
49398
  `);
49462
- writeFileSync16(join30(bundlePath, "suite.sha256"), `${result.manifestHash}
49399
+ writeFileSync17(join31(bundlePath, "suite.sha256"), `${result.manifestHash}
49463
49400
  `);
49464
49401
  return result;
49465
49402
  }
@@ -49511,8 +49448,9 @@ init_model_catalog();
49511
49448
  init_model_profiles();
49512
49449
  init_global_config();
49513
49450
  init_config();
49514
- import { readFileSync as readFileSync23 } from "fs";
49515
- import { join as join31 } from "path";
49451
+ init_branding();
49452
+ import { readFileSync as readFileSync24 } from "fs";
49453
+ import { join as join32 } from "path";
49516
49454
  var [, , command, ...args2] = process.argv;
49517
49455
  if (command === "--version" || command === "-v" || command === "version") {
49518
49456
  console.log(packageVersion());
@@ -49626,7 +49564,9 @@ async function setup(args3) {
49626
49564
  const baseUrl = flag(args3, "--base-url") ?? flag(args3, "--baseURL");
49627
49565
  const apiKeyEnv = flag(args3, "--api-key-env");
49628
49566
  const setDefault = args3.includes("--set-default") || args3.includes("--default") || Boolean(model);
49629
- console.log("[*] setting up Farai");
49567
+ console.log(FARAI_BANNER);
49568
+ console.log();
49569
+ console.log("[*] setting up farai");
49630
49570
  console.log(`[+] config: ${globalConfigPath()}`);
49631
49571
  console.log(`[+] auth: ${authPath("global")}`);
49632
49572
  if (model) {
@@ -49926,7 +49866,7 @@ function parseProviderModel(value) {
49926
49866
  }
49927
49867
  function packageVersion() {
49928
49868
  try {
49929
- const raw = readFileSync23(join31(import.meta.dir, "..", "..", "package.json"), "utf8");
49869
+ const raw = readFileSync24(join32(import.meta.dir, "..", "..", "package.json"), "utf8");
49930
49870
  const parsed = JSON.parse(raw);
49931
49871
  return typeof parsed.version === "string" ? parsed.version : "0.0.0";
49932
49872
  } catch {
@@ -50027,5 +49967,5 @@ Examples:
50027
49967
  `);
50028
49968
  }
50029
49969
 
50030
- //# debugId=B5CAF1E621BDD38864756E2164756E21
49970
+ //# debugId=231A9E4A58F7AFCB64756E2164756E21
50031
49971
  //# sourceMappingURL=index.js.map