@ganglion/xacpx 0.17.0-beta.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 +95 -17
- package/dist/control/control-event-bus.d.ts +13 -0
- package/dist/control/control-service.d.ts +28 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -33889,6 +33889,7 @@ var init_workspace_fs = __esm(() => {
|
|
|
33889
33889
|
|
|
33890
33890
|
// src/control/control-service.ts
|
|
33891
33891
|
import path15 from "node:path";
|
|
33892
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
33892
33893
|
|
|
33893
33894
|
class ControlService {
|
|
33894
33895
|
deps;
|
|
@@ -34056,12 +34057,23 @@ class ControlService {
|
|
|
34056
34057
|
return task;
|
|
34057
34058
|
}
|
|
34058
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
|
+
}
|
|
34059
34070
|
async prompt(input) {
|
|
34060
34071
|
return this.executeTurn({
|
|
34061
34072
|
chatKey: input.chatKey,
|
|
34062
34073
|
sessionAlias: input.sessionAlias,
|
|
34063
34074
|
text: input.text,
|
|
34064
34075
|
senderId: input.senderId,
|
|
34076
|
+
queueable: true,
|
|
34065
34077
|
...input.isOwner !== undefined ? { isOwner: input.isOwner } : {},
|
|
34066
34078
|
...input.accountId !== undefined ? { accountId: input.accountId } : {},
|
|
34067
34079
|
...input.media !== undefined ? { media: input.media } : {}
|
|
@@ -34081,14 +34093,34 @@ class ControlService {
|
|
|
34081
34093
|
}
|
|
34082
34094
|
async executeTurn(params) {
|
|
34083
34095
|
const key = turnKey(params.chatKey, params.sessionAlias);
|
|
34084
|
-
|
|
34085
|
-
|
|
34086
|
-
|
|
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
|
+
}
|
|
34087
34117
|
return { ok: false, errorMessage: "turn-already-running" };
|
|
34088
34118
|
}
|
|
34089
|
-
|
|
34090
|
-
|
|
34091
|
-
|
|
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
|
+
}
|
|
34092
34124
|
}
|
|
34093
34125
|
}
|
|
34094
34126
|
const controller = new AbortController;
|
|
@@ -34103,6 +34135,9 @@ class ControlService {
|
|
|
34103
34135
|
resolveSettled = resolve4;
|
|
34104
34136
|
});
|
|
34105
34137
|
this.inFlight.set(key, { controller, settled });
|
|
34138
|
+
if (params.drained) {
|
|
34139
|
+
this.draining.delete(key);
|
|
34140
|
+
}
|
|
34106
34141
|
let internalAlias;
|
|
34107
34142
|
let wasArchived = false;
|
|
34108
34143
|
let priorTransportSession;
|
|
@@ -34115,8 +34150,8 @@ class ControlService {
|
|
|
34115
34150
|
try {
|
|
34116
34151
|
await this.deps.sessions.useSession(params.chatKey, params.sessionAlias);
|
|
34117
34152
|
} catch (error2) {
|
|
34118
|
-
this.inFlight.delete(key);
|
|
34119
34153
|
resolveSettled();
|
|
34154
|
+
this.advanceQueue(key, params.chatKey, params.sessionAlias);
|
|
34120
34155
|
return { ok: false, errorMessage: toErrorMessage(error2) };
|
|
34121
34156
|
}
|
|
34122
34157
|
if (wasArchived) {
|
|
@@ -34127,7 +34162,8 @@ class ControlService {
|
|
|
34127
34162
|
chatKey: params.chatKey,
|
|
34128
34163
|
sessionAlias: params.sessionAlias,
|
|
34129
34164
|
...params.turnStarted?.prompt ? { prompt: params.turnStarted.prompt } : {},
|
|
34130
|
-
...params.turnStarted?.scheduled ? { scheduled: params.turnStarted.scheduled } : {}
|
|
34165
|
+
...params.turnStarted?.scheduled ? { scheduled: params.turnStarted.scheduled } : {},
|
|
34166
|
+
...params.turnStarted?.queueItemId ? { queueItemId: params.turnStarted.queueItemId } : {}
|
|
34131
34167
|
});
|
|
34132
34168
|
let streamMode = false;
|
|
34133
34169
|
try {
|
|
@@ -34251,8 +34287,9 @@ ${chunk}` : chunk
|
|
|
34251
34287
|
});
|
|
34252
34288
|
return { ok: false, errorMessage };
|
|
34253
34289
|
} finally {
|
|
34254
|
-
this.
|
|
34255
|
-
|
|
34290
|
+
if ((this.queues.get(key)?.length ?? 0) > 0) {
|
|
34291
|
+
this.draining.add(key);
|
|
34292
|
+
}
|
|
34256
34293
|
if (internalAlias && priorTransportSession) {
|
|
34257
34294
|
try {
|
|
34258
34295
|
const after = await this.deps.sessions.getSession(internalAlias);
|
|
@@ -34261,6 +34298,33 @@ ${chunk}` : chunk
|
|
|
34261
34298
|
}
|
|
34262
34299
|
} catch {}
|
|
34263
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);
|
|
34264
34328
|
}
|
|
34265
34329
|
}
|
|
34266
34330
|
cancelTurn(chatKey, sessionAlias) {
|
|
@@ -34271,6 +34335,20 @@ ${chunk}` : chunk
|
|
|
34271
34335
|
entry.controller.abort();
|
|
34272
34336
|
return true;
|
|
34273
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
|
+
}
|
|
34274
34352
|
async executeCommand(input) {
|
|
34275
34353
|
const chunks = [];
|
|
34276
34354
|
const response = await this.deps.agent.chat({
|
|
@@ -34332,7 +34410,7 @@ function buildControlMetadata(senderId, isOwner) {
|
|
|
34332
34410
|
...isOwner === undefined ? {} : { isOwner }
|
|
34333
34411
|
};
|
|
34334
34412
|
}
|
|
34335
|
-
var CANCEL_DRAIN_TIMEOUT_MS = 5000;
|
|
34413
|
+
var CANCEL_DRAIN_TIMEOUT_MS = 5000, QUEUE_PREVIEW_MAX = 120;
|
|
34336
34414
|
var init_control_service = __esm(() => {
|
|
34337
34415
|
init_channel_scope();
|
|
34338
34416
|
init_native_session_history();
|
|
@@ -34340,7 +34418,7 @@ var init_control_service = __esm(() => {
|
|
|
34340
34418
|
});
|
|
34341
34419
|
|
|
34342
34420
|
// src/control/terminal-service.ts
|
|
34343
|
-
import { randomUUID as
|
|
34421
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
34344
34422
|
import { createRequire as createRequire6 } from "node:module";
|
|
34345
34423
|
import { spawn as spawnPty2 } from "node-pty";
|
|
34346
34424
|
function scrubEnv() {
|
|
@@ -34392,7 +34470,7 @@ function createTerminalService(deps) {
|
|
|
34392
34470
|
create({ cwd, cols, rows }) {
|
|
34393
34471
|
if (platform === "win32")
|
|
34394
34472
|
throw new Error("terminal-unsupported-platform");
|
|
34395
|
-
const terminalId =
|
|
34473
|
+
const terminalId = randomUUID4();
|
|
34396
34474
|
const handle = spawn11(defaultShell(platform), [], { name: "xterm-256color", cols, rows, cwd, env: scrubEnv() });
|
|
34397
34475
|
const session3 = { handle, seq: 0, idleTimer: null };
|
|
34398
34476
|
sessions.set(terminalId, session3);
|
|
@@ -34646,7 +34724,7 @@ __export(exports_main, {
|
|
|
34646
34724
|
main: () => main,
|
|
34647
34725
|
buildApp: () => buildApp
|
|
34648
34726
|
});
|
|
34649
|
-
import { randomUUID as
|
|
34727
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
34650
34728
|
import { homedir as homedir14 } from "node:os";
|
|
34651
34729
|
import { dirname as dirname13, join as join23 } from "node:path";
|
|
34652
34730
|
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
@@ -35026,7 +35104,7 @@ async function buildApp(paths, deps = {}) {
|
|
|
35026
35104
|
};
|
|
35027
35105
|
orchestration3 = new OrchestrationService({
|
|
35028
35106
|
now: deps.loggerNow ?? (() => new Date),
|
|
35029
|
-
createId: () =>
|
|
35107
|
+
createId: () => randomUUID5(),
|
|
35030
35108
|
config: config4,
|
|
35031
35109
|
loadState: async () => JSON.parse(JSON.stringify(state)),
|
|
35032
35110
|
saveState: async (nextState) => {
|
|
@@ -37130,7 +37208,7 @@ var init_doctor2 = __esm(async () => {
|
|
|
37130
37208
|
|
|
37131
37209
|
// src/cli.ts
|
|
37132
37210
|
init_core_home();
|
|
37133
|
-
import { randomUUID as
|
|
37211
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
37134
37212
|
import { homedir as homedir20 } from "node:os";
|
|
37135
37213
|
import { dirname as dirname15, join as join27, sep as sep2 } from "node:path";
|
|
37136
37214
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
@@ -52806,7 +52884,7 @@ async function prepareMcpCoordinatorStartup(input) {
|
|
|
52806
52884
|
return { kind: "external-coordinator" };
|
|
52807
52885
|
}
|
|
52808
52886
|
function createMcpStdioIdentityResolver(input) {
|
|
52809
|
-
const instanceId =
|
|
52887
|
+
const instanceId = randomUUID6().slice(0, 8);
|
|
52810
52888
|
return async (context) => {
|
|
52811
52889
|
const parsedCoordinatorSession = input.parsedCoordinatorSession?.trim() || null;
|
|
52812
52890
|
const workspace3 = input.workspace?.trim() || null;
|
|
@@ -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;
|
|
@@ -93,6 +93,23 @@ export interface ControlPromptResult {
|
|
|
93
93
|
ok: boolean;
|
|
94
94
|
text?: string;
|
|
95
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[];
|
|
96
113
|
}
|
|
97
114
|
/** A turn started by a fired scheduled task. Runs through the same agent + turn-event
|
|
98
115
|
* machinery as a normal prompt, so it streams live and persists to history — but it
|
|
@@ -173,6 +190,9 @@ export declare class ControlService {
|
|
|
173
190
|
getOrchestrationTask(taskId: string): Promise<OrchestrationTaskRecord | null>;
|
|
174
191
|
cancelOrchestrationTask(input: CancelTaskInput): Promise<OrchestrationTaskRecord>;
|
|
175
192
|
private readonly inFlight;
|
|
193
|
+
private readonly queues;
|
|
194
|
+
private readonly draining;
|
|
195
|
+
private emitQueueUpdated;
|
|
176
196
|
prompt(input: ControlPromptInput): Promise<ControlPromptResult>;
|
|
177
197
|
/** Run a fired scheduled task as a real turn through the same machinery as a manual
|
|
178
198
|
* prompt — so it streams live and persists to history — while tagging turn-started
|
|
@@ -180,7 +200,15 @@ export declare class ControlService {
|
|
|
180
200
|
* the web can badge it. Owner-authorized: the task was owner-gated at creation. */
|
|
181
201
|
runScheduledTurn(input: ControlScheduledTurnInput): Promise<ControlPromptResult>;
|
|
182
202
|
private executeTurn;
|
|
203
|
+
private advanceQueue;
|
|
183
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
|
+
};
|
|
184
212
|
executeCommand(input: ControlExecuteCommandInput): Promise<string>;
|
|
185
213
|
/** Open an interactive terminal in the session's workspace cwd. Rejected when terminal is disabled. */
|
|
186
214
|
createTerminal(chatKey: string, sessionAlias: string, cols: number, rows: number): Promise<{
|