diff --git a/agents/hermes/config/hermes-config.ts b/agents/hermes/config/hermes-config.ts index 5e634bbf154..3fd25c3ff02 100644 --- a/agents/hermes/config/hermes-config.ts +++ b/agents/hermes/config/hermes-config.ts @@ -99,7 +99,7 @@ export function buildHermesConfig(settings: HermesBuildSettings): Record 127.0.0.1:18642. - config.platforms = { + const platforms: Record = { api_server: { enabled: true, extra: { @@ -109,5 +109,11 @@ export function buildHermesConfig(settings: HermesBuildSettings): Record/dev/null || true)" + case "${platforms_state}" in + yes) + e2e_pass "expected-state.messaging.slack.hermes-platforms-enabled platforms.slack.enabled true in config.yaml" + ;; + missing-config) + e2e_fail "expected-state.messaging.slack.hermes-platforms-enabled /sandbox/.hermes/config.yaml not found" + ;; + *) + e2e_fail "expected-state.messaging.slack.hermes-platforms-enabled platforms.slack.enabled not true (${platforms_state})" + ;; + esac + + env_state="$(openshell sandbox exec --name "${sandbox_name}" -- sh -c 'grep -E "^SLACK_ALLOWED_CHANNELS=" /sandbox/.hermes/.env 2>/dev/null | head -n1' 2>/dev/null || true)" + case "${env_state}" in + SLACK_ALLOWED_CHANNELS=*[!\ ]*) + e2e_pass "expected-state.messaging.slack.hermes-allowed-channels-scoped allowlist present in .env" + ;; + "") + e2e_pass "expected-state.messaging.slack.hermes-allowed-channels-scoped no channel allowlist requested (open scope)" + ;; + *) + e2e_fail "expected-state.messaging.slack.hermes-allowed-channels-scoped malformed SLACK_ALLOWED_CHANNELS entry" + ;; + esac + + # Hermes ships two surfaces that carry the gateway boot trace: + # - /sandbox/.hermes/logs/gateway.log: Hermes' own structured logger. + # - /gateway.log: stdout captured by agents/hermes/start.sh:862,910 + # when `hermes gateway run` is supervised by the entrypoint. + # Tail both; either is acceptable evidence the Slack platform booted. + tmp_dir=/tmp + gateway_log_basename=gateway.log + gateway_log="" + for log_path in "/sandbox/.hermes/logs/${gateway_log_basename}" "${tmp_dir}/${gateway_log_basename}"; do + chunk="$(openshell sandbox exec --name "${sandbox_name}" -- sh -c "tail -n 200 ${log_path} 2>/dev/null || true" 2>/dev/null || true)" + if [[ -n "${chunk}" ]]; then + if [[ -n "${gateway_log}" ]]; then + gateway_log="${gateway_log}"$'\n'"${chunk}" + else + gateway_log="${chunk}" + fi + fi + done + if [[ -z "${gateway_log}" ]]; then + e2e_fail "expected-state.messaging.slack.hermes-gateway-running could not read gateway log from sandbox or entrypoint surface" + fi + if printf '%s\n' "${gateway_log}" | grep -qE '\[Slack\] Socket Mode connected|✓ slack connected|slack_bolt\.AsyncApp.*Bolt app is running'; then + e2e_pass "expected-state.messaging.slack.hermes-gateway-running gateway booted slack platform" + else + sanitized_tail="$(printf '%s\n' "${gateway_log}" | tail -n 20 | sed -E \ + -e 's/xox[bpaors]-[A-Za-z0-9-]+//g' \ + -e 's/xapp-[A-Za-z0-9-]+//g' \ + -e 's/[Tt][0-9A-Z]{8,}//g' \ + -e 's/[UCWBDG][0-9A-Z]{8,}//g')" + e2e_fail "expected-state.messaging.slack.hermes-gateway-running gateway log shows slack platform never started (sanitized tail: ${sanitized_tail})" + fi + fi +fi e2e_pass "expected-state.messaging.slack.provider-state ${provider} provider state configured" diff --git a/test/e2e/test-hermes-slack-e2e.sh b/test/e2e/test-hermes-slack-e2e.sh index 4815d3f1b8e..49c0921ae02 100755 --- a/test/e2e/test-hermes-slack-e2e.sh +++ b/test/e2e/test-hermes-slack-e2e.sh @@ -338,8 +338,14 @@ config_text = Path("/sandbox/.hermes/config.yaml").read_text(encoding="utf-8") cfg = yaml.safe_load(config_text) or {} errors = [] platforms = cfg.get("platforms") -if isinstance(platforms, dict) and "slack" in platforms: - errors.append("platforms.slack present") +if not isinstance(platforms, dict): + errors.append("platforms map missing or not a mapping") +else: + slack = platforms.get("slack") + if not isinstance(slack, dict): + errors.append("platforms.slack missing or not a mapping") + elif slack.get("enabled") is not True: + errors.append(f"platforms.slack.enabled is not true ({slack!r})") if "SLACK_BOT_TOKEN" in config_text or "SLACK_APP_TOKEN" in config_text: errors.append("config.yaml contains Slack token env keys") if errors: @@ -350,7 +356,7 @@ PY ) if [ "$config_probe" = "OK" ]; then - pass "config.yaml has no generic platforms.slack block or Slack token keys" + pass "config.yaml enables platforms.slack and contains no Slack token keys" else fail "config.yaml check failed: ${config_probe:0:400}" fi diff --git a/test/generate-hermes-config.test.ts b/test/generate-hermes-config.test.ts index 4fe6dd7f8e4..3651992bf8f 100644 --- a/test/generate-hermes-config.test.ts +++ b/test/generate-hermes-config.test.ts @@ -238,7 +238,7 @@ describe("agents/hermes/generate-config.ts", () => { expect(envFile).not.toContain("DISCORD_ALLOWED_USERS="); }); - it("does not emit generic platforms blocks for Telegram or Slack messaging tokens", () => { + it("enables Slack under platforms and keeps Telegram top-level only when messaging tokens are configured", () => { const { config, envFile } = runConfigScript({ NEMOCLAW_MESSAGING_CHANNELS_B64: encodeJson(["telegram", "slack"]), NEMOCLAW_MESSAGING_ALLOWED_IDS_B64: encodeJson({ @@ -253,7 +253,7 @@ describe("agents/hermes/generate-config.ts", () => { expect(config.telegram).toEqual({ require_mention: true }); expect(config.platforms.telegram).toBeUndefined(); - expect(config.platforms.slack).toBeUndefined(); + expect(config.platforms.slack).toEqual({ enabled: true }); expect(envFile).toContain("TELEGRAM_BOT_TOKEN=openshell:resolve:env:TELEGRAM_BOT_TOKEN\n"); expect(envFile).toContain("TELEGRAM_ALLOWED_USERS=123456789\n"); expect(envFile).toContain( @@ -268,6 +268,24 @@ describe("agents/hermes/generate-config.ts", () => { expect(envFile).toContain("SLACK_ALLOWED_CHANNELS=C012AB3CD,C987ZY6XW\n"); }); + it("omits platforms.slack when Slack channel is not enabled", () => { + const { config } = runConfigScript({ + NEMOCLAW_MESSAGING_CHANNELS_B64: encodeJson([]), + }); + + expect(config.platforms.slack).toBeUndefined(); + expect(Object.keys(config.platforms)).toEqual(["api_server"]); + }); + + it("enables Slack under platforms even when the slack token allowlist is empty", () => { + const { config } = runConfigScript({ + NEMOCLAW_MESSAGING_CHANNELS_B64: encodeJson(["slack"]), + }); + + expect(config.platforms.slack).toEqual({ enabled: true }); + expect(config.platforms.api_server.enabled).toBe(true); + }); + it("bridges captured WeChat metadata to Hermes' WEIXIN_* env contract", () => { // Hermes' adapter reads WEIXIN_TOKEN + WEIXIN_ACCOUNT_ID (plus optional // WEIXIN_BASE_URL, WEIXIN_ALLOWED_USERS) per