@chenacc/agentbridge 0.1.2 → 0.1.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.
- package/package.json +1 -1
- package/src/config.js +4 -0
- package/src/server.js +45 -11
package/package.json
CHANGED
package/src/config.js
CHANGED
|
@@ -170,6 +170,10 @@ export function loadConfig(env = process.env, workspace = process.cwd()) {
|
|
|
170
170
|
// recognise its own child in /api/health and restart it to change the public
|
|
171
171
|
// address, without ever touching a bridge the user started by hand.
|
|
172
172
|
autostart: env.AGENTBRIDGE_AUTOSTART === "1",
|
|
173
|
+
// Shared secret between the plugin that spawned this bridge and the bridge
|
|
174
|
+
// itself, so the plugin may ask for a restart to change the advertised address
|
|
175
|
+
// without handing that power to anything else that can reach loopback.
|
|
176
|
+
autostartToken: (env.AGENTBRIDGE_AUTOSTART_TOKEN ?? "").trim() || null,
|
|
173
177
|
// Outbound notifications: unset webhook means the feature is simply off.
|
|
174
178
|
notifyWebhook: pick(env, "AGENTBRIDGE_NOTIFY_WEBHOOK", notifyFile.webhook),
|
|
175
179
|
notifyFormat: pick(env, "AGENTBRIDGE_NOTIFY_FORMAT", notifyFile.format ?? "json").toLowerCase(),
|
package/src/server.js
CHANGED
|
@@ -386,6 +386,25 @@ export async function createBridge(options = {}) {
|
|
|
386
386
|
return shutdownPromise;
|
|
387
387
|
};
|
|
388
388
|
|
|
389
|
+
/**
|
|
390
|
+
* A restart requested by the plugin must not cut a running Agent turn in half.
|
|
391
|
+
*
|
|
392
|
+
* The plugin retries on the next desktop launch in that case; refusing is the
|
|
393
|
+
* safer answer than silently killing work the user is waiting on.
|
|
394
|
+
*/
|
|
395
|
+
const refuseWhenBusy = () => {
|
|
396
|
+
const active = sessions.list().filter((session) => ["starting", "running", "stopping"].includes(session.status));
|
|
397
|
+
if (!active.length) return;
|
|
398
|
+
throw Object.assign(new Error("仍有 Agent 任务正在运行,请先停止任务再关闭 Bridge"), {
|
|
399
|
+
statusCode: 409,
|
|
400
|
+
code: "ACTIVE_SESSIONS",
|
|
401
|
+
diagnostics: {
|
|
402
|
+
count: active.length,
|
|
403
|
+
sessions: active.map(({ id, title, status, adapterId }) => ({ id, title, status, adapterId })),
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
};
|
|
407
|
+
|
|
389
408
|
const server = http.createServer(async (req, res) => {
|
|
390
409
|
const url = new URL(req.url, `http://${req.headers.host ?? "localhost"}`);
|
|
391
410
|
try {
|
|
@@ -654,6 +673,31 @@ export async function createBridge(options = {}) {
|
|
|
654
673
|
// point remain authenticated (and gated by the access key when enabled).
|
|
655
674
|
if (req.method === "GET" && await serveStatic(res, url.pathname, url.search)) return;
|
|
656
675
|
|
|
676
|
+
// The DSH plugin starts this bridge itself and sometimes has to restart it,
|
|
677
|
+
// because the address it advertises (cloudflared hostname) only exists after
|
|
678
|
+
// the tunnel is up. Restarting needs a stop, and a stop needs an
|
|
679
|
+
// authorisation: loopback alone would let any local page POST here, so the
|
|
680
|
+
// caller must also present the token the plugin generated when it spawned
|
|
681
|
+
// this process. The token lives in the bridge's data directory, which keeps
|
|
682
|
+
// it out of the command line and out of the logs.
|
|
683
|
+
if (req.method === "POST" && url.pathname === "/api/local/autostart/shutdown") {
|
|
684
|
+
if (!isLoopback(req.socket.remoteAddress)) throw Object.assign(new Error("Local access only"), { statusCode: 403 });
|
|
685
|
+
if (!config.autostartToken || !keyMatches(req.headers["x-agentbridge-autostart"], config.autostartToken)) {
|
|
686
|
+
throw Object.assign(new Error("Autostart token required"), { statusCode: 401 });
|
|
687
|
+
}
|
|
688
|
+
refuseWhenBusy();
|
|
689
|
+
shuttingDown = true;
|
|
690
|
+
json(res, 202, { accepted: true, message: "AgentBridge is restarting to change its public address" });
|
|
691
|
+
setImmediate(() => {
|
|
692
|
+
void shutdown().catch((error) => {
|
|
693
|
+
console.error("AgentBridge safe shutdown failed", error);
|
|
694
|
+
shuttingDown = false;
|
|
695
|
+
shutdownPromise = null;
|
|
696
|
+
});
|
|
697
|
+
});
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
|
|
657
701
|
requireAccessKey(req, url, config);
|
|
658
702
|
const device = pairing.authenticate(requestToken(req));
|
|
659
703
|
if (!device) throw Object.assign(new Error("Authentication required"), { statusCode: 401 });
|
|
@@ -794,17 +838,7 @@ export async function createBridge(options = {}) {
|
|
|
794
838
|
let match;
|
|
795
839
|
if (req.method === "POST" && url.pathname === "/api/local/shutdown") {
|
|
796
840
|
if (!isLoopback(req.socket.remoteAddress)) throw Object.assign(new Error("Local access only"), { statusCode: 403 });
|
|
797
|
-
|
|
798
|
-
if (active.length) {
|
|
799
|
-
throw Object.assign(new Error("仍有 Agent 任务正在运行,请先停止任务再关闭 Bridge"), {
|
|
800
|
-
statusCode: 409,
|
|
801
|
-
code: "ACTIVE_SESSIONS",
|
|
802
|
-
diagnostics: {
|
|
803
|
-
count: active.length,
|
|
804
|
-
sessions: active.map(({ id, title, status, adapterId }) => ({ id, title, status, adapterId })),
|
|
805
|
-
},
|
|
806
|
-
});
|
|
807
|
-
}
|
|
841
|
+
refuseWhenBusy();
|
|
808
842
|
shuttingDown = true;
|
|
809
843
|
json(res, 202, { accepted: true, message: "AgentBridge 正在安全停止" });
|
|
810
844
|
setImmediate(() => {
|