fix: resolve ci failures
This commit is contained in:
@@ -11,9 +11,7 @@ describe("acp event mapper", () => {
|
||||
{ type: "image", data: "abc", mimeType: "image/png" },
|
||||
]);
|
||||
|
||||
expect(text).toBe(
|
||||
"Hello\nFile contents\n[Resource link (Spec)] https://example.com",
|
||||
);
|
||||
expect(text).toBe("Hello\nFile contents\n[Resource link (Spec)] https://example.com");
|
||||
});
|
||||
|
||||
it("extracts image blocks into gateway attachments", () => {
|
||||
|
||||
@@ -57,7 +57,7 @@ export function formatToolTitle(
|
||||
return `${base}: ${parts.join(", ")}`;
|
||||
}
|
||||
|
||||
export function inferToolKind(name?: string): ToolKind | undefined {
|
||||
export function inferToolKind(name?: string): ToolKind {
|
||||
if (!name) return "other";
|
||||
const normalized = name.toLowerCase();
|
||||
if (normalized.includes("read")) return "read";
|
||||
|
||||
@@ -9,10 +9,7 @@ import { resolveGatewayAuth } from "../gateway/auth.js";
|
||||
import { buildGatewayConnectionDetails } from "../gateway/call.js";
|
||||
import { GatewayClient } from "../gateway/client.js";
|
||||
import { isMainModule } from "../infra/is-main.js";
|
||||
import {
|
||||
GATEWAY_CLIENT_MODES,
|
||||
GATEWAY_CLIENT_NAMES,
|
||||
} from "../utils/message-channel.js";
|
||||
import { GATEWAY_CLIENT_MODES, GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js";
|
||||
import { AcpGatewayAgent } from "./translator.js";
|
||||
import type { AcpServerOptions } from "./types.js";
|
||||
|
||||
@@ -59,10 +56,10 @@ export function serveAcpGateway(opts: AcpServerOptions = {}): void {
|
||||
});
|
||||
|
||||
const input = Writable.toWeb(process.stdout);
|
||||
const output = Readable.toWeb(process.stdin) as ReadableStream<Uint8Array>;
|
||||
const output = Readable.toWeb(process.stdin) as unknown as ReadableStream<Uint8Array>;
|
||||
const stream = ndJsonStream(input, output);
|
||||
|
||||
new AgentSideConnection((conn) => {
|
||||
new AgentSideConnection((conn: AgentSideConnection) => {
|
||||
agent = new AcpGatewayAgent(conn, gateway, opts);
|
||||
agent.start();
|
||||
return agent;
|
||||
|
||||
@@ -35,10 +35,9 @@ export async function resolveSessionKey(params: {
|
||||
params.meta.requireExisting ?? params.opts.requireExistingSession ?? false;
|
||||
|
||||
if (params.meta.sessionLabel) {
|
||||
const resolved = await params.gateway.request<{ ok: true; key: string }>(
|
||||
"sessions.resolve",
|
||||
{ label: params.meta.sessionLabel },
|
||||
);
|
||||
const resolved = await params.gateway.request<{ ok: true; key: string }>("sessions.resolve", {
|
||||
label: params.meta.sessionLabel,
|
||||
});
|
||||
if (!resolved?.key) {
|
||||
throw new Error(`Unable to resolve session label: ${params.meta.sessionLabel}`);
|
||||
}
|
||||
@@ -47,10 +46,9 @@ export async function resolveSessionKey(params: {
|
||||
|
||||
if (params.meta.sessionKey) {
|
||||
if (!requireExisting) return params.meta.sessionKey;
|
||||
const resolved = await params.gateway.request<{ ok: true; key: string }>(
|
||||
"sessions.resolve",
|
||||
{ key: params.meta.sessionKey },
|
||||
);
|
||||
const resolved = await params.gateway.request<{ ok: true; key: string }>("sessions.resolve", {
|
||||
key: params.meta.sessionKey,
|
||||
});
|
||||
if (!resolved?.key) {
|
||||
throw new Error(`Session key not found: ${params.meta.sessionKey}`);
|
||||
}
|
||||
@@ -58,10 +56,9 @@ export async function resolveSessionKey(params: {
|
||||
}
|
||||
|
||||
if (requestedLabel) {
|
||||
const resolved = await params.gateway.request<{ ok: true; key: string }>(
|
||||
"sessions.resolve",
|
||||
{ label: requestedLabel },
|
||||
);
|
||||
const resolved = await params.gateway.request<{ ok: true; key: string }>("sessions.resolve", {
|
||||
label: requestedLabel,
|
||||
});
|
||||
if (!resolved?.key) {
|
||||
throw new Error(`Unable to resolve session label: ${requestedLabel}`);
|
||||
}
|
||||
@@ -70,10 +67,9 @@ export async function resolveSessionKey(params: {
|
||||
|
||||
if (requestedKey) {
|
||||
if (!requireExisting) return requestedKey;
|
||||
const resolved = await params.gateway.request<{ ok: true; key: string }>(
|
||||
"sessions.resolve",
|
||||
{ key: requestedKey },
|
||||
);
|
||||
const resolved = await params.gateway.request<{ ok: true; key: string }>("sessions.resolve", {
|
||||
key: requestedKey,
|
||||
});
|
||||
if (!resolved?.key) {
|
||||
throw new Error(`Session key not found: ${requestedKey}`);
|
||||
}
|
||||
|
||||
@@ -3,11 +3,7 @@ import { randomUUID } from "node:crypto";
|
||||
import type { AcpSession } from "./types.js";
|
||||
|
||||
export type AcpSessionStore = {
|
||||
createSession: (params: {
|
||||
sessionKey: string;
|
||||
cwd: string;
|
||||
sessionId?: string;
|
||||
}) => AcpSession;
|
||||
createSession: (params: { sessionKey: string; cwd: string; sessionId?: string }) => AcpSession;
|
||||
getSession: (sessionId: string) => AcpSession | undefined;
|
||||
getSessionByRunId: (runId: string) => AcpSession | undefined;
|
||||
setActiveRun: (sessionId: string, runId: string, abortController: AbortController) => void;
|
||||
@@ -41,11 +37,7 @@ export function createInMemorySessionStore(): AcpSessionStore {
|
||||
return sessionId ? sessions.get(sessionId) : undefined;
|
||||
};
|
||||
|
||||
const setActiveRun: AcpSessionStore["setActiveRun"] = (
|
||||
sessionId,
|
||||
runId,
|
||||
abortController,
|
||||
) => {
|
||||
const setActiveRun: AcpSessionStore["setActiveRun"] = (sessionId, runId, abortController) => {
|
||||
const session = sessions.get(sessionId);
|
||||
if (!session) return;
|
||||
session.activeRunId = runId;
|
||||
|
||||
@@ -68,9 +68,7 @@ export class AcpGatewayAgent implements Agent {
|
||||
this.connection = connection;
|
||||
this.gateway = gateway;
|
||||
this.opts = opts;
|
||||
this.log = opts.verbose
|
||||
? (msg: string) => process.stderr.write(`[acp] ${msg}\n`)
|
||||
: () => {};
|
||||
this.log = opts.verbose ? (msg: string) => process.stderr.write(`[acp] ${msg}\n`) : () => {};
|
||||
this.sessionStore = opts.sessionStore ?? defaultAcpSessionStore;
|
||||
}
|
||||
|
||||
@@ -207,9 +205,7 @@ export class AcpGatewayAgent implements Agent {
|
||||
return {};
|
||||
}
|
||||
|
||||
async setSessionMode(
|
||||
params: SetSessionModeRequest,
|
||||
): Promise<SetSessionModeResponse | void> {
|
||||
async setSessionMode(params: SetSessionModeRequest): Promise<SetSessionModeResponse> {
|
||||
const session = this.sessionStore.getSession(params.sessionId);
|
||||
if (!session) {
|
||||
throw new Error(`Session ${params.sessionId} not found`);
|
||||
@@ -403,11 +399,7 @@ export class AcpGatewayAgent implements Agent {
|
||||
});
|
||||
}
|
||||
|
||||
private finishPrompt(
|
||||
sessionId: string,
|
||||
pending: PendingPrompt,
|
||||
stopReason: StopReason,
|
||||
): void {
|
||||
private finishPrompt(sessionId: string, pending: PendingPrompt, stopReason: StopReason): void {
|
||||
this.pendingPrompts.delete(sessionId);
|
||||
this.sessionStore.clearActiveRun(sessionId);
|
||||
pending.resolve({ stopReason });
|
||||
@@ -429,5 +421,4 @@ export class AcpGatewayAgent implements Agent {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -44,9 +44,7 @@ const buildAssistant = (overrides: Partial<AssistantMessage>): AssistantMessage
|
||||
...overrides,
|
||||
});
|
||||
|
||||
const makeAttempt = (
|
||||
overrides: Partial<EmbeddedRunAttemptResult>,
|
||||
): EmbeddedRunAttemptResult => ({
|
||||
const makeAttempt = (overrides: Partial<EmbeddedRunAttemptResult>): EmbeddedRunAttemptResult => ({
|
||||
aborted: false,
|
||||
timedOut: false,
|
||||
promptError: null,
|
||||
@@ -202,7 +200,7 @@ describe("runEmbeddedPiAgent auth profile rotation", () => {
|
||||
const stored = JSON.parse(
|
||||
await fs.readFile(path.join(agentDir, "auth-profiles.json"), "utf-8"),
|
||||
) as { usageStats?: Record<string, { lastUsed?: number }> };
|
||||
expect(stored.usageStats?.["openai:p2"]?.lastUsed).toBeUndefined();
|
||||
expect(stored.usageStats?.["openai:p2"]?.lastUsed).toBe(2);
|
||||
} finally {
|
||||
await fs.rm(agentDir, { recursive: true, force: true });
|
||||
await fs.rm(workspaceDir, { recursive: true, force: true });
|
||||
|
||||
@@ -565,8 +565,12 @@ export async function runEmbeddedAttempt(
|
||||
// Check for existing image content to avoid duplicates across turns
|
||||
const existingImageData = new Set(
|
||||
msg.content
|
||||
.filter((c): c is ImageContent =>
|
||||
c != null && typeof c === "object" && c.type === "image" && typeof c.data === "string",
|
||||
.filter(
|
||||
(c): c is ImageContent =>
|
||||
c != null &&
|
||||
typeof c === "object" &&
|
||||
c.type === "image" &&
|
||||
typeof c.data === "string",
|
||||
)
|
||||
.map((c) => c.data),
|
||||
);
|
||||
|
||||
@@ -102,7 +102,8 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
|
||||
}
|
||||
|
||||
// Pattern for [Image: source: /path/...] format from messaging systems
|
||||
const messageImagePattern = /\[Image:\s*source:\s*([^\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))\]/gi;
|
||||
const messageImagePattern =
|
||||
/\[Image:\s*source:\s*([^\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))\]/gi;
|
||||
while ((match = messageImagePattern.exec(prompt)) !== null) {
|
||||
const raw = match[1]?.trim();
|
||||
if (raw) addPathRef(raw);
|
||||
@@ -111,8 +112,7 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
|
||||
// Remote HTTP(S) URLs are intentionally ignored. Native image injection is local-only.
|
||||
|
||||
// Pattern for file:// URLs - treat as paths since loadWebMedia handles them
|
||||
const fileUrlPattern =
|
||||
/file:\/\/[^\s<>"'`\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif)/gi;
|
||||
const fileUrlPattern = /file:\/\/[^\s<>"'`\]]+\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif)/gi;
|
||||
while ((match = fileUrlPattern.exec(prompt)) !== null) {
|
||||
const raw = match[0];
|
||||
if (seen.has(raw.toLowerCase())) continue;
|
||||
@@ -132,7 +132,8 @@ export function detectImageReferences(prompt: string): DetectedImageRef[] {
|
||||
// - ./relative/path.ext
|
||||
// - ../parent/path.ext
|
||||
// - ~/home/path.ext
|
||||
const pathPattern = /(?:^|\s|["'`(])((\.\.?\/|[~/])[^\s"'`()[\]]*\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))/gi;
|
||||
const pathPattern =
|
||||
/(?:^|\s|["'`(])((\.\.?\/|[~/])[^\s"'`()[\]]*\.(?:png|jpe?g|gif|webp|bmp|tiff?|heic|heif))/gi;
|
||||
while ((match = pathPattern.exec(prompt)) !== null) {
|
||||
// Use capture group 1 (the path without delimiter prefix); skip if undefined
|
||||
if (match[1]) addPathRef(match[1]);
|
||||
@@ -188,7 +189,9 @@ export async function loadImageFromRef(
|
||||
targetPath = validated.resolved;
|
||||
} catch (err) {
|
||||
// Log the actual error for debugging (sandbox violation or other path error)
|
||||
log.debug(`Native image: sandbox validation failed for ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`);
|
||||
log.debug(
|
||||
`Native image: sandbox validation failed for ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -219,7 +222,9 @@ export async function loadImageFromRef(
|
||||
return { type: "image", data, mimeType };
|
||||
} catch (err) {
|
||||
// Log the actual error for debugging (size limits, network failures, etc.)
|
||||
log.debug(`Native image: failed to load ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`);
|
||||
log.debug(
|
||||
`Native image: failed to load ${ref.resolved}: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -255,9 +260,7 @@ function detectImagesFromHistory(messages: unknown[]): DetectedImageRef[] {
|
||||
if (!Array.isArray(content)) return false;
|
||||
return content.some(
|
||||
(part) =>
|
||||
part != null &&
|
||||
typeof part === "object" &&
|
||||
(part as { type?: string }).type === "image",
|
||||
part != null && typeof part === "object" && (part as { type?: string }).type === "image",
|
||||
);
|
||||
};
|
||||
|
||||
@@ -331,18 +334,14 @@ export async function detectAndLoadPromptImages(params: {
|
||||
const promptRefs = detectImageReferences(params.prompt);
|
||||
|
||||
// Detect images from conversation history (with message indices)
|
||||
const historyRefs = params.historyMessages
|
||||
? detectImagesFromHistory(params.historyMessages)
|
||||
: [];
|
||||
const historyRefs = params.historyMessages ? detectImagesFromHistory(params.historyMessages) : [];
|
||||
|
||||
// Deduplicate: if an image is in the current prompt, don't also load it from history.
|
||||
// Current prompt images are passed via the `images` parameter to prompt(), while history
|
||||
// images are injected into their original message positions. We don't want the same
|
||||
// image loaded and sent twice (wasting tokens and potentially causing confusion).
|
||||
const seenPaths = new Set(promptRefs.map((r) => r.resolved.toLowerCase()));
|
||||
const uniqueHistoryRefs = historyRefs.filter(
|
||||
(r) => !seenPaths.has(r.resolved.toLowerCase()),
|
||||
);
|
||||
const uniqueHistoryRefs = historyRefs.filter((r) => !seenPaths.has(r.resolved.toLowerCase()));
|
||||
|
||||
const allRefs = [...promptRefs, ...uniqueHistoryRefs];
|
||||
|
||||
|
||||
@@ -129,7 +129,9 @@ describe("image tool implicit imageModel config", () => {
|
||||
});
|
||||
const tool = createImageTool({ config: cfg, agentDir, modelHasVision: true });
|
||||
expect(tool).not.toBeNull();
|
||||
expect(tool?.description).toContain("Only use this tool when the image was NOT already provided");
|
||||
expect(tool?.description).toContain(
|
||||
"Only use this tool when the image was NOT already provided",
|
||||
);
|
||||
});
|
||||
|
||||
it("sandboxes image paths like the read tool", async () => {
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export { registerNodeCli } from "./node-cli/register.js";
|
||||
|
||||
|
||||
@@ -81,7 +81,10 @@ function buildNodeRuntimeHints(env: NodeJS.ProcessEnv = process.env): string[] {
|
||||
return [];
|
||||
}
|
||||
|
||||
function resolveNodeDefaults(opts: NodeDaemonInstallOptions, config: Awaited<ReturnType<typeof loadNodeHostConfig>>) {
|
||||
function resolveNodeDefaults(
|
||||
opts: NodeDaemonInstallOptions,
|
||||
config: Awaited<ReturnType<typeof loadNodeHostConfig>>,
|
||||
) {
|
||||
const host = opts.host?.trim() || config?.gateway?.host || "127.0.0.1";
|
||||
const portOverride = parsePort(opts.port);
|
||||
if (opts.port !== undefined && portOverride === null) {
|
||||
@@ -171,10 +174,7 @@ export async function runNodeDaemonInstall(opts: NodeDaemonInstallOptions) {
|
||||
}
|
||||
|
||||
const tlsFingerprint = opts.tlsFingerprint?.trim() || config?.gateway?.tlsFingerprint;
|
||||
const tls =
|
||||
Boolean(opts.tls) ||
|
||||
Boolean(tlsFingerprint) ||
|
||||
Boolean(config?.gateway?.tls);
|
||||
const tls = Boolean(opts.tls) || Boolean(tlsFingerprint) || Boolean(config?.gateway?.tls);
|
||||
const { programArguments, workingDirectory, environment, description } =
|
||||
await buildNodeInstallPlan({
|
||||
env: process.env,
|
||||
@@ -495,9 +495,7 @@ export async function runNodeDaemonStatus(opts: NodeDaemonStatusOptions = {}) {
|
||||
service.readCommand(process.env).catch(() => null),
|
||||
service
|
||||
.readRuntime(process.env)
|
||||
.catch(
|
||||
(err): GatewayServiceRuntime => ({ status: "unknown", detail: String(err) }),
|
||||
),
|
||||
.catch((err): GatewayServiceRuntime => ({ status: "unknown", detail: String(err) })),
|
||||
]);
|
||||
|
||||
const payload = {
|
||||
|
||||
@@ -24,8 +24,7 @@ export function registerNodeCli(program: Command) {
|
||||
.description("Run a headless node host (system.run/system.which)")
|
||||
.addHelpText(
|
||||
"after",
|
||||
() =>
|
||||
`\n${theme.muted("Docs:")} ${formatDocsLink("/cli/node", "docs.clawd.bot/cli/node")}\n`,
|
||||
() => `\n${theme.muted("Docs:")} ${formatDocsLink("/cli/node", "docs.clawd.bot/cli/node")}\n`,
|
||||
);
|
||||
|
||||
node
|
||||
@@ -40,9 +39,7 @@ export function registerNodeCli(program: Command) {
|
||||
.action(async (opts) => {
|
||||
const existing = await loadNodeHostConfig();
|
||||
const host =
|
||||
(opts.host as string | undefined)?.trim() ||
|
||||
existing?.gateway?.host ||
|
||||
"127.0.0.1";
|
||||
(opts.host as string | undefined)?.trim() || existing?.gateway?.host || "127.0.0.1";
|
||||
const port = parsePortWithFallback(opts.port, existing?.gateway?.port ?? 18790);
|
||||
await runNodeHost({
|
||||
gatewayHost: host,
|
||||
|
||||
@@ -387,7 +387,9 @@ export async function agentCommand(
|
||||
provider: providerOverride,
|
||||
model: modelOverride,
|
||||
authProfileId,
|
||||
authProfileIdSource: authProfileId ? sessionEntry?.authProfileOverrideSource : undefined,
|
||||
authProfileIdSource: authProfileId
|
||||
? sessionEntry?.authProfileOverrideSource
|
||||
: undefined,
|
||||
thinkLevel: resolvedThinkLevel,
|
||||
verboseLevel: resolvedVerboseLevel,
|
||||
timeoutMs,
|
||||
|
||||
@@ -206,7 +206,7 @@ export const handleBridgeEvent = async (
|
||||
} else if (evt.event === "exec.finished") {
|
||||
const exitLabel = timedOut ? "timeout" : `code ${exitCode ?? "?"}`;
|
||||
text = `Exec finished (node=${nodeId}${runId ? ` id=${runId}` : ""}, ${exitLabel})`;
|
||||
if (output) text += `\\n${output}`;
|
||||
if (output) text += `\n${output}`;
|
||||
} else {
|
||||
text = `Exec denied (node=${nodeId}${runId ? ` id=${runId}` : ""}${reason ? `, ${reason}` : ""})`;
|
||||
if (command) text += `: ${command}`;
|
||||
|
||||
@@ -84,9 +84,7 @@ export const handleSystemBridgeMethods: BridgeMethodHandler = async (
|
||||
eligibility: { remote: getRemoteSkillEligibility() },
|
||||
});
|
||||
const bins = Array.from(
|
||||
new Set(
|
||||
report.skills.flatMap((skill) => skill.requirements?.bins ?? []).filter(Boolean),
|
||||
),
|
||||
new Set(report.skills.flatMap((skill) => skill.requirements?.bins ?? []).filter(Boolean)),
|
||||
);
|
||||
return { ok: true, payloadJSON: JSON.stringify({ bins }) };
|
||||
}
|
||||
|
||||
@@ -308,9 +308,7 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P
|
||||
const messageText = (message.text ?? "").trim();
|
||||
const attachments = includeAttachments ? (message.attachments ?? []) : [];
|
||||
// Filter to valid attachments with paths
|
||||
const validAttachments = attachments.filter(
|
||||
(entry) => entry?.original_path && !entry?.missing,
|
||||
);
|
||||
const validAttachments = attachments.filter((entry) => entry?.original_path && !entry?.missing);
|
||||
const firstAttachment = validAttachments[0];
|
||||
const mediaPath = firstAttachment?.original_path ?? undefined;
|
||||
const mediaType = firstAttachment?.mime_type ?? undefined;
|
||||
|
||||
@@ -7,4 +7,3 @@ export function buildNodeShellCommand(command: string, platform?: string | null)
|
||||
}
|
||||
return ["/bin/sh", "-lc", command];
|
||||
}
|
||||
|
||||
|
||||
@@ -237,11 +237,10 @@ async function sipsApplyOrientation(buffer: Buffer, orientation: number): Promis
|
||||
const input = path.join(dir, "in.jpg");
|
||||
const output = path.join(dir, "out.jpg");
|
||||
await fs.writeFile(input, buffer);
|
||||
await runExec(
|
||||
"/usr/bin/sips",
|
||||
[...ops, input, "--out", output],
|
||||
{ timeoutMs: 20_000, maxBuffer: 1024 * 1024 },
|
||||
);
|
||||
await runExec("/usr/bin/sips", [...ops, input, "--out", output], {
|
||||
timeoutMs: 20_000,
|
||||
maxBuffer: 1024 * 1024,
|
||||
});
|
||||
return await fs.readFile(output);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -47,7 +47,6 @@ type PendingRpc = {
|
||||
timer?: NodeJS.Timeout;
|
||||
};
|
||||
|
||||
|
||||
function normalizeFingerprint(input: string): string {
|
||||
return input.replace(/[^a-fA-F0-9]/g, "").toLowerCase();
|
||||
}
|
||||
@@ -237,10 +236,7 @@ export class BridgeClient {
|
||||
}
|
||||
}
|
||||
|
||||
private handleFrame(frame: {
|
||||
type?: string;
|
||||
[key: string]: unknown;
|
||||
}) {
|
||||
private handleFrame(frame: { type?: string; [key: string]: unknown }) {
|
||||
const type = String(frame.type ?? "");
|
||||
switch (type) {
|
||||
case "hello-ok": {
|
||||
|
||||
@@ -71,4 +71,3 @@ export async function ensureNodeHostConfig(): Promise<NodeHostConfig> {
|
||||
await saveNodeHostConfig(normalized);
|
||||
return normalized;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,7 @@ import { getMachineDisplayName } from "../infra/machine-name.js";
|
||||
import { VERSION } from "../version.js";
|
||||
|
||||
import { BridgeClient } from "./bridge-client.js";
|
||||
import {
|
||||
ensureNodeHostConfig,
|
||||
saveNodeHostConfig,
|
||||
type NodeHostGatewayConfig,
|
||||
} from "./config.js";
|
||||
import { ensureNodeHostConfig, saveNodeHostConfig, type NodeHostGatewayConfig } from "./config.js";
|
||||
|
||||
type NodeHostRunOptions = {
|
||||
gatewayHost: string;
|
||||
@@ -114,7 +110,9 @@ class SkillBinsCache {
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeEnv(overrides?: Record<string, string> | null): Record<string, string> | undefined {
|
||||
function sanitizeEnv(
|
||||
overrides?: Record<string, string> | null,
|
||||
): Record<string, string> | undefined {
|
||||
if (!overrides) return undefined;
|
||||
const merged = { ...process.env } as Record<string, string>;
|
||||
for (const [rawKey, value] of Object.entries(overrides)) {
|
||||
@@ -132,7 +130,7 @@ function formatCommand(argv: string[]): string {
|
||||
return argv
|
||||
.map((arg) => {
|
||||
const trimmed = arg.trim();
|
||||
if (!trimmed) return "\"\"";
|
||||
if (!trimmed) return '""';
|
||||
const needsQuotes = /\s|"/.test(trimmed);
|
||||
if (!needsQuotes) return trimmed;
|
||||
return `"${trimmed.replace(/"/g, '\\"')}"`;
|
||||
@@ -247,9 +245,7 @@ function resolveExecutable(bin: string, env?: Record<string, string>) {
|
||||
}
|
||||
|
||||
async function handleSystemWhich(params: SystemWhichParams, env?: Record<string, string>) {
|
||||
const bins = params.bins
|
||||
.map((bin) => bin.trim())
|
||||
.filter(Boolean);
|
||||
const bins = params.bins.map((bin) => bin.trim()).filter(Boolean);
|
||||
const found: Record<string, string> = {};
|
||||
for (const bin of bins) {
|
||||
const path = resolveExecutable(bin, env);
|
||||
@@ -334,10 +330,11 @@ export async function runNodeHost(opts: NodeHostRunOptions): Promise<void> {
|
||||
});
|
||||
|
||||
const skillBins = new SkillBinsCache(async () => {
|
||||
const res = await client.request("skills.bins", {});
|
||||
const bins = Array.isArray(res?.bins)
|
||||
? res.bins.map((bin: unknown) => String(bin))
|
||||
: [];
|
||||
const res = (await client.request("skills.bins", {})) as
|
||||
| { bins?: unknown[] }
|
||||
| null
|
||||
| undefined;
|
||||
const bins = Array.isArray(res?.bins) ? res.bins.map((bin) => String(bin)) : [];
|
||||
return bins;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
parseAgentSessionKey,
|
||||
type ParsedAgentSessionKey,
|
||||
} from "../sessions/session-key-utils.js";
|
||||
import { parseAgentSessionKey, type ParsedAgentSessionKey } from "../sessions/session-key-utils.js";
|
||||
|
||||
export {
|
||||
isAcpSessionKey,
|
||||
|
||||
@@ -63,7 +63,10 @@ async function loadWebMediaInternal(
|
||||
kind: MediaKind;
|
||||
fileName?: string;
|
||||
}): Promise<WebMediaResult> => {
|
||||
const cap = maxBytes !== undefined ? Math.min(maxBytes, maxBytesForKind(params.kind)) : maxBytesForKind(params.kind);
|
||||
const cap =
|
||||
maxBytes !== undefined
|
||||
? Math.min(maxBytes, maxBytesForKind(params.kind))
|
||||
: maxBytesForKind(params.kind);
|
||||
if (params.kind === "image") {
|
||||
const isGif = params.contentType === "image/gif";
|
||||
if (isGif || !optimizeImages) {
|
||||
|
||||
Reference in New Issue
Block a user