micro-models-agent 1.6.0 → 1.7.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/CHANGELOG.md +16 -0
- package/dist/main.js +186 -144
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,22 @@ All notable changes to Micro Models Agent (MMA) will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/), and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [1.7.0] - 2026-09-22
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **Model capability resolution** (`src/modules/providers/capabilities.ts`, `src/config/types.ts`): a single source of truth for a model's `vision`/`tools` abilities, merged from user override (`config.modelCapabilities[model]`) → live native probe (LM Studio `/api/v1/models` `capabilities`, read by `probeModel` in `src/llm/model-loader.ts`) → provider preset. Exposed to tools via the live `ToolContext.capabilities` getter.
|
|
12
|
+
- **First-class tool image channel** (`src/tools/types.ts` `ToolResult.images`, `src/core/agent/tool-images.ts`): tools can return images; the agent loop queues them onto the next user message (`queueToolImages`), keeping `role:"tool"` content text-only and portable.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Vision misdetection for non-`-vl` models** (`src/tools/browser.ts`): the `qwen3.5-9b`-style models report `vision: true` in LM Studio but the name-based `modelSupportsVision` heuristic returned `false`, so screenshots were silently downgraded to a text notice. Name matching is removed; the browser tool now reads `ctx.capabilities.vision`.
|
|
17
|
+
- **Browser screenshots never reached the model** (`src/tools/browser.ts`): screenshots were embedded as base64 text inside the `role:"tool"` output, which backends never decode and the budget truncates. They are now delivered as `image_url` parts; text-only models get the snapshot instead.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- **Browser module prompt** (`src/modules/browser/module.ts`): no longer asks the model to self-assess vision support — screenshot delivery is automatic.
|
|
22
|
+
|
|
7
23
|
## [1.6.0] - 2026-09-22
|
|
8
24
|
|
|
9
25
|
### Added
|
package/dist/main.js
CHANGED
|
@@ -2312,6 +2312,7 @@ var init_defaults = __esm(() => {
|
|
|
2312
2312
|
description: "browser + file reading for visual tasks"
|
|
2313
2313
|
}
|
|
2314
2314
|
},
|
|
2315
|
+
modelCapabilities: {},
|
|
2315
2316
|
contextWindow: 32768,
|
|
2316
2317
|
contextBudget: {
|
|
2317
2318
|
systemPrompt: 0.1,
|
|
@@ -5118,7 +5119,7 @@ var init_domains = __esm(() => {
|
|
|
5118
5119
|
"reasoning",
|
|
5119
5120
|
"hallucination"
|
|
5120
5121
|
],
|
|
5121
|
-
provider: ["provider", "orchestrator", "retry", "pricing"],
|
|
5122
|
+
provider: ["provider", "orchestrator", "retry", "pricing", "modelCapabilities"],
|
|
5122
5123
|
moe: ["moe", "experts"],
|
|
5123
5124
|
security: ["security"],
|
|
5124
5125
|
browser: ["browser"],
|
|
@@ -7547,7 +7548,7 @@ var init_factory = __esm(() => {
|
|
|
7547
7548
|
});
|
|
7548
7549
|
|
|
7549
7550
|
// src/llm/model-loader.ts
|
|
7550
|
-
async function
|
|
7551
|
+
async function probeModel(baseUrl, model, timeoutMs = 3000) {
|
|
7551
7552
|
const base = baseUrl.replace(/\/$/, "").replace(/\/v1\/?$/, "");
|
|
7552
7553
|
try {
|
|
7553
7554
|
const response = await fetch(`${base}/api/v1/models`, {
|
|
@@ -7561,10 +7562,18 @@ async function getLoadedContextLength(baseUrl, model, timeoutMs = 3000) {
|
|
|
7561
7562
|
if (!found)
|
|
7562
7563
|
return null;
|
|
7563
7564
|
const loadedInstance = (found.loaded_instances ?? [])[0];
|
|
7564
|
-
const
|
|
7565
|
-
if (typeof
|
|
7565
|
+
const contextLength = loadedInstance?.config?.context_length ?? found.max_context_length;
|
|
7566
|
+
if (typeof contextLength !== "number" || contextLength <= 0)
|
|
7566
7567
|
return null;
|
|
7567
|
-
|
|
7568
|
+
const raw = found.capabilities;
|
|
7569
|
+
let capabilities;
|
|
7570
|
+
if (raw && typeof raw.vision === "boolean") {
|
|
7571
|
+
capabilities = { vision: raw.vision };
|
|
7572
|
+
if (typeof raw.trained_for_tool_use === "boolean") {
|
|
7573
|
+
capabilities.tools = raw.trained_for_tool_use;
|
|
7574
|
+
}
|
|
7575
|
+
}
|
|
7576
|
+
return { model: found.key ?? found.name ?? model, contextLength, capabilities };
|
|
7568
7577
|
} catch {
|
|
7569
7578
|
return null;
|
|
7570
7579
|
}
|
|
@@ -15436,6 +15445,15 @@ var init_tool_output = __esm(() => {
|
|
|
15436
15445
|
init_i18n();
|
|
15437
15446
|
});
|
|
15438
15447
|
|
|
15448
|
+
// src/core/agent/tool-images.ts
|
|
15449
|
+
function queueToolImages(sink, images) {
|
|
15450
|
+
if (!sink || !images || images.length === 0)
|
|
15451
|
+
return;
|
|
15452
|
+
for (const image of images) {
|
|
15453
|
+
sink.addPendingImage(image);
|
|
15454
|
+
}
|
|
15455
|
+
}
|
|
15456
|
+
|
|
15439
15457
|
// src/core/agent/tool-batch.ts
|
|
15440
15458
|
class ToolBatchExecutor {
|
|
15441
15459
|
deps;
|
|
@@ -15565,6 +15583,7 @@ class ToolBatchExecutor {
|
|
|
15565
15583
|
arguments: call.arguments
|
|
15566
15584
|
});
|
|
15567
15585
|
answeredToolCallIds.add(call.id);
|
|
15586
|
+
queueToolImages(contextManager, result.images);
|
|
15568
15587
|
const tokensAfterTool = contextManager.getEstimatedTokens();
|
|
15569
15588
|
const costAfterTool = this.deps.costTracker.total;
|
|
15570
15589
|
onTool?.({
|
|
@@ -21290,6 +21309,119 @@ var init_session = __esm(() => {
|
|
|
21290
21309
|
init_i18n();
|
|
21291
21310
|
});
|
|
21292
21311
|
|
|
21312
|
+
// src/llm/image-utils.ts
|
|
21313
|
+
var exports_image_utils = {};
|
|
21314
|
+
__export(exports_image_utils, {
|
|
21315
|
+
readClipboardImage: () => readClipboardImage,
|
|
21316
|
+
loadUrlAsDataUrl: () => loadUrlAsDataUrl,
|
|
21317
|
+
loadFileAsDataUrl: () => loadFileAsDataUrl,
|
|
21318
|
+
detectMime: () => detectMime,
|
|
21319
|
+
bufferToDataUrl: () => bufferToDataUrl
|
|
21320
|
+
});
|
|
21321
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
21322
|
+
import { extname as extname4 } from "path";
|
|
21323
|
+
function detectMime(filePath) {
|
|
21324
|
+
const ext = extname4(filePath).toLowerCase();
|
|
21325
|
+
return MIME_MAP[ext] ?? "image/png";
|
|
21326
|
+
}
|
|
21327
|
+
async function readClipboardImage() {
|
|
21328
|
+
if (typeof Bun !== "undefined" && typeof Bun.Image !== "undefined") {
|
|
21329
|
+
try {
|
|
21330
|
+
const img = Bun.Image.fromClipboard();
|
|
21331
|
+
if (img) {
|
|
21332
|
+
const buf = await img.resize(800, 800, { fit: "inside" }).jpeg({ quality: 60 }).buffer();
|
|
21333
|
+
return Buffer.from(buf);
|
|
21334
|
+
}
|
|
21335
|
+
} catch {}
|
|
21336
|
+
}
|
|
21337
|
+
return readClipboardFallback();
|
|
21338
|
+
}
|
|
21339
|
+
async function readClipboardFallback() {
|
|
21340
|
+
const { platform: platform7 } = await import("os");
|
|
21341
|
+
const { execSync } = await import("child_process");
|
|
21342
|
+
const { readFileSync: readFileSync21, unlinkSync: unlinkSync5 } = await import("fs");
|
|
21343
|
+
const { join: join29 } = await import("path");
|
|
21344
|
+
if (platform7() !== "linux")
|
|
21345
|
+
return null;
|
|
21346
|
+
const tmpPath = join29(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
|
|
21347
|
+
const commands = [
|
|
21348
|
+
`wl-paste --type image/png > "${tmpPath}" 2>/dev/null`,
|
|
21349
|
+
`xclip -selection clipboard -t image/png -o > "${tmpPath}" 2>/dev/null`
|
|
21350
|
+
];
|
|
21351
|
+
for (const cmd of commands) {
|
|
21352
|
+
try {
|
|
21353
|
+
execSync(cmd, { timeout: 5000 });
|
|
21354
|
+
const buf = readFileSync21(tmpPath);
|
|
21355
|
+
unlinkSync5(tmpPath);
|
|
21356
|
+
if (buf.length > 0)
|
|
21357
|
+
return buf;
|
|
21358
|
+
} catch {}
|
|
21359
|
+
}
|
|
21360
|
+
try {
|
|
21361
|
+
unlinkSync5(tmpPath);
|
|
21362
|
+
} catch {}
|
|
21363
|
+
return null;
|
|
21364
|
+
}
|
|
21365
|
+
async function loadFileAsDataUrl(filePath) {
|
|
21366
|
+
const buf = readFileSync20(filePath);
|
|
21367
|
+
if (typeof Bun !== "undefined" && typeof Bun.Image !== "undefined") {
|
|
21368
|
+
try {
|
|
21369
|
+
const img = new Bun.Image(buf);
|
|
21370
|
+
const { width } = await img.metadata();
|
|
21371
|
+
const targetWidth = Math.min(800, width || 800);
|
|
21372
|
+
const dataUrl = await img.resize(targetWidth, 800, { fit: "inside" }).jpeg({ quality: 60 }).dataurl();
|
|
21373
|
+
return { dataUrl };
|
|
21374
|
+
} catch {}
|
|
21375
|
+
}
|
|
21376
|
+
const b64 = buf.toString("base64");
|
|
21377
|
+
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
21378
|
+
const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
21379
|
+
return { dataUrl: `data:${mime};base64,${b64}` };
|
|
21380
|
+
}
|
|
21381
|
+
async function loadUrlAsDataUrl(url) {
|
|
21382
|
+
const resp = await fetch(url);
|
|
21383
|
+
if (!resp.ok) {
|
|
21384
|
+
throw new Error(`Failed to fetch image: ${resp.status} ${resp.statusText}`);
|
|
21385
|
+
}
|
|
21386
|
+
const buf = Buffer.from(await resp.arrayBuffer());
|
|
21387
|
+
if (typeof Bun !== "undefined" && typeof Bun.Image !== "undefined") {
|
|
21388
|
+
try {
|
|
21389
|
+
const img = new Bun.Image(buf);
|
|
21390
|
+
const { width } = await img.metadata();
|
|
21391
|
+
const targetWidth = Math.min(800, width || 800);
|
|
21392
|
+
const dataUrl = await img.resize(targetWidth, 800, { fit: "inside" }).jpeg({ quality: 60 }).dataurl();
|
|
21393
|
+
return { dataUrl };
|
|
21394
|
+
} catch {}
|
|
21395
|
+
}
|
|
21396
|
+
const b64 = buf.toString("base64");
|
|
21397
|
+
return { dataUrl: `data:image/png;base64,${b64}` };
|
|
21398
|
+
}
|
|
21399
|
+
async function bufferToDataUrl(buf) {
|
|
21400
|
+
if (typeof Bun !== "undefined" && typeof Bun.Image !== "undefined") {
|
|
21401
|
+
try {
|
|
21402
|
+
const img = new Bun.Image(buf);
|
|
21403
|
+
const { width } = await img.metadata();
|
|
21404
|
+
const targetWidth = Math.min(800, width || 800);
|
|
21405
|
+
const dataUrl = await img.resize(targetWidth, 800, { fit: "inside" }).jpeg({ quality: 60 }).dataurl();
|
|
21406
|
+
return { dataUrl };
|
|
21407
|
+
} catch {}
|
|
21408
|
+
}
|
|
21409
|
+
const b64 = buf.toString("base64");
|
|
21410
|
+
return { dataUrl: `data:image/png;base64,${b64}` };
|
|
21411
|
+
}
|
|
21412
|
+
var MIME_MAP;
|
|
21413
|
+
var init_image_utils = __esm(() => {
|
|
21414
|
+
MIME_MAP = {
|
|
21415
|
+
".png": "image/png",
|
|
21416
|
+
".jpg": "image/jpeg",
|
|
21417
|
+
".jpeg": "image/jpeg",
|
|
21418
|
+
".gif": "image/gif",
|
|
21419
|
+
".webp": "image/webp",
|
|
21420
|
+
".bmp": "image/bmp",
|
|
21421
|
+
".svg": "image/svg+xml"
|
|
21422
|
+
};
|
|
21423
|
+
});
|
|
21424
|
+
|
|
21293
21425
|
// src/tools/browser.ts
|
|
21294
21426
|
import { join as join29 } from "path";
|
|
21295
21427
|
import { mkdirSync as mkdirSync16 } from "fs";
|
|
@@ -21326,18 +21458,9 @@ function formatScreenshotForTextModel(snapshot) {
|
|
|
21326
21458
|
|
|
21327
21459
|
${snapshot}`;
|
|
21328
21460
|
}
|
|
21329
|
-
function
|
|
21330
|
-
const
|
|
21331
|
-
return
|
|
21332
|
-
}
|
|
21333
|
-
function formatScreenshotResult(snapshot, screenshot, supportsVision) {
|
|
21334
|
-
if (!supportsVision) {
|
|
21335
|
-
return formatScreenshotForTextModel(snapshot);
|
|
21336
|
-
}
|
|
21337
|
-
const base642 = screenshot.toString("base64");
|
|
21338
|
-
return `${snapshot}
|
|
21339
|
-
|
|
21340
|
-
[Screenshot: data:image/png;base64,${base642}]`;
|
|
21461
|
+
async function screenshotToImagePart(screenshot) {
|
|
21462
|
+
const { dataUrl } = await bufferToDataUrl(screenshot);
|
|
21463
|
+
return { type: "image_url", image_url: { url: dataUrl } };
|
|
21341
21464
|
}
|
|
21342
21465
|
function createBrowserTool() {
|
|
21343
21466
|
return {
|
|
@@ -21402,12 +21525,15 @@ function createBrowserTool() {
|
|
|
21402
21525
|
}
|
|
21403
21526
|
const s = getSession(ctx);
|
|
21404
21527
|
const result = await serialize(() => s.execute(action, args));
|
|
21405
|
-
const supportsVision = modelSupportsVision(ctx.config.model);
|
|
21406
21528
|
if (result.screenshot) {
|
|
21407
|
-
|
|
21408
|
-
|
|
21409
|
-
|
|
21410
|
-
|
|
21529
|
+
if (ctx.capabilities?.vision) {
|
|
21530
|
+
return {
|
|
21531
|
+
success: result.success,
|
|
21532
|
+
output: result.output,
|
|
21533
|
+
images: [await screenshotToImagePart(result.screenshot)]
|
|
21534
|
+
};
|
|
21535
|
+
}
|
|
21536
|
+
return { success: result.success, output: formatScreenshotForTextModel(result.output) };
|
|
21411
21537
|
}
|
|
21412
21538
|
return { success: result.success, output: result.output };
|
|
21413
21539
|
}
|
|
@@ -21426,125 +21552,13 @@ var session = null, sessionFactory, actionChain;
|
|
|
21426
21552
|
var init_browser = __esm(() => {
|
|
21427
21553
|
init_session();
|
|
21428
21554
|
init_types();
|
|
21555
|
+
init_image_utils();
|
|
21429
21556
|
init_i18n();
|
|
21430
21557
|
init_output();
|
|
21431
21558
|
init_utils();
|
|
21432
21559
|
actionChain = Promise.resolve();
|
|
21433
21560
|
});
|
|
21434
21561
|
|
|
21435
|
-
// src/llm/image-utils.ts
|
|
21436
|
-
var exports_image_utils = {};
|
|
21437
|
-
__export(exports_image_utils, {
|
|
21438
|
-
readClipboardImage: () => readClipboardImage,
|
|
21439
|
-
loadUrlAsDataUrl: () => loadUrlAsDataUrl,
|
|
21440
|
-
loadFileAsDataUrl: () => loadFileAsDataUrl,
|
|
21441
|
-
detectMime: () => detectMime,
|
|
21442
|
-
bufferToDataUrl: () => bufferToDataUrl
|
|
21443
|
-
});
|
|
21444
|
-
import { readFileSync as readFileSync20 } from "fs";
|
|
21445
|
-
import { extname as extname4 } from "path";
|
|
21446
|
-
function detectMime(filePath) {
|
|
21447
|
-
const ext = extname4(filePath).toLowerCase();
|
|
21448
|
-
return MIME_MAP[ext] ?? "image/png";
|
|
21449
|
-
}
|
|
21450
|
-
async function readClipboardImage() {
|
|
21451
|
-
if (typeof Bun !== "undefined" && typeof Bun.Image !== "undefined") {
|
|
21452
|
-
try {
|
|
21453
|
-
const img = Bun.Image.fromClipboard();
|
|
21454
|
-
if (img) {
|
|
21455
|
-
const buf = await img.resize(800, 800, { fit: "inside" }).jpeg({ quality: 60 }).buffer();
|
|
21456
|
-
return Buffer.from(buf);
|
|
21457
|
-
}
|
|
21458
|
-
} catch {}
|
|
21459
|
-
}
|
|
21460
|
-
return readClipboardFallback();
|
|
21461
|
-
}
|
|
21462
|
-
async function readClipboardFallback() {
|
|
21463
|
-
const { platform: platform7 } = await import("os");
|
|
21464
|
-
const { execSync } = await import("child_process");
|
|
21465
|
-
const { readFileSync: readFileSync21, unlinkSync: unlinkSync5 } = await import("fs");
|
|
21466
|
-
const { join: join30 } = await import("path");
|
|
21467
|
-
if (platform7() !== "linux")
|
|
21468
|
-
return null;
|
|
21469
|
-
const tmpPath = join30(process.env.TEMP || process.env.TMP || "/tmp", `mma-clip-${Date.now()}.png`);
|
|
21470
|
-
const commands = [
|
|
21471
|
-
`wl-paste --type image/png > "${tmpPath}" 2>/dev/null`,
|
|
21472
|
-
`xclip -selection clipboard -t image/png -o > "${tmpPath}" 2>/dev/null`
|
|
21473
|
-
];
|
|
21474
|
-
for (const cmd of commands) {
|
|
21475
|
-
try {
|
|
21476
|
-
execSync(cmd, { timeout: 5000 });
|
|
21477
|
-
const buf = readFileSync21(tmpPath);
|
|
21478
|
-
unlinkSync5(tmpPath);
|
|
21479
|
-
if (buf.length > 0)
|
|
21480
|
-
return buf;
|
|
21481
|
-
} catch {}
|
|
21482
|
-
}
|
|
21483
|
-
try {
|
|
21484
|
-
unlinkSync5(tmpPath);
|
|
21485
|
-
} catch {}
|
|
21486
|
-
return null;
|
|
21487
|
-
}
|
|
21488
|
-
async function loadFileAsDataUrl(filePath) {
|
|
21489
|
-
const buf = readFileSync20(filePath);
|
|
21490
|
-
if (typeof Bun !== "undefined" && typeof Bun.Image !== "undefined") {
|
|
21491
|
-
try {
|
|
21492
|
-
const img = new Bun.Image(buf);
|
|
21493
|
-
const { width } = await img.metadata();
|
|
21494
|
-
const targetWidth = Math.min(800, width || 800);
|
|
21495
|
-
const dataUrl = await img.resize(targetWidth, 800, { fit: "inside" }).jpeg({ quality: 60 }).dataurl();
|
|
21496
|
-
return { dataUrl };
|
|
21497
|
-
} catch {}
|
|
21498
|
-
}
|
|
21499
|
-
const b64 = buf.toString("base64");
|
|
21500
|
-
const ext = filePath.split(".").pop()?.toLowerCase();
|
|
21501
|
-
const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg" : ext === "webp" ? "image/webp" : "image/png";
|
|
21502
|
-
return { dataUrl: `data:${mime};base64,${b64}` };
|
|
21503
|
-
}
|
|
21504
|
-
async function loadUrlAsDataUrl(url) {
|
|
21505
|
-
const resp = await fetch(url);
|
|
21506
|
-
if (!resp.ok) {
|
|
21507
|
-
throw new Error(`Failed to fetch image: ${resp.status} ${resp.statusText}`);
|
|
21508
|
-
}
|
|
21509
|
-
const buf = Buffer.from(await resp.arrayBuffer());
|
|
21510
|
-
if (typeof Bun !== "undefined" && typeof Bun.Image !== "undefined") {
|
|
21511
|
-
try {
|
|
21512
|
-
const img = new Bun.Image(buf);
|
|
21513
|
-
const { width } = await img.metadata();
|
|
21514
|
-
const targetWidth = Math.min(800, width || 800);
|
|
21515
|
-
const dataUrl = await img.resize(targetWidth, 800, { fit: "inside" }).jpeg({ quality: 60 }).dataurl();
|
|
21516
|
-
return { dataUrl };
|
|
21517
|
-
} catch {}
|
|
21518
|
-
}
|
|
21519
|
-
const b64 = buf.toString("base64");
|
|
21520
|
-
return { dataUrl: `data:image/png;base64,${b64}` };
|
|
21521
|
-
}
|
|
21522
|
-
async function bufferToDataUrl(buf) {
|
|
21523
|
-
if (typeof Bun !== "undefined" && typeof Bun.Image !== "undefined") {
|
|
21524
|
-
try {
|
|
21525
|
-
const img = new Bun.Image(buf);
|
|
21526
|
-
const { width } = await img.metadata();
|
|
21527
|
-
const targetWidth = Math.min(800, width || 800);
|
|
21528
|
-
const dataUrl = await img.resize(targetWidth, 800, { fit: "inside" }).jpeg({ quality: 60 }).dataurl();
|
|
21529
|
-
return { dataUrl };
|
|
21530
|
-
} catch {}
|
|
21531
|
-
}
|
|
21532
|
-
const b64 = buf.toString("base64");
|
|
21533
|
-
return { dataUrl: `data:image/png;base64,${b64}` };
|
|
21534
|
-
}
|
|
21535
|
-
var MIME_MAP;
|
|
21536
|
-
var init_image_utils = __esm(() => {
|
|
21537
|
-
MIME_MAP = {
|
|
21538
|
-
".png": "image/png",
|
|
21539
|
-
".jpg": "image/jpeg",
|
|
21540
|
-
".jpeg": "image/jpeg",
|
|
21541
|
-
".gif": "image/gif",
|
|
21542
|
-
".webp": "image/webp",
|
|
21543
|
-
".bmp": "image/bmp",
|
|
21544
|
-
".svg": "image/svg+xml"
|
|
21545
|
-
};
|
|
21546
|
-
});
|
|
21547
|
-
|
|
21548
21562
|
// src/tools/attach-image.ts
|
|
21549
21563
|
import { existsSync as existsSync35 } from "fs";
|
|
21550
21564
|
import { resolve as resolve17 } from "path";
|
|
@@ -25291,7 +25305,7 @@ class BrowserModule {
|
|
|
25291
25305
|
"Workflow: open(url) → read snapshot → click/type on element numbers → read updated snapshot.",
|
|
25292
25306
|
'Each snapshot includes a "Content" section with the visible page text, a "Console" section with browser messages, and network errors. Use these to understand the page, detect JS errors, and verify the result of your actions.',
|
|
25293
25307
|
'Always start with "open" action. Use element numbers from snapshot for click/type targets.',
|
|
25294
|
-
'
|
|
25308
|
+
'The "screenshot" action is delivered automatically: a vision model receives the image, a text-only model receives the text snapshot instead. You do not need to decide — just call it and read what you get.'
|
|
25295
25309
|
].join(`
|
|
25296
25310
|
`),
|
|
25297
25311
|
priority: "normal",
|
|
@@ -27704,6 +27718,19 @@ var init_context_log = __esm(() => {
|
|
|
27704
27718
|
SEP2 = "─".repeat(78);
|
|
27705
27719
|
});
|
|
27706
27720
|
|
|
27721
|
+
// src/modules/providers/capabilities.ts
|
|
27722
|
+
function resolveModelCapabilities(preset, probe, override) {
|
|
27723
|
+
const base = preset ? { vision: preset.vision, tools: preset.tools } : { ...DEFAULT_MODEL_CAPABILITIES };
|
|
27724
|
+
return { ...base, ...probe ?? {}, ...override ?? {} };
|
|
27725
|
+
}
|
|
27726
|
+
var DEFAULT_MODEL_CAPABILITIES;
|
|
27727
|
+
var init_capabilities = __esm(() => {
|
|
27728
|
+
DEFAULT_MODEL_CAPABILITIES = {
|
|
27729
|
+
vision: false,
|
|
27730
|
+
tools: false
|
|
27731
|
+
};
|
|
27732
|
+
});
|
|
27733
|
+
|
|
27707
27734
|
// src/core/environment.ts
|
|
27708
27735
|
import { existsSync as existsSync50, readFileSync as readFileSync32, readdirSync as readdirSync16 } from "fs";
|
|
27709
27736
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
@@ -28335,7 +28362,9 @@ function buildSystemInfo(config, baseDir, profileCompressed) {
|
|
|
28335
28362
|
lines.push(`Runtime verification: when a bug is described in terms of runtime behavior (what happens when the app runs), reproduce it — run the app / build / browser tool — and confirm the fix against actual runtime output before calling "plan update step=N status=done". Check existing servers first with process_list/process_log: a stale dev server started BEFORE your change still runs the old pipeline and keeps failing even after the files on disk are fixed. Restart or kill it (process_kill) and verify the exact endpoint/port the user reported — a fresh server on a new port does NOT prove the user's error is gone.`);
|
|
28336
28363
|
const sec = config.security;
|
|
28337
28364
|
if (sec?.enabled) {
|
|
28338
|
-
const secLines = [
|
|
28365
|
+
const secLines = [
|
|
28366
|
+
`SECURITY RESTRICTIONS (enforced — blocked calls return "[SECURITY BLOCKED]", do not retry them):`
|
|
28367
|
+
];
|
|
28339
28368
|
if (sec.bash?.enabled && sec.bash.blacklist.length > 0) {
|
|
28340
28369
|
const cmds = sec.bash.blacklist.slice(0, 15).join(", ");
|
|
28341
28370
|
secLines.push(`Forbidden bash commands: ${cmds}${sec.bash.blacklist.length > 15 ? ", …" : ""}.`);
|
|
@@ -28417,19 +28446,23 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete, reas
|
|
|
28417
28446
|
logger4.info(t("env.model_loaded", { model: config.model, seconds: String(loadResult.loadTime) }));
|
|
28418
28447
|
}
|
|
28419
28448
|
}
|
|
28449
|
+
const capabilitiesRef = {};
|
|
28420
28450
|
const contextProbePromise = (async () => {
|
|
28421
28451
|
try {
|
|
28422
|
-
const probe = await
|
|
28452
|
+
const probe = await probeModel(config.provider.baseUrl, config.model);
|
|
28423
28453
|
if (!probe)
|
|
28424
28454
|
return null;
|
|
28425
|
-
if (probe.
|
|
28426
|
-
|
|
28427
|
-
|
|
28428
|
-
|
|
28455
|
+
if (probe.capabilities)
|
|
28456
|
+
capabilitiesRef.probe = probe.capabilities;
|
|
28457
|
+
const contextProbe = { model: probe.model, actual: probe.contextLength };
|
|
28458
|
+
if (probe.contextLength < config.contextWindow) {
|
|
28459
|
+
logger4.logSilent("warn", `Context probe: model "${probe.model}" is loaded with ${probe.contextLength} tokens, but contextWindow is configured as ${config.contextWindow} — overflow/429 risk. Lower contextWindow or reload the model with a larger context.`);
|
|
28460
|
+
} else if (probe.contextLength > config.contextWindow) {
|
|
28461
|
+
logger4.logSilent("info", `Context probe: model "${probe.model}" supports ${probe.contextLength} tokens — consider "mma context ${probe.contextLength}" to use the full window.`);
|
|
28429
28462
|
} else {
|
|
28430
|
-
logger4.logSilent("debug", `Context probe: configured contextWindow matches loaded context (${probe.
|
|
28463
|
+
logger4.logSilent("debug", `Context probe: configured contextWindow matches loaded context (${probe.contextLength})`);
|
|
28431
28464
|
}
|
|
28432
|
-
return
|
|
28465
|
+
return contextProbe;
|
|
28433
28466
|
} catch {
|
|
28434
28467
|
return null;
|
|
28435
28468
|
}
|
|
@@ -28608,6 +28641,14 @@ async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplete, reas
|
|
|
28608
28641
|
get: () => sessionManager,
|
|
28609
28642
|
configurable: true
|
|
28610
28643
|
});
|
|
28644
|
+
Object.defineProperty(toolCtx, "capabilities", {
|
|
28645
|
+
get: () => {
|
|
28646
|
+
const cfg = configRef.current;
|
|
28647
|
+
const spec = BUILTIN_PROVIDERS.find((p) => p.type === cfg.provider.type);
|
|
28648
|
+
return resolveModelCapabilities(spec?.capabilities, capabilitiesRef.probe, cfg.modelCapabilities?.[cfg.model]);
|
|
28649
|
+
},
|
|
28650
|
+
configurable: true
|
|
28651
|
+
});
|
|
28611
28652
|
const toolExecutor = new ToolExecutor(toolRegistry, toolCtx, pluginManager);
|
|
28612
28653
|
toolCtx.llmProvider = llmProvider;
|
|
28613
28654
|
toolCtx.toolExecutor = toolExecutor;
|
|
@@ -28831,6 +28872,7 @@ var init_bootstrap = __esm(() => {
|
|
|
28831
28872
|
init_i18n();
|
|
28832
28873
|
init_agent();
|
|
28833
28874
|
init_context_log();
|
|
28875
|
+
init_capabilities();
|
|
28834
28876
|
init_version();
|
|
28835
28877
|
init_environment();
|
|
28836
28878
|
init_utils();
|