@sma1lboy/kobe 0.8.166 → 0.8.168

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.
@@ -683,6 +683,85 @@ async function terminatePtyChild(proc, onSettled) {
683
683
  onSettled();
684
684
  }
685
685
 
686
+ // ../kobe-daemon/src/daemon/pty-child-controller.ts
687
+ class PtyChildController {
688
+ deps;
689
+ constructor(deps) {
690
+ this.deps = deps;
691
+ }
692
+ spawn(key, spec, spare = false) {
693
+ const argv = spec.command && spec.command.length > 0 ? [...spec.command] : [spec.shell ?? resolveLoginShell()];
694
+ const session = freshSessionState(key, spec, argv);
695
+ this.startChild(session);
696
+ if (session.alive) {
697
+ this.deps.log?.("pty", `spawned ${argv[0]} for ${key} (pid ${session.proc?.pid})`);
698
+ this.deps.onSessionStart?.(spare);
699
+ }
700
+ return session;
701
+ }
702
+ startChild(session) {
703
+ try {
704
+ session.proc = (this.deps.driver ?? bunTerminalDriver())({
705
+ argv: [...session.command],
706
+ cwd: session.cwd,
707
+ env: embeddedTerminalEnv(process.env, {
708
+ TERM: "xterm-256color",
709
+ COLUMNS: String(session.cols),
710
+ LINES: String(session.rows),
711
+ BASH_SILENCE_DEPRECATION_WARNING: "1",
712
+ KOBE_TERMINAL_PTY: "1"
713
+ }),
714
+ cols: session.cols,
715
+ rows: session.rows,
716
+ onData: (data) => this.onData(session, data)
717
+ });
718
+ session.alive = true;
719
+ session.proc.exited.then((exit) => this.markExited(session, exit), () => this.markExited(session));
720
+ } catch (err) {
721
+ session.alive = false;
722
+ this.deps.log?.("pty", `spawn failed for ${session.key}: ${err instanceof Error ? err.message : String(err)}`);
723
+ }
724
+ }
725
+ async endChild(session) {
726
+ if (!session.alive)
727
+ return;
728
+ const proc = session.proc;
729
+ if (!proc) {
730
+ this.markExited(session);
731
+ return;
732
+ }
733
+ await terminatePtyChild(proc, () => this.markExited(session));
734
+ }
735
+ markExited(session, exit) {
736
+ if (!session.alive)
737
+ return;
738
+ session.alive = false;
739
+ const sessionExit = {
740
+ code: exit?.code ?? null,
741
+ signal: exit?.signal ?? null,
742
+ at: new Date().toISOString()
743
+ };
744
+ session.exit = sessionExit;
745
+ try {
746
+ session.proc?.close();
747
+ } catch {}
748
+ this.deps.onExit?.(session, sessionExit);
749
+ }
750
+ onData(session, data) {
751
+ const buf = typeof data === "string" ? Buffer.from(data, "utf8") : Buffer.from(data);
752
+ scanOscTitle(session, buf);
753
+ session.chunks.push(buf);
754
+ session.bytes += buf.byteLength;
755
+ session.totalBytes += buf.byteLength;
756
+ while (session.bytes > this.deps.scrollbackCap && session.chunks.length > 1) {
757
+ const dropped = session.chunks.shift();
758
+ if (dropped)
759
+ session.bytes -= dropped.byteLength;
760
+ }
761
+ this.deps.onOutput?.(session, buf);
762
+ }
763
+ }
764
+
686
765
  // ../kobe-daemon/src/daemon/pty-warm.ts
687
766
  class WarmSpare {
688
767
  deps;
@@ -746,16 +825,61 @@ class PtyHost {
746
825
  scrollbackCap;
747
826
  parkRestoreDeltas = 0;
748
827
  parkRestoreFallbacks = 0;
749
- warmSpare = new WarmSpare({
750
- spawn: (key, spec, spare) => this.spawn(key, spec, spare),
751
- endChild: (session) => this.endChild(session),
752
- markExited: (session) => this.markExited(session),
753
- log: (event, message) => this.opts.log?.(event, message),
754
- onSessionStart: () => this.opts.onSessionStart?.()
755
- });
828
+ childController;
829
+ warmSpare;
756
830
  constructor(opts = {}) {
757
831
  this.opts = opts;
758
832
  this.scrollbackCap = opts.scrollbackCap ?? DEFAULT_SCROLLBACK_CAP;
833
+ this.childController = new PtyChildController({
834
+ driver: opts.driver,
835
+ scrollbackCap: this.scrollbackCap,
836
+ onSessionStart: (spare) => {
837
+ if (!spare)
838
+ this.opts.onSessionStart?.();
839
+ },
840
+ onOutput: (session, data) => {
841
+ if (session.sinks.size === 0) {
842
+ this.maybeFreeze(session);
843
+ return;
844
+ }
845
+ const frame = {
846
+ type: "event",
847
+ name: "pty.data",
848
+ payload: { key: session.key, data: data.toString("base64") }
849
+ };
850
+ for (const sink of session.sinks.values())
851
+ sink(frame);
852
+ this.maybeFreeze(session);
853
+ },
854
+ onExit: (session, exit) => {
855
+ const frame = {
856
+ type: "event",
857
+ name: "pty.exit",
858
+ payload: { key: session.key, pid: session.proc?.pid ?? null, ...exit }
859
+ };
860
+ for (const sink of session.sinks.values())
861
+ sink(frame);
862
+ this.opts.log?.("pty", `session ${session.key} exited${describeExit(exit)}`);
863
+ this.maybeFreeze(session, true);
864
+ try {
865
+ this.opts.onSessionExit?.({
866
+ key: session.key,
867
+ pid: session.proc?.pid ?? null,
868
+ exit,
869
+ tail: ringTail(session.chunks, session.bytes, EXIT_TAIL_BYTES)
870
+ });
871
+ } catch {}
872
+ this.opts.onSessionEnd?.();
873
+ },
874
+ log: (event, message) => this.opts.log?.(event, message)
875
+ });
876
+ this.warmSpare = new WarmSpare({
877
+ spawn: (key, spec, spare) => this.childController.spawn(key, spec, spare),
878
+ endChild: (session) => this.childController.endChild(session),
879
+ markExited: (session) => this.childController.markExited(session),
880
+ log: (event, message) => this.opts.log?.(event, message),
881
+ onSessionStart: () => this.opts.onSessionStart?.()
882
+ });
759
883
  }
760
884
  open(key, spec, token, sink, sinceOffset, sincePid) {
761
885
  let session = this.sessions.get(key);
@@ -763,7 +887,7 @@ class PtyHost {
763
887
  let respawned = false;
764
888
  if (!session) {
765
889
  created = true;
766
- session = this.warmSpare.adopt(key, spec) ?? this.spawn(key, spec);
890
+ session = this.warmSpare.adopt(key, spec) ?? this.childController.spawn(key, spec);
767
891
  this.sessions.set(key, session);
768
892
  } else if (!session.alive && session.restored) {
769
893
  this.respawn(session, spec);
@@ -824,7 +948,7 @@ class PtyHost {
824
948
  return Promise.resolve();
825
949
  this.sessions.delete(key);
826
950
  this.opts.freeze?.drop(key);
827
- return this.endChild(session);
951
+ return this.childController.endChild(session);
828
952
  }
829
953
  rename(from, to) {
830
954
  const session = this.sessions.get(from);
@@ -880,7 +1004,7 @@ class PtyHost {
880
1004
  }
881
1005
  async shutdown() {
882
1006
  this.flushFrozen();
883
- const endings = Array.from(this.sessions.values(), (session) => this.endChild(session));
1007
+ const endings = Array.from(this.sessions.values(), (session) => this.childController.endChild(session));
884
1008
  endings.push(this.warmSpare.end());
885
1009
  await Promise.all(endings);
886
1010
  }
@@ -910,7 +1034,7 @@ class PtyHost {
910
1034
  session.command = [...spec.command];
911
1035
  session.cols = spec.cols ?? session.cols;
912
1036
  session.rows = spec.rows ?? session.rows;
913
- this.startChild(session);
1037
+ this.childController.startChild(session);
914
1038
  if (!session.alive)
915
1039
  return;
916
1040
  this.opts.log?.("pty", `respawned restored session ${session.key} (pid ${session.proc?.pid})`);
@@ -933,101 +1057,6 @@ class PtyHost {
933
1057
  n++;
934
1058
  return n;
935
1059
  }
936
- spawn(key, spec, spare = false) {
937
- const argv = spec.command && spec.command.length > 0 ? [...spec.command] : [spec.shell ?? resolveLoginShell()];
938
- const session = freshSessionState(key, spec, argv);
939
- this.startChild(session);
940
- if (session.alive) {
941
- this.opts.log?.("pty", `spawned ${argv[0]} for ${key} (pid ${session.proc?.pid})`);
942
- if (!spare)
943
- this.opts.onSessionStart?.();
944
- }
945
- return session;
946
- }
947
- startChild(session) {
948
- try {
949
- session.proc = (this.opts.driver ?? bunTerminalDriver())({
950
- argv: [...session.command],
951
- cwd: session.cwd,
952
- env: embeddedTerminalEnv(process.env, {
953
- TERM: "xterm-256color",
954
- COLUMNS: String(session.cols),
955
- LINES: String(session.rows),
956
- BASH_SILENCE_DEPRECATION_WARNING: "1",
957
- KOBE_TERMINAL_PTY: "1"
958
- }),
959
- cols: session.cols,
960
- rows: session.rows,
961
- onData: (data) => this.onData(session, data)
962
- });
963
- session.alive = true;
964
- session.proc.exited.then((exit) => this.markExited(session, exit), () => this.markExited(session));
965
- } catch (err) {
966
- session.alive = false;
967
- this.opts.log?.("pty", `spawn failed for ${session.key}: ${err instanceof Error ? err.message : String(err)}`);
968
- }
969
- }
970
- onData(session, data) {
971
- const buf = typeof data === "string" ? Buffer.from(data, "utf8") : Buffer.from(data);
972
- scanOscTitle(session, buf);
973
- session.chunks.push(buf);
974
- session.bytes += buf.byteLength;
975
- session.totalBytes += buf.byteLength;
976
- while (session.bytes > this.scrollbackCap && session.chunks.length > 1) {
977
- const dropped = session.chunks.shift();
978
- if (dropped)
979
- session.bytes -= dropped.byteLength;
980
- }
981
- if (session.sinks.size === 0) {
982
- this.maybeFreeze(session);
983
- return;
984
- }
985
- const frame = {
986
- type: "event",
987
- name: "pty.data",
988
- payload: { key: session.key, data: buf.toString("base64") }
989
- };
990
- for (const sink of session.sinks.values())
991
- sink(frame);
992
- this.maybeFreeze(session);
993
- }
994
- markExited(session, exit) {
995
- if (!session.alive)
996
- return;
997
- session.alive = false;
998
- session.exit = { code: exit?.code ?? null, signal: exit?.signal ?? null, at: new Date().toISOString() };
999
- try {
1000
- session.proc?.close();
1001
- } catch {}
1002
- const frame = {
1003
- type: "event",
1004
- name: "pty.exit",
1005
- payload: { key: session.key, pid: session.proc?.pid ?? null, ...session.exit }
1006
- };
1007
- for (const sink of session.sinks.values())
1008
- sink(frame);
1009
- this.opts.log?.("pty", `session ${session.key} exited${describeExit(session.exit)}`);
1010
- this.maybeFreeze(session, true);
1011
- try {
1012
- this.opts.onSessionExit?.({
1013
- key: session.key,
1014
- pid: session.proc?.pid ?? null,
1015
- exit: session.exit,
1016
- tail: ringTail(session.chunks, session.bytes, EXIT_TAIL_BYTES)
1017
- });
1018
- } catch {}
1019
- this.opts.onSessionEnd?.();
1020
- }
1021
- async endChild(session) {
1022
- if (!session.alive)
1023
- return;
1024
- const proc = session.proc;
1025
- if (!proc) {
1026
- this.markExited(session);
1027
- return;
1028
- }
1029
- await terminatePtyChild(proc, () => this.markExited(session));
1030
- }
1031
1060
  }
1032
1061
 
1033
1062
  // ../kobe-daemon/src/daemon/pty-server.ts