Skip to content

feat: remote environments for SSH and Docker execution targets (phase 1: creation-time binding) - #4062

Open
7Sageer wants to merge 383 commits into
mainfrom
feat/remote-env-phase1
Open

7Sageer wants to merge 383 commits into
mainfrom
feat/remote-env-phase1

Conversation

@7Sageer

@7Sageer 7Sageer commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator

Requirement or Bug

支持在创建会话时把 agent 绑定到 SSH / Docker / 自定义命令环境(#3856 的 phase 1,不含会话内切换环境)。

Bug Reproduction Steps

N/A

Root Cause

N/A

Code Changes

三个概念各管一件事:

Environment       在哪执行     App 级 registry:local + config.toml [environments] 声明的 ssh / docker / command
Workspace         归属哪里     仍是启动 kimi 的本地目录:会话存储与列表、trust、配置、MCP(与 main 相同)
Session workdir   看到什么     创建时选定 (environmentId, cwd);远端会话的 fs / git / AGENTS.md / 项目 skills 与 profiles 按会话构建
 packages/agent-core-v2/src
+├── environment/, app/environment*     EnvironmentRegistry 与用户 [environments] 声明
+├── remote/{protocol,client,server}    exec-server NDJSON 协议、launcher、fs/process 桩(跨重连保持同一对象)
 ├── program/program.ts                 仍是单个本地 generation,新增 createWorkdir 工厂
 ├── workspace/sessionLifecycle         创建/恢复时连接环境、校验 cwd、构建会话 workdir
+├── session/workdir                    ISessionWorkdir:会话的 environmentId + fs
+├── agent/environmentBinding           agent 级 binding(journal 可回放),工具经 lease 访问目标
-└── runtime/                           旧 runtime 抽象,由 environment 取代
 apps/kimi-code                         kimi exec-server、--environment / --environment-cwd、footer 环境状态、/new 继承环境
 packages/kap-server, node-sdk, klient  会话创建/恢复的环境参数、只读环境接口;统一删除旧 runtime API 与别名
SessionManager.create
  resolveCreationEnvironment   显式 local 直接放行;其余先校验配置与声明,再按显式选择 > [environments].default > local
  SessionLifecycleService.create
    openWorkdir                acquireWhenReady → stat cwd → Program.createWorkdir
    session meta 写入 { environmentId, environmentCwd }
    创建 main agent,binding 指向该环境
resume                         读 meta → 走同一路径重连;连不上直接失败,不回落 local

不在本 PR:change_environment / connect 工具、Agent 工具的 environment 参数、/environment 管理器、项目级 .kimi-code/environments.toml。

Behavior Changes and Affected Users

Behavior Before After Who relies on the old behavior Escape hatch
Agent 文件与进程工具的执行位置 只在本机 创建时可通过 [environments]、--environment 或 session-create 参数绑定远端 TUI、print、ACP、SDK 的默认本地用户;未指定环境时不变 不配置 default、不传 --environment
环境配置读取或校验失败 警告后按未设置 default 继续,可能创建 local 会话 创建直接报 config.invalid;ACP 仍从 local 初始化,再绑定客户端环境 使用损坏 [environments] 或 config.toml 的 TUI、print、ACP、SDK 用户 显式选择 local(ACP 自动)
远端工作目录 N/A 默认取 defaultCwd,--environment-cwd 可覆盖;必须是 POSIX 绝对路径 新功能 省略该参数
恢复远程会话 N/A 读 session meta 重连;环境未注册或连不上时报错,不打开本地会话 新功能;本分支早期创建、meta 无环境字段的会话会按 local 恢复(未发布) 无
远程断线 N/A footer 显示 disconnected;下次工具调用重连,会话的 fs / git 服务继续可用 新功能 无
TUI 远程 @ 补全 扫描本机 workDir 查询会话所在环境;会话就绪前不给远端候选,不回落本地 新功能 本地会话照旧用本地补全
TUI /new 新会话使用本地工作目录 继承当前会话的环境与 cwd 远程 TUI 用户 重新启动、不带 --environment
kap-server fs 路由的 runtime_id 会话 fs:{action}、下载、/workspace/fs::search、/workspace/fs::suggest、/fs::suggest 可按请求选环境(0.37.0 起,已写入文档) 参数删除:会话路由固定用会话自己的环境,workspace 与 root 路由只走本地 直接传该参数的 HTTP 客户端与脚本;已知只有本仓库 kimi-inspect(已同步),code-app 的 web / desktop 未使用 无;维护者已确认删除,不加 changeset
旧 runtime API(REST / SDK / klient / MCP / debug / ACP / journal) 提供 runtime 路由与方法,接受 runtime_id / runtimeId,回放 runtime.set_binding 旧 API 与字段统一移除;旧 local journal 记录作为未知记录跳过,仍恢复 local 无实际 runtime 用户;已知内部消费方已改用 environment;含旧 local binding 记录的历史会话仍按 local 恢复 无;维护者已确认统一删除,不加额外 changeset
Read 工具描述(prompt) “local filesystem” “active environment's filesystem” 所有会话;只更正为工具实际访问的文件系统 无
Plan 文件位置 会话持久目录 本地会话不变;远程会话写入远端 tempDir 远程会话用户 无
粘贴的压缩图片原图 说明里引用服务端本机路径,远端读不到 统一使用会话持有的 kimi-file://;fork 与恢复后可读;注册失败报错并拒绝发送 本地与远程 TUI 粘贴图片的用户 修复存储后重新粘贴
远程恢复时的相对 additionalDirs 可能按本机 cwd 解析 按远端 cwd 解析 使用相对额外目录的远程 CLI / SDK 用户 使用目标侧绝对路径
关闭远程环境 launcher 立即 TERM 先关 stdin,给 executor 2 秒清理,再 TERM / KILL 运行难以终止的子进程的远程用户 无
leader 已退出的后台进程组 5 秒后或超过 4096 组后不再追踪 确认 ESRCH 后才释放 远程后台子进程较多的用户 无
Windows 客户端连接 POSIX 目标 launcher 扩展名重复、项目 skills 漏找、rg 补装找错二进制 按目标平台处理路径语义与归档 Windows 客户端用户 无需配置

受影响模块与测试覆盖:

  • agent-core-v2:registry、binding、远程协议、Program 与会话 workdir、会话生命周期;全量 7493 通过,唯一失败的 tool.test.ts foreground subagent timeout 在未改动的分支上同样超时。
  • kap-server:环境路由、会话 fs 路由(含远程会话读写与下载、远程会话拒绝 reveal);全量 1516 通过,1 个 search 用例并发下偶发失败,单独运行通过。
  • node-sdk:创建写入 meta、远端 cwd 无效时拒绝、恢复连接失败报错、按会话补全、附件、冷恢复;192 通过。
  • apps/kimi-code 4114、acp-server 150、klient 141、kimi-inspect 118 全部通过;各包 typecheck、lint(0 errors)、no-comments 与 import 边界检查通过。
  • 本次 fail-fast 追加:覆盖无效 default、坏 sibling、损坏 TOML、无通知失败/恢复与 ACP 初始化;SDK 92、ACP 151、Klient 141 通过,全仓 typecheck 与 lint(0 errors)通过。kap-server 全量的 search 与 prompt metadata 各 1 个并发失败,单独运行均通过。
  • 本次 runtime cleanup:合并后的 remote/process/protocol 与 architecture 共 37 通过;node-sdk、vis(181 + 42)全量通过;kap-server 全量 1516 通过、1 skipped,search 失败项单独 118 通过;core 全量 7536 通过、2 skipped,4 个失败中 tool foreground timeout 与 profile watch 在 base 同样失败,reconcile 与 taskManager 单独复跑通过;根 typecheck、lint(0 errors)通过。
  • 尚未在真实 SSH / Docker / Windows 环境上手工验收。

Checklist

  • I have read the CONTRIBUTING document.
  • I have linked a related issue — 内部 feature,从 feat: remote environments for SSH and Docker execution targets #3856 拆出,无独立 issue。
  • I have added tests that prove my feature works.
  • The behavior-change table above is complete, and every removed behavior or flipped default is named in the changeset and either has an escape hatch or was explicitly approved by a maintainer in this PR.
  • Ran gen-changesets skill;功能 changeset .changeset/remote-environments.md,Windows 修复 changeset .changeset/windows-remote-targets.md;fs 路由参数与旧 runtime API 删除经维护者确认,不新增 changeset。
  • Ran gen-docs skill;中英文远程环境指南、配置与 server API 文档已更新。

EnvironmentError from registry acquire (unknown or disconnected
environment id) fell through sendMappedError to the INTERNAL_ERROR
fallback, returning 50001 with the raw stack string. Route it through
the same environmentErrorCode mapping the environment routes use:
unknown ids answer ENVIRONMENT_NOT_FOUND, disconnected environments
ENVIRONMENT_UNAVAILABLE, with no stack in the envelope.
The footer badge already hides the local git state for remote-bound
sessions, but statusLinePayload still fed the local cache branch into
the custom status line JSON, showing the host branch while the session
runs remote. Share the badge's remote check so the payload carries
gitBranch null under the same condition.
stdio is the only supported transport, so the flag was pure ceremony.
The light pre-dispatch in src/main.ts now accepts the bare
[exec-server] argv shape alongside the explicit
[exec-server, --listen, stdio] one, and the hidden Commander
subcommand's --listen option defaults to stdio so both entry paths
agree. --listen <other> still exits 2, and unknown options or excess
args still fail in Commander instead of silently starting. The
explicit spelling keeps working for existing launcher configs; the
remote-exec client keeps emitting it since a new client may meet an
old executor.
…cquire failure

Route the three acquire sites in agentsMdReminderService (ensureSeeded,
targetDirs, probeDir) through the M53 acquireEnvironmentLease helper so a
disconnected environment binding abstains per-site instead of relying on
the tool-did-execute hook's blanket catch. The helper gains an optional
required-capabilities param; permission-policy callers stay untouched.
…o a view

On a successful environment binding commit to a previously unvisited
(environmentId, cwd) view, probe the AGENTS.md path chain for the new cwd
on the target filesystem by reusing the agentsMdReminder probing
machinery (loadAgentsMdDetailed) and inject a path-only project-context
reminder (new project_context variant) that names the view, supersedes
earlier directories' guidance, and lists the paths for the agent to read
with its tools. The initial binding counts as visited; revisiting a view
injects nothing. The existing environment reminder keeps its
machine-identity semantics and may co-fire unmerged.
Home-level AGENTS.md files (brand home, ~/.agents) may already be
reflected in the system prompt for sessions that started local, so
claim the listed paths 'may not be reflected in your system prompt'
instead of 'were not included'.
Add an agent_environment_tools experimental flag (default off). When
enabled, the main agent gains a tool group for environment switching:

- change_environment(id, cwd?) switches the session binding through the
  same machinery as /environment (connect, cwd validation, undo
  participant), taking effect at the turn boundary with the environment
  reminder queued for the next turn. Rejected in plan mode; asks in
  manual/yolo permission modes, allowed without asking in auto mode.
- connect creates a temporary environment from a launcher spec
  (ssh/docker/command) without writing it to config, registered in the
  workspace registry so it does not persist.
- the system prompt gains an available-environments section so the
  agent knows which ids exist.
- the Agent tool gains an optional environment parameter to bind the
  spawned subagent to a chosen environment instead of inheriting the
  parent's binding.

The tool group is not registered while tower mode is active.
- re-fetch the environment view after connect in the subagent spawn
  binding so the cwd fallback reads the connected host, not the stale
  pending view
- reject the Agent tool environment parameter at execution time when
  the agent_environment_tools flag is off (resumes still ignore it)
- dispose the executor connection when registering an ephemeral
  environment fails
- fix a never-intersection type collapse in the spawn test
…record

Remove boundCwd/reroot from the shared environment record and interface:
a cwd switch now only updates the switching session's own binding, so it
no longer replaces the shared connection generation or drains other
sessions' tracked resources. Session resume/restore no longer reroots or
background-connects the persisted environment, and session create no
longer reroots the shared environment to the new session's cwd.
…solation

Resuming a session no longer reconnects the restored remote binding in
the background; the environment stays disconnected until an explicit
reconnect. Rewrites the resume paragraph in both locales (the previous
'one exception' wording contradicted the new behavior; content removed
for staleness) and drops the stale 'background reconnect failure after
resume' trigger example from the refreshEnvironmentSlot doc comment.
…r own cwd

Resume no longer connects a persisted remote environment; the first
environment acquire connects on demand. Process calls carry the
session's own cwd as an explicit spawn option, and remote program
generations are keyed and rooted per session binding cwd instead of
the local workspace root.
Narrow tool outputs to string before constructing errors and pass the
full ExecutableToolContext (turnId, toolCallId) to execute.
…nection

Business calls (fs/*, process/start, process/write, ...) now carry a
per-request timeout that rejects only the stalled call with
RequestTimeoutError; the connection and its generation survive, and a
late response is discarded via a bounded expired-id set instead of
faulting the connection as an unknown-id response. Control calls
(environment/status health check) keep the kill-the-connection timeout,
so a genuinely wedged executor still drains the environment exactly like
a transport drop.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b06fdb692f

ℹ️ 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".

stagedDiscarded = true;
if (attachmentsSink === undefined || stagedPaths.size === 0) return;
const sink = attachmentsSink;
await Promise.all([...stagedPaths].map((path) => sink.remove(path).catch(() => undefined)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid deleting attachments shared by concurrent prompts

When two prompt submissions materialize the same file ID/name or identical inline payload, they use the same deterministic target path. If request A writes that path, request B reuses it because its size matches, and then A fails during later profile/model/tool-policy validation, A's discardStaged() removes the file even if B has already enqueued successfully. B still returns success but its attachment path is now missing when the agent tries to read it; use request-unique staging paths or reference-count ownership before removing shared files.

AGENTS.md reference: AGENTS.md:L118-L120

Useful? React with 👍 / 👎.

get agentProfiles(): IWorkspaceAgentProfileLoader { return this.requireGeneration().agentProfiles; }
get sessionControllerGeneration(): string { return this.requireGeneration().id; }
get state(): IWorkspaceStateService { return this.requireGeneration(LOCAL_ENVIRONMENT_ID).state; }
get dirs(): IWorkspaceDirs { return this.requireGeneration(LOCAL_ENVIRONMENT_ID).dirs; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Route added directories to the session's bound generation

When /add-dir or Session.addAdditionalDir() is used by an SSH, Docker, or command-bound session, the SDK reaches workspace.program.dirs, and this getter now always returns the local generation even though the session was seeded from a separate remote generation's IWorkspaceDirs. A remote-only path is therefore checked on the host and rejected; if the path happens to exist on both machines, the call reports success while only the local roots change, so the remote session's tools continue rejecting the directory. Resolve the session's bound generation before applying the update.

AGENTS.md reference: AGENTS.md:L118-L120

Useful? React with 👍 / 👎.

@7Hanrui

7Hanrui commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

@codex review

@7Hanrui

7Hanrui commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b8dcfad22

ℹ️ 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".

Comment on lines +220 to +221
environments: {
...resolver, ready: this.environments.ready,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep live sessions pinned to one environment generation

When a watched remote-environment declaration changes its host, container, or command while a session is live, applyLauncher rotates and disconnects the registry generation, but this controller retains the old generation's instructions, skills, and profiles while supplying agents an ID-based resolver that acquires the new generation. The next tool call therefore reconnects and executes on the new target using project context loaded from the old target, silently mixing policy and content across machines; pin the resolver to the controller generation or invalidate/rematerialize affected sessions.

AGENTS.md reference: AGENTS.md:L118-L120

Useful? React with 👍 / 👎.

@@ -22,12 +22,11 @@ const mcpServerCommonFields = {

export const mcpServerStdioConfigSchema = z.object({
transport: z.literal('stdio'),
runtime_id: z.string().min(1).optional(),
environment_id: z.string().min(1).optional(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve executor in the Klient MCP schema

When an existing Klient or IPC caller sends a stdio MCP configuration containing the supported legacy executor: 'local', this narrowed Zod schema strips the property and its inferred facade input no longer accepts it. The engine schema and client-stdio compatibility test explicitly retain this value, so restore the optional field rather than narrowing the public wire contract.

AGENTS.md reference: packages/klient/AGENTS.md:L14-L18

Useful? React with 👍 / 👎.

Comment on lines +42 to +43
export function isExecServerArgv(argv: readonly string[]): boolean {
return hasExecServerShape(argv.slice(1)) || hasExecServerShape(argv.slice(2));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Restrict light dispatch to the actual executable slot

In the supported no-script argv shape, any invocation whose arguments after the first option equal an executor shape is misclassified because argv.slice(2) is checked unconditionally. For example, kimi -p exec-server becomes ['exec-server'] after that slice and starts the stdio executor instead of running print mode with the prompt exec-server; it can then wait on stdin or exit successfully without producing the requested answer. Only accept the offset-two form when argv[1] is the duplicated executable/script slot.

Useful? React with 👍 / 👎.

} from './question';
export { projectHistoryToSessionUpdates } from './replay';
export { AcpRuntimeProviderFactory } from './acp-terminal';
export { AcpEnvironmentProviderFactory } from './acp-terminal';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the ACP runtime-provider export

Downstream embeddings that import the previously exported AcpRuntimeProviderFactory from @moonshot-ai/acp-server now fail to type-check or load because the package barrel exposes only the renamed class and provides no compatibility alias. Keep a deprecated adapter or alias for the old export while introducing AcpEnvironmentProviderFactory, since this package is an external contract surface.

AGENTS.md reference: AGENTS.md:L110-L112

Useful? React with 👍 / 👎.

@@ -41,11 +41,11 @@ export function registerBusinessSnapshotRoutes(
() => sessionWorkspaceAssociation(core, requestParams(req)['session_id'] ?? ''),
callTimeoutMs,
));
app.get(`${basePath}/session/:session_id/agent/:agent_id/runtime-binding`, async (req, reply) =>
app.get(`${basePath}/session/:session_id/agent/:agent_id/environment-binding`, async (req, reply) =>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep the legacy runtime-binding debug route

Existing desktop, extension, or inspector clients that call /api/v1/debug/session/{session_id}/agent/{agent_id}/runtime-binding now receive a 404 because only the renamed environment-binding route is registered and there is no compatibility alias. Retain the old path while introducing the new spelling, since the checked-in kap-server API surface is an externally consumed contract.

AGENTS.md reference: AGENTS.md:L110-L112

Useful? React with 👍 / 👎.

@@ -242,6 +245,9 @@ export function registerSessionsRoutes(
const handle = await core.accessor.get(ISessionManager).create({
workspaceId: touched.id,
workDir,
environmentId: body.environment_id,
environmentCwd: body.environment_cwd,
ensureMainAgent: body.environment_id !== undefined && body.environment_id !== 'local',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Materialize explicit local bindings with a cwd

When a REST caller creates a session with environment_id: "local" and an environment_cwd, the schema accepts the request but this condition prevents creation of the main agent that would persist and apply that cwd. The lazily created agent is subsequently seeded with the default local binding, so the request succeeds while silently ignoring the requested working directory; materialize the binding when a cwd is supplied or reject this combination.

AGENTS.md reference: AGENTS.md:L118-L120

Useful? React with 👍 / 👎.

Comment on lines +203 to +206
const cwd =
this.config.cwd === undefined
? base
: path.resolve(base, this.config.cwd);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid applying host-normalized MCP cwd paths remotely

When a project-root .mcp.json stdio entry selects a non-local environment_id, the config loader has already converted its cwd—including an omitted cwd—to the host's absolute Git-root path. Resolving that absolute value here preserves paths such as /Users/me/project on the SSH or Docker target, so the MCP process fails to spawn unless the target coincidentally has the same path; preserve the unnormalized cwd/origin or skip host normalization for remote entries.

AGENTS.md reference: AGENTS.md:L114-L116

Useful? React with 👍 / 👎.

@7Hanrui

7Hanrui commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1f65ccc3f8

ℹ️ 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".

environmentId: string,
required: readonly EnvironmentCapability[],
): EnvironmentFsScope {
const lease = accessor.get(IEnvironmentService).acquire({ environmentId }, required);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Authorize environment filesystem acquisition by workspace

When a filesystem caller in workspace B supplies an environmentId declared only by trusted workspace A, this helper ignores its workspaceId and acquires the app-wide registry entry directly. The new kap-server session/workspace filesystem routes and SDK suggestFiles all use this helper, so a client that guesses the ID can read or search workspace A's SSH/Docker target; call IEnvironmentDeclarationService.assertAllowed(workspaceId, environmentId) before acquisition.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

const agent = await this.agentScope(input.sessionId);
const lease = await agent.accessor.get(IEnvironmentService).acquireWhenReady({ environmentId: input.runtimeId });
try {
const binding = agent.accessor.get(IAgentEnvironmentBindingService).bind(input.runtimeId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the declared cwd when switching runtimes

When a legacy SDK caller switches to a declared remote runtime with defaultCwd, this binds only its ID and drops that cwd. The binding service consequently falls back to the host-local session cwd for relative tools and persists the incomplete binding, so operations can fail or target the wrong remote directory after both the switch and a resume; resolve the declaration's default cwd before binding.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

if (this.creationEnvironment?.environmentId !== undefined && this.creationEnvironment.environmentId !== 'local') {
// The remote session is created eagerly, so the session-less
// "No session yet" notice would contradict the live banner.
void this.ensureSession();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Propagate remote connection failures during TUI startup

With a configured remote default or --environment, this fire-and-forget call lets initMainTui() finish before session creation connects. If the target is unreachable, lazyCreateSession() catches the error and returns undefined, after which the outer startup path still reports “Ready” and leaves the UI sessionless, contrary to docs/en/guides/remote-environment.md, which says connection failures abort startup; await the eager creation and propagate failure or avoid reporting successful startup.

Useful? React with 👍 / 👎.

- Program returns to one local generation per workspace; remote project
  content (fs, git, AGENTS.md, project skills/profiles) is built per session
  through a workdir factory and disposed with the session.
- SessionLifecycleService connects the target, validates the cwd, persists
  environmentId/environmentCwd in session meta at creation, and reads them
  back on resume; SessionManager only resolves the creation environment and
  routes to one controller per workspace.
- Remote environments expose fs/process facades that survive reconnects.
- Drop project-level .kimi-code/environments.toml with its trust gating,
  conflict detection, and environmentProject provenance; declarations come
  from the user [environments] config only.
- Agent environment binding keeps a single replayable state key and no
  longer writes session meta.
- Seed each session with ISessionWorkdir (its environment and fs service);
  remote sessions get their session-owned workdir fs, local sessions the
  workspace fs. Remove createEnvironmentFs and its fabricated workspace
  context.
- kap-server session fs actions and downloads use the session's own
  environment; workspace and root suggest/search routes run locally through
  a private rooted fs. Drop the runtime_id / environment_id parameters from
  these routes.
- SDK suggestFiles takes a sessionId instead of an environment binding; the
  TUI completes remote @ mentions through the live session.
- kimi-inspect stops sending environment_id; docs updated.
@7Hanrui

7Hanrui commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1fbd3b97f7

ℹ️ 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".

lease: { track: (resource) => resource, dispose: () => {} },
};
}
const lease = core.accessor.get(IEnvironmentService).acquire({ environmentId }, ['fs']);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Connect cold environments before serving their files

When /fs::content or /fs::mkdir is the first operation against a declared SSH, Docker, or command environment—or follows a dropped connection—the entry is pending or disconnected, and acquire() rejects it without invoking connect(). The new environment_id support therefore cannot open a cold target and continues failing after a disconnect until an unrelated session or tool reconnects it; use acquireWhenReady() here so both filesystem routes establish the explicitly selected environment on demand.

Useful? React with 👍 / 👎.

const expectedGeneration = pinnedGeneration(inspected);
const view = new EnvironmentWorkspaceView(inspected, {
workDir: this.workspaceCtx.workDir,
additionalDirs: [...this.workspaceCtx.additionalDirs, ...this.skillCatalog.catalog.getSkillRoots()],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep local skill roots on the host filesystem

When an SSH or Docker session uses a built-in, user, explicit, extra, or plugin skill, Program.createWorkdir() merges those host-loaded sources with target-side project skills, so getSkillRoots() contains paths from both filesystems. Passing every root through this target EnvironmentWorkspaceView makes Read, Glob, Grep, Write, and Edit interpret host skill paths on the remote machine; skills that reference bundled scripts or templates through their expanded skill directory therefore fail, while a coincidentally matching remote path is incorrectly treated as an allowed skill root. Preserve each root's filesystem provenance or materialize host skills on the target instead of mapping every root remotely.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

const env =
this.options.environmentId === LOCAL_ENVIRONMENT_ID
? mergeStdioEnv(this.config.env)
: (this.config.env ?? {});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Normalize proxy variables for remote MCP children

When a remotely executed stdio MCP configuration supplies HTTP_PROXY or HTTPS_PROXY, this branch bypasses mergeStdioEnv(), so the child never receives the derived NODE_USE_ENV_PROXY, case-normalized aliases, or reconciled NO_PROXY values. Node MCP servers that previously honored the configured proxy can therefore connect directly or fail behind a corporate proxy; normalize the target-side configured environment without merging the host process environment.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

@7Hanrui

7Hanrui commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a7a53311e3

ℹ️ 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".

Comment on lines +20 to +23
export class EnvironmentSetBinding extends AgentEvent2<z.infer<typeof environmentSetBindingSchema>> {
static override readonly type = 'environment.set_binding';
static override readonly durable = true;
static override readonly schema = environmentSetBindingSchema;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make older binaries reject remote-bound journals

When a session is created or resumed on a remote environment and the user later rolls back to the previous release, this new durable record is still written under wire protocol version 1.5. The older dispatcher treats environment.set_binding as an unknown record and skips it, while its session lifecycle ignores the new metadata fields and seeds the main agent with the local runtime; reopening the session can therefore silently execute commands intended for SSH or Docker on the host workspace. Bump the persisted protocol/version so older builds fail closed, or encode a compatible binding that cannot fall back to local.

AGENTS.md reference: AGENTS.md:L123-L125

Useful? React with 👍 / 👎.

Comment on lines +90 to +92
private applySessionWorkDir(binding: EnvironmentBinding): void {
if (this.scopeContext.agentId !== MAIN_AGENT_ID) return;
this.workspaceContext.setWorkDir(binding.cwd ?? this.session.cwd);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep local terminal cwd separate from remote binding

When an SSH or Docker session uses a target cwd such as /remote/app while its host workspace is /Users/me/app, this stores /remote/app in the session-wide workspace context. The terminal REST route explicitly defaults environment_id to local, but SessionTerminalService derives that local terminal's cwd from this shared state, so it tries to spawn the host PTY in /remote/app and fails—or opens an unrelated host directory when that path happens to exist. Preserve the host workspace roots for local terminals, or key the workspace state by environment.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

@7Hanrui

7Hanrui commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 591b2a5f8c

ℹ️ 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".

Comment on lines +96 to +100
({ content: agentsMd, paths: agentsMdPaths } = await loadAgentsMdDetailed(
{ fs: lease.environment.fs!, homeDir },
workDir,
this.bootstrap.homeDir,
));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep personal instructions local during remote init

When /init runs in a remote-bound session, this reload reads every personal source through the remote filesystem: the local bootstrap.homeDir is interpreted on the target, and ~/.agents/AGENTS.md comes from the target account. The normal remote workdir loader deliberately uses the local filesystem for personal instructions and the target filesystem only for project instructions, so the completion reminder can omit the user's active local directives and inject remote-home directives that were absent from the system prompt. Preserve that split filesystem provenance here as well.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

Comment on lines +519 to +522
if (
relativePath !== '' &&
!relativePath.startsWith('..') &&
!semantics.isAbsolute(relativePath)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Distinguish dot-prefixed names from parent traversal

For an in-workspace file whose basename starts with two dots, such as /workspace/..env, relative() returns ..env; this condition treats it as outside the workspace and stores the absolute path as the history key. Before this change the path was normalized to ..env, so contentAt(turnId, '..env') now returns no entry and change payloads unexpectedly expose an absolute path. Reject only .. itself or a ..${separator} prefix rather than every relative path beginning with ...

AGENTS.md reference: AGENTS.md:L111-L113

Useful? React with 👍 / 👎.

Comment on lines +282 to +283
} else if (targetKey === 'environments' && isPlainObject(value)) {
result[targetKey] = transformRecord(value, transformPlainObject);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Salvage valid environment entries independently

When loadRuntimeConfigSafe reads multiple [environments.*] entries and any one entry is invalid, this new record section produces a validation path such as environments.bad, but salvageConfigData still treats only providers and models as entry-keyed. It therefore deletes the entire environments section, including a valid configured default, and session creation falls back to local instead of the requested remote target. Add environments to the entry-level salvage handling so one malformed sibling cannot discard valid execution-target configuration.

AGENTS.md reference: AGENTS.md:L131-L133

Useful? React with 👍 / 👎.

Comment on lines +89 to +90
for (let offset = 0; offset < data.byteLength; offset += FS_WRITE_FILE_CHUNK_BYTES) {
await this.writeMode(path, data.subarray(offset, offset + FS_WRITE_FILE_CHUNK_BYTES), offset === 0 ? mode : 'append');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Make each large remote write indivisible

For payloads larger than 1 MiB, one logical writeText or writeBytes operation becomes an independently dispatched truncate followed by multiple append RPCs, while the executor runs filesystem requests concurrently. If parallel agents or prompt materializations write the same path, their chunks can interleave after either truncate, leaving a successful operation with bytes from both payloads rather than normal last-writer-wins behavior. Use an upload handle/temporary file plus rename, or otherwise serialize the complete write per path before exposing it.

AGENTS.md reference: AGENTS.md:L131-L133

Useful? React with 👍 / 👎.

Comment on lines +2071 to +2072
if (environment === undefined) {
return { ...binding, type: binding.environmentId === 'local' ? 'local' : 'command', status: 'disposed' };

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the declared type after disposal

When a session remains bound to a declared SSH or Docker environment after its provider is disposed or fails to register, environment is undefined and this branch reports its public type as command. The checked-in provider-disposal flow already leaves such a binding with status disposed, so getEnvironmentState() returns misleading type information to SDK and TUI consumers precisely when diagnosing the unavailable target. Resolve the declaration before this early return and derive the disposed entry's actual type.

AGENTS.md reference: AGENTS.md:L123-L125

Useful? React with 👍 / 👎.

# Conflicts:
#	apps/kimi-code/test/cli/v2-run-print.test.ts
#	docs/en/reference/server-api.md
#	docs/zh/reference/server-api.md
#	packages/agent-core-v2/src/agent/tools/os/glob/globTool.ts
#	packages/agent-core-v2/src/agent/tools/os/grep/grepTool.ts
#	packages/agent-core-v2/src/app/mcpManagement/mcpManagementService.ts
#	packages/agent-core-v2/src/app/sessionManager/sessionManagerService.ts
#	packages/agent-core-v2/src/environment/fakeEnvironment.ts
#	packages/agent-core-v2/src/environment/localEnvironment.ts
#	packages/agent-core-v2/src/features/plan/planService.ts
#	packages/agent-core-v2/src/features/plan/tools/enter-plan-mode/enterPlanModeTool.ts
#	packages/agent-core-v2/src/os/backends/node-local/tools/rgLocator.ts
#	packages/agent-core-v2/src/os/interface/terminal.ts
#	packages/agent-core-v2/src/program/program.ts
#	packages/agent-core-v2/src/program/programDependencies.ts
#	packages/agent-core-v2/src/runtime/runtime.ts
#	packages/agent-core-v2/src/runtime/standaloneRuntime.ts
#	packages/agent-core-v2/src/session/terminal/terminalService.ts
#	packages/agent-core-v2/src/workspace/workspaceInstance/workspaceInstanceManagerService.ts
#	packages/agent-core-v2/test/agent/agentsMdReminder/agentsMdReminder.test.ts
#	packages/agent-core-v2/test/agent/changeNotifier/changeNotifier.test.ts
#	packages/agent-core-v2/test/agent/loop/loop.test.ts
#	packages/agent-core-v2/test/agent/runtimeBinding/runtimeBindingService.test.ts
#	packages/agent-core-v2/test/app/sessionManager/sessionManagerService.test.ts
#	packages/agent-core-v2/test/features/plan/planGuard.test.ts
#	packages/agent-core-v2/test/runtime/architectureBoundaries.test.ts
#	packages/agent-core-v2/test/runtime/runtimeRegistry.test.ts
#	packages/agent-core-v2/test/session/subagent/forkParity.test.ts
#	packages/agent-core-v2/test/session/terminal/terminalService.test.ts
#	packages/agent-core-v2/test/tool/tool.test.ts
#	packages/agent-core-v2/test/workspace/workspaceInstance/workspaceInstanceManager.test.ts
#	packages/kap-server/src/protocol/rest-terminal.ts
#	packages/kap-server/src/routes/terminals.ts
#	packages/kap-server/test/terminals.test.ts
@7Hanrui

7Hanrui commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fbdbf02201

ℹ️ 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".

@@ -425,11 +315,11 @@ export function registerFsRoutes(app: FsRouteHost, core: Scope): void {
operationId: 'workspaceFsSearch',
},
async (req, reply) => {
const { workspace, runtime_id, ...searchRequest } = req.body;
let runtimeFs: RuntimeFsScope | undefined;
const { workspace, ...searchRequest } = req.body;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve runtime_id on filesystem requests

When a client from the previous release sends runtime_id to /workspace/fs::search, validation now strips that field and this path always resolves the local filesystem; the same regression affects workspace/root suggestions, session FS actions, and downloads. Requests intended for a remote runtime can therefore return host data or fail without any validation signal. Continue accepting runtime_id as a deprecated environment-id alias, or reject it explicitly instead of silently ignoring it.

AGENTS.md reference: AGENTS.md:L111-L113

Useful? React with 👍 / 👎.

Comment on lines +474 to +476
lease = this.environment.acquire(['fs']);
} catch (error) {
if (error instanceof EnvironmentError) return 'unreadable';

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reconnect before capturing remote file history

When a remote connection has dropped just before an Edit or Write, acquire() throws and this catch converts the failure to unreadable, so capture() silently returns without recording the pre-edit bytes. The tool then uses acquireOrWhenReady(), reconnects, and mutates successfully, leaving the turn without an undo snapshot or change entry for that file; reconnect here before treating the file as unreadable so the first post-drop edit remains recoverable.

AGENTS.md reference: AGENTS.md:L131-L133

Useful? React with 👍 / 👎.

this.mirrors.set(target.fs, mirrors);
const path = (target.path?.join ?? join)(target.dir, `${taskId}.log`);
const mirrored = mirrors.get(path);
if (mirrored?.scope === output.root.scope && mirrored.size === output.data.byteLength) return true;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Revalidate cached remote task-output mirrors

When the target temp directory is cleared or a Docker/command target is replaced under the same environment, the remote environment keeps the same fs wrapper, so this size-only cache returns true without checking the current target. readTaskOutputSnapshot() then advertises a remote log path that no longer exists instead of falling back to the retained local output; truncated task results become unreadable beyond their preview. Include the environment generation in the cache key or stat/rewrite the mirror before returning its path.

AGENTS.md reference: AGENTS.md:L131-L133

Useful? React with 👍 / 👎.

@7Hanrui

7Hanrui commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 893f58a339

ℹ️ 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".

@@ -391,7 +438,7 @@ export interface ReloadSessionOptions {
export interface PlanInfo {
readonly id: string;
readonly content: string;
readonly path: string;
readonly path: string | null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep the SDK plan path non-nullable

When an SDK consumer upgrades and handles SessionPlan using the previously guaranteed string path (for example, passing it directly to a filesystem API), widening this public property to string | null breaks their TypeScript build. The engine's planFileTarget() still always produces a path and status() never returns null for this field, so this is an unnecessary public-contract break; retain string unless a real pathless state is implemented and released as an explicit breaking change.

AGENTS.md reference: AGENTS.md:L123-L125

Useful? React with 👍 / 👎.

@@ -17,6 +17,7 @@ export const createSessionOptionsSchema = z.object({
sessionId: z.string().optional(),
workDir: z.string(),
additionalDirs: z.array(z.string()).optional(),
environmentId: z.string().optional(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Forward the environment cwd through Klient creation

When a Klient caller explicitly selects a valid declared environment that omits defaultCwd, SessionManager.resolveCreationEnvironment() requires the caller to provide environmentCwd; however, this new wire schema accepts only environmentId, and the facade likewise cannot forward a cwd. Such environments can therefore be configured but never used through the public Klient creation API. Add the optional cwd to both the mirrored contract and facade input.

AGENTS.md reference: packages/klient/AGENTS.md:L14-L18

Useful? React with 👍 / 👎.

if (n === 0) return new Uint8Array(0);
const chunks: Uint8Array[] = [];
let total = 0;
for await (const chunk of this.readChunks(path, offset)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound remote reads to the requested length

When a remote caller requests a bounded read, this always starts readChunks() with its fixed 1 MiB RPC size and only truncates the received result afterward. The new remote download stream requests 64 KiB at a time, so a large download repeatedly transfers and decodes overlapping 1 MiB ranges—roughly 16× the required remote I/O—and even a 4 KiB binary sample can fetch 1 MiB. Pass the remaining requested length into readRange() instead of discarding the excess.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

Comment on lines +181 to +184
size: result.size,
modifiedAtMs: result.mtimeMs,
mode: result.mode & 0o7777,
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve file generation in remote metadata

When a remote file is atomically replaced by another file with the same size and preserved millisecond mtime, this metadata omits the inode that the local HostFileStat path supplies to buildEtag(). /fs::content and session downloads consequently generate the same ETag for the replacement and can return 304 Not Modified, leaving clients with stale content. Carry the inode or another generation token through FsGetMetadataResult and the remote stat mapping.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

NO_COLOR: '1',
TERM: 'dumb',
GIT_TERMINAL_PROMPT: process.env['GIT_TERMINAL_PROMPT'] ?? '0',
GIT_TERMINAL_PROMPT: '0',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the Git prompt environment override

When Kimi is started locally with an explicit GIT_TERMINAL_PROMPT value, every Bash command now overwrites it with 0. Before this refactor the tool passed the user's value through and used 0 only when unset, so upgrading silently changes local Git behavior even though adding remote execution need not alter the local path; retain the old fallback for local environments and apply any stricter default only to remote targets.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

Comment on lines 569 to +570
cwd: state.workDir,
gitBranch: this.gitCache.getStatus()?.branch ?? null,
gitBranch: isRemoteEnvironment(state.environment) ? null : (this.gitCache.getStatus()?.branch ?? null),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Pass the remote cwd to custom status lines

When status_line.command is configured for a remote-bound session, the built-in footer uses the environment binding's cwd, but this JSON payload still sends the host-local state.workDir while only suppressing the local Git branch. Custom status scripts therefore display a plausible but incorrect working directory or run their own checks against the host workspace; populate cwd from state.environment.cwd for remote sessions.

Useful? React with 👍 / 👎.

@7Hanrui

7Hanrui commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 715dbe0c31

ℹ️ 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 key = `${environment.identity.environmentId} ${environment.identity.generation}`;
let pending = remoteRgResolutions.get(key);
if (pending === undefined) {
pending = resolveRemoteRgPath(probe, environment, options).catch((error: unknown) => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep caller cancellation out of shared rg resolution

When two Glob/Grep calls concurrently bootstrap ripgrep on the same remote generation, this caches the first call's options.signal in the promise shared by both. If that first tool is canceled while either version probe is in flight on a target without system ripgrep, a subsequent throwIfAborted rejects the shared promise, so the still-active second tool incorrectly fails too. Keep cancellation in the per-caller abortable(...) wrapper and run the cached resolution without a request-scoped signal.

Useful? React with 👍 / 👎.

const local = this.environments.inspect(this.binding);
const context: IWorkspaceContext = { ...this.context, cwd };
const state = own(new WorkspaceStateService(this.dependencies.appState));
const localConfig = new FileProjectLocalConfigService(this.dependencies.bootstrap, hostFs);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Expand remote additional dirs against the target home

When a remote session is created with --add-dir ~/shared, this constructs the remote FileProjectLocalConfigService with the host-local bootstrap. Its expandHome() therefore turns ~ into the client machine's home and then checks that path through the remote filesystem, so creation either fails or authorizes an unrelated coincident path. Pass the target environment's home directory, or an environment-specific path resolver, to preserve ~ semantics for creation-time additional directories.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

Comment on lines +136 to +138
program: resolveProgramPath(spec.program),
args: [...(spec.args ?? [])],
env: commandLauncherEnv(spec.env),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve command launchers against their configured PATH

When a command environment supplies env.PATH so that its named launcher is discoverable—for example command = "sandbox" with the binary only under /opt/sandbox/bin—this resolves the program against the ambient process PATH before constructing the configured launcher environment. Resolution therefore throws “not found” even though the spawned child would receive the requested PATH; compute the launcher environment first and pass its PATH to resolveProgramPath.

Useful? React with 👍 / 👎.

);
}
return {
dir: environment.path.join(tempDir, 'kimi-code', subdir),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Isolate remote staged attachments before cleanup

When two sessions on the same remote environment prepare the same uploaded file concurrently, they use the same deterministic path under this shared $TMP/kimi-code/attachments directory. If request A writes the file, request B observes its matching size and enqueues a prompt referencing it, and A then fails later, A's discardStaged() deletes the path out from under B, leaving B's prompt with a missing attachment. Namespace remote staging by session or request, as the local session directory did, or make cleanup reference-safe.

AGENTS.md reference: AGENTS.md:L127-L129

Useful? React with 👍 / 👎.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants