@better_openclaw/betterclaw 3.0.2 → 3.0.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better_openclaw/betterclaw",
3
- "version": "3.0.2",
3
+ "version": "3.0.3",
4
4
  "description": "Intelligent event filtering, context tracking, and proactive triggers for BetterClaw",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {
@@ -9,12 +9,17 @@
9
9
  },
10
10
  "type": "module",
11
11
  "openclaw": {
12
- "extensions": ["./src/index.ts"]
12
+ "extensions": [
13
+ "./src/index.ts"
14
+ ]
13
15
  },
14
16
  "dependencies": {
15
17
  "@sinclair/typebox": "^0.34.0"
16
18
  },
17
19
  "devDependencies": {
20
+ "@types/node": "^25.5.2",
21
+ "openclaw": "^2026.4.5",
22
+ "typescript": "^6.0.2",
18
23
  "vitest": "^3.0.0"
19
24
  }
20
25
  }
package/src/context.ts CHANGED
@@ -9,7 +9,7 @@ export class ContextManager {
9
9
  private contextPath: string;
10
10
  private patternsPath: string;
11
11
  private context: DeviceContext;
12
- private runtimeState: RuntimeState = { tier: "free", smartMode: false };
12
+ private runtimeState: RuntimeState = { tier: null, smartMode: false };
13
13
  private timestamps: Record<string, number> = {};
14
14
  private deviceConfig: DeviceConfig = {};
15
15
 
@@ -44,6 +44,11 @@ export class ContextManager {
44
44
  const parsed = JSON.parse(raw);
45
45
  this.timestamps = parsed._timestamps ?? {};
46
46
  delete parsed._timestamps;
47
+ const savedTier = parsed._tier;
48
+ delete parsed._tier;
49
+ if (savedTier === "free" || savedTier === "premium") {
50
+ this.runtimeState = { ...this.runtimeState, tier: savedTier };
51
+ }
47
52
  this.context = parsed as DeviceContext;
48
53
  } catch {
49
54
  this.context = ContextManager.empty();
@@ -78,7 +83,7 @@ export class ContextManager {
78
83
 
79
84
  async save(): Promise<void> {
80
85
  await fs.mkdir(path.dirname(this.contextPath), { recursive: true });
81
- const data = { ...this.context, _timestamps: this.timestamps };
86
+ const data = { ...this.context, _timestamps: this.timestamps, _tier: this.runtimeState.tier };
82
87
  await fs.writeFile(this.contextPath, JSON.stringify(data, null, 2) + "\n", "utf8");
83
88
  const configPath = path.join(path.dirname(this.contextPath), "device-config.json");
84
89
  await fs.writeFile(configPath, JSON.stringify(this.deviceConfig, null, 2) + "\n", "utf8");
package/src/index.ts CHANGED
@@ -70,7 +70,6 @@ export default {
70
70
 
71
71
  // Calibration state
72
72
  let calibrationStartedAt: number | null = null;
73
- let pingReceived = false;
74
73
 
75
74
  const calibrationFile = path.join(stateDir, "calibration.json");
76
75
  (async () => {
@@ -142,9 +141,9 @@ export default {
142
141
 
143
142
  // Ping health check
144
143
  api.registerGatewayMethod("betterclaw.ping", async ({ params, respond, context }) => {
145
- const validTiers: Array<"free" | "premium" | "premium+"> = ["free", "premium", "premium+"];
144
+ const validTiers: Array<"free" | "premium"> = ["free", "premium"];
146
145
  const rawTier = (params as Record<string, unknown>)?.tier as string;
147
- const tier = validTiers.includes(rawTier as any) ? (rawTier as "free" | "premium" | "premium+") : "free";
146
+ const tier = validTiers.includes(rawTier as any) ? (rawTier as "free" | "premium") : "free";
148
147
  const smartMode = (params as Record<string, unknown>)?.smartMode === true;
149
148
 
150
149
  const jwt = (params as Record<string, unknown>)?.jwt as string | undefined;
@@ -159,10 +158,8 @@ export default {
159
158
 
160
159
  ctxManager.setRuntimeState({ tier, smartMode });
161
160
 
162
- pingReceived = true;
163
-
164
161
  // Initialize calibration on first premium ping
165
- if ((tier === "premium" || tier === "premium+") && calibrationStartedAt === null) {
162
+ if (tier === "premium" && calibrationStartedAt === null) {
166
163
  const existingProfile = await loadTriageProfile(stateDir);
167
164
  if (existingProfile?.computedAt) {
168
165
  calibrationStartedAt = existingProfile.computedAt - config.calibrationDays * 86400;
@@ -365,7 +362,6 @@ export default {
365
362
  // Agent tools
366
363
  api.registerTool(
367
364
  createCheckTierTool(ctxManager, () => ({
368
- pingReceived,
369
365
  calibrating: isCalibrating(),
370
366
  calibrationEndsAt: calibrationStartedAt
371
367
  ? calibrationStartedAt + config.calibrationDays * 86400
@@ -454,7 +450,7 @@ export default {
454
450
  if (ctxManager.getRuntimeState().smartMode) {
455
451
  // Scan reactions first (feeds into learner)
456
452
  try {
457
- await scanPendingReactions({ reactions: reactionTracker, api, config, stateDir });
453
+ await scanPendingReactions({ reactions: reactionTracker, api });
458
454
  } catch (err) {
459
455
  api.logger.error(`betterclaw: reaction scan failed: ${err}`);
460
456
  }
package/src/jwt.ts CHANGED
@@ -54,7 +54,7 @@ export async function verifyJwt(
54
54
  const valid = await crypto.subtle.verify(
55
55
  { name: "ECDSA", hash: "SHA-256" },
56
56
  publicKey,
57
- signature,
57
+ signature as Uint8Array<ArrayBuffer>,
58
58
  signingInput
59
59
  );
60
60
  if (!valid) return null;
package/src/learner.ts CHANGED
@@ -165,7 +165,7 @@ export async function runLearner(deps: RunLearnerDeps): Promise<void> {
165
165
  });
166
166
 
167
167
  // 11. Parse last assistant message — handle both string and content-block formats
168
- const lastAssistant = messages.filter((m: any) => m.role === "assistant").pop();
168
+ const lastAssistant = (messages as any[]).filter((m) => m.role === "assistant").pop();
169
169
  if (lastAssistant) {
170
170
  const content = typeof lastAssistant.content === "string"
171
171
  ? lastAssistant.content
@@ -17,9 +17,6 @@ export interface ClassificationResult {
17
17
  export interface ScanDeps {
18
18
  api: OpenClawPluginApi;
19
19
  reactions: ReactionTracker;
20
- classificationModel: string;
21
- classificationApiBase?: string;
22
- getApiKey: () => Promise<string | undefined>;
23
20
  }
24
21
 
25
22
  // --- Helpers ---
@@ -151,7 +148,7 @@ export async function scanPendingReactions(deps: ScanDeps): Promise<void> {
151
148
  sessionKey: "main",
152
149
  limit: 200,
153
150
  });
154
- messages = fetched;
151
+ messages = fetched as typeof messages;
155
152
  } catch (err) {
156
153
  api.logger.error(
157
154
  `reaction-scanner: failed to fetch session messages: ${err instanceof Error ? err.message : String(err)}`,
@@ -198,7 +195,7 @@ export async function scanPendingReactions(deps: ScanDeps): Promise<void> {
198
195
  limit: 5,
199
196
  });
200
197
 
201
- const lastAssistant = classifyMessages.filter((m: any) => m.role === "assistant").pop();
198
+ const lastAssistant = (classifyMessages as any[]).filter((m) => m.role === "assistant").pop();
202
199
  if (lastAssistant) {
203
200
  const content = extractText(lastAssistant.content);
204
201
  if (content) {
@@ -2,7 +2,6 @@ import { Type } from "@sinclair/typebox";
2
2
  import type { ContextManager } from "../context.js";
3
3
 
4
4
  export interface CheckTierState {
5
- pingReceived: boolean;
6
5
  calibrating: boolean;
7
6
  calibrationEndsAt?: number;
8
7
  }
@@ -16,8 +15,9 @@ export function createCheckTierTool(ctx: ContextManager, getState: () => CheckTi
16
15
  parameters: Type.Object({}),
17
16
  async execute(_id: string, _params: Record<string, unknown>) {
18
17
  const state = getState();
18
+ const runtime = ctx.getRuntimeState();
19
19
 
20
- if (!state.pingReceived) {
20
+ if (runtime.tier === null) {
21
21
  return {
22
22
  content: [{
23
23
  type: "text" as const,
@@ -28,13 +28,12 @@ export function createCheckTierTool(ctx: ContextManager, getState: () => CheckTi
28
28
  cacheInstruction: "Re-check in about a minute.",
29
29
  }, null, 2),
30
30
  }],
31
+ details: undefined,
31
32
  };
32
33
  }
33
-
34
- const runtime = ctx.getRuntimeState();
35
34
  const cacheUntil = Math.floor(Date.now() / 1000) + 86400;
36
35
 
37
- const isPremium = runtime.tier === "premium" || runtime.tier === "premium+";
36
+ const isPremium = runtime.tier === "premium";
38
37
 
39
38
  const dataPath = isPremium
40
39
  ? "Use node commands for current device readings: location.get, device.battery, health.steps, health.heartrate, health.hrv, health.sleep, health.distance, health.restinghr, health.workouts, health.summary, geofence.list. Use get_context for patterns, trends, history, and broad situational awareness — its device snapshot may not be perfectly recent but is useful for the big picture."
@@ -57,6 +56,7 @@ export function createCheckTierTool(ctx: ContextManager, getState: () => CheckTi
57
56
 
58
57
  return {
59
58
  content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
59
+ details: undefined,
60
60
  };
61
61
  },
62
62
  };
@@ -14,7 +14,7 @@ export function createGetContextTool(ctx: ContextManager, stateDir?: string) {
14
14
  const patterns = await ctx.readPatterns();
15
15
  const dataAge = ctx.getDataAge();
16
16
 
17
- const isPremium = runtime.tier === "premium" || runtime.tier === "premium+";
17
+ const isPremium = runtime.tier === "premium";
18
18
 
19
19
  const result: Record<string, unknown> = {
20
20
  tierHint: {
@@ -53,6 +53,7 @@ export function createGetContextTool(ctx: ContextManager, stateDir?: string) {
53
53
 
54
54
  return {
55
55
  content: [{ type: "text" as const, text: JSON.stringify(result, null, 2) }],
56
+ details: undefined,
56
57
  };
57
58
  },
58
59
  };
package/src/types.ts CHANGED
@@ -157,8 +157,7 @@ export interface DeviceConfig {
157
157
  proactiveEnabled?: boolean;
158
158
  }
159
159
 
160
- // Runtime state (not persisted)
161
160
  export interface RuntimeState {
162
- tier: "free" | "premium" | "premium+";
161
+ tier: "free" | "premium" | null;
163
162
  smartMode: boolean;
164
163
  }