Update files
This commit is contained in:
@@ -9,6 +9,7 @@ import { AgentManager } from "./agent-manager.js";
|
|||||||
import { AgentStorage } from "./agent-storage.js";
|
import { AgentStorage } from "./agent-storage.js";
|
||||||
import type {
|
import type {
|
||||||
AgentClient,
|
AgentClient,
|
||||||
|
AgentPersistenceHandle,
|
||||||
AgentRunResult,
|
AgentRunResult,
|
||||||
AgentSession,
|
AgentSession,
|
||||||
AgentSessionConfig,
|
AgentSessionConfig,
|
||||||
@@ -149,6 +150,99 @@ describe("AgentManager", () => {
|
|||||||
).rejects.toThrow("Working directory does not exist");
|
).rejects.toThrow("Working directory does not exist");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("resumeAgent keeps metadata config and applies systemPrompt/mcpServers overrides", async () => {
|
||||||
|
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-resume-"));
|
||||||
|
const storagePath = join(workdir, "agents");
|
||||||
|
const storage = new AgentStorage(storagePath, logger);
|
||||||
|
|
||||||
|
class ResumeCaptureClient implements AgentClient {
|
||||||
|
readonly provider = "codex" as const;
|
||||||
|
readonly capabilities = TEST_CAPABILITIES;
|
||||||
|
lastResumeOverrides: Partial<AgentSessionConfig> | undefined;
|
||||||
|
|
||||||
|
async isAvailable(): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createSession(config: AgentSessionConfig): Promise<AgentSession> {
|
||||||
|
return new TestAgentSession(config);
|
||||||
|
}
|
||||||
|
|
||||||
|
async resumeSession(
|
||||||
|
handle: AgentPersistenceHandle,
|
||||||
|
overrides?: Partial<AgentSessionConfig>
|
||||||
|
): Promise<AgentSession> {
|
||||||
|
this.lastResumeOverrides = overrides;
|
||||||
|
const metadata = (handle.metadata ?? {}) as Partial<AgentSessionConfig>;
|
||||||
|
const merged: AgentSessionConfig = {
|
||||||
|
...metadata,
|
||||||
|
...overrides,
|
||||||
|
provider: "codex",
|
||||||
|
cwd: overrides?.cwd ?? metadata.cwd ?? process.cwd(),
|
||||||
|
};
|
||||||
|
return new TestAgentSession(merged);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = new ResumeCaptureClient();
|
||||||
|
const manager = new AgentManager({
|
||||||
|
clients: {
|
||||||
|
codex: client,
|
||||||
|
},
|
||||||
|
registry: storage,
|
||||||
|
logger,
|
||||||
|
idFactory: () => "00000000-0000-4000-8000-000000000106",
|
||||||
|
});
|
||||||
|
|
||||||
|
const handle: AgentPersistenceHandle = {
|
||||||
|
provider: "codex",
|
||||||
|
sessionId: "resume-session-1",
|
||||||
|
metadata: {
|
||||||
|
provider: "codex",
|
||||||
|
cwd: workdir,
|
||||||
|
systemPrompt: "old prompt",
|
||||||
|
mcpServers: {
|
||||||
|
legacy: {
|
||||||
|
type: "stdio",
|
||||||
|
command: "legacy-bridge",
|
||||||
|
args: ["/tmp/legacy.sock"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const resumed = await manager.resumeAgent(handle, {
|
||||||
|
cwd: workdir,
|
||||||
|
systemPrompt: "new prompt",
|
||||||
|
mcpServers: {
|
||||||
|
paseo: {
|
||||||
|
type: "stdio",
|
||||||
|
command: "node",
|
||||||
|
args: ["/tmp/mcp-bridge.mjs", "--socket", "/tmp/paseo.sock"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(resumed.config.systemPrompt).toBe("new prompt");
|
||||||
|
expect(resumed.config.mcpServers).toEqual({
|
||||||
|
paseo: {
|
||||||
|
type: "stdio",
|
||||||
|
command: "node",
|
||||||
|
args: ["/tmp/mcp-bridge.mjs", "--socket", "/tmp/paseo.sock"],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(client.lastResumeOverrides).toMatchObject({
|
||||||
|
systemPrompt: "new prompt",
|
||||||
|
mcpServers: {
|
||||||
|
paseo: {
|
||||||
|
type: "stdio",
|
||||||
|
command: "node",
|
||||||
|
args: ["/tmp/mcp-bridge.mjs", "--socket", "/tmp/paseo.sock"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("createAgent fails when generated agent ID is not a UUID", async () => {
|
test("createAgent fails when generated agent ID is not a UUID", 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");
|
||||||
|
|||||||
@@ -129,6 +129,9 @@ function buildSerializableConfig(
|
|||||||
if (extra !== undefined) {
|
if (extra !== undefined) {
|
||||||
serializable.extra = extra;
|
serializable.extra = extra;
|
||||||
}
|
}
|
||||||
|
if (config.systemPrompt) {
|
||||||
|
serializable.systemPrompt = config.systemPrompt;
|
||||||
|
}
|
||||||
if (config.mcpServers) {
|
if (config.mcpServers) {
|
||||||
serializable.mcpServers = config.mcpServers;
|
serializable.mcpServers = config.mcpServers;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ function createManagedAgent(
|
|||||||
modeId: configOverrides.modeId ?? "plan",
|
modeId: configOverrides.modeId ?? "plan",
|
||||||
model: configOverrides.model ?? "gpt-5.1",
|
model: configOverrides.model ?? "gpt-5.1",
|
||||||
extra: configOverrides.extra ?? { claude: { maxThinkingTokens: 1024 } },
|
extra: configOverrides.extra ?? { claude: { maxThinkingTokens: 1024 } },
|
||||||
|
systemPrompt: configOverrides.systemPrompt,
|
||||||
mcpServers: configOverrides.mcpServers,
|
mcpServers: configOverrides.mcpServers,
|
||||||
};
|
};
|
||||||
const session =
|
const session =
|
||||||
@@ -119,6 +120,7 @@ describe("AgentStorage", () => {
|
|||||||
config: {
|
config: {
|
||||||
modeId: "coding",
|
modeId: "coding",
|
||||||
model: "gpt-5.1",
|
model: "gpt-5.1",
|
||||||
|
systemPrompt: "Be terse and explicit.",
|
||||||
extra: { claude: { maxThinkingTokens: 1024 } },
|
extra: { claude: { maxThinkingTokens: 1024 } },
|
||||||
mcpServers: {
|
mcpServers: {
|
||||||
paseo: {
|
paseo: {
|
||||||
@@ -137,6 +139,7 @@ describe("AgentStorage", () => {
|
|||||||
expect(record.provider).toBe("claude");
|
expect(record.provider).toBe("claude");
|
||||||
expect(record.config?.modeId).toBe("coding");
|
expect(record.config?.modeId).toBe("coding");
|
||||||
expect(record.config?.model).toBe("gpt-5.1");
|
expect(record.config?.model).toBe("gpt-5.1");
|
||||||
|
expect(record.config?.systemPrompt).toBe("Be terse and explicit.");
|
||||||
expect(record.config?.mcpServers).toEqual({
|
expect(record.config?.mcpServers).toEqual({
|
||||||
paseo: {
|
paseo: {
|
||||||
type: "stdio",
|
type: "stdio",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const SERIALIZABLE_CONFIG_SCHEMA = z
|
|||||||
model: z.string().nullable().optional(),
|
model: z.string().nullable().optional(),
|
||||||
thinkingOptionId: z.string().nullable().optional(),
|
thinkingOptionId: z.string().nullable().optional(),
|
||||||
extra: z.record(z.any()).nullable().optional(),
|
extra: z.record(z.any()).nullable().optional(),
|
||||||
|
systemPrompt: z.string().nullable().optional(),
|
||||||
mcpServers: z.record(z.any()).nullable().optional(),
|
mcpServers: z.record(z.any()).nullable().optional(),
|
||||||
})
|
})
|
||||||
.nullable()
|
.nullable()
|
||||||
@@ -62,7 +63,7 @@ const STORED_AGENT_SCHEMA = z.object({
|
|||||||
|
|
||||||
export type SerializableAgentConfig = Pick<
|
export type SerializableAgentConfig = Pick<
|
||||||
AgentSessionConfig,
|
AgentSessionConfig,
|
||||||
"modeId" | "model" | "thinkingOptionId" | "extra" | "mcpServers"
|
"modeId" | "model" | "thinkingOptionId" | "extra" | "systemPrompt" | "mcpServers"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export type StoredAgentRecord = z.infer<typeof STORED_AGENT_SCHEMA>;
|
export type StoredAgentRecord = z.infer<typeof STORED_AGENT_SCHEMA>;
|
||||||
|
|||||||
@@ -270,6 +270,9 @@ function coerceSessionMetadata(metadata: AgentMetadata | undefined): Partial<Age
|
|||||||
result.extra = extra;
|
result.extra = extra;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (typeof metadata.systemPrompt === "string") {
|
||||||
|
result.systemPrompt = metadata.systemPrompt;
|
||||||
|
}
|
||||||
if (isMcpServersRecord(metadata.mcpServers)) {
|
if (isMcpServersRecord(metadata.mcpServers)) {
|
||||||
result.mcpServers = metadata.mcpServers;
|
result.mcpServers = metadata.mcpServers;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1597,6 +1597,9 @@ class CodexAppServerAgentSession implements AgentSession {
|
|||||||
modeId: this.currentMode,
|
modeId: this.currentMode,
|
||||||
model: this.config.model ?? null,
|
model: this.config.model ?? null,
|
||||||
thinkingOptionId,
|
thinkingOptionId,
|
||||||
|
extra: this.config.extra,
|
||||||
|
systemPrompt: this.config.systemPrompt,
|
||||||
|
mcpServers: this.config.mcpServers,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1961,6 +1961,7 @@ const AgentSessionConfigSchema = z
|
|||||||
networkAccess: z.boolean().optional(),
|
networkAccess: z.boolean().optional(),
|
||||||
webSearch: z.boolean().optional(),
|
webSearch: z.boolean().optional(),
|
||||||
extra: AgentSessionExtraSchema.optional(),
|
extra: AgentSessionExtraSchema.optional(),
|
||||||
|
systemPrompt: z.string().optional(),
|
||||||
mcpServers: z.record(McpServerConfigSchema).optional(),
|
mcpServers: z.record(McpServerConfigSchema).optional(),
|
||||||
})
|
})
|
||||||
.passthrough();
|
.passthrough();
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import type { ManagedAgent } from "./agent/agent-manager.js";
|
|||||||
import type { StoredAgentRecord } from "./agent/agent-storage.js";
|
import type { StoredAgentRecord } from "./agent/agent-storage.js";
|
||||||
import {
|
import {
|
||||||
attachAgentStoragePersistence,
|
attachAgentStoragePersistence,
|
||||||
|
buildConfigOverrides,
|
||||||
|
buildSessionConfig,
|
||||||
} from "./persistence-hooks.js";
|
} from "./persistence-hooks.js";
|
||||||
import type {
|
import type {
|
||||||
AgentPermissionRequest,
|
AgentPermissionRequest,
|
||||||
@@ -140,4 +142,72 @@ describe("persistence hooks", () => {
|
|||||||
});
|
});
|
||||||
expect(applySnapshot).toHaveBeenCalledTimes(1);
|
expect(applySnapshot).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("buildConfigOverrides carries systemPrompt and mcpServers", () => {
|
||||||
|
const record = createRecord({
|
||||||
|
title: "Voice agent",
|
||||||
|
config: {
|
||||||
|
modeId: "default",
|
||||||
|
model: "gpt-5.1-codex-mini",
|
||||||
|
thinkingOptionId: "minimal",
|
||||||
|
systemPrompt: "Use speak first.",
|
||||||
|
mcpServers: {
|
||||||
|
paseo: {
|
||||||
|
type: "stdio",
|
||||||
|
command: "node",
|
||||||
|
args: ["/tmp/bridge.mjs", "--socket", "/tmp/agent.sock"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(buildConfigOverrides(record)).toMatchObject({
|
||||||
|
cwd: "/tmp/project",
|
||||||
|
modeId: "plan",
|
||||||
|
model: "gpt-5.1-codex-mini",
|
||||||
|
thinkingOptionId: "minimal",
|
||||||
|
title: "Voice agent",
|
||||||
|
systemPrompt: "Use speak first.",
|
||||||
|
mcpServers: {
|
||||||
|
paseo: {
|
||||||
|
type: "stdio",
|
||||||
|
command: "node",
|
||||||
|
args: ["/tmp/bridge.mjs", "--socket", "/tmp/agent.sock"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("buildSessionConfig includes persisted systemPrompt and mcpServers", () => {
|
||||||
|
const record = createRecord({
|
||||||
|
provider: "codex",
|
||||||
|
config: {
|
||||||
|
modeId: "default",
|
||||||
|
model: "gpt-5.1-codex-mini",
|
||||||
|
systemPrompt: "Confirm and speak first.",
|
||||||
|
mcpServers: {
|
||||||
|
paseo: {
|
||||||
|
type: "stdio",
|
||||||
|
command: "node",
|
||||||
|
args: ["/tmp/bridge.mjs", "--socket", "/tmp/agent.sock"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(buildSessionConfig(record)).toMatchObject({
|
||||||
|
provider: "codex",
|
||||||
|
cwd: "/tmp/project",
|
||||||
|
modeId: "plan",
|
||||||
|
model: "gpt-5.1-codex-mini",
|
||||||
|
systemPrompt: "Confirm and speak first.",
|
||||||
|
mcpServers: {
|
||||||
|
paseo: {
|
||||||
|
type: "stdio",
|
||||||
|
command: "node",
|
||||||
|
args: ["/tmp/bridge.mjs", "--socket", "/tmp/agent.sock"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ export function buildConfigOverrides(
|
|||||||
thinkingOptionId: record.config?.thinkingOptionId ?? undefined,
|
thinkingOptionId: record.config?.thinkingOptionId ?? undefined,
|
||||||
title: record.title ?? undefined,
|
title: record.title ?? undefined,
|
||||||
extra: record.config?.extra ?? undefined,
|
extra: record.config?.extra ?? undefined,
|
||||||
|
systemPrompt: record.config?.systemPrompt ?? undefined,
|
||||||
mcpServers: record.config?.mcpServers ?? undefined,
|
mcpServers: record.config?.mcpServers ?? undefined,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -75,6 +76,7 @@ export function buildSessionConfig(
|
|||||||
thinkingOptionId: overrides.thinkingOptionId,
|
thinkingOptionId: overrides.thinkingOptionId,
|
||||||
title: overrides.title,
|
title: overrides.title,
|
||||||
extra: overrides.extra,
|
extra: overrides.extra,
|
||||||
|
systemPrompt: overrides.systemPrompt,
|
||||||
mcpServers: overrides.mcpServers,
|
mcpServers: overrides.mcpServers,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,7 @@ const AgentSessionConfigSchema = z.object({
|
|||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
.optional(),
|
.optional(),
|
||||||
|
systemPrompt: z.string().optional(),
|
||||||
mcpServers: z.record(McpServerConfigSchema).optional(),
|
mcpServers: z.record(McpServerConfigSchema).optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user