forked from farhoodlabs/paperclip
Clarify manual workspace runtime behavior
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -3,11 +3,13 @@ import type { agents } from "@paperclipai/db";
|
||||
import { sessionCodec as codexSessionCodec } from "@paperclipai/adapter-codex-local/server";
|
||||
import { resolveDefaultAgentWorkspaceDir } from "../home-paths.js";
|
||||
import {
|
||||
applyPersistedExecutionWorkspaceConfig,
|
||||
buildExplicitResumeSessionOverride,
|
||||
formatRuntimeWorkspaceWarningLog,
|
||||
prioritizeProjectWorkspaceCandidatesForRun,
|
||||
parseSessionCompactionPolicy,
|
||||
resolveRuntimeSessionParamsForWorkspace,
|
||||
stripWorkspaceRuntimeFromExecutionRunConfig,
|
||||
shouldResetTaskSessionForWake,
|
||||
type ResolvedWorkspaceForRun,
|
||||
} from "../services/heartbeat.ts";
|
||||
@@ -120,6 +122,64 @@ describe("resolveRuntimeSessionParamsForWorkspace", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("applyPersistedExecutionWorkspaceConfig", () => {
|
||||
it("does not add workspace runtime when only the project workspace had manual runtime config", () => {
|
||||
const result = applyPersistedExecutionWorkspaceConfig({
|
||||
config: {},
|
||||
workspaceConfig: null,
|
||||
mode: "isolated_workspace",
|
||||
});
|
||||
|
||||
expect("workspaceRuntime" in result).toBe(false);
|
||||
});
|
||||
|
||||
it("applies explicit persisted execution workspace runtime config when present", () => {
|
||||
const result = applyPersistedExecutionWorkspaceConfig({
|
||||
config: {},
|
||||
workspaceConfig: {
|
||||
provisionCommand: null,
|
||||
teardownCommand: null,
|
||||
cleanupCommand: null,
|
||||
desiredState: null,
|
||||
workspaceRuntime: {
|
||||
services: [{ name: "workspace-web" }],
|
||||
},
|
||||
},
|
||||
mode: "isolated_workspace",
|
||||
});
|
||||
|
||||
expect(result.workspaceRuntime).toEqual({
|
||||
services: [{ name: "workspace-web" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("stripWorkspaceRuntimeFromExecutionRunConfig", () => {
|
||||
it("removes workspace runtime before heartbeat execution", () => {
|
||||
const input = {
|
||||
cwd: "/tmp/project",
|
||||
workspaceStrategy: {
|
||||
type: "git_worktree",
|
||||
},
|
||||
workspaceRuntime: {
|
||||
services: [{ name: "web" }],
|
||||
},
|
||||
};
|
||||
|
||||
const result = stripWorkspaceRuntimeFromExecutionRunConfig(input);
|
||||
|
||||
expect(result).toEqual({
|
||||
cwd: "/tmp/project",
|
||||
workspaceStrategy: {
|
||||
type: "git_worktree",
|
||||
},
|
||||
});
|
||||
expect(input.workspaceRuntime).toEqual({
|
||||
services: [{ name: "web" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("shouldResetTaskSessionForWake", () => {
|
||||
it("resets session context on assignment wake", () => {
|
||||
expect(shouldResetTaskSessionForWake({ wakeReason: "issue_assigned" })).toBe(true);
|
||||
|
||||
+1
-10
@@ -28,7 +28,7 @@ import { createApp } from "./app.js";
|
||||
import { loadConfig } from "./config.js";
|
||||
import { logger } from "./middleware/logger.js";
|
||||
import { setupLiveEventsWebSocketServer } from "./realtime/live-events-ws.js";
|
||||
import { heartbeatService, reconcilePersistedRuntimeServicesOnStartup, restartDesiredRuntimeServicesOnStartup, routineService } from "./services/index.js";
|
||||
import { heartbeatService, reconcilePersistedRuntimeServicesOnStartup, routineService } from "./services/index.js";
|
||||
import { createStorageServiceFromConfig } from "./storage/index.js";
|
||||
import { printStartupBanner } from "./startup-banner.js";
|
||||
import { getBoardClaimWarningUrl, initializeBoardClaimChallenge } from "./board-claim.js";
|
||||
@@ -557,15 +557,6 @@ export async function startServer(): Promise<StartedServer> {
|
||||
"reconciled persisted runtime services from a previous server process",
|
||||
);
|
||||
}
|
||||
return restartDesiredRuntimeServicesOnStartup(db as any);
|
||||
})
|
||||
.then((result) => {
|
||||
if (result && result.restarted > 0) {
|
||||
logger.warn(
|
||||
{ restarted: result.restarted, failed: result.failed },
|
||||
"restarted desired workspace runtime services on startup",
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
logger.error({ err }, "startup reconciliation of persisted runtime services failed");
|
||||
|
||||
@@ -51,7 +51,6 @@ import {
|
||||
resolveExecutionWorkspaceMode,
|
||||
} from "./execution-workspace-policy.js";
|
||||
import { instanceSettingsService } from "./instance-settings.js";
|
||||
import { readProjectWorkspaceRuntimeConfig } from "./project-workspace-runtime-config.js";
|
||||
import { redactCurrentUserText, redactCurrentUserValue } from "../log-redaction.js";
|
||||
import {
|
||||
hasSessionCompactionThresholds,
|
||||
@@ -77,10 +76,9 @@ const SESSIONED_LOCAL_ADAPTERS = new Set([
|
||||
"pi_local",
|
||||
]);
|
||||
|
||||
function applyPersistedExecutionWorkspaceConfig(input: {
|
||||
export function applyPersistedExecutionWorkspaceConfig(input: {
|
||||
config: Record<string, unknown>;
|
||||
workspaceConfig: ExecutionWorkspaceConfig | null;
|
||||
projectWorkspaceRuntime: Record<string, unknown> | null;
|
||||
mode: ReturnType<typeof resolveExecutionWorkspaceMode>;
|
||||
}) {
|
||||
const nextConfig = { ...input.config };
|
||||
@@ -90,8 +88,6 @@ function applyPersistedExecutionWorkspaceConfig(input: {
|
||||
delete nextConfig.workspaceRuntime;
|
||||
} else if (input.workspaceConfig?.workspaceRuntime) {
|
||||
nextConfig.workspaceRuntime = { ...input.workspaceConfig.workspaceRuntime };
|
||||
} else if (input.projectWorkspaceRuntime) {
|
||||
nextConfig.workspaceRuntime = { ...input.projectWorkspaceRuntime };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +103,12 @@ function applyPersistedExecutionWorkspaceConfig(input: {
|
||||
return nextConfig;
|
||||
}
|
||||
|
||||
export function stripWorkspaceRuntimeFromExecutionRunConfig(config: Record<string, unknown>) {
|
||||
const nextConfig = { ...config };
|
||||
delete nextConfig.workspaceRuntime;
|
||||
return nextConfig;
|
||||
}
|
||||
|
||||
function buildExecutionWorkspaceConfigSnapshot(config: Record<string, unknown>): Partial<ExecutionWorkspaceConfig> | null {
|
||||
const strategy = parseObject(config.workspaceStrategy);
|
||||
const snapshot: Partial<ExecutionWorkspaceConfig> = {};
|
||||
@@ -2114,35 +2116,19 @@ export function heartbeatService(db: Db) {
|
||||
: null;
|
||||
const existingExecutionWorkspace =
|
||||
issueRef?.executionWorkspaceId ? await executionWorkspacesSvc.getById(issueRef.executionWorkspaceId) : null;
|
||||
const resolvedProjectWorkspace =
|
||||
resolvedWorkspace.workspaceId
|
||||
? await db
|
||||
.select({ metadata: projectWorkspaces.metadata })
|
||||
.from(projectWorkspaces)
|
||||
.where(
|
||||
and(
|
||||
eq(projectWorkspaces.id, resolvedWorkspace.workspaceId),
|
||||
eq(projectWorkspaces.companyId, agent.companyId),
|
||||
),
|
||||
)
|
||||
.then((rows) => rows[0] ?? null)
|
||||
: null;
|
||||
const projectWorkspaceRuntimeConfig = readProjectWorkspaceRuntimeConfig(
|
||||
(resolvedProjectWorkspace?.metadata as Record<string, unknown> | null) ?? null,
|
||||
);
|
||||
const persistedWorkspaceManagedConfig = applyPersistedExecutionWorkspaceConfig({
|
||||
config: workspaceManagedConfig,
|
||||
workspaceConfig: existingExecutionWorkspace?.config ?? null,
|
||||
projectWorkspaceRuntime: projectWorkspaceRuntimeConfig?.workspaceRuntime ?? null,
|
||||
mode: executionWorkspaceMode,
|
||||
});
|
||||
const mergedConfig = issueAssigneeOverrides?.adapterConfig
|
||||
? { ...persistedWorkspaceManagedConfig, ...issueAssigneeOverrides.adapterConfig }
|
||||
: persistedWorkspaceManagedConfig;
|
||||
const configSnapshot = buildExecutionWorkspaceConfigSnapshot(mergedConfig);
|
||||
const executionRunConfig = stripWorkspaceRuntimeFromExecutionRunConfig(mergedConfig);
|
||||
const { config: resolvedConfig, secretKeys } = await secretsSvc.resolveAdapterConfigForRuntime(
|
||||
agent.companyId,
|
||||
mergedConfig,
|
||||
executionRunConfig,
|
||||
);
|
||||
const runtimeSkillEntries = await companySkills.listRuntimeSkillEntries(agent.companyId);
|
||||
const runtimeConfig = {
|
||||
|
||||
Reference in New Issue
Block a user