entroly-openclaw 1.0.53 → 1.0.57
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 +81 -11
- package/bridge-client.js +33 -3
- package/engine.js +428 -39
- package/index.js +17 -5
- package/openclaw.plugin.json +31 -1
- package/package.json +17 -3
package/README.md
CHANGED
|
@@ -3,26 +3,41 @@
|
|
|
3
3
|
Entroly provides budget-aware, auditable context assembly for OpenClaw while
|
|
4
4
|
leaving OpenClaw's persisted transcript untouched.
|
|
5
5
|
|
|
6
|
+
**Every OpenClaw provider benefits from the same Entroly context engine.**
|
|
7
|
+
OpenClaw continues to own provider selection, authentication, model failover,
|
|
8
|
+
and wire formats. Entroly operates once on OpenClaw's normalized messages
|
|
9
|
+
before each model run, whether the route is OpenAI, Anthropic, Gemini,
|
|
10
|
+
Nemotron, OpenRouter, Ollama, or a private/custom provider.
|
|
11
|
+
|
|
6
12
|
Unlike uniform history summarization, Entroly scores older messages against the
|
|
7
13
|
current request, reserves a bounded part of the context budget for matching
|
|
8
14
|
evidence, and keeps evidence messages verbatim when they fit. Lower-value
|
|
9
15
|
history is compressed around those evidence pins. The receipt records every
|
|
10
|
-
score,
|
|
16
|
+
score, query-term match count, allocation, and transformation without storing
|
|
17
|
+
the request text or matched terms.
|
|
11
18
|
|
|
12
|
-
## Install
|
|
19
|
+
## Install from ClawHub
|
|
13
20
|
|
|
14
|
-
|
|
21
|
+
Install the Python engine, then install the plugin from OpenClaw's official
|
|
22
|
+
ClawHub registry:
|
|
15
23
|
|
|
16
24
|
```bash
|
|
17
|
-
pip install entroly
|
|
18
|
-
openclaw plugins install entroly-openclaw
|
|
25
|
+
pip install "entroly>=1.0.57"
|
|
26
|
+
openclaw plugins install clawhub:entroly-openclaw
|
|
27
|
+
openclaw plugins enable entroly
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The npm-only fallback remains available as:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
openclaw plugins install npm:entroly-openclaw
|
|
19
34
|
openclaw plugins enable entroly
|
|
20
35
|
```
|
|
21
36
|
|
|
22
37
|
From an Entroly source checkout:
|
|
23
38
|
|
|
24
39
|
```bash
|
|
25
|
-
pip install entroly
|
|
40
|
+
pip install "entroly>=1.0.57"
|
|
26
41
|
openclaw plugins install ./integrations/openclaw
|
|
27
42
|
openclaw plugins enable entroly
|
|
28
43
|
```
|
|
@@ -49,7 +64,27 @@ openclaw plugins doctor
|
|
|
49
64
|
After the first agent turn, run `/entroly-context` in any connected channel to
|
|
50
65
|
see the estimated before/after context size, reduction, warnings, and receipt.
|
|
51
66
|
Run `/entroly-context doctor` to verify the configured Python executable and
|
|
52
|
-
local JSONL bridge before inviting users onto the Gateway.
|
|
67
|
+
local JSONL bridge before inviting users onto the Gateway. The plugin requires
|
|
68
|
+
the Entroly 1.0.57 bridge v2 protocol; doctor reports an actionable upgrade
|
|
69
|
+
instead of accepting an older, incompatible Python installation.
|
|
70
|
+
|
|
71
|
+
OpenClaw's resolved prompt token budget is authoritative. Entroly never guesses
|
|
72
|
+
a context window from the provider name. For a custom model whose limit
|
|
73
|
+
OpenClaw cannot resolve, configure that model's context window in OpenClaw or
|
|
74
|
+
set an explicit operator-approved fallback:
|
|
75
|
+
|
|
76
|
+
```json5
|
|
77
|
+
{
|
|
78
|
+
plugins: {
|
|
79
|
+
entries: {
|
|
80
|
+
entroly: { config: { fallbackTokenBudget: 32768 } }
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Without either budget, Entroly returns the exact original context with an
|
|
87
|
+
actionable warning instead of risking a provider overflow.
|
|
53
88
|
|
|
54
89
|
## Reproduce the evidence-pinning control
|
|
55
90
|
|
|
@@ -63,16 +98,51 @@ uniform budget compression at the same estimated token budget. See the
|
|
|
63
98
|
no model calls and does not claim downstream task accuracy.
|
|
64
99
|
|
|
65
100
|
Receipts are written under `<workspace>/.entroly/receipts/openclaw/` unless
|
|
66
|
-
`receiptDir` is configured. They record per-message
|
|
67
|
-
estimated tokens, reduction, warnings, and whether context changed.
|
|
68
|
-
|
|
101
|
+
`receiptDir` is configured. They record keyed per-message digests and decisions,
|
|
102
|
+
estimated tokens, reduction, warnings, and whether context changed. Query text
|
|
103
|
+
and matched terms are not persisted; only lengths and counts remain. Each
|
|
104
|
+
append-only proposal has a cryptographic nonce and immutable payload digest. It
|
|
105
|
+
is first marked `proposed`, then atomically marked `accepted` only after the
|
|
106
|
+
plugin validates the returned message structure and reveals its precommitted
|
|
107
|
+
256-bit challenge. Entroly stores only a host-commit digest plus an HMAC
|
|
108
|
+
signature from a private key outside the workspace receipt; it never persists
|
|
109
|
+
the challenge secret. Acceptance proves plugin validation, not provider
|
|
110
|
+
delivery. Content digests are HMAC-keyed as well, preventing offline guesses of
|
|
111
|
+
low-entropy user prompts from a receipt alone.
|
|
112
|
+
The original content remains recoverable from OpenClaw's unchanged transcript.
|
|
69
113
|
Entroly makes no remote calls in this path.
|
|
70
114
|
|
|
115
|
+
The signing key is initialized atomically outside the workspace receipt store
|
|
116
|
+
under the user state directory (`%LOCALAPPDATA%/Entroly` on Windows or
|
|
117
|
+
`$XDG_STATE_HOME/entroly` on Unix). Controlled deployments may set
|
|
118
|
+
`ENTROLY_OPENCLAW_RECEIPT_KEY_FILE` to an absolute protected path. Entroly
|
|
119
|
+
quarantines an interrupted empty initialization artifact; a non-empty corrupt
|
|
120
|
+
key fails open with its exact recovery path instead of silently rotating and
|
|
121
|
+
invalidating earlier receipt signatures. `/entroly-context doctor` checks this
|
|
122
|
+
key lifecycle as part of bridge readiness.
|
|
123
|
+
|
|
124
|
+
The receipt directory defaults to private `0700` permissions and receipt files
|
|
125
|
+
to `0600` on Unix; Windows uses the workspace ACL. Entroly never auto-deletes
|
|
126
|
+
audit history. The default append-only quota is 512 files or 64 MiB, whichever
|
|
127
|
+
comes first. At the limit, assembly fails open with an actionable message;
|
|
128
|
+
archive receipts or raise `receiptMaxFiles` / `receiptMaxBytes` explicitly.
|
|
129
|
+
|
|
71
130
|
## Safety contract
|
|
72
131
|
|
|
73
132
|
- System and developer messages are never modified.
|
|
74
|
-
-
|
|
133
|
+
- Signed text, thinking/reasoning, images, tool calls, tool-result metadata,
|
|
134
|
+
opaque provider signatures, and unknown content blocks are never modified.
|
|
135
|
+
- Only unsigned text fields in older normalized messages are compressible;
|
|
136
|
+
provider/model/usage metadata cannot change selection or allocation.
|
|
75
137
|
- Recent messages are preserved verbatim.
|
|
76
138
|
- Bridge errors return the exact original message list.
|
|
139
|
+
- A malformed host message envelope is surfaced as an error; it is never
|
|
140
|
+
coerced into an empty prompt.
|
|
141
|
+
- If the minimum safe normalized context cannot fit the host budget, Entroly
|
|
142
|
+
returns the exact original context and requests OpenClaw recovery.
|
|
77
143
|
- Entroly does not rewrite the OpenClaw transcript or claim persistent
|
|
78
144
|
compaction.
|
|
145
|
+
- `/compact` and provider-overflow recovery delegate to OpenClaw's native
|
|
146
|
+
compaction runtime, preserving its provider-aware retry behavior.
|
|
147
|
+
- Active OpenClaw memory guidance remains present when Entroly is selected as
|
|
148
|
+
the context engine.
|
package/bridge-client.js
CHANGED
|
@@ -1,17 +1,36 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import readline from "node:readline";
|
|
3
3
|
|
|
4
|
+
export const ENTROLY_BRIDGE_SCHEMA = "entroly.openclaw.bridge.v2";
|
|
5
|
+
|
|
6
|
+
export function validateBridgeHealth(result) {
|
|
7
|
+
if (
|
|
8
|
+
!result ||
|
|
9
|
+
result.ok !== true ||
|
|
10
|
+
result.schema_version !== ENTROLY_BRIDGE_SCHEMA ||
|
|
11
|
+
result.provider_independent !== true ||
|
|
12
|
+
result.receipt_commit_protocol !== "two_phase"
|
|
13
|
+
) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"Incompatible Entroly Python bridge; install entroly>=1.0.57 with `python -m pip install -U entroly`",
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
|
|
4
21
|
export class EntrolyBridgeClient {
|
|
5
22
|
constructor({
|
|
6
23
|
pythonCommand = "python",
|
|
7
24
|
timeoutMs = 5000,
|
|
8
25
|
logger = console,
|
|
9
26
|
spawnProcess = spawn,
|
|
27
|
+
environment = process.env,
|
|
10
28
|
} = {}) {
|
|
11
29
|
this.pythonCommand = pythonCommand;
|
|
12
30
|
this.timeoutMs = timeoutMs;
|
|
13
31
|
this.logger = logger;
|
|
14
32
|
this.spawnProcess = spawnProcess;
|
|
33
|
+
this.environment = environment;
|
|
15
34
|
this.nextId = 1;
|
|
16
35
|
this.pending = new Map();
|
|
17
36
|
this.process = undefined;
|
|
@@ -24,7 +43,11 @@ export class EntrolyBridgeClient {
|
|
|
24
43
|
const child = this.spawnProcess(
|
|
25
44
|
this.pythonCommand,
|
|
26
45
|
["-m", "entroly.openclaw_bridge", "--jsonl"],
|
|
27
|
-
{
|
|
46
|
+
{
|
|
47
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
48
|
+
windowsHide: true,
|
|
49
|
+
env: this.environment,
|
|
50
|
+
},
|
|
28
51
|
);
|
|
29
52
|
this.process = child;
|
|
30
53
|
const lines = readline.createInterface({ input: child.stdout });
|
|
@@ -61,8 +84,15 @@ export class EntrolyBridgeClient {
|
|
|
61
84
|
});
|
|
62
85
|
}
|
|
63
86
|
|
|
64
|
-
health() {
|
|
65
|
-
return
|
|
87
|
+
async health({ workspaceDir, receiptDir, writeReceipts = true } = {}) {
|
|
88
|
+
return validateBridgeHealth(
|
|
89
|
+
await this.request({
|
|
90
|
+
operation: "health",
|
|
91
|
+
workspace_dir: workspaceDir,
|
|
92
|
+
receipt_dir: receiptDir,
|
|
93
|
+
write_receipt: writeReceipts,
|
|
94
|
+
}),
|
|
95
|
+
);
|
|
66
96
|
}
|
|
67
97
|
|
|
68
98
|
#onLine(line) {
|
package/engine.js
CHANGED
|
@@ -1,4 +1,269 @@
|
|
|
1
|
-
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { ENTROLY_BRIDGE_SCHEMA } from "./bridge-client.js";
|
|
4
|
+
|
|
5
|
+
const PROVIDER_MODE = "openclaw_managed";
|
|
6
|
+
|
|
7
|
+
function positiveInteger(value) {
|
|
8
|
+
return typeof value === "number" && Number.isInteger(value) && value > 0
|
|
9
|
+
? value
|
|
10
|
+
: undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function boundedString(value, limit = 256) {
|
|
14
|
+
return typeof value === "string" && value.trim()
|
|
15
|
+
? value.trim().slice(0, limit)
|
|
16
|
+
: null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function safeDiagnostic(value, limit = 400) {
|
|
20
|
+
let diagnostic = String(value?.message ?? value ?? "unknown error")
|
|
21
|
+
.replace(/\s+/g, " ")
|
|
22
|
+
.trim();
|
|
23
|
+
diagnostic = diagnostic
|
|
24
|
+
.replace(
|
|
25
|
+
/\bAuthorization\s*:\s*(?:Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi,
|
|
26
|
+
"Authorization: [REDACTED]",
|
|
27
|
+
)
|
|
28
|
+
.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]")
|
|
29
|
+
.replace(
|
|
30
|
+
/\b(api[_-]?key|authorization|password|secret|token)\b\s*[:=]\s*["']?[^\s,;"']+/gi,
|
|
31
|
+
"$1=[REDACTED]",
|
|
32
|
+
)
|
|
33
|
+
.replace(/\b(?:sk|gh[opusr])[-_][A-Za-z0-9_-]{8,}\b/gi, "[REDACTED]");
|
|
34
|
+
return diagnostic.slice(0, limit) || "unknown error";
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function statusSnapshot(status) {
|
|
38
|
+
const snapshot = { ok: status?.ok === true };
|
|
39
|
+
const numericFields = [
|
|
40
|
+
"estimated_tokens",
|
|
41
|
+
"source_tokens",
|
|
42
|
+
"tokens_saved",
|
|
43
|
+
"evidence_pinned",
|
|
44
|
+
"evidence_pin_blocked",
|
|
45
|
+
];
|
|
46
|
+
const booleanFields = ["changed", "provider_independent"];
|
|
47
|
+
const stringFields = [
|
|
48
|
+
"schema_version",
|
|
49
|
+
"receipt_id",
|
|
50
|
+
"provider_mode",
|
|
51
|
+
"budget_source",
|
|
52
|
+
"model",
|
|
53
|
+
"provider_hint",
|
|
54
|
+
"assembly_strategy",
|
|
55
|
+
"error",
|
|
56
|
+
];
|
|
57
|
+
for (const field of numericFields) {
|
|
58
|
+
if (typeof status?.[field] === "number" && Number.isFinite(status[field])) {
|
|
59
|
+
snapshot[field] = status[field];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
for (const field of booleanFields) {
|
|
63
|
+
if (typeof status?.[field] === "boolean") snapshot[field] = status[field];
|
|
64
|
+
}
|
|
65
|
+
for (const field of stringFields) {
|
|
66
|
+
if (status?.[field] === undefined || status?.[field] === null) continue;
|
|
67
|
+
const value =
|
|
68
|
+
field === "error"
|
|
69
|
+
? safeDiagnostic(status?.[field])
|
|
70
|
+
: boundedString(status?.[field], field === "receipt_id" ? 160 : 256);
|
|
71
|
+
if (value !== null) snapshot[field] = value;
|
|
72
|
+
}
|
|
73
|
+
if (Array.isArray(status?.warnings)) {
|
|
74
|
+
snapshot.warnings = status.warnings
|
|
75
|
+
.slice(0, 8)
|
|
76
|
+
.map((warning) => safeDiagnostic(warning));
|
|
77
|
+
}
|
|
78
|
+
return snapshot;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function validateReceiptCommit(result, proposal) {
|
|
82
|
+
if (
|
|
83
|
+
!result ||
|
|
84
|
+
result.ok !== true ||
|
|
85
|
+
result.schema_version !== ENTROLY_BRIDGE_SCHEMA ||
|
|
86
|
+
result.committed !== true ||
|
|
87
|
+
result.receipt_id !== proposal.receiptId ||
|
|
88
|
+
result.proposal_id !== proposal.proposalId ||
|
|
89
|
+
result.proposal_sha256 !== proposal.proposalSha256 ||
|
|
90
|
+
result.receipt_path !== proposal.receiptPath ||
|
|
91
|
+
result.acceptance_commit_sha256 !== proposal.acceptanceCommitSha256
|
|
92
|
+
) {
|
|
93
|
+
throw new Error("Entroly bridge did not acknowledge the validated receipt");
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function sha256(value) {
|
|
98
|
+
return createHash("sha256").update(value, "utf8").digest("hex");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function resolveAssemblyRuntime({ tokenBudget, model, runtimeSettings, fallbackTokenBudget }) {
|
|
102
|
+
const explicitBudget = positiveInteger(tokenBudget);
|
|
103
|
+
const runtimeBudget = positiveInteger(runtimeSettings?.limits?.promptTokenBudget);
|
|
104
|
+
const fallbackCandidate = positiveInteger(fallbackTokenBudget);
|
|
105
|
+
const configuredFallback =
|
|
106
|
+
fallbackCandidate !== undefined && fallbackCandidate >= 1024
|
|
107
|
+
? fallbackCandidate
|
|
108
|
+
: undefined;
|
|
109
|
+
const requestedModel = boundedString(runtimeSettings?.model?.requested);
|
|
110
|
+
const resolvedModel =
|
|
111
|
+
boundedString(runtimeSettings?.model?.resolved) ?? requestedModel ?? boundedString(model);
|
|
112
|
+
const runtimeMetadata = {
|
|
113
|
+
schema_version: runtimeSettings?.schemaVersion === 1 ? 1 : null,
|
|
114
|
+
runtime: {
|
|
115
|
+
host: boundedString(runtimeSettings?.runtime?.host, 64),
|
|
116
|
+
mode: boundedString(runtimeSettings?.runtime?.mode, 64),
|
|
117
|
+
harness_id: boundedString(runtimeSettings?.runtime?.harnessId, 128),
|
|
118
|
+
runtime_id: boundedString(runtimeSettings?.runtime?.runtimeId, 128),
|
|
119
|
+
},
|
|
120
|
+
model: {
|
|
121
|
+
requested: requestedModel ?? boundedString(model),
|
|
122
|
+
resolved: resolvedModel,
|
|
123
|
+
provider: boundedString(runtimeSettings?.model?.provider, 128),
|
|
124
|
+
family: boundedString(runtimeSettings?.model?.family, 128),
|
|
125
|
+
},
|
|
126
|
+
limits: {
|
|
127
|
+
prompt_token_budget: runtimeBudget ?? null,
|
|
128
|
+
max_output_tokens: positiveInteger(runtimeSettings?.limits?.maxOutputTokens) ?? null,
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
if (explicitBudget !== undefined) {
|
|
133
|
+
return {
|
|
134
|
+
tokenBudget: explicitBudget,
|
|
135
|
+
budgetSource: "openclaw_token_budget",
|
|
136
|
+
model: resolvedModel,
|
|
137
|
+
runtimeMetadata,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (runtimeBudget !== undefined) {
|
|
141
|
+
return {
|
|
142
|
+
tokenBudget: runtimeBudget,
|
|
143
|
+
budgetSource: "openclaw_runtime_settings",
|
|
144
|
+
model: resolvedModel,
|
|
145
|
+
runtimeMetadata,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (configuredFallback !== undefined) {
|
|
149
|
+
return {
|
|
150
|
+
tokenBudget: configuredFallback,
|
|
151
|
+
budgetSource: "operator_fallback",
|
|
152
|
+
model: resolvedModel,
|
|
153
|
+
runtimeMetadata,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
tokenBudget: undefined,
|
|
158
|
+
budgetSource: "missing",
|
|
159
|
+
model: resolvedModel,
|
|
160
|
+
runtimeMetadata,
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function canonicalJson(value) {
|
|
165
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
166
|
+
if (value && typeof value === "object") {
|
|
167
|
+
return `{${Object.keys(value)
|
|
168
|
+
.sort()
|
|
169
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
170
|
+
.join(",")}}`;
|
|
171
|
+
}
|
|
172
|
+
return JSON.stringify(value);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function isCompressibleTextBlock(block) {
|
|
176
|
+
return Boolean(
|
|
177
|
+
block &&
|
|
178
|
+
typeof block === "object" &&
|
|
179
|
+
!Array.isArray(block) &&
|
|
180
|
+
block.type === "text" &&
|
|
181
|
+
typeof block.text === "string" &&
|
|
182
|
+
Object.keys(block).every((key) => key === "type" || key === "text"),
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function validateAssemblyResult(result, sourceMessages, preserveLastN) {
|
|
187
|
+
if (
|
|
188
|
+
!result ||
|
|
189
|
+
result.ok !== true ||
|
|
190
|
+
result.schema_version !== ENTROLY_BRIDGE_SCHEMA
|
|
191
|
+
) {
|
|
192
|
+
throw new Error("Entroly bridge did not return a successful assembly result");
|
|
193
|
+
}
|
|
194
|
+
if (
|
|
195
|
+
!Array.isArray(result.messages) ||
|
|
196
|
+
result.messages.length !== sourceMessages.length ||
|
|
197
|
+
!result.messages.every(
|
|
198
|
+
(message) => message && typeof message === "object" && !Array.isArray(message),
|
|
199
|
+
)
|
|
200
|
+
) {
|
|
201
|
+
throw new Error("Entroly bridge returned an invalid or incomplete message list");
|
|
202
|
+
}
|
|
203
|
+
if (
|
|
204
|
+
typeof result.estimated_tokens !== "number" ||
|
|
205
|
+
!Number.isFinite(result.estimated_tokens) ||
|
|
206
|
+
result.estimated_tokens < 0
|
|
207
|
+
) {
|
|
208
|
+
throw new Error("Entroly bridge returned an invalid token estimate");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const recentStart = Math.max(0, sourceMessages.length - preserveLastN);
|
|
212
|
+
for (let index = 0; index < sourceMessages.length; index += 1) {
|
|
213
|
+
const source = sourceMessages[index];
|
|
214
|
+
const assembled = result.messages[index];
|
|
215
|
+
if (source.role !== assembled.role) {
|
|
216
|
+
throw new Error(`Entroly bridge changed message role at index ${index}`);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const { content: sourceContent, ...sourceMetadata } = source;
|
|
220
|
+
const { content: assembledContent, ...assembledMetadata } = assembled;
|
|
221
|
+
if (canonicalJson(sourceMetadata) !== canonicalJson(assembledMetadata)) {
|
|
222
|
+
throw new Error(`Entroly bridge changed message metadata at index ${index}`);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const exactMessage =
|
|
226
|
+
source.role === "system" || source.role === "developer" || index >= recentStart;
|
|
227
|
+
if (exactMessage) {
|
|
228
|
+
if (canonicalJson(sourceContent) !== canonicalJson(assembledContent)) {
|
|
229
|
+
throw new Error(`Entroly bridge changed protected message at index ${index}`);
|
|
230
|
+
}
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (typeof sourceContent === "string") {
|
|
235
|
+
if (typeof assembledContent !== "string") {
|
|
236
|
+
throw new Error(`Entroly bridge changed text content shape at index ${index}`);
|
|
237
|
+
}
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (!Array.isArray(sourceContent) || !Array.isArray(assembledContent)) {
|
|
241
|
+
if (canonicalJson(sourceContent) !== canonicalJson(assembledContent)) {
|
|
242
|
+
throw new Error(`Entroly bridge changed opaque content at index ${index}`);
|
|
243
|
+
}
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (sourceContent.length !== assembledContent.length) {
|
|
247
|
+
throw new Error(`Entroly bridge changed content block count at index ${index}`);
|
|
248
|
+
}
|
|
249
|
+
for (let blockIndex = 0; blockIndex < sourceContent.length; blockIndex += 1) {
|
|
250
|
+
const sourceBlock = sourceContent[blockIndex];
|
|
251
|
+
const assembledBlock = assembledContent[blockIndex];
|
|
252
|
+
if (isCompressibleTextBlock(sourceBlock)) {
|
|
253
|
+
if (!isCompressibleTextBlock(assembledBlock)) {
|
|
254
|
+
throw new Error(
|
|
255
|
+
`Entroly bridge changed text block shape at index ${index}:${blockIndex}`,
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
} else if (canonicalJson(sourceBlock) !== canonicalJson(assembledBlock)) {
|
|
259
|
+
throw new Error(
|
|
260
|
+
`Entroly bridge changed opaque content block at index ${index}:${blockIndex}`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
return result;
|
|
266
|
+
}
|
|
2
267
|
|
|
3
268
|
function estimateTokens(messages) {
|
|
4
269
|
return Math.ceil(JSON.stringify(messages).length / 4);
|
|
@@ -9,7 +274,11 @@ export function formatEntrolyStatus(status) {
|
|
|
9
274
|
return "Entroly: no context assembly has completed for this session yet.";
|
|
10
275
|
}
|
|
11
276
|
if (!status.ok) {
|
|
12
|
-
return
|
|
277
|
+
return [
|
|
278
|
+
"Entroly: last assembly failed open to the original context.",
|
|
279
|
+
"Provider routing remains OpenClaw-managed.",
|
|
280
|
+
`Reason: ${safeDiagnostic(status.error)}`,
|
|
281
|
+
].join("\n");
|
|
13
282
|
}
|
|
14
283
|
const source = status.source_tokens ?? 0;
|
|
15
284
|
const assembled = status.estimated_tokens ?? source;
|
|
@@ -17,6 +286,8 @@ export function formatEntrolyStatus(status) {
|
|
|
17
286
|
const reduction = source > 0 ? ((saved / source) * 100).toFixed(1) : "0.0";
|
|
18
287
|
const lines = [
|
|
19
288
|
"Entroly protected the last context assembly",
|
|
289
|
+
"Provider routing: OpenClaw-managed (Entroly is provider-independent)",
|
|
290
|
+
`Budget source: ${status.budget_source ?? "unknown"}`,
|
|
20
291
|
`Strategy: ${status.assembly_strategy ?? "budgeted_context"}`,
|
|
21
292
|
`Evidence pinned verbatim: ${status.evidence_pinned ?? 0} message(s)`,
|
|
22
293
|
`Evidence pins blocked by firewall: ${status.evidence_pin_blocked ?? 0}`,
|
|
@@ -24,8 +295,10 @@ export function formatEntrolyStatus(status) {
|
|
|
24
295
|
`Estimated reduction: ${reduction}% (${saved.toLocaleString()} tokens)`,
|
|
25
296
|
`Changed: ${status.changed ? "yes" : "no"}`,
|
|
26
297
|
];
|
|
27
|
-
if (status.receipt_id) lines.push(`Receipt: ${status.receipt_id}`);
|
|
28
|
-
if (status.warnings?.length)
|
|
298
|
+
if (status.receipt_id) lines.push(`Receipt: ${boundedString(status.receipt_id, 160)}`);
|
|
299
|
+
if (status.warnings?.length) {
|
|
300
|
+
lines.push(`Warnings: ${status.warnings.map((warning) => safeDiagnostic(warning)).join(" | ")}`);
|
|
301
|
+
}
|
|
29
302
|
return lines.join("\n");
|
|
30
303
|
}
|
|
31
304
|
|
|
@@ -34,11 +307,12 @@ export function formatEntrolyDoctor({ ok, error, pythonCommand = "python" }) {
|
|
|
34
307
|
return [
|
|
35
308
|
"Entroly doctor: ready",
|
|
36
309
|
`Python command: ${pythonCommand}`,
|
|
37
|
-
"Bridge:
|
|
310
|
+
"Bridge: compatible (v2, two-phase receipts)",
|
|
38
311
|
"Local-only context assembly: available",
|
|
312
|
+
"Provider-neutral bridge: ready; routing and authentication remain OpenClaw-managed",
|
|
39
313
|
].join("\n");
|
|
40
314
|
}
|
|
41
|
-
const reason =
|
|
315
|
+
const reason = safeDiagnostic(error);
|
|
42
316
|
return [
|
|
43
317
|
"Entroly doctor: not ready",
|
|
44
318
|
`Python command: ${pythonCommand}`,
|
|
@@ -50,16 +324,42 @@ export function formatEntrolyDoctor({ ok, error, pythonCommand = "python" }) {
|
|
|
50
324
|
|
|
51
325
|
export function createEntrolyContextEngine({
|
|
52
326
|
bridge,
|
|
327
|
+
delegateCompaction,
|
|
328
|
+
buildMemoryPrompt = () => undefined,
|
|
53
329
|
config = {},
|
|
54
330
|
logger = console,
|
|
55
331
|
statusBySession = new Map(),
|
|
332
|
+
maxStatusSessions = 512,
|
|
56
333
|
}) {
|
|
334
|
+
if (typeof delegateCompaction !== "function") {
|
|
335
|
+
throw new TypeError("Entroly requires OpenClaw's compaction delegate");
|
|
336
|
+
}
|
|
337
|
+
const statusLimit = positiveInteger(maxStatusSessions) ?? 512;
|
|
338
|
+
const storeStatus = (sessionId, status) => {
|
|
339
|
+
statusBySession.delete(sessionId);
|
|
340
|
+
statusBySession.set(sessionId, statusSnapshot(status));
|
|
341
|
+
while (statusBySession.size > statusLimit) {
|
|
342
|
+
statusBySession.delete(statusBySession.keys().next().value);
|
|
343
|
+
}
|
|
344
|
+
};
|
|
57
345
|
|
|
58
346
|
return {
|
|
59
347
|
info: {
|
|
60
348
|
id: "entroly",
|
|
61
349
|
name: "Entroly Context Engine",
|
|
62
350
|
ownsCompaction: false,
|
|
351
|
+
hostRequirements: {
|
|
352
|
+
"agent-run": {
|
|
353
|
+
requiredCapabilities: ["assemble-before-prompt"],
|
|
354
|
+
unsupportedMessage:
|
|
355
|
+
"Entroly requires a native OpenClaw host that applies assembled context before each model call.",
|
|
356
|
+
},
|
|
357
|
+
"manual-compact": {
|
|
358
|
+
requiredCapabilities: ["compact"],
|
|
359
|
+
unsupportedMessage:
|
|
360
|
+
"Entroly delegates transcript compaction to the native OpenClaw runtime.",
|
|
361
|
+
},
|
|
362
|
+
},
|
|
63
363
|
},
|
|
64
364
|
|
|
65
365
|
async ingest() {
|
|
@@ -72,61 +372,150 @@ export function createEntrolyContextEngine({
|
|
|
72
372
|
|
|
73
373
|
async assemble({
|
|
74
374
|
sessionId,
|
|
375
|
+
sessionKey,
|
|
75
376
|
messages,
|
|
76
377
|
tokenBudget,
|
|
378
|
+
availableTools,
|
|
379
|
+
citationsMode,
|
|
77
380
|
model,
|
|
78
381
|
prompt,
|
|
79
382
|
runtimeSettings,
|
|
80
383
|
}) {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
384
|
+
if (!Array.isArray(messages)) {
|
|
385
|
+
const error = new TypeError(
|
|
386
|
+
"OpenClaw did not supply context messages as an array; Entroly refused to assemble an empty prompt",
|
|
387
|
+
);
|
|
388
|
+
logger.error?.(`entroly: ${safeDiagnostic(error)}`);
|
|
389
|
+
storeStatus(sessionId, { ok: false, error: error.message });
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
const sourceMessages = messages;
|
|
393
|
+
const assemblyRuntime = resolveAssemblyRuntime({
|
|
394
|
+
tokenBudget,
|
|
395
|
+
model,
|
|
396
|
+
runtimeSettings,
|
|
397
|
+
fallbackTokenBudget: config.fallbackTokenBudget,
|
|
398
|
+
});
|
|
399
|
+
const preserveLastN = positiveInteger(config.preserveLastN) ?? 4;
|
|
400
|
+
let systemPromptAddition;
|
|
84
401
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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,
|
|
402
|
+
systemPromptAddition = buildMemoryPrompt({
|
|
403
|
+
availableTools: availableTools ?? new Set(),
|
|
404
|
+
citationsMode,
|
|
405
|
+
agentSessionKey: sessionKey,
|
|
98
406
|
});
|
|
99
|
-
statusBySession.set(sessionId, result);
|
|
100
|
-
return {
|
|
101
|
-
messages: result.messages,
|
|
102
|
-
estimatedTokens: result.estimated_tokens,
|
|
103
|
-
promptAuthority: "assembled",
|
|
104
|
-
};
|
|
105
407
|
} catch (error) {
|
|
106
408
|
logger.warn?.(
|
|
107
|
-
`entroly:
|
|
409
|
+
`entroly: OpenClaw memory guidance could not be added: ${safeDiagnostic(error)}`,
|
|
108
410
|
);
|
|
109
|
-
|
|
411
|
+
}
|
|
412
|
+
const failOpen = (error) => {
|
|
413
|
+
const reason = safeDiagnostic(error);
|
|
414
|
+
logger.warn?.(`entroly: assembly failed; passing exact original context: ${reason}`);
|
|
415
|
+
storeStatus(sessionId, {
|
|
110
416
|
ok: false,
|
|
111
|
-
error:
|
|
417
|
+
error: reason,
|
|
112
418
|
estimated_tokens: estimateTokens(sourceMessages),
|
|
419
|
+
provider_mode: PROVIDER_MODE,
|
|
420
|
+
provider_independent: true,
|
|
421
|
+
budget_source: assemblyRuntime.budgetSource,
|
|
113
422
|
});
|
|
114
423
|
return {
|
|
115
424
|
messages: sourceMessages,
|
|
116
425
|
estimatedTokens: estimateTokens(sourceMessages),
|
|
117
426
|
promptAuthority: "preassembly_may_overflow",
|
|
427
|
+
systemPromptAddition,
|
|
118
428
|
};
|
|
429
|
+
};
|
|
430
|
+
if (assemblyRuntime.tokenBudget === undefined) {
|
|
431
|
+
return failOpen(
|
|
432
|
+
new Error(
|
|
433
|
+
"OpenClaw did not provide a positive prompt token budget; configure the model context window or plugins.entries.entroly.config.fallbackTokenBudget",
|
|
434
|
+
),
|
|
435
|
+
);
|
|
436
|
+
}
|
|
437
|
+
try {
|
|
438
|
+
const receiptCommitToken =
|
|
439
|
+
config.writeReceipts === false ? null : randomBytes(32).toString("hex");
|
|
440
|
+
const result = validateAssemblyResult(
|
|
441
|
+
await bridge.request({
|
|
442
|
+
operation: "assemble",
|
|
443
|
+
session_id: sessionId,
|
|
444
|
+
messages: sourceMessages,
|
|
445
|
+
token_budget: assemblyRuntime.tokenBudget,
|
|
446
|
+
budget_source: assemblyRuntime.budgetSource,
|
|
447
|
+
model: assemblyRuntime.model,
|
|
448
|
+
openclaw_runtime: assemblyRuntime.runtimeMetadata,
|
|
449
|
+
prompt,
|
|
450
|
+
workspace_dir: config.workspaceDir,
|
|
451
|
+
preserve_last_n: preserveLastN,
|
|
452
|
+
receipt_dir: config.receiptDir,
|
|
453
|
+
write_receipt: config.writeReceipts !== false,
|
|
454
|
+
receipt_commit_challenge_sha256:
|
|
455
|
+
receiptCommitToken === null ? undefined : sha256(receiptCommitToken),
|
|
456
|
+
receipt_max_files: positiveInteger(config.receiptMaxFiles),
|
|
457
|
+
receipt_max_bytes: positiveInteger(config.receiptMaxBytes),
|
|
458
|
+
distill: config.distill !== false,
|
|
459
|
+
evidence_pinning: config.evidencePinning !== false,
|
|
460
|
+
}),
|
|
461
|
+
sourceMessages,
|
|
462
|
+
preserveLastN,
|
|
463
|
+
);
|
|
464
|
+
if (result.receipt_commit_required === true) {
|
|
465
|
+
const proposal = {
|
|
466
|
+
receiptId: boundedString(result.receipt_id, 160),
|
|
467
|
+
proposalId: boundedString(result.proposal_id, 160),
|
|
468
|
+
proposalSha256: boundedString(result.proposal_sha256, 64),
|
|
469
|
+
receiptPath: boundedString(result.receipt_path, 1024),
|
|
470
|
+
acceptanceCommitSha256:
|
|
471
|
+
receiptCommitToken === null
|
|
472
|
+
? null
|
|
473
|
+
: sha256(
|
|
474
|
+
`entroly.openclaw.accept.v1:${result.proposal_sha256}:${receiptCommitToken}`,
|
|
475
|
+
),
|
|
476
|
+
};
|
|
477
|
+
if (
|
|
478
|
+
!/^ocr_[0-9a-f]{20}$/.test(proposal.receiptId ?? "") ||
|
|
479
|
+
!/^ocp_[0-9a-f]{32}$/.test(proposal.proposalId ?? "") ||
|
|
480
|
+
!/^[0-9a-f]{64}$/.test(proposal.proposalSha256 ?? "") ||
|
|
481
|
+
!proposal.receiptPath
|
|
482
|
+
) {
|
|
483
|
+
throw new Error("Entroly bridge returned an invalid receipt proposal");
|
|
484
|
+
}
|
|
485
|
+
validateReceiptCommit(
|
|
486
|
+
await bridge.request({
|
|
487
|
+
operation: "commit_receipt",
|
|
488
|
+
receipt_id: proposal.receiptId,
|
|
489
|
+
proposal_id: proposal.proposalId,
|
|
490
|
+
proposal_sha256: proposal.proposalSha256,
|
|
491
|
+
receipt_path: proposal.receiptPath,
|
|
492
|
+
receipt_commit_token: receiptCommitToken,
|
|
493
|
+
workspace_dir: config.workspaceDir,
|
|
494
|
+
}),
|
|
495
|
+
proposal,
|
|
496
|
+
);
|
|
497
|
+
} else if (result.receipt_path) {
|
|
498
|
+
throw new Error(
|
|
499
|
+
"Entroly bridge wrote a receipt without the required acceptance handshake",
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
storeStatus(sessionId, result);
|
|
503
|
+
return {
|
|
504
|
+
messages: result.messages,
|
|
505
|
+
estimatedTokens: result.estimated_tokens,
|
|
506
|
+
promptAuthority:
|
|
507
|
+
result.estimated_tokens > assemblyRuntime.tokenBudget
|
|
508
|
+
? "preassembly_may_overflow"
|
|
509
|
+
: "assembled",
|
|
510
|
+
systemPromptAddition,
|
|
511
|
+
};
|
|
512
|
+
} catch (error) {
|
|
513
|
+
return failOpen(error);
|
|
119
514
|
}
|
|
120
515
|
},
|
|
121
516
|
|
|
122
|
-
async compact(
|
|
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
|
-
};
|
|
517
|
+
async compact(params) {
|
|
518
|
+
return await delegateCompaction(params);
|
|
130
519
|
},
|
|
131
520
|
|
|
132
521
|
getStatus(sessionId) {
|
package/index.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
|
|
2
|
+
import {
|
|
3
|
+
buildMemorySystemPromptAddition,
|
|
4
|
+
delegateCompactionToRuntime,
|
|
5
|
+
} from "openclaw/plugin-sdk/core";
|
|
2
6
|
import { EntrolyBridgeClient } from "./bridge-client.js";
|
|
3
7
|
import {
|
|
4
8
|
createEntrolyContextEngine,
|
|
@@ -11,20 +15,24 @@ export default definePluginEntry({
|
|
|
11
15
|
name: "Entroly Context Engine",
|
|
12
16
|
register(api) {
|
|
13
17
|
const config = api.pluginConfig ?? {};
|
|
18
|
+
let latestWorkspaceDir;
|
|
14
19
|
const bridge = new EntrolyBridgeClient({
|
|
15
20
|
pythonCommand: config.pythonCommand ?? "python",
|
|
16
21
|
timeoutMs: config.timeoutMs ?? 5000,
|
|
17
22
|
logger: api.logger,
|
|
18
23
|
});
|
|
19
24
|
const statusBySession = new Map();
|
|
20
|
-
api.registerContextEngine("entroly", (factoryContext) =>
|
|
21
|
-
|
|
25
|
+
api.registerContextEngine("entroly", (factoryContext) => {
|
|
26
|
+
latestWorkspaceDir = factoryContext.workspaceDir;
|
|
27
|
+
return createEntrolyContextEngine({
|
|
22
28
|
bridge,
|
|
29
|
+
delegateCompaction: delegateCompactionToRuntime,
|
|
30
|
+
buildMemoryPrompt: buildMemorySystemPromptAddition,
|
|
23
31
|
config: { ...config, workspaceDir: factoryContext.workspaceDir },
|
|
24
32
|
logger: api.logger,
|
|
25
33
|
statusBySession,
|
|
26
|
-
})
|
|
27
|
-
);
|
|
34
|
+
});
|
|
35
|
+
});
|
|
28
36
|
api.registerCommand({
|
|
29
37
|
name: "entroly-context",
|
|
30
38
|
description: "Show Entroly context savings or run `doctor`.",
|
|
@@ -32,7 +40,11 @@ export default definePluginEntry({
|
|
|
32
40
|
handler: async (ctx) => {
|
|
33
41
|
if (ctx.args?.trim().toLowerCase() === "doctor") {
|
|
34
42
|
try {
|
|
35
|
-
await bridge.health(
|
|
43
|
+
await bridge.health({
|
|
44
|
+
workspaceDir: latestWorkspaceDir,
|
|
45
|
+
receiptDir: config.receiptDir,
|
|
46
|
+
writeReceipts: config.writeReceipts !== false,
|
|
47
|
+
});
|
|
36
48
|
return {
|
|
37
49
|
text: formatEntrolyDoctor({
|
|
38
50
|
ok: true,
|
package/openclaw.plugin.json
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
"onStartup": true
|
|
5
5
|
},
|
|
6
6
|
"name": "Entroly Context Engine",
|
|
7
|
-
"description": "
|
|
7
|
+
"description": "Provider-independent context assembly for every OpenClaw model route, with protected blocks, recovery delegation, and local audit receipts.",
|
|
8
|
+
"icon": "https://raw.githubusercontent.com/juyterman1000/entroly/main/docs/assets/entroly_wordmark.svg",
|
|
8
9
|
"configSchema": {
|
|
9
10
|
"type": "object",
|
|
10
11
|
"additionalProperties": false,
|
|
@@ -26,10 +27,27 @@
|
|
|
26
27
|
"maximum": 30000,
|
|
27
28
|
"default": 5000
|
|
28
29
|
},
|
|
30
|
+
"fallbackTokenBudget": {
|
|
31
|
+
"type": "integer",
|
|
32
|
+
"minimum": 1024,
|
|
33
|
+
"maximum": 4000000
|
|
34
|
+
},
|
|
29
35
|
"receiptDir": {
|
|
30
36
|
"type": "string",
|
|
31
37
|
"minLength": 1
|
|
32
38
|
},
|
|
39
|
+
"receiptMaxFiles": {
|
|
40
|
+
"type": "integer",
|
|
41
|
+
"minimum": 8,
|
|
42
|
+
"maximum": 10000,
|
|
43
|
+
"default": 512
|
|
44
|
+
},
|
|
45
|
+
"receiptMaxBytes": {
|
|
46
|
+
"type": "integer",
|
|
47
|
+
"minimum": 1048576,
|
|
48
|
+
"maximum": 1073741824,
|
|
49
|
+
"default": 67108864
|
|
50
|
+
},
|
|
33
51
|
"writeReceipts": {
|
|
34
52
|
"type": "boolean",
|
|
35
53
|
"default": true
|
|
@@ -57,10 +75,22 @@
|
|
|
57
75
|
"label": "Bridge Timeout (ms)",
|
|
58
76
|
"help": "Maximum time for one Entroly context assembly request."
|
|
59
77
|
},
|
|
78
|
+
"fallbackTokenBudget": {
|
|
79
|
+
"label": "Fallback Prompt Token Budget",
|
|
80
|
+
"help": "Optional operator-approved budget used only when OpenClaw cannot resolve a finite model prompt budget."
|
|
81
|
+
},
|
|
60
82
|
"receiptDir": {
|
|
61
83
|
"label": "Receipt Directory",
|
|
62
84
|
"help": "Optional local directory for Entroly OpenClaw receipts."
|
|
63
85
|
},
|
|
86
|
+
"receiptMaxFiles": {
|
|
87
|
+
"label": "Receipt File Limit",
|
|
88
|
+
"help": "Fail safely instead of writing when this append-only receipt count is reached."
|
|
89
|
+
},
|
|
90
|
+
"receiptMaxBytes": {
|
|
91
|
+
"label": "Receipt Byte Limit",
|
|
92
|
+
"help": "Fail safely instead of allowing the receipt directory to exceed this byte quota."
|
|
93
|
+
},
|
|
64
94
|
"writeReceipts": {
|
|
65
95
|
"label": "Write Local Receipts",
|
|
66
96
|
"help": "Record context hashes, estimated savings, warnings, and decision metadata locally."
|
package/package.json
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "entroly-openclaw",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.57",
|
|
4
|
+
"description": "Provider-independent, auditable context optimization for every OpenClaw model route",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
|
+
"homepage": "https://github.com/juyterman1000/entroly#entroly-for-openclaw",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/juyterman1000/entroly.git",
|
|
11
|
+
"directory": "integrations/openclaw"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/juyterman1000/entroly/issues"
|
|
15
|
+
},
|
|
7
16
|
"engines": {
|
|
8
17
|
"node": ">=22.19"
|
|
9
18
|
},
|
|
@@ -32,12 +41,17 @@
|
|
|
32
41
|
"./index.js"
|
|
33
42
|
],
|
|
34
43
|
"install": {
|
|
44
|
+
"clawhubSpec": "clawhub:entroly-openclaw",
|
|
35
45
|
"npmSpec": "entroly-openclaw",
|
|
36
46
|
"defaultChoice": "npm",
|
|
37
47
|
"minHostVersion": ">=2026.6.11"
|
|
38
48
|
},
|
|
39
49
|
"compat": {
|
|
40
|
-
"pluginApi": ">=2026.6.11"
|
|
50
|
+
"pluginApi": ">=2026.6.11",
|
|
51
|
+
"minGatewayVersion": ">=2026.6.11"
|
|
52
|
+
},
|
|
53
|
+
"build": {
|
|
54
|
+
"openclawVersion": "2026.6.11"
|
|
41
55
|
},
|
|
42
56
|
"release": {
|
|
43
57
|
"publishToClawHub": true,
|