Skip to content

Commit 079be65

Browse files
authored
Release 1.0.4 responsiveness fixes (#85)
* Fix local responsiveness under disk pressure * Fix unbounded Files workspace indexing * Avoid repeated avatars in message history * Prepare 1.0.4 responsiveness release
1 parent 981f433 commit 079be65

18 files changed

Lines changed: 297 additions & 62 deletions

CHANGELOG.md

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

88
## [Unreleased]
99

10+
## [1.0.4] - 2026-08-12
11+
12+
### Fixed
13+
14+
- Opening Files in a large resident workspace no longer blocks every channel,
15+
thread, or health request while recursively walking dependency and cache
16+
trees. Files now loads one bounded directory at a time, upgrade cleanup
17+
removes obsolete auto-indexed metadata while preserving uploads and explicit
18+
attachments, and ordinary shell commands no longer rebuild that metadata.
19+
- Channel history no longer repeats the same inline profile photo in every
20+
message. The client reuses the user and agent records it already loaded,
21+
keeping channel switches fast on bandwidth- or latency-sensitive links.
22+
- SQLite write durability, read receipts, checkpoints, and fleet reconciliation
23+
no longer put avoidable synchronous storage pressure on foreground requests.
24+
- Denied service-user `sudo` calls no longer start mail delivery processes that
25+
can retry forever inside the hardened Linux service sandbox.
26+
1027
## [1.0.3] - 2026-08-10
1128

1229
### Added

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "1helm",
33
"productName": "1Helm",
4-
"version": "1.0.3",
4+
"version": "1.0.4",
55
"private": true,
66
"type": "module",
77
"license": "AGPL-3.0-only",

scripts/run-test-suite.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ const suites = [
1919
["--test",
2020
"test/phase6-modules.mjs",
2121
"test/routing.mjs", "test/routing-disabled-account.mjs", "test/routing-antigravity.mjs", "test/desktop.mjs", "test/update-service.mjs",
22-
"test/channel-computers.mjs", "test/channel-computers-isolated-backends.mjs", "test/event-loop-unblocking.mjs",
22+
"test/channel-computers.mjs", "test/channel-computers-isolated-backends.mjs", "test/event-loop-unblocking.mjs", "test/read-state.mjs",
2323
"test/cloudflare-worker.mjs", "test/connectors.mjs", "test/chatgpt-image.mjs", "test/autonomy-platform.mjs",
2424
"test/feedback.mjs", "test/feedback-browser.mjs", "test/cowork-browser.mjs", "test/files-latency.mjs", "test/gmail.mjs", "test/photon.mjs", "test/site.mjs", "test/release-license.mjs",
2525
"test/channel-surfaces.mjs", "test/workspace-interactions.mjs", "test/sweep-fleet-telemetry.mjs", "test/sweep-server-integration.mjs", "test/thread-followup-chat.mjs",

site/public/install-oci-runtime.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,8 @@ install -o root -g root -m 0755 "$APP_SOURCE/scripts/1helm-oci-runtime" "$HELPER
152152

153153
TEMP_ROOT="$(mktemp -d)"
154154
trap 'rm -rf -- "$TEMP_ROOT"' EXIT
155-
printf '%s ALL=(root) NOPASSWD: %s *\n' "$SERVICE_USER" "$HELPER_PATH" >"$TEMP_ROOT/sudoers"
155+
printf 'Defaults:%s !mail_badpass, !mail_no_user\n%s ALL=(root) NOPASSWD: %s *\n' \
156+
"$SERVICE_USER" "$SERVICE_USER" "$HELPER_PATH" >"$TEMP_ROOT/sudoers"
156157
visudo -cf "$TEMP_ROOT/sudoers" >/dev/null
157158
install -o root -g root -m 0440 "$TEMP_ROOT/sudoers" "$SUDOERS_PATH"
158159

src/server/agents.ts

Lines changed: 14 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -915,44 +915,46 @@ export function deleteWorkspaceEntry(channelId: number, input: string): void {
915915

916916
/** A safe folder tree for the Files and Cowork navigation rails. */
917917
export function listWorkspaceDirectories(channelId: number): WorkspaceFile[] {
918+
const maxDepth = 2;
918919
if (windowsOciStorageRequired(channelId)) {
919920
const result: WorkspaceFile[] = [];
920-
const walk = (path: string): void => {
921+
const walk = (path: string, depth: number): void => {
921922
const listed = listWorkspaceDirectory(channelId, path);
922923
for (const entry of listed.files) {
923924
if (entry.kind !== "directory") continue;
924925
result.push(entry);
925-
walk(entry.path);
926+
if (depth + 1 < maxDepth) walk(entry.path, depth + 1);
926927
}
927928
};
928-
walk("");
929+
walk("", 0);
929930
return result.sort((a, b) => a.path.localeCompare(b.path));
930931
}
931932
const result: WorkspaceFile[] = [];
932-
const walk = (path: string): void => {
933+
const walk = (path: string, depth: number): void => {
933934
const directory = existingWorkspaceDirectory(channelId, path);
934935
for (const entry of readdirSync(directory.host, { withFileTypes: true })) {
935936
if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
937+
if (!directory.path && entry.name === "files") continue;
936938
const child = directory.path ? `${directory.path}/${entry.name}` : entry.name;
937939
const host = join(directory.host, entry.name);
938940
result.push(workspaceFileView(child, host));
939-
walk(child);
941+
if (depth + 1 < maxDepth) walk(child, depth + 1);
940942
}
941943
};
942-
walk("");
944+
walk("", 0);
943945
ensureChannelWorkspace(channelId);
944946
const uploads = channelFiles(channelId);
945947
result.push(workspaceFileView("files", uploads));
946-
const walkUploads = (path: string): void => {
948+
const walkUploads = (path: string, depth: number): void => {
947949
const directory = existingWorkspaceDirectory(channelId, path);
948950
for (const entry of readdirSync(directory.host, { withFileTypes: true })) {
949951
if (!entry.isDirectory() || entry.isSymbolicLink()) continue;
950952
const child = `${directory.path}/${entry.name}`;
951953
result.push(workspaceFileView(child, join(directory.host, entry.name)));
952-
walkUploads(child);
954+
if (depth + 1 < maxDepth) walkUploads(child, depth + 1);
953955
}
954956
};
955-
walkUploads("files");
957+
walkUploads("files", 1);
956958
return result.sort((a, b) => a.path.localeCompare(b.path));
957959
}
958960

@@ -1087,20 +1089,6 @@ export function resolveWorldFile(channelId: number, requested: string): string {
10871089
throw new Error("File not found.");
10881090
}
10891091

1090-
export function syncWorkspaceArtifacts(channelId: number, threadId: number | null, createdBy = "agent"): WorkspaceFile[] {
1091-
const files = listWorkspaceFiles(channelId);
1092-
const paths = new Set(files.filter((entry) => entry.kind === "file").map((entry) => entry.path));
1093-
for (const artifact of q("SELECT id, path FROM artifacts WHERE channel_id=?", channelId)) {
1094-
if (!paths.has(String(artifact.path))) run("DELETE FROM artifacts WHERE id=?", artifact.id);
1095-
}
1096-
for (const file of files.filter((entry) => entry.kind === "file")) {
1097-
run(`INSERT INTO artifacts (channel_id, thread_id, path, kind, created_by, size, modified, created) VALUES (?,?,?,'file',?,?,?,?)
1098-
ON CONFLICT(channel_id,path) DO UPDATE SET thread_id=COALESCE(excluded.thread_id,artifacts.thread_id),size=excluded.size,modified=excluded.modified`,
1099-
channelId, threadId, file.path, createdBy, file.size, file.modified, now());
1100-
}
1101-
return files;
1102-
}
1103-
11041092
export function importAttachment(channelId: number, threadId: number | null, token: string, name: string, createdBy: string): string | null {
11051093
return importWorkspaceUpload(channelId, threadId, token, name, createdBy, "files");
11061094
}
@@ -1232,13 +1220,12 @@ export function attachWorkspaceFileToMessage(
12321220
).lastInsertRowid;
12331221

12341222
const worldRel = worldRelSafe(channelId, absolute);
1235-
const underChannelFiles = worldRel.startsWith("files/");
1236-
if (!underChannelFiles && (absolute.startsWith(channelWsAbs + sep) || absolute === channelWsAbs)) {
1237-
// Ensure Files tab sees workspace-originated artifacts
1223+
if ([channelWsAbs, channelFilesAbs].some((root) => absolute.startsWith(root + sep) || absolute === root)) {
1224+
// Explicitly attached files are durable artifacts; dependency trees are not.
12381225
run(
12391226
`INSERT INTO artifacts (channel_id, thread_id, path, kind, created_by, size, modified, created) VALUES (?,?,?,'file',?,?,?,?)
12401227
ON CONFLICT(channel_id,path) DO UPDATE SET thread_id=COALESCE(excluded.thread_id,artifacts.thread_id),size=excluded.size,modified=excluded.modified`,
1241-
channelId, threadId, worldRel.startsWith("workspace/") ? worldRel : `workspace/${basename(absolute)}`, createdBy, stat.size, stat.mtimeMs, now(),
1228+
channelId, threadId, worldRel, createdBy, stat.size, stat.mtimeMs, now(),
12421229
);
12431230
}
12441231

src/server/bots.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import {
2626
refreshThreadSummary,
2727
relevantMemory,
2828
setAgentStatus,
29-
syncWorkspaceArtifacts,
3029
threadIdForRoot,
3130
addThreadUsage,
3231
archiveChannel,
@@ -737,7 +736,6 @@ export async function generateAndAttachImage(
737736
const { join } = await import("node:path");
738737
const { writeFileSync } = await import("node:fs");
739738
writeFileSync(join(channelFiles(channelId), fileName), await generator(prompt, signal));
740-
syncWorkspaceArtifacts(channelId, threadId, actor);
741739
return attachWorkspaceFileToMessage(channelId, messageId, threadId, relativePath, actor, fileName);
742740
}
743741

@@ -1697,7 +1695,6 @@ async function executeBot(bot: Row, channelId: number, triggerId: number, thread
16971695
result = await runCommand(bot, agent, channelId, input, Number(args.computer_id) || 0, turnSignal);
16981696
requireActiveTurn(channelId, controller.signal);
16991697
if (agent?.kind === "channel") {
1700-
syncWorkspaceArtifacts(channelId, threadId, "agent");
17011698
if (cowork && coworkBefore) {
17021699
const contractError = enforceCoworkCommandOutput(channelId, threadId, cowork, coworkBefore);
17031700
if (contractError) result = contractError;
@@ -1728,7 +1725,6 @@ async function executeBot(bot: Row, channelId: number, triggerId: number, thread
17281725
const { join } = await import("node:path");
17291726
const { writeFileSync } = await import("node:fs");
17301727
writeFileSync(join(channelFiles(channelId), fileName), fetched.body);
1731-
syncWorkspaceArtifacts(channelId, threadId, actor);
17321728
const attached = attachWorkspaceFileToMessage(channelId, msgId, threadId, relativePath, actor, fileName);
17331729
emit();
17341730
result = `Attached real sourced image ${attached.name} (${attached.mime}, ${attached.size} bytes). Caption: ${String(args.caption || searched.title)}. Source: ${sourceUrl}. Image URL: ${fetched.final_url}. Retrieved SHA-256: ${fetched.sha256}.`;

src/server/channel-computers.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ const OCI_HELPER_CANDIDATES = [
7979
].filter(Boolean) as string[];
8080
const COMMAND_TIMEOUT_MS = Math.max(5_000, Number(process.env.HELM_MACHINE_COMMAND_TIMEOUT_MS || 120_000));
8181
const IDLE_AFTER_MS = Math.max(60_000, Number(process.env.HELM_MACHINE_IDLE_MS || 15 * 60_000));
82-
const RECONCILE_EVERY_MS = Math.max(15_000, Number(process.env.HELM_FLEET_INTERVAL_MS || 60_000));
83-
const INITIAL_RECONCILE_MS = Math.max(25, Number(process.env.HELM_FLEET_INITIAL_MS || 2_000));
82+
const RECONCILE_EVERY_MS = Math.max(15_000, Number(process.env.HELM_FLEET_INTERVAL_MS || 5 * 60_000));
83+
const INITIAL_RECONCILE_MS = Math.max(25, Number(process.env.HELM_FLEET_INITIAL_MS || 30_000));
8484
const UPDATE_EVERY_MS = Math.max(24 * 60 * 60_000, Number(process.env.HELM_MACHINE_UPDATE_MS || 7 * 24 * 60 * 60_000));
8585
const UPDATE_RETRY_MS = Math.max(60 * 60_000, Number(process.env.HELM_MACHINE_UPDATE_RETRY_MS || 6 * 60 * 60_000));
8686
const MAX_WORKSPACE_SYNC_BYTES = Math.max(64 * 1024 ** 2, Number(process.env.HELM_WORKSPACE_SYNC_MAX_BYTES || 2 * 1024 ** 3));

src/server/cowork-contract.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { deleteWorkspaceEntry, listWorkspaceFiles, readWorkspaceTextFile, syncWorkspaceArtifacts } from "./agents.ts";
1+
import { deleteWorkspaceEntry, listWorkspaceFiles, readWorkspaceTextFile } from "./agents.ts";
22

33
export type CoworkContext = {
44
kind: "file" | "folder";
@@ -105,7 +105,6 @@ export function enforceCoworkCommandOutput(channelId: number, threadId: number |
105105
const rejected = created.filter((path) => !compatibleCoworkFile(channelId, context, path));
106106
for (const path of rejected) deleteWorkspaceEntry(channelId, path);
107107
if (!rejected.length) return null;
108-
syncWorkspaceArtifacts(channelId, threadId || null, "agent");
109108
const expected = context.surface === "presentations" ? "one valid `.slides.json` deck"
110109
: context.surface === "whiteboards" ? "one valid `.whiteboard.json` Excalidraw scene"
111110
: context.surface === "docs" || context.surface === "notes" ? "Markdown `.md`"

src/server/database-migrations.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
type Execute = (sql: string, ...params: unknown[]) => unknown;
2+
3+
/** Remove legacy recursive file indexes without touching uploads or attachments. */
4+
export function cleanupLegacyWorkspaceArtifacts(run: Execute): void {
5+
run(`DELETE FROM artifacts
6+
WHERE kind='file'
7+
AND NOT EXISTS (
8+
SELECT 1 FROM attachments at
9+
JOIN messages m ON m.id=at.message_id
10+
WHERE m.channel_id=artifacts.channel_id
11+
AND at.workspace_path=artifacts.path
12+
)`);
13+
}

0 commit comments

Comments
 (0)