import { NodeLspManager } from "@sema-agent/core"; import { TaskEnvRegistry } from "../capabilities/sandbox-file-send.js"; import { SessionEnvironmentSelection } from "../capabilities/select-environment-tool.js"; import type { ServiceConfig } from "../config.js"; import type { Logger } from "../observability/logger.js"; import type { Metrics } from "../observability/metrics.js"; import { PerTaskImageRegistry } from "../per-task-image.js"; import type { DeviceEnrollment } from "../device-enrollment.js"; import type { DeviceStore } from "../device-store.js"; import type { DeviceWsHub } from "../device-ws-hub.js"; import { type TaskAttachmentStore } from "../plugins/task-attachment-store.js"; /** * per-session 的 **cwd / shellEnv 登记簿**(TOC cwd seam + R-survey shellEnv seam)+ 它们的**租户围栏**。 * * ── 为什么围栏不是可选的(codex R1-F1,判真)──────────────────────────────────────────────────── * 这两张表只按 `sessionId` 键控。在 host lane 上那是安全的:那条车道的闸带 `requirePrincipal !== true`, * 整台机器就一个用户。**device lane 把这个前提撤掉了** —— 它的闸(`deviceCwdHonored`)刻意不要求单用户 * (员工各连各的设备)。于是一条真实的超范围路径出现了: * ① sessionId 由**调用方自选**,而且 `DELETE /v1/sessions/:id` 之后可以被**别人** `register` 重新登记 * (`boot/session-faces.ts` 的化身围栏注逐字写着这件事); * ② 会话删除的级联删的是库里的行,**够不着这两张进程内的表**; * ③ 新主如果没有重发 `settings.env`,工厂读到的就是**上一位主人的** shell env —— 那是秘密面(NPM_TOKEN * 一类),而且会被注进新主自己设备上的指令;cwd 也会跨化身残留。 * * 所以围栏做在**身份已知**的那一处:每次提交经 resolve-spec 调 {@link SessionScopedRegistries.fenceSessionOwner} * 对拍,换主即清。删除侧再加一条 {@link SessionScopedRegistries.dropSession}(E21 级联)——那条不是为了 * 关这个洞(读面必经提交、提交必过围栏),而是**右侧删除权**:一次删除之后,秘密不该还留在进程内存里。 * * ⚠️ 诚实残余(如实登记,不假装关掉):两张表是**每副本进程内**的。围栏与清除都只在处理该请求/该删除的 * 那一个副本上发生。跨副本的陈值靠「读面必经本副本的一次提交 ⇒ 必过本副本的围栏」兜住 —— 这条推理成立 * 是因为 run 在受理它的那个副本上执行(ARCHITECTURE 副本模型)。device lane 的部署形本身也是单副本(§4.6)。 */ export interface SessionScopedRegistries { readonly perSessionCwd: Map; readonly perSessionShellEnv: Map>; setSessionCwd(sid: string, cwd: string): void; setSessionShellEnv(sid: string, env: Record): void; /** 记录/对拍本会话的主人。与上次不同 ⇒ **先清两张表**再记新主(换主即清)。`null` = 匿名会话, * 它同样是一个**稳定**的身份值(不是「未知」),所以匿名重入不会被判成换主。 */ fenceSessionOwner(sid: string, owner: string | null): void; /** 会话删除:两张表 + 身份记录一起清(留着身份记录会让同 id 的下一位与一个幽灵身份比对成「同主」)。 */ dropSession(sid: string): void; } export declare function createSessionScopedRegistries(maxEntries?: number): SessionScopedRegistries; /** device lane 的三件活对象(店 / 汇聚端 / 准入链)。缺席 = 本部署没有 device 车道。 */ export interface DeviceLaneWiring { store: DeviceStore; hub: DeviceWsHub; enrollment: DeviceEnrollment; } export interface ExecutionEnvCtx { config: ServiceConfig; logger: Logger; metrics: Metrics; taskAttachmentStore: TaskAttachmentStore | undefined; /** 车A-4:device lane 的装配件(`REMOTE_EXEC=device` 且 SQL 店在场时由 main.ts 建好传进来)。 */ deviceLane?: DeviceLaneWiring; } export declare function createExecutionEnv(ctx: ExecutionEnvCtx): { perTaskImage: PerTaskImageRegistry; sessionEnvSelection: SessionEnvironmentSelection; perSessionCwd: Map; setSessionCwd: (sid: string, cwd: string) => void; setSessionShellEnv: (sid: string, env: Record) => void; fenceSessionOwner: (sid: string, owner: string | null) => void; dropSessionScoped: (sid: string) => void; executionEnvFactory: import("@sema-agent/core").ExecutionEnvFactory | undefined; worktreeReap: (() => Promise) | undefined; sendUserFileTaskEnvs: TaskEnvRegistry | undefined; lspManager: import("../lsp/manager.js").E2bLspManager | NodeLspManager | undefined; }; //# sourceMappingURL=execution-env.d.ts.map