Dev #11

Merged
cpfarhood merged 86 commits from dev into local 2026-05-12 00:02:32 +00:00
14 changed files with 383 additions and 14 deletions
Showing only changes of commit 90631b09b3 - Show all commits
@@ -2,6 +2,7 @@ import { afterEach, describe, expect, it, vi } from "vitest";
import * as ssh from "./ssh.js";
import {
adapterExecutionTargetUsesManagedHome,
ensureAdapterExecutionTargetRuntimeCommandInstalled,
resolveAdapterExecutionTargetCwd,
runAdapterExecutionTargetShellCommand,
} from "./execution-target.js";
@@ -161,6 +162,80 @@ describe("runAdapterExecutionTargetShellCommand", () => {
});
});
describe("ensureAdapterExecutionTargetRuntimeCommandInstalled", () => {
afterEach(() => {
vi.restoreAllMocks();
});
it("runs install commands for sandbox targets", async () => {
const runner = {
execute: vi.fn(async () => ({
exitCode: 0,
signal: null,
timedOut: false,
stdout: "",
stderr: "",
pid: null,
startedAt: new Date().toISOString(),
})),
};
await ensureAdapterExecutionTargetRuntimeCommandInstalled({
runId: "run-install",
target: {
kind: "remote",
transport: "sandbox",
providerKey: "e2b",
remoteCwd: "/remote/workspace",
runner,
},
installCommand: "npm install -g @google/gemini-cli",
cwd: "/local/workspace",
env: { PATH: "/usr/bin" },
timeoutSec: 30,
});
expect(runner.execute).toHaveBeenCalledWith(expect.objectContaining({
command: "sh",
args: ["-lc", "npm install -g @google/gemini-cli"],
cwd: "/remote/workspace",
env: { PATH: "/usr/bin" },
timeoutMs: 30_000,
}));
});
it("skips install commands for SSH targets", async () => {
const runSshCommandSpy = vi.spyOn(ssh, "runSshCommand").mockResolvedValue({
stdout: "",
stderr: "",
});
await ensureAdapterExecutionTargetRuntimeCommandInstalled({
runId: "run-skip",
target: {
kind: "remote",
transport: "ssh",
remoteCwd: "/srv/paperclip/workspace",
spec: {
host: "ssh.example.test",
port: 22,
username: "ssh-user",
remoteCwd: "/srv/paperclip/workspace",
remoteWorkspacePath: "/srv/paperclip/workspace",
privateKey: null,
knownHosts: null,
strictHostKeyChecking: true,
},
},
installCommand: "npm install -g @google/gemini-cli",
cwd: "/tmp/local",
env: {},
});
expect(runSshCommandSpy).not.toHaveBeenCalled();
});
});
describe("resolveAdapterExecutionTargetCwd", () => {
const sshTarget = {
kind: "remote" as const,
@@ -393,6 +393,60 @@ export async function readAdapterExecutionTargetHomeDir(
return homeDir.length > 0 ? homeDir : null;
}
export async function ensureAdapterExecutionTargetRuntimeCommandInstalled(input: {
runId: string;
target: AdapterExecutionTarget | null | undefined;
installCommand?: string | null;
detectCommand?: string | null;
cwd: string;
env: Record<string, string>;
timeoutSec?: number;
graceSec?: number;
onLog?: AdapterExecutionTargetShellOptions["onLog"];
}): Promise<void> {
const installCommand = input.installCommand?.trim();
if (!installCommand || input.target?.kind !== "remote" || input.target.transport !== "sandbox") {
return;
}
const detectCommand = input.detectCommand?.trim();
if (detectCommand) {
const probe = 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 (!probe.timedOut && probe.exitCode === 0) {
return;
}
}
const result = await runAdapterExecutionTargetShellCommand(
input.runId,
input.target,
installCommand,
{
cwd: input.cwd,
env: input.env,
timeoutSec: input.timeoutSec,
graceSec: input.graceSec,
onLog: input.onLog,
},
);
if (result.timedOut) {
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}`);
}
}
export async function ensureAdapterExecutionTargetFile(
runId: string,
target: AdapterExecutionTarget | null | undefined,
+1
View File
@@ -27,6 +27,7 @@ export type {
ConfigFieldOption,
ConfigFieldSchema,
AdapterConfigSchema,
AdapterRuntimeCommandSpec,
ServerAdapterModule,
QuotaWindow,
ProviderQuotaResult,
+23
View File
@@ -125,6 +125,7 @@ export interface AdapterExecutionContext {
runtime: AdapterRuntime;
config: Record<string, unknown>;
context: Record<string, unknown>;
runtimeCommandSpec?: AdapterRuntimeCommandSpec | null;
executionTarget?: AdapterExecutionTarget | null;
/**
* Legacy remote transport view. Prefer `executionTarget`, which is the
@@ -328,6 +329,23 @@ export interface AdapterConfigSchema {
fields: ConfigFieldSchema[];
}
export interface AdapterRuntimeCommandSpec {
/**
* The command Paperclip should execute for this adapter in the current config.
*/
command: string;
/**
* Optional command name/path to probe for availability before launch.
* Defaults to `command` when omitted by the consumer.
*/
detectCommand?: string | null;
/**
* Optional shell snippet that can install or expose the adapter command in a
* fresh remote runtime. It should be idempotent.
*/
installCommand?: string | null;
}
export interface ServerAdapterModule {
type: string;
execute(ctx: AdapterExecutionContext): Promise<AdapterExecutionResult>;
@@ -406,6 +424,11 @@ export interface ServerAdapterModule {
* rather than reading config.paperclipRuntimeSkills.
*/
requiresMaterializedRuntimeSkills?: boolean;
/**
* Optional: describe how this adapter's runtime command should be launched
* and provisioned in fresh remote environments such as sandboxes.
*/
getRuntimeCommandSpec?: (config: Record<string, unknown>) => AdapterRuntimeCommandSpec | null;
}
// ---------------------------------------------------------------------------
@@ -12,6 +12,7 @@ import {
adapterExecutionTargetUsesPaperclipBridge,
describeAdapterExecutionTarget,
ensureAdapterExecutionTargetCommandResolvable,
ensureAdapterExecutionTargetRuntimeCommandInstalled,
prepareAdapterExecutionTargetRuntime,
readAdapterExecutionTarget,
resolveAdapterExecutionTargetCommandForLogs,
@@ -61,8 +62,10 @@ interface ClaudeExecutionInput {
agent: AdapterExecutionContext["agent"];
config: Record<string, unknown>;
context: Record<string, unknown>;
runtimeCommandSpec?: AdapterExecutionContext["runtimeCommandSpec"];
executionTarget?: ReturnType<typeof readAdapterExecutionTarget>;
authToken?: string;
onLog?: (stream: "stdout" | "stderr", chunk: string) => Promise<void>;
}
interface ClaudeRuntimeConfig {
@@ -112,7 +115,8 @@ function resolveClaudeBillingType(env: Record<string, string>): "api" | "subscri
}
async function buildClaudeRuntimeConfig(input: ClaudeExecutionInput): Promise<ClaudeRuntimeConfig> {
const { runId, agent, config, context, executionTarget, authToken } = input;
const { runId, agent, config, context, runtimeCommandSpec, executionTarget, authToken } = input;
const onLog = input.onLog ?? (async () => {});
const command = asString(config.command, "claude");
const workspaceContext = parseObject(context.paperclipWorkspace);
@@ -239,7 +243,24 @@ async function buildClaudeRuntimeConfig(input: ClaudeExecutionInput): Promise<Cl
env.PAPERCLIP_API_KEY = authToken;
}
const runtimeEnv = ensurePathInEnv({ ...process.env, ...env });
const runtimeEnv = Object.fromEntries(
Object.entries(ensurePathInEnv({ ...process.env, ...env })).filter(
(entry): entry is [string, string] => typeof entry[1] === "string",
),
);
const timeoutSec = asNumber(config.timeoutSec, 0);
const graceSec = asNumber(config.graceSec, 20);
await ensureAdapterExecutionTargetRuntimeCommandInstalled({
runId,
target: executionTarget,
installCommand: runtimeCommandSpec?.installCommand,
detectCommand: runtimeCommandSpec?.detectCommand,
cwd,
env: runtimeEnv,
timeoutSec,
graceSec,
onLog,
});
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv);
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
const loggedEnv = buildInvocationEnvForLogs(env, {
@@ -248,8 +269,6 @@ async function buildClaudeRuntimeConfig(input: ClaudeExecutionInput): Promise<Cl
resolvedCommand,
});
const timeoutSec = asNumber(config.timeoutSec, 0);
const graceSec = asNumber(config.graceSec, 20);
const extraArgs = (() => {
const fromExtraArgs = asStringArray(config.extraArgs);
if (fromExtraArgs.length > 0) return fromExtraArgs;
@@ -335,8 +354,10 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
agent,
config,
context,
runtimeCommandSpec: ctx.runtimeCommandSpec,
executionTarget,
authToken,
onLog,
});
const {
command,
@@ -10,6 +10,7 @@ import {
adapterExecutionTargetUsesPaperclipBridge,
describeAdapterExecutionTarget,
ensureAdapterExecutionTargetCommandResolvable,
ensureAdapterExecutionTargetRuntimeCommandInstalled,
prepareAdapterExecutionTargetRuntime,
readAdapterExecutionTarget,
resolveAdapterExecutionTargetCommandForLogs,
@@ -481,7 +482,22 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
),
);
const billingType = resolveCodexBillingType(effectiveEnv);
const runtimeEnv = ensurePathInEnv(effectiveEnv);
const runtimeEnv = Object.fromEntries(
Object.entries(ensurePathInEnv(effectiveEnv)).filter(
(entry): entry is [string, string] => typeof entry[1] === "string",
),
);
await ensureAdapterExecutionTargetRuntimeCommandInstalled({
runId,
target: executionTarget,
installCommand: ctx.runtimeCommandSpec?.installCommand,
detectCommand: ctx.runtimeCommandSpec?.detectCommand,
cwd,
env: runtimeEnv,
timeoutSec: asNumber(config.timeoutSec, 0),
graceSec: asNumber(config.graceSec, 20),
onLog,
});
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv);
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
const loggedEnv = buildInvocationEnvForLogs(env, {
@@ -12,6 +12,7 @@ import {
adapterExecutionTargetUsesPaperclipBridge,
describeAdapterExecutionTarget,
ensureAdapterExecutionTargetCommandResolvable,
ensureAdapterExecutionTargetRuntimeCommandInstalled,
prepareAdapterExecutionTargetRuntime,
readAdapterExecutionTarget,
readAdapterExecutionTargetHomeDir,
@@ -307,6 +308,17 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
}
const timeoutSec = asNumber(config.timeoutSec, 0);
const graceSec = asNumber(config.graceSec, 20);
await ensureAdapterExecutionTargetRuntimeCommandInstalled({
runId,
target: executionTarget,
installCommand: ctx.runtimeCommandSpec?.installCommand,
detectCommand: ctx.runtimeCommandSpec?.detectCommand,
cwd,
env,
timeoutSec,
graceSec,
onLog,
});
// Probe the sandbox before the managed-home override so we discover
// cursor-agent from the real system HOME (e.g. ~/.local/bin/cursor-agent).
// The managed HOME set later is for runtime isolation, not for finding the CLI.
@@ -13,6 +13,7 @@ import {
adapterExecutionTargetUsesPaperclipBridge,
describeAdapterExecutionTarget,
ensureAdapterExecutionTargetCommandResolvable,
ensureAdapterExecutionTargetRuntimeCommandInstalled,
prepareAdapterExecutionTargetRuntime,
readAdapterExecutionTarget,
readAdapterExecutionTargetHomeDir,
@@ -273,7 +274,24 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
),
);
const billingType = resolveGeminiBillingType(effectiveEnv);
const runtimeEnv = ensurePathInEnv(effectiveEnv);
const runtimeEnv = Object.fromEntries(
Object.entries(ensurePathInEnv(effectiveEnv)).filter(
(entry): entry is [string, string] => typeof entry[1] === "string",
),
);
const timeoutSec = asNumber(config.timeoutSec, 0);
const graceSec = asNumber(config.graceSec, 20);
await ensureAdapterExecutionTargetRuntimeCommandInstalled({
runId,
target: executionTarget,
installCommand: ctx.runtimeCommandSpec?.installCommand,
detectCommand: ctx.runtimeCommandSpec?.detectCommand,
cwd,
env: runtimeEnv,
timeoutSec,
graceSec,
onLog,
});
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv);
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
let loggedEnv = buildInvocationEnvForLogs(env, {
@@ -282,8 +300,6 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
resolvedCommand,
});
const timeoutSec = asNumber(config.timeoutSec, 0);
const graceSec = asNumber(config.graceSec, 20);
const extraArgs = (() => {
const fromExtraArgs = asStringArray(config.extraArgs);
if (fromExtraArgs.length > 0) return fromExtraArgs;
@@ -12,6 +12,7 @@ import {
adapterExecutionTargetUsesPaperclipBridge,
describeAdapterExecutionTarget,
ensureAdapterExecutionTargetCommandResolvable,
ensureAdapterExecutionTargetRuntimeCommandInstalled,
prepareAdapterExecutionTargetRuntime,
readAdapterExecutionTarget,
readAdapterExecutionTargetHomeDir,
@@ -302,6 +303,19 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
(entry): entry is [string, string] => typeof entry[1] === "string",
),
);
const timeoutSec = asNumber(config.timeoutSec, 0);
const graceSec = asNumber(config.graceSec, 20);
await ensureAdapterExecutionTargetRuntimeCommandInstalled({
runId,
target: executionTarget,
installCommand: ctx.runtimeCommandSpec?.installCommand,
detectCommand: ctx.runtimeCommandSpec?.detectCommand,
cwd,
env: runtimeEnv,
timeoutSec,
graceSec,
onLog,
});
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv);
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
let loggedEnv = buildInvocationEnvForLogs(preparedRuntimeConfig.env, {
@@ -309,9 +323,6 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
includeRuntimeKeys: ["HOME"],
resolvedCommand,
});
const timeoutSec = asNumber(config.timeoutSec, 0);
const graceSec = asNumber(config.graceSec, 20);
if (!executionTargetIsRemote) {
await ensureOpenCodeModelConfiguredAndAvailable({
model,
@@ -13,6 +13,7 @@ import {
describeAdapterExecutionTarget,
ensureAdapterExecutionTargetCommandResolvable,
ensureAdapterExecutionTargetFile,
ensureAdapterExecutionTargetRuntimeCommandInstalled,
prepareAdapterExecutionTargetRuntime,
readAdapterExecutionTarget,
resolveAdapterExecutionTargetCommandForLogs,
@@ -347,6 +348,19 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
(entry): entry is [string, string] => typeof entry[1] === "string",
),
);
const timeoutSec = asNumber(config.timeoutSec, 0);
const graceSec = asNumber(config.graceSec, 20);
await ensureAdapterExecutionTargetRuntimeCommandInstalled({
runId,
target: executionTarget,
installCommand: ctx.runtimeCommandSpec?.installCommand,
detectCommand: ctx.runtimeCommandSpec?.detectCommand,
cwd,
env: runtimeEnv,
timeoutSec,
graceSec,
onLog,
});
await ensureAdapterExecutionTargetCommandResolvable(command, executionTarget, cwd, runtimeEnv);
const resolvedCommand = await resolveAdapterExecutionTargetCommandForLogs(command, executionTarget, cwd, runtimeEnv);
let loggedEnv = buildInvocationEnvForLogs(env, {
@@ -364,8 +378,6 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
});
}
const timeoutSec = asNumber(config.timeoutSec, 0);
const graceSec = asNumber(config.graceSec, 20);
const extraArgs = (() => {
const fromExtraArgs = asStringArray(config.extraArgs);
if (fromExtraArgs.length > 0) return fromExtraArgs;
@@ -420,6 +420,85 @@ describe("environmentRunOrchestrator — realizeForRun", () => {
}));
});
it("runs project-level provision commands for ssh environments", async () => {
mockBuildWorkspaceRealizationRequest.mockReturnValue({
version: 1,
adapterType: "gemini_local",
companyId: "company-1",
environmentId: "env-1",
executionWorkspaceId: null,
issueId: null,
heartbeatRunId: "run-1",
requestedMode: null,
source: {
kind: "project_primary",
localPath: "/workspace/project",
projectId: null,
projectWorkspaceId: null,
repoUrl: null,
repoRef: null,
strategy: "project_primary",
branchName: null,
worktreePath: null,
},
runtimeOverlay: {
provisionCommand: "npm install -g @google/gemini-cli",
},
});
mockResolveEnvironmentExecutionTarget.mockResolvedValue({
kind: "remote",
transport: "ssh",
remoteCwd: "/remote/workspace",
environmentId: "env-1",
leaseId: "lease-1",
spec: {
host: "ssh.example.test",
port: 22,
username: "ssh-user",
remoteCwd: "/remote/workspace",
remoteWorkspacePath: "/remote/workspace",
privateKey: null,
knownHosts: null,
strictHostKeyChecking: true,
},
});
const runtime = makeMockRuntime({
realizeWorkspace: vi.fn().mockResolvedValue({
cwd: "/remote/workspace",
metadata: {
workspaceRealization: {
version: 1,
transport: "ssh",
remote: { path: "/remote/workspace" },
},
},
}),
});
const orchestrator = environmentRunOrchestrator(mockDb, { environmentRuntime: runtime });
await orchestrator.realizeForRun(makeRealizeInput({
environment: makeEnvironment("ssh"),
lease: makeLease({
provider: "ssh",
metadata: {
driver: "ssh",
remoteCwd: "/remote/workspace",
remoteWorkspacePath: "/remote/workspace",
host: "ssh.example.test",
port: 22,
username: "ssh-user",
},
}),
}));
expect(runtime.execute).toHaveBeenCalledWith(expect.objectContaining({
command: "bash",
args: ["-lc", "npm install -g @google/gemini-cli"],
}));
expect(mockResolveEnvironmentExecutionTarget).toHaveBeenCalledOnce();
});
it("surfaces remote provision command failures before resolving the adapter target", async () => {
mockBuildWorkspaceRealizationRequest.mockReturnValue({
version: 1,
+48 -1
View File
@@ -1,4 +1,4 @@
import type { AdapterModelProfileDefinition, ServerAdapterModule } from "./types.js";
import type { AdapterModelProfileDefinition, AdapterRuntimeCommandSpec, ServerAdapterModule } from "./types.js";
import { getAdapterSessionManagement } from "@paperclipai/adapter-utils";
import {
execute as acpxExecute,
@@ -113,6 +113,44 @@ import { getDisabledAdapterTypes } from "../services/adapter-plugin-store.js";
import { processAdapter } from "./process/index.js";
import { httpAdapter } from "./http/index.js";
function readConfiguredCommand(config: Record<string, unknown>, fallback: string): string {
const value = typeof config.command === "string" ? config.command.trim() : "";
return value.length > 0 ? value : fallback;
}
function hasPathSeparator(command: string): boolean {
return command.includes("/") || command.includes("\\");
}
function shellQuote(value: string): string {
return `'${value.replace(/'/g, `'"'"'`)}'`;
}
function buildNpmRuntimeCommandSpec(
config: Record<string, unknown>,
fallbackCommand: string,
packageName: string,
): AdapterRuntimeCommandSpec {
const command = readConfiguredCommand(config, fallbackCommand);
const canSelfInstall = !hasPathSeparator(command) && command === fallbackCommand;
return {
command,
detectCommand: command,
installCommand: canSelfInstall
? `if ! command -v ${shellQuote(command)} >/dev/null 2>&1; then npm install -g ${shellQuote(packageName)}; fi`
: null,
};
}
function buildCursorRuntimeCommandSpec(config: Record<string, unknown>): AdapterRuntimeCommandSpec {
const command = readConfiguredCommand(config, "agent");
return {
command,
detectCommand: command,
installCommand: null,
};
}
function normalizeHermesConfig<T extends { config?: unknown; agent?: unknown }>(ctx: T): T {
const config =
ctx && typeof ctx === "object" && "config" in ctx && ctx.config && typeof ctx.config === "object"
@@ -159,6 +197,8 @@ const claudeLocalAdapter: ServerAdapterModule = {
supportsInstructionsBundle: true,
instructionsPathKey: "instructionsFilePath",
requiresMaterializedRuntimeSkills: false,
getRuntimeCommandSpec: (config) =>
buildNpmRuntimeCommandSpec(config, "claude", "@anthropic-ai/claude-code"),
agentConfigurationDoc: claudeAgentConfigurationDoc,
getQuotaWindows: claudeGetQuotaWindows,
};
@@ -195,6 +235,7 @@ const codexLocalAdapter: ServerAdapterModule = {
supportsInstructionsBundle: true,
instructionsPathKey: "instructionsFilePath",
requiresMaterializedRuntimeSkills: false,
getRuntimeCommandSpec: (config) => buildNpmRuntimeCommandSpec(config, "codex", "@openai/codex"),
agentConfigurationDoc: codexAgentConfigurationDoc,
getQuotaWindows: codexGetQuotaWindows,
};
@@ -214,6 +255,7 @@ const cursorLocalAdapter: ServerAdapterModule = {
supportsInstructionsBundle: true,
instructionsPathKey: "instructionsFilePath",
requiresMaterializedRuntimeSkills: true,
getRuntimeCommandSpec: buildCursorRuntimeCommandSpec,
agentConfigurationDoc: cursorAgentConfigurationDoc,
};
@@ -231,6 +273,8 @@ const geminiLocalAdapter: ServerAdapterModule = {
supportsInstructionsBundle: true,
instructionsPathKey: "instructionsFilePath",
requiresMaterializedRuntimeSkills: true,
getRuntimeCommandSpec: (config) =>
buildNpmRuntimeCommandSpec(config, "gemini", "@google/gemini-cli"),
agentConfigurationDoc: geminiAgentConfigurationDoc,
};
@@ -260,6 +304,7 @@ const openCodeLocalAdapter: ServerAdapterModule = {
supportsInstructionsBundle: true,
instructionsPathKey: "instructionsFilePath",
requiresMaterializedRuntimeSkills: true,
getRuntimeCommandSpec: (config) => buildNpmRuntimeCommandSpec(config, "opencode", "opencode-ai"),
agentConfigurationDoc: openCodeAgentConfigurationDoc,
};
@@ -278,6 +323,8 @@ const piLocalAdapter: ServerAdapterModule = {
supportsInstructionsBundle: true,
instructionsPathKey: "instructionsFilePath",
requiresMaterializedRuntimeSkills: true,
getRuntimeCommandSpec: (config) =>
buildNpmRuntimeCommandSpec(config, "pi", "@mariozechner/pi-coding-agent"),
agentConfigurationDoc: piAgentConfigurationDoc,
};
+1
View File
@@ -30,5 +30,6 @@ export type {
ConfigFieldOption,
ConfigFieldSchema,
AdapterConfigSchema,
AdapterRuntimeCommandSpec,
ServerAdapterModule,
} from "@paperclipai/adapter-utils";
+1
View File
@@ -6984,6 +6984,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
runtime: runtimeForAdapter,
config: runtimeConfig,
context,
runtimeCommandSpec: adapter.getRuntimeCommandSpec?.(runtimeConfig) ?? null,
executionTarget,
executionTransport: remoteExecution
? { remoteExecution: remoteExecution as unknown as Record<string, unknown> }