Dev #11
@@ -144,6 +144,8 @@ export async function prepareCommandManagedRuntime(input: {
|
|||||||
preserveAbsentOnRestore?: string[];
|
preserveAbsentOnRestore?: string[];
|
||||||
assets?: CommandManagedRuntimeAsset[];
|
assets?: CommandManagedRuntimeAsset[];
|
||||||
installCommand?: string | null;
|
installCommand?: string | null;
|
||||||
|
/** When provided alongside `installCommand`, skip the install if `command -v <detectCommand>` succeeds. */
|
||||||
|
detectCommand?: string | null;
|
||||||
}): Promise<PreparedSandboxManagedRuntime> {
|
}): Promise<PreparedSandboxManagedRuntime> {
|
||||||
const timeoutMs = input.spec.timeoutMs && input.spec.timeoutMs > 0 ? input.spec.timeoutMs : 300_000;
|
const timeoutMs = input.spec.timeoutMs && input.spec.timeoutMs > 0 ? input.spec.timeoutMs : 300_000;
|
||||||
const workspaceRemoteDir = input.workspaceRemoteDir ?? input.spec.remoteCwd;
|
const workspaceRemoteDir = input.workspaceRemoteDir ?? input.spec.remoteCwd;
|
||||||
@@ -164,13 +166,52 @@ export async function prepareCommandManagedRuntime(input: {
|
|||||||
const shellCommand = preferredShellForSandbox(input.spec.shellCommand);
|
const shellCommand = preferredShellForSandbox(input.spec.shellCommand);
|
||||||
|
|
||||||
if (input.installCommand?.trim()) {
|
if (input.installCommand?.trim()) {
|
||||||
|
const installCommand = input.installCommand.trim();
|
||||||
|
const detectCommand = input.detectCommand?.trim();
|
||||||
|
// Skip the install when the binary is already on PATH. Without this
|
||||||
|
// probe the install runs unconditionally on every execute() call (and
|
||||||
|
// also runs a second time after `ensureAdapterExecutionTargetCommandResolvable`
|
||||||
|
// has already installed it during the resolvability gate).
|
||||||
|
if (detectCommand) {
|
||||||
|
const probe = await input.runner.execute({
|
||||||
|
command: shellCommand,
|
||||||
|
args: ["-lc", `command -v ${shellQuote(detectCommand)} >/dev/null 2>&1`],
|
||||||
|
cwd: workspaceRemoteDir,
|
||||||
|
timeoutMs,
|
||||||
|
});
|
||||||
|
if (!probe.timedOut && (probe.exitCode ?? 1) === 0) {
|
||||||
|
return await prepareSandboxManagedRuntime({
|
||||||
|
spec: runtimeSpec,
|
||||||
|
client,
|
||||||
|
adapterKey: input.adapterKey,
|
||||||
|
workspaceLocalDir: input.workspaceLocalDir,
|
||||||
|
workspaceRemoteDir,
|
||||||
|
workspaceExclude: mergeRuntimeExcludes(input.workspaceExclude),
|
||||||
|
preserveAbsentOnRestore: input.preserveAbsentOnRestore,
|
||||||
|
assets: input.assets,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
const result = await input.runner.execute({
|
const result = await input.runner.execute({
|
||||||
command: shellCommand,
|
command: shellCommand,
|
||||||
args: ["-lc", input.installCommand.trim()],
|
args: ["-lc", installCommand],
|
||||||
cwd: workspaceRemoteDir,
|
cwd: workspaceRemoteDir,
|
||||||
timeoutMs,
|
timeoutMs,
|
||||||
});
|
});
|
||||||
requireSuccessfulResult(result, input.installCommand.trim());
|
// A failed install is not always fatal: the CLI may already be on PATH
|
||||||
|
// from a previous lease, the template image, or another path entry. Log
|
||||||
|
// and continue rather than aborting the agent run; downstream code that
|
||||||
|
// exec's the CLI will surface a clear "command not found" if it is in
|
||||||
|
// fact missing. The test path's `maybeRunSandboxInstallCommand` already
|
||||||
|
// honors this contract — keep them consistent.
|
||||||
|
if (result.timedOut || (result.exitCode ?? 0) !== 0) {
|
||||||
|
const tail = (text: string) =>
|
||||||
|
text.split(/\r?\n/).filter((line) => line.trim().length > 0).slice(-3).join(" | ").slice(0, 480);
|
||||||
|
const reason = result.timedOut ? "timed out" : `exited ${result.exitCode ?? "?"}`;
|
||||||
|
console.warn(
|
||||||
|
`[paperclip] managed-runtime install command ${reason}: ${installCommand} :: ${tail(result.stderr || result.stdout)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return await prepareSandboxManagedRuntime({
|
return await prepareSandboxManagedRuntime({
|
||||||
|
|||||||
@@ -230,9 +230,10 @@ export async function ensureAdapterExecutionTargetCommandResolvable(
|
|||||||
target: AdapterExecutionTarget | null | undefined,
|
target: AdapterExecutionTarget | null | undefined,
|
||||||
cwd: string,
|
cwd: string,
|
||||||
env: NodeJS.ProcessEnv,
|
env: NodeJS.ProcessEnv,
|
||||||
|
options: { installCommand?: string | null } = {},
|
||||||
) {
|
) {
|
||||||
if (target?.kind === "remote" && target.transport === "sandbox") {
|
if (target?.kind === "remote" && target.transport === "sandbox") {
|
||||||
await ensureSandboxCommandResolvable(command, target);
|
await ensureSandboxCommandResolvable(command, target, options.installCommand?.trim() || null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await ensureCommandResolvable(command, cwd, env, {
|
await ensureCommandResolvable(command, cwd, env, {
|
||||||
@@ -240,17 +241,10 @@ export async function ensureAdapterExecutionTargetCommandResolvable(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function ensureSandboxCommandResolvable(
|
async function probeSandboxCommandResolvable(
|
||||||
command: string,
|
command: string,
|
||||||
target: AdapterSandboxExecutionTarget,
|
target: AdapterSandboxExecutionTarget,
|
||||||
): Promise<void> {
|
): Promise<{ resolved: boolean; timedOut: boolean; stderr: string }> {
|
||||||
// Probe whether the binary is resolvable inside the sandbox. We previously
|
|
||||||
// short-circuited this for sandbox targets, which let the caller report a
|
|
||||||
// success message even when the CLI was missing from the image. Now we run
|
|
||||||
// a real `command -v` through the same runner the hello probe will use, so
|
|
||||||
// the first step honestly reflects whether the binary is on PATH. The
|
|
||||||
// sandbox provider is responsible for sourcing login profiles (e2b mirrors
|
|
||||||
// SSH's buildSshSpawnTarget) so this and the hello probe agree on PATH.
|
|
||||||
const runner = requireSandboxRunner(target);
|
const runner = requireSandboxRunner(target);
|
||||||
const probeScript = `command -v ${shellQuote(command)}`;
|
const probeScript = `command -v ${shellQuote(command)}`;
|
||||||
const result = await runner.execute({
|
const result = await runner.execute({
|
||||||
@@ -259,14 +253,67 @@ async function ensureSandboxCommandResolvable(
|
|||||||
cwd: target.remoteCwd,
|
cwd: target.remoteCwd,
|
||||||
timeoutMs: target.timeoutMs ?? 15_000,
|
timeoutMs: target.timeoutMs ?? 15_000,
|
||||||
});
|
});
|
||||||
if (result.timedOut) {
|
return {
|
||||||
|
resolved: !result.timedOut && (result.exitCode ?? 1) === 0,
|
||||||
|
timedOut: result.timedOut,
|
||||||
|
stderr: result.stderr.trim(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async function ensureSandboxCommandResolvable(
|
||||||
|
command: string,
|
||||||
|
target: AdapterSandboxExecutionTarget,
|
||||||
|
installCommand: string | null,
|
||||||
|
): Promise<void> {
|
||||||
|
// Probe whether the binary is resolvable inside the sandbox. We previously
|
||||||
|
// short-circuited this for sandbox targets, which let the caller report a
|
||||||
|
// success message even when the CLI was missing from the image. Now we run
|
||||||
|
// a real `command -v` through the same runner the hello probe will use, so
|
||||||
|
// the first step honestly reflects whether the binary is on PATH. The
|
||||||
|
// sandbox provider is responsible for sourcing login profiles (e2b mirrors
|
||||||
|
// SSH's buildSshSpawnTarget) so this and the hello probe agree on PATH.
|
||||||
|
let probe = await probeSandboxCommandResolvable(command, target);
|
||||||
|
if (probe.resolved) return;
|
||||||
|
if (probe.timedOut) {
|
||||||
throw new Error(`Timed out checking command "${command}" on sandbox target.`);
|
throw new Error(`Timed out checking command "${command}" on sandbox target.`);
|
||||||
}
|
}
|
||||||
if ((result.exitCode ?? 1) === 0) return;
|
|
||||||
const stderr = result.stderr.trim();
|
// If the caller supplied an install command, attempt the install once via
|
||||||
const detail = stderr.length > 0 ? ` (${stderr})` : "";
|
// the sandbox runner (which the sandbox provider wraps in a login shell)
|
||||||
|
// and re-probe before reporting failure. This lets fresh sandbox leases
|
||||||
|
// bring up the CLI before the resolvability gate, mirroring the test path.
|
||||||
|
let installFailureDetail: string | null = null;
|
||||||
|
if (installCommand) {
|
||||||
|
const runner = requireSandboxRunner(target);
|
||||||
|
try {
|
||||||
|
const installResult = await runner.execute({
|
||||||
|
command: "sh",
|
||||||
|
args: ["-lc", installCommand],
|
||||||
|
cwd: target.remoteCwd,
|
||||||
|
timeoutMs: target.timeoutMs ?? 300_000,
|
||||||
|
});
|
||||||
|
if (installResult.timedOut) {
|
||||||
|
installFailureDetail = `install command timed out: ${installCommand}`;
|
||||||
|
} else if ((installResult.exitCode ?? 0) !== 0) {
|
||||||
|
const tail = (text: string) =>
|
||||||
|
text.split(/\r?\n/).filter((line) => line.trim().length > 0).slice(-2).join(" | ").slice(0, 240);
|
||||||
|
const reason = tail(installResult.stderr || installResult.stdout) || `exit ${installResult.exitCode ?? "?"}`;
|
||||||
|
installFailureDetail = `install command exited ${installResult.exitCode ?? "?"}: ${reason}`;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
installFailureDetail = `install command threw: ${err instanceof Error ? err.message : String(err)}`;
|
||||||
|
}
|
||||||
|
probe = await probeSandboxCommandResolvable(command, target);
|
||||||
|
if (probe.resolved) return;
|
||||||
|
if (probe.timedOut) {
|
||||||
|
throw new Error(`Timed out checking command "${command}" on sandbox target.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const probeStderr = probe.stderr.length > 0 ? ` probe stderr: ${probe.stderr}` : "";
|
||||||
|
const installDetail = installFailureDetail ? `; ${installFailureDetail}` : "";
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Command "${command}" is not installed or not on PATH in the sandbox environment${detail}.`,
|
`Command "${command}" is not installed or not on PATH in the sandbox environment${installDetail}.${probeStderr}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,6 +456,111 @@ export async function runAdapterExecutionTargetShellCommand(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AdapterSandboxInstallCommandCheck {
|
||||||
|
code: string;
|
||||||
|
level: "info" | "warn" | "error";
|
||||||
|
message: string;
|
||||||
|
detail?: string;
|
||||||
|
hint?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Best-effort run of an adapter-supplied install command on a sandbox target
|
||||||
|
// before the resolvability + hello probe. Returns null for non-sandbox
|
||||||
|
// targets so callers can no-op. Returns a structured check otherwise — never
|
||||||
|
// throws — so the rest of the test still runs and reports the post-install
|
||||||
|
// state honestly. Caller pushes the check into its result array; the test
|
||||||
|
// report shows whether install was attempted and what came back.
|
||||||
|
export async function maybeRunSandboxInstallCommand(input: {
|
||||||
|
runId: string;
|
||||||
|
target: AdapterExecutionTarget | null | undefined;
|
||||||
|
adapterKey: string;
|
||||||
|
installCommand: string;
|
||||||
|
/** When provided, skip the install if `command -v <detectCommand>` succeeds. */
|
||||||
|
detectCommand?: string | null;
|
||||||
|
env?: Record<string, string>;
|
||||||
|
timeoutSec?: number;
|
||||||
|
}): Promise<AdapterSandboxInstallCommandCheck | null> {
|
||||||
|
const { target, adapterKey, installCommand } = input;
|
||||||
|
if (!target || target.kind !== "remote" || target.transport !== "sandbox") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const trimmed = installCommand.trim();
|
||||||
|
if (trimmed.length === 0) return null;
|
||||||
|
|
||||||
|
const code = `${adapterKey}_install_command_run`;
|
||||||
|
|
||||||
|
// Skip install when the binary is already on PATH. Avoids running
|
||||||
|
// network-dependent installers (e.g. `curl ... | bash`) on every test
|
||||||
|
// probe when the CLI is preinstalled on the lease/template.
|
||||||
|
const detectCommand = input.detectCommand?.trim();
|
||||||
|
if (detectCommand) {
|
||||||
|
try {
|
||||||
|
const probe = await runAdapterExecutionTargetShellCommand(
|
||||||
|
input.runId,
|
||||||
|
target,
|
||||||
|
`command -v ${shellQuote(detectCommand)} >/dev/null 2>&1`,
|
||||||
|
{
|
||||||
|
cwd: target.remoteCwd,
|
||||||
|
env: input.env ?? {},
|
||||||
|
timeoutSec: 30,
|
||||||
|
graceSec: 5,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!probe.timedOut && probe.exitCode === 0) {
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
level: "info",
|
||||||
|
message: `${detectCommand} already on PATH; skipped install.`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Fall through to actually running the install — failure to probe
|
||||||
|
// is not a reason to skip the install gate.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = await runAdapterExecutionTargetShellCommand(input.runId, target, trimmed, {
|
||||||
|
cwd: target.remoteCwd,
|
||||||
|
env: input.env ?? {},
|
||||||
|
timeoutSec: input.timeoutSec ?? 240,
|
||||||
|
graceSec: 10,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
level: "warn",
|
||||||
|
message: "Install command threw before completion.",
|
||||||
|
detail: err instanceof Error ? err.message : String(err),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const tail = (text: string) =>
|
||||||
|
text.split(/\r?\n/).filter((line) => line.trim().length > 0).slice(-3).join(" | ").slice(0, 480);
|
||||||
|
if (result.timedOut) {
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
level: "warn",
|
||||||
|
message: `Install command timed out: ${trimmed}`,
|
||||||
|
detail: tail(result.stderr || result.stdout),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if ((result.exitCode ?? 1) === 0) {
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
level: "info",
|
||||||
|
message: `Install command ran: ${trimmed}`,
|
||||||
|
...(tail(result.stdout) ? { detail: tail(result.stdout) } : {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
level: "warn",
|
||||||
|
message: `Install command exited ${result.exitCode}: ${trimmed}`,
|
||||||
|
detail: tail(result.stderr || result.stdout),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export async function readAdapterExecutionTargetHomeDir(
|
export async function readAdapterExecutionTargetHomeDir(
|
||||||
runId: string,
|
runId: string,
|
||||||
target: AdapterExecutionTarget | null | undefined,
|
target: AdapterExecutionTarget | null | undefined,
|
||||||
@@ -470,12 +622,43 @@ export async function ensureAdapterExecutionTargetRuntimeCommandInstalled(input:
|
|||||||
onLog: input.onLog,
|
onLog: input.onLog,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// A failed or timed-out install is not necessarily fatal: the CLI may already
|
||||||
|
// be on PATH from a previous lease's install, the template image, or another
|
||||||
|
// path entry. Re-run the detect probe (when one is configured) so a transient
|
||||||
|
// install failure does not abort the agent run when the binary is reachable.
|
||||||
|
const installFailed = result.timedOut || (result.exitCode ?? 0) !== 0;
|
||||||
|
if (!installFailed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (detectCommand) {
|
||||||
|
const recheck = await runAdapterExecutionTargetShellCommand(
|
||||||
|
input.runId,
|
||||||
|
input.target,
|
||||||
|
`command -v ${shellQuote(detectCommand)} >/dev/null 2>&1`,
|
||||||
|
{
|
||||||
|
cwd: input.cwd,
|
||||||
|
env: input.env,
|
||||||
|
timeoutSec: input.timeoutSec,
|
||||||
|
graceSec: input.graceSec,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
if (!recheck.timedOut && recheck.exitCode === 0) {
|
||||||
|
if (input.onLog) {
|
||||||
|
const reason = result.timedOut ? "timed out" : `exited ${result.exitCode ?? "?"}`;
|
||||||
|
await input.onLog(
|
||||||
|
"stderr",
|
||||||
|
`[paperclip] Install command ${reason} (${installCommand}) but ${detectCommand} is on PATH; continuing.\n`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (result.timedOut) {
|
if (result.timedOut) {
|
||||||
throw new Error(`Timed out while installing the adapter runtime command via: ${installCommand}`);
|
throw new Error(`Timed out while installing the adapter runtime command via: ${installCommand}`);
|
||||||
}
|
}
|
||||||
if ((result.exitCode ?? 0) !== 0) {
|
throw new Error(`Failed to install the adapter runtime command via: ${installCommand}`);
|
||||||
throw new Error(`Failed to install the adapter runtime command via: ${installCommand}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function ensureAdapterExecutionTargetFile(
|
export async function ensureAdapterExecutionTargetFile(
|
||||||
@@ -666,6 +849,8 @@ export async function prepareAdapterExecutionTargetRuntime(input: {
|
|||||||
preserveAbsentOnRestore?: string[];
|
preserveAbsentOnRestore?: string[];
|
||||||
assets?: AdapterManagedRuntimeAsset[];
|
assets?: AdapterManagedRuntimeAsset[];
|
||||||
installCommand?: string | null;
|
installCommand?: string | null;
|
||||||
|
/** When provided alongside `installCommand`, skip the install if the binary is already on PATH. */
|
||||||
|
detectCommand?: string | null;
|
||||||
}): Promise<PreparedAdapterExecutionTargetRuntime> {
|
}): Promise<PreparedAdapterExecutionTargetRuntime> {
|
||||||
const target = input.target ?? { kind: "local" as const };
|
const target = input.target ?? { kind: "local" as const };
|
||||||
if (target.kind === "local") {
|
if (target.kind === "local") {
|
||||||
@@ -707,6 +892,7 @@ export async function prepareAdapterExecutionTargetRuntime(input: {
|
|||||||
preserveAbsentOnRestore: input.preserveAbsentOnRestore,
|
preserveAbsentOnRestore: input.preserveAbsentOnRestore,
|
||||||
assets: input.assets,
|
assets: input.assets,
|
||||||
installCommand: input.installCommand,
|
installCommand: input.installCommand,
|
||||||
|
detectCommand: input.detectCommand,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
target,
|
target,
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import type { AdapterModelProfileDefinition } from "@paperclipai/adapter-utils";
|
|||||||
export const type = "claude_local";
|
export const type = "claude_local";
|
||||||
export const label = "Claude Code (local)";
|
export const label = "Claude Code (local)";
|
||||||
|
|
||||||
|
export const SANDBOX_INSTALL_COMMAND = "npm install -g @anthropic-ai/claude-code";
|
||||||
|
|
||||||
export const models = [
|
export const models = [
|
||||||
{ id: "claude-opus-4-7", label: "Claude Opus 4.7" },
|
{ id: "claude-opus-4-7", label: "Claude Opus 4.7" },
|
||||||
{ id: "claude-opus-4-6", label: "Claude Opus 4.6" },
|
{ id: "claude-opus-4-6", label: "Claude Opus 4.6" },
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import { prepareClaudeConfigSeed } from "./claude-config.js";
|
|||||||
import { resolveClaudeDesiredSkillNames } from "./skills.js";
|
import { resolveClaudeDesiredSkillNames } from "./skills.js";
|
||||||
import { isBedrockModelId } from "./models.js";
|
import { isBedrockModelId } from "./models.js";
|
||||||
import { prepareClaudePromptBundle } from "./prompt-cache.js";
|
import { prepareClaudePromptBundle } from "./prompt-cache.js";
|
||||||
|
import { SANDBOX_INSTALL_COMMAND } from "../index.js";
|
||||||
|
|
||||||
const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
@@ -261,7 +262,7 @@ async function buildClaudeRuntimeConfig(input: ClaudeExecutionInput): Promise<Cl
|
|||||||
graceSec,
|
graceSec,
|
||||||
onLog,
|
onLog,
|
||||||
});
|
});
|
||||||
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv);
|
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv, { installCommand: SANDBOX_INSTALL_COMMAND });
|
||||||
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
|
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
|
||||||
const loggedEnv = buildInvocationEnvForLogs(env, {
|
const loggedEnv = buildInvocationEnvForLogs(env, {
|
||||||
runtimeEnv,
|
runtimeEnv,
|
||||||
@@ -430,6 +431,8 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||||||
target: executionTarget,
|
target: executionTarget,
|
||||||
adapterKey: "claude",
|
adapterKey: "claude",
|
||||||
workspaceLocalDir: cwd,
|
workspaceLocalDir: cwd,
|
||||||
|
installCommand: SANDBOX_INSTALL_COMMAND,
|
||||||
|
detectCommand: command,
|
||||||
assets: [
|
assets: [
|
||||||
{
|
{
|
||||||
key: "skills",
|
key: "skills",
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
ensureAdapterExecutionTargetCommandResolvable,
|
ensureAdapterExecutionTargetCommandResolvable,
|
||||||
ensureAdapterExecutionTargetDirectory,
|
ensureAdapterExecutionTargetDirectory,
|
||||||
|
maybeRunSandboxInstallCommand,
|
||||||
runAdapterExecutionTargetProcess,
|
runAdapterExecutionTargetProcess,
|
||||||
describeAdapterExecutionTarget,
|
describeAdapterExecutionTarget,
|
||||||
resolveAdapterExecutionTargetCwd,
|
resolveAdapterExecutionTargetCwd,
|
||||||
@@ -21,6 +22,7 @@ import {
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { detectClaudeLoginRequired, parseClaudeStreamJson } from "./parse.js";
|
import { detectClaudeLoginRequired, parseClaudeStreamJson } from "./parse.js";
|
||||||
import { isBedrockModelId } from "./models.js";
|
import { isBedrockModelId } from "./models.js";
|
||||||
|
import { SANDBOX_INSTALL_COMMAND } from "../index.js";
|
||||||
|
|
||||||
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
|
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
|
||||||
if (checks.some((check) => check.level === "error")) return "fail";
|
if (checks.some((check) => check.level === "error")) return "fail";
|
||||||
@@ -102,6 +104,15 @@ export async function testEnvironment(
|
|||||||
if (typeof value === "string") env[key] = value;
|
if (typeof value === "string") env[key] = value;
|
||||||
}
|
}
|
||||||
const runtimeEnv = ensurePathInEnv({ ...process.env, ...env });
|
const runtimeEnv = ensurePathInEnv({ ...process.env, ...env });
|
||||||
|
const installCheck = await maybeRunSandboxInstallCommand({
|
||||||
|
runId,
|
||||||
|
target,
|
||||||
|
adapterKey: "claude",
|
||||||
|
installCommand: SANDBOX_INSTALL_COMMAND,
|
||||||
|
detectCommand: command,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
if (installCheck) checks.push(installCheck);
|
||||||
try {
|
try {
|
||||||
await ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, runtimeEnv);
|
await ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, runtimeEnv);
|
||||||
checks.push({
|
checks.push({
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import type { AdapterModelProfileDefinition } from "@paperclipai/adapter-utils";
|
|||||||
export const type = "codex_local";
|
export const type = "codex_local";
|
||||||
export const label = "Codex (local)";
|
export const label = "Codex (local)";
|
||||||
|
|
||||||
|
export const SANDBOX_INSTALL_COMMAND = "npm install -g @openai/codex";
|
||||||
|
|
||||||
export const DEFAULT_CODEX_LOCAL_MODEL = "gpt-5.3-codex";
|
export const DEFAULT_CODEX_LOCAL_MODEL = "gpt-5.3-codex";
|
||||||
export const DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX = true;
|
export const DEFAULT_CODEX_LOCAL_BYPASS_APPROVALS_AND_SANDBOX = true;
|
||||||
export const CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS = ["gpt-5.4"] as const;
|
export const CODEX_LOCAL_FAST_MODE_SUPPORTED_MODELS = ["gpt-5.4"] as const;
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import {
|
|||||||
import { pathExists, prepareManagedCodexHome, resolveManagedCodexHomeDir, resolveSharedCodexHomeDir } from "./codex-home.js";
|
import { pathExists, prepareManagedCodexHome, resolveManagedCodexHomeDir, resolveSharedCodexHomeDir } from "./codex-home.js";
|
||||||
import { resolveCodexDesiredSkillNames } from "./skills.js";
|
import { resolveCodexDesiredSkillNames } from "./skills.js";
|
||||||
import { buildCodexExecArgs } from "./codex-args.js";
|
import { buildCodexExecArgs } from "./codex-args.js";
|
||||||
|
import { SANDBOX_INSTALL_COMMAND } from "../index.js";
|
||||||
|
|
||||||
const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
const CODEX_ROLLOUT_NOISE_RE =
|
const CODEX_ROLLOUT_NOISE_RE =
|
||||||
@@ -374,6 +375,8 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||||||
target: executionTarget,
|
target: executionTarget,
|
||||||
adapterKey: "codex",
|
adapterKey: "codex",
|
||||||
workspaceLocalDir: cwd,
|
workspaceLocalDir: cwd,
|
||||||
|
installCommand: SANDBOX_INSTALL_COMMAND,
|
||||||
|
detectCommand: command,
|
||||||
assets: [
|
assets: [
|
||||||
{
|
{
|
||||||
key: "home",
|
key: "home",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
ensureAdapterExecutionTargetCommandResolvable,
|
ensureAdapterExecutionTargetCommandResolvable,
|
||||||
ensureAdapterExecutionTargetDirectory,
|
ensureAdapterExecutionTargetDirectory,
|
||||||
|
maybeRunSandboxInstallCommand,
|
||||||
runAdapterExecutionTargetProcess,
|
runAdapterExecutionTargetProcess,
|
||||||
describeAdapterExecutionTarget,
|
describeAdapterExecutionTarget,
|
||||||
resolveAdapterExecutionTargetCwd,
|
resolveAdapterExecutionTargetCwd,
|
||||||
@@ -18,6 +19,7 @@ import {
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import { parseCodexJsonl } from "./parse.js";
|
import { parseCodexJsonl } from "./parse.js";
|
||||||
|
import { SANDBOX_INSTALL_COMMAND } from "../index.js";
|
||||||
import { codexHomeDir, readCodexAuthInfo } from "./quota.js";
|
import { codexHomeDir, readCodexAuthInfo } from "./quota.js";
|
||||||
import { buildCodexExecArgs } from "./codex-args.js";
|
import { buildCodexExecArgs } from "./codex-args.js";
|
||||||
|
|
||||||
@@ -104,6 +106,15 @@ export async function testEnvironment(
|
|||||||
if (typeof value === "string") env[key] = value;
|
if (typeof value === "string") env[key] = value;
|
||||||
}
|
}
|
||||||
const runtimeEnv = ensurePathInEnv({ ...process.env, ...env });
|
const runtimeEnv = ensurePathInEnv({ ...process.env, ...env });
|
||||||
|
const installCheck = await maybeRunSandboxInstallCommand({
|
||||||
|
runId,
|
||||||
|
target,
|
||||||
|
adapterKey: "codex",
|
||||||
|
installCommand: SANDBOX_INSTALL_COMMAND,
|
||||||
|
detectCommand: command,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
if (installCheck) checks.push(installCheck);
|
||||||
try {
|
try {
|
||||||
await ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, runtimeEnv);
|
await ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, runtimeEnv);
|
||||||
checks.push({
|
checks.push({
|
||||||
|
|||||||
@@ -3,6 +3,15 @@ import type { AdapterModelProfileDefinition } from "@paperclipai/adapter-utils";
|
|||||||
export const type = "cursor";
|
export const type = "cursor";
|
||||||
export const label = "Cursor CLI (local)";
|
export const label = "Cursor CLI (local)";
|
||||||
|
|
||||||
|
// Cursor CLI is not distributed as an npm package — the official install
|
||||||
|
// path is the upstream installer script at cursor.com/install. Other adapters
|
||||||
|
// in this repo prefer `npm install -g <pkg>` which is content-addressed by the
|
||||||
|
// registry; cursor must use `curl | bash` until upstream publishes a registry
|
||||||
|
// artifact. Pinning a commit/version here would require shipping our own
|
||||||
|
// mirror of the installer; revisit if Cursor adds an npm/release-asset
|
||||||
|
// equivalent.
|
||||||
|
export const SANDBOX_INSTALL_COMMAND = "curl https://cursor.com/install -fsS | bash";
|
||||||
|
|
||||||
export const DEFAULT_CURSOR_LOCAL_MODEL = "auto";
|
export const DEFAULT_CURSOR_LOCAL_MODEL = "auto";
|
||||||
|
|
||||||
const CURSOR_FALLBACK_MODEL_IDS = [
|
const CURSOR_FALLBACK_MODEL_IDS = [
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ import {
|
|||||||
DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE,
|
DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE,
|
||||||
joinPromptSections,
|
joinPromptSections,
|
||||||
} from "@paperclipai/adapter-utils/server-utils";
|
} from "@paperclipai/adapter-utils/server-utils";
|
||||||
import { DEFAULT_CURSOR_LOCAL_MODEL } from "../index.js";
|
import { DEFAULT_CURSOR_LOCAL_MODEL, SANDBOX_INSTALL_COMMAND } from "../index.js";
|
||||||
import { parseCursorJsonl, isCursorUnknownSessionError } from "./parse.js";
|
import { parseCursorJsonl, isCursorUnknownSessionError } from "./parse.js";
|
||||||
import { prepareCursorSandboxCommand } from "./remote-command.js";
|
import { prepareCursorSandboxCommand } from "./remote-command.js";
|
||||||
import { normalizeCursorStreamLine } from "../shared/stream.js";
|
import { normalizeCursorStreamLine } from "../shared/stream.js";
|
||||||
@@ -340,7 +340,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||||||
);
|
);
|
||||||
const billingType = resolveCursorBillingType(effectiveEnv);
|
const billingType = resolveCursorBillingType(effectiveEnv);
|
||||||
const runtimeEnv = ensurePathInEnv(effectiveEnv);
|
const runtimeEnv = ensurePathInEnv(effectiveEnv);
|
||||||
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv);
|
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv, { installCommand: SANDBOX_INSTALL_COMMAND });
|
||||||
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
|
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
|
||||||
let loggedEnv = buildInvocationEnvForLogs(env, {
|
let loggedEnv = buildInvocationEnvForLogs(env, {
|
||||||
runtimeEnv,
|
runtimeEnv,
|
||||||
@@ -370,6 +370,8 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||||||
target: executionTarget,
|
target: executionTarget,
|
||||||
adapterKey: "cursor",
|
adapterKey: "cursor",
|
||||||
workspaceLocalDir: cwd,
|
workspaceLocalDir: cwd,
|
||||||
|
installCommand: SANDBOX_INSTALL_COMMAND,
|
||||||
|
detectCommand: command,
|
||||||
assets: [{
|
assets: [{
|
||||||
key: "skills",
|
key: "skills",
|
||||||
localDir: localSkillsDir,
|
localDir: localSkillsDir,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
import {
|
import {
|
||||||
ensureAdapterExecutionTargetCommandResolvable,
|
ensureAdapterExecutionTargetCommandResolvable,
|
||||||
ensureAdapterExecutionTargetDirectory,
|
ensureAdapterExecutionTargetDirectory,
|
||||||
|
maybeRunSandboxInstallCommand,
|
||||||
runAdapterExecutionTargetProcess,
|
runAdapterExecutionTargetProcess,
|
||||||
describeAdapterExecutionTarget,
|
describeAdapterExecutionTarget,
|
||||||
resolveAdapterExecutionTargetCwd,
|
resolveAdapterExecutionTargetCwd,
|
||||||
@@ -19,7 +20,7 @@ import {
|
|||||||
import fs from "node:fs/promises";
|
import fs from "node:fs/promises";
|
||||||
import os from "node:os";
|
import os from "node:os";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { DEFAULT_CURSOR_LOCAL_MODEL } from "../index.js";
|
import { DEFAULT_CURSOR_LOCAL_MODEL, SANDBOX_INSTALL_COMMAND } from "../index.js";
|
||||||
import { parseCursorJsonl } from "./parse.js";
|
import { parseCursorJsonl } from "./parse.js";
|
||||||
import { isDefaultCursorCommand, prepareCursorSandboxCommand } from "./remote-command.js";
|
import { isDefaultCursorCommand, prepareCursorSandboxCommand } from "./remote-command.js";
|
||||||
import { hasCursorTrustBypassArg } from "../shared/trust.js";
|
import { hasCursorTrustBypassArg } from "../shared/trust.js";
|
||||||
@@ -148,6 +149,15 @@ export async function testEnvironment(
|
|||||||
command = sandboxCommand.command;
|
command = sandboxCommand.command;
|
||||||
env = sandboxCommand.env;
|
env = sandboxCommand.env;
|
||||||
const runtimeEnv = ensurePathInEnv({ ...process.env, ...env });
|
const runtimeEnv = ensurePathInEnv({ ...process.env, ...env });
|
||||||
|
const installCheck = await maybeRunSandboxInstallCommand({
|
||||||
|
runId,
|
||||||
|
target,
|
||||||
|
adapterKey: "cursor",
|
||||||
|
installCommand: SANDBOX_INSTALL_COMMAND,
|
||||||
|
detectCommand: command,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
if (installCheck) checks.push(installCheck);
|
||||||
try {
|
try {
|
||||||
await ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, runtimeEnv);
|
await ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, runtimeEnv);
|
||||||
checks.push({
|
checks.push({
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import type { AdapterModelProfileDefinition } from "@paperclipai/adapter-utils";
|
|||||||
export const type = "gemini_local";
|
export const type = "gemini_local";
|
||||||
export const label = "Gemini CLI (local)";
|
export const label = "Gemini CLI (local)";
|
||||||
|
|
||||||
|
export const SANDBOX_INSTALL_COMMAND = "npm install -g @google/gemini-cli";
|
||||||
|
|
||||||
export const DEFAULT_GEMINI_LOCAL_MODEL = "auto";
|
export const DEFAULT_GEMINI_LOCAL_MODEL = "auto";
|
||||||
|
|
||||||
export const models = [
|
export const models = [
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ import {
|
|||||||
DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE,
|
DEFAULT_PAPERCLIP_AGENT_PROMPT_TEMPLATE,
|
||||||
runChildProcess,
|
runChildProcess,
|
||||||
} from "@paperclipai/adapter-utils/server-utils";
|
} from "@paperclipai/adapter-utils/server-utils";
|
||||||
import { DEFAULT_GEMINI_LOCAL_MODEL } from "../index.js";
|
import { DEFAULT_GEMINI_LOCAL_MODEL, SANDBOX_INSTALL_COMMAND } from "../index.js";
|
||||||
import {
|
import {
|
||||||
describeGeminiFailure,
|
describeGeminiFailure,
|
||||||
detectGeminiAuthRequired,
|
detectGeminiAuthRequired,
|
||||||
@@ -292,7 +292,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||||||
graceSec,
|
graceSec,
|
||||||
onLog,
|
onLog,
|
||||||
});
|
});
|
||||||
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv);
|
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv, { installCommand: SANDBOX_INSTALL_COMMAND });
|
||||||
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
|
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
|
||||||
let loggedEnv = buildInvocationEnvForLogs(env, {
|
let loggedEnv = buildInvocationEnvForLogs(env, {
|
||||||
runtimeEnv,
|
runtimeEnv,
|
||||||
@@ -322,6 +322,8 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||||||
target: executionTarget,
|
target: executionTarget,
|
||||||
adapterKey: "gemini",
|
adapterKey: "gemini",
|
||||||
workspaceLocalDir: cwd,
|
workspaceLocalDir: cwd,
|
||||||
|
installCommand: SANDBOX_INSTALL_COMMAND,
|
||||||
|
detectCommand: command,
|
||||||
assets: [{
|
assets: [{
|
||||||
key: "skills",
|
key: "skills",
|
||||||
localDir: localSkillsDir,
|
localDir: localSkillsDir,
|
||||||
|
|||||||
@@ -14,12 +14,13 @@ import {
|
|||||||
} from "@paperclipai/adapter-utils/server-utils";
|
} from "@paperclipai/adapter-utils/server-utils";
|
||||||
import {
|
import {
|
||||||
ensureAdapterExecutionTargetCommandResolvable,
|
ensureAdapterExecutionTargetCommandResolvable,
|
||||||
|
maybeRunSandboxInstallCommand,
|
||||||
ensureAdapterExecutionTargetDirectory,
|
ensureAdapterExecutionTargetDirectory,
|
||||||
runAdapterExecutionTargetProcess,
|
runAdapterExecutionTargetProcess,
|
||||||
describeAdapterExecutionTarget,
|
describeAdapterExecutionTarget,
|
||||||
resolveAdapterExecutionTargetCwd,
|
resolveAdapterExecutionTargetCwd,
|
||||||
} from "@paperclipai/adapter-utils/execution-target";
|
} from "@paperclipai/adapter-utils/execution-target";
|
||||||
import { DEFAULT_GEMINI_LOCAL_MODEL } from "../index.js";
|
import { DEFAULT_GEMINI_LOCAL_MODEL, SANDBOX_INSTALL_COMMAND } from "../index.js";
|
||||||
import { detectGeminiAuthRequired, detectGeminiQuotaExhausted, parseGeminiJsonl } from "./parse.js";
|
import { detectGeminiAuthRequired, detectGeminiQuotaExhausted, parseGeminiJsonl } from "./parse.js";
|
||||||
import { firstNonEmptyLine } from "./utils.js";
|
import { firstNonEmptyLine } from "./utils.js";
|
||||||
|
|
||||||
@@ -94,6 +95,15 @@ export async function testEnvironment(
|
|||||||
if (typeof value === "string") env[key] = value;
|
if (typeof value === "string") env[key] = value;
|
||||||
}
|
}
|
||||||
const runtimeEnv = ensurePathInEnv({ ...process.env, ...env });
|
const runtimeEnv = ensurePathInEnv({ ...process.env, ...env });
|
||||||
|
const installCheck = await maybeRunSandboxInstallCommand({
|
||||||
|
runId,
|
||||||
|
target,
|
||||||
|
adapterKey: "gemini",
|
||||||
|
installCommand: SANDBOX_INSTALL_COMMAND,
|
||||||
|
detectCommand: command,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
if (installCheck) checks.push(installCheck);
|
||||||
try {
|
try {
|
||||||
await ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, runtimeEnv);
|
await ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, runtimeEnv);
|
||||||
checks.push({
|
checks.push({
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import type { AdapterModelProfileDefinition } from "@paperclipai/adapter-utils";
|
|||||||
export const type = "opencode_local";
|
export const type = "opencode_local";
|
||||||
export const label = "OpenCode (local)";
|
export const label = "OpenCode (local)";
|
||||||
|
|
||||||
|
export const SANDBOX_INSTALL_COMMAND = "npm install -g opencode-ai";
|
||||||
|
|
||||||
export const DEFAULT_OPENCODE_LOCAL_MODEL = "openai/gpt-5.2-codex";
|
export const DEFAULT_OPENCODE_LOCAL_MODEL = "openai/gpt-5.2-codex";
|
||||||
|
|
||||||
export function isValidOpenCodeModelId(value: unknown): value is string {
|
export function isValidOpenCodeModelId(value: unknown): value is string {
|
||||||
|
|||||||
@@ -50,6 +50,7 @@ import {
|
|||||||
} from "./models.js";
|
} from "./models.js";
|
||||||
import { removeMaintainerOnlySkillSymlinks } from "@paperclipai/adapter-utils/server-utils";
|
import { removeMaintainerOnlySkillSymlinks } from "@paperclipai/adapter-utils/server-utils";
|
||||||
import { prepareOpenCodeRuntimeConfig } from "./runtime-config.js";
|
import { prepareOpenCodeRuntimeConfig } from "./runtime-config.js";
|
||||||
|
import { SANDBOX_INSTALL_COMMAND } from "../index.js";
|
||||||
|
|
||||||
const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
@@ -316,7 +317,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||||||
graceSec,
|
graceSec,
|
||||||
onLog,
|
onLog,
|
||||||
});
|
});
|
||||||
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv);
|
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv, { installCommand: SANDBOX_INSTALL_COMMAND });
|
||||||
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
|
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
|
||||||
let loggedEnv = buildInvocationEnvForLogs(preparedRuntimeConfig.env, {
|
let loggedEnv = buildInvocationEnvForLogs(preparedRuntimeConfig.env, {
|
||||||
runtimeEnv,
|
runtimeEnv,
|
||||||
@@ -352,6 +353,8 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||||||
target: executionTarget,
|
target: executionTarget,
|
||||||
adapterKey: "opencode",
|
adapterKey: "opencode",
|
||||||
workspaceLocalDir: cwd,
|
workspaceLocalDir: cwd,
|
||||||
|
installCommand: SANDBOX_INSTALL_COMMAND,
|
||||||
|
detectCommand: command,
|
||||||
assets: [
|
assets: [
|
||||||
{
|
{
|
||||||
key: "skills",
|
key: "skills",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from "@paperclipai/adapter-utils/server-utils";
|
} from "@paperclipai/adapter-utils/server-utils";
|
||||||
import {
|
import {
|
||||||
ensureAdapterExecutionTargetCommandResolvable,
|
ensureAdapterExecutionTargetCommandResolvable,
|
||||||
|
maybeRunSandboxInstallCommand,
|
||||||
ensureAdapterExecutionTargetDirectory,
|
ensureAdapterExecutionTargetDirectory,
|
||||||
runAdapterExecutionTargetProcess,
|
runAdapterExecutionTargetProcess,
|
||||||
describeAdapterExecutionTarget,
|
describeAdapterExecutionTarget,
|
||||||
@@ -19,6 +20,7 @@ import {
|
|||||||
} from "@paperclipai/adapter-utils/execution-target";
|
} from "@paperclipai/adapter-utils/execution-target";
|
||||||
import { discoverOpenCodeModels, ensureOpenCodeModelConfiguredAndAvailable } from "./models.js";
|
import { discoverOpenCodeModels, ensureOpenCodeModelConfiguredAndAvailable } from "./models.js";
|
||||||
import { parseOpenCodeJsonl } from "./parse.js";
|
import { parseOpenCodeJsonl } from "./parse.js";
|
||||||
|
import { SANDBOX_INSTALL_COMMAND } from "../index.js";
|
||||||
import { prepareOpenCodeRuntimeConfig } from "./runtime-config.js";
|
import { prepareOpenCodeRuntimeConfig } from "./runtime-config.js";
|
||||||
|
|
||||||
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
|
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
|
||||||
@@ -136,6 +138,15 @@ export async function testEnvironment(
|
|||||||
detail: command,
|
detail: command,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
const installCheck = await maybeRunSandboxInstallCommand({
|
||||||
|
runId,
|
||||||
|
target,
|
||||||
|
adapterKey: "opencode",
|
||||||
|
installCommand: SANDBOX_INSTALL_COMMAND,
|
||||||
|
detectCommand: command,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
if (installCheck) checks.push(installCheck);
|
||||||
try {
|
try {
|
||||||
await ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, runtimeEnv);
|
await ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, runtimeEnv);
|
||||||
checks.push({
|
checks.push({
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ import type { AdapterModelProfileDefinition } from "@paperclipai/adapter-utils";
|
|||||||
export const type = "pi_local";
|
export const type = "pi_local";
|
||||||
export const label = "Pi (local)";
|
export const label = "Pi (local)";
|
||||||
|
|
||||||
|
export const SANDBOX_INSTALL_COMMAND = "npm install -g @mariozechner/pi-coding-agent";
|
||||||
|
|
||||||
export const models: Array<{ id: string; label: string }> = [];
|
export const models: Array<{ id: string; label: string }> = [];
|
||||||
|
|
||||||
export const modelProfiles: AdapterModelProfileDefinition[] = [];
|
export const modelProfiles: AdapterModelProfileDefinition[] = [];
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import {
|
|||||||
import { shellQuote } from "@paperclipai/adapter-utils/ssh";
|
import { shellQuote } from "@paperclipai/adapter-utils/ssh";
|
||||||
import { isPiUnknownSessionError, parsePiJsonl } from "./parse.js";
|
import { isPiUnknownSessionError, parsePiJsonl } from "./parse.js";
|
||||||
import { ensurePiModelConfiguredAndAvailable } from "./models.js";
|
import { ensurePiModelConfiguredAndAvailable } from "./models.js";
|
||||||
|
import { SANDBOX_INSTALL_COMMAND } from "../index.js";
|
||||||
|
|
||||||
const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
const __moduleDir = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
@@ -361,7 +362,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||||||
graceSec,
|
graceSec,
|
||||||
onLog,
|
onLog,
|
||||||
});
|
});
|
||||||
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv);
|
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv, { installCommand: SANDBOX_INSTALL_COMMAND });
|
||||||
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
|
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
|
||||||
let loggedEnv = buildInvocationEnvForLogs(env, {
|
let loggedEnv = buildInvocationEnvForLogs(env, {
|
||||||
runtimeEnv,
|
runtimeEnv,
|
||||||
@@ -400,6 +401,8 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||||||
target: executionTarget,
|
target: executionTarget,
|
||||||
adapterKey: "pi",
|
adapterKey: "pi",
|
||||||
workspaceLocalDir: cwd,
|
workspaceLocalDir: cwd,
|
||||||
|
installCommand: SANDBOX_INSTALL_COMMAND,
|
||||||
|
detectCommand: command,
|
||||||
assets: [
|
assets: [
|
||||||
{
|
{
|
||||||
key: "skills",
|
key: "skills",
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
} from "@paperclipai/adapter-utils/server-utils";
|
} from "@paperclipai/adapter-utils/server-utils";
|
||||||
import {
|
import {
|
||||||
ensureAdapterExecutionTargetCommandResolvable,
|
ensureAdapterExecutionTargetCommandResolvable,
|
||||||
|
maybeRunSandboxInstallCommand,
|
||||||
ensureAdapterExecutionTargetDirectory,
|
ensureAdapterExecutionTargetDirectory,
|
||||||
runAdapterExecutionTargetProcess,
|
runAdapterExecutionTargetProcess,
|
||||||
describeAdapterExecutionTarget,
|
describeAdapterExecutionTarget,
|
||||||
@@ -20,6 +21,7 @@ import {
|
|||||||
} from "@paperclipai/adapter-utils/execution-target";
|
} from "@paperclipai/adapter-utils/execution-target";
|
||||||
import { discoverPiModelsCached } from "./models.js";
|
import { discoverPiModelsCached } from "./models.js";
|
||||||
import { parsePiJsonl } from "./parse.js";
|
import { parsePiJsonl } from "./parse.js";
|
||||||
|
import { SANDBOX_INSTALL_COMMAND } from "../index.js";
|
||||||
|
|
||||||
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
|
function summarizeStatus(checks: AdapterEnvironmentCheck[]): AdapterEnvironmentTestResult["status"] {
|
||||||
if (checks.some((check) => check.level === "error")) return "fail";
|
if (checks.some((check) => check.level === "error")) return "fail";
|
||||||
@@ -134,6 +136,15 @@ export async function testEnvironment(
|
|||||||
detail: command,
|
detail: command,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
const installCheck = await maybeRunSandboxInstallCommand({
|
||||||
|
runId,
|
||||||
|
target,
|
||||||
|
adapterKey: "pi",
|
||||||
|
installCommand: SANDBOX_INSTALL_COMMAND,
|
||||||
|
detectCommand: command,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
if (installCheck) checks.push(installCheck);
|
||||||
try {
|
try {
|
||||||
await ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, runtimeEnv);
|
await ensureAdapterExecutionTargetCommandResolvable(command, target, cwd, runtimeEnv);
|
||||||
checks.push({
|
checks.push({
|
||||||
|
|||||||
Reference in New Issue
Block a user