mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 15:47:28 +00:00
chore(channels): remove bluebubbles bundled surface
This commit is contained in:
@@ -14,7 +14,6 @@ query-filters:
|
||||
- security
|
||||
|
||||
paths:
|
||||
- extensions/bluebubbles/src
|
||||
- extensions/discord/src
|
||||
- extensions/feishu/src
|
||||
- extensions/googlechat/src
|
||||
|
||||
5
.github/labeler.yml
vendored
5
.github/labeler.yml
vendored
@@ -1,8 +1,3 @@
|
||||
"channel: bluebubbles":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
- "extensions/bluebubbles/**"
|
||||
- "docs/channels/bluebubbles.md"
|
||||
"plugin: azure-speech":
|
||||
- changed-files:
|
||||
- any-glob-to-any-file:
|
||||
|
||||
@@ -36,7 +36,6 @@ on:
|
||||
- "src/*.ts"
|
||||
- "src/**/*.ts"
|
||||
- "src/config/**"
|
||||
- "extensions/bluebubbles/src/**"
|
||||
- "extensions/discord/src/**"
|
||||
- "extensions/feishu/src/**"
|
||||
- "extensions/googlechat/src/**"
|
||||
@@ -229,7 +228,7 @@ jobs:
|
||||
src/auto-reply/reply/post-compaction-context.ts|src/auto-reply/reply/queue/*|src/auto-reply/reply/startup-context.ts|src/commands/doctor-session-*.ts|src/commands/session-store-targets.ts|src/commands/sessions*.ts|src/infra/diagnostic-*.ts|src/infra/diagnostics-timeline.ts|src/infra/session-delivery-queue*.ts|src/logging/diagnostic*.ts)
|
||||
session_diagnostics=true
|
||||
;;
|
||||
extensions/bluebubbles/src/*|extensions/discord/src/*|extensions/feishu/src/*|extensions/googlechat/src/*|extensions/imessage/src/*|extensions/irc/src/*|extensions/line/src/*|extensions/matrix/src/*|extensions/mattermost/src/*|extensions/msteams/src/*|extensions/nextcloud-talk/src/*|extensions/nostr/src/*|extensions/qa-channel/src/*|extensions/qqbot/src/*|extensions/signal/src/*|extensions/slack/src/*|extensions/synology-chat/src/*|extensions/telegram/src/*|extensions/tlon/src/*|extensions/twitch/src/*|extensions/whatsapp/src/*|extensions/zalo/src/*|extensions/zalouser/src/*|src/channels/*)
|
||||
extensions/discord/src/*|extensions/feishu/src/*|extensions/googlechat/src/*|extensions/imessage/src/*|extensions/irc/src/*|extensions/line/src/*|extensions/matrix/src/*|extensions/mattermost/src/*|extensions/msteams/src/*|extensions/nextcloud-talk/src/*|extensions/nostr/src/*|extensions/qa-channel/src/*|extensions/qqbot/src/*|extensions/signal/src/*|extensions/slack/src/*|extensions/synology-chat/src/*|extensions/telegram/src/*|extensions/tlon/src/*|extensions/twitch/src/*|extensions/whatsapp/src/*|extensions/zalo/src/*|extensions/zalouser/src/*|src/channels/*)
|
||||
channel=true
|
||||
;;
|
||||
src/config/*)
|
||||
|
||||
@@ -144,7 +144,6 @@ uninstall, and publishing commands.
|
||||
| Plugin | Description | Distribution | Surface |
|
||||
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- |
|
||||
| [acpx](/plugins/reference/acpx) | Embedded ACP runtime backend with plugin-owned session and transport management. | `@openclaw/acpx`<br />npm; ClawHub | skills |
|
||||
| [bluebubbles](/plugins/reference/bluebubbles) | Adds the BlueBubbles channel surface for sending and receiving OpenClaw messages. | `@openclaw/bluebubbles`<br />npm; ClawHub | channels: bluebubbles |
|
||||
| [brave](/plugins/reference/brave) | Adds web search provider support. | `@openclaw/brave-plugin`<br />npm; ClawHub | contracts: webSearchProviders |
|
||||
| [codex](/plugins/reference/codex) | Codex app-server harness and Codex-managed GPT model catalog. | `@openclaw/codex`<br />npm; ClawHub | providers: codex; contracts: mediaUnderstandingProviders, migrationProviders |
|
||||
| [diagnostics-otel](/plugins/reference/diagnostics-otel) | OpenClaw diagnostics OpenTelemetry exporter. | `@openclaw/diagnostics-otel`<br />npm; ClawHub: `clawhub:@openclaw/diagnostics-otel` | plugin |
|
||||
|
||||
@@ -25,7 +25,6 @@ pnpm plugins:inventory:gen
|
||||
| [anthropic-vertex](/plugins/reference/anthropic-vertex) | Adds Anthropic Vertex model provider support to OpenClaw. | `@openclaw/anthropic-vertex-provider`<br />included in OpenClaw | providers: anthropic-vertex |
|
||||
| [arcee](/plugins/reference/arcee) | Adds Arcee model provider support to OpenClaw. | `@openclaw/arcee-provider`<br />included in OpenClaw | providers: arcee |
|
||||
| [azure-speech](/plugins/reference/azure-speech) | Azure AI Speech text-to-speech (MP3, native Ogg/Opus voice notes, PCM telephony). | `@openclaw/azure-speech`<br />included in OpenClaw | contracts: speechProviders |
|
||||
| [bluebubbles](/plugins/reference/bluebubbles) | Adds the BlueBubbles channel surface for sending and receiving OpenClaw messages. | `@openclaw/bluebubbles`<br />npm; ClawHub | channels: bluebubbles |
|
||||
| [bonjour](/plugins/reference/bonjour) | Advertise the local OpenClaw gateway over Bonjour/mDNS. | `@openclaw/bonjour`<br />included in OpenClaw | plugin |
|
||||
| [brave](/plugins/reference/brave) | Adds web search provider support. | `@openclaw/brave-plugin`<br />npm; ClawHub | contracts: webSearchProviders |
|
||||
| [browser](/plugins/reference/browser) | Adds agent-callable tools. | `@openclaw/browser-plugin`<br />included in OpenClaw | contracts: tools; skills |
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
"!dist/extensions/acpx/**",
|
||||
"!dist/extensions/node_modules/**",
|
||||
"!dist/extensions/*/node_modules/**",
|
||||
"!dist/extensions/bluebubbles/**",
|
||||
"!dist/extensions/brave/**",
|
||||
"!dist/extensions/codex/**",
|
||||
"!dist/extensions/diagnostics-otel/**",
|
||||
|
||||
9
pnpm-lock.yaml
generated
9
pnpm-lock.yaml
generated
@@ -397,15 +397,6 @@ importers:
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/plugin-sdk
|
||||
|
||||
extensions/bluebubbles:
|
||||
devDependencies:
|
||||
'@openclaw/plugin-sdk':
|
||||
specifier: workspace:*
|
||||
version: link:../../packages/plugin-sdk
|
||||
openclaw:
|
||||
specifier: workspace:*
|
||||
version: link:../..
|
||||
|
||||
extensions/bonjour:
|
||||
dependencies:
|
||||
'@homebridge/ciao':
|
||||
|
||||
@@ -38,7 +38,6 @@ const systemMarkLiteralGuardSources = [
|
||||
];
|
||||
|
||||
const channelIds = [
|
||||
"bluebubbles",
|
||||
"discord",
|
||||
"googlechat",
|
||||
"imessage",
|
||||
@@ -103,7 +102,7 @@ function matchesChannelModuleSpecifier(specifier) {
|
||||
}
|
||||
|
||||
const userFacingChannelNameRe =
|
||||
/\b(?:discord|telegram|slack|signal|imessage|whatsapp|google\s*chat|irc|line|zalo|matrix|msteams|bluebubbles)\b/i;
|
||||
/\b(?:discord|telegram|slack|signal|imessage|whatsapp|google\s*chat|irc|line|zalo|matrix|msteams)\b/i;
|
||||
const systemMarkLiteral = "⚙️";
|
||||
|
||||
function isModuleSpecifierStringNode(node) {
|
||||
|
||||
@@ -14,8 +14,6 @@ const sourceRoots = ["src/channels", "src/routing", "src/line", "extensions"];
|
||||
// Temporary allowlist for legacy callsites. New raw fetch callsites in channel/plugin runtime
|
||||
// code should be rejected and migrated to fetchWithSsrFGuard/shared channel helpers.
|
||||
const allowedRawFetchCallsites = new Set([
|
||||
bundledPluginCallsite("bluebubbles", "src/test-harness.ts", 132),
|
||||
bundledPluginCallsite("bluebubbles", "src/types.ts", 204),
|
||||
bundledPluginCallsite("browser", "src/browser/cdp.helpers.ts", 268),
|
||||
bundledPluginCallsite("browser", "src/browser/client-fetch.ts", 192),
|
||||
bundledPluginCallsite("chutes", "models.ts", 536),
|
||||
|
||||
@@ -8,7 +8,6 @@ import { runAsScript, toLine, unwrapExpression } from "./lib/ts-guard-utils.mjs"
|
||||
|
||||
const sourceRoots = ["extensions"];
|
||||
const enforcedFiles = new Set([
|
||||
bundledPluginFile("bluebubbles", "src/monitor.ts"),
|
||||
bundledPluginFile("feishu", "src/monitor.transport.ts"),
|
||||
bundledPluginFile("googlechat", "src/monitor.ts"),
|
||||
bundledPluginFile("zalo", "src/monitor.webhook.ts"),
|
||||
|
||||
@@ -91,7 +91,6 @@ function humanizeId(value) {
|
||||
["api", "API"],
|
||||
["aws", "AWS"],
|
||||
["azure", "Azure"],
|
||||
["bluebubbles", "BlueBubbles"],
|
||||
["byteplus", "BytePlus"],
|
||||
["codex", "Codex"],
|
||||
["cli", "CLI"],
|
||||
|
||||
@@ -2,7 +2,6 @@ import fs from "node:fs";
|
||||
import path from "node:path";
|
||||
import { channelTestRoots } from "../../test/vitest/vitest.channel-paths.mjs";
|
||||
import { isAcpxExtensionRoot } from "../../test/vitest/vitest.extension-acpx-paths.mjs";
|
||||
import { isBlueBubblesExtensionRoot } from "../../test/vitest/vitest.extension-bluebubbles-paths.mjs";
|
||||
import { isBrowserExtensionRoot } from "../../test/vitest/vitest.extension-browser-paths.mjs";
|
||||
import { resolveSplitChannelExtensionShard } from "../../test/vitest/vitest.extension-channel-split-paths.mjs";
|
||||
import { isDiffsExtensionRoot } from "../../test/vitest/vitest.extension-diffs-paths.mjs";
|
||||
@@ -34,7 +33,6 @@ const EXTENSION_TEST_COST_MULTIPLIERS = {
|
||||
// These ratios come from Blacksmith extension batch timings; import-heavy
|
||||
// suites vary widely, and file count alone leaves long tail shards.
|
||||
"test/vitest/vitest.extension-acpx.config.ts": 0.75,
|
||||
"test/vitest/vitest.extension-bluebubbles.config.ts": 0.8,
|
||||
"test/vitest/vitest.extension-browser.config.ts": 0.5,
|
||||
"test/vitest/vitest.extension-diffs.config.ts": 0.6,
|
||||
"test/vitest/vitest.extension-discord.config.ts": 0.62,
|
||||
@@ -149,7 +147,6 @@ export function resolveExtensionTestPlan(params = {}) {
|
||||
const usesAcpxConfig = roots.some((root) => isAcpxExtensionRoot(root));
|
||||
const usesBrowserConfig = roots.some((root) => isBrowserExtensionRoot(root));
|
||||
const usesDiffsConfig = roots.some((root) => isDiffsExtensionRoot(root));
|
||||
const usesBlueBubblesConfig = roots.some((root) => isBlueBubblesExtensionRoot(root));
|
||||
const usesFeishuConfig = roots.some((root) => isFeishuExtensionRoot(root));
|
||||
const usesIrcConfig = roots.some((root) => isIrcExtensionRoot(root));
|
||||
const usesMattermostConfig = roots.some((root) => isMattermostExtensionRoot(root));
|
||||
@@ -172,45 +169,43 @@ export function resolveExtensionTestPlan(params = {}) {
|
||||
? "test/vitest/vitest.extension-channels.config.ts"
|
||||
: usesAcpxConfig
|
||||
? "test/vitest/vitest.extension-acpx.config.ts"
|
||||
: usesBlueBubblesConfig
|
||||
? "test/vitest/vitest.extension-bluebubbles.config.ts"
|
||||
: usesBrowserConfig
|
||||
? "test/vitest/vitest.extension-browser.config.ts"
|
||||
: usesDiffsConfig
|
||||
? "test/vitest/vitest.extension-diffs.config.ts"
|
||||
: usesFeishuConfig
|
||||
? "test/vitest/vitest.extension-feishu.config.ts"
|
||||
: usesIrcConfig
|
||||
? "test/vitest/vitest.extension-irc.config.ts"
|
||||
: usesMattermostConfig
|
||||
? "test/vitest/vitest.extension-mattermost.config.ts"
|
||||
: usesMatrixConfig
|
||||
? "test/vitest/vitest.extension-matrix.config.ts"
|
||||
: usesMediaConfig
|
||||
? "test/vitest/vitest.extension-media.config.ts"
|
||||
: usesMemoryConfig
|
||||
? "test/vitest/vitest.extension-memory.config.ts"
|
||||
: usesMessagingConfig
|
||||
? "test/vitest/vitest.extension-messaging.config.ts"
|
||||
: usesMiscConfig
|
||||
? "test/vitest/vitest.extension-misc.config.ts"
|
||||
: usesMsTeamsConfig
|
||||
? "test/vitest/vitest.extension-msteams.config.ts"
|
||||
: usesQaConfig
|
||||
? "test/vitest/vitest.extension-qa.config.ts"
|
||||
: usesTelegramConfig
|
||||
? "test/vitest/vitest.extension-telegram.config.ts"
|
||||
: usesVoiceCallConfig
|
||||
? "test/vitest/vitest.extension-voice-call.config.ts"
|
||||
: usesWhatsAppConfig
|
||||
? "test/vitest/vitest.extension-whatsapp.config.ts"
|
||||
: usesZaloConfig
|
||||
? "test/vitest/vitest.extension-zalo.config.ts"
|
||||
: usesProviderOpenAiConfig
|
||||
? "test/vitest/vitest.extension-provider-openai.config.ts"
|
||||
: usesProviderConfig
|
||||
? "test/vitest/vitest.extension-providers.config.ts"
|
||||
: "test/vitest/vitest.extensions.config.ts";
|
||||
: usesBrowserConfig
|
||||
? "test/vitest/vitest.extension-browser.config.ts"
|
||||
: usesDiffsConfig
|
||||
? "test/vitest/vitest.extension-diffs.config.ts"
|
||||
: usesFeishuConfig
|
||||
? "test/vitest/vitest.extension-feishu.config.ts"
|
||||
: usesIrcConfig
|
||||
? "test/vitest/vitest.extension-irc.config.ts"
|
||||
: usesMattermostConfig
|
||||
? "test/vitest/vitest.extension-mattermost.config.ts"
|
||||
: usesMatrixConfig
|
||||
? "test/vitest/vitest.extension-matrix.config.ts"
|
||||
: usesMediaConfig
|
||||
? "test/vitest/vitest.extension-media.config.ts"
|
||||
: usesMemoryConfig
|
||||
? "test/vitest/vitest.extension-memory.config.ts"
|
||||
: usesMessagingConfig
|
||||
? "test/vitest/vitest.extension-messaging.config.ts"
|
||||
: usesMiscConfig
|
||||
? "test/vitest/vitest.extension-misc.config.ts"
|
||||
: usesMsTeamsConfig
|
||||
? "test/vitest/vitest.extension-msteams.config.ts"
|
||||
: usesQaConfig
|
||||
? "test/vitest/vitest.extension-qa.config.ts"
|
||||
: usesTelegramConfig
|
||||
? "test/vitest/vitest.extension-telegram.config.ts"
|
||||
: usesVoiceCallConfig
|
||||
? "test/vitest/vitest.extension-voice-call.config.ts"
|
||||
: usesWhatsAppConfig
|
||||
? "test/vitest/vitest.extension-whatsapp.config.ts"
|
||||
: usesZaloConfig
|
||||
? "test/vitest/vitest.extension-zalo.config.ts"
|
||||
: usesProviderOpenAiConfig
|
||||
? "test/vitest/vitest.extension-provider-openai.config.ts"
|
||||
: usesProviderConfig
|
||||
? "test/vitest/vitest.extension-providers.config.ts"
|
||||
: "test/vitest/vitest.extensions.config.ts";
|
||||
const testFileCount = roots.reduce(
|
||||
(sum, root) => sum + countTestFiles(path.join(repoRoot, root)),
|
||||
0,
|
||||
|
||||
@@ -121,32 +121,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "@openclaw/bluebubbles",
|
||||
"description": "OpenClaw BlueBubbles channel plugin",
|
||||
"source": "official",
|
||||
"kind": "channel",
|
||||
"openclaw": {
|
||||
"channel": {
|
||||
"id": "bluebubbles",
|
||||
"label": "BlueBubbles",
|
||||
"selectionLabel": "BlueBubbles (macOS app)",
|
||||
"detailLabel": "BlueBubbles",
|
||||
"docsPath": "/channels/bluebubbles",
|
||||
"docsLabel": "bluebubbles",
|
||||
"blurb": "iMessage via the BlueBubbles mac app + REST API.",
|
||||
"aliases": ["bb"],
|
||||
"preferOver": ["imessage"],
|
||||
"systemImage": "bubble.left.and.text.bubble.right",
|
||||
"order": 75
|
||||
},
|
||||
"install": {
|
||||
"npmSpec": "@openclaw/bluebubbles",
|
||||
"defaultChoice": "npm",
|
||||
"minHostVersion": ">=2026.4.10"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "@openclaw/discord",
|
||||
"description": "OpenClaw Discord channel plugin",
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
resolveCommandsLightIncludePattern,
|
||||
} from "../test/vitest/vitest.commands-light-paths.mjs";
|
||||
import { isAcpxExtensionRoot } from "../test/vitest/vitest.extension-acpx-paths.mjs";
|
||||
import { isBlueBubblesExtensionRoot } from "../test/vitest/vitest.extension-bluebubbles-paths.mjs";
|
||||
import { isBrowserExtensionRoot } from "../test/vitest/vitest.extension-browser-paths.mjs";
|
||||
import { resolveSplitChannelExtensionShard } from "../test/vitest/vitest.extension-channel-split-paths.mjs";
|
||||
import { isDiffsExtensionRoot } from "../test/vitest/vitest.extension-diffs-paths.mjs";
|
||||
@@ -72,7 +71,6 @@ const CRON_VITEST_CONFIG = "test/vitest/vitest.cron.config.ts";
|
||||
const DAEMON_VITEST_CONFIG = "test/vitest/vitest.daemon.config.ts";
|
||||
const E2E_VITEST_CONFIG = "test/vitest/vitest.e2e.config.ts";
|
||||
const EXTENSION_ACPX_VITEST_CONFIG = "test/vitest/vitest.extension-acpx.config.ts";
|
||||
const EXTENSION_BLUEBUBBLES_VITEST_CONFIG = "test/vitest/vitest.extension-bluebubbles.config.ts";
|
||||
const EXTENSION_BROWSER_VITEST_CONFIG = "test/vitest/vitest.extension-browser.config.ts";
|
||||
const EXTENSION_CHANNELS_VITEST_CONFIG = "test/vitest/vitest.extension-channels.config.ts";
|
||||
const EXTENSION_DIFFS_VITEST_CONFIG = "test/vitest/vitest.extension-diffs.config.ts";
|
||||
@@ -170,7 +168,6 @@ const FULL_SUITE_CONFIG_WEIGHT = new Map([
|
||||
[UNIT_SECURITY_VITEST_CONFIG, 30],
|
||||
[UNIT_SUPPORT_VITEST_CONFIG, 28],
|
||||
[EXTENSION_ZALO_VITEST_CONFIG, 24],
|
||||
[EXTENSION_BLUEBUBBLES_VITEST_CONFIG, 22],
|
||||
[EXTENSION_IRC_VITEST_CONFIG, 20],
|
||||
[EXTENSION_FEISHU_VITEST_CONFIG, 18],
|
||||
[EXTENSION_MATTERMOST_VITEST_CONFIG, 16],
|
||||
@@ -252,7 +249,6 @@ const VITEST_CONFIG_BY_KIND = {
|
||||
extension: EXTENSIONS_VITEST_CONFIG,
|
||||
extensionFull: FULL_EXTENSIONS_VITEST_CONFIG,
|
||||
extensionAcpx: EXTENSION_ACPX_VITEST_CONFIG,
|
||||
extensionBlueBubbles: EXTENSION_BLUEBUBBLES_VITEST_CONFIG,
|
||||
extensionBrowser: EXTENSION_BROWSER_VITEST_CONFIG,
|
||||
extensionChannel: EXTENSION_CHANNELS_VITEST_CONFIG,
|
||||
extensionDiffs: EXTENSION_DIFFS_VITEST_CONFIG,
|
||||
@@ -1090,9 +1086,6 @@ function classifyTarget(arg, cwd) {
|
||||
if (isDiffsExtensionRoot(extensionRoot)) {
|
||||
return "extensionDiffs";
|
||||
}
|
||||
if (isBlueBubblesExtensionRoot(extensionRoot)) {
|
||||
return "extensionBlueBubbles";
|
||||
}
|
||||
if (isBrowserExtensionRoot(extensionRoot)) {
|
||||
return "extensionBrowser";
|
||||
}
|
||||
@@ -1398,7 +1391,6 @@ export function buildVitestRunPlans(
|
||||
"e2e",
|
||||
"extensionAcpx",
|
||||
"extensionDiffs",
|
||||
"extensionBlueBubbles",
|
||||
"extensionBrowser",
|
||||
"extensionDiscord",
|
||||
"extensionFeishu",
|
||||
|
||||
@@ -200,8 +200,8 @@ const announceFormatChannelPlugins = [
|
||||
source: "test",
|
||||
},
|
||||
{
|
||||
pluginId: "bluebubbles",
|
||||
plugin: createChannelTestPluginBase({ id: "bluebubbles", label: "BlueBubbles" }),
|
||||
pluginId: "imessage",
|
||||
plugin: createChannelTestPluginBase({ id: "imessage", label: "iMessage" }),
|
||||
source: "test",
|
||||
},
|
||||
{
|
||||
@@ -707,10 +707,10 @@ describe("subagent announce formatting", () => {
|
||||
it("keeps completion delivery enabled for extension channels captured from requester origin", async () => {
|
||||
const didAnnounce = await runSubagentAnnounceFlow({
|
||||
childSessionKey: "agent:main:subagent:test",
|
||||
childRunId: "run-direct-completion-bluebubbles",
|
||||
childRunId: "run-direct-completion-imessage",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "main",
|
||||
requesterOrigin: { channel: "bluebubbles", to: "+1234567890", accountId: "acct-bb" },
|
||||
requesterOrigin: { channel: "imessage", to: "+1234567890", accountId: "acct-bb" },
|
||||
...defaultOutcomeAnnounce,
|
||||
expectsCompletionMessage: true,
|
||||
});
|
||||
@@ -720,7 +720,7 @@ describe("subagent announce formatting", () => {
|
||||
expect(agentSpy).toHaveBeenCalledTimes(1);
|
||||
const call = agentSpy.mock.calls[0]?.[0] as { params?: Record<string, unknown> };
|
||||
expect(call?.params?.deliver).toBe(true);
|
||||
expect(call?.params?.channel).toBe("bluebubbles");
|
||||
expect(call?.params?.channel).toBe("imessage");
|
||||
expect(call?.params?.to).toBe("+1234567890");
|
||||
expect(call?.params?.accountId).toBe("acct-bb");
|
||||
});
|
||||
@@ -1591,7 +1591,7 @@ describe("subagent announce formatting", () => {
|
||||
hasSubagentDeliveryTargetHook = true;
|
||||
subagentDeliveryTargetHookMock.mockResolvedValueOnce({
|
||||
origin: {
|
||||
channel: "bluebubbles",
|
||||
channel: "imessage",
|
||||
accountId: "acct-bb",
|
||||
to: "+1234567890",
|
||||
},
|
||||
@@ -1599,7 +1599,7 @@ describe("subagent announce formatting", () => {
|
||||
|
||||
const didAnnounce = await runSubagentAnnounceFlow({
|
||||
childSessionKey: "agent:main:subagent:test",
|
||||
childRunId: "run-direct-hook-bluebubbles",
|
||||
childRunId: "run-direct-hook-imessage",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterDisplayKey: "main",
|
||||
requesterOrigin: {
|
||||
@@ -1617,7 +1617,7 @@ describe("subagent announce formatting", () => {
|
||||
expect(agentSpy).toHaveBeenCalledTimes(1);
|
||||
const call = agentSpy.mock.calls[0]?.[0] as { params?: Record<string, unknown> };
|
||||
expect(call?.params?.deliver).toBe(true);
|
||||
expect(call?.params?.channel).toBe("bluebubbles");
|
||||
expect(call?.params?.channel).toBe("imessage");
|
||||
expect(call?.params?.to).toBe("+1234567890");
|
||||
expect(call?.params?.accountId).toBe("acct-bb");
|
||||
});
|
||||
@@ -2141,9 +2141,9 @@ describe("subagent announce formatting", () => {
|
||||
|
||||
const didAnnounce = await runSubagentAnnounceFlow({
|
||||
childSessionKey: "agent:main:subagent:test",
|
||||
childRunId: "run-direct-bluebubbles",
|
||||
childRunId: "run-direct-imessage",
|
||||
requesterSessionKey: "agent:main:main",
|
||||
requesterOrigin: { channel: "bluebubbles", accountId: "acct-bb", to: "+1234567890" },
|
||||
requesterOrigin: { channel: "imessage", accountId: "acct-bb", to: "+1234567890" },
|
||||
requesterDisplayKey: "main",
|
||||
...defaultOutcomeAnnounce,
|
||||
});
|
||||
@@ -2156,7 +2156,7 @@ describe("subagent announce formatting", () => {
|
||||
expectFinal?: boolean;
|
||||
};
|
||||
expect(call?.params?.deliver).toBe(true);
|
||||
expect(call?.params?.channel).toBe("bluebubbles");
|
||||
expect(call?.params?.channel).toBe("imessage");
|
||||
expect(call?.params?.to).toBe("+1234567890");
|
||||
expect(call?.params?.accountId).toBe("acct-bb");
|
||||
expect(call?.expectFinal).toBe(true);
|
||||
@@ -2949,7 +2949,7 @@ describe("subagent announce formatting", () => {
|
||||
it("prefers requesterOrigin channel over stale session lastChannel in queued announce", async () => {
|
||||
embeddedRunMock.isEmbeddedPiRunActive.mockReturnValue(false);
|
||||
embeddedRunMock.isEmbeddedPiRunStreaming.mockReturnValue(false);
|
||||
// Session store has stale whatsapp channel, but the requesterOrigin says bluebubbles.
|
||||
// Session store has stale whatsapp channel, but the requesterOrigin says imessage.
|
||||
sessionStore = {
|
||||
"agent:main:main": {
|
||||
sessionId: "session-stale",
|
||||
|
||||
@@ -1001,11 +1001,11 @@ describe("message tool description", () => {
|
||||
setActivePluginRegistry(createTestRegistry([]));
|
||||
});
|
||||
|
||||
const bluebubblesPlugin = createChannelPlugin({
|
||||
id: "bluebubbles",
|
||||
label: "BlueBubbles",
|
||||
docsPath: "/channels/bluebubbles",
|
||||
blurb: "BlueBubbles test plugin.",
|
||||
const imessagePlugin = createChannelPlugin({
|
||||
id: "imessage",
|
||||
label: "iMessage",
|
||||
docsPath: "/channels/imessage",
|
||||
blurb: "iMessage test plugin.",
|
||||
describeMessageTool: ({ currentChannelId }) => {
|
||||
const all: ChannelMessageActionName[] = [
|
||||
"react",
|
||||
@@ -1031,7 +1031,7 @@ describe("message tool description", () => {
|
||||
},
|
||||
messaging: {
|
||||
normalizeTarget: (raw) => {
|
||||
const trimmed = raw.trim().replace(/^bluebubbles:/i, "");
|
||||
const trimmed = raw.trim().replace(/^imessage:/i, "");
|
||||
const lower = trimmed.toLowerCase();
|
||||
if (lower.startsWith("chat_guid:")) {
|
||||
const guid = trimmed.slice("chat_guid:".length);
|
||||
@@ -1059,15 +1059,15 @@ describe("message tool description", () => {
|
||||
expect(target?.description).toContain("Telegram chat id/@username");
|
||||
});
|
||||
|
||||
it("hides BlueBubbles group actions for DM targets", () => {
|
||||
it("hides iMessage group actions for DM targets", () => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([{ pluginId: "bluebubbles", source: "test", plugin: bluebubblesPlugin }]),
|
||||
createTestRegistry([{ pluginId: "imessage", source: "test", plugin: imessagePlugin }]),
|
||||
);
|
||||
|
||||
const tool = createMessageTool({
|
||||
config: {} as never,
|
||||
currentChannelProvider: "bluebubbles",
|
||||
currentChannelId: "bluebubbles:chat_guid:iMessage;-;+15551234567",
|
||||
currentChannelProvider: "imessage",
|
||||
currentChannelId: "imessage:chat_guid:iMessage;-;+15551234567",
|
||||
});
|
||||
|
||||
expect(tool.description).not.toContain("renameGroup");
|
||||
@@ -1189,12 +1189,12 @@ describe("message tool description", () => {
|
||||
|
||||
it("keeps the current-channel description stable when only one channel is configured", () => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([{ pluginId: "bluebubbles", source: "test", plugin: bluebubblesPlugin }]),
|
||||
createTestRegistry([{ pluginId: "imessage", source: "test", plugin: imessagePlugin }]),
|
||||
);
|
||||
|
||||
const tool = createMessageTool({
|
||||
config: {} as never,
|
||||
currentChannelProvider: "bluebubbles",
|
||||
currentChannelProvider: "imessage",
|
||||
});
|
||||
|
||||
expect(tool.description).toContain("Supports actions:");
|
||||
|
||||
@@ -569,7 +569,7 @@ describe("resolveChunkMode", () => {
|
||||
it.each([
|
||||
{ cfg: undefined, provider: "telegram", accountId: undefined, expected: "length" },
|
||||
{ cfg: {}, provider: "discord", accountId: undefined, expected: "length" },
|
||||
{ cfg: undefined, provider: "bluebubbles", accountId: undefined, expected: "length" },
|
||||
{ cfg: undefined, provider: "imessage", accountId: undefined, expected: "length" },
|
||||
{ cfg: providerCfg, provider: "__internal__", accountId: undefined, expected: "length" },
|
||||
{ cfg: providerCfg, provider: "slack", accountId: undefined, expected: "newline" },
|
||||
{ cfg: providerCfg, provider: "discord", accountId: undefined, expected: "length" },
|
||||
|
||||
@@ -109,8 +109,8 @@ export function installGroupRequireMentionTestPlugins() {
|
||||
source: "test",
|
||||
},
|
||||
{
|
||||
pluginId: "bluebubbles",
|
||||
plugin: createChannelTestPluginBase({ id: "bluebubbles" }),
|
||||
pluginId: "imessage",
|
||||
plugin: createChannelTestPluginBase({ id: "imessage" }),
|
||||
source: "test",
|
||||
},
|
||||
]),
|
||||
|
||||
@@ -1027,7 +1027,7 @@ describe("resolveGroupRequireMention", () => {
|
||||
it("preserves plugin-backed channel requireMention resolution", async () => {
|
||||
const cfg: OpenClawConfig = {
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
imessage: {
|
||||
groups: {
|
||||
"chat:primary": { requireMention: false },
|
||||
},
|
||||
@@ -1035,12 +1035,12 @@ describe("resolveGroupRequireMention", () => {
|
||||
},
|
||||
};
|
||||
const ctx: TemplateContext = {
|
||||
Provider: "bluebubbles",
|
||||
From: "bluebubbles:group:chat:primary",
|
||||
Provider: "imessage",
|
||||
From: "imessage:group:chat:primary",
|
||||
};
|
||||
const groupResolution: GroupKeyResolution = {
|
||||
key: "bluebubbles:group:chat:primary",
|
||||
channel: "bluebubbles",
|
||||
key: "imessage:group:chat:primary",
|
||||
channel: "imessage",
|
||||
id: "chat:primary",
|
||||
chatType: "group",
|
||||
};
|
||||
|
||||
@@ -127,7 +127,7 @@ export function buildThreadingToolContext(params: {
|
||||
};
|
||||
}
|
||||
const provider = normalizeChannelId(rawProvider) ?? normalizeAnyChannelId(rawProvider);
|
||||
// Fallback for unrecognized/plugin channels (e.g., BlueBubbles before plugin registry init)
|
||||
// Fallback for unrecognized/plugin channels (e.g., iMessage before plugin registry init)
|
||||
const threading = provider ? getChannelPlugin(provider)?.threading : undefined;
|
||||
if (!threading?.buildToolContext) {
|
||||
return {
|
||||
|
||||
@@ -47,7 +47,7 @@ describe("resolveEffectiveBlockStreamingConfig", () => {
|
||||
it("honors newline chunkMode for plugin channels even before the plugin registry is loaded", () => {
|
||||
const cfg = {
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
imessage: {
|
||||
chunkMode: "newline",
|
||||
},
|
||||
},
|
||||
@@ -64,7 +64,7 @@ describe("resolveEffectiveBlockStreamingConfig", () => {
|
||||
|
||||
const resolved = resolveEffectiveBlockStreamingConfig({
|
||||
cfg,
|
||||
provider: "bluebubbles",
|
||||
provider: "imessage",
|
||||
});
|
||||
|
||||
expect(resolved.chunking.flushOnParagraph).toBe(true);
|
||||
|
||||
@@ -201,7 +201,7 @@ function resolveFirstConversationTargetForTest(params: {
|
||||
|
||||
function parsePrefixedConversationIdForTest(
|
||||
raw: string | undefined | null,
|
||||
channel: "bluebubbles" | "imessage",
|
||||
channel: "imessage",
|
||||
): string | undefined {
|
||||
const trimmed = raw
|
||||
?.trim()
|
||||
@@ -212,7 +212,7 @@ function parsePrefixedConversationIdForTest(
|
||||
|
||||
function resolvePrefixedConversationIdForTest(
|
||||
targets: Array<string | undefined | null>,
|
||||
channel: "bluebubbles" | "imessage",
|
||||
channel: "imessage",
|
||||
): string | undefined {
|
||||
return targets.map((target) => parsePrefixedConversationIdForTest(target, channel)).find(Boolean);
|
||||
}
|
||||
@@ -318,30 +318,6 @@ function setMinimalAcpCommandRegistryForTests(): void {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
pluginId: "bluebubbles",
|
||||
source: "test",
|
||||
plugin: {
|
||||
...createChannelTestPluginBase({ id: "bluebubbles", label: "BlueBubbles" }),
|
||||
bindings: {
|
||||
resolveCommandConversation: ({
|
||||
originatingTo,
|
||||
commandTo,
|
||||
fallbackTo,
|
||||
}: {
|
||||
originatingTo?: string;
|
||||
commandTo?: string;
|
||||
fallbackTo?: string;
|
||||
}) => {
|
||||
const conversationId = resolvePrefixedConversationIdForTest(
|
||||
[originatingTo, commandTo, fallbackTo],
|
||||
"bluebubbles",
|
||||
);
|
||||
return conversationId ? { conversationId } : null;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
pluginId: "imessage",
|
||||
source: "test",
|
||||
@@ -423,7 +399,7 @@ function setMinimalAcpCommandRegistryForTests(): void {
|
||||
},
|
||||
},
|
||||
},
|
||||
...(["bluebubbles", "imessage", "feishu", "line"] as const).map((channelId) => ({
|
||||
...(["feishu", "line"] as const).map((channelId) => ({
|
||||
pluginId: channelId,
|
||||
source: "test",
|
||||
plugin: {
|
||||
@@ -791,20 +767,6 @@ async function runLineDmAcpCommand(commandBody: string, cfg: OpenClawConfig = ba
|
||||
);
|
||||
}
|
||||
|
||||
async function runBlueBubblesDmAcpCommand(commandBody: string, cfg: OpenClawConfig = baseCfg) {
|
||||
return handleAcpCommand(
|
||||
createConversationParams(
|
||||
commandBody,
|
||||
{
|
||||
channel: "bluebubbles",
|
||||
originatingTo: "bluebubbles:+15555550123",
|
||||
},
|
||||
cfg,
|
||||
),
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
async function runIMessageDmAcpCommand(commandBody: string, cfg: OpenClawConfig = baseCfg) {
|
||||
return handleAcpCommand(
|
||||
createConversationParams(
|
||||
@@ -1199,15 +1161,15 @@ describe("/acp command", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("binds BlueBubbles DMs with --bind here", async () => {
|
||||
const result = await runBlueBubblesDmAcpCommand("/acp spawn codex --bind here");
|
||||
it("binds iMessage DMs with --bind here", async () => {
|
||||
const result = await runIMessageDmAcpCommand("/acp spawn codex --bind here");
|
||||
|
||||
expect(result?.reply?.text).toContain("Bound this conversation to");
|
||||
expect(hoisted.sessionBindingBindMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
placement: "current",
|
||||
conversation: expect.objectContaining({
|
||||
channel: "bluebubbles",
|
||||
channel: "imessage",
|
||||
accountId: "default",
|
||||
conversationId: "+15555550123",
|
||||
}),
|
||||
|
||||
@@ -75,15 +75,6 @@ function parseFeishuDirectConversationIdForTest(raw?: string | null): string | u
|
||||
return trimmed.replace(/^(user|dm):/i, "").trim() || undefined;
|
||||
}
|
||||
|
||||
function parseBlueBubblesConversationIdFromTargetForTest(raw?: string | null): string | undefined {
|
||||
const trimmed = raw?.trim().replace(/^bluebubbles:/i, "");
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
const prefixed = /^(chat_guid|chat_identifier|chat_id):(.+)$/i.exec(trimmed);
|
||||
return (prefixed?.[2] ?? trimmed).trim() || undefined;
|
||||
}
|
||||
|
||||
function parseIMessageConversationIdFromTargetForTest(raw?: string | null): string | undefined {
|
||||
const trimmed = raw?.trim().replace(/^imessage:/i, "");
|
||||
if (!trimmed) {
|
||||
@@ -294,30 +285,6 @@ function setMinimalAcpContextRegistryForTests(): void {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
pluginId: "bluebubbles",
|
||||
source: "test",
|
||||
plugin: {
|
||||
...createChannelTestPluginBase({ id: "bluebubbles", label: "BlueBubbles" }),
|
||||
bindings: {
|
||||
resolveCommandConversation: ({
|
||||
originatingTo,
|
||||
commandTo,
|
||||
fallbackTo,
|
||||
}: {
|
||||
originatingTo?: string;
|
||||
commandTo?: string;
|
||||
fallbackTo?: string;
|
||||
}) => {
|
||||
const conversationId =
|
||||
parseBlueBubblesConversationIdFromTargetForTest(originatingTo) ??
|
||||
parseBlueBubblesConversationIdFromTargetForTest(commandTo) ??
|
||||
parseBlueBubblesConversationIdFromTargetForTest(fallbackTo);
|
||||
return conversationId ? { conversationId } : null;
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
pluginId: "imessage",
|
||||
source: "test",
|
||||
@@ -698,16 +665,16 @@ describe("commands-acp context", () => {
|
||||
expect(resolveAcpCommandParentConversationId(params)).toBe("!room:example.org");
|
||||
});
|
||||
|
||||
it("resolves BlueBubbles DM conversation ids from current targets", () => {
|
||||
it("resolves iMessage DM conversation ids from current targets", () => {
|
||||
const params = buildCommandTestParams("/acp status", baseCfg, {
|
||||
Provider: "bluebubbles",
|
||||
Surface: "bluebubbles",
|
||||
OriginatingChannel: "bluebubbles",
|
||||
OriginatingTo: "bluebubbles:+15555550123",
|
||||
Provider: "imessage",
|
||||
Surface: "imessage",
|
||||
OriginatingChannel: "imessage",
|
||||
OriginatingTo: "imessage:+15555550123",
|
||||
});
|
||||
|
||||
expect(resolveAcpCommandBindingContext(params)).toEqual({
|
||||
channel: "bluebubbles",
|
||||
channel: "imessage",
|
||||
accountId: "default",
|
||||
threadId: undefined,
|
||||
conversationId: "+15555550123",
|
||||
@@ -716,17 +683,17 @@ describe("commands-acp context", () => {
|
||||
expect(resolveAcpCommandConversationId(params)).toBe("+15555550123");
|
||||
});
|
||||
|
||||
it("resolves BlueBubbles group conversation ids from explicit chat targets", () => {
|
||||
it("resolves iMessage group conversation ids from explicit chat targets", () => {
|
||||
const params = buildCommandTestParams("/acp status", baseCfg, {
|
||||
Provider: "bluebubbles",
|
||||
Surface: "bluebubbles",
|
||||
OriginatingChannel: "bluebubbles",
|
||||
OriginatingTo: "bluebubbles:chat_guid:iMessage;+;chat123",
|
||||
Provider: "imessage",
|
||||
Surface: "imessage",
|
||||
OriginatingChannel: "imessage",
|
||||
OriginatingTo: "imessage:chat_guid:iMessage;+;chat123",
|
||||
AccountId: "work",
|
||||
});
|
||||
|
||||
expect(resolveAcpCommandBindingContext(params)).toEqual({
|
||||
channel: "bluebubbles",
|
||||
channel: "imessage",
|
||||
accountId: "work",
|
||||
threadId: undefined,
|
||||
conversationId: "iMessage;+;chat123",
|
||||
|
||||
@@ -23,7 +23,6 @@ const BUNDLED_EXTENSION_IDS = [...bundledPluginRoots.keys()].toSorted(
|
||||
(left, right) => right.length - left.length,
|
||||
);
|
||||
const GUARDED_CHANNEL_EXTENSIONS = new Set([
|
||||
"bluebubbles",
|
||||
"discord",
|
||||
"feishu",
|
||||
"googlechat",
|
||||
@@ -196,7 +195,6 @@ const CHANNEL_CONFIG_SCHEMA_GUARDS: GuardedSource[] = [
|
||||
|
||||
const LOCAL_EXTENSION_API_BARREL_GUARDS = [
|
||||
"acpx",
|
||||
"bluebubbles",
|
||||
"device-pair",
|
||||
"diagnostics-otel",
|
||||
"diagnostics-prometheus",
|
||||
|
||||
@@ -10,7 +10,6 @@ export const channelPluginSurfaceKeys = [
|
||||
] as const;
|
||||
|
||||
export const sessionBindingContractChannelIds = [
|
||||
"bluebubbles",
|
||||
"discord",
|
||||
"feishu",
|
||||
"imessage",
|
||||
|
||||
@@ -125,13 +125,6 @@ type ChannelConversationBindingManagerFactory = NonNullable<
|
||||
NonNullable<ChannelPlugin["conversationBindings"]>["createManager"]
|
||||
>;
|
||||
|
||||
type BlueBubblesContractApi = {
|
||||
blueBubblesConversationBindingTesting: {
|
||||
resetBlueBubblesConversationBindingsForTests: () => void;
|
||||
};
|
||||
createBlueBubblesConversationBindingManager: ChannelConversationBindingManagerFactory;
|
||||
};
|
||||
|
||||
type DiscordContractApi = {
|
||||
createThreadBindingManager: (params: {
|
||||
accountId: string;
|
||||
@@ -216,15 +209,6 @@ function setRegistryBackedConversationBindingPlugin(params: {
|
||||
);
|
||||
}
|
||||
|
||||
async function prepareBlueBubblesSessionBindingContract() {
|
||||
const api = await getContractApi<BlueBubblesContractApi>("bluebubbles");
|
||||
api.blueBubblesConversationBindingTesting.resetBlueBubblesConversationBindingsForTests();
|
||||
setRegistryBackedConversationBindingPlugin({
|
||||
id: "bluebubbles",
|
||||
createManager: api.createBlueBubblesConversationBindingManager,
|
||||
});
|
||||
}
|
||||
|
||||
async function prepareDiscordSessionBindingContract() {
|
||||
const api = await getContractApi<DiscordContractApi>("discord");
|
||||
api.discordThreadBindingTesting.resetThreadBindingsForTests();
|
||||
@@ -258,69 +242,6 @@ const sessionBindingContractEntries: Record<
|
||||
SessionBindingContractChannelId,
|
||||
Omit<SessionBindingContractEntry, "id">
|
||||
> = {
|
||||
bluebubbles: {
|
||||
beforeEach: prepareBlueBubblesSessionBindingContract,
|
||||
expectedCapabilities: {
|
||||
adapterAvailable: true,
|
||||
bindSupported: true,
|
||||
unbindSupported: true,
|
||||
placements: ["current"],
|
||||
},
|
||||
getCapabilities: () => {
|
||||
void createChannelConversationBindingManager({
|
||||
channelId: "bluebubbles",
|
||||
cfg: baseSessionBindingCfg,
|
||||
accountId: "default",
|
||||
});
|
||||
return getSessionBindingService().getCapabilities({
|
||||
channel: "bluebubbles",
|
||||
accountId: "default",
|
||||
});
|
||||
},
|
||||
bindAndResolve: async () => {
|
||||
await createChannelConversationBindingManager({
|
||||
channelId: "bluebubbles",
|
||||
cfg: baseSessionBindingCfg,
|
||||
accountId: "default",
|
||||
});
|
||||
const service = getSessionBindingService();
|
||||
const binding = await service.bind({
|
||||
targetSessionKey: "agent:codex:acp:binding:bluebubbles:default:abc123",
|
||||
targetKind: "session",
|
||||
conversation: {
|
||||
channel: "bluebubbles",
|
||||
accountId: "default",
|
||||
conversationId: "+15555550123",
|
||||
},
|
||||
placement: "current",
|
||||
metadata: {
|
||||
agentId: "codex",
|
||||
label: "codex-main",
|
||||
},
|
||||
});
|
||||
expectResolvedSessionBinding({
|
||||
channel: "bluebubbles",
|
||||
accountId: "default",
|
||||
conversationId: "+15555550123",
|
||||
targetSessionKey: "agent:codex:acp:binding:bluebubbles:default:abc123",
|
||||
});
|
||||
return binding;
|
||||
},
|
||||
unbindAndVerify: unbindAndExpectClearedSessionBinding,
|
||||
cleanup: async () => {
|
||||
const manager = await createChannelConversationBindingManager({
|
||||
channelId: "bluebubbles",
|
||||
cfg: baseSessionBindingCfg,
|
||||
accountId: "default",
|
||||
});
|
||||
await manager?.stop();
|
||||
expectClearedSessionBinding({
|
||||
channel: "bluebubbles",
|
||||
accountId: "default",
|
||||
conversationId: "+15555550123",
|
||||
});
|
||||
},
|
||||
},
|
||||
discord: {
|
||||
beforeEach: prepareDiscordSessionBindingContract,
|
||||
expectedCapabilities: {
|
||||
|
||||
@@ -11,7 +11,6 @@ type DirectoryContractRef = {
|
||||
};
|
||||
|
||||
const threadingContractPluginIds = new Set<ChannelId>([
|
||||
"bluebubbles",
|
||||
"discord",
|
||||
"googlechat",
|
||||
"matrix",
|
||||
|
||||
@@ -705,7 +705,7 @@ describe("promptParsedAllowFromForAccount", () => {
|
||||
const next = await promptParsedAllowFromForAccount({
|
||||
cfg: {
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
imessage: {
|
||||
accounts: {
|
||||
alt: {
|
||||
allowFrom: ["old"],
|
||||
@@ -717,7 +717,7 @@ describe("promptParsedAllowFromForAccount", () => {
|
||||
accountId: "alt",
|
||||
defaultAccountId: DEFAULT_ACCOUNT_ID,
|
||||
prompter,
|
||||
noteTitle: "BlueBubbles allowlist",
|
||||
noteTitle: "iMessage allowlist",
|
||||
noteLines: ["line"],
|
||||
message: "msg",
|
||||
placeholder: "placeholder",
|
||||
@@ -725,7 +725,7 @@ describe("promptParsedAllowFromForAccount", () => {
|
||||
parseSetupEntriesWithParser(raw, (entry) => ({ value: entry.toLowerCase() })),
|
||||
getExistingAllowFrom: ({ cfg, accountId }) => [
|
||||
...((
|
||||
cfg.channels?.bluebubbles?.accounts?.[accountId] as
|
||||
cfg.channels?.imessage?.accounts?.[accountId] as
|
||||
| { allowFrom?: ReadonlyArray<string | number> }
|
||||
| undefined
|
||||
)?.allowFrom ?? []),
|
||||
@@ -733,7 +733,7 @@ describe("promptParsedAllowFromForAccount", () => {
|
||||
applyAllowFrom: ({ cfg, accountId, allowFrom }) =>
|
||||
patchChannelConfigForAccount({
|
||||
cfg,
|
||||
channel: "bluebubbles",
|
||||
channel: "imessage",
|
||||
accountId,
|
||||
patch: { allowFrom },
|
||||
}),
|
||||
@@ -741,12 +741,12 @@ describe("promptParsedAllowFromForAccount", () => {
|
||||
|
||||
expect(
|
||||
(
|
||||
next.channels?.bluebubbles?.accounts?.alt as
|
||||
next.channels?.imessage?.accounts?.alt as
|
||||
| { allowFrom?: ReadonlyArray<string | number> }
|
||||
| undefined
|
||||
)?.allowFrom,
|
||||
).toEqual(["alice"]);
|
||||
expect(prompter.note).toHaveBeenCalledWith("line", "BlueBubbles allowlist");
|
||||
expect(prompter.note).toHaveBeenCalledWith("line", "iMessage allowlist");
|
||||
});
|
||||
|
||||
it("can merge parsed values with existing entries", async () => {
|
||||
@@ -788,7 +788,7 @@ describe("createPromptParsedAllowFromForAccount", () => {
|
||||
parseEntries: (raw) => ({ entries: [raw.trim().toLowerCase()] }),
|
||||
getExistingAllowFrom: ({ cfg, accountId }) => [
|
||||
...((
|
||||
cfg.channels?.bluebubbles?.accounts?.[accountId] as
|
||||
cfg.channels?.imessage?.accounts?.[accountId] as
|
||||
| { allowFrom?: ReadonlyArray<string | number> }
|
||||
| undefined
|
||||
)?.allowFrom ?? []),
|
||||
@@ -796,7 +796,7 @@ describe("createPromptParsedAllowFromForAccount", () => {
|
||||
applyAllowFrom: ({ cfg, accountId, allowFrom }) =>
|
||||
patchChannelConfigForAccount({
|
||||
cfg,
|
||||
channel: "bluebubbles",
|
||||
channel: "imessage",
|
||||
accountId,
|
||||
patch: { allowFrom },
|
||||
}),
|
||||
@@ -806,7 +806,7 @@ describe("createPromptParsedAllowFromForAccount", () => {
|
||||
const next = await promptAllowFrom({
|
||||
cfg: {
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
imessage: {
|
||||
accounts: {
|
||||
work: {
|
||||
allowFrom: ["old"],
|
||||
@@ -820,7 +820,7 @@ describe("createPromptParsedAllowFromForAccount", () => {
|
||||
|
||||
expect(
|
||||
(
|
||||
next.channels?.bluebubbles?.accounts?.work as
|
||||
next.channels?.imessage?.accounts?.work as
|
||||
| { allowFrom?: ReadonlyArray<string | number> }
|
||||
| undefined
|
||||
)?.allowFrom,
|
||||
|
||||
@@ -31,8 +31,8 @@ describe("resolveChannelTtsVoiceDelivery", () => {
|
||||
setActivePluginRegistry(
|
||||
createTestRegistry([
|
||||
{
|
||||
pluginId: "bluebubbles",
|
||||
plugin: createChannelPlugin("bluebubbles", {
|
||||
pluginId: "imessage",
|
||||
plugin: createChannelPlugin("imessage", {
|
||||
chatTypes: ["direct"],
|
||||
tts: {
|
||||
voice: {
|
||||
@@ -85,7 +85,7 @@ describe("resolveChannelTtsVoiceDelivery", () => {
|
||||
},
|
||||
]),
|
||||
);
|
||||
expect(resolveChannelTtsVoiceDelivery("bluebubbles")).toEqual({
|
||||
expect(resolveChannelTtsVoiceDelivery("imessage")).toEqual({
|
||||
synthesisTarget: "audio-file",
|
||||
audioFileFormats: ["mp3", "caf", "audio/mpeg", "audio/x-caf"],
|
||||
});
|
||||
|
||||
@@ -277,7 +277,7 @@ export type ChannelGroupContext = {
|
||||
* Container tokens (file-extension shape, no leading dot) that the host
|
||||
* speech-core pipeline knows how to pre-transcode synthesized audio into.
|
||||
* Channels that benefit from a specific container — currently only
|
||||
* BlueBubbles, which needs Apple's native voice-memo CAF descriptor — name
|
||||
* iMessage, which needs Apple's native voice-memo CAF descriptor — name
|
||||
* one here. Adding a new entry requires extending the host transcoder
|
||||
* recipe table in lockstep so a typed declaration cannot silently no-op.
|
||||
*/
|
||||
@@ -292,7 +292,7 @@ export type ChannelTtsVoiceDeliveryCapabilities = {
|
||||
* delivery. When set and the host can transcode (e.g. `afconvert` on
|
||||
* macOS), the TTS pipeline pre-encodes synthesized audio to this format
|
||||
* before handing it to the channel. Useful for channels (such as
|
||||
* BlueBubbles) whose downstream attempts its own container conversion
|
||||
* iMessage) whose downstream attempts its own container conversion
|
||||
* that races against the upload write and fails.
|
||||
*/
|
||||
preferAudioFileFormat?: PreferredAudioFileFormat;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -97,7 +97,7 @@ describe("config plugin validation", () => {
|
||||
let suiteHome = "";
|
||||
let badPluginDir = "";
|
||||
let enumPluginDir = "";
|
||||
let bluebubblesPluginDir = "";
|
||||
let chatPluginDir = "";
|
||||
let googleOverridePluginDir = "";
|
||||
let voiceCallSchemaPluginDir = "";
|
||||
let bundlePluginDir = "";
|
||||
@@ -135,7 +135,7 @@ describe("config plugin validation", () => {
|
||||
await mkdirSafe(suiteHome);
|
||||
badPluginDir = path.join(suiteHome, "bad-plugin");
|
||||
enumPluginDir = path.join(suiteHome, "enum-plugin");
|
||||
bluebubblesPluginDir = path.join(suiteHome, "bluebubbles-plugin");
|
||||
chatPluginDir = path.join(suiteHome, "chat-plugin");
|
||||
await writePluginFixture({
|
||||
dir: badPluginDir,
|
||||
id: "bad-plugin",
|
||||
@@ -163,9 +163,9 @@ describe("config plugin validation", () => {
|
||||
},
|
||||
});
|
||||
await writePluginFixture({
|
||||
dir: bluebubblesPluginDir,
|
||||
id: "bluebubbles-plugin",
|
||||
channels: ["bluebubbles"],
|
||||
dir: chatPluginDir,
|
||||
id: "chat-plugin",
|
||||
channels: ["chat"],
|
||||
schema: { type: "object" },
|
||||
});
|
||||
googleOverridePluginDir = path.join(suiteHome, "google");
|
||||
@@ -640,7 +640,7 @@ describe("config plugin validation", () => {
|
||||
it("does not auto-allow config-loaded overrides of bundled web search plugin ids", async () => {
|
||||
const res = validateInSuite({
|
||||
plugins: {
|
||||
allow: ["bluebubbles", "memory-core"],
|
||||
allow: ["imessage", "memory-core"],
|
||||
load: {
|
||||
paths: [googleOverridePluginDir],
|
||||
},
|
||||
@@ -909,8 +909,8 @@ describe("config plugin validation", () => {
|
||||
|
||||
it("accepts plugin heartbeat targets", async () => {
|
||||
const res = validateInSuite({
|
||||
agents: { defaults: { heartbeat: { target: "bluebubbles" } }, list: [{ id: "pi" }] },
|
||||
plugins: { enabled: false, load: { paths: [bluebubblesPluginDir] } },
|
||||
agents: { defaults: { heartbeat: { target: "chat" } }, list: [{ id: "pi" }] },
|
||||
plugins: { enabled: false, load: { paths: [chatPluginDir] } },
|
||||
});
|
||||
expect(res.ok).toBe(true);
|
||||
});
|
||||
|
||||
@@ -244,7 +244,7 @@ describe("web search provider config", () => {
|
||||
it("does not warn for brave plugin config when bundled web search allowlist compat applies", () => {
|
||||
const res = validateConfigObjectWithPlugins({
|
||||
plugins: {
|
||||
allow: ["bluebubbles", "memory-core"],
|
||||
allow: ["imessage", "memory-core"],
|
||||
entries: {
|
||||
brave: {
|
||||
config: {
|
||||
|
||||
@@ -445,13 +445,12 @@ describe("config io write prepare", () => {
|
||||
).toBeUndefined();
|
||||
});
|
||||
|
||||
it("keeps plugin AJV defaults out of the persisted candidate", () => {
|
||||
it("keeps runtime-only channel defaults out of the persisted candidate", () => {
|
||||
const sourceConfig = {
|
||||
gateway: { port: 18789 },
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test-password",
|
||||
imessage: {
|
||||
cliPath: "/usr/local/bin/imsg",
|
||||
},
|
||||
},
|
||||
} satisfies OpenClawConfig;
|
||||
@@ -459,10 +458,9 @@ describe("config io write prepare", () => {
|
||||
const runtimeConfig: OpenClawConfig = {
|
||||
gateway: { port: 18789 },
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
serverUrl: "http://localhost:1234",
|
||||
password: "test-password",
|
||||
enrichGroupParticipantsFromContacts: true,
|
||||
imessage: {
|
||||
cliPath: "/usr/local/bin/imsg",
|
||||
runtimeOnlyDefault: true,
|
||||
},
|
||||
},
|
||||
} satisfies OpenClawConfig;
|
||||
@@ -484,10 +482,9 @@ describe("config io write prepare", () => {
|
||||
auth: { mode: "token" },
|
||||
});
|
||||
const channels = persisted.channels as Record<string, Record<string, unknown>> | undefined;
|
||||
expect(channels?.bluebubbles).toBeDefined();
|
||||
expect(channels?.bluebubbles).not.toHaveProperty("enrichGroupParticipantsFromContacts");
|
||||
expect(channels?.bluebubbles?.serverUrl).toBe("http://localhost:1234");
|
||||
expect(channels?.bluebubbles?.password).toBe("test-password");
|
||||
expect(channels?.imessage).toBeDefined();
|
||||
expect(channels?.imessage).not.toHaveProperty("runtimeOnlyDefault");
|
||||
expect(channels?.imessage?.cliPath).toBe("/usr/local/bin/imsg");
|
||||
});
|
||||
|
||||
it("does not reintroduce legacy nested dm.policy defaults in the persisted candidate", () => {
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
} from "./plugin-auto-enable.js";
|
||||
import {
|
||||
makeApnChannelConfig,
|
||||
makeBluebubblesAndImessageChannels,
|
||||
makeIsolatedEnv,
|
||||
makeRegistry,
|
||||
makeTempDir,
|
||||
@@ -27,18 +26,6 @@ function applyWithApnChannelConfig(extra?: {
|
||||
});
|
||||
}
|
||||
|
||||
function applyWithBluebubblesImessageConfig(extra?: {
|
||||
plugins?: { entries?: Record<string, { enabled: boolean }>; deny?: string[] };
|
||||
}) {
|
||||
return applyPluginAutoEnable({
|
||||
config: {
|
||||
channels: makeBluebubblesAndImessageChannels(),
|
||||
...(extra?.plugins ? { plugins: extra.plugins } : {}),
|
||||
},
|
||||
env: makeIsolatedEnv(),
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
resetPluginAutoEnableTestState();
|
||||
});
|
||||
@@ -417,45 +404,6 @@ describe("applyPluginAutoEnable channels", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("prefers bluebubbles: skips imessage auto-configure when both are configured", () => {
|
||||
const result = applyWithBluebubblesImessageConfig();
|
||||
|
||||
expect(result.config.channels?.bluebubbles?.enabled).toBe(true);
|
||||
expect(result.config.plugins?.entries?.imessage?.enabled).toBe(false);
|
||||
expect(result.changes.join("\n")).toContain("BlueBubbles configured, enabled automatically.");
|
||||
expect(result.changes.join("\n")).not.toContain(
|
||||
"iMessage configured, enabled automatically.",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps imessage enabled if already explicitly enabled (non-destructive)", () => {
|
||||
const result = applyWithBluebubblesImessageConfig({
|
||||
plugins: { entries: { imessage: { enabled: true } } },
|
||||
});
|
||||
|
||||
expect(result.config.channels?.bluebubbles?.enabled).toBe(true);
|
||||
expect(result.config.plugins?.entries?.imessage?.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it("allows imessage auto-configure when bluebubbles is explicitly disabled", () => {
|
||||
const result = applyWithBluebubblesImessageConfig({
|
||||
plugins: { entries: { bluebubbles: { enabled: false } } },
|
||||
});
|
||||
|
||||
expect(result.config.plugins?.entries?.bluebubbles?.enabled).toBe(false);
|
||||
expect(result.config.channels?.imessage?.enabled).toBe(true);
|
||||
expect(result.changes.join("\n")).toContain("iMessage configured, enabled automatically.");
|
||||
});
|
||||
|
||||
it("allows imessage auto-configure when bluebubbles is in deny list", () => {
|
||||
const result = applyWithBluebubblesImessageConfig({
|
||||
plugins: { deny: ["bluebubbles"] },
|
||||
});
|
||||
|
||||
expect(result.config.plugins?.entries?.bluebubbles).toBeUndefined();
|
||||
expect(result.config.channels?.imessage?.enabled).toBe(true);
|
||||
});
|
||||
|
||||
it("auto-enables imessage when only imessage is configured", () => {
|
||||
const result = applyPluginAutoEnable({
|
||||
config: {
|
||||
|
||||
@@ -72,10 +72,3 @@ export function makeRegistry(
|
||||
export function makeApnChannelConfig() {
|
||||
return { channels: { apn: { someKey: "value" } } };
|
||||
}
|
||||
|
||||
export function makeBluebubblesAndImessageChannels() {
|
||||
return {
|
||||
bluebubbles: { serverUrl: "http://localhost:1234", password: "x" },
|
||||
imessage: { cliPath: "/usr/local/bin/imsg" },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import { sensitive } from "./zod-schema.sensitive.js";
|
||||
|
||||
const { collectMatchingSchemaPaths, mapSensitivePaths } = __test__;
|
||||
const BUNDLED_CHANNEL_HINT_PREFIXES = [
|
||||
"channels.bluebubbles",
|
||||
"channels.discord",
|
||||
"channels.imessage",
|
||||
"channels.irc",
|
||||
|
||||
@@ -68,8 +68,8 @@ describe("config schema", () => {
|
||||
heartbeatChannelInput = {
|
||||
channels: [
|
||||
{
|
||||
id: "bluebubbles",
|
||||
label: "BlueBubbles",
|
||||
id: "imessage",
|
||||
label: "iMessage",
|
||||
configSchema: { type: "object" },
|
||||
},
|
||||
],
|
||||
@@ -276,9 +276,9 @@ describe("config schema", () => {
|
||||
|
||||
const defaultsHint = res.uiHints["agents.defaults.heartbeat.target"];
|
||||
const listHint = res.uiHints["agents.list.*.heartbeat.target"];
|
||||
expect(defaultsHint?.help).toContain("bluebubbles");
|
||||
expect(defaultsHint?.help).toContain("imessage");
|
||||
expect(defaultsHint?.help).toContain("last");
|
||||
expect(listHint?.help).toContain("bluebubbles");
|
||||
expect(listHint?.help).toContain("imessage");
|
||||
});
|
||||
|
||||
it("caches merged schemas for identical plugin/channel metadata", () => {
|
||||
|
||||
@@ -216,18 +216,18 @@ describe("appendAssistantMessageToSessionTranscript", () => {
|
||||
});
|
||||
|
||||
it("finds session entry using normalized (lowercased) key", async () => {
|
||||
const storeKey = "agent:main:bluebubbles:direct:+15551234567";
|
||||
const storeKey = "agent:main:imessage:direct:+15551234567";
|
||||
const store = {
|
||||
[storeKey]: {
|
||||
sessionId: "test-session-normalized",
|
||||
chatType: "direct",
|
||||
channel: "bluebubbles",
|
||||
channel: "imessage",
|
||||
},
|
||||
};
|
||||
fs.writeFileSync(fixture.storePath(), JSON.stringify(store), "utf-8");
|
||||
|
||||
const result = await appendAssistantMessageToSessionTranscript({
|
||||
sessionKey: "agent:main:BlueBubbles:direct:+15551234567",
|
||||
sessionKey: "agent:main:iMessage:direct:+15551234567",
|
||||
text: "Hello normalized!",
|
||||
storePath: fixture.storePath(),
|
||||
});
|
||||
|
||||
@@ -182,7 +182,7 @@ describe("validateConfigObjectRawWithPlugins channel metadata", () => {
|
||||
it("still injects channel AJV defaults even in raw mode — persistence safety is handled by io.ts", async () => {
|
||||
// Channel and plugin AJV validation always runs with applyDefaults: true
|
||||
// (hardcoded) to avoid breaking schemas that mark defaulted fields as
|
||||
// required (e.g., BlueBubbles enrichGroupParticipantsFromContacts).
|
||||
// required.
|
||||
//
|
||||
// The actual protection against leaking these defaults to disk lives in
|
||||
// writeConfigFile (io.ts), which uses persistCandidate (the pre-validation
|
||||
|
||||
@@ -173,8 +173,8 @@ describe("resolveCronDeliveryPlan", () => {
|
||||
it("does not treat channel-owned service prefixes as provider selection", () => {
|
||||
setCronDeliveryTestRegistry([
|
||||
{
|
||||
pluginId: "bluebubbles",
|
||||
plugin: createPrefixOnlyChannelPlugin("bluebubbles", ["bluebubbles"]),
|
||||
pluginId: "imessage",
|
||||
plugin: createPrefixOnlyChannelPlugin("imessage", ["imessage"]),
|
||||
},
|
||||
{ pluginId: "imessage", plugin: createPrefixOnlyChannelPlugin("imessage") },
|
||||
]);
|
||||
|
||||
@@ -85,7 +85,7 @@ describe("resolveChannelSetupSelectionContributions", () => {
|
||||
vi.clearAllMocks();
|
||||
listChatChannels.mockReturnValue([
|
||||
makeMeta("discord", "Discord"),
|
||||
makeMeta("bluebubbles", "BlueBubbles"),
|
||||
makeMeta("imessage", "iMessage"),
|
||||
]);
|
||||
resolveChannelSetupEntries.mockReturnValue(makeChannelSetupEntries());
|
||||
formatChannelPrimerLine.mockImplementation(
|
||||
@@ -121,11 +121,11 @@ describe("resolveChannelSetupSelectionContributions", () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
id: "bluebubbles",
|
||||
id: "imessage",
|
||||
meta: {
|
||||
id: "bluebubbles",
|
||||
label: "BlueBubbles",
|
||||
selectionLabel: "BlueBubbles (macOS app)",
|
||||
id: "imessage",
|
||||
label: "iMessage",
|
||||
selectionLabel: "iMessage (macOS app)",
|
||||
},
|
||||
},
|
||||
],
|
||||
@@ -134,7 +134,7 @@ describe("resolveChannelSetupSelectionContributions", () => {
|
||||
});
|
||||
|
||||
expect(contributions.map((contribution) => contribution.option.label)).toEqual([
|
||||
"BlueBubbles (macOS app)",
|
||||
"iMessage (macOS app)",
|
||||
"Discord (Bot API)",
|
||||
"Zalo (Bot API)",
|
||||
]);
|
||||
|
||||
@@ -672,7 +672,7 @@ describe("setupChannels workspace shadow exclusion", () => {
|
||||
"declares an already-installed plugin whose runtime cannot be loaded",
|
||||
async () => {
|
||||
// Regression: users who uninstalled an externalized channel plugin
|
||||
// (qqbot / bluebubbles / discord / ...) while a non-empty
|
||||
// (qqbot / imessage / discord / ...) while a non-empty
|
||||
// `channels.<id>` entry remained in their config got dead-ended with
|
||||
// "<channel> plugin not available" because the installed-catalog
|
||||
// branch did not fall back to the catalog install flow.
|
||||
|
||||
@@ -640,7 +640,7 @@ export async function setupChannels(
|
||||
// disk keeps it out of `installedCatalogEntries`. Before falling back
|
||||
// to the bundled-plugin enable path, consult the catalog directly so
|
||||
// users with a stale config entry for an externalized channel (qqbot,
|
||||
// bluebubbles, discord, whatsapp, ...) still get auto-install instead
|
||||
// imessage, discord, whatsapp, ...) still get auto-install instead
|
||||
// of a dead-end "plugin not available" note.
|
||||
const fallbackCatalogEntry = getTrustedChannelPluginCatalogEntry(channel, {
|
||||
cfg: next,
|
||||
|
||||
@@ -48,7 +48,7 @@ describe("classifyControlUiRequest", () => {
|
||||
},
|
||||
{
|
||||
name: "falls through non-read requests",
|
||||
pathname: "/bluebubbles-webhook",
|
||||
pathname: "/imessage-webhook",
|
||||
method: "POST",
|
||||
expected: { kind: "not-control-ui" as const },
|
||||
},
|
||||
|
||||
@@ -1100,7 +1100,7 @@ describe("handleControlUiHttpRequest", () => {
|
||||
it("does not handle POST to root-mounted paths (plugin webhook passthrough)", async () => {
|
||||
await withControlUiRoot({
|
||||
fn: async (tmp) => {
|
||||
for (const webhookPath of ["/bluebubbles-webhook", "/custom-webhook", "/callback"]) {
|
||||
for (const webhookPath of ["/imessage-webhook", "/custom-webhook", "/callback"]) {
|
||||
const { res } = makeMockHttpResponse();
|
||||
const handled = await handleControlUiHttpRequest(
|
||||
{ url: webhookPath, method: "POST" } as IncomingMessage,
|
||||
@@ -1120,7 +1120,7 @@ describe("handleControlUiHttpRequest", () => {
|
||||
fn: async (tmp) => {
|
||||
const { res } = makeMockHttpResponse();
|
||||
const handled = await handleControlUiHttpRequest(
|
||||
{ url: "/bluebubbles-webhook", method: "POST" } as IncomingMessage,
|
||||
{ url: "/imessage-webhook", method: "POST" } as IncomingMessage,
|
||||
res,
|
||||
{ basePath: "/openclaw", root: { kind: "resolved", path: tmp } },
|
||||
);
|
||||
@@ -1163,7 +1163,7 @@ describe("handleControlUiHttpRequest", () => {
|
||||
await withControlUiRoot({
|
||||
fn: async (tmp) => {
|
||||
const { handled, end } = await runControlUiRequest({
|
||||
url: "/webhook/bluebubbles",
|
||||
url: "/webhook/imessage",
|
||||
method: "POST",
|
||||
rootPath: tmp,
|
||||
});
|
||||
|
||||
@@ -623,7 +623,7 @@ describe("gateway plugin HTTP auth boundary", () => {
|
||||
test("passes POST webhook routes through root-mounted control ui to plugins", async () => {
|
||||
const handlePluginRequest = vi.fn(async (req: IncomingMessage, res: ServerResponse) => {
|
||||
const pathname = new URL(req.url ?? "/", "http://localhost").pathname;
|
||||
if (req.method !== "POST" || pathname !== "/bluebubbles-webhook") {
|
||||
if (req.method !== "POST" || pathname !== "/imessage-webhook") {
|
||||
return false;
|
||||
}
|
||||
res.statusCode = 200;
|
||||
@@ -637,7 +637,7 @@ describe("gateway plugin HTTP auth boundary", () => {
|
||||
handlePluginRequest,
|
||||
run: async (server) => {
|
||||
const response = await sendRequest(server, {
|
||||
path: "/bluebubbles-webhook",
|
||||
path: "/imessage-webhook",
|
||||
method: "POST",
|
||||
});
|
||||
|
||||
|
||||
@@ -332,7 +332,7 @@ describe("createGatewayPluginRequestHandler", () => {
|
||||
setActivePluginRegistry(laterActiveRegistry);
|
||||
|
||||
const unregister = registerPluginHttpRoute({
|
||||
path: "/bluebubbles-webhook",
|
||||
path: "/imessage-webhook",
|
||||
auth: "plugin",
|
||||
handler: routeHandler,
|
||||
});
|
||||
@@ -344,7 +344,7 @@ describe("createGatewayPluginRequestHandler", () => {
|
||||
});
|
||||
|
||||
const { res } = makeMockHttpResponse();
|
||||
const handled = await handler({ url: "/bluebubbles-webhook" } as IncomingMessage, res);
|
||||
const handled = await handler({ url: "/imessage-webhook" } as IncomingMessage, res);
|
||||
expect(handled).toBe(true);
|
||||
expect(routeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(laterActiveRegistry.httpRoutes).toHaveLength(0);
|
||||
@@ -367,7 +367,7 @@ describe("createGatewayPluginRequestHandler", () => {
|
||||
pinActivePluginHttpRouteRegistry(startupRegistry);
|
||||
|
||||
const unregister = registerPluginHttpRoute({
|
||||
path: "/bluebubbles-webhook",
|
||||
path: "/imessage-webhook",
|
||||
auth: "plugin",
|
||||
handler: routeHandler,
|
||||
});
|
||||
@@ -379,7 +379,7 @@ describe("createGatewayPluginRequestHandler", () => {
|
||||
});
|
||||
|
||||
const { res } = makeMockHttpResponse();
|
||||
const handled = await handler({ url: "/bluebubbles-webhook" } as IncomingMessage, res);
|
||||
const handled = await handler({ url: "/imessage-webhook" } as IncomingMessage, res);
|
||||
expect(handled).toBe(true);
|
||||
expect(routeHandler).toHaveBeenCalledTimes(1);
|
||||
expect(staleExplicitRegistry.httpRoutes).toHaveLength(1);
|
||||
|
||||
@@ -115,9 +115,9 @@ export function createDefaultGatewayTestChannels() {
|
||||
plugin: createStubChannelPlugin({ id: "zalouser", label: "Zalo Personal" }),
|
||||
},
|
||||
{
|
||||
pluginId: "bluebubbles",
|
||||
pluginId: "imessage",
|
||||
source: "test" as const,
|
||||
plugin: createStubChannelPlugin({ id: "bluebubbles", label: "BlueBubbles" }),
|
||||
plugin: createStubChannelPlugin({ id: "imessage", label: "iMessage" }),
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ describe("mime detection", () => {
|
||||
// CAF files start with the four-byte ASCII tag "caff". `file-type` v22 has
|
||||
// no native CAF detector, so without the manual magic-byte fallback the
|
||||
// host-local-media validator drops `afconvert`-produced voice-memo CAFs as
|
||||
// unknown binary blobs. Regression guard for the BlueBubbles voice-memo
|
||||
// unknown binary blobs. Regression guard for the iMessage voice-memo
|
||||
// pre-transcode path.
|
||||
const buf = Buffer.concat([Buffer.from("caff", "ascii"), Buffer.alloc(60)]);
|
||||
const mime = await detectMime({ buffer: buf });
|
||||
|
||||
@@ -65,7 +65,7 @@ describe("createChannelReplyPipeline", () => {
|
||||
const pipeline = createChannelReplyPipeline({
|
||||
cfg: {},
|
||||
agentId: "main",
|
||||
channel: "bluebubbles",
|
||||
channel: "imessage",
|
||||
typingCallbacks: {
|
||||
onReplyStart,
|
||||
onIdle,
|
||||
|
||||
@@ -108,9 +108,3 @@ export type ReplyPrefixOptions = ReplyPrefixOptionsCompat;
|
||||
export type SourceReplyDeliveryMode = SourceReplyDeliveryModeCompat;
|
||||
/** @deprecated Use `openclaw/plugin-sdk/channel-message`. */
|
||||
export type TypingCallbacks = TypingCallbacksCompat;
|
||||
|
||||
export {
|
||||
resolveBlueBubblesGroupRequireMention,
|
||||
resolveBlueBubblesGroupToolPolicy,
|
||||
} from "./bluebubbles-policy.js";
|
||||
export { collectBlueBubblesStatusIssues } from "./bluebubbles.js";
|
||||
|
||||
@@ -114,7 +114,7 @@ describe("config footprint guardrails", () => {
|
||||
});
|
||||
|
||||
it("keeps bundled channel private-network config canonical in generated metadata", () => {
|
||||
const pluginIds = ["bluebubbles", "matrix", "nextcloud-talk", "tlon"];
|
||||
const pluginIds = ["matrix", "nextcloud-talk", "tlon"];
|
||||
|
||||
for (const pluginId of pluginIds) {
|
||||
const metadata = GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA.find(
|
||||
|
||||
@@ -3,7 +3,6 @@ import { describePackageManifestContract } from "openclaw/plugin-sdk/plugin-test
|
||||
type PackageManifestContractParams = Parameters<typeof describePackageManifestContract>[0];
|
||||
|
||||
const packageManifestContractTests: PackageManifestContractParams[] = [
|
||||
{ pluginId: "bluebubbles", minHostVersionBaseline: "2026.3.22" },
|
||||
{
|
||||
pluginId: "discord",
|
||||
pluginLocalRuntimeDeps: ["@discordjs/voice", "discord-api-types", "opusscript"],
|
||||
|
||||
@@ -23,7 +23,7 @@ const PRIVATE_BUNDLED_SDK_SURFACE_PATTERN =
|
||||
/\b(?:Private helper surface|Narrow plugin-sdk surface for the bundled|Narrow .*runtime exports used by the bundled)\b/i;
|
||||
const GENERIC_CORE_HELPER_FILES = ["src/polls.ts", "src/poll-params.ts"] as const;
|
||||
const GENERIC_CORE_PLUGIN_OWNER_NAME_PATTERN =
|
||||
/\b(?:bluebubbles|discord|feishu|googlechat|matrix|mattermost|msteams|slack|telegram|whatsapp|zalo|zalouser)\b/gi;
|
||||
/\b(?:imessage|discord|feishu|googlechat|matrix|mattermost|msteams|slack|telegram|whatsapp|zalo|zalouser)\b/gi;
|
||||
const PACKAGE_CONTRACT_SCAN_TIMEOUT_MS = 240_000;
|
||||
const DEPRECATED_EXTENSION_SDK_SPECIFIERS = new Set([
|
||||
"openclaw/plugin-sdk",
|
||||
|
||||
@@ -214,13 +214,13 @@ describe("registerPluginHttpRoute", () => {
|
||||
setActivePluginRegistry(laterActiveRegistry);
|
||||
|
||||
const unregister = registerPluginHttpRoute({
|
||||
path: "/bluebubbles-webhook",
|
||||
path: "/imessage-webhook",
|
||||
auth: "plugin",
|
||||
handler: vi.fn(),
|
||||
});
|
||||
|
||||
expectRegisteredRouteShape(startupRegistry, {
|
||||
path: "/bluebubbles-webhook",
|
||||
path: "/imessage-webhook",
|
||||
auth: "plugin",
|
||||
});
|
||||
expect(laterActiveRegistry.httpRoutes).toHaveLength(0);
|
||||
|
||||
@@ -133,7 +133,7 @@ describe("plugin runtime route registry", () => {
|
||||
},
|
||||
{
|
||||
name: "prefers the pinned route registry when it already owns routes",
|
||||
pinnedRegistry: createRegistryWithRoute("/bluebubbles-webhook"),
|
||||
pinnedRegistry: createRegistryWithRoute("/imessage-webhook"),
|
||||
explicitRegistry: createRegistryWithRoute("/plugins/diffs"),
|
||||
expected: "pinned",
|
||||
},
|
||||
|
||||
@@ -23,8 +23,8 @@ describe("syncPluginVersions", () => {
|
||||
name: "openclaw",
|
||||
version: "2026.4.1",
|
||||
});
|
||||
writeJson(path.join(rootDir, "extensions/bluebubbles/package.json"), {
|
||||
name: "@openclaw/bluebubbles",
|
||||
writeJson(path.join(rootDir, "extensions/imessage/package.json"), {
|
||||
name: "@openclaw/imessage",
|
||||
version: "2026.3.30",
|
||||
devDependencies: {
|
||||
openclaw: "workspace:*",
|
||||
@@ -47,7 +47,7 @@ describe("syncPluginVersions", () => {
|
||||
|
||||
const summary = syncPluginVersions(rootDir);
|
||||
const updatedPackage = JSON.parse(
|
||||
fs.readFileSync(path.join(rootDir, "extensions/bluebubbles/package.json"), "utf8"),
|
||||
fs.readFileSync(path.join(rootDir, "extensions/imessage/package.json"), "utf8"),
|
||||
) as {
|
||||
version?: string;
|
||||
devDependencies?: Record<string, string>;
|
||||
@@ -65,7 +65,7 @@ describe("syncPluginVersions", () => {
|
||||
};
|
||||
};
|
||||
|
||||
expect(summary.updated).toContain("@openclaw/bluebubbles");
|
||||
expect(summary.updated).toContain("@openclaw/imessage");
|
||||
expect(updatedPackage.version).toBe("2026.4.1");
|
||||
expect(updatedPackage.devDependencies?.openclaw).toBe("workspace:*");
|
||||
expect(updatedPackage.peerDependencies?.openclaw).toBe(">=2026.4.1");
|
||||
|
||||
@@ -723,17 +723,6 @@ describe("test-projects args", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("routes bluebubbles extension tests to the bluebubbles config", () => {
|
||||
expect(buildVitestRunPlans(["extensions/bluebubbles/src/monitor.test.ts"])).toEqual([
|
||||
{
|
||||
config: "test/vitest/vitest.extension-bluebubbles.config.ts",
|
||||
forwardedArgs: [],
|
||||
includePatterns: ["extensions/bluebubbles/src/monitor.test.ts"],
|
||||
watchMode: false,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it("routes feishu extension tests to the feishu config", () => {
|
||||
expect(buildVitestRunPlans(["extensions/feishu/src/channel.test.ts"])).toEqual([
|
||||
{
|
||||
|
||||
@@ -8,14 +8,14 @@ const { loadPluginManifestRegistryMock } = vi.hoisted(() => ({
|
||||
const { loadBundledPluginPublicArtifactModuleSyncMock } = vi.hoisted(() => ({
|
||||
loadBundledPluginPublicArtifactModuleSyncMock: vi.fn(
|
||||
({ artifactBasename, dirName }: { artifactBasename: string; dirName: string }) => {
|
||||
if (dirName === "bluebubbles" && artifactBasename === "secret-contract-api.js") {
|
||||
if (dirName === "discord" && artifactBasename === "secret-contract-api.js") {
|
||||
return {
|
||||
collectRuntimeConfigAssignments: () => undefined,
|
||||
secretTargetRegistryEntries: [
|
||||
{
|
||||
id: "channels.bluebubbles.accounts.*.password",
|
||||
id: "channels.discord.accounts.*.token",
|
||||
type: "channel",
|
||||
path: "channels.bluebubbles.accounts.*.password",
|
||||
path: "channels.discord.accounts.*.token",
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -52,17 +52,17 @@ describe("channel contract api explicit fast path", () => {
|
||||
});
|
||||
|
||||
it("resolves bundled channel secret contracts by explicit channel id without manifest scans", () => {
|
||||
const api = loadBundledChannelSecretContractApi("bluebubbles");
|
||||
const api = loadBundledChannelSecretContractApi("discord");
|
||||
|
||||
expect(api?.collectRuntimeConfigAssignments).toBeTypeOf("function");
|
||||
expect(loadBundledPluginPublicArtifactModuleSyncMock).toHaveBeenCalledWith({
|
||||
dirName: "bluebubbles",
|
||||
dirName: "discord",
|
||||
artifactBasename: "secret-contract-api.js",
|
||||
});
|
||||
expect(api?.secretTargetRegistryEntries).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: "channels.bluebubbles.accounts.*.password",
|
||||
id: "channels.discord.accounts.*.token",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
||||
@@ -11,7 +11,7 @@ const CORE_SECRET_SURFACE_GUARDS = [
|
||||
path: "src/secrets/runtime-config-collectors-channels.ts",
|
||||
forbiddenPatterns: [
|
||||
/["']irc["']/,
|
||||
/["']bluebubbles["']/,
|
||||
/["']imessage["']/,
|
||||
/["']msteams["']/,
|
||||
/["']nextcloud-talk["']/,
|
||||
],
|
||||
@@ -20,7 +20,7 @@ const CORE_SECRET_SURFACE_GUARDS = [
|
||||
path: "src/secrets/target-registry-data.ts",
|
||||
forbiddenPatterns: [
|
||||
/channels\.irc\./,
|
||||
/channels\.bluebubbles\./,
|
||||
/channels\.imessage\./,
|
||||
/channels\.msteams\./,
|
||||
/channels\.nextcloud-talk\./,
|
||||
/plugins\.entries\.(?:brave|google|exa|xai|moonshot|perplexity|firecrawl|tavily|minimax)\.config\.web(?:Search|Fetch)\.apiKey/,
|
||||
|
||||
@@ -31,7 +31,7 @@ describe("runtime channel config collectors", () => {
|
||||
collectChannelConfigAssignments({
|
||||
config: {
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
imessage: {
|
||||
accounts: {
|
||||
ops: {},
|
||||
},
|
||||
@@ -43,7 +43,7 @@ describe("runtime channel config collectors", () => {
|
||||
});
|
||||
|
||||
expect(loadChannelSecretContractApi).toHaveBeenCalledWith({
|
||||
channelId: "bluebubbles",
|
||||
channelId: "imessage",
|
||||
config: expect.any(Object),
|
||||
env: undefined,
|
||||
loadablePluginOrigins: undefined,
|
||||
|
||||
@@ -39,7 +39,6 @@ import {
|
||||
const { prepareSecretsRuntimeSnapshot } = setupSecretsRuntimeSnapshotTestHooks();
|
||||
|
||||
const EXTERNALIZED_CHANNEL_IDS = [
|
||||
"bluebubbles",
|
||||
"discord",
|
||||
"feishu",
|
||||
"googlechat",
|
||||
@@ -128,20 +127,6 @@ describe("secrets runtime externalized channel SecretRef audit", () => {
|
||||
const records = configureExternalChannelRecords();
|
||||
const config = asConfig({
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
serverUrl: "http://127.0.0.1:1234",
|
||||
password: ref("BLUEBUBBLES_PASSWORD"),
|
||||
accounts: {
|
||||
inherited: {
|
||||
enabled: true,
|
||||
},
|
||||
work: {
|
||||
enabled: true,
|
||||
serverUrl: "http://127.0.0.1:1235",
|
||||
password: ref("BLUEBUBBLES_WORK_PASSWORD"),
|
||||
},
|
||||
},
|
||||
},
|
||||
discord: {
|
||||
token: ref("DISCORD_TOKEN"),
|
||||
pluralkit: {
|
||||
@@ -248,8 +233,6 @@ describe("secrets runtime externalized channel SecretRef audit", () => {
|
||||
const snapshot = await prepareSecretsRuntimeSnapshot({
|
||||
config,
|
||||
env: {
|
||||
BLUEBUBBLES_PASSWORD: "bluebubbles-password",
|
||||
BLUEBUBBLES_WORK_PASSWORD: "bluebubbles-work-password",
|
||||
DISCORD_TOKEN: "discord-token",
|
||||
DISCORD_PLURALKIT_TOKEN: "discord-pluralkit-token",
|
||||
DISCORD_VOICE_TTS_API_KEY: "discord-voice-tts-api-key",
|
||||
@@ -279,8 +262,6 @@ describe("secrets runtime externalized channel SecretRef audit", () => {
|
||||
});
|
||||
|
||||
expectResolvedPaths(snapshot.config, {
|
||||
"channels.bluebubbles.password": "bluebubbles-password",
|
||||
"channels.bluebubbles.accounts.work.password": "bluebubbles-work-password",
|
||||
"channels.discord.token": "discord-token",
|
||||
"channels.discord.pluralkit.token": "discord-pluralkit-token",
|
||||
"channels.discord.voice.tts.providers.openai.apiKey": "discord-voice-tts-api-key",
|
||||
@@ -314,16 +295,6 @@ describe("secrets runtime externalized channel SecretRef audit", () => {
|
||||
const records = configureExternalChannelRecords();
|
||||
const config = asConfig({
|
||||
channels: {
|
||||
bluebubbles: {
|
||||
enabled: false,
|
||||
password: inactiveExecRef("BLUEBUBBLES_DISABLED_PASSWORD"),
|
||||
accounts: {
|
||||
disabled: {
|
||||
enabled: false,
|
||||
password: inactiveExecRef("BLUEBUBBLES_DISABLED_ACCOUNT_PASSWORD"),
|
||||
},
|
||||
},
|
||||
},
|
||||
discord: {
|
||||
enabled: false,
|
||||
token: inactiveExecRef("DISCORD_DISABLED_TOKEN"),
|
||||
@@ -435,8 +406,6 @@ describe("secrets runtime externalized channel SecretRef audit", () => {
|
||||
).toEqual(inactiveExecRef("ZALO_DISABLED_ACCOUNT_BOT_TOKEN"));
|
||||
expect(snapshot.warnings.map((warning) => warning.path)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"channels.bluebubbles.password",
|
||||
"channels.bluebubbles.accounts.disabled.password",
|
||||
"channels.discord.token",
|
||||
"channels.discord.pluralkit.token",
|
||||
"channels.discord.voice.tts.providers.openai.apiKey",
|
||||
|
||||
@@ -342,7 +342,7 @@ describe("security/dm-policy-shared", () => {
|
||||
});
|
||||
|
||||
const channels = [
|
||||
"bluebubbles",
|
||||
"imessage",
|
||||
"imessage",
|
||||
"signal",
|
||||
"telegram",
|
||||
|
||||
@@ -12,7 +12,7 @@ const ENVELOPE_CHANNELS = [
|
||||
"Matrix",
|
||||
"Zalo",
|
||||
"Zalo Personal",
|
||||
"BlueBubbles",
|
||||
"iMessage",
|
||||
];
|
||||
|
||||
const MESSAGE_ID_LINE = /^\s*\[message_id:\s*[^\]]+\]\s*$/i;
|
||||
|
||||
@@ -92,12 +92,6 @@ describe("bundled plugin build entries", () => {
|
||||
const entries = listBundledPluginBuildEntries();
|
||||
const artifacts = listBundledPluginPackArtifacts();
|
||||
|
||||
expect(Object.keys(entries).some((entry) => entry.startsWith("extensions/bluebubbles/"))).toBe(
|
||||
true,
|
||||
);
|
||||
expect(artifacts.some((artifact) => artifact.startsWith("dist/extensions/bluebubbles/"))).toBe(
|
||||
false,
|
||||
);
|
||||
for (const pluginId of ["acpx", "googlechat", "line"]) {
|
||||
expect(
|
||||
Object.keys(entries).some((entry) => entry.startsWith(`extensions/${pluginId}/`)),
|
||||
|
||||
@@ -47,15 +47,6 @@ describe("scripts/test-extension.mjs", () => {
|
||||
expect(plan.hasTests).toBe(true);
|
||||
});
|
||||
|
||||
it("resolves bluebubbles onto the bluebubbles vitest config", () => {
|
||||
const plan = resolveExtensionTestPlan({ targetArg: "bluebubbles", cwd: process.cwd() });
|
||||
|
||||
expect(plan.extensionId).toBe("bluebubbles");
|
||||
expect(plan.config).toBe("test/vitest/vitest.extension-bluebubbles.config.ts");
|
||||
expect(plan.roots).toContain(bundledPluginRoot("bluebubbles"));
|
||||
expect(plan.hasTests).toBe(true);
|
||||
});
|
||||
|
||||
it("resolves acpx onto the acpx vitest config", () => {
|
||||
const plan = resolveExtensionTestPlan({ targetArg: "acpx", cwd: process.cwd() });
|
||||
|
||||
@@ -272,7 +263,6 @@ describe("scripts/test-extension.mjs", () => {
|
||||
"msteams",
|
||||
"feishu",
|
||||
"irc",
|
||||
"bluebubbles",
|
||||
"acpx",
|
||||
"diffs",
|
||||
"browser",
|
||||
@@ -283,7 +273,6 @@ describe("scripts/test-extension.mjs", () => {
|
||||
|
||||
expect(batch.extensionIds).toEqual([
|
||||
"acpx",
|
||||
"bluebubbles",
|
||||
"browser",
|
||||
"diffs",
|
||||
"feishu",
|
||||
@@ -312,13 +301,6 @@ describe("scripts/test-extension.mjs", () => {
|
||||
roots: [bundledPluginRoot("acpx")],
|
||||
testFileCount: expect.any(Number),
|
||||
},
|
||||
{
|
||||
config: "test/vitest/vitest.extension-bluebubbles.config.ts",
|
||||
estimatedCost: expect.any(Number),
|
||||
extensionIds: ["bluebubbles"],
|
||||
roots: [bundledPluginRoot("bluebubbles")],
|
||||
testFileCount: expect.any(Number),
|
||||
},
|
||||
{
|
||||
config: "test/vitest/vitest.extension-browser.config.ts",
|
||||
estimatedCost: expect.any(Number),
|
||||
|
||||
@@ -1227,7 +1227,6 @@ describe("scripts/test-projects full-suite sharding", () => {
|
||||
"test/vitest/vitest.auto-reply-top-level.config.ts",
|
||||
"test/vitest/vitest.auto-reply-reply.config.ts",
|
||||
"test/vitest/vitest.extension-acpx.config.ts",
|
||||
"test/vitest/vitest.extension-bluebubbles.config.ts",
|
||||
"test/vitest/vitest.extension-diffs.config.ts",
|
||||
"test/vitest/vitest.extension-discord.config.ts",
|
||||
"test/vitest/vitest.extension-feishu.config.ts",
|
||||
|
||||
@@ -19,7 +19,6 @@ import { createCommandsVitestConfig } from "./vitest/vitest.commands.config.ts";
|
||||
import { createCronVitestConfig } from "./vitest/vitest.cron.config.ts";
|
||||
import { createDaemonVitestConfig } from "./vitest/vitest.daemon.config.ts";
|
||||
import { createExtensionAcpxVitestConfig } from "./vitest/vitest.extension-acpx.config.ts";
|
||||
import { createExtensionBlueBubblesVitestConfig } from "./vitest/vitest.extension-bluebubbles.config.ts";
|
||||
import { createExtensionBrowserVitestConfig } from "./vitest/vitest.extension-browser.config.ts";
|
||||
import { createExtensionChannelsVitestConfig } from "./vitest/vitest.extension-channels.config.ts";
|
||||
import { createExtensionDiffsVitestConfig } from "./vitest/vitest.extension-diffs.config.ts";
|
||||
@@ -236,7 +235,6 @@ describe("scoped vitest configs", () => {
|
||||
const defaultCliConfig = createCliVitestConfig({});
|
||||
const defaultExtensionsConfig = createExtensionsVitestConfig({});
|
||||
const defaultExtensionAcpxConfig = createExtensionAcpxVitestConfig({});
|
||||
const defaultExtensionBlueBubblesConfig = createExtensionBlueBubblesVitestConfig({});
|
||||
const defaultExtensionChannelsConfig = createExtensionChannelsVitestConfig({});
|
||||
const defaultExtensionBrowserConfig = createExtensionBrowserVitestConfig({});
|
||||
const defaultExtensionDiffsConfig = createExtensionDiffsVitestConfig({});
|
||||
@@ -432,13 +430,6 @@ describe("scoped vitest configs", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("normalizes bluebubbles extension include patterns relative to the scoped dir", () => {
|
||||
expect(defaultExtensionBlueBubblesConfig.test?.dir).toBe(
|
||||
path.join(process.cwd(), "extensions"),
|
||||
);
|
||||
expect(defaultExtensionBlueBubblesConfig.test?.include).toEqual(["bluebubbles/**/*.test.ts"]);
|
||||
});
|
||||
|
||||
it("normalizes acpx extension include patterns relative to the scoped dir", () => {
|
||||
expect(defaultExtensionAcpxConfig.test?.dir).toBe(path.join(process.cwd(), "extensions"));
|
||||
expect(defaultExtensionAcpxConfig.test?.include).toEqual(["acpx/**/*.test.ts"]);
|
||||
@@ -625,15 +616,6 @@ describe("scoped vitest configs", () => {
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps bluebubbles tests out of the shared extensions lane", () => {
|
||||
const extensionExcludes = defaultExtensionsConfig.test?.exclude ?? [];
|
||||
expect(
|
||||
extensionExcludes.some((pattern) =>
|
||||
path.matchesGlob("bluebubbles/src/monitor.test.ts", pattern),
|
||||
),
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("keeps feishu tests out of the shared extensions lane", () => {
|
||||
const extensionExcludes = defaultExtensionsConfig.test?.exclude ?? [];
|
||||
expect(
|
||||
|
||||
@@ -55,7 +55,6 @@ export const rootVitestProjects = [
|
||||
"test/vitest/vitest.wizard.config.ts",
|
||||
"test/vitest/vitest.channels.config.ts",
|
||||
"test/vitest/vitest.extension-acpx.config.ts",
|
||||
"test/vitest/vitest.extension-bluebubbles.config.ts",
|
||||
"test/vitest/vitest.extension-diffs.config.ts",
|
||||
"test/vitest/vitest.extension-discord.config.ts",
|
||||
"test/vitest/vitest.extension-feishu.config.ts",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { BUNDLED_PLUGIN_TEST_GLOB } from "./vitest.bundled-plugin-paths.ts";
|
||||
import { extensionExcludedChannelTestGlobs } from "./vitest.channel-paths.mjs";
|
||||
import { acpxExtensionTestRoots } from "./vitest.extension-acpx-paths.mjs";
|
||||
import { blueBubblesExtensionTestRoots } from "./vitest.extension-bluebubbles-paths.mjs";
|
||||
import { browserExtensionTestRoots } from "./vitest.extension-browser-paths.mjs";
|
||||
import { diffsExtensionTestRoots } from "./vitest.extension-diffs-paths.mjs";
|
||||
import { feishuExtensionTestRoots } from "./vitest.extension-feishu-paths.mjs";
|
||||
@@ -45,7 +44,6 @@ export function createExtensionsVitestConfig(
|
||||
exclude: [
|
||||
...extensionExcludedChannelTestGlobs,
|
||||
...acpxExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
|
||||
...blueBubblesExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
|
||||
...browserExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
|
||||
...diffsExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
|
||||
...feishuExtensionTestRoots.map((root) => `${root.replace(/^extensions\//u, "")}/**`),
|
||||
|
||||
@@ -62,7 +62,6 @@ const SCOPED_PROJECT_GROUP_ORDER_BY_NAME = new Map(
|
||||
"cron",
|
||||
"daemon",
|
||||
"extension-acpx",
|
||||
"extension-bluebubbles",
|
||||
"extension-channels",
|
||||
"extension-diffs",
|
||||
"extension-discord",
|
||||
|
||||
@@ -206,8 +206,6 @@ export const sharedVitestConfig = {
|
||||
"test/vitest/vitest.e2e.config.ts",
|
||||
"test/vitest/vitest.extension-acpx-paths.mjs",
|
||||
"test/vitest/vitest.extension-acpx.config.ts",
|
||||
"test/vitest/vitest.extension-bluebubbles-paths.mjs",
|
||||
"test/vitest/vitest.extension-bluebubbles.config.ts",
|
||||
"test/vitest/vitest.extension-channel-single-config.ts",
|
||||
"test/vitest/vitest.extension-channel-split-paths.mjs",
|
||||
"test/vitest/vitest.extension-channels.config.ts",
|
||||
|
||||
@@ -111,7 +111,6 @@ export const fullSuiteVitestShards = [
|
||||
name: "extensions",
|
||||
projects: [
|
||||
"test/vitest/vitest.extension-acpx.config.ts",
|
||||
"test/vitest/vitest.extension-bluebubbles.config.ts",
|
||||
"test/vitest/vitest.extension-diffs.config.ts",
|
||||
"test/vitest/vitest.extension-discord.config.ts",
|
||||
"test/vitest/vitest.extension-feishu.config.ts",
|
||||
|
||||
@@ -190,7 +190,7 @@ describe("parseSessionKey", () => {
|
||||
});
|
||||
|
||||
it("identifies direct chat with known channel", () => {
|
||||
expect(parseSessionKey("agent:main:bluebubbles:direct:+19257864429")).toEqual({
|
||||
expect(parseSessionKey("agent:main:imessage:direct:+19257864429")).toEqual({
|
||||
prefix: "",
|
||||
fallbackName: "iMessage · +19257864429",
|
||||
});
|
||||
@@ -218,7 +218,7 @@ describe("parseSessionKey", () => {
|
||||
});
|
||||
|
||||
it("identifies channel-prefixed legacy keys", () => {
|
||||
expect(parseSessionKey("bluebubbles:g-agent-main-bluebubbles-direct-+19257864429")).toEqual({
|
||||
expect(parseSessionKey("imessage:g-agent-main-imessage-direct-+19257864429")).toEqual({
|
||||
prefix: "",
|
||||
fallbackName: "iMessage Session",
|
||||
});
|
||||
@@ -309,7 +309,7 @@ describe("resolveSessionDisplayName", () => {
|
||||
});
|
||||
|
||||
it("parses direct chat key with channel", () => {
|
||||
expect(resolveSessionDisplayName("agent:main:bluebubbles:direct:+19257864429")).toBe(
|
||||
expect(resolveSessionDisplayName("agent:main:imessage:direct:+19257864429")).toBe(
|
||||
"iMessage · +19257864429",
|
||||
);
|
||||
});
|
||||
@@ -448,8 +448,8 @@ describe("resolveSessionDisplayName", () => {
|
||||
it("does not prefix non-typed sessions with labels", () => {
|
||||
expect(
|
||||
resolveSessionDisplayName(
|
||||
"agent:main:bluebubbles:direct:+19257864429",
|
||||
row({ key: "agent:main:bluebubbles:direct:+19257864429", label: "Tyler" }),
|
||||
"agent:main:imessage:direct:+19257864429",
|
||||
row({ key: "agent:main:imessage:direct:+19257864429", label: "Tyler" }),
|
||||
),
|
||||
).toBe("Tyler");
|
||||
});
|
||||
|
||||
@@ -407,7 +407,7 @@ async function switchChatThinkingLevel(state: AppViewState, nextThinkingLevel: s
|
||||
|
||||
/* Channel display labels. */
|
||||
const CHANNEL_LABELS: Record<string, string> = {
|
||||
bluebubbles: "iMessage",
|
||||
imessage: "iMessage",
|
||||
telegram: "Telegram",
|
||||
discord: "Discord",
|
||||
signal: "Signal",
|
||||
@@ -471,7 +471,7 @@ export function parseSessionKey(key: string): SessionKeyInfo {
|
||||
return { prefix: "", fallbackName: `${channelLabel} Group` };
|
||||
}
|
||||
|
||||
// Channel-prefixed legacy keys, for example "bluebubbles:g-...".
|
||||
// Channel-prefixed legacy keys, for example "imessage:g-...".
|
||||
for (const ch of KNOWN_CHANNEL_KEYS) {
|
||||
if (key === ch || key.startsWith(`${ch}:`)) {
|
||||
return { prefix: "", fallbackName: `${CHANNEL_LABELS[ch]} Session` };
|
||||
|
||||
Reference in New Issue
Block a user