refactor(signal): consume verified link accounts

This commit is contained in:
jesse-merhi
2026-08-11 00:16:56 +10:00
parent 07d2805a00
commit 08d1beef02
2 changed files with 36 additions and 60 deletions
+15 -32
View File
@@ -329,39 +329,22 @@ describe("signalSetupWizard QR linking", () => {
expect(linkSignalCliAccountMock).not.toHaveBeenCalled();
});
it.each([
{
name: "a failed link",
linkResult: { ok: false as const, error: "Link request timed out" },
note: "Link request timed out",
showCompletionNote: true,
},
{
name: "a linked account that signal-cli did not identify",
linkResult: { ok: true as const },
note: "signal-cli linked successfully, but OpenClaw could not identify the linked account. Enter its Signal number to finish setup.",
showCompletionNote: false,
},
])(
"accepts a manual number after $name",
async ({ linkResult, note: expectedNote, showCompletionNote }) => {
linkSignalCliAccountMock.mockResolvedValueOnce(linkResult);
const note = vi.fn<WizardPrompter["note"]>(async () => undefined);
const text = vi.fn(async () => "+15555550199");
it("accepts a manual number after a failed link", async () => {
linkSignalCliAccountMock.mockResolvedValueOnce({
ok: false,
error: "Link request timed out",
});
const note = vi.fn<WizardPrompter["note"]>(async () => undefined);
const text = vi.fn(async () => "+15555550199");
const result = await configure({ prompter: { ...createQrPrompter(), note, text } });
const result = await configure({ prompter: { ...createQrPrompter(), note, text } });
expect(result.cfg.channels?.signal?.account).toBe("+15555550199");
expect(text).toHaveBeenCalledOnce();
expect(note).toHaveBeenCalledWith(expectedNote, "Signal account linking");
const completionCall = note.mock.calls.find(([, title]) => title === "Signal next steps");
if (showCompletionNote) {
expect(completionCall?.[0]).toContain('signal-cli link -n "OpenClaw"');
} else {
expect(completionCall).toBeUndefined();
}
},
);
expect(result.cfg.channels?.signal?.account).toBe("+15555550199");
expect(text).toHaveBeenCalledOnce();
expect(note).toHaveBeenCalledWith("Link request timed out", "Signal account linking");
const completionCall = note.mock.calls.find(([, title]) => title === "Signal next steps");
expect(completionCall?.[0]).toContain('signal-cli link -n "OpenClaw"');
});
it("preserves a successful linked account when cancellation races completion", async () => {
const abortController = new AbortController();
@@ -488,7 +471,7 @@ describe("signalSetupWizard QR linking", () => {
it("does not assign a QR-linked identity already owned by a sibling account", async () => {
linkSignalCliAccountMock.mockResolvedValueOnce({
ok: true,
associatedAccount: "signal:+1 (555) 555-0123",
associatedAccount: "+15555550123",
});
const transport = {
kind: "managed-native" as const,
+21 -28
View File
@@ -213,36 +213,29 @@ export const signalSetupWizard: ChannelSetupWizard = {
// A successful signal-cli result means the account is already linked on disk.
// Preserve that authoritative result even if cancellation raced its final output.
preparedCredentialValues[SIGNAL_LINK_COMPLETED_INPUT_KEY] = "true";
const linkedAccount = normalizeSignalAccountInput(linkResult.associatedAccount);
if (linkedAccount) {
if (siblingAccounts.has(linkedAccount)) {
// The device link succeeded, but this setup target must not claim a sibling's identity.
// Abort before later setup inputs can create a transport-only target.
const error = new Error(
`${linkedAccount} is already assigned to another OpenClaw Signal account. Choose a different account or remove the existing assignment, then retry setup.`,
);
await prompter.note(error.message, "Signal account linking");
throw error;
}
if (configuredAccount && linkedAccount !== configuredAccount) {
const replaceConfiguredAccount = await prompter.confirm({
message: `Use ${linkedAccount} instead of configured Signal account ${configuredAccount}?`,
initialValue: false,
});
if (!replaceConfiguredAccount) {
throw new WizardCancelledError(
`Signal setup cancelled: ${linkedAccount} was linked in signal-cli, but replacing configured Signal account ${configuredAccount} was declined.`,
);
}
}
preparedCredentialValues.signalNumber = linkedAccount;
preparedCredentialValues[SIGNAL_LINKED_ACCOUNT_INPUT_KEY] = "true";
} else {
await prompter.note(
"signal-cli linked successfully, but OpenClaw could not identify the linked account. Enter its Signal number to finish setup.",
"Signal account linking",
const linkedAccount = linkResult.associatedAccount;
if (siblingAccounts.has(linkedAccount)) {
// The device link succeeded, but this setup target must not claim a sibling's identity.
// Abort before later setup inputs can create a transport-only target.
const error = new Error(
`${linkedAccount} is already assigned to another OpenClaw Signal account. Choose a different account or remove the existing assignment, then retry setup.`,
);
await prompter.note(error.message, "Signal account linking");
throw error;
}
if (configuredAccount && linkedAccount !== configuredAccount) {
const replaceConfiguredAccount = await prompter.confirm({
message: `Use ${linkedAccount} instead of configured Signal account ${configuredAccount}?`,
initialValue: false,
});
if (!replaceConfiguredAccount) {
throw new WizardCancelledError(
`Signal setup cancelled: ${linkedAccount} was linked in signal-cli, but replacing configured Signal account ${configuredAccount} was declined.`,
);
}
}
preparedCredentialValues.signalNumber = linkedAccount;
preparedCredentialValues[SIGNAL_LINKED_ACCOUNT_INPUT_KEY] = "true";
return { credentialValues: preparedCredentialValues };
},
credentials: [],