mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-21 16:52:40 +00:00
fix(cli): support mode when importing agents
This commit is contained in:
@@ -46,6 +46,11 @@ describe("canonical CLI surface", () => {
|
||||
expect(help).toContain("--forge <forge>");
|
||||
});
|
||||
|
||||
it("offers provider mode selection when importing an agent", () => {
|
||||
const importCommand = createCli().commands.find((command) => command.name() === "import");
|
||||
expect(importCommand?.helpInformation()).toContain("--mode <mode>");
|
||||
});
|
||||
|
||||
it("uses background for execution and reserves detach for ownership", () => {
|
||||
const run = createCli().commands.find((command) => command.name() === "run");
|
||||
expect(run?.helpInformation()).toContain("--background");
|
||||
|
||||
@@ -57,4 +57,32 @@ describe("resolveImportCwd", () => {
|
||||
});
|
||||
expect(result.data.provider).toBe("pi");
|
||||
});
|
||||
|
||||
it("passes the requested mode to the import request", async () => {
|
||||
importAgent.mockResolvedValueOnce({
|
||||
id: "agent-2",
|
||||
status: "idle",
|
||||
provider: "claude",
|
||||
cwd: "/tmp/project",
|
||||
title: "Imported Claude session",
|
||||
});
|
||||
|
||||
await runImportCommand(
|
||||
"claude-session-1",
|
||||
{
|
||||
daemonTarget: { kind: "endpoint", host: "example.test:12345" },
|
||||
provider: "claude",
|
||||
cwd: "/tmp/project",
|
||||
mode: "auto",
|
||||
},
|
||||
{} as never,
|
||||
);
|
||||
|
||||
expect(importAgent).toHaveBeenCalledWith({
|
||||
provider: "claude",
|
||||
sessionId: "claude-session-1",
|
||||
cwd: "/tmp/project",
|
||||
modeId: "auto",
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ export function addImportOptions(cmd: Command): Command {
|
||||
.argument("<id>", "Provider session/thread ID to import")
|
||||
.requiredOption("--provider <provider>", "Agent provider id")
|
||||
.option("--cwd <path>", "Working directory for providers that require it")
|
||||
.option("--mode <mode>", "Provider-specific mode (e.g., plan, default, bypass)")
|
||||
.option(
|
||||
"--label <key=value>",
|
||||
"Add label(s) to the agent (can be used multiple times)",
|
||||
@@ -22,6 +23,7 @@ export function addImportOptions(cmd: Command): Command {
|
||||
export interface AgentImportOptions extends CommandOptions {
|
||||
provider?: string;
|
||||
cwd?: string;
|
||||
mode?: string;
|
||||
label?: string[];
|
||||
host?: string;
|
||||
}
|
||||
@@ -119,6 +121,7 @@ export async function runImportCommand(
|
||||
provider,
|
||||
sessionId,
|
||||
cwd,
|
||||
...(options.mode ? { modeId: options.mode } : {}),
|
||||
...(Object.keys(labels).length > 0 ? { labels } : {}),
|
||||
});
|
||||
|
||||
|
||||
@@ -4717,6 +4717,7 @@ test("imports an agent by provider handle id", async () => {
|
||||
providerId: "custom-codex",
|
||||
providerHandleId: "thread-1",
|
||||
cwd: "/tmp/repo",
|
||||
modeId: "full-access",
|
||||
});
|
||||
|
||||
expect(mock.sent).toHaveLength(1);
|
||||
@@ -4729,6 +4730,7 @@ test("imports an agent by provider handle id", async () => {
|
||||
providerHandleId?: string;
|
||||
sessionId?: string;
|
||||
cwd?: string;
|
||||
modeId?: string;
|
||||
};
|
||||
};
|
||||
expect(request.message).toMatchObject({
|
||||
@@ -4736,6 +4738,7 @@ test("imports an agent by provider handle id", async () => {
|
||||
providerId: "custom-codex",
|
||||
providerHandleId: "thread-1",
|
||||
cwd: "/tmp/repo",
|
||||
modeId: "full-access",
|
||||
});
|
||||
expect(request.message).not.toHaveProperty("sessionId");
|
||||
|
||||
|
||||
@@ -192,6 +192,7 @@ interface ImportAgentInputBase {
|
||||
cwd?: string;
|
||||
workspaceId?: string;
|
||||
labels?: Record<string, string>;
|
||||
modeId?: string;
|
||||
}
|
||||
|
||||
export type ImportAgentInput =
|
||||
@@ -2969,6 +2970,7 @@ export class DaemonClient {
|
||||
...(input.cwd ? { cwd: input.cwd } : {}),
|
||||
...(input.workspaceId ? { workspaceId: input.workspaceId } : {}),
|
||||
...(input.labels && Object.keys(input.labels).length > 0 ? { labels: input.labels } : {}),
|
||||
...(input.modeId ? { modeId: input.modeId } : {}),
|
||||
});
|
||||
|
||||
const status = await this.sendRequest({
|
||||
|
||||
@@ -1763,6 +1763,7 @@ export const ImportAgentRequestMessageSchema = z.object({
|
||||
cwd: z.string().optional(),
|
||||
workspaceId: z.string().optional(),
|
||||
labels: z.record(z.string(), z.string()).optional(),
|
||||
modeId: z.string().optional(),
|
||||
requestId: z.string(),
|
||||
});
|
||||
|
||||
|
||||
@@ -411,6 +411,7 @@ describe("workspace message schemas", () => {
|
||||
providerId: "custom-codex",
|
||||
providerHandleId: "thread-1",
|
||||
cwd: "/tmp/repo",
|
||||
modeId: "full-access",
|
||||
});
|
||||
const legacyRequest = SessionInboundMessageSchema.parse({
|
||||
type: "import_agent_request",
|
||||
@@ -426,6 +427,7 @@ describe("workspace message schemas", () => {
|
||||
providerId: "custom-codex",
|
||||
providerHandleId: "thread-1",
|
||||
cwd: "/tmp/repo",
|
||||
modeId: "full-access",
|
||||
});
|
||||
expect(legacyRequest).toEqual({
|
||||
type: "import_agent_request",
|
||||
|
||||
@@ -4071,6 +4071,7 @@ test("importProviderSession imports the selected session without listing and pub
|
||||
class ImportClient extends TestAgentClient {
|
||||
listCalls = 0;
|
||||
importInput: unknown = null;
|
||||
importConfig: AgentSessionConfig | null = null;
|
||||
importLaunchContext: AgentLaunchContext | undefined;
|
||||
|
||||
async listImportableSessions() {
|
||||
@@ -4080,10 +4081,11 @@ test("importProviderSession imports the selected session without listing and pub
|
||||
|
||||
async importSession(input: ImportProviderSessionInput, context: ImportProviderSessionContext) {
|
||||
this.importInput = input;
|
||||
this.importConfig = context.storedConfig;
|
||||
this.importLaunchContext = context.launchContext;
|
||||
return {
|
||||
session,
|
||||
config: { provider: "codex" as const, cwd: workdir },
|
||||
config: context.storedConfig,
|
||||
persistence: {
|
||||
provider: "codex" as const,
|
||||
sessionId: input.providerHandleId,
|
||||
@@ -4156,10 +4158,12 @@ test("importProviderSession imports the selected session without listing and pub
|
||||
providerHandleId: "thread-selected",
|
||||
cwd: workdir,
|
||||
workspaceId: "ws-imported",
|
||||
modeId: "full-access",
|
||||
});
|
||||
|
||||
expect(client.listCalls).toBe(0);
|
||||
expect(client.importInput).toEqual({ providerHandleId: "thread-selected", cwd: workdir });
|
||||
expect(client.importConfig?.modeId).toBe("full-access");
|
||||
expect(client.importLaunchContext).toEqual({
|
||||
agentId: imported.id,
|
||||
env: {
|
||||
@@ -4168,6 +4172,7 @@ test("importProviderSession imports the selected session without listing and pub
|
||||
},
|
||||
});
|
||||
expect(imported.lifecycle).toBe("idle");
|
||||
expect(imported.config.modeId).toBe("full-access");
|
||||
expect(imported.historyPrimed).toBe(true);
|
||||
expect(manager.getTimeline(imported.id)).toEqual([
|
||||
{ type: "user_message", text: "Trace provider imports" },
|
||||
@@ -8146,6 +8151,7 @@ test("unarchiveSnapshot unarchives native provider storage before clearing archi
|
||||
const unarchived = await manager.unarchiveSnapshot(agent.id, {
|
||||
workspaceId: "ws-restored",
|
||||
labels: { [PARENT_AGENT_ID_LABEL]: null, source: "reimport" },
|
||||
modeId: "full-access",
|
||||
});
|
||||
const stored = await storage.get(agent.id);
|
||||
|
||||
@@ -8156,6 +8162,7 @@ test("unarchiveSnapshot unarchives native provider storage before clearing archi
|
||||
expect(stored?.archivedAt).toBeNull();
|
||||
expect(stored?.workspaceId).toBe("ws-restored");
|
||||
expect(stored?.labels).toEqual({ retained: "yes", source: "reimport" });
|
||||
expect(stored?.config?.modeId).toBe("full-access");
|
||||
});
|
||||
|
||||
test("unarchiveSnapshotByHandle unarchives native provider storage for the matched snapshot", async () => {
|
||||
|
||||
@@ -1357,6 +1357,7 @@ export class AgentManager {
|
||||
cwd: string;
|
||||
workspaceId: string;
|
||||
labels?: Record<string, string>;
|
||||
modeId?: string;
|
||||
}): Promise<ManagedAgent> {
|
||||
return this.trackAgentRegistrationOperation(this.importProviderSessionInternal(input));
|
||||
}
|
||||
@@ -1367,6 +1368,7 @@ export class AgentManager {
|
||||
cwd: string;
|
||||
workspaceId: string;
|
||||
labels?: Record<string, string>;
|
||||
modeId?: string;
|
||||
}): Promise<ManagedAgent> {
|
||||
this.assertAcceptingAgentRegistrations();
|
||||
const resolvedAgentId = validateAgentId(this.idFactory(), "importProviderSession");
|
||||
@@ -1381,6 +1383,7 @@ export class AgentManager {
|
||||
{
|
||||
provider: input.provider,
|
||||
cwd: input.cwd,
|
||||
modeId: input.modeId,
|
||||
},
|
||||
resolvedAgentId,
|
||||
);
|
||||
@@ -2143,7 +2146,7 @@ export class AgentManager {
|
||||
|
||||
async unarchiveSnapshot(
|
||||
agentId: string,
|
||||
updates?: { workspaceId?: string; labels?: AgentLabelPatch },
|
||||
updates?: { workspaceId?: string; labels?: AgentLabelPatch; modeId?: string },
|
||||
): Promise<boolean> {
|
||||
const registry = this.requireRegistry();
|
||||
const record = await registry.get(agentId);
|
||||
@@ -2159,6 +2162,7 @@ export class AgentManager {
|
||||
...record,
|
||||
...(updates?.workspaceId ? { workspaceId: updates.workspaceId } : {}),
|
||||
...(updates?.labels ? { labels: applyLabelPatch(record.labels, updates.labels) } : {}),
|
||||
...(updates?.modeId ? { config: { ...record.config, modeId: updates.modeId } } : {}),
|
||||
archivedAt: null,
|
||||
updatedAt: new Date().toISOString(),
|
||||
});
|
||||
|
||||
@@ -198,7 +198,7 @@ export async function unarchiveAgentState(
|
||||
_agentStorage: AgentStorage,
|
||||
agentManager: AgentUnarchiveController,
|
||||
agentId: string,
|
||||
updates?: { workspaceId?: string; labels?: Record<string, string | null> },
|
||||
updates?: { workspaceId?: string; labels?: Record<string, string | null>; modeId?: string },
|
||||
): Promise<boolean> {
|
||||
const unarchived = await agentManager.unarchiveSnapshot(agentId, updates);
|
||||
if (!unarchived) return false;
|
||||
|
||||
@@ -558,11 +558,13 @@ test("normalizeImportAgentRequest accepts new and legacy import handle shapes",
|
||||
requestId: "new-shape",
|
||||
providerId: "custom-codex",
|
||||
providerHandleId: "thread-1",
|
||||
modeId: "full-access",
|
||||
}),
|
||||
).toEqual({
|
||||
requestId: "new-shape",
|
||||
provider: "custom-codex",
|
||||
providerHandleId: "thread-1",
|
||||
modeId: "full-access",
|
||||
});
|
||||
|
||||
expect(
|
||||
@@ -633,7 +635,11 @@ class ProviderImportHarness {
|
||||
},
|
||||
unarchiveSnapshot: async (
|
||||
agentId: string,
|
||||
updates?: { workspaceId?: string; labels?: Record<string, string | null> },
|
||||
updates?: {
|
||||
workspaceId?: string;
|
||||
labels?: Record<string, string | null>;
|
||||
modeId?: string;
|
||||
},
|
||||
) => {
|
||||
if (this.unarchiveWait) {
|
||||
await this.unarchiveWait;
|
||||
@@ -654,6 +660,7 @@ class ProviderImportHarness {
|
||||
...record,
|
||||
workspaceId: updates?.workspaceId ?? record.workspaceId,
|
||||
labels,
|
||||
config: updates?.modeId ? { ...record.config, modeId: updates.modeId } : record.config,
|
||||
archivedAt: null,
|
||||
});
|
||||
return true;
|
||||
@@ -735,7 +742,12 @@ class ProviderImportHarness {
|
||||
};
|
||||
}
|
||||
|
||||
import(input: { providerHandleId: string; cwd?: string; labels?: Record<string, string> }) {
|
||||
import(input: {
|
||||
providerHandleId: string;
|
||||
cwd?: string;
|
||||
labels?: Record<string, string>;
|
||||
modeId?: string;
|
||||
}) {
|
||||
return importProviderSession({
|
||||
request: {
|
||||
requestId: "import-thread",
|
||||
@@ -743,6 +755,7 @@ class ProviderImportHarness {
|
||||
providerHandleId: input.providerHandleId,
|
||||
cwd: input.cwd,
|
||||
labels: input.labels,
|
||||
modeId: input.modeId,
|
||||
},
|
||||
workspaceProvisioning: createImportWorkspace("ws-restored"),
|
||||
agentManager: this.manager,
|
||||
@@ -752,7 +765,7 @@ class ProviderImportHarness {
|
||||
}
|
||||
}
|
||||
|
||||
test("importProviderSession uses the provider import path with the requested labels", async () => {
|
||||
test("importProviderSession uses the provider import path with the requested labels and mode", async () => {
|
||||
const harness = await ProviderImportHarness.create();
|
||||
harness.timeline = [
|
||||
{ type: "user_message", text: "Trace recent provider sessions" },
|
||||
@@ -763,6 +776,7 @@ test("importProviderSession uses the provider import path with the requested lab
|
||||
providerHandleId: "thread-imported",
|
||||
cwd: "/tmp/imported-agent",
|
||||
labels: { source: "import" },
|
||||
modeId: "full-access",
|
||||
});
|
||||
|
||||
expect(harness.freshImports).toEqual([
|
||||
@@ -772,6 +786,7 @@ test("importProviderSession uses the provider import path with the requested lab
|
||||
cwd: "/tmp/imported-agent",
|
||||
workspaceId: "ws-restored",
|
||||
labels: { source: "import" },
|
||||
modeId: "full-access",
|
||||
},
|
||||
]);
|
||||
expect(result).toEqual({
|
||||
@@ -813,6 +828,7 @@ test("importProviderSession restores an archived session as the same standalone
|
||||
providerHandleId: "thread-archived",
|
||||
cwd: harness.snapshot.cwd,
|
||||
labels: { source: "reimport" },
|
||||
modeId: "full-access",
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@@ -824,6 +840,7 @@ test("importProviderSession restores an archived session as the same standalone
|
||||
id: harness.snapshot.id,
|
||||
workspaceId: "ws-restored",
|
||||
labels: { existing: "label", source: "reimport" },
|
||||
config: { modeId: "full-access" },
|
||||
archivedAt: null,
|
||||
});
|
||||
expect((await harness.storage.get(harness.snapshot.id))?.labels).not.toHaveProperty(
|
||||
|
||||
@@ -48,6 +48,7 @@ export interface NormalizedImportAgentRequest {
|
||||
cwd?: string;
|
||||
workspaceId?: string;
|
||||
labels?: Record<string, string>;
|
||||
modeId?: string;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
@@ -112,6 +113,7 @@ export function normalizeImportAgentRequest(
|
||||
cwd: msg.cwd,
|
||||
workspaceId: msg.workspaceId,
|
||||
labels: msg.labels,
|
||||
...(msg.modeId ? { modeId: msg.modeId } : {}),
|
||||
requestId: msg.requestId,
|
||||
};
|
||||
}
|
||||
@@ -230,6 +232,7 @@ async function importProviderSessionNow(
|
||||
await unarchiveAgentState(input.agentStorage, input.agentManager, archivedRecord.id, {
|
||||
workspaceId,
|
||||
labels: Object.keys(labelPatch).length > 0 ? labelPatch : undefined,
|
||||
modeId: input.request.modeId,
|
||||
});
|
||||
try {
|
||||
const snapshot = await ensureAgentLoaded(archivedRecord.id, {
|
||||
@@ -253,6 +256,7 @@ async function importProviderSessionNow(
|
||||
cwd,
|
||||
workspaceId,
|
||||
labels,
|
||||
...(input.request.modeId ? { modeId: input.request.modeId } : {}),
|
||||
});
|
||||
await unarchiveAgentState(input.agentStorage, input.agentManager, snapshot.id);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user