@remnic/plugin-openclaw 1.0.10 → 1.0.12
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/dist/{calibration-674TDQNV.js → calibration-WCHOK6DX.js} +12 -4
- package/dist/capsule-cli-GBM3WPAM.js +33 -0
- package/dist/capsule-crypto-K3IRTKRH.js +17 -0
- package/dist/capsule-export-IXVERCQG.js +17 -0
- package/dist/capsule-import-IA6VIOPQ.js +16 -0
- package/dist/capsule-merge-IWOQ34KL.js +189 -0
- package/dist/{causal-chain-OKDZSDEB.js → causal-chain-WYN5QOPS.js} +3 -2
- package/dist/{causal-consolidation-5BEXLQV5.js → causal-consolidation-YI53C2AO.js} +16 -12
- package/dist/{causal-retrieval-3BKBXVXD.js → causal-retrieval-NZHQOZOE.js} +6 -5
- package/dist/{causal-trajectory-graph-RQIT37DN.js → causal-trajectory-graph-VBPE2WPM.js} +1 -1
- package/dist/chunk-37NKFWSO.js +233 -0
- package/dist/chunk-3G7FAF6S.js +60 -0
- package/dist/{chunk-Z7GRLVK3.js → chunk-3GUF7RQI.js} +235 -19
- package/dist/chunk-4G2XCSD2.js +186 -0
- package/dist/chunk-4LYQ4ONL.js +185 -0
- package/dist/chunk-6F6EKSVP.js +453 -0
- package/dist/chunk-6IWEAUN6.js +148 -0
- package/dist/{chunk-LN5UZQVG.js → chunk-6UFI73TJ.js} +5 -3
- package/dist/chunk-7OQEPGQF.js +529 -0
- package/dist/{chunk-JJSNPSCD.js → chunk-7UZNLMW5.js} +652 -174
- package/dist/chunk-B52XADV3.js +244 -0
- package/dist/chunk-BU5KJVWF.js +78 -0
- package/dist/chunk-CDAZGIGT.js +720 -0
- package/dist/chunk-CXM7EBAO.js +289 -0
- package/dist/{chunk-HCFFXBLV.js → chunk-EXDYWXMB.js} +6 -1861
- package/dist/chunk-FGTYFLL5.js +274 -0
- package/dist/chunk-FQRSVYY4.js +110 -0
- package/dist/chunk-HRGFO6AW.js +349 -0
- package/dist/chunk-I6B2W2IY.js +47 -0
- package/dist/chunk-JZBOXOUC.js +259 -0
- package/dist/chunk-L6I4MQKO.js +227 -0
- package/dist/chunk-LLUROTZJ.js +328 -0
- package/dist/chunk-MBIFE6SA.js +250 -0
- package/dist/chunk-NKVIN6RD.js +118 -0
- package/dist/chunk-OAE7AQ6R.js +1832 -0
- package/dist/chunk-OEI7GLV2.js +17 -0
- package/dist/chunk-RKR6PTPA.js +308 -0
- package/dist/{chunk-7TENHBV2.js → chunk-RQCTMECT.js} +10 -48
- package/dist/chunk-SSFTU6LP.js +182 -0
- package/dist/{chunk-BXTMZDRT.js → chunk-SVSQAG6M.js} +7 -5
- package/dist/{chunk-S2ISS4AH.js → chunk-TILAJIJR.js} +10 -10
- package/dist/chunk-TLVIQLB4.js +874 -0
- package/dist/{chunk-YHH3SXKD.js → chunk-WPINX4MF.js} +1 -59
- package/dist/chunk-YGGGUTG3.js +125 -0
- package/dist/cipher-VHAFCG7Z.js +27 -0
- package/dist/dreams-ledger-3I52ISYR.js +285 -0
- package/dist/{engine-65C2J63X.js → engine-BIYI3P4J.js} +7 -2
- package/dist/{fallback-llm-LVK5PDIM.js → fallback-llm-WCWNGIQ3.js} +2 -1
- package/dist/first-start-migration-I24M2JEE.js +258 -0
- package/dist/forget-NI4RBDPB.js +68 -0
- package/dist/fs-utils-PZRI2HDZ.js +29 -0
- package/dist/graph-edge-decay-5CVKWBYH.js +203 -0
- package/dist/index.js +10654 -3067
- package/dist/kdf-H5B23ZM2.js +25 -0
- package/dist/memory-governance-SJ5DGRB3.js +25 -0
- package/dist/metadata-JAGIWHEA.js +20 -0
- package/dist/migrate-from-identity-anchor-N3354WMP.js +7 -0
- package/dist/path-5LCUBAAZ.js +8 -0
- package/dist/peers-JF2I6RCR.js +43 -0
- package/dist/purge-XN2VSPZ2.js +204 -0
- package/dist/secure-store-A4NGCNXV.js +155 -0
- package/dist/state-PVISYXRH.js +7 -0
- package/dist/state-store-LP5BO6SF.js +15 -0
- package/dist/{storage-DM4ZGOCN.js → storage-PTQ2H2YJ.js} +3 -1
- package/dist/tier-stats-IZNW66NC.js +147 -0
- package/dist/trace-NJESSGH7.js +289 -0
- package/dist/tui-MGK2LYJY.js +12 -0
- package/dist/types-R4DO7AKM.js +30 -0
- package/openclaw.plugin.json +519 -4
- package/package.json +2 -2
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
// ../remnic-core/src/secure-store/kdf.ts
|
|
2
|
+
import { scryptSync, timingSafeEqual } from "crypto";
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
var requireArgon2 = createRequire(import.meta.url);
|
|
5
|
+
var argon2Runtime;
|
|
6
|
+
function loadArgon2Runtime() {
|
|
7
|
+
try {
|
|
8
|
+
argon2Runtime ??= requireArgon2("@node-rs/argon2");
|
|
9
|
+
return argon2Runtime;
|
|
10
|
+
} catch (cause) {
|
|
11
|
+
throw new Error(
|
|
12
|
+
"Argon2id KDF requires @node-rs/argon2 to be installed and loadable on this platform. Use scrypt compatibility mode for stores that must run without the Argon2 native binding.",
|
|
13
|
+
{ cause }
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
var DEFAULT_SCRYPT_PARAMS = Object.freeze({
|
|
18
|
+
// 2^17. Hex-coding the literal would obscure the doubling chain.
|
|
19
|
+
N: 1 << 17,
|
|
20
|
+
r: 8,
|
|
21
|
+
p: 1,
|
|
22
|
+
keyLength: 32,
|
|
23
|
+
// 256 MiB ceiling — comfortably above the 128 MiB scrypt needs at N=2^17.
|
|
24
|
+
maxmem: 256 * 1024 * 1024
|
|
25
|
+
});
|
|
26
|
+
var DEFAULT_ARGON2ID_PARAMS = Object.freeze({
|
|
27
|
+
memoryKiB: 64 * 1024,
|
|
28
|
+
iterations: 3,
|
|
29
|
+
parallelism: 4,
|
|
30
|
+
keyLength: 32
|
|
31
|
+
});
|
|
32
|
+
var KDF_SALT_LENGTH = 16;
|
|
33
|
+
var KDF_KEY_LENGTH = 32;
|
|
34
|
+
function validateScryptParams(params) {
|
|
35
|
+
const { N, r, p, keyLength, maxmem } = params;
|
|
36
|
+
if (!Number.isInteger(N) || N < 2) {
|
|
37
|
+
throw new Error(`scrypt N must be an integer \u2265 2, got ${N}`);
|
|
38
|
+
}
|
|
39
|
+
if (N > 2 ** 30) {
|
|
40
|
+
throw new Error(`scrypt N is unreasonably large (max 2^30), got ${N}`);
|
|
41
|
+
}
|
|
42
|
+
if (!Number.isInteger(Math.log2(N))) {
|
|
43
|
+
throw new Error(`scrypt N must be a power of 2, got ${N}`);
|
|
44
|
+
}
|
|
45
|
+
if (!Number.isInteger(r) || r < 1) {
|
|
46
|
+
throw new Error(`scrypt r must be a positive integer, got ${r}`);
|
|
47
|
+
}
|
|
48
|
+
if (!Number.isInteger(p) || p < 1) {
|
|
49
|
+
throw new Error(`scrypt p must be a positive integer, got ${p}`);
|
|
50
|
+
}
|
|
51
|
+
if (!Number.isInteger(keyLength) || keyLength < 16) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`scrypt keyLength must be \u2265 16 (need 32 for AES-256), got ${keyLength}`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
if (!Number.isInteger(maxmem) || maxmem < 1024) {
|
|
57
|
+
throw new Error(`scrypt maxmem must be a sane positive integer, got ${maxmem}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
function validateArgon2idParams(params) {
|
|
61
|
+
const { memoryKiB, iterations, parallelism, keyLength } = params;
|
|
62
|
+
if (!Number.isInteger(memoryKiB) || memoryKiB < 8) {
|
|
63
|
+
throw new Error(`argon2id memoryKiB must be an integer \u2265 8, got ${memoryKiB}`);
|
|
64
|
+
}
|
|
65
|
+
if (!Number.isInteger(iterations) || iterations < 1) {
|
|
66
|
+
throw new Error(`argon2id iterations must be a positive integer, got ${iterations}`);
|
|
67
|
+
}
|
|
68
|
+
if (!Number.isInteger(parallelism) || parallelism < 1 || parallelism > 255) {
|
|
69
|
+
throw new Error(`argon2id parallelism must be an integer in [1, 255], got ${parallelism}`);
|
|
70
|
+
}
|
|
71
|
+
if (!Number.isInteger(keyLength) || keyLength !== KDF_KEY_LENGTH) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`argon2id keyLength must be ${KDF_KEY_LENGTH} (AES-256 requires a 32-byte key), got ${keyLength}`
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function deriveKeyScrypt(passphrase, salt, params = DEFAULT_SCRYPT_PARAMS) {
|
|
78
|
+
if (typeof passphrase !== "string") {
|
|
79
|
+
throw new Error("passphrase must be a string");
|
|
80
|
+
}
|
|
81
|
+
if (passphrase.length === 0) {
|
|
82
|
+
throw new Error("passphrase must not be empty");
|
|
83
|
+
}
|
|
84
|
+
if (!(salt instanceof Uint8Array) || salt.length < 8) {
|
|
85
|
+
throw new Error(
|
|
86
|
+
`salt must be a Uint8Array of at least 8 bytes, got ${salt?.length ?? "non-buffer"}`
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
validateScryptParams(params);
|
|
90
|
+
return scryptSync(passphrase, Buffer.from(salt), params.keyLength, {
|
|
91
|
+
N: params.N,
|
|
92
|
+
r: params.r,
|
|
93
|
+
p: params.p,
|
|
94
|
+
maxmem: params.maxmem
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function deriveKeyArgon2id(passphrase, salt, params = DEFAULT_ARGON2ID_PARAMS) {
|
|
98
|
+
if (typeof passphrase !== "string") {
|
|
99
|
+
throw new Error("passphrase must be a string");
|
|
100
|
+
}
|
|
101
|
+
if (passphrase.length === 0) {
|
|
102
|
+
throw new Error("passphrase must not be empty");
|
|
103
|
+
}
|
|
104
|
+
if (!(salt instanceof Uint8Array) || salt.length < 8) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`salt must be a Uint8Array of at least 8 bytes, got ${salt?.length ?? "non-buffer"}`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
validateArgon2idParams(params);
|
|
110
|
+
const { Algorithm, Version, hashRawSync } = loadArgon2Runtime();
|
|
111
|
+
return hashRawSync(passphrase, {
|
|
112
|
+
algorithm: Algorithm.Argon2id,
|
|
113
|
+
version: Version.V0x13,
|
|
114
|
+
memoryCost: params.memoryKiB,
|
|
115
|
+
timeCost: params.iterations,
|
|
116
|
+
parallelism: params.parallelism,
|
|
117
|
+
outputLen: params.keyLength,
|
|
118
|
+
salt
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
function deriveKey(algorithm, passphrase, salt, params) {
|
|
122
|
+
if (algorithm === "scrypt") {
|
|
123
|
+
return deriveKeyScrypt(passphrase, salt, params);
|
|
124
|
+
}
|
|
125
|
+
if (algorithm === "argon2id") {
|
|
126
|
+
return deriveKeyArgon2id(passphrase, salt, params);
|
|
127
|
+
}
|
|
128
|
+
throw new Error(`unknown KDF algorithm: ${algorithm}`);
|
|
129
|
+
}
|
|
130
|
+
function constantTimeEqual(a, b) {
|
|
131
|
+
if (a.length !== b.length) {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
return timingSafeEqual(Buffer.from(a), Buffer.from(b));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
DEFAULT_SCRYPT_PARAMS,
|
|
139
|
+
DEFAULT_ARGON2ID_PARAMS,
|
|
140
|
+
KDF_SALT_LENGTH,
|
|
141
|
+
KDF_KEY_LENGTH,
|
|
142
|
+
validateScryptParams,
|
|
143
|
+
validateArgon2idParams,
|
|
144
|
+
deriveKeyScrypt,
|
|
145
|
+
deriveKeyArgon2id,
|
|
146
|
+
deriveKey,
|
|
147
|
+
constantTimeEqual
|
|
148
|
+
};
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
countRecallTokenOverlap,
|
|
3
|
+
normalizeRecallTokens
|
|
4
|
+
} from "./chunk-WPINX4MF.js";
|
|
1
5
|
import {
|
|
2
6
|
assertIsoRecordedAt,
|
|
3
7
|
assertSafePathSegment,
|
|
4
8
|
assertString,
|
|
5
|
-
countRecallTokenOverlap,
|
|
6
9
|
isRecord,
|
|
7
|
-
normalizeRecallTokens,
|
|
8
10
|
optionalString,
|
|
9
11
|
optionalStringArray,
|
|
10
12
|
validateStringRecord
|
|
11
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-3G7FAF6S.js";
|
|
12
14
|
import {
|
|
13
15
|
listJsonFiles,
|
|
14
16
|
readJsonFile
|
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clamp01,
|
|
3
|
+
computeLifecycleValueInputs,
|
|
4
|
+
daysSince
|
|
5
|
+
} from "./chunk-4G2XCSD2.js";
|
|
6
|
+
import {
|
|
7
|
+
assertIsoRecordedAt,
|
|
8
|
+
assertSafePathSegment,
|
|
9
|
+
assertString,
|
|
10
|
+
isRecord,
|
|
11
|
+
optionalStringArray,
|
|
12
|
+
recordStoreDay,
|
|
13
|
+
validateStringRecord
|
|
14
|
+
} from "./chunk-3G7FAF6S.js";
|
|
15
|
+
import {
|
|
16
|
+
listJsonFiles,
|
|
17
|
+
readJsonFile
|
|
18
|
+
} from "./chunk-5LE4HTVL.js";
|
|
19
|
+
|
|
20
|
+
// ../remnic-core/src/tier-routing.ts
|
|
21
|
+
function computeTierValueScore(memory, now, signals) {
|
|
22
|
+
const fm = memory.frontmatter;
|
|
23
|
+
const inputs = computeLifecycleValueInputs(memory, now, signals);
|
|
24
|
+
const correctionBoost = fm.category === "correction" ? 0.08 : 0;
|
|
25
|
+
const confirmedBoost = fm.verificationState === "user_confirmed" ? 0.05 : 0;
|
|
26
|
+
const score = inputs.confidence * 0.24 + inputs.access * 0.26 + inputs.recency * 0.2 + inputs.importance * 0.2 + inputs.feedback * 0.1 + correctionBoost + confirmedBoost - inputs.disputedPenalty * 0.5;
|
|
27
|
+
return clamp01(score);
|
|
28
|
+
}
|
|
29
|
+
function decideTierTransition(memory, currentTier, policy, now, signals) {
|
|
30
|
+
const valueScore = computeTierValueScore(memory, now, signals);
|
|
31
|
+
if (!policy.enabled) {
|
|
32
|
+
return {
|
|
33
|
+
currentTier,
|
|
34
|
+
nextTier: currentTier,
|
|
35
|
+
valueScore,
|
|
36
|
+
changed: false,
|
|
37
|
+
reason: "tier_migration_disabled"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (currentTier === "hot") {
|
|
41
|
+
const ageDays = daysSince(memory.frontmatter.updated ?? memory.frontmatter.created, now.getTime());
|
|
42
|
+
if (ageDays >= policy.demotionMinAgeDays && valueScore <= policy.demotionValueThreshold) {
|
|
43
|
+
return {
|
|
44
|
+
currentTier,
|
|
45
|
+
nextTier: "cold",
|
|
46
|
+
valueScore,
|
|
47
|
+
changed: true,
|
|
48
|
+
reason: "value_below_demotion_threshold"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
currentTier,
|
|
53
|
+
nextTier: currentTier,
|
|
54
|
+
valueScore,
|
|
55
|
+
changed: false,
|
|
56
|
+
reason: ageDays < policy.demotionMinAgeDays ? "demotion_min_age_not_met" : "value_above_demotion_threshold"
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
if (valueScore >= policy.promotionValueThreshold) {
|
|
60
|
+
return {
|
|
61
|
+
currentTier,
|
|
62
|
+
nextTier: "hot",
|
|
63
|
+
valueScore,
|
|
64
|
+
changed: true,
|
|
65
|
+
reason: "value_above_promotion_threshold"
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
currentTier,
|
|
70
|
+
nextTier: currentTier,
|
|
71
|
+
valueScore,
|
|
72
|
+
changed: false,
|
|
73
|
+
reason: "value_below_promotion_threshold"
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ../remnic-core/src/utility-learner.ts
|
|
78
|
+
import path2 from "path";
|
|
79
|
+
import { mkdir as mkdir2, readFile, rename, writeFile as writeFile2 } from "fs/promises";
|
|
80
|
+
|
|
81
|
+
// ../remnic-core/src/utility-telemetry.ts
|
|
82
|
+
import path from "path";
|
|
83
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
84
|
+
function resolveUtilityTelemetryDir(memoryDir, overrideDir) {
|
|
85
|
+
if (typeof overrideDir === "string" && overrideDir.trim().length > 0) {
|
|
86
|
+
return overrideDir.trim();
|
|
87
|
+
}
|
|
88
|
+
return path.join(memoryDir, "state", "utility-telemetry");
|
|
89
|
+
}
|
|
90
|
+
function assertUtilityScore(value) {
|
|
91
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
92
|
+
throw new Error("utilityScore must be a finite number");
|
|
93
|
+
}
|
|
94
|
+
if (value < -1 || value > 1) {
|
|
95
|
+
throw new Error("utilityScore must be between -1 and 1");
|
|
96
|
+
}
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
function validateUtilityTelemetryEvent(raw) {
|
|
100
|
+
if (!isRecord(raw)) throw new Error("utility telemetry event must be an object");
|
|
101
|
+
if (raw.schemaVersion !== 1) throw new Error("schemaVersion must be 1");
|
|
102
|
+
const source = assertString(raw.source, "source");
|
|
103
|
+
if (!["cli", "system", "benchmark", "tool_result"].includes(source)) {
|
|
104
|
+
throw new Error("source must be one of cli|system|benchmark|tool_result");
|
|
105
|
+
}
|
|
106
|
+
const target = assertString(raw.target, "target");
|
|
107
|
+
if (!["promotion", "ranking"].includes(target)) {
|
|
108
|
+
throw new Error("target must be one of promotion|ranking");
|
|
109
|
+
}
|
|
110
|
+
const decision = assertString(raw.decision, "decision");
|
|
111
|
+
if (!["promote", "demote", "hold", "boost", "suppress"].includes(decision)) {
|
|
112
|
+
throw new Error("decision must be one of promote|demote|hold|boost|suppress");
|
|
113
|
+
}
|
|
114
|
+
const outcome = assertString(raw.outcome, "outcome");
|
|
115
|
+
if (!["helpful", "neutral", "harmful"].includes(outcome)) {
|
|
116
|
+
throw new Error("outcome must be one of helpful|neutral|harmful");
|
|
117
|
+
}
|
|
118
|
+
return {
|
|
119
|
+
schemaVersion: 1,
|
|
120
|
+
eventId: assertSafePathSegment(assertString(raw.eventId, "eventId"), "eventId"),
|
|
121
|
+
recordedAt: assertIsoRecordedAt(assertString(raw.recordedAt, "recordedAt")),
|
|
122
|
+
sessionKey: assertString(raw.sessionKey, "sessionKey"),
|
|
123
|
+
source,
|
|
124
|
+
target,
|
|
125
|
+
decision,
|
|
126
|
+
outcome,
|
|
127
|
+
utilityScore: assertUtilityScore(raw.utilityScore),
|
|
128
|
+
summary: assertString(raw.summary, "summary"),
|
|
129
|
+
memoryIds: optionalStringArray(raw.memoryIds, "memoryIds"),
|
|
130
|
+
entityRefs: optionalStringArray(raw.entityRefs, "entityRefs"),
|
|
131
|
+
tags: optionalStringArray(raw.tags, "tags"),
|
|
132
|
+
metadata: validateStringRecord(raw.metadata, "metadata")
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async function recordUtilityTelemetryEvent(options) {
|
|
136
|
+
const rootDir = resolveUtilityTelemetryDir(options.memoryDir, options.utilityTelemetryDir);
|
|
137
|
+
const validated = validateUtilityTelemetryEvent(options.event);
|
|
138
|
+
const day = recordStoreDay(validated.recordedAt);
|
|
139
|
+
const eventsDir = path.join(rootDir, "events", day);
|
|
140
|
+
const filePath = path.join(eventsDir, `${validated.eventId}.json`);
|
|
141
|
+
await mkdir(eventsDir, { recursive: true });
|
|
142
|
+
await writeFile(filePath, JSON.stringify(validated, null, 2), "utf8");
|
|
143
|
+
return filePath;
|
|
144
|
+
}
|
|
145
|
+
async function readUtilityTelemetryEvents(options) {
|
|
146
|
+
const rootDir = resolveUtilityTelemetryDir(options.memoryDir, options.utilityTelemetryDir);
|
|
147
|
+
const files = await listJsonFiles(path.join(rootDir, "events"));
|
|
148
|
+
const events = [];
|
|
149
|
+
const invalidEvents = [];
|
|
150
|
+
for (const filePath of files) {
|
|
151
|
+
try {
|
|
152
|
+
events.push(validateUtilityTelemetryEvent(await readJsonFile(filePath)));
|
|
153
|
+
} catch (error) {
|
|
154
|
+
invalidEvents.push({
|
|
155
|
+
path: filePath,
|
|
156
|
+
error: error instanceof Error ? error.message : String(error)
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return { files, events, invalidEvents };
|
|
161
|
+
}
|
|
162
|
+
async function getUtilityTelemetryStatus(options) {
|
|
163
|
+
const rootDir = resolveUtilityTelemetryDir(options.memoryDir, options.utilityTelemetryDir);
|
|
164
|
+
const eventsDir = path.join(rootDir, "events");
|
|
165
|
+
if (!options.enabled) {
|
|
166
|
+
return {
|
|
167
|
+
enabled: false,
|
|
168
|
+
promotionByOutcomeEnabled: options.promotionByOutcomeEnabled === true,
|
|
169
|
+
rootDir,
|
|
170
|
+
eventsDir,
|
|
171
|
+
events: {
|
|
172
|
+
total: 0,
|
|
173
|
+
valid: 0,
|
|
174
|
+
invalid: 0,
|
|
175
|
+
byTarget: {},
|
|
176
|
+
byDecision: {},
|
|
177
|
+
byOutcome: {}
|
|
178
|
+
},
|
|
179
|
+
invalidEvents: []
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
const { files, events, invalidEvents } = await readUtilityTelemetryEvents(options);
|
|
183
|
+
events.sort((a, b) => b.recordedAt.localeCompare(a.recordedAt));
|
|
184
|
+
const byTarget = {};
|
|
185
|
+
const byDecision = {};
|
|
186
|
+
const byOutcome = {};
|
|
187
|
+
for (const event of events) {
|
|
188
|
+
byTarget[event.target] = (byTarget[event.target] ?? 0) + 1;
|
|
189
|
+
byDecision[event.decision] = (byDecision[event.decision] ?? 0) + 1;
|
|
190
|
+
byOutcome[event.outcome] = (byOutcome[event.outcome] ?? 0) + 1;
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
enabled: true,
|
|
194
|
+
promotionByOutcomeEnabled: options.promotionByOutcomeEnabled === true,
|
|
195
|
+
rootDir,
|
|
196
|
+
eventsDir,
|
|
197
|
+
events: {
|
|
198
|
+
total: files.length,
|
|
199
|
+
valid: events.length,
|
|
200
|
+
invalid: invalidEvents.length,
|
|
201
|
+
byTarget,
|
|
202
|
+
byDecision,
|
|
203
|
+
byOutcome,
|
|
204
|
+
latestEventId: events[0]?.eventId,
|
|
205
|
+
latestRecordedAt: events[0]?.recordedAt,
|
|
206
|
+
latestSessionKey: events[0]?.sessionKey
|
|
207
|
+
},
|
|
208
|
+
latestEvent: events[0],
|
|
209
|
+
invalidEvents
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ../remnic-core/src/utility-learner.ts
|
|
214
|
+
var UTILITY_LEARNING_SNAPSHOT_VERSION = 1;
|
|
215
|
+
var UTILITY_LEARNING_STATE_FILE = "learning-state.json";
|
|
216
|
+
function clampWeight(value, maxWeightMagnitude) {
|
|
217
|
+
const limit = Number.isFinite(maxWeightMagnitude) && maxWeightMagnitude > 0 ? maxWeightMagnitude : 0;
|
|
218
|
+
return Math.max(-limit, Math.min(limit, value));
|
|
219
|
+
}
|
|
220
|
+
function coerceLearningWindowDays(value) {
|
|
221
|
+
if (!Number.isFinite(value)) return 14;
|
|
222
|
+
return Math.max(1, Math.floor(value));
|
|
223
|
+
}
|
|
224
|
+
function coerceMinEventCount(value) {
|
|
225
|
+
if (!Number.isFinite(value)) return 3;
|
|
226
|
+
return Math.max(1, Math.floor(value));
|
|
227
|
+
}
|
|
228
|
+
function coerceMaxWeightMagnitude(value) {
|
|
229
|
+
if (!Number.isFinite(value)) return 0.35;
|
|
230
|
+
return Math.max(0, Math.min(1, value));
|
|
231
|
+
}
|
|
232
|
+
function roundWeight(value) {
|
|
233
|
+
return Math.round(value * 1e3) / 1e3;
|
|
234
|
+
}
|
|
235
|
+
function outcomeCountsFor(events) {
|
|
236
|
+
const counts = {};
|
|
237
|
+
for (const event of events) {
|
|
238
|
+
counts[event.outcome] = (counts[event.outcome] ?? 0) + 1;
|
|
239
|
+
}
|
|
240
|
+
return counts;
|
|
241
|
+
}
|
|
242
|
+
function selectRecentEvents(events, now, windowDays) {
|
|
243
|
+
if (!Number.isFinite(windowDays) || windowDays <= 0) return [...events];
|
|
244
|
+
const minTimestamp = now.getTime() - windowDays * 864e5;
|
|
245
|
+
return events.filter((event) => {
|
|
246
|
+
const ts = Date.parse(event.recordedAt);
|
|
247
|
+
return Number.isFinite(ts) && ts >= minTimestamp;
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
function confidenceFromEvents(eventCount, averageUtilityScore) {
|
|
251
|
+
if (eventCount <= 0) return 0;
|
|
252
|
+
return roundWeight(clamp01(Math.abs(averageUtilityScore) * Math.min(1, eventCount / 10)));
|
|
253
|
+
}
|
|
254
|
+
function validateUtilityLearningSnapshot(raw) {
|
|
255
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
256
|
+
throw new Error("utility learning snapshot must be an object");
|
|
257
|
+
}
|
|
258
|
+
const record = raw;
|
|
259
|
+
if (record.version !== UTILITY_LEARNING_SNAPSHOT_VERSION) {
|
|
260
|
+
throw new Error("utility learning snapshot version must be 1");
|
|
261
|
+
}
|
|
262
|
+
if (typeof record.updatedAt !== "string" || record.updatedAt.length === 0) {
|
|
263
|
+
throw new Error("utility learning snapshot updatedAt must be a string");
|
|
264
|
+
}
|
|
265
|
+
if (typeof record.windowDays !== "number" || !Number.isFinite(record.windowDays) || record.windowDays < 0) {
|
|
266
|
+
throw new Error("utility learning snapshot windowDays must be a non-negative number");
|
|
267
|
+
}
|
|
268
|
+
if (typeof record.minEventCount !== "number" || !Number.isFinite(record.minEventCount) || record.minEventCount < 1) {
|
|
269
|
+
throw new Error("utility learning snapshot minEventCount must be >= 1");
|
|
270
|
+
}
|
|
271
|
+
if (typeof record.maxWeightMagnitude !== "number" || !Number.isFinite(record.maxWeightMagnitude) || record.maxWeightMagnitude < 0) {
|
|
272
|
+
throw new Error("utility learning snapshot maxWeightMagnitude must be >= 0");
|
|
273
|
+
}
|
|
274
|
+
if (!Array.isArray(record.weights)) {
|
|
275
|
+
throw new Error("utility learning snapshot weights must be an array");
|
|
276
|
+
}
|
|
277
|
+
const weights = record.weights.map((entry) => {
|
|
278
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
279
|
+
throw new Error("utility learning weight must be an object");
|
|
280
|
+
}
|
|
281
|
+
const weight = entry;
|
|
282
|
+
const target = weight.target;
|
|
283
|
+
const decision = weight.decision;
|
|
284
|
+
if (target !== "promotion" && target !== "ranking") {
|
|
285
|
+
throw new Error("utility learning weight target must be promotion|ranking");
|
|
286
|
+
}
|
|
287
|
+
if (!["promote", "demote", "hold", "boost", "suppress"].includes(String(decision))) {
|
|
288
|
+
throw new Error("utility learning weight decision is invalid");
|
|
289
|
+
}
|
|
290
|
+
if (typeof weight.eventCount !== "number" || !Number.isFinite(weight.eventCount) || weight.eventCount < 0) {
|
|
291
|
+
throw new Error("utility learning weight eventCount must be >= 0");
|
|
292
|
+
}
|
|
293
|
+
if (typeof weight.learnedWeight !== "number" || !Number.isFinite(weight.learnedWeight)) {
|
|
294
|
+
throw new Error("utility learning weight learnedWeight must be finite");
|
|
295
|
+
}
|
|
296
|
+
if (typeof weight.averageUtilityScore !== "number" || !Number.isFinite(weight.averageUtilityScore)) {
|
|
297
|
+
throw new Error("utility learning weight averageUtilityScore must be finite");
|
|
298
|
+
}
|
|
299
|
+
if (typeof weight.confidence !== "number" || !Number.isFinite(weight.confidence)) {
|
|
300
|
+
throw new Error("utility learning weight confidence must be finite");
|
|
301
|
+
}
|
|
302
|
+
if (typeof weight.updatedAt !== "string" || weight.updatedAt.length === 0) {
|
|
303
|
+
throw new Error("utility learning weight updatedAt must be a string");
|
|
304
|
+
}
|
|
305
|
+
const outcomeCounts = weight.outcomeCounts ?? {};
|
|
306
|
+
return {
|
|
307
|
+
target,
|
|
308
|
+
decision,
|
|
309
|
+
eventCount: weight.eventCount,
|
|
310
|
+
learnedWeight: roundWeight(weight.learnedWeight),
|
|
311
|
+
averageUtilityScore: roundWeight(weight.averageUtilityScore),
|
|
312
|
+
confidence: roundWeight(clamp01(weight.confidence)),
|
|
313
|
+
outcomeCounts,
|
|
314
|
+
updatedAt: weight.updatedAt
|
|
315
|
+
};
|
|
316
|
+
});
|
|
317
|
+
return {
|
|
318
|
+
version: 1,
|
|
319
|
+
updatedAt: record.updatedAt,
|
|
320
|
+
windowDays: record.windowDays,
|
|
321
|
+
minEventCount: record.minEventCount,
|
|
322
|
+
maxWeightMagnitude: record.maxWeightMagnitude,
|
|
323
|
+
weights
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
function resolveUtilityLearningStatePath(memoryDir, utilityTelemetryDir) {
|
|
327
|
+
return path2.join(resolveUtilityTelemetryDir(memoryDir, utilityTelemetryDir), UTILITY_LEARNING_STATE_FILE);
|
|
328
|
+
}
|
|
329
|
+
async function readUtilityLearningSnapshot(memoryDir, utilityTelemetryDir) {
|
|
330
|
+
const statePath = resolveUtilityLearningStatePath(memoryDir, utilityTelemetryDir);
|
|
331
|
+
try {
|
|
332
|
+
const raw = JSON.parse(await readFile(statePath, "utf8"));
|
|
333
|
+
return validateUtilityLearningSnapshot(raw);
|
|
334
|
+
} catch {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
async function writeUtilityLearningSnapshot(statePath, snapshot) {
|
|
339
|
+
const tempPath = `${statePath}.tmp`;
|
|
340
|
+
await mkdir2(path2.dirname(statePath), { recursive: true });
|
|
341
|
+
await writeFile2(tempPath, `${JSON.stringify(snapshot, null, 2)}
|
|
342
|
+
`, "utf8");
|
|
343
|
+
await rename(tempPath, statePath);
|
|
344
|
+
}
|
|
345
|
+
async function learnUtilityPromotionWeights(options) {
|
|
346
|
+
const statePath = resolveUtilityLearningStatePath(options.memoryDir, options.utilityTelemetryDir);
|
|
347
|
+
if (!options.enabled) {
|
|
348
|
+
return {
|
|
349
|
+
applied: false,
|
|
350
|
+
reason: "disabled",
|
|
351
|
+
statePath,
|
|
352
|
+
snapshot: null
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
356
|
+
const updatedAt = now.toISOString();
|
|
357
|
+
const windowDays = coerceLearningWindowDays(options.learningWindowDays);
|
|
358
|
+
const minEventCount = coerceMinEventCount(options.minEventCount);
|
|
359
|
+
const maxWeightMagnitude = coerceMaxWeightMagnitude(options.maxWeightMagnitude);
|
|
360
|
+
const recentEvents = selectRecentEvents(
|
|
361
|
+
(await readUtilityTelemetryEvents({
|
|
362
|
+
memoryDir: options.memoryDir,
|
|
363
|
+
utilityTelemetryDir: options.utilityTelemetryDir
|
|
364
|
+
})).events,
|
|
365
|
+
now,
|
|
366
|
+
windowDays
|
|
367
|
+
);
|
|
368
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
369
|
+
for (const event of recentEvents) {
|
|
370
|
+
const key = `${event.target}:${event.decision}`;
|
|
371
|
+
const existing = grouped.get(key);
|
|
372
|
+
if (existing) {
|
|
373
|
+
existing.push(event);
|
|
374
|
+
} else {
|
|
375
|
+
grouped.set(key, [event]);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
const weights = [];
|
|
379
|
+
for (const events of grouped.values()) {
|
|
380
|
+
if (events.length < minEventCount) continue;
|
|
381
|
+
const target = events[0].target;
|
|
382
|
+
const decision = events[0].decision;
|
|
383
|
+
const averageUtilityScore = events.reduce((sum, event) => sum + event.utilityScore, 0) / events.length;
|
|
384
|
+
const confidence = confidenceFromEvents(events.length, averageUtilityScore);
|
|
385
|
+
const learnedWeight = roundWeight(
|
|
386
|
+
clampWeight(averageUtilityScore * confidence, maxWeightMagnitude)
|
|
387
|
+
);
|
|
388
|
+
weights.push({
|
|
389
|
+
target,
|
|
390
|
+
decision,
|
|
391
|
+
eventCount: events.length,
|
|
392
|
+
learnedWeight,
|
|
393
|
+
averageUtilityScore: roundWeight(averageUtilityScore),
|
|
394
|
+
confidence,
|
|
395
|
+
outcomeCounts: outcomeCountsFor(events),
|
|
396
|
+
updatedAt
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
weights.sort((left, right) => {
|
|
400
|
+
const targetCompare = left.target.localeCompare(right.target);
|
|
401
|
+
if (targetCompare !== 0) return targetCompare;
|
|
402
|
+
return left.decision.localeCompare(right.decision);
|
|
403
|
+
});
|
|
404
|
+
const snapshot = {
|
|
405
|
+
version: 1,
|
|
406
|
+
updatedAt,
|
|
407
|
+
windowDays,
|
|
408
|
+
minEventCount,
|
|
409
|
+
maxWeightMagnitude,
|
|
410
|
+
weights
|
|
411
|
+
};
|
|
412
|
+
if (weights.length === 0) {
|
|
413
|
+
return {
|
|
414
|
+
applied: false,
|
|
415
|
+
reason: "insufficient_events",
|
|
416
|
+
statePath,
|
|
417
|
+
snapshot
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
await writeUtilityLearningSnapshot(statePath, snapshot);
|
|
421
|
+
return {
|
|
422
|
+
applied: true,
|
|
423
|
+
reason: "learned",
|
|
424
|
+
statePath,
|
|
425
|
+
snapshot
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
async function getUtilityLearningStatus(options) {
|
|
429
|
+
const rootDir = resolveUtilityTelemetryDir(options.memoryDir, options.utilityTelemetryDir);
|
|
430
|
+
const statePath = resolveUtilityLearningStatePath(options.memoryDir, options.utilityTelemetryDir);
|
|
431
|
+
if (!options.enabled) {
|
|
432
|
+
return {
|
|
433
|
+
enabled: false,
|
|
434
|
+
promotionByOutcomeEnabled: options.promotionByOutcomeEnabled === true,
|
|
435
|
+
rootDir,
|
|
436
|
+
statePath,
|
|
437
|
+
snapshot: null,
|
|
438
|
+
weights: {
|
|
439
|
+
total: 0,
|
|
440
|
+
positive: 0,
|
|
441
|
+
negative: 0,
|
|
442
|
+
zero: 0
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const snapshot = await readUtilityLearningSnapshot(options.memoryDir, options.utilityTelemetryDir);
|
|
447
|
+
const weights = snapshot?.weights ?? [];
|
|
448
|
+
return {
|
|
449
|
+
enabled: true,
|
|
450
|
+
promotionByOutcomeEnabled: options.promotionByOutcomeEnabled === true,
|
|
451
|
+
rootDir,
|
|
452
|
+
statePath,
|
|
453
|
+
snapshot,
|
|
454
|
+
weights: {
|
|
455
|
+
total: weights.length,
|
|
456
|
+
positive: weights.filter((entry) => entry.learnedWeight > 0).length,
|
|
457
|
+
negative: weights.filter((entry) => entry.learnedWeight < 0).length,
|
|
458
|
+
zero: weights.filter((entry) => entry.learnedWeight === 0).length,
|
|
459
|
+
latestUpdatedAt: snapshot?.updatedAt
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ../remnic-core/src/utility-runtime.ts
|
|
465
|
+
var RANKING_MULTIPLIER_LIMIT = 0.12;
|
|
466
|
+
var PROMOTION_THRESHOLD_DELTA_LIMIT = 0.07;
|
|
467
|
+
var PROMOTION_THRESHOLD_WEIGHT_FACTOR = 0.2;
|
|
468
|
+
function roundRuntimeValue(value) {
|
|
469
|
+
return Math.round(value * 1e3) / 1e3;
|
|
470
|
+
}
|
|
471
|
+
function clampSigned(value, limit) {
|
|
472
|
+
if (!Number.isFinite(value) || limit <= 0) return 0;
|
|
473
|
+
return Math.max(-limit, Math.min(limit, value));
|
|
474
|
+
}
|
|
475
|
+
function learnedWeightFor(snapshot, target, decision) {
|
|
476
|
+
const entry = snapshot.weights.find((weight) => weight.target === target && weight.decision === decision);
|
|
477
|
+
return entry?.learnedWeight ?? 0;
|
|
478
|
+
}
|
|
479
|
+
async function loadUtilityRuntimeValues(options) {
|
|
480
|
+
if (!options.memoryUtilityLearningEnabled || !options.promotionByOutcomeEnabled) return null;
|
|
481
|
+
const snapshot = await readUtilityLearningSnapshot(options.memoryDir, options.utilityTelemetryDir);
|
|
482
|
+
if (!snapshot) return null;
|
|
483
|
+
return {
|
|
484
|
+
rankingBoostMultiplier: roundRuntimeValue(
|
|
485
|
+
1 + clampSigned(learnedWeightFor(snapshot, "ranking", "boost"), RANKING_MULTIPLIER_LIMIT)
|
|
486
|
+
),
|
|
487
|
+
rankingSuppressMultiplier: roundRuntimeValue(
|
|
488
|
+
1 + clampSigned(learnedWeightFor(snapshot, "ranking", "suppress"), RANKING_MULTIPLIER_LIMIT)
|
|
489
|
+
),
|
|
490
|
+
promoteThresholdDelta: roundRuntimeValue(
|
|
491
|
+
clampSigned(
|
|
492
|
+
learnedWeightFor(snapshot, "promotion", "promote") * -PROMOTION_THRESHOLD_WEIGHT_FACTOR,
|
|
493
|
+
PROMOTION_THRESHOLD_DELTA_LIMIT
|
|
494
|
+
)
|
|
495
|
+
),
|
|
496
|
+
demoteThresholdDelta: roundRuntimeValue(
|
|
497
|
+
clampSigned(
|
|
498
|
+
learnedWeightFor(snapshot, "promotion", "demote") * PROMOTION_THRESHOLD_WEIGHT_FACTOR,
|
|
499
|
+
PROMOTION_THRESHOLD_DELTA_LIMIT
|
|
500
|
+
)
|
|
501
|
+
),
|
|
502
|
+
snapshotUpdatedAt: snapshot.updatedAt
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
function applyUtilityRankingRuntimeDelta(delta, runtime, mode) {
|
|
506
|
+
if (!runtime || !Number.isFinite(delta) || delta === 0) return delta;
|
|
507
|
+
const multiplier = mode === "boost" ? runtime.rankingBoostMultiplier : runtime.rankingSuppressMultiplier;
|
|
508
|
+
return roundRuntimeValue(delta * multiplier);
|
|
509
|
+
}
|
|
510
|
+
function applyUtilityPromotionRuntimePolicy(policy, runtime) {
|
|
511
|
+
if (!runtime) return policy;
|
|
512
|
+
return {
|
|
513
|
+
...policy,
|
|
514
|
+
demotionValueThreshold: roundRuntimeValue(clamp01(policy.demotionValueThreshold + runtime.demoteThresholdDelta)),
|
|
515
|
+
promotionValueThreshold: roundRuntimeValue(clamp01(policy.promotionValueThreshold + runtime.promoteThresholdDelta))
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
export {
|
|
520
|
+
computeTierValueScore,
|
|
521
|
+
decideTierTransition,
|
|
522
|
+
recordUtilityTelemetryEvent,
|
|
523
|
+
getUtilityTelemetryStatus,
|
|
524
|
+
learnUtilityPromotionWeights,
|
|
525
|
+
getUtilityLearningStatus,
|
|
526
|
+
loadUtilityRuntimeValues,
|
|
527
|
+
applyUtilityRankingRuntimeDelta,
|
|
528
|
+
applyUtilityPromotionRuntimePolicy
|
|
529
|
+
};
|