shadok-ai 0.13.15 → 0.14.1

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/README.md CHANGED
@@ -345,12 +345,21 @@ location / {
345
345
  proxy_set_header Upgrade $http_upgrade;
346
346
  proxy_set_header Connection "upgrade";
347
347
  proxy_set_header Host $host;
348
+ client_max_body_size 50m; # match the server's paste limit
348
349
  }
349
350
  ```
350
351
 
351
352
  The client picks `wss://` on its own when the page is HTTPS, so there is nothing
352
353
  to configure on that side.
353
354
 
355
+ **Upload size.** The server accepts a pasted/dropped file up to 50 MB, but
356
+ nginx's default `client_max_body_size` is **1 MB** — a full-page screenshot
357
+ sails past that, and nginx answers its OWN `413` HTML page **before** the request
358
+ reaches the cockpit. Raise the limit to match (`client_max_body_size 50m;`),
359
+ or a paste larger than 1 MB fails at the proxy. The cockpit now shows a plain
360
+ "too large" instead of a JSON parser error, but the file still won't go through
361
+ until the proxy allows it.
362
+
354
363
  **Idle timeout.** A resting agent's `/ws` carries no traffic, and proxies
355
364
  idle-close a quiet socket (nginx `proxy_read_timeout`, 60s by default;
356
365
  Cloudflare ~100s) — the symptom is a cockpit that keeps flashing
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shadok-ai",
3
- "version": "0.13.15",
3
+ "version": "0.14.1",
4
4
  "main": "dist/session.js",
5
5
  "scripts": {
6
6
  "test": "node --import tsx --test test/*.ts context/*/test/*.test.mjs",
package/public/index.html CHANGED
@@ -2351,6 +2351,8 @@
2351
2351
  window.bangCommands = bangCommands;
2352
2352
  import { claudeUpdateState } from "/update-ready.js?v=__ASSET_V__";
2353
2353
  window.claudeUpdateState = claudeUpdateState;
2354
+ import { uploadFailure } from "/upload.js?v=__ASSET_V__";
2355
+ window.uploadFailure = uploadFailure;
2354
2356
  window.MODEL_CHOICES = MODEL_CHOICES;
2355
2357
  window.composeModel = composeModel;
2356
2358
  window.pickChannelSource = pickChannelSource;
@@ -2446,6 +2448,8 @@
2446
2448
  // cache. instanceKey is stamped synchronously, so the stub can namespace.
2447
2449
  dirKey: (base) => (instanceKey ? base + ":" + instanceKey : base),
2448
2450
  serverSwapped: (pageKey, serverKey) => !!pageKey && !!serverKey && pageKey !== serverKey,
2451
+ uploadFailure: (status, contentType) =>
2452
+ /json/i.test(contentType || "") ? null : status === 413 ? "too large (over the server's upload limit)" : `upload failed (HTTP ${status || "?"})`,
2449
2453
  };
2450
2454
  for (const [name, stub] of Object.entries(BRIDGE_STUBS)) {
2451
2455
  if (typeof window[name] !== "function") { stub.__stub = true; window[name] = stub; }
@@ -6455,6 +6459,12 @@
6455
6459
  },
6456
6460
  body: file,
6457
6461
  });
6462
+ // A proxy rejects an oversized body with a 413 text/html page that never
6463
+ // reaches /paste — parsing THAT as JSON is what showed the user
6464
+ // "Unexpected token '<'" instead of "too large". Decide from the status
6465
+ // first; only parse when the response is actually ours (JSON).
6466
+ const fail = window.uploadFailure(r.status, r.headers.get("content-type"));
6467
+ if (fail) throw new Error(fail);
6458
6468
  const j = await r.json();
6459
6469
  if (!r.ok || !j.line) throw new Error(j.error || `HTTP ${r.status}`);
6460
6470
  promptInput.value = promptInput.value.replace(mark, j.line);
@@ -0,0 +1,24 @@
1
+ // Pure helper for the composer's file upload (paste / drag-and-drop). ESM:
2
+ // loaded by the browser (bridged to window) AND imported by test/upload.test.ts.
3
+ //
4
+ // Why it exists: `attachFile` POSTs to /paste and calls `r.json()`. A reverse
5
+ // proxy (nginx, default `client_max_body_size` 1 MB) rejects an oversized body
6
+ // with its OWN error page — `413 text/html` — which never reaches our JSON
7
+ // route. `r.json()` on that HTML threw `Unexpected token '<', "<html>…"`, and
8
+ // the composer surfaced the raw parser error instead of a plain "too large".
9
+ // The server itself accepts up to PASTE_LIMIT (50 MB), so this is always the
10
+ // proxy, and the fix is to read the STATUS, not the body.
11
+
12
+ /**
13
+ * Should the caller refuse to parse this /paste response, and with what message?
14
+ * Returns null for a JSON response (success OR our `{error}` body — the caller's
15
+ * existing `r.json()` path handles both); otherwise a human string, because a
16
+ * non-JSON response is a proxy/gateway page, never something to `JSON.parse`.
17
+ * @param {number} status - the HTTP status
18
+ * @param {string|null|undefined} contentType - the response Content-Type header
19
+ */
20
+ export function uploadFailure(status, contentType) {
21
+ if (/json/i.test(contentType || "")) return null; // ours — let the caller read it
22
+ if (status === 413) return "too large (over the server's upload limit)";
23
+ return `upload failed (HTTP ${status || "?"})`;
24
+ }