entroly-openclaw 1.0.46
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/README.md +78 -0
- package/bridge-client.js +119 -0
- package/engine.js +140 -0
- package/index.js +60 -0
- package/openclaw.plugin.json +77 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Entroly for OpenClaw
|
|
2
|
+
|
|
3
|
+
Entroly provides budget-aware, auditable context assembly for OpenClaw while
|
|
4
|
+
leaving OpenClaw's persisted transcript untouched.
|
|
5
|
+
|
|
6
|
+
Unlike uniform history summarization, Entroly scores older messages against the
|
|
7
|
+
current request, reserves a bounded part of the context budget for matching
|
|
8
|
+
evidence, and keeps evidence messages verbatim when they fit. Lower-value
|
|
9
|
+
history is compressed around those evidence pins. The receipt records every
|
|
10
|
+
score, matched query term, allocation, and transformation.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
|
|
14
|
+
After the package is published:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install entroly
|
|
18
|
+
openclaw plugins install entroly-openclaw
|
|
19
|
+
openclaw plugins enable entroly
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
From an Entroly source checkout:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install entroly
|
|
26
|
+
openclaw plugins install ./integrations/openclaw
|
|
27
|
+
openclaw plugins enable entroly
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Select the engine in `~/.openclaw/openclaw.json`:
|
|
31
|
+
|
|
32
|
+
```json5
|
|
33
|
+
{
|
|
34
|
+
plugins: {
|
|
35
|
+
slots: {
|
|
36
|
+
contextEngine: "entroly"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Restart the Gateway and verify the loaded plugin with:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
openclaw plugins inspect entroly --runtime --json
|
|
46
|
+
openclaw plugins doctor
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
After the first agent turn, run `/entroly-context` in any connected channel to
|
|
50
|
+
see the estimated before/after context size, reduction, warnings, and receipt.
|
|
51
|
+
Run `/entroly-context doctor` to verify the configured Python executable and
|
|
52
|
+
local JSONL bridge before inviting users onto the Gateway.
|
|
53
|
+
|
|
54
|
+
## Reproduce the evidence-pinning control
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
python -m benchmarks.openclaw_evidence_pinning
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The committed synthetic workload compares query-aware evidence pinning with
|
|
61
|
+
uniform budget compression at the same estimated token budget. See the
|
|
62
|
+
[result JSON](../../benchmarks/results/openclaw_evidence_pinning.json). It uses
|
|
63
|
+
no model calls and does not claim downstream task accuracy.
|
|
64
|
+
|
|
65
|
+
Receipts are written under `<workspace>/.entroly/receipts/openclaw/` unless
|
|
66
|
+
`receiptDir` is configured. They record per-message hashes and decisions,
|
|
67
|
+
estimated tokens, reduction, warnings, and whether context changed. The
|
|
68
|
+
original content remains recoverable from OpenClaw's unchanged transcript.
|
|
69
|
+
Entroly makes no remote calls in this path.
|
|
70
|
+
|
|
71
|
+
## Safety contract
|
|
72
|
+
|
|
73
|
+
- System and developer messages are never modified.
|
|
74
|
+
- Structured message content is never modified.
|
|
75
|
+
- Recent messages are preserved verbatim.
|
|
76
|
+
- Bridge errors return the exact original message list.
|
|
77
|
+
- Entroly does not rewrite the OpenClaw transcript or claim persistent
|
|
78
|
+
compaction.
|
package/bridge-client.js
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import readline from "node:readline";
|
|
3
|
+
|
|
4
|
+
export class EntrolyBridgeClient {
|
|
5
|
+
constructor({
|
|
6
|
+
pythonCommand = "python",
|
|
7
|
+
timeoutMs = 5000,
|
|
8
|
+
logger = console,
|
|
9
|
+
spawnProcess = spawn,
|
|
10
|
+
} = {}) {
|
|
11
|
+
this.pythonCommand = pythonCommand;
|
|
12
|
+
this.timeoutMs = timeoutMs;
|
|
13
|
+
this.logger = logger;
|
|
14
|
+
this.spawnProcess = spawnProcess;
|
|
15
|
+
this.nextId = 1;
|
|
16
|
+
this.pending = new Map();
|
|
17
|
+
this.process = undefined;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
start() {
|
|
21
|
+
if (this.process && !this.process.killed) {
|
|
22
|
+
return this.process;
|
|
23
|
+
}
|
|
24
|
+
const child = this.spawnProcess(
|
|
25
|
+
this.pythonCommand,
|
|
26
|
+
["-m", "entroly.openclaw_bridge", "--jsonl"],
|
|
27
|
+
{ stdio: ["pipe", "pipe", "pipe"], windowsHide: true },
|
|
28
|
+
);
|
|
29
|
+
this.process = child;
|
|
30
|
+
const lines = readline.createInterface({ input: child.stdout });
|
|
31
|
+
lines.on("line", (line) => this.#onLine(line));
|
|
32
|
+
child.stderr.on("data", (chunk) => {
|
|
33
|
+
const message = String(chunk).trim();
|
|
34
|
+
if (message) this.logger.warn?.(`entroly bridge: ${message}`);
|
|
35
|
+
});
|
|
36
|
+
child.on("error", (error) => this.#handleChildFailure(child, error));
|
|
37
|
+
child.on("exit", (code, signal) => {
|
|
38
|
+
this.#handleChildFailure(
|
|
39
|
+
child,
|
|
40
|
+
new Error(`Entroly bridge exited (${code ?? signal ?? "unknown"})`),
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
return child;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
request(payload) {
|
|
47
|
+
const child = this.start();
|
|
48
|
+
const requestId = String(this.nextId++);
|
|
49
|
+
return new Promise((resolve, reject) => {
|
|
50
|
+
const timer = setTimeout(() => {
|
|
51
|
+
this.#terminate(
|
|
52
|
+
child,
|
|
53
|
+
new Error(`Entroly bridge timed out after ${this.timeoutMs}ms`),
|
|
54
|
+
);
|
|
55
|
+
}, this.timeoutMs);
|
|
56
|
+
this.pending.set(requestId, { resolve, reject, timer });
|
|
57
|
+
child.stdin.write(`${JSON.stringify({ ...payload, request_id: requestId })}\n`, (error) => {
|
|
58
|
+
if (!error) return;
|
|
59
|
+
this.#terminate(child, error);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
health() {
|
|
65
|
+
return this.request({ operation: "health" });
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#onLine(line) {
|
|
69
|
+
let response;
|
|
70
|
+
try {
|
|
71
|
+
response = JSON.parse(line);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
this.logger.warn?.(`entroly bridge emitted invalid JSON: ${error.message}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const pending = this.pending.get(String(response.request_id));
|
|
77
|
+
if (!pending) return;
|
|
78
|
+
clearTimeout(pending.timer);
|
|
79
|
+
this.pending.delete(String(response.request_id));
|
|
80
|
+
if (response.ok) pending.resolve(response);
|
|
81
|
+
else pending.reject(new Error(response.error || "Entroly bridge request failed"));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
#failAll(error) {
|
|
85
|
+
for (const pending of this.pending.values()) {
|
|
86
|
+
clearTimeout(pending.timer);
|
|
87
|
+
pending.reject(error);
|
|
88
|
+
}
|
|
89
|
+
this.pending.clear();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
#handleChildFailure(child, error) {
|
|
93
|
+
if (this.process !== child) return;
|
|
94
|
+
this.process = undefined;
|
|
95
|
+
this.#failAll(error);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
#terminate(child, error) {
|
|
99
|
+
if (this.process !== child) return;
|
|
100
|
+
this.process = undefined;
|
|
101
|
+
this.#failAll(error);
|
|
102
|
+
child.stdin.destroy();
|
|
103
|
+
if (!child.killed) child.kill();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async dispose() {
|
|
107
|
+
const child = this.process;
|
|
108
|
+
this.process = undefined;
|
|
109
|
+
if (!child || child.killed) return;
|
|
110
|
+
this.#failAll(new Error("Entroly bridge disposed"));
|
|
111
|
+
child.stdin.end();
|
|
112
|
+
// Brief grace for the process to exit on stdin EOF before SIGTERM.
|
|
113
|
+
await new Promise((resolve) => {
|
|
114
|
+
const timer = setTimeout(() => resolve(), 200);
|
|
115
|
+
child.on("exit", () => { clearTimeout(timer); resolve(); });
|
|
116
|
+
});
|
|
117
|
+
if (!child.killed) child.kill();
|
|
118
|
+
}
|
|
119
|
+
}
|
package/engine.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const DEFAULT_TOKEN_BUDGET = 50_000;
|
|
2
|
+
|
|
3
|
+
function estimateTokens(messages) {
|
|
4
|
+
return Math.ceil(JSON.stringify(messages).length / 4);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function formatEntrolyStatus(status) {
|
|
8
|
+
if (!status) {
|
|
9
|
+
return "Entroly: no context assembly has completed for this session yet.";
|
|
10
|
+
}
|
|
11
|
+
if (!status.ok) {
|
|
12
|
+
return `Entroly: last assembly failed open to the original context.\nReason: ${status.error}`;
|
|
13
|
+
}
|
|
14
|
+
const source = status.source_tokens ?? 0;
|
|
15
|
+
const assembled = status.estimated_tokens ?? source;
|
|
16
|
+
const saved = status.tokens_saved ?? Math.max(0, source - assembled);
|
|
17
|
+
const reduction = source > 0 ? ((saved / source) * 100).toFixed(1) : "0.0";
|
|
18
|
+
const lines = [
|
|
19
|
+
"Entroly protected the last context assembly",
|
|
20
|
+
`Strategy: ${status.assembly_strategy ?? "budgeted_context"}`,
|
|
21
|
+
`Evidence pinned verbatim: ${status.evidence_pinned ?? 0} message(s)`,
|
|
22
|
+
`Evidence pins blocked by firewall: ${status.evidence_pin_blocked ?? 0}`,
|
|
23
|
+
`Estimated tokens: ${source.toLocaleString()} -> ${assembled.toLocaleString()}`,
|
|
24
|
+
`Estimated reduction: ${reduction}% (${saved.toLocaleString()} tokens)`,
|
|
25
|
+
`Changed: ${status.changed ? "yes" : "no"}`,
|
|
26
|
+
];
|
|
27
|
+
if (status.receipt_id) lines.push(`Receipt: ${status.receipt_id}`);
|
|
28
|
+
if (status.warnings?.length) lines.push(`Warnings: ${status.warnings.join(" | ")}`);
|
|
29
|
+
return lines.join("\n");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function formatEntrolyDoctor({ ok, error, pythonCommand = "python" }) {
|
|
33
|
+
if (ok) {
|
|
34
|
+
return [
|
|
35
|
+
"Entroly doctor: ready",
|
|
36
|
+
`Python command: ${pythonCommand}`,
|
|
37
|
+
"Bridge: responsive",
|
|
38
|
+
"Local-only context assembly: available",
|
|
39
|
+
].join("\n");
|
|
40
|
+
}
|
|
41
|
+
const reason = String(error?.message ?? error ?? "unknown error").replace(/\s+/g, " ");
|
|
42
|
+
return [
|
|
43
|
+
"Entroly doctor: not ready",
|
|
44
|
+
`Python command: ${pythonCommand}`,
|
|
45
|
+
`Reason: ${reason}`,
|
|
46
|
+
"Fix: install Entroly into that Python environment with `python -m pip install -U entroly`,",
|
|
47
|
+
"or set plugins.entries.entroly.config.pythonCommand to the correct Python executable.",
|
|
48
|
+
].join("\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function createEntrolyContextEngine({
|
|
52
|
+
bridge,
|
|
53
|
+
config = {},
|
|
54
|
+
logger = console,
|
|
55
|
+
statusBySession = new Map(),
|
|
56
|
+
}) {
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
info: {
|
|
60
|
+
id: "entroly",
|
|
61
|
+
name: "Entroly Context Engine",
|
|
62
|
+
ownsCompaction: false,
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
async ingest() {
|
|
66
|
+
return { ingested: false };
|
|
67
|
+
},
|
|
68
|
+
|
|
69
|
+
async ingestBatch() {
|
|
70
|
+
return { ingestedCount: 0 };
|
|
71
|
+
},
|
|
72
|
+
|
|
73
|
+
async assemble({
|
|
74
|
+
sessionId,
|
|
75
|
+
messages,
|
|
76
|
+
tokenBudget,
|
|
77
|
+
model,
|
|
78
|
+
prompt,
|
|
79
|
+
runtimeSettings,
|
|
80
|
+
}) {
|
|
81
|
+
const sourceMessages = Array.isArray(messages) ? messages : [];
|
|
82
|
+
const effectiveBudget =
|
|
83
|
+
tokenBudget ?? runtimeSettings?.limits?.promptTokenBudget ?? DEFAULT_TOKEN_BUDGET;
|
|
84
|
+
try {
|
|
85
|
+
const result = await bridge.request({
|
|
86
|
+
operation: "assemble",
|
|
87
|
+
session_id: sessionId,
|
|
88
|
+
messages: sourceMessages,
|
|
89
|
+
token_budget: effectiveBudget,
|
|
90
|
+
model,
|
|
91
|
+
prompt,
|
|
92
|
+
workspace_dir: config.workspaceDir,
|
|
93
|
+
preserve_last_n: config.preserveLastN ?? 4,
|
|
94
|
+
receipt_dir: config.receiptDir,
|
|
95
|
+
write_receipt: config.writeReceipts !== false,
|
|
96
|
+
distill: config.distill !== false,
|
|
97
|
+
evidence_pinning: config.evidencePinning !== false,
|
|
98
|
+
});
|
|
99
|
+
statusBySession.set(sessionId, result);
|
|
100
|
+
return {
|
|
101
|
+
messages: result.messages,
|
|
102
|
+
estimatedTokens: result.estimated_tokens,
|
|
103
|
+
promptAuthority: "assembled",
|
|
104
|
+
};
|
|
105
|
+
} catch (error) {
|
|
106
|
+
logger.warn?.(
|
|
107
|
+
`entroly: assembly failed; passing exact original context: ${error.message}`,
|
|
108
|
+
);
|
|
109
|
+
statusBySession.set(sessionId, {
|
|
110
|
+
ok: false,
|
|
111
|
+
error: error.message,
|
|
112
|
+
estimated_tokens: estimateTokens(sourceMessages),
|
|
113
|
+
});
|
|
114
|
+
return {
|
|
115
|
+
messages: sourceMessages,
|
|
116
|
+
estimatedTokens: estimateTokens(sourceMessages),
|
|
117
|
+
promptAuthority: "preassembly_may_overflow",
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
async compact({ currentTokenCount }) {
|
|
123
|
+
return {
|
|
124
|
+
ok: true,
|
|
125
|
+
compacted: false,
|
|
126
|
+
reason:
|
|
127
|
+
"Entroly applies reversible per-turn context assembly and does not rewrite the OpenClaw transcript.",
|
|
128
|
+
result: { tokensBefore: currentTokenCount ?? 0 },
|
|
129
|
+
};
|
|
130
|
+
},
|
|
131
|
+
|
|
132
|
+
getStatus(sessionId) {
|
|
133
|
+
return statusBySession.get(sessionId);
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
async dispose() {
|
|
137
|
+
await bridge.dispose();
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
package/index.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
import { EntrolyBridgeClient } from "./bridge-client.js";
|
|
3
|
+
import {
|
|
4
|
+
createEntrolyContextEngine,
|
|
5
|
+
formatEntrolyDoctor,
|
|
6
|
+
formatEntrolyStatus,
|
|
7
|
+
} from "./engine.js";
|
|
8
|
+
|
|
9
|
+
export default definePluginEntry({
|
|
10
|
+
id: "entroly",
|
|
11
|
+
name: "Entroly Context Engine",
|
|
12
|
+
register(api) {
|
|
13
|
+
const config = api.pluginConfig ?? {};
|
|
14
|
+
const bridge = new EntrolyBridgeClient({
|
|
15
|
+
pythonCommand: config.pythonCommand ?? "python",
|
|
16
|
+
timeoutMs: config.timeoutMs ?? 5000,
|
|
17
|
+
logger: api.logger,
|
|
18
|
+
});
|
|
19
|
+
const statusBySession = new Map();
|
|
20
|
+
api.registerContextEngine("entroly", (factoryContext) =>
|
|
21
|
+
createEntrolyContextEngine({
|
|
22
|
+
bridge,
|
|
23
|
+
config: { ...config, workspaceDir: factoryContext.workspaceDir },
|
|
24
|
+
logger: api.logger,
|
|
25
|
+
statusBySession,
|
|
26
|
+
}),
|
|
27
|
+
);
|
|
28
|
+
api.registerCommand({
|
|
29
|
+
name: "entroly-context",
|
|
30
|
+
description: "Show Entroly context savings or run `doctor`.",
|
|
31
|
+
acceptsArgs: true,
|
|
32
|
+
handler: async (ctx) => {
|
|
33
|
+
if (ctx.args?.trim().toLowerCase() === "doctor") {
|
|
34
|
+
try {
|
|
35
|
+
await bridge.health();
|
|
36
|
+
return {
|
|
37
|
+
text: formatEntrolyDoctor({
|
|
38
|
+
ok: true,
|
|
39
|
+
pythonCommand: config.pythonCommand ?? "python",
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return {
|
|
44
|
+
text: formatEntrolyDoctor({
|
|
45
|
+
ok: false,
|
|
46
|
+
error,
|
|
47
|
+
pythonCommand: config.pythonCommand ?? "python",
|
|
48
|
+
}),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
text: formatEntrolyStatus(
|
|
54
|
+
ctx.sessionId ? statusBySession.get(ctx.sessionId) : undefined,
|
|
55
|
+
),
|
|
56
|
+
};
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
});
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "entroly",
|
|
3
|
+
"activation": {
|
|
4
|
+
"onStartup": true
|
|
5
|
+
},
|
|
6
|
+
"name": "Entroly Context Engine",
|
|
7
|
+
"description": "Selects and compresses context under budget while preserving protected messages and writing local audit receipts.",
|
|
8
|
+
"configSchema": {
|
|
9
|
+
"type": "object",
|
|
10
|
+
"additionalProperties": false,
|
|
11
|
+
"properties": {
|
|
12
|
+
"pythonCommand": {
|
|
13
|
+
"type": "string",
|
|
14
|
+
"minLength": 1,
|
|
15
|
+
"default": "python"
|
|
16
|
+
},
|
|
17
|
+
"preserveLastN": {
|
|
18
|
+
"type": "integer",
|
|
19
|
+
"minimum": 1,
|
|
20
|
+
"maximum": 32,
|
|
21
|
+
"default": 4
|
|
22
|
+
},
|
|
23
|
+
"timeoutMs": {
|
|
24
|
+
"type": "integer",
|
|
25
|
+
"minimum": 100,
|
|
26
|
+
"maximum": 30000,
|
|
27
|
+
"default": 5000
|
|
28
|
+
},
|
|
29
|
+
"receiptDir": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"minLength": 1
|
|
32
|
+
},
|
|
33
|
+
"writeReceipts": {
|
|
34
|
+
"type": "boolean",
|
|
35
|
+
"default": true
|
|
36
|
+
},
|
|
37
|
+
"distill": {
|
|
38
|
+
"type": "boolean",
|
|
39
|
+
"default": true
|
|
40
|
+
},
|
|
41
|
+
"evidencePinning": {
|
|
42
|
+
"type": "boolean",
|
|
43
|
+
"default": true
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"uiHints": {
|
|
48
|
+
"pythonCommand": {
|
|
49
|
+
"label": "Python Command",
|
|
50
|
+
"help": "Python executable where the entroly package is installed."
|
|
51
|
+
},
|
|
52
|
+
"preserveLastN": {
|
|
53
|
+
"label": "Recent Messages Preserved",
|
|
54
|
+
"help": "Number of recent messages kept byte-for-byte unchanged."
|
|
55
|
+
},
|
|
56
|
+
"timeoutMs": {
|
|
57
|
+
"label": "Bridge Timeout (ms)",
|
|
58
|
+
"help": "Maximum time for one Entroly context assembly request."
|
|
59
|
+
},
|
|
60
|
+
"receiptDir": {
|
|
61
|
+
"label": "Receipt Directory",
|
|
62
|
+
"help": "Optional local directory for Entroly OpenClaw receipts."
|
|
63
|
+
},
|
|
64
|
+
"writeReceipts": {
|
|
65
|
+
"label": "Write Local Receipts",
|
|
66
|
+
"help": "Record context hashes, estimated savings, warnings, and decision metadata locally."
|
|
67
|
+
},
|
|
68
|
+
"distill": {
|
|
69
|
+
"label": "Distill Older Assistant Messages",
|
|
70
|
+
"help": "Remove filler from compressible older assistant responses before budget compression."
|
|
71
|
+
},
|
|
72
|
+
"evidencePinning": {
|
|
73
|
+
"label": "Query-Aware Evidence Pinning",
|
|
74
|
+
"help": "Keep older messages that match the current request verbatim when they fit the bounded evidence reserve."
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "entroly-openclaw",
|
|
3
|
+
"version": "1.0.46",
|
|
4
|
+
"description": "Auditable, budget-aware Entroly context engine for OpenClaw",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=22.19"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"bridge-client.js",
|
|
12
|
+
"engine.js",
|
|
13
|
+
"index.js",
|
|
14
|
+
"openclaw.plugin.json",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"test": "node --test test/*.test.js",
|
|
19
|
+
"check": "node --check bridge-client.js && node --check engine.js && node --check index.js",
|
|
20
|
+
"pack:check": "npm pack --dry-run"
|
|
21
|
+
},
|
|
22
|
+
"peerDependencies": {
|
|
23
|
+
"openclaw": ">=2026.6.11"
|
|
24
|
+
},
|
|
25
|
+
"peerDependenciesMeta": {
|
|
26
|
+
"openclaw": {
|
|
27
|
+
"optional": true
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"openclaw": {
|
|
31
|
+
"extensions": [
|
|
32
|
+
"./index.js"
|
|
33
|
+
],
|
|
34
|
+
"install": {
|
|
35
|
+
"npmSpec": "entroly-openclaw",
|
|
36
|
+
"defaultChoice": "npm",
|
|
37
|
+
"minHostVersion": ">=2026.6.11"
|
|
38
|
+
},
|
|
39
|
+
"compat": {
|
|
40
|
+
"pluginApi": ">=2026.6.11"
|
|
41
|
+
},
|
|
42
|
+
"release": {
|
|
43
|
+
"publishToClawHub": true,
|
|
44
|
+
"publishToNpm": true,
|
|
45
|
+
"bundleRuntimeDependencies": false
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|