fix(rebase): migrate the replayed E2B branch onto the rebased parent

The linear replay carried old-lineage content over parent-owned files;
this checkpoint restores them and adapts the branch to the parent's
post-rebase seam:

- restore all pty/lsp/subprocess/code-runtime surfaces to the parent's
  exact content (this branch claims none of them) and drop the net-zero
  code-runtime-e2b/pty-e2b/lsp-e2b residue and its registrations
- widen serializeRemoteEnvironment to the seam's NodeJS.ProcessEnv
  tombstone contract: an explicit undefined removes an ambient entry
- migrate the two E2B fixture Agent stubs to the Inbox-model interface
  and Session.create
- re-apply the branch's gen-doc-graphs roles, THIRD_PARTY_NOTICES e2b
  row, and packages/README group row (trimmed to the doc budget);
  regenerate catalogs and re-record bilingual pairings
This commit is contained in:
Tianyi Cui
2026-08-07 21:04:33 +08:00
parent 6e1ae76c9b
commit d488330ba4
40 changed files with 1043 additions and 446 deletions

View File

@@ -3,6 +3,7 @@ import { join, posix } from 'node:path'
import { fileURLToPath } from 'node:url'
import { Context } from 'cordis'
import { describe, expect, it } from 'vitest'
import { Inbox } from '@deepseek-ai/dsh-agent'
import type { Agent } from '@deepseek-ai/dsh-agent'
import { runLoaderSmoke } from '@deepseek-ai/dsh-loader-smoke'
import {
@@ -78,18 +79,20 @@ describe.skipIf(!process.env.E2B_API_KEY)('E2B live Loader composition', () => {
expect(environmentProbe.collected.stdout?.readFrom(0).text).toBe('DIRECT=<> LEAK=<0>\n')
await expect(sandbox.files.read(profileLeakPath)).rejects.toBeInstanceOf(FileNotFoundError)
const ownerId = SessionId('e2b-pty-env-owner')
const ownerSession = Session.create(ownerId)
const owner: Agent = {
id: ownerId,
options: {},
session: new Session(ownerId),
session: ownerSession,
inbox: new Inbox(ownerSession, { inserted: () => {}, discarded: () => {}, claimed: () => {} }),
status: 'idle',
acceptsNextStep: false,
ctx,
followup() {},
steer: () => ({ outcome: Promise.resolve({ status: 'rejected' as const }) }),
inject() {},
send() {},
followup() {},
steer() {},
inject() {},
cancel() {},
runMaintenance: task => task(new AbortController().signal),
whenIdle: () => Promise.resolve(),
}
const backend = new LocalPtyBackend(ctx, {

View File

@@ -84,19 +84,21 @@ export function bootstrapEnvironment(raw: string): Record<string, string> {
/**
* Overlay explicit entries and serialize one validated E2B environment.
* @param raw - The complete NUL-delimited remote environment.
* @param explicit - Deliberate caller overrides applied after ambient scrubbing.
* @param explicit - Deliberate caller overrides applied after ambient scrubbing; an `undefined` tombstone removes an ambient entry.
* @returns NUL-delimited `name=value` entries accepted by `env -i`.
*/
export function serializeRemoteEnvironment(
raw: string,
explicit: Readonly<Record<string, string>> | undefined,
explicit: Readonly<NodeJS.ProcessEnv> | undefined,
): string {
const environment = scrubRemoteEnvironment(raw)
for (const [name, value] of Object.entries(explicit ?? {})) {
if (name.length === 0 || name.includes('=') || name.includes('\0') || value.includes('\0')) {
if (name.length === 0 || name.includes('=') || name.includes('\0') || value?.includes('\0') === true) {
throw new Error('subprocess-e2b: environment entries require non-empty NUL-free names without = and NUL-free values')
}
environment.set(name, value)
// An explicit undefined is the seam's tombstone: remove the ambient entry.
if (value === undefined) environment.delete(name)
else environment.set(name, value)
}
return [...environment].map(([name, value]) => `${name}=${value}\0`).join('')
}