Skip to content

Commit e51fc5b

Browse files
committed
Ship isolated hosts and on-demand transcript recall
1 parent 9d2e0d9 commit e51fc5b

61 files changed

Lines changed: 3471 additions & 345 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,54 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [0.0.6] - 2026-07-24
11+
12+
### Added
13+
14+
- Linux systemd hosts now give every ordinary channel a persistent
15+
unprivileged LXC computer with private networking, subordinate UID/GID maps,
16+
exact owner markers, managed CPU/RAM, host-mirrored files, and no host-home
17+
mount. The installer pins and verifies the Ubuntu Noble image payloads and
18+
installs a narrow root-owned lifecycle boundary.
19+
- The accepted Windows 11 implementation now has a native x64 desktop and one
20+
private WSL 2 Ubuntu distribution per ordinary channel. Windows-drive
21+
automount and process interop are disabled, Ubuntu root filesystems are
22+
immutable-version and SHA-256 pinned for x64 and arm64, and setup/removal
23+
verify exact ownership. Its public installer is withheld until Authenticode
24+
signing is available.
25+
- Durable feedback is always visible, accepts optional attachments and
26+
diagnostics, retries central delivery, and provides a Captain feedback inbox.
27+
- Residents can search their own authoritative raw channel transcripts by
28+
meaning, exact text/date, or recency and hydrate a complete prior session on
29+
demand. Each channel keeps a separate Mnemosyne index; guests and other
30+
channel residents cannot access it.
31+
- SkillsMD discovery now queries the open registry directly and displays every
32+
returned result. Revision pinning, bounds, scanning, hashing, and runtime
33+
wrapping remain enforced when the user chooses to install.
34+
35+
### Changed
36+
37+
- Linux and Windows are production isolation backends; the in-process native
38+
computer remains an explicit development/test seam rather than the default.
39+
- Linux update requests are executed entirely by the host. The verified root
40+
updater migrates runtime files and systemd units transactionally, refuses
41+
downgrades, health-checks for up to one minute, and reports rollback complete
42+
only after the restored host answers its health endpoint.
43+
- The website and documentation describe the macOS, Linux/LXC, and Windows/WSL
44+
product contracts without claiming an unsigned Windows artifact is public.
45+
46+
### Fixed
47+
48+
- Newly created and newly assigned skills now appear immediately in already
49+
open Arsenal and Channel Settings views without a page refresh.
50+
- Linux upgrades migrate existing compatibility computer records to LXC while
51+
retaining channels, workspaces, obligations, and durable application state.
52+
- Fresh/repeat Linux installs reject unsafe rollback symlinks and install
53+
Python venv support required by long-term Mnemosyne memory.
54+
- App removal, fleet inspection, lifecycle commands, terminal execution,
55+
workspace synchronization, and ownership refusal now cover Apple, LXC, and
56+
WSL backends consistently.
57+
1058
## [0.0.5] - 2026-07-24
1159

1260
### Changed
@@ -133,7 +181,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
133181
notarization, stapled tickets, Gatekeeper verification, persistent
134182
Application Support, and isolated Apple container machines.
135183

136-
[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.5...HEAD
184+
[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.6...HEAD
185+
[0.0.6]: https://github.com/gitcommit90/1Helm/releases/tag/v0.0.6
137186
[0.0.5]: https://github.com/gitcommit90/1Helm/releases/tag/v0.0.5
138187
[0.0.4]: https://github.com/gitcommit90/1Helm/releases/tag/v0.0.4
139188
[0.0.3]: https://github.com/gitcommit90/1Helm/releases/tag/v0.0.3

README.md

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ release acceptance tests.
2929
<p align="center">
3030
<code>Apple Silicon native</code>&nbsp;&nbsp;
3131
<code>34 complete playbooks</code>&nbsp;&nbsp;
32-
<code>Focused SkillsMD catalog</code>&nbsp;&nbsp;
32+
<code>Open SkillsMD search</code>&nbsp;&nbsp;
3333
<code>Signed + notarized</code>
3434
</p>
3535

@@ -102,7 +102,7 @@ The model receives a compact inventory of the arsenal—not all 34 procedures in
102102
every prompt. It can inspect metadata and load one complete skill when useful.
103103
It can also:
104104

105-
- search the focused SkillsMD catalog of ready GitHub-backed repositories;
105+
- search SkillsMD directly without a 1Helm-curated subset, then inspect and install a selected GitHub-backed skill;
106106
- install ready skills only after immutable revision pinning, bounds,
107107
scanning, hashing, provenance storage, and runtime-authority wrapping;
108108
- route sources without a ready repository-specific procedure through the
@@ -154,6 +154,11 @@ does not run on the laptop or phone viewing the web UI.
154154
- Exactly one resident for every ordinary channel and one Skipper in `#main`.
155155
- A persistent Apple `container machine` Linux VM per ordinary channel, with
156156
`home-mount=none`, on supported Apple Silicon Macs.
157+
- A persistent unprivileged LXC per ordinary channel on supported Linux
158+
systemd hosts, with subordinate UID/GID mapping and exact ownership checks.
159+
- A private WSL 2 Ubuntu distribution per ordinary channel in the accepted
160+
Windows implementation, with Windows-drive mounts and interop disabled. Its
161+
public installer remains withheld until Authenticode signing is available.
157162
- Shared channel `/workspace` for the agent command surface and human Terminal.
158163
- Durable files, threads, curated memory, Mnemosyne long-term recall,
159164
corrections, follow-ups, and recurring workflows.
@@ -176,12 +181,12 @@ does not run on the laptop or phone viewing the web UI.
176181
| Platform | Current contract |
177182
|---|---|
178183
| **Apple Silicon macOS 26** | Native desktop product and real isolated Linux computer per resident. |
179-
| **Linux / CI** | Durable headless compatibility backend; not per-resident VM isolation. |
180-
| **Windows + WSL** | Headless compatibility path; not a native Windows app. |
184+
| **Linux / CI** | Supported headless systemd host with one unprivileged LXC per resident; CI may select an explicit test backend. |
185+
| **Windows + WSL** | Native x64 Electron and private WSL 2 worlds have passed real-host acceptance; the public installer awaits Authenticode signing. |
181186

182-
Not yet shipped: native Windows and Linux desktop packages, mobile clients,
183-
Linux resident VM isolation, a hosted control plane, rich Photon attachment
184-
fidelity, or blind execution of community skills.
187+
Not yet shipped: a signed public Windows installer, a native Linux desktop
188+
shell, mobile clients, a hosted control plane, rich Photon attachment fidelity,
189+
or blind execution of community skills.
185190

186191
## Install on Apple Silicon
187192

@@ -212,8 +217,8 @@ checkouts remain operator-managed and never send a Mac installer to the browser.
212217

213218
## Run the source workspace
214219

215-
The native Mac app is the complete consumer product. For development and
216-
headless compatibility deployments, use Node 22:
220+
For development or a source deployment outside the verified platform
221+
installers, use Node 22:
217222

218223
```bash
219224
PUPPETEER_SKIP_DOWNLOAD=1 npm install
@@ -230,8 +235,8 @@ A fresh data directory opens first-run setup. The source runtime defaults to
230235
|---|---|---|
231236
| `PORT` | `8123` | HTTP/WebSocket control-plane port. |
232237
| `CTRL_DATA_DIR` | `./data` | Databases, routing state, uploads, and narrow workspace mirrors. |
233-
| `HELM_CHANNEL_COMPUTER_BACKEND` | `apple` on macOS, `native` elsewhere | Explicit development/test backend override. |
234-
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.5` | Versioned Apple channel-machine image. |
238+
| `HELM_CHANNEL_COMPUTER_BACKEND` | `apple` on macOS, `lxc` on Linux, `wsl` on Windows | Host isolation backend; `native` and `mock` are explicit development/test overrides. |
239+
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.6` | Versioned channel-machine image contract. |
235240

236241
### Agent-first JSON CLI
237242

@@ -249,20 +254,21 @@ npm run helm -- audit-verify
249254

250255
## Architecture
251256

252-
1Helm is a compact Node/TypeScript control plane hosted by Electron on macOS.
253-
It does not need an external database or a server transpilation step.
257+
1Helm is a compact Node/TypeScript control plane hosted by Electron on macOS
258+
and in the accepted Windows implementation, or by systemd on Linux. It does not
259+
need an external database or a server transpilation step.
254260

255261
| Layer | Implementation |
256262
|---|---|
257263
| Runtime | Official Node 22 with native TypeScript stripping. |
258264
| Control plane | `node:http`, WebSocket, additive SQLite migrations. |
259265
| Client | Vanilla TypeScript bundled with esbuild and Tailwind CSS. |
260266
| Model routing | Embedded ReRouted headless engine, private internal gateway, account pools, retries, routes, quotas, and logs. |
261-
| Computers | Defensive argv-only Apple `container machine` backend; explicit compatibility backend elsewhere. |
267+
| Computers | Defensive argv-only Apple `container machine`, narrow root-owned unprivileged LXC, and private WSL 2 backends; explicit `native`/`mock` test seams. |
262268
| Terminal | `node-pty`; ordinary terminals enter their channel VM while Skipper remains native. |
263269
| Memory | Curated records with provenance plus an isolated Mnemosyne SQLite store per identity. |
264270
| Scheduling | Durable obligations, wake reconciliation, lifecycle safety, repair, update, and pressure-aware sizing. |
265-
| Desktop | Sandboxed Electron renderer, ephemeral loopback server, persistent Application Support, native wake agent. |
271+
| Desktop | Sandboxed Electron renderer, ephemeral loopback server, persistent host data, and native wake/update integration on supported desktop hosts. |
266272

267273
Start with [`docs/VISION.md`](docs/VISION.md) for the product record and
268274
[`docs/architecture`](https://1helm.com/docs/architecture) for the readable
@@ -286,7 +292,9 @@ or a complete security score.
286292

287293
## Security boundary
288294

289-
- Resident Macs use separate Linux VMs with no native Mac home mount.
295+
- Residents use separate Linux worlds: Apple machines with no Mac home mount,
296+
unprivileged LXC with subordinate host IDs, or private WSL 2 distributions
297+
with Windows-drive mounts and interop disabled.
290298
- Skipper's host tools require Captain-authorized provenance.
291299
- Workspace file mirrors are channel-scoped, size-bounded, and symlink-contained.
292300
- Provider and connection credentials stay in host-owned storage.
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
CREATE TABLE IF NOT EXISTS workspaces (
2+
slug TEXT PRIMARY KEY,
3+
hostname TEXT NOT NULL UNIQUE,
4+
installation_id TEXT NOT NULL UNIQUE,
5+
workspace_name TEXT NOT NULL,
6+
management_secret_hash TEXT NOT NULL,
7+
tunnel_id TEXT NOT NULL DEFAULT '',
8+
connector_secret_cipher TEXT NOT NULL DEFAULT '',
9+
status TEXT NOT NULL DEFAULT 'provisioning',
10+
enabled INTEGER NOT NULL DEFAULT 1,
11+
error TEXT NOT NULL DEFAULT '',
12+
created_at INTEGER NOT NULL,
13+
updated_at INTEGER NOT NULL
14+
);
15+
16+
CREATE INDEX IF NOT EXISTS idx_workspaces_installation ON workspaces(installation_id);
17+
18+
CREATE TABLE IF NOT EXISTS feedback_reports (
19+
public_id TEXT PRIMARY KEY,
20+
installation_id TEXT NOT NULL,
21+
workspace_name TEXT NOT NULL DEFAULT '',
22+
comment TEXT NOT NULL,
23+
diagnostics TEXT NOT NULL DEFAULT '{}',
24+
attachment_count INTEGER NOT NULL DEFAULT 0,
25+
created_at INTEGER NOT NULL,
26+
received_at INTEGER NOT NULL
27+
);
28+
29+
CREATE INDEX IF NOT EXISTS idx_feedback_received ON feedback_reports(received_at DESC);
30+
31+
CREATE TABLE IF NOT EXISTS feedback_attachments (
32+
id INTEGER PRIMARY KEY AUTOINCREMENT,
33+
report_id TEXT NOT NULL REFERENCES feedback_reports(public_id) ON DELETE CASCADE,
34+
name TEXT NOT NULL,
35+
mime TEXT NOT NULL,
36+
size INTEGER NOT NULL,
37+
data TEXT NOT NULL,
38+
created_at INTEGER NOT NULL
39+
);

cloudflare/src/worker.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -174,7 +174,11 @@ async function feedbackIntake(request: Request, env: Env): Promise<Response> {
174174
for (const attachment of attachments) {
175175
const size = Number(attachment.size || 0);
176176
const data = String(attachment.data || "");
177-
if (size < 0 || size > 5 * 1024 * 1024 || data.length > 7 * 1024 * 1024) {
177+
const validBase64 = data.length % 4 === 0 && /^[A-Za-z0-9+/]*={0,2}$/.test(data);
178+
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
179+
const decodedSize = data.length ? (data.length / 4) * 3 - padding : 0;
180+
if (!Number.isSafeInteger(size) || !validBase64 || decodedSize !== size
181+
|| size < 0 || size > 5 * 1024 * 1024 || data.length > 7 * 1024 * 1024) {
178182
return json({ error: "A feedback attachment is too large." }, 413);
179183
}
180184
total += size;

deploy/1helm-lxc-unprivileged.conf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# 1Helm channel computers are user-namespaced Ubuntu containers. Root inside a
2+
# channel maps to an unprivileged host subuid/subgid and each container receives
3+
# only the private LXC bridge—not a host directory or device passthrough.
4+
lxc.include = /usr/share/lxc/config/ubuntu.userns.conf
5+
lxc.idmap = u 0 100000 65536
6+
lxc.idmap = g 0 100000 65536
7+
lxc.apparmor.profile = generated
8+
lxc.apparmor.allow_nesting = 0
9+
lxc.mount.auto = proc:mixed sys:ro cgroup:mixed
10+
lxc.net.0.type = veth
11+
lxc.net.0.link = lxcbr0
12+
lxc.net.0.flags = up
13+
lxc.net.0.name = eth0
14+
lxc.start.auto = 0

desktop/main.cjs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,26 @@ let quitting = false;
1717
let hostUpdateService = null;
1818
const remoteWorkspacePath = () => path.join(app.getPath("userData"), "remote-workspace");
1919

20+
function handleSquirrelEvent() {
21+
if (process.platform !== "win32") return false;
22+
const event = process.argv[1];
23+
if (!["--squirrel-install", "--squirrel-updated", "--squirrel-uninstall", "--squirrel-obsolete"].includes(event)) return false;
24+
const appFolder = path.resolve(process.execPath, "..");
25+
const updateExe = path.resolve(appFolder, "..", "Update.exe");
26+
const exe = path.basename(process.execPath);
27+
if (event === "--squirrel-install" || event === "--squirrel-updated") {
28+
spawnSync(updateExe, ["--createShortcut", exe], { stdio: "ignore", windowsHide: true });
29+
} else if (event === "--squirrel-uninstall") {
30+
const dataRoot = path.join(String(process.env.APPDATA || ""), "1Helm");
31+
const wslRoot = path.join(path.dirname(dataRoot), "1Helm-WSL");
32+
const cleanup = path.resolve(__dirname, "..", "scripts", "windows-removal.cjs");
33+
spawnSync(process.execPath, [cleanup, dataRoot, wslRoot], { env: { ...process.env, ELECTRON_RUN_AS_NODE: "1" }, stdio: "ignore", windowsHide: true, timeout: 10 * 60_000 });
34+
spawnSync(updateExe, ["--removeShortcut", exe], { stdio: "ignore", windowsHide: true });
35+
}
36+
setTimeout(() => app.quit(), 1000);
37+
return true;
38+
}
39+
2040
function preferredWorkspaceOrigin() {
2141
try {
2242
const value = fs.readFileSync(remoteWorkspacePath(), "utf8").trim();
@@ -74,7 +94,7 @@ async function startLocalRuntime() {
7494
process.env.HELM_HOST = LOOPBACK;
7595
process.env.PORT = String(port);
7696
process.env.CTRL_DATA_DIR = app.getPath("userData");
77-
process.env.SHELL ||= "/bin/zsh";
97+
if (process.platform !== "win32") process.env.SHELL ||= "/bin/zsh";
7898
process.env.HELM_INTERNAL_WAKE_TOKEN ||= crypto.randomBytes(32).toString("hex");
7999
process.chdir(appRoot);
80100
localOrigin = `http://${LOOPBACK}:${port}`;
@@ -97,6 +117,13 @@ function keepSkipperAvailable() {
97117
app.setLoginItemSettings({ openAtLogin: true, type: "mainAppService" });
98118
}
99119

120+
function prepareWindowsWslDataRoot() {
121+
if (process.platform !== "win32") return;
122+
// Per-channel virtual disks stay outside the replaceable application
123+
// directory and beside the durable Electron userData directory.
124+
fs.mkdirSync(path.join(path.dirname(app.getPath("userData")), "1Helm-WSL"), { recursive: true });
125+
}
126+
100127
function allowedLocalUrl(raw) {
101128
try {
102129
const url = new URL(raw);
@@ -181,7 +208,10 @@ function createWindow(showWhenReady = true) {
181208
mainWindow = window;
182209
}
183210

184-
if (!app.requestSingleInstanceLock()) {
211+
if (handleSquirrelEvent()) {
212+
// Squirrel install/update/uninstall work must exit before the application
213+
// acquires its normal single-instance lock or starts the local server.
214+
} else if (!app.requestSingleInstanceLock()) {
185215
app.quit();
186216
} else {
187217
app.on("second-instance", (_event, argv) => {
@@ -193,6 +223,7 @@ if (!app.requestSingleInstanceLock()) {
193223
});
194224

195225
app.whenReady().then(async () => {
226+
if (process.platform === "win32") app.setAppUserModelId("com.squirrel.1Helm.1Helm");
196227
session.defaultSession.setPermissionRequestHandler((_webContents, _permission, callback) => callback(false));
197228
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
198229
if (!allowedLocalUrl(details.url)) {
@@ -211,6 +242,7 @@ if (!app.requestSingleInstanceLock()) {
211242
try {
212243
removeLegacyWakeLaunchAgent();
213244
keepSkipperAvailable();
245+
prepareWindowsWslDataRoot();
214246
hostUpdateService = createNativeUpdateService({ app, autoUpdater });
215247
hostUpdateService.initialize();
216248
globalThis[Symbol.for("1helm.nativeUpdater")] = {
@@ -227,7 +259,7 @@ if (!app.requestSingleInstanceLock()) {
227259
await dialog.showMessageBox({
228260
type: "error",
229261
title: "1Helm could not start",
230-
message: "The local 1Helm runtime could not start on this Mac.",
262+
message: `The local 1Helm runtime could not start on this ${process.platform === "win32" ? "Windows PC" : "Mac"}.`,
231263
detail: error instanceof Error ? error.stack || error.message : String(error),
232264
});
233265
app.quit();

desktop/updater.cjs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,24 @@ function createNativeUpdateService({ app, autoUpdater, platform = process.platfo
2121
let busy = false;
2222
let initialTimer = null;
2323
let intervalTimer = null;
24+
const nativeMode = platform === "win32" ? "native-windows" : "native-macos";
2425
let state = {
25-
mode: "native-macos",
26+
mode: nativeMode,
2627
status: "idle",
2728
current_version: app.getVersion(),
2829
version: null,
2930
checked_at: null,
3031
error: null,
31-
message: "Check for a signed 1Helm update on this Mac.",
32+
message: `Check for a signed 1Helm update on this ${platform === "win32" ? "Windows PC" : "Mac"}.`,
3233
};
3334

3435
let inApplications = true;
3536
if (platform === "darwin" && typeof app.isInApplicationsFolder === "function") {
3637
try { inApplications = app.isInApplicationsFolder(); } catch { inApplications = false; }
3738
}
38-
const supported = platform === "darwin" && arch === "arm64" && app.isPackaged === true && inApplications;
39-
const feedUrl = `https://update.electronjs.org/gitcommit90/1Helm/darwin-arm64/${encodeURIComponent(app.getVersion())}`;
39+
const feedPlatform = platform === "win32" && arch === "x64" ? "win32-x64" : "darwin-arm64";
40+
const supported = app.isPackaged === true && ((platform === "darwin" && arch === "arm64" && inApplications) || (platform === "win32" && arch === "x64"));
41+
const feedUrl = `https://update.electronjs.org/gitcommit90/1Helm/${feedPlatform}/${encodeURIComponent(app.getVersion())}`;
4042

4143
const snapshot = () => ({ ...state });
4244
const setState = (patch) => { state = { ...state, ...patch }; };
@@ -51,7 +53,7 @@ function createNativeUpdateService({ app, autoUpdater, platform = process.platfo
5153
? "Move 1Helm to Applications to enable host updates."
5254
: null,
5355
message: app.isPackaged
54-
? "Signed automatic updates are available for Apple Silicon macOS hosts."
56+
? "Signed automatic updates are available for supported macOS and Windows hosts."
5557
: "Development builds are updated from their source checkout.",
5658
});
5759
return false;
@@ -120,7 +122,7 @@ function createNativeUpdateService({ app, autoUpdater, platform = process.platfo
120122
if (state.status !== "ready") {
121123
return { ...snapshot(), error: "No downloaded host update is ready." };
122124
}
123-
setState({ status: "installing", error: null, message: "1Helm is restarting this Mac host to install the verified update…" });
125+
setState({ status: "installing", error: null, message: `1Helm is restarting this ${platform === "win32" ? "Windows" : "Mac"} host to install the verified update…` });
124126
process.env.HELM_UPDATE_INSTALLING = "1";
125127
return snapshot();
126128
}

docs/RELIABILITY.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ ownership, recovery, and tests rather than depend on a prompt alone.
2323
- The prompt names the size and categories of the skill arsenal. Skill metadata
2424
is available through `list_skills`; a complete procedure enters context only
2525
when the model calls `read_skill` for that skill.
26+
- A resident can search its own raw prior-session transcripts and hydrate a
27+
returned session in full. These are factual tools, not an injected rule about
28+
when the model must recall history; cross-channel access is server-rejected.
2629
- Channel memory and agent recall are provenance-bearing reference data, never
2730
higher-priority instructions.
2831
- `ask_user` is a validated tool for evidenced human judgment, credentials,

0 commit comments

Comments
 (0)