ai-or-die 0.1.70 → 0.1.71
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/bin/ai-or-die.js +29 -3
- package/package.json +5 -1
- package/src/base-bridge.js +88 -1
- package/src/public/app.js +285 -47
- package/src/public/components/bottom-nav.css +2 -1
- package/src/public/components/sticky-note.css +147 -0
- package/src/public/components/tabs.css +38 -0
- package/src/public/index.html +22 -0
- package/src/public/session-manager.js +38 -2
- package/src/public/sticky-note-card.js +335 -0
- package/src/public/voice-handler.js +63 -0
- package/src/server.js +707 -53
- package/src/sticky-note-engine.js +313 -0
- package/src/sticky-note-jsonl.js +278 -0
- package/src/sticky-note-prompt.js +216 -0
- package/src/sticky-note-summarizer.js +446 -0
- package/src/sticky-note-transcript.js +116 -0
- package/src/sticky-note-worker.js +103 -0
- package/src/utils/gguf-model-manager.js +198 -0
- package/src/utils/runtime.js +15 -0
- package/src/utils/secret-redact.js +86 -0
- package/src/utils/session-store.js +43 -2
package/bin/ai-or-die.js
CHANGED
|
@@ -12,6 +12,7 @@ try {
|
|
|
12
12
|
open = openModule.default || openModule;
|
|
13
13
|
} catch { open = null; }
|
|
14
14
|
const { ClaudeCodeWebServer } = require('../src/server');
|
|
15
|
+
const { isBun } = require('../src/utils/runtime');
|
|
15
16
|
|
|
16
17
|
const program = new Command();
|
|
17
18
|
|
|
@@ -35,10 +36,14 @@ program
|
|
|
35
36
|
.option('--terminal-alias <name>', 'display alias for Terminal (default: env TERMINAL_ALIAS or "Terminal")')
|
|
36
37
|
.option('--tunnel', 'enable dev tunnel (requires devtunnel CLI installed)')
|
|
37
38
|
.option('--tunnel-allow-anonymous', 'allow anonymous access to dev tunnel')
|
|
38
|
-
.option('--stt', '
|
|
39
|
+
.option('--no-stt', 'disable local speech-to-text (on by default; downloads ~670MB Parakeet V3 model on first use)')
|
|
39
40
|
.option('--stt-endpoint <url>', 'use external STT endpoint (OpenAI-compatible)')
|
|
40
41
|
.option('--stt-model-dir <path>', 'custom directory for STT model files')
|
|
41
|
-
.option('--stt-threads <number>', 'CPU threads for STT inference (default: auto, max 4)')
|
|
42
|
+
.option('--stt-threads <number>', 'CPU threads for STT inference (default: auto, max 4)')
|
|
43
|
+
.option('--no-sticky-notes', 'disable per-tab AI session summaries + auto tab titles (on by default)')
|
|
44
|
+
.option('--sticky-notes-model-dir <path>', 'custom directory for the sticky-note model file')
|
|
45
|
+
.option('--sticky-notes-model <url>', 'override the sticky-note model GGUF download URL')
|
|
46
|
+
.option('--sticky-notes-threads <number>', 'CPU threads for sticky-note inference (default: auto, max 4)');
|
|
42
47
|
|
|
43
48
|
// Auto-open is OFF by default and opt-in via --open. Legacy callers may still pass
|
|
44
49
|
// --no-open (the old opt-out flag); filter it out so it parses harmlessly as a no-op.
|
|
@@ -64,6 +69,22 @@ async function main() {
|
|
|
64
69
|
process.exit(1);
|
|
65
70
|
}
|
|
66
71
|
|
|
72
|
+
// Bun: limited support. The app continues to run, but two native
|
|
73
|
+
// incompatibilities apply (both externally confirmed, neither fixable here):
|
|
74
|
+
// • node-llama-cpp's N-API addon crashes Bun (NAPI FATAL ERROR, exit 133),
|
|
75
|
+
// so the sticky-note model is force-disabled (server + engine self-gate).
|
|
76
|
+
// • node-pty cannot read the PTY master under Bun (oven-sh/bun#25822) — the
|
|
77
|
+
// terminal may "start" but never show a prompt (it hangs).
|
|
78
|
+
// STT still works under Bun. For a guaranteed-working terminal, use Node.js.
|
|
79
|
+
if (isBun()) {
|
|
80
|
+
const bunVer = (process.versions && process.versions.bun) || 'unknown';
|
|
81
|
+
console.log(`\n\x1b[33m⚠ Running under Bun ${bunVer} — limited support. Continuing with sticky-notes disabled.\x1b[0m`);
|
|
82
|
+
console.log(' • Sticky-note summaries are disabled under Bun (node-llama-cpp crashes Bun’s N-API).');
|
|
83
|
+
console.log(' • Heads-up: terminal output can hang under Bun (node-pty/#25822).');
|
|
84
|
+
console.log(' If the prompt never appears, run with Node.js instead:');
|
|
85
|
+
console.log(` \x1b[1mnode ${path.relative(process.cwd(), __filename) || 'bin/ai-or-die.js'} ${process.argv.slice(2).join(' ')}\x1b[0m\n`);
|
|
86
|
+
}
|
|
87
|
+
|
|
67
88
|
// Handle authentication logic
|
|
68
89
|
// Tunnel mode disables auth — the tunnel itself controls access
|
|
69
90
|
let authToken = null;
|
|
@@ -93,10 +114,15 @@ async function main() {
|
|
|
93
114
|
geminiAlias: options.geminiAlias || process.env.GEMINI_ALIAS || 'Gemini',
|
|
94
115
|
terminalAlias: options.terminalAlias || process.env.TERMINAL_ALIAS || 'Terminal',
|
|
95
116
|
folderMode: true, // Always use folder mode
|
|
96
|
-
stt: options.stt
|
|
117
|
+
stt: options.stt !== false && process.env.STT_DISABLED !== '1',
|
|
97
118
|
sttEndpoint: options.sttEndpoint || process.env.STT_ENDPOINT,
|
|
98
119
|
sttModelDir: options.sttModelDir || process.env.AI_OR_DIE_MODELS_DIR,
|
|
99
120
|
sttThreads: options.sttThreads || process.env.STT_THREADS,
|
|
121
|
+
// Per-tab AI session summaries (on by default; --no-sticky-notes disables).
|
|
122
|
+
stickyNotes: options.stickyNotes !== false && process.env.STICKY_NOTES_DISABLED !== '1',
|
|
123
|
+
stickyNotesModelDir: options.stickyNotesModelDir || process.env.STICKY_NOTES_MODEL_DIR,
|
|
124
|
+
stickyNotesModel: options.stickyNotesModel || process.env.STICKY_NOTES_MODEL,
|
|
125
|
+
stickyNotesThreads: options.stickyNotesThreads || process.env.STICKY_NOTES_THREADS,
|
|
100
126
|
};
|
|
101
127
|
|
|
102
128
|
console.log('Starting ai-or-die...');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-or-die",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.71",
|
|
4
4
|
"description": "Universal AI coding terminal — Claude, Copilot, Gemini & more in your browser",
|
|
5
5
|
"main": "src/server.js",
|
|
6
6
|
"bin": {
|
|
@@ -41,6 +41,7 @@
|
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@lydell/node-pty": "1.2.0-beta.10",
|
|
43
43
|
"@vscode/ripgrep": "^1.18.0",
|
|
44
|
+
"@xterm/headless": "^6.0.0",
|
|
44
45
|
"chokidar": "^5.0.0",
|
|
45
46
|
"commander": "^12.1.0",
|
|
46
47
|
"cors": "^2.8.5",
|
|
@@ -66,5 +67,8 @@
|
|
|
66
67
|
"jsdom": "^24.1.3",
|
|
67
68
|
"mocha": "^11.7.1",
|
|
68
69
|
"postject": "^1.0.0-alpha.6"
|
|
70
|
+
},
|
|
71
|
+
"optionalDependencies": {
|
|
72
|
+
"node-llama-cpp": "^3.18.1"
|
|
69
73
|
}
|
|
70
74
|
}
|
package/src/base-bridge.js
CHANGED
|
@@ -8,6 +8,22 @@ const os = require('os');
|
|
|
8
8
|
const PTY_WRITE_CHUNK_SIZE = 4096;
|
|
9
9
|
/** Inter-chunk delay in ms — allows ConPTY buffer to drain */
|
|
10
10
|
const PTY_WRITE_CHUNK_DELAY_MS = 10;
|
|
11
|
+
/**
|
|
12
|
+
* Grace window (ms) during which a read EAGAIN with no life-sign yet is treated
|
|
13
|
+
* as a benign transient startup blip and swallowed. After this, a *sustained*
|
|
14
|
+
* EAGAIN flood with no output is treated as a real failure and surfaced (rather
|
|
15
|
+
* than hanging silently until the 30s spawn watchdog). Node delivers its first
|
|
16
|
+
* PTY output well within this window; Bun's permanent read EAGAIN does not.
|
|
17
|
+
*/
|
|
18
|
+
const PTY_EAGAIN_GRACE_MS = 3000;
|
|
19
|
+
/**
|
|
20
|
+
* Minimum number of pre-life-sign EAGAIN errors before a session is treated as a
|
|
21
|
+
* sustained-failure (the Bun + node-pty read loop fires EAGAIN continuously,
|
|
22
|
+
* forever). Set well above any plausible Node startup count (node-pty emits
|
|
23
|
+
* "EAGAIN twice at first"), so a stray late EAGAIN on a slow Node session can
|
|
24
|
+
* never trip a false teardown — worst case it falls through to the 30s watchdog.
|
|
25
|
+
*/
|
|
26
|
+
const PTY_EAGAIN_FAIL_THRESHOLD = 50;
|
|
11
27
|
|
|
12
28
|
class BaseBridge {
|
|
13
29
|
constructor(toolName, options = {}) {
|
|
@@ -28,6 +44,40 @@ class BaseBridge {
|
|
|
28
44
|
this._commandReady = this.initCommand();
|
|
29
45
|
}
|
|
30
46
|
|
|
47
|
+
/**
|
|
48
|
+
* True when a PTY 'error' is a read EAGAIN ("resource temporarily unavailable").
|
|
49
|
+
* @param {Error & {code?: string}} error
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
static isEagainError(error) {
|
|
53
|
+
return !!(error && (error.code === 'EAGAIN' || (error.message && error.message.includes('EAGAIN'))));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Decide whether a PTY 'error' is a benign, swallowable transient read EAGAIN.
|
|
58
|
+
*
|
|
59
|
+
* Swallow when the error is EAGAIN AND any of: a life-sign already arrived
|
|
60
|
+
* (post-startup blip); we are still inside the startup grace window; or the
|
|
61
|
+
* EAGAIN count has not yet reached the sustained-failure threshold. Only a
|
|
62
|
+
* *sustained* EAGAIN flood with no life-sign past the grace window (the Bun +
|
|
63
|
+
* node-pty read failure, oven-sh/bun#25822, where the master never delivers
|
|
64
|
+
* data) is surfaced — so the session tears down + the client gets feedback
|
|
65
|
+
* instead of an infinite hang, while a stray late EAGAIN on a slow Node
|
|
66
|
+
* session is never enough to false-fail it.
|
|
67
|
+
*
|
|
68
|
+
* @param {Error & {code?: string}} error
|
|
69
|
+
* @param {boolean} receivedLifeSign - true once any onData/onExit fired
|
|
70
|
+
* @param {number} elapsedMs - ms since the PTY was spawned
|
|
71
|
+
* @param {number} eagainCount - count of EAGAIN errors seen so far (incl. this one)
|
|
72
|
+
* @returns {boolean} true → swallow (return early); false → handle the error
|
|
73
|
+
*/
|
|
74
|
+
static shouldSwallowTransientEagain(error, receivedLifeSign, elapsedMs, eagainCount) {
|
|
75
|
+
if (!BaseBridge.isEagainError(error)) return false;
|
|
76
|
+
if (receivedLifeSign) return true;
|
|
77
|
+
if (elapsedMs < PTY_EAGAIN_GRACE_MS) return true;
|
|
78
|
+
return eagainCount < PTY_EAGAIN_FAIL_THRESHOLD;
|
|
79
|
+
}
|
|
80
|
+
|
|
31
81
|
/**
|
|
32
82
|
* Async command discovery — runs where/which without blocking the event loop.
|
|
33
83
|
* Called automatically from the constructor; await bridge._commandReady to ensure it's done.
|
|
@@ -264,9 +314,16 @@ class BaseBridge {
|
|
|
264
314
|
|
|
265
315
|
// Spawn watchdog: if no data, exit, or error arrives within 30s, treat as failure
|
|
266
316
|
let receivedLifeSign = false;
|
|
317
|
+
const ptyStartedAt = Date.now();
|
|
318
|
+
let eagainCount = 0;
|
|
319
|
+
// One-shot guard so the watchdog + error paths can each tear the session
|
|
320
|
+
// down + call onError at most once (a sustained EAGAIN flood fires the
|
|
321
|
+
// error handler repeatedly).
|
|
322
|
+
let terminalFailureHandled = false;
|
|
267
323
|
const SPAWN_TIMEOUT_MS = 30000;
|
|
268
324
|
const spawnWatchdog = setTimeout(() => {
|
|
269
|
-
if (!receivedLifeSign && session.active) {
|
|
325
|
+
if (!receivedLifeSign && session.active && !terminalFailureHandled) {
|
|
326
|
+
terminalFailureHandled = true;
|
|
270
327
|
console.error(`${this.toolName} session ${sessionId}: no response within ${SPAWN_TIMEOUT_MS}ms, treating as spawn failure`);
|
|
271
328
|
session.active = false;
|
|
272
329
|
this.sessions.delete(sessionId);
|
|
@@ -340,6 +397,26 @@ class BaseBridge {
|
|
|
340
397
|
this._addPtyDisposable(session, onExitDisposable);
|
|
341
398
|
|
|
342
399
|
const errorHandler = (error) => {
|
|
400
|
+
// read EAGAIN ("resource temporarily unavailable") is a known transient
|
|
401
|
+
// PTY-startup condition under Node — node-pty's own socket 'error'
|
|
402
|
+
// handler ignores it ("fs.ReadStream gets EAGAIN twice at first") and
|
|
403
|
+
// keeps the master fd alive. We attach a *second* 'error' listener on the
|
|
404
|
+
// same socket, so we swallow the same transient blips: any EAGAIN after a
|
|
405
|
+
// life-sign (output/exit already arrived), within a short startup grace
|
|
406
|
+
// window, or below the sustained-failure threshold. That stops a benign
|
|
407
|
+
// EAGAIN from tearing the session down and surfacing a fatal "Connection
|
|
408
|
+
// Error" that makes the client retry + double-spawn.
|
|
409
|
+
//
|
|
410
|
+
// But a *sustained* EAGAIN flood with no life-sign is NOT transient — it
|
|
411
|
+
// is the Bun + node-pty read failure (oven-sh/bun#25822), where the PTY
|
|
412
|
+
// master never delivers data. Swallowing it forever turns a dead session
|
|
413
|
+
// into a silent hang until the 30s watchdog. Once the grace window passes
|
|
414
|
+
// AND the EAGAIN count crosses the threshold (with no life-sign), fall
|
|
415
|
+
// through so the error surfaces (client gets feedback / can reconnect).
|
|
416
|
+
if (BaseBridge.isEagainError(error)) eagainCount++;
|
|
417
|
+
if (BaseBridge.shouldSwallowTransientEagain(error, receivedLifeSign, Date.now() - ptyStartedAt, eagainCount)) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
343
420
|
if (!receivedLifeSign) {
|
|
344
421
|
receivedLifeSign = true;
|
|
345
422
|
clearTimeout(spawnWatchdog);
|
|
@@ -349,12 +426,22 @@ class BaseBridge {
|
|
|
349
426
|
if (error.message && error.message.includes('read EIO')) {
|
|
350
427
|
return;
|
|
351
428
|
}
|
|
429
|
+
// One-shot: a sustained EAGAIN flood (or the watchdog) must not tear the
|
|
430
|
+
// session down or call onError more than once.
|
|
431
|
+
if (terminalFailureHandled) {
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
terminalFailureHandled = true;
|
|
352
435
|
console.error(`${this.toolName} session ${sessionId} error:`, error);
|
|
353
436
|
if (session.killTimeout) {
|
|
354
437
|
clearTimeout(session.killTimeout);
|
|
355
438
|
session.killTimeout = null;
|
|
356
439
|
}
|
|
357
440
|
this._disposePtyDisposables(session, sessionId);
|
|
441
|
+
// Kill the PTY child so a failed session (e.g. a Bun EAGAIN flood, where
|
|
442
|
+
// the shell is alive but unreadable) doesn't leak as a zombie process /
|
|
443
|
+
// FD — mirrors the spawn-watchdog teardown. Harmless if already dead.
|
|
444
|
+
try { ptyProcess.kill(); } catch (e) { /* ignore — may already be dead */ }
|
|
358
445
|
if (this.sessions.has(sessionId)) {
|
|
359
446
|
session.active = false;
|
|
360
447
|
this.sessions.delete(sessionId);
|