mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-01 22:23:07 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
abf9a6ff77 | ||
|
|
ae153721c8 |
@@ -135,6 +135,7 @@ export async function ensureAgentLoaded(
|
|||||||
labels: record.labels,
|
labels: record.labels,
|
||||||
workspaceId: record.workspaceId,
|
workspaceId: record.workspaceId,
|
||||||
owner: record.owner,
|
owner: record.owner,
|
||||||
|
env: record.launchEnv,
|
||||||
});
|
});
|
||||||
deps.logger.info({ agentId, provider: record.provider }, "Agent created from stored config");
|
deps.logger.info({ agentId, provider: record.provider }, "Agent created from stored config");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2869,6 +2869,134 @@ test("createAgent passes daemon launch env through the provider launch context",
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test.each([
|
||||||
|
{ provider: "codex", hasHandle: true },
|
||||||
|
{ provider: "opencode", hasHandle: true },
|
||||||
|
{ provider: "codex", hasHandle: false },
|
||||||
|
{ provider: "opencode", hasHandle: false },
|
||||||
|
])(
|
||||||
|
"$provider preserves explicit launch env across reloads and storage-backed resumes (handle: $hasHandle)",
|
||||||
|
async ({ provider, hasHandle }) => {
|
||||||
|
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-launch-env-"));
|
||||||
|
const storagePath = join(workdir, "agents");
|
||||||
|
class LaunchEnvClient extends TestAgentClient {
|
||||||
|
lastLaunchContext: AgentLaunchContext | undefined;
|
||||||
|
|
||||||
|
override async createSession(
|
||||||
|
config: AgentSessionConfig,
|
||||||
|
context?: AgentLaunchContext,
|
||||||
|
): Promise<AgentSession> {
|
||||||
|
this.lastLaunchContext = context;
|
||||||
|
const session = await super.createSession(config);
|
||||||
|
session.describePersistence = () => ({ provider, sessionId: session.id });
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
override async resumeSession(
|
||||||
|
handle: AgentPersistenceHandle,
|
||||||
|
config?: Partial<AgentSessionConfig>,
|
||||||
|
context?: AgentLaunchContext,
|
||||||
|
): Promise<AgentSession> {
|
||||||
|
this.lastLaunchContext = context;
|
||||||
|
const session = await super.resumeSession(handle, config, context);
|
||||||
|
session.describePersistence = () => ({ provider, sessionId: handle.sessionId });
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new LaunchEnvClient(provider);
|
||||||
|
let storage = new AgentStorage(storagePath, logger);
|
||||||
|
let manager = new AgentManager({ clients: { [provider]: client }, registry: storage, logger });
|
||||||
|
const env = { XDG_DATA_HOME: join(workdir, "agent-data"), CUSTOM_EMPTY: "" };
|
||||||
|
const expectedEnv = { ...env };
|
||||||
|
const agent = await manager.createAgent({ provider, cwd: workdir }, undefined, {
|
||||||
|
workspaceId: undefined,
|
||||||
|
env,
|
||||||
|
});
|
||||||
|
onTestFinished(async () => {
|
||||||
|
await manager.closeAgent(agent.id);
|
||||||
|
await manager.flush();
|
||||||
|
rmSync(workdir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
env.XDG_DATA_HOME = "caller-mutated";
|
||||||
|
|
||||||
|
await manager.closeAgent(agent.id);
|
||||||
|
await manager.flush();
|
||||||
|
|
||||||
|
if (!hasHandle) {
|
||||||
|
const record = await storage.get(agent.id);
|
||||||
|
if (!record) throw new Error("Created agent was not persisted");
|
||||||
|
await storage.upsert({ ...record, persistence: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fresh storage and manager instances must read the environment from disk.
|
||||||
|
for (let restart = 0; restart < 2; restart += 1) {
|
||||||
|
storage = new AgentStorage(storagePath, logger);
|
||||||
|
manager = new AgentManager({ clients: { [provider]: client }, registry: storage, logger });
|
||||||
|
const resumed = await ensureAgentLoaded(agent.id, {
|
||||||
|
agentManager: manager,
|
||||||
|
agentStorage: storage,
|
||||||
|
logger,
|
||||||
|
});
|
||||||
|
expect(client.lastLaunchContext).toEqual({
|
||||||
|
agentId: agent.id,
|
||||||
|
env: { ...expectedEnv, PASEO_AGENT_ID: agent.id, PASEO_AGENT_CWD: workdir },
|
||||||
|
});
|
||||||
|
await manager.reloadAgentSession(agent.id);
|
||||||
|
expect(client.lastLaunchContext).toEqual({
|
||||||
|
agentId: agent.id,
|
||||||
|
env: { ...expectedEnv, PASEO_AGENT_ID: agent.id, PASEO_AGENT_CWD: workdir },
|
||||||
|
});
|
||||||
|
expect(toAgentPayload(resumed)).not.toHaveProperty("launchEnv");
|
||||||
|
expect(toAgentPayload(resumed).persistence?.metadata).not.toHaveProperty("launchEnv");
|
||||||
|
await manager.closeAgent(agent.id);
|
||||||
|
await manager.flush();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
test("session-open hooks override persisted launch env without persisting per-opening values", async () => {
|
||||||
|
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-launch-env-hook-"));
|
||||||
|
const storage = new AgentStorage(join(workdir, "agents"), logger);
|
||||||
|
const observedEnv: Array<Record<string, string> | undefined> = [];
|
||||||
|
const manager = new AgentManager({
|
||||||
|
clients: { codex: new TestAgentClient() },
|
||||||
|
registry: storage,
|
||||||
|
logger,
|
||||||
|
pluginLifecycle: {
|
||||||
|
emit() {},
|
||||||
|
async before(name, request) {
|
||||||
|
if (name === "agent.create" && "env" in request) {
|
||||||
|
request.env = { ...request.env, FROM_CREATE_HOOK: "saved" };
|
||||||
|
}
|
||||||
|
if (name === "agent.session_open" && "env" in request) {
|
||||||
|
observedEnv.push({ ...request.env });
|
||||||
|
request.env = { ...request.env, XDG_DATA_HOME: "current-hook-data" };
|
||||||
|
}
|
||||||
|
return request;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const agent = await manager.createAgent({ provider: "codex", cwd: workdir }, undefined, {
|
||||||
|
workspaceId: undefined,
|
||||||
|
env: { XDG_DATA_HOME: "original-data" },
|
||||||
|
});
|
||||||
|
onTestFinished(async () => {
|
||||||
|
await manager.closeAgent(agent.id);
|
||||||
|
await manager.flush();
|
||||||
|
rmSync(workdir, { recursive: true, force: true });
|
||||||
|
});
|
||||||
|
await manager.reloadAgentSession(agent.id);
|
||||||
|
expect(observedEnv).toEqual([
|
||||||
|
{ XDG_DATA_HOME: "original-data", FROM_CREATE_HOOK: "saved" },
|
||||||
|
{ XDG_DATA_HOME: "original-data", FROM_CREATE_HOOK: "saved" },
|
||||||
|
]);
|
||||||
|
expect((await storage.get(agent.id))?.launchEnv).toEqual({
|
||||||
|
XDG_DATA_HOME: "original-data",
|
||||||
|
FROM_CREATE_HOOK: "saved",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("createAgent passes persistSession to provider create options", async () => {
|
test("createAgent passes persistSession to provider create options", async () => {
|
||||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
|
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
|
||||||
const storagePath = join(workdir, "agents");
|
const storagePath = join(workdir, "agents");
|
||||||
|
|||||||
@@ -414,6 +414,8 @@ interface ManagedAgentBase {
|
|||||||
owner?: AgentOwner;
|
owner?: AgentOwner;
|
||||||
capabilities: AgentCapabilityFlags;
|
capabilities: AgentCapabilityFlags;
|
||||||
config: AgentSessionConfig;
|
config: AgentSessionConfig;
|
||||||
|
/** Explicit launch overrides, before per-opening hooks and daemon-owned variables. */
|
||||||
|
launchEnv?: Record<string, string>;
|
||||||
runtimeInfo?: AgentRuntimeInfo;
|
runtimeInfo?: AgentRuntimeInfo;
|
||||||
createdAt: Date;
|
createdAt: Date;
|
||||||
updatedAt: Date;
|
updatedAt: Date;
|
||||||
@@ -1323,6 +1325,7 @@ export class AgentManager {
|
|||||||
initialTitle: options.initialTitle,
|
initialTitle: options.initialTitle,
|
||||||
workspaceId: options.workspaceId,
|
workspaceId: options.workspaceId,
|
||||||
owner: options.owner,
|
owner: options.owner,
|
||||||
|
launchEnv: options.env ? { ...options.env } : undefined,
|
||||||
historyPrimed: true,
|
historyPrimed: true,
|
||||||
});
|
});
|
||||||
if (!agent.internal) {
|
if (!agent.internal) {
|
||||||
@@ -1414,7 +1417,7 @@ export class AgentManager {
|
|||||||
const { storedConfig, launchConfig, paseoToolPolicy } = await this.prepareSessionConfig(
|
const { storedConfig, launchConfig, paseoToolPolicy } = await this.prepareSessionConfig(
|
||||||
mergedConfig,
|
mergedConfig,
|
||||||
resolvedAgentId,
|
resolvedAgentId,
|
||||||
{ purpose },
|
{ purpose, env: record?.launchEnv },
|
||||||
);
|
);
|
||||||
const client = this.requireClient(handle.provider);
|
const client = this.requireClient(handle.provider);
|
||||||
const available = await client.isAvailable();
|
const available = await client.isAvailable();
|
||||||
@@ -1429,7 +1432,7 @@ export class AgentManager {
|
|||||||
client,
|
client,
|
||||||
storedConfig.cwd,
|
storedConfig.cwd,
|
||||||
paseoToolPolicy,
|
paseoToolPolicy,
|
||||||
undefined,
|
record?.launchEnv,
|
||||||
{
|
{
|
||||||
reason: "resume",
|
reason: "resume",
|
||||||
purpose,
|
purpose,
|
||||||
@@ -1446,6 +1449,7 @@ export class AgentManager {
|
|||||||
await this.requireExternalMcpSupport(session, storedConfig);
|
await this.requireExternalMcpSupport(session, storedConfig);
|
||||||
return this.registerSession(session, storedConfig, resolvedAgentId, {
|
return this.registerSession(session, storedConfig, resolvedAgentId, {
|
||||||
...options,
|
...options,
|
||||||
|
launchEnv: record?.launchEnv,
|
||||||
persistence: handle,
|
persistence: handle,
|
||||||
restoring: true,
|
restoring: true,
|
||||||
});
|
});
|
||||||
@@ -1577,6 +1581,7 @@ export class AgentManager {
|
|||||||
const { storedConfig, launchConfig, paseoToolPolicy } = await this.prepareSessionConfig(
|
const { storedConfig, launchConfig, paseoToolPolicy } = await this.prepareSessionConfig(
|
||||||
refreshConfig,
|
refreshConfig,
|
||||||
agentId,
|
agentId,
|
||||||
|
{ env: existing.launchEnv },
|
||||||
);
|
);
|
||||||
const hadPreviousPaseoToolPolicy = this.paseoToolPolicies.has(agentId);
|
const hadPreviousPaseoToolPolicy = this.paseoToolPolicies.has(agentId);
|
||||||
const previousPaseoToolPolicy = this.paseoToolPolicies.get(agentId);
|
const previousPaseoToolPolicy = this.paseoToolPolicies.get(agentId);
|
||||||
@@ -1585,7 +1590,7 @@ export class AgentManager {
|
|||||||
client,
|
client,
|
||||||
storedConfig.cwd,
|
storedConfig.cwd,
|
||||||
paseoToolPolicy,
|
paseoToolPolicy,
|
||||||
undefined,
|
existing.launchEnv,
|
||||||
{ reason: "refresh", purpose: "interactive", workspaceId: existing.workspaceId },
|
{ reason: "refresh", purpose: "interactive", workspaceId: existing.workspaceId },
|
||||||
);
|
);
|
||||||
const providerLaunchConfig = this.resolveProviderLaunchConfig(launchConfig, launchContext);
|
const providerLaunchConfig = this.resolveProviderLaunchConfig(launchConfig, launchContext);
|
||||||
@@ -1630,6 +1635,7 @@ export class AgentManager {
|
|||||||
labels: existing.labels,
|
labels: existing.labels,
|
||||||
workspaceId: existing.workspaceId,
|
workspaceId: existing.workspaceId,
|
||||||
owner: existing.owner,
|
owner: existing.owner,
|
||||||
|
launchEnv: existing.launchEnv,
|
||||||
createdAt: existing.createdAt,
|
createdAt: existing.createdAt,
|
||||||
updatedAt: existing.updatedAt,
|
updatedAt: existing.updatedAt,
|
||||||
lastUserMessageAt: existing.lastUserMessageAt,
|
lastUserMessageAt: existing.lastUserMessageAt,
|
||||||
@@ -3668,6 +3674,7 @@ export class AgentManager {
|
|||||||
publishWhenReady?: boolean;
|
publishWhenReady?: boolean;
|
||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
owner?: AgentOwner;
|
owner?: AgentOwner;
|
||||||
|
launchEnv?: Record<string, string>;
|
||||||
},
|
},
|
||||||
): Promise<ManagedAgent> {
|
): Promise<ManagedAgent> {
|
||||||
let registered = false;
|
let registered = false;
|
||||||
@@ -3846,6 +3853,7 @@ export class AgentManager {
|
|||||||
persistence?: AgentPersistenceHandle;
|
persistence?: AgentPersistenceHandle;
|
||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
owner?: AgentOwner;
|
owner?: AgentOwner;
|
||||||
|
launchEnv?: Record<string, string>;
|
||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
}): ActiveManagedAgent {
|
}): ActiveManagedAgent {
|
||||||
@@ -3856,6 +3864,7 @@ export class AgentManager {
|
|||||||
cwd: config.cwd,
|
cwd: config.cwd,
|
||||||
workspaceId: options?.workspaceId,
|
workspaceId: options?.workspaceId,
|
||||||
owner: options?.owner,
|
owner: options?.owner,
|
||||||
|
launchEnv: options?.launchEnv,
|
||||||
session,
|
session,
|
||||||
capabilities: session.capabilities,
|
capabilities: session.capabilities,
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -95,6 +95,7 @@ export function toStoredAgentRecord(
|
|||||||
lastStatus: agent.lifecycle,
|
lastStatus: agent.lifecycle,
|
||||||
lastModeId: agent.currentModeId ?? config?.modeId ?? null,
|
lastModeId: agent.currentModeId ?? config?.modeId ?? null,
|
||||||
config: config ?? null,
|
config: config ?? null,
|
||||||
|
launchEnv: agent.launchEnv ? { ...agent.launchEnv } : undefined,
|
||||||
runtimeInfo,
|
runtimeInfo,
|
||||||
features: normalizeFeatures(agent.features),
|
features: normalizeFeatures(agent.features),
|
||||||
persistence,
|
persistence,
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ const STORED_AGENT_SCHEMA = z.object({
|
|||||||
lastStatus: AgentStatusSchema.default("closed"),
|
lastStatus: AgentStatusSchema.default("closed"),
|
||||||
lastModeId: z.string().nullable().optional(),
|
lastModeId: z.string().nullable().optional(),
|
||||||
config: SERIALIZABLE_CONFIG_SCHEMA,
|
config: SERIALIZABLE_CONFIG_SCHEMA,
|
||||||
|
launchEnv: z.record(z.string(), z.string()).optional(),
|
||||||
runtimeInfo: z
|
runtimeInfo: z
|
||||||
.object({
|
.object({
|
||||||
provider: z.string(),
|
provider: z.string(),
|
||||||
|
|||||||
Reference in New Issue
Block a user