@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.
Files changed (136) hide show
  1. package/build/client/_app/immutable/chunks/Bj5wFimK.js +3 -0
  2. package/build/client/_app/immutable/chunks/Bj5wFimK.js.br +0 -0
  3. package/build/client/_app/immutable/chunks/Bj5wFimK.js.gz +0 -0
  4. package/build/client/_app/immutable/chunks/{DhK7PwI_.js → BjYr_-Ss.js} +1 -1
  5. package/build/client/_app/immutable/chunks/BjYr_-Ss.js.br +0 -0
  6. package/build/client/_app/immutable/chunks/{DhK7PwI_.js.gz → BjYr_-Ss.js.gz} +0 -0
  7. package/build/client/_app/immutable/chunks/DULfdsh6.js +6 -0
  8. package/build/client/_app/immutable/chunks/DULfdsh6.js.br +0 -0
  9. package/build/client/_app/immutable/chunks/DULfdsh6.js.gz +0 -0
  10. package/build/client/_app/immutable/chunks/{CZg4kn4E.js → fcNfTA-E.js} +1 -1
  11. package/build/client/_app/immutable/chunks/fcNfTA-E.js.br +0 -0
  12. package/build/client/_app/immutable/chunks/fcNfTA-E.js.gz +0 -0
  13. package/build/client/_app/immutable/entry/{app.CTqz33nP.js → app.Bvoqymnp.js} +2 -2
  14. package/build/client/_app/immutable/entry/app.Bvoqymnp.js.br +0 -0
  15. package/build/client/_app/immutable/entry/app.Bvoqymnp.js.gz +0 -0
  16. package/build/client/_app/immutable/entry/start.BqXCPPZJ.js +1 -0
  17. package/build/client/_app/immutable/entry/start.BqXCPPZJ.js.br +2 -0
  18. package/build/client/_app/immutable/entry/start.BqXCPPZJ.js.gz +0 -0
  19. package/build/client/_app/immutable/nodes/{0.Qn7Ktiht.js → 0.Bv_TwEnq.js} +1 -1
  20. package/build/client/_app/immutable/nodes/0.Bv_TwEnq.js.br +0 -0
  21. package/build/client/_app/immutable/nodes/0.Bv_TwEnq.js.gz +0 -0
  22. package/build/client/_app/immutable/nodes/{1.BxWOfNlo.js → 1.7lffTIeb.js} +1 -1
  23. package/build/client/_app/immutable/nodes/1.7lffTIeb.js.br +0 -0
  24. package/build/client/_app/immutable/nodes/1.7lffTIeb.js.gz +0 -0
  25. package/build/client/_app/immutable/nodes/{10.BGPYD1s1.js → 10.ChiIrIDl.js} +1 -1
  26. package/build/client/_app/immutable/nodes/10.ChiIrIDl.js.br +0 -0
  27. package/build/client/_app/immutable/nodes/10.ChiIrIDl.js.gz +0 -0
  28. package/build/client/_app/immutable/nodes/{11.BxY1PUjC.js → 11.DO3vyXEv.js} +2 -2
  29. package/build/client/_app/immutable/nodes/11.DO3vyXEv.js.br +0 -0
  30. package/build/client/_app/immutable/nodes/{11.BxY1PUjC.js.gz → 11.DO3vyXEv.js.gz} +0 -0
  31. package/build/client/_app/immutable/nodes/{2.Bc2qALkX.js → 2.iMIqsE7n.js} +1 -1
  32. package/build/client/_app/immutable/nodes/2.iMIqsE7n.js.br +0 -0
  33. package/build/client/_app/immutable/nodes/2.iMIqsE7n.js.gz +0 -0
  34. package/build/client/_app/immutable/nodes/{3.N2-A8noI.js → 3.CArnSHOO.js} +1 -1
  35. package/build/client/_app/immutable/nodes/3.CArnSHOO.js.br +0 -0
  36. package/build/client/_app/immutable/nodes/3.CArnSHOO.js.gz +0 -0
  37. package/build/client/_app/immutable/nodes/{6.BWF9Qx6F.js → 6.B8l1RwkB.js} +1 -1
  38. package/build/client/_app/immutable/nodes/6.B8l1RwkB.js.br +0 -0
  39. package/build/client/_app/immutable/nodes/6.B8l1RwkB.js.gz +0 -0
  40. package/build/client/_app/immutable/nodes/{7.DHuDIdpz.js → 7.BPyfhDis.js} +1 -1
  41. package/build/client/_app/immutable/nodes/7.BPyfhDis.js.br +0 -0
  42. package/build/client/_app/immutable/nodes/7.BPyfhDis.js.gz +0 -0
  43. package/build/client/_app/immutable/nodes/{8.D0Ijt9Vv.js → 8.D_vszZ9E.js} +1 -1
  44. package/build/client/_app/immutable/nodes/8.D_vszZ9E.js.br +0 -0
  45. package/build/client/_app/immutable/nodes/8.D_vszZ9E.js.gz +0 -0
  46. package/build/client/_app/immutable/nodes/{9.2Piwo35J.js → 9.Drah-do-.js} +1 -1
  47. package/build/client/_app/immutable/nodes/9.Drah-do-.js.br +0 -0
  48. package/build/client/_app/immutable/nodes/9.Drah-do-.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-DAB_6Vm1.js} +2 -2
  53. package/build/server/chunks/{0-CVGsyVKN.js.map → 0-DAB_6Vm1.js.map} +1 -1
  54. package/build/server/chunks/{1-BAlAsKdp.js → 1-D-qMYaCx.js} +2 -2
  55. package/build/server/chunks/{1-BAlAsKdp.js.map → 1-D-qMYaCx.js.map} +1 -1
  56. package/build/server/chunks/{10-BUCX7Aqz.js → 10-CeFFGo-X.js} +2 -2
  57. package/build/server/chunks/{10-BUCX7Aqz.js.map → 10-CeFFGo-X.js.map} +1 -1
  58. package/build/server/chunks/{11-DHPvc2yA.js → 11-DRMu_ATU.js} +2 -2
  59. package/build/server/chunks/{11-DHPvc2yA.js.map → 11-DRMu_ATU.js.map} +1 -1
  60. package/build/server/chunks/{2-DLOMdCHW.js → 2-B7OLBMNH.js} +2 -2
  61. package/build/server/chunks/{2-DLOMdCHW.js.map → 2-B7OLBMNH.js.map} +1 -1
  62. package/build/server/chunks/{3-DCf69LYo.js → 3-B38ZarLw.js} +2 -2
  63. package/build/server/chunks/{3-DCf69LYo.js.map → 3-B38ZarLw.js.map} +1 -1
  64. package/build/server/chunks/{6-DUrC2Naz.js → 6-DP46cUej.js} +2 -2
  65. package/build/server/chunks/{6-DUrC2Naz.js.map → 6-DP46cUej.js.map} +1 -1
  66. package/build/server/chunks/{7-TXwjMHt2.js → 7-B29_3ar6.js} +2 -2
  67. package/build/server/chunks/{7-TXwjMHt2.js.map → 7-B29_3ar6.js.map} +1 -1
  68. package/build/server/chunks/{8-D2X_jBsT.js → 8-DCnSDVrX.js} +2 -2
  69. package/build/server/chunks/{8-D2X_jBsT.js.map → 8-DCnSDVrX.js.map} +1 -1
  70. package/build/server/chunks/{9-DK0hH5Xa.js → 9-BwqDc8wC.js} +2 -2
  71. package/build/server/chunks/{9-DK0hH5Xa.js.map → 9-BwqDc8wC.js.map} +1 -1
  72. package/build/server/chunks/{_server.ts-B54Pvhgc.js → _server.ts-Blx6TuRU.js} +3 -2
  73. package/build/server/chunks/_server.ts-Blx6TuRU.js.map +1 -0
  74. package/build/server/chunks/{_server.ts-C0PO_cAu.js → _server.ts-CYWXjihn.js} +3 -2
  75. package/build/server/chunks/{_server.ts-C0PO_cAu.js.map → _server.ts-CYWXjihn.js.map} +1 -1
  76. package/build/server/chunks/{_server.ts-DiBMY7Ho.js → _server.ts-D0___krA.js} +3 -2
  77. package/build/server/chunks/_server.ts-D0___krA.js.map +1 -0
  78. package/build/server/chunks/{_server.ts-CZb-BI5H.js → _server.ts-Da1kSClZ.js} +3 -2
  79. package/build/server/chunks/_server.ts-Da1kSClZ.js.map +1 -0
  80. package/build/server/chunks/{_server.ts-Bol54_Qo.js → _server.ts-l3cd4Cto.js} +3 -2
  81. package/build/server/chunks/_server.ts-l3cd4Cto.js.map +1 -0
  82. package/build/server/chunks/{pty-manager-CoWVT56F.js → pty-manager-DDjG7DlH.js} +272 -27
  83. package/build/server/chunks/pty-manager-DDjG7DlH.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 +2 -2
  90. package/src/lib/modules/client/terminal/xterm-wrapper.ts +52 -12
  91. package/src/lib/modules/server/terminal/pty-manager.ts +279 -35
  92. package/src/lib/modules/server/terminal/terminal-emulator.ts +102 -0
  93. package/src/lib/modules/server/ws/server.ts +18 -2
  94. package/src/lib/modules/server/ws/terminal-handler.ts +11 -6
  95. package/src/lib/types/generated/WsProtocol.ts +10 -1
  96. package/src/lib/types/server.ts +27 -1
  97. package/src/lib/types/terminal-client.ts +3 -0
  98. package/src/lib/types/ws.ts +3 -2
  99. package/build/client/_app/immutable/chunks/BfbPKMXz.js +0 -3
  100. package/build/client/_app/immutable/chunks/BfbPKMXz.js.br +0 -0
  101. package/build/client/_app/immutable/chunks/BfbPKMXz.js.gz +0 -0
  102. package/build/client/_app/immutable/chunks/CZg4kn4E.js.br +0 -0
  103. package/build/client/_app/immutable/chunks/CZg4kn4E.js.gz +0 -0
  104. package/build/client/_app/immutable/chunks/DhK7PwI_.js.br +0 -0
  105. package/build/client/_app/immutable/chunks/J5-Cr5oR.js +0 -6
  106. package/build/client/_app/immutable/chunks/J5-Cr5oR.js.br +0 -0
  107. package/build/client/_app/immutable/chunks/J5-Cr5oR.js.gz +0 -0
  108. package/build/client/_app/immutable/entry/app.CTqz33nP.js.br +0 -0
  109. package/build/client/_app/immutable/entry/app.CTqz33nP.js.gz +0 -0
  110. package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js +0 -1
  111. package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js.br +0 -2
  112. package/build/client/_app/immutable/entry/start.Dj-Kvgwo.js.gz +0 -0
  113. package/build/client/_app/immutable/nodes/0.Qn7Ktiht.js.br +0 -0
  114. package/build/client/_app/immutable/nodes/0.Qn7Ktiht.js.gz +0 -0
  115. package/build/client/_app/immutable/nodes/1.BxWOfNlo.js.br +0 -0
  116. package/build/client/_app/immutable/nodes/1.BxWOfNlo.js.gz +0 -0
  117. package/build/client/_app/immutable/nodes/10.BGPYD1s1.js.br +0 -0
  118. package/build/client/_app/immutable/nodes/10.BGPYD1s1.js.gz +0 -0
  119. package/build/client/_app/immutable/nodes/11.BxY1PUjC.js.br +0 -0
  120. package/build/client/_app/immutable/nodes/2.Bc2qALkX.js.br +0 -0
  121. package/build/client/_app/immutable/nodes/2.Bc2qALkX.js.gz +0 -0
  122. package/build/client/_app/immutable/nodes/3.N2-A8noI.js.br +0 -0
  123. package/build/client/_app/immutable/nodes/3.N2-A8noI.js.gz +0 -0
  124. package/build/client/_app/immutable/nodes/6.BWF9Qx6F.js.br +0 -0
  125. package/build/client/_app/immutable/nodes/6.BWF9Qx6F.js.gz +0 -0
  126. package/build/client/_app/immutable/nodes/7.DHuDIdpz.js.br +0 -0
  127. package/build/client/_app/immutable/nodes/7.DHuDIdpz.js.gz +0 -0
  128. package/build/client/_app/immutable/nodes/8.D0Ijt9Vv.js.br +0 -0
  129. package/build/client/_app/immutable/nodes/8.D0Ijt9Vv.js.gz +0 -0
  130. package/build/client/_app/immutable/nodes/9.2Piwo35J.js.br +0 -0
  131. package/build/client/_app/immutable/nodes/9.2Piwo35J.js.gz +0 -0
  132. package/build/server/chunks/_server.ts-B54Pvhgc.js.map +0 -1
  133. package/build/server/chunks/_server.ts-Bol54_Qo.js.map +0 -1
  134. package/build/server/chunks/_server.ts-CZb-BI5H.js.map +0 -1
  135. package/build/server/chunks/_server.ts-DiBMY7Ho.js.map +0 -1
  136. 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-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-CZb-BI5H.js.map
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-CoWVT56F.js';
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-Bol54_Qo.js.map
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
- // disconnectAllgraceful shutdown: disconnect clients, keep holders alive
1020
+ // reconnectAllrecover 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
- // get
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
- // list — running first, then recently exited, each group sorted by
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
- // killroute through holder: SIGTERM, then SIGKILL after 5 s
1169
+ // listrunning 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
- // removeremove an exited terminal from the map
1195
+ // killroute 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
- // resize
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
- // attach — register a WebSocket client and replay scrollback
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
- // detachremove a WebSocket client
1277
+ // attachregister 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
- // getScrollbackreturn raw scrollback data for replay
1290
+ // detachremove 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
- // cleanupevict exited terminals older than 1 hour, cap at 10 exited;
1187
- // also clean up old SQLite records
1310
+ // getScrollbackreturn 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
- // destroyemergency forced kill (kills holder processes too)
1324
+ // cleanupevict 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 msg = JSON.stringify({ data, type: "output" });
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: data.length, type: "output-dropped" }));
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
- const dropMsg = JSON.stringify({
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-CoWVT56F.js.map
1959
+ //# sourceMappingURL=pty-manager-DDjG7DlH.js.map