entroly-openclaw 1.0.61 → 1.0.63
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 +51 -3
- package/engine.js +46 -1
- package/index.js +21 -0
- package/openclaw.plugin.json +39 -1
- package/package.json +14 -3
- package/proof-hooks.js +198 -0
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ Install the Python engine, then install the plugin from OpenClaw's official
|
|
|
22
22
|
ClawHub registry:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
|
-
pip install "entroly>=1.0.
|
|
25
|
+
pip install "entroly>=1.0.63"
|
|
26
26
|
openclaw plugins install clawhub:entroly-openclaw
|
|
27
27
|
openclaw plugins enable entroly
|
|
28
28
|
```
|
|
@@ -37,7 +37,7 @@ openclaw plugins enable entroly
|
|
|
37
37
|
From an Entroly source checkout:
|
|
38
38
|
|
|
39
39
|
```bash
|
|
40
|
-
pip install "entroly>=1.0.
|
|
40
|
+
pip install "entroly>=1.0.63"
|
|
41
41
|
openclaw plugins install ./integrations/openclaw
|
|
42
42
|
openclaw plugins enable entroly
|
|
43
43
|
```
|
|
@@ -65,7 +65,7 @@ After the first agent turn, run `/entroly-context` in any connected channel to
|
|
|
65
65
|
see the estimated before/after context size, reduction, warnings, and receipt.
|
|
66
66
|
Run `/entroly-context doctor` to verify the configured Python executable and
|
|
67
67
|
local JSONL bridge before inviting users onto the Gateway. The plugin requires
|
|
68
|
-
the Entroly 1.0.
|
|
68
|
+
the Entroly 1.0.63 bridge v2 protocol; doctor reports an actionable upgrade
|
|
69
69
|
instead of accepting an older, incompatible Python installation.
|
|
70
70
|
|
|
71
71
|
OpenClaw's resolved prompt token budget is authoritative. When an older or
|
|
@@ -100,6 +100,54 @@ actionable warning instead of risking a provider overflow. Explicit OpenClaw
|
|
|
100
100
|
budgets always win, followed by the operator's `fallbackTokenBudget`; automatic
|
|
101
101
|
registry discovery is used only when neither is available.
|
|
102
102
|
|
|
103
|
+
## Optional proof-guided exact recovery
|
|
104
|
+
|
|
105
|
+
Default behavior performs local context assembly only and makes no additional
|
|
106
|
+
model call. To let Entroly verify a draft, recover exact omitted messages, and
|
|
107
|
+
ask OpenClaw for a bounded revision, opt in explicitly:
|
|
108
|
+
|
|
109
|
+
```json5
|
|
110
|
+
{
|
|
111
|
+
plugins: {
|
|
112
|
+
slots: { contextEngine: "entroly" },
|
|
113
|
+
entries: {
|
|
114
|
+
entroly: {
|
|
115
|
+
hooks: { allowConversationAccess: true },
|
|
116
|
+
config: {
|
|
117
|
+
proofGuidedRecovery: true,
|
|
118
|
+
proofGuidedMaxRounds: 2,
|
|
119
|
+
proofGuidedRecoveryTokens: 1200,
|
|
120
|
+
proofGuidedMaxMessages: 3
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
`proofGuidedMaxRounds` includes the first response, so the default maximum of
|
|
129
|
+
two allows at most one additional model call. Entroly never receives provider
|
|
130
|
+
credentials and never calls the provider itself: it returns an idempotent
|
|
131
|
+
revision request to OpenClaw, which keeps its normal provider routing and
|
|
132
|
+
billing behavior. The retry contains exact recovered message text and SHA-256
|
|
133
|
+
commitments, not a generated summary.
|
|
134
|
+
|
|
135
|
+
The local bridge checks the draft with EICV and the context firewall. If a
|
|
136
|
+
claim is unsupported, it either identifies exact omitted evidence for the
|
|
137
|
+
bounded revision or supplies a safer verified output. If bridge verification
|
|
138
|
+
fails or model output is unsafe, the delivery hook substitutes a clear withheld
|
|
139
|
+
response instead of silently passing unverified text. `/entroly-context` shows
|
|
140
|
+
the proof status, attempt count, and local audit artifact ID.
|
|
141
|
+
|
|
142
|
+
This feature requires a current OpenClaw build that exposes `llm_output`,
|
|
143
|
+
`before_agent_finalize`, and `reply_payload_sending`, plus the explicit
|
|
144
|
+
`allowConversationAccess` grant shown above. Verify registration after restart:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
openclaw plugins inspect entroly --runtime --json
|
|
148
|
+
openclaw plugins doctor
|
|
149
|
+
```
|
|
150
|
+
|
|
103
151
|
## Reproduce the evidence-pinning control
|
|
104
152
|
|
|
105
153
|
```bash
|
package/engine.js
CHANGED
|
@@ -45,6 +45,7 @@ function statusSnapshot(status) {
|
|
|
45
45
|
"context_window",
|
|
46
46
|
"context_output_reserve",
|
|
47
47
|
"context_safety_tokens",
|
|
48
|
+
"proof_guided_attempts",
|
|
48
49
|
];
|
|
49
50
|
const booleanFields = ["changed", "provider_independent"];
|
|
50
51
|
const stringFields = [
|
|
@@ -58,6 +59,8 @@ function statusSnapshot(status) {
|
|
|
58
59
|
"model",
|
|
59
60
|
"provider_hint",
|
|
60
61
|
"assembly_strategy",
|
|
62
|
+
"proof_guided_status",
|
|
63
|
+
"proof_guided_audit_artifact_id",
|
|
61
64
|
"error",
|
|
62
65
|
];
|
|
63
66
|
for (const field of numericFields) {
|
|
@@ -385,6 +388,17 @@ export function formatEntrolyStatus(status) {
|
|
|
385
388
|
);
|
|
386
389
|
}
|
|
387
390
|
if (status.receipt_id) lines.push(`Receipt: ${boundedString(status.receipt_id, 160)}`);
|
|
391
|
+
if (status.proof_guided_status) {
|
|
392
|
+
lines.push(
|
|
393
|
+
`Proof-guided recovery: ${boundedString(status.proof_guided_status, 160)}`,
|
|
394
|
+
`Proof model attempts: ${Number(status.proof_guided_attempts ?? 0).toLocaleString()}`,
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
if (status.proof_guided_audit_artifact_id) {
|
|
398
|
+
lines.push(
|
|
399
|
+
`Proof audit: ${boundedString(status.proof_guided_audit_artifact_id, 160)}`,
|
|
400
|
+
);
|
|
401
|
+
}
|
|
388
402
|
if (status.warnings?.length) {
|
|
389
403
|
lines.push(`Warnings: ${status.warnings.map((warning) => safeDiagnostic(warning)).join(" | ")}`);
|
|
390
404
|
}
|
|
@@ -418,6 +432,7 @@ export function createEntrolyContextEngine({
|
|
|
418
432
|
config = {},
|
|
419
433
|
logger = console,
|
|
420
434
|
statusBySession = new Map(),
|
|
435
|
+
proofStateBySession = new Map(),
|
|
421
436
|
maxStatusSessions = 512,
|
|
422
437
|
}) {
|
|
423
438
|
if (typeof delegateCompaction !== "function") {
|
|
@@ -548,6 +563,7 @@ export function createEntrolyContextEngine({
|
|
|
548
563
|
context_safety_tokens:
|
|
549
564
|
assemblyRuntime.runtimeMetadata.context_discovery?.safety_tokens,
|
|
550
565
|
});
|
|
566
|
+
proofStateBySession.delete(sessionId);
|
|
551
567
|
return {
|
|
552
568
|
messages: sourceMessages,
|
|
553
569
|
estimatedTokens: estimateTokens(sourceMessages),
|
|
@@ -627,7 +643,36 @@ export function createEntrolyContextEngine({
|
|
|
627
643
|
"Entroly bridge wrote a receipt without the required acceptance handshake",
|
|
628
644
|
);
|
|
629
645
|
}
|
|
630
|
-
|
|
646
|
+
if (config.proofGuidedRecovery === true) {
|
|
647
|
+
const existing = proofStateBySession.get(sessionId);
|
|
648
|
+
if (!existing || existing.prompt !== prompt || existing.disabled) {
|
|
649
|
+
proofStateBySession.set(sessionId, {
|
|
650
|
+
prompt,
|
|
651
|
+
workspaceDir: config.workspaceDir,
|
|
652
|
+
sourceMessages: structuredClone(sourceMessages),
|
|
653
|
+
assembledMessages: structuredClone(result.messages),
|
|
654
|
+
recoveredMessages: [],
|
|
655
|
+
attempts: 0,
|
|
656
|
+
runId: null,
|
|
657
|
+
lastOutputSha256: null,
|
|
658
|
+
lastProofResult: null,
|
|
659
|
+
retryIssued: false,
|
|
660
|
+
disabled: false,
|
|
661
|
+
});
|
|
662
|
+
}
|
|
663
|
+
while (proofStateBySession.size > statusLimit) {
|
|
664
|
+
proofStateBySession.delete(proofStateBySession.keys().next().value);
|
|
665
|
+
}
|
|
666
|
+
} else {
|
|
667
|
+
proofStateBySession.delete(sessionId);
|
|
668
|
+
}
|
|
669
|
+
storeStatus(sessionId, {
|
|
670
|
+
...result,
|
|
671
|
+
proof_guided_status:
|
|
672
|
+
config.proofGuidedRecovery === true ? "armed" : "disabled",
|
|
673
|
+
proof_guided_attempts:
|
|
674
|
+
proofStateBySession.get(sessionId)?.attempts ?? 0,
|
|
675
|
+
});
|
|
631
676
|
return {
|
|
632
677
|
messages: result.messages,
|
|
633
678
|
estimatedTokens: result.estimated_tokens,
|
package/index.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
formatEntrolyDoctor,
|
|
10
10
|
formatEntrolyStatus,
|
|
11
11
|
} from "./engine.js";
|
|
12
|
+
import { createProofGuidedHooks } from "./proof-hooks.js";
|
|
12
13
|
|
|
13
14
|
export default definePluginEntry({
|
|
14
15
|
id: "entroly",
|
|
@@ -22,6 +23,7 @@ export default definePluginEntry({
|
|
|
22
23
|
logger: api.logger,
|
|
23
24
|
});
|
|
24
25
|
const statusBySession = new Map();
|
|
26
|
+
const proofStateBySession = new Map();
|
|
25
27
|
api.registerContextEngine("entroly", (factoryContext) => {
|
|
26
28
|
latestWorkspaceDir = factoryContext.workspaceDir;
|
|
27
29
|
return createEntrolyContextEngine({
|
|
@@ -31,8 +33,27 @@ export default definePluginEntry({
|
|
|
31
33
|
config: { ...config, workspaceDir: factoryContext.workspaceDir },
|
|
32
34
|
logger: api.logger,
|
|
33
35
|
statusBySession,
|
|
36
|
+
proofStateBySession,
|
|
34
37
|
});
|
|
35
38
|
});
|
|
39
|
+
if (config.proofGuidedRecovery === true) {
|
|
40
|
+
if (typeof api.on !== "function") {
|
|
41
|
+
api.logger.error?.(
|
|
42
|
+
"entroly: this OpenClaw host does not expose typed proof-guided hooks; disable proofGuidedRecovery or upgrade OpenClaw",
|
|
43
|
+
);
|
|
44
|
+
} else {
|
|
45
|
+
const proofHooks = createProofGuidedHooks({
|
|
46
|
+
bridge,
|
|
47
|
+
config,
|
|
48
|
+
logger: api.logger,
|
|
49
|
+
proofStateBySession,
|
|
50
|
+
statusBySession,
|
|
51
|
+
});
|
|
52
|
+
api.on("llm_output", proofHooks.onLlmOutput);
|
|
53
|
+
api.on("before_agent_finalize", proofHooks.onBeforeAgentFinalize);
|
|
54
|
+
api.on("reply_payload_sending", proofHooks.onReplyPayloadSending);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
36
57
|
api.registerCommand({
|
|
37
58
|
name: "entroly-context",
|
|
38
59
|
description: "Show Entroly context savings or run `doctor`.",
|
package/openclaw.plugin.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"onStartup": true
|
|
5
5
|
},
|
|
6
6
|
"name": "Entroly Context Engine",
|
|
7
|
-
"description": "Provider-independent context
|
|
7
|
+
"description": "Provider-independent context engineering for every OpenClaw model route, with protected blocks, recovery delegation, and local audit receipts.",
|
|
8
8
|
"icon": "https://raw.githubusercontent.com/juyterman1000/entroly/main/docs/assets/entroly_wordmark.svg",
|
|
9
9
|
"configSchema": {
|
|
10
10
|
"type": "object",
|
|
@@ -63,6 +63,28 @@
|
|
|
63
63
|
"evidencePinning": {
|
|
64
64
|
"type": "boolean",
|
|
65
65
|
"default": true
|
|
66
|
+
},
|
|
67
|
+
"proofGuidedRecovery": {
|
|
68
|
+
"type": "boolean",
|
|
69
|
+
"default": false
|
|
70
|
+
},
|
|
71
|
+
"proofGuidedMaxRounds": {
|
|
72
|
+
"type": "integer",
|
|
73
|
+
"minimum": 1,
|
|
74
|
+
"maximum": 4,
|
|
75
|
+
"default": 2
|
|
76
|
+
},
|
|
77
|
+
"proofGuidedRecoveryTokens": {
|
|
78
|
+
"type": "integer",
|
|
79
|
+
"minimum": 1,
|
|
80
|
+
"maximum": 100000,
|
|
81
|
+
"default": 1200
|
|
82
|
+
},
|
|
83
|
+
"proofGuidedMaxMessages": {
|
|
84
|
+
"type": "integer",
|
|
85
|
+
"minimum": 1,
|
|
86
|
+
"maximum": 16,
|
|
87
|
+
"default": 3
|
|
66
88
|
}
|
|
67
89
|
}
|
|
68
90
|
},
|
|
@@ -110,6 +132,22 @@
|
|
|
110
132
|
"evidencePinning": {
|
|
111
133
|
"label": "Query-Aware Evidence Pinning",
|
|
112
134
|
"help": "Keep older messages that match the current request verbatim when they fit the bounded evidence reserve."
|
|
135
|
+
},
|
|
136
|
+
"proofGuidedRecovery": {
|
|
137
|
+
"label": "Proof-Guided Recovery",
|
|
138
|
+
"help": "Opt in to local output verification and at most the configured number of OpenClaw revision passes using exact recovered evidence. Requires hooks.allowConversationAccess=true because it inspects model input and output."
|
|
139
|
+
},
|
|
140
|
+
"proofGuidedMaxRounds": {
|
|
141
|
+
"label": "Maximum Proof Rounds",
|
|
142
|
+
"help": "Hard cap on model attempts, including the first response. Additional paid calls occur only when Proof-Guided Recovery is explicitly enabled."
|
|
143
|
+
},
|
|
144
|
+
"proofGuidedRecoveryTokens": {
|
|
145
|
+
"label": "Recovery Token Budget",
|
|
146
|
+
"help": "Maximum estimated exact-evidence tokens recovered for one revision decision."
|
|
147
|
+
},
|
|
148
|
+
"proofGuidedMaxMessages": {
|
|
149
|
+
"label": "Recovery Message Limit",
|
|
150
|
+
"help": "Maximum exact omitted messages appended to one revision instruction."
|
|
113
151
|
}
|
|
114
152
|
}
|
|
115
153
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "entroly-openclaw",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Provider-independent, auditable context
|
|
3
|
+
"version": "1.0.63",
|
|
4
|
+
"description": "Provider-independent, auditable context engineering for every OpenClaw model route",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"homepage": "https://github.com/juyterman1000/entroly#entroly-for-openclaw",
|
|
@@ -13,6 +13,16 @@
|
|
|
13
13
|
"bugs": {
|
|
14
14
|
"url": "https://github.com/juyterman1000/entroly/issues"
|
|
15
15
|
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"openclaw",
|
|
18
|
+
"openclaw-plugin",
|
|
19
|
+
"ai-agents",
|
|
20
|
+
"context-engineering",
|
|
21
|
+
"context-compression",
|
|
22
|
+
"context-optimization",
|
|
23
|
+
"context-receipts",
|
|
24
|
+
"model-context-protocol"
|
|
25
|
+
],
|
|
16
26
|
"engines": {
|
|
17
27
|
"node": ">=22.19"
|
|
18
28
|
},
|
|
@@ -20,12 +30,13 @@
|
|
|
20
30
|
"bridge-client.js",
|
|
21
31
|
"engine.js",
|
|
22
32
|
"index.js",
|
|
33
|
+
"proof-hooks.js",
|
|
23
34
|
"openclaw.plugin.json",
|
|
24
35
|
"README.md"
|
|
25
36
|
],
|
|
26
37
|
"scripts": {
|
|
27
38
|
"test": "node --test test/*.test.js",
|
|
28
|
-
"check": "node --check bridge-client.js && node --check engine.js && node --check index.js",
|
|
39
|
+
"check": "node --check bridge-client.js && node --check engine.js && node --check proof-hooks.js && node --check index.js",
|
|
29
40
|
"pack:check": "npm pack --dry-run"
|
|
30
41
|
},
|
|
31
42
|
"peerDependencies": {
|
package/proof-hooks.js
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { ENTROLY_BRIDGE_SCHEMA } from "./bridge-client.js";
|
|
4
|
+
|
|
5
|
+
function sha256(value) {
|
|
6
|
+
return createHash("sha256").update(String(value), "utf8").digest("hex");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function safeDiagnostic(value, limit = 400) {
|
|
10
|
+
return String(value?.message ?? value ?? "unknown error")
|
|
11
|
+
.replace(/\s+/g, " ")
|
|
12
|
+
.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, "Bearer [REDACTED]")
|
|
13
|
+
.replace(
|
|
14
|
+
/\b(api[_-]?key|authorization|password|secret|token)\b\s*[:=]\s*["']?[^\s,;"']+/gi,
|
|
15
|
+
"$1=[REDACTED]",
|
|
16
|
+
)
|
|
17
|
+
.slice(0, limit);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function positiveInteger(value, fallback, maximum) {
|
|
21
|
+
return typeof value === "number" &&
|
|
22
|
+
Number.isInteger(value) &&
|
|
23
|
+
value > 0 &&
|
|
24
|
+
value <= maximum
|
|
25
|
+
? value
|
|
26
|
+
: fallback;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function validateProofResult(result) {
|
|
30
|
+
if (
|
|
31
|
+
!result ||
|
|
32
|
+
result.ok !== true ||
|
|
33
|
+
result.schema_version !== ENTROLY_BRIDGE_SCHEMA ||
|
|
34
|
+
result.provider_call_performed !== false ||
|
|
35
|
+
result.local_only !== true ||
|
|
36
|
+
typeof result.status !== "string" ||
|
|
37
|
+
typeof result.verified_output !== "string" ||
|
|
38
|
+
!Array.isArray(result.recovered_messages) ||
|
|
39
|
+
typeof result.audit_artifact_id !== "string"
|
|
40
|
+
) {
|
|
41
|
+
throw new Error("Entroly bridge returned an invalid proof-guided result");
|
|
42
|
+
}
|
|
43
|
+
if (
|
|
44
|
+
result.status === "retry_with_exact_evidence" &&
|
|
45
|
+
(typeof result.retry_instruction !== "string" || !result.retry_instruction.trim())
|
|
46
|
+
) {
|
|
47
|
+
throw new Error("Entroly bridge omitted the exact-evidence retry instruction");
|
|
48
|
+
}
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function mergeRecoveredMessages(existing, recovered) {
|
|
53
|
+
const seen = new Set(existing.map((message) => sha256(JSON.stringify(message))));
|
|
54
|
+
const merged = [...existing];
|
|
55
|
+
for (const message of recovered) {
|
|
56
|
+
const fingerprint = sha256(JSON.stringify(message));
|
|
57
|
+
if (seen.has(fingerprint)) continue;
|
|
58
|
+
seen.add(fingerprint);
|
|
59
|
+
merged.push(message);
|
|
60
|
+
}
|
|
61
|
+
return merged;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function createProofGuidedHooks({
|
|
65
|
+
bridge,
|
|
66
|
+
config = {},
|
|
67
|
+
logger = console,
|
|
68
|
+
proofStateBySession = new Map(),
|
|
69
|
+
statusBySession = new Map(),
|
|
70
|
+
}) {
|
|
71
|
+
const maxRounds = positiveInteger(config.proofGuidedMaxRounds, 2, 4);
|
|
72
|
+
const recoveryTokenBudget = positiveInteger(
|
|
73
|
+
config.proofGuidedRecoveryTokens,
|
|
74
|
+
1200,
|
|
75
|
+
100000,
|
|
76
|
+
);
|
|
77
|
+
const maxRecoveryMessages = positiveInteger(
|
|
78
|
+
config.proofGuidedMaxMessages,
|
|
79
|
+
3,
|
|
80
|
+
16,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
async onLlmOutput(event) {
|
|
85
|
+
const state = proofStateBySession.get(event?.sessionId);
|
|
86
|
+
if (!state || state.disabled || state.attempts >= maxRounds) return;
|
|
87
|
+
const output = Array.isArray(event.assistantTexts)
|
|
88
|
+
? event.assistantTexts.filter((value) => typeof value === "string").join("\n\n")
|
|
89
|
+
: "";
|
|
90
|
+
if (!output.trim()) return;
|
|
91
|
+
const outputSha = sha256(output);
|
|
92
|
+
if (state.lastOutputSha256 === outputSha && state.lastProofResult) return;
|
|
93
|
+
try {
|
|
94
|
+
const result = validateProofResult(
|
|
95
|
+
await bridge.request({
|
|
96
|
+
operation: "verify_proof_guided_output",
|
|
97
|
+
session_id: event.sessionId,
|
|
98
|
+
run_id: event.runId,
|
|
99
|
+
round_index: state.attempts,
|
|
100
|
+
source_messages: state.sourceMessages,
|
|
101
|
+
assembled_messages: state.assembledMessages,
|
|
102
|
+
recovered_messages: state.recoveredMessages,
|
|
103
|
+
model_output: output,
|
|
104
|
+
workspace_dir: state.workspaceDir,
|
|
105
|
+
profile: config.proofGuidedProfile ?? "rag",
|
|
106
|
+
recovery_token_budget: recoveryTokenBudget,
|
|
107
|
+
max_recovery_messages: maxRecoveryMessages,
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
state.attempts += 1;
|
|
111
|
+
state.runId = event.runId;
|
|
112
|
+
state.lastOutputSha256 = outputSha;
|
|
113
|
+
state.lastProofResult = result;
|
|
114
|
+
state.recoveredMessages = mergeRecoveredMessages(
|
|
115
|
+
state.recoveredMessages,
|
|
116
|
+
result.recovered_messages,
|
|
117
|
+
);
|
|
118
|
+
state.retryIssued = false;
|
|
119
|
+
statusBySession.set(event.sessionId, {
|
|
120
|
+
...(statusBySession.get(event.sessionId) ?? {}),
|
|
121
|
+
proof_guided_status: result.status,
|
|
122
|
+
proof_guided_attempts: state.attempts,
|
|
123
|
+
proof_guided_audit_artifact_id: result.audit_artifact_id,
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
state.disabled = true;
|
|
127
|
+
state.error = safeDiagnostic(error);
|
|
128
|
+
state.runId = event?.runId;
|
|
129
|
+
state.lastProofResult = {
|
|
130
|
+
status: "verification_error",
|
|
131
|
+
verified_output:
|
|
132
|
+
"Entroly withheld this response because local proof verification failed. " +
|
|
133
|
+
"Review the OpenClaw plugin log and retry after resolving the reported error.",
|
|
134
|
+
changed: true,
|
|
135
|
+
recovered_messages: [],
|
|
136
|
+
audit_artifact_id: "",
|
|
137
|
+
};
|
|
138
|
+
statusBySession.set(event.sessionId, {
|
|
139
|
+
...(statusBySession.get(event.sessionId) ?? {}),
|
|
140
|
+
proof_guided_status: "verification_error",
|
|
141
|
+
proof_guided_attempts: state.attempts,
|
|
142
|
+
error: state.error,
|
|
143
|
+
});
|
|
144
|
+
logger.warn?.(
|
|
145
|
+
`entroly: proof-guided verification failed; delivery will be withheld: ${state.error}`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
|
|
150
|
+
async onBeforeAgentFinalize(event) {
|
|
151
|
+
const state = proofStateBySession.get(event?.sessionId);
|
|
152
|
+
const result = state?.lastProofResult;
|
|
153
|
+
if (
|
|
154
|
+
!state ||
|
|
155
|
+
!result ||
|
|
156
|
+
state.runId !== event?.runId ||
|
|
157
|
+
result.status !== "retry_with_exact_evidence" ||
|
|
158
|
+
state.attempts >= maxRounds ||
|
|
159
|
+
state.retryIssued
|
|
160
|
+
) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
state.retryIssued = true;
|
|
164
|
+
return {
|
|
165
|
+
action: "revise",
|
|
166
|
+
reason: "Entroly found unsupported claims and recovered exact omitted evidence.",
|
|
167
|
+
retry: {
|
|
168
|
+
instruction: result.retry_instruction,
|
|
169
|
+
idempotencyKey: `entroly-proof-${sha256(
|
|
170
|
+
`${event.runId}:${result.audit_artifact_id}:${state.attempts}`,
|
|
171
|
+
).slice(0, 24)}`,
|
|
172
|
+
maxAttempts: 1,
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
|
|
177
|
+
async onReplyPayloadSending(event) {
|
|
178
|
+
const sessionId = event?.usageState?.sessionId;
|
|
179
|
+
const state = sessionId ? proofStateBySession.get(sessionId) : undefined;
|
|
180
|
+
const result = state?.lastProofResult;
|
|
181
|
+
if (
|
|
182
|
+
!state ||
|
|
183
|
+
!result ||
|
|
184
|
+
(event?.runId && state.runId !== event.runId) ||
|
|
185
|
+
result.changed !== true ||
|
|
186
|
+
typeof event?.payload?.text !== "string"
|
|
187
|
+
) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
payload: {
|
|
192
|
+
...event.payload,
|
|
193
|
+
text: result.verified_output,
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|