@ganglion/xacpx 0.19.2 → 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
|
|
@@ -5763,8 +5839,7 @@ var init_agent_templates = __esm(() => {
|
|
|
5763
5839
|
driver: "grok-build"
|
|
5764
5840
|
},
|
|
5765
5841
|
hermes: {
|
|
5766
|
-
driver: "hermes"
|
|
5767
|
-
command: "hermes acp"
|
|
5842
|
+
driver: "hermes"
|
|
5768
5843
|
},
|
|
5769
5844
|
iflow: {
|
|
5770
5845
|
driver: "iflow"
|
|
@@ -12755,11 +12830,11 @@ var require_dist = __commonJS((exports, module) => {
|
|
|
12755
12830
|
// src/version.ts
|
|
12756
12831
|
import fs from "node:fs";
|
|
12757
12832
|
import path2 from "node:path";
|
|
12758
|
-
import { fileURLToPath } from "node:url";
|
|
12833
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
12759
12834
|
function readVersion(moduleUrl = import.meta.url) {
|
|
12760
12835
|
let dir;
|
|
12761
12836
|
try {
|
|
12762
|
-
dir = path2.dirname(
|
|
12837
|
+
dir = path2.dirname(fileURLToPath2(moduleUrl));
|
|
12763
12838
|
} catch {
|
|
12764
12839
|
return "unknown";
|
|
12765
12840
|
}
|
|
@@ -13832,10 +13907,10 @@ import { readFileSync as readFileSync3 } from "node:fs";
|
|
|
13832
13907
|
import { copyFile, mkdir as mkdir4, readFile as readFile7, writeFile as writeFile4 } from "node:fs/promises";
|
|
13833
13908
|
import { homedir as homedir4 } from "node:os";
|
|
13834
13909
|
import { dirname as dirname4, join as join8 } from "node:path";
|
|
13835
|
-
import { fileURLToPath as
|
|
13910
|
+
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
13836
13911
|
function resolveCoreRoot() {
|
|
13837
13912
|
try {
|
|
13838
|
-
let dir = dirname4(
|
|
13913
|
+
let dir = dirname4(fileURLToPath3(import.meta.url));
|
|
13839
13914
|
for (let depth = 0;depth < 12; depth++) {
|
|
13840
13915
|
try {
|
|
13841
13916
|
const pkg = JSON.parse(readFileSync3(join8(dir, "package.json"), "utf-8"));
|
|
@@ -31351,7 +31426,8 @@ class SessionService {
|
|
|
31351
31426
|
const effectiveReplyMode = channelId === "relay" ? "stream" : undefined;
|
|
31352
31427
|
const currentAgentCommand = resolveConfiguredAgentCommand(agentConfig, this.config.transport);
|
|
31353
31428
|
const configuredAgentCommand = resolveAgentCommand(agentConfig.driver, agentConfig.command);
|
|
31354
|
-
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;
|
|
31355
31431
|
return {
|
|
31356
31432
|
alias: session3.alias,
|
|
31357
31433
|
agent: session3.agent,
|
|
@@ -31519,6 +31595,7 @@ class SessionService {
|
|
|
31519
31595
|
var init_session_service = __esm(() => {
|
|
31520
31596
|
init_resolve_agent_command();
|
|
31521
31597
|
init_adapter_catalog();
|
|
31598
|
+
init_hermes_shim();
|
|
31522
31599
|
init_i18n();
|
|
31523
31600
|
init_channel_scope();
|
|
31524
31601
|
});
|
|
@@ -32232,7 +32309,7 @@ var init_command_timeouts = __esm(() => {
|
|
|
32232
32309
|
|
|
32233
32310
|
// src/transport/acpx-bridge/acpx-bridge-client.ts
|
|
32234
32311
|
import { spawn as spawn10 } from "node:child_process";
|
|
32235
|
-
import { fileURLToPath as
|
|
32312
|
+
import { fileURLToPath as fileURLToPath5 } from "node:url";
|
|
32236
32313
|
import { createInterface } from "node:readline";
|
|
32237
32314
|
function bridgeRequestTimeoutMs(method, sessionInitTimeoutMs = DEFAULT_SESSION_INIT_TIMEOUT_MS) {
|
|
32238
32315
|
switch (method) {
|
|
@@ -32440,7 +32517,7 @@ function buildBridgeSpawnSpec(options) {
|
|
|
32440
32517
|
};
|
|
32441
32518
|
}
|
|
32442
32519
|
async function spawnAcpxBridgeClient(options = {}) {
|
|
32443
|
-
const bridgeEntryPath = options.bridgeEntryPath ??
|
|
32520
|
+
const bridgeEntryPath = options.bridgeEntryPath ?? fileURLToPath5(new URL("../../bridge/bridge-main.ts", import.meta.url));
|
|
32444
32521
|
const spawnSpec = buildBridgeSpawnSpec({
|
|
32445
32522
|
execPath: process.execPath,
|
|
32446
32523
|
bridgeEntryPath
|
|
@@ -37193,7 +37270,7 @@ function buildControlMetadata(senderId, isOwner) {
|
|
|
37193
37270
|
...isOwner === undefined ? {} : { isOwner }
|
|
37194
37271
|
};
|
|
37195
37272
|
}
|
|
37196
|
-
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;
|
|
37197
37274
|
var init_turn_support = __esm(() => {
|
|
37198
37275
|
TURN_IDLE_TIMEOUT_REASON = Symbol("turn-idle-timeout");
|
|
37199
37276
|
});
|
|
@@ -37392,14 +37469,17 @@ class TurnQueue {
|
|
|
37392
37469
|
deps;
|
|
37393
37470
|
setTimer;
|
|
37394
37471
|
clearTimer;
|
|
37472
|
+
cancelDrainTimeoutMs;
|
|
37395
37473
|
constructor(deps) {
|
|
37396
37474
|
this.deps = deps;
|
|
37397
37475
|
this.setTimer = deps.setTimer ?? ((fn, ms) => setTimeout(fn, ms));
|
|
37398
37476
|
this.clearTimer = deps.clearTimer ?? ((id) => clearTimeout(id));
|
|
37477
|
+
this.cancelDrainTimeoutMs = deps.cancelDrainTimeoutMs ?? CANCEL_DRAIN_TIMEOUT_MS;
|
|
37399
37478
|
}
|
|
37400
37479
|
inFlight = new Map;
|
|
37401
37480
|
queues = new Map;
|
|
37402
37481
|
draining = new Set;
|
|
37482
|
+
removing = new Set;
|
|
37403
37483
|
queueLength(chatKey, sessionAlias) {
|
|
37404
37484
|
return this.queues.get(turnKey(chatKey, sessionAlias))?.length ?? 0;
|
|
37405
37485
|
}
|
|
@@ -37420,9 +37500,13 @@ class TurnQueue {
|
|
|
37420
37500
|
const key = turnKey(params.chatKey, params.sessionAlias);
|
|
37421
37501
|
if (!params.drained) {
|
|
37422
37502
|
const existing = this.inFlight.get(key);
|
|
37423
|
-
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;
|
|
37424
37504
|
if (busy) {
|
|
37425
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
|
+
}
|
|
37426
37510
|
const id = randomUUID3();
|
|
37427
37511
|
const item = {
|
|
37428
37512
|
id,
|
|
@@ -37433,7 +37517,6 @@ class TurnQueue {
|
|
|
37433
37517
|
...params.accountId !== undefined ? { accountId: params.accountId } : {},
|
|
37434
37518
|
...params.media !== undefined ? { media: params.media } : {}
|
|
37435
37519
|
};
|
|
37436
|
-
const q = this.queues.get(key) ?? [];
|
|
37437
37520
|
q.push(item);
|
|
37438
37521
|
this.queues.set(key, q);
|
|
37439
37522
|
this.emitQueueUpdated(params.chatKey, params.sessionAlias);
|
|
@@ -37554,6 +37637,28 @@ class TurnQueue {
|
|
|
37554
37637
|
entry.controller.abort();
|
|
37555
37638
|
return true;
|
|
37556
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
|
+
}
|
|
37557
37662
|
cancelQueuedItem(chatKey, sessionAlias, itemId) {
|
|
37558
37663
|
const key = turnKey(chatKey, sessionAlias);
|
|
37559
37664
|
const q = this.queues.get(key);
|
|
@@ -37595,6 +37700,7 @@ class ControlService {
|
|
|
37595
37700
|
runTurn: (req, signal, onActivity) => this.runner.run(req, signal, onActivity),
|
|
37596
37701
|
...this.deps.turnIdleTimeoutMs ? { turnIdleTimeoutMs: this.deps.turnIdleTimeoutMs } : {},
|
|
37597
37702
|
...this.deps.onTurnIdleTimeout ? { onIdleTimeout: this.deps.onTurnIdleTimeout } : {},
|
|
37703
|
+
...this.deps.cancelDrainTimeoutMs !== undefined ? { cancelDrainTimeoutMs: this.deps.cancelDrainTimeoutMs } : {},
|
|
37598
37704
|
emitQueueUpdated: (chatKey, sessionAlias, items) => this.deps.events.emit({ type: "queue-updated", chatKey, sessionAlias, items }),
|
|
37599
37705
|
detectSessionsChanged: async (detection) => {
|
|
37600
37706
|
try {
|
|
@@ -37912,19 +38018,35 @@ class ControlService {
|
|
|
37912
38018
|
}
|
|
37913
38019
|
async removeSession(chatKey, alias) {
|
|
37914
38020
|
const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
|
|
37915
|
-
const
|
|
37916
|
-
|
|
37917
|
-
|
|
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
|
+
}
|
|
37918
38032
|
}
|
|
37919
38033
|
async archiveSession(chatKey, alias) {
|
|
37920
38034
|
const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
|
|
37921
|
-
await this.
|
|
37922
|
-
|
|
37923
|
-
|
|
37924
|
-
}
|
|
37925
|
-
|
|
37926
|
-
this.deps.
|
|
37927
|
-
|
|
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
|
+
}
|
|
37928
38050
|
}
|
|
37929
38051
|
async unarchiveSession(chatKey, alias) {
|
|
37930
38052
|
const internalAlias = await this.deps.sessions.resolveAliasForChat(chatKey, alias);
|
|
@@ -38555,7 +38677,7 @@ __export(exports_main, {
|
|
|
38555
38677
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
38556
38678
|
import { homedir as homedir17 } from "node:os";
|
|
38557
38679
|
import { dirname as dirname16, join as join26 } from "node:path";
|
|
38558
|
-
import { fileURLToPath as
|
|
38680
|
+
import { fileURLToPath as fileURLToPath6 } from "node:url";
|
|
38559
38681
|
function startProgressHeartbeat(orchestration3, config4, logger, channel) {
|
|
38560
38682
|
const thresholdSeconds = config4.orchestration.progressHeartbeatSeconds;
|
|
38561
38683
|
if (thresholdSeconds <= 0) {
|
|
@@ -39311,9 +39433,9 @@ function resolveRuntimePaths() {
|
|
|
39311
39433
|
}
|
|
39312
39434
|
function resolveBridgeEntryPath() {
|
|
39313
39435
|
if (import.meta.url.includes("/dist/")) {
|
|
39314
|
-
return
|
|
39436
|
+
return fileURLToPath6(new URL("./bridge/bridge-main.js", import.meta.url));
|
|
39315
39437
|
}
|
|
39316
|
-
return
|
|
39438
|
+
return fileURLToPath6(new URL("./bridge/bridge-main.ts", import.meta.url));
|
|
39317
39439
|
}
|
|
39318
39440
|
function resolveAppLogPath(configPath) {
|
|
39319
39441
|
const rootDir = dirname16(configPath);
|
|
@@ -39568,7 +39690,7 @@ var init_config_check = __esm(async () => {
|
|
|
39568
39690
|
|
|
39569
39691
|
// src/doctor/checks/daemon-check.ts
|
|
39570
39692
|
import { readdir as readdir7, readFile as readFile17, rm as rm12 } from "node:fs/promises";
|
|
39571
|
-
import { fileURLToPath as
|
|
39693
|
+
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
39572
39694
|
import { homedir as homedir18 } from "node:os";
|
|
39573
39695
|
import { join as join27 } from "node:path";
|
|
39574
39696
|
async function checkDaemon(options = {}) {
|
|
@@ -39724,7 +39846,7 @@ async function defaultRemoveConsumerLock(path16) {
|
|
|
39724
39846
|
await rm12(path16, { force: true });
|
|
39725
39847
|
}
|
|
39726
39848
|
function resolveCliEntryPath() {
|
|
39727
|
-
return process.argv[1] ??
|
|
39849
|
+
return process.argv[1] ?? fileURLToPath7(import.meta.url);
|
|
39728
39850
|
}
|
|
39729
39851
|
function formatError5(error2) {
|
|
39730
39852
|
return error2 instanceof Error ? error2.message : String(error2);
|
|
@@ -41080,7 +41202,7 @@ init_core_home();
|
|
|
41080
41202
|
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
41081
41203
|
import { homedir as homedir23 } from "node:os";
|
|
41082
41204
|
import { dirname as dirname18, join as join30, sep as sep3 } from "node:path";
|
|
41083
|
-
import { fileURLToPath as
|
|
41205
|
+
import { fileURLToPath as fileURLToPath8 } from "node:url";
|
|
41084
41206
|
|
|
41085
41207
|
// src/runtime/migrate-core-home.ts
|
|
41086
41208
|
init_core_home();
|
|
@@ -55122,7 +55244,7 @@ init_plugin_home();
|
|
|
55122
55244
|
import { spawn as spawn4 } from "node:child_process";
|
|
55123
55245
|
import { readFile as readFile9 } from "node:fs/promises";
|
|
55124
55246
|
import { dirname as dirname8, join as join13 } from "node:path";
|
|
55125
|
-
import { fileURLToPath as
|
|
55247
|
+
import { fileURLToPath as fileURLToPath4 } from "node:url";
|
|
55126
55248
|
|
|
55127
55249
|
// src/plugins/package-manager.ts
|
|
55128
55250
|
init_plugin_home();
|
|
@@ -55467,7 +55589,7 @@ async function runInherit(command, args) {
|
|
|
55467
55589
|
}
|
|
55468
55590
|
async function readPackageName() {
|
|
55469
55591
|
try {
|
|
55470
|
-
const here = dirname8(
|
|
55592
|
+
const here = dirname8(fileURLToPath4(import.meta.url));
|
|
55471
55593
|
for (const candidate of [join13(here, "..", "package.json"), join13(here, "..", "..", "package.json")]) {
|
|
55472
55594
|
try {
|
|
55473
55595
|
const parsed = JSON.parse(await readFile9(candidate, "utf8"));
|
|
@@ -58126,7 +58248,7 @@ function resolveCliEntryPath2() {
|
|
|
58126
58248
|
if (process.argv[1]) {
|
|
58127
58249
|
return process.argv[1];
|
|
58128
58250
|
}
|
|
58129
|
-
return
|
|
58251
|
+
return fileURLToPath8(import.meta.url);
|
|
58130
58252
|
}
|
|
58131
58253
|
function parseDoctorArgs(args) {
|
|
58132
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",
|