fix(codex): warn when sandbox auth is shadowed (#9259)
This commit is contained in:
parent
89ce36d7af
commit
3a727bf780
|
|
@ -6,6 +6,7 @@ export type {
|
|||
AdapterRuntimeServiceReport,
|
||||
AdapterExecutionResult,
|
||||
AdapterInvocationMeta,
|
||||
AdapterRuntimeEvent,
|
||||
AdapterRuntimeMcpServer,
|
||||
AdapterRuntimeMcpAccess,
|
||||
AdapterExecutionContext,
|
||||
|
|
|
|||
|
|
@ -144,6 +144,15 @@ export interface AdapterRuntimeMcpAccess {
|
|||
getServers(): AdapterRuntimeMcpServer[];
|
||||
}
|
||||
|
||||
export interface AdapterRuntimeEvent {
|
||||
eventType: string;
|
||||
stream?: "system" | "stdout" | "stderr";
|
||||
level?: "info" | "warn" | "error";
|
||||
color?: string;
|
||||
message?: string;
|
||||
payload?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface AdapterExecutionContext {
|
||||
runId: string;
|
||||
agent: AdapterAgent;
|
||||
|
|
@ -162,6 +171,7 @@ export interface AdapterExecutionContext {
|
|||
runtimeMcp?: AdapterRuntimeMcpAccess;
|
||||
onLog: (stream: "stdout" | "stderr", chunk: string) => Promise<void>;
|
||||
onMeta?: (meta: AdapterInvocationMeta) => Promise<void>;
|
||||
onEvent?: (event: AdapterRuntimeEvent) => Promise<void>;
|
||||
onRuntimeProgress?: RuntimeStatusSink;
|
||||
onSpawn?: (meta: { pid: number; processGroupId: number | null; startedAt: string }) => Promise<void>;
|
||||
authToken?: string;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
CODEX_SANDBOX_AUTH_EXISTS_COMMAND,
|
||||
CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING,
|
||||
CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING_LOG_LINE,
|
||||
resolveCodexAuthPrecedence,
|
||||
} from "./auth-precedence.js";
|
||||
|
||||
describe("resolveCodexAuthPrecedence", () => {
|
||||
describe("precedence order", () => {
|
||||
it("configured_api_key wins over all other sources", () => {
|
||||
const result = resolveCodexAuthPrecedence({
|
||||
configuredApiKey: true,
|
||||
hostAuthJson: true,
|
||||
sandboxAuthJson: true,
|
||||
});
|
||||
expect(result.winner).toBe("configured_api_key");
|
||||
});
|
||||
|
||||
it("host_auth_json wins when no configured api key", () => {
|
||||
const result = resolveCodexAuthPrecedence({
|
||||
configuredApiKey: false,
|
||||
hostAuthJson: true,
|
||||
sandboxAuthJson: true,
|
||||
});
|
||||
expect(result.winner).toBe("host_auth_json");
|
||||
});
|
||||
|
||||
it("sandbox_auth_json wins when no host credentials", () => {
|
||||
const result = resolveCodexAuthPrecedence({
|
||||
configuredApiKey: false,
|
||||
hostAuthJson: false,
|
||||
sandboxAuthJson: true,
|
||||
});
|
||||
expect(result.winner).toBe("sandbox_auth_json");
|
||||
});
|
||||
|
||||
it("none when no credentials present", () => {
|
||||
const result = resolveCodexAuthPrecedence({
|
||||
configuredApiKey: false,
|
||||
hostAuthJson: false,
|
||||
sandboxAuthJson: false,
|
||||
});
|
||||
expect(result.winner).toBe("none");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sandbox login shadowing and warning", () => {
|
||||
it("warns when configured_api_key shadows sandbox login", () => {
|
||||
const result = resolveCodexAuthPrecedence({
|
||||
configuredApiKey: true,
|
||||
hostAuthJson: false,
|
||||
sandboxAuthJson: true,
|
||||
});
|
||||
expect(result.sandboxLoginShadowed).toBe(true);
|
||||
expect(result.shouldWarn).toBe(true);
|
||||
});
|
||||
|
||||
it("warns when host_auth_json shadows sandbox login", () => {
|
||||
const result = resolveCodexAuthPrecedence({
|
||||
configuredApiKey: false,
|
||||
hostAuthJson: true,
|
||||
sandboxAuthJson: true,
|
||||
});
|
||||
expect(result.sandboxLoginShadowed).toBe(true);
|
||||
expect(result.shouldWarn).toBe(true);
|
||||
});
|
||||
|
||||
it("does not warn when sandbox login wins (no host credentials)", () => {
|
||||
const result = resolveCodexAuthPrecedence({
|
||||
configuredApiKey: false,
|
||||
hostAuthJson: false,
|
||||
sandboxAuthJson: true,
|
||||
});
|
||||
expect(result.sandboxLoginShadowed).toBe(false);
|
||||
expect(result.shouldWarn).toBe(false);
|
||||
});
|
||||
|
||||
it("does not warn when no sandbox login exists (sandbox-only gate)", () => {
|
||||
const result = resolveCodexAuthPrecedence({
|
||||
configuredApiKey: true,
|
||||
hostAuthJson: true,
|
||||
sandboxAuthJson: false,
|
||||
});
|
||||
expect(result.sandboxLoginShadowed).toBe(false);
|
||||
expect(result.shouldWarn).toBe(false);
|
||||
});
|
||||
|
||||
it("does not warn when no credentials exist at all (fail-open)", () => {
|
||||
const result = resolveCodexAuthPrecedence({
|
||||
configuredApiKey: false,
|
||||
hostAuthJson: false,
|
||||
sandboxAuthJson: false,
|
||||
});
|
||||
expect(result.sandboxLoginShadowed).toBe(false);
|
||||
expect(result.shouldWarn).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("constants", () => {
|
||||
it("warning message is stable and human-readable", () => {
|
||||
expect(CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING).toBe(
|
||||
"snapshot login present but configured or host credentials take precedence",
|
||||
);
|
||||
});
|
||||
|
||||
it("log line wraps the warning message", () => {
|
||||
expect(CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING_LOG_LINE).toContain(
|
||||
CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING,
|
||||
);
|
||||
expect(CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING_LOG_LINE).toMatch(
|
||||
/^\[paperclip\] Warning:/,
|
||||
);
|
||||
});
|
||||
|
||||
it("sandbox auth exists command is a static shell test", () => {
|
||||
expect(CODEX_SANDBOX_AUTH_EXISTS_COMMAND).toBe(
|
||||
'test -f "$HOME/.codex/auth.json"',
|
||||
);
|
||||
expect(CODEX_SANDBOX_AUTH_EXISTS_COMMAND).not.toContain("cat");
|
||||
expect(CODEX_SANDBOX_AUTH_EXISTS_COMMAND).not.toContain("readFile");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
export const CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING =
|
||||
"snapshot login present but configured or host credentials take precedence";
|
||||
export const CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING_LOG_LINE =
|
||||
`[paperclip] Warning: ${CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING}.\n`;
|
||||
export const CODEX_SANDBOX_AUTH_EXISTS_COMMAND =
|
||||
'test -f "$HOME/.codex/auth.json"';
|
||||
|
||||
export type CodexAuthPrecedenceWinner =
|
||||
| "configured_api_key"
|
||||
| "host_auth_json"
|
||||
| "sandbox_auth_json"
|
||||
| "none";
|
||||
|
||||
export interface CodexAuthPrecedenceInput {
|
||||
configuredApiKey: boolean;
|
||||
hostAuthJson: boolean;
|
||||
sandboxAuthJson: boolean;
|
||||
}
|
||||
|
||||
export interface CodexAuthPrecedenceResolution {
|
||||
winner: CodexAuthPrecedenceWinner;
|
||||
sandboxLoginShadowed: boolean;
|
||||
shouldWarn: boolean;
|
||||
}
|
||||
|
||||
export function resolveCodexAuthPrecedence(
|
||||
input: CodexAuthPrecedenceInput,
|
||||
): CodexAuthPrecedenceResolution {
|
||||
const winner: CodexAuthPrecedenceWinner =
|
||||
input.configuredApiKey
|
||||
? "configured_api_key"
|
||||
: input.hostAuthJson
|
||||
? "host_auth_json"
|
||||
: input.sandboxAuthJson
|
||||
? "sandbox_auth_json"
|
||||
: "none";
|
||||
const sandboxLoginShadowed =
|
||||
input.sandboxAuthJson &&
|
||||
(winner === "configured_api_key" || winner === "host_auth_json");
|
||||
|
||||
return {
|
||||
winner,
|
||||
sandboxLoginShadowed,
|
||||
shouldWarn: sandboxLoginShadowed,
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,162 @@
|
|||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
CODEX_SANDBOX_AUTH_EXISTS_COMMAND,
|
||||
CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING,
|
||||
CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING_LOG_LINE,
|
||||
} from "./auth-precedence.js";
|
||||
|
||||
const {
|
||||
ensureAdapterExecutionTargetCommandResolvable,
|
||||
ensureAdapterExecutionTargetRuntimeCommandInstalled,
|
||||
prepareAdapterExecutionTargetRuntime,
|
||||
resolveAdapterExecutionTargetCommandForLogs,
|
||||
runAdapterExecutionTargetProcess,
|
||||
runAdapterExecutionTargetShellCommand,
|
||||
startAdapterExecutionTargetPaperclipBridge,
|
||||
} = vi.hoisted(() => ({
|
||||
ensureAdapterExecutionTargetCommandResolvable: vi.fn(async () => undefined),
|
||||
ensureAdapterExecutionTargetRuntimeCommandInstalled: vi.fn(async () => undefined),
|
||||
prepareAdapterExecutionTargetRuntime: vi.fn(async () => ({
|
||||
target: { kind: "remote", transport: "sandbox", remoteCwd: "/sandbox/workspace" },
|
||||
workspaceRemoteDir: "/sandbox/workspace",
|
||||
runtimeRootDir: "/sandbox/.paperclip-runtime",
|
||||
assetDirs: { home: "/sandbox/.paperclip-runtime/codex/home" },
|
||||
restoreWorkspace: vi.fn(async () => undefined),
|
||||
})),
|
||||
resolveAdapterExecutionTargetCommandForLogs: vi.fn(async () => "/usr/bin/codex"),
|
||||
runAdapterExecutionTargetProcess: vi.fn(async () => ({
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
pid: 123,
|
||||
startedAt: new Date().toISOString(),
|
||||
})),
|
||||
runAdapterExecutionTargetShellCommand: vi.fn(async () => ({
|
||||
exitCode: 0,
|
||||
signal: null,
|
||||
timedOut: false,
|
||||
stdout: "",
|
||||
stderr: "",
|
||||
pid: null,
|
||||
startedAt: new Date().toISOString(),
|
||||
})),
|
||||
startAdapterExecutionTargetPaperclipBridge: vi.fn(async () => null),
|
||||
}));
|
||||
|
||||
vi.mock("@paperclipai/adapter-utils/execution-target", async () => {
|
||||
const actual = await vi.importActual<typeof import("@paperclipai/adapter-utils/execution-target")>(
|
||||
"@paperclipai/adapter-utils/execution-target",
|
||||
);
|
||||
return {
|
||||
...actual,
|
||||
ensureAdapterExecutionTargetCommandResolvable,
|
||||
ensureAdapterExecutionTargetRuntimeCommandInstalled,
|
||||
prepareAdapterExecutionTargetRuntime,
|
||||
resolveAdapterExecutionTargetCommandForLogs,
|
||||
runAdapterExecutionTargetProcess,
|
||||
runAdapterExecutionTargetShellCommand,
|
||||
startAdapterExecutionTargetPaperclipBridge,
|
||||
};
|
||||
});
|
||||
|
||||
import { execute } from "./execute.js";
|
||||
|
||||
describe("codex sandbox auth precedence warning", () => {
|
||||
const cleanupDirs: string[] = [];
|
||||
|
||||
afterEach(async () => {
|
||||
vi.clearAllMocks();
|
||||
while (cleanupDirs.length > 0) {
|
||||
const dir = cleanupDirs.pop();
|
||||
if (!dir) continue;
|
||||
await fs.rm(dir, { recursive: true, force: true }).catch(() => undefined);
|
||||
}
|
||||
});
|
||||
|
||||
it("logs and emits a run event when sandbox login is shadowed by host auth", async () => {
|
||||
const root = await fs.mkdtemp(path.join(os.tmpdir(), "paperclip-codex-auth-precedence-"));
|
||||
cleanupDirs.push(root);
|
||||
const workspaceDir = path.join(root, "workspace");
|
||||
const hostCodexHome = path.join(root, "host-codex-home");
|
||||
await fs.mkdir(workspaceDir, { recursive: true });
|
||||
await fs.mkdir(hostCodexHome, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(hostCodexHome, "auth.json"),
|
||||
JSON.stringify({ OPENAI_API_KEY: "fake-host-auth" }),
|
||||
"utf8",
|
||||
);
|
||||
|
||||
const logs: Array<{ stream: "stdout" | "stderr"; chunk: string }> = [];
|
||||
const events: Array<{ eventType: string; level?: string; message?: string; payload?: Record<string, unknown> }> = [];
|
||||
|
||||
await execute({
|
||||
runId: "run-auth-precedence",
|
||||
agent: {
|
||||
id: "agent-1",
|
||||
companyId: "company-1",
|
||||
name: "CodexCoder",
|
||||
adapterType: "codex_local",
|
||||
adapterConfig: { engine: "cli" },
|
||||
},
|
||||
runtime: {
|
||||
sessionId: null,
|
||||
sessionParams: null,
|
||||
sessionDisplayId: null,
|
||||
taskKey: null,
|
||||
},
|
||||
config: {
|
||||
engine: "cli",
|
||||
command: "codex",
|
||||
cwd: workspaceDir,
|
||||
env: { CODEX_HOME: hostCodexHome },
|
||||
},
|
||||
context: {},
|
||||
executionTarget: {
|
||||
kind: "remote",
|
||||
transport: "sandbox",
|
||||
providerKey: "fixture",
|
||||
remoteCwd: "/workspace",
|
||||
runner: { execute: vi.fn() },
|
||||
},
|
||||
onLog: async (stream, chunk) => {
|
||||
logs.push({ stream, chunk });
|
||||
},
|
||||
onEvent: async (event) => {
|
||||
events.push(event);
|
||||
},
|
||||
});
|
||||
|
||||
expect(prepareAdapterExecutionTargetRuntime).toHaveBeenCalledWith(expect.objectContaining({
|
||||
assets: [expect.objectContaining({ key: "home", localDir: hostCodexHome })],
|
||||
}));
|
||||
expect(runAdapterExecutionTargetShellCommand).toHaveBeenCalledWith(
|
||||
"run-auth-precedence",
|
||||
expect.objectContaining({ kind: "remote", transport: "sandbox", remoteCwd: "/sandbox/workspace" }),
|
||||
CODEX_SANDBOX_AUTH_EXISTS_COMMAND,
|
||||
expect.objectContaining({ env: {}, timeoutSec: 5 }),
|
||||
);
|
||||
expect(logs).toContainEqual({
|
||||
stream: "stderr",
|
||||
chunk: CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING_LOG_LINE,
|
||||
});
|
||||
expect(events).toContainEqual({
|
||||
eventType: "codex.auth_precedence_warning",
|
||||
stream: "system",
|
||||
level: "warn",
|
||||
message: CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING,
|
||||
payload: {
|
||||
configuredApiKey: false,
|
||||
hostAuthJson: true,
|
||||
sandboxAuthJson: true,
|
||||
winner: "host_auth_json",
|
||||
sandboxLoginShadowed: true,
|
||||
},
|
||||
});
|
||||
expect(runAdapterExecutionTargetProcess).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
|
|
@ -17,6 +17,7 @@ import {
|
|||
resolveAdapterExecutionTargetTimeoutSec,
|
||||
resolveAdapterExecutionTargetCommandForLogs,
|
||||
runAdapterExecutionTargetProcess,
|
||||
runAdapterExecutionTargetShellCommand,
|
||||
startAdapterExecutionTargetPaperclipBridge,
|
||||
} from "@paperclipai/adapter-utils/execution-target";
|
||||
import {
|
||||
|
|
@ -54,6 +55,7 @@ import {
|
|||
isCodexUnknownSessionError,
|
||||
} from "./parse.js";
|
||||
import {
|
||||
codexHomeHasUsableAuth,
|
||||
evaluateCodexCredentialReadiness,
|
||||
isManagedCodexHomePath,
|
||||
pathExists,
|
||||
|
|
@ -65,6 +67,12 @@ import {
|
|||
writeManagedCodexMcpConfig,
|
||||
type ManagedCodexMcpGateway,
|
||||
} from "./codex-home.js";
|
||||
import {
|
||||
CODEX_SANDBOX_AUTH_EXISTS_COMMAND,
|
||||
CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING,
|
||||
CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING_LOG_LINE,
|
||||
resolveCodexAuthPrecedence,
|
||||
} from "./auth-precedence.js";
|
||||
import { prepareCodexRuntimeConfig } from "./runtime-config.js";
|
||||
import { resolveCodexDesiredSkillNames } from "./skills.js";
|
||||
import { buildCodexExecArgs } from "./codex-args.js";
|
||||
|
|
@ -270,6 +278,76 @@ function managedMcpGatewaysFromContext(context: Record<string, unknown>): Manage
|
|||
.filter((gateway): gateway is ManagedCodexMcpGateway => Boolean(gateway));
|
||||
}
|
||||
|
||||
type ResolvedExecutionTarget = ReturnType<typeof readAdapterExecutionTarget>;
|
||||
type MaybeResolvedExecutionTarget = ResolvedExecutionTarget | undefined;
|
||||
|
||||
async function sandboxCodexAuthJsonExists(input: {
|
||||
runId: string;
|
||||
target: MaybeResolvedExecutionTarget;
|
||||
cwd: string;
|
||||
}): Promise<boolean> {
|
||||
if (!input.target || input.target.kind !== "remote" || input.target.transport !== "sandbox") {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await runAdapterExecutionTargetShellCommand(
|
||||
input.runId,
|
||||
input.target,
|
||||
CODEX_SANDBOX_AUTH_EXISTS_COMMAND,
|
||||
{
|
||||
cwd: input.cwd,
|
||||
env: {},
|
||||
timeoutSec: 5,
|
||||
},
|
||||
);
|
||||
return !result.timedOut && result.exitCode === 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function emitSandboxAuthPrecedenceWarningIfNeeded(input: {
|
||||
runId: string;
|
||||
target: MaybeResolvedExecutionTarget;
|
||||
cwd: string;
|
||||
configuredApiKey: boolean;
|
||||
hostAuthJson: boolean;
|
||||
onLog: AdapterExecutionContext["onLog"];
|
||||
onEvent: AdapterExecutionContext["onEvent"];
|
||||
}): Promise<void> {
|
||||
if (!input.target || input.target.kind !== "remote" || input.target.transport !== "sandbox") {
|
||||
return;
|
||||
}
|
||||
|
||||
const sandboxAuthJson = await sandboxCodexAuthJsonExists({
|
||||
runId: input.runId,
|
||||
target: input.target,
|
||||
cwd: input.cwd,
|
||||
});
|
||||
const resolution = resolveCodexAuthPrecedence({
|
||||
configuredApiKey: input.configuredApiKey,
|
||||
hostAuthJson: input.hostAuthJson,
|
||||
sandboxAuthJson,
|
||||
});
|
||||
if (!resolution.shouldWarn) return;
|
||||
|
||||
await input.onLog("stderr", CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING_LOG_LINE);
|
||||
await input.onEvent?.({
|
||||
eventType: "codex.auth_precedence_warning",
|
||||
stream: "system",
|
||||
level: "warn",
|
||||
message: CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING,
|
||||
payload: {
|
||||
configuredApiKey: input.configuredApiKey,
|
||||
hostAuthJson: input.hostAuthJson,
|
||||
sandboxAuthJson,
|
||||
winner: resolution.winner,
|
||||
sandboxLoginShadowed: resolution.sandboxLoginShadowed,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function buildCodexTransientHandoffNote(input: {
|
||||
previousSessionId: string | null;
|
||||
fallbackMode: CodexTransientFallbackMode;
|
||||
|
|
@ -372,7 +450,7 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||
await ctx.onLog("stderr", formatCodexAcpFallbackMessage(engineSelection.fallbackReason));
|
||||
}
|
||||
|
||||
const { runId, agent, runtime, config, context, onLog, onMeta, onSpawn, authToken } = ctx;
|
||||
const { runId, agent, runtime, config, context, onLog, onMeta, onEvent, onSpawn, authToken } = ctx;
|
||||
|
||||
const promptTemplate = asString(
|
||||
config.promptTemplate,
|
||||
|
|
@ -581,6 +659,15 @@ export async function execute(ctx: AdapterExecutionContext): Promise<AdapterExec
|
|||
? preparedExecutionTargetRuntime?.assetDirs.home ??
|
||||
path.posix.join(effectiveExecutionCwd, ".paperclip-runtime", "codex", "home")
|
||||
: null;
|
||||
await emitSandboxAuthPrecedenceWarningIfNeeded({
|
||||
runId,
|
||||
target: runtimeExecutionTarget,
|
||||
cwd: effectiveExecutionCwd,
|
||||
configuredApiKey: Boolean(configuredOpenAiApiKey),
|
||||
hostAuthJson: await codexHomeHasUsableAuth(effectiveCodexHome),
|
||||
onLog,
|
||||
onEvent,
|
||||
});
|
||||
const hasExplicitApiKey =
|
||||
typeof envConfig.PAPERCLIP_API_KEY === "string" && envConfig.PAPERCLIP_API_KEY.trim().length > 0;
|
||||
const env: Record<string, string> = { ...paperclipBaseEnv };
|
||||
|
|
|
|||
|
|
@ -1,4 +1,13 @@
|
|||
export { execute, ensureCodexSkillsInjected } from "./execute.js";
|
||||
export {
|
||||
resolveCodexAuthPrecedence,
|
||||
CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING,
|
||||
CODEX_SANDBOX_AUTH_PRECEDENCE_WARNING_LOG_LINE,
|
||||
CODEX_SANDBOX_AUTH_EXISTS_COMMAND,
|
||||
type CodexAuthPrecedenceInput,
|
||||
type CodexAuthPrecedenceResolution,
|
||||
type CodexAuthPrecedenceWinner,
|
||||
} from "./auth-precedence.js";
|
||||
export * from "./acp.js";
|
||||
export { getConfigSchema } from "./config-schema.js";
|
||||
export {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ export type {
|
|||
AdapterExecutionContext,
|
||||
AdapterExecutionResult,
|
||||
AdapterInvocationMeta,
|
||||
AdapterRuntimeEvent,
|
||||
AdapterRuntimeMcpServer,
|
||||
AdapterRuntimeMcpAccess,
|
||||
AdapterModelProfileDefinition,
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ import type {
|
|||
AdapterExecutionResult,
|
||||
AdapterInvocationMeta,
|
||||
AdapterModelProfileDefinition,
|
||||
AdapterRuntimeEvent,
|
||||
AdapterRuntimeMcpAccess,
|
||||
AdapterRuntimeMcpServer,
|
||||
AdapterSessionCodec,
|
||||
|
|
@ -12851,6 +12852,19 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
|||
});
|
||||
};
|
||||
|
||||
const onAdapterEvent = async (event: AdapterRuntimeEvent) => {
|
||||
const eventType = event.eventType.trim();
|
||||
if (!eventType) return;
|
||||
await appendRunEvent(currentRun, seq++, {
|
||||
eventType: eventType.slice(0, 120),
|
||||
stream: event.stream,
|
||||
level: event.level,
|
||||
color: event.color,
|
||||
message: event.message,
|
||||
payload: event.payload,
|
||||
});
|
||||
};
|
||||
|
||||
const adapter = getServerAdapter(agent.adapterType);
|
||||
const localAgentJwtScope =
|
||||
issueRef?.workMode === "skill_test"
|
||||
|
|
@ -13089,6 +13103,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
|||
runtimeMcp,
|
||||
onLog,
|
||||
onMeta: onAdapterMeta,
|
||||
onEvent: onAdapterEvent,
|
||||
onRuntimeProgress: async (progress) => {
|
||||
await recordCurrentHeartbeatRunRuntimeProgress(run, progress, issueId);
|
||||
},
|
||||
|
|
|
|||
Loading…
Reference in New Issue