pty-snapshot-backend.mjs 1.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
  1. /** Deterministic in-memory PTY backend for transcript snapshots. */
  2. class SnapshotSession {
  3. motd = 'dsh> '
  4. statusValue = { kind: 'running' }
  5. scrollback = 'dsh> '
  6. startSend(request) {
  7. const viewport = `${request.text}\nPTY_OK\ndsh> `
  8. this.scrollback += viewport
  9. const result = {
  10. viewport,
  11. waitReason: 'stdin_read',
  12. sessionStatus: this.statusValue,
  13. truncated: false,
  14. }
  15. let consumed = false
  16. return {
  17. done: Promise.resolve(result),
  18. readOutput: () => {
  19. if (consumed) return { delta: '', truncated: false }
  20. consumed = true
  21. return { delta: viewport, truncated: false }
  22. },
  23. cancel: () => false,
  24. }
  25. }
  26. read(request) {
  27. const lines = this.scrollback.split('\n')
  28. const offset = request.offset ?? 0
  29. const count = request.count ?? 500
  30. const end = lines.length - offset
  31. const start = Math.max(0, end - count)
  32. const text = lines.slice(start, end).join('\n')
  33. return { text, totalLines: lines.length, lineBegin: offset, lineEnd: offset + text.split('\n').length, truncated: false }
  34. }
  35. signal() {
  36. return Promise.resolve({ delivered: true, targetPgid: 1 })
  37. }
  38. status() {
  39. return this.statusValue
  40. }
  41. close() {
  42. this.statusValue = { kind: 'exited', exitCode: 0, signal: null }
  43. return Promise.resolve()
  44. }
  45. }
  46. /** Cordis plugin name. */
  47. export const name = 'pty-snapshot-backend'
  48. /** Required PTY service. */
  49. export const inject = ['pty']
  50. /** Register the deterministic snapshot backend. */
  51. export function apply(ctx) {
  52. ctx.pty.registerBackend({
  53. type: 'shell',
  54. spawn: () => Promise.resolve(new SnapshotSession()),
  55. })
  56. }