@juspay/shooter 1.22.0 → 1.23.0
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/build/client/_app/immutable/chunks/Bj5wFimK.js +3 -0
- package/build/client/_app/immutable/chunks/Bj5wFimK.js.br +0 -0
- package/build/client/_app/immutable/chunks/Bj5wFimK.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DhK7PwI_.js → BjYr_-Ss.js} +1 -1
- package/build/client/_app/immutable/chunks/BjYr_-Ss.js.br +0 -0
- package/build/client/_app/immutable/chunks/{DhK7PwI_.js.gz → BjYr_-Ss.js.gz} +0 -0
- package/build/client/_app/immutable/chunks/DULfdsh6.js +6 -0
- package/build/client/_app/immutable/chunks/DULfdsh6.js.br +0 -0
- package/build/client/_app/immutable/chunks/DULfdsh6.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{CZg4kn4E.js → fcNfTA-E.js} +1 -1
- package/build/client/_app/immutable/chunks/fcNfTA-E.js.br +0 -0
- package/build/client/_app/immutable/chunks/fcNfTA-E.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.CTqz33nP.js → app.Bvoqymnp.js} +2 -2
- package/build/client/_app/immutable/entry/app.Bvoqymnp.js.br +0 -0
- package/build/client/_app/immutable/entry/app.Bvoqymnp.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.BqXCPPZJ.js +1 -0
- package/build/client/_app/immutable/entry/start.BqXCPPZJ.js.br +2 -0
- package/build/client/_app/immutable/entry/start.BqXCPPZJ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{0.Qn7Ktiht.js → 0.Bv_TwEnq.js} +1 -1
- package/build/client/_app/immutable/nodes/0.Bv_TwEnq.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Bv_TwEnq.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.BxWOfNlo.js → 1.7lffTIeb.js} +1 -1
- package/build/client/_app/immutable/nodes/1.7lffTIeb.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.7lffTIeb.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{10.BGPYD1s1.js → 10.ChiIrIDl.js} +1 -1
- package/build/client/_app/immutable/nodes/10.ChiIrIDl.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.ChiIrIDl.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{11.BxY1PUjC.js → 11.DO3vyXEv.js} +2 -2
- package/build/client/_app/immutable/nodes/11.DO3vyXEv.js.br +0 -0
- package/build/client/_app/immutable/nodes/{11.BxY1PUjC.js.gz → 11.DO3vyXEv.js.gz} +0 -0
- package/build/client/_app/immutable/nodes/{2.Bc2qALkX.js → 2.iMIqsE7n.js} +1 -1
- package/build/client/_app/immutable/nodes/2.iMIqsE7n.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.iMIqsE7n.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{3.N2-A8noI.js → 3.CArnSHOO.js} +1 -1
- package/build/client/_app/immutable/nodes/3.CArnSHOO.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.CArnSHOO.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{6.BWF9Qx6F.js → 6.B8l1RwkB.js} +1 -1
- package/build/client/_app/immutable/nodes/6.B8l1RwkB.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.B8l1RwkB.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{7.DHuDIdpz.js → 7.BPyfhDis.js} +1 -1
- package/build/client/_app/immutable/nodes/7.BPyfhDis.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.BPyfhDis.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{8.D0Ijt9Vv.js → 8.D_vszZ9E.js} +1 -1
- package/build/client/_app/immutable/nodes/8.D_vszZ9E.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.D_vszZ9E.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{9.2Piwo35J.js → 9.Drah-do-.js} +1 -1
- package/build/client/_app/immutable/nodes/9.Drah-do-.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.Drah-do-.js.gz +0 -0
- package/build/client/_app/version.json +1 -1
- package/build/client/_app/version.json.br +0 -0
- package/build/client/_app/version.json.gz +0 -0
- package/build/server/chunks/{0-CVGsyVKN.js → 0-DAB_6Vm1.js} +2 -2
- package/build/server/chunks/{0-CVGsyVKN.js.map → 0-DAB_6Vm1.js.map} +1 -1
- package/build/server/chunks/{1-BAlAsKdp.js → 1-D-qMYaCx.js} +2 -2
- package/build/server/chunks/{1-BAlAsKdp.js.map → 1-D-qMYaCx.js.map} +1 -1
- package/build/server/chunks/{10-BUCX7Aqz.js → 10-CeFFGo-X.js} +2 -2
- package/build/server/chunks/{10-BUCX7Aqz.js.map → 10-CeFFGo-X.js.map} +1 -1
- package/build/server/chunks/{11-DHPvc2yA.js → 11-DRMu_ATU.js} +2 -2
- package/build/server/chunks/{11-DHPvc2yA.js.map → 11-DRMu_ATU.js.map} +1 -1
- package/build/server/chunks/{2-DLOMdCHW.js → 2-B7OLBMNH.js} +2 -2
- package/build/server/chunks/{2-DLOMdCHW.js.map → 2-B7OLBMNH.js.map} +1 -1
- package/build/server/chunks/{3-DCf69LYo.js → 3-B38ZarLw.js} +2 -2
- package/build/server/chunks/{3-DCf69LYo.js.map → 3-B38ZarLw.js.map} +1 -1
- package/build/server/chunks/{6-DUrC2Naz.js → 6-DP46cUej.js} +2 -2
- package/build/server/chunks/{6-DUrC2Naz.js.map → 6-DP46cUej.js.map} +1 -1
- package/build/server/chunks/{7-TXwjMHt2.js → 7-B29_3ar6.js} +2 -2
- package/build/server/chunks/{7-TXwjMHt2.js.map → 7-B29_3ar6.js.map} +1 -1
- package/build/server/chunks/{8-D2X_jBsT.js → 8-DCnSDVrX.js} +2 -2
- package/build/server/chunks/{8-D2X_jBsT.js.map → 8-DCnSDVrX.js.map} +1 -1
- package/build/server/chunks/{9-DK0hH5Xa.js → 9-BwqDc8wC.js} +2 -2
- package/build/server/chunks/{9-DK0hH5Xa.js.map → 9-BwqDc8wC.js.map} +1 -1
- package/build/server/chunks/{_server.ts-B54Pvhgc.js → _server.ts-Blx6TuRU.js} +3 -2
- package/build/server/chunks/_server.ts-Blx6TuRU.js.map +1 -0
- package/build/server/chunks/{_server.ts-C0PO_cAu.js → _server.ts-CYWXjihn.js} +3 -2
- package/build/server/chunks/{_server.ts-C0PO_cAu.js.map → _server.ts-CYWXjihn.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DiBMY7Ho.js → _server.ts-D0___krA.js} +3 -2
- package/build/server/chunks/_server.ts-D0___krA.js.map +1 -0
- package/build/server/chunks/{_server.ts-CZb-BI5H.js → _server.ts-Da1kSClZ.js} +3 -2
- package/build/server/chunks/_server.ts-Da1kSClZ.js.map +1 -0
- package/build/server/chunks/{_server.ts-Bol54_Qo.js → _server.ts-l3cd4Cto.js} +3 -2
- package/build/server/chunks/_server.ts-l3cd4Cto.js.map +1 -0
- package/build/server/chunks/{pty-manager-CoWVT56F.js → pty-manager-DDjG7DlH.js} +272 -27
- package/build/server/chunks/pty-manager-DDjG7DlH.js.map +1 -0
- package/build/server/index.js +1 -1
- package/build/server/index.js.map +1 -1
- package/build/server/manifest.js +16 -16
- package/build/server/manifest.js.map +1 -1
- package/package.json +4 -2
- package/server.ts +2 -2
- package/src/lib/modules/client/terminal/xterm-wrapper.ts +52 -12
- package/src/lib/modules/server/terminal/pty-manager.ts +279 -35
- package/src/lib/modules/server/terminal/terminal-emulator.ts +102 -0
- package/src/lib/modules/server/ws/server.ts +18 -2
- package/src/lib/modules/server/ws/terminal-handler.ts +11 -6
- package/src/lib/types/generated/WsProtocol.ts +10 -1
- package/src/lib/types/server.ts +27 -1
- package/src/lib/types/terminal-client.ts +3 -0
- package/src/lib/types/ws.ts +3 -2
- package/build/client/_app/immutable/chunks/BfbPKMXz.js +0 -3
- package/build/client/_app/immutable/chunks/BfbPKMXz.js.br +0 -0
- package/build/client/_app/immutable/chunks/BfbPKMXz.js.gz +0 -0
- package/build/client/_app/immutable/chunks/CZg4kn4E.js.br +0 -0
- package/build/client/_app/immutable/chunks/CZg4kn4E.js.gz +0 -0
- package/build/client/_app/immutable/chunks/DhK7PwI_.js.br +0 -0
- package/build/client/_app/immutable/chunks/J5-Cr5oR.js +0 -6
- package/build/client/_app/immutable/chunks/J5-Cr5oR.js.br +0 -0
- package/build/client/_app/immutable/chunks/J5-Cr5oR.js.gz +0 -0
- package/build/client/_app/immutable/entry/app.CTqz33nP.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CTqz33nP.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js +0 -1
- package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js.br +0 -2
- package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js.gz +0 -0
- package/build/client/_app/immutable/nodes/0.Qn7Ktiht.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.Qn7Ktiht.js.gz +0 -0
- package/build/client/_app/immutable/nodes/1.BxWOfNlo.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.BxWOfNlo.js.gz +0 -0
- package/build/client/_app/immutable/nodes/10.BGPYD1s1.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.BGPYD1s1.js.gz +0 -0
- package/build/client/_app/immutable/nodes/11.BxY1PUjC.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.Bc2qALkX.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.Bc2qALkX.js.gz +0 -0
- package/build/client/_app/immutable/nodes/3.N2-A8noI.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.N2-A8noI.js.gz +0 -0
- package/build/client/_app/immutable/nodes/6.BWF9Qx6F.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.BWF9Qx6F.js.gz +0 -0
- package/build/client/_app/immutable/nodes/7.DHuDIdpz.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.DHuDIdpz.js.gz +0 -0
- package/build/client/_app/immutable/nodes/8.D0Ijt9Vv.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.D0Ijt9Vv.js.gz +0 -0
- package/build/client/_app/immutable/nodes/9.2Piwo35J.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.2Piwo35J.js.gz +0 -0
- package/build/server/chunks/_server.ts-B54Pvhgc.js.map +0 -1
- package/build/server/chunks/_server.ts-Bol54_Qo.js.map +0 -1
- package/build/server/chunks/_server.ts-CZb-BI5H.js.map +0 -1
- package/build/server/chunks/_server.ts-DiBMY7Ho.js.map +0 -1
- package/build/server/chunks/pty-manager-CoWVT56F.js.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { v as validateAuth } from './auth-DuunT7Cg.js';
|
|
2
|
-
import { p as ptyManager } from './pty-manager-
|
|
2
|
+
import { p as ptyManager } from './pty-manager-DDjG7DlH.js';
|
|
3
3
|
import { s as shareStore, h as hashPassword } from './share-store-B9jMpVg0.js';
|
|
4
4
|
import { c as closeGuests } from './guest-registry-Dxvd7p-g.js';
|
|
5
5
|
import { j as json } from './index-CoD1IJuy.js';
|
|
@@ -16,6 +16,7 @@ import './registry-D4J_CuzW.js';
|
|
|
16
16
|
import 'better-sqlite3';
|
|
17
17
|
import './coordinator-DMU_ADXf.js';
|
|
18
18
|
import 'net';
|
|
19
|
+
import 'node:module';
|
|
19
20
|
import './shooter-home-4f_HkdGI.js';
|
|
20
21
|
|
|
21
22
|
const MIN_PASSWORD_LENGTH = 6;
|
|
@@ -93,4 +94,4 @@ const DELETE = ({ params, request }) => {
|
|
|
93
94
|
};
|
|
94
95
|
|
|
95
96
|
export { DELETE, GET, PUT };
|
|
96
|
-
//# sourceMappingURL=_server.ts-
|
|
97
|
+
//# sourceMappingURL=_server.ts-Da1kSClZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_server.ts-Da1kSClZ.js","sources":["../../../.svelte-kit/adapter-node/entries/endpoints/api/terminals/_id_/share/_server.ts.js"],"sourcesContent":["import { v as validateAuth } from \"../../../../../../chunks/auth.js\";\nimport { p as ptyManager } from \"../../../../../../chunks/pty-manager.js\";\nimport { s as shareStore, h as hashPassword } from \"../../../../../../chunks/share-store.js\";\nimport { c as closeGuests } from \"../../../../../../chunks/guest-registry.js\";\nimport { json } from \"@sveltejs/kit\";\nconst MIN_PASSWORD_LENGTH = 6;\nconst MODES = [\"view\", \"control\"];\nfunction toInfo(terminalId) {\n const share = shareStore.getShare(terminalId);\n if (!share) {\n return { active: false, createdAt: null, mode: null, updatedAt: null };\n }\n return { active: true, createdAt: share.createdAt, mode: share.mode, updatedAt: share.updatedAt };\n}\nconst GET = ({ params, request }) => {\n const authError = validateAuth(request);\n if (authError) {\n return authError;\n }\n return json(toInfo(params.id));\n};\nconst PUT = async ({ params, request }) => {\n const authError = validateAuth(request);\n if (authError) {\n return authError;\n }\n if (!ptyManager.get(params.id)) {\n return json({ error: \"Terminal not found\" }, { status: 404 });\n }\n let body;\n try {\n body = await request.json();\n } catch {\n return json({ error: \"Invalid JSON\" }, { status: 400 });\n }\n if (!MODES.includes(body.mode)) {\n return json({ error: \"mode must be 'view' or 'control'\" }, { status: 400 });\n }\n const existing = shareStore.getShare(params.id);\n const password = typeof body.password === \"string\" ? body.password : \"\";\n if (!existing && password.length < MIN_PASSWORD_LENGTH) {\n return json(\n { error: `password is required (min ${String(MIN_PASSWORD_LENGTH)} chars)` },\n { status: 400 }\n );\n }\n if (password && password.length < MIN_PASSWORD_LENGTH) {\n return json(\n { error: `password must be at least ${String(MIN_PASSWORD_LENGTH)} chars` },\n { status: 400 }\n );\n }\n const now = Date.now();\n shareStore.setShare({\n createdAt: existing?.createdAt ?? now,\n mode: body.mode,\n // `existing` is guaranteed non-null when password is empty (validated above).\n passwordHash: password ? hashPassword(password) : existing?.passwordHash ?? \"\",\n terminalId: params.id,\n updatedAt: now\n });\n if (password) {\n shareStore.deleteSessions(params.id);\n }\n if (password || existing?.mode !== body.mode) {\n closeGuests(params.id);\n }\n return json(toInfo(params.id));\n};\nconst DELETE = ({ params, request }) => {\n const authError = validateAuth(request);\n if (authError) {\n return authError;\n }\n shareStore.deleteShare(params.id);\n const closed = closeGuests(params.id);\n return json({ closedConnections: closed, success: true });\n};\nexport {\n DELETE,\n GET,\n PUT\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { v as validateAuth } from './auth-DuunT7Cg.js';
|
|
2
2
|
import { P as PROVIDER_COMMANDS } from './registry-D4J_CuzW.js';
|
|
3
|
-
import { p as ptyManager } from './pty-manager-
|
|
3
|
+
import { p as ptyManager } from './pty-manager-DDjG7DlH.js';
|
|
4
4
|
import { t as toErrorMessage } from './error-DDXB3duW.js';
|
|
5
5
|
import { j as json } from './index-CoD1IJuy.js';
|
|
6
6
|
import { realpathSync, statSync } from 'fs';
|
|
@@ -16,6 +16,7 @@ import './guest-registry-Dxvd7p-g.js';
|
|
|
16
16
|
import './super-session-handler-DPyxFgmz.js';
|
|
17
17
|
import './coordinator-DMU_ADXf.js';
|
|
18
18
|
import 'net';
|
|
19
|
+
import 'node:module';
|
|
19
20
|
import './shooter-home-4f_HkdGI.js';
|
|
20
21
|
|
|
21
22
|
const ALLOWED_COMMANDS = ["zsh", "bash", "sh", "fish", ...PROVIDER_COMMANDS];
|
|
@@ -139,4 +140,4 @@ const POST = async ({ request }) => {
|
|
|
139
140
|
};
|
|
140
141
|
|
|
141
142
|
export { GET, POST };
|
|
142
|
-
//# sourceMappingURL=_server.ts-
|
|
143
|
+
//# sourceMappingURL=_server.ts-l3cd4Cto.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_server.ts-l3cd4Cto.js","sources":["../../../.svelte-kit/adapter-node/entries/endpoints/api/terminals/_server.ts.js"],"sourcesContent":["import { v as validateAuth } from \"../../../../chunks/auth.js\";\nimport { P as PROVIDER_COMMANDS } from \"../../../../chunks/registry.js\";\nimport { p as ptyManager } from \"../../../../chunks/pty-manager.js\";\nimport { t as toErrorMessage } from \"../../../../chunks/error.js\";\nimport { json } from \"@sveltejs/kit\";\nimport { realpathSync, statSync } from \"fs\";\nimport { relative, isAbsolute } from \"path\";\nconst ALLOWED_COMMANDS = [\"zsh\", \"bash\", \"sh\", \"fish\", ...PROVIDER_COMMANDS];\nfunction lastScrollbackLine(scrollback) {\n if (!scrollback) {\n return null;\n }\n const lines = scrollback.trimEnd().split(\"\\n\");\n for (let i = lines.length - 1; i >= 0; i--) {\n const line = lines[i].trim();\n if (line) {\n return line.slice(0, 200);\n }\n }\n return null;\n}\nconst GET = ({ request }) => {\n const authError = validateAuth(request);\n if (authError) {\n return authError;\n }\n try {\n const terminals = ptyManager.list().map((t) => ({\n args: t.args,\n clientCount: t.clients.size,\n command: t.command,\n createdAt: t.createdAt.toISOString(),\n currentCwd: t.currentCwd,\n cwd: t.cwd,\n exitCode: t.exitCode,\n exitedAt: t.exitedAt?.toISOString() ?? null,\n id: t.id,\n isActive: t.isActive,\n lastOutput: lastScrollbackLine(t.scrollback),\n pid: t.pid,\n status: t.status\n }));\n return json({\n count: terminals.length,\n terminals,\n timestamp: (/* @__PURE__ */ new Date()).toISOString()\n });\n } catch (error) {\n console.error(\"[terminals] Failed to list terminals:\", toErrorMessage(error));\n return json({ error: \"Failed to list terminals\" }, { status: 500 });\n }\n};\nconst POST = async ({ request }) => {\n const authError = validateAuth(request);\n if (authError) {\n return authError;\n }\n let body;\n try {\n body = await request.json();\n } catch {\n return json({ error: \"Invalid JSON in request body\" }, { status: 400 });\n }\n try {\n const { args, cols, command, cwd, rows } = body;\n if (!command) {\n return json({ error: \"command is required\" }, { status: 400 });\n }\n if (command.includes(\"/\") || command.includes(\"\\\\\") || !ALLOWED_COMMANDS.includes(command)) {\n return json(\n { error: `Command not allowed. Allowed: ${ALLOWED_COMMANDS.join(\", \")}` },\n { status: 400 }\n );\n }\n if (!cwd) {\n return json({ error: \"cwd is required\" }, { status: 400 });\n }\n let realCwd;\n try {\n realCwd = realpathSync(cwd);\n if (!statSync(realCwd).isDirectory()) {\n return json({ error: \"cwd must be a directory\" }, { status: 400 });\n }\n } catch {\n return json({ error: \"cwd must be a directory\" }, { status: 400 });\n }\n const home = process.env.HOME || \"\";\n if (home) {\n const rel = relative(home, realCwd);\n if (rel.startsWith(\"..\") || isAbsolute(rel)) {\n return json({ error: \"Working directory must be under home directory\" }, { status: 400 });\n }\n }\n if (args !== void 0 && !Array.isArray(args)) {\n return json({ error: \"args must be an array of strings\" }, { status: 400 });\n }\n if (args && !args.every((a) => typeof a === \"string\")) {\n return json({ error: \"All args must be strings\" }, { status: 400 });\n }\n if (cols !== void 0 && (typeof cols !== \"number\" || cols < 1)) {\n return json({ error: \"cols must be a positive number\" }, { status: 400 });\n }\n if (rows !== void 0 && (typeof rows !== \"number\" || rows < 1)) {\n return json({ error: \"rows must be a positive number\" }, { status: 400 });\n }\n const terminal = await ptyManager.create(command, args ?? [], realCwd, cols ?? 80, rows ?? 24);\n console.log(\n `[terminals] Created terminal ${terminal.id} (pid=${terminal.pid}, command=${command})`\n );\n return json(\n {\n command: terminal.command,\n createdAt: terminal.createdAt instanceof Date ? terminal.createdAt.toISOString() : terminal.createdAt,\n cwd: terminal.cwd,\n id: terminal.id,\n pid: terminal.pid,\n sessionWs: `/ws/session/${terminal.id}`,\n ws: `/ws/terminal/${terminal.id}`\n },\n { status: 201 }\n );\n } catch (error) {\n console.error(\"[terminals] Failed to create terminal:\", toErrorMessage(error));\n return json({ error: \"Failed to create terminal\" }, { status: 500 });\n }\n};\nexport {\n GET,\n POST\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -10,6 +10,7 @@ import { b as broadcastEvent } from './guest-registry-Dxvd7p-g.js';
|
|
|
10
10
|
import './super-session-handler-DPyxFgmz.js';
|
|
11
11
|
import * as net from 'net';
|
|
12
12
|
import Database from 'better-sqlite3';
|
|
13
|
+
import { createRequire } from 'node:module';
|
|
13
14
|
import { s as shooterDataDir } from './shooter-home-4f_HkdGI.js';
|
|
14
15
|
|
|
15
16
|
function readOnlySourceForCommand(command) {
|
|
@@ -721,6 +722,62 @@ function toMillis(timestamp) {
|
|
|
721
722
|
const OW_GLOBAL_KEY = "__shooter_opencode_watcher";
|
|
722
723
|
const openCodeWatcher = globalThis[OW_GLOBAL_KEY] || new OpenCodeWatcher();
|
|
723
724
|
globalThis[OW_GLOBAL_KEY] = openCodeWatcher;
|
|
725
|
+
const require$1 = createRequire(import.meta.url);
|
|
726
|
+
const { Terminal } = require$1("@xterm/headless");
|
|
727
|
+
const { SerializeAddon } = require$1("@xterm/addon-serialize");
|
|
728
|
+
const SNAPSHOT_SCROLLBACK_LINES = 1e3;
|
|
729
|
+
const HIDE_CURSOR = "\x1B[?25l";
|
|
730
|
+
const SHOW_CURSOR = "\x1B[?25h";
|
|
731
|
+
class TerminalEmulator {
|
|
732
|
+
cursorHidden = false;
|
|
733
|
+
serializer;
|
|
734
|
+
term;
|
|
735
|
+
constructor(cols, rows) {
|
|
736
|
+
this.term = new Terminal({
|
|
737
|
+
allowProposedApi: true,
|
|
738
|
+
cols: cols > 0 ? cols : 80,
|
|
739
|
+
rows: rows > 0 ? rows : 24,
|
|
740
|
+
scrollback: SNAPSHOT_SCROLLBACK_LINES
|
|
741
|
+
});
|
|
742
|
+
this.serializer = new SerializeAddon();
|
|
743
|
+
this.term.loadAddon(this.serializer);
|
|
744
|
+
}
|
|
745
|
+
dispose() {
|
|
746
|
+
try {
|
|
747
|
+
this.serializer.dispose();
|
|
748
|
+
this.term.dispose();
|
|
749
|
+
} catch {
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
resize(cols, rows) {
|
|
753
|
+
if (cols > 0 && rows > 0) {
|
|
754
|
+
this.term.resize(cols, rows);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Capture the current screen as a VT-escape string. Serialization runs inside
|
|
759
|
+
* a write() callback so all previously-written bytes are parsed first.
|
|
760
|
+
*/
|
|
761
|
+
snapshot() {
|
|
762
|
+
return new Promise((resolve) => {
|
|
763
|
+
this.term.write("", () => {
|
|
764
|
+
let data = this.serializer.serialize({ scrollback: SNAPSHOT_SCROLLBACK_LINES });
|
|
765
|
+
if (this.cursorHidden) {
|
|
766
|
+
data += HIDE_CURSOR;
|
|
767
|
+
}
|
|
768
|
+
resolve({ cols: this.term.cols, data, rows: this.term.rows });
|
|
769
|
+
});
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
write(data) {
|
|
773
|
+
const hideIdx = data.lastIndexOf(HIDE_CURSOR);
|
|
774
|
+
const showIdx = data.lastIndexOf(SHOW_CURSOR);
|
|
775
|
+
if (hideIdx !== -1 || showIdx !== -1) {
|
|
776
|
+
this.cursorHidden = hideIdx > showIdx;
|
|
777
|
+
}
|
|
778
|
+
this.term.write(data);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
724
781
|
const DB_DIR = shooterDataDir();
|
|
725
782
|
const DB_PATH = path.join(DB_DIR, "shooter.db");
|
|
726
783
|
const COLUMNS = [
|
|
@@ -865,6 +922,11 @@ globalThis[TS_GLOBAL_KEY] = terminalStore;
|
|
|
865
922
|
const MAX_SCROLLBACK_BYTES = 512 * 1024;
|
|
866
923
|
const MAX_OUTPUT_BUFFER_BYTES = 1024 * 1024;
|
|
867
924
|
const SCROLLBACK_CHUNK_SIZE = 50 * 1024;
|
|
925
|
+
const SEQ_RING_MAX_ENTRIES = 2e3;
|
|
926
|
+
const SNAPSHOT_ENABLED = process.env.SHOOTER_SNAPSHOT_FALLBACK !== "raw";
|
|
927
|
+
const RESNAPSHOT_LOW_WATER_BYTES = MAX_OUTPUT_BUFFER_BYTES / 4;
|
|
928
|
+
const RESNAPSHOT_POLL_MS = 100;
|
|
929
|
+
const RESNAPSHOT_MAX_WAIT_MS = 1e4;
|
|
868
930
|
const CLEANUP_INTERVAL_MS = 5 * 60 * 1e3;
|
|
869
931
|
const EXITED_TTL_MS = 60 * 60 * 1e3;
|
|
870
932
|
const MAX_EXITED_TERMINALS = 10;
|
|
@@ -875,6 +937,10 @@ const __filename$1 = fileURLToPath(import.meta.url);
|
|
|
875
937
|
const __dirname$1 = path__default.dirname(__filename$1);
|
|
876
938
|
class PtyManager {
|
|
877
939
|
cleanupTimer = null;
|
|
940
|
+
// Clients currently converging via a resnapshot (Phase 2). While pending, a
|
|
941
|
+
// client receives no normal output frames — the forthcoming snapshot brings
|
|
942
|
+
// it to the current screen. WeakSet so disconnected sockets drop out on GC.
|
|
943
|
+
resnapshotPending = /* @__PURE__ */ new WeakSet();
|
|
878
944
|
terminals = /* @__PURE__ */ new Map();
|
|
879
945
|
constructor() {
|
|
880
946
|
this.cleanupTimer = setInterval(() => {
|
|
@@ -885,19 +951,42 @@ class PtyManager {
|
|
|
885
951
|
// create — now async: forks a holder process, connects via HolderClient,
|
|
886
952
|
// persists to SQLite
|
|
887
953
|
// -----------------------------------------------------------------------
|
|
888
|
-
attach(id, ws) {
|
|
954
|
+
attach(id, ws, opts) {
|
|
889
955
|
const terminal = this.terminals.get(id);
|
|
890
956
|
if (!terminal) {
|
|
891
957
|
return false;
|
|
892
958
|
}
|
|
893
|
-
terminal.clients.add(ws);
|
|
894
959
|
terminal.outputBuffers.set(ws, { data: [], size: 0 });
|
|
960
|
+
const wantsSnapshot = opts?.snapshot === true;
|
|
961
|
+
const lastSeq = opts?.lastSeq ?? 0;
|
|
962
|
+
if (wantsSnapshot && lastSeq > 0) {
|
|
963
|
+
const gap = this.getSeqRingFrom(id, lastSeq);
|
|
964
|
+
if (gap !== null) {
|
|
965
|
+
for (const entry of gap) {
|
|
966
|
+
this.safeSend(ws, JSON.stringify({ data: entry.data, seq: entry.seq, type: "output" }));
|
|
967
|
+
}
|
|
968
|
+
terminal.clients.add(ws);
|
|
969
|
+
return true;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
if (wantsSnapshot && terminal.emulator) {
|
|
973
|
+
void this.snapshotAndSend(terminal, ws).then((ok) => {
|
|
974
|
+
if (ws.readyState !== 1) {
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
if (!ok) {
|
|
978
|
+
terminal.clients.add(ws);
|
|
979
|
+
void this.sendScrollback(terminal, ws);
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
terminal.clients.add(ws);
|
|
983
|
+
});
|
|
984
|
+
return true;
|
|
985
|
+
}
|
|
986
|
+
terminal.clients.add(ws);
|
|
895
987
|
void this.sendScrollback(terminal, ws);
|
|
896
988
|
return true;
|
|
897
989
|
}
|
|
898
|
-
// -----------------------------------------------------------------------
|
|
899
|
-
// reconnectAll — recover persisted terminals on server startup
|
|
900
|
-
// -----------------------------------------------------------------------
|
|
901
990
|
cleanup() {
|
|
902
991
|
const now = Date.now();
|
|
903
992
|
const exited = [];
|
|
@@ -928,7 +1017,7 @@ class PtyManager {
|
|
|
928
1017
|
}
|
|
929
1018
|
}
|
|
930
1019
|
// -----------------------------------------------------------------------
|
|
931
|
-
//
|
|
1020
|
+
// reconnectAll — recover persisted terminals on server startup
|
|
932
1021
|
// -----------------------------------------------------------------------
|
|
933
1022
|
async create(command, args, cwd, cols, rows) {
|
|
934
1023
|
const id = randomBytes(4).toString("hex");
|
|
@@ -981,6 +1070,7 @@ class PtyManager {
|
|
|
981
1070
|
createdAt: now,
|
|
982
1071
|
currentCwd: null,
|
|
983
1072
|
cwd,
|
|
1073
|
+
emulator: SNAPSHOT_ENABLED ? new TerminalEmulator(cols, rows) : null,
|
|
984
1074
|
exitCode: connectResult.exitCode,
|
|
985
1075
|
exitedAt: null,
|
|
986
1076
|
holderPid,
|
|
@@ -994,6 +1084,8 @@ class PtyManager {
|
|
|
994
1084
|
pty: client,
|
|
995
1085
|
rows,
|
|
996
1086
|
scrollback: connectResult.scrollback,
|
|
1087
|
+
seqCounter: 0,
|
|
1088
|
+
seqRing: [],
|
|
997
1089
|
sessionFile: null,
|
|
998
1090
|
socketPath,
|
|
999
1091
|
status: connectResult.exited ? "exited" : "running",
|
|
@@ -1027,7 +1119,7 @@ class PtyManager {
|
|
|
1027
1119
|
return terminal;
|
|
1028
1120
|
}
|
|
1029
1121
|
// -----------------------------------------------------------------------
|
|
1030
|
-
//
|
|
1122
|
+
// disconnectAll — graceful shutdown: disconnect clients, keep holders alive
|
|
1031
1123
|
// -----------------------------------------------------------------------
|
|
1032
1124
|
destroy() {
|
|
1033
1125
|
if (this.cleanupTimer) {
|
|
@@ -1062,8 +1154,7 @@ class PtyManager {
|
|
|
1062
1154
|
}
|
|
1063
1155
|
}
|
|
1064
1156
|
// -----------------------------------------------------------------------
|
|
1065
|
-
//
|
|
1066
|
-
// createdAt descending
|
|
1157
|
+
// get
|
|
1067
1158
|
// -----------------------------------------------------------------------
|
|
1068
1159
|
detach(id, ws) {
|
|
1069
1160
|
const terminal = this.terminals.get(id);
|
|
@@ -1075,7 +1166,8 @@ class PtyManager {
|
|
|
1075
1166
|
return true;
|
|
1076
1167
|
}
|
|
1077
1168
|
// -----------------------------------------------------------------------
|
|
1078
|
-
//
|
|
1169
|
+
// list — running first, then recently exited, each group sorted by
|
|
1170
|
+
// createdAt descending
|
|
1079
1171
|
// -----------------------------------------------------------------------
|
|
1080
1172
|
disconnectAll() {
|
|
1081
1173
|
if (this.cleanupTimer) {
|
|
@@ -1100,13 +1192,13 @@ class PtyManager {
|
|
|
1100
1192
|
this.terminals.clear();
|
|
1101
1193
|
}
|
|
1102
1194
|
// -----------------------------------------------------------------------
|
|
1103
|
-
//
|
|
1195
|
+
// kill — route through holder: SIGTERM, then SIGKILL after 5 s
|
|
1104
1196
|
// -----------------------------------------------------------------------
|
|
1105
1197
|
get(id) {
|
|
1106
1198
|
return this.terminals.get(id) ?? null;
|
|
1107
1199
|
}
|
|
1108
1200
|
// -----------------------------------------------------------------------
|
|
1109
|
-
//
|
|
1201
|
+
// remove — remove an exited terminal from the map
|
|
1110
1202
|
// -----------------------------------------------------------------------
|
|
1111
1203
|
getScrollback(id) {
|
|
1112
1204
|
const terminal = this.terminals.get(id);
|
|
@@ -1116,8 +1208,40 @@ class PtyManager {
|
|
|
1116
1208
|
return terminal.scrollback;
|
|
1117
1209
|
}
|
|
1118
1210
|
// -----------------------------------------------------------------------
|
|
1119
|
-
//
|
|
1211
|
+
// resize
|
|
1120
1212
|
// -----------------------------------------------------------------------
|
|
1213
|
+
/** Current highest assigned seq for a terminal, or null if unknown. */
|
|
1214
|
+
getSeqCounter(id) {
|
|
1215
|
+
return this.terminals.get(id)?.seqCounter ?? null;
|
|
1216
|
+
}
|
|
1217
|
+
/**
|
|
1218
|
+
* Return the ring entries with seq > afterSeq, in order. Returns an empty
|
|
1219
|
+
* array when the caller is already current, or null when the gap is
|
|
1220
|
+
* unresolvable from the ring (caller must take a full snapshot). Unresolvable
|
|
1221
|
+
* means any of:
|
|
1222
|
+
* - afterSeq > seqCounter: the caller claims a seq we never produced — this
|
|
1223
|
+
* happens when the seq counter reset across a server restart (the client
|
|
1224
|
+
* is from a previous terminal lifetime), so its content is unrelated.
|
|
1225
|
+
* - ring empty but afterSeq > 0: nothing buffered to bridge the gap.
|
|
1226
|
+
* - afterSeq predates the oldest retained entry: the gap aged out.
|
|
1227
|
+
*/
|
|
1228
|
+
getSeqRingFrom(id, afterSeq) {
|
|
1229
|
+
const terminal = this.terminals.get(id);
|
|
1230
|
+
if (!terminal) {
|
|
1231
|
+
return null;
|
|
1232
|
+
}
|
|
1233
|
+
if (afterSeq > terminal.seqCounter) {
|
|
1234
|
+
return null;
|
|
1235
|
+
}
|
|
1236
|
+
const ring = terminal.seqRing;
|
|
1237
|
+
if (ring.length === 0) {
|
|
1238
|
+
return afterSeq <= 0 ? [] : null;
|
|
1239
|
+
}
|
|
1240
|
+
if (afterSeq < ring[0].seq - 1) {
|
|
1241
|
+
return null;
|
|
1242
|
+
}
|
|
1243
|
+
return ring.filter((e) => e.seq > afterSeq);
|
|
1244
|
+
}
|
|
1121
1245
|
kill(id) {
|
|
1122
1246
|
const terminal = this.terminals.get(id);
|
|
1123
1247
|
if (!terminal) {
|
|
@@ -1150,7 +1274,7 @@ class PtyManager {
|
|
|
1150
1274
|
return true;
|
|
1151
1275
|
}
|
|
1152
1276
|
// -----------------------------------------------------------------------
|
|
1153
|
-
//
|
|
1277
|
+
// attach — register a WebSocket client and replay scrollback
|
|
1154
1278
|
// -----------------------------------------------------------------------
|
|
1155
1279
|
list() {
|
|
1156
1280
|
const all = Array.from(this.terminals.values());
|
|
@@ -1163,7 +1287,7 @@ class PtyManager {
|
|
|
1163
1287
|
return [...running, ...exited];
|
|
1164
1288
|
}
|
|
1165
1289
|
// -----------------------------------------------------------------------
|
|
1166
|
-
//
|
|
1290
|
+
// detach — remove a WebSocket client
|
|
1167
1291
|
// -----------------------------------------------------------------------
|
|
1168
1292
|
async reconnectAll() {
|
|
1169
1293
|
const running = terminalStore.listRunning();
|
|
@@ -1183,8 +1307,7 @@ class PtyManager {
|
|
|
1183
1307
|
}
|
|
1184
1308
|
}
|
|
1185
1309
|
// -----------------------------------------------------------------------
|
|
1186
|
-
//
|
|
1187
|
-
// also clean up old SQLite records
|
|
1310
|
+
// getScrollback — return raw scrollback data for replay
|
|
1188
1311
|
// -----------------------------------------------------------------------
|
|
1189
1312
|
remove(id) {
|
|
1190
1313
|
const terminal = this.terminals.get(id);
|
|
@@ -1198,7 +1321,8 @@ class PtyManager {
|
|
|
1198
1321
|
return true;
|
|
1199
1322
|
}
|
|
1200
1323
|
// -----------------------------------------------------------------------
|
|
1201
|
-
//
|
|
1324
|
+
// cleanup — evict exited terminals older than 1 hour, cap at 10 exited;
|
|
1325
|
+
// also clean up old SQLite records
|
|
1202
1326
|
// -----------------------------------------------------------------------
|
|
1203
1327
|
resize(id, cols, rows) {
|
|
1204
1328
|
const terminal = this.terminals.get(id);
|
|
@@ -1209,6 +1333,7 @@ class PtyManager {
|
|
|
1209
1333
|
terminal.pty.resize(cols, rows);
|
|
1210
1334
|
terminal.cols = cols;
|
|
1211
1335
|
terminal.rows = rows;
|
|
1336
|
+
terminal.emulator?.resize(cols, rows);
|
|
1212
1337
|
const msg = JSON.stringify({ cols, rows, type: "resize" });
|
|
1213
1338
|
for (const ws of terminal.clients) {
|
|
1214
1339
|
this.safeSend(ws, msg);
|
|
@@ -1219,6 +1344,39 @@ class PtyManager {
|
|
|
1219
1344
|
}
|
|
1220
1345
|
}
|
|
1221
1346
|
// -----------------------------------------------------------------------
|
|
1347
|
+
// destroy — emergency forced kill (kills holder processes too)
|
|
1348
|
+
// -----------------------------------------------------------------------
|
|
1349
|
+
/**
|
|
1350
|
+
* Compute the current-screen snapshot from the emulator and send it as a
|
|
1351
|
+
* single {type:'snapshot'} frame stamped with the current seq. Returns false
|
|
1352
|
+
* if there is no emulator, the socket closed, or serialization failed.
|
|
1353
|
+
* Reused by Phase 2 to resnapshot a client after a backpressure gap.
|
|
1354
|
+
*/
|
|
1355
|
+
async snapshotAndSend(terminal, ws) {
|
|
1356
|
+
if (!terminal.emulator) {
|
|
1357
|
+
return false;
|
|
1358
|
+
}
|
|
1359
|
+
try {
|
|
1360
|
+
const snap = await terminal.emulator.snapshot();
|
|
1361
|
+
if (ws.readyState !== 1) {
|
|
1362
|
+
return false;
|
|
1363
|
+
}
|
|
1364
|
+
this.safeSend(
|
|
1365
|
+
ws,
|
|
1366
|
+
JSON.stringify({
|
|
1367
|
+
cols: snap.cols,
|
|
1368
|
+
data: snap.data,
|
|
1369
|
+
rows: snap.rows,
|
|
1370
|
+
seq: terminal.seqCounter,
|
|
1371
|
+
type: "snapshot"
|
|
1372
|
+
})
|
|
1373
|
+
);
|
|
1374
|
+
return true;
|
|
1375
|
+
} catch {
|
|
1376
|
+
return false;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
// -----------------------------------------------------------------------
|
|
1222
1380
|
// Private: reconnectOne — reconnect to a single persisted terminal
|
|
1223
1381
|
// -----------------------------------------------------------------------
|
|
1224
1382
|
appendScrollback(terminal, data) {
|
|
@@ -1233,21 +1391,103 @@ class PtyManager {
|
|
|
1233
1391
|
}
|
|
1234
1392
|
}
|
|
1235
1393
|
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Assign the next sequence number to an output chunk and append it to the
|
|
1396
|
+
* bounded replay ring. Returns the new seq. Phase 2 uses the ring to replay
|
|
1397
|
+
* the gap to a reconnecting client without a full snapshot.
|
|
1398
|
+
*/
|
|
1399
|
+
appendSeqRing(terminal, data) {
|
|
1400
|
+
const seq = terminal.seqCounter + 1;
|
|
1401
|
+
terminal.seqRing.push({ data, seq });
|
|
1402
|
+
if (terminal.seqRing.length > SEQ_RING_MAX_ENTRIES) {
|
|
1403
|
+
terminal.seqRing.shift();
|
|
1404
|
+
}
|
|
1405
|
+
terminal.seqCounter = seq;
|
|
1406
|
+
return seq;
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Mark a client for convergence (Phase 2). Its queued output is discarded and
|
|
1410
|
+
* further live frames are withheld until its socket drains below the low-water
|
|
1411
|
+
* mark (or a hard timeout elapses), at which point a fresh snapshot resets it
|
|
1412
|
+
* to the current screen. This replaces silent byte-dropping so a slow or
|
|
1413
|
+
* throttled client can never diverge permanently (G1).
|
|
1414
|
+
*/
|
|
1415
|
+
beginResnapshot(terminal, ws) {
|
|
1416
|
+
if (this.resnapshotPending.has(ws)) {
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
this.resnapshotPending.add(ws);
|
|
1420
|
+
const buffer = terminal.outputBuffers.get(ws);
|
|
1421
|
+
if (buffer) {
|
|
1422
|
+
buffer.data.length = 0;
|
|
1423
|
+
buffer.size = 0;
|
|
1424
|
+
}
|
|
1425
|
+
this.safeSend(ws, JSON.stringify({ bytes: 0, type: "output-dropped" }));
|
|
1426
|
+
const startedAt = Date.now();
|
|
1427
|
+
const poll = () => {
|
|
1428
|
+
if (!this.resnapshotPending.has(ws)) {
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
if (ws.readyState !== 1 || !terminal.emulator || !terminal.clients.has(ws)) {
|
|
1432
|
+
this.resnapshotPending.delete(ws);
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
const drained = ws.bufferedAmount <= RESNAPSHOT_LOW_WATER_BYTES;
|
|
1436
|
+
const timedOut = Date.now() - startedAt > RESNAPSHOT_MAX_WAIT_MS;
|
|
1437
|
+
if (drained || timedOut) {
|
|
1438
|
+
void this.snapshotAndSend(terminal, ws).finally(() => {
|
|
1439
|
+
this.resnapshotPending.delete(ws);
|
|
1440
|
+
});
|
|
1441
|
+
return;
|
|
1442
|
+
}
|
|
1443
|
+
setTimeout(poll, RESNAPSHOT_POLL_MS);
|
|
1444
|
+
};
|
|
1445
|
+
setTimeout(poll, RESNAPSHOT_POLL_MS);
|
|
1446
|
+
}
|
|
1236
1447
|
// -----------------------------------------------------------------------
|
|
1237
1448
|
// Private: handleReconnectFailure — handle failed reconnection
|
|
1238
1449
|
// -----------------------------------------------------------------------
|
|
1239
1450
|
broadcastOutput(terminal, data) {
|
|
1240
|
-
const
|
|
1451
|
+
const seq = this.appendSeqRing(terminal, data);
|
|
1452
|
+
const msg = JSON.stringify({ data, seq, type: "output" });
|
|
1453
|
+
if (!terminal.emulator) {
|
|
1454
|
+
this.broadcastOutputLegacy(terminal, msg);
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
const msgSize = Buffer.byteLength(msg, "utf8");
|
|
1458
|
+
for (const ws of terminal.clients) {
|
|
1459
|
+
if (this.resnapshotPending.has(ws)) {
|
|
1460
|
+
continue;
|
|
1461
|
+
}
|
|
1462
|
+
const buffer = terminal.outputBuffers.get(ws);
|
|
1463
|
+
if (!buffer) {
|
|
1464
|
+
continue;
|
|
1465
|
+
}
|
|
1466
|
+
if (ws.bufferedAmount > MAX_OUTPUT_BUFFER_BYTES || buffer.size + msgSize > MAX_OUTPUT_BUFFER_BYTES) {
|
|
1467
|
+
this.beginResnapshot(terminal, ws);
|
|
1468
|
+
continue;
|
|
1469
|
+
}
|
|
1470
|
+
buffer.data.push(msg);
|
|
1471
|
+
buffer.size += msgSize;
|
|
1472
|
+
this.flushOutputBuffer(ws, buffer);
|
|
1473
|
+
}
|
|
1474
|
+
}
|
|
1475
|
+
/**
|
|
1476
|
+
* Legacy broadcast path used only when the emulator is disabled
|
|
1477
|
+
* (SHOOTER_SNAPSHOT_FALLBACK=raw). Drops the oldest buffered output to make
|
|
1478
|
+
* room and notifies the client; there is no snapshot to converge it to.
|
|
1479
|
+
*/
|
|
1480
|
+
broadcastOutputLegacy(terminal, msg) {
|
|
1481
|
+
const msgSize = Buffer.byteLength(msg, "utf8");
|
|
1241
1482
|
for (const ws of terminal.clients) {
|
|
1242
1483
|
if (ws.bufferedAmount > MAX_OUTPUT_BUFFER_BYTES) {
|
|
1243
|
-
this.safeSend(ws, JSON.stringify({ bytes:
|
|
1484
|
+
this.safeSend(ws, JSON.stringify({ bytes: msgSize, type: "output-dropped" }));
|
|
1244
1485
|
continue;
|
|
1245
1486
|
}
|
|
1246
1487
|
const buffer = terminal.outputBuffers.get(ws);
|
|
1247
1488
|
if (!buffer) {
|
|
1248
1489
|
continue;
|
|
1249
1490
|
}
|
|
1250
|
-
const msgSize = Buffer.byteLength(msg, "utf8");
|
|
1251
1491
|
if (buffer.size + msgSize > MAX_OUTPUT_BUFFER_BYTES) {
|
|
1252
1492
|
let droppedBytes = 0;
|
|
1253
1493
|
while (buffer.size + msgSize > MAX_OUTPUT_BUFFER_BYTES && buffer.data.length > 0) {
|
|
@@ -1259,11 +1499,7 @@ class PtyManager {
|
|
|
1259
1499
|
}
|
|
1260
1500
|
}
|
|
1261
1501
|
if (droppedBytes > 0) {
|
|
1262
|
-
|
|
1263
|
-
bytes: droppedBytes,
|
|
1264
|
-
type: "output-dropped"
|
|
1265
|
-
});
|
|
1266
|
-
this.safeSend(ws, dropMsg);
|
|
1502
|
+
this.safeSend(ws, JSON.stringify({ bytes: droppedBytes, type: "output-dropped" }));
|
|
1267
1503
|
}
|
|
1268
1504
|
}
|
|
1269
1505
|
buffer.data.push(msg);
|
|
@@ -1296,6 +1532,8 @@ class PtyManager {
|
|
|
1296
1532
|
openCodeWatcher.stop(terminal.openCodeSessionId, terminal.openCodeNoopCb);
|
|
1297
1533
|
terminal.openCodeNoopCb = null;
|
|
1298
1534
|
}
|
|
1535
|
+
terminal.emulator?.dispose();
|
|
1536
|
+
terminal.emulator = null;
|
|
1299
1537
|
terminal.pty.disconnect();
|
|
1300
1538
|
for (const ws of terminal.clients) {
|
|
1301
1539
|
try {
|
|
@@ -1387,6 +1625,7 @@ class PtyManager {
|
|
|
1387
1625
|
createdAt: new Date(record.createdAt),
|
|
1388
1626
|
currentCwd: null,
|
|
1389
1627
|
cwd: record.cwd,
|
|
1628
|
+
emulator: SNAPSHOT_ENABLED ? new TerminalEmulator(record.cols, record.rows) : null,
|
|
1390
1629
|
exitCode: connectResult.exitCode,
|
|
1391
1630
|
exitedAt: record.exitedAt ? new Date(record.exitedAt) : null,
|
|
1392
1631
|
holderPid: record.holderPid ?? 0,
|
|
@@ -1400,11 +1639,16 @@ class PtyManager {
|
|
|
1400
1639
|
pty: client,
|
|
1401
1640
|
rows: record.rows,
|
|
1402
1641
|
scrollback: connectResult.scrollback,
|
|
1642
|
+
seqCounter: 0,
|
|
1643
|
+
seqRing: [],
|
|
1403
1644
|
sessionFile: record.sessionFile ?? null,
|
|
1404
1645
|
socketPath: record.socketPath,
|
|
1405
1646
|
status: connectResult.exited ? "exited" : "running",
|
|
1406
1647
|
watcherOffset: 0
|
|
1407
1648
|
};
|
|
1649
|
+
if (terminal.emulator && connectResult.scrollback.length > 0) {
|
|
1650
|
+
terminal.emulator.write(connectResult.scrollback);
|
|
1651
|
+
}
|
|
1408
1652
|
if (connectResult.exited) {
|
|
1409
1653
|
terminal.exitedAt = terminal.exitedAt ?? /* @__PURE__ */ new Date();
|
|
1410
1654
|
terminalStore.markExited(record.id, connectResult.exitCode);
|
|
@@ -1667,6 +1911,7 @@ class PtyManager {
|
|
|
1667
1911
|
}
|
|
1668
1912
|
});
|
|
1669
1913
|
client.onOutput((data) => {
|
|
1914
|
+
terminal.emulator?.write(data);
|
|
1670
1915
|
this.appendScrollback(terminal, data);
|
|
1671
1916
|
this.broadcastOutput(terminal, data);
|
|
1672
1917
|
});
|
|
@@ -1711,4 +1956,4 @@ const ptyManager = globalThis[PTY_GLOBAL_KEY] || new PtyManager();
|
|
|
1711
1956
|
globalThis[PTY_GLOBAL_KEY] = ptyManager;
|
|
1712
1957
|
|
|
1713
1958
|
export { ptyManager as p };
|
|
1714
|
-
//# sourceMappingURL=pty-manager-
|
|
1959
|
+
//# sourceMappingURL=pty-manager-DDjG7DlH.js.map
|