Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled
Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11), free to diverge. Tree copied sans upstream .git; upstream remote added for future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19. Preserves docs/ARCHITECTURE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
266
extensions/imessage/src/accounts.ts
Normal file
266
extensions/imessage/src/accounts.ts
Normal file
@@ -0,0 +1,266 @@
|
||||
// Imessage plugin module implements accounts behavior.
|
||||
import { DEFAULT_ACCOUNT_ID } from "openclaw/plugin-sdk/account-id";
|
||||
import {
|
||||
createAccountListHelpers,
|
||||
normalizeAccountId,
|
||||
resolveMergedAccountConfig,
|
||||
type OpenClawConfig,
|
||||
} from "openclaw/plugin-sdk/account-resolution";
|
||||
import { resolveAccountEntry } from "openclaw/plugin-sdk/routing";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime";
|
||||
import type { IMessageAccountConfig } from "./account-types.js";
|
||||
|
||||
export type ResolvedIMessageAccount = {
|
||||
accountId: string;
|
||||
enabled: boolean;
|
||||
name?: string;
|
||||
config: IMessageAccountConfig;
|
||||
configured: boolean;
|
||||
};
|
||||
|
||||
const { listAccountIds, resolveDefaultAccountId } = createAccountListHelpers("imessage", {
|
||||
implicitDefaultAccount: {
|
||||
channelKeys: ["cliPath", "dbPath"],
|
||||
},
|
||||
});
|
||||
export const listIMessageAccountIds = listAccountIds;
|
||||
export const resolveDefaultIMessageAccountId = resolveDefaultAccountId;
|
||||
|
||||
function resolveIMessageAccountConfig(
|
||||
cfg: OpenClawConfig,
|
||||
accountId: string,
|
||||
): IMessageAccountConfig | undefined {
|
||||
return resolveAccountEntry(cfg.channels?.imessage?.accounts, accountId);
|
||||
}
|
||||
|
||||
type IMessageStreamingConfig = NonNullable<IMessageAccountConfig["streaming"]>;
|
||||
|
||||
function asStreamingConfigObject(value: unknown): IMessageStreamingConfig | undefined {
|
||||
return value && typeof value === "object" && !Array.isArray(value)
|
||||
? (value as IMessageStreamingConfig)
|
||||
: undefined;
|
||||
}
|
||||
|
||||
function asOwnBooleanProperty(value: unknown, key: string): boolean | undefined {
|
||||
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
||||
return undefined;
|
||||
}
|
||||
const record = value as Record<string, unknown>;
|
||||
return Object.hasOwn(record, key) && typeof record[key] === "boolean" ? record[key] : undefined;
|
||||
}
|
||||
|
||||
function mergeIMessageStreamingConfig(
|
||||
base: unknown,
|
||||
account: unknown,
|
||||
accountFlatBlockStreaming: unknown,
|
||||
): IMessageStreamingConfig | undefined {
|
||||
const baseConfig = asStreamingConfigObject(base);
|
||||
const accountConfig = asStreamingConfigObject(account);
|
||||
const accountBlockEnabled = asOwnBooleanProperty(accountConfig?.block, "enabled");
|
||||
const flatAccountBlockEnabled =
|
||||
accountBlockEnabled === undefined && typeof accountFlatBlockStreaming === "boolean"
|
||||
? accountFlatBlockStreaming
|
||||
: undefined;
|
||||
const applyFlatAccountBlockEnabled = (
|
||||
config: IMessageStreamingConfig | undefined,
|
||||
): IMessageStreamingConfig | undefined => {
|
||||
if (flatAccountBlockEnabled === undefined || config === undefined) {
|
||||
return config;
|
||||
}
|
||||
return {
|
||||
...config,
|
||||
block: {
|
||||
...config.block,
|
||||
enabled: flatAccountBlockEnabled,
|
||||
},
|
||||
};
|
||||
};
|
||||
if (!baseConfig || !accountConfig) {
|
||||
return applyFlatAccountBlockEnabled(accountConfig ?? baseConfig);
|
||||
}
|
||||
return applyFlatAccountBlockEnabled({
|
||||
...baseConfig,
|
||||
...accountConfig,
|
||||
...(baseConfig.block || accountConfig.block
|
||||
? {
|
||||
block: {
|
||||
...baseConfig.block,
|
||||
...accountConfig.block,
|
||||
...(baseConfig.block?.coalesce || accountConfig.block?.coalesce
|
||||
? {
|
||||
coalesce: {
|
||||
...baseConfig.block?.coalesce,
|
||||
...accountConfig.block?.coalesce,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
|
||||
function mergeIMessageAccountConfig(cfg: OpenClawConfig, accountId: string): IMessageAccountConfig {
|
||||
const accountConfig = resolveIMessageAccountConfig(cfg, accountId);
|
||||
const merged = resolveMergedAccountConfig<IMessageAccountConfig>({
|
||||
channelConfig: cfg.channels?.imessage as IMessageAccountConfig | undefined,
|
||||
accounts: cfg.channels?.imessage?.accounts as
|
||||
| Record<string, Partial<IMessageAccountConfig>>
|
||||
| undefined,
|
||||
accountId,
|
||||
});
|
||||
const streaming = mergeIMessageStreamingConfig(
|
||||
(cfg.channels?.imessage as Record<string, unknown> | undefined)?.streaming,
|
||||
(accountConfig as Record<string, unknown> | undefined)?.streaming,
|
||||
(accountConfig as Record<string, unknown> | undefined)?.blockStreaming,
|
||||
);
|
||||
return streaming !== undefined ? ({ ...merged, streaming } as IMessageAccountConfig) : merged;
|
||||
}
|
||||
|
||||
export function resolveIMessageAccount(params: {
|
||||
cfg: OpenClawConfig;
|
||||
accountId?: string | null;
|
||||
}): ResolvedIMessageAccount {
|
||||
const accountId = normalizeAccountId(
|
||||
params.accountId ?? resolveDefaultIMessageAccountId(params.cfg),
|
||||
);
|
||||
const baseEnabled = params.cfg.channels?.imessage?.enabled !== false;
|
||||
const merged = mergeIMessageAccountConfig(params.cfg, accountId);
|
||||
const accountEnabled = merged.enabled !== false;
|
||||
const configured = Boolean(
|
||||
merged.cliPath?.trim() ||
|
||||
merged.dbPath?.trim() ||
|
||||
merged.service ||
|
||||
merged.sendTransport ||
|
||||
merged.region?.trim() ||
|
||||
(merged.allowFrom && merged.allowFrom.length > 0) ||
|
||||
(merged.groupAllowFrom && merged.groupAllowFrom.length > 0) ||
|
||||
merged.dmPolicy ||
|
||||
merged.groupPolicy ||
|
||||
typeof merged.includeAttachments === "boolean" ||
|
||||
(merged.attachmentRoots && merged.attachmentRoots.length > 0) ||
|
||||
(merged.remoteAttachmentRoots && merged.remoteAttachmentRoots.length > 0) ||
|
||||
typeof merged.mediaMaxMb === "number" ||
|
||||
typeof merged.textChunkLimit === "number" ||
|
||||
(merged.groups && Object.keys(merged.groups).length > 0),
|
||||
);
|
||||
return {
|
||||
accountId,
|
||||
enabled: baseEnabled && accountEnabled,
|
||||
name: normalizeOptionalString(merged.name),
|
||||
config: merged,
|
||||
configured,
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeIMessageCliPath(value: string | undefined | null): string {
|
||||
return value?.trim() || "imsg";
|
||||
}
|
||||
|
||||
function normalizeIMessageDbPath(value: string | undefined | null): string {
|
||||
return value?.trim() ?? "";
|
||||
}
|
||||
|
||||
// Stable signature for the local Messages backend an iMessage account targets.
|
||||
// Two enabled accounts that share a signature watch the same source, which
|
||||
// caused duplicate inbound handling in openclaw/openclaw#65141.
|
||||
export function resolveIMessageAccountSourceSignature(account: ResolvedIMessageAccount): string {
|
||||
return JSON.stringify([
|
||||
normalizeIMessageCliPath(account.config.cliPath),
|
||||
normalizeIMessageDbPath(account.config.dbPath),
|
||||
]);
|
||||
}
|
||||
|
||||
function resolveIMessageAccountSourceOwner(params: {
|
||||
cfg: OpenClawConfig;
|
||||
signature: string;
|
||||
}): string | undefined {
|
||||
// Prefer an explicit named account over the implicit "default" so that
|
||||
// bindings tied to the named account keep working (openclaw/openclaw#65141).
|
||||
let defaultOwner: string | undefined;
|
||||
for (const candidateAccountId of listIMessageAccountIds(params.cfg)) {
|
||||
const candidate = resolveIMessageAccount({
|
||||
cfg: params.cfg,
|
||||
accountId: candidateAccountId,
|
||||
});
|
||||
if (!candidate.enabled) {
|
||||
continue;
|
||||
}
|
||||
if (resolveIMessageAccountSourceSignature(candidate) !== params.signature) {
|
||||
continue;
|
||||
}
|
||||
if (candidate.accountId === DEFAULT_ACCOUNT_ID) {
|
||||
defaultOwner ??= candidate.accountId;
|
||||
continue;
|
||||
}
|
||||
return candidate.accountId;
|
||||
}
|
||||
return defaultOwner;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the owner account id when `account` is an enabled duplicate of
|
||||
* another enabled account that targets the same local Messages source. Used
|
||||
* by the iMessage gateway lifecycle to skip starting redundant `imsg rpc`
|
||||
* watchers (openclaw/openclaw#65141) without otherwise marking the duplicate
|
||||
* disabled — outbound selection, status surfaces, and capability listings
|
||||
* keep treating both accounts normally.
|
||||
*/
|
||||
export function resolveIMessageDuplicateSourceOwner(params: {
|
||||
cfg: OpenClawConfig;
|
||||
account: ResolvedIMessageAccount;
|
||||
}): string | undefined {
|
||||
if (!params.account.enabled) {
|
||||
return undefined;
|
||||
}
|
||||
const owner = resolveIMessageAccountSourceOwner({
|
||||
cfg: params.cfg,
|
||||
signature: resolveIMessageAccountSourceSignature(params.account),
|
||||
});
|
||||
return owner && owner !== params.account.accountId ? owner : undefined;
|
||||
}
|
||||
|
||||
export function listEnabledIMessageAccounts(cfg: OpenClawConfig): ResolvedIMessageAccount[] {
|
||||
return listIMessageAccountIds(cfg)
|
||||
.map((accountId) => resolveIMessageAccount({ cfg, accountId }))
|
||||
.filter((account) => account.enabled);
|
||||
}
|
||||
|
||||
export function collectIMessageDuplicateAccountSourceWarnings(params: {
|
||||
cfg: OpenClawConfig;
|
||||
}): string[] {
|
||||
const groups = new Map<string, ResolvedIMessageAccount[]>();
|
||||
for (const accountId of listIMessageAccountIds(params.cfg)) {
|
||||
const account = resolveIMessageAccount({ cfg: params.cfg, accountId });
|
||||
if (!account.enabled) {
|
||||
continue;
|
||||
}
|
||||
const signature = resolveIMessageAccountSourceSignature(account);
|
||||
const existing = groups.get(signature);
|
||||
if (existing) {
|
||||
existing.push(account);
|
||||
} else {
|
||||
groups.set(signature, [account]);
|
||||
}
|
||||
}
|
||||
const warnings: string[] = [];
|
||||
for (const collisions of groups.values()) {
|
||||
if (collisions.length < 2) {
|
||||
continue;
|
||||
}
|
||||
const ownerId = resolveIMessageAccountSourceOwner({
|
||||
cfg: params.cfg,
|
||||
signature: resolveIMessageAccountSourceSignature(collisions[0]),
|
||||
});
|
||||
const owner = collisions.find((a) => a.accountId === ownerId) ?? collisions[0];
|
||||
const duplicates = collisions.filter((a) => a.accountId !== owner.accountId);
|
||||
const dupIds = duplicates.map((a) => `"${a.accountId}"`).join(", ");
|
||||
const cliPath = normalizeIMessageCliPath(owner.config.cliPath);
|
||||
const dbPath = normalizeIMessageDbPath(owner.config.dbPath);
|
||||
const where = dbPath ? `cliPath=${cliPath}, dbPath=${dbPath}` : `cliPath=${cliPath}`;
|
||||
warnings.push(
|
||||
`- channels.imessage: accounts "${owner.accountId}" and ${dupIds} watch the same local Messages source (${where}). OpenClaw runs one watcher (owner: "${owner.accountId}") and idles the duplicate; the other accounts stay enabled for outbound sends and status. Inbound messages arrive tagged with accountId="${owner.accountId}", so bindings pinned to ${dupIds} should be re-pointed at "${owner.accountId}" (or set "enabled": false on "${owner.accountId}" to flip ownership). Set "enabled": false on the unused duplicates to silence this warning.`,
|
||||
);
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
Reference in New Issue
Block a user