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