@code-yeongyu/senpi-pty 2026.10.19 → 2026.10.20-1

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.
@@ -39,7 +39,7 @@ export declare function shouldUsePipeFallback(nativeLoadResult: NativePtyLoadRes
39
39
  * the group is already gone, it still falls back to a direct kill, so the
40
40
  * child is never left running.
41
41
  */
42
- export declare function terminateChildTree(child: Pick<ChildProcessHandle, "pid" | "kill">, signal: NodeJS.Signals): "group" | "direct";
42
+ export declare function terminateChildTree(child: Pick<ChildProcessHandle, "pid" | "kill">, signal: NodeJS.Signals): "group" | "direct" | "none";
43
43
  export declare class PipeFallbackSession {
44
44
  readonly backend = "pipe-fallback";
45
45
  readonly note = "Running with child_process pipe fallback because no PTY backend is active; terminal screen state and resize are unavailable.";
@@ -18,7 +18,10 @@ export function shouldUsePipeFallback(nativeLoadResult, env = process.env) {
18
18
  */
19
19
  export function terminateChildTree(child, signal) {
20
20
  const pid = child.pid;
21
- if (pid !== undefined && process.platform !== "win32") {
21
+ // A child that never started has no pid; on Node < 24.22 / 26.10 its kill() signals our own process group.
22
+ if (pid === undefined)
23
+ return "none";
24
+ if (process.platform !== "win32") {
22
25
  try {
23
26
  process.kill(-pid, signal);
24
27
  return "group";
@@ -208,7 +211,15 @@ export class PipeFallbackSession {
208
211
  return { ok: true, note: `Sent ${signal} to child_process pipe fallback session.` };
209
212
  }
210
213
  }
211
- if (terminateChildTree(this.child, signal) === "group") {
214
+ const route = terminateChildTree(this.child, signal);
215
+ if (route === "none") {
216
+ return {
217
+ ok: false,
218
+ code: "not_started",
219
+ note: "Cannot kill pipe fallback session: its process never started.",
220
+ };
221
+ }
222
+ if (route === "group") {
212
223
  return { ok: true, note: `Sent ${signal} to child_process pipe fallback process group (pgid ${pid}).` };
213
224
  }
214
225
  return { ok: true, note: `Sent ${signal} to child_process pipe fallback session.` };
@@ -62,6 +62,11 @@ export interface SessionRegistrySweepOptions {
62
62
  }
63
63
  export interface SessionRegistryOptions<TSession extends SessionRegistrySession = SessionRegistrySession> {
64
64
  readonly maxSessions?: number;
65
+ /**
66
+ * Inserted between the command prefix and the counter (`bash_<scope>_1`), so two registries whose ids reach the same
67
+ * caller - a host's and its tool child's - never hand out the same id. Letters and digits only.
68
+ */
69
+ readonly idScope?: string;
65
70
  readonly initialSessions?: readonly InitialSessionRegistryEntry<TSession>[];
66
71
  readonly createSession?: (options: SessionRegistryCreateContext) => MaybePromise<TSession>;
67
72
  readonly killProcess?: (target: number, signal: TerminalSessionSignal) => void;
@@ -11,6 +11,7 @@ export declare class SessionRegistry<TSession extends SessionRegistrySession = S
11
11
  private readonly platform;
12
12
  private sequence;
13
13
  readonly maxSessions: number;
14
+ private readonly idScope;
14
15
  private readonly stopExitGraceMs;
15
16
  private readonly forcedExitGraceMs;
16
17
  private readonly detachedExitGraceMs;
package/dist/registry.js CHANGED
@@ -14,6 +14,9 @@ export class SessionRegistry {
14
14
  this.idCounters = new Map();
15
15
  this.sequence = 0;
16
16
  this.maxSessions = normalizeMaxSessions(options.maxSessions);
17
+ if (options.idScope !== undefined && !/^[A-Za-z0-9]+$/.test(options.idScope))
18
+ throw new Error("idScope must be letters and digits only.");
19
+ this.idScope = options.idScope;
17
20
  this.stopExitGraceMs =
18
21
  options.stopExitGraceMs !== undefined &&
19
22
  Number.isFinite(options.stopExitGraceMs) &&
@@ -229,7 +232,8 @@ export class SessionRegistry {
229
232
  this.entries.delete(id);
230
233
  }
231
234
  allocateId(command) {
232
- const prefix = sessionIdPrefix(command);
235
+ const commandPrefix = sessionIdPrefix(command);
236
+ const prefix = this.idScope === undefined ? commandPrefix : `${commandPrefix}_${this.idScope}`;
233
237
  let next = (this.idCounters.get(prefix) ?? 0) + 1;
234
238
  while (this.entries.has(`${prefix}_${next}`))
235
239
  next += 1;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code-yeongyu/senpi-pty",
3
- "version": "2026.10.19",
3
+ "version": "2026.10.20-1",
4
4
  "description": "Typed PTY session loader for Senpi persistent terminals",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",