@inetafrica/open-claudia 2.2.5 → 2.2.7

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## v2.2.7
4
+ - 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.
5
+ - 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.
6
+ - 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.
7
+
8
+ ## v2.2.6
9
+ - 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.
10
+ - 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.
11
+ - New `_socketEmit(event, payload, timeoutMs)` adapter helper that promisifies socket.io acks with a timeout, used by the new outbound path.
12
+
3
13
  ## v2.2.5
4
14
  - Fix `/codex` resume crash: `buildCodexArgs` no longer appends `--add-dir <transcripts-dir>`, which the Codex CLI does not accept and which caused every `codex exec resume` invocation to exit 2 with `error: unexpected argument '--add-dir' found`. Transcript pointer is still injected into the prompt via `promptWithTranscriptPointer`.
5
15
  - Backend-aware empty-output failure message: when a non-Claude backend exits with no assistant output, the reply now labels it correctly (`Codex` / `Cursor`) and points at the right diagnostic commands (`/codex_auth_status`, `/codex_login`, `/codex_setup_token`, or `agent login`) instead of always saying "Claude exited" and recommending Claude-only commands.
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,9 @@ 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
+
34
47
  # Entrypoint auto-configures from env vars on first run
35
48
  COPY docker-entrypoint.sh /usr/local/bin/
36
49
  RUN chmod +x /usr/local/bin/docker-entrypoint.sh
@@ -138,8 +138,14 @@ class KazeeAdapter {
138
138
  raw: msg,
139
139
  };
140
140
 
141
- if (msg.attachments && msg.attachments.length) {
142
- envelope.media = msg.attachments.map((a) => {
141
+ // chat-central V2 emits attached media on msg.medias (array) or
142
+ // msg.media (single). Older shapes used msg.attachments keep that
143
+ // as a fallback for safety.
144
+ const rawMedia = (Array.isArray(msg.medias) && msg.medias.length)
145
+ ? msg.medias
146
+ : (msg.media ? [msg.media] : (msg.attachments || []));
147
+ if (rawMedia.length) {
148
+ envelope.media = rawMedia.map((a) => {
143
149
  let kind = a.type;
144
150
  if (!kind) {
145
151
  const mime = (a.mimeType || "").toLowerCase();
@@ -229,22 +235,37 @@ class KazeeAdapter {
229
235
  try {
230
236
  const buffer = fs.readFileSync(filePath);
231
237
  const fileName = path.basename(filePath);
232
- const upload = await this._uploadMultipart("/upload", { file: { buffer, fileName } });
233
- const url = upload?.url || upload?.fileUrl || upload?.location;
234
- if (!url) {
235
- console.error("Kazee upload returned no url");
238
+ // chat-central action `uploadMedia` is mounted at
239
+ // POST /chat/media/:chatId and expects the file under the `media`
240
+ // multipart field. It responds with { success, media: [<savedDoc>] }
241
+ // where each saved doc carries `_id` and a presigned `url`.
242
+ const upload = await this._uploadMultipart(
243
+ `/chat/media/${encodeURIComponent(channelId)}`,
244
+ { media: { buffer, fileName } },
245
+ );
246
+ const savedMedia = Array.isArray(upload?.media) ? upload.media[0] : null;
247
+ const mediaId = savedMedia?._id;
248
+ if (!mediaId) {
249
+ console.error("Kazee upload returned no media id:", JSON.stringify(upload).slice(0, 300));
236
250
  return false;
237
251
  }
238
- const body = {
239
- chat_id: channelId,
240
- sender: this.botUserId,
241
- content: caption || fileName,
252
+ // Post the message via the v2 socket event `message:send`, which
253
+ // accepts `mediaIds` referencing already-saved Media docs. We avoid
254
+ // the REST sendMessage action because it tries to re-Media.create
255
+ // from media_url and that call omits required schema fields
256
+ // (chat/bucketName/fileName/minioPath) — it always 500s.
257
+ const payload = {
258
+ chatId: channelId,
259
+ content: caption || "",
242
260
  type: this._kazeeFileType(fileName),
243
261
  findMeCode: `oc-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
244
- media_url: url,
245
- attachments: [{ url, name: fileName }],
262
+ mediaIds: [mediaId],
246
263
  };
247
- await this._request("POST", `/chat/${encodeURIComponent(channelId)}/message`, body);
264
+ const res = await this._socketEmit("message:send", payload, 30000);
265
+ if (res && res.code) {
266
+ console.error("Kazee message:send error:", res.code, res.message);
267
+ return false;
268
+ }
248
269
  return true;
249
270
  } catch (e) {
250
271
  console.error("Kazee sendFile error:", e.message);
@@ -252,6 +273,30 @@ class KazeeAdapter {
252
273
  }
253
274
  }
254
275
 
276
+ // Promisified ack-style socket emit. chat-central v2 handlers respond
277
+ // either with a success body or an { code, message, action } error.
278
+ _socketEmit(event, payload, timeoutMs = 15000) {
279
+ return new Promise((resolve, reject) => {
280
+ const socket = this._socket;
281
+ if (!socket || !socket.connected) {
282
+ reject(new Error(`Kazee socket not connected for ${event}`));
283
+ return;
284
+ }
285
+ let settled = false;
286
+ const timer = setTimeout(() => {
287
+ if (settled) return;
288
+ settled = true;
289
+ reject(new Error(`Kazee ${event} ack timeout`));
290
+ }, timeoutMs);
291
+ socket.emit(event, payload, (ack) => {
292
+ if (settled) return;
293
+ settled = true;
294
+ clearTimeout(timer);
295
+ resolve(ack);
296
+ });
297
+ });
298
+ }
299
+
255
300
  _kazeeFileType(fileName) {
256
301
  const ext = path.extname(fileName).toLowerCase();
257
302
  if ([".jpg", ".jpeg", ".png", ".gif", ".webp"].includes(ext)) return "image";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inetafrica/open-claudia",
3
- "version": "2.2.5",
3
+ "version": "2.2.7",
4
4
  "description": "Your always-on AI coding assistant — Claude Code, Cursor Agent, and OpenAI Codex via Telegram or Kazee Chat",
5
5
  "main": "bot.js",
6
6
  "bin": {