farai 0.3.3 → 0.3.4

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
@@ -61,10 +61,11 @@ function isDefaultSessionTitle(value) {
61
61
  return !value?.trim() || DEFAULT_TITLES.has(value.trim().toLowerCase());
62
62
  }
63
63
  function normalizeSessionTitle(value, fallback = DEFAULT_SESSION_TITLE) {
64
- const clean = value.replace(/<[^>]+>/g, " ").replace(/^\s*(?:[-*#>]+|\d+[.)])\s*/, "").replace(/\s+/g, " ").trim().replace(/[.!?,;:]+$/, "").trim();
64
+ const clean = value.replace(/<[^>]+>/g, " ").replace(/[`*_#>~|]+/g, " ").replace(/["'\u201C\u201D\u2018\u2019()\[\]{}]/g, " ").replace(/^\s*(?:[-*#>]+|\d+[.)])\s*/, "").replace(/[^\p{L}\p{N}\s.\/&+-]+/gu, " ").replace(/\s+/g, " ").trim().replace(/[.!?,;:\/&+-]+$/, "").trim().toLowerCase();
65
65
  if (!clean)
66
66
  return fallback;
67
- return clean.length > 72 ? `${clean.slice(0, 69).trimEnd()}...` : clean;
67
+ const byWords = clean.split(" ").slice(0, TITLE_MAX_WORDS).join(" ");
68
+ return byWords.length > TITLE_MAX_CHARS ? byWords.slice(0, TITLE_MAX_CHARS).trimEnd() : byWords;
68
69
  }
69
70
  function titleFromPrompt(prompt, fallback = DEFAULT_SESSION_TITLE) {
70
71
  const first = prompt.split(`
@@ -73,12 +74,21 @@ function titleFromPrompt(prompt, fallback = DEFAULT_SESSION_TITLE) {
73
74
  return fallback;
74
75
  return normalizeSessionTitle(first.replace(LEADING_FILLER, ""), fallback);
75
76
  }
77
+ function titleFromModelText(text, fallback = DEFAULT_SESSION_TITLE) {
78
+ const stripped = text.replace(/<think>[\s\S]*?<\/think>/gi, "").replace(/<[^>]+>/g, " ").replace(/^\s*title\s*[:\-]\s*/i, "");
79
+ const first = stripped.split(`
80
+ `).map((line) => line.trim()).find(Boolean) ?? "";
81
+ return normalizeSessionTitle(first, fallback);
82
+ }
76
83
  function sessionDisplayName(session) {
77
84
  if (isDefaultSessionTitle(session?.title))
78
85
  return DEFAULT_SESSION_TITLE;
79
86
  return normalizeSessionTitle(session.title, DEFAULT_SESSION_TITLE);
80
87
  }
81
- var DEFAULT_SESSION_TITLE = "new session", DEFAULT_TITLES, LOW_INFORMATION, LEADING_FILLER;
88
+ var DEFAULT_SESSION_TITLE = "new session", DEFAULT_TITLES, LOW_INFORMATION, LEADING_FILLER, TITLE_MAX_WORDS = 6, TITLE_MAX_CHARS = 48, SESSION_TITLE_PROMPT = `Write a short title for this session, describing the user's overall task.
89
+ Rules: 3 to 6 words, at most 48 characters, lowercase, plain text only.
90
+ No quotes, no markdown, no emoji, no trailing punctuation, no prefixes like "title:".
91
+ Be general about the whole task, not a single step. Respond with the title only.`;
82
92
  var init_session_title = __esm(() => {
83
93
  DEFAULT_TITLES = new Set(["new session", "untitled", "untitled session"]);
84
94
  LOW_INFORMATION = /^(?:hi|hey|hello|halo|hai|yo|bro|test|testing|ping|p|ok|oke|okay|sip|thanks|thank you|makasih|terima kasih)[.!?\s]*$/i;
@@ -8774,8 +8784,8 @@ var init_process_output = __esm(() => {
8774
8784
 
8775
8785
  // src/version.ts
8776
8786
  function resolveFaraiVersion() {
8777
- if ("0.3.3")
8778
- return "0.3.3";
8787
+ if ("0.3.4")
8788
+ return "0.3.4";
8779
8789
  try {
8780
8790
  const parsed = JSON.parse(readBoundedFileTextSync(new URL("../package.json", import.meta.url), 1024 * 1024, "package metadata"));
8781
8791
  if (typeof parsed.version === "string" && parsed.version)
@@ -29415,40 +29425,15 @@ function renderCtfNotes(input) {
29415
29425
  // src/agent-tools/tool-guidance.ts
29416
29426
  function modelToolDescription(tool, _detailed = false) {
29417
29427
  const exact = EXACT_GUIDANCE[tool.name];
29418
- const highValue = new Set(["report_add_finding", "cvss_calculate", "internet_search", "agent_spawn"]);
29419
- if (!exact || !_detailed && !highValue.has(tool.name))
29428
+ if (!exact)
29420
29429
  return tool.description;
29421
29430
  return `${tool.description}
29422
29431
 
29423
29432
  model contract: ${exact}`;
29424
29433
  }
29425
- function toolGuidanceMatchesQuery(toolName, query) {
29426
- const normalized = query.toLowerCase();
29427
- const terms = toolName.split("_").filter((term) => term.length >= 3);
29428
- const words = new Set(normalized.match(/[a-z0-9]+/g) ?? []);
29429
- const fileIntent = /\b(file|path|write|edit|patch|markdown|\.md|report)\b/.test(normalized);
29430
- const fileTools = ["fs_read", "fs_list", "fs_grep", "fs_write", "fs_edit", "patch_apply", "code_write_script", "report_add_finding", "report_update_finding"];
29431
- return terms.some((term) => words.has(term)) || fileIntent && fileTools.includes(toolName) || normalized.includes("finding") && ["report_add_finding", "report_update_finding", "campaign_verify", "campaign_test", "cvss_calculate"].includes(toolName) || normalized.includes("email") && toolName.startsWith("email_") || normalized.includes("browser") && toolName.startsWith("browser_") || normalized.includes("proxy") && toolName.startsWith("proxy_") || normalized.includes("campaign") && toolName.startsWith("campaign_");
29432
- }
29433
- function modelToolSchema(schema, detailed = false, toolName) {
29434
- if (!detailed && !new Set(["report_add_finding", "cvss_calculate", "internet_search", "agent_spawn"]).has(toolName ?? "")) {
29435
- return compactSchemaNode(schema);
29436
- }
29434
+ function modelToolSchema(schema, _detailed = false, toolName) {
29437
29435
  return enrichSchemaNode(schema, [], toolName);
29438
29436
  }
29439
- function compactSchemaNode(value) {
29440
- if (Array.isArray(value))
29441
- return value.map(compactSchemaNode);
29442
- if (!isRecord9(value))
29443
- return value;
29444
- const compact = {};
29445
- for (const [key, child] of Object.entries(value)) {
29446
- if (key === "description")
29447
- continue;
29448
- compact[key] = compactSchemaNode(child);
29449
- }
29450
- return compact;
29451
- }
29452
29437
  function enrichSchemaNode(value, path, toolName) {
29453
29438
  if (Array.isArray(value))
29454
29439
  return value.map((item) => enrichSchemaNode(item, path, toolName));
@@ -33048,22 +33033,17 @@ class HeuristicPlanner {
33048
33033
  });
33049
33034
  }
33050
33035
  }
33051
- function buildToolsPayload(toolNames, availableTools, options = {}) {
33036
+ function buildToolsPayload(toolNames, availableTools, _options = {}) {
33052
33037
  const payload = [];
33053
33038
  const available = availableTools ? new Map(availableTools.map((tool) => [tool.name, tool])) : undefined;
33054
- let detailedCount = 0;
33055
33039
  for (const name of [...new Set(toolNames.map(canonicalToolName))].sort()) {
33056
33040
  const tool = available?.get(name) ?? getTool(name);
33057
33041
  if (!tool)
33058
33042
  continue;
33059
- const matched = Boolean(options.userText && toolGuidanceMatchesQuery(tool.name, options.userText));
33060
- const detailed = matched && (options.maxDetailedTools === undefined || detailedCount < options.maxDetailedTools);
33061
- if (detailed)
33062
- detailedCount += 1;
33063
33043
  payload.push({
33064
33044
  name: tool.name,
33065
- description: modelToolDescription(tool, detailed),
33066
- parameters: modelToolSchema(tool.inputSchema, detailed, tool.name)
33045
+ description: modelToolDescription(tool),
33046
+ parameters: modelToolSchema(tool.inputSchema, true, tool.name)
33067
33047
  });
33068
33048
  }
33069
33049
  return payload;
@@ -35377,10 +35357,7 @@ function mergeProviderToolCatalog(advertised, selected, availableTools) {
35377
35357
  const current = buildToolsPayload([definition.name], availableTools)[0];
35378
35358
  if (!current)
35379
35359
  continue;
35380
- const detailed = buildToolsPayload([definition.name], availableTools, {
35381
- userText: definition.name.replaceAll("_", " ")
35382
- })[0];
35383
- const isCurrent = sameProviderTool(prior, current) || (detailed ? sameProviderTool(prior, detailed) : false);
35360
+ const isCurrent = sameProviderTool(prior, current);
35384
35361
  merged.push(isCurrent ? prior : selectedByName.get(prior.name) ?? current);
35385
35362
  seen.add(prior.name);
35386
35363
  }
@@ -37090,7 +37067,9 @@ function validateToolArgs(schema, args) {
37090
37067
  if (validate(args))
37091
37068
  return;
37092
37069
  const error = validate.errors?.[0];
37093
- return error ? formatValidationError(error, schema) : "arguments do not match the tool input schema";
37070
+ if (!error)
37071
+ return "arguments do not match the tool input schema";
37072
+ return `${formatValidationError(error, schema)}${compositionShapes(error, schema)}`;
37094
37073
  }
37095
37074
  function compiledValidator(schema) {
37096
37075
  const cached = validatorCache.get(schema);
@@ -37151,6 +37130,58 @@ function formatValidationError(error, schema) {
37151
37130
  return `${fieldName(path)} ${error.message ?? `failed ${error.keyword} validation`}`;
37152
37131
  }
37153
37132
  }
37133
+ function compositionShapes(error, schema) {
37134
+ const compositionPath = compositionPointer(error.schemaPath);
37135
+ const branches = compositionPath ? resolveSchemaPointer(schema, compositionPath) : undefined;
37136
+ if (!Array.isArray(branches) || branches.length < 2)
37137
+ return "";
37138
+ const shapes = branches.map(describeBranch).filter((text2) => Boolean(text2));
37139
+ if (shapes.length < 2)
37140
+ return "";
37141
+ return `; provide exactly one shape: ${shapes.map((text2, index) => `${index + 1}) ${text2}`).join(" or ")}`;
37142
+ }
37143
+ function compositionPointer(schemaPath) {
37144
+ if (typeof schemaPath !== "string")
37145
+ return;
37146
+ const segments = schemaPath.split("/");
37147
+ for (let index = segments.length - 1;index >= 0; index -= 1) {
37148
+ if (segments[index] === "oneOf" || segments[index] === "anyOf") {
37149
+ return segments.slice(0, index + 1).join("/");
37150
+ }
37151
+ }
37152
+ return;
37153
+ }
37154
+ function describeBranch(branch) {
37155
+ if (!branch || typeof branch !== "object" || Array.isArray(branch))
37156
+ return;
37157
+ const record3 = branch;
37158
+ const required = Array.isArray(record3.required) ? record3.required.map(String) : [];
37159
+ if (required.length > 0)
37160
+ return `{ ${required.join(", ")} }`;
37161
+ if (typeof record3.type === "string")
37162
+ return `a ${record3.type}`;
37163
+ if (typeof record3.const !== "undefined")
37164
+ return JSON.stringify(record3.const);
37165
+ return;
37166
+ }
37167
+ function resolveSchemaPointer(schema, schemaPath) {
37168
+ if (typeof schemaPath !== "string")
37169
+ return;
37170
+ const pointer = schemaPath.startsWith("#") ? schemaPath.slice(1) : schemaPath;
37171
+ let node = schema;
37172
+ for (const raw of pointer.split("/")) {
37173
+ if (!raw)
37174
+ continue;
37175
+ const key = raw.replace(/~1/g, "/").replace(/~0/g, "~");
37176
+ if (Array.isArray(node))
37177
+ node = node[Number(key)];
37178
+ else if (node && typeof node === "object")
37179
+ node = node[key];
37180
+ else
37181
+ return;
37182
+ }
37183
+ return node;
37184
+ }
37154
37185
  function unexpectedFieldError(path, property, schema) {
37155
37186
  const field = joinFieldPath(path, property);
37156
37187
  const enumOwner = enumOwnerForValue(schema, property);
@@ -38068,6 +38099,7 @@ class AgentRuntime {
38068
38099
  this.maxTurnMs = resolveMaxTurnMs(options.maxTurnSeconds ?? config.maxTurnSeconds);
38069
38100
  this.maxCostUsd = positiveFinite(options.maxCostUsd ?? config.maxCostUsd);
38070
38101
  this.maxInputTokens = positiveFinite(options.maxInputTokens);
38102
+ this.sessionTitlesEnabled = options.enableSessionTitles === true;
38071
38103
  this.mailbox = new SessionMailbox(this.store, this.runtimeId);
38072
38104
  this.inputQueue = new SessionInputQueue(this.mailbox, (sessionId, type, payload) => this.event(sessionId, type, payload));
38073
38105
  this.userInputs = new SessionUserInputCoordinator({
@@ -39369,6 +39401,7 @@ class AgentRuntime {
39369
39401
  const activeCampaignRun = source === "user" && !trimmed.startsWith("/") && !trimmed.startsWith("!") ? this.campaignSupervisor.prepare(session.id, input) : undefined;
39370
39402
  if (activeCampaignRun)
39371
39403
  session = this.store.loadSession(session.id);
39404
+ let autoTitleBaseline;
39372
39405
  if (source === "user" && isDefaultSessionTitle(session.title)) {
39373
39406
  const title = titleFromPrompt(input);
39374
39407
  if (!isDefaultSessionTitle(title)) {
@@ -39377,6 +39410,7 @@ class AgentRuntime {
39377
39410
  });
39378
39411
  this.recordSession(session);
39379
39412
  }
39413
+ autoTitleBaseline = session.title ?? DEFAULT_SESSION_TITLE;
39380
39414
  }
39381
39415
  if (source === "user" && (trimmed === "/compact" || trimmed.startsWith("/compact "))) {
39382
39416
  const cursor2 = this.store.latestEventSequence(session.id);
@@ -39480,6 +39514,11 @@ class AgentRuntime {
39480
39514
  if (source === "user" && !this.shuttingDown && this.store.loadTurn(turn.id).status !== "cancelled") {
39481
39515
  this.mailboxDispatcher.wakeQueuedInputs(session.id);
39482
39516
  }
39517
+ if (autoTitleBaseline !== undefined && this.sessionTitlesEnabled && !trimmed.startsWith("/") && !trimmed.startsWith("!") && this.store.loadTurn(turn.id).status === "completed") {
39518
+ this.generateSessionTitle(session.id, input, response, autoTitleBaseline).catch(() => {
39519
+ return;
39520
+ });
39521
+ }
39483
39522
  const cursor = startedEvents.at(-1)?.sequence ?? 0;
39484
39523
  return {
39485
39524
  session,
@@ -39487,6 +39526,48 @@ class AgentRuntime {
39487
39526
  events: this.store.listEventsAfter(session.id, cursor, 1e4)
39488
39527
  };
39489
39528
  }
39529
+ async generateSessionTitle(sessionId, userText, assistantText, baseline) {
39530
+ let session = this.store.loadSession(sessionId);
39531
+ if (session.title !== baseline)
39532
+ return;
39533
+ let planner;
39534
+ if (this.planner)
39535
+ planner = this.planner;
39536
+ else
39537
+ planner = new ChatProviderPlanner(this.chatProviderOverride ?? await createChatProviderForSession(session, this.workspace));
39538
+ if (planner.compactionMode !== "model")
39539
+ return;
39540
+ const history = [{
39541
+ role: "user",
39542
+ text: userText.slice(0, 4000)
39543
+ }];
39544
+ const reply = sanitizeVisibleResponse(assistantText).trim();
39545
+ if (reply)
39546
+ history.push({
39547
+ role: "assistant",
39548
+ text: reply.slice(0, 4000)
39549
+ });
39550
+ const actions = await planner.plan({
39551
+ session,
39552
+ userText: "title",
39553
+ systemInstruction: SESSION_TITLE_PROMPT,
39554
+ history,
39555
+ tools: [],
39556
+ toolCatalog: [],
39557
+ toolChoice: "none"
39558
+ });
39559
+ const text2 = actions.filter((action) => action.kind === "respond").map((action) => action.text).join(" ");
39560
+ const title = titleFromModelText(text2, "");
39561
+ if (!title)
39562
+ return;
39563
+ session = this.store.loadSession(sessionId);
39564
+ if (session.title !== baseline)
39565
+ return;
39566
+ session = this.store.updateSession(sessionId, {
39567
+ title
39568
+ });
39569
+ this.recordSession(session);
39570
+ }
39490
39571
  async runAgentLoop(session, turn, contextMessage, assistantMessage, input, userAuthored = true, mailboxItems = []) {
39491
39572
  const responses = [];
39492
39573
  let planner;
@@ -66289,9 +66370,10 @@ function StatusIndicator(props) {
66289
66370
  const value = tui.store.ui.statusDetail;
66290
66371
  return value && value !== "working" && value !== props.activity && !isFooterStatusDetail(value) ? ` \u2022 ${value}` : "";
66291
66372
  };
66373
+ const glyph = () => SPINNER_FRAMES[(props.spinnerFrame ?? 0) % SPINNER_FRAMES.length];
66292
66374
  const text2 = () => {
66293
66375
  if (props.activity) {
66294
- const value2 = dims().width >= 56 ? `\u2022 ${props.activity} (${fmtElapsed(props.elapsed)}${detail()} \u2022 esc to interrupt)` : `\u2022 ${props.activity} ${fmtElapsed(props.elapsed)} \xB7 esc interrupt`;
66376
+ const value2 = dims().width >= 56 ? `${glyph()} ${props.activity} (${fmtElapsed(props.elapsed)}${detail()} \u2022 esc to interrupt)` : `${glyph()} ${props.activity} ${fmtElapsed(props.elapsed)} \xB7 esc interrupt`;
66295
66377
  return truncateLine2(value2.toLowerCase(), Math.max(1, dims().width));
66296
66378
  }
66297
66379
  const value = tui.store.ui.statusDetail;
@@ -66310,6 +66392,7 @@ function StatusIndicator(props) {
66310
66392
  return _el$;
66311
66393
  })();
66312
66394
  }
66395
+ var SPINNER_FRAMES;
66313
66396
  var init_status_indicator = __esm(() => {
66314
66397
  init_solid2();
66315
66398
  init_solid2();
@@ -66321,6 +66404,7 @@ var init_status_indicator = __esm(() => {
66321
66404
  init_terminal();
66322
66405
  init_theme();
66323
66406
  init_footer_state();
66407
+ SPINNER_FRAMES = ["\xB7", "\u2022", "\xB7"];
66324
66408
  });
66325
66409
 
66326
66410
  // src/agent-tui/dialog/list-selection.ts
@@ -69476,7 +69560,9 @@ function BottomPane() {
69476
69560
  const dims = useTuiDimensions();
69477
69561
  const commandRegistryRevision = useCommandRegistryRevision();
69478
69562
  const [elapsed, setElapsed] = createSignal(0);
69563
+ const [spinner, setSpinner] = createSignal(0);
69479
69564
  let tick;
69565
+ let spinTick;
69480
69566
  const frame = () => tui.store.ui.overlayStack.at(-1);
69481
69567
  const listFrame = () => {
69482
69568
  const top = frame();
@@ -69552,16 +69638,24 @@ function BottomPane() {
69552
69638
  clearInterval(tick);
69553
69639
  tick = undefined;
69554
69640
  }
69641
+ if (spinTick) {
69642
+ clearInterval(spinTick);
69643
+ spinTick = undefined;
69644
+ }
69555
69645
  if (!started) {
69556
69646
  setElapsed(0);
69647
+ setSpinner(0);
69557
69648
  return;
69558
69649
  }
69559
69650
  setElapsed(Math.max(0, Math.floor((Date.now() - started) / 1000)));
69560
69651
  tick = setInterval(() => setElapsed(Math.max(0, Math.floor((Date.now() - started) / 1000))), 1000);
69652
+ spinTick = setInterval(() => setSpinner((value) => value + 1), 120);
69561
69653
  });
69562
69654
  onCleanup(() => {
69563
69655
  if (tick)
69564
69656
  clearInterval(tick);
69657
+ if (spinTick)
69658
+ clearInterval(spinTick);
69565
69659
  });
69566
69660
  return (() => {
69567
69661
  var _el$ = createElement("box");
@@ -69579,6 +69673,9 @@ function BottomPane() {
69579
69673
  get elapsed() {
69580
69674
  return elapsed();
69581
69675
  },
69676
+ get spinnerFrame() {
69677
+ return spinner();
69678
+ },
69582
69679
  get activity() {
69583
69680
  return statusActivity();
69584
69681
  }
@@ -70750,7 +70847,9 @@ async function launchOpenTui(workspace, sessionId) {
70750
70847
  const located = sessionId ? resolveSessionLocation(sessionId) : undefined;
70751
70848
  const effectiveWorkspace = located?.workspace ?? workspace;
70752
70849
  const effectiveSessionId = located?.id ?? sessionId;
70753
- const runtime = new AgentRuntime(effectiveWorkspace);
70850
+ const runtime = new AgentRuntime(effectiveWorkspace, undefined, {
70851
+ enableSessionTitles: true
70852
+ });
70754
70853
  let port;
70755
70854
  try {
70756
70855
  await runtime.recover();
@@ -74303,5 +74402,5 @@ Examples:
74303
74402
  `);
74304
74403
  }
74305
74404
 
74306
- //# debugId=11DF5F38873B6FC764756E2164756E21
74405
+ //# debugId=32EC65247FDE45AE64756E2164756E21
74307
74406
  //# sourceMappingURL=index.js.map