@ganglion/xacpx 0.17.0-beta.3 → 0.17.0-beta.4

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.
package/dist/cli.js CHANGED
@@ -25585,6 +25585,10 @@ function terminalIdleTimeoutSeconds(config4) {
25585
25585
  const v = config4.terminal?.idleTimeoutSeconds;
25586
25586
  return typeof v === "number" && v > 0 ? v : 900;
25587
25587
  }
25588
+ function terminalShell(config4) {
25589
+ const v = config4.terminal?.shell;
25590
+ return typeof v === "string" && v.trim() ? v : undefined;
25591
+ }
25588
25592
  function filesWriteEnabled(config4) {
25589
25593
  return config4.files?.writeEnabled === true;
25590
25594
  }
@@ -34840,6 +34844,11 @@ ${chunk}` : chunk
34840
34844
  throw new Error("session-not-found");
34841
34845
  return this.deps.terminal.create({ cwd: session3.cwd, cols, rows });
34842
34846
  }
34847
+ attachTerminal(terminalId) {
34848
+ if (!this.deps.terminalEnabled())
34849
+ throw new Error("terminal-disabled");
34850
+ return this.deps.terminal.attach(terminalId);
34851
+ }
34843
34852
  writeTerminal(terminalId, data) {
34844
34853
  this.deps.terminal.write(terminalId, data);
34845
34854
  }
@@ -34886,6 +34895,7 @@ var init_control_service = __esm(() => {
34886
34895
  // src/control/terminal-service.ts
34887
34896
  import { randomUUID as randomUUID4 } from "node:crypto";
34888
34897
  import { createRequire as createRequire6 } from "node:module";
34898
+ import { statSync as statSync3 } from "node:fs";
34889
34899
  import { spawn as spawnPty2 } from "node-pty";
34890
34900
  function scrubEnv() {
34891
34901
  const out = {};
@@ -34900,9 +34910,33 @@ function scrubEnv() {
34900
34910
  out.LANG = out.LANG ?? "en_US.UTF-8";
34901
34911
  return out;
34902
34912
  }
34903
- function defaultShell(platform) {
34904
- if (process.env.SHELL)
34905
- return process.env.SHELL;
34913
+ function defaultExists(p) {
34914
+ try {
34915
+ return statSync3(p).isFile();
34916
+ } catch {
34917
+ return false;
34918
+ }
34919
+ }
34920
+ function resolveShell(args) {
34921
+ const { platform, env, shellOverride } = args;
34922
+ const exists = args.exists ?? defaultExists;
34923
+ if (shellOverride && shellOverride.trim())
34924
+ return shellOverride;
34925
+ if (platform === "win32") {
34926
+ const pathValue = env.PATH ?? env.Path ?? "";
34927
+ const dirs = pathValue.split(";").filter(Boolean);
34928
+ for (const name of ["pwsh.exe", "powershell.exe"]) {
34929
+ for (const dir of dirs) {
34930
+ const clean = dir.replace(/^"|"$/g, "");
34931
+ const full = `${clean}\\${name}`;
34932
+ if (exists(full))
34933
+ return full;
34934
+ }
34935
+ }
34936
+ return env.ComSpec ?? "cmd.exe";
34937
+ }
34938
+ if (env.SHELL)
34939
+ return env.SHELL;
34906
34940
  return platform === "darwin" ? "/bin/zsh" : "/bin/bash";
34907
34941
  }
34908
34942
  function realPtySpawn(file, args, opts) {
@@ -34910,6 +34944,26 @@ function realPtySpawn(file, args, opts) {
34910
34944
  ensureNodePtyHelperExecutable(helperPath);
34911
34945
  return spawnPty2(file, args, opts);
34912
34946
  }
34947
+ function appendToBuffer(session3, data) {
34948
+ session3.buffer += data;
34949
+ session3.bufBytes += Buffer.byteLength(data, "utf8");
34950
+ if (session3.bufBytes <= MAX_BUFFER_BYTES)
34951
+ return;
34952
+ while (session3.bufBytes > MAX_BUFFER_BYTES) {
34953
+ const nl = session3.buffer.indexOf(`
34954
+ `);
34955
+ if (nl === -1)
34956
+ break;
34957
+ const removed = session3.buffer.slice(0, nl + 1);
34958
+ session3.buffer = session3.buffer.slice(nl + 1);
34959
+ session3.bufBytes -= Buffer.byteLength(removed, "utf8");
34960
+ }
34961
+ while (session3.bufBytes > MAX_BUFFER_BYTES && session3.buffer.length > 0) {
34962
+ const ch = session3.buffer[0];
34963
+ session3.buffer = session3.buffer.slice(1);
34964
+ session3.bufBytes -= Buffer.byteLength(ch, "utf8");
34965
+ }
34966
+ }
34913
34967
  function createTerminalService(deps) {
34914
34968
  const spawn12 = deps.spawn ?? realPtySpawn;
34915
34969
  const platform = deps.platform ?? process.platform;
@@ -34934,13 +34988,13 @@ function createTerminalService(deps) {
34934
34988
  };
34935
34989
  return {
34936
34990
  create({ cwd, cols, rows }) {
34937
- if (platform === "win32")
34938
- throw new Error("terminal-unsupported-platform");
34991
+ const shell = resolveShell({ platform, env: process.env, shellOverride: deps.shell?.() });
34939
34992
  const terminalId = randomUUID4();
34940
- const handle = spawn12(defaultShell(platform), [], { name: "xterm-256color", cols, rows, cwd, env: scrubEnv() });
34941
- const session3 = { handle, seq: 0, idleTimer: null };
34993
+ const handle = spawn12(shell, [], { name: "xterm-256color", cols, rows, cwd, env: scrubEnv() });
34994
+ const session3 = { handle, seq: 0, idleTimer: null, buffer: "", bufBytes: 0 };
34942
34995
  sessions.set(terminalId, session3);
34943
34996
  handle.onData((data) => {
34997
+ appendToBuffer(session3, data);
34944
34998
  deps.events.emit({ type: "terminal-output", terminalId, seq: session3.seq++, data });
34945
34999
  });
34946
35000
  handle.onExit(({ exitCode }) => {
@@ -34952,6 +35006,13 @@ function createTerminalService(deps) {
34952
35006
  resetIdle(terminalId);
34953
35007
  return { terminalId };
34954
35008
  },
35009
+ attach(terminalId) {
35010
+ const s = sessions.get(terminalId);
35011
+ if (!s)
35012
+ return { ok: false };
35013
+ resetIdle(terminalId);
35014
+ return { ok: true, buffer: s.buffer, lastSeq: s.seq - 1 };
35015
+ },
34955
35016
  write(terminalId, data) {
34956
35017
  const s = sessions.get(terminalId);
34957
35018
  if (!s)
@@ -34990,7 +35051,7 @@ function createTerminalService(deps) {
34990
35051
  }
34991
35052
  };
34992
35053
  }
34993
- var require5, SENSITIVE_ENV_KEYS;
35054
+ var require5, SENSITIVE_ENV_KEYS, MAX_BUFFER_BYTES;
34994
35055
  var init_terminal_service = __esm(() => {
34995
35056
  init_node_pty_helper();
34996
35057
  require5 = createRequire6(import.meta.url);
@@ -35008,6 +35069,7 @@ var init_terminal_service = __esm(() => {
35008
35069
  "GITHUB_TOKEN",
35009
35070
  "NPM_TOKEN"
35010
35071
  ];
35072
+ MAX_BUFFER_BYTES = 256 * 1024;
35011
35073
  });
35012
35074
 
35013
35075
  // src/control/upload-store.ts
@@ -35685,7 +35747,8 @@ async function buildApp(paths, deps = {}) {
35685
35747
  const controlEvents = createControlEventBus(logger2);
35686
35748
  const terminalService = createTerminalService({
35687
35749
  events: controlEvents,
35688
- idleTimeoutSeconds: () => terminalIdleTimeoutSeconds(config4)
35750
+ idleTimeoutSeconds: () => terminalIdleTimeoutSeconds(config4),
35751
+ shell: () => terminalShell(config4)
35689
35752
  });
35690
35753
  const uploadStore = new UploadStore;
35691
35754
  uploadStore.cleanup();
@@ -47,6 +47,8 @@ export interface TerminalConfig {
47
47
  enabled: boolean;
48
48
  /** Idle seconds before a terminal PTY is auto-killed. Defaults to 900 (15 min). */
49
49
  idleTimeoutSeconds?: number;
50
+ /** Explicit shell override (absolute path or bare name). Cross-platform: wins over SHELL / platform default. */
51
+ shell?: string;
50
52
  }
51
53
  export interface FilesConfig {
52
54
  /** Default false. When false, all fs write ops (new/rename/delete/copy) are rejected
@@ -119,4 +121,5 @@ export interface AppConfig {
119
121
  }
120
122
  export declare function terminalEnabled(config: AppConfig): boolean;
121
123
  export declare function terminalIdleTimeoutSeconds(config: AppConfig): number;
124
+ export declare function terminalShell(config: AppConfig): string | undefined;
122
125
  export declare function filesWriteEnabled(config: AppConfig): boolean;
@@ -241,6 +241,7 @@ export declare class ControlService {
241
241
  createTerminal(chatKey: string, sessionAlias: string, cols: number, rows: number): Promise<{
242
242
  terminalId: string;
243
243
  }>;
244
+ attachTerminal(terminalId: string): import("./terminal-service").TerminalAttachResult;
244
245
  writeTerminal(terminalId: string, data: string): void;
245
246
  resizeTerminal(terminalId: string, cols: number, rows: number): void;
246
247
  closeTerminal(terminalId: string): void;
@@ -27,10 +27,18 @@ export interface TerminalCreateInput {
27
27
  cols: number;
28
28
  rows: number;
29
29
  }
30
+ export type TerminalAttachResult = {
31
+ ok: false;
32
+ } | {
33
+ ok: true;
34
+ buffer: string;
35
+ lastSeq: number;
36
+ };
30
37
  export interface TerminalService {
31
38
  create(input: TerminalCreateInput): {
32
39
  terminalId: string;
33
40
  };
41
+ attach(terminalId: string): TerminalAttachResult;
34
42
  write(terminalId: string, data: string): void;
35
43
  resize(terminalId: string, cols: number, rows: number): void;
36
44
  close(terminalId: string): void;
@@ -39,10 +47,25 @@ export interface TerminalService {
39
47
  export interface TerminalServiceDeps {
40
48
  events: ControlEventBus;
41
49
  idleTimeoutSeconds: () => number;
50
+ /** Optional explicit shell override from config (terminal.shell). */
51
+ shell?: () => string | undefined;
42
52
  spawn?: PtySpawn;
43
53
  platform?: NodeJS.Platform;
44
54
  /** Injectable timer primitives; defaults to global setTimeout/clearTimeout. */
45
55
  setTimer?: (fn: () => void, ms: number) => unknown;
46
56
  clearTimer?: (id: unknown) => void;
47
57
  }
58
+ export interface ResolveShellArgs {
59
+ platform: NodeJS.Platform;
60
+ env: NodeJS.ProcessEnv;
61
+ /** Explicit override from config terminal.shell — wins on every platform. */
62
+ shellOverride?: string;
63
+ /** Injectable executable-existence predicate (defaults to fs statSync). */
64
+ exists?: (p: string) => boolean;
65
+ }
66
+ /** Pick the shell to spawn. Priority: explicit override -> platform default.
67
+ * win32 deliberately ignores SHELL (git-bash sets it to an MSYS path that
68
+ * node-pty can't spawn on Windows) and scans PATH for pwsh -> powershell,
69
+ * falling back to %ComSpec% (cmd.exe). Never throws — always returns a value. */
70
+ export declare function resolveShell(args: ResolveShellArgs): string;
48
71
  export declare function createTerminalService(deps: TerminalServiceDeps): TerminalService;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ganglion/xacpx",
3
- "version": "0.17.0-beta.3",
3
+ "version": "0.17.0-beta.4",
4
4
  "description": "随时随地通过聊天频道(微信 / 飞书 / 元宝等)远程控制 `acpx` 上的 Claude Code、Codex 等 Agents。",
5
5
  "keywords": [
6
6
  "acpx",