farai 0.3.2 → 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 +224 -82
- package/dist/cli/index.js.map +15 -15
- package/docker/kali/Dockerfile +57 -1
- package/docker/kali/farai-image-contract +1 -0
- package/docker/kali/farai-image-doctor.py +14 -1
- package/package.json +1 -1
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(/[
|
|
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
|
-
|
|
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;
|
|
@@ -5398,7 +5408,7 @@ function parseManifest(value) {
|
|
|
5398
5408
|
if (!value || typeof value !== "object")
|
|
5399
5409
|
throw new Error("invalid farai tool manifest");
|
|
5400
5410
|
const candidate = value;
|
|
5401
|
-
if (
|
|
5411
|
+
if (!Array.isArray(candidate.aptPackages) || !candidate.pinnedTools || typeof candidate.pinnedTools !== "object" || !candidate.pinnedAssets || typeof candidate.pinnedAssets !== "object" || !candidate.workflows || typeof candidate.workflows !== "object") {
|
|
5402
5412
|
throw new Error("invalid farai tool manifest");
|
|
5403
5413
|
}
|
|
5404
5414
|
const aptPackages = candidate.aptPackages.filter((item) => typeof item === "string" && Boolean(item));
|
|
@@ -5445,7 +5455,6 @@ function parseManifest(value) {
|
|
|
5445
5455
|
if (!Object.keys(pinnedAssets).length)
|
|
5446
5456
|
throw new Error("empty pinned asset manifest");
|
|
5447
5457
|
return {
|
|
5448
|
-
contract: candidate.contract,
|
|
5449
5458
|
aptPackages,
|
|
5450
5459
|
pinnedTools,
|
|
5451
5460
|
pinnedAssets,
|
|
@@ -5455,11 +5464,15 @@ function parseManifest(value) {
|
|
|
5455
5464
|
function isSha256(value) {
|
|
5456
5465
|
return typeof value === "string" && /^[a-f0-9]{64}$/.test(value);
|
|
5457
5466
|
}
|
|
5458
|
-
var KALI_TOOL_MANIFEST_PATH, KALI_TOOL_MANIFEST;
|
|
5467
|
+
var KALI_TOOL_MANIFEST_PATH, KALI_IMAGE_CONTRACT_PATH, KALI_TOOL_MANIFEST, KALI_IMAGE_CONTRACT;
|
|
5459
5468
|
var init_kali_tool_manifest = __esm(() => {
|
|
5460
5469
|
init_file_read();
|
|
5461
5470
|
KALI_TOOL_MANIFEST_PATH = join4(import.meta.dir, "..", "..", "docker", "kali", "farai-tool-manifest.json");
|
|
5471
|
+
KALI_IMAGE_CONTRACT_PATH = join4(import.meta.dir, "..", "..", "docker", "kali", "farai-image-contract");
|
|
5462
5472
|
KALI_TOOL_MANIFEST = parseManifest(JSON.parse(readBoundedFileTextSync(KALI_TOOL_MANIFEST_PATH, 1024 * 1024, "kali tool manifest")));
|
|
5473
|
+
KALI_IMAGE_CONTRACT = readBoundedFileTextSync(KALI_IMAGE_CONTRACT_PATH, 1024, "kali image contract").trim();
|
|
5474
|
+
if (!KALI_IMAGE_CONTRACT)
|
|
5475
|
+
throw new Error("empty kali image contract");
|
|
5463
5476
|
});
|
|
5464
5477
|
|
|
5465
5478
|
// src/agent-tools/mcp-builtins.ts
|
|
@@ -7284,20 +7297,59 @@ class KaliContainerBackend {
|
|
|
7284
7297
|
pullImageCommand() {
|
|
7285
7298
|
return ["docker", "pull", this.image];
|
|
7286
7299
|
}
|
|
7287
|
-
async
|
|
7288
|
-
const
|
|
7289
|
-
if (
|
|
7300
|
+
async remoteImageDigest() {
|
|
7301
|
+
const inspect = await this.processRunner("docker", ["buildx", "imagetools", "inspect", this.image, "--format", "{{.Manifest.Digest}}"]);
|
|
7302
|
+
if (inspect.exitCode !== 0)
|
|
7303
|
+
return;
|
|
7304
|
+
return digestOf(inspect.stdout.trim());
|
|
7305
|
+
}
|
|
7306
|
+
async checkForImageUpdate() {
|
|
7307
|
+
const local = await this.resolveImage();
|
|
7308
|
+
if (!local.exists)
|
|
7290
7309
|
return {
|
|
7291
|
-
|
|
7292
|
-
|
|
7293
|
-
|
|
7294
|
-
|
|
7295
|
-
|
|
7310
|
+
exists: false,
|
|
7311
|
+
upToDate: false,
|
|
7312
|
+
...local.error ? {
|
|
7313
|
+
error: local.error
|
|
7314
|
+
} : {}
|
|
7315
|
+
};
|
|
7316
|
+
const remote = await this.remoteImageDigest();
|
|
7317
|
+
if (!remote || !local.repoDigest)
|
|
7318
|
+
return {
|
|
7319
|
+
exists: true,
|
|
7320
|
+
upToDate: true
|
|
7296
7321
|
};
|
|
7322
|
+
return {
|
|
7323
|
+
exists: true,
|
|
7324
|
+
upToDate: local.repoDigest === remote
|
|
7325
|
+
};
|
|
7326
|
+
}
|
|
7327
|
+
async ensureImage() {
|
|
7328
|
+
const local = await this.resolveImage();
|
|
7329
|
+
if (local.exists) {
|
|
7330
|
+
const remote = await this.remoteImageDigest();
|
|
7331
|
+
if (!remote || local.repoDigest !== undefined && local.repoDigest === remote) {
|
|
7332
|
+
return {
|
|
7333
|
+
exitCode: 0,
|
|
7334
|
+
stdout: "image ready",
|
|
7335
|
+
stderr: "",
|
|
7336
|
+
durationMs: 0,
|
|
7337
|
+
timedOut: false
|
|
7338
|
+
};
|
|
7339
|
+
}
|
|
7297
7340
|
}
|
|
7298
7341
|
const started = Date.now();
|
|
7299
7342
|
const pulled = await this.pullRunner("docker", ["pull", this.image]);
|
|
7300
7343
|
if (pulled.exitCode !== 0) {
|
|
7344
|
+
if (local.exists) {
|
|
7345
|
+
return {
|
|
7346
|
+
exitCode: 0,
|
|
7347
|
+
stdout: pulled.stdout || "using local kali image",
|
|
7348
|
+
stderr: "",
|
|
7349
|
+
durationMs: Date.now() - started,
|
|
7350
|
+
timedOut: false
|
|
7351
|
+
};
|
|
7352
|
+
}
|
|
7301
7353
|
return {
|
|
7302
7354
|
...pulled,
|
|
7303
7355
|
stderr: dockerFailure(pulled, `could not pull kali image ${this.image}; check network access to ${KALI_IMAGE_REPO}`),
|
|
@@ -7315,15 +7367,6 @@ class KaliContainerBackend {
|
|
|
7315
7367
|
timedOut: false
|
|
7316
7368
|
};
|
|
7317
7369
|
}
|
|
7318
|
-
if (resolved.contract !== KALI_IMAGE_CONTRACT) {
|
|
7319
|
-
return {
|
|
7320
|
-
exitCode: 1,
|
|
7321
|
-
stdout: "",
|
|
7322
|
-
stderr: `pulled kali image ${this.image} does not satisfy the farai capability contract (${resolved.contract ?? "missing"})`,
|
|
7323
|
-
durationMs: Date.now() - started,
|
|
7324
|
-
timedOut: false
|
|
7325
|
-
};
|
|
7326
|
-
}
|
|
7327
7370
|
return {
|
|
7328
7371
|
exitCode: 0,
|
|
7329
7372
|
stdout: pulled.stdout || "image pulled",
|
|
@@ -7922,6 +7965,7 @@ function parseImageInspect(raw) {
|
|
|
7922
7965
|
exists: true
|
|
7923
7966
|
};
|
|
7924
7967
|
const contract = image.Config?.Labels?.[KALI_IMAGE_CONTRACT_LABEL];
|
|
7968
|
+
const repoDigest = digestOf((image.RepoDigests ?? []).find((entry) => entry.includes("@sha256:")));
|
|
7925
7969
|
return {
|
|
7926
7970
|
exists: true,
|
|
7927
7971
|
...image.Id ? {
|
|
@@ -7929,6 +7973,9 @@ function parseImageInspect(raw) {
|
|
|
7929
7973
|
} : {},
|
|
7930
7974
|
...contract ? {
|
|
7931
7975
|
contract
|
|
7976
|
+
} : {},
|
|
7977
|
+
...repoDigest ? {
|
|
7978
|
+
repoDigest
|
|
7932
7979
|
} : {}
|
|
7933
7980
|
};
|
|
7934
7981
|
} catch {
|
|
@@ -7937,6 +7984,12 @@ function parseImageInspect(raw) {
|
|
|
7937
7984
|
};
|
|
7938
7985
|
}
|
|
7939
7986
|
}
|
|
7987
|
+
function digestOf(reference) {
|
|
7988
|
+
if (!reference)
|
|
7989
|
+
return;
|
|
7990
|
+
const match = reference.match(/sha256:[a-f0-9]{64}/i);
|
|
7991
|
+
return match ? match[0].toLowerCase() : undefined;
|
|
7992
|
+
}
|
|
7940
7993
|
function dockerFailure(result, fallback) {
|
|
7941
7994
|
const detail = `${result.stderr}
|
|
7942
7995
|
${result.stdout}`.trim().replace(/\s+/g, " ");
|
|
@@ -7960,7 +8013,7 @@ function containerDoesNotExist2(result) {
|
|
|
7960
8013
|
return /no such (container|object)/i.test(`${result.stdout}
|
|
7961
8014
|
${result.stderr}`);
|
|
7962
8015
|
}
|
|
7963
|
-
var CONTAINER_WORKSPACE_MOUNT = "/workspace", CONTAINER_WORKTREES_MOUNT = "/worktrees", kaliSessions, kaliPtySessions, containerStartLocks, globalContainerStartChain, CONTAINER_EXEC_MARKER_DIR = "/tmp/farai-exec", CONTAINER_EXEC_WRAPPER, CONTAINER_EXEC_KILLER, CONTAINER_PREFIX,
|
|
8016
|
+
var CONTAINER_WORKSPACE_MOUNT = "/workspace", CONTAINER_WORKTREES_MOUNT = "/worktrees", kaliSessions, kaliPtySessions, containerStartLocks, globalContainerStartChain, CONTAINER_EXEC_MARKER_DIR = "/tmp/farai-exec", CONTAINER_EXEC_WRAPPER, CONTAINER_EXEC_KILLER, CONTAINER_PREFIX, KALI_IMAGE_REPO = "ghcr.io/pajarori/farai-kali", KALI_IMAGE_TAG = "latest", DEFAULT_KALI_IMAGE, KALI_IMAGE_CONTRACT_LABEL = "org.farai.kali.contract", KALI_IMAGE_PULL_TIMEOUT_MS;
|
|
7964
8017
|
var init_kali = __esm(() => {
|
|
7965
8018
|
init_spawn_session();
|
|
7966
8019
|
init_pty_session();
|
|
@@ -7978,8 +8031,7 @@ var init_kali = __esm(() => {
|
|
|
7978
8031
|
CONTAINER_EXEC_KILLER = ["import os, signal, sys, time", "path = sys.argv[1]", "root = None", "marker_deadline = time.monotonic() + 0.25", "while root is None and time.monotonic() < marker_deadline:", " try:", " with open(path, encoding='ascii') as handle:", " root = int(handle.read().strip())", " except (FileNotFoundError, OSError, ValueError):", " time.sleep(0.01)", "if root is None:", " raise SystemExit(0)", "def direct_children(pid):", " try:", " with open(f'/proc/{pid}/task/{pid}/children', encoding='ascii') as handle:", " return [int(value) for value in handle.read().split()]", " except (FileNotFoundError, OSError, ValueError):", " return []", "def descendants(pid):", " found = []", " pending = direct_children(pid)", " seen = set()", " while pending:", " child = pending.pop()", " if child in seen:", " continue", " seen.add(child)", " found.append(child)", " pending.extend(direct_children(child))", " return found", "def alive(pid):", " try:", " os.kill(pid, 0)", " return True", " except ProcessLookupError:", " return False", " except PermissionError:", " return True", "targets = descendants(root)", "for pid in [*reversed(targets), root]:", " try:", " os.kill(pid, signal.SIGTERM)", " except (ProcessLookupError, PermissionError):", " pass", "deadline = time.monotonic() + 1.0", "while time.monotonic() < deadline and any(alive(pid) for pid in [root, *targets]):", " time.sleep(0.025)", "targets = list(dict.fromkeys([*targets, *descendants(root)]))", "for pid in [*reversed(targets), root]:", " try:", " os.kill(pid, signal.SIGKILL)", " except (ProcessLookupError, PermissionError):", " pass", "try:", " os.unlink(path)", "except FileNotFoundError:", " pass"].join(`
|
|
7979
8032
|
`);
|
|
7980
8033
|
CONTAINER_PREFIX = FARAI_CONTAINER_NAME_PREFIX;
|
|
7981
|
-
|
|
7982
|
-
DEFAULT_KALI_IMAGE = `${KALI_IMAGE_REPO}:${KALI_IMAGE_CONTRACT}`;
|
|
8034
|
+
DEFAULT_KALI_IMAGE = `${KALI_IMAGE_REPO}:${KALI_IMAGE_TAG}`;
|
|
7983
8035
|
KALI_IMAGE_PULL_TIMEOUT_MS = 30 * 60 * 1000;
|
|
7984
8036
|
});
|
|
7985
8037
|
|
|
@@ -8732,8 +8784,8 @@ var init_process_output = __esm(() => {
|
|
|
8732
8784
|
|
|
8733
8785
|
// src/version.ts
|
|
8734
8786
|
function resolveFaraiVersion() {
|
|
8735
|
-
if ("0.3.
|
|
8736
|
-
return "0.3.
|
|
8787
|
+
if ("0.3.4")
|
|
8788
|
+
return "0.3.4";
|
|
8737
8789
|
try {
|
|
8738
8790
|
const parsed = JSON.parse(readBoundedFileTextSync(new URL("../package.json", import.meta.url), 1024 * 1024, "package metadata"));
|
|
8739
8791
|
if (typeof parsed.version === "string" && parsed.version)
|
|
@@ -29373,40 +29425,15 @@ function renderCtfNotes(input) {
|
|
|
29373
29425
|
// src/agent-tools/tool-guidance.ts
|
|
29374
29426
|
function modelToolDescription(tool, _detailed = false) {
|
|
29375
29427
|
const exact = EXACT_GUIDANCE[tool.name];
|
|
29376
|
-
|
|
29377
|
-
if (!exact || !_detailed && !highValue.has(tool.name))
|
|
29428
|
+
if (!exact)
|
|
29378
29429
|
return tool.description;
|
|
29379
29430
|
return `${tool.description}
|
|
29380
29431
|
|
|
29381
29432
|
model contract: ${exact}`;
|
|
29382
29433
|
}
|
|
29383
|
-
function
|
|
29384
|
-
const normalized = query.toLowerCase();
|
|
29385
|
-
const terms = toolName.split("_").filter((term) => term.length >= 3);
|
|
29386
|
-
const words = new Set(normalized.match(/[a-z0-9]+/g) ?? []);
|
|
29387
|
-
const fileIntent = /\b(file|path|write|edit|patch|markdown|\.md|report)\b/.test(normalized);
|
|
29388
|
-
const fileTools = ["fs_read", "fs_list", "fs_grep", "fs_write", "fs_edit", "patch_apply", "code_write_script", "report_add_finding", "report_update_finding"];
|
|
29389
|
-
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_");
|
|
29390
|
-
}
|
|
29391
|
-
function modelToolSchema(schema, detailed = false, toolName) {
|
|
29392
|
-
if (!detailed && !new Set(["report_add_finding", "cvss_calculate", "internet_search", "agent_spawn"]).has(toolName ?? "")) {
|
|
29393
|
-
return compactSchemaNode(schema);
|
|
29394
|
-
}
|
|
29434
|
+
function modelToolSchema(schema, _detailed = false, toolName) {
|
|
29395
29435
|
return enrichSchemaNode(schema, [], toolName);
|
|
29396
29436
|
}
|
|
29397
|
-
function compactSchemaNode(value) {
|
|
29398
|
-
if (Array.isArray(value))
|
|
29399
|
-
return value.map(compactSchemaNode);
|
|
29400
|
-
if (!isRecord9(value))
|
|
29401
|
-
return value;
|
|
29402
|
-
const compact = {};
|
|
29403
|
-
for (const [key, child] of Object.entries(value)) {
|
|
29404
|
-
if (key === "description")
|
|
29405
|
-
continue;
|
|
29406
|
-
compact[key] = compactSchemaNode(child);
|
|
29407
|
-
}
|
|
29408
|
-
return compact;
|
|
29409
|
-
}
|
|
29410
29437
|
function enrichSchemaNode(value, path, toolName) {
|
|
29411
29438
|
if (Array.isArray(value))
|
|
29412
29439
|
return value.map((item) => enrichSchemaNode(item, path, toolName));
|
|
@@ -33006,22 +33033,17 @@ class HeuristicPlanner {
|
|
|
33006
33033
|
});
|
|
33007
33034
|
}
|
|
33008
33035
|
}
|
|
33009
|
-
function buildToolsPayload(toolNames, availableTools,
|
|
33036
|
+
function buildToolsPayload(toolNames, availableTools, _options = {}) {
|
|
33010
33037
|
const payload = [];
|
|
33011
33038
|
const available = availableTools ? new Map(availableTools.map((tool) => [tool.name, tool])) : undefined;
|
|
33012
|
-
let detailedCount = 0;
|
|
33013
33039
|
for (const name of [...new Set(toolNames.map(canonicalToolName))].sort()) {
|
|
33014
33040
|
const tool = available?.get(name) ?? getTool(name);
|
|
33015
33041
|
if (!tool)
|
|
33016
33042
|
continue;
|
|
33017
|
-
const matched = Boolean(options.userText && toolGuidanceMatchesQuery(tool.name, options.userText));
|
|
33018
|
-
const detailed = matched && (options.maxDetailedTools === undefined || detailedCount < options.maxDetailedTools);
|
|
33019
|
-
if (detailed)
|
|
33020
|
-
detailedCount += 1;
|
|
33021
33043
|
payload.push({
|
|
33022
33044
|
name: tool.name,
|
|
33023
|
-
description: modelToolDescription(tool
|
|
33024
|
-
parameters: modelToolSchema(tool.inputSchema,
|
|
33045
|
+
description: modelToolDescription(tool),
|
|
33046
|
+
parameters: modelToolSchema(tool.inputSchema, true, tool.name)
|
|
33025
33047
|
});
|
|
33026
33048
|
}
|
|
33027
33049
|
return payload;
|
|
@@ -35335,10 +35357,7 @@ function mergeProviderToolCatalog(advertised, selected, availableTools) {
|
|
|
35335
35357
|
const current = buildToolsPayload([definition.name], availableTools)[0];
|
|
35336
35358
|
if (!current)
|
|
35337
35359
|
continue;
|
|
35338
|
-
const
|
|
35339
|
-
userText: definition.name.replaceAll("_", " ")
|
|
35340
|
-
})[0];
|
|
35341
|
-
const isCurrent = sameProviderTool(prior, current) || (detailed ? sameProviderTool(prior, detailed) : false);
|
|
35360
|
+
const isCurrent = sameProviderTool(prior, current);
|
|
35342
35361
|
merged.push(isCurrent ? prior : selectedByName.get(prior.name) ?? current);
|
|
35343
35362
|
seen.add(prior.name);
|
|
35344
35363
|
}
|
|
@@ -37048,7 +37067,9 @@ function validateToolArgs(schema, args) {
|
|
|
37048
37067
|
if (validate(args))
|
|
37049
37068
|
return;
|
|
37050
37069
|
const error = validate.errors?.[0];
|
|
37051
|
-
|
|
37070
|
+
if (!error)
|
|
37071
|
+
return "arguments do not match the tool input schema";
|
|
37072
|
+
return `${formatValidationError(error, schema)}${compositionShapes(error, schema)}`;
|
|
37052
37073
|
}
|
|
37053
37074
|
function compiledValidator(schema) {
|
|
37054
37075
|
const cached = validatorCache.get(schema);
|
|
@@ -37109,6 +37130,58 @@ function formatValidationError(error, schema) {
|
|
|
37109
37130
|
return `${fieldName(path)} ${error.message ?? `failed ${error.keyword} validation`}`;
|
|
37110
37131
|
}
|
|
37111
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
|
+
}
|
|
37112
37185
|
function unexpectedFieldError(path, property, schema) {
|
|
37113
37186
|
const field = joinFieldPath(path, property);
|
|
37114
37187
|
const enumOwner = enumOwnerForValue(schema, property);
|
|
@@ -38026,6 +38099,7 @@ class AgentRuntime {
|
|
|
38026
38099
|
this.maxTurnMs = resolveMaxTurnMs(options.maxTurnSeconds ?? config.maxTurnSeconds);
|
|
38027
38100
|
this.maxCostUsd = positiveFinite(options.maxCostUsd ?? config.maxCostUsd);
|
|
38028
38101
|
this.maxInputTokens = positiveFinite(options.maxInputTokens);
|
|
38102
|
+
this.sessionTitlesEnabled = options.enableSessionTitles === true;
|
|
38029
38103
|
this.mailbox = new SessionMailbox(this.store, this.runtimeId);
|
|
38030
38104
|
this.inputQueue = new SessionInputQueue(this.mailbox, (sessionId, type, payload) => this.event(sessionId, type, payload));
|
|
38031
38105
|
this.userInputs = new SessionUserInputCoordinator({
|
|
@@ -39327,6 +39401,7 @@ class AgentRuntime {
|
|
|
39327
39401
|
const activeCampaignRun = source === "user" && !trimmed.startsWith("/") && !trimmed.startsWith("!") ? this.campaignSupervisor.prepare(session.id, input) : undefined;
|
|
39328
39402
|
if (activeCampaignRun)
|
|
39329
39403
|
session = this.store.loadSession(session.id);
|
|
39404
|
+
let autoTitleBaseline;
|
|
39330
39405
|
if (source === "user" && isDefaultSessionTitle(session.title)) {
|
|
39331
39406
|
const title = titleFromPrompt(input);
|
|
39332
39407
|
if (!isDefaultSessionTitle(title)) {
|
|
@@ -39335,6 +39410,7 @@ class AgentRuntime {
|
|
|
39335
39410
|
});
|
|
39336
39411
|
this.recordSession(session);
|
|
39337
39412
|
}
|
|
39413
|
+
autoTitleBaseline = session.title ?? DEFAULT_SESSION_TITLE;
|
|
39338
39414
|
}
|
|
39339
39415
|
if (source === "user" && (trimmed === "/compact" || trimmed.startsWith("/compact "))) {
|
|
39340
39416
|
const cursor2 = this.store.latestEventSequence(session.id);
|
|
@@ -39438,6 +39514,11 @@ class AgentRuntime {
|
|
|
39438
39514
|
if (source === "user" && !this.shuttingDown && this.store.loadTurn(turn.id).status !== "cancelled") {
|
|
39439
39515
|
this.mailboxDispatcher.wakeQueuedInputs(session.id);
|
|
39440
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
|
+
}
|
|
39441
39522
|
const cursor = startedEvents.at(-1)?.sequence ?? 0;
|
|
39442
39523
|
return {
|
|
39443
39524
|
session,
|
|
@@ -39445,6 +39526,48 @@ class AgentRuntime {
|
|
|
39445
39526
|
events: this.store.listEventsAfter(session.id, cursor, 1e4)
|
|
39446
39527
|
};
|
|
39447
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
|
+
}
|
|
39448
39571
|
async runAgentLoop(session, turn, contextMessage, assistantMessage, input, userAuthored = true, mailboxItems = []) {
|
|
39449
39572
|
const responses = [];
|
|
39450
39573
|
let planner;
|
|
@@ -43757,17 +43880,17 @@ async function runStartupContainerPreflight(workspace) {
|
|
|
43757
43880
|
const backend2 = new KaliContainerBackend({
|
|
43758
43881
|
workspace
|
|
43759
43882
|
});
|
|
43760
|
-
const
|
|
43883
|
+
const update = await backend2.checkForImageUpdate().catch(() => {
|
|
43761
43884
|
return;
|
|
43762
43885
|
});
|
|
43763
|
-
if (!
|
|
43886
|
+
if (!update || update.error)
|
|
43764
43887
|
return "continue";
|
|
43765
|
-
if (
|
|
43888
|
+
if (update.exists && update.upToDate)
|
|
43766
43889
|
return "continue";
|
|
43767
43890
|
const config = loadConfig(workspace);
|
|
43768
43891
|
if (config.updates?.prompt === false || !process.stdin.isTTY || !process.stdout.isTTY)
|
|
43769
43892
|
return "continue";
|
|
43770
|
-
const answer = await promptForImagePull(
|
|
43893
|
+
const answer = await promptForImagePull(update.exists);
|
|
43771
43894
|
if (answer === "cancelled")
|
|
43772
43895
|
return "cancelled";
|
|
43773
43896
|
if (answer === "later")
|
|
@@ -43779,11 +43902,11 @@ async function runStartupContainerPreflight(workspace) {
|
|
|
43779
43902
|
}
|
|
43780
43903
|
return "continue";
|
|
43781
43904
|
}
|
|
43782
|
-
async function promptForImagePull(
|
|
43905
|
+
async function promptForImagePull(exists) {
|
|
43783
43906
|
console.log("");
|
|
43784
43907
|
console.log(FARAI_BANNER);
|
|
43785
43908
|
console.log("");
|
|
43786
|
-
console.log(exists ?
|
|
43909
|
+
console.log(exists ? "a newer kali container image is available" : "kali container image is not installed");
|
|
43787
43910
|
const interfaceHandle = createInterface2({
|
|
43788
43911
|
input: process.stdin,
|
|
43789
43912
|
output: process.stdout
|
|
@@ -66247,9 +66370,10 @@ function StatusIndicator(props) {
|
|
|
66247
66370
|
const value = tui.store.ui.statusDetail;
|
|
66248
66371
|
return value && value !== "working" && value !== props.activity && !isFooterStatusDetail(value) ? ` \u2022 ${value}` : "";
|
|
66249
66372
|
};
|
|
66373
|
+
const glyph = () => SPINNER_FRAMES[(props.spinnerFrame ?? 0) % SPINNER_FRAMES.length];
|
|
66250
66374
|
const text2 = () => {
|
|
66251
66375
|
if (props.activity) {
|
|
66252
|
-
const value2 = dims().width >= 56 ?
|
|
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`;
|
|
66253
66377
|
return truncateLine2(value2.toLowerCase(), Math.max(1, dims().width));
|
|
66254
66378
|
}
|
|
66255
66379
|
const value = tui.store.ui.statusDetail;
|
|
@@ -66268,6 +66392,7 @@ function StatusIndicator(props) {
|
|
|
66268
66392
|
return _el$;
|
|
66269
66393
|
})();
|
|
66270
66394
|
}
|
|
66395
|
+
var SPINNER_FRAMES;
|
|
66271
66396
|
var init_status_indicator = __esm(() => {
|
|
66272
66397
|
init_solid2();
|
|
66273
66398
|
init_solid2();
|
|
@@ -66279,6 +66404,7 @@ var init_status_indicator = __esm(() => {
|
|
|
66279
66404
|
init_terminal();
|
|
66280
66405
|
init_theme();
|
|
66281
66406
|
init_footer_state();
|
|
66407
|
+
SPINNER_FRAMES = ["\xB7", "\u2022", "\xB7"];
|
|
66282
66408
|
});
|
|
66283
66409
|
|
|
66284
66410
|
// src/agent-tui/dialog/list-selection.ts
|
|
@@ -69434,7 +69560,9 @@ function BottomPane() {
|
|
|
69434
69560
|
const dims = useTuiDimensions();
|
|
69435
69561
|
const commandRegistryRevision = useCommandRegistryRevision();
|
|
69436
69562
|
const [elapsed, setElapsed] = createSignal(0);
|
|
69563
|
+
const [spinner, setSpinner] = createSignal(0);
|
|
69437
69564
|
let tick;
|
|
69565
|
+
let spinTick;
|
|
69438
69566
|
const frame = () => tui.store.ui.overlayStack.at(-1);
|
|
69439
69567
|
const listFrame = () => {
|
|
69440
69568
|
const top = frame();
|
|
@@ -69510,16 +69638,24 @@ function BottomPane() {
|
|
|
69510
69638
|
clearInterval(tick);
|
|
69511
69639
|
tick = undefined;
|
|
69512
69640
|
}
|
|
69641
|
+
if (spinTick) {
|
|
69642
|
+
clearInterval(spinTick);
|
|
69643
|
+
spinTick = undefined;
|
|
69644
|
+
}
|
|
69513
69645
|
if (!started) {
|
|
69514
69646
|
setElapsed(0);
|
|
69647
|
+
setSpinner(0);
|
|
69515
69648
|
return;
|
|
69516
69649
|
}
|
|
69517
69650
|
setElapsed(Math.max(0, Math.floor((Date.now() - started) / 1000)));
|
|
69518
69651
|
tick = setInterval(() => setElapsed(Math.max(0, Math.floor((Date.now() - started) / 1000))), 1000);
|
|
69652
|
+
spinTick = setInterval(() => setSpinner((value) => value + 1), 120);
|
|
69519
69653
|
});
|
|
69520
69654
|
onCleanup(() => {
|
|
69521
69655
|
if (tick)
|
|
69522
69656
|
clearInterval(tick);
|
|
69657
|
+
if (spinTick)
|
|
69658
|
+
clearInterval(spinTick);
|
|
69523
69659
|
});
|
|
69524
69660
|
return (() => {
|
|
69525
69661
|
var _el$ = createElement("box");
|
|
@@ -69537,6 +69673,9 @@ function BottomPane() {
|
|
|
69537
69673
|
get elapsed() {
|
|
69538
69674
|
return elapsed();
|
|
69539
69675
|
},
|
|
69676
|
+
get spinnerFrame() {
|
|
69677
|
+
return spinner();
|
|
69678
|
+
},
|
|
69540
69679
|
get activity() {
|
|
69541
69680
|
return statusActivity();
|
|
69542
69681
|
}
|
|
@@ -70708,7 +70847,9 @@ async function launchOpenTui(workspace, sessionId) {
|
|
|
70708
70847
|
const located = sessionId ? resolveSessionLocation(sessionId) : undefined;
|
|
70709
70848
|
const effectiveWorkspace = located?.workspace ?? workspace;
|
|
70710
70849
|
const effectiveSessionId = located?.id ?? sessionId;
|
|
70711
|
-
const runtime = new AgentRuntime(effectiveWorkspace
|
|
70850
|
+
const runtime = new AgentRuntime(effectiveWorkspace, undefined, {
|
|
70851
|
+
enableSessionTitles: true
|
|
70852
|
+
});
|
|
70712
70853
|
let port;
|
|
70713
70854
|
try {
|
|
70714
70855
|
await runtime.recover();
|
|
@@ -73872,11 +74013,12 @@ async function doctor() {
|
|
|
73872
74013
|
const backend2 = new KaliContainerBackend({
|
|
73873
74014
|
workspace: process.cwd()
|
|
73874
74015
|
});
|
|
73875
|
-
const
|
|
73876
|
-
|
|
73877
|
-
|
|
73878
|
-
|
|
73879
|
-
console.log(`kali
|
|
74016
|
+
const update = await backend2.checkForImageUpdate().catch(() => {
|
|
74017
|
+
return;
|
|
74018
|
+
});
|
|
74019
|
+
const status = !update ? "unknown (docker unavailable)" : !update.exists ? "not installed (pulled on first run)" : update.upToDate ? "up to date" : "update available (pulled on first run)";
|
|
74020
|
+
console.log(`kali image: ${backend2.image}`);
|
|
74021
|
+
console.log(`kali image status: ${status}`);
|
|
73880
74022
|
const {
|
|
73881
74023
|
contentStatus: contentStatus2
|
|
73882
74024
|
} = await Promise.resolve().then(() => (init_updater(), exports_updater));
|
|
@@ -74260,5 +74402,5 @@ Examples:
|
|
|
74260
74402
|
`);
|
|
74261
74403
|
}
|
|
74262
74404
|
|
|
74263
|
-
//# debugId=
|
|
74405
|
+
//# debugId=32EC65247FDE45AE64756E2164756E21
|
|
74264
74406
|
//# sourceMappingURL=index.js.map
|