@sma1lboy/rove 0.9.237 → 0.9.238
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/dist/cli/{chunk-aj3fk7xz.js → chunk-0wgheepg.js} +2 -2
- package/dist/cli/{chunk-kavsx69v.js → chunk-1265fmzk.js} +1 -1
- package/dist/cli/{chunk-4bmap7dg.js → chunk-1bm6sryf.js} +1 -1
- package/dist/cli/{chunk-qyzvcgva.js → chunk-2yd128k1.js} +1 -1
- package/dist/cli/chunk-507zn8rs.js +3 -0
- package/dist/cli/{chunk-sp99y6eb.js → chunk-67ev8k22.js} +1 -1
- package/dist/cli/{chunk-63xy91mc.js → chunk-77w83wgh.js} +1 -1
- package/dist/cli/{chunk-40dk2add.js → chunk-8h0khkap.js} +1 -1
- package/dist/cli/{chunk-xhgmnvjz.js → chunk-9ek7xm94.js} +2 -2
- package/dist/cli/{chunk-hkq7c6hy.js → chunk-a1k2z44b.js} +1 -1
- package/dist/cli/{chunk-rvx5zz72.js → chunk-acw32w13.js} +1 -1
- package/dist/cli/{chunk-b9ywdsdj.js → chunk-aj7fkrha.js} +1 -1
- package/dist/cli/{chunk-3gmzrstj.js → chunk-b503z32q.js} +1 -1
- package/dist/cli/{chunk-4y1fdz34.js → chunk-be757xzj.js} +1 -1
- package/dist/cli/{chunk-tdze2mnn.js → chunk-bhqracn8.js} +1 -1
- package/dist/cli/{chunk-v4y0sf0x.js → chunk-grt1e2s8.js} +1 -1
- package/dist/cli/{chunk-5e39zhvj.js → chunk-hk8mc43r.js} +1 -1
- package/dist/cli/{chunk-5bsk7qmg.js → chunk-janvvpv5.js} +1 -1
- package/dist/cli/{chunk-12kgbrj1.js → chunk-mb1e73jm.js} +2 -2
- package/dist/cli/chunk-nntg9d85.js +3 -0
- package/dist/cli/{chunk-kbje1cpy.js → chunk-pzbrj3gd.js} +1 -1
- package/dist/cli/{chunk-efx1gqmp.js → chunk-r8xqvg26.js} +1 -1
- package/dist/cli/{chunk-1q8tbbpz.js → chunk-tcj36cqg.js} +1 -1
- package/dist/cli/{chunk-a8et9f63.js → chunk-y4048296.js} +1 -1
- package/dist/cli/{chunk-072x915d.js → chunk-yj5pjxx2.js} +1 -1
- package/dist/cli/{chunk-dhtsd706.js → chunk-z5fnv7nk.js} +4 -4
- package/dist/cli/{chunk-c4dx3er1.js → chunk-zwgns5ce.js} +1 -1
- package/dist/cli/index.js +3 -3
- package/dist/cli/kobe.js +1 -1
- package/dist/cli/pty-host-node.mjs +310 -9
- package/dist/cli/rove.js +1 -1
- package/dist/web-ui/assets/harness-CQ-1WI4q.js +87 -0
- package/dist/web-ui/assets/index-C4dLQEqf.css +1 -0
- package/dist/web-ui/assets/{index-D2wALq0b.js → index-_ttAn6pY.js} +2 -2
- package/dist/web-ui/index.html +2 -2
- package/dist/web-ui/pty-server.mjs +44 -0
- package/dist/web-ui/pty-session-lifecycle.mjs +41 -2
- package/package.json +1 -1
- package/dist/cli/chunk-1m7cp5ze.js +0 -3
- package/dist/cli/chunk-vz5g1p3w.js +0 -3
- package/dist/web-ui/assets/harness-CLsfHgXB.js +0 -86
- package/dist/web-ui/assets/index-B4knjCIs.css +0 -1
package/dist/web-ui/index.html
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
<meta charset="UTF-8" />
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
7
|
<title>Rove</title>
|
|
8
|
-
<script type="module" crossorigin src="/assets/index-
|
|
9
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
8
|
+
<script type="module" crossorigin src="/assets/index-_ttAn6pY.js"></script>
|
|
9
|
+
<link rel="stylesheet" crossorigin href="/assets/index-C4dLQEqf.css">
|
|
10
10
|
</head>
|
|
11
11
|
<body>
|
|
12
12
|
<div id="app"></div>
|
|
@@ -12,6 +12,10 @@
|
|
|
12
12
|
* ws /pty?tab=<id>&taskId=<id>&mode=engine|shell&cols=<n>&rows=<n>
|
|
13
13
|
* POST /pty/close { tab } kill the tab process
|
|
14
14
|
* POST /pty/send { tab, taskId, text } paste text + Enter into the tab's engine
|
|
15
|
+
*
|
|
16
|
+
* With KOBE_PTY_CAST=1 (film capture) every tab is also recorded:
|
|
17
|
+
* POST /pty/mark { tab, label } storyboard marker into the recording
|
|
18
|
+
* GET /pty/cast?tab=<id> the recording (asciicast v2), then dropped
|
|
15
19
|
*/
|
|
16
20
|
|
|
17
21
|
import { createServer } from "node:http"
|
|
@@ -24,6 +28,7 @@ import { createScrollback } from "./pty-scrollback.mjs"
|
|
|
24
28
|
import { watchParent } from "./pty-parent-watch.mjs"
|
|
25
29
|
import { createPtySessionManager } from "./pty-session-lifecycle.mjs"
|
|
26
30
|
import { createSpecFetcher } from "./pty-spec.mjs"
|
|
31
|
+
import { createCast } from "./pty-cast.mjs"
|
|
27
32
|
|
|
28
33
|
const PORT = Number.parseInt(process.env.KOBE_PTY_PORT ?? "5175", 10)
|
|
29
34
|
const SCROLLBACK_CAP = 256 * 1024 // bytes of recent output replayed on (re)attach
|
|
@@ -40,6 +45,7 @@ const ptySessions = createPtySessionManager({
|
|
|
40
45
|
createScrollback,
|
|
41
46
|
scrollbackCap: SCROLLBACK_CAP,
|
|
42
47
|
env: ptyEnv,
|
|
48
|
+
createCast: process.env.KOBE_PTY_CAST === "1" ? createCast : null,
|
|
43
49
|
})
|
|
44
50
|
|
|
45
51
|
/**
|
|
@@ -123,6 +129,44 @@ const server = createServer((req, res) => {
|
|
|
123
129
|
})
|
|
124
130
|
return
|
|
125
131
|
}
|
|
132
|
+
if (req.method === "POST" && url.pathname === "/pty/mark") {
|
|
133
|
+
const denial = ptyRouteDenial(req, url)
|
|
134
|
+
if (denial) {
|
|
135
|
+
res.writeHead(denial)
|
|
136
|
+
res.end()
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
let body = ""
|
|
140
|
+
req.on("data", (c) => {
|
|
141
|
+
body += c
|
|
142
|
+
})
|
|
143
|
+
req.on("end", () => {
|
|
144
|
+
let tab
|
|
145
|
+
let label
|
|
146
|
+
try {
|
|
147
|
+
;({ tab, label } = JSON.parse(body || "{}"))
|
|
148
|
+
} catch {
|
|
149
|
+
/* ignore */
|
|
150
|
+
}
|
|
151
|
+
const ok = typeof tab === "string" && typeof label === "string" && ptySessions.markCast(tab, label)
|
|
152
|
+
res.writeHead(ok ? 200 : 404, { "content-type": "application/json" })
|
|
153
|
+
res.end(JSON.stringify({ marked: ok }))
|
|
154
|
+
})
|
|
155
|
+
return
|
|
156
|
+
}
|
|
157
|
+
if (req.method === "GET" && url.pathname === "/pty/cast") {
|
|
158
|
+
// The recording is everything the tab displayed — same gate as attaching.
|
|
159
|
+
const denial = ptyRouteDenial(req, url)
|
|
160
|
+
if (denial) {
|
|
161
|
+
res.writeHead(denial)
|
|
162
|
+
res.end()
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
const cast = ptySessions.takeCast(url.searchParams.get("tab") ?? "")
|
|
166
|
+
res.writeHead(cast === null ? 404 : 200, { "content-type": "application/x-asciicast" })
|
|
167
|
+
res.end(cast ?? "")
|
|
168
|
+
return
|
|
169
|
+
}
|
|
126
170
|
if (req.method === "POST" && url.pathname === "/pty/close") {
|
|
127
171
|
// Killing a tab is a side effect any caller that reached this port could
|
|
128
172
|
// abuse to DoS the session (tab ids are client-generated/observable), so
|
|
@@ -80,11 +80,17 @@ export function createPtySessionManager({
|
|
|
80
80
|
maxSessions = DEFAULT_MAX_SESSIONS,
|
|
81
81
|
detachGraceMs = DEFAULT_DETACH_GRACE_MS,
|
|
82
82
|
backpressure = DEFAULT_BACKPRESSURE,
|
|
83
|
+
/** Film capture only (`KOBE_PTY_CAST=1`): record each tab as an asciicast. */
|
|
84
|
+
createCast = null,
|
|
83
85
|
}) {
|
|
84
86
|
/** @type {Map<string, { pty: any, scrollback: ReturnType<createScrollback>, sockets: Set<any> }>} */
|
|
85
87
|
const sessions = new Map()
|
|
86
88
|
/** @type {Map<string, Promise<any>>} */
|
|
87
89
|
const pendingSpawns = new Map()
|
|
90
|
+
// Keyed by tab and kept past the process's exit: a take that quits and
|
|
91
|
+
// reattaches the TUI is still one recording, and the cast is read after close.
|
|
92
|
+
/** @type {Map<string, ReturnType<NonNullable<typeof createCast>>>} */
|
|
93
|
+
const casts = new Map()
|
|
88
94
|
|
|
89
95
|
/** Pause the pty once a socket is saturated and poll for drain to resume.
|
|
90
96
|
* Idempotent: a second saturation while already paused is a no-op. */
|
|
@@ -158,6 +164,12 @@ export function createPtySessionManager({
|
|
|
158
164
|
const path = typeof spawnEnv.PATH === "string" && spawnEnv.PATH ? "set" : "missing"
|
|
159
165
|
throw new Error(`PTY spawn failed (command ${cmd}, cwd ${spec.cwd}, PATH ${path}): ${error instanceof Error ? error.message : String(error)}`)
|
|
160
166
|
}
|
|
167
|
+
let cast = null
|
|
168
|
+
if (createCast) {
|
|
169
|
+
cast = casts.get(tabId) ?? null
|
|
170
|
+
if (cast) cast.respawn(cols, rows)
|
|
171
|
+
else casts.set(tabId, (cast = createCast({ cols, rows })))
|
|
172
|
+
}
|
|
161
173
|
const entry = {
|
|
162
174
|
pty,
|
|
163
175
|
scrollback: createScrollback(scrollbackCap),
|
|
@@ -165,9 +177,11 @@ export function createPtySessionManager({
|
|
|
165
177
|
paused: false,
|
|
166
178
|
drainTimer: null,
|
|
167
179
|
detachTimer: null,
|
|
180
|
+
cast,
|
|
168
181
|
}
|
|
169
182
|
pty.onData((data) => {
|
|
170
183
|
entry.scrollback.push(data)
|
|
184
|
+
entry.cast?.output(data)
|
|
171
185
|
for (const ws of entry.sockets) {
|
|
172
186
|
if (ws.readyState === ws.OPEN) ws.send(data)
|
|
173
187
|
}
|
|
@@ -242,6 +256,13 @@ export function createPtySessionManager({
|
|
|
242
256
|
}
|
|
243
257
|
}
|
|
244
258
|
|
|
259
|
+
function resizePty(tabId, entry, cols, rows) {
|
|
260
|
+
safePty(tabId, entry, (pty) => {
|
|
261
|
+
pty.resize(cols, rows)
|
|
262
|
+
entry.cast?.resize(cols, rows)
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
|
|
245
266
|
async function attachSocket({ ws, tabId, taskId, mode, cols, rows }) {
|
|
246
267
|
const entry = await ensureSession(tabId, taskId, mode, cols, rows)
|
|
247
268
|
// The browser can leave during the spawn; its close event already fired,
|
|
@@ -251,7 +272,7 @@ export function createPtySessionManager({
|
|
|
251
272
|
entry.sockets.add(ws)
|
|
252
273
|
clearDetachTimer(entry)
|
|
253
274
|
if (replay && ws.readyState === ws.OPEN) ws.send(replay)
|
|
254
|
-
|
|
275
|
+
resizePty(tabId, entry, cols, rows)
|
|
255
276
|
|
|
256
277
|
ws.on("message", (raw) => {
|
|
257
278
|
const text = raw.toString()
|
|
@@ -259,7 +280,7 @@ export function createPtySessionManager({
|
|
|
259
280
|
try {
|
|
260
281
|
const msg = JSON.parse(text)
|
|
261
282
|
if (msg && msg.type === "resize" && Number.isFinite(msg.cols) && Number.isFinite(msg.rows)) {
|
|
262
|
-
|
|
283
|
+
resizePty(tabId, entry, Math.max(1, msg.cols | 0), Math.max(1, msg.rows | 0))
|
|
263
284
|
return
|
|
264
285
|
}
|
|
265
286
|
} catch {
|
|
@@ -306,6 +327,22 @@ export function createPtySessionManager({
|
|
|
306
327
|
return { sent: true, spawned }
|
|
307
328
|
}
|
|
308
329
|
|
|
330
|
+
/** Append a storyboard marker; false when the tab never recorded. */
|
|
331
|
+
function markCast(tabId, label) {
|
|
332
|
+
const cast = casts.get(tabId)
|
|
333
|
+
if (!cast) return false
|
|
334
|
+
cast.mark(label)
|
|
335
|
+
return true
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/** Hand the tab's recording over (asciicast v2) and forget it. */
|
|
339
|
+
function takeCast(tabId) {
|
|
340
|
+
const cast = casts.get(tabId)
|
|
341
|
+
if (!cast) return null
|
|
342
|
+
casts.delete(tabId)
|
|
343
|
+
return cast.serialize()
|
|
344
|
+
}
|
|
345
|
+
|
|
309
346
|
function shutdown() {
|
|
310
347
|
for (const entry of sessions.values()) {
|
|
311
348
|
clearDrainTimer(entry)
|
|
@@ -325,6 +362,8 @@ export function createPtySessionManager({
|
|
|
325
362
|
closeSession,
|
|
326
363
|
ensureSession,
|
|
327
364
|
sendText,
|
|
365
|
+
markCast,
|
|
366
|
+
takeCast,
|
|
328
367
|
shutdown,
|
|
329
368
|
sessionCount: () => sessions.size,
|
|
330
369
|
pendingSpawnCount: () => pendingSpawns.size,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@sma1lboy/rove",
|
|
4
|
-
"version": "0.9.
|
|
4
|
+
"version": "0.9.238",
|
|
5
5
|
"description": "Rove — the agent multiplexer for your terminal. Run coding agents on parallel tasks with isolated worktrees and persistent sessions.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"terminal",
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import{DEFAULT_TERMINAL_COLORS}from"./chunk-7q67dz4c.js";import{OWNER_ONLY_DIR_MODE,OWNER_ONLY_FILE_MODE,tightenDirPermissionsSync,tightenFilePermissionsSync}from"./chunk-9prawccj.js";import{defaultPtyFreezeDir}from"./chunk-y8fqdnh3.js";import{randomUUID}from"crypto";import{mkdirSync,readFileSync,readdirSync,renameSync,rmSync,statSync,writeFileSync}from"fs";import{join}from"path";import{StringDecoder}from"string_decoder";var FREEZE_VERSION=1;function freezeSession(session,now=new Date){return{v:FREEZE_VERSION,key:session.key,cwd:session.cwd,command:[...session.command],cols:session.cols,rows:session.rows,title:session.title,totalBytes:session.totalBytes,exit:session.exit,ringB64:Buffer.concat(session.chunks).toString("base64"),updatedAt:now.toISOString()}}function thawRing(record,cap){let ring;try{ring=Buffer.from(record.ringB64,"base64")}catch{return null}let trimmed=ring.byteLength>cap?ring.subarray(ring.byteLength-cap):ring;return{chunks:trimmed.byteLength>0?[trimmed]:[],bytes:trimmed.byteLength}}function thawSession(record,cap){let ring=thawRing(record,cap);if(!ring)return null;return{key:record.key,generation:randomUUID(),cwd:record.cwd,proc:null,alive:!1,chunks:[...ring.chunks],bytes:ring.bytes,totalBytes:Math.max(record.totalBytes,ring.bytes),cols:record.cols,rows:record.rows,command:record.command,title:record.title,titleCarry:"",titleDecoder:new StringDecoder("utf8"),colorQueryCarry:"",defaultColors:DEFAULT_TERMINAL_COLORS,sinks:new Map,parked:!1,parkedScreenBytes:0,exit:record.exit,restored:!0,lastFreezeAtMs:0}}function recordFile(dir,key){return join(dir,`${encodeURIComponent(key)}.json`)}function parseRecord(raw){try{let parsed=JSON.parse(raw);if(parsed?.v!==FREEZE_VERSION)return null;if(typeof parsed.key!=="string"||parsed.key.length===0)return null;if(parsed.key.startsWith("::"))return null;if(typeof parsed.cwd!=="string"||!Array.isArray(parsed.command))return null;if(typeof parsed.ringB64!=="string"||typeof parsed.totalBytes!=="number")return null;return parsed}catch{return null}}var FREEZE_TTL_MS=1209600000,FREEZE_RESTORE_MAX_BYTES=67108864;function updatedAtMs(record){let t=Date.parse(record.updatedAt);return Number.isFinite(t)?t:0}function loadFrozenSessions(dir=defaultPtyFreezeDir(),now=Date.now(),report){let names;try{names=readdirSync(dir)}catch{return[]}let entries=[];for(let name of names){if(!name.endsWith(".json"))continue;try{let st=statSync(join(dir,name));entries.push({name,size:st.size,mtimeMs:st.mtimeMs})}catch{}}entries.sort((a,b)=>b.mtimeMs-a.mtimeMs);let kept=[],stale=[],bytesRead=0,deferred=0,unreadable=0;for(let entry of entries){if(now-entry.mtimeMs>FREEZE_TTL_MS){stale.push(entry.name);continue}if(bytesRead+entry.size>FREEZE_RESTORE_MAX_BYTES&&kept.length>0){deferred++;continue}let record=null;try{record=parseRecord(readFileSync(join(dir,entry.name),"utf8"))}catch{}if(bytesRead+=entry.size,!record){unreadable++;continue}if(now-updatedAtMs(record)>FREEZE_TTL_MS)stale.push(entry.name);else kept.push(record)}for(let name of stale)try{rmSync(join(dir,name),{force:!0})}catch{}return kept.sort((a,b)=>updatedAtMs(b)-updatedAtMs(a)),report?.({restored:kept.length,bytesRead,deferred,expired:stale.length,unreadable}),kept}function tightenExistingPermissions(dir){tightenDirPermissionsSync(dir);let names;try{names=readdirSync(dir)}catch{return}for(let name of names)if(name.endsWith(".json"))tightenFilePermissionsSync(join(dir,name))}function fileFreezeSink(dir=defaultPtyFreezeDir()){return tightenExistingPermissions(dir),{save(record){try{mkdirSync(dir,{recursive:!0,mode:OWNER_ONLY_DIR_MODE});let target=recordFile(dir,record.key),staging=`${target}.${process.pid}.tmp`;writeFileSync(staging,JSON.stringify(record),{encoding:"utf8",mode:OWNER_ONLY_FILE_MODE}),renameSync(staging,target)}catch{}},drop(key){try{rmSync(recordFile(dir,key),{force:!0})}catch{}}}}function clearFrozenSessions(dir=defaultPtyFreezeDir()){try{rmSync(dir,{recursive:!0,force:!0})}catch{}}
|
|
3
|
-
export{freezeSession,thawSession,FREEZE_TTL_MS,FREEZE_RESTORE_MAX_BYTES,loadFrozenSessions,fileFreezeSink,clearFrozenSessions};
|
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
// @bun
|
|
2
|
-
import{embeddedTerminalEnv}from"./chunk-ef39ym8f.js";import{FREEZE_RESTORE_MAX_BYTES,FREEZE_TTL_MS,clearFrozenSessions,fileFreezeSink,freezeSession,loadFrozenSessions,thawSession}from"./chunk-1m7cp5ze.js";import{ClientWriter,LineReceiver,linkLegacyRuntimePath,objectPayload,optionalBoolean,requireString}from"./chunk-rz8zs7j8.js";import{writeTextAtomic}from"./chunk-xsrcwcjc.js";import{installDaemonCrashHandlers,logDaemonError}from"./chunk-rcyasw34.js";import{DEFAULT_TERMINAL_COLORS,foldDefaultColorQueries,formatDefaultColorReply,parseTerminalDefaultColors}from"./chunk-7q67dz4c.js";import{resolveLoginShell}from"./chunk-exfyqsdd.js";import{recordPtyExit}from"./chunk-g3r1dwy9.js";import"./chunk-w25b7xxz.js";import{CURRENT_VERSION}from"./chunk-4y1fdz34.js";import"./chunk-8hbtca1g.js";import"./chunk-sb0ns0c7.js";import{listenOnUnixSocket}from"./chunk-vnpqemb0.js";import{ensureOwnerOnlyStateDir}from"./chunk-9prawccj.js";import{DAEMON_PROTOCOL_VERSION,frameToLine}from"./chunk-544bwsss.js";import"./chunk-hqbmzz4t.js";import{rotateLogIfNeeded}from"./chunk-pw6yfj10.js";import"./chunk-f10n6063.js";import{defaultPtyFreezeDir,defaultPtyHostLogPath,defaultPtyHostPidPath,defaultPtyHostSocketPath,isWindowsPipePath,legacyPtyHostPidPath,legacyPtyHostSocketPath,resolveDaemonHomeDir}from"./chunk-y8fqdnh3.js";import{COMPAT_STATE_DIR_BASENAME,ROVE_STATE_DIR_BASENAME,readRoveHomeDirEnv}from"./chunk-hq4b3fca.js";import"./chunk-wqfp8d9w.js";function formatPtyHostLine(event,message,now=new Date){return`[${now.toISOString()}] pty-host [${event}]: ${message}`}import{readFileSync}from"fs";import{mkdir,unlink}from"fs/promises";import{createServer}from"net";import{dirname}from"path";import{existsSync,lstatSync,mkdirSync,renameSync,symlinkSync}from"fs";import{homedir}from"os";import{join}from"path";var PTY_HOST_DATA_ENTRIES=["pty-exits.json","pty-sessions"];function lstatIfExists(path){try{return lstatSync(path)}catch{return}}function migrateLegacyPtyHostData(homeDir=readRoveHomeDirEnv()??homedir()){let canonicalDir=join(homeDir,ROVE_STATE_DIR_BASENAME),legacyDir=join(homeDir,COMPAT_STATE_DIR_BASENAME);if(canonicalDir===legacyDir)return[];let moved=[];for(let name of PTY_HOST_DATA_ENTRIES){let canonical=join(canonicalDir,name),legacy=join(legacyDir,name);try{if(existsSync(canonical))continue;let stat=lstatIfExists(legacy);if(!stat||stat.isSymbolicLink())continue;mkdirSync(canonicalDir,{recursive:!0}),renameSync(legacy,canonical),moved.push(name);try{symlinkSync(canonical,legacy)}catch{}}catch{}}return moved}import{randomUUID as randomUUID2}from"crypto";var TERMINAL_NAME="xterm-256color";function bunTerminalDriver(spawn){let spawnTerminal=spawn??((argv,options)=>Bun.spawn(argv,options));return(request)=>{let proc=spawnTerminal([...request.argv],{cwd:request.cwd,env:request.env,terminal:{cols:request.cols,rows:request.rows,name:TERMINAL_NAME,data:(_terminal,data)=>request.onData(data)}});return{pid:proc.pid,exited:proc.exited.then(()=>({code:proc.exitCode??null,signal:proc.signalCode??null}),()=>({code:null,signal:null})),write:(data)=>proc.terminal?.write(data),resize:(cols,rows)=>proc.terminal?.resize(cols,rows),close:()=>proc.terminal?.close(),kill:(signal)=>proc.kill(signal)}}}import{randomUUID}from"crypto";import{StringDecoder}from"string_decoder";function freshSessionState(key,spec,argv){return{key,generation:randomUUID(),cwd:spec.cwd,proc:null,alive:!0,chunks:[],bytes:0,totalBytes:0,cols:spec.cols??80,rows:spec.rows??24,command:argv,title:"",titleCarry:"",titleDecoder:new StringDecoder("utf8"),colorQueryCarry:"",defaultColors:parseTerminalDefaultColors(spec.defaultColors)??DEFAULT_TERMINAL_COLORS,sinks:new Map,parked:!1,parkedScreenBytes:0,exit:null,restored:!1,lastFreezeAtMs:0,frozenTotalBytes:0}}function sessionInfo(s){return{key:s.key,generation:s.generation,alive:s.alive,pid:s.proc?.pid??null,command:s.command,title:s.title,totalBytes:s.totalBytes,parked:s.parked,parkedScreenBytes:s.parkedScreenBytes,exit:s.exit,restored:s.restored||void 0}}function describeExit(exit){if(exit?.signal)return` (signal ${exit.signal})`;if(exit&&exit.code!==null)return` (code ${exit.code})`;return" (cause unknown)"}function ringTail(chunks,bytes,maxBytes){let skip=Math.max(0,bytes-maxBytes),seen=0,parts=[];for(let chunk of chunks){if(seen+chunk.byteLength>skip)parts.push(seen>=skip?chunk:chunk.subarray(skip-seen));seen+=chunk.byteLength}return Buffer.concat(parts).toString("utf8")}function hostStats(sessions,perSessionCap,parkRestoreDeltas,parkRestoreFallbacks){let ringBytes=0,count=0,parkedSessions=0,parkedScreenBytes=0;for(let session of sessions)if(count++,ringBytes+=session.bytes,session.parked)parkedSessions++,parkedScreenBytes+=session.parkedScreenBytes;return{ringBytes,ringCapacityBytes:count*perSessionCap,parkedSessions,parkedScreenBytes,parkRestoreDeltas,parkRestoreFallbacks}}var OSC_TITLE_RE=/\x1b\][02];([^\x07\x1b]*)(?:\x07|\x1b\\)/g,TITLE_CARRY_CAP=1024;function titleCarryFrom(rest){let osc=rest.lastIndexOf("\x1B]");if(osc!==-1)return rest.slice(osc,osc+TITLE_CARRY_CAP);return rest.endsWith("\x1B")?"\x1B":""}function foldOscTitle(prevCarry,chunkText){let text=prevCarry+chunkText,title=null,end=0;OSC_TITLE_RE.lastIndex=0;for(let match=OSC_TITLE_RE.exec(text);match;match=OSC_TITLE_RE.exec(text))title=match[1]??"",end=match.index+match[0].length;return{title,carry:titleCarryFrom(text.slice(end))}}function scanOscTitle(session,buf){let{title,carry}=foldOscTitle(session.titleCarry,session.titleDecoder.write(buf));if(title!==null)session.title=title;session.titleCarry=carry}function peekRing(session,sinceOffset){if(!session)return{exists:!1,alive:!1,pid:null,offset:0,data:"",sinceValid:!1,exit:null};let windowStart=session.totalBytes-session.bytes,buf=Buffer.concat(session.chunks),sinceValid=!1;if(sinceOffset!==void 0&&sinceOffset>=windowStart&&sinceOffset<=session.totalBytes)buf=buf.subarray(sinceOffset-windowStart),sinceValid=!0;return{exists:!0,alive:session.alive,pid:session.proc?.pid??null,offset:session.totalBytes,data:buf.toString("base64"),sinceValid,exit:session.exit??null}}async function settledWithin(exited,ms){let timer,settled=await Promise.race([exited.then(()=>!0,()=>!0),new Promise((resolve)=>{timer=setTimeout(()=>resolve(!1),ms)})]);if(timer)clearTimeout(timer);return settled}function signalProcessGroup(pid,signal,fallback,platform=process.platform,onSignal){if(platform!=="win32"&&pid>1)try{process.kill(-pid,signal),onSignal?.(`sent ${signal} to process group -${pid}`);return}catch{}try{fallback(),onSignal?.(`sent ${signal} to pid ${pid} (per-process fallback)`)}catch{}}var TERMINATION_GRACE_MS=500;async function terminatePtyChild(proc,onSettled,onSignal){if(proc.endTree){onSignal?.(await proc.endTree()),signalProcessGroup(proc.pid,"SIGKILL",()=>proc.kill("SIGKILL"),process.platform,onSignal),await settledWithin(proc.exited,TERMINATION_GRACE_MS),onSettled();return}if(signalProcessGroup(proc.pid,"SIGTERM",()=>proc.kill("SIGTERM"),process.platform,onSignal),!await settledWithin(proc.exited,TERMINATION_GRACE_MS))signalProcessGroup(proc.pid,"SIGKILL",()=>proc.kill("SIGKILL"),process.platform,onSignal),await settledWithin(proc.exited,TERMINATION_GRACE_MS);onSettled()}class PtyChildController{deps;constructor(deps){this.deps=deps}spawn(key,spec,spare=!1){let argv=spec.command&&spec.command.length>0?[...spec.command]:[spec.shell??resolveLoginShell()],session=freshSessionState(key,spec,argv);if(this.startChild(session),session.alive)this.deps.log?.("pty",`spawned ${argv[0]} for ${key} (pid ${session.proc?.pid})`),this.deps.onSessionStart?.(spare);return session}startChild(session){session.generation=randomUUID2();try{session.proc=(this.deps.driver??bunTerminalDriver())({argv:[...session.command],cwd:session.cwd,env:embeddedTerminalEnv(process.env,{TERM:"xterm-256color",COLUMNS:String(session.cols),LINES:String(session.rows),BASH_SILENCE_DEPRECATION_WARNING:"1",KOBE_TERMINAL_PTY:"1"}),cols:session.cols,rows:session.rows,onData:(data)=>this.onData(session,data)}),session.alive=!0,session.proc.exited.then((exit)=>this.markExited(session,exit),()=>this.markExited(session))}catch(err){session.alive=!1,this.deps.log?.("pty",`spawn failed for ${session.key}: ${err instanceof Error?err.message:String(err)}`)}}async endChild(session){if(!session.alive)return;let proc=session.proc;if(!proc){this.markExited(session);return}await terminatePtyChild(proc,()=>this.markExited(session),(line)=>this.deps.log?.("pty-signal",`${session.key}: ${line}`))}markExited(session,exit){if(!session.alive)return;session.alive=!1;let sessionExit={code:exit?.code??null,signal:exit?.signal??null,at:new Date().toISOString()};session.exit=sessionExit;try{session.proc?.close()}catch{}this.deps.onExit?.(session,sessionExit)}onData(session,data){let buf=typeof data==="string"?Buffer.from(data,"utf8"):Buffer.from(data),colorQueries=foldDefaultColorQueries(session.colorQueryCarry,buf.toString("latin1"));session.colorQueryCarry=colorQueries.carry;for(let slot of colorQueries.slots)try{session.proc?.write(formatDefaultColorReply(slot,session.defaultColors))}catch{}scanOscTitle(session,buf),session.chunks.push(buf),session.bytes+=buf.byteLength,session.totalBytes+=buf.byteLength;while(session.bytes>this.deps.scrollbackCap&&session.chunks.length>1){let dropped=session.chunks.shift();if(dropped)session.bytes-=dropped.byteLength}this.deps.onOutput?.(session,buf)}}function shouldFreeze(session,now){let appended=session.totalBytes-(session.frozenTotalBytes??0);if(appended<=0)return!1;let since=now-session.lastFreezeAtMs;if(since<5000)return!1;return appended>=65536||since>=60000}class WarmSpare{deps;spare=null;constructor(deps){this.deps=deps}warm(cwd,shell,cols=80,rows=24){let argv0=shell??resolveLoginShell();if(this.spare?.alive&&this.spare.cwd===cwd&&this.spare.command[0]===argv0)return;let old=this.spare;if(this.spare=null,old)this.deps.endChild(old);let session=this.deps.spawn("::spare",{cwd,command:[argv0],cols,rows},!0);this.spare=session.alive?session:null}adopt(key,spec){let spare=this.spare;if(!spare?.alive||spare.cwd!==spec.cwd)return null;let want=spec.command&&spec.command.length>0?spec.command:[spec.shell??resolveLoginShell()];if(want.length!==1||want[0]!==spare.command[0])return null;this.spare=null,spare.key=key,spare.defaultColors=parseTerminalDefaultColors(spec.defaultColors)??spare.defaultColors;let cols=spec.cols??spare.cols,rows=spec.rows??spare.rows;if(spare.cols!==cols||spare.rows!==rows){spare.cols=cols,spare.rows=rows;try{spare.proc?.resize(cols,rows)}catch{return this.deps.markExited(spare),null}}return this.deps.log?.("pty",`adopted warm shell for ${key} (pid ${spare.proc?.pid})`),this.deps.onSessionStart?.(),this.warm(spec.cwd,spare.command[0],cols,rows),spare}async end(){let spare=this.spare;if(this.spare=null,spare)await this.deps.endChild(spare)}}var LOGGED_WRITE_BYTES=1024,DEFAULT_SCROLLBACK_CAP=524288,EXIT_TAIL_BYTES=16384;class PtyHost{sessions=new Map;opts;scrollbackCap;parkRestoreDeltas=0;parkRestoreFallbacks=0;childController;warmSpare;constructor(opts={}){this.opts=opts,this.scrollbackCap=opts.scrollbackCap??DEFAULT_SCROLLBACK_CAP,this.childController=new PtyChildController({driver:opts.driver,scrollbackCap:this.scrollbackCap,onSessionStart:(spare)=>{if(!spare)this.opts.onSessionStart?.()},onOutput:(session,data)=>{if(session.sinks.size===0){this.maybeFreeze(session);return}let frame={type:"event",name:"pty.data",payload:{key:session.key,data:data.toString("base64")}};for(let sink of session.sinks.values())sink(frame);this.maybeFreeze(session)},onExit:(session,exit)=>{let frame={type:"event",name:"pty.exit",payload:{key:session.key,pid:session.proc?.pid??null,...exit}};for(let sink of session.sinks.values())sink(frame);if(this.opts.log?.("pty",`session ${session.key} exited${describeExit(exit)}`),this.maybeFreeze(session,!0),!session.closedByRequest)try{this.opts.onSessionExit?.({key:session.key,pid:session.proc?.pid??null,exit,tail:ringTail(session.chunks,session.bytes,EXIT_TAIL_BYTES)})}catch{}this.opts.onSessionEnd?.()},log:(event,message)=>this.opts.log?.(event,message)}),this.warmSpare=new WarmSpare({spawn:(key,spec,spare)=>this.childController.spawn(key,spec,spare),endChild:(session)=>this.childController.endChild(session),markExited:(session)=>this.childController.markExited(session),log:(event,message)=>this.opts.log?.(event,message),onSessionStart:()=>this.opts.onSessionStart?.()})}open(key,spec,token,sink,sinceOffset,sincePid){let session=this.sessions.get(key),created=!1,respawned=!1;if(!session)created=!0,session=this.warmSpare.adopt(key,spec)??this.childController.spawn(key,spec),this.sessions.set(key,session);else if(!session.alive&&session.restored)this.respawn(session,spec),respawned=session.alive;else if(session.alive&&spec.cols!==void 0&&spec.rows!==void 0&&(session.cols!==spec.cols||session.rows!==spec.rows))this.resize(key,spec.cols,spec.rows);let defaultColors=parseTerminalDefaultColors(spec.defaultColors);if(defaultColors)session.defaultColors=defaultColors;session.sinks.set(token,sink),session.parked=!1,session.parkedScreenBytes=0;let windowStart=session.totalBytes-session.bytes,replay=Buffer.concat(session.chunks),sinceValid=!1;if(!created&&sinceOffset!==void 0&&sinceOffset>=windowStart&&sinceOffset<=session.totalBytes&&sincePid!==void 0&&sincePid===session.proc?.pid)replay=replay.subarray(sinceOffset-windowStart),sinceValid=!0;if(sinceOffset!==void 0&&sincePid!==void 0)if(sinceValid)this.parkRestoreDeltas++;else this.parkRestoreFallbacks++;return{replay:replay.toString("base64"),alive:session.alive,pid:session.proc?.pid??null,created,respawned,offset:session.totalBytes,sinceValid}}warm(cwd,shell,cols=80,rows=24){this.warmSpare.warm(cwd,shell,cols,rows)}write(key,data){let session=this.sessions.get(key);if(!session?.alive||data.length===0)return;if(data.length>=LOGGED_WRITE_BYTES)this.opts.log?.("pty",`wrote ${data.length} bytes to ${key}`);try{session.proc?.write(data)}catch(error){this.opts.log?.("pty",`write failed on ${key} (${data.length} bytes): ${String(error)}`)}}resize(key,cols,rows){let session=this.sessions.get(key);if(!session?.alive)return;session.cols=cols,session.rows=rows;try{session.proc?.resize(cols,rows)}catch{}}killIfGeneration(key,expectedGeneration){let session=this.sessions.get(key);if(!session)return{accepted:!1,reason:"missing-session"};if(session.generation!==expectedGeneration)return{accepted:!1,reason:"generation-mismatch"};return this.kill(key).catch((err)=>logDaemonError("pty-kill",err)),{accepted:!0}}kill(key){let session=this.sessions.get(key);if(!session)return Promise.resolve();return this.sessions.delete(key),session.closedByRequest=!0,this.opts.freeze?.drop(key),this.childController.endChild(session)}rename(from,to){let session=this.sessions.get(from);if(!session||from===to||this.sessions.has(to))return!1;return this.sessions.delete(from),session.key=to,this.sessions.set(to,session),this.opts.freeze?.drop(from),this.maybeFreeze(session,!0),this.opts.log?.("pty",`renamed session ${from} \u2192 ${to}`),!0}detach(key,token,parked=!1,parkedScreenBytes=0){let session=this.sessions.get(key);if(!session)return;session.sinks.delete(token),this.applyParkedOnDetach(session,parked,parkedScreenBytes)}detachClient(token){for(let session of this.sessions.values()){if(!session.sinks.delete(token))continue;this.applyParkedOnDetach(session)}}applyParkedOnDetach(session,parked=!1,parkedScreenBytes=0){if(session.sinks.size!==0)return;session.parked=parked,session.parkedScreenBytes=parked?Math.max(0,parkedScreenBytes):0}list(){return Array.from(this.sessions.values(),(s)=>sessionInfo(s))}peek(key,sinceOffset){return peekRing(this.sessions.get(key),sinceOffset)}stats(){return hostStats(this.sessions.values(),this.scrollbackCap,this.parkRestoreDeltas,this.parkRestoreFallbacks)}async killAll(){let sessions=Array.from(this.sessions.keys(),(key)=>this.kill(key));sessions.push(this.warmSpare.end()),await Promise.all(sessions)}async shutdown(){this.flushFrozen();let endings=Array.from(this.sessions.values(),(session)=>this.childController.endChild(session));endings.push(this.warmSpare.end()),await Promise.all(endings)}flushFrozen(){for(let session of this.sessions.values())this.maybeFreeze(session,!0)}restoreFrozen(records){let restoredCount=0;for(let record of records){if(this.sessions.has(record.key))continue;let session=thawSession(record,this.scrollbackCap);if(!session)continue;this.sessions.set(record.key,session),restoredCount++}if(restoredCount>0)this.opts.log?.("pty",`restored ${restoredCount} frozen session(s) from disk`);return restoredCount}respawn(session,spec){if(session.restored=!1,session.exit=null,spec.command&&spec.command.length>0)session.command=[...spec.command];if(session.cols=spec.cols??session.cols,session.rows=spec.rows??session.rows,session.defaultColors=parseTerminalDefaultColors(spec.defaultColors)??session.defaultColors,this.childController.startChild(session),!session.alive)return;this.opts.log?.("pty",`respawned restored session ${session.key} (pid ${session.proc?.pid})`),this.opts.onSessionStart?.()}maybeFreeze(session,force=!1){let freeze=this.opts.freeze;if(!freeze||session.key.startsWith("::"))return;if(session.closedByRequest)return;let now=Date.now();if(!force&&!shouldFreeze(session,now))return;session.lastFreezeAtMs=now,session.frozenTotalBytes=session.totalBytes,freeze.save(freezeSession(session))}liveCount(){let n=0;for(let session of this.sessions.values())if(session.alive)n++;return n}}function dispatchPtyRequest(req,client,deps){let{ptys}=deps;switch(req.name){case"hello":return{protocolVersion:DAEMON_PROTOCOL_VERSION,ptyHost:!0,pid:process.pid};case"pty.open":{let payload=objectPayload(req.payload);return ptys.open(requireString(payload,"key"),{cwd:requireString(payload,"cwd"),command:Array.isArray(payload.command)?payload.command.filter((c)=>typeof c==="string"):void 0,shell:typeof payload.shell==="string"?payload.shell:void 0,cols:typeof payload.cols==="number"?payload.cols:void 0,rows:typeof payload.rows==="number"?payload.rows:void 0,defaultColors:parseTerminalDefaultColors(payload.defaultColors)??void 0},client,(frame)=>deps.writeFrame(client,frame),typeof payload.sinceOffset==="number"?payload.sinceOffset:void 0,typeof payload.sincePid==="number"?payload.sincePid:void 0)}case"pty.write":{let payload=objectPayload(req.payload);return ptys.write(requireString(payload,"key"),typeof payload.data==="string"?payload.data:""),{}}case"pty.resize":{let payload=objectPayload(req.payload);return ptys.resize(requireString(payload,"key"),typeof payload.cols==="number"?payload.cols:80,typeof payload.rows==="number"?payload.rows:24),{}}case"pty.kill":{let payload=objectPayload(req.payload),key=requireString(payload,"key");if("expectedGeneration"in payload)return ptys.killIfGeneration(key,requireString(payload,"expectedGeneration"));if(optionalBoolean(payload,"wait")===!0)return ptys.kill(key).then(()=>({accepted:!0,ended:!0}),(err)=>{return logDaemonError("pty-kill",err),{accepted:!0,ended:!1}});return ptys.kill(key).catch((err)=>logDaemonError("pty-kill",err)),{accepted:!0}}case"pty.rename":{let payload=objectPayload(req.payload);return{renamed:ptys.rename(requireString(payload,"from"),requireString(payload,"to"))}}case"pty.detach":{let payload=objectPayload(req.payload);ptys.detach(requireString(payload,"key"),client,payload.parked===!0,typeof payload.parkedScreenBytes==="number"?payload.parkedScreenBytes:0)}return{};case"pty.list":return{pid:process.pid,rssBytes:process.memoryUsage().rss,sessions:ptys.list(),stats:ptys.stats(),...deps.version?{version:deps.version}:{}};case"pty.peek":{let payload=objectPayload(req.payload);return ptys.peek(requireString(payload,"key"),typeof payload.sinceOffset==="number"?payload.sinceOffset:void 0)}case"pty.warm":{let payload=objectPayload(req.payload);return ptys.warm(requireString(payload,"cwd"),typeof payload.shell==="string"?payload.shell:void 0,typeof payload.cols==="number"?payload.cols:void 0,typeof payload.rows==="number"?payload.rows:void 0),{}}case"daemon.stop":return deps.requestStop(),{};default:throw Error(`unknown pty-host request: ${req.name}`)}}var DEFAULT_IDLE_EXIT_MS=60000,DEFAULT_ORPHAN_CHECK_MS=30000;function resolveMaxLifetimeMs(){let raw=process.env.KOBE_PTY_MAX_LIFETIME_MS;if(raw===void 0)return null;let n=Number(raw);return Number.isFinite(n)&&n>0?n:null}function resolveIdleExitMs(){let raw=process.env.KOBE_PTY_IDLE_EXIT_MS;if(raw===void 0)return DEFAULT_IDLE_EXIT_MS;let n=Number(raw);return Number.isFinite(n)&&n>=0?n:DEFAULT_IDLE_EXIT_MS}async function startPtyHostServer(options={}){migrateLegacyPtyHostData();let socketPath=options.socketPath??defaultPtyHostSocketPath(),pidPath=options.pidPath??defaultPtyHostPidPath(),freezeDir=options.freezeDir??defaultPtyFreezeDir(),idleExitMs=options.idleExitMs||resolveIdleExitMs(),orphanCheckMs=options.orphanCheckMs||DEFAULT_ORPHAN_CHECK_MS,maxLifetimeMs=options.maxLifetimeMs??resolveMaxLifetimeMs(),bootedAtMs=Date.now(),log=options.log??(()=>{}),clients=new Set,stopping=!1,wipeFreezeOnStop=!1,idleTimer=null,cancelIdle=()=>{if(idleTimer)clearTimeout(idleTimer);idleTimer=null},armIdle=()=>{if(stopping)return;cancelIdle(),idleTimer=setTimeout(()=>{if(stopping||ptys.liveCount()>0)return;log("idle",`no live sessions for ${idleExitMs}ms \u2014 exiting`),stop()},idleExitMs)},orphaned=()=>{if(maxLifetimeMs!==null&&Date.now()-bootedAtMs>=maxLifetimeMs)return`exceeded its ${maxLifetimeMs}ms lifetime ceiling`;let owner;try{owner=readFileSync(pidPath,"utf8").trim()}catch{return`pidfile ${pidPath} is gone`}let pid=Number.parseInt(owner,10);if(pid===process.pid)return null;return Number.isInteger(pid)?`pidfile ${pidPath} now names pid ${pid}`:`pidfile ${pidPath} is unreadable`},orphanTimer=setInterval(()=>{if(stopping)return;let reason=orphaned();if(!reason)return;log("orphan",`${reason} \u2014 exiting rather than outliving my owner`),stop()},orphanCheckMs);orphanTimer.unref?.();let ptys=new PtyHost({onSessionStart:cancelIdle,onSessionEnd:()=>{if(ptys.liveCount()===0)armIdle()},onSessionExit:(info)=>{try{recordPtyExit(info)}catch(err){log("pty",`exit record write failed for ${info.key}: ${err instanceof Error?err.message:String(err)}`)}},freeze:fileFreezeSink(freezeDir),driver:options.driver,log});ptys.restoreFrozen(loadFrozenSessions(freezeDir,Date.now(),(s)=>{let mib=(bytes)=>`${Math.round(bytes/1048576)}MB`,notes=[`restored ${s.restored} (${mib(s.bytesRead)})`];if(s.deferred>0)notes.push(`${s.deferred} left unread past the ${mib(FREEZE_RESTORE_MAX_BYTES)} budget`);if(s.expired>0)notes.push(`${s.expired} deleted past the ${FREEZE_TTL_MS/86400000}-day TTL`);if(s.unreadable>0)notes.push(`${s.unreadable} unreadable`);log("freeze",notes.join(", "))}));let pipeSocket=isWindowsPipePath(socketPath);if(await ensureOwnerOnlyStateDir(resolveDaemonHomeDir()),!pipeSocket)await mkdir(dirname(socketPath),{recursive:!0});await mkdir(dirname(pidPath),{recursive:!0});let server=createServer((socket)=>{let client={socket,writer:new ClientWriter(socket,{onOverflow:()=>{log("backpressure","disconnecting PTY client whose critical queue exceeded 8MiB"),socket.destroy()}})};clients.add(client);let receiver=new LineReceiver;socket.on("data",(chunk)=>{if(!receiver.push(chunk,(line)=>handleLine(client,line)))log("framing","disconnecting PTY client whose request exceeded 8MiB"),socket.destroy()}),socket.on("error",()=>{}),socket.on("close",()=>{clients.delete(client),ptys.detachClient(client)})}),api={socketPath,pidPath,async close(){if(stopping)return;if(stopping=!0,cancelIdle(),clearInterval(orphanTimer),await ptys.shutdown(),wipeFreezeOnStop)clearFrozenSessions(freezeDir);for(let client of Array.from(clients))client.socket.destroy();if(await new Promise((resolve)=>server.close(()=>resolve())),!pipeSocket)await unlink(socketPath).catch(()=>{});await unlink(pidPath).catch(()=>{})}};async function stop(){await api.close().catch((err)=>logDaemonError("pty-host-shutdown",err)),options.onStop?.()}let verbDeps={ptys,writeFrame,requestStop:()=>{wipeFreezeOnStop=!0,setTimeout(()=>void stop(),0).unref()},...options.version?{version:options.version}:{}};function handleLine(client,line){if(line.trim().length>0){let frame=null;try{frame=JSON.parse(line)}catch{writeFrame(client,{type:"response",id:"parse-error",error:{message:"malformed frame"}})}if(frame)if(frame.type!=="request")writeFrame(client,{type:"response",id:"parse-error",error:{message:"requests only"}});else{let req=frame,reply=(payload)=>writeFrame(client,{type:"response",id:req.id,name:req.name,payload}),fail=(err)=>writeFrame(client,{type:"response",id:req.id,name:req.name,error:{message:err instanceof Error?err.message:String(err)}});try{let payload=dispatchPtyRequest(req,client,verbDeps);if(payload instanceof Promise)payload.then(reply,fail);else reply(payload)}catch(err){fail(err)}}}}if(await listenOnUnixSocket(server,socketPath),await writeTextAtomic(pidPath,`${process.pid}
|
|
3
|
-
`),!pipeSocket){let home=resolveDaemonHomeDir();await linkLegacyRuntimePath(socketPath,legacyPtyHostSocketPath(home)),await linkLegacyRuntimePath(pidPath,legacyPtyHostPidPath(home))}return armIdle(),log("boot",`pty host listening on ${socketPath}`),api}function writeFrame(client,frame){client.writer.write(frameToLine(frame))}async function runPtyHostSubcommand(_argv){rotateLogIfNeeded(defaultPtyHostLogPath()),installDaemonCrashHandlers();let server=await startPtyHostServer({log:(event,message)=>console.log(formatPtyHostLine(event,message)),version:CURRENT_VERSION,onStop:()=>process.exit(0)});console.log(formatPtyHostLine("listen",`v${CURRENT_VERSION} listening on ${server.socketPath}`));let shutdown=async(signal)=>{console.log(formatPtyHostLine("signal",`${signal} received \u2014 closing host, frozen sessions kept`)),await server.close(),process.exit(0)};process.once("SIGINT",()=>void shutdown("SIGINT")),process.once("SIGTERM",()=>void shutdown("SIGTERM"))}export{runPtyHostSubcommand};
|