@chrysb/alphaclaw 0.6.0-beta.1 → 0.6.0-beta.3

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.
Files changed (28) hide show
  1. package/lib/public/js/components/agents-tab/agent-bindings-section/channel-item-trailing.js +203 -0
  2. package/lib/public/js/components/agents-tab/agent-bindings-section/helpers.js +10 -12
  3. package/lib/public/js/components/agents-tab/agent-bindings-section/index.js +19 -287
  4. package/lib/public/js/components/agents-tab/agent-bindings-section/use-agent-bindings.js +1 -1
  5. package/lib/public/js/components/agents-tab/agent-bindings-section/use-channel-items.js +211 -0
  6. package/lib/public/js/components/agents-tab/agent-pairing-section.js +17 -4
  7. package/lib/public/js/components/agents-tab/create-channel-modal.js +29 -6
  8. package/lib/public/js/components/channels.js +19 -14
  9. package/lib/public/js/components/models-tab/provider-auth-card.js +18 -1
  10. package/lib/public/js/components/models-tab/use-models.js +15 -8
  11. package/lib/public/js/lib/channel-accounts.js +20 -0
  12. package/lib/public/js/lib/model-config.js +8 -4
  13. package/lib/server/agents/agents.js +207 -0
  14. package/lib/server/agents/bindings.js +74 -0
  15. package/lib/server/agents/channels.js +674 -0
  16. package/lib/server/agents/service.js +28 -1458
  17. package/lib/server/agents/shared.js +631 -0
  18. package/lib/server/constants.js +6 -0
  19. package/lib/server/db/usage/pricing.js +1 -0
  20. package/lib/server/openclaw-config.js +13 -0
  21. package/lib/server/routes/models.js +12 -1
  22. package/lib/server/routes/pairings.js +29 -3
  23. package/lib/server/routes/system.js +1 -6
  24. package/lib/server/routes/telegram.js +34 -16
  25. package/lib/server/telegram-workspace.js +22 -7
  26. package/lib/server/topic-registry.js +1 -4
  27. package/lib/server/utils/channels.js +13 -0
  28. package/package.json +1 -1
@@ -3,6 +3,11 @@ const path = require("path");
3
3
  const { OPENCLAW_DIR } = require("../constants");
4
4
  const { buildManagedPaths } = require("../internal-files-migration");
5
5
  const { parseJsonObjectFromNoisyOutput } = require("../utils/json");
6
+ const { quoteShellArg } = require("../utils/shell");
7
+
8
+ const kAllowedPairingChannels = new Set(["telegram", "discord"]);
9
+ const kSafePairingArgPattern = /^[\w\-:.]+$/;
10
+ const quoteCliArg = (value) => quoteShellArg(value, { strategy: "single" });
6
11
 
7
12
  const resolvePairingStorePath = ({ openclawDir, channel }) =>
8
13
  path.join(openclawDir, "credentials", `${String(channel).trim().toLowerCase()}-pairing.json`);
@@ -133,11 +138,32 @@ const registerPairingRoutes = ({ app, clawCmd, isOnboarded, fsModule = fs, openc
133
138
  });
134
139
 
135
140
  app.post("/api/pairings/:id/approve", async (req, res) => {
136
- const channel = req.body.channel || "telegram";
141
+ const channel = String(req.body?.channel || "telegram")
142
+ .trim()
143
+ .toLowerCase();
137
144
  const accountId = String(req.body?.accountId || "").trim();
145
+ const pairingId = String(req.params.id || "").trim();
146
+ if (!kAllowedPairingChannels.has(channel)) {
147
+ return res.status(400).json({
148
+ ok: false,
149
+ error: `Unsupported pairing channel "${channel}"`,
150
+ });
151
+ }
152
+ if (!pairingId || !kSafePairingArgPattern.test(pairingId)) {
153
+ return res.status(400).json({
154
+ ok: false,
155
+ error: "Invalid pairing id",
156
+ });
157
+ }
158
+ if (accountId && !kSafePairingArgPattern.test(accountId)) {
159
+ return res.status(400).json({
160
+ ok: false,
161
+ error: "Invalid account id",
162
+ });
163
+ }
138
164
  const approveCmd = accountId
139
- ? `pairing approve --channel ${channel} --account ${accountId} ${req.params.id}`
140
- : `pairing approve ${channel} ${req.params.id}`;
165
+ ? `pairing approve --channel ${quoteCliArg(channel)} --account ${quoteCliArg(accountId)} ${quoteCliArg(pairingId)}`
166
+ : `pairing approve ${quoteCliArg(channel)} ${quoteCliArg(pairingId)}`;
141
167
  const result = await clawCmd(approveCmd);
142
168
  res.json(result);
143
169
  });
@@ -1,5 +1,6 @@
1
1
  const { buildManagedPaths } = require("../internal-files-migration");
2
2
  const { readOpenclawConfig } = require("../openclaw-config");
3
+ const { hasScopedBindingFields } = require("../utils/channels");
3
4
 
4
5
  const registerSystemRoutes = ({
5
6
  app,
@@ -80,12 +81,6 @@ const registerSystemRoutes = ({
80
81
  .filter(Boolean)
81
82
  .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
82
83
  .join(" ");
83
- const hasScopedBindingFields = (match = {}) =>
84
- !!match.peer ||
85
- !!match.parentPeer ||
86
- !!String(match.guildId || "").trim() ||
87
- !!String(match.teamId || "").trim() ||
88
- (Array.isArray(match.roles) && match.roles.length > 0);
89
84
  const resolveTelegramAccountIdForAgent = ({ config, agentId }) => {
90
85
  const normalizedAgentId = String(agentId || "").trim();
91
86
  if (!normalizedAgentId) return "default";
@@ -1,7 +1,15 @@
1
1
  const fs = require("fs");
2
2
  const { OPENCLAW_DIR } = require("../constants");
3
3
  const { isDebugEnabled } = require("../helpers");
4
+ const {
5
+ readOpenclawConfig,
6
+ writeOpenclawConfig,
7
+ } = require("../openclaw-config");
4
8
  const { parseBooleanValue } = require("../utils/boolean");
9
+ const {
10
+ hasScopedBindingFields,
11
+ normalizeAccountId,
12
+ } = require("../utils/channels");
5
13
  const { quoteShellArg } = require("../utils/shell");
6
14
  const topicRegistry = require("../topic-registry");
7
15
  const { syncConfigForTelegram } = require("../telegram-workspace");
@@ -54,7 +62,6 @@ const buildTelegramGitSyncCommand = (action, target = "") => {
54
62
  const { createTelegramApi } = require("../telegram-api");
55
63
 
56
64
  const kTelegramEnvKeyBase = "TELEGRAM_BOT_TOKEN";
57
- const normalizeAccountId = (value) => String(value || "").trim() || "default";
58
65
 
59
66
  const deriveAccountEnvKey = (accountId) => {
60
67
  const normalized = normalizeAccountId(accountId);
@@ -101,13 +108,6 @@ const resolveTelegramConfigForAccount = ({ telegramConfig, accountId }) => {
101
108
  };
102
109
  };
103
110
 
104
- const hasScopedBindingFields = (match) =>
105
- !!match.peer ||
106
- !!match.parentPeer ||
107
- !!String(match.guildId || "").trim() ||
108
- !!String(match.teamId || "").trim() ||
109
- (Array.isArray(match.roles) && match.roles.length > 0);
110
-
111
111
  const resolveBoundAgentIdForAccount = ({ cfg, accountId }) => {
112
112
  const bindings = Array.isArray(cfg?.bindings) ? cfg.bindings : [];
113
113
  const normalizedAccountId = normalizeAccountId(accountId);
@@ -510,8 +510,11 @@ const registerTelegramRoutes = ({
510
510
  });
511
511
 
512
512
  // Save metadata in local topic registry only.
513
- const configPath = `${OPENCLAW_DIR}/openclaw.json`;
514
- const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
513
+ const cfg = readOpenclawConfig({
514
+ fsModule: fs,
515
+ openclawDir: OPENCLAW_DIR,
516
+ fallback: {},
517
+ });
515
518
  const boundAgentId = resolveBoundAgentIdForAccount({ cfg, accountId });
516
519
  topicRegistry.setGroup(groupId, {
517
520
  ...(groupName ? { name: groupName } : {}),
@@ -537,8 +540,11 @@ const registerTelegramRoutes = ({
537
540
  try {
538
541
  const accountId = resolveAccountId(req);
539
542
  const debugEnabled = isDebugEnabled();
540
- const configPath = `${OPENCLAW_DIR}/openclaw.json`;
541
- let cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
543
+ let cfg = readOpenclawConfig({
544
+ fsModule: fs,
545
+ openclawDir: OPENCLAW_DIR,
546
+ fallback: {},
547
+ });
542
548
  let telegramConfig = cfg.channels?.telegram || {};
543
549
  const { accountConfig } = resolveTelegramConfigForAccount({
544
550
  telegramConfig,
@@ -580,7 +586,11 @@ const registerTelegramRoutes = ({
580
586
  }
581
587
  }
582
588
  if (anyRepaired) {
583
- cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
589
+ cfg = readOpenclawConfig({
590
+ fsModule: fs,
591
+ openclawDir: OPENCLAW_DIR,
592
+ fallback: {},
593
+ });
584
594
  telegramConfig = cfg.channels?.telegram || {};
585
595
  }
586
596
  const refreshedAccountConfig = resolveTelegramConfigForAccount({
@@ -634,8 +644,11 @@ const registerTelegramRoutes = ({
634
644
  app.post("/api/telegram/workspace/reset", async (req, res) => {
635
645
  try {
636
646
  const accountId = resolveAccountId(req);
637
- const configPath = `${OPENCLAW_DIR}/openclaw.json`;
638
- const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
647
+ const cfg = readOpenclawConfig({
648
+ fsModule: fs,
649
+ openclawDir: OPENCLAW_DIR,
650
+ fallback: {},
651
+ });
639
652
  const telegramConfig = cfg.channels?.telegram;
640
653
  if (!telegramConfig || typeof telegramConfig !== "object") {
641
654
  return res.json({ ok: true, syncWarning: null });
@@ -660,7 +673,12 @@ const registerTelegramRoutes = ({
660
673
  delete telegramConfig.groups;
661
674
  delete telegramConfig.groupAllowFrom;
662
675
  }
663
- fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
676
+ writeOpenclawConfig({
677
+ fsModule: fs,
678
+ openclawDir: OPENCLAW_DIR,
679
+ config: cfg,
680
+ spacing: 2,
681
+ });
664
682
 
665
683
  // Remove corresponding groups from topic registry
666
684
  const registry = topicRegistry.readRegistry();
@@ -1,8 +1,11 @@
1
1
  const kTelegramTopicConcurrencyMultiplier = 3;
2
2
  const kAgentConcurrencyFloor = 8;
3
3
  const kSubagentConcurrencyFloor = 4;
4
-
5
- const normalizeAccountId = (value) => String(value || "").trim() || "default";
4
+ const { normalizeAccountId } = require("./utils/channels");
5
+ const {
6
+ readOpenclawConfig,
7
+ writeOpenclawConfig,
8
+ } = require("./openclaw-config");
6
9
 
7
10
  const resolveTelegramAccountConfig = ({ telegramConfig, accountId }) => {
8
11
  const normalizedAccountId = normalizeAccountId(accountId);
@@ -38,8 +41,11 @@ const syncConfigForTelegram = ({
38
41
  requireMention = false,
39
42
  resolvedUserId = "",
40
43
  }) => {
41
- const configPath = `${openclawDir}/openclaw.json`;
42
- const cfg = JSON.parse(fs.readFileSync(configPath, "utf8"));
44
+ const cfg = readOpenclawConfig({
45
+ fsModule: fs,
46
+ openclawDir,
47
+ fallback: {},
48
+ });
43
49
 
44
50
  // Remove legacy root keys from older setup flow.
45
51
  delete cfg.sessions;
@@ -82,8 +88,12 @@ const syncConfigForTelegram = ({
82
88
  const promptTopics = {};
83
89
  for (const [threadId, topic] of Object.entries(registryTopics)) {
84
90
  const systemPrompt = String(topic?.systemInstructions || "").trim();
85
- if (!systemPrompt) continue;
86
- promptTopics[threadId] = { systemPrompt };
91
+ const topicAgentId = String(topic?.agentId || "").trim();
92
+ if (!systemPrompt && !topicAgentId) continue;
93
+ promptTopics[threadId] = {
94
+ ...(systemPrompt ? { systemPrompt } : {}),
95
+ ...(topicAgentId ? { agentId: topicAgentId } : {}),
96
+ };
87
97
  }
88
98
  if (Object.keys(promptTopics).length > 0) {
89
99
  targetConfig.groups[groupId].topics = promptTopics;
@@ -121,7 +131,12 @@ const syncConfigForTelegram = ({
121
131
  kSubagentConcurrencyFloor,
122
132
  );
123
133
 
124
- fs.writeFileSync(configPath, JSON.stringify(cfg, null, 2));
134
+ writeOpenclawConfig({
135
+ fsModule: fs,
136
+ openclawDir,
137
+ config: cfg,
138
+ spacing: 2,
139
+ });
125
140
 
126
141
  return {
127
142
  totalTopics,
@@ -1,13 +1,10 @@
1
1
  const fs = require("fs");
2
2
  const { WORKSPACE_DIR } = require("./constants");
3
+ const { normalizeAccountId } = require("./utils/channels");
3
4
 
4
5
  const kRegistryPath = `${WORKSPACE_DIR}/topic-registry.json`;
5
- const kDefaultAccountId = "default";
6
6
  const kDefaultAgentId = "default";
7
7
 
8
- const normalizeAccountId = (value) =>
9
- String(value || "").trim() || kDefaultAccountId;
10
-
11
8
  const normalizeGroupAgentId = (value) =>
12
9
  String(value || "").trim() || kDefaultAgentId;
13
10
 
@@ -0,0 +1,13 @@
1
+ const normalizeAccountId = (value) => String(value || "").trim() || "default";
2
+
3
+ const hasScopedBindingFields = (match = {}) =>
4
+ !!match.peer ||
5
+ !!match.parentPeer ||
6
+ !!String(match.guildId || "").trim() ||
7
+ !!String(match.teamId || "").trim() ||
8
+ (Array.isArray(match.roles) && match.roles.length > 0);
9
+
10
+ module.exports = {
11
+ normalizeAccountId,
12
+ hasScopedBindingFields,
13
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.6.0-beta.1",
3
+ "version": "0.6.0-beta.3",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },