Repository navigation
Conversation
🦋 Changeset detectedLatest commit: 475f54f The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
@codex review |
There was a problem hiding this comment.
Copilot was unable to review this pull request because the user who requested the review has insufficient remaining quota.
An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b1772e652c
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (!isDangerousCommandGuardEnabled(this.config)) return undefined; | ||
| const cwd = this.workspace.workDir; | ||
| const environmentId = context.environmentId; | ||
| const cwd = environmentId === undefined ? this.workspace.workDir : this.environment.workspaceRoots(environmentId).workDir; |
There was a problem hiding this comment.
Connect the routed environment before resolving its cwd
When a secondary remote omits defaultCwd, its cwd is intentionally unavailable until the first connection, but a routed Bash call reaches this permission policy before BashTool.execution() performs that lazy connection. With the default-enabled dangerous-command guard, workspaceRoots(environmentId) therefore throws environment.unavailable for ordinary first calls such as pwd, so the documented fallback to the target-reported cwd never runs. Acquire/connect the target before resolving its workspace root, or defer the lookup until a ready lease exists.
Useful? React with 👍 / 👎.
| private async capture(path: string, turnId: number): Promise<void> { | ||
| const pathKey = this.pathKey(path); | ||
| private async capture(path: string, turnId: number, environmentId?: string): Promise<void> { | ||
| const pathKey = environmentId === undefined ? this.pathKey(path) : `${environmentId}:${path}`; |
There was a problem hiding this comment.
Preserve target case semantics in file-history keys
When the primary environment is Windows and a routed POSIX environment contains distinct files such as /repo/Foo.ts and /repo/foo.ts, both qualified keys are stored here, but contentAt() later passes them through pathKey(), which derives case-insensitivity from the primary environment and returns the first case-insensitive tracked match. Requesting the second path through the file-history API can therefore return the first file's backup, showing incorrect before/after content in desktop or web clients; qualified paths need exact lookup or normalization based on the target environment.
AGENTS.md reference: AGENTS.md:L123-L125
Useful? React with 👍 / 👎.
| const planTarget = await this.planService?.resolveFile(args.path); | ||
| async resolveExecution(args: WriteInput, context?: ToolResolutionContext): Promise<ToolExecution> { | ||
| const environmentId = context?.environmentId; | ||
| const planTarget = environmentId === undefined ? await this.planService?.resolveFile(args.path) : undefined; |
There was a problem hiding this comment.
Keep routed writes behind the plan-file guard
When plan mode is active and the model includes environment on a Write or Edit targeting the advertised plan-file path, this condition skips resolveFile(). The routed tool retains the same absolute path string, while the plan guard checks only that string against the local plan path and calls allow(), so the operation writes or edits that path on the secondary filesystem instead of updating the local plan and does so without plan approval. Plan-file operations must remain local, or the guard must include the execution environment when authorizing them.
AGENTS.md reference: AGENTS.md:L131-L133
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c241a7c635
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| private entryKey(path: string, environmentId: string | undefined): string { | ||
| return environmentId === undefined ? this.pathKey(path) : `${environmentId}:${path}`; |
There was a problem hiding this comment.
Use collision-free file-history keys
When valid environment IDs contain : or /, this concatenation can map distinct environment/path pairs to the same key; for example, environment a with path /b:/x and environment a:/b with path /x both produce a:/b:/x. If both files are edited in one turn, capture() treats the second as already tracked, silently omitting its backup and causing the history API to return content or changes for the wrong target. Encode the pair unambiguously, such as with a tuple or length prefix.
AGENTS.md reference: AGENTS.md:L131-L133
Useful? React with 👍 / 👎.
| const byEnvironment = s.environmentSessionApprovalRulePatterns ?? {}; | ||
| const patterns = byEnvironment[e.environmentId] ?? []; | ||
| if (patterns.includes(pattern)) return; |
There was a problem hiding this comment.
Avoid prototype lookups for environment approval buckets
When a configured environment uses an accepted identifier such as __proto__, constructor, or toString, this lookup returns an inherited object or function rather than the fallback array, so the subsequent patterns.includes(...) throws while recording an “allow for this session” approval. The routed operation has already been approved, but its session approval is not persisted and later calls unexpectedly prompt again; use an own-property lookup or a null-prototype map for these buckets.
AGENTS.md reference: AGENTS.md:L131-L133
Useful? React with 👍 / 👎.
| } | ||
| const lines = targets.map((target) => { | ||
| const label = target.primary ? `\`${target.environmentId}\` (primary)` : `\`${target.environmentId}\``; | ||
| const cwd = target.primary ? environment.workspaceRoots().workDir : target.cwd; |
There was a problem hiding this comment.
Refresh lazily resolved cwd in the environment roster
When a secondary environment omits defaultCwd, target.cwd remains undefined even after the first routed call has connected and workspaceRoots(target.environmentId) can resolve the host-reported cwd. Consequently every later reminder still says the directory will be resolved on first use, and because its disclosure never changes no corrected roster is injected, leaving the model with stale environment guidance. Read the connected target's workspace root when available and retain this fallback only before connection.
AGENTS.md reference: AGENTS.md:L119-L121
Useful? React with 👍 / 👎.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b0f13a560d
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| const current = await this.readCurrent(pathKey, before.environmentId); | ||
| if (current === 'unreadable') continue; |
There was a problem hiding this comment.
Mark disconnected remote history as incomplete
When a routed write succeeds but the target disconnects before TurnEnded, readCurrent() returns unreadable here and the path is omitted from the end checkpoint. readChanges() then enumerates only end-checkpoint entries while turnRecorded() still returns true, so desktop and web file-history consumers silently receive a complete-looking record with the edited remote file missing. Preserve an explicit unknown entry or mark the turn unrecorded when a tracked target cannot be read.
AGENTS.md reference: AGENTS.md:L131-L133
Useful? React with 👍 / 👎.
| if (!resolvedCwds.has(environmentId) && environment.isAvailable([], environmentId)) { | ||
| try { | ||
| resolvedCwds.set(environmentId, environment.workspaceRoots(environmentId).workDir); |
There was a problem hiding this comment.
Refresh cached cwd after a secondary reconnects
When a declaration without defaultCwd reconnects the same environment ID to a different host—for example after its launcher configuration is hot-reloaded—this condition never calls workspaceRoots() again because the ID is already cached. The tools use the new target-reported cwd, but the roster and its disclosure retain the old cwd and can direct the model to incorrect absolute paths. The first-connect case from the earlier review is fixed, but this newly added permanent cache is fresh evidence of a remaining stale-roster case; refresh the cached value whenever the target is ready while retaining the last value only during disconnects.
AGENTS.md reference: AGENTS.md:L119-L121
Useful? React with 👍 / 👎.
| async resolveExecution(args: GlobInput, context?: ToolResolutionContext): Promise<ToolExecution> { | ||
| const environmentId = context?.environment?.environmentId; | ||
| const primary = context?.environment?.primary !== false; | ||
| const resolutionLease = await acquireOrWhenReady(this.environment, ['fs'], environmentId); |
There was a problem hiding this comment.
Describe the routed target's path semantics
When the primary and routed target use different path semantics—for example a POSIX remote primary with the local Windows environment as a secondary—this call resolves and executes against the requested target, but Glob.description is still derived solely from the primary environment. A routed local-Windows call therefore omits the Windows note explaining that results use backslashes and must be converted before passing them to Git Bash; the inverse topology incorrectly advertises Windows behavior for a POSIX remote. Include each target's path semantics in the environment guidance or make the tool description valid for every advertised target.
AGENTS.md reference: AGENTS.md:L119-L121
Useful? React with 👍 / 👎.
| const secondary = [LOCAL_ENVIRONMENT_ID, ...declared.keys()] | ||
| .filter((environmentId) => environmentId !== current.environmentId && this.resolver.current(environmentId) !== undefined) | ||
| .toSorted((left, right) => left.localeCompare(right)) |
There was a problem hiding this comment.
Exclude declarations that failed registration
When a configured declaration ID conflicts with an environment already registered by another provider or temporary SDK connection, the remote provider records a registration error and leaves the pre-existing environment in the registry. This filter checks only that some environment currently has the ID, so the declaration is still advertised and calls are routed to that unrelated environment using the declaration's defaultCwd; a model can consequently read or modify the wrong host instead of failing the call. Exclude IDs with declarations.registrationError(environmentId) or otherwise verify that the registered target belongs to the declaration.
AGENTS.md reference: AGENTS.md:L131-L133
Useful? React with 👍 / 👎.
| private entryKey(path: string, environmentId: string | undefined): string { | ||
| return environmentId === undefined ? this.pathKey(path) : JSON.stringify([environmentId, path]); |
There was a problem hiding this comment.
Namespace secondary file-history keys
The JSON tuple removes the earlier collision between two environment/path pairs, but it can still equal a valid primary path key. For example, remote environment gpu with path /x produces ["gpu","/x"], which is also the relative key for a primary file at /workspace/["gpu","/x"]; capturing both in one turn makes the second appear already tracked and silently associates history with only one file. This is fresh evidence that the current encoding is not collision-free across the primary and secondary namespaces; prefix or structurally separate every key, including primary entries.
AGENTS.md reference: AGENTS.md:L131-L133
Useful? React with 👍 / 👎.
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubCodex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback". |
commit: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4c390ccc7e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| name: 'Bash', | ||
| domain: 'os/backends', | ||
| requiredEnvironmentCapabilities: ['process'], | ||
| environmentScoped: true, |
There was a problem hiding this comment.
Keep Bash guidance accurate for routed shells
When the primary and routed target use different shells, this exposes Bash for calls that execute on the secondary, but the BashTool.description getter still names the primary shell while execution uses the selected target's shellPath. The model can therefore generate commands for one shell that fail or behave differently on the routed target. Keep the guidance shell-neutral or make it valid for every advertised target.
AGENTS.md reference: AGENTS.md:L119-L121
Useful? React with 👍 / 👎.
Model the primary as a full environment target: tools and git policies read workspace roots from the environment service for every target, and only primary-only features check the target role. Persisted records keep omitting the environment id for the primary. File history now exposes the environment as a structured field on its REST routes, judges routed captures against its own primary, and never connects an environment while recording; an unreachable environment only skips its own files.
…r cwd Encode file-history keys for non-primary environments as an unambiguous tuple so environment ids containing ':' or '/' cannot collide, look up per-environment approval buckets by own property so ids such as '__proto__' keep their approvals, and let the environment roster report a working directory resolved on first connection without reverting on disconnect.
Once a secondary environment connects, the session opens its workdir view once: the roster lists the AGENTS.md files found there and its project skills join the session catalog as <skill>@<environment>. Skill activations outside the primary environment name their owning environment, and views of non-local environments no longer merge local workspace registry dirs that happen to share a path.
4c390cc to
4dffeea
Compare
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4dffeea860
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| this.catalog.set(`environment:${environmentId}`, { skills }, { priority: SKILL_SOURCE_PRIORITY.workspace }); | ||
| } | ||
| const primaryPaths = new Set(this.instructions.agentsMdPaths ?? []); | ||
| const agentsMdPaths = (workdir.instructions.snapshot.agentsMdPaths ?? []).filter((path) => !primaryPaths.has(path)); |
There was a problem hiding this comment.
Preserve same-path remote AGENTS.md entries
When the primary and secondary environments use the same absolute layout, such as /workspace/AGENTS.md on both the local host and a container, this string-only deduplication removes the secondary file even though it is a different file on a different filesystem. Because the mount does not inject its contents and the roster only asks the model to read returned paths, the model never learns about the secondary environment's instructions before editing it; keep these paths environment-qualified rather than deduplicating them against the primary.
AGENTS.md reference: AGENTS.md:L131-L133
Useful? React with 👍 / 👎.
| createDecorator<ISessionEnvironmentMounts>('sessionEnvironmentMounts'); | ||
|
|
||
| export function environmentSkillName(name: string, environmentId: string): string { | ||
| return `${name}@${environmentId}`; |
There was a problem hiding this comment.
Make environment-qualified skill names collision-free
When the primary already has a skill named deploy@gpu and environment gpu contributes deploy, both resolve to the same catalog name; distinct (skill, environment) pairs can also collide because both components may contain @. Mounted contributions are merged with replacement enabled, so connecting the secondary silently replaces the primary skill and later activation loads instructions from the wrong host; use an unambiguous namespace or reject collisions.
AGENTS.md reference: AGENTS.md:L131-L133
Useful? React with 👍 / 👎.
| environmentId, | ||
| })); | ||
| if (skills.length > 0) { | ||
| this.catalog.set(`environment:${environmentId}`, { skills }, { priority: SKILL_SOURCE_PRIORITY.workspace }); |
There was a problem hiding this comment.
Remove skills when their environment disappears
When a mounted remote declaration is deleted during a configuration reload, the provider unregisters that environment and the roster reports its removal, but this catalog contribution is never removed. Consequently Skill and slash-command listings continue advertising name@environment, and activating it loads stale instructions from a host that tools can no longer target; remove the contribution when its environment leaves the target roster.
AGENTS.md reference: AGENTS.md:L131-L133
Useful? React with 👍 / 👎.
| cwd?: string | undefined; | ||
| description?: string | undefined; | ||
| language?: 'bash' | undefined; | ||
| environment?: string | undefined; |
There was a problem hiding this comment.
Add environment to the Kap display schema
When a consumer parses a routed command or file_io display through the exported ToolInputDisplaySchema, the parallel schema in packages/kap-server/src/protocol/display.ts omits this property, so Zod strips it and the schema's inferred type does not expose it. Kap consumers consequently cannot distinguish the routed target from the primary environment even though the core display contract now promises that information; update both corresponding protocol variants alongside this type.
AGENTS.md reference: AGENTS.md:L123-L125
Useful? React with 👍 / 👎.
List the AGENTS.md files a mounted environment found from its own working directory instead of deduplicating paths against the primary, which hid same-path files on another filesystem. Drop a mounted environment's skills and workdir once it leaves the roster, and accept the routed environment in the Kap tool display schema.
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 82b6d6deab
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (pending === undefined && environment.isAvailable([], target.environmentId)) { | ||
| const cwd = connectedCwd(target.environmentId); | ||
| if (cwd !== undefined) { | ||
| pending = { cwd, mount: mounts.mount(target.environmentId, cwd) }; | ||
| mounted.set(target.environmentId, pending); |
There was a problem hiding this comment.
Mount instructions before allowing the first remote mutation
When a disconnected secondary's first call is Write, Edit, or a mutating Bash, the pre-call roster only says that the environment is not connected. This mount occurs only when the roster reminder is rendered again after tool resolution has connected the target, while the already-issued mutation executes in the same turn; the model therefore changes the remote project before it can see or read that project's AGENTS.md. Mount the context and deliver its instructions before permitting the first mutating call, or reject that call and require a retry after the roster update.
AGENTS.md reference: AGENTS.md:L11-L11
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 475f54f4a9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| private recordedEnvironmentId(environmentId: string | undefined): string | undefined { | ||
| const primary = this.environment.targets().find((target) => target.primary); | ||
| return environmentId === primary?.environmentId ? undefined : environmentId; |
There was a problem hiding this comment.
Preserve file-history keys across environment rebinding
When a file is edited in a secondary environment and that environment is later rebound as the agent's primary, capture() stored it under a key containing the environment ID, but contentAt(..., environment_id) now normalizes that ID to undefined based on the current target list and searches a different key. The saved before/after content is therefore returned as null even though the changes endpoint still lists the edit; keep history keying independent of the current binding. 【AGENTS.md†L133-L133】
Useful? React with 👍 / 👎.
| const declared = new Map( | ||
| this.declarations.snapshot().entries.map((declaration) => [declaration.id, declaration.entry.defaultCwd]), | ||
| ); | ||
| if (current.environmentId !== LOCAL_ENVIRONMENT_ID && !declared.has(current.environmentId)) return [primary]; |
There was a problem hiding this comment.
Preserve other targets for an undeclared primary
When the main agent is started on a process-registered but undeclared environment (supported by session creation when environmentCwd is supplied), this early return hides every configured target even with multi_environment enabled. routeEnvironment() then has only one target, so the tool schema omits environment and calls cannot reach the configured remotes or local host from that session; keep the declared targets and filter only the primary ID.
Useful? React with 👍 / 👎.
| execution.isError !== true && | ||
| (execution.display?.kind === 'command' || execution.display?.kind === 'file_io') | ||
| ) { | ||
| execution = { ...execution, display: { ...execution.display, environment: call.environment.environmentId } }; |
There was a problem hiding this comment.
Carry routed environments into VS Code displays
When a VS Code session with multi-environment routing enabled approves or reviews a routed Bash, Read, Write, Edit, Glob, or Grep call, this annotation is dropped by apps/vscode/src/runtime/tool-display.ts (describeToolDisplay/toLegacyDisplay emit only the command or path), so the approval and tool-call card do not identify the target host. Fresh evidence beyond the earlier Kap-schema issue is that the VS Code adapter still converts the newly annotated display to a legacy payload without an environment field; preserve the target in that projection. 【AGENTS.md†L125-L125】
Useful? React with 👍 / 👎.
Requirement or Bug
PR #4062 让一个 session 只能绑定一个执行环境(本机、SSH 或 Docker)。这个 PR 在实验 flag 后面,让同一个 session 的文件和 shell 工具可以按调用选择环境,并且在另一个环境连上后,让模型看到那个环境的项目说明(
AGENTS.md)和项目 skills。延续 #3856,衔接 #4062。Bug Reproduction Steps
N/A
Root Cause
N/A
Code Changes
核心思路:环境不再只是工具的路由目标,而是「挂载的工作区」。
[environments]里声明的环境都是候选,仍然在第一次有调用落到它上面时才连接;连上之后,session 会为它打开一次 workdir view,把它的AGENTS.md路径通过 roster reminder 告诉模型,把它的项目 skills 以<skill>@<环境>加进会话 skill 列表。系统提示仍只描述 primary,flag 关闭时 prompt 和落盘格式都不变。environment时调用落在哪AGENTS.md路径和项目 skillsenvironment_rosterreminder:首次发完整列表,之后只发变化的环境flowchart LR subgraph AppScope[App] Registry[EnvironmentRegistry<br/>连接与 generation] end subgraph SessionScope[Session] Mounts[SessionEnvironmentMounts<br/>每个次环境挂载一次] Catalog[SessionSkillCatalog<br/>primary skills + skill@env] end subgraph AgentScope[Agent] Targets[AgentEnvironmentService<br/>targets / acquire] Tools[ToolRegistry + ToolExecutor<br/>environment 参数与路由] Roster[EnvironmentRoster<br/>完整列表或增量] end Tools --> Targets --> Registry Roster -- 环境已连上 --> Mounts Mounts -- Program workdir view --> Registry Mounts -- 项目 skills --> Catalog连接归 App 层的 registry,跨 session 共享;挂载放在 Session 层,是因为一个 (环境, cwd) 下的
AGENTS.md和 skills 与是哪个 agent 无关,而 skill catalog 本来就是 Session 级的,子 agent 生成时不会重复扫描。Agent 层只负责路由和渲染 roster:子 agent 和 fork 都继承调用方的 primary,所以同一个 session 里所有 agent 看到的是同一份挂载结果。[environments]通常是用户级配置,声明只说明「可以用」,不说明这个 session 要用;提前连接会让每个 session 都对所有声明发起 SSH 握手、拉起 docker exec-serverAGENTS.mdAGENTS.md提醒一致,reminder 不会因正文膨胀<skill>@<环境><环境>:<skill>,或只在重名时加前缀:已被 plugin skill 占用;只在重名时加前缀会让同一个 skill 的名字随环境集合变化environment固定为 string,合法 id 由 roster 说明,执行时校验enumsequenceDiagram participant Model participant Tools as ToolExecutor participant Registry as EnvironmentRegistry participant Roster as EnvironmentRoster participant Mounts as SessionEnvironmentMounts Note over Roster: 第 1 步发完整 roster,devbox 标为 not connected yet Model->>Tools: Read(path, environment="devbox") Tools->>Registry: acquireWhenReady(devbox),首次连接 Registry-->>Tools: 文件内容 Roster->>Mounts: 第 2 步开始时 mount(devbox, cwd) Mounts-->>Roster: AGENTS.md 路径;deploy@devbox 加入 catalog Roster-->>Model: 增量:devbox 的 cwd 和 AGENTS.md 路径 Model->>Tools: Skill("deploy@devbox") Tools-->>Model: skill-loaded environment="devbox"阅读指南:
packages/agent-core-v2/src/agent/environmentBinding/agentEnvironment.ts:候选环境怎么算、primary 和次环境的 binding 与 cwd 怎么解析。packages/agent-core-v2/src/agent/toolRegistry/与src/agent/toolExecutor/:environment参数何时出现,preflight 怎么把它解析成EnvironmentTarget并从工具参数里剥掉。packages/agent-core-v2/src/session/environmentMounts/environmentMounts.ts:挂载做了什么、只取哪些 skill、环境离开 roster 时如何卸载。packages/agent-core-v2/src/agent/environmentBinding/environmentRoster.ts:roster 的三种状态,以及完整列表和增量的切换条件。packages/agent-core-v2/src/features/fileHistory/与src/agent/permissionPolicy/:fileHistory 和会话审批按环境区分。7 个工具里各自的
resolveExecution改动、display 和 wire 字段透传、ISessionWorkdir的接线、测试 stub 补set/remove、文档可以略读。roster 示例(真实 CLI 运行,临时目录替换为 <tmp>)
第 1 步:
Read(environment="devbox")连上之后的第 2 步只追加:同一步的 skill 变更提醒里出现
- deploy@devbox: Deploy the devbox app,之后Skill("deploy@devbox")的加载块为<skill-loaded name="deploy@devbox" trigger="model-tool" source="project" environment="devbox" dir="<tmp>/devbox/.agents/skills/deploy" args="">。Behavior Changes and Affected Users
除最后一行外,所有变化只在开启
multi_environmentflag(默认关闭)且可用环境 ≥2 时出现。Bash、Read、Write、Edit、Glob、Grep、ReadMediaFile可按调用传environment;缺省仍是 primary[environments]的 experimental 用户defaultCwd;未设置时用首次连接时目标报告的 cwd;primary 的 cwd 和附加目录不变defaultCwdAGENTS.mdAGENTS.md路径(与 primary 同路径的文件也照样列出,本机个人AGENTS.md不重复),要求修改前先读;次环境调用不再扫描 primary 的文件系统Skill调不到<skill>@<环境>加入会话 skill 列表,main agent 通过现有 skill 变更提醒看到;只取项目级 skill,本机 user / plugin / builtin skill 不重复;环境离开 roster(声明被删或 flag 关闭)时一并移除name/source/dir等属性environment属性;远端 primary 时本机 user 级 skill 标为localenvironment_roster:历史里没有时发完整列表与说明,之后只发变化的环境;环境移除时发is no longer available;compaction 后重发完整版fileHistorychanges中返回原生path与environment_id;content接受可选environment_id;该环境在轮次结束时不可达只跳过它自己的文件display.environment(Kap 的ToolInputDisplaySchema同步接受)、审批的environmentId、skill 的environmentId、workspace 指令快照的agentsMdProjectPaths;旧数据缺字段时按 primary 处理stream-json与kimi export消费方tool_input为工具原始参数environment参数,hook 与实际执行看到同一个选择tool_input字段的脚本/home/alice/proj、docker 的/workspace),本机 workspace 的源目录被并进远端环境的附加目录,#4520 之后还会被拿去扫 skillsPrompt 文本逐句:
AGENTS.md。现在改为 "...describe the primary environment." 加 "Once another environment connects, the AGENTS.md files found there are listed under it; read them before changing files in that environment.",由挂载列出的具体路径约束。<skill>@<environment>.",让模型知道 skill 列表里带后缀的条目是什么。- `<id>` is no longer available.,以及有AGENTS.md路径时的 "Read the AGENTS.md files listed above before changing files in that environment."。契约影响:
docs/wire-manifest.d.ts、docs/state-manifest.d.ts只有新增可选字段;RESTfile-history只多一个可选environment_id;REST skill 结构、packages/node-sdk/src/index.ts、WS 路由、ACP API 都没改。code-app 的 remote workspaces 按 primary binding 分组,本 PR 不改变 primary,分组与分页不受影响;后续需要在工具卡片和轮次文件变更视图里带上environment_id。实验版不做次环境 MCP、按环境配置权限、次环境 agent profiles、跨环境文件复制和手动切换 primary。测试覆盖:
Read:toolExecutor.test.ts。environmentBindingService.test.ts。@env、与 primary 同路径的AGENTS.md照样列出、每个环境只加载一次、随 session 释放 workdir、打开失败不加 skill、卸载后移除 skill 并可重新挂载、过期的卸载请求不影响新挂载、加载中被卸载不加 skill:environmentBindingService.test.ts;项目级路径与个人路径的区分:context.test.ts。environment属性:prompt.test.ts。非 local view 不读 registry:workspaceDirsService.test.ts。kimi -p+ mock LLM + command 类型环境拉起本机kimi exec-server)跑通了上面的三步流程。没验证:真实 SSH / Docker 目标。测试文件与运行结果
environmentBindingService.test.ts、toolExecutor.test.ts、environmentGenerationGuard.test.tspermissionPolicyService.test.ts、permissionRules.test.ts:环境级审批隔离、Git 策略(含显式指名 primary)、__proto__等环境 id 与 replay。fileHistory.test.ts:跨环境同名路径、备份复用、次环境不可达时不发起连接、按 main 的 primary 判定子 agent 的编辑、环境 id 含://时 key 不冲突。agentsMdReminder.test.ts、approval.test.ts、prompt.test.ts、workspaceDirsService.test.tstypecheck,check-no-comments和改动文件的 oxlint 通过;kap-server 的 skills / apiSurface / fileHistory 测试与 node-sdk 全量通过。tool.test.ts的 "returns the spawned agent id when a foreground subagent times out" 在本机去掉本次改动后同样超时,上一轮 CI 中通过;fullCompaction.test.ts的一条用例只在全量并发下失败,单独重跑两次均通过。以 CI 为准。kap-server 全量 1,442 通过。Checklist
gen-changesetsskill:multi-environment-routing.md。gen-docsskill:中英文guides/remote-environment.md。