feat(signal): add target aliases#95738
Conversation
|
Codex review: needs maintainer review before merge. Reviewed July 3, 2026, 9:35 AM ET / 13:35 UTC. Summary PR surface: Source +403, Tests +863, Docs +56, Generated 0. Total +1322 across 23 files. Reproducibility: not applicable. This PR adds a new Signal feature rather than fixing a reported current-main bug. The PR body includes live Signal group and DM alias proof plus focused automated checks. Review metrics: 1 noteworthy metric.
Stored data model Merge readiness Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch. Rank-up moves:
Risk before merge
Maintainer options:
Next step before merge
Security Review detailsBest possible solution: Maintainers should explicitly accept or narrow the Signal alias config contract, then land with the existing live Signal proof and focused regression coverage. Do we have a high-confidence way to reproduce the issue? Not applicable; this PR adds a new Signal feature rather than fixing a reported current-main bug. The PR body includes live Signal group and DM alias proof plus focused automated checks. Is this the best way to solve the issue? Yes, if maintainers accept the product/config contract: keeping Signal target aliases inside the Signal plugin is narrower than a core address book and still canonicalizes delivery, approval, directory, and session paths. The remaining question is whether this supported config surface should ship. AGENTS.md: found and applied where relevant. Codex review notes: model internal, reasoning high; reviewed against 153fed790a5c. Label changesLabel justifications:
Evidence reviewedPR surface: Source +403, Tests +863, Docs +56, Generated 0. Total +1322 across 23 files. View PR surface stats
What I checked:
Likely related people:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. How this review workflow works
|
068e582 to
3ac6eb0
Compare
|
@clawsweeper re-review. |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
/clawsweeper re-review |
There was a problem hiding this comment.
Pull request overview
Adds Signal-specific, config-backed target aliases (e.g. signal:me, signal:ops) inside the Signal plugin boundary, and updates outbound directory caching so query-scoped misses don’t contaminate later lookups.
Changes:
- Add Signal alias resolution + alias-backed directory listing in
extensions/signal, including alias support in approval routing/session routes/outbound sends. - Expand Signal config surface to accept
aliasesat both top-level and per-account scopes (schema + types), and document the new feature. - Partition outbound directory-cache entries by normalized query to prevent cross-query cache poisoning.
Best-fix verdict: Best (Signal-specific target normalization belongs in the Signal plugin; outbound directory cache keying by query addresses the stated cross-channel/miss-poisoning concern at the right shared boundary).
Alternatives considered (rejected):
- Core/global address book abstraction: broader product surface and cross-channel semantics; out of scope for Signal-only normalization.
- Mutating/reading Signal contacts live: violates the stated “config-backed only, no contact discovery/mutation” constraint.
- Resolving aliases only at final send: wouldn’t canonicalize targets for approval/session routing/history/directory surfaces as required.
Code read (key paths):
src/infra/outbound/target-resolver.ts,src/infra/outbound/directory-cache.tsextensions/signal/src/channel.ts,extensions/signal/src/aliases.ts,extensions/signal/src/normalize.tsextensions/signal/src/accounts.ts,src/channels/plugins/account-helpers.ts(merge semantics)
Reviewed changes
Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/infra/outbound/target-resolver.ts | Include normalized query in directory-cache keys for scoped caching. |
| src/infra/outbound/target-resolver.test.ts | Add regression test ensuring query-scoped misses don’t block later hits. |
| src/infra/outbound/directory-cache.ts | Extend cache-key dimensions to include query. |
| src/infra/outbound/directory-cache.test.ts | Update expectations for new cache-key serialization. |
| src/config/zod-schema.providers-core.ts | Allow aliases on Signal account config schema. |
| src/config/types.signal.ts | Add aliases to SignalAccountConfig type. |
| extensions/signal/src/shared.ts | Resolve defaultTo through alias resolution (best-effort). |
| extensions/signal/src/core.test.ts | Cover alias behavior across target resolution, outbound sends, session routing, and directory listing. |
| extensions/signal/src/config-schema.test.ts | Validate config accepts top-level + per-account aliases. |
| extensions/signal/src/channel.ts | Resolve aliases in messaging target resolver, directory listing, outbound resolveTarget, and outbound sends/session routes. |
| extensions/signal/src/approval-reactions.ts | Canonicalize alias targets for approval reaction routing + persistence keys. |
| extensions/signal/src/approval-reactions.test.ts | Ensure delivered prompts are registered under canonical targets when aliases are configured. |
| extensions/signal/src/approval-handler.runtime.ts | Resolve aliases (and default-account aliases) when preparing approval prompt delivery targets. |
| extensions/signal/src/approval-handler.runtime.test.ts | Add tests asserting alias resolution before native approval prompt delivery. |
| extensions/signal/src/approval-auth.ts | Resolve defaultTo aliases when computing approval approvers. |
| extensions/signal/src/approval-auth.test.ts | Add tests for alias-backed defaultTo authorization + recursive alias ignore behavior. |
| extensions/signal/src/aliases.ts | Implement config-backed alias resolution + alias-backed directory entries. |
| extensions/signal/src/aliases.test.ts | Add focused unit tests for alias resolution and alias directory behavior. |
| extensions/signal/src/accounts.ts | Merge per-account alias maps with channel-level aliases (nestedObjectKeys). |
| docs/cli/directory.md | Note that Signal targets can resolve from configured aliases. |
| docs/channels/signal.md | Document channels.signal.aliases and per-account alias configuration and directory listing behavior. |
| export function resolveSignalAliasTarget(params: { | ||
| cfg: OpenClawConfig; | ||
| accountId?: string | null; | ||
| input: string; | ||
| }): ResolvedSignalAliasTarget | null { | ||
| const aliases = resolveAliasMap(params); | ||
| const initialAlias = normalizeAliasKey(params.input); | ||
| if (!initialAlias || !aliases.has(initialAlias)) { | ||
| return null; | ||
| } |
| export function listSignalAliasDirectoryEntries(params: { | ||
| cfg: OpenClawConfig; | ||
| accountId?: string | null; | ||
| kind: SignalResolvedTargetKind; | ||
| query?: string | null; | ||
| limit?: number | null; | ||
| }): ChannelDirectoryEntry[] { | ||
| const aliases = resolveAliasMap(params); | ||
| const query = normalizeLowercaseStringOrEmpty(params.query); | ||
| const limit = typeof params.limit === "number" && params.limit > 0 ? params.limit : undefined; | ||
| const exactAlias = params.query ? normalizeAliasKey(params.query) : undefined; | ||
| if (exactAlias && aliases.has(exactAlias)) { | ||
| const target = resolveSignalAliasTarget({ ...params, input: exactAlias }); | ||
| if (target?.kind === params.kind) { | ||
| return [{ kind: params.kind, id: target.to, name: target.alias }]; | ||
| } | ||
| if (target) { | ||
| return []; | ||
| } | ||
| } | ||
| const entries: ChannelDirectoryEntry[] = []; | ||
| for (const alias of aliases.keys()) { | ||
| let target: ResolvedSignalAliasTarget | null; | ||
| try { | ||
| target = resolveSignalAliasTarget({ ...params, input: alias }); | ||
| } catch { | ||
| continue; | ||
| } |
|
/clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
/clawsweeper re-review |
1 similar comment
|
/clawsweeper re-review |
0a48bb5 to
183223f
Compare
|
/clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
/clawsweeper re-review |
1 similar comment
|
/clawsweeper re-review |
6de2b11 to
9accb68
Compare
|
/clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
/clawsweeper re-review |
3 similar comments
|
/clawsweeper re-review |
|
/clawsweeper re-review |
|
/clawsweeper re-review |
|
/clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
/clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
/clawsweeper re-review |
1 similar comment
|
/clawsweeper re-review |
|
/clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. |
|
/clawsweeper re-review |
1 similar comment
|
/clawsweeper re-review |
|
Merged via squash.
|
What Problem This Solves
Signal delivery targets are hard to reuse because DMs can be phone numbers or UUIDs, while groups use opaque group IDs. This adds OpenClaw-side Signal aliases so users can configure friendly names like
signal:meorsignal:opsonce and use them in normal Signal delivery paths.Why This Change Was Made
The alias mapping belongs in the Signal plugin boundary because it is Signal-specific target normalization, not a cross-channel address book or Signal contact mutation. The implementation resolves config-backed aliases before outbound delivery, default target lookup, approval authorization/routing, directory listing, and outbound session route construction so Signal history and approval reactions use canonical Signal targets.
This intentionally does not mutate Signal contacts, discover live Signal contacts, or add wildcard/broad Signal access behavior. The only shared outbound change keeps directory-cache entries query-scoped so an exact alias lookup or miss cannot poison later peer/group resolution for Signal or sibling channel adapters.
User Impact
Users can configure
channels.signal.aliasesorchannels.signal.accounts.<id>.aliaseswith DM and group targets, then send to aliases such assignal:ops. Per-account aliases inherit top-level aliases and may override them. Invalid, blank, or recursive aliases fail before delivery with a clear Signal alias error.The Signal directory surface can list configured aliases as peers or groups, and exact alias lookups keep peer and group targets separated. Aliases are config-backed only; they do not imply live Signal contact discovery or contact updates.
Evidence
Manual Signal proof from Jesse:
openclaw-signal-aliases-directory/jesse/signal-aliases-directory.channels.signal.aliases.opstogroup:l+j9mBRLUCHN+2xue4rlVLOiEbVsKi7yF3WacQazOB0=andchannels.signal.aliases.meto Jesse's configured E.164 alias, withchannels.signal.defaultTo = "signal:me".openclaw message send --channel signal --target signal:ops --message "alias ops smoke ..." --jsonresolved togroup:l+j9mBRLUCHN+2xue4rlVLOiEbVsKi7yF3WacQazOB0=, and Jesse confirmed it appeared in the Testing realm.signal:me; Jesse confirmed the response was visible in the intended Signal DM.Automated checks on the final reviewed branch at
3ac6eb093a:node scripts/run-vitest.mjs extensions/signal/src/aliases.test.ts extensions/signal/src/core.test.ts extensions/signal/src/approval-auth.test.ts extensions/signal/src/approval-handler.runtime.test.ts extensions/signal/src/approval-reactions.test.ts extensions/signal/src/config-schema.test.ts src/infra/outbound/target-resolver.test.ts src/infra/outbound/directory-cache.test.tspassed: 2 Vitest shards, 8 files, 117 tests.pnpm tsgo:prodpassed.pnpm config:channels:checkpassed.pnpm docs:listpassed.git diff --check origin/main...HEADpassed../node_modules/.bin/oxfmt --check --threads=1 <changed TypeScript files>passed: 20 files.origin/main...HEADdiff after the formatted media alias coverage fix.Known proof gap:
to, so this PR relies onmessage send --jsonresolved-target evidence and manual confirmation; gateway logs still do not literally showsignal:meresolving to Jesse's configured E.164 alias.