mirror of
https://github.com/getpaseo/paseo.git
synced 2026-11-06 16:34:36 +00:00
fix(server): give agents their workspace id as PASEO_WORKSPACE_ID
Agents got PASEO_AGENT_ID and PASEO_AGENT_CWD but not their workspace id, so an agent couldn't rename or archive its own workspace, or name it to create_agent, when several workspaces share its cwd. The launch env now sets PASEO_WORKSPACE_ID whenever the agent has a workspace, including on resume after a daemon restart, where the id now comes from the stored record. OpenCode treats it as a per-session key, so it doesn't force a dedicated server. Refs #5098 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
committed by
paseo-bot[bot]
co-authored by
Claude Opus 5.5
parent
471db4bdb5
commit
005ffbffd5
@@ -3286,6 +3286,71 @@ test.each([
|
||||
},
|
||||
);
|
||||
|
||||
test("createAgent and resume give the agent its workspace id", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-workspace-env-"));
|
||||
const storagePath = join(workdir, "agents");
|
||||
const storage = new AgentStorage(storagePath, logger);
|
||||
|
||||
class CaptureClient extends TestAgentClient {
|
||||
createLaunchContext: AgentLaunchContext | undefined;
|
||||
resumeLaunchContext: AgentLaunchContext | undefined;
|
||||
|
||||
override async createSession(
|
||||
config: AgentSessionConfig,
|
||||
launchContext?: AgentLaunchContext,
|
||||
): Promise<AgentSession> {
|
||||
this.createLaunchContext = launchContext;
|
||||
return new TestAgentSession(config);
|
||||
}
|
||||
|
||||
override async resumeSession(
|
||||
handle: AgentPersistenceHandle,
|
||||
overrides?: Partial<AgentSessionConfig>,
|
||||
launchContext?: AgentLaunchContext,
|
||||
): Promise<AgentSession> {
|
||||
this.resumeLaunchContext = launchContext;
|
||||
const metadata = (handle.metadata ?? {}) as Partial<AgentSessionConfig>;
|
||||
return new TestAgentSession({ ...metadata, ...overrides, provider: "codex", cwd: workdir });
|
||||
}
|
||||
}
|
||||
|
||||
const client = new CaptureClient();
|
||||
const manager = new AgentManager({
|
||||
clients: {
|
||||
codex: client,
|
||||
},
|
||||
registry: storage,
|
||||
logger,
|
||||
idFactory: () => "00000000-0000-4000-8000-000000000140",
|
||||
});
|
||||
|
||||
const snapshot = await manager.createAgent({ provider: "codex", cwd: workdir }, undefined, {
|
||||
workspaceId: "ws-agent-home",
|
||||
});
|
||||
expect(client.createLaunchContext?.env).toEqual({
|
||||
PASEO_AGENT_ID: snapshot.id,
|
||||
PASEO_AGENT_CWD: workdir,
|
||||
PASEO_WORKSPACE_ID: "ws-agent-home",
|
||||
});
|
||||
|
||||
// A daemon restart resumes from the stored record, without a workspace id in the call.
|
||||
await manager.closeAgent(snapshot.id);
|
||||
await manager.resumeAgentFromPersistence(
|
||||
{
|
||||
provider: "codex",
|
||||
sessionId: "workspace-env-session",
|
||||
metadata: { provider: "codex", cwd: workdir },
|
||||
},
|
||||
{ cwd: workdir },
|
||||
snapshot.id,
|
||||
);
|
||||
expect(client.resumeLaunchContext?.env).toEqual({
|
||||
PASEO_AGENT_ID: snapshot.id,
|
||||
PASEO_AGENT_CWD: workdir,
|
||||
PASEO_WORKSPACE_ID: "ws-agent-home",
|
||||
});
|
||||
});
|
||||
|
||||
test("createAgent passes persistSession to provider create options", async () => {
|
||||
const workdir = mkdtempSync(join(tmpdir(), "agent-manager-test-"));
|
||||
const storagePath = join(workdir, "agents");
|
||||
@@ -4731,6 +4796,7 @@ test("importProviderSession imports the selected session without listing and pub
|
||||
env: {
|
||||
PASEO_AGENT_ID: imported.id,
|
||||
PASEO_AGENT_CWD: workdir,
|
||||
PASEO_WORKSPACE_ID: "ws-imported",
|
||||
},
|
||||
});
|
||||
expect(imported.lifecycle).toBe("idle");
|
||||
|
||||
@@ -664,6 +664,36 @@ function createAbortError(signal: AbortSignal | undefined, fallbackMessage: stri
|
||||
return Object.assign(new Error(message), { name: "AbortError" });
|
||||
}
|
||||
|
||||
// PASEO_WORKSPACE_ID lets an agent rename or archive its own workspace; several workspaces can
|
||||
// share one cwd, so the agent can't work it out from PASEO_AGENT_CWD.
|
||||
function agentIdentityEnv({
|
||||
agentId,
|
||||
cwd,
|
||||
opening,
|
||||
}: {
|
||||
agentId: string;
|
||||
cwd: string;
|
||||
opening: { workspaceId?: string | null } | undefined;
|
||||
}): Record<string, string> {
|
||||
const workspaceId = opening?.workspaceId;
|
||||
return {
|
||||
PASEO_AGENT_ID: agentId,
|
||||
PASEO_AGENT_CWD: cwd,
|
||||
...(workspaceId ? { PASEO_WORKSPACE_ID: workspaceId } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
// A restart resumes from the stored record, with no workspace id in the call.
|
||||
function resolveResumeWorkspaceId({
|
||||
options,
|
||||
record,
|
||||
}: {
|
||||
options: { workspaceId?: string } | undefined;
|
||||
record: Pick<StoredAgentRecord, "workspaceId"> | null;
|
||||
}): string | null {
|
||||
return options?.workspaceId ?? record?.workspaceId ?? null;
|
||||
}
|
||||
|
||||
function validateAgentId(agentId: string, source: string): string {
|
||||
const result = AgentIdSchema.safeParse(agentId);
|
||||
if (!result.success) {
|
||||
@@ -1593,7 +1623,7 @@ export class AgentManager {
|
||||
{
|
||||
reason: "resume",
|
||||
purpose,
|
||||
workspaceId: options?.workspaceId ?? null,
|
||||
workspaceId: resolveResumeWorkspaceId({ options, record }),
|
||||
title: record?.title ?? mergedConfig.title ?? null,
|
||||
labels: record?.labels ?? options?.labels ?? {},
|
||||
},
|
||||
@@ -5774,8 +5804,7 @@ export class AgentManager {
|
||||
mcpServerOwners,
|
||||
env: {
|
||||
...env,
|
||||
PASEO_AGENT_ID: agentId,
|
||||
PASEO_AGENT_CWD: cwd,
|
||||
...agentIdentityEnv({ agentId, cwd, opening }),
|
||||
},
|
||||
};
|
||||
const useNativeTools =
|
||||
|
||||
@@ -349,7 +349,11 @@ type OpenCodeAgentConfig = Omit<AgentSessionConfig, "providerOptions"> & {
|
||||
providerOptions: OpenCodeProviderOptions;
|
||||
};
|
||||
|
||||
const OPENCODE_SESSION_ENV_KEYS = new Set(["PASEO_AGENT_ID", "PASEO_AGENT_CWD"]);
|
||||
const OPENCODE_SESSION_ENV_KEYS = new Set([
|
||||
"PASEO_AGENT_ID",
|
||||
"PASEO_AGENT_CWD",
|
||||
"PASEO_WORKSPACE_ID",
|
||||
]);
|
||||
|
||||
function requiresDedicatedOpenCodeServer(
|
||||
config: OpenCodeAgentConfig,
|
||||
|
||||
@@ -24,7 +24,14 @@ describe("OpenCode v2 helper isolation", () => {
|
||||
expect(
|
||||
requiresDedicatedV2Server(
|
||||
{ provider: "opencode", cwd: "/tmp/project" },
|
||||
{ agentId: "agent", env: { PASEO_AGENT_ID: "agent", PASEO_AGENT_CWD: "/tmp/project" } },
|
||||
{
|
||||
agentId: "agent",
|
||||
env: {
|
||||
PASEO_AGENT_ID: "agent",
|
||||
PASEO_AGENT_CWD: "/tmp/project",
|
||||
PASEO_WORKSPACE_ID: "ws-1",
|
||||
},
|
||||
},
|
||||
),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
@@ -72,7 +72,7 @@ export async function applyResumeOverrides(
|
||||
// it must not force a dedicated server. Custom MCP is location-scoped, and any
|
||||
// other env key belongs to the process, so both need isolation. Mirrors the v1
|
||||
// adapter's `requiresDedicatedOpenCodeServer`.
|
||||
const V2_SESSION_ENV_KEYS = new Set(["PASEO_AGENT_ID", "PASEO_AGENT_CWD"]);
|
||||
const V2_SESSION_ENV_KEYS = new Set(["PASEO_AGENT_ID", "PASEO_AGENT_CWD", "PASEO_WORKSPACE_ID"]);
|
||||
|
||||
export function requiresDedicatedV2Server(
|
||||
config: AgentSessionConfig,
|
||||
|
||||
@@ -211,6 +211,8 @@ paseo workspace rename <workspace-id> --reset # back to the branch or director
|
||||
paseo workspace archive <workspace-id>
|
||||
```
|
||||
|
||||
Inside an agent session, `PASEO_WORKSPACE_ID` holds the agent's own workspace ID, so an agent can run `paseo workspace rename "$PASEO_WORKSPACE_ID" "Auth rework"` or `paseo workspace archive "$PASEO_WORKSPACE_ID"` even when other workspaces share its directory.
|
||||
|
||||
Add `--forge <name>` to PR checkout when Paseo cannot infer the forge from the source checkout. See [Git worktrees](/docs/worktrees) for setup hooks and services.
|
||||
|
||||
## Terminals
|
||||
|
||||
@@ -202,6 +202,8 @@ paseo workspace rename <workspace-id> --reset # back to the branch or director
|
||||
paseo workspace archive <workspace-id>
|
||||
```
|
||||
|
||||
在 Agent 会话中,`PASEO_WORKSPACE_ID` 保存该 Agent 自己的工作区 ID。即使其他工作区共享同一个目录,Agent 也可以运行 `paseo workspace rename "$PASEO_WORKSPACE_ID" "Auth rework"` 或 `paseo workspace archive "$PASEO_WORKSPACE_ID"`。
|
||||
|
||||
当 Paseo 无法从源检出推断代码托管平台时,为 PR 检出添加 `--forge <name>`。setup 钩子和服务见 [Git worktree](/docs/worktrees)。
|
||||
|
||||
## 终端 {#terminals}
|
||||
|
||||
@@ -598,7 +598,7 @@ Creation request
|
||||
→ resolve defaults and validate provider configuration
|
||||
→ derive launch configuration with Paseo runtime tools and daemon prompt
|
||||
→ agent.open.before hooks (same ordering; env and plugin-local tools and MCP servers)
|
||||
→ set PASEO_AGENT_ID and PASEO_AGENT_CWD
|
||||
→ set PASEO_AGENT_ID, PASEO_AGENT_CWD, and PASEO_WORKSPACE_ID when the agent has a workspace
|
||||
→ open provider session and save agent configuration
|
||||
```
|
||||
|
||||
|
||||
@@ -581,7 +581,7 @@ Creation request
|
||||
→ resolve defaults and validate provider configuration
|
||||
→ derive launch configuration with Paseo runtime tools and daemon prompt
|
||||
→ agent.open.before hooks (same ordering; env and plugin-local tools and MCP servers)
|
||||
→ set PASEO_AGENT_ID and PASEO_AGENT_CWD
|
||||
→ set PASEO_AGENT_ID, PASEO_AGENT_CWD, and PASEO_WORKSPACE_ID when the agent has a workspace
|
||||
→ open provider session and save agent configuration
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user