@chrysb/alphaclaw 0.9.6 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/lib/public/assets/icons/whatsapp.svg +14 -0
- package/lib/public/css/tailwind.generated.css +1 -1
- package/lib/public/dist/app.bundle.js +2031 -1925
- package/lib/public/js/components/agents-tab/create-channel-modal.js +30 -13
- package/lib/public/js/components/channel-login-modal.js +82 -0
- package/lib/public/js/components/channels.js +347 -1
- package/lib/public/js/components/general/index.js +56 -8
- package/lib/public/js/components/modal-shell.js +18 -2
- package/lib/public/js/components/onboarding/welcome-pairing-step.js +11 -6
- package/lib/public/js/components/pairings.js +1 -1
- package/lib/public/js/components/welcome/index.js +0 -1
- package/lib/public/js/components/welcome/use-welcome.js +1 -1
- package/lib/public/js/lib/api.js +23 -0
- package/lib/public/js/lib/channel-provider-availability.js +1 -1
- package/lib/server/agents/channels.js +268 -4
- package/lib/server/agents/service.js +2 -0
- package/lib/server/agents/shared.js +133 -42
- package/lib/server/alphaclaw-version.js +7 -3
- package/lib/server/commands.js +5 -1
- package/lib/server/constants.js +7 -0
- package/lib/server/gateway.js +61 -18
- package/lib/server/onboarding/import/secret-detector.js +9 -0
- package/lib/server/onboarding/openclaw.js +39 -0
- package/lib/server/onboarding/validation.js +1 -1
- package/lib/server/routes/agents.js +39 -0
- package/lib/server/routes/pairings.js +2 -2
- package/lib/server/watchdog-notify.js +54 -13
- package/lib/server.js +1 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { h } from "preact";
|
|
2
|
-
import { useEffect } from "preact/hooks";
|
|
2
|
+
import { useEffect, useRef } from "preact/hooks";
|
|
3
3
|
import { createPortal } from "preact/compat";
|
|
4
4
|
import htm from "htm";
|
|
5
5
|
|
|
@@ -13,6 +13,8 @@ export const ModalShell = ({
|
|
|
13
13
|
panelClassName = "bg-modal border border-border rounded-xl p-5 max-w-md w-full space-y-3",
|
|
14
14
|
children = null,
|
|
15
15
|
}) => {
|
|
16
|
+
const overlayPointerDownRef = useRef(false);
|
|
17
|
+
|
|
16
18
|
useEffect(() => {
|
|
17
19
|
if (!visible || !closeOnEscape) return;
|
|
18
20
|
|
|
@@ -30,8 +32,22 @@ export const ModalShell = ({
|
|
|
30
32
|
html`
|
|
31
33
|
<div
|
|
32
34
|
class="fixed inset-0 bg-overlay flex items-start justify-center overflow-y-auto p-4 sm:items-center z-50"
|
|
35
|
+
onpointerdown=${(event) => {
|
|
36
|
+
overlayPointerDownRef.current = event.target === event.currentTarget;
|
|
37
|
+
}}
|
|
38
|
+
onpointerup=${(event) => {
|
|
39
|
+
const shouldClose =
|
|
40
|
+
closeOnOverlayClick &&
|
|
41
|
+
overlayPointerDownRef.current &&
|
|
42
|
+
event.target === event.currentTarget;
|
|
43
|
+
overlayPointerDownRef.current = false;
|
|
44
|
+
if (shouldClose) onClose?.();
|
|
45
|
+
}}
|
|
46
|
+
onpointercancel=${() => {
|
|
47
|
+
overlayPointerDownRef.current = false;
|
|
48
|
+
}}
|
|
33
49
|
onclick=${(event) => {
|
|
34
|
-
|
|
50
|
+
event.preventDefault();
|
|
35
51
|
}}
|
|
36
52
|
>
|
|
37
53
|
<div class=${panelClassName}>${children}</div>
|
|
@@ -14,6 +14,14 @@ const kChannelMeta = {
|
|
|
14
14
|
label: "Discord",
|
|
15
15
|
iconSrc: "/assets/icons/discord.svg",
|
|
16
16
|
},
|
|
17
|
+
slack: {
|
|
18
|
+
label: "Slack",
|
|
19
|
+
iconSrc: "/assets/icons/slack.svg",
|
|
20
|
+
},
|
|
21
|
+
whatsapp: {
|
|
22
|
+
label: "WhatsApp",
|
|
23
|
+
iconSrc: "/assets/icons/whatsapp.svg",
|
|
24
|
+
},
|
|
17
25
|
};
|
|
18
26
|
|
|
19
27
|
const PairingRow = ({ pairing, onApprove, onReject }) => {
|
|
@@ -79,7 +87,6 @@ const PairingRow = ({ pairing, onApprove, onReject }) => {
|
|
|
79
87
|
export const WelcomePairingStep = ({
|
|
80
88
|
channel,
|
|
81
89
|
pairings,
|
|
82
|
-
channels,
|
|
83
90
|
loading,
|
|
84
91
|
error,
|
|
85
92
|
onApprove,
|
|
@@ -94,15 +101,13 @@ export const WelcomePairingStep = ({
|
|
|
94
101
|
: "Channel",
|
|
95
102
|
iconSrc: "",
|
|
96
103
|
};
|
|
97
|
-
const channelInfo = channels?.[channel];
|
|
98
104
|
|
|
99
105
|
if (!channel) {
|
|
100
106
|
return html`
|
|
101
107
|
<div
|
|
102
108
|
class="bg-status-error-bg border border-status-error-border rounded-xl p-3 text-status-error text-sm"
|
|
103
109
|
>
|
|
104
|
-
Missing channel configuration. Go back and add a
|
|
105
|
-
token.
|
|
110
|
+
Missing channel configuration. Go back and add a channel credential.
|
|
106
111
|
</div>
|
|
107
112
|
`;
|
|
108
113
|
}
|
|
@@ -116,8 +121,8 @@ export const WelcomePairingStep = ({
|
|
|
116
121
|
🎉 Setup complete
|
|
117
122
|
</p>
|
|
118
123
|
<p class="text-xs text-body">
|
|
119
|
-
Your ${channelMeta.label} channel is connected. You can switch
|
|
120
|
-
|
|
124
|
+
Your ${channelMeta.label} channel is connected. You can switch to${" "}
|
|
125
|
+
${channelMeta.label} and start using your agent now.
|
|
121
126
|
</p>
|
|
122
127
|
<p class="text-xs text-fg-muted font-normal opacity-85">
|
|
123
128
|
Continue to the dashboard to explore extras like Google Workspace
|
|
@@ -62,7 +62,7 @@ export const PairingRow = ({ p, onApprove, onReject }) => {
|
|
|
62
62
|
</div>`;
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
-
const ALL_CHANNELS = ['telegram', 'discord', 'slack'];
|
|
65
|
+
const ALL_CHANNELS = ['telegram', 'discord', 'slack', 'whatsapp'];
|
|
66
66
|
|
|
67
67
|
const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1);
|
|
68
68
|
|
|
@@ -66,7 +66,6 @@ export const Welcome = ({ onComplete, acVersion }) => {
|
|
|
66
66
|
? html`<${WelcomePairingStep}
|
|
67
67
|
channel=${state.selectedPairingChannel}
|
|
68
68
|
pairings=${state.pairingRequestsPoll.data || []}
|
|
69
|
-
channels=${state.pairingChannels}
|
|
70
69
|
loading=${!state.pairingStatusPoll.data}
|
|
71
70
|
error=${state.pairingError}
|
|
72
71
|
onApprove=${actions.handlePairingApprove}
|
|
@@ -334,7 +334,7 @@ export const useWelcome = ({ onComplete }) => {
|
|
|
334
334
|
const pairingChannel = getPreferredPairingChannel(normalizedVals);
|
|
335
335
|
if (!pairingChannel) {
|
|
336
336
|
throw new Error(
|
|
337
|
-
"No
|
|
337
|
+
"No channel credential configured for pairing.",
|
|
338
338
|
);
|
|
339
339
|
}
|
|
340
340
|
setVals((prev) => ({
|
package/lib/public/js/lib/api.js
CHANGED
|
@@ -982,6 +982,29 @@ export const deleteChannelAccount = async (payload) => {
|
|
|
982
982
|
return parseJsonOrThrow(res, "Could not delete channel account");
|
|
983
983
|
};
|
|
984
984
|
|
|
985
|
+
export const runChannelAccountLogin = async (payload) => {
|
|
986
|
+
const res = await authFetch("/api/channels/accounts/login", {
|
|
987
|
+
method: "POST",
|
|
988
|
+
headers: { "Content-Type": "application/json" },
|
|
989
|
+
body: JSON.stringify(payload || {}),
|
|
990
|
+
});
|
|
991
|
+
return parseJsonOrThrow(res, "Could not run channel login");
|
|
992
|
+
};
|
|
993
|
+
|
|
994
|
+
export const fetchChannelAccountLoginStatus = async ({
|
|
995
|
+
provider = "",
|
|
996
|
+
accountId = "default",
|
|
997
|
+
} = {}) => {
|
|
998
|
+
const params = new URLSearchParams({
|
|
999
|
+
provider: String(provider || ""),
|
|
1000
|
+
accountId: String(accountId || "default"),
|
|
1001
|
+
});
|
|
1002
|
+
const res = await authFetch(
|
|
1003
|
+
`/api/channels/accounts/login-status?${params.toString()}`,
|
|
1004
|
+
);
|
|
1005
|
+
return parseJsonOrThrow(res, "Could not load channel login status");
|
|
1006
|
+
};
|
|
1007
|
+
|
|
985
1008
|
export const fetchAgent = async (agentId) => {
|
|
986
1009
|
const res = await authFetch(`/api/agents/${encodeURIComponent(String(agentId || ""))}`);
|
|
987
1010
|
return parseJsonOrThrow(res, "Could not load agent");
|
|
@@ -18,6 +18,7 @@ const {
|
|
|
18
18
|
deriveChannelExtraEnvKeys,
|
|
19
19
|
getConfiguredChannelEnvKeys,
|
|
20
20
|
assertActiveChannelTokenEnvVars,
|
|
21
|
+
hasSavedWhatsAppCredentials,
|
|
21
22
|
normalizeChannelConfig,
|
|
22
23
|
appendBindingToConfig,
|
|
23
24
|
buildBindingSpec,
|
|
@@ -101,8 +102,6 @@ const createChannelsDomain = ({
|
|
|
101
102
|
const provider = normalizeChannelProvider(input.provider);
|
|
102
103
|
const name =
|
|
103
104
|
String(input.name || "").trim() || kChannelLabels[provider] || provider;
|
|
104
|
-
const token = String(input.token || "").trim();
|
|
105
|
-
if (!token) throw new Error("Channel token is required");
|
|
106
105
|
|
|
107
106
|
const cfg = withNormalizedAgentsConfig({
|
|
108
107
|
OPENCLAW_DIR,
|
|
@@ -143,12 +142,31 @@ const createChannelsDomain = ({
|
|
|
143
142
|
`Channel account "${provider}/${accountId}" already exists`,
|
|
144
143
|
);
|
|
145
144
|
}
|
|
146
|
-
if (
|
|
145
|
+
if (
|
|
146
|
+
(provider === "discord" || provider === "whatsapp") &&
|
|
147
|
+
Object.keys(existingAccounts).length > 0
|
|
148
|
+
) {
|
|
147
149
|
throw new Error(
|
|
148
150
|
`${kChannelLabels[provider] || "This provider"} supports a single channel account`,
|
|
149
151
|
);
|
|
150
152
|
}
|
|
151
153
|
|
|
154
|
+
if (provider === "whatsapp") {
|
|
155
|
+
return await createWhatsAppChannelAccount({
|
|
156
|
+
input,
|
|
157
|
+
cfg,
|
|
158
|
+
agentId,
|
|
159
|
+
accountId,
|
|
160
|
+
name,
|
|
161
|
+
normalizedChannelConfig,
|
|
162
|
+
existingAccounts,
|
|
163
|
+
onProgress,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const token = String(input.token || "").trim();
|
|
168
|
+
if (!token) throw new Error("Channel token is required");
|
|
169
|
+
|
|
152
170
|
const envKey = deriveChannelEnvKey({ provider, accountId });
|
|
153
171
|
const extraEnvKeys = deriveChannelExtraEnvKeys({ provider, accountId });
|
|
154
172
|
const appToken = String(input.appToken || "").trim();
|
|
@@ -157,7 +175,9 @@ const createChannelsDomain = ({
|
|
|
157
175
|
}
|
|
158
176
|
const tokenField = kChannelTokenFields[provider];
|
|
159
177
|
const currentEnvVars = readEnvFile();
|
|
160
|
-
const previousEnvVars = Array.isArray(currentEnvVars)
|
|
178
|
+
const previousEnvVars = Array.isArray(currentEnvVars)
|
|
179
|
+
? currentEnvVars
|
|
180
|
+
: [];
|
|
161
181
|
const duplicateEnvEntry = previousEnvVars.find((entry) => {
|
|
162
182
|
const existingKey = String(entry?.key || "").trim();
|
|
163
183
|
const existingValue = String(entry?.value || "").trim();
|
|
@@ -555,6 +575,53 @@ const createChannelsDomain = ({
|
|
|
555
575
|
}
|
|
556
576
|
};
|
|
557
577
|
|
|
578
|
+
const cleanupWhatsAppAuthFiles = ({ accountId }) => {
|
|
579
|
+
const credDir = resolveCredentialsDirPath({ OPENCLAW_DIR });
|
|
580
|
+
const providerCredDir = path.join(credDir, "whatsapp");
|
|
581
|
+
const normalizedAccountId =
|
|
582
|
+
String(accountId || "")
|
|
583
|
+
.trim()
|
|
584
|
+
.toLowerCase() || "default";
|
|
585
|
+
|
|
586
|
+
try {
|
|
587
|
+
fsImpl.rmSync(path.join(credDir, "whatsapp", normalizedAccountId), {
|
|
588
|
+
recursive: true,
|
|
589
|
+
force: true,
|
|
590
|
+
});
|
|
591
|
+
} catch {}
|
|
592
|
+
|
|
593
|
+
try {
|
|
594
|
+
fsImpl.rmSync(providerCredDir, {
|
|
595
|
+
recursive: true,
|
|
596
|
+
force: true,
|
|
597
|
+
});
|
|
598
|
+
} catch {}
|
|
599
|
+
|
|
600
|
+
if (normalizedAccountId !== "default") {
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
const legacyAuthPatterns = [
|
|
605
|
+
"creds.json",
|
|
606
|
+
"creds.json.bak",
|
|
607
|
+
];
|
|
608
|
+
try {
|
|
609
|
+
const entries = fsImpl.readdirSync(credDir);
|
|
610
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
611
|
+
const fileName = String(entry || "").trim();
|
|
612
|
+
if (!fileName) continue;
|
|
613
|
+
if (
|
|
614
|
+
legacyAuthPatterns.includes(fileName) ||
|
|
615
|
+
/^(app-state-sync|session|sender-key|pre-key)-.*\.json$/.test(fileName)
|
|
616
|
+
) {
|
|
617
|
+
try {
|
|
618
|
+
fsImpl.rmSync(path.join(credDir, fileName), { force: true });
|
|
619
|
+
} catch {}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
} catch {}
|
|
623
|
+
};
|
|
624
|
+
|
|
558
625
|
const deleteChannelAccount = async (input = {}) => {
|
|
559
626
|
const provider = normalizeChannelProvider(input.provider);
|
|
560
627
|
const accountId = String(input.accountId || "").trim() || "default";
|
|
@@ -731,9 +798,21 @@ const createChannelsDomain = ({
|
|
|
731
798
|
if (hasScopedFields) return true;
|
|
732
799
|
return !matchesBinding(match, targetMatch);
|
|
733
800
|
});
|
|
801
|
+
if (!nextChannels[provider] && nextCfg.plugins?.entries?.[provider]) {
|
|
802
|
+
nextCfg.plugins.entries[provider] = {
|
|
803
|
+
...(nextCfg.plugins.entries[provider] || {}),
|
|
804
|
+
enabled: false,
|
|
805
|
+
};
|
|
806
|
+
}
|
|
734
807
|
saveConfig({ fsImpl, OPENCLAW_DIR, config: nextCfg });
|
|
735
808
|
|
|
736
809
|
cleanupChannelAccountPairingFiles({ provider, accountId });
|
|
810
|
+
if (provider === "whatsapp") {
|
|
811
|
+
cleanupWhatsAppAuthFiles({ accountId });
|
|
812
|
+
}
|
|
813
|
+
if (provider === "whatsapp") {
|
|
814
|
+
await restartGateway();
|
|
815
|
+
}
|
|
737
816
|
return { ok: true };
|
|
738
817
|
};
|
|
739
818
|
|
|
@@ -763,11 +842,196 @@ const createChannelsDomain = ({
|
|
|
763
842
|
}));
|
|
764
843
|
};
|
|
765
844
|
|
|
845
|
+
const createWhatsAppChannelAccount = async ({
|
|
846
|
+
input,
|
|
847
|
+
cfg,
|
|
848
|
+
agentId,
|
|
849
|
+
accountId,
|
|
850
|
+
name,
|
|
851
|
+
normalizedChannelConfig,
|
|
852
|
+
existingAccounts,
|
|
853
|
+
onProgress,
|
|
854
|
+
}) => {
|
|
855
|
+
const ownerNumber = String(input.token || "").trim();
|
|
856
|
+
if (!ownerNumber) throw new Error("WhatsApp owner number is required");
|
|
857
|
+
|
|
858
|
+
const envKey = deriveChannelEnvKey({ provider: "whatsapp", accountId });
|
|
859
|
+
const currentEnvVars = readEnvFile();
|
|
860
|
+
const previousEnvVars = Array.isArray(currentEnvVars) ? currentEnvVars : [];
|
|
861
|
+
const previousConfig = cloneJson(cfg);
|
|
862
|
+
|
|
863
|
+
const nextEnvVars = previousEnvVars.filter(
|
|
864
|
+
(entry) => String(entry?.key || "").trim() !== envKey,
|
|
865
|
+
);
|
|
866
|
+
nextEnvVars.push({ key: envKey, value: ownerNumber });
|
|
867
|
+
|
|
868
|
+
try {
|
|
869
|
+
onProgress({ phase: "configuring", label: "Configuring..." });
|
|
870
|
+
writeEnvFile(nextEnvVars);
|
|
871
|
+
reloadEnv();
|
|
872
|
+
|
|
873
|
+
const nextCfg = withNormalizedAgentsConfig({
|
|
874
|
+
OPENCLAW_DIR,
|
|
875
|
+
cfg: loadConfig({ fsImpl, OPENCLAW_DIR }),
|
|
876
|
+
});
|
|
877
|
+
ensurePluginAllowed({ cfg: nextCfg, pluginKey: "whatsapp" });
|
|
878
|
+
saveConfig({ fsImpl, OPENCLAW_DIR, config: nextCfg });
|
|
879
|
+
|
|
880
|
+
onProgress({ phase: "configuring", label: "Adding channel..." });
|
|
881
|
+
const addArgs = [
|
|
882
|
+
"channels add",
|
|
883
|
+
"--channel whatsapp",
|
|
884
|
+
accountId !== "default" ? `--account ${shellEscapeArg(accountId)}` : "",
|
|
885
|
+
name ? `--name ${shellEscapeArg(name)}` : "",
|
|
886
|
+
`--token ${shellEscapeArg(ownerNumber)}`,
|
|
887
|
+
].filter(Boolean);
|
|
888
|
+
const addResult = await clawCmd(addArgs.join(" "), {
|
|
889
|
+
quiet: true,
|
|
890
|
+
timeoutMs: 30000,
|
|
891
|
+
});
|
|
892
|
+
if (!addResult?.ok) {
|
|
893
|
+
throw new Error(
|
|
894
|
+
addResult?.stderr ||
|
|
895
|
+
addResult?.stdout ||
|
|
896
|
+
"Could not add WhatsApp channel account",
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
const refreshedCfg = withNormalizedAgentsConfig({
|
|
901
|
+
OPENCLAW_DIR,
|
|
902
|
+
cfg: loadConfig({ fsImpl, OPENCLAW_DIR }),
|
|
903
|
+
});
|
|
904
|
+
|
|
905
|
+
const nextAccounts = { ...existingAccounts };
|
|
906
|
+
nextAccounts[accountId] = {
|
|
907
|
+
...(nextAccounts[accountId] &&
|
|
908
|
+
typeof nextAccounts[accountId] === "object"
|
|
909
|
+
? nextAccounts[accountId]
|
|
910
|
+
: {}),
|
|
911
|
+
...(name ? { name } : {}),
|
|
912
|
+
allowFrom: [`\${${envKey}}`],
|
|
913
|
+
groupAllowFrom: [`\${${envKey}}`],
|
|
914
|
+
dmPolicy: "allowlist",
|
|
915
|
+
groupPolicy: "allowlist",
|
|
916
|
+
selfChatMode: true,
|
|
917
|
+
};
|
|
918
|
+
normalizedChannelConfig.accounts = nextAccounts;
|
|
919
|
+
normalizedChannelConfig.enabled = true;
|
|
920
|
+
if (!String(normalizedChannelConfig.defaultAccount || "").trim()) {
|
|
921
|
+
normalizedChannelConfig.defaultAccount = "default";
|
|
922
|
+
}
|
|
923
|
+
refreshedCfg.channels =
|
|
924
|
+
refreshedCfg.channels && typeof refreshedCfg.channels === "object"
|
|
925
|
+
? { ...refreshedCfg.channels }
|
|
926
|
+
: {};
|
|
927
|
+
refreshedCfg.channels.whatsapp = normalizedChannelConfig;
|
|
928
|
+
|
|
929
|
+
const bindSpec = buildBindingSpec({ provider: "whatsapp", accountId });
|
|
930
|
+
appendBindingToConfig({
|
|
931
|
+
cfg: refreshedCfg,
|
|
932
|
+
agentId,
|
|
933
|
+
match: normalizeBindingMatch({ channel: "whatsapp", accountId }),
|
|
934
|
+
});
|
|
935
|
+
saveConfig({ fsImpl, OPENCLAW_DIR, config: refreshedCfg });
|
|
936
|
+
|
|
937
|
+
onProgress({ phase: "restarting", label: "Rebooting..." });
|
|
938
|
+
await restartGateway();
|
|
939
|
+
} catch (error) {
|
|
940
|
+
try {
|
|
941
|
+
await clawCmd(
|
|
942
|
+
[
|
|
943
|
+
"channels remove",
|
|
944
|
+
"--channel whatsapp",
|
|
945
|
+
accountId !== "default" ? `--account ${shellEscapeArg(accountId)}` : "",
|
|
946
|
+
"--delete",
|
|
947
|
+
]
|
|
948
|
+
.filter(Boolean)
|
|
949
|
+
.join(" "),
|
|
950
|
+
{ quiet: true, timeoutMs: 30000 },
|
|
951
|
+
);
|
|
952
|
+
} catch {}
|
|
953
|
+
try {
|
|
954
|
+
writeEnvFile(previousEnvVars);
|
|
955
|
+
reloadEnv();
|
|
956
|
+
} catch {}
|
|
957
|
+
try {
|
|
958
|
+
saveConfig({ fsImpl, OPENCLAW_DIR, config: previousConfig });
|
|
959
|
+
} catch {}
|
|
960
|
+
throw error;
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
return {
|
|
964
|
+
channel: "whatsapp",
|
|
965
|
+
account: { id: accountId, name, envKey },
|
|
966
|
+
binding: {
|
|
967
|
+
agentId,
|
|
968
|
+
match: normalizeBindingMatch({ channel: "whatsapp", accountId }),
|
|
969
|
+
},
|
|
970
|
+
restartRequired: true,
|
|
971
|
+
};
|
|
972
|
+
};
|
|
973
|
+
|
|
974
|
+
const runChannelAccountLogin = async ({
|
|
975
|
+
provider: rawProvider,
|
|
976
|
+
accountId: rawAccountId,
|
|
977
|
+
} = {}) => {
|
|
978
|
+
const provider = normalizeChannelProvider(rawProvider);
|
|
979
|
+
if (provider !== "whatsapp") {
|
|
980
|
+
throw new Error("Channel login is currently only supported for WhatsApp");
|
|
981
|
+
}
|
|
982
|
+
const accountId = String(rawAccountId || "").trim() || "default";
|
|
983
|
+
const loginArgs = [
|
|
984
|
+
"channels login",
|
|
985
|
+
`--channel ${shellEscapeArg(provider)}`,
|
|
986
|
+
accountId !== "default" ? `--account ${shellEscapeArg(accountId)}` : "",
|
|
987
|
+
].filter(Boolean);
|
|
988
|
+
const loginStartedAt = Date.now();
|
|
989
|
+
const result = await clawCmd(loginArgs.join(" "), {
|
|
990
|
+
quiet: true,
|
|
991
|
+
timeoutMs: 12000,
|
|
992
|
+
killSignal: "SIGKILL",
|
|
993
|
+
});
|
|
994
|
+
const elapsedMs = Date.now() - loginStartedAt;
|
|
995
|
+
console.log(
|
|
996
|
+
`[channels] login ${provider}/${accountId} finished ok=${!!result?.ok} code=${String(
|
|
997
|
+
result?.code ?? "",
|
|
998
|
+
)} elapsedMs=${elapsedMs}`,
|
|
999
|
+
);
|
|
1000
|
+
return {
|
|
1001
|
+
ok: !!result?.ok,
|
|
1002
|
+
stdout: String(result?.stdout || ""),
|
|
1003
|
+
stderr: String(result?.stderr || ""),
|
|
1004
|
+
completed: !!result?.ok,
|
|
1005
|
+
};
|
|
1006
|
+
};
|
|
1007
|
+
|
|
1008
|
+
const getChannelAccountLoginStatus = ({
|
|
1009
|
+
provider: rawProvider,
|
|
1010
|
+
accountId: rawAccountId,
|
|
1011
|
+
} = {}) => {
|
|
1012
|
+
const provider = normalizeChannelProvider(rawProvider);
|
|
1013
|
+
if (provider !== "whatsapp") {
|
|
1014
|
+
throw new Error("Channel login status is currently only supported for WhatsApp");
|
|
1015
|
+
}
|
|
1016
|
+
const accountId = String(rawAccountId || "").trim() || "default";
|
|
1017
|
+
return {
|
|
1018
|
+
provider,
|
|
1019
|
+
accountId,
|
|
1020
|
+
linked: hasSavedWhatsAppCredentials({
|
|
1021
|
+
fsImpl,
|
|
1022
|
+
OPENCLAW_DIR,
|
|
1023
|
+
accountId,
|
|
1024
|
+
}),
|
|
1025
|
+
};
|
|
1026
|
+
};
|
|
1027
|
+
|
|
766
1028
|
return {
|
|
767
1029
|
getChannelAccountToken,
|
|
768
1030
|
createChannelAccount,
|
|
769
1031
|
updateChannelAccount,
|
|
770
1032
|
deleteChannelAccount,
|
|
1033
|
+
runChannelAccountLogin,
|
|
1034
|
+
getChannelAccountLoginStatus,
|
|
771
1035
|
listConfiguredChannelAccountsWithMaskedTokens,
|
|
772
1036
|
};
|
|
773
1037
|
};
|
|
@@ -41,6 +41,8 @@ const createAgentsService = ({
|
|
|
41
41
|
createChannelAccount: channelsDomain.createChannelAccount,
|
|
42
42
|
updateChannelAccount: channelsDomain.updateChannelAccount,
|
|
43
43
|
deleteChannelAccount: channelsDomain.deleteChannelAccount,
|
|
44
|
+
runChannelAccountLogin: channelsDomain.runChannelAccountLogin,
|
|
45
|
+
getChannelAccountLoginStatus: channelsDomain.getChannelAccountLoginStatus,
|
|
44
46
|
listConfiguredChannelAccounts:
|
|
45
47
|
channelsDomain.listConfiguredChannelAccountsWithMaskedTokens,
|
|
46
48
|
};
|