diff --git a/packages/cli/__tests__/commands/doctor.test.ts b/packages/cli/__tests__/commands/doctor.test.ts index 60f149efd..5c2f7ce8e 100644 --- a/packages/cli/__tests__/commands/doctor.test.ts +++ b/packages/cli/__tests__/commands/doctor.test.ts @@ -6,6 +6,8 @@ const { mockFindConfigFile, mockLoadConfig, mockCreatePluginRegistry, + mockCreateProjectObserver, + mockRecordNotificationDelivery, mockDetectOpenClawInstallation, mockValidateToken, mockRegistry, @@ -18,6 +20,8 @@ const { mockFindConfigFile: vi.fn(), mockLoadConfig: vi.fn(), mockCreatePluginRegistry: vi.fn(), + mockCreateProjectObserver: vi.fn(), + mockRecordNotificationDelivery: vi.fn(), mockDetectOpenClawInstallation: vi.fn(), mockValidateToken: vi.fn(), mockRegistry: { @@ -41,9 +45,11 @@ vi.mock("@aoagents/ao-core", () => ({ buildReactionNotificationData: () => ({ schemaVersion: 3 }), buildSessionTransitionNotificationData: () => ({ schemaVersion: 3 }), createPluginRegistry: (...args: unknown[]) => mockCreatePluginRegistry(...args), + createProjectObserver: (...args: unknown[]) => mockCreateProjectObserver(...args), findConfigFile: (...args: unknown[]) => mockFindConfigFile(...args), getObservabilityBaseDir: () => "/tmp/.agent-orchestrator/observability", loadConfig: (...args: unknown[]) => mockLoadConfig(...args), + recordNotificationDelivery: (...args: unknown[]) => mockRecordNotificationDelivery(...args), resolveNotifierTarget: ( config: { notifiers?: Record }, reference: string, @@ -161,6 +167,12 @@ describe("doctor command", () => { mockCreatePluginRegistry.mockReset(); mockCreatePluginRegistry.mockReturnValue(mockRegistry); + mockCreateProjectObserver.mockReset(); + mockCreateProjectObserver.mockReturnValue({ + recordOperation: vi.fn(), + setHealth: vi.fn(), + }); + mockRecordNotificationDelivery.mockReset(); mockRegistry.loadFromConfig.mockReset(); mockRegistry.loadFromConfig.mockResolvedValue(undefined); diff --git a/packages/cli/__tests__/commands/notify.test.ts b/packages/cli/__tests__/commands/notify.test.ts index 314d8be4c..2f0d94f61 100644 --- a/packages/cli/__tests__/commands/notify.test.ts +++ b/packages/cli/__tests__/commands/notify.test.ts @@ -1,11 +1,19 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { Command } from "commander"; -const { mockCreatePluginRegistry, mockFindConfigFile, mockLoadConfig, mockRegistry } = vi.hoisted( - () => ({ +const { + mockCreatePluginRegistry, + mockCreateProjectObserver, + mockFindConfigFile, + mockLoadConfig, + mockRecordNotificationDelivery, + mockRegistry, +} = vi.hoisted(() => ({ mockCreatePluginRegistry: vi.fn(), + mockCreateProjectObserver: vi.fn(), mockFindConfigFile: vi.fn(), mockLoadConfig: vi.fn(), + mockRecordNotificationDelivery: vi.fn(), mockRegistry: { loadFromConfig: vi.fn(), get: vi.fn(), @@ -13,8 +21,7 @@ const { mockCreatePluginRegistry, mockFindConfigFile, mockLoadConfig, mockRegist register: vi.fn(), loadBuiltins: vi.fn(), }, - }), -); + })); vi.mock("@aoagents/ao-core", () => { function buildSubject(input: { @@ -43,8 +50,10 @@ vi.mock("@aoagents/ao-core", () => { return { createPluginRegistry: (...args: unknown[]) => mockCreatePluginRegistry(...args), + createProjectObserver: (...args: unknown[]) => mockCreateProjectObserver(...args), findConfigFile: (...args: unknown[]) => mockFindConfigFile(...args), loadConfig: (...args: unknown[]) => mockLoadConfig(...args), + recordNotificationDelivery: (...args: unknown[]) => mockRecordNotificationDelivery(...args), resolveNotifierTarget: ( config: { notifiers?: Record }, reference: string, @@ -163,6 +172,12 @@ describe("notify command", () => { mockLoadConfig.mockReturnValue(makeConfig()); mockCreatePluginRegistry.mockReset(); mockCreatePluginRegistry.mockReturnValue(mockRegistry); + mockCreateProjectObserver.mockReset(); + mockCreateProjectObserver.mockReturnValue({ + recordOperation: vi.fn(), + setHealth: vi.fn(), + }); + mockRecordNotificationDelivery.mockReset(); mockRegistry.loadFromConfig.mockReset(); mockRegistry.loadFromConfig.mockResolvedValue(undefined); mockRegistry.get.mockReset(); diff --git a/packages/core/src/notification-observability.ts b/packages/core/src/notification-observability.ts index 1105b8c4b..8601b895a 100644 --- a/packages/core/src/notification-observability.ts +++ b/packages/core/src/notification-observability.ts @@ -79,7 +79,38 @@ export function sanitizeNotificationDeliveryReason(reason: string): string { } function notificationSurface(reference: string): string { - const suffix = reference.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, ""); + let suffix = ""; + let lastNonHyphenLength = 0; + let previousWasGeneratedHyphen = false; + + for (const ch of reference) { + const code = ch.charCodeAt(0); + const isAlphaNumeric = + (code >= 0x30 && code <= 0x39) || + (code >= 0x41 && code <= 0x5a) || + (code >= 0x61 && code <= 0x7a); + const isAllowed = isAlphaNumeric || ch === "_" || ch === "-"; + + if (!isAllowed) { + if (suffix.length > 0 && !previousWasGeneratedHyphen) { + suffix += "-"; + previousWasGeneratedHyphen = true; + } + continue; + } + + if (ch === "-" && suffix.length === 0) { + continue; + } + + suffix += ch; + previousWasGeneratedHyphen = false; + if (ch !== "-") { + lastNonHyphenLength = suffix.length; + } + } + + suffix = suffix.slice(0, lastNonHyphenLength); return `notification.delivery.${suffix || "target"}`; }