@ganglion/xacpx 0.19.1 → 0.19.3
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.
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
function __accessProp(key) {
|
|
8
|
+
return this[key];
|
|
9
|
+
}
|
|
10
|
+
var __toESMCache_node;
|
|
11
|
+
var __toESMCache_esm;
|
|
12
|
+
var __toESM = (mod, isNodeMode, target) => {
|
|
13
|
+
var canCache = mod != null && typeof mod === "object";
|
|
14
|
+
if (canCache) {
|
|
15
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
16
|
+
var cached = cache.get(mod);
|
|
17
|
+
if (cached)
|
|
18
|
+
return cached;
|
|
19
|
+
}
|
|
20
|
+
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
21
|
+
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
22
|
+
for (let key of __getOwnPropNames(mod))
|
|
23
|
+
if (!__hasOwnProp.call(to, key))
|
|
24
|
+
__defProp(to, key, {
|
|
25
|
+
get: __accessProp.bind(mod, key),
|
|
26
|
+
enumerable: true
|
|
27
|
+
});
|
|
28
|
+
if (canCache)
|
|
29
|
+
cache.set(mod, to);
|
|
30
|
+
return to;
|
|
31
|
+
};
|
|
32
|
+
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
|
+
var __returnValue = (v) => v;
|
|
34
|
+
function __exportSetter(name, newValue) {
|
|
35
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
36
|
+
}
|
|
37
|
+
var __export = (target, all) => {
|
|
38
|
+
for (var name in all)
|
|
39
|
+
__defProp(target, name, {
|
|
40
|
+
get: all[name],
|
|
41
|
+
enumerable: true,
|
|
42
|
+
configurable: true,
|
|
43
|
+
set: __exportSetter.bind(all, name)
|
|
44
|
+
});
|
|
45
|
+
};
|
|
46
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
47
|
+
var __promiseAll = (args) => Promise.all(args);
|
|
48
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
49
|
+
|
|
50
|
+
// src/adapters/hermes-shim.ts
|
|
51
|
+
import { fileURLToPath } from "node:url";
|
|
52
|
+
function isDefaultHermesCommand(command) {
|
|
53
|
+
return command.trim().replaceAll(/\s+/g, " ") === DEFAULT_HERMES_COMMAND;
|
|
54
|
+
}
|
|
55
|
+
function isHermesShimCommand(command) {
|
|
56
|
+
return command.includes("/hermes-acp-shim.") || command.includes("\\hermes-acp-shim.");
|
|
57
|
+
}
|
|
58
|
+
function resolveHermesAcpShimEntry(moduleUrl = import.meta.url) {
|
|
59
|
+
if (moduleUrl.endsWith(".ts")) {
|
|
60
|
+
return fileURLToPath(new URL("./hermes-acp-shim.ts", moduleUrl));
|
|
61
|
+
}
|
|
62
|
+
const idx = moduleUrl.lastIndexOf(DIST_MARKER);
|
|
63
|
+
if (idx !== -1) {
|
|
64
|
+
return fileURLToPath(new URL(`${moduleUrl.slice(0, idx + DIST_MARKER.length)}adapters/hermes-acp-shim.js`));
|
|
65
|
+
}
|
|
66
|
+
return fileURLToPath(new URL("./hermes-acp-shim.js", moduleUrl));
|
|
67
|
+
}
|
|
68
|
+
function quoteAgentCommandToken(token) {
|
|
69
|
+
return `"${token.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}"`;
|
|
70
|
+
}
|
|
71
|
+
function hermesAcpShimCommand(execPath = process.execPath, shimEntry = resolveHermesAcpShimEntry()) {
|
|
72
|
+
return [
|
|
73
|
+
quoteAgentCommandToken(execPath),
|
|
74
|
+
quoteAgentCommandToken(shimEntry),
|
|
75
|
+
"hermes",
|
|
76
|
+
"acp"
|
|
77
|
+
].join(" ");
|
|
78
|
+
}
|
|
79
|
+
function stripResumeCapability(line) {
|
|
80
|
+
let message;
|
|
81
|
+
try {
|
|
82
|
+
message = JSON.parse(line);
|
|
83
|
+
} catch {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (!message || typeof message !== "object")
|
|
87
|
+
return null;
|
|
88
|
+
const result = message.result;
|
|
89
|
+
if (!result || typeof result !== "object")
|
|
90
|
+
return null;
|
|
91
|
+
const capabilities = result.agentCapabilities;
|
|
92
|
+
if (!capabilities || typeof capabilities !== "object")
|
|
93
|
+
return null;
|
|
94
|
+
const sessionCapabilities = capabilities.sessionCapabilities;
|
|
95
|
+
if (!sessionCapabilities || typeof sessionCapabilities !== "object")
|
|
96
|
+
return null;
|
|
97
|
+
if (!Object.hasOwn(sessionCapabilities, "resume"))
|
|
98
|
+
return null;
|
|
99
|
+
delete sessionCapabilities.resume;
|
|
100
|
+
return JSON.stringify(message);
|
|
101
|
+
}
|
|
102
|
+
function isInitializeResponse(line) {
|
|
103
|
+
let message;
|
|
104
|
+
try {
|
|
105
|
+
message = JSON.parse(line);
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (!message || typeof message !== "object")
|
|
110
|
+
return false;
|
|
111
|
+
const result = message.result;
|
|
112
|
+
if (!result || typeof result !== "object")
|
|
113
|
+
return false;
|
|
114
|
+
const capabilities = result.agentCapabilities;
|
|
115
|
+
return Boolean(capabilities) && typeof capabilities === "object";
|
|
116
|
+
}
|
|
117
|
+
var DEFAULT_HERMES_COMMAND = "hermes acp", DIST_MARKER = "/dist/";
|
|
118
|
+
var init_hermes_shim = () => {};
|
|
119
|
+
|
|
120
|
+
// src/adapters/hermes-acp-shim.ts
|
|
121
|
+
init_hermes_shim();
|
|
122
|
+
import { spawn } from "node:child_process";
|
|
123
|
+
import { constants as osConstants } from "node:os";
|
|
124
|
+
var argv = process.argv.slice(2);
|
|
125
|
+
var command = argv.length > 0 ? argv : ["hermes", "acp"];
|
|
126
|
+
var child = spawn(command[0], command.slice(1), {
|
|
127
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
128
|
+
shell: process.platform === "win32"
|
|
129
|
+
});
|
|
130
|
+
child.on("error", (error) => {
|
|
131
|
+
process.stderr.write(`[hermes-acp-shim] failed to spawn "${command.join(" ")}": ${error.message}
|
|
132
|
+
`);
|
|
133
|
+
process.exit(1);
|
|
134
|
+
});
|
|
135
|
+
child.on("exit", (code, signal) => {
|
|
136
|
+
process.exitCode = signal ? 128 + (osConstants.signals[signal] ?? 0) : code ?? 0;
|
|
137
|
+
});
|
|
138
|
+
for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
|
|
139
|
+
process.on(signal, () => {
|
|
140
|
+
child.kill(signal);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
process.stdin.pipe(child.stdin);
|
|
144
|
+
var patched = false;
|
|
145
|
+
var pending = Buffer.alloc(0);
|
|
146
|
+
var NEWLINE = 10;
|
|
147
|
+
var interceptStdout = (chunk) => {
|
|
148
|
+
pending = pending.length > 0 ? Buffer.concat([pending, chunk]) : chunk;
|
|
149
|
+
let newlineIndex;
|
|
150
|
+
while (!patched && (newlineIndex = pending.indexOf(NEWLINE)) !== -1) {
|
|
151
|
+
const line = pending.subarray(0, newlineIndex + 1);
|
|
152
|
+
pending = pending.subarray(newlineIndex + 1);
|
|
153
|
+
const text = line.toString("utf8");
|
|
154
|
+
const replaced = stripResumeCapability(text);
|
|
155
|
+
if (replaced !== null) {
|
|
156
|
+
patched = true;
|
|
157
|
+
process.stdout.write(`${replaced}
|
|
158
|
+
`);
|
|
159
|
+
} else {
|
|
160
|
+
process.stdout.write(line);
|
|
161
|
+
if (isInitializeResponse(text))
|
|
162
|
+
patched = true;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (patched) {
|
|
166
|
+
if (pending.length > 0) {
|
|
167
|
+
process.stdout.write(pending);
|
|
168
|
+
pending = Buffer.alloc(0);
|
|
169
|
+
}
|
|
170
|
+
child.stdout.off("data", interceptStdout);
|
|
171
|
+
child.stdout.pipe(process.stdout, { end: false });
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
child.stdout.on("data", interceptStdout);
|
|
175
|
+
child.stdout.on("end", () => {
|
|
176
|
+
if (pending.length > 0) {
|
|
177
|
+
process.stdout.write(pending);
|
|
178
|
+
pending = Buffer.alloc(0);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** The command the 0.19.2 template baked into configs; treated as "no explicit
|
|
2
|
+
* command" so those configs migrate onto the shim without hand-editing. */
|
|
3
|
+
export declare function isDefaultHermesCommand(command: string): boolean;
|
|
4
|
+
/** A recorded shim command is derived, machine-specific state — the current
|
|
5
|
+
* install's shim (whose dist path may differ after an upgrade) must replace it.
|
|
6
|
+
* The leading separator keeps user wrappers like `my-hermes-acp-shim.sh` custom. */
|
|
7
|
+
export declare function isHermesShimCommand(command: string): boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Path to the shim entry script. Unbundled dev runs (module URL still ends in .ts)
|
|
10
|
+
* resolve the sibling .ts source, which the dev runtime (bun) executes directly —
|
|
11
|
+
* checked FIRST so a checkout path that happens to contain `/dist/` cannot
|
|
12
|
+
* misresolve. Bundled builds (cli.js AND bridge/bridge-main.js both sit under
|
|
13
|
+
* dist/) anchor on the last `/dist/` segment.
|
|
14
|
+
*/
|
|
15
|
+
export declare function resolveHermesAcpShimEntry(moduleUrl?: string): string;
|
|
16
|
+
/** Quote one token for acpx's `--agent` parser (double quotes; `\` and `"` escaped —
|
|
17
|
+
* its splitCommandLine treats backslash as an escape inside double quotes). */
|
|
18
|
+
export declare function quoteAgentCommandToken(token: string): string;
|
|
19
|
+
/** Runtime-only agent command for the hermes driver: never persisted to config.
|
|
20
|
+
*
|
|
21
|
+
* Tradeoff: the string embeds `process.execPath` and this install's dist path,
|
|
22
|
+
* and acpx keys its backend session records by EXACT agentCommand equality — so a
|
|
23
|
+
* node upgrade (nvm paths embed the version) or an install relocation re-keys
|
|
24
|
+
* hermes session identity: acpx starts a fresh backend record and the old queue
|
|
25
|
+
* owner ages out via TTL. Accepted because the alternative (a stable bin shim)
|
|
26
|
+
* isn't worth the packaging surface for a workaround slated for removal. */
|
|
27
|
+
export declare function hermesAcpShimCommand(execPath?: string, shimEntry?: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* If `line` is the initialize response advertising `sessionCapabilities.resume`,
|
|
30
|
+
* return the same frame re-serialized without it; otherwise null (caller forwards
|
|
31
|
+
* the original bytes untouched).
|
|
32
|
+
*/
|
|
33
|
+
export declare function stripResumeCapability(line: string): string | null;
|
|
34
|
+
/** Is `line` an initialize response (the only frame carrying agentCapabilities)?
|
|
35
|
+
* Lets the shim latch to raw passthrough even when hermes stops advertising
|
|
36
|
+
* `resume` after the upstream fix, instead of parsing every line forever. */
|
|
37
|
+
export declare function isInitializeResponse(line: string): boolean;
|
package/dist/cli.js
CHANGED
|
@@ -4868,6 +4868,76 @@ var init_local_agent_bin = __esm(() => {
|
|
|
4868
4868
|
};
|
|
4869
4869
|
});
|
|
4870
4870
|
|
|
4871
|
+
// src/adapters/hermes-shim.ts
|
|
4872
|
+
import { fileURLToPath } from "node:url";
|
|
4873
|
+
function isDefaultHermesCommand(command) {
|
|
4874
|
+
return command.trim().replaceAll(/\s+/g, " ") === DEFAULT_HERMES_COMMAND;
|
|
4875
|
+
}
|
|
4876
|
+
function isHermesShimCommand(command) {
|
|
4877
|
+
return command.includes("/hermes-acp-shim.") || command.includes("\\hermes-acp-shim.");
|
|
4878
|
+
}
|
|
4879
|
+
function resolveHermesAcpShimEntry(moduleUrl = import.meta.url) {
|
|
4880
|
+
if (moduleUrl.endsWith(".ts")) {
|
|
4881
|
+
return fileURLToPath(new URL("./hermes-acp-shim.ts", moduleUrl));
|
|
4882
|
+
}
|
|
4883
|
+
const idx = moduleUrl.lastIndexOf(DIST_MARKER);
|
|
4884
|
+
if (idx !== -1) {
|
|
4885
|
+
return fileURLToPath(new URL(`${moduleUrl.slice(0, idx + DIST_MARKER.length)}adapters/hermes-acp-shim.js`));
|
|
4886
|
+
}
|
|
4887
|
+
return fileURLToPath(new URL("./hermes-acp-shim.js", moduleUrl));
|
|
4888
|
+
}
|
|
4889
|
+
function quoteAgentCommandToken(token) {
|
|
4890
|
+
return `"${token.replaceAll("\\", "\\\\").replaceAll('"', "\\\"")}"`;
|
|
4891
|
+
}
|
|
4892
|
+
function hermesAcpShimCommand(execPath = process.execPath, shimEntry = resolveHermesAcpShimEntry()) {
|
|
4893
|
+
return [
|
|
4894
|
+
quoteAgentCommandToken(execPath),
|
|
4895
|
+
quoteAgentCommandToken(shimEntry),
|
|
4896
|
+
"hermes",
|
|
4897
|
+
"acp"
|
|
4898
|
+
].join(" ");
|
|
4899
|
+
}
|
|
4900
|
+
function stripResumeCapability(line) {
|
|
4901
|
+
let message;
|
|
4902
|
+
try {
|
|
4903
|
+
message = JSON.parse(line);
|
|
4904
|
+
} catch {
|
|
4905
|
+
return null;
|
|
4906
|
+
}
|
|
4907
|
+
if (!message || typeof message !== "object")
|
|
4908
|
+
return null;
|
|
4909
|
+
const result = message.result;
|
|
4910
|
+
if (!result || typeof result !== "object")
|
|
4911
|
+
return null;
|
|
4912
|
+
const capabilities = result.agentCapabilities;
|
|
4913
|
+
if (!capabilities || typeof capabilities !== "object")
|
|
4914
|
+
return null;
|
|
4915
|
+
const sessionCapabilities = capabilities.sessionCapabilities;
|
|
4916
|
+
if (!sessionCapabilities || typeof sessionCapabilities !== "object")
|
|
4917
|
+
return null;
|
|
4918
|
+
if (!Object.hasOwn(sessionCapabilities, "resume"))
|
|
4919
|
+
return null;
|
|
4920
|
+
delete sessionCapabilities.resume;
|
|
4921
|
+
return JSON.stringify(message);
|
|
4922
|
+
}
|
|
4923
|
+
function isInitializeResponse(line) {
|
|
4924
|
+
let message;
|
|
4925
|
+
try {
|
|
4926
|
+
message = JSON.parse(line);
|
|
4927
|
+
} catch {
|
|
4928
|
+
return false;
|
|
4929
|
+
}
|
|
4930
|
+
if (!message || typeof message !== "object")
|
|
4931
|
+
return false;
|
|
4932
|
+
const result = message.result;
|
|
4933
|
+
if (!result || typeof result !== "object")
|
|
4934
|
+
return false;
|
|
4935
|
+
const capabilities = result.agentCapabilities;
|
|
4936
|
+
return Boolean(capabilities) && typeof capabilities === "object";
|
|
4937
|
+
}
|
|
4938
|
+
var DEFAULT_HERMES_COMMAND = "hermes acp", DIST_MARKER = "/dist/";
|
|
4939
|
+
var init_hermes_shim = () => {};
|
|
4940
|
+
|
|
4871
4941
|
// src/config/resolve-agent-command.ts
|
|
4872
4942
|
function resolveAgentCommand(driver, command) {
|
|
4873
4943
|
if (!command) {
|
|
@@ -4876,6 +4946,9 @@ function resolveAgentCommand(driver, command) {
|
|
|
4876
4946
|
if (driver === "codex" && isLegacyCodexCommand(command)) {
|
|
4877
4947
|
return;
|
|
4878
4948
|
}
|
|
4949
|
+
if (driver === "hermes" && isDefaultHermesCommand(command)) {
|
|
4950
|
+
return;
|
|
4951
|
+
}
|
|
4879
4952
|
return command;
|
|
4880
4953
|
}
|
|
4881
4954
|
function resolveRuntimeAgentCommand(driver, command, preferLocal = true, adapterVersions, adapterRegistry) {
|
|
@@ -4886,6 +4959,8 @@ function resolveRuntimeAgentCommand(driver, command, preferLocal = true, adapter
|
|
|
4886
4959
|
const managed = resolveManagedAdapterCommand(driver, adapterVersions, adapterRegistry);
|
|
4887
4960
|
if (managed)
|
|
4888
4961
|
return managed;
|
|
4962
|
+
if (driver === "hermes")
|
|
4963
|
+
return hermesAcpShimCommand();
|
|
4889
4964
|
return preferLocal ? resolveLocalAgentCommand(driver) : undefined;
|
|
4890
4965
|
}
|
|
4891
4966
|
function resolveConfiguredAgentCommand(agent3, transport) {
|
|
@@ -4898,6 +4973,7 @@ function isLegacyCodexCommand(command) {
|
|
|
4898
4973
|
var init_resolve_agent_command = __esm(() => {
|
|
4899
4974
|
init_local_agent_bin();
|
|
4900
4975
|
init_adapter_catalog();
|
|
4976
|
+
init_hermes_shim();
|
|
4901
4977
|
});
|
|
4902
4978
|
|
|
4903
4979
|
// src/config/load-config.ts
|
|
@@ -5762,6 +5838,9 @@ var init_agent_templates = __esm(() => {
|
|
|
5762
5838
|
"grok-build": {
|
|
5763
5839
|
driver: "grok-build"
|
|
5764
5840
|
},
|
|
5841
|
+
hermes: {
|
|
5842
|
+
driver: "hermes"
|
|
5843
|
+
},
|
|
5765
5844
|
iflow: {
|
|
5766
5845
|
driver: "iflow"
|
|
5767
5846
|
},
|
|
@@ -12751,11 +12830,11 @@ var require_dist = __commonJS((exports, module) => {
|
|
|
12751
12830
|
// src/version.ts
|
|
12752
12831
|
import fs from "node:fs";
|
|
12753
12832
|
import path2 from "node:path";
|
|
12754
|
-
import { fileURLToPath } from "node:url";
|
|
12833
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12755
12834
|
function readVersion(moduleUrl = import.meta.url) {
|
|
12756
12835
|
let dir;
|
|
12757
12836
|
try {
|
|
12758
|
-
dir = path2.dirname(
|
|
12837
|
+
dir = path2.dirname(fileURLToPath2(moduleUrl));
|
|
12759
12838
|
} catch {
|
|
12760
12839
|
return "unknown";
|
|
12761
12840
|
}
|
|
@@ -13828,10 +13907,10 @@ import { readFileSync as readFileSync3 } from "node:fs";
|
|
|
13828
13907
|
import { copyFile, mkdir as mkdir4, readFile as readFile7, writeFile as writeFile4 } from "node:fs/promises";
|
|
13829
13908
|
import { homedir as homedir4 } from "node:os";
|
|
13830
13909
|
import { dirname as dirname4, join as join8 } from "node:path";
|
|
13831
|
-
import { fileURLToPath as
|
|
13910
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
13832
13911
|
function resolveCoreRoot() {
|
|
13833
13912
|
try {
|
|
13834
|
-
let dir = dirname4(
|
|
13913
|
+
let dir = dirname4(fileURLToPath3(import.meta.url));
|
|
13835
13914
|
for (let depth = 0;depth < 12; depth++) {
|
|
13836
13915
|
try {
|
|
13837
13916
|
const pkg = JSON.parse(readFileSync3(join8(dir, "package.json"), "utf-8"));
|
|
@@ -31347,7 +31426,8 @@ class SessionService {
|
|
|
31347
31426
|
const effectiveReplyMode = channelId === "relay" ? "stream" : undefined;
|
|
31348
31427
|
const currentAgentCommand = resolveConfiguredAgentCommand(agentConfig, this.config.transport);
|
|
31349
31428
|
const configuredAgentCommand = resolveAgentCommand(agentConfig.driver, agentConfig.command);
|
|
31350
|
-
const
|
|
31429
|
+
const recordedIsDerived = Boolean(session3.transport_agent_command) && (agentConfig.driver === "codex" && isLegacyCodexCommand(session3.transport_agent_command) || agentConfig.driver === "hermes" && (isDefaultHermesCommand(session3.transport_agent_command) || isHermesShimCommand(session3.transport_agent_command)));
|
|
31430
|
+
const recordedAgentCommand = recordedIsDerived ? undefined : session3.transport_agent_command;
|
|
31351
31431
|
return {
|
|
31352
31432
|
alias: session3.alias,
|
|
31353
31433
|
agent: session3.agent,
|
|
@@ -31515,6 +31595,7 @@ class SessionService {
|
|
|
31515
31595
|
var init_session_service = __esm(() => {
|
|
31516
31596
|
init_resolve_agent_command();
|
|
31517
31597
|
init_adapter_catalog();
|
|
31598
|
+
init_hermes_shim();
|
|
31518
31599
|
init_i18n();
|
|
31519
31600
|
init_channel_scope();
|
|
31520
31601
|
});
|
|
@@ -32228,7 +32309,7 @@ var init_command_timeouts = __esm(() => {
|
|
|
32228
32309
|
|
|
32229
32310
|
// src/transport/acpx-bridge/acpx-bridge-client.ts
|
|
32230
32311
|
import { spawn as spawn10 } from "node:child_process";
|
|
32231
|
-
import { fileURLToPath as
|
|
32312
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
32232
32313
|
import { createInterface } from "node:readline";
|
|
32233
32314
|
function bridgeRequestTimeoutMs(method, sessionInitTimeoutMs = DEFAULT_SESSION_INIT_TIMEOUT_MS) {
|
|
32234
32315
|
switch (method) {
|
|
@@ -32436,7 +32517,7 @@ function buildBridgeSpawnSpec(options) {
|
|
|
32436
32517
|
};
|
|
32437
32518
|
}
|
|
32438
32519
|
async function spawnAcpxBridgeClient(options = {}) {
|
|
32439
|
-
const bridgeEntryPath = options.bridgeEntryPath ??
|
|
32520
|
+
const bridgeEntryPath = options.bridgeEntryPath ?? fileURLToPath5(new URL("../../bridge/bridge-main.ts", import.meta.url));
|
|
32440
32521
|
const spawnSpec = buildBridgeSpawnSpec({
|
|
32441
32522
|
execPath: process.execPath,
|
|
32442
32523
|
bridgeEntryPath
|
|
@@ -37189,7 +37270,7 @@ function buildControlMetadata(senderId, isOwner) {
|
|
|
37189
37270
|
...isOwner === undefined ? {} : { isOwner }
|
|
37190
37271
|
};
|
|
37191
37272
|
}
|
|
37192
|
-
var CANCEL_DRAIN_TIMEOUT_MS = 5000, QUEUE_PREVIEW_MAX = 120, TURN_IDLE_TIMEOUT_REASON;
|
|
37273
|
+
var CANCEL_DRAIN_TIMEOUT_MS = 5000, QUEUE_PREVIEW_MAX = 120, QUEUE_MAX_DEPTH = 20, TURN_IDLE_TIMEOUT_REASON;
|
|
37193
37274
|
var init_turn_support = __esm(() => {
|
|
37194
37275
|
TURN_IDLE_TIMEOUT_REASON = Symbol("turn-idle-timeout");
|
|
37195
37276
|
});
|
|
@@ -37388,14 +37469,17 @@ class TurnQueue {
|
|
|
37388
37469
|
deps;
|
|
37389
37470
|
setTimer;
|
|
37390
37471
|
clearTimer;
|
|
37472
|
+
cancelDrainTimeoutMs;
|
|
37391
37473
|
constructor(deps) {
|
|
37392
37474
|
this.deps = deps;
|
|
37393
37475
|
this.setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
37394
37476
|
this.clearTimer = deps.clearTimer ?? ((id) => clearTimeout(id));
|
|
37477
|
+
this.cancelDrainTimeoutMs = deps.cancelDrainTimeoutMs ?? CANCEL_DRAIN_TIMEOUT_MS;
|
|
37395
37478
|
}
|
|
37396
37479
|
inFlight = new Map;
|
|
37397
37480
|
queues = new Map;
|
|
37398
37481
|
draining = new Set;
|
|
37482
|
+
removing = new Set;
|
|
37399
37483
|
queueLength(chatKey, sessionAlias) {
|
|
37400
37484
|
return this.queues.get(turnKey(chatKey, sessionAlias))?.length ?? 0;
|
|
37401
37485
|
}
|
|
@@ -37416,9 +37500,13 @@ class TurnQueue {
|
|
|
37416
37500
|
const key = turnKey(params.chatKey, params.sessionAlias);
|
|
37417
37501
|
if (!params.drained) {
|
|
37418
37502
|
const existing = this.inFlight.get(key);
|
|
37419
|
-
const busy = this.draining.has(key) || existing !== undefined && !existing.controller.signal.aborted;
|
|
37503
|
+
const busy = this.removing.has(key) || this.draining.has(key) || existing !== undefined && !existing.controller.signal.aborted;
|
|
37420
37504
|
if (busy) {
|
|
37421
37505
|
if (params.queueable) {
|
|
37506
|
+
const q = this.queues.get(key) ?? [];
|
|
37507
|
+
if (q.length >= QUEUE_MAX_DEPTH) {
|
|
37508
|
+
return { ok: false, errorMessage: "queue-full" };
|
|
37509
|
+
}
|
|
37422
37510
|
const id = randomUUID3();
|
|
37423
37511
|
const item = {
|
|
37424
37512
|
id,
|
|
@@ -37429,7 +37517,6 @@ class TurnQueue {
|
|
|
37429
37517
|
...params.accountId !== undefined ? { accountId: params.accountId } : {},
|
|
37430
37518
|
...params.media !== undefined ? { media: params.media } : {}
|
|
37431
37519
|
};
|
|
37432
|
-
const q = this.queues.get(key) ?? [];
|
|
37433
37520
|
q.push(item);
|
|
37434
37521
|
this.queues.set(key, q);
|
|
37435
37522
|
this.emitQueueUpdated(params.chatKey, params.sessionAlias);
|
|
@@ -37550,6 +37637,28 @@ class TurnQueue {
|
|
|
37550
37637
|
entry.controller.abort();
|
|
37551
37638
|
return true;
|
|
37552
37639
|
}
|
|
37640
|
+
async clearSession(chatKey, sessionAlias) {
|
|
37641
|
+
const key = turnKey(chatKey, sessionAlias);
|
|
37642
|
+
if (this.queues.delete(key)) {
|
|
37643
|
+
this.emitQueueUpdated(chatKey, sessionAlias);
|
|
37644
|
+
}
|
|
37645
|
+
const entry = this.inFlight.get(key);
|
|
37646
|
+
if (entry) {
|
|
37647
|
+
entry.controller.abort();
|
|
37648
|
+
await raceWithTimeout(entry.settled, this.cancelDrainTimeoutMs);
|
|
37649
|
+
if (this.queues.delete(key)) {
|
|
37650
|
+
this.emitQueueUpdated(chatKey, sessionAlias);
|
|
37651
|
+
}
|
|
37652
|
+
}
|
|
37653
|
+
const cleared = !this.inFlight.has(key) && !this.draining.has(key);
|
|
37654
|
+
if (cleared) {
|
|
37655
|
+
this.removing.add(key);
|
|
37656
|
+
}
|
|
37657
|
+
return { cleared };
|
|
37658
|
+
}
|
|
37659
|
+
finishClear(chatKey, sessionAlias) {
|
|
37660
|
+
this.removing.delete(turnKey(chatKey, sessionAlias));
|
|
37661
|
+
}
|
|
37553
37662
|
cancelQueuedItem(chatKey, sessionAlias, itemId) {
|
|
37554
37663
|
const key = turnKey(chatKey, sessionAlias);
|
|
37555
37664
|
const q = this.queues.get(key);
|
|
@@ -37591,6 +37700,7 @@ class ControlService {
|
|
|
37591
37700
|
runTurn: (req, signal, onActivity) => this.runner.run(req, signal, onActivity),
|
|
37592
37701
|
...this.deps.turnIdleTimeoutMs ? { turnIdleTimeoutMs: this.deps.turnIdleTimeoutMs } : {},
|
|
37593
37702
|
...this.deps.onTurnIdleTimeout ? { onIdleTimeout: this.deps.onTurnIdleTimeout } : {},
|
|
37703
|
+
...this.deps.cancelDrainTimeoutMs !== undefined ? { cancelDrainTimeoutMs: this.deps.cancelDrainTimeoutMs } : {},
|
|
37594
37704
|
emitQueueUpdated: (chatKey, sessionAlias, items) => this.deps.events.emit({ type: "queue-updated", chatKey, sessionAlias, items }),
|
|
37595
37705
|
detectSessionsChanged: async (detection) => {
|
|
37596
37706
|
try {
|
|
@@ -37908,19 +38018,35 @@ class ControlService {
|
|
|
37908
38018
|
}
|
|
37909
38019
|
async removeSession(chatKey, alias) {
|
|
37910
38020
|
const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
|
|
37911
|
-
const
|
|
37912
|
-
|
|
37913
|
-
|
|
38021
|
+
const { cleared } = await this.turnQueue.clearSession(chatKey, alias);
|
|
38022
|
+
if (!cleared) {
|
|
38023
|
+
throw new Error(`session "${alias}" is still finishing a stopped turn; retry in a moment`);
|
|
38024
|
+
}
|
|
38025
|
+
try {
|
|
38026
|
+
const result = await this.deps.removeSessionWithTransport(internalAlias);
|
|
38027
|
+
this.deps.events.emit({ type: "sessions-changed" });
|
|
38028
|
+
return result;
|
|
38029
|
+
} finally {
|
|
38030
|
+
this.turnQueue.finishClear(chatKey, alias);
|
|
38031
|
+
}
|
|
37914
38032
|
}
|
|
37915
38033
|
async archiveSession(chatKey, alias) {
|
|
37916
38034
|
const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
|
|
37917
|
-
await this.
|
|
37918
|
-
|
|
37919
|
-
|
|
37920
|
-
}
|
|
37921
|
-
|
|
37922
|
-
this.deps.
|
|
37923
|
-
|
|
38035
|
+
const { cleared } = await this.turnQueue.clearSession(chatKey, alias);
|
|
38036
|
+
if (!cleared) {
|
|
38037
|
+
throw new Error(`session "${alias}" is still finishing a stopped turn; retry in a moment`);
|
|
38038
|
+
}
|
|
38039
|
+
try {
|
|
38040
|
+
await this.deps.archiveSessionWithTransport(internalAlias);
|
|
38041
|
+
const session3 = await this.deps.sessions.getSession(internalAlias).catch(() => {
|
|
38042
|
+
return;
|
|
38043
|
+
});
|
|
38044
|
+
if (session3)
|
|
38045
|
+
this.deps.sessionWarmth?.markCold(session3);
|
|
38046
|
+
this.deps.events.emit({ type: "sessions-changed" });
|
|
38047
|
+
} finally {
|
|
38048
|
+
this.turnQueue.finishClear(chatKey, alias);
|
|
38049
|
+
}
|
|
37924
38050
|
}
|
|
37925
38051
|
async unarchiveSession(chatKey, alias) {
|
|
37926
38052
|
const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
|
|
@@ -38551,7 +38677,7 @@ __export(exports_main, {
|
|
|
38551
38677
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
38552
38678
|
import { homedir as homedir17 } from "node:os";
|
|
38553
38679
|
import { dirname as dirname16, join as join26 } from "node:path";
|
|
38554
|
-
import { fileURLToPath as
|
|
38680
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
38555
38681
|
function startProgressHeartbeat(orchestration3, config4, logger, channel) {
|
|
38556
38682
|
const thresholdSeconds = config4.orchestration.progressHeartbeatSeconds;
|
|
38557
38683
|
if (thresholdSeconds <= 0) {
|
|
@@ -39307,9 +39433,9 @@ function resolveRuntimePaths() {
|
|
|
39307
39433
|
}
|
|
39308
39434
|
function resolveBridgeEntryPath() {
|
|
39309
39435
|
if (import.meta.url.includes("/dist/")) {
|
|
39310
|
-
return
|
|
39436
|
+
return fileURLToPath6(new URL("./bridge/bridge-main.js", import.meta.url));
|
|
39311
39437
|
}
|
|
39312
|
-
return
|
|
39438
|
+
return fileURLToPath6(new URL("./bridge/bridge-main.ts", import.meta.url));
|
|
39313
39439
|
}
|
|
39314
39440
|
function resolveAppLogPath(configPath) {
|
|
39315
39441
|
const rootDir = dirname16(configPath);
|
|
@@ -39564,7 +39690,7 @@ var init_config_check = __esm(async () => {
|
|
|
39564
39690
|
|
|
39565
39691
|
// src/doctor/checks/daemon-check.ts
|
|
39566
39692
|
import { readdir as readdir7, readFile as readFile17, rm as rm12 } from "node:fs/promises";
|
|
39567
|
-
import { fileURLToPath as
|
|
39693
|
+
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
39568
39694
|
import { homedir as homedir18 } from "node:os";
|
|
39569
39695
|
import { join as join27 } from "node:path";
|
|
39570
39696
|
async function checkDaemon(options = {}) {
|
|
@@ -39720,7 +39846,7 @@ async function defaultRemoveConsumerLock(path16) {
|
|
|
39720
39846
|
await rm12(path16, { force: true });
|
|
39721
39847
|
}
|
|
39722
39848
|
function resolveCliEntryPath() {
|
|
39723
|
-
return process.argv[1] ??
|
|
39849
|
+
return process.argv[1] ?? fileURLToPath7(import.meta.url);
|
|
39724
39850
|
}
|
|
39725
39851
|
function formatError5(error2) {
|
|
39726
39852
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
@@ -41076,7 +41202,7 @@ init_core_home();
|
|
|
41076
41202
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
41077
41203
|
import { homedir as homedir23 } from "node:os";
|
|
41078
41204
|
import { dirname as dirname18, join as join30, sep as sep3 } from "node:path";
|
|
41079
|
-
import { fileURLToPath as
|
|
41205
|
+
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
41080
41206
|
|
|
41081
41207
|
// src/runtime/migrate-core-home.ts
|
|
41082
41208
|
init_core_home();
|
|
@@ -55118,7 +55244,7 @@ init_plugin_home();
|
|
|
55118
55244
|
import { spawn as spawn4 } from "node:child_process";
|
|
55119
55245
|
import { readFile as readFile9 } from "node:fs/promises";
|
|
55120
55246
|
import { dirname as dirname8, join as join13 } from "node:path";
|
|
55121
|
-
import { fileURLToPath as
|
|
55247
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
55122
55248
|
|
|
55123
55249
|
// src/plugins/package-manager.ts
|
|
55124
55250
|
init_plugin_home();
|
|
@@ -55463,7 +55589,7 @@ async function runInherit(command, args) {
|
|
|
55463
55589
|
}
|
|
55464
55590
|
async function readPackageName() {
|
|
55465
55591
|
try {
|
|
55466
|
-
const here = dirname8(
|
|
55592
|
+
const here = dirname8(fileURLToPath4(import.meta.url));
|
|
55467
55593
|
for (const candidate of [join13(here, "..", "package.json"), join13(here, "..", "..", "package.json")]) {
|
|
55468
55594
|
try {
|
|
55469
55595
|
const parsed = JSON.parse(await readFile9(candidate, "utf8"));
|
|
@@ -58122,7 +58248,7 @@ function resolveCliEntryPath2() {
|
|
|
58122
58248
|
if (process.argv[1]) {
|
|
58123
58249
|
return process.argv[1];
|
|
58124
58250
|
}
|
|
58125
|
-
return
|
|
58251
|
+
return fileURLToPath8(import.meta.url);
|
|
58126
58252
|
}
|
|
58127
58253
|
function parseDoctorArgs(args) {
|
|
58128
58254
|
const options = {};
|
|
@@ -100,6 +100,7 @@ export interface ControlServiceDeps {
|
|
|
100
100
|
gitWorktreesRoot?: string;
|
|
101
101
|
turnIdleTimeoutMs?: () => number;
|
|
102
102
|
onTurnIdleTimeout?: (detail: TurnIdleTimeoutDetail) => void;
|
|
103
|
+
cancelDrainTimeoutMs?: number;
|
|
103
104
|
}
|
|
104
105
|
export interface ControlPromptInput {
|
|
105
106
|
chatKey: string;
|
|
@@ -14,6 +14,7 @@ export interface TurnQueueDeps {
|
|
|
14
14
|
onIdleTimeout?: (detail: TurnIdleTimeoutDetail) => void;
|
|
15
15
|
setTimer?: (fn: () => void, ms: number) => unknown;
|
|
16
16
|
clearTimer?: (id: unknown) => void;
|
|
17
|
+
cancelDrainTimeoutMs?: number;
|
|
17
18
|
}
|
|
18
19
|
export interface SubmitParams {
|
|
19
20
|
chatKey: string;
|
|
@@ -37,16 +38,41 @@ export declare class TurnQueue {
|
|
|
37
38
|
private readonly deps;
|
|
38
39
|
private readonly setTimer;
|
|
39
40
|
private readonly clearTimer;
|
|
41
|
+
private readonly cancelDrainTimeoutMs;
|
|
40
42
|
constructor(deps: TurnQueueDeps);
|
|
41
43
|
private readonly inFlight;
|
|
42
44
|
private readonly queues;
|
|
43
45
|
private readonly draining;
|
|
46
|
+
private readonly removing;
|
|
44
47
|
queueLength(chatKey: string, sessionAlias: string): number;
|
|
45
48
|
isBusy(chatKey: string, sessionAlias: string): boolean;
|
|
46
49
|
private emitQueueUpdated;
|
|
47
50
|
submit(params: SubmitParams): Promise<SubmitResult>;
|
|
48
51
|
private advanceQueue;
|
|
49
52
|
cancelTurn(chatKey: string, sessionAlias: string): boolean;
|
|
53
|
+
/** Tear down all turn state for a session that is being removed or archived: drop every
|
|
54
|
+
* queued prompt, abort a running turn, and wait (bounded) for it to unwind. The queue is
|
|
55
|
+
* cleared BEFORE the abort so the aborting turn's finally sees it empty and releases the
|
|
56
|
+
* slot instead of draining a head onto the dead session. Returns `cleared: false` when the
|
|
57
|
+
* session still holds turn state after the bounded wait (a wedged turn that outlived the
|
|
58
|
+
* timeout, or a fresh prompt that slipped in during the unwind) — the caller MUST NOT
|
|
59
|
+
* proceed with removal/archive then, or the surviving turn's events would write history
|
|
60
|
+
* rows for a session that no longer exists.
|
|
61
|
+
*
|
|
62
|
+
* NOT side-effect-free even when it returns `cleared: false`: it has already aborted the
|
|
63
|
+
* in-flight turn and dropped every queued prompt (emitting `queue-updated([])`). The caller
|
|
64
|
+
* should surface a retry, not present the failure as a no-op.
|
|
65
|
+
*
|
|
66
|
+
* On `cleared: true` it arms a teardown guard (the busy gate now rejects new turns for this
|
|
67
|
+
* session) so a scheduled turn cannot cold-start during the caller's transport teardown. The
|
|
68
|
+
* caller MUST call `finishClear` once teardown settles (success or failure) to release it. */
|
|
69
|
+
clearSession(chatKey: string, sessionAlias: string): Promise<{
|
|
70
|
+
cleared: boolean;
|
|
71
|
+
}>;
|
|
72
|
+
/** Release the teardown guard armed by a successful `clearSession`. MUST be called by the
|
|
73
|
+
* caller once transport removal/archive settles (in a finally), whether it succeeded or
|
|
74
|
+
* threw — otherwise the session key stays wedged as busy forever. */
|
|
75
|
+
finishClear(chatKey: string, sessionAlias: string): void;
|
|
50
76
|
/** Remove a pending queued prompt (by id) before it drains. No-ops (returns
|
|
51
77
|
* `{ cancelled: false }`) when the queue or the id is absent/already drained — e.g. a race
|
|
52
78
|
* where the item drained into a running turn just before the cancel arrived. Does NOT touch
|
|
@@ -13,6 +13,7 @@ export interface QueuedPrompt {
|
|
|
13
13
|
}
|
|
14
14
|
export declare const CANCEL_DRAIN_TIMEOUT_MS = 5000;
|
|
15
15
|
export declare const QUEUE_PREVIEW_MAX = 120;
|
|
16
|
+
export declare const QUEUE_MAX_DEPTH = 20;
|
|
16
17
|
export declare function raceWithTimeout(promise: Promise<void>, ms: number): Promise<void>;
|
|
17
18
|
export declare const TURN_IDLE_TIMEOUT_REASON: unique symbol;
|
|
18
19
|
/** Detail handed to the idle-timeout observability hook when the inactivity watchdog reclaims a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ganglion/xacpx",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.3",
|
|
4
4
|
"description": "随时随地通过聊天频道(微信 / 飞书 / 元宝等)远程控制 `acpx` 上的 Claude Code、Codex 等 Agents。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"acpx",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"clean:dist": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
|
|
43
43
|
"clean:channel-yuanbao": "node -e \"require('node:fs').rmSync('packages/channel-yuanbao/dist',{recursive:true,force:true})\"",
|
|
44
44
|
"clean:channel-feishu": "node -e \"require('node:fs').rmSync('packages/channel-feishu/dist',{recursive:true,force:true})\"",
|
|
45
|
-
"build": "bun run clean:dist && bun build ./src/cli.ts ./src/bridge/bridge-main.ts ./src/plugin-api.ts --outdir ./dist --target node --external node-pty && bun run build:plugin-api",
|
|
45
|
+
"build": "bun run clean:dist && bun build ./src/cli.ts ./src/bridge/bridge-main.ts ./src/adapters/hermes-acp-shim.ts ./src/plugin-api.ts --outdir ./dist --target node --external node-pty && bun run build:plugin-api",
|
|
46
46
|
"build:plugin-api": "tsc -p tsconfig.plugin-api.json",
|
|
47
47
|
"build:channel-yuanbao": "bun run build:plugin-api && bun run clean:channel-yuanbao && bun build ./packages/channel-yuanbao/src/index.ts --outdir ./packages/channel-yuanbao/dist --target node --external xacpx && tsc -p packages/channel-yuanbao/tsconfig.json",
|
|
48
48
|
"build:channel-feishu": "bun run build:plugin-api && bun run clean:channel-feishu && bun build ./packages/channel-feishu/src/index.ts --outdir ./packages/channel-feishu/dist --target node --external xacpx && tsc -p packages/channel-feishu/tsconfig.json",
|