nolo-cli 0.173.0-alpha.3 → 0.175.0-alpha.1
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/{agentDeleteCommand-HG5EDCO6.js → agentDeleteCommand-WI6U4NFI.js} +2 -2
- package/{agentEmailCommands-7YX27N6M.js → agentEmailCommands-Z3TNB4JY.js} +3 -3
- package/{agentListCommands-AT5IGY5W.js → agentListCommands-UHHP6HFB.js} +1 -1
- package/{agentMachineCommands-WR3IFBB6.js → agentMachineCommands-FIUBHYRT.js} +2 -2
- package/{agentRecordCommands-T6PXLK4B.js → agentRecordCommands-EQDWG4RY.js} +3 -3
- package/{agentRunCommand-RB3TFF6O.js → agentRunCommand-WZCHXCYS.js} +3 -3
- package/{agentRuntimeLocal-72TUBESS.js → agentRuntimeLocal-VQZCOFTL.js} +1 -1
- package/{appDeleteCommands-THIHX7BV.js → appDeleteCommands-3QISRABE.js} +1 -1
- package/{appDeployCommands-5JA5DDMR.js → appDeployCommands-UIMXDPOG.js} +1 -1
- package/{appDeployStatusCommands-EHFPCQHM.js → appDeployStatusCommands-D2ZA54DA.js} +1 -1
- package/{appGetCommands-4HSRXSYD.js → appGetCommands-QH2XYWOK.js} +1 -1
- package/{appListCommands-TD7BQFWQ.js → appListCommands-HH2HYJDZ.js} +1 -1
- package/{chunk-IVPXG433.js → chunk-FGSVT353.js} +425 -153
- package/{chunk-EVW2U6RH.js → chunk-MG63GUYM.js} +2 -2
- package/{chunk-EY3QEZZR.js → chunk-RNIZNIVT.js} +3 -3
- package/{chunk-2OWYKHKI.js → chunk-UILL43UU.js} +1 -1
- package/{chunk-FDPODXE3.js → chunk-Z4UP2EDE.js} +1 -1
- package/{commandRegistry-PJ3EQ6U7.js → commandRegistry-S54U5ZHX.js} +40 -40
- package/{dialogCommands-CPK6MUIJ.js → dialogCommands-A5CHG67A.js} +1 -1
- package/{docCreateCommands-GLR2HLYH.js → docCreateCommands-BF354FQW.js} +1 -1
- package/{docDeleteCommands-MPE53Z5W.js → docDeleteCommands-3K5K4XFO.js} +1 -1
- package/{docReadCommands-MFKWAFDI.js → docReadCommands-A7ZDZIQV.js} +1 -1
- package/{docUpdateCommands-3QBT2XTK.js → docUpdateCommands-5MGDR5ME.js} +1 -1
- package/index.js +3 -3
- package/{localRuntimeAdapter-KQHAGAXR.js → localRuntimeAdapter-ZAXRSVWI.js} +2 -2
- package/{machineCommands-DMPHPCQS.js → machineCommands-MT4FDVVG.js} +3 -3
- package/{machineWsRunDispatch-736CCGUF.js → machineWsRunDispatch-7T3SEMB6.js} +3 -3
- package/package.json +1 -1
- package/{readlineWorkspace-K4WNNW2D.js → readlineWorkspace-MTYLMWBM.js} +3 -3
- package/{spaceCommands-U4UYF6IB.js → spaceCommands-M44RQPCK.js} +1 -1
|
@@ -15723,11 +15723,334 @@ async function invokeCapability(capabilityOrName, input, ctx = {}) {
|
|
|
15723
15723
|
}
|
|
15724
15724
|
|
|
15725
15725
|
// packages/agent-runtime/localWorkspaceTools.ts
|
|
15726
|
-
import { existsSync as
|
|
15726
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
15727
15727
|
import { createHash as createHash4 } from "node:crypto";
|
|
15728
|
-
|
|
15728
|
+
|
|
15729
|
+
// packages/agent-runtime/visualRenderer.node.ts
|
|
15730
|
+
import { createRequire } from "node:module";
|
|
15731
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
15732
|
+
import { mkdir, realpath, stat, readFile, writeFile } from "node:fs/promises";
|
|
15733
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, relative as relative2, win32 } from "node:path";
|
|
15734
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
15735
|
+
import { lookup as lookup2 } from "node:dns/promises";
|
|
15736
|
+
import { isIP } from "node:net";
|
|
15737
|
+
function isVisualPathOutside(rel) {
|
|
15738
|
+
return rel === ".." || /^\.\.[\\/]/.test(rel) || isAbsolute2(rel) || win32.isAbsolute(rel);
|
|
15739
|
+
}
|
|
15740
|
+
async function validateVisualUrl(input, workspaceRoot) {
|
|
15741
|
+
const url = new URL(input);
|
|
15742
|
+
if (url.protocol === "file:") {
|
|
15743
|
+
const [root, path] = await Promise.all([realpath(workspaceRoot), realpath(fileURLToPath(url))]);
|
|
15744
|
+
const rel = relative2(root, path);
|
|
15745
|
+
if (isVisualPathOutside(rel)) throw new Error("Renderer file URL is outside workspaceRoot.");
|
|
15746
|
+
if (!(await stat(path)).isFile()) throw new Error("Renderer file URL must point to a file, not a directory.");
|
|
15747
|
+
return pathToFileURL(path);
|
|
15748
|
+
}
|
|
15749
|
+
if (!["http:", "https:"].includes(url.protocol)) throw new Error("Renderer URL must use http:, https:, or file:.");
|
|
15750
|
+
await assertVisualNetworkHost(url.hostname);
|
|
15751
|
+
return url;
|
|
15752
|
+
}
|
|
15753
|
+
async function assertVisualNetworkHost(host) {
|
|
15754
|
+
const hostname = host.replace(/^\[|\]$/g, "");
|
|
15755
|
+
const addresses = isIP(hostname) ? [{ address: hostname }] : await lookup2(hostname, { all: true });
|
|
15756
|
+
for (const { address } of addresses) {
|
|
15757
|
+
const normalized = new URL(`http://${address.includes(":") ? `[${address}]` : address}`).hostname.replace(/^\[|\]$/g, "").toLowerCase();
|
|
15758
|
+
const mapped = normalized.match(/^::ffff:([a-f0-9]+):([a-f0-9]+)$/);
|
|
15759
|
+
const ipv4 = mapped ? `${parseInt(mapped[1], 16) >> 8}.${parseInt(mapped[1], 16) & 255}.${parseInt(mapped[2], 16) >> 8}.${parseInt(mapped[2], 16) & 255}` : normalized;
|
|
15760
|
+
const octets = ipv4.split(".").map(Number);
|
|
15761
|
+
if (ipv4.startsWith("169.254.") || normalized === "fd00:ec2::254" || /^fe[89ab][0-9a-f]:/.test(normalized) || ipv4 === "100.100.100.200") {
|
|
15762
|
+
throw new Error(`Renderer denies metadata/link-local address: ${hostname}`);
|
|
15763
|
+
}
|
|
15764
|
+
const privateV4 = octets.length === 4 && (octets[0] === 10 || octets[0] === 172 && octets[1] >= 16 && octets[1] <= 31 || octets[0] === 192 && octets[1] === 168 || octets[0] === 100 && octets[1] >= 64 && octets[1] <= 127);
|
|
15765
|
+
const privateV6 = /^(fc|fd)[a-f0-9]{2}:/.test(normalized);
|
|
15766
|
+
if ((privateV4 || privateV6) && process.env.NOLO_VISUAL_RENDERER_ALLOW_PRIVATE !== "1") {
|
|
15767
|
+
throw new Error(`Renderer denies private address (LAN access requires NOLO_VISUAL_RENDERER_ALLOW_PRIVATE=1): ${hostname}`);
|
|
15768
|
+
}
|
|
15769
|
+
if (ipv4 === "0.0.0.0" || normalized === "::" || octets.length === 4 && octets[0] >= 224 || normalized.startsWith("ff")) {
|
|
15770
|
+
throw new Error(`Renderer denies non-unicast address: ${hostname}`);
|
|
15771
|
+
}
|
|
15772
|
+
}
|
|
15773
|
+
}
|
|
15774
|
+
var VisualResourceError = class extends Error {
|
|
15775
|
+
detectedAt = performance.now();
|
|
15776
|
+
};
|
|
15777
|
+
var VisualRenderSemaphore = class {
|
|
15778
|
+
constructor(limit) {
|
|
15779
|
+
this.limit = limit;
|
|
15780
|
+
}
|
|
15781
|
+
active = 0;
|
|
15782
|
+
queue = [];
|
|
15783
|
+
async run(task) {
|
|
15784
|
+
if (this.active >= this.limit) await new Promise((resolve6) => this.queue.push(resolve6));
|
|
15785
|
+
else this.active++;
|
|
15786
|
+
try {
|
|
15787
|
+
return await task();
|
|
15788
|
+
} finally {
|
|
15789
|
+
const next = this.queue.shift();
|
|
15790
|
+
if (next) next();
|
|
15791
|
+
else this.active--;
|
|
15792
|
+
}
|
|
15793
|
+
}
|
|
15794
|
+
};
|
|
15795
|
+
var configuredConcurrency = Number(process.env.NOLO_VISUAL_RENDERER_CONCURRENCY ?? 2);
|
|
15796
|
+
var renderSlots = new VisualRenderSemaphore(Number.isFinite(configuredConcurrency) ? Math.max(1, Math.min(2, Math.floor(configuredConcurrency))) : 2);
|
|
15797
|
+
var require2 = createRequire(import.meta.url);
|
|
15798
|
+
function visualRendererCapability() {
|
|
15799
|
+
if (process.env.NOLO_VISUAL_RENDERER_DISABLED === "1") return { available: false, reason: "Platform renderer disabled by NOLO_VISUAL_RENDERER_DISABLED=1 on this runtime host." };
|
|
15800
|
+
try {
|
|
15801
|
+
const { chromium } = require2("playwright");
|
|
15802
|
+
const executablePath = [chromium.executablePath(), resolveExecutableOnPath("google-chrome"), resolveExecutableOnPath("chromium"), resolveExecutableOnPath("chromium-browser")].find((path) => !!path && existsSync4(path));
|
|
15803
|
+
if (!executablePath) return { available: false, reason: "No Chromium renderer installed. Install Playwright Chromium (playwright install chromium) or system Chromium/Google Chrome on the runtime host." };
|
|
15804
|
+
return { available: true, chromium, executablePath };
|
|
15805
|
+
} catch {
|
|
15806
|
+
return { available: false, reason: "Platform renderer unavailable: install the platform Playwright dependency and Chromium on the runtime host (not a workspace npm script)." };
|
|
15807
|
+
}
|
|
15808
|
+
}
|
|
15809
|
+
async function renderVisualPage(args2) {
|
|
15810
|
+
const capability = visualRendererCapability();
|
|
15811
|
+
if (!capability.available) throw new Error(capability.reason);
|
|
15812
|
+
const url = await validateVisualUrl(args2.url, args2.workspaceRoot);
|
|
15813
|
+
return renderSlots.run(async () => {
|
|
15814
|
+
const timeout4 = Math.max(1, Math.min(args2.timeoutMs ?? 2e4, 2e4));
|
|
15815
|
+
const deadline = Date.now() + timeout4;
|
|
15816
|
+
const browser = await capability.chromium.launch({ executablePath: capability.executablePath, headless: true, timeout: timeout4 });
|
|
15817
|
+
let timer;
|
|
15818
|
+
try {
|
|
15819
|
+
const page = await browser.newPage({ viewport: { width: 1440, height: 1e3 }, serviceWorkers: "block" });
|
|
15820
|
+
const localOrigin = "https://nolo-workspace.invalid";
|
|
15821
|
+
const localFile = url.protocol === "file:";
|
|
15822
|
+
let routeError;
|
|
15823
|
+
let rejectRoute;
|
|
15824
|
+
const routeFailure = new Promise((_, reject) => {
|
|
15825
|
+
rejectRoute = reject;
|
|
15826
|
+
});
|
|
15827
|
+
await page.route("**/*", async (route) => {
|
|
15828
|
+
try {
|
|
15829
|
+
const requestUrl = new URL(route.request().url());
|
|
15830
|
+
if (localFile && requestUrl.origin === localOrigin) {
|
|
15831
|
+
const file = await validateVisualUrl(`file://${requestUrl.pathname}`, args2.workspaceRoot);
|
|
15832
|
+
const path = fileURLToPath(file);
|
|
15833
|
+
const type = path.endsWith(".html") ? "text/html; charset=utf-8" : path.endsWith(".css") ? "text/css; charset=utf-8" : path.endsWith(".js") ? "application/javascript; charset=utf-8" : void 0;
|
|
15834
|
+
await route.fulfill({ body: await readFile(path), ...type ? { contentType: type } : {} });
|
|
15835
|
+
} else {
|
|
15836
|
+
await validateVisualUrl(requestUrl.href, args2.workspaceRoot);
|
|
15837
|
+
if (requestUrl.protocol === "file:") throw new Error("Direct file requests are forbidden.");
|
|
15838
|
+
let next = requestUrl;
|
|
15839
|
+
for (let hop = 0; ; hop++) {
|
|
15840
|
+
await validateVisualUrl(next.href, args2.workspaceRoot);
|
|
15841
|
+
const response = await route.fetch({ url: next.href, maxRedirects: 0, timeout: Math.max(1, deadline - Date.now()) });
|
|
15842
|
+
const location = response.headers()["location"];
|
|
15843
|
+
if ([301, 302, 303, 307, 308].includes(response.status()) && location) {
|
|
15844
|
+
if (hop >= 10) throw new Error("Renderer redirect limit exceeded.");
|
|
15845
|
+
next = new URL(location, next);
|
|
15846
|
+
if (!["http:", "https:"].includes(next.protocol)) throw new Error("Renderer redirects must use http(s).");
|
|
15847
|
+
continue;
|
|
15848
|
+
}
|
|
15849
|
+
await route.fulfill({ response });
|
|
15850
|
+
break;
|
|
15851
|
+
}
|
|
15852
|
+
}
|
|
15853
|
+
} catch (error) {
|
|
15854
|
+
routeError = new VisualResourceError(error instanceof Error ? error.message : String(error));
|
|
15855
|
+
const closing = page.close({ runBeforeUnload: false }).catch(() => {
|
|
15856
|
+
});
|
|
15857
|
+
rejectRoute(routeError);
|
|
15858
|
+
await Promise.all([closing, route.abort().catch(() => {
|
|
15859
|
+
})]);
|
|
15860
|
+
}
|
|
15861
|
+
});
|
|
15862
|
+
const work = async () => {
|
|
15863
|
+
page.setDefaultTimeout(timeout4);
|
|
15864
|
+
await page.goto(localFile ? `${localOrigin}${url.pathname}` : url.href, { waitUntil: "load", timeout: timeout4 });
|
|
15865
|
+
await page.locator(args2.waitSelector).first().waitFor({ state: "visible" });
|
|
15866
|
+
if (args2.scrollSelector) await page.locator(args2.scrollSelector).first().scrollIntoViewIfNeeded();
|
|
15867
|
+
const targetSelector = args2.focusSelector ?? args2.waitSelector;
|
|
15868
|
+
await page.locator(targetSelector).first().scrollIntoViewIfNeeded();
|
|
15869
|
+
if (args2.expectText) await page.getByText(args2.expectText, { exact: false }).first().waitFor({ state: "visible" });
|
|
15870
|
+
const screenshot = await page.screenshot({ type: "png", timeout: timeout4 });
|
|
15871
|
+
const metrics = await page.evaluate(({ waitSelector, scrollSelector, targetSelector: targetSelector2 }) => {
|
|
15872
|
+
const target = document.querySelector(targetSelector2);
|
|
15873
|
+
const rect = target.getBoundingClientRect();
|
|
15874
|
+
const style = getComputedStyle(target);
|
|
15875
|
+
return {
|
|
15876
|
+
waitCount: document.querySelectorAll(waitSelector).length,
|
|
15877
|
+
scrollCount: scrollSelector ? document.querySelectorAll(scrollSelector).length : null,
|
|
15878
|
+
focusCount: document.querySelectorAll(targetSelector2).length,
|
|
15879
|
+
targetText: target.textContent?.trim().slice(0, 240) ?? "",
|
|
15880
|
+
targetRect: Object.fromEntries(["width", "height", "left", "right", "top", "bottom"].map((key) => [key, Math.round(rect[key])])),
|
|
15881
|
+
targetComputed: Object.fromEntries(["display", "visibility", "opacity", "position", "width", "height", "pointerEvents"].map((key) => [key, style[key]])),
|
|
15882
|
+
bodyTextSample: document.body.innerText.slice(0, 500)
|
|
15883
|
+
};
|
|
15884
|
+
}, { waitSelector: args2.waitSelector, scrollSelector: args2.scrollSelector, targetSelector });
|
|
15885
|
+
if (routeError) throw routeError;
|
|
15886
|
+
const result4 = { ...metrics, pageUrl: localFile ? url.href : page.url(), title: await page.title(), viewport: page.viewportSize(), renderer: "platform-playwright", capturedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
15887
|
+
if (Date.now() >= deadline) throw new Error("Renderer timed out before screenshot persistence.");
|
|
15888
|
+
if (timer) clearTimeout(timer);
|
|
15889
|
+
await mkdir(dirname2(args2.screenshotPath), { recursive: true });
|
|
15890
|
+
await writeFile(args2.screenshotPath, screenshot);
|
|
15891
|
+
return result4;
|
|
15892
|
+
};
|
|
15893
|
+
return await Promise.race([routeFailure, work(), new Promise((_, reject) => {
|
|
15894
|
+
timer = setTimeout(() => reject(new Error(`Renderer timed out after ${timeout4}ms.`)), Math.max(1, deadline - Date.now()));
|
|
15895
|
+
})]);
|
|
15896
|
+
} finally {
|
|
15897
|
+
if (timer) clearTimeout(timer);
|
|
15898
|
+
await browser.close();
|
|
15899
|
+
}
|
|
15900
|
+
});
|
|
15901
|
+
}
|
|
15902
|
+
|
|
15903
|
+
// packages/agent-runtime/hostWindowCapture.ts
|
|
15904
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
15905
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
15906
|
+
var HOST_CAPTURE_TIMEOUT_MS = 9e3;
|
|
15907
|
+
var UNAVAILABLE = "\u5F53\u524D\u73AF\u5883\u65E0\u53EF\u7528\u622A\u5C4F\u6743\u9650/\u670D\u52A1";
|
|
15908
|
+
var PORTAL_SCRIPT = String.raw`
|
|
15909
|
+
import json, signal
|
|
15910
|
+
from gi.repository import Gio, GLib
|
|
15911
|
+
bus = Gio.bus_get_sync(Gio.BusType.SESSION, None)
|
|
15912
|
+
loop = GLib.MainLoop()
|
|
15913
|
+
handle = None
|
|
15914
|
+
finished = False
|
|
15915
|
+
result = None
|
|
15916
|
+
responses = {}
|
|
15917
|
+
def response(conn, sender, path, interface, name, parameters, data):
|
|
15918
|
+
global result, finished
|
|
15919
|
+
responses[path] = parameters.unpack()
|
|
15920
|
+
if handle == path:
|
|
15921
|
+
result = responses[path]
|
|
15922
|
+
finished = True
|
|
15923
|
+
loop.quit()
|
|
15924
|
+
def stop(*args):
|
|
15925
|
+
loop.quit()
|
|
15926
|
+
return False
|
|
15927
|
+
subscription = bus.signal_subscribe('org.freedesktop.portal.Desktop', 'org.freedesktop.portal.Request', 'Response', None, None, Gio.DBusSignalFlags.NONE, response, None)
|
|
15928
|
+
signal.signal(signal.SIGTERM, stop)
|
|
15929
|
+
try:
|
|
15930
|
+
options = {'interactive': GLib.Variant('b', True), 'handle_token': GLib.Variant('s', 'nolo_capture')}
|
|
15931
|
+
handle = bus.call_sync('org.freedesktop.portal.Desktop', '/org/freedesktop/portal/desktop', 'org.freedesktop.portal.Screenshot', 'Screenshot', GLib.Variant('(sa{sv})', ('', options)), GLib.VariantType.new('(o)'), Gio.DBusCallFlags.NONE, 7000, None).unpack()[0]
|
|
15932
|
+
if handle in responses:
|
|
15933
|
+
result = responses[handle]
|
|
15934
|
+
finished = True
|
|
15935
|
+
else:
|
|
15936
|
+
GLib.timeout_add(8000, stop)
|
|
15937
|
+
loop.run()
|
|
15938
|
+
if result is None:
|
|
15939
|
+
print(json.dumps({'error': 'portal response timeout'}), flush=True)
|
|
15940
|
+
else:
|
|
15941
|
+
code, values = result
|
|
15942
|
+
print(json.dumps({'code': code, 'uri': values.get('uri')}), flush=True)
|
|
15943
|
+
finally:
|
|
15944
|
+
if handle and not finished:
|
|
15945
|
+
try:
|
|
15946
|
+
bus.call_sync('org.freedesktop.portal.Desktop', handle, 'org.freedesktop.portal.Request', 'Close', None, None, Gio.DBusCallFlags.NONE, 200, None)
|
|
15947
|
+
except Exception:
|
|
15948
|
+
pass
|
|
15949
|
+
bus.signal_unsubscribe(subscription)
|
|
15950
|
+
`;
|
|
15951
|
+
var capturePortalScreenshot = (signal) => new Promise((resolve6, reject) => {
|
|
15952
|
+
const child = spawn2("/usr/bin/python3", ["-c", PORTAL_SCRIPT], { stdio: ["ignore", "pipe", "pipe"] });
|
|
15953
|
+
let stdout = "";
|
|
15954
|
+
let stderr = "";
|
|
15955
|
+
const stop = () => {
|
|
15956
|
+
child.kill("SIGTERM");
|
|
15957
|
+
};
|
|
15958
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
15959
|
+
if (signal.aborted) stop();
|
|
15960
|
+
child.stdout.on("data", (data) => {
|
|
15961
|
+
stdout = (stdout + data).slice(-16384);
|
|
15962
|
+
});
|
|
15963
|
+
child.stderr.on("data", (data) => {
|
|
15964
|
+
stderr = (stderr + data).slice(-4096);
|
|
15965
|
+
});
|
|
15966
|
+
child.once("error", reject);
|
|
15967
|
+
child.once("close", (code) => {
|
|
15968
|
+
signal.removeEventListener("abort", stop);
|
|
15969
|
+
if (signal.aborted) return reject(new Error("portal capture timeout"));
|
|
15970
|
+
if (code !== 0) return reject(new Error(stderr.trim() || `portal helper exited ${code}`));
|
|
15971
|
+
try {
|
|
15972
|
+
resolve6(JSON.parse(stdout));
|
|
15973
|
+
} catch {
|
|
15974
|
+
reject(new Error("invalid portal response"));
|
|
15975
|
+
}
|
|
15976
|
+
});
|
|
15977
|
+
});
|
|
15978
|
+
function createHostWindowCapture(options = {}) {
|
|
15979
|
+
const concurrency = Math.max(1, Math.min(2, Math.floor(options.concurrency || 1)));
|
|
15980
|
+
const timeoutMs = Math.max(1, Math.min(HOST_CAPTURE_TIMEOUT_MS, options.timeoutMs || HOST_CAPTURE_TIMEOUT_MS));
|
|
15981
|
+
let active = 0;
|
|
15982
|
+
const waiters = [];
|
|
15983
|
+
function acquire(signal) {
|
|
15984
|
+
return new Promise((resolve6, reject) => {
|
|
15985
|
+
const abort = () => {
|
|
15986
|
+
const index = waiters.indexOf(start);
|
|
15987
|
+
if (index >= 0) waiters.splice(index, 1);
|
|
15988
|
+
reject(new Error("portal capture timeout (queue)"));
|
|
15989
|
+
};
|
|
15990
|
+
const start = () => {
|
|
15991
|
+
signal.removeEventListener("abort", abort);
|
|
15992
|
+
active++;
|
|
15993
|
+
resolve6();
|
|
15994
|
+
};
|
|
15995
|
+
if (signal.aborted) return abort();
|
|
15996
|
+
if (active < concurrency) start();
|
|
15997
|
+
else {
|
|
15998
|
+
waiters.push(start);
|
|
15999
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
16000
|
+
}
|
|
16001
|
+
});
|
|
16002
|
+
}
|
|
16003
|
+
return async () => {
|
|
16004
|
+
const controller = new AbortController();
|
|
16005
|
+
let timer;
|
|
16006
|
+
const work = async () => {
|
|
16007
|
+
await acquire(controller.signal);
|
|
16008
|
+
try {
|
|
16009
|
+
const reply = await (options.portal ?? capturePortalScreenshot)(controller.signal);
|
|
16010
|
+
if (controller.signal.aborted) throw new Error("portal capture timeout");
|
|
16011
|
+
if (reply.error) throw new Error(reply.error);
|
|
16012
|
+
if (reply.code !== 0) throw new Error(reply.code === 1 ? "portal denied/cancelled (Response 1)" : `portal failed (Response ${reply.code})`);
|
|
16013
|
+
if (!reply.uri) throw new Error("portal returned no file URI");
|
|
16014
|
+
const uri = new URL(reply.uri);
|
|
16015
|
+
if (uri.protocol !== "file:" || uri.hostname && uri.hostname !== "localhost") throw new Error("portal returned a non-local file URI");
|
|
16016
|
+
const path = fileURLToPath2(uri);
|
|
16017
|
+
const image = await (options.readImage ?? readLocalImage)(path);
|
|
16018
|
+
if (!image.images?.length) throw new Error("portal image could not be decoded");
|
|
16019
|
+
const dimensions = /\[image (\d+x\d+) (\d+) bytes/.exec(image.content);
|
|
16020
|
+
if (!dimensions) throw new Error("invalid image summary");
|
|
16021
|
+
return {
|
|
16022
|
+
...image,
|
|
16023
|
+
content: `[screenshot ${dimensions[1]} ${Math.round(Number(dimensions[2]) / 1024)}KB \u2192 ${path}]
|
|
16024
|
+
\u6765\u6E90\uFF1A\u5BBF\u4E3B portal \u5168\u5C4F/\u4EA4\u4E92\u9009\u62E9\u7ED3\u679C\uFF1Bportal \u672A\u63D0\u4F9B\u5E94\u7528/\u7A97\u53E3\u8EAB\u4EFD\uFF0C\u65E0\u6CD5\u786E\u8BA4\u7A97\u53E3\u540D\u3002`
|
|
16025
|
+
};
|
|
16026
|
+
} finally {
|
|
16027
|
+
active--;
|
|
16028
|
+
waiters.shift()?.();
|
|
16029
|
+
}
|
|
16030
|
+
};
|
|
16031
|
+
try {
|
|
16032
|
+
return await Promise.race([
|
|
16033
|
+
work(),
|
|
16034
|
+
new Promise((_, reject) => {
|
|
16035
|
+
timer = setTimeout(() => {
|
|
16036
|
+
controller.abort();
|
|
16037
|
+
reject(new Error("portal capture timeout"));
|
|
16038
|
+
}, timeoutMs);
|
|
16039
|
+
})
|
|
16040
|
+
]);
|
|
16041
|
+
} catch (error) {
|
|
16042
|
+
return { content: `${UNAVAILABLE}\uFF1A${error instanceof Error ? error.message : String(error)}` };
|
|
16043
|
+
} finally {
|
|
16044
|
+
clearTimeout(timer);
|
|
16045
|
+
}
|
|
16046
|
+
};
|
|
16047
|
+
}
|
|
16048
|
+
var captureHostWindow = createHostWindowCapture();
|
|
16049
|
+
|
|
16050
|
+
// packages/agent-runtime/localWorkspaceTools.ts
|
|
16051
|
+
import { lstat, mkdir as mkdir2, readdir as readdir2, readFile as readFile4, realpath as realpath2, stat as stat4, writeFile as writeFile2 } from "node:fs/promises";
|
|
15729
16052
|
import { homedir } from "node:os";
|
|
15730
|
-
import { dirname as
|
|
16053
|
+
import { dirname as dirname3, isAbsolute as isAbsolute3, relative as relative4, resolve as resolve4, sep } from "node:path";
|
|
15731
16054
|
import { spawn as spawnChildProcess3 } from "node:child_process";
|
|
15732
16055
|
|
|
15733
16056
|
// packages/core/readRangeLedger.ts
|
|
@@ -15763,15 +16086,15 @@ function isLineRangeCovered(request3, covered) {
|
|
|
15763
16086
|
}
|
|
15764
16087
|
|
|
15765
16088
|
// packages/agent-runtime/fileTextSearch.ts
|
|
15766
|
-
import { readdir, readFile as
|
|
15767
|
-
import { basename, relative as
|
|
16089
|
+
import { readdir, readFile as readFile3, stat as stat3 } from "node:fs/promises";
|
|
16090
|
+
import { basename, relative as relative3, resolve as resolve3 } from "node:path";
|
|
15768
16091
|
import { spawn as spawnChildProcess2 } from "node:child_process";
|
|
15769
16092
|
|
|
15770
16093
|
// packages/agent-runtime/gitignore.ts
|
|
15771
|
-
import { readFile, stat } from "node:fs/promises";
|
|
16094
|
+
import { readFile as readFile2, stat as stat2 } from "node:fs/promises";
|
|
15772
16095
|
import { resolve as resolve2 } from "node:path";
|
|
15773
16096
|
async function readRootGitignorePatterns(workspaceRoot) {
|
|
15774
|
-
const content = await
|
|
16097
|
+
const content = await readFile2(resolve2(workspaceRoot, ".gitignore"), "utf8").catch(() => null);
|
|
15775
16098
|
if (content === null) return [];
|
|
15776
16099
|
return content.split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith("#") && !line.startsWith("!")).flatMap((line) => {
|
|
15777
16100
|
const unanchored = line.replace(/^\//, "");
|
|
@@ -15782,7 +16105,7 @@ async function readRootGitignorePatterns(workspaceRoot) {
|
|
|
15782
16105
|
}
|
|
15783
16106
|
async function hasRootGitignore(workspaceRoot) {
|
|
15784
16107
|
try {
|
|
15785
|
-
await
|
|
16108
|
+
await stat2(resolve2(workspaceRoot, ".gitignore"));
|
|
15786
16109
|
return true;
|
|
15787
16110
|
} catch {
|
|
15788
16111
|
return false;
|
|
@@ -15791,7 +16114,7 @@ async function hasRootGitignore(workspaceRoot) {
|
|
|
15791
16114
|
|
|
15792
16115
|
// packages/agent-runtime/fileTextSearch.ts
|
|
15793
16116
|
var normalizePath = (value) => value.replaceAll("\\", "/");
|
|
15794
|
-
var pathFor = (root, file, absolute = false) => absolute ? normalizePath(file) : normalizePath(
|
|
16117
|
+
var pathFor = (root, file, absolute = false) => absolute ? normalizePath(file) : normalizePath(relative3(root, file) || basename(file));
|
|
15795
16118
|
var COMMAND_LIMIT_HEADROOM = 1;
|
|
15796
16119
|
async function runCommand(binary, args2, cwd, maxResults) {
|
|
15797
16120
|
const recordLimit = maxResults === void 0 ? 0 : Math.max(0, maxResults) + COMMAND_LIMIT_HEADROOM;
|
|
@@ -15851,7 +16174,7 @@ async function decorate(root, raw, context3, absolute = false) {
|
|
|
15851
16174
|
let lines = cache.get(x.file);
|
|
15852
16175
|
if (!lines) {
|
|
15853
16176
|
try {
|
|
15854
|
-
lines = (await
|
|
16177
|
+
lines = (await readFile3(x.file, "utf8")).split(/\r\n|\r|\n/);
|
|
15855
16178
|
} catch {
|
|
15856
16179
|
continue;
|
|
15857
16180
|
}
|
|
@@ -15880,13 +16203,13 @@ function globMatch(pattern, value) {
|
|
|
15880
16203
|
const r = new RegExp(`^${source}$`);
|
|
15881
16204
|
return r.test(value) || r.test(basename(value));
|
|
15882
16205
|
}
|
|
15883
|
-
var defaultReadTextFile = (filePath) =>
|
|
16206
|
+
var defaultReadTextFile = (filePath) => readFile3(filePath, "utf8").catch(() => "");
|
|
15884
16207
|
async function jsSearch(o, targetPath) {
|
|
15885
16208
|
const max4 = o.maxResults ?? Number.MAX_SAFE_INTEGER;
|
|
15886
16209
|
const excludes = o.exclude ?? [];
|
|
15887
16210
|
const readText = o.readTextFile ?? defaultReadTextFile;
|
|
15888
16211
|
const files = [];
|
|
15889
|
-
const targetStat = await
|
|
16212
|
+
const targetStat = await stat3(targetPath).catch(() => null);
|
|
15890
16213
|
if (!targetStat) return { matches: [], totalMatches: 0, truncated: false, engine: "js" };
|
|
15891
16214
|
const ignorePatterns = o.includeIgnored ? [] : await readRootGitignorePatterns(o.root);
|
|
15892
16215
|
const isIgnored = (rel) => ignorePatterns.some((pattern) => globMatch(pattern, rel));
|
|
@@ -15932,7 +16255,7 @@ var COMMAND_ENGINES = [
|
|
|
15932
16255
|
];
|
|
15933
16256
|
async function searchFileTextMatches(o) {
|
|
15934
16257
|
const targetPath = resolve3(o.root, o.target || ".");
|
|
15935
|
-
const targetStat = await
|
|
16258
|
+
const targetStat = await stat3(targetPath).catch(() => null);
|
|
15936
16259
|
if (!targetStat) return { matches: [], totalMatches: 0, truncated: false, engine: "js" };
|
|
15937
16260
|
if (targetStat.isFile()) return jsSearch(o, targetPath);
|
|
15938
16261
|
const run2 = o.runCommand ?? runCommand, max4 = o.maxResults;
|
|
@@ -16241,6 +16564,7 @@ function runTasks(args2) {
|
|
|
16241
16564
|
var WORKSPACE_TOOL_NAMES = [
|
|
16242
16565
|
"readFile",
|
|
16243
16566
|
"read_image",
|
|
16567
|
+
"captureHostWindow",
|
|
16244
16568
|
"writeFile",
|
|
16245
16569
|
"editFile",
|
|
16246
16570
|
"globFiles",
|
|
@@ -16397,7 +16721,7 @@ function buildCaptureVisualStateTool() {
|
|
|
16397
16721
|
type: "function",
|
|
16398
16722
|
function: {
|
|
16399
16723
|
name: "captureVisualState",
|
|
16400
|
-
description: "
|
|
16724
|
+
description: "Render a URL to an image and DOM/computed-style metrics using platform Playwright/Chromium on the runtime host. Requires an installed renderer; never runs workspace scripts.",
|
|
16401
16725
|
parameters: {
|
|
16402
16726
|
type: "object",
|
|
16403
16727
|
properties: {
|
|
@@ -16546,6 +16870,16 @@ function buildTaskStopTool() {
|
|
|
16546
16870
|
};
|
|
16547
16871
|
}
|
|
16548
16872
|
function buildWorkspaceToolDefinition(toolName) {
|
|
16873
|
+
if (toolName === "captureHostWindow") {
|
|
16874
|
+
return {
|
|
16875
|
+
type: "function",
|
|
16876
|
+
function: {
|
|
16877
|
+
name: "captureHostWindow",
|
|
16878
|
+
description: "Capture the host desktop through the Screenshot portal (interactive permission/selection may appear). Returns an image or a fail-closed reason within 9s. Portal does not identify the application/window: never infer a window name. Linux host requires /usr/bin/python3 with Gio. No arguments.",
|
|
16879
|
+
parameters: { type: "object", properties: {}, additionalProperties: false }
|
|
16880
|
+
}
|
|
16881
|
+
};
|
|
16882
|
+
}
|
|
16549
16883
|
if (toolName === "read_image") {
|
|
16550
16884
|
return {
|
|
16551
16885
|
type: "function",
|
|
@@ -16668,6 +17002,7 @@ function buildLocalWorkspaceOpenAiTools(args2) {
|
|
|
16668
17002
|
const declaredTools = new Set(args2.toolNames ?? []);
|
|
16669
17003
|
return WORKSPACE_TOOL_NAMES.filter((toolName) => {
|
|
16670
17004
|
if (!declaredTools.has(toolName)) return false;
|
|
17005
|
+
if (toolName === "captureVisualState" && !visualRendererCapability().available) return false;
|
|
16671
17006
|
if (!args2.exposeShellTools && SHELL_TOOL_NAMES.includes(toolName)) {
|
|
16672
17007
|
return false;
|
|
16673
17008
|
}
|
|
@@ -16682,11 +17017,11 @@ function parseWorkspaceToolArguments(raw) {
|
|
|
16682
17017
|
}
|
|
16683
17018
|
}
|
|
16684
17019
|
function normalizeWorkspaceRelativePath(args2) {
|
|
16685
|
-
const relativePath =
|
|
17020
|
+
const relativePath = relative4(args2.workspaceRoot, args2.targetPath);
|
|
16686
17021
|
return relativePath || ".";
|
|
16687
17022
|
}
|
|
16688
17023
|
function isPathInsideWorkspace(args2) {
|
|
16689
|
-
const relativePath =
|
|
17024
|
+
const relativePath = relative4(args2.workspaceRoot, args2.targetPath);
|
|
16690
17025
|
return relativePath === "" || !relativePath.startsWith("..") && !relativePath.includes(`..${sep}`);
|
|
16691
17026
|
}
|
|
16692
17027
|
var PATH_FIELD_ALIASES = ["path", "file_path", "filePath", "filename", "file", "target_file", "targetFile", "file_to_read", "fileToRead", "target_path", "read_path", "path_to_read", "pathToRead"];
|
|
@@ -16950,77 +17285,11 @@ function readVisualStateCaptureArgs(args2) {
|
|
|
16950
17285
|
metricsPath
|
|
16951
17286
|
};
|
|
16952
17287
|
}
|
|
16953
|
-
async function readWorkspacePackageScripts(workspaceRoot) {
|
|
16954
|
-
try {
|
|
16955
|
-
const raw = await readFile3(resolve4(workspaceRoot, "package.json"), "utf8");
|
|
16956
|
-
const parsed = JSON.parse(raw);
|
|
16957
|
-
const scripts = parsed && typeof parsed === "object" && parsed.scripts && typeof parsed.scripts === "object" ? Object.keys(parsed.scripts).sort() : [];
|
|
16958
|
-
return { scripts };
|
|
16959
|
-
} catch (error) {
|
|
16960
|
-
return {
|
|
16961
|
-
scripts: [],
|
|
16962
|
-
error: toErrorMessage(error)
|
|
16963
|
-
};
|
|
16964
|
-
}
|
|
16965
|
-
}
|
|
16966
|
-
function formatAvailableScripts(scripts) {
|
|
16967
|
-
return scripts.length ? scripts.join(", ") : "(none)";
|
|
16968
|
-
}
|
|
16969
|
-
async function runWorkspacePackageScript(args2) {
|
|
16970
|
-
const script = args2.script.trim();
|
|
16971
|
-
if (!script) throw new Error("package script name is required.");
|
|
16972
|
-
const extraArgs = args2.extraArgs ?? [];
|
|
16973
|
-
const packageScripts = await readWorkspacePackageScripts(args2.workspaceRoot);
|
|
16974
|
-
if (!packageScripts.scripts.includes(script)) {
|
|
16975
|
-
return {
|
|
16976
|
-
content: [
|
|
16977
|
-
`script not found: ${script}`,
|
|
16978
|
-
`available scripts: ${formatAvailableScripts(packageScripts.scripts)}`,
|
|
16979
|
-
...packageScripts.error ? [`package.json read error: ${packageScripts.error}`] : []
|
|
16980
|
-
].join("\n"),
|
|
16981
|
-
metadata: {
|
|
16982
|
-
script,
|
|
16983
|
-
exitCode: 1,
|
|
16984
|
-
reason: "script-not-found",
|
|
16985
|
-
availableScripts: packageScripts.scripts,
|
|
16986
|
-
...packageScripts.error ? { packageJsonError: packageScripts.error } : {}
|
|
16987
|
-
}
|
|
16988
|
-
};
|
|
16989
|
-
}
|
|
16990
|
-
const result4 = await runWorkspaceCommand({
|
|
16991
|
-
workspaceRoot: args2.workspaceRoot,
|
|
16992
|
-
command: ["bun", "run", script, ...extraArgs.length ? ["--", ...extraArgs] : []],
|
|
16993
|
-
timeoutMs: args2.commandTimeoutMs
|
|
16994
|
-
});
|
|
16995
|
-
return {
|
|
16996
|
-
content: result4.content,
|
|
16997
|
-
metadata: {
|
|
16998
|
-
script,
|
|
16999
|
-
args: extraArgs,
|
|
17000
|
-
exitCode: result4.exitCode,
|
|
17001
|
-
reason: result4.exitCode === 0 ? "ok" : "script-failed",
|
|
17002
|
-
timedOut: result4.timedOut,
|
|
17003
|
-
stdoutTail: result4.stdout.trim().slice(-4e3),
|
|
17004
|
-
stderrTail: result4.stderr.trim().slice(-4e3)
|
|
17005
|
-
}
|
|
17006
|
-
};
|
|
17007
|
-
}
|
|
17008
|
-
function parseLastJsonObject(text2) {
|
|
17009
|
-
const trimmed = text2.trim();
|
|
17010
|
-
for (let index = trimmed.lastIndexOf("{"); index >= 0; index = trimmed.lastIndexOf("{", index - 1)) {
|
|
17011
|
-
try {
|
|
17012
|
-
const parsed = JSON.parse(trimmed.slice(index));
|
|
17013
|
-
return parsed && typeof parsed === "object" ? parsed : null;
|
|
17014
|
-
} catch {
|
|
17015
|
-
}
|
|
17016
|
-
}
|
|
17017
|
-
return null;
|
|
17018
|
-
}
|
|
17019
17288
|
function resolveRipgrepBinary(env = process.env) {
|
|
17020
17289
|
const fromEnv = asOptionalTrimmedString(env.NOLO_BUNDLED_RG);
|
|
17021
17290
|
if (fromEnv) {
|
|
17022
17291
|
try {
|
|
17023
|
-
if (
|
|
17292
|
+
if (existsSync5(fromEnv)) return fromEnv;
|
|
17024
17293
|
} catch {
|
|
17025
17294
|
}
|
|
17026
17295
|
}
|
|
@@ -17028,8 +17297,8 @@ function resolveRipgrepBinary(env = process.env) {
|
|
|
17028
17297
|
}
|
|
17029
17298
|
var SEARCH_OUTPUT_CHAR_LIMIT = 2e4;
|
|
17030
17299
|
function isPathInsideRoot(root, targetPath) {
|
|
17031
|
-
const rel =
|
|
17032
|
-
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !
|
|
17300
|
+
const rel = relative4(resolve4(root), resolve4(targetPath));
|
|
17301
|
+
return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute3(rel);
|
|
17033
17302
|
}
|
|
17034
17303
|
async function resolveRuntimeOwnedSpillPath(targetPath) {
|
|
17035
17304
|
const spillRoot = resolveNoloStateDir("spills");
|
|
@@ -17037,7 +17306,7 @@ async function resolveRuntimeOwnedSpillPath(targetPath) {
|
|
|
17037
17306
|
const rootStat = await lstat(spillRoot).catch(() => void 0);
|
|
17038
17307
|
if (rootStat?.isDirectory() !== true) return null;
|
|
17039
17308
|
try {
|
|
17040
|
-
const [realRoot, realTarget] = await Promise.all([
|
|
17309
|
+
const [realRoot, realTarget] = await Promise.all([realpath2(spillRoot), realpath2(targetPath)]);
|
|
17041
17310
|
return isPathInsideRoot(realRoot, realTarget) ? realTarget : null;
|
|
17042
17311
|
} catch {
|
|
17043
17312
|
return null;
|
|
@@ -17137,7 +17406,7 @@ async function readFileTool(args2) {
|
|
|
17137
17406
|
allowRuntimeOwnedSpill: true,
|
|
17138
17407
|
...args2.confirmExternalFileAccess ? { confirmExternalFileAccess: args2.confirmExternalFileAccess } : {}
|
|
17139
17408
|
});
|
|
17140
|
-
const content = await
|
|
17409
|
+
const content = await readFile4(absolutePath, "utf8");
|
|
17141
17410
|
const activity = extractActivity(parsed);
|
|
17142
17411
|
const sliced = sliceReadFileContent({
|
|
17143
17412
|
content,
|
|
@@ -17221,8 +17490,8 @@ async function writeFileTool(args2) {
|
|
|
17221
17490
|
requestedPath,
|
|
17222
17491
|
...args2.confirmExternalFileAccess ? { confirmExternalFileAccess: args2.confirmExternalFileAccess } : {}
|
|
17223
17492
|
});
|
|
17224
|
-
await
|
|
17225
|
-
await
|
|
17493
|
+
await mkdir2(dirname3(absolutePath), { recursive: true });
|
|
17494
|
+
await writeFile2(absolutePath, content, "utf8");
|
|
17226
17495
|
const relativePath = normalizeWorkspaceRelativePath({
|
|
17227
17496
|
workspaceRoot: resolve4(args2.workspaceRoot),
|
|
17228
17497
|
targetPath: absolutePath
|
|
@@ -17330,7 +17599,7 @@ async function editFileTool(args2) {
|
|
|
17330
17599
|
requestedPath,
|
|
17331
17600
|
...args2.confirmExternalFileAccess ? { confirmExternalFileAccess: args2.confirmExternalFileAccess } : {}
|
|
17332
17601
|
});
|
|
17333
|
-
const content = await
|
|
17602
|
+
const content = await readFile4(absolutePath, "utf8");
|
|
17334
17603
|
const { count: replacementCount, result: nextContent } = countAndReplaceEolTolerant({
|
|
17335
17604
|
content,
|
|
17336
17605
|
oldText,
|
|
@@ -17344,7 +17613,7 @@ async function editFileTool(args2) {
|
|
|
17344
17613
|
${hint}` : "")
|
|
17345
17614
|
);
|
|
17346
17615
|
}
|
|
17347
|
-
await
|
|
17616
|
+
await writeFile2(absolutePath, nextContent, "utf8");
|
|
17348
17617
|
const relativePath = normalizeWorkspaceRelativePath({
|
|
17349
17618
|
workspaceRoot: resolve4(args2.workspaceRoot),
|
|
17350
17619
|
targetPath: absolutePath
|
|
@@ -17387,8 +17656,8 @@ async function listWorkspaceEntries(args2) {
|
|
|
17387
17656
|
for (const name of names) {
|
|
17388
17657
|
if (truncated) break;
|
|
17389
17658
|
const absolutePath = resolve4(dirPath, name);
|
|
17390
|
-
const info = await
|
|
17391
|
-
const relativePath =
|
|
17659
|
+
const info = await stat4(absolutePath);
|
|
17660
|
+
const relativePath = relative4(args2.dirPath, absolutePath) || ".";
|
|
17392
17661
|
const isDirectory = info.isDirectory();
|
|
17393
17662
|
entries.push(isDirectory ? `${relativePath}/` : relativePath);
|
|
17394
17663
|
if (args2.maxResults && entries.length >= args2.maxResults) {
|
|
@@ -17600,59 +17869,52 @@ async function resolveVisualStateBaseUrl(args2) {
|
|
|
17600
17869
|
return DEFAULT_LOCAL_API_ORIGIN;
|
|
17601
17870
|
}
|
|
17602
17871
|
async function captureVisualStateTool(args2) {
|
|
17603
|
-
|
|
17604
|
-
|
|
17605
|
-
|
|
17606
|
-
|
|
17607
|
-
|
|
17608
|
-
|
|
17609
|
-
"
|
|
17610
|
-
|
|
17611
|
-
|
|
17612
|
-
captureArgs.
|
|
17613
|
-
|
|
17614
|
-
|
|
17615
|
-
|
|
17616
|
-
|
|
17617
|
-
|
|
17618
|
-
|
|
17619
|
-
|
|
17620
|
-
|
|
17621
|
-
|
|
17622
|
-
|
|
17623
|
-
|
|
17624
|
-
|
|
17625
|
-
|
|
17626
|
-
|
|
17627
|
-
|
|
17628
|
-
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17635
|
-
|
|
17636
|
-
|
|
17637
|
-
|
|
17638
|
-
|
|
17639
|
-
|
|
17640
|
-
|
|
17641
|
-
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17645
|
-
|
|
17646
|
-
|
|
17647
|
-
|
|
17648
|
-
|
|
17649
|
-
focusSelector: captureArgs.focusSelector,
|
|
17650
|
-
expectText: captureArgs.expectText,
|
|
17651
|
-
screenshotPath: captureArgs.screenshotPath,
|
|
17652
|
-
metricsPath: captureArgs.metricsPath,
|
|
17653
|
-
...metrics ?? {}
|
|
17654
|
-
}
|
|
17655
|
-
};
|
|
17872
|
+
try {
|
|
17873
|
+
const parsed = parseWorkspaceToolArguments(args2.call.arguments);
|
|
17874
|
+
const captureArgs = readVisualStateCaptureArgs(parsed);
|
|
17875
|
+
const capability = visualRendererCapability();
|
|
17876
|
+
if (!capability.available) return { content: `captureVisualState unavailable: ${capability.reason}`, metadata: { ok: false, rendererAvailable: false } };
|
|
17877
|
+
const baseUrl = await resolveVisualStateBaseUrl({ explicitBaseUrl: captureArgs.baseUrl });
|
|
17878
|
+
const pageUrl = new URL(captureArgs.path, baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`).href;
|
|
17879
|
+
const screenshotPath = resolveRequestedWorkspacePath(args2.workspaceRoot, captureArgs.screenshotPath);
|
|
17880
|
+
const metricsPath = resolveRequestedWorkspacePath(args2.workspaceRoot, captureArgs.metricsPath);
|
|
17881
|
+
const rendered = await renderVisualPage({ ...captureArgs, workspaceRoot: args2.workspaceRoot, url: pageUrl, screenshotPath, timeoutMs: args2.commandTimeoutMs });
|
|
17882
|
+
const metrics = {
|
|
17883
|
+
...captureArgs,
|
|
17884
|
+
...rendered,
|
|
17885
|
+
scrollSelector: captureArgs.scrollSelector ?? null,
|
|
17886
|
+
focusSelector: captureArgs.focusSelector ?? null,
|
|
17887
|
+
expectText: captureArgs.expectText ?? null
|
|
17888
|
+
};
|
|
17889
|
+
await mkdir2(dirname3(metricsPath), { recursive: true });
|
|
17890
|
+
await writeFile2(metricsPath, JSON.stringify(metrics, null, 2));
|
|
17891
|
+
const image = await readLocalImage(screenshotPath);
|
|
17892
|
+
return {
|
|
17893
|
+
content: [
|
|
17894
|
+
`pageUrl: ${metrics.pageUrl}`,
|
|
17895
|
+
`screenshotPath: ${captureArgs.screenshotPath}`,
|
|
17896
|
+
`metricsPath: ${captureArgs.metricsPath}`,
|
|
17897
|
+
`waitSelector: ${captureArgs.waitSelector}`,
|
|
17898
|
+
...captureArgs.focusSelector ? [`focusSelector: ${captureArgs.focusSelector}`] : [],
|
|
17899
|
+
...captureArgs.expectText ? [`expectText: ${captureArgs.expectText}`] : [],
|
|
17900
|
+
image.content.replace("[image ", "[screenshot ").replace(/(\d+) bytes/, (_, bytes) => `${Math.ceil(Number(bytes) / 1024)}KB`)
|
|
17901
|
+
].join("\n"),
|
|
17902
|
+
images: image.images,
|
|
17903
|
+
metadata: {
|
|
17904
|
+
...captureArgs,
|
|
17905
|
+
...metrics,
|
|
17906
|
+
ok: true,
|
|
17907
|
+
exitCode: 0,
|
|
17908
|
+
baseUrl,
|
|
17909
|
+
script: null,
|
|
17910
|
+
args: [],
|
|
17911
|
+
compatibilityVersion: 2,
|
|
17912
|
+
compatibilityNote: "Platform rendering replaces workspace scripts; script/args/stdoutTail no longer describe a subprocess. Existing page metrics and path fields are retained."
|
|
17913
|
+
}
|
|
17914
|
+
};
|
|
17915
|
+
} catch (error) {
|
|
17916
|
+
return { content: `captureVisualState failed: ${toErrorMessage(error)}`, metadata: { ok: false } };
|
|
17917
|
+
}
|
|
17656
17918
|
}
|
|
17657
17919
|
async function execShellTool(args2) {
|
|
17658
17920
|
return invokeCapability(
|
|
@@ -18023,6 +18285,16 @@ function createLocalWorkspaceToolExecutors(args2) {
|
|
|
18023
18285
|
...fileAccess,
|
|
18024
18286
|
...args2.resolveRipgrepBinary ? { resolveRipgrepBinary: args2.resolveRipgrepBinary } : {}
|
|
18025
18287
|
}),
|
|
18288
|
+
captureHostWindow: async (call) => {
|
|
18289
|
+
let parsed;
|
|
18290
|
+
try {
|
|
18291
|
+
parsed = JSON.parse(call.arguments || "{}");
|
|
18292
|
+
} catch {
|
|
18293
|
+
return { content: "captureHostWindow accepts an empty JSON object" };
|
|
18294
|
+
}
|
|
18295
|
+
if (!isRecord(parsed) || Object.keys(parsed).length) return { content: "captureHostWindow accepts no arguments" };
|
|
18296
|
+
return captureHostWindow();
|
|
18297
|
+
},
|
|
18026
18298
|
read_image: async (call) => {
|
|
18027
18299
|
const parsed = parseWorkspaceToolArguments(call.arguments);
|
|
18028
18300
|
if (Object.keys(parsed).some((key) => key !== "path")) throw new Error("read_image accepts exactly one path");
|
|
@@ -21893,11 +22165,11 @@ async function readNoloProcessStream(readable) {
|
|
|
21893
22165
|
async function runNoloWorkspaceCliTool(call, args2) {
|
|
21894
22166
|
const cliArgs = buildNoloWorkspaceCommandArgs(call);
|
|
21895
22167
|
const bunSpawn = globalThis.Bun?.spawn;
|
|
21896
|
-
const
|
|
22168
|
+
const spawn3 = args2.spawn ?? (typeof bunSpawn === "function" ? bunSpawn : nodeSpawnFallback);
|
|
21897
22169
|
const execPath = args2.processExecPath ?? process.execPath;
|
|
21898
22170
|
const entrypoint = args2.cliEntrypoint;
|
|
21899
22171
|
const cmd = entrypoint && entrypoint !== execPath ? [execPath, entrypoint, ...cliArgs] : [execPath, ...cliArgs];
|
|
21900
|
-
const proc =
|
|
22172
|
+
const proc = spawn3({
|
|
21901
22173
|
cmd,
|
|
21902
22174
|
stdout: "pipe",
|
|
21903
22175
|
stderr: "pipe",
|
|
@@ -22623,17 +22895,17 @@ function createDevinProvider(options) {
|
|
|
22623
22895
|
}
|
|
22624
22896
|
|
|
22625
22897
|
// packages/agent-runtime/executionLeaseValidation.ts
|
|
22626
|
-
import { isAbsolute as
|
|
22898
|
+
import { isAbsolute as isAbsolute4, relative as relative5, resolve as resolve5 } from "node:path";
|
|
22627
22899
|
function withinRoot(root, child) {
|
|
22628
|
-
if (!
|
|
22900
|
+
if (!isAbsolute4(root) || !isAbsolute4(child)) return false;
|
|
22629
22901
|
const canonicalRoot = resolve5(root);
|
|
22630
22902
|
const canonicalChild = resolve5(child);
|
|
22631
|
-
const suffix =
|
|
22632
|
-
return suffix === "" || suffix !== ".." && !suffix.startsWith("../") && !suffix.startsWith("..\\") && !
|
|
22903
|
+
const suffix = relative5(canonicalRoot, canonicalChild);
|
|
22904
|
+
return suffix === "" || suffix !== ".." && !suffix.startsWith("../") && !suffix.startsWith("..\\") && !isAbsolute4(suffix);
|
|
22633
22905
|
}
|
|
22634
22906
|
function subsetOf(allowed, actual) {
|
|
22635
22907
|
return actual.every(
|
|
22636
|
-
(root) =>
|
|
22908
|
+
(root) => isAbsolute4(root) && allowed.some((parent) => withinRoot(parent, root))
|
|
22637
22909
|
);
|
|
22638
22910
|
}
|
|
22639
22911
|
function validateConfiguredRootExecutionLease(ticket, lease) {
|