Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

## [0.0.36] - 2026-08-02

### Fixed

- Kept the Windows desktop control plane available across a genuine cold WSL
reboot by deferring retained OCI directory maintenance until runtime access
or fleet reconciliation actually needs it, instead of synchronously waking
WSL before the HTTP server can listen.
- Gave the Windows desktop a bounded three-minute cold-runtime readiness window
while preserving the existing 30-second startup budget on other platforms.

## [0.0.35] - 2026-08-01

### Fixed
Expand Down Expand Up @@ -989,7 +1000,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
notarization, stapled tickets, Gatekeeper verification, persistent
Application Support, and isolated Apple container machines.

[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.35...HEAD
[Unreleased]: https://github.com/gitcommit90/1Helm/compare/v0.0.36...HEAD
[0.0.36]: https://github.com/gitcommit90/1Helm/compare/v0.0.35...v0.0.36
[0.0.35]: https://github.com/gitcommit90/1Helm/compare/v0.0.34...v0.0.35
[0.0.34]: https://github.com/gitcommit90/1Helm/compare/v0.0.33...v0.0.34
[0.0.33]: https://github.com/gitcommit90/1Helm/compare/v0.0.32...v0.0.33
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ A fresh data directory opens first-run setup. The source runtime defaults to
| `PORT` | `8123` | HTTP/WebSocket control-plane port. |
| `CTRL_DATA_DIR` | `./data` | Databases, routing state, uploads, and non-OCI development/Apple workspace mirrors. |
| `HELM_CHANNEL_COMPUTER_BACKEND` | `apple` on macOS, `oci` on Linux and Windows | Host isolation backend; `native` and `mock` are explicit development/test overrides. |
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.35` | Versioned channel-machine image contract. |
| `HELM_CHANNEL_MACHINE_IMAGE` | `local/1helm-channel-machine:0.0.36` | Versioned channel-machine image contract. |

### Agent-first JSON CLI

Expand Down
3 changes: 2 additions & 1 deletion desktop/main.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const LOOPBACK = "127.0.0.1";
// installation's Application Support/AppData tree untouched and never import
// it implicitly into this runtime generation.
const DATA_NAMESPACE = "1Helm-OCI-v1";
const SERVER_READY_TIMEOUT_MS = process.platform === "win32" ? 3 * 60_000 : 30_000;
app.setPath("userData", path.join(app.getPath("appData"), DATA_NAMESPACE));
let mainWindow = null;
let authWindow = null;
Expand Down Expand Up @@ -95,7 +96,7 @@ function freePort() {
});
}

async function waitForServer(origin, timeoutMs = 30_000) {
async function waitForServer(origin, timeoutMs = SERVER_READY_TIMEOUT_MS) {
const deadline = Date.now() + timeoutMs;
let lastError = null;
while (Date.now() < deadline) {
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "1helm",
"productName": "1Helm",
"version": "0.0.35",
"version": "0.0.36",
"private": true,
"type": "module",
"license": "AGPL-3.0-only",
Expand Down
8 changes: 5 additions & 3 deletions src/server/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export const channelRoot = hostChannelRoot;
export const channelWorkspace = channelWorkspacePath;
export const channelFiles = channelFilesPath;

export function ensureChannelWorkspace(channelId: number): string {
export function ensureChannelWorkspace(channelId: number, options: { initializeRuntimeStorage?: boolean } = {}): string {
const channel = q1("SELECT id,name,personal_main_owner_id FROM channels WHERE id=? AND status<>'deleted'", channelId);
if (!channel) throw new Error("Channel workspace not found.");
const root = channelRoot(channelId);
Expand All @@ -71,8 +71,10 @@ export function ensureChannelWorkspace(channelId: number): string {
if (channelUsesRuntimeStorage(channelId)) {
// OCI workspace/files live in runtime storage. On Windows that path is
// \\wsl.localhost\\... and host mkdir there fails; create via the WSL runtime.
try { ensureOciChannelWorkspaceDirs(channelId); }
catch { /* provision path creates these once the computer exists */ }
if (options.initializeRuntimeStorage !== false) {
try { ensureOciChannelWorkspaceDirs(channelId); }
catch { /* provision path creates these once the computer exists */ }
}
} else {
mkdirSync(channelWorkspace(channelId), { recursive: true });
mkdirSync(channelFiles(channelId), { recursive: true });
Expand Down
2 changes: 1 addition & 1 deletion src/server/channel-computers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const APPLE_RUNTIME_VERSION = "1.1.0";
export const APPLE_RUNTIME_PACKAGE = `container-${APPLE_RUNTIME_VERSION}-installer-signed.pkg`;
export const APPLE_RUNTIME_URL = `https://github.com/apple/container/releases/download/${APPLE_RUNTIME_VERSION}/${APPLE_RUNTIME_PACKAGE}`;
export const APPLE_RUNTIME_SHA256 = "0ca1c42a2269c2557efb1d82b1b38ac553e6a3a3da1b1179c439bcee1e7d6714";
export const DEFAULT_CHANNEL_IMAGE = process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.35";
export const DEFAULT_CHANNEL_IMAGE = process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.36";
const CONTAINER_CANDIDATES = [process.env.HELM_CONTAINER_CLI, "/usr/local/bin/container", "/opt/homebrew/bin/container", "container"].filter(Boolean) as string[];
const OCI_RUNTIME_VERSION = "1helm-oci-runtime-v1";
const OCI_HELPER_CANDIDATES = [
Expand Down
2 changes: 1 addition & 1 deletion src/server/db.ts
Original file line number Diff line number Diff line change
Expand Up @@ -939,7 +939,7 @@ export function migrate(): void {
const platformBackend = process.platform === "darwin" ? "apple" : "oci";
const configuredBackend = String(process.env.HELM_CHANNEL_COMPUTER_BACKEND || platformBackend);
const backend = ["apple", "oci", "native", "mock"].includes(configuredBackend) ? configuredBackend : platformBackend;
const image = String(process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.35");
const image = String(process.env.HELM_CHANNEL_MACHINE_IMAGE || "local/1helm-channel-machine:0.0.36");
for (const channel of q(`SELECT c.id FROM channels c JOIN agent_channels ac ON ac.channel_id=c.id
WHERE c.kind='channel' AND c.status<>'deleted'`)) {
const channelId = Number(channel.id);
Expand Down
7 changes: 6 additions & 1 deletion src/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2287,7 +2287,12 @@ async function bootstrap(): Promise<void> {
// Resident agents own their Linux machine, never the Captain's native Mac.
run("DELETE FROM bot_computers WHERE computer_id=? AND bot_id IN (SELECT bot_id FROM agents WHERE kind='channel')", computerId);
for (const channel of q("SELECT id FROM channels WHERE kind='channel' AND status<>'deleted'")) {
ensureChannelWorkspace(Number(channel.id));
// Retained OCI storage is already authoritative. In particular, a cold
// Windows login can take minutes to wake WSL, and that maintenance must
// never hold the HTTP control plane behind a synchronous startup probe.
// Provisioning, Files/Cowork access, Terminal, and the fleet reconciler
// initialize or verify runtime directories when they actually need them.
ensureChannelWorkspace(Number(channel.id), { initializeRuntimeStorage: false });
Comment on lines +2290 to +2295

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 12 \
  '\b(startChannelComputerReconciler|ensureOciChannelWorkspaceDirs|ensureChannelWorkspace|spawnSync)\b' \
  src/server

Repository: gitcommit90/1Helm

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- server startup ---'
sed -n '2265,2325p' src/server/index.ts

printf '%s\n' '--- reconciler symbols and call sites ---'
rg -n -C 18 \
  'export function startChannelComputerReconciler|function startChannelComputerReconciler|startChannelComputerReconciler\(|ensureOciChannelWorkspaceDirs|function ensureOciChannelWorkspaceDirs|export function ensureOciChannelWorkspaceDirs' \
  src/server/channel-computers.ts src/server/index.ts src/server/agents.ts

printf '%s\n' '--- related runtime-storage calls in the reconciler module ---'
rg -n -C 8 \
  'wake|initialize|runtime|workspace|spawnSync|spawn\(|exec|ensure.*Dirs|reconcil' \
  src/server/channel-computers.ts

Repository: gitcommit90/1Helm

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- reconciler execution path ---'
sed -n '730,835p' src/server/channel-computers.ts
sed -n '1420,1588p' src/server/channel-computers.ts

printf '%s\n' '--- connector awaited before listen ---'
rg -n -C 24 \
  'function startPhotonConnector|export function startPhotonConnector|export async function startPhotonConnector|startPhotonConnector\(' \
  src/server

printf '%s\n' '--- exact startup ordering ---'
python3 - <<'PY'
from pathlib import Path
p = Path("src/server/index.ts").read_text()
for needle in ("startChannelComputerReconciler();", "server.listen(", "await startPhotonConnector()"):
    print(needle, p.find(needle))
PY

Repository: gitcommit90/1Helm

Length of output: 33142


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
import re

index = Path("src/server/index.ts").read_text()
computers = Path("src/server/channel-computers.ts").read_text()

startup = index[index.index("startChannelComputerReconciler();"):index.index("server.listen(", index.index("startChannelComputerReconciler();"))]
reconciler = computers[computers.index("async function reconcileOne"):computers.index("export async function reconcileChannelComputers")]
helper = computers[computers.index("function ensureWindowsOciHelperInstalled"):computers.index("/**\n * Create OCI channel workspace dirs")]

checks = {
    "reconciler starts before listener": index.index("startChannelComputerReconciler();") < index.index("server.listen("),
    "listener is after awaited Photon startup": index.index("await startPhotonConnector()") < index.index("server.listen("),
    "first tick uses a delayed timer": "setTimeout(() => { reconcileStartupTimer = null; tick(); }, INITIAL_RECONCILE_MS)" in computers,
    "reconcileOne inspects isolated computer before later provisioning": "inspection = await inspectIsolated(computer)" in reconciler and reconciler.index("inspection = await inspectIsolated(computer)") < reconciler.index("await provisionChannelComputer"),
    "Windows helper installation uses synchronous WSL execution": "spawnSync(wsl, [\"--distribution\", runtime" in helper,
}
for name, value in checks.items():
    print(f"{name}: {value}")

print("startup sequence:")
for token in ("startChannelComputerReconciler();", "await startPhotonConnector()", "server.listen("):
    print(f"  {token}: {index.index(token)}")
PY

Repository: gitcommit90/1Helm

Length of output: 543


Start the reconciler after server.listen()

If Photon startup takes more than two seconds, the reconciler's initial timer can fire before the listener opens. Its OCI inspection can synchronously invoke the Windows WSL helper and wake WSL. Move startChannelComputerReconciler() after server.listen() to preserve cold-start readiness.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/server/index.ts` around lines 2290 - 2295, Move the
startChannelComputerReconciler() invocation to execute only after
server.listen() has successfully opened the HTTP listener. Keep the existing
channel workspace initialization unchanged, and ensure no reconciler timer or
OCI inspection can run before the listener is ready.

}
startImprovementLoop();
startThreadAuditLoop();
Expand Down
11 changes: 11 additions & 0 deletions test/autonomy-platform.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,17 @@ test("Cowork contracts survive follow-ups and reject only newly-created incompat
assert.equal(cowork.enforceCoworkCommandOutput(channelId, null, whiteboards, boardBefore), null);
});

test("retained OCI channel metadata can initialize without synchronously touching runtime storage", () => {
seed();
const channelId = run("INSERT INTO channels (name,slug,kind,topic,purpose,status,created) VALUES ('retained-cold-start','retained-cold-start','channel','','','active',?)", now()).lastInsertRowid;
const botId = run("INSERT INTO bots (name,model,prompt,created) VALUES ('retained-cold-agent','mock','Resident.',?)", now()).lastInsertRowid;
const agentId = run("INSERT INTO agents (bot_id,kind,name,status,created) VALUES (?,'channel','retained-cold-agent','ready',?)", botId, now()).lastInsertRowid;
run("INSERT INTO agent_channels (agent_id,channel_id,bound_at) VALUES (?,?,?)", agentId, channelId, now());
run("INSERT INTO agent_profiles (agent_id,purpose,instructions,updated) VALUES (?,'Cold start','Resident.',?)", agentId, now());
assert.doesNotThrow(() => agents.ensureChannelWorkspace(channelId, { initializeRuntimeStorage: false }));
assert.equal(q1("SELECT root_ref FROM channel_workspaces WHERE channel_id=?", channelId).root_ref, `channels/${channelId}`);
});
Comment on lines +203 to +212

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

printf '%s\n' 'Relevant files:'
git ls-files | rg '(^|/)(src/server/agents\.ts|test/autonomy-platform\.mjs)$'
printf '%s\n' 'Relevant symbols and references:'
rg -n -C 8 'ensureChannelWorkspace|channelUsesRuntimeStorage|initializeRuntimeStorage|channel-computer|runtime storage|OCI' src/server/agents.ts test/autonomy-platform.mjs

Repository: gitcommit90/1Helm

Length of output: 35958


🏁 Script executed:

git ls-files | rg '(^|/)(src/server/agents\.ts|test/autonomy-platform\.mjs)$'
rg -n -C 8 'ensureChannelWorkspace|channelUsesRuntimeStorage|initializeRuntimeStorage|channel-computer|runtime storage|OCI' src/server/agents.ts test/autonomy-platform.mjs

Repository: gitcommit90/1Helm

Length of output: 35909


🏁 Script executed:

printf '%s\n' 'Channel storage implementation:'
ast-grep outline src/server/channel-storage.ts
printf '%s\n' 'Channel computer implementation:'
ast-grep outline src/server/channel-computers.ts
printf '%s\n' 'Relevant definitions and schema references:'
rg -n -C 12 'function channelUsesRuntimeStorage|export .*channelUsesRuntimeStorage|channel_computers|runtime_storage|oci|computer' src/server/channel-storage.ts src/server/channel-computers.ts src/server/db.ts test/autonomy-platform.mjs

Repository: gitcommit90/1Helm

Length of output: 50374


🏁 Script executed:

printf '%s\n' 'Storage and initialization logic:'
sed -n '1,90p' src/server/channel-storage.ts
sed -n '200,235p' src/server/channel-computers.ts
sed -n '60,84p' src/server/agents.ts
printf '%s\n' 'Test setup and seed definition:'
rg -n -C 15 'function seed|export function seed|HELM_CHANNEL_COMPUTER_BACKEND|process\.platform|autonomy-platform' src/server/db.ts test/autonomy-platform.mjs package.json
printf '%s\n' 'Test file metadata:'
sed -n '1,22p' test/autonomy-platform.mjs

Repository: gitcommit90/1Helm

Length of output: 13806


🏁 Script executed:

printf '%s\n' 'OCI directory initialization and test-observable effects:'
sed -n '350,430p' src/server/channel-computers.ts
sed -n '430,525p' src/server/channel-computers.ts
printf '%s\n' 'Exports usable by a regression test:'
rg -n '^export (async )?(function|const)|^export \{' src/server/channel-computers.ts src/server/channel-storage.ts | rg 'ensureOci|channelUsesRuntime|channelComputer|configuredChannelBackend'

Repository: gitcommit90/1Helm

Length of output: 9992


Make the regression test select the OCI path explicitly.

Insert channel_computers metadata with backend='oci'. Without this row, the test uses the host default and selects the Apple path on macOS.

Assert that runtime storage remains untouched after ensureChannelWorkspace(channelId, { initializeRuntimeStorage: false }). assert.doesNotThrow is insufficient because ensureChannelWorkspace catches runtime initialization errors.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/autonomy-platform.mjs` around lines 203 - 212, Update the regression
test around ensureChannelWorkspace to insert channel_computers metadata for the
channel with backend='oci', ensuring the OCI path is selected regardless of host
platform. Replace assert.doesNotThrow with an assertion that runtime storage
remains untouched after calling ensureChannelWorkspace(channelId, {
initializeRuntimeStorage: false }), while preserving the existing workspace
root_ref assertion.


test("resident raw transcript search is semantic, exact, readable, and channel-isolated", () => {
const ownerId = run("INSERT INTO users (username,pass,display,is_admin,created) VALUES ('history-owner','x','History Owner',1,?)", now()).lastInsertRowid;
const channelId = run("INSERT INTO channels (name,slug,kind,topic,purpose,status,created_by,created) VALUES ('history-home','history-home','channel','','Recall prior sessions','active',?,?)", ownerId, now()).lastInsertRowid;
Expand Down
2 changes: 1 addition & 1 deletion test/channel-computers.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ test("Apple channel-computer contract preserves isolation, files, wakes, archive
test("runtime digest and packaged image recipe stay pinned", async () => {
assert.equal(computers.APPLE_RUNTIME_SHA256, "0ca1c42a2269c2557efb1d82b1b38ac553e6a3a3da1b1179c439bcee1e7d6714");
assert.match(computers.APPLE_RUNTIME_URL, /\/1\.1\.0\/container-1\.1\.0-installer-signed\.pkg$/);
assert.equal(computers.DEFAULT_CHANNEL_IMAGE, "local/1helm-channel-machine:0.0.35");
assert.equal(computers.DEFAULT_CHANNEL_IMAGE, "local/1helm-channel-machine:0.0.36");
const packaging = await readFile(join(root, "scripts", "package-mac-dmg.cjs"), "utf8");
assert.match(packaging, /container\(\?:\$\|\\\/\)/, "release packaging includes container/ image assets");
const image = await readFile(join(root, "container", "Containerfile"), "utf8");
Expand Down
2 changes: 2 additions & 0 deletions test/desktop.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,14 @@ test("desktop entrypoint keeps the renderer sandboxed and data on the Mac", asyn
const bootstrap = server.slice(server.indexOf("async function bootstrap"), server.indexOf("void bootstrap()"));
assert.doesNotMatch(bootstrap, /ensureAgentMemory\(/, "retained resident memory initialization cannot block the event loop during a bounded host update");
assert.match(bootstrap, /void memoryRuntime\.catch/, "optional memory-runtime preparation remains asynchronous during startup");
assert.match(bootstrap, /ensureChannelWorkspace\(Number\(channel\.id\), \{ initializeRuntimeStorage: false \}\)/, "retained OCI directories cannot synchronously wake WSL before the HTTP control plane is ready");
assert.match(server, /\["native-macos", "native-windows"\]\.includes\(update\.mode\)/, "both packaged host updaters quiesce the local server before replacement");
assert.match(nativeUpdater, /update\.electronjs\.org\/gitcommit90\/1Helm\/\$\{feedPlatform\}/);
assert.match(nativeUpdater, /win32-x64/, "Windows checks and installs on its host through the native updater");
assert.match(source, /handleSquirrelEvent/);
assert.match(source, /setAppUserModelId\("com\.squirrel\.1Helm\.1Helm"\)/, "Windows uses Squirrel's stable taskbar identity");
assert.match(source, /DATA_NAMESPACE = "1Helm-OCI-v1"/, "the clean-slate build uses a fresh durable application-data namespace");
assert.match(source, /SERVER_READY_TIMEOUT_MS = process\.platform === "win32" \? 3 \* 60_000 : 30_000/, "Windows gives a bounded cold WSL host enough time without weakening other desktop startup budgets");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Assert that waitForServer uses the timeout constant.

The current assertion verifies only the declaration. A regression to waitForServer(origin, timeoutMs = 30_000) would still pass.

Add an assertion for the default parameter or test the delayed-response behavior.

This assessment compares the assertion with the supplied desktop/main.cjs implementation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/desktop.mjs` at line 122, Extend the assertions in test/desktop.mjs to
verify that waitForServer uses SERVER_READY_TIMEOUT_MS as its default timeout,
not merely that the constant is declared. Assert the waitForServer default
parameter or cover equivalent delayed-response behavior while preserving the
existing platform-specific timeout check.

assert.match(source, /windows-removal\.cjs/, "Windows uninstall invokes ownership-checked shared-runtime cleanup before removing shortcuts");
const onboardingClient = await readFile(join(root, "src", "client", "onboarding.ts"), "utf8");
const clientApi = await readFile(join(root, "src", "client", "api.ts"), "utf8");
Expand Down
Loading