ai-or-die 0.1.69 → 0.1.70
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 +1 -1
- package/src/public/app.js +14 -0
- package/src/public/file-browser.js +59 -1
- package/src/server.js +64 -4
package/package.json
CHANGED
package/src/public/app.js
CHANGED
|
@@ -2182,6 +2182,16 @@ class ClaudeCodeWebInterface {
|
|
|
2182
2182
|
this._sessionWorkingDirs.set(message.sessionId, message.workingDir);
|
|
2183
2183
|
}
|
|
2184
2184
|
this.updateSessionButton(message.sessionName);
|
|
2185
|
+
|
|
2186
|
+
// Re-root the (singleton) file-browser panel to the newly
|
|
2187
|
+
// active session's dir. open() short-circuits when already
|
|
2188
|
+
// open, so without this an open panel would keep showing the
|
|
2189
|
+
// previous tab's directory after a tab switch. Fires only when
|
|
2190
|
+
// the panel is open and the session actually changed.
|
|
2191
|
+
if (this._fileBrowserPanel &&
|
|
2192
|
+
typeof this._fileBrowserPanel.notifyActiveSessionChanged === 'function') {
|
|
2193
|
+
this._fileBrowserPanel.notifyActiveSessionChanged(message.sessionId);
|
|
2194
|
+
}
|
|
2185
2195
|
|
|
2186
2196
|
// Update tab status
|
|
2187
2197
|
if (this.sessionTabManager) {
|
|
@@ -4138,6 +4148,10 @@ class ClaudeCodeWebInterface {
|
|
|
4138
4148
|
// opens picks up the new cwd (per ADR-0016 / task #14).
|
|
4139
4149
|
initialPath: this.getCurrentWorkingDir(),
|
|
4140
4150
|
getCwd: () => this.getCurrentWorkingDir(),
|
|
4151
|
+
// Active session id → sent as ?session on /api/files so the
|
|
4152
|
+
// server can resolve the per-tab default root even when the
|
|
4153
|
+
// client cwd cache is cold (e.g. just after a page reload).
|
|
4154
|
+
getSessionId: () => this.currentClaudeSessionId,
|
|
4141
4155
|
});
|
|
4142
4156
|
}
|
|
4143
4157
|
return this._fileBrowserPanel;
|
|
@@ -338,6 +338,17 @@
|
|
|
338
338
|
// callback is also tolerated (defensive coding per agent-instructions/05).
|
|
339
339
|
this.getCwd = typeof options.getCwd === 'function' ? options.getCwd : null;
|
|
340
340
|
|
|
341
|
+
// Optional callback returning the active session's id. Sent as the
|
|
342
|
+
// `session` query param on /api/files so the SERVER can resolve the
|
|
343
|
+
// default root (session.liveCwd || session.workingDir) even when the
|
|
344
|
+
// client's cwd cache is cold (e.g. right after a page reload). Tolerant
|
|
345
|
+
// of falsy/throwing callbacks like getCwd.
|
|
346
|
+
this.getSessionId = typeof options.getSessionId === 'function' ? options.getSessionId : null;
|
|
347
|
+
// Server-reported "home" for the active session (its working dir). Set
|
|
348
|
+
// from each /api/files response; navigateHome() roots here so "Home"
|
|
349
|
+
// means the tab's session dir, not the server's global baseFolder.
|
|
350
|
+
this._homePath = null;
|
|
351
|
+
|
|
341
352
|
this._open = false;
|
|
342
353
|
this._currentPath = null;
|
|
343
354
|
this._basePath = null;
|
|
@@ -858,9 +869,23 @@
|
|
|
858
869
|
var self = this;
|
|
859
870
|
var params = new URLSearchParams();
|
|
860
871
|
if (dirPath) params.append('path', dirPath);
|
|
872
|
+
// Always forward the active session id (when known). The server uses it
|
|
873
|
+
// ONLY to pick the default root when no `path` is given, and to report
|
|
874
|
+
// `home`; it is ignored when `path` is present, so explicit navigation
|
|
875
|
+
// (breadcrumbs / up / folder clicks) is unaffected.
|
|
876
|
+
var sid = null;
|
|
877
|
+
if (this.getSessionId) { try { sid = this.getSessionId(); } catch (_) { sid = null; } }
|
|
878
|
+
if (sid) params.append('session', sid);
|
|
879
|
+
this._lastRenderedSession = sid;
|
|
861
880
|
params.append('limit', '500');
|
|
862
881
|
params.append('offset', '0');
|
|
863
882
|
|
|
883
|
+
// Monotonic request token: tab switches / rapid folder clicks can leave
|
|
884
|
+
// several /api/files fetches in flight at once. Only the LATEST request is
|
|
885
|
+
// allowed to commit its response, so a slow earlier fetch can't overwrite
|
|
886
|
+
// the UI (or _homePath / the watcher root) with stale data.
|
|
887
|
+
var reqId = (this._navSeq = (this._navSeq || 0) + 1);
|
|
888
|
+
|
|
864
889
|
this._statusBar.textContent = 'Loading...';
|
|
865
890
|
|
|
866
891
|
this.authFetch('/api/files?' + params.toString())
|
|
@@ -869,14 +894,28 @@
|
|
|
869
894
|
return resp.json();
|
|
870
895
|
})
|
|
871
896
|
.then(function (data) {
|
|
897
|
+
if (reqId !== self._navSeq) return; // superseded by a newer navigateTo
|
|
872
898
|
self._currentPath = data.currentPath;
|
|
873
899
|
self._basePath = data.baseFolder;
|
|
900
|
+
// `home` is the session's working dir (server-resolved); navigateHome
|
|
901
|
+
// roots here. Falls back to baseFolder for sessionless/legacy responses.
|
|
902
|
+
self._homePath = data.home || data.baseFolder;
|
|
874
903
|
self._items = data.items;
|
|
875
904
|
self._renderBreadcrumbs();
|
|
876
905
|
self._renderItems();
|
|
877
906
|
self._showBrowseView();
|
|
878
907
|
self._statusBar.textContent = data.totalCount + ' item' + (data.totalCount !== 1 ? 's' : '');
|
|
879
908
|
|
|
909
|
+
// fs-watcher: (re)connect to the dir the server actually resolved.
|
|
910
|
+
// open() only connects when it knows the path client-side; on a cold
|
|
911
|
+
// cache it passes null and the server resolves the session root here,
|
|
912
|
+
// so connect against data.currentPath to cover that case (and re-root
|
|
913
|
+
// when a tab switch re-navigates). connect() is idempotent per path.
|
|
914
|
+
var w = self._ensureFileWatcher();
|
|
915
|
+
if (w && typeof w.connect === 'function' && data.currentPath) {
|
|
916
|
+
try { w.connect(data.currentPath); } catch (_) { /* swallow */ }
|
|
917
|
+
}
|
|
918
|
+
|
|
880
919
|
// fs-watcher (#41 / ADR-0017 — wire model post-ff79038): one
|
|
881
920
|
// EventSource per session at panel mount, refcount-based per-path
|
|
882
921
|
// subscriptions multiplexed over it. Listing direct-child paths
|
|
@@ -900,6 +939,7 @@
|
|
|
900
939
|
}
|
|
901
940
|
})
|
|
902
941
|
.catch(function (err) {
|
|
942
|
+
if (reqId !== self._navSeq) return; // superseded; don't clobber newer status
|
|
903
943
|
self._statusBar.textContent = 'Error: ' + err.message;
|
|
904
944
|
});
|
|
905
945
|
};
|
|
@@ -916,7 +956,9 @@
|
|
|
916
956
|
|
|
917
957
|
FileBrowserPanel.prototype.navigateHome = function () {
|
|
918
958
|
this._markManualNav();
|
|
919
|
-
|
|
959
|
+
// "Home" is the active session's working dir (server-reported `home`),
|
|
960
|
+
// falling back to the sandbox base if we haven't loaded a listing yet.
|
|
961
|
+
this.navigateTo(this._homePath || this._basePath);
|
|
920
962
|
};
|
|
921
963
|
|
|
922
964
|
// ---------------------------------------------------------------------------
|
|
@@ -945,6 +987,22 @@
|
|
|
945
987
|
return this._activeSessionId() === sessionId;
|
|
946
988
|
};
|
|
947
989
|
|
|
990
|
+
/**
|
|
991
|
+
* Entry point for app.js when the active tab/session changes. The panel is
|
|
992
|
+
* a singleton shared across tabs, so a tab switch must re-root it to the
|
|
993
|
+
* new session's working dir — open() short-circuits when already open and
|
|
994
|
+
* would otherwise keep showing the previous tab's directory. Re-navigates
|
|
995
|
+
* with NO explicit path so the server resolves the new session's root from
|
|
996
|
+
* the `?session` param (getSessionId() now returns the new id). No-op when
|
|
997
|
+
* the panel is closed or the session is unchanged. _markManualNav() is NOT
|
|
998
|
+
* called: a tab switch should re-root and resume following the new tab.
|
|
999
|
+
*/
|
|
1000
|
+
FileBrowserPanel.prototype.notifyActiveSessionChanged = function (sessionId) {
|
|
1001
|
+
if (!this._open) return;
|
|
1002
|
+
if (!sessionId || sessionId === this._lastRenderedSession) return;
|
|
1003
|
+
this.navigateTo(null);
|
|
1004
|
+
};
|
|
1005
|
+
|
|
948
1006
|
/**
|
|
949
1007
|
* Returns true when the panel is currently configured to follow the
|
|
950
1008
|
* given session's OSC-7 CWD. Defaults to true on first lookup, and
|
package/src/server.js
CHANGED
|
@@ -870,7 +870,21 @@ class ClaudeCodeWebServer {
|
|
|
870
870
|
|
|
871
871
|
setupExpress() {
|
|
872
872
|
this.app.use(cors());
|
|
873
|
-
|
|
873
|
+
// Global JSON parser for normal endpoints (Express default ~100kb limit).
|
|
874
|
+
// The upload route mounts its own higher-limit parser (see
|
|
875
|
+
// POST /api/files/upload below); exempt it here so a base64 file body
|
|
876
|
+
// isn't rejected by the ~100kb default before the route runs. The
|
|
877
|
+
// trailing-slash normalization matches exactly the set of paths Express
|
|
878
|
+
// routes to that handler (`/api/files/upload` and `/api/files/upload/`).
|
|
879
|
+
const _globalJsonParser = express.json();
|
|
880
|
+
this.app.use((req, res, next) => {
|
|
881
|
+
// Case-insensitive + trailing-slash-normalized to match Express's
|
|
882
|
+
// default route matching (case-insensitive routing), so every form that
|
|
883
|
+
// reaches the upload handler is exempt from the ~100kb global parser.
|
|
884
|
+
const p = req.path.replace(/\/+$/, '').toLowerCase() || '/';
|
|
885
|
+
if (p === '/api/files/upload') return next();
|
|
886
|
+
return _globalJsonParser(req, res, next);
|
|
887
|
+
});
|
|
874
888
|
|
|
875
889
|
// Serve manifest.json with correct MIME type
|
|
876
890
|
this.app.get('/manifest.json', (req, res) => {
|
|
@@ -1403,7 +1417,30 @@ class ClaudeCodeWebServer {
|
|
|
1403
1417
|
|
|
1404
1418
|
// GET /api/files — List directory (files + folders), paginated
|
|
1405
1419
|
this.app.get('/api/files', (req, res) => {
|
|
1406
|
-
|
|
1420
|
+
// Per-tab file-browser root. Resolve the requesting session's home dir
|
|
1421
|
+
// (live OSC 7 cwd if tracked, else the spawn dir) when a `session` id is
|
|
1422
|
+
// supplied and its dir still validates. Used as (a) the default root
|
|
1423
|
+
// when the client sends no explicit `path`, and (b) the `home` value the
|
|
1424
|
+
// client points "Home" at — so Home stays the tab's dir even while
|
|
1425
|
+
// browsing subdirs. Mirrors GET /api/files/find. Falls back to baseFolder
|
|
1426
|
+
// for unknown/stale sessions so the browser never 403s on open.
|
|
1427
|
+
let sessionHome = null;
|
|
1428
|
+
const sid = typeof req.query.session === 'string' ? req.query.session : '';
|
|
1429
|
+
if (sid) {
|
|
1430
|
+
const session = this.claudeSessions.get(sid);
|
|
1431
|
+
if (session) {
|
|
1432
|
+
const candidate = session.liveCwd || session.workingDir;
|
|
1433
|
+
if (candidate && this.validatePath(candidate).valid) {
|
|
1434
|
+
sessionHome = candidate;
|
|
1435
|
+
} else {
|
|
1436
|
+
// Known session but its dir is missing or no longer inside the
|
|
1437
|
+
// sandbox — a real misconfiguration worth logging. (Unknown session
|
|
1438
|
+
// ids fall through silently: expected during cold-cache races.)
|
|
1439
|
+
console.warn(`/api/files: session ${sid} working dir unavailable; using baseFolder`);
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
const requestedPath = req.query.path || sessionHome || this.baseFolder;
|
|
1407
1444
|
const validation = this.validatePath(requestedPath);
|
|
1408
1445
|
if (!validation.valid) {
|
|
1409
1446
|
return res.status(403).json({ error: validation.error });
|
|
@@ -1480,7 +1517,7 @@ class ClaudeCodeWebServer {
|
|
|
1480
1517
|
totalCount,
|
|
1481
1518
|
offset,
|
|
1482
1519
|
limit,
|
|
1483
|
-
home: normalizePath(this.baseFolder),
|
|
1520
|
+
home: normalizePath(sessionHome || this.baseFolder),
|
|
1484
1521
|
baseFolder: normalizePath(this.baseFolder),
|
|
1485
1522
|
});
|
|
1486
1523
|
} catch (error) {
|
|
@@ -2739,7 +2776,12 @@ class ClaudeCodeWebServer {
|
|
|
2739
2776
|
// per spec ("user shell config is sacrosanct" applies to .gitignore
|
|
2740
2777
|
// too — we don't want to silently introduce a new tracked-by-default
|
|
2741
2778
|
// side effect on the user's repo).
|
|
2742
|
-
|
|
2779
|
+
// Route parser limit is sized for base64 of the 10 MB decoded cap
|
|
2780
|
+
// (~14 MB) plus the small JSON envelope. The decoded-size guard below
|
|
2781
|
+
// (buffer.length > 10 MB) remains the real per-file cap. This parser is
|
|
2782
|
+
// the ONLY one that runs for this route — the global parser above skips
|
|
2783
|
+
// `/api/files/upload`, so this limit governs (not the ~100kb default).
|
|
2784
|
+
this.app.post('/api/files/upload', express.json({ limit: '20mb' }), async (req, res) => {
|
|
2743
2785
|
const { targetDir, fileName, content, overwrite } = req.body;
|
|
2744
2786
|
if (!targetDir || !fileName || !content) {
|
|
2745
2787
|
return res.status(400).json({ error: 'targetDir, fileName, and content are required' });
|
|
@@ -2932,6 +2974,24 @@ class ClaudeCodeWebServer {
|
|
|
2932
2974
|
res.sendFile(path.join(__dirname, 'public', 'index.html'));
|
|
2933
2975
|
}
|
|
2934
2976
|
});
|
|
2977
|
+
|
|
2978
|
+
// Body-parser error handler (4-arg, must be registered AFTER routes).
|
|
2979
|
+
// express.json() rejects oversized/malformed bodies via next(err) BEFORE
|
|
2980
|
+
// the route runs; without this, Express's default handler returns HTML,
|
|
2981
|
+
// which the JSON API clients can't parse. We key on err.type — the marker
|
|
2982
|
+
// body-parser stamps on its own errors — so unrelated next(err) calls are
|
|
2983
|
+
// left to the default handler.
|
|
2984
|
+
this.app.use((err, req, res, next) => {
|
|
2985
|
+
if (res.headersSent || !err || !err.type) return next(err);
|
|
2986
|
+
if (err.type === 'entity.too.large') {
|
|
2987
|
+
return res.status(413).json({ error: 'Request body too large' });
|
|
2988
|
+
}
|
|
2989
|
+
if (err.type === 'entity.parse.failed' || err.type === 'encoding.unsupported'
|
|
2990
|
+
|| err.type === 'charset.unsupported' || err.type === 'entity.verify.failed') {
|
|
2991
|
+
return res.status(err.status || err.statusCode || 400).json({ error: 'Invalid request body' });
|
|
2992
|
+
}
|
|
2993
|
+
return next(err);
|
|
2994
|
+
});
|
|
2935
2995
|
}
|
|
2936
2996
|
|
|
2937
2997
|
/**
|