@rubytech/create-maxy-lite 0.1.13 → 0.1.15
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/lib/paths.mjs +8 -1
- package/package.json +1 -1
- package/payload/webchat/server.mjs +58 -4
package/lib/paths.mjs
CHANGED
|
@@ -183,7 +183,14 @@ export function launcherScript({
|
|
|
183
183
|
port = PATHS.webchatPort,
|
|
184
184
|
distro = PATHS.distro,
|
|
185
185
|
} = {}) {
|
|
186
|
-
|
|
186
|
+
// Kill any prior relay before starting so `maxy-lite` always runs the freshly
|
|
187
|
+
// installed server.mjs, never a stale process still holding the port (which made
|
|
188
|
+
// a new build appear to "have no effect"). The relay writes its pid to
|
|
189
|
+
// <vault>/.maxy-lite/relay.pid; kill via the bash builtin (no external tools),
|
|
190
|
+
// and a best-effort pkill catches a relay started before pid-file support. The
|
|
191
|
+
// relay's own EADDRINUSE handler makes any remaining conflict loud.
|
|
192
|
+
const killPrior = `P=$(cat ${vaultGuest}/.maxy-lite/relay.pid 2>/dev/null); [ -n "$P" ] && kill "$P" 2>/dev/null; pkill -f "node server.mjs" 2>/dev/null; sleep 1`
|
|
193
|
+
const inner = `${killPrior}; cd ${webchatDir} && ${webchatRunEnv({ vaultGuest, port })} exec node server.mjs`
|
|
187
194
|
return `#!${prefix}/bin/bash
|
|
188
195
|
# maxy-lite launcher — starts the on-device web chat relay.
|
|
189
196
|
# Enters the glibc Ubuntu layer with the shared-storage vault bound in, then runs
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rubytech/create-maxy-lite",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"description": "Install maxy-lite on an Android phone: orchestrates proot-distro Ubuntu, glibc Node, claude, the web-chat relay, the vault and its bind-mount — run via npx in bare Termux.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -34,8 +34,10 @@ const CLAUDE_BIN = process.env.LITE_CLAUDE_BIN || 'claude'
|
|
|
34
34
|
const PROJECTS_DIR = path.join(os.homedir(), '.claude', 'projects')
|
|
35
35
|
const STATE_DIR = process.env.LITE_STATE_DIR || path.join(AGENT_HOME, '.maxy-lite')
|
|
36
36
|
const SESSION_FILE = path.join(STATE_DIR, 'session-id')
|
|
37
|
+
const PID_FILE = path.join(STATE_DIR, 'relay.pid') // so the launcher can kill a prior relay before starting a fresh one
|
|
37
38
|
const TAIL_MS = 250 // JSONL poll interval
|
|
38
39
|
const QUIET_MS = 1500 // silence that ends a turn for op=done
|
|
40
|
+
const NO_REPLY_MS = 8000 // a turn with NO transcript growth this long = swallowed; surface why
|
|
39
41
|
|
|
40
42
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
41
43
|
|
|
@@ -89,9 +91,26 @@ const child = pty.spawn(CLAUDE_BIN, args, {
|
|
|
89
91
|
cwd: AGENT_HOME,
|
|
90
92
|
env: process.env,
|
|
91
93
|
})
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
|
|
94
|
+
// Render comes from the JSONL, not these bytes — but capture a bounded, sanitised
|
|
95
|
+
// sample of claude's own pty output so a swallow is diagnosable. It reveals a
|
|
96
|
+
// first-run trust/onboarding prompt eating the input, an error screen, or silence
|
|
97
|
+
// (input never submitted). Draining also prevents backpressure stalling claude.
|
|
98
|
+
let ptyBytes = 0
|
|
99
|
+
let turnPtyBase = 0
|
|
100
|
+
let ptySample = ''
|
|
101
|
+
const sanitizePty = (s) =>
|
|
102
|
+
s
|
|
103
|
+
.replace(/\x1b\[[0-9;?]*[A-Za-z]/g, '')
|
|
104
|
+
.replace(/\x1b[()][AB0]/g, '')
|
|
105
|
+
.replace(/[\r\n]+/g, ' ')
|
|
106
|
+
.replace(/[^\x20-\x7e]/g, '')
|
|
107
|
+
child.onData((d) => {
|
|
108
|
+
ptyBytes += d.length
|
|
109
|
+
ptySample = (ptySample + sanitizePty(d)).slice(-300) // most recent 300 printable chars
|
|
110
|
+
})
|
|
111
|
+
// Log the fresh claude screen once it has settled — a trust/onboarding prompt here
|
|
112
|
+
// explains a swallowed first message before any turn is even sent.
|
|
113
|
+
setTimeout(() => log('output', { turn: 0, ptyBytes, head: ptySample.slice(0, 300) }), 3000)
|
|
95
114
|
child.onExit(({ exitCode }) => log('child-exit', { exitCode }))
|
|
96
115
|
log('spawn', { turn: 0, pty: true, pid: child.pid })
|
|
97
116
|
|
|
@@ -162,9 +181,26 @@ setInterval(tail, TAIL_MS)
|
|
|
162
181
|
// Gated on turnSawGrowth so a slow first JSONL write (cold start, discovery
|
|
163
182
|
// latency) cannot mark the turn done before any reply has rendered.
|
|
164
183
|
setInterval(() => {
|
|
165
|
-
if (turnActive
|
|
184
|
+
if (!turnActive) return
|
|
185
|
+
if (turnSawGrowth && Date.now() - lastActivity > QUIET_MS) {
|
|
166
186
|
turnActive = false
|
|
167
187
|
log('done', { turn, ranMs: Date.now() - turnStart })
|
|
188
|
+
} else if (!turnSawGrowth && Date.now() - turnStart > NO_REPLY_MS) {
|
|
189
|
+
// No transcript growth after the input → the message was swallowed. Surface the
|
|
190
|
+
// two facts that locate it: what claude actually emitted to its pty, and whether
|
|
191
|
+
// its transcript was even found (and if not, what dirs exist vs the one wanted).
|
|
192
|
+
turnActive = false
|
|
193
|
+
log('output', { turn, ptyBytes: ptyBytes - turnPtyBase, head: ptySample.slice(0, 300) })
|
|
194
|
+
if (!transcriptPath) {
|
|
195
|
+
let dirs = []
|
|
196
|
+
try {
|
|
197
|
+
dirs = fs.readdirSync(PROJECTS_DIR)
|
|
198
|
+
} catch {
|
|
199
|
+
/* PROJECTS_DIR absent */
|
|
200
|
+
}
|
|
201
|
+
log('jsonl-missing', { want: `${sessionId}.jsonl`, projectsDir: PROJECTS_DIR, dirs: `[${dirs.join(',')}]` })
|
|
202
|
+
}
|
|
203
|
+
log('done', { turn, ok: false, reason: 'no-transcript-growth', ranMs: Date.now() - turnStart })
|
|
168
204
|
}
|
|
169
205
|
}, 500)
|
|
170
206
|
|
|
@@ -180,6 +216,8 @@ function submitToAgent(text) {
|
|
|
180
216
|
turnSawGrowth = false
|
|
181
217
|
turnStart = Date.now()
|
|
182
218
|
lastActivity = Date.now()
|
|
219
|
+
turnPtyBase = ptyBytes // baseline so op=output reports bytes claude emitted THIS turn
|
|
220
|
+
ptySample = ''
|
|
183
221
|
log('inbound', { turn, chars: text.length })
|
|
184
222
|
child.write(ptyLine(text))
|
|
185
223
|
log('spawn', { turn, pty: true, pid: child.pid })
|
|
@@ -224,6 +262,22 @@ wss.on('connection', (ws) => {
|
|
|
224
262
|
})
|
|
225
263
|
})
|
|
226
264
|
|
|
265
|
+
// A stale relay still holding the port is the usual cause of "new build installed
|
|
266
|
+
// but old code serving". Make it loud instead of a silent stale-serve.
|
|
267
|
+
server.on('error', (e) => {
|
|
268
|
+
log('listen-error', { code: e.code, port: PORT })
|
|
269
|
+
process.exit(1)
|
|
270
|
+
})
|
|
227
271
|
server.listen(PORT, '127.0.0.1', () => {
|
|
272
|
+
// Record our pid so the next launch can kill this relay and take over cleanly,
|
|
273
|
+
// and emit a build marker: op=relay-start proves THIS server.mjs (not a stale
|
|
274
|
+
// one) is the live process.
|
|
275
|
+
try {
|
|
276
|
+
fs.mkdirSync(STATE_DIR, { recursive: true })
|
|
277
|
+
fs.writeFileSync(PID_FILE, String(process.pid))
|
|
278
|
+
} catch {
|
|
279
|
+
/* pid file is best-effort */
|
|
280
|
+
}
|
|
281
|
+
log('relay-start', { pid: process.pid, port: PORT })
|
|
228
282
|
log('listening', { port: PORT, sessionId, home: AGENT_HOME })
|
|
229
283
|
})
|