@0xmaxma/claude-gateway 1.7.10 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +167 -41
- package/dist/agent/builtin-commands.js +2 -2
- package/dist/agent/builtin-commands.js.map +1 -1
- package/dist/agent/knowledge/config.d.ts.map +1 -1
- package/dist/agent/knowledge/config.js +2 -9
- package/dist/agent/knowledge/config.js.map +1 -1
- package/dist/agent/model-catalog.d.ts +62 -0
- package/dist/agent/model-catalog.d.ts.map +1 -0
- package/dist/agent/model-catalog.js +264 -0
- package/dist/agent/model-catalog.js.map +1 -0
- package/dist/agent/runner.d.ts +76 -0
- package/dist/agent/runner.d.ts.map +1 -1
- package/dist/agent/runner.js +189 -36
- package/dist/agent/runner.js.map +1 -1
- package/dist/api/cron-router.d.ts +12 -8
- package/dist/api/cron-router.d.ts.map +1 -1
- package/dist/api/cron-router.js +82 -16
- package/dist/api/cron-router.js.map +1 -1
- package/dist/api/gateway-router.d.ts +4 -0
- package/dist/api/gateway-router.d.ts.map +1 -1
- package/dist/api/gateway-router.js +14 -0
- package/dist/api/gateway-router.js.map +1 -1
- package/dist/api/manifest-sources.d.ts +15 -0
- package/dist/api/manifest-sources.d.ts.map +1 -0
- package/dist/api/manifest-sources.js +27 -0
- package/dist/api/manifest-sources.js.map +1 -0
- package/dist/api/meta-router.d.ts +13 -0
- package/dist/api/meta-router.d.ts.map +1 -0
- package/dist/api/meta-router.js +26 -0
- package/dist/api/meta-router.js.map +1 -0
- package/dist/api/packages.d.ts.map +1 -1
- package/dist/api/packages.js +18 -198
- package/dist/api/packages.js.map +1 -1
- package/dist/api/route-registry.d.ts +81 -0
- package/dist/api/route-registry.d.ts.map +1 -0
- package/dist/api/route-registry.js +44 -0
- package/dist/api/route-registry.js.map +1 -0
- package/dist/api/router.d.ts.map +1 -1
- package/dist/api/router.js +21 -3
- package/dist/api/router.js.map +1 -1
- package/dist/apps/installer.d.ts +73 -5
- package/dist/apps/installer.d.ts.map +1 -1
- package/dist/apps/installer.js +237 -22
- package/dist/apps/installer.js.map +1 -1
- package/dist/cli/args.d.ts +25 -0
- package/dist/cli/args.d.ts.map +1 -0
- package/dist/cli/args.js +55 -0
- package/dist/cli/args.js.map +1 -0
- package/dist/cli/colors.d.ts +37 -0
- package/dist/cli/colors.d.ts.map +1 -0
- package/dist/cli/colors.js +63 -0
- package/dist/cli/colors.js.map +1 -0
- package/dist/cli/command-names.d.ts +98 -0
- package/dist/cli/command-names.d.ts.map +1 -0
- package/dist/cli/command-names.js +109 -0
- package/dist/cli/command-names.js.map +1 -0
- package/dist/cli/commands/agents.d.ts +28 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/agents.js +321 -0
- package/dist/cli/commands/agents.js.map +1 -0
- package/dist/cli/commands/channels.d.ts +17 -0
- package/dist/cli/commands/channels.d.ts.map +1 -0
- package/dist/cli/commands/channels.js +101 -0
- package/dist/cli/commands/channels.js.map +1 -0
- package/dist/cli/commands/debug-bundle.d.ts +6 -0
- package/dist/cli/commands/debug-bundle.d.ts.map +1 -0
- package/dist/cli/commands/debug-bundle.js +220 -0
- package/dist/cli/commands/debug-bundle.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +3 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/doctor.js +104 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/gateway.d.ts +12 -0
- package/dist/cli/commands/gateway.d.ts.map +1 -0
- package/dist/cli/commands/gateway.js +145 -0
- package/dist/cli/commands/gateway.js.map +1 -0
- package/dist/cli/commands/service.d.ts +33 -0
- package/dist/cli/commands/service.d.ts.map +1 -0
- package/dist/cli/commands/service.js +513 -0
- package/dist/cli/commands/service.js.map +1 -0
- package/dist/cli/commands/update.d.ts +16 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.js +149 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/commands.generated.d.ts +4 -0
- package/dist/cli/commands.generated.d.ts.map +1 -0
- package/dist/cli/commands.generated.js +220 -0
- package/dist/cli/commands.generated.js.map +1 -0
- package/dist/cli/health.d.ts +31 -0
- package/dist/cli/health.d.ts.map +1 -0
- package/dist/cli/health.js +49 -0
- package/dist/cli/health.js.map +1 -0
- package/dist/cli/http-client.d.ts +140 -0
- package/dist/cli/http-client.d.ts.map +1 -0
- package/dist/cli/http-client.js +332 -0
- package/dist/cli/http-client.js.map +1 -0
- package/dist/cli/index.d.ts +27 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +372 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/manager.d.ts +64 -0
- package/dist/cli/manager.d.ts.map +1 -0
- package/dist/cli/manager.js +181 -0
- package/dist/cli/manager.js.map +1 -0
- package/dist/cli/output.d.ts +49 -0
- package/dist/cli/output.d.ts.map +1 -0
- package/dist/cli/output.js +101 -0
- package/dist/cli/output.js.map +1 -0
- package/dist/cli/prompt.d.ts +29 -0
- package/dist/cli/prompt.d.ts.map +1 -0
- package/dist/cli/prompt.js +154 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/redact.d.ts +3 -0
- package/dist/cli/redact.d.ts.map +1 -0
- package/dist/cli/redact.js +33 -0
- package/dist/cli/redact.js.map +1 -0
- package/dist/cli/types.d.ts +22 -0
- package/dist/cli/types.d.ts.map +1 -0
- package/dist/cli/types.js +4 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/config/bootstrap.d.ts +33 -0
- package/dist/config/bootstrap.d.ts.map +1 -0
- package/dist/config/bootstrap.js +117 -0
- package/dist/config/bootstrap.js.map +1 -0
- package/dist/config/loader.d.ts.map +1 -1
- package/dist/config/loader.js +5 -1
- package/dist/config/loader.js.map +1 -1
- package/dist/config/watcher.d.ts.map +1 -1
- package/dist/config/watcher.js +2 -35
- package/dist/config/watcher.js.map +1 -1
- package/dist/discord/receiver.d.ts +6 -1
- package/dist/discord/receiver.d.ts.map +1 -1
- package/dist/discord/receiver.js +9 -1
- package/dist/discord/receiver.js.map +1 -1
- package/dist/entry.d.ts +3 -0
- package/dist/entry.d.ts.map +1 -0
- package/dist/entry.js +83 -0
- package/dist/entry.js.map +1 -0
- package/dist/index.js +160 -46
- package/dist/index.js.map +1 -1
- package/dist/load-dotenv.d.ts +2 -0
- package/dist/load-dotenv.d.ts.map +1 -0
- package/dist/load-dotenv.js +68 -0
- package/dist/load-dotenv.js.map +1 -0
- package/dist/packages/registry.d.ts +50 -0
- package/dist/packages/registry.d.ts.map +1 -0
- package/dist/packages/registry.js +209 -0
- package/dist/packages/registry.js.map +1 -0
- package/dist/shutdown-signals.d.ts +47 -0
- package/dist/shutdown-signals.d.ts.map +1 -0
- package/dist/shutdown-signals.js +62 -0
- package/dist/shutdown-signals.js.map +1 -0
- package/dist/telegram/receiver.d.ts +6 -1
- package/dist/telegram/receiver.d.ts.map +1 -1
- package/dist/telegram/receiver.js +9 -1
- package/dist/telegram/receiver.js.map +1 -1
- package/dist/utils/orphan-receivers.d.ts +90 -0
- package/dist/utils/orphan-receivers.d.ts.map +1 -0
- package/dist/utils/orphan-receivers.js +218 -0
- package/dist/utils/orphan-receivers.js.map +1 -0
- package/dist/utils/paths.d.ts +16 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +60 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/stop-child.d.ts +21 -0
- package/dist/utils/stop-child.d.ts.map +1 -0
- package/dist/utils/stop-child.js +64 -0
- package/dist/utils/stop-child.js.map +1 -0
- package/lib/pairing.ts +1 -1
- package/mcp/tools/agent/handlers.ts +1 -1
- package/mcp/tools/discord/receiver-server.ts +4 -0
- package/mcp/tools/telegram/receiver-server.ts +6 -4
- package/package.json +6 -4
|
@@ -13,7 +13,12 @@ export declare class TelegramReceiver {
|
|
|
13
13
|
start(): void;
|
|
14
14
|
private spawnProcess;
|
|
15
15
|
private scheduleRestart;
|
|
16
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Stop the receiver and resolve only once the child has actually exited.
|
|
18
|
+
* Escalates SIGTERM → SIGKILL so a wedged child cannot outlive the gateway's
|
|
19
|
+
* shutdown. See issue #405.
|
|
20
|
+
*/
|
|
21
|
+
stop(): Promise<void>;
|
|
17
22
|
isRunning(): boolean;
|
|
18
23
|
}
|
|
19
24
|
//# sourceMappingURL=receiver.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"receiver.d.ts","sourceRoot":"","sources":["../../src/telegram/receiver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"receiver.d.ts","sourceRoot":"","sources":["../../src/telegram/receiver.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAUvC,qBAAa,gBAAgB;IASzB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAC5B,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM;IAVzB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,YAAY,CAA8C;IAClE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;gBAGtC,WAAW,EAAE,WAAW,EACxB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM;IAKjC,KAAK,IAAI,IAAI;IAOb,OAAO,CAAC,YAAY;IAgCpB,OAAO,CAAC,eAAe;IAuBvB;;;;OAIG;IACH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBrB,SAAS,IAAI,OAAO;CAGrB"}
|
|
@@ -37,6 +37,7 @@ exports.TelegramReceiver = void 0;
|
|
|
37
37
|
const child_process_1 = require("child_process");
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const logger_1 = require("../logger");
|
|
40
|
+
const stop_child_1 = require("../utils/stop-child");
|
|
40
41
|
const AUTO_RESTART_DELAY_MS = 5000;
|
|
41
42
|
const MAX_RESTARTS = 3;
|
|
42
43
|
// After MAX_RESTARTS fast attempts, fall back to a slow indefinite retry
|
|
@@ -106,13 +107,20 @@ class TelegramReceiver {
|
|
|
106
107
|
this.spawnProcess();
|
|
107
108
|
}, delay);
|
|
108
109
|
}
|
|
110
|
+
/**
|
|
111
|
+
* Stop the receiver and resolve only once the child has actually exited.
|
|
112
|
+
* Escalates SIGTERM → SIGKILL so a wedged child cannot outlive the gateway's
|
|
113
|
+
* shutdown. See issue #405.
|
|
114
|
+
*/
|
|
109
115
|
stop() {
|
|
110
116
|
this.stopping = true;
|
|
111
117
|
if (this.restartTimer) {
|
|
112
118
|
clearTimeout(this.restartTimer);
|
|
113
119
|
this.restartTimer = null;
|
|
114
120
|
}
|
|
115
|
-
this.process
|
|
121
|
+
return (0, stop_child_1.stopChildProcess)(this.process, {
|
|
122
|
+
onEscalate: (pid) => this.logger.warn(`TelegramReceiver did not exit within ${stop_child_1.STOP_GRACE_MS}ms of SIGTERM; sending SIGKILL`, { pid }),
|
|
123
|
+
});
|
|
116
124
|
}
|
|
117
125
|
isRunning() {
|
|
118
126
|
return this.process !== null && !this.process.killed;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"receiver.js","sourceRoot":"","sources":["../../src/telegram/receiver.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,iDAAoD;AACpD,2CAA6B;AAE7B,sCAAyC;
|
|
1
|
+
{"version":3,"file":"receiver.js","sourceRoot":"","sources":["../../src/telegram/receiver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reclaim channel receiver processes left behind by a previous gateway.
|
|
3
|
+
*
|
|
4
|
+
* The gateway spawns one `bun .../mcp/tools/<channel>/receiver-server.ts` child
|
|
5
|
+
* per channel per agent. Those children only die if the gateway runs its
|
|
6
|
+
* shutdown path. Any exit that bypasses it — SIGKILL, the OOM killer, or (until
|
|
7
|
+
* issue #405) a SIGHUP — reparents them to init, where they keep polling with no
|
|
8
|
+
* supervisor to hand messages to and survive every subsequent restart.
|
|
9
|
+
*
|
|
10
|
+
* Handling more signals cannot fix SIGKILL or OOM, so a boot-time sweep is the
|
|
11
|
+
* only mechanism that can recover from those. See issue #405.
|
|
12
|
+
*/
|
|
13
|
+
export interface OrphanedReceiver {
|
|
14
|
+
pid: number;
|
|
15
|
+
/** Full argv of the orphan, for logging. */
|
|
16
|
+
command: string;
|
|
17
|
+
}
|
|
18
|
+
/** Injectable so tests can drive the sweep without real processes. */
|
|
19
|
+
export interface SweepDeps {
|
|
20
|
+
listProcesses?: () => Promise<string>;
|
|
21
|
+
kill?: (pid: number, signal: NodeJS.Signals) => void;
|
|
22
|
+
wait?: (ms: number) => Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export interface SweepResult {
|
|
25
|
+
/** Orphans that are gone as a result of this sweep. */
|
|
26
|
+
reclaimed: OrphanedReceiver[];
|
|
27
|
+
/** Orphans that ignored SIGTERM and had to be SIGKILLed. */
|
|
28
|
+
forced: number[];
|
|
29
|
+
/**
|
|
30
|
+
* Orphans that could NOT be terminated — most realistically EPERM, when a
|
|
31
|
+
* previous gateway ran under a different account or under sudo. Reported
|
|
32
|
+
* separately so the boot log never claims to have reclaimed a process that is
|
|
33
|
+
* still alive and still polling its bot token.
|
|
34
|
+
*/
|
|
35
|
+
failed: Array<{
|
|
36
|
+
pid: number;
|
|
37
|
+
reason: string;
|
|
38
|
+
}>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Grace period between SIGTERM and SIGKILL for an orphan.
|
|
42
|
+
*
|
|
43
|
+
* Must stay comfortably above the receivers' own exit deadline — both
|
|
44
|
+
* `mcp/tools/discord/receiver-server.ts` and `mcp/tools/telegram/receiver-server.ts`
|
|
45
|
+
* force-exit on a 2000ms timer after SIGTERM, and Discord's is unconditional. A
|
|
46
|
+
* 2000ms grace would be a dead heat, so healthy receivers would routinely be
|
|
47
|
+
* SIGKILLed and counted as wedged, making the "needed SIGKILL" figure useless as
|
|
48
|
+
* a signal.
|
|
49
|
+
*/
|
|
50
|
+
export declare const ORPHAN_SIGKILL_GRACE_MS = 5000;
|
|
51
|
+
/**
|
|
52
|
+
* Find receiver processes spawned from `receiverToolsDir` that have been
|
|
53
|
+
* reparented to init.
|
|
54
|
+
*
|
|
55
|
+
* Both conditions are load-bearing:
|
|
56
|
+
*
|
|
57
|
+
* - `ppid === 1` proves the supervising gateway is *gone*. A receiver owned by a
|
|
58
|
+
* live gateway always has that gateway's pid as its parent, including when the
|
|
59
|
+
* gateway itself is a systemd service whose own ppid is 1. So this never
|
|
60
|
+
* matches a receiver another running gateway still owns.
|
|
61
|
+
*
|
|
62
|
+
* Caveat: on a host where an ancestor has called `prctl(PR_SET_CHILD_SUBREAPER)`
|
|
63
|
+
* — `systemd --user` for user services, `docker run --init` / tini, s6 —
|
|
64
|
+
* orphans reparent to that subreaper rather than to pid 1, and this sweep finds
|
|
65
|
+
* nothing. The gateway still shuts down cleanly there (gap 1 covers the signal
|
|
66
|
+
* paths); what is lost is the SIGKILL/OOM recovery, and such a host has a
|
|
67
|
+
* supervisor that generally reaps the process group itself.
|
|
68
|
+
*
|
|
69
|
+
* - The argv must *execute* the receiver from this installation's own tools
|
|
70
|
+
* directory. Matching is on argv tokens in the exact shape the receivers are
|
|
71
|
+
* spawned with (`bun <toolsDir>/<channel>/receiver-server.ts`) rather than a
|
|
72
|
+
* substring test, so a detached `grep -r receiver-server.ts <toolsDir>/`, an
|
|
73
|
+
* editor, or a `bun test` over the same file is not mistaken for a receiver and
|
|
74
|
+
* killed. `tests/unit/receiver-stop-teardown.test.ts` pins this shape against
|
|
75
|
+
* the receivers' real spawn arguments.
|
|
76
|
+
*/
|
|
77
|
+
export declare function findOrphanedReceivers(psOutput: string, receiverToolsDir: string): OrphanedReceiver[];
|
|
78
|
+
/**
|
|
79
|
+
* Terminate every orphaned receiver spawned from `receiverToolsDir`.
|
|
80
|
+
*
|
|
81
|
+
* Escalates SIGTERM → SIGKILL so a receiver wedged in an in-flight long-poll
|
|
82
|
+
* cannot survive the sweep.
|
|
83
|
+
*
|
|
84
|
+
* Rejects if the process list cannot be read at all — the caller must decide
|
|
85
|
+
* whether that is fatal (it is not: boot continues, loudly). Once the sweep is
|
|
86
|
+
* under way it stops throwing, and anything it could not terminate is reported
|
|
87
|
+
* in `failed` rather than silently counted as reclaimed.
|
|
88
|
+
*/
|
|
89
|
+
export declare function sweepOrphanedReceivers(receiverToolsDir: string, deps?: SweepDeps): Promise<SweepResult>;
|
|
90
|
+
//# sourceMappingURL=orphan-receivers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orphan-receivers.d.ts","sourceRoot":"","sources":["../../src/utils/orphan-receivers.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;GAWG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,GAAG,EAAE,MAAM,CAAC;IACZ,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,sEAAsE;AACtE,MAAM,WAAW,SAAS;IACxB,aAAa,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,OAAO,KAAK,IAAI,CAAC;IACrD,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,WAAW;IAC1B,uDAAuD;IACvD,SAAS,EAAE,gBAAgB,EAAE,CAAC;IAC9B,4DAA4D;IAC5D,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB;;;;;OAKG;IACH,MAAM,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAChD;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,uBAAuB,OAAQ,CAAC;AA+B7C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,gBAAgB,EAAE,MAAM,GAAG,gBAAgB,EAAE,CAqBpG;AAqBD;;;;;;;;;;GAUG;AACH,wBAAsB,sBAAsB,CAC1C,gBAAgB,EAAE,MAAM,EACxB,IAAI,GAAE,SAAc,GACnB,OAAO,CAAC,WAAW,CAAC,CAwDtB"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ORPHAN_SIGKILL_GRACE_MS = void 0;
|
|
37
|
+
exports.findOrphanedReceivers = findOrphanedReceivers;
|
|
38
|
+
exports.sweepOrphanedReceivers = sweepOrphanedReceivers;
|
|
39
|
+
const child_process_1 = require("child_process");
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
/**
|
|
42
|
+
* Grace period between SIGTERM and SIGKILL for an orphan.
|
|
43
|
+
*
|
|
44
|
+
* Must stay comfortably above the receivers' own exit deadline — both
|
|
45
|
+
* `mcp/tools/discord/receiver-server.ts` and `mcp/tools/telegram/receiver-server.ts`
|
|
46
|
+
* force-exit on a 2000ms timer after SIGTERM, and Discord's is unconditional. A
|
|
47
|
+
* 2000ms grace would be a dead heat, so healthy receivers would routinely be
|
|
48
|
+
* SIGKILLed and counted as wedged, making the "needed SIGKILL" figure useless as
|
|
49
|
+
* a signal.
|
|
50
|
+
*/
|
|
51
|
+
exports.ORPHAN_SIGKILL_GRACE_MS = 5000;
|
|
52
|
+
const defaultListProcesses = () => new Promise((resolve, reject) => {
|
|
53
|
+
// `pid=,ppid=,args=` suppresses the header, so every line is a record.
|
|
54
|
+
// `-ww` disables width truncation: a clipped argv would silently stop
|
|
55
|
+
// matching the tools-directory prefix and reclaim nothing.
|
|
56
|
+
(0, child_process_1.execFile)('ps', ['-ww', '-eo', 'pid=,ppid=,args='], { maxBuffer: 8 * 1024 * 1024 }, (err, stdout) => {
|
|
57
|
+
if (err)
|
|
58
|
+
reject(err);
|
|
59
|
+
else
|
|
60
|
+
resolve(stdout);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
const defaultKill = (pid, signal) => {
|
|
64
|
+
process.kill(pid, signal);
|
|
65
|
+
};
|
|
66
|
+
const defaultWait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
67
|
+
/** ESRCH means the process is already gone — the one "failure" that is a success. */
|
|
68
|
+
function isAlreadyGone(err) {
|
|
69
|
+
return err?.code === 'ESRCH';
|
|
70
|
+
}
|
|
71
|
+
function describeKillError(err) {
|
|
72
|
+
const e = err;
|
|
73
|
+
if (e?.code === 'EPERM')
|
|
74
|
+
return 'EPERM (owned by another user)';
|
|
75
|
+
return e?.code ?? (e instanceof Error ? e.message : String(err));
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Find receiver processes spawned from `receiverToolsDir` that have been
|
|
79
|
+
* reparented to init.
|
|
80
|
+
*
|
|
81
|
+
* Both conditions are load-bearing:
|
|
82
|
+
*
|
|
83
|
+
* - `ppid === 1` proves the supervising gateway is *gone*. A receiver owned by a
|
|
84
|
+
* live gateway always has that gateway's pid as its parent, including when the
|
|
85
|
+
* gateway itself is a systemd service whose own ppid is 1. So this never
|
|
86
|
+
* matches a receiver another running gateway still owns.
|
|
87
|
+
*
|
|
88
|
+
* Caveat: on a host where an ancestor has called `prctl(PR_SET_CHILD_SUBREAPER)`
|
|
89
|
+
* — `systemd --user` for user services, `docker run --init` / tini, s6 —
|
|
90
|
+
* orphans reparent to that subreaper rather than to pid 1, and this sweep finds
|
|
91
|
+
* nothing. The gateway still shuts down cleanly there (gap 1 covers the signal
|
|
92
|
+
* paths); what is lost is the SIGKILL/OOM recovery, and such a host has a
|
|
93
|
+
* supervisor that generally reaps the process group itself.
|
|
94
|
+
*
|
|
95
|
+
* - The argv must *execute* the receiver from this installation's own tools
|
|
96
|
+
* directory. Matching is on argv tokens in the exact shape the receivers are
|
|
97
|
+
* spawned with (`bun <toolsDir>/<channel>/receiver-server.ts`) rather than a
|
|
98
|
+
* substring test, so a detached `grep -r receiver-server.ts <toolsDir>/`, an
|
|
99
|
+
* editor, or a `bun test` over the same file is not mistaken for a receiver and
|
|
100
|
+
* killed. `tests/unit/receiver-stop-teardown.test.ts` pins this shape against
|
|
101
|
+
* the receivers' real spawn arguments.
|
|
102
|
+
*/
|
|
103
|
+
function findOrphanedReceivers(psOutput, receiverToolsDir) {
|
|
104
|
+
const toolsDir = path.resolve(receiverToolsDir);
|
|
105
|
+
const found = [];
|
|
106
|
+
for (const line of psOutput.split('\n')) {
|
|
107
|
+
// `pid ppid args...` — args may contain spaces, so split off only the first two.
|
|
108
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(line);
|
|
109
|
+
if (!match)
|
|
110
|
+
continue;
|
|
111
|
+
const pid = Number(match[1]);
|
|
112
|
+
const ppid = Number(match[2]);
|
|
113
|
+
const command = match[3].trim();
|
|
114
|
+
if (ppid !== 1)
|
|
115
|
+
continue;
|
|
116
|
+
if (pid === process.pid)
|
|
117
|
+
continue;
|
|
118
|
+
if (!isReceiverInvocation(command, toolsDir))
|
|
119
|
+
continue;
|
|
120
|
+
found.push({ pid, command });
|
|
121
|
+
}
|
|
122
|
+
return found;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* True when `command` is the gateway spawning a receiver of its own — i.e.
|
|
126
|
+
* `bun <toolsDir>/<channel>/receiver-server.ts`, the script as the runtime's
|
|
127
|
+
* first argument, with exactly one directory level for the channel.
|
|
128
|
+
*/
|
|
129
|
+
function isReceiverInvocation(command, toolsDir) {
|
|
130
|
+
const tokens = command.split(/\s+/);
|
|
131
|
+
if (tokens.length < 2)
|
|
132
|
+
return false;
|
|
133
|
+
if (path.basename(tokens[0]) !== 'bun')
|
|
134
|
+
return false;
|
|
135
|
+
const script = tokens[1];
|
|
136
|
+
const relative = path.relative(toolsDir, script);
|
|
137
|
+
// Outside the tools directory (`..`) or on another root (absolute) — not ours.
|
|
138
|
+
if (relative.startsWith('..') || path.isAbsolute(relative))
|
|
139
|
+
return false;
|
|
140
|
+
const segments = relative.split(path.sep);
|
|
141
|
+
return segments.length === 2 && segments[1] === 'receiver-server.ts';
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Terminate every orphaned receiver spawned from `receiverToolsDir`.
|
|
145
|
+
*
|
|
146
|
+
* Escalates SIGTERM → SIGKILL so a receiver wedged in an in-flight long-poll
|
|
147
|
+
* cannot survive the sweep.
|
|
148
|
+
*
|
|
149
|
+
* Rejects if the process list cannot be read at all — the caller must decide
|
|
150
|
+
* whether that is fatal (it is not: boot continues, loudly). Once the sweep is
|
|
151
|
+
* under way it stops throwing, and anything it could not terminate is reported
|
|
152
|
+
* in `failed` rather than silently counted as reclaimed.
|
|
153
|
+
*/
|
|
154
|
+
async function sweepOrphanedReceivers(receiverToolsDir, deps = {}) {
|
|
155
|
+
const listProcesses = deps.listProcesses ?? defaultListProcesses;
|
|
156
|
+
const kill = deps.kill ?? defaultKill;
|
|
157
|
+
const wait = deps.wait ?? defaultWait;
|
|
158
|
+
const orphans = findOrphanedReceivers(await listProcesses(), receiverToolsDir);
|
|
159
|
+
if (orphans.length === 0)
|
|
160
|
+
return { reclaimed: [], forced: [], failed: [] };
|
|
161
|
+
const failed = new Map();
|
|
162
|
+
for (const orphan of orphans) {
|
|
163
|
+
try {
|
|
164
|
+
kill(orphan.pid, 'SIGTERM');
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
// ESRCH: it exited between the listing and now — already reclaimed.
|
|
168
|
+
// Anything else (EPERM in practice) means we cannot touch it at all.
|
|
169
|
+
if (!isAlreadyGone(err))
|
|
170
|
+
failed.set(orphan.pid, describeKillError(err));
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
await wait(exports.ORPHAN_SIGKILL_GRACE_MS);
|
|
174
|
+
// Re-list before escalating. A pid freed by SIGTERM can be recycled by an
|
|
175
|
+
// unrelated process within the grace period, and SIGKILLing that would be a
|
|
176
|
+
// far worse bug than the one being fixed — so only pids that are STILL a
|
|
177
|
+
// matching orphan are escalated, not merely pids that still exist.
|
|
178
|
+
let survivors;
|
|
179
|
+
try {
|
|
180
|
+
survivors = new Set(findOrphanedReceivers(await listProcesses(), receiverToolsDir).map((o) => o.pid));
|
|
181
|
+
}
|
|
182
|
+
catch (err) {
|
|
183
|
+
// The second listing failed even though the first succeeded. Skipping
|
|
184
|
+
// escalation leaves a wedged receiver alive, which the next boot will retry;
|
|
185
|
+
// guessing from stale pids could kill an unrelated process. Prefer the
|
|
186
|
+
// recoverable failure — but report the survivors as unreclaimed, not as won.
|
|
187
|
+
const reason = `escalation skipped: ${err.message}`;
|
|
188
|
+
for (const orphan of orphans) {
|
|
189
|
+
if (!failed.has(orphan.pid))
|
|
190
|
+
failed.set(orphan.pid, reason);
|
|
191
|
+
}
|
|
192
|
+
return finish(orphans, [], failed);
|
|
193
|
+
}
|
|
194
|
+
const forced = [];
|
|
195
|
+
for (const orphan of orphans) {
|
|
196
|
+
if (failed.has(orphan.pid))
|
|
197
|
+
continue; // could not be signalled at all
|
|
198
|
+
if (!survivors.has(orphan.pid))
|
|
199
|
+
continue; // exited on SIGTERM, as expected
|
|
200
|
+
try {
|
|
201
|
+
kill(orphan.pid, 'SIGKILL');
|
|
202
|
+
forced.push(orphan.pid);
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
if (!isAlreadyGone(err))
|
|
206
|
+
failed.set(orphan.pid, describeKillError(err));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return finish(orphans, forced, failed);
|
|
210
|
+
}
|
|
211
|
+
function finish(orphans, forced, failed) {
|
|
212
|
+
return {
|
|
213
|
+
reclaimed: orphans.filter((o) => !failed.has(o.pid)),
|
|
214
|
+
forced,
|
|
215
|
+
failed: [...failed].map(([pid, reason]) => ({ pid, reason })),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=orphan-receivers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"orphan-receivers.js","sourceRoot":"","sources":["../../src/utils/orphan-receivers.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8GA,sDAqBC;AAgCD,wDA2DC;AA9ND,iDAAyC;AACzC,2CAA6B;AA0C7B;;;;;;;;;GASG;AACU,QAAA,uBAAuB,GAAG,IAAK,CAAC;AAE7C,MAAM,oBAAoB,GAAG,GAAoB,EAAE,CACjD,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;IAC9B,uEAAuE;IACvE,sEAAsE;IACtE,2DAA2D;IAC3D,IAAA,wBAAQ,EAAC,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,EAAE,kBAAkB,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,GAAG,IAAI,GAAG,IAAI,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;QACjG,IAAI,GAAG;YAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;YAChB,OAAO,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEL,MAAM,WAAW,GAAG,CAAC,GAAW,EAAE,MAAsB,EAAQ,EAAE;IAChE,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AAC5B,CAAC,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,EAAU,EAAiB,EAAE,CAChD,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAEpD,qFAAqF;AACrF,SAAS,aAAa,CAAC,GAAY;IACjC,OAAQ,GAAyC,EAAE,IAAI,KAAK,OAAO,CAAC;AACtE,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAY;IACrC,MAAM,CAAC,GAAG,GAAwC,CAAC;IACnD,IAAI,CAAC,EAAE,IAAI,KAAK,OAAO;QAAE,OAAO,+BAA+B,CAAC;IAChE,OAAO,CAAC,EAAE,IAAI,IAAI,CAAC,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AACnE,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,SAAgB,qBAAqB,CAAC,QAAgB,EAAE,gBAAwB;IAC9E,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAChD,MAAM,KAAK,GAAuB,EAAE,CAAC;IAErC,KAAK,MAAM,IAAI,IAAI,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACxC,iFAAiF;QACjF,MAAM,KAAK,GAAG,2BAA2B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACrD,IAAI,CAAC,KAAK;YAAE,SAAS;QAErB,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9B,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAEhC,IAAI,IAAI,KAAK,CAAC;YAAE,SAAS;QACzB,IAAI,GAAG,KAAK,OAAO,CAAC,GAAG;YAAE,SAAS;QAClC,IAAI,CAAC,oBAAoB,CAAC,OAAO,EAAE,QAAQ,CAAC;YAAE,SAAS;QAEvD,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,OAAe,EAAE,QAAgB;IAC7D,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IACpC,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,KAAK,CAAC;IACpC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,KAAK;QAAE,OAAO,KAAK,CAAC;IAErD,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;IACzB,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACjD,+EAA+E;IAC/E,IAAI,QAAQ,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,KAAK,CAAC;IAEzE,MAAM,QAAQ,GAAG,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1C,OAAO,QAAQ,CAAC,MAAM,KAAK,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,KAAK,oBAAoB,CAAC;AACvE,CAAC;AAED;;;;;;;;;;GAUG;AACI,KAAK,UAAU,sBAAsB,CAC1C,gBAAwB,EACxB,OAAkB,EAAE;IAEpB,MAAM,aAAa,GAAG,IAAI,CAAC,aAAa,IAAI,oBAAoB,CAAC;IACjE,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC;IACtC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,WAAW,CAAC;IAEtC,MAAM,OAAO,GAAG,qBAAqB,CAAC,MAAM,aAAa,EAAE,EAAE,gBAAgB,CAAC,CAAC;IAC/E,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;IAE3E,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IAEzC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC9B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,oEAAoE;YACpE,qEAAqE;YACrE,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC;gBAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IAED,MAAM,IAAI,CAAC,+BAAuB,CAAC,CAAC;IAEpC,0EAA0E;IAC1E,4EAA4E;IAC5E,yEAAyE;IACzE,mEAAmE;IACnE,IAAI,SAAsB,CAAC;IAC3B,IAAI,CAAC;QACH,SAAS,GAAG,IAAI,GAAG,CACjB,qBAAqB,CAAC,MAAM,aAAa,EAAE,EAAE,gBAAgB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CACjF,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,sEAAsE;QACtE,6EAA6E;QAC7E,uEAAuE;QACvE,6EAA6E;QAC7E,MAAM,MAAM,GAAG,uBAAwB,GAAa,CAAC,OAAO,EAAE,CAAC;QAC/D,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;gBAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,MAAM,CAAC,OAAO,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;YAAE,SAAS,CAAC,gCAAgC;QACtE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC;YAAE,SAAS,CAAC,iCAAiC;QAC3E,IAAI,CAAC;YACH,IAAI,CAAC,MAAM,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YAC5B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC;gBAAE,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC;QAC1E,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;AACzC,CAAC;AAED,SAAS,MAAM,CACb,OAA2B,EAC3B,MAAgB,EAChB,MAA2B;IAE3B,OAAO;QACL,SAAS,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QACpD,MAAM;QACN,MAAM,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,CAAC;KAC9D,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expand a leading `~` to the home directory.
|
|
3
|
+
*
|
|
4
|
+
* A `~` reaches the gateway unexpanded whenever a path is not read through a
|
|
5
|
+
* shell: config files (`gateway.logDir` is written as `~/.claude-gateway/logs`
|
|
6
|
+
* by the shipped template), environment variables set in a unit file or a
|
|
7
|
+
* Dockerfile, and `--flag` values. Leaving it literal turns into an ENOENT that
|
|
8
|
+
* reads like "the directory is empty" rather than "the path was never
|
|
9
|
+
* resolved" — the shape of the `debug-bundle` bug found in review.
|
|
10
|
+
*
|
|
11
|
+
* There were four copies of this, on both the server and the CLI side. This
|
|
12
|
+
* module has no imports beyond `os`/`path` precisely so the server entry point
|
|
13
|
+
* can use it without pulling in the CLI.
|
|
14
|
+
*/
|
|
15
|
+
export declare function expandHome(p: string): string;
|
|
16
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAI5C"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.expandHome = expandHome;
|
|
37
|
+
const os = __importStar(require("os"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
/**
|
|
40
|
+
* Expand a leading `~` to the home directory.
|
|
41
|
+
*
|
|
42
|
+
* A `~` reaches the gateway unexpanded whenever a path is not read through a
|
|
43
|
+
* shell: config files (`gateway.logDir` is written as `~/.claude-gateway/logs`
|
|
44
|
+
* by the shipped template), environment variables set in a unit file or a
|
|
45
|
+
* Dockerfile, and `--flag` values. Leaving it literal turns into an ENOENT that
|
|
46
|
+
* reads like "the directory is empty" rather than "the path was never
|
|
47
|
+
* resolved" — the shape of the `debug-bundle` bug found in review.
|
|
48
|
+
*
|
|
49
|
+
* There were four copies of this, on both the server and the CLI side. This
|
|
50
|
+
* module has no imports beyond `os`/`path` precisely so the server entry point
|
|
51
|
+
* can use it without pulling in the CLI.
|
|
52
|
+
*/
|
|
53
|
+
function expandHome(p) {
|
|
54
|
+
if (p === '~')
|
|
55
|
+
return os.homedir();
|
|
56
|
+
if (p.startsWith('~/'))
|
|
57
|
+
return path.join(os.homedir(), p.slice(2));
|
|
58
|
+
return p;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/utils/paths.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiBA,gCAIC;AArBD,uCAAyB;AACzB,2CAA6B;AAE7B;;;;;;;;;;;;;GAaG;AACH,SAAgB,UAAU,CAAC,CAAS;IAClC,IAAI,CAAC,KAAK,GAAG;QAAE,OAAO,EAAE,CAAC,OAAO,EAAE,CAAC;IACnC,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACnE,OAAO,CAAC,CAAC;AACX,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { ChildProcess } from 'child_process';
|
|
2
|
+
/** How long a supervised child gets to exit on SIGTERM before it is SIGKILLed. */
|
|
3
|
+
export declare const STOP_GRACE_MS = 5000;
|
|
4
|
+
export interface StopChildOptions {
|
|
5
|
+
/** Milliseconds to wait for a clean exit before escalating. */
|
|
6
|
+
graceMs?: number;
|
|
7
|
+
/** Called just before SIGKILL, so the caller can log with its own identity. */
|
|
8
|
+
onEscalate?: (pid: number | undefined) => void;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Terminate a supervised child and resolve only once it has actually exited.
|
|
12
|
+
*
|
|
13
|
+
* A bare `kill('SIGTERM')` is not enough: a receiver blocked in an in-flight
|
|
14
|
+
* long-poll can sit in its own handler indefinitely, and the caller had no way to
|
|
15
|
+
* know. Escalating to SIGKILL after a bounded grace period guarantees the child
|
|
16
|
+
* cannot outlive the gateway's shutdown. See issue #405.
|
|
17
|
+
*
|
|
18
|
+
* Never rejects — a shutdown path must not be derailed by a teardown failure.
|
|
19
|
+
*/
|
|
20
|
+
export declare function stopChildProcess(proc: ChildProcess | null, opts?: StopChildOptions): Promise<void>;
|
|
21
|
+
//# sourceMappingURL=stop-child.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stop-child.d.ts","sourceRoot":"","sources":["../../src/utils/stop-child.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,kFAAkF;AAClF,eAAO,MAAM,aAAa,OAAQ,CAAC;AAEnC,MAAM,WAAW,gBAAgB;IAC/B,+DAA+D;IAC/D,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,KAAK,IAAI,CAAC;CAChD;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,YAAY,GAAG,IAAI,EACzB,IAAI,GAAE,gBAAqB,GAC1B,OAAO,CAAC,IAAI,CAAC,CA6Cf"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.STOP_GRACE_MS = void 0;
|
|
4
|
+
exports.stopChildProcess = stopChildProcess;
|
|
5
|
+
/** How long a supervised child gets to exit on SIGTERM before it is SIGKILLed. */
|
|
6
|
+
exports.STOP_GRACE_MS = 5000;
|
|
7
|
+
/**
|
|
8
|
+
* Terminate a supervised child and resolve only once it has actually exited.
|
|
9
|
+
*
|
|
10
|
+
* A bare `kill('SIGTERM')` is not enough: a receiver blocked in an in-flight
|
|
11
|
+
* long-poll can sit in its own handler indefinitely, and the caller had no way to
|
|
12
|
+
* know. Escalating to SIGKILL after a bounded grace period guarantees the child
|
|
13
|
+
* cannot outlive the gateway's shutdown. See issue #405.
|
|
14
|
+
*
|
|
15
|
+
* Never rejects — a shutdown path must not be derailed by a teardown failure.
|
|
16
|
+
*/
|
|
17
|
+
function stopChildProcess(proc, opts = {}) {
|
|
18
|
+
if (!proc)
|
|
19
|
+
return Promise.resolve();
|
|
20
|
+
const graceMs = opts.graceMs ?? exports.STOP_GRACE_MS;
|
|
21
|
+
return new Promise((resolve) => {
|
|
22
|
+
// An already-exited child never emits another 'exit', and `kill()` on it
|
|
23
|
+
// returns false *without throwing* (Node has cleared its handle) — so the
|
|
24
|
+
// catch below would not fire and the promise would idle for the whole grace
|
|
25
|
+
// period before "escalating" with a SIGKILL that does nothing.
|
|
26
|
+
//
|
|
27
|
+
// Loose `!= null` is deliberate: a real ChildProcess always exposes both
|
|
28
|
+
// fields as null while running, but anything that does not expose them at
|
|
29
|
+
// all must be treated as ALIVE and signalled, never assumed dead and
|
|
30
|
+
// silently skipped. Failing safe here means attempting the kill.
|
|
31
|
+
if (proc.exitCode != null || proc.signalCode != null) {
|
|
32
|
+
resolve();
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
let settled = false;
|
|
36
|
+
const finish = () => {
|
|
37
|
+
if (settled)
|
|
38
|
+
return;
|
|
39
|
+
settled = true;
|
|
40
|
+
clearTimeout(killTimer);
|
|
41
|
+
resolve();
|
|
42
|
+
};
|
|
43
|
+
const killTimer = setTimeout(() => {
|
|
44
|
+
opts.onEscalate?.(proc.pid);
|
|
45
|
+
// SIGKILL is delivered by the kernel whether or not we stay alive to
|
|
46
|
+
// observe the exit, so resolving here cannot leave the child running.
|
|
47
|
+
try {
|
|
48
|
+
proc.kill('SIGKILL');
|
|
49
|
+
}
|
|
50
|
+
catch { /* already gone */ }
|
|
51
|
+
finish();
|
|
52
|
+
}, graceMs);
|
|
53
|
+
proc.once('exit', finish);
|
|
54
|
+
// Returns false rather than throwing if the child died in the moment before
|
|
55
|
+
// this line; the exit event that clears the handle also settles us.
|
|
56
|
+
try {
|
|
57
|
+
proc.kill('SIGTERM');
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
finish();
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=stop-child.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stop-child.js","sourceRoot":"","sources":["../../src/utils/stop-child.ts"],"names":[],"mappings":";;;AAsBA,4CAgDC;AApED,kFAAkF;AACrE,QAAA,aAAa,GAAG,IAAK,CAAC;AASnC;;;;;;;;;GASG;AACH,SAAgB,gBAAgB,CAC9B,IAAyB,EACzB,OAAyB,EAAE;IAE3B,IAAI,CAAC,IAAI;QAAE,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAEpC,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,qBAAa,CAAC;IAE9C,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACnC,yEAAyE;QACzE,0EAA0E;QAC1E,4EAA4E;QAC5E,+DAA+D;QAC/D,EAAE;QACF,yEAAyE;QACzE,0EAA0E;QAC1E,qEAAqE;QACrE,iEAAiE;QACjE,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,IAAI,IAAI,CAAC,UAAU,IAAI,IAAI,EAAE,CAAC;YACrD,OAAO,EAAE,CAAC;YACV,OAAO;QACT,CAAC;QAED,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,MAAM,MAAM,GAAG,GAAG,EAAE;YAClB,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,SAAS,CAAC,CAAC;YACxB,OAAO,EAAE,CAAC;QACZ,CAAC,CAAC;QAEF,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,IAAI,CAAC,UAAU,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAC5B,qEAAqE;YACrE,sEAAsE;YACtE,IAAI,CAAC;gBAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;YAC1D,MAAM,EAAE,CAAC;QACX,CAAC,EAAE,OAAO,CAAC,CAAC;QAEZ,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAC1B,4EAA4E;QAC5E,oEAAoE;QACpE,IAAI,CAAC;YACH,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACvB,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,EAAE,CAAC;QACX,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/lib/pairing.ts
CHANGED
|
@@ -26,7 +26,7 @@ const FENCE_CLOSE_REGEX = /^```$/;
|
|
|
26
26
|
const STANDARD_STUB_FILES = ['HEARTBEAT.md', 'MEMORY.md', 'SOUL.md', 'USER.md'];
|
|
27
27
|
|
|
28
28
|
// ---------------------------------------------------------------------------
|
|
29
|
-
// Path helpers
|
|
29
|
+
// Path helpers
|
|
30
30
|
// ---------------------------------------------------------------------------
|
|
31
31
|
|
|
32
32
|
export function expandHome(p: string): string {
|
|
@@ -21,6 +21,10 @@ async function postCallback(inbound: InboundMessage): Promise<void> {
|
|
|
21
21
|
user: inbound.senderId,
|
|
22
22
|
message_id: inbound.messageId ?? '',
|
|
23
23
|
ts: new Date(inbound.ts).toISOString(),
|
|
24
|
+
// 'direct' | 'group'. The gateway gates agent-global commands on this:
|
|
25
|
+
// Discord's guild gate checks guild/channel/mention but never the user, so
|
|
26
|
+
// a command that changes state for every chat must be DM-only.
|
|
27
|
+
chat_type: inbound.chatType,
|
|
24
28
|
};
|
|
25
29
|
|
|
26
30
|
if (inbound.attachmentFileId) {
|
|
@@ -878,10 +878,12 @@ const BOT_COMMANDS = [
|
|
|
878
878
|
{ command: 'help', description: 'What this bot can do' },
|
|
879
879
|
]
|
|
880
880
|
|
|
881
|
-
// Available AI models for /models command.
|
|
882
|
-
// uses
|
|
883
|
-
//
|
|
884
|
-
//
|
|
881
|
+
// Available AI models for /models command. Last-resort fallback: the picker
|
|
882
|
+
// normally uses get_models, which itself resolves the live catalog and only
|
|
883
|
+
// then falls back to config.json's gateway.models (issue #409). This copy is
|
|
884
|
+
// reached only when the gateway call itself fails, so it is two fallbacks deep
|
|
885
|
+
// and will drift — keep it roughly in step with DEFAULT_MODELS
|
|
886
|
+
// (src/agent/runner.ts) and config.template.json gateway.models.
|
|
885
887
|
const AVAILABLE_MODELS = [
|
|
886
888
|
{ id: 'claude-fable-5[1m]', label: 'Fable 5 (1M)', alias: 'fable[1m]' },
|
|
887
889
|
{ id: 'claude-opus-5[1m]', label: 'Opus 5 (1M)', alias: 'opus[1m]' },
|