@chrysb/alphaclaw 0.9.9 → 0.9.11

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 (43) hide show
  1. package/lib/public/dist/app.bundle.js +1773 -1747
  2. package/lib/public/js/components/agents-tab/agent-overview/use-model-card.js +4 -5
  3. package/lib/public/js/components/agents-tab/agent-pairing-section.js +11 -5
  4. package/lib/public/js/components/cron-tab/cron-helpers.js +13 -1
  5. package/lib/public/js/components/cron-tab/use-cron-tab.js +4 -3
  6. package/lib/public/js/components/general/index.js +6 -1
  7. package/lib/public/js/components/general/use-general-tab.js +17 -20
  8. package/lib/public/js/components/models-tab/index.js +5 -1
  9. package/lib/public/js/components/models-tab/model-picker.js +52 -0
  10. package/lib/public/js/components/onboarding/use-welcome-pairing.js +4 -1
  11. package/lib/public/js/components/pairings.js +75 -4
  12. package/lib/public/js/components/welcome/use-welcome.js +37 -8
  13. package/lib/public/js/hooks/usePolling.js +46 -13
  14. package/lib/public/js/lib/model-config.js +6 -2
  15. package/lib/server/agents/channels.js +53 -9
  16. package/lib/server/commands.js +4 -1
  17. package/lib/server/constants.js +14 -3
  18. package/lib/server/cost-utils.js +9 -0
  19. package/lib/server/cron-service.js +12 -1
  20. package/lib/server/db/doctor/index.js +9 -0
  21. package/lib/server/db/usage/index.js +13 -0
  22. package/lib/server/db/watchdog/index.js +13 -1
  23. package/lib/server/db/webhooks/index.js +13 -1
  24. package/lib/server/gateway.js +119 -8
  25. package/lib/server/init/register-server-routes.js +3 -0
  26. package/lib/server/init/runtime-init.js +2 -0
  27. package/lib/server/internal-files-migration.js +11 -1
  28. package/lib/server/model-catalog-bootstrap.json +3193 -0
  29. package/lib/server/model-catalog-cache.js +124 -32
  30. package/lib/server/onboarding/github.js +79 -2
  31. package/lib/server/onboarding/index.js +2 -9
  32. package/lib/server/onboarding/openclaw.js +18 -4
  33. package/lib/server/openclaw-runtime-env.js +55 -0
  34. package/lib/server/openclaw-version.js +2 -1
  35. package/lib/server/routes/models.js +28 -0
  36. package/lib/server/routes/pairings.js +106 -15
  37. package/lib/server/usage-tracker-config.js +28 -3
  38. package/lib/server/utils/command-output.js +11 -0
  39. package/lib/server.js +4 -0
  40. package/lib/setup/gitignore +2 -0
  41. package/package.json +2 -2
  42. package/patches/openclaw+2026.4.23.patch +63 -0
  43. package/patches/openclaw+2026.4.15.patch +0 -13
@@ -2,12 +2,14 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const { OPENCLAW_DIR } = require("../constants");
4
4
  const { buildManagedPaths } = require("../internal-files-migration");
5
+ const { readOpenclawConfig } = require("../openclaw-config");
5
6
  const { parseJsonObjectFromNoisyOutput } = require("../utils/json");
6
7
  const { quoteShellArg } = require("../utils/shell");
7
8
 
8
9
  const kAllowedPairingChannels = new Set(["telegram", "discord", "slack", "whatsapp"]);
9
10
  const kSafePairingArgPattern = /^[\w\-:.]+$/;
10
11
  const kDevicesListCliTimeoutMs = 5000;
12
+ const kPairingRequestTtlMs = 60 * 60 * 1000;
11
13
  const quoteCliArg = (value) => quoteShellArg(value, { strategy: "single" });
12
14
 
13
15
  const resolvePairingStorePath = ({ openclawDir, channel }) =>
@@ -23,6 +25,77 @@ const readPairingStore = ({ fsModule, filePath }) => {
23
25
  }
24
26
  };
25
27
 
28
+ const normalizePairingCode = (value) =>
29
+ String(value || "")
30
+ .trim()
31
+ .toUpperCase();
32
+
33
+ const normalizePairingAccountId = (value) => String(value || "").trim() || "default";
34
+
35
+ const parseTimestampMs = (value) => {
36
+ const parsed = Date.parse(String(value || "").trim());
37
+ return Number.isFinite(parsed) ? parsed : null;
38
+ };
39
+
40
+ const mapPairingStoreEntry = ({ entry, channel, nowMs = Date.now() }) => {
41
+ const code = normalizePairingCode(entry?.code || entry?.pairingCode);
42
+ if (!code) return null;
43
+ const createdAt = String(entry?.createdAt || "").trim();
44
+ const createdAtMs = parseTimestampMs(createdAt);
45
+ if (!createdAtMs || nowMs - createdAtMs > kPairingRequestTtlMs) {
46
+ return null;
47
+ }
48
+ return {
49
+ id: code,
50
+ code,
51
+ channel: String(channel || "").trim(),
52
+ accountId: normalizePairingAccountId(entry?.meta?.accountId || entry?.accountId),
53
+ requesterId: String(entry?.id || entry?.requesterId || "").trim(),
54
+ createdAt,
55
+ };
56
+ };
57
+
58
+ const readPendingPairingsFromStore = ({ fsModule, openclawDir, channel, nowMs = Date.now() }) => {
59
+ const filePath = resolvePairingStorePath({ openclawDir, channel });
60
+ return readPairingStore({ fsModule, filePath })
61
+ .map((entry) => mapPairingStoreEntry({ entry, channel, nowMs }))
62
+ .filter(Boolean);
63
+ };
64
+
65
+ const mergePendingPairings = (...lists) => {
66
+ const merged = [];
67
+ const seen = new Map();
68
+ for (const list of lists) {
69
+ for (const entry of Array.isArray(list) ? list : []) {
70
+ const code = normalizePairingCode(entry?.code || entry?.id);
71
+ const channel = String(entry?.channel || "").trim();
72
+ if (!code || !channel) continue;
73
+ const accountId = normalizePairingAccountId(entry?.accountId);
74
+ const key = `${channel}\u0000${accountId}\u0000${code}`;
75
+ const current = seen.get(key);
76
+ if (!current) {
77
+ const nextEntry = {
78
+ ...entry,
79
+ id: code,
80
+ code,
81
+ channel,
82
+ accountId,
83
+ };
84
+ seen.set(key, nextEntry);
85
+ merged.push(nextEntry);
86
+ continue;
87
+ }
88
+ if (!current.requesterId && entry?.requesterId) {
89
+ current.requesterId = String(entry.requesterId).trim();
90
+ }
91
+ if (!current.createdAt && entry?.createdAt) {
92
+ current.createdAt = String(entry.createdAt).trim();
93
+ }
94
+ }
95
+ }
96
+ return merged;
97
+ };
98
+
26
99
  const writePairingStore = ({ fsModule, filePath, requests }) => {
27
100
  fsModule.mkdirSync(path.dirname(filePath), { recursive: true });
28
101
  fsModule.writeFileSync(filePath, JSON.stringify({ version: 1, requests }, null, 2));
@@ -64,8 +137,9 @@ const removeAccountRequestsFromPairingStore = ({ fsModule, openclawDir, channel,
64
137
  };
65
138
 
66
139
  const registerPairingRoutes = ({ app, clawCmd, isOnboarded, fsModule = fs, openclawDir = OPENCLAW_DIR }) => {
67
- let pairingCache = { pending: [], ts: 0 };
68
- const PAIRING_CACHE_TTL = 10000;
140
+ let pairingCache = { pending: [], ts: 0, ttlMs: 0 };
141
+ const kPairingCacheTtlMs = 10000;
142
+ const kEmptyPairingCacheTtlMs = 1000;
69
143
  const {
70
144
  cliDeviceAutoApprovedPath: kCliAutoApproveMarkerPath,
71
145
  internalDir: kManagedFilesDir,
@@ -107,34 +181,50 @@ const registerPairingRoutes = ({ app, clawCmd, isOnboarded, fsModule = fs, openc
107
181
  };
108
182
 
109
183
  app.get("/api/pairings", async (req, res) => {
110
- if (Date.now() - pairingCache.ts < PAIRING_CACHE_TTL) {
184
+ if (Date.now() - pairingCache.ts < Number(pairingCache.ttlMs || 0)) {
111
185
  return res.json({ pending: pairingCache.pending });
112
186
  }
113
187
 
114
188
  const pending = [];
115
189
  const channels = ["telegram", "discord", "slack", "whatsapp"];
190
+ const config = readOpenclawConfig({
191
+ fsModule,
192
+ openclawDir,
193
+ fallback: {},
194
+ });
116
195
 
117
196
  for (const ch of channels) {
118
- try {
119
- const config = JSON.parse(
120
- fsModule.readFileSync(`${openclawDir}/openclaw.json`, "utf8"),
121
- );
122
- if (!config.channels?.[ch]?.enabled) continue;
123
- } catch {
124
- continue;
125
- }
197
+ const pendingFromStore = readPendingPairingsFromStore({
198
+ fsModule,
199
+ openclawDir,
200
+ channel: ch,
201
+ });
202
+ const isEnabledInConfig = config.channels?.[ch]?.enabled === true;
203
+ if (!isEnabledInConfig && pendingFromStore.length === 0) continue;
126
204
 
127
205
  const result = await clawCmd(`pairing list --channel ${ch} --json`, { quiet: true });
128
- if (result.ok && result.stdout) {
206
+ const rawOutput = [result.stdout, result.stderr].filter(Boolean).join("\n");
207
+ if (rawOutput) {
129
208
  try {
130
- pending.push(...parsePendingPairings(result.stdout, ch));
209
+ pending.push(
210
+ ...mergePendingPairings(
211
+ parsePendingPairings(rawOutput, ch),
212
+ pendingFromStore,
213
+ ),
214
+ );
131
215
  } catch {
132
- // Ignore malformed output for a single channel and keep the rest of the response.
216
+ pending.push(...pendingFromStore);
133
217
  }
218
+ continue;
134
219
  }
220
+ pending.push(...pendingFromStore);
135
221
  }
136
222
 
137
- pairingCache = { pending, ts: Date.now() };
223
+ pairingCache = {
224
+ pending,
225
+ ts: Date.now(),
226
+ ttlMs: pending.length > 0 ? kPairingCacheTtlMs : kEmptyPairingCacheTtlMs,
227
+ };
138
228
  res.json({ pending });
139
229
  });
140
230
 
@@ -166,6 +256,7 @@ const registerPairingRoutes = ({ app, clawCmd, isOnboarded, fsModule = fs, openc
166
256
  ? `pairing approve --channel ${quoteCliArg(channel)} --account ${quoteCliArg(accountId)} ${quoteCliArg(pairingId)}`
167
257
  : `pairing approve ${quoteCliArg(channel)} ${quoteCliArg(pairingId)}`;
168
258
  const result = await clawCmd(approveCmd);
259
+ pairingCache.ts = 0;
169
260
  res.json(result);
170
261
  });
171
262
 
@@ -7,6 +7,7 @@ const kUsageTrackerPluginPath = path.resolve(
7
7
  "plugin",
8
8
  "usage-tracker",
9
9
  );
10
+ const kConversationAccessHookPolicyKey = "allowConversationAccess";
10
11
 
11
12
  const ensurePluginsShell = (cfg = {}) => {
12
13
  if (!cfg.plugins || typeof cfg.plugins !== "object") cfg.plugins = {};
@@ -29,19 +30,43 @@ const ensurePluginAllowed = ({ cfg = {}, pluginKey = "" }) => {
29
30
  }
30
31
  };
31
32
 
33
+ const buildUsageTrackerHookPolicy = ({ existingHooks = {} } = {}) => {
34
+ const hooks = {};
35
+ if (typeof existingHooks.allowPromptInjection === "boolean") {
36
+ hooks.allowPromptInjection = existingHooks.allowPromptInjection;
37
+ }
38
+ hooks[kConversationAccessHookPolicyKey] = true;
39
+ return hooks;
40
+ };
41
+
32
42
  const ensureUsageTrackerPluginEntry = (cfg = {}) => {
33
43
  const before = JSON.stringify(cfg);
34
44
  ensurePluginAllowed({ cfg, pluginKey: "usage-tracker" });
35
45
  if (!cfg.plugins.load.paths.includes(kUsageTrackerPluginPath)) {
36
46
  cfg.plugins.load.paths.push(kUsageTrackerPluginPath);
37
47
  }
38
- cfg.plugins.entries["usage-tracker"] = {
39
- ...(cfg.plugins.entries["usage-tracker"] &&
48
+ const existingEntry =
49
+ cfg.plugins.entries["usage-tracker"] &&
40
50
  typeof cfg.plugins.entries["usage-tracker"] === "object"
41
51
  ? cfg.plugins.entries["usage-tracker"]
42
- : {}),
52
+ : {};
53
+ const existingHooks =
54
+ existingEntry.hooks && typeof existingEntry.hooks === "object"
55
+ ? existingEntry.hooks
56
+ : {};
57
+ const hooks = buildUsageTrackerHookPolicy({
58
+ existingHooks,
59
+ });
60
+ const nextEntry = {
61
+ ...existingEntry,
43
62
  enabled: true,
44
63
  };
64
+ if (Object.keys(hooks).length > 0) {
65
+ nextEntry.hooks = hooks;
66
+ } else {
67
+ delete nextEntry.hooks;
68
+ }
69
+ cfg.plugins.entries["usage-tracker"] = nextEntry;
45
70
  return JSON.stringify(cfg) !== before;
46
71
  };
47
72
 
@@ -0,0 +1,11 @@
1
+ const getCommandOutputCandidates = (error) => {
2
+ const stdout = String(error?.stdout || "").trim();
3
+ const stderr = String(error?.stderr || "").trim();
4
+ const combined = [stdout, stderr].filter(Boolean).join("\n").trim();
5
+
6
+ return [...new Set([combined, stdout, stderr].filter(Boolean))];
7
+ };
8
+
9
+ module.exports = {
10
+ getCommandOutputCandidates,
11
+ };
package/lib/server.js CHANGED
@@ -141,12 +141,16 @@ const {
141
141
  const {
142
142
  ensureManagedExecDefaults,
143
143
  } = require("./server/exec-defaults-config");
144
+ const {
145
+ ensureOpenclawStartupEnv,
146
+ } = require("./server/openclaw-runtime-env");
144
147
 
145
148
  const { PORT, kTrustProxyHops, SETUP_API_PREFIXES } = constants;
146
149
 
147
150
  initializeServerRuntime({
148
151
  fs,
149
152
  constants,
153
+ ensureOpenclawStartupEnv,
150
154
  startEnvWatcher,
151
155
  attachGatewaySignalHandlers,
152
156
  cleanupStaleImportTempDirs,
@@ -18,5 +18,7 @@ db/**
18
18
  !hooks/transforms/**
19
19
  !cron/
20
20
  !cron/jobs.json
21
+ # OpenClaw 2026.4.20+: runtime execution state (job definitions stay in cron/jobs.json).
22
+ cron/jobs-state.json
21
23
  !openclaw.json
22
24
  !.gitignore
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chrysb/alphaclaw",
3
- "version": "0.9.9",
3
+ "version": "0.9.11",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -36,7 +36,7 @@
36
36
  "dependencies": {
37
37
  "express": "^4.21.0",
38
38
  "http-proxy": "^1.18.1",
39
- "openclaw": "2026.4.15",
39
+ "openclaw": "2026.4.23",
40
40
  "patch-package": "^8.0.1",
41
41
  "ws": "^8.19.0"
42
42
  },
@@ -0,0 +1,63 @@
1
+ diff --git a/node_modules/openclaw/dist/plugin-sdk/src/config/zod-schema.d.ts b/node_modules/openclaw/dist/plugin-sdk/src/config/zod-schema.d.ts
2
+ index 8964b19..2285c33 100644
3
+ --- a/node_modules/openclaw/dist/plugin-sdk/src/config/zod-schema.d.ts
4
+ +++ b/node_modules/openclaw/dist/plugin-sdk/src/config/zod-schema.d.ts
5
+ @@ -4171,6 +4171,7 @@ export declare const OpenClawSchema: z.ZodObject<{
6
+ enabled: z.ZodOptional<z.ZodBoolean>;
7
+ hooks: z.ZodOptional<z.ZodObject<{
8
+ allowPromptInjection: z.ZodOptional<z.ZodBoolean>;
9
+ + allowConversationAccess: z.ZodOptional<z.ZodBoolean>;
10
+ }, z.core.$strict>>;
11
+ subagent: z.ZodOptional<z.ZodObject<{
12
+ allowModelOverride: z.ZodOptional<z.ZodBoolean>;
13
+ diff --git a/node_modules/openclaw/dist/runtime-schema-Dgzy-2rz.js b/node_modules/openclaw/dist/runtime-schema-Dgzy-2rz.js
14
+ index eaaaab6..9847a80 100644
15
+ --- a/node_modules/openclaw/dist/runtime-schema-Dgzy-2rz.js
16
+ +++ b/node_modules/openclaw/dist/runtime-schema-Dgzy-2rz.js
17
+ @@ -19939,5 +19939,12 @@ const GENERATED_BASE_CONFIG_SCHEMA = {
18
+ - properties: { allowPromptInjection: {
19
+ - type: "boolean",
20
+ - title: "Allow Prompt Injection Hooks",
21
+ - description: "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior."
22
+ - } },
23
+ + properties: {
24
+ + allowPromptInjection: {
25
+ + type: "boolean",
26
+ + title: "Allow Prompt Injection Hooks",
27
+ + description: "Controls whether this plugin may mutate prompts through typed hooks. Set false to block `before_prompt_build` and ignore prompt-mutating fields from legacy `before_agent_start`, while preserving legacy `modelOverride` and `providerOverride` behavior."
28
+ + },
29
+ + allowConversationAccess: {
30
+ + type: "boolean",
31
+ + title: "Allow Conversation Access Hooks",
32
+ + description: "Explicitly allows a non-bundled plugin to receive conversation content in typed hooks. Keep false unless the plugin is trusted to inspect conversation data."
33
+ + }
34
+ + },
35
+ @@ -24760,0 +24761,5 @@ const GENERATED_BASE_CONFIG_SCHEMA = {
36
+ + "plugins.entries.*.hooks.allowConversationAccess": {
37
+ + label: "Allow Conversation Access Hooks",
38
+ + help: "Explicitly allows a non-bundled plugin to receive conversation content in typed hooks. Keep false unless the plugin is trusted to inspect conversation data.",
39
+ + tags: ["access"]
40
+ + },
41
+ diff --git a/node_modules/openclaw/dist/server.impl-DhtU4okW.js b/node_modules/openclaw/dist/server.impl-DhtU4okW.js
42
+ index 93890d3..7b3c701 100644
43
+ --- a/node_modules/openclaw/dist/server.impl-DhtU4okW.js
44
+ +++ b/node_modules/openclaw/dist/server.impl-DhtU4okW.js
45
+ @@ -11165,7 +11165,7 @@ function attachGatewayWsMessageHandler(params) {
46
+ close(1008, truncateCloseReason(authMessage));
47
+ };
48
+ const clearUnboundScopes = () => {
49
+ - if (scopes.length > 0) {
50
+ + if (scopes.length > 0 && !sharedAuthOk) {
51
+ scopes = [];
52
+ connectParams.scopes = scopes;
53
+ }
54
+ diff --git a/node_modules/openclaw/dist/zod-schema-BhKK4qYw.js b/node_modules/openclaw/dist/zod-schema-BhKK4qYw.js
55
+ index 1dc871e..6803778 100644
56
+ --- a/node_modules/openclaw/dist/zod-schema-BhKK4qYw.js
57
+ +++ b/node_modules/openclaw/dist/zod-schema-BhKK4qYw.js
58
+ @@ -775 +775,4 @@ const PluginEntrySchema = z.object({
59
+ - hooks: z.object({ allowPromptInjection: z.boolean().optional() }).strict().optional(),
60
+ + hooks: z.object({
61
+ + allowPromptInjection: z.boolean().optional(),
62
+ + allowConversationAccess: z.boolean().optional()
63
+ + }).strict().optional(),
@@ -1,13 +0,0 @@
1
- diff --git a/node_modules/openclaw/dist/server.impl-GQ72oJBa.js b/node_modules/openclaw/dist/server.impl-GQ72oJBa.js
2
- index 70c273a..04a6d54 100644
3
- --- a/node_modules/openclaw/dist/server.impl-GQ72oJBa.js
4
- +++ b/node_modules/openclaw/dist/server.impl-GQ72oJBa.js
5
- @@ -22362,7 +22362,7 @@ function attachGatewayWsMessageHandler(params) {
6
- close(1008, truncateCloseReason(authMessage));
7
- };
8
- const clearUnboundScopes = () => {
9
- - if (scopes.length > 0) {
10
- + if (scopes.length > 0 && !sharedAuthOk) {
11
- scopes = [];
12
- connectParams.scopes = scopes;
13
- }