@ganglion/xacpx 0.16.0 → 0.17.0-beta.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.
- package/dist/cli.js +262 -17
- package/dist/config/types.d.ts +9 -0
- package/dist/control/control-event-bus.d.ts +22 -0
- package/dist/control/control-service.d.ts +37 -0
- package/dist/control/terminal-service.d.ts +48 -0
- package/dist/transport/acpx-cli/node-pty-helper.d.ts +4 -0
- package/package.json +1 -1
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";
|
|
@@ -33874,6 +33889,7 @@ var init_workspace_fs = __esm(() => {
|
|
|
33874
33889
|
|
|
33875
33890
|
// src/control/control-service.ts
|
|
33876
33891
|
import path15 from "node:path";
|
|
33892
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
33877
33893
|
|
|
33878
33894
|
class ControlService {
|
|
33879
33895
|
deps;
|
|
@@ -34041,12 +34057,23 @@ class ControlService {
|
|
|
34041
34057
|
return task;
|
|
34042
34058
|
}
|
|
34043
34059
|
inFlight = new Map;
|
|
34060
|
+
queues = new Map;
|
|
34061
|
+
draining = new Set;
|
|
34062
|
+
emitQueueUpdated(chatKey, sessionAlias) {
|
|
34063
|
+
const items = (this.queues.get(turnKey(chatKey, sessionAlias)) ?? []).map((q) => ({
|
|
34064
|
+
id: q.id,
|
|
34065
|
+
textPreview: q.text.length > QUEUE_PREVIEW_MAX ? q.text.slice(0, QUEUE_PREVIEW_MAX) : q.text,
|
|
34066
|
+
enqueuedAt: q.enqueuedAt
|
|
34067
|
+
}));
|
|
34068
|
+
this.deps.events.emit({ type: "queue-updated", chatKey, sessionAlias, items });
|
|
34069
|
+
}
|
|
34044
34070
|
async prompt(input) {
|
|
34045
34071
|
return this.executeTurn({
|
|
34046
34072
|
chatKey: input.chatKey,
|
|
34047
34073
|
sessionAlias: input.sessionAlias,
|
|
34048
34074
|
text: input.text,
|
|
34049
34075
|
senderId: input.senderId,
|
|
34076
|
+
queueable: true,
|
|
34050
34077
|
...input.isOwner !== undefined ? { isOwner: input.isOwner } : {},
|
|
34051
34078
|
...input.accountId !== undefined ? { accountId: input.accountId } : {},
|
|
34052
34079
|
...input.media !== undefined ? { media: input.media } : {}
|
|
@@ -34066,14 +34093,34 @@ class ControlService {
|
|
|
34066
34093
|
}
|
|
34067
34094
|
async executeTurn(params) {
|
|
34068
34095
|
const key = turnKey(params.chatKey, params.sessionAlias);
|
|
34069
|
-
|
|
34070
|
-
|
|
34071
|
-
|
|
34096
|
+
if (!params.drained) {
|
|
34097
|
+
const existing = this.inFlight.get(key);
|
|
34098
|
+
const busy = this.draining.has(key) || existing !== undefined && !existing.controller.signal.aborted;
|
|
34099
|
+
if (busy) {
|
|
34100
|
+
if (params.queueable) {
|
|
34101
|
+
const id = randomUUID3();
|
|
34102
|
+
const item = {
|
|
34103
|
+
id,
|
|
34104
|
+
text: params.text,
|
|
34105
|
+
enqueuedAt: new Date().toISOString(),
|
|
34106
|
+
senderId: params.senderId,
|
|
34107
|
+
...params.isOwner !== undefined ? { isOwner: params.isOwner } : {},
|
|
34108
|
+
...params.accountId !== undefined ? { accountId: params.accountId } : {},
|
|
34109
|
+
...params.media !== undefined ? { media: params.media } : {}
|
|
34110
|
+
};
|
|
34111
|
+
const q = this.queues.get(key) ?? [];
|
|
34112
|
+
q.push(item);
|
|
34113
|
+
this.queues.set(key, q);
|
|
34114
|
+
this.emitQueueUpdated(params.chatKey, params.sessionAlias);
|
|
34115
|
+
return { ok: true, queued: true, queueItemId: id };
|
|
34116
|
+
}
|
|
34072
34117
|
return { ok: false, errorMessage: "turn-already-running" };
|
|
34073
34118
|
}
|
|
34074
|
-
|
|
34075
|
-
|
|
34076
|
-
|
|
34119
|
+
if (existing) {
|
|
34120
|
+
await raceWithTimeout(existing.settled, CANCEL_DRAIN_TIMEOUT_MS);
|
|
34121
|
+
if (this.inFlight.has(key)) {
|
|
34122
|
+
return { ok: false, errorMessage: "turn-already-running" };
|
|
34123
|
+
}
|
|
34077
34124
|
}
|
|
34078
34125
|
}
|
|
34079
34126
|
const controller = new AbortController;
|
|
@@ -34088,6 +34135,9 @@ class ControlService {
|
|
|
34088
34135
|
resolveSettled = resolve4;
|
|
34089
34136
|
});
|
|
34090
34137
|
this.inFlight.set(key, { controller, settled });
|
|
34138
|
+
if (params.drained) {
|
|
34139
|
+
this.draining.delete(key);
|
|
34140
|
+
}
|
|
34091
34141
|
let internalAlias;
|
|
34092
34142
|
let wasArchived = false;
|
|
34093
34143
|
let priorTransportSession;
|
|
@@ -34100,8 +34150,8 @@ class ControlService {
|
|
|
34100
34150
|
try {
|
|
34101
34151
|
await this.deps.sessions.useSession(params.chatKey, params.sessionAlias);
|
|
34102
34152
|
} catch (error2) {
|
|
34103
|
-
this.inFlight.delete(key);
|
|
34104
34153
|
resolveSettled();
|
|
34154
|
+
this.advanceQueue(key, params.chatKey, params.sessionAlias);
|
|
34105
34155
|
return { ok: false, errorMessage: toErrorMessage(error2) };
|
|
34106
34156
|
}
|
|
34107
34157
|
if (wasArchived) {
|
|
@@ -34112,7 +34162,8 @@ class ControlService {
|
|
|
34112
34162
|
chatKey: params.chatKey,
|
|
34113
34163
|
sessionAlias: params.sessionAlias,
|
|
34114
34164
|
...params.turnStarted?.prompt ? { prompt: params.turnStarted.prompt } : {},
|
|
34115
|
-
...params.turnStarted?.scheduled ? { scheduled: params.turnStarted.scheduled } : {}
|
|
34165
|
+
...params.turnStarted?.scheduled ? { scheduled: params.turnStarted.scheduled } : {},
|
|
34166
|
+
...params.turnStarted?.queueItemId ? { queueItemId: params.turnStarted.queueItemId } : {}
|
|
34116
34167
|
});
|
|
34117
34168
|
let streamMode = false;
|
|
34118
34169
|
try {
|
|
@@ -34236,8 +34287,9 @@ ${chunk}` : chunk
|
|
|
34236
34287
|
});
|
|
34237
34288
|
return { ok: false, errorMessage };
|
|
34238
34289
|
} finally {
|
|
34239
|
-
this.
|
|
34240
|
-
|
|
34290
|
+
if ((this.queues.get(key)?.length ?? 0) > 0) {
|
|
34291
|
+
this.draining.add(key);
|
|
34292
|
+
}
|
|
34241
34293
|
if (internalAlias && priorTransportSession) {
|
|
34242
34294
|
try {
|
|
34243
34295
|
const after = await this.deps.sessions.getSession(internalAlias);
|
|
@@ -34246,6 +34298,33 @@ ${chunk}` : chunk
|
|
|
34246
34298
|
}
|
|
34247
34299
|
} catch {}
|
|
34248
34300
|
}
|
|
34301
|
+
resolveSettled();
|
|
34302
|
+
this.advanceQueue(key, params.chatKey, params.sessionAlias);
|
|
34303
|
+
}
|
|
34304
|
+
}
|
|
34305
|
+
advanceQueue(key, chatKey, sessionAlias) {
|
|
34306
|
+
const q = this.queues.get(key);
|
|
34307
|
+
const next = q?.shift();
|
|
34308
|
+
if (q && q.length === 0)
|
|
34309
|
+
this.queues.delete(key);
|
|
34310
|
+
if (next) {
|
|
34311
|
+
this.draining.add(key);
|
|
34312
|
+
this.emitQueueUpdated(chatKey, sessionAlias);
|
|
34313
|
+
this.executeTurn({
|
|
34314
|
+
chatKey,
|
|
34315
|
+
sessionAlias,
|
|
34316
|
+
text: next.text,
|
|
34317
|
+
senderId: next.senderId,
|
|
34318
|
+
queueable: true,
|
|
34319
|
+
drained: true,
|
|
34320
|
+
...next.isOwner !== undefined ? { isOwner: next.isOwner } : {},
|
|
34321
|
+
...next.accountId !== undefined ? { accountId: next.accountId } : {},
|
|
34322
|
+
...next.media !== undefined ? { media: next.media } : {},
|
|
34323
|
+
turnStarted: { queueItemId: next.id }
|
|
34324
|
+
});
|
|
34325
|
+
} else {
|
|
34326
|
+
this.draining.delete(key);
|
|
34327
|
+
this.inFlight.delete(key);
|
|
34249
34328
|
}
|
|
34250
34329
|
}
|
|
34251
34330
|
cancelTurn(chatKey, sessionAlias) {
|
|
@@ -34256,6 +34335,20 @@ ${chunk}` : chunk
|
|
|
34256
34335
|
entry.controller.abort();
|
|
34257
34336
|
return true;
|
|
34258
34337
|
}
|
|
34338
|
+
cancelQueuedItem(chatKey, sessionAlias, itemId) {
|
|
34339
|
+
const key = turnKey(chatKey, sessionAlias);
|
|
34340
|
+
const q = this.queues.get(key);
|
|
34341
|
+
if (!q)
|
|
34342
|
+
return { cancelled: false };
|
|
34343
|
+
const i = q.findIndex((x) => x.id === itemId);
|
|
34344
|
+
if (i < 0)
|
|
34345
|
+
return { cancelled: false };
|
|
34346
|
+
q.splice(i, 1);
|
|
34347
|
+
if (q.length === 0)
|
|
34348
|
+
this.queues.delete(key);
|
|
34349
|
+
this.emitQueueUpdated(chatKey, sessionAlias);
|
|
34350
|
+
return { cancelled: true };
|
|
34351
|
+
}
|
|
34259
34352
|
async executeCommand(input) {
|
|
34260
34353
|
const chunks = [];
|
|
34261
34354
|
const response = await this.deps.agent.chat({
|
|
@@ -34273,6 +34366,23 @@ ${chunk}` : chunk
|
|
|
34273
34366
|
return chunks.join(`
|
|
34274
34367
|
`);
|
|
34275
34368
|
}
|
|
34369
|
+
async createTerminal(chatKey, sessionAlias, cols, rows) {
|
|
34370
|
+
if (!this.deps.terminalEnabled())
|
|
34371
|
+
throw new Error("terminal-disabled");
|
|
34372
|
+
const session3 = await this.resolveControlSession(chatKey, sessionAlias);
|
|
34373
|
+
if (!session3)
|
|
34374
|
+
throw new Error("session-not-found");
|
|
34375
|
+
return this.deps.terminal.create({ cwd: session3.cwd, cols, rows });
|
|
34376
|
+
}
|
|
34377
|
+
writeTerminal(terminalId, data) {
|
|
34378
|
+
this.deps.terminal.write(terminalId, data);
|
|
34379
|
+
}
|
|
34380
|
+
resizeTerminal(terminalId, cols, rows) {
|
|
34381
|
+
this.deps.terminal.resize(terminalId, cols, rows);
|
|
34382
|
+
}
|
|
34383
|
+
closeTerminal(terminalId) {
|
|
34384
|
+
this.deps.terminal.close(terminalId);
|
|
34385
|
+
}
|
|
34276
34386
|
}
|
|
34277
34387
|
async function raceWithTimeout(promise2, ms) {
|
|
34278
34388
|
let timer;
|
|
@@ -34300,13 +34410,140 @@ function buildControlMetadata(senderId, isOwner) {
|
|
|
34300
34410
|
...isOwner === undefined ? {} : { isOwner }
|
|
34301
34411
|
};
|
|
34302
34412
|
}
|
|
34303
|
-
var CANCEL_DRAIN_TIMEOUT_MS = 5000;
|
|
34413
|
+
var CANCEL_DRAIN_TIMEOUT_MS = 5000, QUEUE_PREVIEW_MAX = 120;
|
|
34304
34414
|
var init_control_service = __esm(() => {
|
|
34305
34415
|
init_channel_scope();
|
|
34306
34416
|
init_native_session_history();
|
|
34307
34417
|
init_workspace_fs();
|
|
34308
34418
|
});
|
|
34309
34419
|
|
|
34420
|
+
// src/control/terminal-service.ts
|
|
34421
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
34422
|
+
import { createRequire as createRequire6 } from "node:module";
|
|
34423
|
+
import { spawn as spawnPty2 } from "node-pty";
|
|
34424
|
+
function scrubEnv() {
|
|
34425
|
+
const out = {};
|
|
34426
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
34427
|
+
if (v === undefined)
|
|
34428
|
+
continue;
|
|
34429
|
+
if (SENSITIVE_ENV_KEYS.includes(k) || k.startsWith("XACPX_"))
|
|
34430
|
+
continue;
|
|
34431
|
+
out[k] = v;
|
|
34432
|
+
}
|
|
34433
|
+
out.TERM = "xterm-256color";
|
|
34434
|
+
out.LANG = out.LANG ?? "en_US.UTF-8";
|
|
34435
|
+
return out;
|
|
34436
|
+
}
|
|
34437
|
+
function defaultShell(platform) {
|
|
34438
|
+
if (process.env.SHELL)
|
|
34439
|
+
return process.env.SHELL;
|
|
34440
|
+
return platform === "darwin" ? "/bin/zsh" : "/bin/bash";
|
|
34441
|
+
}
|
|
34442
|
+
function realPtySpawn(file, args, opts) {
|
|
34443
|
+
const helperPath = resolveNodePtyHelperPath(require5.resolve("node-pty/package.json"), process.platform, process.arch);
|
|
34444
|
+
ensureNodePtyHelperExecutable(helperPath);
|
|
34445
|
+
return spawnPty2(file, args, opts);
|
|
34446
|
+
}
|
|
34447
|
+
function createTerminalService(deps) {
|
|
34448
|
+
const spawn11 = deps.spawn ?? realPtySpawn;
|
|
34449
|
+
const platform = deps.platform ?? process.platform;
|
|
34450
|
+
const sessions = new Map;
|
|
34451
|
+
const setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
34452
|
+
const clearTimer = deps.clearTimer ?? ((id) => clearTimeout(id));
|
|
34453
|
+
const resetIdle = (terminalId) => {
|
|
34454
|
+
const s = sessions.get(terminalId);
|
|
34455
|
+
if (!s)
|
|
34456
|
+
return;
|
|
34457
|
+
if (s.idleTimer)
|
|
34458
|
+
clearTimer(s.idleTimer);
|
|
34459
|
+
const ms = deps.idleTimeoutSeconds() * 1000;
|
|
34460
|
+
s.idleTimer = setTimer(() => {
|
|
34461
|
+
try {
|
|
34462
|
+
s.handle.kill();
|
|
34463
|
+
} catch {}
|
|
34464
|
+
}, ms);
|
|
34465
|
+
const t2 = s.idleTimer;
|
|
34466
|
+
if (typeof t2.unref === "function")
|
|
34467
|
+
t2.unref();
|
|
34468
|
+
};
|
|
34469
|
+
return {
|
|
34470
|
+
create({ cwd, cols, rows }) {
|
|
34471
|
+
if (platform === "win32")
|
|
34472
|
+
throw new Error("terminal-unsupported-platform");
|
|
34473
|
+
const terminalId = randomUUID4();
|
|
34474
|
+
const handle = spawn11(defaultShell(platform), [], { name: "xterm-256color", cols, rows, cwd, env: scrubEnv() });
|
|
34475
|
+
const session3 = { handle, seq: 0, idleTimer: null };
|
|
34476
|
+
sessions.set(terminalId, session3);
|
|
34477
|
+
handle.onData((data) => {
|
|
34478
|
+
deps.events.emit({ type: "terminal-output", terminalId, seq: session3.seq++, data });
|
|
34479
|
+
});
|
|
34480
|
+
handle.onExit(({ exitCode }) => {
|
|
34481
|
+
if (session3.idleTimer)
|
|
34482
|
+
clearTimer(session3.idleTimer);
|
|
34483
|
+
sessions.delete(terminalId);
|
|
34484
|
+
deps.events.emit({ type: "terminal-exit", terminalId, code: exitCode });
|
|
34485
|
+
});
|
|
34486
|
+
resetIdle(terminalId);
|
|
34487
|
+
return { terminalId };
|
|
34488
|
+
},
|
|
34489
|
+
write(terminalId, data) {
|
|
34490
|
+
const s = sessions.get(terminalId);
|
|
34491
|
+
if (!s)
|
|
34492
|
+
return;
|
|
34493
|
+
try {
|
|
34494
|
+
s.handle.write(data);
|
|
34495
|
+
} catch {}
|
|
34496
|
+
resetIdle(terminalId);
|
|
34497
|
+
},
|
|
34498
|
+
resize(terminalId, cols, rows) {
|
|
34499
|
+
const s = sessions.get(terminalId);
|
|
34500
|
+
if (!s)
|
|
34501
|
+
return;
|
|
34502
|
+
try {
|
|
34503
|
+
s.handle.resize(cols, rows);
|
|
34504
|
+
} catch {}
|
|
34505
|
+
resetIdle(terminalId);
|
|
34506
|
+
},
|
|
34507
|
+
close(terminalId) {
|
|
34508
|
+
const s = sessions.get(terminalId);
|
|
34509
|
+
if (!s)
|
|
34510
|
+
return;
|
|
34511
|
+
try {
|
|
34512
|
+
s.handle.kill();
|
|
34513
|
+
} catch {}
|
|
34514
|
+
},
|
|
34515
|
+
disposeAll() {
|
|
34516
|
+
for (const s of sessions.values()) {
|
|
34517
|
+
if (s.idleTimer)
|
|
34518
|
+
clearTimer(s.idleTimer);
|
|
34519
|
+
try {
|
|
34520
|
+
s.handle.kill();
|
|
34521
|
+
} catch {}
|
|
34522
|
+
}
|
|
34523
|
+
sessions.clear();
|
|
34524
|
+
}
|
|
34525
|
+
};
|
|
34526
|
+
}
|
|
34527
|
+
var require5, SENSITIVE_ENV_KEYS;
|
|
34528
|
+
var init_terminal_service = __esm(() => {
|
|
34529
|
+
init_node_pty_helper();
|
|
34530
|
+
require5 = createRequire6(import.meta.url);
|
|
34531
|
+
SENSITIVE_ENV_KEYS = [
|
|
34532
|
+
"ANTHROPIC_API_KEY",
|
|
34533
|
+
"OPENAI_API_KEY",
|
|
34534
|
+
"OPENROUTER_API_KEY",
|
|
34535
|
+
"GEMINI_API_KEY",
|
|
34536
|
+
"GOOGLE_API_KEY",
|
|
34537
|
+
"DEEPSEEK_API_KEY",
|
|
34538
|
+
"GROQ_API_KEY",
|
|
34539
|
+
"AWS_SECRET_ACCESS_KEY",
|
|
34540
|
+
"AWS_SESSION_TOKEN",
|
|
34541
|
+
"GH_TOKEN",
|
|
34542
|
+
"GITHUB_TOKEN",
|
|
34543
|
+
"NPM_TOKEN"
|
|
34544
|
+
];
|
|
34545
|
+
});
|
|
34546
|
+
|
|
34310
34547
|
// src/control/upload-store.ts
|
|
34311
34548
|
import { mkdtemp as mkdtemp2, readdir as readdir5, rm as rm10, stat as stat4, writeFile as writeFile8 } from "node:fs/promises";
|
|
34312
34549
|
import { homedir as homedir13 } from "node:os";
|
|
@@ -34487,7 +34724,7 @@ __export(exports_main, {
|
|
|
34487
34724
|
main: () => main,
|
|
34488
34725
|
buildApp: () => buildApp
|
|
34489
34726
|
});
|
|
34490
|
-
import { randomUUID as
|
|
34727
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
34491
34728
|
import { homedir as homedir14 } from "node:os";
|
|
34492
34729
|
import { dirname as dirname13, join as join23 } from "node:path";
|
|
34493
34730
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
@@ -34867,7 +35104,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
34867
35104
|
};
|
|
34868
35105
|
orchestration3 = new OrchestrationService({
|
|
34869
35106
|
now: deps.loggerNow ?? (() => new Date),
|
|
34870
|
-
createId: () =>
|
|
35107
|
+
createId: () => randomUUID5(),
|
|
34871
35108
|
config: config4,
|
|
34872
35109
|
loadState: async () => JSON.parse(JSON.stringify(state)),
|
|
34873
35110
|
saveState: async (nextState) => {
|
|
@@ -34980,6 +35217,10 @@ async function buildApp(paths, deps = {}) {
|
|
|
34980
35217
|
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
35218
|
const agent3 = new ConsoleAgent(router3, logger2);
|
|
34982
35219
|
const controlEvents = createControlEventBus(logger2);
|
|
35220
|
+
const terminalService = createTerminalService({
|
|
35221
|
+
events: controlEvents,
|
|
35222
|
+
idleTimeoutSeconds: () => terminalIdleTimeoutSeconds(config4)
|
|
35223
|
+
});
|
|
34983
35224
|
const uploadStore = new UploadStore;
|
|
34984
35225
|
uploadStore.cleanup();
|
|
34985
35226
|
const uploadCleanupInterval = setInterval(() => void uploadStore.cleanup().catch(() => {}), 60 * 60 * 1000);
|
|
@@ -35028,7 +35269,9 @@ async function buildApp(paths, deps = {}) {
|
|
|
35028
35269
|
controlEvents.emit({ type: "workspaces-changed" });
|
|
35029
35270
|
}
|
|
35030
35271
|
},
|
|
35031
|
-
uploadStore
|
|
35272
|
+
uploadStore,
|
|
35273
|
+
terminal: terminalService,
|
|
35274
|
+
terminalEnabled: () => terminalEnabled(config4)
|
|
35032
35275
|
});
|
|
35033
35276
|
const workspaceSignature = (cfg) => JSON.stringify(Object.keys(cfg.workspaces).sort().map((name) => {
|
|
35034
35277
|
const ws = cfg.workspaces[name];
|
|
@@ -35120,6 +35363,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
35120
35363
|
scheduledScheduler.stop();
|
|
35121
35364
|
configWatcher.close();
|
|
35122
35365
|
clearInterval(uploadCleanupInterval);
|
|
35366
|
+
terminalService.disposeAll();
|
|
35123
35367
|
if (progressHeartbeatInterval !== undefined) {
|
|
35124
35368
|
clearInterval(progressHeartbeatInterval);
|
|
35125
35369
|
}
|
|
@@ -35262,6 +35506,7 @@ var init_main = __esm(async () => {
|
|
|
35262
35506
|
init_render_text();
|
|
35263
35507
|
init_quota_manager();
|
|
35264
35508
|
init_control_service();
|
|
35509
|
+
init_terminal_service();
|
|
35265
35510
|
init_upload_store();
|
|
35266
35511
|
init_agent_catalog();
|
|
35267
35512
|
init_config_watcher();
|
|
@@ -36963,7 +37208,7 @@ var init_doctor2 = __esm(async () => {
|
|
|
36963
37208
|
|
|
36964
37209
|
// src/cli.ts
|
|
36965
37210
|
init_core_home();
|
|
36966
|
-
import { randomUUID as
|
|
37211
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
36967
37212
|
import { homedir as homedir20 } from "node:os";
|
|
36968
37213
|
import { dirname as dirname15, join as join27, sep as sep2 } from "node:path";
|
|
36969
37214
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
@@ -52639,7 +52884,7 @@ async function prepareMcpCoordinatorStartup(input) {
|
|
|
52639
52884
|
return { kind: "external-coordinator" };
|
|
52640
52885
|
}
|
|
52641
52886
|
function createMcpStdioIdentityResolver(input) {
|
|
52642
|
-
const instanceId =
|
|
52887
|
+
const instanceId = randomUUID6().slice(0, 8);
|
|
52643
52888
|
return async (context) => {
|
|
52644
52889
|
const parsedCoordinatorSession = input.parsedCoordinatorSession?.trim() || null;
|
|
52645
52890
|
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;
|
|
@@ -6,6 +6,13 @@ export interface ScheduledOrigin {
|
|
|
6
6
|
taskId: string;
|
|
7
7
|
executeAt: string;
|
|
8
8
|
}
|
|
9
|
+
/** A single pending item in the per-session server-side prompt queue, as surfaced on
|
|
10
|
+
* the wire by `queue-updated`. `textPreview` is truncated server-side (~120 chars). */
|
|
11
|
+
export interface QueuedItemInfo {
|
|
12
|
+
id: string;
|
|
13
|
+
textPreview: string;
|
|
14
|
+
enqueuedAt: string;
|
|
15
|
+
}
|
|
9
16
|
export type ControlEvent = {
|
|
10
17
|
type: "turn-output";
|
|
11
18
|
chatKey: string;
|
|
@@ -17,6 +24,12 @@ export type ControlEvent = {
|
|
|
17
24
|
sessionAlias: string;
|
|
18
25
|
prompt?: string;
|
|
19
26
|
scheduled?: ScheduledOrigin;
|
|
27
|
+
queueItemId?: string;
|
|
28
|
+
} | {
|
|
29
|
+
type: "queue-updated";
|
|
30
|
+
chatKey: string;
|
|
31
|
+
sessionAlias: string;
|
|
32
|
+
items: QueuedItemInfo[];
|
|
20
33
|
} | {
|
|
21
34
|
type: "tool-event";
|
|
22
35
|
chatKey: string;
|
|
@@ -64,6 +77,15 @@ export type ControlEvent = {
|
|
|
64
77
|
chatKey: string;
|
|
65
78
|
sessionAlias: string;
|
|
66
79
|
messages: NativeHistoryMessage[];
|
|
80
|
+
} | {
|
|
81
|
+
type: "terminal-output";
|
|
82
|
+
terminalId: string;
|
|
83
|
+
seq: number;
|
|
84
|
+
data: string;
|
|
85
|
+
} | {
|
|
86
|
+
type: "terminal-exit";
|
|
87
|
+
terminalId: string;
|
|
88
|
+
code: number;
|
|
67
89
|
} | {
|
|
68
90
|
type: "orchestration-changed";
|
|
69
91
|
};
|
|
@@ -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;
|
|
@@ -91,6 +93,23 @@ export interface ControlPromptResult {
|
|
|
91
93
|
ok: boolean;
|
|
92
94
|
text?: string;
|
|
93
95
|
errorMessage?: string;
|
|
96
|
+
/** True when this prompt did not run immediately and was instead appended to the
|
|
97
|
+
* per-session server-side queue (a turn was already in flight). */
|
|
98
|
+
queued?: boolean;
|
|
99
|
+
/** Id of the queued item, present only when `queued` is true. Used to cancel it via
|
|
100
|
+
* `cancelQueuedItem` before it drains. */
|
|
101
|
+
queueItemId?: string;
|
|
102
|
+
}
|
|
103
|
+
/** A prompt held in the per-session server-side queue while a turn is in flight.
|
|
104
|
+
* Runs through the same `executeTurn` machinery as a manual prompt once drained. */
|
|
105
|
+
export interface QueuedPrompt {
|
|
106
|
+
id: string;
|
|
107
|
+
text: string;
|
|
108
|
+
enqueuedAt: string;
|
|
109
|
+
senderId: string;
|
|
110
|
+
isOwner?: boolean;
|
|
111
|
+
accountId?: string;
|
|
112
|
+
media?: PromptAttachmentRef[];
|
|
94
113
|
}
|
|
95
114
|
/** A turn started by a fired scheduled task. Runs through the same agent + turn-event
|
|
96
115
|
* machinery as a normal prompt, so it streams live and persists to history — but it
|
|
@@ -171,6 +190,9 @@ export declare class ControlService {
|
|
|
171
190
|
getOrchestrationTask(taskId: string): Promise<OrchestrationTaskRecord | null>;
|
|
172
191
|
cancelOrchestrationTask(input: CancelTaskInput): Promise<OrchestrationTaskRecord>;
|
|
173
192
|
private readonly inFlight;
|
|
193
|
+
private readonly queues;
|
|
194
|
+
private readonly draining;
|
|
195
|
+
private emitQueueUpdated;
|
|
174
196
|
prompt(input: ControlPromptInput): Promise<ControlPromptResult>;
|
|
175
197
|
/** Run a fired scheduled task as a real turn through the same machinery as a manual
|
|
176
198
|
* prompt — so it streams live and persists to history — while tagging turn-started
|
|
@@ -178,6 +200,21 @@ export declare class ControlService {
|
|
|
178
200
|
* the web can badge it. Owner-authorized: the task was owner-gated at creation. */
|
|
179
201
|
runScheduledTurn(input: ControlScheduledTurnInput): Promise<ControlPromptResult>;
|
|
180
202
|
private executeTurn;
|
|
203
|
+
private advanceQueue;
|
|
181
204
|
cancelTurn(chatKey: string, sessionAlias: string): boolean;
|
|
205
|
+
/** Remove a pending queued prompt (by id) before it drains. No-ops (returns
|
|
206
|
+
* `{ cancelled: false }`) when the queue or the id is absent/already drained —
|
|
207
|
+
* e.g. a race where the item drained into a running turn just before the cancel
|
|
208
|
+
* arrived. Does NOT touch a turn that is already running (use `cancelTurn`). */
|
|
209
|
+
cancelQueuedItem(chatKey: string, sessionAlias: string, itemId: string): {
|
|
210
|
+
cancelled: boolean;
|
|
211
|
+
};
|
|
182
212
|
executeCommand(input: ControlExecuteCommandInput): Promise<string>;
|
|
213
|
+
/** Open an interactive terminal in the session's workspace cwd. Rejected when terminal is disabled. */
|
|
214
|
+
createTerminal(chatKey: string, sessionAlias: string, cols: number, rows: number): Promise<{
|
|
215
|
+
terminalId: string;
|
|
216
|
+
}>;
|
|
217
|
+
writeTerminal(terminalId: string, data: string): void;
|
|
218
|
+
resizeTerminal(terminalId: string, cols: number, rows: number): void;
|
|
219
|
+
closeTerminal(terminalId: string): void;
|
|
183
220
|
}
|
|
@@ -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 {};
|