@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 +7 -2
- package/src/context.ts +7 -2
- package/src/index.ts +4 -8
- package/src/jwt.ts +1 -1
- package/src/learner.ts +1 -1
- package/src/reaction-scanner.ts +2 -5
- package/src/tools/check-tier.ts +5 -5
- package/src/tools/get-context.ts +2 -1
- package/src/types.ts +1 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better_openclaw/betterclaw",
|
|
3
|
-
"version": "3.0.
|
|
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": [
|
|
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:
|
|
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"
|
|
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"
|
|
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 (
|
|
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
|
|
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
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
|
|
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
|
package/src/reaction-scanner.ts
CHANGED
|
@@ -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
|
|
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) {
|
package/src/tools/check-tier.ts
CHANGED
|
@@ -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 (
|
|
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"
|
|
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
|
};
|
package/src/tools/get-context.ts
CHANGED
|
@@ -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"
|
|
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" |
|
|
161
|
+
tier: "free" | "premium" | null;
|
|
163
162
|
smartMode: boolean;
|
|
164
163
|
}
|