loopat 0.1.20 → 0.1.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -1
- package/scripts/install-sandbox-claude.mjs +51 -0
- package/server/src/bootstrap.ts +11 -6
- package/server/src/claude-binary.ts +19 -0
- package/server/src/podman.ts +2 -2
- package/server/src/session.ts +5 -2
- package/web/dist/assets/{CodeEditor-DL99b11Y.js → CodeEditor-CCmzcfl4.js} +2 -2
- package/web/dist/assets/Editor-Ev1Uuych.js +1 -0
- package/web/dist/assets/{Markdown-DS_QLUsd.js → Markdown-CIlkNxLu.js} +2 -2
- package/web/dist/assets/{MilkdownEditor-DDhGpwvK.js → MilkdownEditor-BEiSN0Gf.js} +7 -7
- package/web/dist/assets/{Terminal-dhf2INUI.js → Terminal-DTMXO4Pg.js} +2 -2
- package/web/dist/assets/{index-Po2JG3Ae.js → index-D_70Ex-K.js} +81 -81
- package/web/dist/assets/jsx-runtime-29NaZBia.js +1 -0
- package/web/dist/index.html +2 -1
- package/web/dist/assets/Editor-DbkUcYr-.js +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "loopat",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "Self-hosted AI workspace built around context management — works solo, scales to teams",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://github.com/simpx/loopat",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"bin/",
|
|
17
|
+
"scripts/install-sandbox-claude.mjs",
|
|
17
18
|
"server/src/",
|
|
18
19
|
"!server/src/serve-rs",
|
|
19
20
|
"!server/src/port-proxy-rs",
|
|
@@ -34,6 +35,7 @@
|
|
|
34
35
|
"build": "bun install && (cd web && bun run build)",
|
|
35
36
|
"build:web": "cd web && bunx tsc -b && bunx vite build",
|
|
36
37
|
"prepublishOnly": "bun install && bun run build:web",
|
|
38
|
+
"postinstall": "node scripts/install-sandbox-claude.mjs",
|
|
37
39
|
"test:e2e": "playwright test",
|
|
38
40
|
"test:e2e:ui": "playwright test --ui"
|
|
39
41
|
},
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* postinstall: make sure a LINUX claude binary is available for the sandbox.
|
|
4
|
+
*
|
|
5
|
+
* loopat runs the AI inside a linux podman sandbox, so it needs the
|
|
6
|
+
* linux-<arch> claude binary. npm only installs the claude-agent-sdk platform
|
|
7
|
+
* binary matching the HOST (os/cpu filtered optionalDependencies) — so on a
|
|
8
|
+
* linux host we already have it (no-op here), but on macOS/Windows npm installs
|
|
9
|
+
* the darwin/win binary and the sandbox would hit "Exec format error".
|
|
10
|
+
*
|
|
11
|
+
* On a non-linux host we fetch the linux-<arch> binary into
|
|
12
|
+
* <loopat>/sandbox-claude (pinned to the SDK version we depend on) using npm's
|
|
13
|
+
* --os/--cpu override. Best-effort: a failure only means sandbox AI won't run
|
|
14
|
+
* on this host until fixed; the install itself still succeeds.
|
|
15
|
+
*/
|
|
16
|
+
import { execFileSync } from "node:child_process"
|
|
17
|
+
import { existsSync, mkdirSync } from "node:fs"
|
|
18
|
+
import { dirname, join, resolve } from "node:path"
|
|
19
|
+
import { fileURLToPath } from "node:url"
|
|
20
|
+
import { createRequire } from "node:module"
|
|
21
|
+
|
|
22
|
+
if (process.platform === "linux") process.exit(0) // host claude IS the sandbox claude
|
|
23
|
+
|
|
24
|
+
const arch = process.arch // "arm64" | "x64"
|
|
25
|
+
const pkg = `@anthropic-ai/claude-agent-sdk-linux-${arch}`
|
|
26
|
+
const installDir = resolve(dirname(fileURLToPath(import.meta.url)), "..")
|
|
27
|
+
const dest = join(installDir, "sandbox-claude")
|
|
28
|
+
const binary = join(dest, "node_modules", pkg, "claude")
|
|
29
|
+
|
|
30
|
+
if (existsSync(binary)) process.exit(0) // already fetched
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const require = createRequire(import.meta.url)
|
|
34
|
+
let version = ""
|
|
35
|
+
try {
|
|
36
|
+
version = require("@anthropic-ai/claude-agent-sdk/package.json").version
|
|
37
|
+
} catch {}
|
|
38
|
+
const spec = version ? `${pkg}@${version}` : pkg
|
|
39
|
+
mkdirSync(dest, { recursive: true })
|
|
40
|
+
console.log(`[loopat] host is ${process.platform}/${arch}; fetching linux claude for the sandbox (${spec})…`)
|
|
41
|
+
execFileSync(
|
|
42
|
+
"npm",
|
|
43
|
+
["install", "--prefix", dest, "--no-save", "--os=linux", `--cpu=${arch}`, spec],
|
|
44
|
+
{ stdio: "inherit" },
|
|
45
|
+
)
|
|
46
|
+
if (existsSync(binary)) console.log(`[loopat] sandbox claude ready at ${binary}`)
|
|
47
|
+
else console.warn(`[loopat] sandbox claude install finished but ${binary} is missing`)
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.warn(`[loopat] could not fetch linux claude for the sandbox: ${e?.message ?? e}`)
|
|
50
|
+
console.warn(`[loopat] sandbox AI won't run on this host until fixed; everything else works.`)
|
|
51
|
+
}
|
package/server/src/bootstrap.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import { existsSync } from "node:fs"
|
|
7
7
|
import { execFileSync } from "node:child_process"
|
|
8
8
|
import { join } from "node:path"
|
|
9
|
-
import {
|
|
9
|
+
import { resolveSandboxClaudeBinary } from "./claude-binary"
|
|
10
10
|
import { configPath, loadKnowledgeConfig, type WorkspaceConfig, type KnowledgeConfig } from "./config"
|
|
11
11
|
import {
|
|
12
12
|
WORKSPACE,
|
|
@@ -68,14 +68,19 @@ function checkPodman(): Check {
|
|
|
68
68
|
}
|
|
69
69
|
|
|
70
70
|
function checkClaudeBinary(): Check {
|
|
71
|
+
// The AI runs in the linux sandbox, so what matters is the SANDBOX claude.
|
|
71
72
|
try {
|
|
72
|
-
const p =
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
const p = resolveSandboxClaudeBinary()
|
|
74
|
+
const tag = process.platform === "linux" ? "" : " [linux, for sandbox]"
|
|
75
|
+
return { ok: true, label: `claude binary${tag} (${p.split("/").slice(-3).join("/")})` }
|
|
76
|
+
} catch {
|
|
75
77
|
return {
|
|
76
78
|
ok: false,
|
|
77
|
-
label: "claude binary",
|
|
78
|
-
hint:
|
|
79
|
+
label: "claude binary (sandbox/linux)",
|
|
80
|
+
hint:
|
|
81
|
+
process.platform === "linux"
|
|
82
|
+
? "run `bun install` in the loopat repo root — SDK ships the binary as a platform-specific package"
|
|
83
|
+
: "the linux claude for the sandbox wasn't fetched (postinstall). Reinstall loopat, or run the `npm install --os=linux ...` command from the resolve error",
|
|
79
84
|
}
|
|
80
85
|
}
|
|
81
86
|
}
|
|
@@ -66,3 +66,22 @@ export function resolveClaudeBinary(): string {
|
|
|
66
66
|
}
|
|
67
67
|
throw new Error(`claude binary not found; tried:\n${candidates.join("\n")}`)
|
|
68
68
|
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* The claude binary the SANDBOX runs (the AI executes inside a linux podman
|
|
72
|
+
* container). On a linux host that's just the host claude. On a non-linux host
|
|
73
|
+
* npm only installed the host (e.g. darwin) binary, so postinstall fetched the
|
|
74
|
+
* linux-<arch> one into <loopat>/sandbox-claude — bind THAT into the sandbox,
|
|
75
|
+
* not the host binary (otherwise: "Exec format error").
|
|
76
|
+
*/
|
|
77
|
+
export function resolveSandboxClaudeBinary(): string {
|
|
78
|
+
if (process.platform === "linux") return resolveClaudeBinary()
|
|
79
|
+
const arch = process.arch
|
|
80
|
+
const installDir = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..")
|
|
81
|
+
const candidate = join(installDir, "sandbox-claude", "node_modules", "@anthropic-ai", `claude-agent-sdk-linux-${arch}`, "claude")
|
|
82
|
+
if (existsSync(candidate)) return candidate
|
|
83
|
+
throw new Error(
|
|
84
|
+
`sandbox (linux) claude not found at ${candidate}; postinstall may have failed. Fix: ` +
|
|
85
|
+
`npm install --prefix "${join(installDir, "sandbox-claude")}" --no-save --os=linux --cpu=${arch} @anthropic-ai/claude-agent-sdk-linux-${arch}`,
|
|
86
|
+
)
|
|
87
|
+
}
|
package/server/src/podman.ts
CHANGED
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
import { loadConfig } from "./config"
|
|
61
61
|
import { DEFAULT_VAULT, listVaultHomeMounts } from "./vaults"
|
|
62
62
|
import { hostExecDir, writeHostShims } from "./host-exec"
|
|
63
|
-
import {
|
|
63
|
+
import { resolveSandboxClaudeBinary } from "./claude-binary"
|
|
64
64
|
import { parse as tomlParse, stringify as tomlStringify } from "smol-toml"
|
|
65
65
|
|
|
66
66
|
const execFileP = promisify(execFile)
|
|
@@ -221,7 +221,7 @@ export async function buildVolumeMounts(opts: ContainerOptions): Promise<VolumeM
|
|
|
221
221
|
// sandbox exec's it by its host path, so bind that path in (ro) when it isn't
|
|
222
222
|
// already covered by the install-dir mount — otherwise the AI is code 127.
|
|
223
223
|
try {
|
|
224
|
-
const claudeDir = dirname(
|
|
224
|
+
const claudeDir = dirname(resolveSandboxClaudeBinary())
|
|
225
225
|
if (existsSync(claudeDir) && !claudeDir.startsWith(LOOPAT_INSTALL_DIR)) {
|
|
226
226
|
mounts.push({ src: claudeDir, dst: claudeDir, ro: true })
|
|
227
227
|
}
|
package/server/src/session.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { createWriteStream, mkdirSync, existsSync } from "node:fs"
|
|
|
5
5
|
import { randomUUID } from "node:crypto"
|
|
6
6
|
import { join } from "node:path"
|
|
7
7
|
import { loopClaudeDir, loopDir, loopHistoryPath, personalSkillsDir, workspaceTeamSkillsDir } from "./paths"
|
|
8
|
-
import {
|
|
8
|
+
import { resolveSandboxClaudeBinary } from "./claude-binary"
|
|
9
9
|
import { loadConfig, loadPersonalConfig, parseDefault, type ProviderConfig } from "./config"
|
|
10
10
|
import { buildLoopatAppend } from "./system-prompt"
|
|
11
11
|
import { composeLoopClaudeConfig, writeLoopSettings } from "./compose"
|
|
@@ -21,8 +21,11 @@ import { updateLoopStatus } from "./loop-status"
|
|
|
21
21
|
// Resolved lazily — each spawn re-reads the env var so the full-suite test
|
|
22
22
|
// run, where module load order isn't guaranteed, sees the test's override
|
|
23
23
|
// even if session.ts was imported earlier with the env var unset.
|
|
24
|
+
// The AI runs inside the linux sandbox, so it needs the linux claude binary
|
|
25
|
+
// (resolveSandboxClaudeBinary) — on a linux host that's the host claude; on a
|
|
26
|
+
// darwin/win host it's the linux binary postinstall fetched into sandbox-claude.
|
|
24
27
|
function getClaudeBinary(): string {
|
|
25
|
-
return process.env.LOOPAT_CLAUDE_BIN ||
|
|
28
|
+
return process.env.LOOPAT_CLAUDE_BIN || resolveSandboxClaudeBinary()
|
|
26
29
|
}
|
|
27
30
|
const DEBUG = !!process.env.LOOPAT_DEBUG || !!process.env.LOOPAT_DEBUG_SPAWN
|
|
28
31
|
|