lembas -l / --local: everything in the project, nothing written elsewhere
Config, data and state are rooted at <project>/.agent/local/lembas (or .lembas/), kept out of git; a local directory the repository carries is refused. The user's logins and connections.yaml are read, never written; the user's config.yaml is not read. The TUI is started with TMPDIR inside it (the compiled binary unpacks its native library there) and OpenTUI's cache goes there too; the agent's commands keep the user's TMPDIR. An instance serves models, chat, speech and the balance only: the link, /remote, the service and the hub are off, deletes are not sent, and the server library, search, fetch, embeddings and personalization are not used. Self-update and uninstall are refused. hardline_disable is ignored, and tools treat the local directory as outside the project. The smoke test runs a whole local session in the compiled binary and checks that HOME, the XDG directories and TMPDIR gained nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
27 files changed
+636
-105
No files matched your search
+2
-2
@@ -20,11 +20,11 @@ export interface Driver {
|
||||
modes(): { mouse: string; altScreen: boolean; cursorHidden: boolean }
|
||||
}
|
||||
|
||||
export async function drive(opts: { cols: number; rows: number; bin?: string; home: string; cwd: string; args?: string[] }): Promise<Driver> {
|
||||
export async function drive(opts: { cols: number; rows: number; bin?: string; home: string; cwd: string; args?: string[]; env?: Record<string, string> }): Promise<Driver> {
|
||||
const cmd = opts.bin ? resolve(opts.bin) : `bun --config=${resolve(import.meta.dir, "../bunfig.toml")} ${resolve(import.meta.dir, "../src/cli.ts")}`
|
||||
const p = Bun.spawn(["script", "-qfec", `stty cols ${opts.cols} rows ${opts.rows}; exec ${cmd} ${(opts.args ?? []).join(" ")}`, "/dev/null"], {
|
||||
cwd: opts.cwd,
|
||||
env: { ...process.env, LEMBAS_HOME: opts.home, TERM: "xterm-256color" },
|
||||
env: { ...process.env, LEMBAS_HOME: opts.home, TERM: "xterm-256color", ...opts.env },
|
||||
stdin: "pipe",
|
||||
stdout: "pipe",
|
||||
})
|
||||
|
||||
+43
-1
@@ -2,7 +2,7 @@
|
||||
// Smoke test of the COMPILED binary — what `bun test` cannot see: that the bundle, OpenTUI's
|
||||
// native library and bun:sqlite FTS5 all survive `bun build --compile`.
|
||||
// bun run scripts/smoke.ts [path/to/binary]
|
||||
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"
|
||||
import { existsSync, mkdirSync, mkdtempSync, readdirSync, writeFileSync } from "node:fs"
|
||||
import { tmpdir } from "node:os"
|
||||
import { join, resolve } from "node:path"
|
||||
import { delta, fakeProvider, toolCall, usage } from "../tests/fake-provider.ts"
|
||||
@@ -192,4 +192,46 @@ for (const [cols, rows] of [
|
||||
check("/reload: the terminal is given back on quit", modes.mouse === "none" && !modes.altScreen, JSON.stringify(modes))
|
||||
}
|
||||
|
||||
// 6. local mode (-l): everything in the project, nothing written anywhere else — the user's
|
||||
// directories, HOME and TMPDIR are checked file by file before and after a whole session.
|
||||
{
|
||||
const { fake: f, home: h, cwd } = fakeHome([
|
||||
{ chunks: [delta({ content: "Writing it." }), toolCall(0, "c1", "bash", '{"command":"echo local > out.txt","description":"Write out.txt"}'), usage(900, 40)] },
|
||||
{ chunks: [delta({ content: "local done" }), usage(1200, 20)] },
|
||||
])
|
||||
const tmp = mkdtempSync(join(tmpdir(), "lembas-smoke-tmp-"))
|
||||
const listing = (dir: string): string[] => {
|
||||
const out: string[] = []
|
||||
for (const e of readdirSync(dir, { withFileTypes: true, recursive: true })) out.push(join(e.parentPath, e.name))
|
||||
return out.sort()
|
||||
}
|
||||
const before = { home: listing(h), tmp: listing(tmp) }
|
||||
const d = await drive({ cols: 100, rows: 30, bin, home: h, cwd, args: ["-l"], env: { HOME: h, TMPDIR: tmp, XDG_CONFIG_HOME: join(h, ".config"), XDG_DATA_HOME: join(h, ".local/share"), XDG_STATE_HOME: join(h, ".local/state") } })
|
||||
const firstRun = await d.waitFor("Trust this directory?")
|
||||
await d.key("\r")
|
||||
const drawn = await d.waitFor("☤ LLeMbas", 15000)
|
||||
await d.type("make a file")
|
||||
await d.key("\r")
|
||||
const asked = await d.waitFor("Allow once")
|
||||
await d.key("y")
|
||||
const answered = await d.waitFor("local done")
|
||||
await d.type("/remote")
|
||||
await d.key("\r")
|
||||
const refused = await d.waitFor("off in local mode")
|
||||
const screen = d.screen()
|
||||
await d.key("\x03")
|
||||
await d.key("\x03")
|
||||
const code = await d.close()
|
||||
f.stop()
|
||||
const after = { home: listing(h), tmp: listing(tmp) }
|
||||
const grew = [...after.home.filter((p) => !before.home.includes(p)), ...after.tmp.filter((p) => !before.tmp.includes(p))]
|
||||
const kept = ["data/sessions.db", "state/trust.json"].every((p) => existsSync(join(cwd, ".agent/local/lembas", p)))
|
||||
check(
|
||||
"local mode: a whole session works from the project",
|
||||
firstRun && drawn && asked && answered && refused && kept && code === 0 && (screen[29] ?? "").includes("local"),
|
||||
`firstRun=${firstRun} drawn=${drawn} asked=${asked} answered=${answered} refused=${refused} kept=${kept} exit=${code}\n${screen.join("\n")}`,
|
||||
)
|
||||
check("local mode: nothing written outside the project (home, XDG dirs, TMPDIR)", grew.length === 0, grew.join(", "))
|
||||
}
|
||||
|
||||
process.exit(failed ? 1 : 0)
|
||||
Reference in new issue
Block a user