@ganglion/xacpx 0.16.0 → 0.17.0-beta.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.
package/dist/cli.js
CHANGED
|
@@ -4727,7 +4727,13 @@ function parseConfig(raw, options = {}) {
|
|
|
4727
4727
|
workspaces,
|
|
4728
4728
|
orchestration: orchestrationConfig,
|
|
4729
4729
|
later: laterConfig,
|
|
4730
|
-
...language ? { language } : {}
|
|
4730
|
+
...language ? { language } : {},
|
|
4731
|
+
...raw.terminal && typeof raw.terminal === "object" ? {
|
|
4732
|
+
terminal: {
|
|
4733
|
+
enabled: raw.terminal.enabled === true,
|
|
4734
|
+
...typeof raw.terminal.idleTimeoutSeconds === "number" ? { idleTimeoutSeconds: raw.terminal.idleTimeoutSeconds } : {}
|
|
4735
|
+
}
|
|
4736
|
+
} : {}
|
|
4731
4737
|
};
|
|
4732
4738
|
}
|
|
4733
4739
|
function parsePluginConfig(raw, index) {
|
|
@@ -25571,6 +25577,15 @@ var init_console_agent = __esm(() => {
|
|
|
25571
25577
|
init_i18n();
|
|
25572
25578
|
});
|
|
25573
25579
|
|
|
25580
|
+
// src/config/types.ts
|
|
25581
|
+
function terminalEnabled(config4) {
|
|
25582
|
+
return config4.terminal?.enabled === true;
|
|
25583
|
+
}
|
|
25584
|
+
function terminalIdleTimeoutSeconds(config4) {
|
|
25585
|
+
const v = config4.terminal?.idleTimeoutSeconds;
|
|
25586
|
+
return typeof v === "number" && v > 0 ? v : 900;
|
|
25587
|
+
}
|
|
25588
|
+
|
|
25574
25589
|
// src/orchestration/orchestration-server.ts
|
|
25575
25590
|
import { chmod as chmod4, rm as rm8 } from "node:fs/promises";
|
|
25576
25591
|
import { createServer } from "node:net";
|
|
@@ -34273,6 +34288,23 @@ ${chunk}` : chunk
|
|
|
34273
34288
|
return chunks.join(`
|
|
34274
34289
|
`);
|
|
34275
34290
|
}
|
|
34291
|
+
async createTerminal(chatKey, sessionAlias, cols, rows) {
|
|
34292
|
+
if (!this.deps.terminalEnabled())
|
|
34293
|
+
throw new Error("terminal-disabled");
|
|
34294
|
+
const session3 = await this.resolveControlSession(chatKey, sessionAlias);
|
|
34295
|
+
if (!session3)
|
|
34296
|
+
throw new Error("session-not-found");
|
|
34297
|
+
return this.deps.terminal.create({ cwd: session3.cwd, cols, rows });
|
|
34298
|
+
}
|
|
34299
|
+
writeTerminal(terminalId, data) {
|
|
34300
|
+
this.deps.terminal.write(terminalId, data);
|
|
34301
|
+
}
|
|
34302
|
+
resizeTerminal(terminalId, cols, rows) {
|
|
34303
|
+
this.deps.terminal.resize(terminalId, cols, rows);
|
|
34304
|
+
}
|
|
34305
|
+
closeTerminal(terminalId) {
|
|
34306
|
+
this.deps.terminal.close(terminalId);
|
|
34307
|
+
}
|
|
34276
34308
|
}
|
|
34277
34309
|
async function raceWithTimeout(promise2, ms) {
|
|
34278
34310
|
let timer;
|
|
@@ -34307,6 +34339,133 @@ var init_control_service = __esm(() => {
|
|
|
34307
34339
|
init_workspace_fs();
|
|
34308
34340
|
});
|
|
34309
34341
|
|
|
34342
|
+
// src/control/terminal-service.ts
|
|
34343
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
34344
|
+
import { createRequire as createRequire6 } from "node:module";
|
|
34345
|
+
import { spawn as spawnPty2 } from "node-pty";
|
|
34346
|
+
function scrubEnv() {
|
|
34347
|
+
const out = {};
|
|
34348
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
34349
|
+
if (v === undefined)
|
|
34350
|
+
continue;
|
|
34351
|
+
if (SENSITIVE_ENV_KEYS.includes(k) || k.startsWith("XACPX_"))
|
|
34352
|
+
continue;
|
|
34353
|
+
out[k] = v;
|
|
34354
|
+
}
|
|
34355
|
+
out.TERM = "xterm-256color";
|
|
34356
|
+
out.LANG = out.LANG ?? "en_US.UTF-8";
|
|
34357
|
+
return out;
|
|
34358
|
+
}
|
|
34359
|
+
function defaultShell(platform) {
|
|
34360
|
+
if (process.env.SHELL)
|
|
34361
|
+
return process.env.SHELL;
|
|
34362
|
+
return platform === "darwin" ? "/bin/zsh" : "/bin/bash";
|
|
34363
|
+
}
|
|
34364
|
+
function realPtySpawn(file, args, opts) {
|
|
34365
|
+
const helperPath = resolveNodePtyHelperPath(require5.resolve("node-pty/package.json"), process.platform, process.arch);
|
|
34366
|
+
ensureNodePtyHelperExecutable(helperPath);
|
|
34367
|
+
return spawnPty2(file, args, opts);
|
|
34368
|
+
}
|
|
34369
|
+
function createTerminalService(deps) {
|
|
34370
|
+
const spawn11 = deps.spawn ?? realPtySpawn;
|
|
34371
|
+
const platform = deps.platform ?? process.platform;
|
|
34372
|
+
const sessions = new Map;
|
|
34373
|
+
const setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
34374
|
+
const clearTimer = deps.clearTimer ?? ((id) => clearTimeout(id));
|
|
34375
|
+
const resetIdle = (terminalId) => {
|
|
34376
|
+
const s = sessions.get(terminalId);
|
|
34377
|
+
if (!s)
|
|
34378
|
+
return;
|
|
34379
|
+
if (s.idleTimer)
|
|
34380
|
+
clearTimer(s.idleTimer);
|
|
34381
|
+
const ms = deps.idleTimeoutSeconds() * 1000;
|
|
34382
|
+
s.idleTimer = setTimer(() => {
|
|
34383
|
+
try {
|
|
34384
|
+
s.handle.kill();
|
|
34385
|
+
} catch {}
|
|
34386
|
+
}, ms);
|
|
34387
|
+
const t2 = s.idleTimer;
|
|
34388
|
+
if (typeof t2.unref === "function")
|
|
34389
|
+
t2.unref();
|
|
34390
|
+
};
|
|
34391
|
+
return {
|
|
34392
|
+
create({ cwd, cols, rows }) {
|
|
34393
|
+
if (platform === "win32")
|
|
34394
|
+
throw new Error("terminal-unsupported-platform");
|
|
34395
|
+
const terminalId = randomUUID3();
|
|
34396
|
+
const handle = spawn11(defaultShell(platform), [], { name: "xterm-256color", cols, rows, cwd, env: scrubEnv() });
|
|
34397
|
+
const session3 = { handle, seq: 0, idleTimer: null };
|
|
34398
|
+
sessions.set(terminalId, session3);
|
|
34399
|
+
handle.onData((data) => {
|
|
34400
|
+
deps.events.emit({ type: "terminal-output", terminalId, seq: session3.seq++, data });
|
|
34401
|
+
});
|
|
34402
|
+
handle.onExit(({ exitCode }) => {
|
|
34403
|
+
if (session3.idleTimer)
|
|
34404
|
+
clearTimer(session3.idleTimer);
|
|
34405
|
+
sessions.delete(terminalId);
|
|
34406
|
+
deps.events.emit({ type: "terminal-exit", terminalId, code: exitCode });
|
|
34407
|
+
});
|
|
34408
|
+
resetIdle(terminalId);
|
|
34409
|
+
return { terminalId };
|
|
34410
|
+
},
|
|
34411
|
+
write(terminalId, data) {
|
|
34412
|
+
const s = sessions.get(terminalId);
|
|
34413
|
+
if (!s)
|
|
34414
|
+
return;
|
|
34415
|
+
try {
|
|
34416
|
+
s.handle.write(data);
|
|
34417
|
+
} catch {}
|
|
34418
|
+
resetIdle(terminalId);
|
|
34419
|
+
},
|
|
34420
|
+
resize(terminalId, cols, rows) {
|
|
34421
|
+
const s = sessions.get(terminalId);
|
|
34422
|
+
if (!s)
|
|
34423
|
+
return;
|
|
34424
|
+
try {
|
|
34425
|
+
s.handle.resize(cols, rows);
|
|
34426
|
+
} catch {}
|
|
34427
|
+
resetIdle(terminalId);
|
|
34428
|
+
},
|
|
34429
|
+
close(terminalId) {
|
|
34430
|
+
const s = sessions.get(terminalId);
|
|
34431
|
+
if (!s)
|
|
34432
|
+
return;
|
|
34433
|
+
try {
|
|
34434
|
+
s.handle.kill();
|
|
34435
|
+
} catch {}
|
|
34436
|
+
},
|
|
34437
|
+
disposeAll() {
|
|
34438
|
+
for (const s of sessions.values()) {
|
|
34439
|
+
if (s.idleTimer)
|
|
34440
|
+
clearTimer(s.idleTimer);
|
|
34441
|
+
try {
|
|
34442
|
+
s.handle.kill();
|
|
34443
|
+
} catch {}
|
|
34444
|
+
}
|
|
34445
|
+
sessions.clear();
|
|
34446
|
+
}
|
|
34447
|
+
};
|
|
34448
|
+
}
|
|
34449
|
+
var require5, SENSITIVE_ENV_KEYS;
|
|
34450
|
+
var init_terminal_service = __esm(() => {
|
|
34451
|
+
init_node_pty_helper();
|
|
34452
|
+
require5 = createRequire6(import.meta.url);
|
|
34453
|
+
SENSITIVE_ENV_KEYS = [
|
|
34454
|
+
"ANTHROPIC_API_KEY",
|
|
34455
|
+
"OPENAI_API_KEY",
|
|
34456
|
+
"OPENROUTER_API_KEY",
|
|
34457
|
+
"GEMINI_API_KEY",
|
|
34458
|
+
"GOOGLE_API_KEY",
|
|
34459
|
+
"DEEPSEEK_API_KEY",
|
|
34460
|
+
"GROQ_API_KEY",
|
|
34461
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
34462
|
+
"AWS_SESSION_TOKEN",
|
|
34463
|
+
"GH_TOKEN",
|
|
34464
|
+
"GITHUB_TOKEN",
|
|
34465
|
+
"NPM_TOKEN"
|
|
34466
|
+
];
|
|
34467
|
+
});
|
|
34468
|
+
|
|
34310
34469
|
// src/control/upload-store.ts
|
|
34311
34470
|
import { mkdtemp as mkdtemp2, readdir as readdir5, rm as rm10, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
|
|
34312
34471
|
import { homedir as homedir13 } from "node:os";
|
|
@@ -34487,7 +34646,7 @@ __export(exports_main, {
|
|
|
34487
34646
|
main: () => main,
|
|
34488
34647
|
buildApp: () => buildApp
|
|
34489
34648
|
});
|
|
34490
|
-
import { randomUUID as
|
|
34649
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
34491
34650
|
import { homedir as homedir14 } from "node:os";
|
|
34492
34651
|
import { dirname as dirname13, join as join23 } from "node:path";
|
|
34493
34652
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
@@ -34867,7 +35026,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
34867
35026
|
};
|
|
34868
35027
|
orchestration3 = new OrchestrationService({
|
|
34869
35028
|
now: deps.loggerNow ?? (() => new Date),
|
|
34870
|
-
createId: () =>
|
|
35029
|
+
createId: () => randomUUID4(),
|
|
34871
35030
|
config: config4,
|
|
34872
35031
|
loadState: async () => JSON.parse(JSON.stringify(state)),
|
|
34873
35032
|
saveState: async (nextState) => {
|
|
@@ -34980,6 +35139,10 @@ async function buildApp(paths, deps = {}) {
|
|
|
34980
35139
|
const router3 = new CommandRouter(sessions, transport, config4, configStore, logger2, undefined, orchestration3, quota, scheduledService, deps.channel?.supportsScheduledMessages ? { supportsScheduledMessages: deps.channel.supportsScheduledMessages.bind(deps.channel) } : undefined, deps.channel?.nativeSessionListFormat ? deps.channel.nativeSessionListFormat.bind(deps.channel) : undefined, activeTurns);
|
|
34981
35140
|
const agent3 = new ConsoleAgent(router3, logger2);
|
|
34982
35141
|
const controlEvents = createControlEventBus(logger2);
|
|
35142
|
+
const terminalService = createTerminalService({
|
|
35143
|
+
events: controlEvents,
|
|
35144
|
+
idleTimeoutSeconds: () => terminalIdleTimeoutSeconds(config4)
|
|
35145
|
+
});
|
|
34983
35146
|
const uploadStore = new UploadStore;
|
|
34984
35147
|
uploadStore.cleanup();
|
|
34985
35148
|
const uploadCleanupInterval = setInterval(() => void uploadStore.cleanup().catch(() => {}), 60 * 60 * 1000);
|
|
@@ -35028,7 +35191,9 @@ async function buildApp(paths, deps = {}) {
|
|
|
35028
35191
|
controlEvents.emit({ type: "workspaces-changed" });
|
|
35029
35192
|
}
|
|
35030
35193
|
},
|
|
35031
|
-
uploadStore
|
|
35194
|
+
uploadStore,
|
|
35195
|
+
terminal: terminalService,
|
|
35196
|
+
terminalEnabled: () => terminalEnabled(config4)
|
|
35032
35197
|
});
|
|
35033
35198
|
const workspaceSignature = (cfg) => JSON.stringify(Object.keys(cfg.workspaces).sort().map((name) => {
|
|
35034
35199
|
const ws = cfg.workspaces[name];
|
|
@@ -35120,6 +35285,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
35120
35285
|
scheduledScheduler.stop();
|
|
35121
35286
|
configWatcher.close();
|
|
35122
35287
|
clearInterval(uploadCleanupInterval);
|
|
35288
|
+
terminalService.disposeAll();
|
|
35123
35289
|
if (progressHeartbeatInterval !== undefined) {
|
|
35124
35290
|
clearInterval(progressHeartbeatInterval);
|
|
35125
35291
|
}
|
|
@@ -35262,6 +35428,7 @@ var init_main = __esm(async () => {
|
|
|
35262
35428
|
init_render_text();
|
|
35263
35429
|
init_quota_manager();
|
|
35264
35430
|
init_control_service();
|
|
35431
|
+
init_terminal_service();
|
|
35265
35432
|
init_upload_store();
|
|
35266
35433
|
init_agent_catalog();
|
|
35267
35434
|
init_config_watcher();
|
|
@@ -36963,7 +37130,7 @@ var init_doctor2 = __esm(async () => {
|
|
|
36963
37130
|
|
|
36964
37131
|
// src/cli.ts
|
|
36965
37132
|
init_core_home();
|
|
36966
|
-
import { randomUUID as
|
|
37133
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
36967
37134
|
import { homedir as homedir20 } from "node:os";
|
|
36968
37135
|
import { dirname as dirname15, join as join27, sep as sep2 } from "node:path";
|
|
36969
37136
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
@@ -52639,7 +52806,7 @@ async function prepareMcpCoordinatorStartup(input) {
|
|
|
52639
52806
|
return { kind: "external-coordinator" };
|
|
52640
52807
|
}
|
|
52641
52808
|
function createMcpStdioIdentityResolver(input) {
|
|
52642
|
-
const instanceId =
|
|
52809
|
+
const instanceId = randomUUID5().slice(0, 8);
|
|
52643
52810
|
return async (context) => {
|
|
52644
52811
|
const parsedCoordinatorSession = input.parsedCoordinatorSession?.trim() || null;
|
|
52645
52812
|
const workspace3 = input.workspace?.trim() || null;
|
package/dist/config/types.d.ts
CHANGED
|
@@ -42,6 +42,12 @@ export interface TransportConfig {
|
|
|
42
42
|
*/
|
|
43
43
|
preferLocalAgents?: boolean;
|
|
44
44
|
}
|
|
45
|
+
export interface TerminalConfig {
|
|
46
|
+
/** Default false. When false, control.terminal.create is rejected before any PTY spawns. */
|
|
47
|
+
enabled: boolean;
|
|
48
|
+
/** Idle seconds before a terminal PTY is auto-killed. Defaults to 900 (15 min). */
|
|
49
|
+
idleTimeoutSeconds?: number;
|
|
50
|
+
}
|
|
45
51
|
export type LoggingLevel = "error" | "info" | "debug";
|
|
46
52
|
export interface PerfLogConfig {
|
|
47
53
|
enabled: boolean;
|
|
@@ -103,4 +109,7 @@ export interface AppConfig {
|
|
|
103
109
|
orchestration: OrchestrationConfig;
|
|
104
110
|
later?: LaterConfig;
|
|
105
111
|
language?: Locale;
|
|
112
|
+
terminal?: TerminalConfig;
|
|
106
113
|
}
|
|
114
|
+
export declare function terminalEnabled(config: AppConfig): boolean;
|
|
115
|
+
export declare function terminalIdleTimeoutSeconds(config: AppConfig): number;
|
|
@@ -64,6 +64,15 @@ export type ControlEvent = {
|
|
|
64
64
|
chatKey: string;
|
|
65
65
|
sessionAlias: string;
|
|
66
66
|
messages: NativeHistoryMessage[];
|
|
67
|
+
} | {
|
|
68
|
+
type: "terminal-output";
|
|
69
|
+
terminalId: string;
|
|
70
|
+
seq: number;
|
|
71
|
+
data: string;
|
|
72
|
+
} | {
|
|
73
|
+
type: "terminal-exit";
|
|
74
|
+
terminalId: string;
|
|
75
|
+
code: number;
|
|
67
76
|
} | {
|
|
68
77
|
type: "orchestration-changed";
|
|
69
78
|
};
|
|
@@ -77,6 +77,8 @@ export interface ControlServiceDeps {
|
|
|
77
77
|
remove(name: string): Promise<void>;
|
|
78
78
|
};
|
|
79
79
|
uploadStore: UploadStore;
|
|
80
|
+
terminal: import("./terminal-service").TerminalService;
|
|
81
|
+
terminalEnabled: () => boolean;
|
|
80
82
|
}
|
|
81
83
|
export interface ControlPromptInput {
|
|
82
84
|
chatKey: string;
|
|
@@ -180,4 +182,11 @@ export declare class ControlService {
|
|
|
180
182
|
private executeTurn;
|
|
181
183
|
cancelTurn(chatKey: string, sessionAlias: string): boolean;
|
|
182
184
|
executeCommand(input: ControlExecuteCommandInput): Promise<string>;
|
|
185
|
+
/** Open an interactive terminal in the session's workspace cwd. Rejected when terminal is disabled. */
|
|
186
|
+
createTerminal(chatKey: string, sessionAlias: string, cols: number, rows: number): Promise<{
|
|
187
|
+
terminalId: string;
|
|
188
|
+
}>;
|
|
189
|
+
writeTerminal(terminalId: string, data: string): void;
|
|
190
|
+
resizeTerminal(terminalId: string, cols: number, rows: number): void;
|
|
191
|
+
closeTerminal(terminalId: string): void;
|
|
183
192
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { ControlEventBus } from "./control-event-bus";
|
|
2
|
+
/**
|
|
3
|
+
* Secret-bearing env keys stripped before handing the shell its environment.
|
|
4
|
+
* Best-effort denylist only — a real shell still inherits the full process env;
|
|
5
|
+
* custom secrets (DATABASE_URL, *_TOKEN, ~/.ssh keys, etc.) not on this list
|
|
6
|
+
* pass through. Do not treat env scrubbing as a security guarantee.
|
|
7
|
+
*/
|
|
8
|
+
export declare const SENSITIVE_ENV_KEYS: string[];
|
|
9
|
+
export interface PtyHandle {
|
|
10
|
+
onData(cb: (data: string) => void): void;
|
|
11
|
+
onExit(cb: (e: {
|
|
12
|
+
exitCode: number;
|
|
13
|
+
}) => void): void;
|
|
14
|
+
write(data: string): void;
|
|
15
|
+
resize(cols: number, rows: number): void;
|
|
16
|
+
kill(): void;
|
|
17
|
+
}
|
|
18
|
+
export type PtySpawn = (file: string, args: string[], opts: {
|
|
19
|
+
name: string;
|
|
20
|
+
cols: number;
|
|
21
|
+
rows: number;
|
|
22
|
+
cwd: string;
|
|
23
|
+
env: Record<string, string>;
|
|
24
|
+
}) => PtyHandle;
|
|
25
|
+
export interface TerminalCreateInput {
|
|
26
|
+
cwd: string;
|
|
27
|
+
cols: number;
|
|
28
|
+
rows: number;
|
|
29
|
+
}
|
|
30
|
+
export interface TerminalService {
|
|
31
|
+
create(input: TerminalCreateInput): {
|
|
32
|
+
terminalId: string;
|
|
33
|
+
};
|
|
34
|
+
write(terminalId: string, data: string): void;
|
|
35
|
+
resize(terminalId: string, cols: number, rows: number): void;
|
|
36
|
+
close(terminalId: string): void;
|
|
37
|
+
disposeAll(): void;
|
|
38
|
+
}
|
|
39
|
+
export interface TerminalServiceDeps {
|
|
40
|
+
events: ControlEventBus;
|
|
41
|
+
idleTimeoutSeconds: () => number;
|
|
42
|
+
spawn?: PtySpawn;
|
|
43
|
+
platform?: NodeJS.Platform;
|
|
44
|
+
/** Injectable timer primitives; defaults to global setTimeout/clearTimeout. */
|
|
45
|
+
setTimer?: (fn: () => void, ms: number) => unknown;
|
|
46
|
+
clearTimer?: (id: unknown) => void;
|
|
47
|
+
}
|
|
48
|
+
export declare function createTerminalService(deps: TerminalServiceDeps): TerminalService;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
type ChmodFunction = (path: string, mode: number) => Promise<void>;
|
|
2
|
+
export declare function resolveNodePtyHelperPath(packageJsonPath: string, platform: NodeJS.Platform, arch: string): string | null;
|
|
3
|
+
export declare function ensureNodePtyHelperExecutable(helperPath: string | null, chmod?: ChmodFunction): Promise<void>;
|
|
4
|
+
export {};
|