@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.
Files changed (139) hide show
  1. package/build/client/_app/immutable/chunks/CbINytmr.js +3 -0
  2. package/build/client/_app/immutable/chunks/CbINytmr.js.br +0 -0
  3. package/build/client/_app/immutable/chunks/CbINytmr.js.gz +0 -0
  4. package/build/client/_app/immutable/chunks/D868VwmX.js +6 -0
  5. package/build/client/_app/immutable/chunks/D868VwmX.js.br +0 -0
  6. package/build/client/_app/immutable/chunks/D868VwmX.js.gz +0 -0
  7. package/build/client/_app/immutable/chunks/{CZg4kn4E.js → Dd1KNHg-.js} +1 -1
  8. package/build/client/_app/immutable/chunks/Dd1KNHg-.js.br +0 -0
  9. package/build/client/_app/immutable/chunks/Dd1KNHg-.js.gz +0 -0
  10. package/build/client/_app/immutable/chunks/{DhK7PwI_.js → V8pbM9cl.js} +1 -1
  11. package/build/client/_app/immutable/chunks/V8pbM9cl.js.br +0 -0
  12. package/build/client/_app/immutable/chunks/V8pbM9cl.js.gz +0 -0
  13. package/build/client/_app/immutable/entry/{app.CTqz33nP.js → app.CiQHPW0j.js} +2 -2
  14. package/build/client/_app/immutable/entry/app.CiQHPW0j.js.br +0 -0
  15. package/build/client/_app/immutable/entry/app.CiQHPW0j.js.gz +0 -0
  16. package/build/client/_app/immutable/entry/start.DUCXuMLl.js +1 -0
  17. package/build/client/_app/immutable/entry/start.DUCXuMLl.js.br +2 -0
  18. package/build/client/_app/immutable/entry/start.DUCXuMLl.js.gz +0 -0
  19. package/build/client/_app/immutable/nodes/{0.Qn7Ktiht.js → 0.BRFdS_ay.js} +1 -1
  20. package/build/client/_app/immutable/nodes/0.BRFdS_ay.js.br +0 -0
  21. package/build/client/_app/immutable/nodes/0.BRFdS_ay.js.gz +0 -0
  22. package/build/client/_app/immutable/nodes/{1.BxWOfNlo.js → 1.B1pgwYu3.js} +1 -1
  23. package/build/client/_app/immutable/nodes/1.B1pgwYu3.js.br +0 -0
  24. package/build/client/_app/immutable/nodes/1.B1pgwYu3.js.gz +0 -0
  25. package/build/client/_app/immutable/nodes/{10.BGPYD1s1.js → 10.558mUFIl.js} +1 -1
  26. package/build/client/_app/immutable/nodes/10.558mUFIl.js.br +0 -0
  27. package/build/client/_app/immutable/nodes/10.558mUFIl.js.gz +0 -0
  28. package/build/client/_app/immutable/nodes/{11.BxY1PUjC.js → 11.CdmPyt4k.js} +2 -2
  29. package/build/client/_app/immutable/nodes/11.CdmPyt4k.js.br +0 -0
  30. package/build/client/_app/immutable/nodes/{11.BxY1PUjC.js.gz → 11.CdmPyt4k.js.gz} +0 -0
  31. package/build/client/_app/immutable/nodes/{2.Bc2qALkX.js → 2.1tiK5o4L.js} +1 -1
  32. package/build/client/_app/immutable/nodes/2.1tiK5o4L.js.br +0 -0
  33. package/build/client/_app/immutable/nodes/2.1tiK5o4L.js.gz +0 -0
  34. package/build/client/_app/immutable/nodes/{3.N2-A8noI.js → 3.DyQTorXE.js} +1 -1
  35. package/build/client/_app/immutable/nodes/3.DyQTorXE.js.br +0 -0
  36. package/build/client/_app/immutable/nodes/3.DyQTorXE.js.gz +0 -0
  37. package/build/client/_app/immutable/nodes/{6.BWF9Qx6F.js → 6.Chn2ZM2V.js} +1 -1
  38. package/build/client/_app/immutable/nodes/6.Chn2ZM2V.js.br +0 -0
  39. package/build/client/_app/immutable/nodes/6.Chn2ZM2V.js.gz +0 -0
  40. package/build/client/_app/immutable/nodes/{7.DHuDIdpz.js → 7.DhJ2K3GQ.js} +1 -1
  41. package/build/client/_app/immutable/nodes/7.DhJ2K3GQ.js.br +0 -0
  42. package/build/client/_app/immutable/nodes/7.DhJ2K3GQ.js.gz +0 -0
  43. package/build/client/_app/immutable/nodes/{8.D0Ijt9Vv.js → 8.B4pLxBkI.js} +1 -1
  44. package/build/client/_app/immutable/nodes/8.B4pLxBkI.js.br +0 -0
  45. package/build/client/_app/immutable/nodes/8.B4pLxBkI.js.gz +0 -0
  46. package/build/client/_app/immutable/nodes/{9.2Piwo35J.js → 9.CVsskPw5.js} +1 -1
  47. package/build/client/_app/immutable/nodes/9.CVsskPw5.js.br +0 -0
  48. package/build/client/_app/immutable/nodes/9.CVsskPw5.js.gz +0 -0
  49. package/build/client/_app/version.json +1 -1
  50. package/build/client/_app/version.json.br +0 -0
  51. package/build/client/_app/version.json.gz +0 -0
  52. package/build/server/chunks/{0-CVGsyVKN.js → 0-DZO0pCuJ.js} +2 -2
  53. package/build/server/chunks/{0-CVGsyVKN.js.map → 0-DZO0pCuJ.js.map} +1 -1
  54. package/build/server/chunks/{1-BAlAsKdp.js → 1-D2SDQFeq.js} +2 -2
  55. package/build/server/chunks/{1-BAlAsKdp.js.map → 1-D2SDQFeq.js.map} +1 -1
  56. package/build/server/chunks/{10-BUCX7Aqz.js → 10-CEJDEhpQ.js} +2 -2
  57. package/build/server/chunks/{10-BUCX7Aqz.js.map → 10-CEJDEhpQ.js.map} +1 -1
  58. package/build/server/chunks/{11-DHPvc2yA.js → 11-CMC_i3co.js} +2 -2
  59. package/build/server/chunks/{11-DHPvc2yA.js.map → 11-CMC_i3co.js.map} +1 -1
  60. package/build/server/chunks/{2-DLOMdCHW.js → 2-C1XSBNj7.js} +2 -2
  61. package/build/server/chunks/{2-DLOMdCHW.js.map → 2-C1XSBNj7.js.map} +1 -1
  62. package/build/server/chunks/{3-DCf69LYo.js → 3-DRjTDzaV.js} +2 -2
  63. package/build/server/chunks/{3-DCf69LYo.js.map → 3-DRjTDzaV.js.map} +1 -1
  64. package/build/server/chunks/{6-DUrC2Naz.js → 6-BcgshtK4.js} +2 -2
  65. package/build/server/chunks/{6-DUrC2Naz.js.map → 6-BcgshtK4.js.map} +1 -1
  66. package/build/server/chunks/{7-TXwjMHt2.js → 7-BBsuxiGz.js} +2 -2
  67. package/build/server/chunks/{7-TXwjMHt2.js.map → 7-BBsuxiGz.js.map} +1 -1
  68. package/build/server/chunks/{8-D2X_jBsT.js → 8-B0qM-Zzs.js} +2 -2
  69. package/build/server/chunks/{8-D2X_jBsT.js.map → 8-B0qM-Zzs.js.map} +1 -1
  70. package/build/server/chunks/{9-DK0hH5Xa.js → 9-XIfsp2D_.js} +2 -2
  71. package/build/server/chunks/{9-DK0hH5Xa.js.map → 9-XIfsp2D_.js.map} +1 -1
  72. package/build/server/chunks/{_server.ts-DiBMY7Ho.js → _server.ts-B-Gekwsu.js} +3 -2
  73. package/build/server/chunks/_server.ts-B-Gekwsu.js.map +1 -0
  74. package/build/server/chunks/{_server.ts-C0PO_cAu.js → _server.ts-BhP3b8A5.js} +3 -2
  75. package/build/server/chunks/{_server.ts-C0PO_cAu.js.map → _server.ts-BhP3b8A5.js.map} +1 -1
  76. package/build/server/chunks/{_server.ts-Bol54_Qo.js → _server.ts-CTdFxJdD.js} +3 -2
  77. package/build/server/chunks/_server.ts-CTdFxJdD.js.map +1 -0
  78. package/build/server/chunks/{_server.ts-B54Pvhgc.js → _server.ts-Cx0S__hk.js} +3 -2
  79. package/build/server/chunks/_server.ts-Cx0S__hk.js.map +1 -0
  80. package/build/server/chunks/{_server.ts-CZb-BI5H.js → _server.ts-DtT-ZXki.js} +3 -2
  81. package/build/server/chunks/_server.ts-DtT-ZXki.js.map +1 -0
  82. package/build/server/chunks/{pty-manager-CoWVT56F.js → pty-manager-BsHXoNks.js} +287 -27
  83. package/build/server/chunks/pty-manager-BsHXoNks.js.map +1 -0
  84. package/build/server/index.js +1 -1
  85. package/build/server/index.js.map +1 -1
  86. package/build/server/manifest.js +16 -16
  87. package/build/server/manifest.js.map +1 -1
  88. package/package.json +4 -2
  89. package/server.ts +5 -2
  90. package/src/lib/modules/client/terminal/xterm-wrapper.ts +56 -15
  91. package/src/lib/modules/server/terminal/pty-manager.ts +291 -35
  92. package/src/lib/modules/server/terminal/terminal-emulator.ts +102 -0
  93. package/src/lib/modules/server/terminal/terminal-store.ts +10 -0
  94. package/src/lib/modules/server/ws/server.ts +18 -2
  95. package/src/lib/modules/server/ws/terminal-handler.ts +60 -14
  96. package/src/lib/types/generated/WsProtocol.ts +10 -1
  97. package/src/lib/types/server.ts +34 -1
  98. package/src/lib/types/terminal-client.ts +3 -0
  99. package/src/lib/types/ws.ts +7 -2
  100. package/src/routes/api/terminals/[id]/resize/+server.ts +3 -0
  101. package/build/client/_app/immutable/chunks/BfbPKMXz.js +0 -3
  102. package/build/client/_app/immutable/chunks/BfbPKMXz.js.br +0 -0
  103. package/build/client/_app/immutable/chunks/BfbPKMXz.js.gz +0 -0
  104. package/build/client/_app/immutable/chunks/CZg4kn4E.js.br +0 -0
  105. package/build/client/_app/immutable/chunks/CZg4kn4E.js.gz +0 -0
  106. package/build/client/_app/immutable/chunks/DhK7PwI_.js.br +0 -0
  107. package/build/client/_app/immutable/chunks/DhK7PwI_.js.gz +0 -0
  108. package/build/client/_app/immutable/chunks/J5-Cr5oR.js +0 -6
  109. package/build/client/_app/immutable/chunks/J5-Cr5oR.js.br +0 -0
  110. package/build/client/_app/immutable/chunks/J5-Cr5oR.js.gz +0 -0
  111. package/build/client/_app/immutable/entry/app.CTqz33nP.js.br +0 -0
  112. package/build/client/_app/immutable/entry/app.CTqz33nP.js.gz +0 -0
  113. package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js +0 -1
  114. package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js.br +0 -2
  115. package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js.gz +0 -0
  116. package/build/client/_app/immutable/nodes/0.Qn7Ktiht.js.br +0 -0
  117. package/build/client/_app/immutable/nodes/0.Qn7Ktiht.js.gz +0 -0
  118. package/build/client/_app/immutable/nodes/1.BxWOfNlo.js.br +0 -0
  119. package/build/client/_app/immutable/nodes/1.BxWOfNlo.js.gz +0 -0
  120. package/build/client/_app/immutable/nodes/10.BGPYD1s1.js.br +0 -0
  121. package/build/client/_app/immutable/nodes/10.BGPYD1s1.js.gz +0 -0
  122. package/build/client/_app/immutable/nodes/11.BxY1PUjC.js.br +0 -0
  123. package/build/client/_app/immutable/nodes/2.Bc2qALkX.js.br +0 -0
  124. package/build/client/_app/immutable/nodes/2.Bc2qALkX.js.gz +0 -0
  125. package/build/client/_app/immutable/nodes/3.N2-A8noI.js.br +0 -0
  126. package/build/client/_app/immutable/nodes/3.N2-A8noI.js.gz +0 -0
  127. package/build/client/_app/immutable/nodes/6.BWF9Qx6F.js.br +0 -0
  128. package/build/client/_app/immutable/nodes/6.BWF9Qx6F.js.gz +0 -0
  129. package/build/client/_app/immutable/nodes/7.DHuDIdpz.js.br +0 -0
  130. package/build/client/_app/immutable/nodes/7.DHuDIdpz.js.gz +0 -0
  131. package/build/client/_app/immutable/nodes/8.D0Ijt9Vv.js.br +0 -0
  132. package/build/client/_app/immutable/nodes/8.D0Ijt9Vv.js.gz +0 -0
  133. package/build/client/_app/immutable/nodes/9.2Piwo35J.js.br +0 -0
  134. package/build/client/_app/immutable/nodes/9.2Piwo35J.js.gz +0 -0
  135. package/build/server/chunks/_server.ts-B54Pvhgc.js.map +0 -1
  136. package/build/server/chunks/_server.ts-Bol54_Qo.js.map +0 -1
  137. package/build/server/chunks/_server.ts-CZb-BI5H.js.map +0 -1
  138. package/build/server/chunks/_server.ts-DiBMY7Ho.js.map +0 -1
  139. 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-CoWVT56F.js';
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-B54Pvhgc.js.map
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-CoWVT56F.js';
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-CZb-BI5H.js.map
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
- // disconnectAllgraceful shutdown: disconnect clients, keep holders alive
1030
+ // reconnectAllrecover 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
- // get
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
- // list — running first, then recently exited, each group sorted by
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
- // killroute through holder: SIGTERM, then SIGKILL after 5 s
1181
+ // listrunning 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
- // removeremove an exited terminal from the map
1207
+ // killroute 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
- // resize
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
- // attach — register a WebSocket client and replay scrollback
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
- // detachremove a WebSocket client
1289
+ // attachregister 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
- // getScrollbackreturn raw scrollback data for replay
1302
+ // detachremove 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
- // cleanupevict exited terminals older than 1 hour, cap at 10 exited;
1187
- // also clean up old SQLite records
1322
+ // getScrollbackreturn 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
- // destroyemergency forced kill (kills holder processes too)
1336
+ // cleanupevict 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 msg = JSON.stringify({ data, type: "output" });
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: data.length, type: "output-dropped" }));
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
- const dropMsg = JSON.stringify({
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-CoWVT56F.js.map
1974
+ //# sourceMappingURL=pty-manager-BsHXoNks.js.map