@octocodeai/freellama-mcp-server 0.9.0 → 0.10.0
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/README.md +42 -0
- package/dist/index.js +558 -314
- package/dist/research/bash-agent.js +21 -12
- package/dist/research/coder-agent.js +21 -12
- package/dist/research/github-agent.js +22 -13
- package/dist/research/octocode-agent.js +21 -12
- package/docs/MODEL_SELECTION.md +1 -1
- package/package.json +6 -6
package/dist/index.js
CHANGED
|
@@ -25057,6 +25057,324 @@ var responseStore = new ResponseStore(
|
|
|
25057
25057
|
envNumber("FREELLAMA_RESPONSE_TTL_DAYS", 7)
|
|
25058
25058
|
);
|
|
25059
25059
|
|
|
25060
|
+
// src/handoffs.ts
|
|
25061
|
+
init_config();
|
|
25062
|
+
import { lstatSync, mkdirSync, readdirSync, realpathSync as realpathSync3, rmSync, statSync as statSync2, writeFileSync } from "node:fs";
|
|
25063
|
+
import path5 from "node:path";
|
|
25064
|
+
|
|
25065
|
+
// src/task-files.ts
|
|
25066
|
+
import { globSync, realpathSync as realpathSync2, statSync, readFileSync } from "node:fs";
|
|
25067
|
+
import { homedir as homedir2, tmpdir } from "node:os";
|
|
25068
|
+
import path4 from "node:path";
|
|
25069
|
+
var MAX_TEXT_FILE_BYTES = 4 * 1024 * 1024;
|
|
25070
|
+
var MAX_IMAGE_FILE_BYTES = 8 * 1024 * 1024;
|
|
25071
|
+
var MAX_TOTAL_FILE_BYTES = 32 * 1024 * 1024;
|
|
25072
|
+
var MAX_FILES = 2e3;
|
|
25073
|
+
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp", ".gif"]);
|
|
25074
|
+
var AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([".wav"]);
|
|
25075
|
+
var EMBEDDING_CHUNK_BYTES = 1536;
|
|
25076
|
+
var CHUNK_OVERLAP_LINES = 8;
|
|
25077
|
+
function chunkLines(text3, limit = EMBEDDING_CHUNK_BYTES) {
|
|
25078
|
+
const lines = text3.split("\n");
|
|
25079
|
+
const chunks = [];
|
|
25080
|
+
let current = [];
|
|
25081
|
+
let bytes = 0;
|
|
25082
|
+
let start2 = 1;
|
|
25083
|
+
const flush = (end, overlap = 0) => {
|
|
25084
|
+
if (current.length > 0 && current.join("\n").trim()) chunks.push({ text: current.join("\n"), lines: [start2, end] });
|
|
25085
|
+
const carried = overlap > 0 ? current.slice(-overlap) : [];
|
|
25086
|
+
const carriedBytes = carried.reduce((sum, line2) => sum + Buffer.byteLength(line2) + 1, 0);
|
|
25087
|
+
current = carriedBytes < limit / 4 ? carried : [];
|
|
25088
|
+
bytes = current === carried ? carriedBytes : 0;
|
|
25089
|
+
start2 = end + 1 - current.length;
|
|
25090
|
+
};
|
|
25091
|
+
lines.forEach((line2, index) => {
|
|
25092
|
+
const size2 = Buffer.byteLength(line2) + 1;
|
|
25093
|
+
if (bytes + size2 > limit && current.length > 0) flush(index, CHUNK_OVERLAP_LINES);
|
|
25094
|
+
if (size2 > limit) {
|
|
25095
|
+
for (let at = 0; at < line2.length; at += Math.floor(limit / 2)) {
|
|
25096
|
+
chunks.push({ text: line2.slice(at, at + Math.floor(limit / 2)), lines: [index + 1, index + 1] });
|
|
25097
|
+
}
|
|
25098
|
+
start2 = index + 2;
|
|
25099
|
+
return;
|
|
25100
|
+
}
|
|
25101
|
+
current.push(line2);
|
|
25102
|
+
bytes += size2;
|
|
25103
|
+
});
|
|
25104
|
+
flush(lines.length);
|
|
25105
|
+
return chunks;
|
|
25106
|
+
}
|
|
25107
|
+
function fileRoots(env = process.env, cwd = process.cwd(), home = homedir2()) {
|
|
25108
|
+
const configured = (env.FREELLAMA_FILE_ROOTS ?? "").split(path4.delimiter).filter(Boolean);
|
|
25109
|
+
const broad = /* @__PURE__ */ new Set([path4.parse(path4.resolve(cwd)).root, resolvedRoot(home)]);
|
|
25110
|
+
const project = broad.has(resolvedRoot(cwd)) ? [] : [cwd];
|
|
25111
|
+
const handoffs = path4.join(env.FREELLAMA_HOME || path4.join(home, ".octocode", "freellama"), "handoffs");
|
|
25112
|
+
const outputs = path4.join(env.FREELLAMA_HOME || path4.join(home, ".octocode", "freellama"), "tmp");
|
|
25113
|
+
return [...project, tmpdir(), ...configured, handoffs, outputs].map(resolvedRoot);
|
|
25114
|
+
}
|
|
25115
|
+
function resolvedRoot(root) {
|
|
25116
|
+
try {
|
|
25117
|
+
return realpathSync2(root);
|
|
25118
|
+
} catch {
|
|
25119
|
+
const absolute = path4.resolve(root);
|
|
25120
|
+
const parent = path4.dirname(absolute);
|
|
25121
|
+
return parent === absolute ? absolute : path4.join(resolvedRoot(parent), path4.basename(absolute));
|
|
25122
|
+
}
|
|
25123
|
+
}
|
|
25124
|
+
function within(file, root) {
|
|
25125
|
+
const relative = path4.relative(root, file);
|
|
25126
|
+
return relative === "" || !relative.startsWith("..") && !path4.isAbsolute(relative);
|
|
25127
|
+
}
|
|
25128
|
+
function isWav(bytes) {
|
|
25129
|
+
return bytes.length >= 12 && bytes.subarray(0, 4).toString() === "RIFF" && bytes.subarray(8, 12).toString() === "WAVE";
|
|
25130
|
+
}
|
|
25131
|
+
function expandFileEntries(entries, cwd = process.cwd()) {
|
|
25132
|
+
const out = [];
|
|
25133
|
+
for (const entry of entries) {
|
|
25134
|
+
if (!/[*?[]/.test(entry)) {
|
|
25135
|
+
out.push(entry);
|
|
25136
|
+
continue;
|
|
25137
|
+
}
|
|
25138
|
+
const matches = globSync(entry, { cwd }).map((match) => path4.resolve(cwd, match)).filter((match) => statSync(match).isFile()).sort();
|
|
25139
|
+
if (matches.length === 0) throw new Error(`no files match ${entry} (globs resolve from ${cwd})`);
|
|
25140
|
+
out.push(...matches);
|
|
25141
|
+
if (out.length > MAX_FILES) break;
|
|
25142
|
+
}
|
|
25143
|
+
return out;
|
|
25144
|
+
}
|
|
25145
|
+
function readFiles(entries, givenRoots) {
|
|
25146
|
+
const roots = givenRoots.map(resolvedRoot);
|
|
25147
|
+
const paths = expandFileEntries(entries);
|
|
25148
|
+
if (paths.length > MAX_FILES) throw new Error(`files names ${paths.length} paths; at most ${MAX_FILES} per call`);
|
|
25149
|
+
let total = 0;
|
|
25150
|
+
return paths.map((name) => {
|
|
25151
|
+
const absolute = path4.resolve(name);
|
|
25152
|
+
let real;
|
|
25153
|
+
try {
|
|
25154
|
+
real = realpathSync2(absolute);
|
|
25155
|
+
} catch {
|
|
25156
|
+
throw new Error(`file not found: ${name}`);
|
|
25157
|
+
}
|
|
25158
|
+
if (!roots.some((root) => within(real, root))) {
|
|
25159
|
+
throw new Error(
|
|
25160
|
+
`file ${name} is outside the readable roots (${roots.join(", ")}); copy it into the project or temp directory, or add its directory to FREELLAMA_FILE_ROOTS`
|
|
25161
|
+
);
|
|
25162
|
+
}
|
|
25163
|
+
const stat3 = statSync(real);
|
|
25164
|
+
if (!stat3.isFile()) throw new Error(`not a regular file: ${name}`);
|
|
25165
|
+
const extension = path4.extname(real).toLowerCase();
|
|
25166
|
+
let media = AUDIO_EXTENSIONS.has(extension) ? "audio" : IMAGE_EXTENSIONS.has(extension) ? "image" : void 0;
|
|
25167
|
+
const limit = media ? MAX_IMAGE_FILE_BYTES : MAX_TEXT_FILE_BYTES;
|
|
25168
|
+
if (stat3.size > limit) {
|
|
25169
|
+
throw new Error(`file ${name} is ${stat3.size} bytes; the limit is ${limit} for ${media ?? "text"}; split it or send the part that matters`);
|
|
25170
|
+
}
|
|
25171
|
+
total += stat3.size;
|
|
25172
|
+
if (total > MAX_TOTAL_FILE_BYTES) throw new Error(`files total more than ${MAX_TOTAL_FILE_BYTES} bytes; split the call`);
|
|
25173
|
+
const bytes = readFileSync(real);
|
|
25174
|
+
if (media === "audio" && !isWav(bytes)) {
|
|
25175
|
+
throw new Error(`audio file ${name} must contain a WAV RIFF/WAVE header`);
|
|
25176
|
+
}
|
|
25177
|
+
if (isWav(bytes)) media = "audio";
|
|
25178
|
+
return { path: name, bytes, media };
|
|
25179
|
+
});
|
|
25180
|
+
}
|
|
25181
|
+
function shortPath(name) {
|
|
25182
|
+
const relative = path4.relative(process.cwd(), path4.resolve(name));
|
|
25183
|
+
return relative && !relative.startsWith("..") && !path4.isAbsolute(relative) ? relative : name;
|
|
25184
|
+
}
|
|
25185
|
+
function leadingComment(body) {
|
|
25186
|
+
const lines = [];
|
|
25187
|
+
for (const line2 of body.split("\n").slice(0, 20)) {
|
|
25188
|
+
const trimmed = line2.trim();
|
|
25189
|
+
if (!/^(\/\/|#(?!include)|\/\*|\*|"""|\s*$)/.test(trimmed) || trimmed.startsWith("#!")) break;
|
|
25190
|
+
if (trimmed) lines.push(trimmed.replace(/^(\/\/[!/]?|#+|\/\*+|\*\/?|""")\s?/, ""));
|
|
25191
|
+
}
|
|
25192
|
+
return lines.join(" ").slice(0, 400);
|
|
25193
|
+
}
|
|
25194
|
+
var MAX_AUDIO_FILE_BYTES = 256 * 1024 * 1024;
|
|
25195
|
+
function readAllowedFile(name, limit, roots = fileRoots()) {
|
|
25196
|
+
const allowed = roots.map(resolvedRoot);
|
|
25197
|
+
let real;
|
|
25198
|
+
try {
|
|
25199
|
+
real = realpathSync2(path4.resolve(name));
|
|
25200
|
+
} catch {
|
|
25201
|
+
throw new Error(`file not found: ${name}`);
|
|
25202
|
+
}
|
|
25203
|
+
if (!allowed.some((root) => within(real, root))) {
|
|
25204
|
+
throw new Error(`file ${name} is outside the readable roots (${allowed.join(", ")}); copy it into the project or temp directory, or add its directory to FREELLAMA_FILE_ROOTS`);
|
|
25205
|
+
}
|
|
25206
|
+
const stat3 = statSync(real);
|
|
25207
|
+
if (!stat3.isFile()) throw new Error(`not a regular file: ${name}`);
|
|
25208
|
+
if (stat3.size > limit) throw new Error(`file ${name} is ${stat3.size} bytes; the limit is ${limit}`);
|
|
25209
|
+
return readFileSync(real);
|
|
25210
|
+
}
|
|
25211
|
+
function text(file) {
|
|
25212
|
+
if (file.bytes.includes(0)) throw new Error(`file ${file.path} is binary; only images (${[...IMAGE_EXTENSIONS].join(", ")}) and text files can be read`);
|
|
25213
|
+
return file.bytes.toString("utf8");
|
|
25214
|
+
}
|
|
25215
|
+
function applyTaskFiles(payload, paths, roots = fileRoots()) {
|
|
25216
|
+
if (paths === void 0 || paths.length === 0) return { payload, files: [] };
|
|
25217
|
+
const read = readFiles(paths, roots);
|
|
25218
|
+
const receipts = [];
|
|
25219
|
+
const media = read.filter((file) => file.media !== void 0);
|
|
25220
|
+
const texts = read.filter((file) => file.media === void 0);
|
|
25221
|
+
const next = { ...payload };
|
|
25222
|
+
const task = payload.task ?? "completion";
|
|
25223
|
+
if (task === "embedding") {
|
|
25224
|
+
const given = payload.input === void 0 ? [] : Array.isArray(payload.input) ? payload.input : [payload.input];
|
|
25225
|
+
const prefix = payload.documentPrefix ?? "";
|
|
25226
|
+
const items = [...given];
|
|
25227
|
+
for (const file of read) {
|
|
25228
|
+
if (file.media) {
|
|
25229
|
+
const encoded = file.bytes.toString("base64");
|
|
25230
|
+
items.push(file.media === "audio" ? { audio: encoded } : { image: encoded });
|
|
25231
|
+
receipts.push({ path: file.path, bytes: file.bytes.length, role: "input" });
|
|
25232
|
+
continue;
|
|
25233
|
+
}
|
|
25234
|
+
const body = text(file);
|
|
25235
|
+
const header = `${shortPath(file.path)}
|
|
25236
|
+
${leadingComment(body)}`.trimEnd();
|
|
25237
|
+
for (const chunk of chunkLines(body)) {
|
|
25238
|
+
items.push(`${prefix}${header}
|
|
25239
|
+
${chunk.text}`);
|
|
25240
|
+
receipts.push({ path: file.path, bytes: Buffer.byteLength(chunk.text), role: "input", lines: chunk.lines });
|
|
25241
|
+
}
|
|
25242
|
+
}
|
|
25243
|
+
next.input = items;
|
|
25244
|
+
return { payload: next, files: receipts };
|
|
25245
|
+
}
|
|
25246
|
+
const audio = media.find((file) => file.media === "audio");
|
|
25247
|
+
if (audio && task !== "decision") throw new Error(`audio file ${audio.path} is read only by tasks "decision" and "embedding" (models with audio input)`);
|
|
25248
|
+
if (media.length > 0) {
|
|
25249
|
+
next.images = [...payload.images ?? [], ...media.map((file) => file.bytes.toString("base64"))];
|
|
25250
|
+
for (const file of media) receipts.push({ path: file.path, bytes: file.bytes.length, role: file.media });
|
|
25251
|
+
}
|
|
25252
|
+
if (task === "decision") {
|
|
25253
|
+
if (texts.length > 0 && payload.state !== void 0) throw new Error("give the decision state either as `state` or as text `files`, not both");
|
|
25254
|
+
if (texts.length === 1) {
|
|
25255
|
+
const body = text(texts[0]);
|
|
25256
|
+
next.state = jsonOrText(body);
|
|
25257
|
+
} else if (texts.length > 1) {
|
|
25258
|
+
next.state = Object.fromEntries(texts.map((file) => [file.path, text(file)]));
|
|
25259
|
+
}
|
|
25260
|
+
for (const file of texts) receipts.push({ path: file.path, bytes: file.bytes.length, role: "state" });
|
|
25261
|
+
return { payload: next, files: receipts };
|
|
25262
|
+
}
|
|
25263
|
+
if (texts.length > 0) {
|
|
25264
|
+
if (payload.prompt === void 0) throw new Error("text `files` are appended to `prompt`; give the instructions in `prompt`");
|
|
25265
|
+
const sections = texts.map((file) => `--- file: ${file.path} ---
|
|
25266
|
+
${text(file)}`);
|
|
25267
|
+
next.prompt = [payload.prompt, ...sections].join("\n\n");
|
|
25268
|
+
for (const file of texts) receipts.push({ path: file.path, bytes: file.bytes.length, role: "prompt" });
|
|
25269
|
+
}
|
|
25270
|
+
return { payload: next, files: receipts };
|
|
25271
|
+
}
|
|
25272
|
+
function jsonOrText(body) {
|
|
25273
|
+
try {
|
|
25274
|
+
const value = JSON.parse(body);
|
|
25275
|
+
if (value !== null && typeof value === "object") return value;
|
|
25276
|
+
} catch {
|
|
25277
|
+
}
|
|
25278
|
+
return body;
|
|
25279
|
+
}
|
|
25280
|
+
function inputLabels(inputCount, files) {
|
|
25281
|
+
const fromFiles = files.filter((file) => file.role === "input").map((file) => file.lines ? `${shortPath(file.path)}:L${file.lines[0]}-${file.lines[1]}` : shortPath(file.path));
|
|
25282
|
+
const given = inputCount - fromFiles.length;
|
|
25283
|
+
return [...Array.from({ length: given }, (_, index) => `input[${index}]`), ...fromFiles];
|
|
25284
|
+
}
|
|
25285
|
+
|
|
25286
|
+
// src/handoffs.ts
|
|
25287
|
+
var HANDOFF_DIR = path5.join(FREELLAMA_HOME, "handoffs");
|
|
25288
|
+
var HANDOFF_NAME = /^[a-z0-9-]{1,60}$/;
|
|
25289
|
+
function summaryOf(text3, lines = 5) {
|
|
25290
|
+
const kept = text3.split("\n").filter((line2) => line2.trim()).slice(0, lines).join("\n");
|
|
25291
|
+
return kept.length > 600 ? `${kept.slice(0, 600)}\u2026` : kept;
|
|
25292
|
+
}
|
|
25293
|
+
function handoffBody(title, content) {
|
|
25294
|
+
return `# ${title}
|
|
25295
|
+
|
|
25296
|
+
${content.source}, ${(/* @__PURE__ */ new Date()).toISOString()}.
|
|
25297
|
+
|
|
25298
|
+
## Summary
|
|
25299
|
+
|
|
25300
|
+
${content.summary.trim() || "(empty)"}
|
|
25301
|
+
|
|
25302
|
+
## Result
|
|
25303
|
+
|
|
25304
|
+
${content.result.trim() || "(empty)"}
|
|
25305
|
+
`;
|
|
25306
|
+
}
|
|
25307
|
+
function writeHandoff(name, content, dir = HANDOFF_DIR) {
|
|
25308
|
+
if (!HANDOFF_NAME.test(name)) throw new Error("saveAs is 1 to 60 characters of a-z, 0-9 and -");
|
|
25309
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
25310
|
+
const file = path5.join(dir, `${name}.md`);
|
|
25311
|
+
writeFileSync(file, handoffBody(name, content), { mode: 384 });
|
|
25312
|
+
return file;
|
|
25313
|
+
}
|
|
25314
|
+
function resolveThroughSymlinks(file) {
|
|
25315
|
+
try {
|
|
25316
|
+
return realpathSync3(file);
|
|
25317
|
+
} catch {
|
|
25318
|
+
const parent = path5.dirname(file);
|
|
25319
|
+
return parent === file ? file : path5.join(resolveThroughSymlinks(parent), path5.basename(file));
|
|
25320
|
+
}
|
|
25321
|
+
}
|
|
25322
|
+
function checkOutputPath(name, roots = fileRoots()) {
|
|
25323
|
+
const given = path5.resolve(name);
|
|
25324
|
+
const file = resolveThroughSymlinks(given);
|
|
25325
|
+
if ([given, file].some((candidate) => candidate.split(path5.sep).includes(".git"))) {
|
|
25326
|
+
throw new Error(`outputPath ${name} is inside a .git directory; FreeLlama does not write there`);
|
|
25327
|
+
}
|
|
25328
|
+
if (!roots.some((root) => within(file, root))) {
|
|
25329
|
+
throw new Error(`outputPath ${name} is outside the writable roots (${roots.join(", ")}); use a path inside the project or temp directory, or add its directory to FREELLAMA_FILE_ROOTS`);
|
|
25330
|
+
}
|
|
25331
|
+
let exists2 = true;
|
|
25332
|
+
try {
|
|
25333
|
+
lstatSync(file);
|
|
25334
|
+
} catch {
|
|
25335
|
+
exists2 = false;
|
|
25336
|
+
}
|
|
25337
|
+
if (exists2 && !statSync2(file).isFile()) throw new Error(`outputPath ${name} exists and is not a regular file`);
|
|
25338
|
+
return file;
|
|
25339
|
+
}
|
|
25340
|
+
function writeOutputFile(name, content, roots = fileRoots()) {
|
|
25341
|
+
const file = checkOutputPath(name, roots);
|
|
25342
|
+
mkdirSync(path5.dirname(file), { recursive: true, mode: 448 });
|
|
25343
|
+
writeFileSync(file, handoffBody(path5.basename(file), content), { mode: 384 });
|
|
25344
|
+
return file;
|
|
25345
|
+
}
|
|
25346
|
+
var OUTPUT_TMP_DIR = path5.join(FREELLAMA_HOME, "tmp");
|
|
25347
|
+
var DAY_MS2 = 864e5;
|
|
25348
|
+
function outputTtlDays(env = process.env) {
|
|
25349
|
+
const configured = Number(env.FREELLAMA_RESPONSE_TTL_DAYS ?? 7);
|
|
25350
|
+
return Number.isFinite(configured) && configured >= 0 ? configured : 7;
|
|
25351
|
+
}
|
|
25352
|
+
function pruneOutputDir(dir, now, ttlDays) {
|
|
25353
|
+
if (ttlDays <= 0) return;
|
|
25354
|
+
let names;
|
|
25355
|
+
try {
|
|
25356
|
+
names = readdirSync(dir);
|
|
25357
|
+
} catch {
|
|
25358
|
+
return;
|
|
25359
|
+
}
|
|
25360
|
+
for (const name of names.filter((entry) => entry.endsWith(".md"))) {
|
|
25361
|
+
const file = path5.join(dir, name);
|
|
25362
|
+
try {
|
|
25363
|
+
if (statSync2(file).mtimeMs < now - ttlDays * DAY_MS2) rmSync(file);
|
|
25364
|
+
} catch {
|
|
25365
|
+
}
|
|
25366
|
+
}
|
|
25367
|
+
}
|
|
25368
|
+
var outputCounter = 0;
|
|
25369
|
+
function writeDefaultOutput(content, dir = OUTPUT_TMP_DIR, now = /* @__PURE__ */ new Date(), ttlDays = outputTtlDays()) {
|
|
25370
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
25371
|
+
pruneOutputDir(dir, now.getTime(), ttlDays);
|
|
25372
|
+
outputCounter += 1;
|
|
25373
|
+
const file = path5.join(dir, `${now.toISOString().replace(/[:.]/g, "-")}-${process.pid}-${outputCounter}.md`);
|
|
25374
|
+
writeFileSync(file, handoffBody(path5.basename(file), content), { mode: 384, flag: "wx" });
|
|
25375
|
+
return file;
|
|
25376
|
+
}
|
|
25377
|
+
|
|
25060
25378
|
// src/tool-results.ts
|
|
25061
25379
|
var nsToMs = (value) => typeof value === "number" ? Math.round(value / 1e6) : void 0;
|
|
25062
25380
|
var object4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
@@ -25113,8 +25431,6 @@ function compactReceipt(receipt) {
|
|
|
25113
25431
|
"selected_model",
|
|
25114
25432
|
"response",
|
|
25115
25433
|
"output_check",
|
|
25116
|
-
"metrics",
|
|
25117
|
-
"telemetry",
|
|
25118
25434
|
"scope",
|
|
25119
25435
|
"warm",
|
|
25120
25436
|
"job",
|
|
@@ -25131,13 +25447,15 @@ function compactReceipt(receipt) {
|
|
|
25131
25447
|
}
|
|
25132
25448
|
const response = withoutThinking(object4(receipt.response));
|
|
25133
25449
|
if (response) compact.response = response;
|
|
25450
|
+
const metrics = object4(receipt.metrics);
|
|
25451
|
+
if (metrics) compact.metrics = Object.fromEntries(Object.entries(metrics).filter(([, value]) => value === null || typeof value !== "object"));
|
|
25134
25452
|
const route3 = pick2(object4(receipt.route), ["selected_model", "task", "confidence", "reasons", "output_reserve_tokens"]);
|
|
25135
25453
|
if (route3) compact.route = route3;
|
|
25136
25454
|
const admission = pick2(object4(receipt.admission), ["mode", "queue_wait_ms", "transition_wait_ms", "resource_wait_ms", "total_wait_ms"]);
|
|
25137
25455
|
if (admission) compact.admission = admission;
|
|
25138
25456
|
if (execution) {
|
|
25139
25457
|
compact.execution = {
|
|
25140
|
-
...pick2(execution, ["placement", "backend", "reason", "observation", "
|
|
25458
|
+
...pick2(execution, ["placement", "backend", "reason", "observation", "keep_alive", "preference_satisfied"]),
|
|
25141
25459
|
...completionPlanning ? { completion_planning: completionPlanning } : {},
|
|
25142
25460
|
...reservation ? { memory_reservation: reservation } : {}
|
|
25143
25461
|
};
|
|
@@ -25178,8 +25496,19 @@ function compactResearchResult(content) {
|
|
|
25178
25496
|
return { shaped, sections };
|
|
25179
25497
|
}
|
|
25180
25498
|
var UNSAVED_TOOLS = /* @__PURE__ */ new Set(["read_response"]);
|
|
25499
|
+
function finishedJob(tool, args, content) {
|
|
25500
|
+
if (tool !== "task_jobs" || args?.action !== "get") return void 0;
|
|
25501
|
+
const job = object4(content.job);
|
|
25502
|
+
const result = object4(job?.result);
|
|
25503
|
+
return job && result ? { job, result } : void 0;
|
|
25504
|
+
}
|
|
25181
25505
|
function shapeResult(tool, args, content, isError, saved) {
|
|
25182
25506
|
const full = args?.receipt === "full" || args?.preview === true || !saved;
|
|
25507
|
+
const finished = finishedJob(tool, args, content);
|
|
25508
|
+
if (finished) {
|
|
25509
|
+
if (full || args?.returnEmbeddings === true) return { shaped: content, sections: [] };
|
|
25510
|
+
return { shaped: { ...content, job: { ...finished.job, result: compactTaskResult(finished.result) } }, sections: ["receipt"] };
|
|
25511
|
+
}
|
|
25183
25512
|
if (tool === "delegate_research") return full ? { shaped: content, sections: [] } : compactResearchResult(content);
|
|
25184
25513
|
if (tool !== "run_task" || isError || content.deferred === true) return { shaped: content, sections: [] };
|
|
25185
25514
|
let shaped = full ? content : compactTaskResult(content);
|
|
@@ -25194,6 +25523,72 @@ function shapeResult(tool, args, content, isError, saved) {
|
|
|
25194
25523
|
}
|
|
25195
25524
|
return { shaped, sections };
|
|
25196
25525
|
}
|
|
25526
|
+
var AUTO_INLINE_CHARS = 8e3;
|
|
25527
|
+
var outputInput = {
|
|
25528
|
+
output: _enum(["auto", "inline", "file"]).optional().describe("auto (default): inline up to 8,000 chars, else a summary plus check lines and where to read the rest; inline: always whole; file: always the summary view"),
|
|
25529
|
+
outputPath: string2().min(1).optional().describe("also write the answer as Markdown here, creating or replacing it (inside the readable roots); default when shortened: FreeLlama home tmp/<timestamp>.md")
|
|
25530
|
+
};
|
|
25531
|
+
var SAVED_TEXT_SECTION = "/result/content/0/text";
|
|
25532
|
+
var KEPT_LINE = /^(check|handoff_file|verification|cited): /;
|
|
25533
|
+
var LABEL = /^\[[^\]\n]*\] /;
|
|
25534
|
+
function shortView(text3, where) {
|
|
25535
|
+
const label = LABEL.exec(text3)?.[0].trim();
|
|
25536
|
+
const lines = text3.replace(LABEL, "").split("\n");
|
|
25537
|
+
const kept = lines.filter((line2) => KEPT_LINE.test(line2));
|
|
25538
|
+
const summary = summaryOf(lines.filter((line2) => !KEPT_LINE.test(line2)).join("\n"));
|
|
25539
|
+
return [label, summary, ...kept, where].filter((part) => part !== void 0 && part !== "").join("\n");
|
|
25540
|
+
}
|
|
25541
|
+
function viewText(args, text3, responseFile) {
|
|
25542
|
+
const mode = typeof args?.output === "string" ? args.output : "auto";
|
|
25543
|
+
const shorten = mode === "file" || mode === "auto" && text3.length > AUTO_INLINE_CHARS;
|
|
25544
|
+
const answer = text3.replace(LABEL, "");
|
|
25545
|
+
const content = { source: "From FreeLlama", summary: summaryOf(answer), result: answer };
|
|
25546
|
+
let written;
|
|
25547
|
+
const notes = [];
|
|
25548
|
+
try {
|
|
25549
|
+
if (typeof args?.outputPath === "string") written = writeOutputFile(args.outputPath, content);
|
|
25550
|
+
else if (shorten) written = writeDefaultOutput(content);
|
|
25551
|
+
} catch (error2) {
|
|
25552
|
+
notes.push(`output_file not written: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
25553
|
+
}
|
|
25554
|
+
const file = written ?? (shorten ? responseFile : void 0);
|
|
25555
|
+
const tail = notes.length > 0 ? `
|
|
25556
|
+
${notes.join("\n")}` : "";
|
|
25557
|
+
if (!shorten) {
|
|
25558
|
+
return { text: `${text3}${written ? `
|
|
25559
|
+
output_file: ${written}` : ""}${tail}`, ...written ? { output_file: written } : {} };
|
|
25560
|
+
}
|
|
25561
|
+
if (!file) return { text: `${text3}${tail}
|
|
25562
|
+
not shortened (${text3.length} chars): no output file could be written and the response store is off, so this text is the only copy` };
|
|
25563
|
+
const where = written ? `output_file: ${written} (the full answer, ${text3.length} chars)` : `full answer (${text3.length} chars): read_response{file:"${responseFile}", section:"${SAVED_TEXT_SECTION}"}`;
|
|
25564
|
+
return {
|
|
25565
|
+
text: `${shortView(text3, where)}${tail}`,
|
|
25566
|
+
output: { mode, chars: text3.length, file },
|
|
25567
|
+
...written ? { output_file: written } : {},
|
|
25568
|
+
where,
|
|
25569
|
+
answer: text3
|
|
25570
|
+
};
|
|
25571
|
+
}
|
|
25572
|
+
var MIN_COPY_CHARS = 400;
|
|
25573
|
+
function withoutAnswerCopies(value, answer, pointer2) {
|
|
25574
|
+
if (typeof value === "string") return value.length >= MIN_COPY_CHARS && answer.includes(value.trim()) ? pointer2 : value;
|
|
25575
|
+
if (Array.isArray(value)) return value.map((item) => withoutAnswerCopies(item, answer, pointer2));
|
|
25576
|
+
const fields = object4(value);
|
|
25577
|
+
return fields ? Object.fromEntries(Object.entries(fields).map(([key, item]) => [key, withoutAnswerCopies(item, answer, pointer2)])) : value;
|
|
25578
|
+
}
|
|
25579
|
+
function reshapedContent(result, args, responseFile) {
|
|
25580
|
+
const parts = result.content;
|
|
25581
|
+
const first = Array.isArray(parts) ? object4(parts[0]) : void 0;
|
|
25582
|
+
if (!Array.isArray(parts) || first?.type !== "text" || typeof first.text !== "string") return void 0;
|
|
25583
|
+
const view = viewText(args, first.text, responseFile);
|
|
25584
|
+
return { view, content: [{ ...first, text: view.text }, ...parts.slice(1)] };
|
|
25585
|
+
}
|
|
25586
|
+
function hasAnswerText(tool, args, content, isError) {
|
|
25587
|
+
if (isError) return false;
|
|
25588
|
+
if (finishedJob(tool, args, content)) return true;
|
|
25589
|
+
if (tool !== "run_task" && tool !== "delegate_research") return false;
|
|
25590
|
+
return args?.preview !== true && args?.defer !== true && content.deferred !== true;
|
|
25591
|
+
}
|
|
25197
25592
|
function resultModel(content) {
|
|
25198
25593
|
const model = content?.selected_model ?? object4(content?.route)?.selected_model;
|
|
25199
25594
|
return typeof model === "string" && model ? model : void 0;
|
|
@@ -25206,13 +25601,19 @@ async function finishResult(tool, args, result, elapsedMs, startedAt, store2 = r
|
|
|
25206
25601
|
return file ? { ...timed, _meta: { ...timed._meta, "freellama/response_file": file } } : timed;
|
|
25207
25602
|
}
|
|
25208
25603
|
const { shaped, sections } = shapeResult(tool, object4(args), content, timed.isError === true, file !== void 0);
|
|
25604
|
+
const reshaped = hasAnswerText(tool, object4(args), content, timed.isError === true) ? reshapedContent(timed, object4(args), file) : void 0;
|
|
25605
|
+
const { where, answer } = reshaped?.view ?? {};
|
|
25606
|
+
const lean = where !== void 0 && answer !== void 0 ? withoutAnswerCopies(shaped, answer, `answer moved: ${where}`) : shaped;
|
|
25209
25607
|
return {
|
|
25210
25608
|
...timed,
|
|
25609
|
+
...reshaped ? { content: reshaped.content } : {},
|
|
25211
25610
|
structuredContent: {
|
|
25212
|
-
...
|
|
25611
|
+
...lean,
|
|
25213
25612
|
timing: content.timing,
|
|
25214
25613
|
...file ? { response_file: file } : {},
|
|
25215
|
-
...file && sections.length > 0 ? { sections } : {}
|
|
25614
|
+
...file && sections.length > 0 ? { sections } : {},
|
|
25615
|
+
...reshaped?.view.output ? { output: reshaped.view.output } : {},
|
|
25616
|
+
...reshaped?.view.output_file ? { output_file: reshaped.view.output_file } : {}
|
|
25216
25617
|
}
|
|
25217
25618
|
};
|
|
25218
25619
|
}
|
|
@@ -25267,16 +25668,16 @@ function taskNotices(content) {
|
|
|
25267
25668
|
}
|
|
25268
25669
|
|
|
25269
25670
|
// src/docs-resources.ts
|
|
25270
|
-
import { existsSync as existsSync3, readdirSync } from "node:fs";
|
|
25671
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2 } from "node:fs";
|
|
25271
25672
|
import { readFile as readFile2 } from "node:fs/promises";
|
|
25272
|
-
import
|
|
25673
|
+
import path6 from "node:path";
|
|
25273
25674
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
25274
25675
|
function registerDocsResources(server2) {
|
|
25275
|
-
const PACKAGED_DOCS_DIR =
|
|
25276
|
-
if (!existsSync3(
|
|
25676
|
+
const PACKAGED_DOCS_DIR = path6.resolve(path6.dirname(fileURLToPath3(import.meta.url)), "..", "docs");
|
|
25677
|
+
if (!existsSync3(path6.join(PACKAGED_DOCS_DIR, "README.md"))) {
|
|
25277
25678
|
throw new Error(`FreeLlama MCP documentation index is missing at ${PACKAGED_DOCS_DIR}/README.md; run the package build.`);
|
|
25278
25679
|
}
|
|
25279
|
-
const packagedDocs =
|
|
25680
|
+
const packagedDocs = readdirSync2(PACKAGED_DOCS_DIR, { recursive: true, encoding: "utf8" }).map((name) => name.split(path6.sep).join("/")).filter((name) => name.endsWith(".md")).sort();
|
|
25280
25681
|
for (const name of packagedDocs) {
|
|
25281
25682
|
const isIndex = name === "README.md";
|
|
25282
25683
|
const slug = isIndex ? "index" : name.replace(/\.md$/, "");
|
|
@@ -25289,14 +25690,14 @@ function registerDocsResources(server2) {
|
|
|
25289
25690
|
description: isIndex ? "Index of packaged FreeLlama operator and agent documentation. Read this first, then fetch one relevant document." : `Packaged FreeLlama documentation: ${name}.`
|
|
25290
25691
|
},
|
|
25291
25692
|
async () => ({
|
|
25292
|
-
contents: [{ uri, mimeType: "text/markdown", text: await readFile2(
|
|
25693
|
+
contents: [{ uri, mimeType: "text/markdown", text: await readFile2(path6.join(PACKAGED_DOCS_DIR, name), "utf8") }]
|
|
25293
25694
|
})
|
|
25294
25695
|
);
|
|
25295
25696
|
}
|
|
25296
25697
|
}
|
|
25297
25698
|
|
|
25298
25699
|
// src/builtin-prompts.ts
|
|
25299
|
-
import
|
|
25700
|
+
import path7 from "node:path";
|
|
25300
25701
|
|
|
25301
25702
|
// ../rust-core/src/model_catalog.json
|
|
25302
25703
|
var model_catalog_default = {
|
|
@@ -25800,235 +26201,15 @@ Verify: local answers are candidates. Each run_task result ends with check: line
|
|
|
25800
26201
|
|
|
25801
26202
|
Coding: the answer is SEARCH/REPLACE blocks. ${APPLY_EDITS}
|
|
25802
26203
|
|
|
25803
|
-
Hand a result on: saveAs:"<name>" writes the answer to handoffs/<name>.md and returns handoff_file; a later run_task reads it with files:[handoff_file].
|
|
26204
|
+
Hand a result on: saveAs:"<name>" writes the answer to handoffs/<name>.md and returns handoff_file; a later run_task reads it with files:[handoff_file]. Answers over 8,000 characters come back as a summary with check: lines and an output_file holding the whole answer; output:"inline" returns the whole text, outputPath:"<path>" writes (or replaces) that file inside your project.
|
|
25804
26205
|
|
|
25805
26206
|
Approval: pull a model only after the user approves one exact tag and its size (ollama_pull approvedSizeBytes). Unload only a model you warmed or the user named; delete only a tag the user names. A memory refusal names what holds memory: tell the user and never stop their processes.`;
|
|
25806
26207
|
|
|
25807
|
-
// src/task-files.ts
|
|
25808
|
-
import { globSync, realpathSync as realpathSync2, statSync, readFileSync } from "node:fs";
|
|
25809
|
-
import { homedir as homedir2, tmpdir } from "node:os";
|
|
25810
|
-
import path5 from "node:path";
|
|
25811
|
-
var MAX_TEXT_FILE_BYTES = 4 * 1024 * 1024;
|
|
25812
|
-
var MAX_IMAGE_FILE_BYTES = 8 * 1024 * 1024;
|
|
25813
|
-
var MAX_TOTAL_FILE_BYTES = 32 * 1024 * 1024;
|
|
25814
|
-
var MAX_FILES = 2e3;
|
|
25815
|
-
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".webp", ".gif"]);
|
|
25816
|
-
var AUDIO_EXTENSIONS = /* @__PURE__ */ new Set([".wav"]);
|
|
25817
|
-
var EMBEDDING_CHUNK_BYTES = 1536;
|
|
25818
|
-
var CHUNK_OVERLAP_LINES = 8;
|
|
25819
|
-
function chunkLines(text3, limit = EMBEDDING_CHUNK_BYTES) {
|
|
25820
|
-
const lines = text3.split("\n");
|
|
25821
|
-
const chunks = [];
|
|
25822
|
-
let current = [];
|
|
25823
|
-
let bytes = 0;
|
|
25824
|
-
let start2 = 1;
|
|
25825
|
-
const flush = (end, overlap = 0) => {
|
|
25826
|
-
if (current.length > 0 && current.join("\n").trim()) chunks.push({ text: current.join("\n"), lines: [start2, end] });
|
|
25827
|
-
const carried = overlap > 0 ? current.slice(-overlap) : [];
|
|
25828
|
-
const carriedBytes = carried.reduce((sum, line2) => sum + Buffer.byteLength(line2) + 1, 0);
|
|
25829
|
-
current = carriedBytes < limit / 4 ? carried : [];
|
|
25830
|
-
bytes = current === carried ? carriedBytes : 0;
|
|
25831
|
-
start2 = end + 1 - current.length;
|
|
25832
|
-
};
|
|
25833
|
-
lines.forEach((line2, index) => {
|
|
25834
|
-
const size2 = Buffer.byteLength(line2) + 1;
|
|
25835
|
-
if (bytes + size2 > limit && current.length > 0) flush(index, CHUNK_OVERLAP_LINES);
|
|
25836
|
-
if (size2 > limit) {
|
|
25837
|
-
for (let at = 0; at < line2.length; at += Math.floor(limit / 2)) {
|
|
25838
|
-
chunks.push({ text: line2.slice(at, at + Math.floor(limit / 2)), lines: [index + 1, index + 1] });
|
|
25839
|
-
}
|
|
25840
|
-
start2 = index + 2;
|
|
25841
|
-
return;
|
|
25842
|
-
}
|
|
25843
|
-
current.push(line2);
|
|
25844
|
-
bytes += size2;
|
|
25845
|
-
});
|
|
25846
|
-
flush(lines.length);
|
|
25847
|
-
return chunks;
|
|
25848
|
-
}
|
|
25849
|
-
function fileRoots(env = process.env, cwd = process.cwd(), home = homedir2()) {
|
|
25850
|
-
const configured = (env.FREELLAMA_FILE_ROOTS ?? "").split(path5.delimiter).filter(Boolean);
|
|
25851
|
-
const broad = /* @__PURE__ */ new Set([path5.parse(path5.resolve(cwd)).root, resolvedRoot(home)]);
|
|
25852
|
-
const project = broad.has(resolvedRoot(cwd)) ? [] : [cwd];
|
|
25853
|
-
const handoffs = path5.join(env.FREELLAMA_HOME || path5.join(home, ".octocode", "freellama"), "handoffs");
|
|
25854
|
-
return [...project, tmpdir(), ...configured, handoffs].map(resolvedRoot);
|
|
25855
|
-
}
|
|
25856
|
-
function resolvedRoot(root) {
|
|
25857
|
-
try {
|
|
25858
|
-
return realpathSync2(root);
|
|
25859
|
-
} catch {
|
|
25860
|
-
const absolute = path5.resolve(root);
|
|
25861
|
-
const parent = path5.dirname(absolute);
|
|
25862
|
-
return parent === absolute ? absolute : path5.join(resolvedRoot(parent), path5.basename(absolute));
|
|
25863
|
-
}
|
|
25864
|
-
}
|
|
25865
|
-
function within(file, root) {
|
|
25866
|
-
const relative = path5.relative(root, file);
|
|
25867
|
-
return relative === "" || !relative.startsWith("..") && !path5.isAbsolute(relative);
|
|
25868
|
-
}
|
|
25869
|
-
function isWav(bytes) {
|
|
25870
|
-
return bytes.length >= 12 && bytes.subarray(0, 4).toString() === "RIFF" && bytes.subarray(8, 12).toString() === "WAVE";
|
|
25871
|
-
}
|
|
25872
|
-
function expandFileEntries(entries, cwd = process.cwd()) {
|
|
25873
|
-
const out = [];
|
|
25874
|
-
for (const entry of entries) {
|
|
25875
|
-
if (!/[*?[]/.test(entry)) {
|
|
25876
|
-
out.push(entry);
|
|
25877
|
-
continue;
|
|
25878
|
-
}
|
|
25879
|
-
const matches = globSync(entry, { cwd }).map((match) => path5.resolve(cwd, match)).filter((match) => statSync(match).isFile()).sort();
|
|
25880
|
-
if (matches.length === 0) throw new Error(`no files match ${entry} (globs resolve from ${cwd})`);
|
|
25881
|
-
out.push(...matches);
|
|
25882
|
-
if (out.length > MAX_FILES) break;
|
|
25883
|
-
}
|
|
25884
|
-
return out;
|
|
25885
|
-
}
|
|
25886
|
-
function readFiles(entries, givenRoots) {
|
|
25887
|
-
const roots = givenRoots.map(resolvedRoot);
|
|
25888
|
-
const paths = expandFileEntries(entries);
|
|
25889
|
-
if (paths.length > MAX_FILES) throw new Error(`files names ${paths.length} paths; at most ${MAX_FILES} per call`);
|
|
25890
|
-
let total = 0;
|
|
25891
|
-
return paths.map((name) => {
|
|
25892
|
-
const absolute = path5.resolve(name);
|
|
25893
|
-
let real;
|
|
25894
|
-
try {
|
|
25895
|
-
real = realpathSync2(absolute);
|
|
25896
|
-
} catch {
|
|
25897
|
-
throw new Error(`file not found: ${name}`);
|
|
25898
|
-
}
|
|
25899
|
-
if (!roots.some((root) => within(real, root))) {
|
|
25900
|
-
throw new Error(
|
|
25901
|
-
`file ${name} is outside the readable roots (${roots.join(", ")}); copy it into the project or temp directory, or add its directory to FREELLAMA_FILE_ROOTS`
|
|
25902
|
-
);
|
|
25903
|
-
}
|
|
25904
|
-
const stat3 = statSync(real);
|
|
25905
|
-
if (!stat3.isFile()) throw new Error(`not a regular file: ${name}`);
|
|
25906
|
-
const extension = path5.extname(real).toLowerCase();
|
|
25907
|
-
let media = AUDIO_EXTENSIONS.has(extension) ? "audio" : IMAGE_EXTENSIONS.has(extension) ? "image" : void 0;
|
|
25908
|
-
const limit = media ? MAX_IMAGE_FILE_BYTES : MAX_TEXT_FILE_BYTES;
|
|
25909
|
-
if (stat3.size > limit) {
|
|
25910
|
-
throw new Error(`file ${name} is ${stat3.size} bytes; the limit is ${limit} for ${media ?? "text"}; split it or send the part that matters`);
|
|
25911
|
-
}
|
|
25912
|
-
total += stat3.size;
|
|
25913
|
-
if (total > MAX_TOTAL_FILE_BYTES) throw new Error(`files total more than ${MAX_TOTAL_FILE_BYTES} bytes; split the call`);
|
|
25914
|
-
const bytes = readFileSync(real);
|
|
25915
|
-
if (media === "audio" && !isWav(bytes)) {
|
|
25916
|
-
throw new Error(`audio file ${name} must contain a WAV RIFF/WAVE header`);
|
|
25917
|
-
}
|
|
25918
|
-
if (isWav(bytes)) media = "audio";
|
|
25919
|
-
return { path: name, bytes, media };
|
|
25920
|
-
});
|
|
25921
|
-
}
|
|
25922
|
-
function shortPath(name) {
|
|
25923
|
-
const relative = path5.relative(process.cwd(), path5.resolve(name));
|
|
25924
|
-
return relative && !relative.startsWith("..") && !path5.isAbsolute(relative) ? relative : name;
|
|
25925
|
-
}
|
|
25926
|
-
function leadingComment(body) {
|
|
25927
|
-
const lines = [];
|
|
25928
|
-
for (const line2 of body.split("\n").slice(0, 20)) {
|
|
25929
|
-
const trimmed = line2.trim();
|
|
25930
|
-
if (!/^(\/\/|#(?!include)|\/\*|\*|"""|\s*$)/.test(trimmed) || trimmed.startsWith("#!")) break;
|
|
25931
|
-
if (trimmed) lines.push(trimmed.replace(/^(\/\/[!/]?|#+|\/\*+|\*\/?|""")\s?/, ""));
|
|
25932
|
-
}
|
|
25933
|
-
return lines.join(" ").slice(0, 400);
|
|
25934
|
-
}
|
|
25935
|
-
var MAX_AUDIO_FILE_BYTES = 256 * 1024 * 1024;
|
|
25936
|
-
function readAllowedFile(name, limit, roots = fileRoots()) {
|
|
25937
|
-
const allowed = roots.map(resolvedRoot);
|
|
25938
|
-
let real;
|
|
25939
|
-
try {
|
|
25940
|
-
real = realpathSync2(path5.resolve(name));
|
|
25941
|
-
} catch {
|
|
25942
|
-
throw new Error(`file not found: ${name}`);
|
|
25943
|
-
}
|
|
25944
|
-
if (!allowed.some((root) => within(real, root))) {
|
|
25945
|
-
throw new Error(`file ${name} is outside the readable roots (${allowed.join(", ")}); copy it into the project or temp directory, or add its directory to FREELLAMA_FILE_ROOTS`);
|
|
25946
|
-
}
|
|
25947
|
-
const stat3 = statSync(real);
|
|
25948
|
-
if (!stat3.isFile()) throw new Error(`not a regular file: ${name}`);
|
|
25949
|
-
if (stat3.size > limit) throw new Error(`file ${name} is ${stat3.size} bytes; the limit is ${limit}`);
|
|
25950
|
-
return readFileSync(real);
|
|
25951
|
-
}
|
|
25952
|
-
function text(file) {
|
|
25953
|
-
if (file.bytes.includes(0)) throw new Error(`file ${file.path} is binary; only images (${[...IMAGE_EXTENSIONS].join(", ")}) and text files can be read`);
|
|
25954
|
-
return file.bytes.toString("utf8");
|
|
25955
|
-
}
|
|
25956
|
-
function applyTaskFiles(payload, paths, roots = fileRoots()) {
|
|
25957
|
-
if (paths === void 0 || paths.length === 0) return { payload, files: [] };
|
|
25958
|
-
const read = readFiles(paths, roots);
|
|
25959
|
-
const receipts = [];
|
|
25960
|
-
const media = read.filter((file) => file.media !== void 0);
|
|
25961
|
-
const texts = read.filter((file) => file.media === void 0);
|
|
25962
|
-
const next = { ...payload };
|
|
25963
|
-
const task = payload.task ?? "completion";
|
|
25964
|
-
if (task === "embedding") {
|
|
25965
|
-
const given = payload.input === void 0 ? [] : Array.isArray(payload.input) ? payload.input : [payload.input];
|
|
25966
|
-
const prefix = payload.documentPrefix ?? "";
|
|
25967
|
-
const items = [...given];
|
|
25968
|
-
for (const file of read) {
|
|
25969
|
-
if (file.media) {
|
|
25970
|
-
const encoded = file.bytes.toString("base64");
|
|
25971
|
-
items.push(file.media === "audio" ? { audio: encoded } : { image: encoded });
|
|
25972
|
-
receipts.push({ path: file.path, bytes: file.bytes.length, role: "input" });
|
|
25973
|
-
continue;
|
|
25974
|
-
}
|
|
25975
|
-
const body = text(file);
|
|
25976
|
-
const header = `${shortPath(file.path)}
|
|
25977
|
-
${leadingComment(body)}`.trimEnd();
|
|
25978
|
-
for (const chunk of chunkLines(body)) {
|
|
25979
|
-
items.push(`${prefix}${header}
|
|
25980
|
-
${chunk.text}`);
|
|
25981
|
-
receipts.push({ path: file.path, bytes: Buffer.byteLength(chunk.text), role: "input", lines: chunk.lines });
|
|
25982
|
-
}
|
|
25983
|
-
}
|
|
25984
|
-
next.input = items;
|
|
25985
|
-
return { payload: next, files: receipts };
|
|
25986
|
-
}
|
|
25987
|
-
const audio = media.find((file) => file.media === "audio");
|
|
25988
|
-
if (audio && task !== "decision") throw new Error(`audio file ${audio.path} is read only by tasks "decision" and "embedding" (models with audio input)`);
|
|
25989
|
-
if (media.length > 0) {
|
|
25990
|
-
next.images = [...payload.images ?? [], ...media.map((file) => file.bytes.toString("base64"))];
|
|
25991
|
-
for (const file of media) receipts.push({ path: file.path, bytes: file.bytes.length, role: file.media });
|
|
25992
|
-
}
|
|
25993
|
-
if (task === "decision") {
|
|
25994
|
-
if (texts.length > 0 && payload.state !== void 0) throw new Error("give the decision state either as `state` or as text `files`, not both");
|
|
25995
|
-
if (texts.length === 1) {
|
|
25996
|
-
const body = text(texts[0]);
|
|
25997
|
-
next.state = jsonOrText(body);
|
|
25998
|
-
} else if (texts.length > 1) {
|
|
25999
|
-
next.state = Object.fromEntries(texts.map((file) => [file.path, text(file)]));
|
|
26000
|
-
}
|
|
26001
|
-
for (const file of texts) receipts.push({ path: file.path, bytes: file.bytes.length, role: "state" });
|
|
26002
|
-
return { payload: next, files: receipts };
|
|
26003
|
-
}
|
|
26004
|
-
if (texts.length > 0) {
|
|
26005
|
-
if (payload.prompt === void 0) throw new Error("text `files` are appended to `prompt`; give the instructions in `prompt`");
|
|
26006
|
-
const sections = texts.map((file) => `--- file: ${file.path} ---
|
|
26007
|
-
${text(file)}`);
|
|
26008
|
-
next.prompt = [payload.prompt, ...sections].join("\n\n");
|
|
26009
|
-
for (const file of texts) receipts.push({ path: file.path, bytes: file.bytes.length, role: "prompt" });
|
|
26010
|
-
}
|
|
26011
|
-
return { payload: next, files: receipts };
|
|
26012
|
-
}
|
|
26013
|
-
function jsonOrText(body) {
|
|
26014
|
-
try {
|
|
26015
|
-
const value = JSON.parse(body);
|
|
26016
|
-
if (value !== null && typeof value === "object") return value;
|
|
26017
|
-
} catch {
|
|
26018
|
-
}
|
|
26019
|
-
return body;
|
|
26020
|
-
}
|
|
26021
|
-
function inputLabels(inputCount, files) {
|
|
26022
|
-
const fromFiles = files.filter((file) => file.role === "input").map((file) => file.lines ? `${shortPath(file.path)}:L${file.lines[0]}-${file.lines[1]}` : shortPath(file.path));
|
|
26023
|
-
const given = inputCount - fromFiles.length;
|
|
26024
|
-
return [...Array.from({ length: given }, (_, index) => `input[${index}]`), ...fromFiles];
|
|
26025
|
-
}
|
|
26026
|
-
|
|
26027
26208
|
// src/builtin-prompts.ts
|
|
26028
26209
|
var list = (value) => value.split(/[,\n]/).map((entry) => entry.trim()).filter(Boolean);
|
|
26029
26210
|
function short(file) {
|
|
26030
|
-
const relative =
|
|
26031
|
-
return relative && !relative.startsWith("..") && !
|
|
26211
|
+
const relative = path7.relative(process.cwd(), path7.resolve(file));
|
|
26212
|
+
return relative && !relative.startsWith("..") && !path7.isAbsolute(relative) ? relative : file;
|
|
26032
26213
|
}
|
|
26033
26214
|
function perFile(files, shared, item = {}) {
|
|
26034
26215
|
let paths = list(files);
|
|
@@ -26236,7 +26417,7 @@ function installedModelsText(data) {
|
|
|
26236
26417
|
].filter(Boolean).join(" ")
|
|
26237
26418
|
);
|
|
26238
26419
|
}
|
|
26239
|
-
lines.push('Context, policy rank and benchmark per model:
|
|
26420
|
+
lines.push('Context, policy rank and benchmark per model: models{view:"installed", includeVerbose:true}; one model: models{view:"detail", model}.');
|
|
26240
26421
|
return lines.join("\n");
|
|
26241
26422
|
}
|
|
26242
26423
|
function residentModelsText(data) {
|
|
@@ -27154,6 +27335,32 @@ var modelsResultSchema = object2({
|
|
|
27154
27335
|
knowledge: unknown().optional(),
|
|
27155
27336
|
library: unknown().optional()
|
|
27156
27337
|
}).loose();
|
|
27338
|
+
var SLIM_MODEL_KEYS = [
|
|
27339
|
+
"name",
|
|
27340
|
+
"model",
|
|
27341
|
+
"size",
|
|
27342
|
+
"capabilities",
|
|
27343
|
+
"resident",
|
|
27344
|
+
"resident_size",
|
|
27345
|
+
"resident_vram",
|
|
27346
|
+
"resident_context",
|
|
27347
|
+
"advertised_context",
|
|
27348
|
+
"placement"
|
|
27349
|
+
];
|
|
27350
|
+
function slimModel(entry, includeKnowledge) {
|
|
27351
|
+
const slim = {};
|
|
27352
|
+
for (const key of SLIM_MODEL_KEYS) if (entry[key] !== void 0) slim[key] = entry[key];
|
|
27353
|
+
const execution = entry.execution;
|
|
27354
|
+
if (execution && typeof execution === "object") {
|
|
27355
|
+
slim.execution = {
|
|
27356
|
+
...execution.placement !== void 0 ? { placement: execution.placement } : {},
|
|
27357
|
+
...execution.upstream !== void 0 ? { upstream: execution.upstream } : {},
|
|
27358
|
+
...execution.observation !== void 0 ? { observation: execution.observation } : {}
|
|
27359
|
+
};
|
|
27360
|
+
}
|
|
27361
|
+
if (includeKnowledge && entry.knowledge !== void 0) slim.knowledge = entry.knowledge;
|
|
27362
|
+
return slim;
|
|
27363
|
+
}
|
|
27157
27364
|
var HOST_MEMORY_FIT_FRACTION = 0.6;
|
|
27158
27365
|
var HUB_TASKS = /* @__PURE__ */ new Map([
|
|
27159
27366
|
["embedding", ["feature-extraction", "sentence-similarity"]],
|
|
@@ -27382,8 +27589,8 @@ function registerModelsTool(server2) {
|
|
|
27382
27589
|
if (selectedView !== "library" && selectedView !== "raw" && [limit, cursor].some((value) => value !== void 0)) {
|
|
27383
27590
|
return errorResult(new Error(`\`limit\` and \`cursor\` apply only to views "library" and "raw", not "${selectedView}".`));
|
|
27384
27591
|
}
|
|
27385
|
-
if (selectedView !== "detail" && includeVerbose !== void 0) {
|
|
27386
|
-
return errorResult(new Error('`includeVerbose` is valid only for
|
|
27592
|
+
if (selectedView !== "detail" && selectedView !== "installed" && selectedView !== "resident" && includeVerbose !== void 0) {
|
|
27593
|
+
return errorResult(new Error('`includeVerbose` is valid only for views "installed", "resident" and "detail".'));
|
|
27387
27594
|
}
|
|
27388
27595
|
if (selectedView !== "detail" && selectedView !== "library" && model !== void 0) {
|
|
27389
27596
|
return errorResult(new Error('`model` is valid only for views "detail" and "library".'));
|
|
@@ -27417,7 +27624,8 @@ function registerModelsTool(server2) {
|
|
|
27417
27624
|
}
|
|
27418
27625
|
};
|
|
27419
27626
|
});
|
|
27420
|
-
|
|
27627
|
+
const shown2 = includeVerbose ? models : models.map((entry) => slimModel(entry, false));
|
|
27628
|
+
return structuredResult({ ...data, models: shown2 }, { text: residentModelsText({ models }) });
|
|
27421
27629
|
}
|
|
27422
27630
|
case "detail": {
|
|
27423
27631
|
if (!model) {
|
|
@@ -27463,7 +27671,8 @@ function registerModelsTool(server2) {
|
|
|
27463
27671
|
includeLibrary ?? false,
|
|
27464
27672
|
async (upstream) => (await ollamaFetch(upstream ?? ollamaEndpoint, "/api/tags")).models ?? []
|
|
27465
27673
|
) };
|
|
27466
|
-
|
|
27674
|
+
const shown2 = includeVerbose ? installed : { ...installed, models: installed.models.map((entry) => slimModel(entry, includeLibrary === true)) };
|
|
27675
|
+
return structuredResult(shown2, { text: installedModelsText(installed) });
|
|
27467
27676
|
}
|
|
27468
27677
|
}
|
|
27469
27678
|
} catch (error2) {
|
|
@@ -27721,36 +27930,6 @@ function mergeProfiles(job, profiles) {
|
|
|
27721
27930
|
return { job, model: models.length === 1 ? models[0] : models.join(", ") || void 0, applied: [...new Set(profiles.flatMap((profile) => profile.applied))] };
|
|
27722
27931
|
}
|
|
27723
27932
|
|
|
27724
|
-
// src/handoffs.ts
|
|
27725
|
-
init_config();
|
|
27726
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
27727
|
-
import path7 from "node:path";
|
|
27728
|
-
var HANDOFF_DIR = path7.join(FREELLAMA_HOME, "handoffs");
|
|
27729
|
-
var HANDOFF_NAME = /^[a-z0-9-]{1,60}$/;
|
|
27730
|
-
function summaryOf(text3, lines = 5) {
|
|
27731
|
-
const kept = text3.split("\n").filter((line2) => line2.trim()).slice(0, lines).join("\n");
|
|
27732
|
-
return kept.length > 600 ? `${kept.slice(0, 600)}\u2026` : kept;
|
|
27733
|
-
}
|
|
27734
|
-
function writeHandoff(name, content, dir = HANDOFF_DIR) {
|
|
27735
|
-
if (!HANDOFF_NAME.test(name)) throw new Error("saveAs is 1 to 60 characters of a-z, 0-9 and -");
|
|
27736
|
-
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
27737
|
-
const file = path7.join(dir, `${name}.md`);
|
|
27738
|
-
const body = `# ${name}
|
|
27739
|
-
|
|
27740
|
-
${content.source}, ${(/* @__PURE__ */ new Date()).toISOString()}.
|
|
27741
|
-
|
|
27742
|
-
## Summary
|
|
27743
|
-
|
|
27744
|
-
${content.summary.trim() || "(empty)"}
|
|
27745
|
-
|
|
27746
|
-
## Result
|
|
27747
|
-
|
|
27748
|
-
${content.result.trim() || "(empty)"}
|
|
27749
|
-
`;
|
|
27750
|
-
writeFileSync(file, body, { mode: 384 });
|
|
27751
|
-
return file;
|
|
27752
|
-
}
|
|
27753
|
-
|
|
27754
27933
|
// src/result-checks.ts
|
|
27755
27934
|
import { readFileSync as readFileSync2 } from "node:fs";
|
|
27756
27935
|
var object5 = (value) => value !== null && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
@@ -28469,6 +28648,7 @@ function registerRunTaskTool(server2) {
|
|
|
28469
28648
|
receipt: _enum(["compact", "full"]).optional().describe("compact (default): full receipt at response_file; full: inline"),
|
|
28470
28649
|
defer: boolean2().optional(),
|
|
28471
28650
|
saveAs: string2().regex(HANDOFF_NAME).optional().describe("answer to handoffs/<name>.md, returned as handoff_file"),
|
|
28651
|
+
...outputInput,
|
|
28472
28652
|
returnEmbeddings: boolean2().optional(),
|
|
28473
28653
|
nearest: object2({
|
|
28474
28654
|
query: string2().min(1).optional(),
|
|
@@ -28491,6 +28671,7 @@ function registerRunTaskTool(server2) {
|
|
|
28491
28671
|
job,
|
|
28492
28672
|
workflow,
|
|
28493
28673
|
saveAs,
|
|
28674
|
+
outputPath,
|
|
28494
28675
|
objective,
|
|
28495
28676
|
model,
|
|
28496
28677
|
sessionId,
|
|
@@ -28528,6 +28709,10 @@ function registerRunTaskTool(server2) {
|
|
|
28528
28709
|
maxParallelism
|
|
28529
28710
|
}, extra) => {
|
|
28530
28711
|
try {
|
|
28712
|
+
if (outputPath !== void 0) {
|
|
28713
|
+
if (preview || defer) return errorResult(new Error("`outputPath` saves a finished answer; it is not valid with `preview` or `defer`."));
|
|
28714
|
+
checkOutputPath(outputPath);
|
|
28715
|
+
}
|
|
28531
28716
|
if (workflow !== void 0) {
|
|
28532
28717
|
const flowJob = WORKFLOWS[workflow].job;
|
|
28533
28718
|
if (job !== void 0 && job !== flowJob) return errorResult(new Error(`workflow "${workflow}" runs job "${flowJob}"; remove \`job\`.`));
|
|
@@ -28832,7 +29017,8 @@ function registerTaskJobsTool(server2) {
|
|
|
28832
29017
|
endpoint: endpointParam,
|
|
28833
29018
|
action: _enum(["list", "get", "cancel", "remove"]),
|
|
28834
29019
|
jobId: string2().uuid().optional(),
|
|
28835
|
-
returnEmbeddings: boolean2().optional()
|
|
29020
|
+
returnEmbeddings: boolean2().optional(),
|
|
29021
|
+
output: outputInput.output.describe("get only; as run_task")
|
|
28836
29022
|
}).strict(),
|
|
28837
29023
|
outputSchema: objectResultSchema,
|
|
28838
29024
|
// One tool for reads and changes, so not read-only (the spec default, not restated). Not destructive either: cancel and remove
|
|
@@ -28840,13 +29026,13 @@ function registerTaskJobsTool(server2) {
|
|
|
28840
29026
|
// outside FreeLlama, and a job's input can be submitted again.
|
|
28841
29027
|
annotations: { destructiveHint: false }
|
|
28842
29028
|
},
|
|
28843
|
-
async ({ endpoint, action, jobId, returnEmbeddings }) => {
|
|
29029
|
+
async ({ endpoint, action, jobId, returnEmbeddings, output }) => {
|
|
28844
29030
|
try {
|
|
28845
29031
|
if (action === "list" ? jobId !== void 0 : jobId === void 0) {
|
|
28846
29032
|
return errorResult(new Error("Use jobId only with get/cancel/remove; all require a jobId."));
|
|
28847
29033
|
}
|
|
28848
|
-
if (returnEmbeddings !== void 0 && action !== "get") {
|
|
28849
|
-
return errorResult(new Error("returnEmbeddings
|
|
29034
|
+
if ((returnEmbeddings !== void 0 || output !== void 0) && action !== "get") {
|
|
29035
|
+
return errorResult(new Error("returnEmbeddings and output are valid only with get."));
|
|
28850
29036
|
}
|
|
28851
29037
|
const result = parsedResult(action === "list" ? await listTaskJobs2(endpoint) : action === "get" ? await getTaskJob2(endpoint, jobId) : action === "cancel" ? await cancelTaskJob2(endpoint, jobId) : await removeTaskJob2(endpoint, jobId));
|
|
28852
29038
|
if (!returnEmbeddings && isStructuredSuccess(result)) {
|
|
@@ -29607,7 +29793,7 @@ function cancelOpenJobs(openJobs, cancel) {
|
|
|
29607
29793
|
}
|
|
29608
29794
|
|
|
29609
29795
|
// src/research/source-evidence.ts
|
|
29610
|
-
import { realpathSync as
|
|
29796
|
+
import { realpathSync as realpathSync4, statSync as statSync3 } from "node:fs";
|
|
29611
29797
|
import path9 from "node:path";
|
|
29612
29798
|
|
|
29613
29799
|
// src/research/canonical-json.ts
|
|
@@ -29743,11 +29929,11 @@ var READS = /* @__PURE__ */ new Set(["cat", "head", "tail", "nl", "wc", "diff",
|
|
|
29743
29929
|
function sourcePath(value, workspace) {
|
|
29744
29930
|
if (!value || value.startsWith("-") || /[*?$`]/.test(value)) return false;
|
|
29745
29931
|
try {
|
|
29746
|
-
const root =
|
|
29747
|
-
const source =
|
|
29932
|
+
const root = realpathSync4(workspace);
|
|
29933
|
+
const source = realpathSync4(path9.resolve(root, value));
|
|
29748
29934
|
const relative = path9.relative(root, source);
|
|
29749
29935
|
if (relative === ".." || relative.startsWith(`..${path9.sep}`) || path9.isAbsolute(relative)) return false;
|
|
29750
|
-
const stat3 =
|
|
29936
|
+
const stat3 = statSync3(source);
|
|
29751
29937
|
return stat3.isFile() || stat3.isDirectory();
|
|
29752
29938
|
} catch {
|
|
29753
29939
|
return false;
|
|
@@ -29826,7 +30012,7 @@ function searchHitCitations(steps, answer, workspace) {
|
|
|
29826
30012
|
}
|
|
29827
30013
|
|
|
29828
30014
|
// src/delegate.ts
|
|
29829
|
-
import { existsSync as existsSync5, readFileSync as readFileSync3, realpathSync as
|
|
30015
|
+
import { existsSync as existsSync5, readFileSync as readFileSync3, realpathSync as realpathSync5, statSync as statSync4 } from "node:fs";
|
|
29830
30016
|
import path11 from "node:path";
|
|
29831
30017
|
init_config();
|
|
29832
30018
|
function researchErrorResult(error2, diagnostic) {
|
|
@@ -29962,7 +30148,7 @@ function assessDelegatedAnswer(question, evidenceCount, model, verifiedDigest) {
|
|
|
29962
30148
|
function extractExistingWorkspacePath(command, workspace) {
|
|
29963
30149
|
let root;
|
|
29964
30150
|
try {
|
|
29965
|
-
root =
|
|
30151
|
+
root = realpathSync5(workspace);
|
|
29966
30152
|
} catch {
|
|
29967
30153
|
return null;
|
|
29968
30154
|
}
|
|
@@ -29976,10 +30162,10 @@ function extractExistingWorkspacePath(command, workspace) {
|
|
|
29976
30162
|
const candidate = path11.isAbsolute(token) ? token : path11.resolve(root, token);
|
|
29977
30163
|
if (!existsSync5(candidate)) continue;
|
|
29978
30164
|
try {
|
|
29979
|
-
const resolved =
|
|
30165
|
+
const resolved = realpathSync5(candidate);
|
|
29980
30166
|
const relative = path11.relative(root, resolved);
|
|
29981
30167
|
const withinRoot = relative && relative !== ".." && !relative.startsWith(`..${path11.sep}`);
|
|
29982
|
-
if (withinRoot &&
|
|
30168
|
+
if (withinRoot && statSync4(resolved).isFile()) return relative;
|
|
29983
30169
|
} catch {
|
|
29984
30170
|
}
|
|
29985
30171
|
}
|
|
@@ -30022,7 +30208,7 @@ function parseAdapterResult(raw) {
|
|
|
30022
30208
|
}
|
|
30023
30209
|
|
|
30024
30210
|
// src/tools/coder.ts
|
|
30025
|
-
import { existsSync as existsSync7, rmSync as
|
|
30211
|
+
import { existsSync as existsSync7, rmSync as rmSync3 } from "node:fs";
|
|
30026
30212
|
import { mkdtemp, realpath as realpath2 } from "node:fs/promises";
|
|
30027
30213
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
30028
30214
|
import path13 from "node:path";
|
|
@@ -30032,13 +30218,13 @@ import { spawnSync } from "node:child_process";
|
|
|
30032
30218
|
import {
|
|
30033
30219
|
cpSync,
|
|
30034
30220
|
existsSync as existsSync6,
|
|
30035
|
-
lstatSync,
|
|
30221
|
+
lstatSync as lstatSync2,
|
|
30036
30222
|
mkdirSync as mkdirSync2,
|
|
30037
30223
|
mkdtempSync,
|
|
30038
30224
|
readFileSync as readFileSync4,
|
|
30039
|
-
readdirSync as
|
|
30040
|
-
realpathSync as
|
|
30041
|
-
rmSync,
|
|
30225
|
+
readdirSync as readdirSync3,
|
|
30226
|
+
realpathSync as realpathSync6,
|
|
30227
|
+
rmSync as rmSync2,
|
|
30042
30228
|
symlinkSync,
|
|
30043
30229
|
writeFileSync as writeFileSync2
|
|
30044
30230
|
} from "node:fs";
|
|
@@ -30069,30 +30255,30 @@ function repositoryTop(dir) {
|
|
|
30069
30255
|
const top = git(["rev-parse", "--show-toplevel"], dir);
|
|
30070
30256
|
if (top.status !== 0) return null;
|
|
30071
30257
|
if (git(["rev-parse", "--verify", "--quiet", "HEAD"], dir).status !== 0) return null;
|
|
30072
|
-
return
|
|
30258
|
+
return realpathSync6(line(top.stdout));
|
|
30073
30259
|
}
|
|
30074
30260
|
function commonDirOf(top) {
|
|
30075
30261
|
const raw = line(gitOk(["rev-parse", "--git-common-dir"], top));
|
|
30076
|
-
return
|
|
30262
|
+
return realpathSync6(path12.resolve(top, raw));
|
|
30077
30263
|
}
|
|
30078
30264
|
function removeIsolatedCopy(workspace, container) {
|
|
30079
30265
|
const work = path12.join(container, WORKTREE_NAME);
|
|
30080
30266
|
let commonDir = null;
|
|
30081
30267
|
try {
|
|
30082
|
-
const top = repositoryTop(
|
|
30268
|
+
const top = repositoryTop(realpathSync6(workspace));
|
|
30083
30269
|
commonDir = top ? commonDirOf(top) : null;
|
|
30084
30270
|
if (top && existsSync6(work)) git(["worktree", "remove", "--force", work], top);
|
|
30085
30271
|
} catch {
|
|
30086
30272
|
}
|
|
30087
30273
|
try {
|
|
30088
|
-
|
|
30274
|
+
rmSync2(container, { recursive: true, force: true });
|
|
30089
30275
|
} catch {
|
|
30090
30276
|
}
|
|
30091
30277
|
if (!commonDir) return;
|
|
30092
30278
|
const entries = path12.join(commonDir, "worktrees");
|
|
30093
30279
|
let names;
|
|
30094
30280
|
try {
|
|
30095
|
-
names =
|
|
30281
|
+
names = readdirSync3(entries);
|
|
30096
30282
|
} catch {
|
|
30097
30283
|
return;
|
|
30098
30284
|
}
|
|
@@ -30100,7 +30286,7 @@ function removeIsolatedCopy(workspace, container) {
|
|
|
30100
30286
|
for (const name of names) {
|
|
30101
30287
|
try {
|
|
30102
30288
|
if (ours.has(readFileSync4(path12.join(entries, name, "gitdir"), "utf8").trim())) {
|
|
30103
|
-
|
|
30289
|
+
rmSync2(path12.join(entries, name), { recursive: true, force: true });
|
|
30104
30290
|
}
|
|
30105
30291
|
} catch {
|
|
30106
30292
|
}
|
|
@@ -30140,7 +30326,7 @@ async function prepareCoderRun(commands) {
|
|
|
30140
30326
|
patchPath,
|
|
30141
30327
|
cleanup: (workspace) => {
|
|
30142
30328
|
removeIsolatedCopy(workspace, container);
|
|
30143
|
-
if (!existsSync7(patchPath))
|
|
30329
|
+
if (!existsSync7(patchPath)) rmSync3(patchDir, { recursive: true, force: true });
|
|
30144
30330
|
}
|
|
30145
30331
|
};
|
|
30146
30332
|
}
|
|
@@ -30216,6 +30402,25 @@ var researchResultSchema = object2({
|
|
|
30216
30402
|
usage: unknown().optional(),
|
|
30217
30403
|
telemetry: unknown().optional()
|
|
30218
30404
|
}).loose();
|
|
30405
|
+
var RESEARCH_ROLES = ["stronger", "default", "small"];
|
|
30406
|
+
var GITHUB_RESEARCH_ROLES = ["stronger", "default"];
|
|
30407
|
+
async function catalogResearchModel(endpoint, adapter) {
|
|
30408
|
+
try {
|
|
30409
|
+
const [installed, available] = await catalogLiveState(endpoint, void 0);
|
|
30410
|
+
const picks = catalogView("research-agent", installed, available).jobs[0]?.picks ?? [];
|
|
30411
|
+
const nameOf = (pick3) => "installed_as" in pick3 && typeof pick3.installed_as === "string" ? pick3.installed_as : pick3.tag;
|
|
30412
|
+
for (const role of adapter === "github" ? GITHUB_RESEARCH_ROLES : RESEARCH_ROLES) {
|
|
30413
|
+
const pick3 = picks.find((entry) => entry.role === role && entry.installed === true && entry.fits_now === true);
|
|
30414
|
+
if (pick3) return { model: nameOf(pick3), reason: `catalog ${role} pick, installed, fits now` };
|
|
30415
|
+
}
|
|
30416
|
+
for (const role of ["default", "stronger"]) {
|
|
30417
|
+
const pick3 = picks.find((entry) => entry.role === role && entry.installed === true);
|
|
30418
|
+
if (pick3) return { model: nameOf(pick3), reason: `catalog ${role} pick, installed; not fitting now, so serve's memory gate decides` };
|
|
30419
|
+
}
|
|
30420
|
+
} catch {
|
|
30421
|
+
}
|
|
30422
|
+
return void 0;
|
|
30423
|
+
}
|
|
30219
30424
|
async function routedResearchModel(endpoint) {
|
|
30220
30425
|
const decision = JSON.parse(
|
|
30221
30426
|
await route2(endpoint, "coding", "balanced", void 0, void 0, void 0, [], void 0, void 0, void 0)
|
|
@@ -30231,7 +30436,7 @@ function registerDelegateResearchTool(server2) {
|
|
|
30231
30436
|
server2.registerTool(
|
|
30232
30437
|
"delegate_research",
|
|
30233
30438
|
{
|
|
30234
|
-
description: 'Use when: a read-only question about files under workspacePath (where is X, what calls Y) would take several searches;
|
|
30439
|
+
description: 'Use when: a read-only question about files under workspacePath (where is X, what calls Y) would take several searches; without `model` it uses the catalog\'s research-agent pick that is installed and fits now. Do not use when: editing (adapter:"coder" edits an isolated copy, if the operator enabled it), facts outside the workspace (GitHub excepted: adapter:"github", which needs no `workspacePath`), final judgment, or a question one search answers; run_task embedding with `nearest` finds files faster. Up to 8 turns, killed after 180 s by default (longer for bigger models, or agent.timeoutSeconds); no defer (a host that cuts calls near 60 s may drop it). Returns: citations/answer; discard verification.recommendation=escalate, otherwise verify citations. coder: review diff, then run its applyCommand.',
|
|
30235
30440
|
inputSchema: object2({
|
|
30236
30441
|
question: string2().min(1),
|
|
30237
30442
|
workspacePath: string2().min(1).optional().describe(
|
|
@@ -30247,7 +30452,8 @@ function registerDelegateResearchTool(server2) {
|
|
|
30247
30452
|
timeoutSeconds: number2().int().positive().max(MAX_DELEGATE_TIMEOUT_SECONDS).optional().describe("default scales with the model's size"),
|
|
30248
30453
|
commands: coderCommandsSchema
|
|
30249
30454
|
}).strict().optional(),
|
|
30250
|
-
receipt: _enum(["compact", "full"]).optional().describe("compact (default): trail at response_file; full: inline")
|
|
30455
|
+
receipt: _enum(["compact", "full"]).optional().describe("compact (default): trail at response_file; full: inline"),
|
|
30456
|
+
...outputInput
|
|
30251
30457
|
}).strict(),
|
|
30252
30458
|
outputSchema: researchResultSchema,
|
|
30253
30459
|
// Read-only lookup; adapter "github" searches GitHub over the internet, so the tool is
|
|
@@ -30255,18 +30461,33 @@ function registerDelegateResearchTool(server2) {
|
|
|
30255
30461
|
annotations: { destructiveHint: false }
|
|
30256
30462
|
},
|
|
30257
30463
|
// `receipt` only shapes the inline view (tool-results.ts); the tool always returns everything.
|
|
30258
|
-
async ({ question, workspacePath, adapter, model, endpoint, agent }, extra) => {
|
|
30464
|
+
async ({ question, workspacePath, adapter, model, endpoint, agent, outputPath }, extra) => {
|
|
30259
30465
|
const chosenAdapter = adapter ?? DEFAULT_RESEARCH_ADAPTER;
|
|
30260
30466
|
let resolvedWorkspace;
|
|
30261
30467
|
let chosenModel;
|
|
30468
|
+
let modelReason = "explicit model";
|
|
30262
30469
|
try {
|
|
30470
|
+
if (outputPath !== void 0) checkOutputPath(outputPath);
|
|
30263
30471
|
if (workspacePath === void 0 && chosenAdapter !== "github") {
|
|
30264
30472
|
throw new Error(`workspacePath is required for adapter "${chosenAdapter}": the absolute directory to research (or one relative to the server's working directory). Only adapter "github" runs without one.`);
|
|
30265
30473
|
}
|
|
30266
30474
|
const refusal = coderRefusal(chosenAdapter, agent?.commands);
|
|
30267
30475
|
if (refusal) throw refusal;
|
|
30268
30476
|
resolvedWorkspace = workspacePath === void 0 ? void 0 : await assertAllowedWorkspace(workspacePath);
|
|
30269
|
-
chosenModel = model
|
|
30477
|
+
if (model) chosenModel = model;
|
|
30478
|
+
else if (DEFAULT_DELEGATE_MODEL) {
|
|
30479
|
+
chosenModel = DEFAULT_DELEGATE_MODEL;
|
|
30480
|
+
modelReason = "FREELLAMA_MCP_DEFAULT_MODEL";
|
|
30481
|
+
} else {
|
|
30482
|
+
const picked = await catalogResearchModel(endpoint, chosenAdapter);
|
|
30483
|
+
if (picked) {
|
|
30484
|
+
chosenModel = picked.model;
|
|
30485
|
+
modelReason = picked.reason;
|
|
30486
|
+
} else {
|
|
30487
|
+
chosenModel = await routedResearchModel(endpoint);
|
|
30488
|
+
modelReason = "routing (no catalog research pick installed)";
|
|
30489
|
+
}
|
|
30490
|
+
}
|
|
30270
30491
|
} catch (error2) {
|
|
30271
30492
|
return researchErrorResult(error2);
|
|
30272
30493
|
}
|
|
@@ -30422,9 +30643,10 @@ ${evidenceText}` : "");
|
|
|
30422
30643
|
}
|
|
30423
30644
|
const digest = verifiedResearchDigest(result.model_metadata?.execution_receipts, chosenModel);
|
|
30424
30645
|
const verification = assessDelegatedAnswer(question, sources.length, chosenModel, digest);
|
|
30425
|
-
const summary = `Delegated answer ready: ${result.tool_calls.length} tool call(s)` + (failed > 0 ? `, ${failed} of which did not succeed` : "") + `; ${result.usage.input_tokens ?? "?"} input / ${result.usage.output_tokens ?? "?"} output local tokens; verification=${verification.recommendation}. Read structuredContent.answer and citations.`;
|
|
30646
|
+
const summary = `Delegated answer ready: ${result.tool_calls.length} tool call(s)` + (failed > 0 ? `, ${failed} of which did not succeed` : "") + `; ${result.usage.input_tokens ?? "?"} input / ${result.usage.output_tokens ?? "?"} output local tokens; verification=${verification.recommendation}; research model: ${chosenModel} (${modelReason}). Read structuredContent.answer and citations.`;
|
|
30426
30647
|
const payload = {
|
|
30427
30648
|
adapter: chosenAdapter,
|
|
30649
|
+
researchModel: { model: chosenModel, reason: modelReason },
|
|
30428
30650
|
verification,
|
|
30429
30651
|
answer: result.final_answer,
|
|
30430
30652
|
// recommendation + why live under `verification`; `citations` completes the triple so a
|
|
@@ -30452,7 +30674,7 @@ ${evidenceText}` : "");
|
|
|
30452
30674
|
|
|
30453
30675
|
verification: ${verification.recommendation}` + (cited.length ? `
|
|
30454
30676
|
cited: ${cited.join(", ")}` : "") + `
|
|
30455
|
-
(${chosenModel}, ${result.tool_calls.length} tool call(s))`;
|
|
30677
|
+
(research model: ${chosenModel} (${modelReason}), ${result.tool_calls.length} tool call(s))`;
|
|
30456
30678
|
return structuredResult(payload, { text: answerText });
|
|
30457
30679
|
} catch (error2) {
|
|
30458
30680
|
return researchErrorResult(error2);
|
|
@@ -30592,7 +30814,7 @@ function registerReadResponseTool(server2) {
|
|
|
30592
30814
|
}
|
|
30593
30815
|
|
|
30594
30816
|
// src/tools/audio.ts
|
|
30595
|
-
import { existsSync as existsSync9, realpathSync as
|
|
30817
|
+
import { existsSync as existsSync9, realpathSync as realpathSync7, writeFileSync as writeFileSync3 } from "node:fs";
|
|
30596
30818
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
30597
30819
|
import path16 from "node:path";
|
|
30598
30820
|
init_config();
|
|
@@ -30663,7 +30885,7 @@ function registerAudioTool(server2) {
|
|
|
30663
30885
|
const target = path16.resolve(outputFile ?? path16.join(tmpdir4(), `freellama-speech-${Date.now()}.wav`));
|
|
30664
30886
|
let directory;
|
|
30665
30887
|
try {
|
|
30666
|
-
directory =
|
|
30888
|
+
directory = realpathSync7(path16.dirname(target));
|
|
30667
30889
|
} catch {
|
|
30668
30890
|
return errorResult(new Error(`outputFile directory does not exist: ${path16.dirname(target)}`));
|
|
30669
30891
|
}
|
|
@@ -30923,10 +31145,18 @@ ${chunk.text}`
|
|
|
30923
31145
|
|
|
30924
31146
|
// src/sweep/store.ts
|
|
30925
31147
|
init_config();
|
|
30926
|
-
import { appendFileSync, existsSync as existsSync10, mkdirSync as mkdirSync3, readdirSync as
|
|
31148
|
+
import { appendFileSync, existsSync as existsSync10, mkdirSync as mkdirSync3, readdirSync as readdirSync4, readFileSync as readFileSync5, renameSync, writeFileSync as writeFileSync4 } from "node:fs";
|
|
30927
31149
|
import path18 from "node:path";
|
|
30928
31150
|
import { createHash as createHash2 } from "node:crypto";
|
|
30929
31151
|
var SWEEP_ROOT = process.env.FREELLAMA_SWEEP_DIR || path18.join(FREELLAMA_HOME, "sweeps");
|
|
31152
|
+
function processAlive(pid) {
|
|
31153
|
+
try {
|
|
31154
|
+
process.kill(pid, 0);
|
|
31155
|
+
return true;
|
|
31156
|
+
} catch (error2) {
|
|
31157
|
+
return error2.code === "EPERM";
|
|
31158
|
+
}
|
|
31159
|
+
}
|
|
30930
31160
|
var hashText = (text3) => createHash2("sha256").update(text3).digest("hex").slice(0, 32);
|
|
30931
31161
|
var SweepStore = class {
|
|
30932
31162
|
constructor(root = SWEEP_ROOT) {
|
|
@@ -30979,7 +31209,7 @@ var SweepStore = class {
|
|
|
30979
31209
|
}
|
|
30980
31210
|
list() {
|
|
30981
31211
|
if (!existsSync10(this.root)) return [];
|
|
30982
|
-
return
|
|
31212
|
+
return readdirSync4(this.root).filter((name) => /^[0-9a-f-]{36}$/.test(name) && existsSync10(path18.join(this.root, name, "manifest.json")));
|
|
30983
31213
|
}
|
|
30984
31214
|
};
|
|
30985
31215
|
function jsonl(file) {
|
|
@@ -31088,6 +31318,8 @@ var SweepRunner = class {
|
|
|
31088
31318
|
resume(id) {
|
|
31089
31319
|
if (this.active.has(id)) return this.view(id);
|
|
31090
31320
|
const manifest = this.store.manifest(id);
|
|
31321
|
+
const other = this.foreignOwner(manifest);
|
|
31322
|
+
if (other !== void 0) throw new Error(`sweep ${id} is still running in server process ${other}; wait for it or stop that process first`);
|
|
31091
31323
|
if (manifest.phase === "collecting") throw new Error("this sweep was interrupted while collecting pages; start a new sweep");
|
|
31092
31324
|
manifest.phase = "running";
|
|
31093
31325
|
delete manifest.finishedAt;
|
|
@@ -31144,7 +31376,9 @@ var SweepRunner = class {
|
|
|
31144
31376
|
if (result.change === "changed") counts.changed += 1;
|
|
31145
31377
|
if (result.change === "new") counts.added += 1;
|
|
31146
31378
|
}
|
|
31147
|
-
const
|
|
31379
|
+
const ownerPid = manifest.phase === "running" || manifest.phase === "collecting" ? this.foreignOwner(manifest) : void 0;
|
|
31380
|
+
const phase = (manifest.phase === "running" || manifest.phase === "collecting") && !this.active.has(id) && ownerPid === void 0 ? "interrupted" : manifest.phase;
|
|
31381
|
+
const resumable = phase === "interrupted" || phase === "cancelled" || phase === "completed" && counts.failed > 0 || phase === "failed" && counts.total > 0;
|
|
31148
31382
|
const elapsedSeconds = Math.round(((manifest.finishedAt ? Date.parse(manifest.finishedAt) : Date.now()) - Date.parse(manifest.startedAt)) / 1e3);
|
|
31149
31383
|
const pending = Math.max(0, counts.total - counts.done - counts.failed);
|
|
31150
31384
|
const ms = this.active.get(id)?.ms ?? [];
|
|
@@ -31158,6 +31392,8 @@ var SweepRunner = class {
|
|
|
31158
31392
|
pages: manifest.pages,
|
|
31159
31393
|
skipped: manifest.skipped.length,
|
|
31160
31394
|
error: manifest.error,
|
|
31395
|
+
...resumable && !(manifest.phase === "collecting") ? { resume: { action: "resume", sweepId: id } } : {},
|
|
31396
|
+
...ownerPid !== void 0 ? { ownerPid } : {},
|
|
31161
31397
|
startedAt: manifest.startedAt,
|
|
31162
31398
|
finishedAt: manifest.finishedAt,
|
|
31163
31399
|
elapsedSeconds,
|
|
@@ -31165,10 +31401,18 @@ var SweepRunner = class {
|
|
|
31165
31401
|
files: this.store.files(id)
|
|
31166
31402
|
};
|
|
31167
31403
|
}
|
|
31404
|
+
/** The pid of another live server process that owns this sweep's run, if any. */
|
|
31405
|
+
foreignOwner(manifest) {
|
|
31406
|
+
if (manifest.phase !== "running" && manifest.phase !== "collecting") return void 0;
|
|
31407
|
+
const pid = manifest.owner?.pid;
|
|
31408
|
+
return pid !== void 0 && pid !== process.pid && !this.active.has(manifest.config.id) && processAlive(pid) ? pid : void 0;
|
|
31409
|
+
}
|
|
31168
31410
|
emit(id, message2) {
|
|
31169
31411
|
for (const listener of this.listeners.get(id) ?? []) listener(message2);
|
|
31170
31412
|
}
|
|
31171
31413
|
launch(manifest, resuming = false) {
|
|
31414
|
+
manifest.owner = { pid: process.pid };
|
|
31415
|
+
this.store.saveManifest(manifest);
|
|
31172
31416
|
const abort = new AbortController();
|
|
31173
31417
|
const active = { abort, done: Promise.resolve(), ms: [] };
|
|
31174
31418
|
this.active.set(manifest.config.id, active);
|
|
@@ -31382,11 +31626,11 @@ function runnerOf(id) {
|
|
|
31382
31626
|
for (const runner of runners.values()) if (runner.isActive(id)) return runner;
|
|
31383
31627
|
return runnerFor(store.manifest(id).config.endpoint);
|
|
31384
31628
|
}
|
|
31385
|
-
function
|
|
31629
|
+
function viewText2(view) {
|
|
31386
31630
|
const c = view.counts;
|
|
31387
31631
|
const changes = c.reused + c.changed + c.added > 0 || c.removed > 0 ? `; since baseline: ${c.added} new, ${c.changed} changed, ${c.reused} unchanged (reused), ${c.removed} removed` : "";
|
|
31388
31632
|
const eta = view.etaSeconds !== void 0 ? `, about ${Math.max(1, Math.round(view.etaSeconds / 60))} min left` : "";
|
|
31389
|
-
const next = view.phase === "running" || view.phase === "collecting" ? `sweep{action:"wait", sweepId:"${view.id}"} to follow it` : view.phase === "interrupted"
|
|
31633
|
+
const next = view.phase === "running" || view.phase === "collecting" ? `sweep{action:"wait", sweepId:"${view.id}"} to follow it` : view.resume ? `${view.phase === "interrupted" ? "its server process exited; " : ""}sweep{action:"resume", sweepId:"${view.id}"} continues it, reusing saved chunk results` : view.phase === "interrupted" ? "it stopped while collecting pages and cannot resume: start a new sweep" : c.failed > 0 ? `sweep{action:"resume", sweepId:"${view.id}"} retries the failed chunks` : `sweep{action:"results", sweepId:"${view.id}"} lists flagged chunks`;
|
|
31390
31634
|
return `sweep ${view.id} ${view.phase}${view.error ? ` (${view.error})` : ""}: ${c.done + c.failed}/${c.total} chunks${view.pages !== void 0 ? ` from ${view.pages} pages` : ""}, ${c.flagged} flagged, ${c.unsure} unsure, ${c.clear} clear, ${c.failed} failed${changes}${eta}. Next: ${next}.`;
|
|
31391
31635
|
}
|
|
31392
31636
|
function answerLine(answers2) {
|
|
@@ -31447,7 +31691,7 @@ function registerSweepTool(server2) {
|
|
|
31447
31691
|
if (action === "list") {
|
|
31448
31692
|
const sweeps = store.list().map((id) => runnerOf(id).view(id)).sort((left, right) => right.startedAt.localeCompare(left.startedAt)).slice(0, 50);
|
|
31449
31693
|
return structuredResult({ sweeps }, {
|
|
31450
|
-
text: sweeps.length === 0 ? "no sweeps yet" : sweeps.map((view2) => `- ${view2.name ?? "(unnamed)"}: ${
|
|
31694
|
+
text: sweeps.length === 0 ? "no sweeps yet" : sweeps.map((view2) => `- ${view2.name ?? "(unnamed)"}: ${viewText2(view2)}`).join("\n")
|
|
31451
31695
|
});
|
|
31452
31696
|
}
|
|
31453
31697
|
if (action === "start") return await start(args);
|
|
@@ -31456,11 +31700,11 @@ function registerSweepTool(server2) {
|
|
|
31456
31700
|
const runner = runnerOf(sweepId);
|
|
31457
31701
|
if (action === "cancel") {
|
|
31458
31702
|
const view2 = await runner.cancel(sweepId);
|
|
31459
|
-
return structuredResult(view2, { text:
|
|
31703
|
+
return structuredResult(view2, { text: viewText2(view2) });
|
|
31460
31704
|
}
|
|
31461
31705
|
if (action === "resume") {
|
|
31462
31706
|
const view2 = runner.resume(sweepId);
|
|
31463
|
-
return structuredResult(view2, { text:
|
|
31707
|
+
return structuredResult(view2, { text: viewText2(view2) });
|
|
31464
31708
|
}
|
|
31465
31709
|
if (action === "wait") {
|
|
31466
31710
|
const report = progressSink(extra);
|
|
@@ -31473,7 +31717,7 @@ function registerSweepTool(server2) {
|
|
|
31473
31717
|
}
|
|
31474
31718
|
if (action === "results") return results(sweepId, args.filter ?? "flagged", 50, args.cursor, args.saveAs ? { name: args.saveAs, view: runner.view(sweepId) } : void 0);
|
|
31475
31719
|
const view = runner.view(sweepId);
|
|
31476
|
-
return structuredResult(view, { text:
|
|
31720
|
+
return structuredResult(view, { text: viewText2(view) });
|
|
31477
31721
|
} catch (error2) {
|
|
31478
31722
|
return errorResult(error2);
|
|
31479
31723
|
}
|
|
@@ -31527,7 +31771,7 @@ async function start(args) {
|
|
|
31527
31771
|
}
|
|
31528
31772
|
};
|
|
31529
31773
|
const view = await runnerFor(args.endpoint).start(config2);
|
|
31530
|
-
return structuredResult(view, { text:
|
|
31774
|
+
return structuredResult(view, { text: viewText2(view) });
|
|
31531
31775
|
}
|
|
31532
31776
|
function rowLine(row) {
|
|
31533
31777
|
const verified = row.verified ? ` \u2192 ${row.verified.model}: ${row.verified.verdict} (${answerLine(row.verified.answers)})` : "";
|
|
@@ -31571,7 +31815,7 @@ function results(id, filter, limit, cursor, handoff2) {
|
|
|
31571
31815
|
(${rows.length - offset - page2.length} more: cursor "${next}")` : "") + "\nThese are a small model's candidates: read 3 to 5 of the cited ranges yourself before relying on the rest.";
|
|
31572
31816
|
const file = handoff2 ? writeHandoff(handoff2.name, {
|
|
31573
31817
|
source: `From sweep ${id}${manifest.config.name ? ` (${manifest.config.name})` : ""}, filter ${filter}`,
|
|
31574
|
-
summary:
|
|
31818
|
+
summary: viewText2(handoff2.view),
|
|
31575
31819
|
result: rows.length === 0 ? `no ${filter} chunks` : rows.map(rowLine).join("\n")
|
|
31576
31820
|
}) : void 0;
|
|
31577
31821
|
if (file) text3 += `
|
|
@@ -31590,7 +31834,7 @@ Name inputs with files (paths or globs under the server's working directory, the
|
|
|
31590
31834
|
You own prompts, output format and verification: local answers are candidates, so check 3 to 5 (more where a mistake costs more) before relying on the rest, and treat low decision probabilities as unknown.
|
|
31591
31835
|
Memory: a refusal names the programs holding memory; tell the user and never stop their processes, then retry once they free memory, or use a smaller installed model with the same capability.
|
|
31592
31836
|
Approval: before ollama_pull, ask the user for one exact tag (name:tag) and its reported size, and pass that size as approvedSizeBytes; search results are not permission. Unload only a model you warmed (when its burst ends) or one the user named; delete only a tag the user names.
|
|
31593
|
-
Results: check isError first, then read the text; open structuredContent or read_response{file:response_file, section} only for a receipt field your next step needs. Pass page.next_cursor (models) and next_offset (read_response) back unchanged. A server restart loses scopes, sessions and jobs. CPU/GPU placement is operator-configured; executionPreference only chooses among eligible backends. Docs: freellama://docs/index.`;
|
|
31837
|
+
Results: check isError first, then read the text; open structuredContent or read_response{file:response_file, section} only for a receipt field your next step needs. A long answer (run_task, delegate_research, task_jobs get) comes back as a summary plus the output_file holding it; output:"inline" returns it whole, outputPath picks the file. Pass page.next_cursor (models) and next_offset (read_response) back unchanged. A server restart loses scopes, sessions and jobs. CPU/GPU placement is operator-configured; executionPreference only chooses among eligible backends. Docs: freellama://docs/index.`;
|
|
31594
31838
|
var server = withToolResults(new McpServer(
|
|
31595
31839
|
{ name: "freellama", version: SERVER_VERSION },
|
|
31596
31840
|
{ instructions: INSTRUCTIONS, capabilities: { logging: {} } }
|