@agenthippo-ai/agenthippo 0.2.3-dataplane → 1.110.11
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/out/agentpack/index.js +3 -1
- package/out/agentpack/paths.js +25 -2
- package/out/analytics/dashboard/localFleetAdapter.js +82 -2
- package/out/analytics/dashboard/provider.js +13 -2
- package/out/analytics/dashboard/webview/panel.js +3 -2
- package/out/analytics/identity.js +0 -0
- package/out/analytics/service.js +29 -25
- package/out/analytics/sessionEvents.js +100 -24
- package/out/auth/authBaseUrl.js +59 -0
- package/out/auth/bootstrapApply.js +9 -10
- package/out/auth/brokerClient.js +48 -14
- package/out/auth/provider.js +8 -21
- package/out/auth/sessionStore.js +3 -40
- package/out/auth/signInFlow.js +2 -1
- package/out/auth-broker/config.js +50 -5
- package/out/auth-broker/server.js +126 -26
- package/out/auth-broker/store.js +25 -24
- package/out/authority/warrant.js +206 -0
- package/out/branding.js +27 -1
- package/out/cli/commands/conformance.js +249 -0
- package/out/cli/commands/data-plane.js +151 -0
- package/out/cli/commands/deploy.js +1 -0
- package/out/cli/commands/gateway.js +4 -0
- package/out/cli/commands/pack.js +182 -3
- package/out/cli/commands/sandbox.js +351 -0
- package/out/cli/commands/serve.js +1145 -130
- package/out/cli/commands/store.js +12 -4
- package/out/cli/credential-context.js +17 -0
- package/out/cli/data-plane/gateway.js +28 -5
- package/out/cli/data-plane/index.js +18 -2
- package/out/cli/data-plane/registry.js +9 -0
- package/out/cli/data-plane/remote.js +106 -0
- package/out/cli/data-plane/sidecar.js +225 -0
- package/out/cli/deploy/layers/identity.js +88 -4
- package/out/cli/deploy-init.js +89 -24
- package/out/cli/deps.js +106 -2
- package/out/cli/idp-init.js +35 -0
- package/out/cli/index.js +171 -17
- package/out/cli/pack-install-ref.js +15 -0
- package/out/cli/pack-loader.js +8 -1
- package/out/cli/pack-runtime.js +61 -0
- package/out/cli/runtime.js +35 -1
- package/out/cli/serve-hardening.js +260 -14
- package/out/cli/utils.js +12 -1
- package/out/engines/base.js +32 -3
- package/out/engines/claude.js +33 -15
- package/out/engines/codex.js +111 -27
- package/out/engines/custom.js +15 -1
- package/out/evidence/sessionShip.js +643 -0
- package/out/extension.js +11 -2
- package/out/gateway/bridge.js +40 -2
- package/out/gateway/client.js +34 -1
- package/out/gateway/installSource.js +68 -0
- package/out/gateway/lifecycle.js +185 -51
- package/out/gateway/outboundPaths.js +4 -1
- package/out/gateway/secretsPreflight.js +90 -0
- package/out/gateway/service.js +7 -2
- package/out/headless/index.js +25 -2
- package/out/live-e2e/mcp-demo/server.mjs +32 -0
- package/out/mcp.js +30 -0
- package/out/pack/caps.js +271 -0
- package/out/pack/governor.js +326 -0
- package/out/pack/lease-schema.js +273 -0
- package/out/pack/lease.js +682 -0
- package/out/pack/reconcile.js +217 -0
- package/out/pack/signature.js +839 -0
- package/out/remote/handleRemoteChat.js +44 -3
- package/out/remote/remoteTrust.js +277 -0
- package/out/remote/serveClient.js +52 -5
- package/out/runner/host.js +141 -0
- package/out/runner/protocol.js +128 -0
- package/out/runner/remoteEngine.js +166 -0
- package/out/runtime/interfaces/edit-tracker.js +2 -0
- package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
- package/out/sandbox/containerDriver.js +572 -0
- package/out/sandbox/processDriver.js +318 -0
- package/out/sandbox/sandboxRunner.js +303 -0
- package/out/sandbox/support.js +301 -0
- package/out/sandbox/types.js +55 -0
- package/out/scripts/agentide-litellm.sh +67 -5
- package/out/scripts/agentide_litellm_hooks_v1.py +266 -21
- package/out/scripts/analytics/agentide-litellm-otel.config.yaml +98 -132
- package/out/scripts/analytics/setup-analytics.ps1 +45 -14
- package/out/scripts/analytics/setup-analytics.sh +106 -15
- package/out/scripts/analytics/start-analytics.sh +86 -14
- package/out/scripts/gateway/setup-gateway.sh +72 -32
- package/out/shared/models.js +11 -110
- package/out/spotlight/install.js +7 -1
- package/out/store/index.js +41 -10
- package/out/sync.js +3 -1
- package/out/test/analytics.test.js +162 -23
- package/out/test/auth-baseurl.test.js +132 -0
- package/out/test/auth-bootstrapApply.test.js +27 -6
- package/out/test/auth-broker-hardening.test.js +227 -0
- package/out/test/auth-broker.test.js +59 -9
- package/out/test/branding.test.js +13 -0
- package/out/test/caps-governor.test.js +460 -0
- package/out/test/codex-new-file-survival.test.js +119 -0
- package/out/test/configuration-scopes.test.js +131 -0
- package/out/test/data-plane-engine-wiring.test.js +32 -0
- package/out/test/data-plane.test.js +380 -0
- package/out/test/deploy-init.test.js +47 -1
- package/out/test/gateway-client.test.js +28 -0
- package/out/test/gateway-install-source.test.js +111 -0
- package/out/test/gateway-lifecycle.test.js +1 -1
- package/out/test/gateway-outbound.test.js +7 -0
- package/out/test/gateway-secrets-preflight.test.js +127 -0
- package/out/test/gateway-session-routing.test.js +18 -0
- package/out/test/governor-hardening.test.js +222 -0
- package/out/test/headless-sync.test.js +22 -0
- package/out/test/lease-signature.test.js +357 -0
- package/out/test/lease-trust-root.test.js +273 -0
- package/out/test/lease-validation.test.js +224 -0
- package/out/test/lease.test.js +114 -0
- package/out/test/localFleetAdapter.test.js +91 -0
- package/out/test/mcp.test.js +27 -1
- package/out/test/pack-admission.test.js +216 -0
- package/out/test/pack-digest-parity.test.js +140 -0
- package/out/test/pack-reconcile.test.js +183 -0
- package/out/test/pack-signature-artifact.test.js +103 -0
- package/out/test/pack-signature.test.js +176 -0
- package/out/test/pack-trust-root.test.js +202 -0
- package/out/test/remote-trust.test.js +242 -0
- package/out/test/runner-protocol.test.js +249 -0
- package/out/test/sandbox-driver.test.js +553 -0
- package/out/test/sandbox-hardening.test.js +406 -0
- package/out/test/sandbox-runner.test.js +343 -0
- package/out/test/sandbox-scope.test.js +449 -0
- package/out/test/serve-cron-acceptance.test.js +370 -0
- package/out/test/serve-gateway-turn.test.js +122 -0
- package/out/test/serve-hardening.test.js +227 -15
- package/out/test/serveClient.test.js +6 -5
- package/out/test/sessionShip.test.js +618 -0
- package/out/test/spotlightPublish.test.js +28 -0
- package/out/test/store-install-safe.test.js +69 -0
- package/out/test/tls-bypass.test.js +90 -0
- package/out/test/warrant.test.js +138 -0
- package/out/utils.js +46 -9
- package/package.json +1 -1
package/out/gateway/bridge.js
CHANGED
|
@@ -6,9 +6,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
6
6
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
7
7
|
};
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.GatewayBridge = void 0;
|
|
9
|
+
exports.GatewayBridge = exports.MAX_GATEWAY_ID_CHARS = void 0;
|
|
10
10
|
exports.isIdeThreadCompositeRouteKey = isIdeThreadCompositeRouteKey;
|
|
11
11
|
exports.parseIdeThreadIdFromCompositeRouteKey = parseIdeThreadIdFromCompositeRouteKey;
|
|
12
|
+
exports.findOversizedRunField = findOversizedRunField;
|
|
12
13
|
exports.buildIdeSessionRouteKey = buildIdeSessionRouteKey;
|
|
13
14
|
exports.extractRouteLabelFromGatewaySessionKey = extractRouteLabelFromGatewaySessionKey;
|
|
14
15
|
exports.looksLikeGatewayChannelRouteKey = looksLikeGatewayChannelRouteKey;
|
|
@@ -52,6 +53,29 @@ function parseIdeThreadIdFromCompositeRouteKey(routeKey) {
|
|
|
52
53
|
const id = routeKey.slice(idx + IDE_THREAD_ROUTE_MARKER.length).trim();
|
|
53
54
|
return id.length > 0 ? id : undefined;
|
|
54
55
|
}
|
|
56
|
+
/** Max length for gateway-supplied identifiers that become filenames or route-index keys. */
|
|
57
|
+
exports.MAX_GATEWAY_ID_CHARS = 512;
|
|
58
|
+
const BOUNDED_RUN_FIELDS = [
|
|
59
|
+
"agentId", "sessionKey", "sessionId", "channel", "accountId",
|
|
60
|
+
"target", "threadId", "groupId", "senderUsername", "senderName", "senderId",
|
|
61
|
+
];
|
|
62
|
+
/**
|
|
63
|
+
* Name the first `agent.run` identifier that is present but not a sane bounded string.
|
|
64
|
+
* Returns undefined when every field is absent or valid.
|
|
65
|
+
*/
|
|
66
|
+
function findOversizedRunField(params) {
|
|
67
|
+
const record = params;
|
|
68
|
+
for (const field of BOUNDED_RUN_FIELDS) {
|
|
69
|
+
const value = record[field];
|
|
70
|
+
if (value === undefined || value === null) {
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
if (typeof value !== "string" || value.length > exports.MAX_GATEWAY_ID_CHARS) {
|
|
74
|
+
return field;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
55
79
|
function buildIdeSessionRouteKey(sessionKey, sessionId) {
|
|
56
80
|
const sk = sessionKey?.trim();
|
|
57
81
|
const sid = sessionId?.trim();
|
|
@@ -409,10 +433,24 @@ class GatewayBridge extends events_1.EventEmitter {
|
|
|
409
433
|
*/
|
|
410
434
|
async handleAgentRun(frame) {
|
|
411
435
|
const params = frame.params;
|
|
412
|
-
if (!params?.message) {
|
|
436
|
+
if (!params?.message || typeof params.message !== "string") {
|
|
413
437
|
this.client.respond(frame.id, undefined, false, { code: "INVALID_PARAMS", message: "message is required" });
|
|
414
438
|
return;
|
|
415
439
|
}
|
|
440
|
+
if (params.message.length > client_js_1.MAX_GATEWAY_MESSAGE_CHARS) {
|
|
441
|
+
this.client.respond(frame.id, undefined, false, {
|
|
442
|
+
code: "MESSAGE_TOO_LARGE",
|
|
443
|
+
message: `message exceeds ${client_js_1.MAX_GATEWAY_MESSAGE_CHARS} characters`,
|
|
444
|
+
});
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
// These land in filenames, route-index keys and JSON stores. An over-long or non-string
|
|
448
|
+
// value from the gateway is rejected here rather than truncated downstream.
|
|
449
|
+
const oversizedField = findOversizedRunField(params);
|
|
450
|
+
if (oversizedField) {
|
|
451
|
+
this.client.respond(frame.id, undefined, false, { code: "INVALID_PARAMS", message: `${oversizedField} is not a valid string` });
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
416
454
|
const agentId = params.agentId || this.getDefaultAgentId();
|
|
417
455
|
if (!agentId) {
|
|
418
456
|
this.client.respond(frame.id, undefined, false, { code: "NO_AGENT", message: "No agent available" });
|
package/out/gateway/client.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Copyright (c) AgentHippo.ai. All rights reserved.
|
|
4
4
|
*--------------------------------------------------------------------------------------------*/
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.GatewayClient = void 0;
|
|
6
|
+
exports.GatewayClient = exports.MAX_GATEWAY_MESSAGE_CHARS = exports.MAX_GATEWAY_FRAME_BYTES = void 0;
|
|
7
7
|
exports.createAgentIDEGatewayClient = createAgentIDEGatewayClient;
|
|
8
8
|
exports.createEphemeralGatewayClient = createEphemeralGatewayClient;
|
|
9
9
|
/* eslint-disable local/code-no-unexternalized-strings */
|
|
@@ -15,6 +15,27 @@ exports.createEphemeralGatewayClient = createEphemeralGatewayClient;
|
|
|
15
15
|
*/
|
|
16
16
|
const events_1 = require("events");
|
|
17
17
|
const protocol_js_1 = require("./protocol.js");
|
|
18
|
+
/**
|
|
19
|
+
* Largest inbound frame we will parse. Without a bound here a hostile or wedged gateway can OOM
|
|
20
|
+
* the extension host by streaming one unbounded frame into `JSON.parse`.
|
|
21
|
+
*
|
|
22
|
+
* This does NOT limit attachment size. Images, PDFs and other media travel as REFERENCES —
|
|
23
|
+
* `MediaAttachment.url` / `.path` pointing at gateway-side storage (e.g. `~/.openclaw/media/`) —
|
|
24
|
+
* so a 50 MB PDF adds a few hundred bytes to the frame, not 50 MB. The cap therefore only bounds
|
|
25
|
+
* the JSON control frame, and is set well above any legitimate one so that a real message is
|
|
26
|
+
* never dropped (a channel like Slack caps messages at ~40k characters).
|
|
27
|
+
*
|
|
28
|
+
* Overridable for operators who front the gateway with something unusual; values below 1 MiB are
|
|
29
|
+
* ignored so a typo cannot silently break normal traffic.
|
|
30
|
+
*/
|
|
31
|
+
exports.MAX_GATEWAY_FRAME_BYTES = (() => {
|
|
32
|
+
const configured = Number(process.env.AGENTHIPPO_GATEWAY_MAX_FRAME_BYTES);
|
|
33
|
+
return Number.isFinite(configured) && configured >= 1024 * 1024
|
|
34
|
+
? configured
|
|
35
|
+
: 16 * 1024 * 1024;
|
|
36
|
+
})();
|
|
37
|
+
/** Largest inbound `agent.run` message we hand to an engine, in characters. */
|
|
38
|
+
exports.MAX_GATEWAY_MESSAGE_CHARS = 256 * 1024;
|
|
18
39
|
function formatWebSocketCloseDetails(ev) {
|
|
19
40
|
if (!ev) {
|
|
20
41
|
return "WebSocket closed";
|
|
@@ -157,6 +178,18 @@ class GatewayClient extends events_1.EventEmitter {
|
|
|
157
178
|
this.ws.send(JSON.stringify(frame));
|
|
158
179
|
}
|
|
159
180
|
handleMessage(data) {
|
|
181
|
+
// Bound before parsing — an oversized frame is dropped, never buffered into an object.
|
|
182
|
+
const byteLength = typeof data === "string"
|
|
183
|
+
? Buffer.byteLength(data, "utf8")
|
|
184
|
+
: 0;
|
|
185
|
+
if (byteLength > exports.MAX_GATEWAY_FRAME_BYTES) {
|
|
186
|
+
// Loud, not silent: to a channel user a dropped frame looks like the agent ignoring
|
|
187
|
+
// them, so name the limit and the override rather than failing quietly.
|
|
188
|
+
this.emit("error", new Error(`Gateway frame of ${byteLength} bytes exceeds the ${exports.MAX_GATEWAY_FRAME_BYTES}-byte limit and was dropped. ` +
|
|
189
|
+
`Attachments normally travel as references, so this suggests inline payloads; ` +
|
|
190
|
+
`raise AGENTHIPPO_GATEWAY_MAX_FRAME_BYTES if this traffic is expected.`));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
160
193
|
let frame;
|
|
161
194
|
try {
|
|
162
195
|
frame = JSON.parse(data);
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*---------------------------------------------------------------------------------------------
|
|
3
|
+
* Copyright (c) AgentHippo.ai. All rights reserved.
|
|
4
|
+
*--------------------------------------------------------------------------------------------*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ANYWHERE_INSTALL_SOURCE_FORMS = exports.ANYWHERE_CONFIG_TEMPLATE_ENV = exports.ANYWHERE_SETUP_SCRIPT_ENV = exports.ANYWHERE_INSTALL_SOURCE_ENV = void 0;
|
|
7
|
+
exports.validateAnywhereInstallSource = validateAnywhereInstallSource;
|
|
8
|
+
/**
|
|
9
|
+
* Validation for `agentide.anywhere.installSource`, kept in its own module (no `vscode` import) so
|
|
10
|
+
* the rule can be tested directly rather than through the extension host.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Environment variable carrying the install source into the setup terminal.
|
|
14
|
+
*
|
|
15
|
+
* The value is never interpolated into the command STRING. `"$AH_INSTALL_SOURCE"` (POSIX) and
|
|
16
|
+
* `$env:AH_INSTALL_SOURCE` (PowerShell) expand to one argument after the shell has finished
|
|
17
|
+
* parsing, so no character in the value can become syntax — which is the property the old
|
|
18
|
+
* blocklist (`[;`|$&\n\r]`, missing the double quote) failed to deliver.
|
|
19
|
+
*/
|
|
20
|
+
exports.ANYWHERE_INSTALL_SOURCE_ENV = 'AH_INSTALL_SOURCE';
|
|
21
|
+
/** Same treatment for the two bundled paths we pass alongside it. */
|
|
22
|
+
exports.ANYWHERE_SETUP_SCRIPT_ENV = 'AH_SETUP_SCRIPT';
|
|
23
|
+
exports.ANYWHERE_CONFIG_TEMPLATE_ENV = 'AH_CONFIG_TEMPLATE';
|
|
24
|
+
/**
|
|
25
|
+
* Strict allowlist for `agentide.anywhere.installSource`.
|
|
26
|
+
*
|
|
27
|
+
* This value names a package that is installed globally and whose `postinstall` script runs with
|
|
28
|
+
* the user's privileges, so it is a code-execution setting, not a string. The setting is declared
|
|
29
|
+
* `"scope": "application"` so a workspace cannot set it at all; this is the second gate, and it is
|
|
30
|
+
* an ALLOWLIST rather than a character blocklist — a blocklist has to be right about every shell
|
|
31
|
+
* metacharacter forever, and the previous one already missed `"`, which was enough to escape
|
|
32
|
+
* `--source "${installSource}"` and run arbitrary commands in the user's setup terminal.
|
|
33
|
+
*
|
|
34
|
+
* Accepted shapes, and nothing else:
|
|
35
|
+
* - npm specifier: `pkg`, `pkg@1.2.3`, `pkg@latest`, `pkg@^1.2.3`, `@scope/pkg@2026.7.28`
|
|
36
|
+
* - `github:owner/repo` optionally `#ref`
|
|
37
|
+
* - an https:// URL ending in a tarball extension (a pinned artifact)
|
|
38
|
+
* - an absolute or explicitly-relative local path (a developer's clone)
|
|
39
|
+
*/
|
|
40
|
+
const NPM_SPECIFIER = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*(?:@[a-zA-Z0-9^~*><=][a-zA-Z0-9.^~*+><=|_-]*)?$/;
|
|
41
|
+
const GITHUB_SPECIFIER = /^github:[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._-]*(?:#[A-Za-z0-9._/-]+)?$/;
|
|
42
|
+
const TARBALL_URL = /^https:\/\/[A-Za-z0-9._-]+(?::\d+)?\/[A-Za-z0-9._~!$'()*+,;=:@%/-]*\.(?:tgz|tar\.gz)$/;
|
|
43
|
+
const POSIX_PATH = /^(?:\.{1,2})?\/[^\0<>"'`;|&$\n\r]*$/;
|
|
44
|
+
const WINDOWS_PATH = /^[A-Za-z]:[\\/][^\0<>"'`;|&$\n\r]*$/;
|
|
45
|
+
/** Named in every refusal so the message tells the user what they CAN write. */
|
|
46
|
+
exports.ANYWHERE_INSTALL_SOURCE_FORMS = 'accepted forms are: an npm specifier (e.g. @agenthippo-ai/hippoclaw@2026.7.28), ' +
|
|
47
|
+
'github:owner/repo[#ref], an https:// .tgz/.tar.gz URL, or a local path';
|
|
48
|
+
function validateAnywhereInstallSource(source) {
|
|
49
|
+
const refuse = (why) => ({
|
|
50
|
+
ok: false,
|
|
51
|
+
reason: `${why} — ${exports.ANYWHERE_INSTALL_SOURCE_FORMS}`,
|
|
52
|
+
});
|
|
53
|
+
const value = source.trim();
|
|
54
|
+
if (!value) {
|
|
55
|
+
return refuse('it is empty');
|
|
56
|
+
}
|
|
57
|
+
if (value !== source) {
|
|
58
|
+
return refuse('it has leading or trailing whitespace');
|
|
59
|
+
}
|
|
60
|
+
if (NPM_SPECIFIER.test(value) ||
|
|
61
|
+
GITHUB_SPECIFIER.test(value) ||
|
|
62
|
+
TARBALL_URL.test(value) ||
|
|
63
|
+
POSIX_PATH.test(value) ||
|
|
64
|
+
WINDOWS_PATH.test(value)) {
|
|
65
|
+
return { ok: true };
|
|
66
|
+
}
|
|
67
|
+
return refuse('it is not one of the accepted forms');
|
|
68
|
+
}
|
package/out/gateway/lifecycle.js
CHANGED
|
@@ -36,7 +36,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
36
36
|
};
|
|
37
37
|
})();
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.GatewayLifecycleManager = exports.AGENT_ANYWHERE_LABEL = void 0;
|
|
39
|
+
exports.GatewayLifecycleManager = exports.validateAnywhereInstallSource = exports.ANYWHERE_SETUP_SCRIPT_ENV = exports.ANYWHERE_INSTALL_SOURCE_ENV = exports.ANYWHERE_CONFIG_TEMPLATE_ENV = exports.AGENT_ANYWHERE_LABEL = void 0;
|
|
40
40
|
exports.detectGateway = detectGateway;
|
|
41
41
|
/* eslint-disable local/code-no-unexternalized-strings */
|
|
42
42
|
/**
|
|
@@ -57,9 +57,11 @@ const path = __importStar(require("path"));
|
|
|
57
57
|
const os = __importStar(require("os"));
|
|
58
58
|
const index_1 = require("../utils/index");
|
|
59
59
|
const agentEnvFile_js_1 = require("../utils/agentEnvFile.js");
|
|
60
|
+
const secretsPreflight_js_1 = require("./secretsPreflight.js");
|
|
60
61
|
const windowsCommandLine_js_1 = require("../utils/windowsCommandLine.js");
|
|
61
62
|
const gateway_scope_errors_js_1 = require("./gateway-scope-errors.js");
|
|
62
63
|
const openclaw_channel_routing_js_1 = require("./openclaw-channel-routing.js");
|
|
64
|
+
const branding_js_1 = require("../branding.js");
|
|
63
65
|
const execAsync = (0, util_1.promisify)(child_process_1.exec);
|
|
64
66
|
const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
|
|
65
67
|
const IS_WINDOWS = process.platform === "win32";
|
|
@@ -228,6 +230,7 @@ const STALE_GATEWAY_LOCK_MIN_AGE_MS = 30_000;
|
|
|
228
230
|
/** PID file for fast status checks (~1-2ms vs 100ms+ for shell commands) */
|
|
229
231
|
const GATEWAY_PID_FILE = index_1.PID_FILES.gateway();
|
|
230
232
|
const FORK_CLI_NAME = "hippoclaw";
|
|
233
|
+
const GATEWAY_NPM_PACKAGE_DIRS = ["@agenthippo-ai/hippoclaw", "openclaw"];
|
|
231
234
|
const MANAGED_GATEWAY_STATE_DIR = path.join(os.homedir(), ".agenthippo", "gateway");
|
|
232
235
|
const MANAGED_GATEWAY_MANIFEST_PATH = path.join(MANAGED_GATEWAY_STATE_DIR, "install.json");
|
|
233
236
|
const GATEWAY_INSTALL_STATUS_FILE = path.join(MANAGED_GATEWAY_STATE_DIR, "install-status.json");
|
|
@@ -239,6 +242,11 @@ const MANAGED_OPENCLAW_WRAPPER_PATH = path.join(MANAGED_GATEWAY_WRAPPER_DIR, IS_
|
|
|
239
242
|
const MANAGED_GATEWAY_WRAPPER_MARKER = IS_WINDOWS
|
|
240
243
|
? "REM Managed by Agent Anywhere"
|
|
241
244
|
: "# Managed by Agent Anywhere";
|
|
245
|
+
const installSource_js_1 = require("./installSource.js");
|
|
246
|
+
Object.defineProperty(exports, "ANYWHERE_CONFIG_TEMPLATE_ENV", { enumerable: true, get: function () { return installSource_js_1.ANYWHERE_CONFIG_TEMPLATE_ENV; } });
|
|
247
|
+
Object.defineProperty(exports, "ANYWHERE_INSTALL_SOURCE_ENV", { enumerable: true, get: function () { return installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV; } });
|
|
248
|
+
Object.defineProperty(exports, "ANYWHERE_SETUP_SCRIPT_ENV", { enumerable: true, get: function () { return installSource_js_1.ANYWHERE_SETUP_SCRIPT_ENV; } });
|
|
249
|
+
Object.defineProperty(exports, "validateAnywhereInstallSource", { enumerable: true, get: function () { return installSource_js_1.validateAnywhereInstallSource; } });
|
|
242
250
|
function installSourceNeedsBuild(source) {
|
|
243
251
|
if (!source) {
|
|
244
252
|
return false;
|
|
@@ -495,6 +503,11 @@ function addManagedOpenclawRoot(roots, candidate) {
|
|
|
495
503
|
roots.add(candidate);
|
|
496
504
|
}
|
|
497
505
|
}
|
|
506
|
+
function addManagedGatewayPackageRoots(roots, nodeModulesDir) {
|
|
507
|
+
for (const pkgDir of GATEWAY_NPM_PACKAGE_DIRS) {
|
|
508
|
+
addManagedOpenclawRoot(roots, path.join(nodeModulesDir, pkgDir));
|
|
509
|
+
}
|
|
510
|
+
}
|
|
498
511
|
/** Scan nvm version dirs when `npm root -g` is unavailable in the extension host PATH. */
|
|
499
512
|
function collectNvmOpenclawPackageRoots() {
|
|
500
513
|
const home = os.homedir();
|
|
@@ -509,7 +522,7 @@ function collectNvmOpenclawPackageRoots() {
|
|
|
509
522
|
if (!entry.isDirectory()) {
|
|
510
523
|
continue;
|
|
511
524
|
}
|
|
512
|
-
candidates.push(path.join(versionsDir, entry.name, "lib", "node_modules",
|
|
525
|
+
candidates.push(...GATEWAY_NPM_PACKAGE_DIRS.map((pkgDir) => path.join(versionsDir, entry.name, "lib", "node_modules", pkgDir)));
|
|
513
526
|
}
|
|
514
527
|
}
|
|
515
528
|
catch {
|
|
@@ -533,15 +546,17 @@ async function getManagedOpenclawRoots() {
|
|
|
533
546
|
continue;
|
|
534
547
|
}
|
|
535
548
|
// Windows npm uses node_modules/ directly (no lib/ prefix); Unix uses lib/node_modules/
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
549
|
+
for (const pkgDir of GATEWAY_NPM_PACKAGE_DIRS) {
|
|
550
|
+
const candidate = path.join(bootstrapNodeRoot, entry.name, ...(IS_WINDOWS ? ["node_modules"] : ["lib", "node_modules"]), pkgDir);
|
|
551
|
+
if (!fs.existsSync(candidate)) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
try {
|
|
555
|
+
roots.add(fs.realpathSync(candidate));
|
|
556
|
+
}
|
|
557
|
+
catch {
|
|
558
|
+
roots.add(candidate);
|
|
559
|
+
}
|
|
545
560
|
}
|
|
546
561
|
}
|
|
547
562
|
}
|
|
@@ -550,8 +565,7 @@ async function getManagedOpenclawRoots() {
|
|
|
550
565
|
}
|
|
551
566
|
}
|
|
552
567
|
// Windows npm uses node_modules/ directly (no lib/ prefix); Unix uses lib/node_modules/
|
|
553
|
-
|
|
554
|
-
addManagedOpenclawRoot(roots, legacyManaged);
|
|
568
|
+
addManagedGatewayPackageRoots(roots, path.join(os.homedir(), ".npm-global", ...(IS_WINDOWS ? ["node_modules"] : ["lib", "node_modules"])));
|
|
555
569
|
for (const nvmManaged of collectNvmOpenclawPackageRoots()) {
|
|
556
570
|
addManagedOpenclawRoot(roots, nvmManaged);
|
|
557
571
|
}
|
|
@@ -559,7 +573,7 @@ async function getManagedOpenclawRoots() {
|
|
|
559
573
|
const { stdout } = await execAsync("npm root -g", { timeout: 3_000 });
|
|
560
574
|
const npmRoot = stdout.trim();
|
|
561
575
|
if (npmRoot) {
|
|
562
|
-
|
|
576
|
+
addManagedGatewayPackageRoots(roots, npmRoot);
|
|
563
577
|
}
|
|
564
578
|
}
|
|
565
579
|
catch {
|
|
@@ -1141,7 +1155,7 @@ class GatewayLifecycleManager {
|
|
|
1141
1155
|
// Auto-start if configured and installed
|
|
1142
1156
|
const autoStart = this.getConfig("autoStart", false);
|
|
1143
1157
|
if (autoStart && this._lastDetection?.installed && !this._lastDetection?.running) {
|
|
1144
|
-
this.log(
|
|
1158
|
+
this.log(`Auto-starting gateway (${(0, branding_js_1.toSettingsDisplayKey)("agentide.anywhere.autoStart")} = true)`);
|
|
1145
1159
|
await this.start();
|
|
1146
1160
|
}
|
|
1147
1161
|
}
|
|
@@ -1763,15 +1777,18 @@ class GatewayLifecycleManager {
|
|
|
1763
1777
|
const installSource = this.getConfig("installSource", "");
|
|
1764
1778
|
const port = this.getGatewayPort();
|
|
1765
1779
|
if (!installSource) {
|
|
1766
|
-
vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Install source not configured. Set "agentide.anywhere.installSource" in settings.`);
|
|
1780
|
+
vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Install source not configured. Set "${(0, branding_js_1.toSettingsDisplayKey)("agentide.anywhere.installSource")}" in settings.`);
|
|
1767
1781
|
return false;
|
|
1768
1782
|
}
|
|
1769
|
-
// Security:
|
|
1770
|
-
//
|
|
1771
|
-
//
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1783
|
+
// Security: this value names a package that is installed globally and whose postinstall
|
|
1784
|
+
// script runs as the user. It is validated against an ALLOWLIST of specifier shapes (see
|
|
1785
|
+
// validateAnywhereInstallSource) and then passed to the setup terminal through an
|
|
1786
|
+
// environment variable rather than being interpolated into a command string.
|
|
1787
|
+
const sourceCheck = (0, installSource_js_1.validateAnywhereInstallSource)(installSource);
|
|
1788
|
+
if (!sourceCheck.ok) {
|
|
1789
|
+
vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Refusing to install from "${installSource}" — ${sourceCheck.reason}. ` +
|
|
1790
|
+
`Fix "${(0, branding_js_1.toSettingsDisplayKey)("agentide.anywhere.installSource")}" in your user settings.`);
|
|
1791
|
+
this.log(`Rejected installSource (${sourceCheck.reason}): ${installSource}`);
|
|
1775
1792
|
return false;
|
|
1776
1793
|
}
|
|
1777
1794
|
if (this._gatewayInstallInProgress) {
|
|
@@ -1816,46 +1833,77 @@ class GatewayLifecycleManager {
|
|
|
1816
1833
|
];
|
|
1817
1834
|
const templatePath = templateCandidates.find((p) => fs.existsSync(p));
|
|
1818
1835
|
let terminalOptions;
|
|
1836
|
+
// NOTE ON WHY THIS IS NOT execFile/spawn WITH AN ARGV ARRAY.
|
|
1837
|
+
// The setup runs in a VISIBLE terminal (the user watches npm/git progress and answers
|
|
1838
|
+
// prompts), and it must run under a LOGIN shell: node/npm/pnpm on macOS commonly live on
|
|
1839
|
+
// a PATH built by ~/.zprofile, and `getAnywhereSetupShellArgs` uses `-lic`/`-lc` for
|
|
1840
|
+
// exactly that reason. A bare argv exec would break every nvm/volta install.
|
|
1841
|
+
// So the shell stays — but no untrusted value is ever spliced into the command text.
|
|
1842
|
+
// `installSource` (and the two bundled paths) travel in the terminal's ENVIRONMENT and
|
|
1843
|
+
// are referenced as `"$VAR"` / `$env:VAR`, which the shell expands AFTER parsing: the
|
|
1844
|
+
// value becomes exactly one argument and none of its characters can become syntax.
|
|
1845
|
+
const setupEnv = {
|
|
1846
|
+
...anywhereSetupTerminalEnv(),
|
|
1847
|
+
[installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV]: installSource,
|
|
1848
|
+
...(scriptPath ? { [installSource_js_1.ANYWHERE_SETUP_SCRIPT_ENV]: scriptPath } : {}),
|
|
1849
|
+
...(templatePath ? { [installSource_js_1.ANYWHERE_CONFIG_TEMPLATE_ENV]: templatePath } : {}),
|
|
1850
|
+
};
|
|
1819
1851
|
if (scriptPath && IS_WINDOWS) {
|
|
1820
1852
|
// Windows: run PowerShell script
|
|
1821
|
-
const args = [`-Source
|
|
1853
|
+
const args = [`-Source $env:${installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV}`];
|
|
1822
1854
|
if (templatePath) {
|
|
1823
|
-
args.push(`-ConfigTemplate
|
|
1855
|
+
args.push(`-ConfigTemplate $env:${installSource_js_1.ANYWHERE_CONFIG_TEMPLATE_ENV}`);
|
|
1824
1856
|
}
|
|
1825
|
-
const psCmd = `&
|
|
1857
|
+
const psCmd = `& $env:${installSource_js_1.ANYWHERE_SETUP_SCRIPT_ENV} ${args.join(" ")}; Write-Host ""; Write-Host "You can close this window."`;
|
|
1826
1858
|
terminalOptions = {
|
|
1827
1859
|
name: AGENT_ANYWHERE_SETUP_TERMINAL,
|
|
1828
1860
|
shellPath: "powershell.exe",
|
|
1829
1861
|
shellArgs: ["-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass", "-Command", psCmd],
|
|
1862
|
+
env: {
|
|
1863
|
+
[installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV]: installSource,
|
|
1864
|
+
[installSource_js_1.ANYWHERE_SETUP_SCRIPT_ENV]: scriptPath,
|
|
1865
|
+
...(templatePath ? { [installSource_js_1.ANYWHERE_CONFIG_TEMPLATE_ENV]: templatePath } : {}),
|
|
1866
|
+
},
|
|
1830
1867
|
};
|
|
1831
1868
|
}
|
|
1832
1869
|
else if (scriptPath) {
|
|
1833
1870
|
// macOS/Linux: run bash script
|
|
1834
|
-
const args = [`--source "
|
|
1871
|
+
const args = [`--source "$${installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV}"`];
|
|
1835
1872
|
if (templatePath) {
|
|
1836
|
-
args.push(`--config-template "
|
|
1873
|
+
args.push(`--config-template "$${installSource_js_1.ANYWHERE_CONFIG_TEMPLATE_ENV}"`);
|
|
1837
1874
|
}
|
|
1838
|
-
const setupCmd = `bash "
|
|
1875
|
+
const setupCmd = `bash "$${installSource_js_1.ANYWHERE_SETUP_SCRIPT_ENV}" ${args.join(" ")}`;
|
|
1839
1876
|
const holdOpenCmd = `${setupCmd}; echo ""; echo "Press Enter to close..."; read`;
|
|
1840
1877
|
terminalOptions = {
|
|
1841
1878
|
name: AGENT_ANYWHERE_SETUP_TERMINAL,
|
|
1842
1879
|
shellPath: getAnywhereSetupShellPath(),
|
|
1843
1880
|
shellArgs: getAnywhereSetupShellArgs(holdOpenCmd),
|
|
1844
|
-
env:
|
|
1881
|
+
env: setupEnv,
|
|
1845
1882
|
};
|
|
1846
1883
|
}
|
|
1847
1884
|
else {
|
|
1848
1885
|
// Fallback: raw npm install (script not bundled)
|
|
1849
1886
|
this.log("Setup script not found, falling back to npm install");
|
|
1850
|
-
const fallbackCmd = `npm install -g
|
|
1887
|
+
const fallbackCmd = `npm install -g "$${installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV}"`;
|
|
1851
1888
|
const holdOpenCmd = `${fallbackCmd}; echo ""; echo "Press Enter to close..."; read`;
|
|
1852
1889
|
terminalOptions = IS_WINDOWS
|
|
1853
|
-
|
|
1890
|
+
// cmd.exe is the one shell here that expands `%VAR%` DURING parsing rather than
|
|
1891
|
+
// after it, so the env indirection alone is not a boundary on Windows — the
|
|
1892
|
+
// allowlist above is what makes this safe (it admits no `&`, `|`, `^`, quote or
|
|
1893
|
+
// whitespace outside a path shape). Kept as cmd.exe because this fallback only
|
|
1894
|
+
// fires when the bundled setup script is missing, and PowerShell is not
|
|
1895
|
+
// guaranteed present on every Windows host we support.
|
|
1896
|
+
? {
|
|
1897
|
+
name: AGENT_ANYWHERE_SETUP_TERMINAL,
|
|
1898
|
+
shellPath: "cmd.exe",
|
|
1899
|
+
shellArgs: ["/c", `npm install -g "%${installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV}%" && echo. && echo Setup complete. && pause`],
|
|
1900
|
+
env: { [installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV]: installSource },
|
|
1901
|
+
}
|
|
1854
1902
|
: {
|
|
1855
1903
|
name: AGENT_ANYWHERE_SETUP_TERMINAL,
|
|
1856
1904
|
shellPath: getAnywhereSetupShellPath(),
|
|
1857
1905
|
shellArgs: getAnywhereSetupShellArgs(holdOpenCmd),
|
|
1858
|
-
env:
|
|
1906
|
+
env: setupEnv,
|
|
1859
1907
|
};
|
|
1860
1908
|
}
|
|
1861
1909
|
// Run in a visible terminal so user can see progress
|
|
@@ -2119,6 +2167,93 @@ class GatewayLifecycleManager {
|
|
|
2119
2167
|
const doc = await vscode.workspace.openTextDocument(this.configPath);
|
|
2120
2168
|
await vscode.window.showTextDocument(doc);
|
|
2121
2169
|
}
|
|
2170
|
+
/**
|
|
2171
|
+
* Check that every enabled channel's env-sourced secrets resolve before spawning the
|
|
2172
|
+
* gateway. On missing secrets, offer to disable the affected channels (config is backed
|
|
2173
|
+
* up first) or to open ~/.agent-hippo/.env with the missing keys stubbed in.
|
|
2174
|
+
*
|
|
2175
|
+
* Configs that are not strict JSON (OpenClaw accepts JSON5 with comments) are left to
|
|
2176
|
+
* the gateway itself: rewriting them would destroy comments, and the fail-fast exit
|
|
2177
|
+
* detection in doStart still surfaces the error promptly.
|
|
2178
|
+
*/
|
|
2179
|
+
async preflightChannelSecrets(gatewayEnv) {
|
|
2180
|
+
let content;
|
|
2181
|
+
try {
|
|
2182
|
+
content = fs.readFileSync(this.configPath, "utf-8");
|
|
2183
|
+
}
|
|
2184
|
+
catch {
|
|
2185
|
+
return "continue"; // No config yet — nothing to resolve.
|
|
2186
|
+
}
|
|
2187
|
+
const config = (0, secretsPreflight_js_1.parseStrictJsonConfig)(content);
|
|
2188
|
+
if (config === undefined) {
|
|
2189
|
+
return "continue";
|
|
2190
|
+
}
|
|
2191
|
+
const missing = (0, secretsPreflight_js_1.collectMissingChannelEnvSecrets)(config, gatewayEnv);
|
|
2192
|
+
if (missing.length === 0) {
|
|
2193
|
+
return "continue";
|
|
2194
|
+
}
|
|
2195
|
+
const envFilePath = (0, agentEnvFile_js_1.getAgentHomeEnvFilePath)();
|
|
2196
|
+
const channelNames = missing.map((m) => m.channel).join(", ");
|
|
2197
|
+
const varNames = [...new Set(missing.flatMap((m) => m.envVars))];
|
|
2198
|
+
this.log(`Channel secrets preflight: ${channelNames} missing ${varNames.join(", ")}`);
|
|
2199
|
+
const disableAndStart = `Disable ${channelNames} & Start`;
|
|
2200
|
+
const addSecrets = "Add Secrets";
|
|
2201
|
+
const choice = await vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: The ${channelNames} channel needs ${varNames.join(", ")}, which ${varNames.length === 1 ? "is" : "are"} not set in ${envFilePath} or the IDE environment. Add the secret${varNames.length === 1 ? "" : "s"} or disable the channel to start the gateway.`, disableAndStart, addSecrets);
|
|
2202
|
+
if (choice === disableAndStart) {
|
|
2203
|
+
const updated = (0, secretsPreflight_js_1.disableChannelsInConfigText)(content, missing.map((m) => m.channel));
|
|
2204
|
+
if (!updated) {
|
|
2205
|
+
void vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Could not update ${this.configPath}. Set "enabled": false on the ${channelNames} channel manually, then start again.`);
|
|
2206
|
+
return "abort";
|
|
2207
|
+
}
|
|
2208
|
+
const backupPath = `${this.configPath}.bak-missing-secrets`;
|
|
2209
|
+
try {
|
|
2210
|
+
fs.copyFileSync(this.configPath, backupPath);
|
|
2211
|
+
fs.writeFileSync(this.configPath, updated, { encoding: "utf-8", mode: IS_WINDOWS ? undefined : 0o600 });
|
|
2212
|
+
}
|
|
2213
|
+
catch (err) {
|
|
2214
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
2215
|
+
void vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Could not update ${this.configPath} (${detail}).`);
|
|
2216
|
+
return "abort";
|
|
2217
|
+
}
|
|
2218
|
+
this.log(`Disabled channels missing secrets: ${channelNames} (backup: ${backupPath})`);
|
|
2219
|
+
void vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Disabled the ${channelNames} channel (backup: ${path.basename(backupPath)}). To re-enable: add ${varNames.join(", ")} to ${envFilePath}, set "enabled": true in the config, then restart the gateway.`);
|
|
2220
|
+
return "continue";
|
|
2221
|
+
}
|
|
2222
|
+
if (choice === addSecrets) {
|
|
2223
|
+
await this.openEnvFileWithSecretStubs(envFilePath, varNames);
|
|
2224
|
+
}
|
|
2225
|
+
return "abort";
|
|
2226
|
+
}
|
|
2227
|
+
/** Append stub lines for missing secret keys to ~/.agent-hippo/.env and open it. */
|
|
2228
|
+
async openEnvFileWithSecretStubs(envFilePath, keys) {
|
|
2229
|
+
try {
|
|
2230
|
+
let content = "";
|
|
2231
|
+
try {
|
|
2232
|
+
content = fs.readFileSync(envFilePath, "utf-8");
|
|
2233
|
+
}
|
|
2234
|
+
catch {
|
|
2235
|
+
// New file.
|
|
2236
|
+
}
|
|
2237
|
+
const toAdd = keys.filter((key) => !new RegExp(`^\\s*#?\\s*${key}=`, "m").test(content));
|
|
2238
|
+
if (toAdd.length) {
|
|
2239
|
+
const separator = content.length && !content.endsWith("\n") ? "\n" : "";
|
|
2240
|
+
const block = [
|
|
2241
|
+
`# Added by ${exports.AGENT_ANYWHERE_LABEL} — fill in, then start the gateway again.`,
|
|
2242
|
+
...toAdd.map((key) => `${key}=`),
|
|
2243
|
+
].join("\n");
|
|
2244
|
+
fs.writeFileSync(envFilePath, `${content}${separator}${block}\n`, {
|
|
2245
|
+
encoding: "utf-8",
|
|
2246
|
+
mode: IS_WINDOWS ? undefined : 0o600,
|
|
2247
|
+
});
|
|
2248
|
+
}
|
|
2249
|
+
const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(envFilePath));
|
|
2250
|
+
await vscode.window.showTextDocument(doc, { preview: false });
|
|
2251
|
+
}
|
|
2252
|
+
catch (err) {
|
|
2253
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
2254
|
+
void vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Could not open ${envFilePath} (${detail}).`);
|
|
2255
|
+
}
|
|
2256
|
+
}
|
|
2122
2257
|
async startGatewayViaHiddenWindowsProcess(spawnExe, spawnArgs, logPath, errorLogPath, gatewayEnv) {
|
|
2123
2258
|
const argumentLine = (0, windowsCommandLine_js_1.buildWindowsArgumentLine)(spawnArgs);
|
|
2124
2259
|
const argumentClause = argumentLine.length > 0
|
|
@@ -2301,6 +2436,13 @@ class GatewayLifecycleManager {
|
|
|
2301
2436
|
this.log(`Gateway env: ${key} present for child process`);
|
|
2302
2437
|
}
|
|
2303
2438
|
}
|
|
2439
|
+
// Channel secrets preflight: an enabled channel referencing an unset env var makes the
|
|
2440
|
+
// gateway crash at startup (SecretRefResolutionError) with the reason buried in the log.
|
|
2441
|
+
// Catch it here and offer a fix instead of spawning a process that cannot survive.
|
|
2442
|
+
if ((await this.preflightChannelSecrets(gatewayEnv)) === "abort") {
|
|
2443
|
+
this.setStatus("installed");
|
|
2444
|
+
return;
|
|
2445
|
+
}
|
|
2304
2446
|
let startedPid;
|
|
2305
2447
|
if (isWindowsLaunch) {
|
|
2306
2448
|
startedPid = await this.startGatewayViaHiddenWindowsProcess(spawnExe, spawnArgs, logPath, errorLogPath, gatewayEnv);
|
|
@@ -2378,7 +2520,12 @@ class GatewayLifecycleManager {
|
|
|
2378
2520
|
processAlive &&
|
|
2379
2521
|
sawLaunchedProcessAlive &&
|
|
2380
2522
|
elapsed < startupTimeoutMs + launchGraceMs;
|
|
2381
|
-
|
|
2523
|
+
// A gateway that crashes at boot (e.g. unresolvable secrets) can exit before
|
|
2524
|
+
// the first aliveness sample, so don't require sawLaunchedProcessAlive: on
|
|
2525
|
+
// Unix the exit handler nulling this.process is just as definitive. Without
|
|
2526
|
+
// this, an instant crash silently spins until the startup timeout.
|
|
2527
|
+
const launchedProcessGone = !processAlive && (sawLaunchedProcessAlive || this.process === null);
|
|
2528
|
+
if (trackingPid && launchedProcessGone && !isWindowsLaunch) {
|
|
2382
2529
|
this.setStatus("error");
|
|
2383
2530
|
this.log(`Gateway process exited before opening port ${port}`);
|
|
2384
2531
|
await showGatewayStartFailedToast(logPath);
|
|
@@ -2609,16 +2756,8 @@ class GatewayLifecycleManager {
|
|
|
2609
2756
|
encoding: "utf-8",
|
|
2610
2757
|
mode: IS_WINDOWS ? undefined : 0o600,
|
|
2611
2758
|
});
|
|
2759
|
+
// Application-scoped setting: user scope is the only writable target.
|
|
2612
2760
|
const gwConfig = vscode.workspace.getConfiguration("agentide.gateway");
|
|
2613
|
-
if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length > 0) {
|
|
2614
|
-
try {
|
|
2615
|
-
await gwConfig.update("token", newToken, vscode.ConfigurationTarget.Workspace);
|
|
2616
|
-
}
|
|
2617
|
-
catch (err) {
|
|
2618
|
-
this.log(`Workspace token update skipped: ${err instanceof Error ? err.message : String(err)}`);
|
|
2619
|
-
}
|
|
2620
|
-
}
|
|
2621
|
-
// Always persist user scope so empty-window mode (no workspace) still works.
|
|
2622
2761
|
await gwConfig.update("token", newToken, vscode.ConfigurationTarget.Global);
|
|
2623
2762
|
await this.restartGateway();
|
|
2624
2763
|
void vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Auth reset; gateway restarted. If you use the Web UI, paste the new token in Control UI settings.`);
|
|
@@ -2702,19 +2841,14 @@ class GatewayLifecycleManager {
|
|
|
2702
2841
|
await gwConfig.update("url", wsUrl, vscode.ConfigurationTarget.Global);
|
|
2703
2842
|
}
|
|
2704
2843
|
// For local gateways, apply gateway.auth.token from ~/.openclaw/openclaw.json.
|
|
2705
|
-
//
|
|
2844
|
+
// The setting is application-scoped (a credential must never land in a committable
|
|
2845
|
+
// .vscode/settings.json), and VS Code rejects any non-user write for that scope.
|
|
2706
2846
|
// Remote URLs: do not override — token must come from settings / env for that host.
|
|
2707
2847
|
const localGateway = /127\.0\.0\.1|localhost|\[::1\]/i.test(wsUrl);
|
|
2708
2848
|
const fileToken = this.readGatewayToken();
|
|
2709
2849
|
if (fileToken && localGateway) {
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
this.log("Gateway token applied from openclaw.json (workspace scope)");
|
|
2713
|
-
}
|
|
2714
|
-
else {
|
|
2715
|
-
await gwConfig.update("token", fileToken, vscode.ConfigurationTarget.Global);
|
|
2716
|
-
this.log("Gateway token applied from openclaw.json (global scope; no workspace open)");
|
|
2717
|
-
}
|
|
2850
|
+
await gwConfig.update("token", fileToken, vscode.ConfigurationTarget.Global);
|
|
2851
|
+
this.log("Gateway token applied from openclaw.json (user scope)");
|
|
2718
2852
|
}
|
|
2719
2853
|
await this._bridgeRef.connect();
|
|
2720
2854
|
this.setBridgeConnected(this._bridgeRef.isConnected);
|
|
@@ -47,7 +47,10 @@ exports.GATEWAY_OUTBOUND_FILENAME = 'gateway-outbound.json';
|
|
|
47
47
|
exports.AGENTHIPPO_GATEWAY_OUTBOUND_FILE_ENV = 'AGENTHIPPO_GATEWAY_OUTBOUND_FILE';
|
|
48
48
|
/** Sanitize gateway session keys for use as `.agent-hippo/sessions/<id>/` directory names. */
|
|
49
49
|
function safeGatewaySessionDirId(value) {
|
|
50
|
-
|
|
50
|
+
const sanitized = value.replace(/[<>:"/\\|?*\x00-\x1F]/g, '_').slice(0, 160);
|
|
51
|
+
// Dot-only keys ("." / "..") would resolve outside the per-session directory — the key is
|
|
52
|
+
// gateway-supplied, so that is a path traversal. Map each dot to "_" to stay collision-free.
|
|
53
|
+
return sanitized.replace(/^\.+$/, m => '_'.repeat(m.length)) || 'session';
|
|
51
54
|
}
|
|
52
55
|
function getGatewayOutboundFilePath(workspaceRoot, sessionDirId) {
|
|
53
56
|
return path.join(workspaceRoot, branding_js_1.AGENTIDE_DIR_NAME, 'sessions', sessionDirId, exports.GATEWAY_OUTBOUND_FILENAME);
|