@juspay/shooter 1.22.0 → 1.24.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/CbINytmr.js +3 -0
- package/build/client/_app/immutable/chunks/CbINytmr.js.br +0 -0
- package/build/client/_app/immutable/chunks/CbINytmr.js.gz +0 -0
- package/build/client/_app/immutable/chunks/D868VwmX.js +6 -0
- package/build/client/_app/immutable/chunks/D868VwmX.js.br +0 -0
- package/build/client/_app/immutable/chunks/D868VwmX.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{CZg4kn4E.js → Dd1KNHg-.js} +1 -1
- package/build/client/_app/immutable/chunks/Dd1KNHg-.js.br +0 -0
- package/build/client/_app/immutable/chunks/Dd1KNHg-.js.gz +0 -0
- package/build/client/_app/immutable/chunks/{DhK7PwI_.js → V8pbM9cl.js} +1 -1
- package/build/client/_app/immutable/chunks/V8pbM9cl.js.br +0 -0
- package/build/client/_app/immutable/chunks/V8pbM9cl.js.gz +0 -0
- package/build/client/_app/immutable/entry/{app.CTqz33nP.js → app.CiQHPW0j.js} +2 -2
- package/build/client/_app/immutable/entry/app.CiQHPW0j.js.br +0 -0
- package/build/client/_app/immutable/entry/app.CiQHPW0j.js.gz +0 -0
- package/build/client/_app/immutable/entry/start.DUCXuMLl.js +1 -0
- package/build/client/_app/immutable/entry/start.DUCXuMLl.js.br +2 -0
- package/build/client/_app/immutable/entry/start.DUCXuMLl.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{0.Qn7Ktiht.js → 0.BRFdS_ay.js} +1 -1
- package/build/client/_app/immutable/nodes/0.BRFdS_ay.js.br +0 -0
- package/build/client/_app/immutable/nodes/0.BRFdS_ay.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{1.BxWOfNlo.js → 1.B1pgwYu3.js} +1 -1
- package/build/client/_app/immutable/nodes/1.B1pgwYu3.js.br +0 -0
- package/build/client/_app/immutable/nodes/1.B1pgwYu3.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{10.BGPYD1s1.js → 10.558mUFIl.js} +1 -1
- package/build/client/_app/immutable/nodes/10.558mUFIl.js.br +0 -0
- package/build/client/_app/immutable/nodes/10.558mUFIl.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{11.BxY1PUjC.js → 11.CdmPyt4k.js} +2 -2
- package/build/client/_app/immutable/nodes/11.CdmPyt4k.js.br +0 -0
- package/build/client/_app/immutable/nodes/{11.BxY1PUjC.js.gz → 11.CdmPyt4k.js.gz} +0 -0
- package/build/client/_app/immutable/nodes/{2.Bc2qALkX.js → 2.1tiK5o4L.js} +1 -1
- package/build/client/_app/immutable/nodes/2.1tiK5o4L.js.br +0 -0
- package/build/client/_app/immutable/nodes/2.1tiK5o4L.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{3.N2-A8noI.js → 3.DyQTorXE.js} +1 -1
- package/build/client/_app/immutable/nodes/3.DyQTorXE.js.br +0 -0
- package/build/client/_app/immutable/nodes/3.DyQTorXE.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{6.BWF9Qx6F.js → 6.Chn2ZM2V.js} +1 -1
- package/build/client/_app/immutable/nodes/6.Chn2ZM2V.js.br +0 -0
- package/build/client/_app/immutable/nodes/6.Chn2ZM2V.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{7.DHuDIdpz.js → 7.DhJ2K3GQ.js} +1 -1
- package/build/client/_app/immutable/nodes/7.DhJ2K3GQ.js.br +0 -0
- package/build/client/_app/immutable/nodes/7.DhJ2K3GQ.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{8.D0Ijt9Vv.js → 8.B4pLxBkI.js} +1 -1
- package/build/client/_app/immutable/nodes/8.B4pLxBkI.js.br +0 -0
- package/build/client/_app/immutable/nodes/8.B4pLxBkI.js.gz +0 -0
- package/build/client/_app/immutable/nodes/{9.2Piwo35J.js → 9.CVsskPw5.js} +1 -1
- package/build/client/_app/immutable/nodes/9.CVsskPw5.js.br +0 -0
- package/build/client/_app/immutable/nodes/9.CVsskPw5.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-DZO0pCuJ.js} +2 -2
- package/build/server/chunks/{0-CVGsyVKN.js.map → 0-DZO0pCuJ.js.map} +1 -1
- package/build/server/chunks/{1-BAlAsKdp.js → 1-D2SDQFeq.js} +2 -2
- package/build/server/chunks/{1-BAlAsKdp.js.map → 1-D2SDQFeq.js.map} +1 -1
- package/build/server/chunks/{10-BUCX7Aqz.js → 10-CEJDEhpQ.js} +2 -2
- package/build/server/chunks/{10-BUCX7Aqz.js.map → 10-CEJDEhpQ.js.map} +1 -1
- package/build/server/chunks/{11-DHPvc2yA.js → 11-CMC_i3co.js} +2 -2
- package/build/server/chunks/{11-DHPvc2yA.js.map → 11-CMC_i3co.js.map} +1 -1
- package/build/server/chunks/{2-DLOMdCHW.js → 2-C1XSBNj7.js} +2 -2
- package/build/server/chunks/{2-DLOMdCHW.js.map → 2-C1XSBNj7.js.map} +1 -1
- package/build/server/chunks/{3-DCf69LYo.js → 3-DRjTDzaV.js} +2 -2
- package/build/server/chunks/{3-DCf69LYo.js.map → 3-DRjTDzaV.js.map} +1 -1
- package/build/server/chunks/{6-DUrC2Naz.js → 6-BcgshtK4.js} +2 -2
- package/build/server/chunks/{6-DUrC2Naz.js.map → 6-BcgshtK4.js.map} +1 -1
- package/build/server/chunks/{7-TXwjMHt2.js → 7-BBsuxiGz.js} +2 -2
- package/build/server/chunks/{7-TXwjMHt2.js.map → 7-BBsuxiGz.js.map} +1 -1
- package/build/server/chunks/{8-D2X_jBsT.js → 8-B0qM-Zzs.js} +2 -2
- package/build/server/chunks/{8-D2X_jBsT.js.map → 8-B0qM-Zzs.js.map} +1 -1
- package/build/server/chunks/{9-DK0hH5Xa.js → 9-XIfsp2D_.js} +2 -2
- package/build/server/chunks/{9-DK0hH5Xa.js.map → 9-XIfsp2D_.js.map} +1 -1
- package/build/server/chunks/{_server.ts-DiBMY7Ho.js → _server.ts-B-Gekwsu.js} +3 -2
- package/build/server/chunks/_server.ts-B-Gekwsu.js.map +1 -0
- package/build/server/chunks/{_server.ts-C0PO_cAu.js → _server.ts-BhP3b8A5.js} +3 -2
- package/build/server/chunks/{_server.ts-C0PO_cAu.js.map → _server.ts-BhP3b8A5.js.map} +1 -1
- package/build/server/chunks/{_server.ts-Bol54_Qo.js → _server.ts-CTdFxJdD.js} +3 -2
- package/build/server/chunks/_server.ts-CTdFxJdD.js.map +1 -0
- package/build/server/chunks/{_server.ts-B54Pvhgc.js → _server.ts-Cx0S__hk.js} +3 -2
- package/build/server/chunks/_server.ts-Cx0S__hk.js.map +1 -0
- package/build/server/chunks/{_server.ts-CZb-BI5H.js → _server.ts-DtT-ZXki.js} +3 -2
- package/build/server/chunks/_server.ts-DtT-ZXki.js.map +1 -0
- package/build/server/chunks/{pty-manager-CoWVT56F.js → pty-manager-BsHXoNks.js} +287 -27
- package/build/server/chunks/pty-manager-BsHXoNks.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 +5 -2
- package/src/lib/modules/client/terminal/xterm-wrapper.ts +56 -15
- package/src/lib/modules/server/terminal/pty-manager.ts +291 -35
- package/src/lib/modules/server/terminal/terminal-emulator.ts +102 -0
- package/src/lib/modules/server/terminal/terminal-store.ts +10 -0
- package/src/lib/modules/server/ws/server.ts +18 -2
- package/src/lib/modules/server/ws/terminal-handler.ts +60 -14
- package/src/lib/types/generated/WsProtocol.ts +10 -1
- package/src/lib/types/server.ts +34 -1
- package/src/lib/types/terminal-client.ts +3 -0
- package/src/lib/types/ws.ts +7 -2
- package/src/routes/api/terminals/[id]/resize/+server.ts +3 -0
- 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/DhK7PwI_.js.gz +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-BsHXoNks.js';
|
|
3
3
|
import { r as resolveAccess } from './share-auth-BS7JuiHf.js';
|
|
4
4
|
import { s as shareStore } from './share-store-B9jMpVg0.js';
|
|
5
5
|
import { t as toErrorMessage } from './error-DDXB3duW.js';
|
|
@@ -18,6 +18,7 @@ import './registry-D4J_CuzW.js';
|
|
|
18
18
|
import 'better-sqlite3';
|
|
19
19
|
import './coordinator-DMU_ADXf.js';
|
|
20
20
|
import 'net';
|
|
21
|
+
import 'node:module';
|
|
21
22
|
import './shooter-home-4f_HkdGI.js';
|
|
22
23
|
|
|
23
24
|
function lastScrollbackLine(scrollback) {
|
|
@@ -103,4 +104,4 @@ const DELETE = ({ params, request }) => {
|
|
|
103
104
|
};
|
|
104
105
|
|
|
105
106
|
export { DELETE, GET };
|
|
106
|
-
//# sourceMappingURL=_server.ts-
|
|
107
|
+
//# sourceMappingURL=_server.ts-Cx0S__hk.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_server.ts-Cx0S__hk.js","sources":["../../../.svelte-kit/adapter-node/entries/endpoints/api/terminals/_id_/_server.ts.js"],"sourcesContent":["import { v as validateAuth } from \"../../../../../chunks/auth.js\";\nimport { p as ptyManager } from \"../../../../../chunks/pty-manager.js\";\nimport { r as resolveAccess } from \"../../../../../chunks/share-auth.js\";\nimport { s as shareStore } from \"../../../../../chunks/share-store.js\";\nimport { t as toErrorMessage } from \"../../../../../chunks/error.js\";\nimport { c as closeGuests } from \"../../../../../chunks/guest-registry.js\";\nimport { json } from \"@sveltejs/kit\";\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 = ({ params, request }) => {\n const access = resolveAccess(request, params.id);\n if (!access) {\n return json({ error: \"Unauthorized\" }, { status: 401 });\n }\n try {\n const terminal = ptyManager.get(params.id);\n if (!terminal) {\n return json({ error: \"Terminal not found\" }, { status: 404 });\n }\n return json({\n args: terminal.args,\n clientCount: terminal.clients.size,\n cols: terminal.cols,\n command: terminal.command,\n createdAt: terminal.createdAt.toISOString(),\n cwd: terminal.cwd,\n exitCode: terminal.exitCode,\n exitedAt: terminal.exitedAt?.toISOString() ?? null,\n id: terminal.id,\n lastOutput: lastScrollbackLine(terminal.scrollback),\n pid: terminal.pid,\n rows: terminal.rows,\n sessionWs: `/ws/session/${terminal.id}`,\n status: terminal.status,\n timestamp: (/* @__PURE__ */ new Date()).toISOString(),\n ws: `/ws/terminal/${terminal.id}`,\n ...access.level === \"guest\" ? { shareMode: access.mode } : {}\n });\n } catch (error) {\n console.error(\"[terminals] Failed to get terminal:\", toErrorMessage(error));\n return json({ error: \"Failed to get terminal\" }, { status: 500 });\n }\n};\nconst DELETE = ({ params, request }) => {\n const authError = validateAuth(request);\n if (authError) {\n return authError;\n }\n try {\n const terminal = ptyManager.get(params.id);\n if (!terminal) {\n return json({ error: \"Terminal not found\" }, { status: 404 });\n }\n if (terminal.status === \"exited\") {\n ptyManager.remove(params.id);\n shareStore.deleteShare(params.id);\n closeGuests(params.id);\n console.log(`[terminals] Removed exited terminal ${params.id}`);\n return json({\n removed: true,\n success: true,\n timestamp: (/* @__PURE__ */ new Date()).toISOString()\n });\n }\n ptyManager.kill(params.id);\n shareStore.deleteShare(params.id);\n closeGuests(params.id);\n console.log(`[terminals] Killed terminal ${params.id} (pid=${terminal.pid})`);\n return json({\n success: true,\n timestamp: (/* @__PURE__ */ new Date()).toISOString()\n });\n } catch (error) {\n console.error(\"[terminals] Failed to kill terminal:\", toErrorMessage(error));\n return json({ error: \"Failed to kill terminal\" }, { status: 500 });\n }\n};\nexport {\n DELETE,\n GET\n};\n"],"names":[],"mappings":"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"}
|
|
@@ -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-BsHXoNks.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-DtT-ZXki.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_server.ts-DtT-ZXki.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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"}
|
|
@@ -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 = [
|
|
@@ -844,6 +901,15 @@ class TerminalStore {
|
|
|
844
901
|
"UPDATE terminals SET status = 'orphaned', exited_at = COALESCE(exited_at, ?) WHERE id = ?"
|
|
845
902
|
).run((/* @__PURE__ */ new Date()).toISOString(), id);
|
|
846
903
|
}
|
|
904
|
+
/**
|
|
905
|
+
* Phase 3: persist the current PTY dimensions so a server restart restores the
|
|
906
|
+
* latest size rather than the creation-time default (fixes G5). Dedicated
|
|
907
|
+
* prepared UPDATE — called on every authoritative resize, so it avoids the
|
|
908
|
+
* generic update()'s Object.entries() overhead on this hot path.
|
|
909
|
+
*/
|
|
910
|
+
resizeDims(id, cols, rows) {
|
|
911
|
+
this.db.prepare("UPDATE terminals SET cols = ?, rows = ? WHERE id = ?").run(cols, rows, id);
|
|
912
|
+
}
|
|
847
913
|
update(id, fields) {
|
|
848
914
|
const entries = Object.entries(fields).filter(
|
|
849
915
|
([key, val]) => key !== "id" && val !== void 0
|
|
@@ -865,6 +931,11 @@ globalThis[TS_GLOBAL_KEY] = terminalStore;
|
|
|
865
931
|
const MAX_SCROLLBACK_BYTES = 512 * 1024;
|
|
866
932
|
const MAX_OUTPUT_BUFFER_BYTES = 1024 * 1024;
|
|
867
933
|
const SCROLLBACK_CHUNK_SIZE = 50 * 1024;
|
|
934
|
+
const SEQ_RING_MAX_ENTRIES = 2e3;
|
|
935
|
+
const SNAPSHOT_ENABLED = process.env.SHOOTER_SNAPSHOT_FALLBACK !== "raw";
|
|
936
|
+
const RESNAPSHOT_LOW_WATER_BYTES = MAX_OUTPUT_BUFFER_BYTES / 4;
|
|
937
|
+
const RESNAPSHOT_POLL_MS = 100;
|
|
938
|
+
const RESNAPSHOT_MAX_WAIT_MS = 1e4;
|
|
868
939
|
const CLEANUP_INTERVAL_MS = 5 * 60 * 1e3;
|
|
869
940
|
const EXITED_TTL_MS = 60 * 60 * 1e3;
|
|
870
941
|
const MAX_EXITED_TERMINALS = 10;
|
|
@@ -875,6 +946,10 @@ const __filename$1 = fileURLToPath(import.meta.url);
|
|
|
875
946
|
const __dirname$1 = path__default.dirname(__filename$1);
|
|
876
947
|
class PtyManager {
|
|
877
948
|
cleanupTimer = null;
|
|
949
|
+
// Clients currently converging via a resnapshot (Phase 2). While pending, a
|
|
950
|
+
// client receives no normal output frames — the forthcoming snapshot brings
|
|
951
|
+
// it to the current screen. WeakSet so disconnected sockets drop out on GC.
|
|
952
|
+
resnapshotPending = /* @__PURE__ */ new WeakSet();
|
|
878
953
|
terminals = /* @__PURE__ */ new Map();
|
|
879
954
|
constructor() {
|
|
880
955
|
this.cleanupTimer = setInterval(() => {
|
|
@@ -885,19 +960,43 @@ class PtyManager {
|
|
|
885
960
|
// create — now async: forks a holder process, connects via HolderClient,
|
|
886
961
|
// persists to SQLite
|
|
887
962
|
// -----------------------------------------------------------------------
|
|
888
|
-
attach(id, ws) {
|
|
963
|
+
attach(id, ws, opts) {
|
|
889
964
|
const terminal = this.terminals.get(id);
|
|
890
965
|
if (!terminal) {
|
|
891
966
|
return false;
|
|
892
967
|
}
|
|
893
|
-
terminal.clients.add(ws);
|
|
894
968
|
terminal.outputBuffers.set(ws, { data: [], size: 0 });
|
|
969
|
+
this.safeSend(ws, JSON.stringify({ cols: terminal.cols, rows: terminal.rows, type: "resize" }));
|
|
970
|
+
const wantsSnapshot = opts?.snapshot === true;
|
|
971
|
+
const lastSeq = opts?.lastSeq ?? 0;
|
|
972
|
+
if (wantsSnapshot && lastSeq > 0) {
|
|
973
|
+
const gap = this.getSeqRingFrom(id, lastSeq);
|
|
974
|
+
if (gap !== null) {
|
|
975
|
+
for (const entry of gap) {
|
|
976
|
+
this.safeSend(ws, JSON.stringify({ data: entry.data, seq: entry.seq, type: "output" }));
|
|
977
|
+
}
|
|
978
|
+
terminal.clients.add(ws);
|
|
979
|
+
return true;
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
if (wantsSnapshot && terminal.emulator) {
|
|
983
|
+
void this.snapshotAndSend(terminal, ws).then((ok) => {
|
|
984
|
+
if (ws.readyState !== 1) {
|
|
985
|
+
return;
|
|
986
|
+
}
|
|
987
|
+
if (!ok) {
|
|
988
|
+
terminal.clients.add(ws);
|
|
989
|
+
void this.sendScrollback(terminal, ws);
|
|
990
|
+
return;
|
|
991
|
+
}
|
|
992
|
+
terminal.clients.add(ws);
|
|
993
|
+
});
|
|
994
|
+
return true;
|
|
995
|
+
}
|
|
996
|
+
terminal.clients.add(ws);
|
|
895
997
|
void this.sendScrollback(terminal, ws);
|
|
896
998
|
return true;
|
|
897
999
|
}
|
|
898
|
-
// -----------------------------------------------------------------------
|
|
899
|
-
// reconnectAll — recover persisted terminals on server startup
|
|
900
|
-
// -----------------------------------------------------------------------
|
|
901
1000
|
cleanup() {
|
|
902
1001
|
const now = Date.now();
|
|
903
1002
|
const exited = [];
|
|
@@ -928,7 +1027,7 @@ class PtyManager {
|
|
|
928
1027
|
}
|
|
929
1028
|
}
|
|
930
1029
|
// -----------------------------------------------------------------------
|
|
931
|
-
//
|
|
1030
|
+
// reconnectAll — recover persisted terminals on server startup
|
|
932
1031
|
// -----------------------------------------------------------------------
|
|
933
1032
|
async create(command, args, cwd, cols, rows) {
|
|
934
1033
|
const id = randomBytes(4).toString("hex");
|
|
@@ -975,12 +1074,15 @@ class PtyManager {
|
|
|
975
1074
|
const now = /* @__PURE__ */ new Date();
|
|
976
1075
|
const terminal = {
|
|
977
1076
|
args: launchArgs,
|
|
1077
|
+
authorityConnectionId: null,
|
|
1078
|
+
// Phase 3: claimed by the first interactive resize
|
|
978
1079
|
clients: /* @__PURE__ */ new Set(),
|
|
979
1080
|
cols,
|
|
980
1081
|
command,
|
|
981
1082
|
createdAt: now,
|
|
982
1083
|
currentCwd: null,
|
|
983
1084
|
cwd,
|
|
1085
|
+
emulator: SNAPSHOT_ENABLED ? new TerminalEmulator(cols, rows) : null,
|
|
984
1086
|
exitCode: connectResult.exitCode,
|
|
985
1087
|
exitedAt: null,
|
|
986
1088
|
holderPid,
|
|
@@ -994,6 +1096,8 @@ class PtyManager {
|
|
|
994
1096
|
pty: client,
|
|
995
1097
|
rows,
|
|
996
1098
|
scrollback: connectResult.scrollback,
|
|
1099
|
+
seqCounter: 0,
|
|
1100
|
+
seqRing: [],
|
|
997
1101
|
sessionFile: null,
|
|
998
1102
|
socketPath,
|
|
999
1103
|
status: connectResult.exited ? "exited" : "running",
|
|
@@ -1027,7 +1131,7 @@ class PtyManager {
|
|
|
1027
1131
|
return terminal;
|
|
1028
1132
|
}
|
|
1029
1133
|
// -----------------------------------------------------------------------
|
|
1030
|
-
//
|
|
1134
|
+
// disconnectAll — graceful shutdown: disconnect clients, keep holders alive
|
|
1031
1135
|
// -----------------------------------------------------------------------
|
|
1032
1136
|
destroy() {
|
|
1033
1137
|
if (this.cleanupTimer) {
|
|
@@ -1062,8 +1166,7 @@ class PtyManager {
|
|
|
1062
1166
|
}
|
|
1063
1167
|
}
|
|
1064
1168
|
// -----------------------------------------------------------------------
|
|
1065
|
-
//
|
|
1066
|
-
// createdAt descending
|
|
1169
|
+
// get
|
|
1067
1170
|
// -----------------------------------------------------------------------
|
|
1068
1171
|
detach(id, ws) {
|
|
1069
1172
|
const terminal = this.terminals.get(id);
|
|
@@ -1075,7 +1178,8 @@ class PtyManager {
|
|
|
1075
1178
|
return true;
|
|
1076
1179
|
}
|
|
1077
1180
|
// -----------------------------------------------------------------------
|
|
1078
|
-
//
|
|
1181
|
+
// list — running first, then recently exited, each group sorted by
|
|
1182
|
+
// createdAt descending
|
|
1079
1183
|
// -----------------------------------------------------------------------
|
|
1080
1184
|
disconnectAll() {
|
|
1081
1185
|
if (this.cleanupTimer) {
|
|
@@ -1100,13 +1204,13 @@ class PtyManager {
|
|
|
1100
1204
|
this.terminals.clear();
|
|
1101
1205
|
}
|
|
1102
1206
|
// -----------------------------------------------------------------------
|
|
1103
|
-
//
|
|
1207
|
+
// kill — route through holder: SIGTERM, then SIGKILL after 5 s
|
|
1104
1208
|
// -----------------------------------------------------------------------
|
|
1105
1209
|
get(id) {
|
|
1106
1210
|
return this.terminals.get(id) ?? null;
|
|
1107
1211
|
}
|
|
1108
1212
|
// -----------------------------------------------------------------------
|
|
1109
|
-
//
|
|
1213
|
+
// remove — remove an exited terminal from the map
|
|
1110
1214
|
// -----------------------------------------------------------------------
|
|
1111
1215
|
getScrollback(id) {
|
|
1112
1216
|
const terminal = this.terminals.get(id);
|
|
@@ -1116,8 +1220,40 @@ class PtyManager {
|
|
|
1116
1220
|
return terminal.scrollback;
|
|
1117
1221
|
}
|
|
1118
1222
|
// -----------------------------------------------------------------------
|
|
1119
|
-
//
|
|
1223
|
+
// resize
|
|
1120
1224
|
// -----------------------------------------------------------------------
|
|
1225
|
+
/** Current highest assigned seq for a terminal, or null if unknown. */
|
|
1226
|
+
getSeqCounter(id) {
|
|
1227
|
+
return this.terminals.get(id)?.seqCounter ?? null;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* Return the ring entries with seq > afterSeq, in order. Returns an empty
|
|
1231
|
+
* array when the caller is already current, or null when the gap is
|
|
1232
|
+
* unresolvable from the ring (caller must take a full snapshot). Unresolvable
|
|
1233
|
+
* means any of:
|
|
1234
|
+
* - afterSeq > seqCounter: the caller claims a seq we never produced — this
|
|
1235
|
+
* happens when the seq counter reset across a server restart (the client
|
|
1236
|
+
* is from a previous terminal lifetime), so its content is unrelated.
|
|
1237
|
+
* - ring empty but afterSeq > 0: nothing buffered to bridge the gap.
|
|
1238
|
+
* - afterSeq predates the oldest retained entry: the gap aged out.
|
|
1239
|
+
*/
|
|
1240
|
+
getSeqRingFrom(id, afterSeq) {
|
|
1241
|
+
const terminal = this.terminals.get(id);
|
|
1242
|
+
if (!terminal) {
|
|
1243
|
+
return null;
|
|
1244
|
+
}
|
|
1245
|
+
if (afterSeq > terminal.seqCounter) {
|
|
1246
|
+
return null;
|
|
1247
|
+
}
|
|
1248
|
+
const ring = terminal.seqRing;
|
|
1249
|
+
if (ring.length === 0) {
|
|
1250
|
+
return afterSeq <= 0 ? [] : null;
|
|
1251
|
+
}
|
|
1252
|
+
if (afterSeq < ring[0].seq - 1) {
|
|
1253
|
+
return null;
|
|
1254
|
+
}
|
|
1255
|
+
return ring.filter((e) => e.seq > afterSeq);
|
|
1256
|
+
}
|
|
1121
1257
|
kill(id) {
|
|
1122
1258
|
const terminal = this.terminals.get(id);
|
|
1123
1259
|
if (!terminal) {
|
|
@@ -1150,7 +1286,7 @@ class PtyManager {
|
|
|
1150
1286
|
return true;
|
|
1151
1287
|
}
|
|
1152
1288
|
// -----------------------------------------------------------------------
|
|
1153
|
-
//
|
|
1289
|
+
// attach — register a WebSocket client and replay scrollback
|
|
1154
1290
|
// -----------------------------------------------------------------------
|
|
1155
1291
|
list() {
|
|
1156
1292
|
const all = Array.from(this.terminals.values());
|
|
@@ -1163,7 +1299,7 @@ class PtyManager {
|
|
|
1163
1299
|
return [...running, ...exited];
|
|
1164
1300
|
}
|
|
1165
1301
|
// -----------------------------------------------------------------------
|
|
1166
|
-
//
|
|
1302
|
+
// detach — remove a WebSocket client
|
|
1167
1303
|
// -----------------------------------------------------------------------
|
|
1168
1304
|
async reconnectAll() {
|
|
1169
1305
|
const running = terminalStore.listRunning();
|
|
@@ -1183,8 +1319,7 @@ class PtyManager {
|
|
|
1183
1319
|
}
|
|
1184
1320
|
}
|
|
1185
1321
|
// -----------------------------------------------------------------------
|
|
1186
|
-
//
|
|
1187
|
-
// also clean up old SQLite records
|
|
1322
|
+
// getScrollback — return raw scrollback data for replay
|
|
1188
1323
|
// -----------------------------------------------------------------------
|
|
1189
1324
|
remove(id) {
|
|
1190
1325
|
const terminal = this.terminals.get(id);
|
|
@@ -1198,7 +1333,8 @@ class PtyManager {
|
|
|
1198
1333
|
return true;
|
|
1199
1334
|
}
|
|
1200
1335
|
// -----------------------------------------------------------------------
|
|
1201
|
-
//
|
|
1336
|
+
// cleanup — evict exited terminals older than 1 hour, cap at 10 exited;
|
|
1337
|
+
// also clean up old SQLite records
|
|
1202
1338
|
// -----------------------------------------------------------------------
|
|
1203
1339
|
resize(id, cols, rows) {
|
|
1204
1340
|
const terminal = this.terminals.get(id);
|
|
@@ -1209,6 +1345,8 @@ class PtyManager {
|
|
|
1209
1345
|
terminal.pty.resize(cols, rows);
|
|
1210
1346
|
terminal.cols = cols;
|
|
1211
1347
|
terminal.rows = rows;
|
|
1348
|
+
terminal.emulator?.resize(cols, rows);
|
|
1349
|
+
terminalStore.resizeDims(id, cols, rows);
|
|
1212
1350
|
const msg = JSON.stringify({ cols, rows, type: "resize" });
|
|
1213
1351
|
for (const ws of terminal.clients) {
|
|
1214
1352
|
this.safeSend(ws, msg);
|
|
@@ -1219,6 +1357,39 @@ class PtyManager {
|
|
|
1219
1357
|
}
|
|
1220
1358
|
}
|
|
1221
1359
|
// -----------------------------------------------------------------------
|
|
1360
|
+
// destroy — emergency forced kill (kills holder processes too)
|
|
1361
|
+
// -----------------------------------------------------------------------
|
|
1362
|
+
/**
|
|
1363
|
+
* Compute the current-screen snapshot from the emulator and send it as a
|
|
1364
|
+
* single {type:'snapshot'} frame stamped with the current seq. Returns false
|
|
1365
|
+
* if there is no emulator, the socket closed, or serialization failed.
|
|
1366
|
+
* Reused by Phase 2 to resnapshot a client after a backpressure gap.
|
|
1367
|
+
*/
|
|
1368
|
+
async snapshotAndSend(terminal, ws) {
|
|
1369
|
+
if (!terminal.emulator) {
|
|
1370
|
+
return false;
|
|
1371
|
+
}
|
|
1372
|
+
try {
|
|
1373
|
+
const snap = await terminal.emulator.snapshot();
|
|
1374
|
+
if (ws.readyState !== 1) {
|
|
1375
|
+
return false;
|
|
1376
|
+
}
|
|
1377
|
+
this.safeSend(
|
|
1378
|
+
ws,
|
|
1379
|
+
JSON.stringify({
|
|
1380
|
+
cols: snap.cols,
|
|
1381
|
+
data: snap.data,
|
|
1382
|
+
rows: snap.rows,
|
|
1383
|
+
seq: terminal.seqCounter,
|
|
1384
|
+
type: "snapshot"
|
|
1385
|
+
})
|
|
1386
|
+
);
|
|
1387
|
+
return true;
|
|
1388
|
+
} catch {
|
|
1389
|
+
return false;
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
// -----------------------------------------------------------------------
|
|
1222
1393
|
// Private: reconnectOne — reconnect to a single persisted terminal
|
|
1223
1394
|
// -----------------------------------------------------------------------
|
|
1224
1395
|
appendScrollback(terminal, data) {
|
|
@@ -1233,21 +1404,103 @@ class PtyManager {
|
|
|
1233
1404
|
}
|
|
1234
1405
|
}
|
|
1235
1406
|
}
|
|
1407
|
+
/**
|
|
1408
|
+
* Assign the next sequence number to an output chunk and append it to the
|
|
1409
|
+
* bounded replay ring. Returns the new seq. Phase 2 uses the ring to replay
|
|
1410
|
+
* the gap to a reconnecting client without a full snapshot.
|
|
1411
|
+
*/
|
|
1412
|
+
appendSeqRing(terminal, data) {
|
|
1413
|
+
const seq = terminal.seqCounter + 1;
|
|
1414
|
+
terminal.seqRing.push({ data, seq });
|
|
1415
|
+
if (terminal.seqRing.length > SEQ_RING_MAX_ENTRIES) {
|
|
1416
|
+
terminal.seqRing.shift();
|
|
1417
|
+
}
|
|
1418
|
+
terminal.seqCounter = seq;
|
|
1419
|
+
return seq;
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Mark a client for convergence (Phase 2). Its queued output is discarded and
|
|
1423
|
+
* further live frames are withheld until its socket drains below the low-water
|
|
1424
|
+
* mark (or a hard timeout elapses), at which point a fresh snapshot resets it
|
|
1425
|
+
* to the current screen. This replaces silent byte-dropping so a slow or
|
|
1426
|
+
* throttled client can never diverge permanently (G1).
|
|
1427
|
+
*/
|
|
1428
|
+
beginResnapshot(terminal, ws) {
|
|
1429
|
+
if (this.resnapshotPending.has(ws)) {
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
this.resnapshotPending.add(ws);
|
|
1433
|
+
const buffer = terminal.outputBuffers.get(ws);
|
|
1434
|
+
if (buffer) {
|
|
1435
|
+
buffer.data.length = 0;
|
|
1436
|
+
buffer.size = 0;
|
|
1437
|
+
}
|
|
1438
|
+
this.safeSend(ws, JSON.stringify({ bytes: 0, type: "output-dropped" }));
|
|
1439
|
+
const startedAt = Date.now();
|
|
1440
|
+
const poll = () => {
|
|
1441
|
+
if (!this.resnapshotPending.has(ws)) {
|
|
1442
|
+
return;
|
|
1443
|
+
}
|
|
1444
|
+
if (ws.readyState !== 1 || !terminal.emulator || !terminal.clients.has(ws)) {
|
|
1445
|
+
this.resnapshotPending.delete(ws);
|
|
1446
|
+
return;
|
|
1447
|
+
}
|
|
1448
|
+
const drained = ws.bufferedAmount <= RESNAPSHOT_LOW_WATER_BYTES;
|
|
1449
|
+
const timedOut = Date.now() - startedAt > RESNAPSHOT_MAX_WAIT_MS;
|
|
1450
|
+
if (drained || timedOut) {
|
|
1451
|
+
void this.snapshotAndSend(terminal, ws).finally(() => {
|
|
1452
|
+
this.resnapshotPending.delete(ws);
|
|
1453
|
+
});
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
setTimeout(poll, RESNAPSHOT_POLL_MS);
|
|
1457
|
+
};
|
|
1458
|
+
setTimeout(poll, RESNAPSHOT_POLL_MS);
|
|
1459
|
+
}
|
|
1236
1460
|
// -----------------------------------------------------------------------
|
|
1237
1461
|
// Private: handleReconnectFailure — handle failed reconnection
|
|
1238
1462
|
// -----------------------------------------------------------------------
|
|
1239
1463
|
broadcastOutput(terminal, data) {
|
|
1240
|
-
const
|
|
1464
|
+
const seq = this.appendSeqRing(terminal, data);
|
|
1465
|
+
const msg = JSON.stringify({ data, seq, type: "output" });
|
|
1466
|
+
if (!terminal.emulator) {
|
|
1467
|
+
this.broadcastOutputLegacy(terminal, msg);
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
const msgSize = Buffer.byteLength(msg, "utf8");
|
|
1471
|
+
for (const ws of terminal.clients) {
|
|
1472
|
+
if (this.resnapshotPending.has(ws)) {
|
|
1473
|
+
continue;
|
|
1474
|
+
}
|
|
1475
|
+
const buffer = terminal.outputBuffers.get(ws);
|
|
1476
|
+
if (!buffer) {
|
|
1477
|
+
continue;
|
|
1478
|
+
}
|
|
1479
|
+
if (ws.bufferedAmount > MAX_OUTPUT_BUFFER_BYTES || buffer.size + msgSize > MAX_OUTPUT_BUFFER_BYTES) {
|
|
1480
|
+
this.beginResnapshot(terminal, ws);
|
|
1481
|
+
continue;
|
|
1482
|
+
}
|
|
1483
|
+
buffer.data.push(msg);
|
|
1484
|
+
buffer.size += msgSize;
|
|
1485
|
+
this.flushOutputBuffer(ws, buffer);
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Legacy broadcast path used only when the emulator is disabled
|
|
1490
|
+
* (SHOOTER_SNAPSHOT_FALLBACK=raw). Drops the oldest buffered output to make
|
|
1491
|
+
* room and notifies the client; there is no snapshot to converge it to.
|
|
1492
|
+
*/
|
|
1493
|
+
broadcastOutputLegacy(terminal, msg) {
|
|
1494
|
+
const msgSize = Buffer.byteLength(msg, "utf8");
|
|
1241
1495
|
for (const ws of terminal.clients) {
|
|
1242
1496
|
if (ws.bufferedAmount > MAX_OUTPUT_BUFFER_BYTES) {
|
|
1243
|
-
this.safeSend(ws, JSON.stringify({ bytes:
|
|
1497
|
+
this.safeSend(ws, JSON.stringify({ bytes: msgSize, type: "output-dropped" }));
|
|
1244
1498
|
continue;
|
|
1245
1499
|
}
|
|
1246
1500
|
const buffer = terminal.outputBuffers.get(ws);
|
|
1247
1501
|
if (!buffer) {
|
|
1248
1502
|
continue;
|
|
1249
1503
|
}
|
|
1250
|
-
const msgSize = Buffer.byteLength(msg, "utf8");
|
|
1251
1504
|
if (buffer.size + msgSize > MAX_OUTPUT_BUFFER_BYTES) {
|
|
1252
1505
|
let droppedBytes = 0;
|
|
1253
1506
|
while (buffer.size + msgSize > MAX_OUTPUT_BUFFER_BYTES && buffer.data.length > 0) {
|
|
@@ -1259,11 +1512,7 @@ class PtyManager {
|
|
|
1259
1512
|
}
|
|
1260
1513
|
}
|
|
1261
1514
|
if (droppedBytes > 0) {
|
|
1262
|
-
|
|
1263
|
-
bytes: droppedBytes,
|
|
1264
|
-
type: "output-dropped"
|
|
1265
|
-
});
|
|
1266
|
-
this.safeSend(ws, dropMsg);
|
|
1515
|
+
this.safeSend(ws, JSON.stringify({ bytes: droppedBytes, type: "output-dropped" }));
|
|
1267
1516
|
}
|
|
1268
1517
|
}
|
|
1269
1518
|
buffer.data.push(msg);
|
|
@@ -1296,6 +1545,8 @@ class PtyManager {
|
|
|
1296
1545
|
openCodeWatcher.stop(terminal.openCodeSessionId, terminal.openCodeNoopCb);
|
|
1297
1546
|
terminal.openCodeNoopCb = null;
|
|
1298
1547
|
}
|
|
1548
|
+
terminal.emulator?.dispose();
|
|
1549
|
+
terminal.emulator = null;
|
|
1299
1550
|
terminal.pty.disconnect();
|
|
1300
1551
|
for (const ws of terminal.clients) {
|
|
1301
1552
|
try {
|
|
@@ -1381,12 +1632,15 @@ class PtyManager {
|
|
|
1381
1632
|
}
|
|
1382
1633
|
const terminal = {
|
|
1383
1634
|
args: parsedArgs,
|
|
1635
|
+
authorityConnectionId: null,
|
|
1636
|
+
// Phase 3: claimed by the first interactive resize
|
|
1384
1637
|
clients: /* @__PURE__ */ new Set(),
|
|
1385
1638
|
cols: record.cols,
|
|
1386
1639
|
command: record.command,
|
|
1387
1640
|
createdAt: new Date(record.createdAt),
|
|
1388
1641
|
currentCwd: null,
|
|
1389
1642
|
cwd: record.cwd,
|
|
1643
|
+
emulator: SNAPSHOT_ENABLED ? new TerminalEmulator(record.cols, record.rows) : null,
|
|
1390
1644
|
exitCode: connectResult.exitCode,
|
|
1391
1645
|
exitedAt: record.exitedAt ? new Date(record.exitedAt) : null,
|
|
1392
1646
|
holderPid: record.holderPid ?? 0,
|
|
@@ -1400,11 +1654,16 @@ class PtyManager {
|
|
|
1400
1654
|
pty: client,
|
|
1401
1655
|
rows: record.rows,
|
|
1402
1656
|
scrollback: connectResult.scrollback,
|
|
1657
|
+
seqCounter: 0,
|
|
1658
|
+
seqRing: [],
|
|
1403
1659
|
sessionFile: record.sessionFile ?? null,
|
|
1404
1660
|
socketPath: record.socketPath,
|
|
1405
1661
|
status: connectResult.exited ? "exited" : "running",
|
|
1406
1662
|
watcherOffset: 0
|
|
1407
1663
|
};
|
|
1664
|
+
if (terminal.emulator && connectResult.scrollback.length > 0) {
|
|
1665
|
+
terminal.emulator.write(connectResult.scrollback);
|
|
1666
|
+
}
|
|
1408
1667
|
if (connectResult.exited) {
|
|
1409
1668
|
terminal.exitedAt = terminal.exitedAt ?? /* @__PURE__ */ new Date();
|
|
1410
1669
|
terminalStore.markExited(record.id, connectResult.exitCode);
|
|
@@ -1667,6 +1926,7 @@ class PtyManager {
|
|
|
1667
1926
|
}
|
|
1668
1927
|
});
|
|
1669
1928
|
client.onOutput((data) => {
|
|
1929
|
+
terminal.emulator?.write(data);
|
|
1670
1930
|
this.appendScrollback(terminal, data);
|
|
1671
1931
|
this.broadcastOutput(terminal, data);
|
|
1672
1932
|
});
|
|
@@ -1711,4 +1971,4 @@ const ptyManager = globalThis[PTY_GLOBAL_KEY] || new PtyManager();
|
|
|
1711
1971
|
globalThis[PTY_GLOBAL_KEY] = ptyManager;
|
|
1712
1972
|
|
|
1713
1973
|
export { ptyManager as p };
|
|
1714
|
-
//# sourceMappingURL=pty-manager-
|
|
1974
|
+
//# sourceMappingURL=pty-manager-BsHXoNks.js.map
|