@inetafrica/open-claudia 2.2.6 → 2.2.8
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/CHANGELOG.md +9 -0
- package/Dockerfile +16 -0
- package/core/handlers.js +84 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.2.8
|
|
4
|
+
- `/upgrade` now works on docker containers that run our baked-in `/app` source (the common self-host layout). Previously the handler fell through to `npm install -g`, which hit `EACCES` because the runtime user is uid 1001 and couldn't write the global node_modules — and even when it could, the bot reads from `/app`, not the global root, so the new version was never picked up. The new branch detects the `/app` layout, `npm pack`s the latest tarball, overlays it onto `/app`, runs `npm install --omit=dev`, and exits so the orchestrator restarts the container on the new source. AgentSpace pods (which have `AGENTSPACE_POD_TOKEN` + `AGENTSPACE_API_URL`) still go through the control plane; nothing else changes for them or for npm-global installs.
|
|
5
|
+
- Dockerfile: `chown -R claudia:claudia /app` after the build steps so the runtime user can overlay new source during the in-place `/upgrade` above. Previously `/app` and its `node_modules` were root-owned because the COPY and `npm ci` ran as root before `USER 1001`.
|
|
6
|
+
|
|
7
|
+
## v2.2.7
|
|
8
|
+
- Docker image now ships `git`, `jq`, `python3`, `python3-pip`, and `build-essential` so spawned coding agents don't fall back to curling random binaries into userspace when a basic tool is missing.
|
|
9
|
+
- Symlink `/app/bin/cli.js` to `/usr/local/bin/open-claudia` so the CLI (used by agents for `send-file`, `task`, etc.) is on PATH from any cwd. Previously agents had to extract the packaged tgz to find it.
|
|
10
|
+
- Grant the `claudia` user passwordless sudo for `apt-get` / `apt` so the model can install additional packages at runtime via the normal path rather than ad-hoc binary downloads.
|
|
11
|
+
|
|
3
12
|
## v2.2.6
|
|
4
13
|
- Kazee inbound photos: V2 socket emits attachments as `msg.media` (single) or `msg.medias` (array), not `msg.attachments`. The adapter now reads all three so images sent from Kazee web/mobile clients reach the bot instead of being silently dropped as zero-attachment text.
|
|
5
14
|
- Kazee outbound files: rewrote `sendFile` to (a) upload via `POST /chat/media/:chatId` with field `media` (the actual route; the old code POSTed to `/upload` with field `file` and 404'd), then (b) post the message via the V2 socket event `message:send` with `mediaIds: [<uploadedId>]` instead of REST `sendMessage` with `media_url`. The REST path is currently broken upstream — `Chat/send_message` calls `Media.create` without the required `chat`/`bucketName`/`fileName`/`minioPath` fields and 500s with `Failed to create media record`. Going via the socket handler reuses the already-saved Media doc and avoids the duplicate create.
|
package/Dockerfile
CHANGED
|
@@ -5,6 +5,12 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
|
|
5
5
|
curl \
|
|
6
6
|
ffmpeg \
|
|
7
7
|
ca-certificates \
|
|
8
|
+
git \
|
|
9
|
+
jq \
|
|
10
|
+
python3 \
|
|
11
|
+
python3-pip \
|
|
12
|
+
build-essential \
|
|
13
|
+
sudo \
|
|
8
14
|
&& rm -rf /var/lib/apt/lists/*
|
|
9
15
|
|
|
10
16
|
# Install Claude Code CLI
|
|
@@ -18,6 +24,10 @@ RUN npm install -g @openai/codex
|
|
|
18
24
|
# node:20-slim already has uid/gid 1000 (node user). Create claudia with different IDs.
|
|
19
25
|
RUN groupadd -g 1001 claudia && useradd -u 1001 -g 1001 -m -d /data claudia
|
|
20
26
|
|
|
27
|
+
# Allow claudia to install packages at runtime without a password
|
|
28
|
+
RUN echo "claudia ALL=(ALL) NOPASSWD: /usr/bin/apt-get, /usr/bin/apt" > /etc/sudoers.d/claudia-apt && \
|
|
29
|
+
chmod 0440 /etc/sudoers.d/claudia-apt
|
|
30
|
+
|
|
21
31
|
# Create app directory
|
|
22
32
|
WORKDIR /app
|
|
23
33
|
|
|
@@ -31,6 +41,12 @@ COPY . .
|
|
|
31
41
|
# Ensure app files are readable regardless of host file perms
|
|
32
42
|
RUN chmod -R a+rX /app
|
|
33
43
|
|
|
44
|
+
# Expose the open-claudia CLI on PATH so spawned agents can send files, manage tasks, etc.
|
|
45
|
+
RUN chmod +x /app/bin/cli.js && ln -s /app/bin/cli.js /usr/local/bin/open-claudia
|
|
46
|
+
|
|
47
|
+
# Let the runtime user overlay new source into /app for in-place /upgrade.
|
|
48
|
+
RUN chown -R claudia:claudia /app
|
|
49
|
+
|
|
34
50
|
# Entrypoint auto-configures from env vars on first run
|
|
35
51
|
COPY docker-entrypoint.sh /usr/local/bin/
|
|
36
52
|
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
package/core/handlers.js
CHANGED
|
@@ -329,6 +329,41 @@ async function requestAgentSpaceUpgrade() {
|
|
|
329
329
|
});
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
+
// True when the running bot's source lives at /app (docker image layout from
|
|
333
|
+
// our Dockerfile). In that case `npm install -g` can't actually upgrade us:
|
|
334
|
+
// the process reads code from /app, not from the global npm root, and uid
|
|
335
|
+
// 1001 can't write to /usr/local/lib/node_modules anyway. Detect this and
|
|
336
|
+
// run an in-place tarball overlay instead.
|
|
337
|
+
function isDockerAppLayout() {
|
|
338
|
+
try {
|
|
339
|
+
const pkgPath = path.resolve(path.join(__dirname, "..", "package.json"));
|
|
340
|
+
return pkgPath === "/app/package.json";
|
|
341
|
+
} catch (e) {
|
|
342
|
+
return false;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function inPlaceTarballUpgrade(targetVersion) {
|
|
347
|
+
const tmpDir = `/tmp/oc-upgrade-${process.pid}-${Date.now()}`;
|
|
348
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
349
|
+
const env = { ...process.env, PATH: FULL_PATH, HOME: process.env.HOME || require("os").homedir() };
|
|
350
|
+
const packOutput = execSync(`npm pack @inetafrica/open-claudia@${targetVersion}`, {
|
|
351
|
+
encoding: "utf-8", cwd: tmpDir, timeout: 120000, env,
|
|
352
|
+
}).trim();
|
|
353
|
+
// Last non-empty line of `npm pack` stdout is the tgz filename.
|
|
354
|
+
const tgzName = packOutput.split("\n").map((s) => s.trim()).filter(Boolean).pop();
|
|
355
|
+
const tgzPath = path.join(tmpDir, tgzName);
|
|
356
|
+
execSync(`tar -xzf "${tgzPath}" -C "${tmpDir}"`, { encoding: "utf-8", timeout: 60000 });
|
|
357
|
+
// npm pack tarballs always extract into a top-level "package/" directory.
|
|
358
|
+
// cp -a preserves perms and symlinks; the trailing /. copies contents only.
|
|
359
|
+
execSync(`cp -a "${tmpDir}/package/." /app/`, { encoding: "utf-8", timeout: 60000 });
|
|
360
|
+
try { fs.chmodSync("/app/bin/cli.js", 0o755); } catch (e) {}
|
|
361
|
+
execSync(`npm install --omit=dev --no-audit --no-fund`, {
|
|
362
|
+
encoding: "utf-8", cwd: "/app", timeout: 240000, env,
|
|
363
|
+
});
|
|
364
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch (e) {}
|
|
365
|
+
}
|
|
366
|
+
|
|
332
367
|
register({
|
|
333
368
|
name: "upgrade", description: "Upgrade and restart", ownerOnly: true,
|
|
334
369
|
handler: async (env) => {
|
|
@@ -350,6 +385,55 @@ register({
|
|
|
350
385
|
return;
|
|
351
386
|
}
|
|
352
387
|
}
|
|
388
|
+
// Docker /app layout: bot runs from baked-in source, not from the global
|
|
389
|
+
// npm root, and as a non-root user. Do an in-place tarball overlay and
|
|
390
|
+
// exit; whatever orchestrates the container (k8s, docker restart=always)
|
|
391
|
+
// brings us back on the new source.
|
|
392
|
+
if (isDockerAppLayout()) {
|
|
393
|
+
try { process.chdir("/tmp"); } catch (e) {}
|
|
394
|
+
let latest = null;
|
|
395
|
+
try {
|
|
396
|
+
latest = execSync("npm view @inetafrica/open-claudia version", {
|
|
397
|
+
encoding: "utf-8", timeout: 15000,
|
|
398
|
+
env: { ...process.env, PATH: FULL_PATH, HOME: process.env.HOME || require("os").homedir() },
|
|
399
|
+
}).trim();
|
|
400
|
+
} catch (e) {
|
|
401
|
+
await send(`Upgrade failed: could not query npm registry (${(e.message || String(e)).slice(0, 200)}).`);
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
if (latest === CURRENT_VERSION) {
|
|
405
|
+
await send(`Already on the latest version (v${CURRENT_VERSION}).`);
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
await send(`Upgrading v${CURRENT_VERSION} → v${latest} (in-place tarball overlay into /app)...`);
|
|
409
|
+
try {
|
|
410
|
+
inPlaceTarballUpgrade(latest);
|
|
411
|
+
} catch (e) {
|
|
412
|
+
const errOutput = (e.stdout || e.stderr || e.message || String(e)).slice(-700);
|
|
413
|
+
await send(`Upgrade failed:\n${errOutput}`);
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
let newVersion = latest;
|
|
417
|
+
let whatsNew = "";
|
|
418
|
+
try {
|
|
419
|
+
newVersion = JSON.parse(fs.readFileSync("/app/package.json", "utf-8")).version;
|
|
420
|
+
const changelog = fs.readFileSync("/app/CHANGELOG.md", "utf-8");
|
|
421
|
+
let versionHeader = `## v${newVersion}`;
|
|
422
|
+
let start = changelog.indexOf(versionHeader);
|
|
423
|
+
if (start < 0) { versionHeader = `## ${newVersion}`; start = changelog.indexOf(versionHeader); }
|
|
424
|
+
if (start >= 0) {
|
|
425
|
+
const afterHeader = changelog.slice(start + versionHeader.length);
|
|
426
|
+
const nextVersion = afterHeader.indexOf("\n## ");
|
|
427
|
+
const section = nextVersion >= 0 ? afterHeader.slice(0, nextVersion) : afterHeader;
|
|
428
|
+
whatsNew = section.trim();
|
|
429
|
+
}
|
|
430
|
+
} catch (e) {}
|
|
431
|
+
const tailNote = "Source refreshed in /app. If this release also changed the Dockerfile (apt packages, env, base image), the host still needs a docker pull + recreate for those.";
|
|
432
|
+
const msg = `Installed v${newVersion}.${whatsNew ? `\n\nWhat's new:\n${whatsNew}` : ""}\n\n${tailNote}\n\nRestarting...`;
|
|
433
|
+
await send(msg.length > 3900 ? msg.slice(0, 3900) : msg);
|
|
434
|
+
setTimeout(() => process.exit(0), 2000);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
353
437
|
try { process.chdir(process.env.HOME || require("os").homedir()); } catch (e) {}
|
|
354
438
|
let latest = null;
|
|
355
439
|
try {
|
package/package.json
CHANGED