@better_openclaw/betterclaw 1.4.0 → 2.0.0

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/src/judgment.ts DELETED
@@ -1,145 +0,0 @@
1
- import * as fs from "node:fs/promises";
2
- import * as os from "node:os";
3
- import * as path from "node:path";
4
- import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
5
- import type { DeviceContext, DeviceEvent } from "./types.js";
6
-
7
- interface JudgmentResult {
8
- push: boolean;
9
- reason: string;
10
- }
11
-
12
- type RunEmbeddedPiAgentFn = (opts: Record<string, unknown>) => Promise<{ payloads?: unknown[] }>;
13
-
14
- let _runFn: RunEmbeddedPiAgentFn | null = null;
15
-
16
- async function loadRunEmbeddedPiAgent(): Promise<RunEmbeddedPiAgentFn> {
17
- if (_runFn) return _runFn;
18
- // Dynamic import from OpenClaw internals (same pattern as llm-task plugin)
19
- const mod = await import("../../../src/agents/pi-embedded.js").catch(() =>
20
- import("openclaw/agents/pi-embedded"),
21
- );
22
- if (typeof (mod as any).runEmbeddedPiAgent !== "function") {
23
- throw new Error("runEmbeddedPiAgent not available");
24
- }
25
- _runFn = (mod as any).runEmbeddedPiAgent as RunEmbeddedPiAgentFn;
26
- return _runFn;
27
- }
28
-
29
- export function buildPrompt(event: DeviceEvent, context: DeviceContext, pushesToday: number, budget: number): string {
30
- // Strip raw coordinates from context for privacy
31
- const sanitizedContext = {
32
- ...context,
33
- device: {
34
- ...context.device,
35
- location: context.device.location
36
- ? {
37
- label: context.device.location.label ?? "Unknown",
38
- updatedAt: context.device.location.updatedAt,
39
- }
40
- : null,
41
- },
42
- };
43
-
44
- return [
45
- "You are an event triage system for a personal AI assistant.",
46
- "Given the device context and a new event, decide: should the AI assistant be told about this?",
47
- "",
48
- "Respond with ONLY valid JSON: {\"push\": true/false, \"reason\": \"one sentence\"}",
49
- "",
50
- `Context: ${JSON.stringify(sanitizedContext)}`,
51
- `Event: ${JSON.stringify(event)}`,
52
- `Pushes today: ${pushesToday} of ~${budget} budget`,
53
- `Time: ${new Date().toISOString()}`,
54
- ].join("\n");
55
- }
56
-
57
- function extractText(payloads: unknown[]): string {
58
- for (const p of payloads) {
59
- if (typeof p === "string") return p;
60
- if (p && typeof p === "object" && "text" in p && typeof (p as any).text === "string") {
61
- return (p as any).text;
62
- }
63
- if (p && typeof p === "object" && "content" in p && Array.isArray((p as any).content)) {
64
- for (const c of (p as any).content) {
65
- if (c && typeof c.text === "string") return c.text;
66
- }
67
- }
68
- }
69
- return "";
70
- }
71
-
72
- export class JudgmentLayer {
73
- private api: OpenClawPluginApi;
74
- private config: { llmModel: string; pushBudgetPerDay: number };
75
-
76
- constructor(api: OpenClawPluginApi, config: { llmModel: string; pushBudgetPerDay: number }) {
77
- this.api = api;
78
- this.config = config;
79
- }
80
-
81
- async evaluate(event: DeviceEvent, context: DeviceContext): Promise<JudgmentResult> {
82
- const prompt = buildPrompt(event, context, context.meta.pushesToday, this.config.pushBudgetPerDay);
83
-
84
- const [provider, ...modelParts] = this.config.llmModel.split("/");
85
- const model = modelParts.join("/");
86
-
87
- if (!provider || !model) {
88
- this.api.logger.warn("betterclaw: invalid llmModel config, defaulting to push");
89
- return { push: true, reason: "llm model not configured — fail open" };
90
- }
91
-
92
- let tmpDir: string | null = null;
93
- try {
94
- tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "betterclaw-judgment-"));
95
- const sessionId = `betterclaw-judgment-${Date.now()}`;
96
- const sessionFile = path.join(tmpDir, "session.json");
97
-
98
- const runEmbeddedPiAgent = await loadRunEmbeddedPiAgent();
99
-
100
- const result = await runEmbeddedPiAgent({
101
- sessionId,
102
- sessionFile,
103
- workspaceDir: (this.api as any).config?.agents?.defaults?.workspace ?? process.cwd(),
104
- config: (this.api as any).config,
105
- prompt,
106
- timeoutMs: 15_000,
107
- runId: `betterclaw-judgment-${Date.now()}`,
108
- provider,
109
- model,
110
- disableTools: true,
111
- });
112
-
113
- const text = extractText(result.payloads ?? []);
114
- if (!text) {
115
- this.api.logger.warn("betterclaw: LLM returned empty output, defaulting to push");
116
- return { push: true, reason: "llm returned empty — fail open" };
117
- }
118
-
119
- // Strip code fences if present
120
- const cleaned = text.replace(/^```(?:json)?\s*/i, "").replace(/\s*```$/i, "").trim();
121
-
122
- try {
123
- const parsed = JSON.parse(cleaned) as { push?: boolean; reason?: string };
124
- return {
125
- push: parsed.push === true,
126
- reason: typeof parsed.reason === "string" ? parsed.reason : "no reason given",
127
- };
128
- } catch {
129
- this.api.logger.warn(`betterclaw: LLM returned invalid JSON: ${text.slice(0, 200)}`);
130
- return { push: true, reason: "llm returned invalid json — fail open" };
131
- }
132
- } catch (err) {
133
- this.api.logger.error(`betterclaw: judgment call failed: ${err instanceof Error ? err.message : String(err)}`);
134
- return { push: true, reason: "llm call failed — fail open" };
135
- } finally {
136
- if (tmpDir) {
137
- try {
138
- await fs.rm(tmpDir, { recursive: true, force: true });
139
- } catch {
140
- // ignore
141
- }
142
- }
143
- }
144
- }
145
- }