nemoris 0.1.0 → 0.1.2
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/.env.example +49 -49
- package/LICENSE +21 -21
- package/README.md +209 -209
- package/SECURITY.md +59 -119
- package/bin/nemoris +46 -46
- package/config/agents/agent.toml.example +28 -28
- package/config/agents/content.toml +23 -0
- package/config/agents/default.toml +22 -22
- package/config/agents/heartbeat.toml +35 -0
- package/config/agents/iris.toml +23 -0
- package/config/agents/lab.toml +23 -0
- package/config/agents/main.toml +45 -0
- package/config/agents/nemo.toml +21 -0
- package/config/agents/ops.toml +38 -0
- package/config/agents/orchestrator.toml +18 -18
- package/config/agents/revenue.toml +23 -0
- package/config/agents/testyboo.toml +19 -0
- package/config/delivery.toml +73 -73
- package/config/embeddings.toml +5 -5
- package/config/identity/content-purpose.md +11 -0
- package/config/identity/content-soul.md +45 -0
- package/config/identity/default-purpose.md +1 -1
- package/config/identity/default-soul.md +3 -3
- package/config/identity/heartbeat-purpose.md +9 -0
- package/config/identity/heartbeat-soul.md +16 -0
- package/config/identity/iris-purpose.md +17 -0
- package/config/identity/iris-soul.md +68 -0
- package/config/identity/lab-purpose.md +10 -0
- package/config/identity/lab-soul.md +38 -0
- package/config/identity/main-purpose.md +17 -0
- package/config/identity/main-soul.md +66 -0
- package/config/identity/main-user.md +22 -0
- package/config/identity/ops-purpose.md +9 -0
- package/config/identity/ops-soul.md +16 -0
- package/config/identity/orchestrator-purpose.md +1 -1
- package/config/identity/orchestrator-soul.md +1 -1
- package/config/identity/revenue-purpose.md +9 -0
- package/config/identity/revenue-soul.md +41 -0
- package/config/identity/testyboo-purpose.md +13 -0
- package/config/identity/testyboo-soul.md +20 -0
- package/config/improvement-targets.toml +15 -15
- package/config/jobs/heartbeat-check.toml +30 -30
- package/config/jobs/memory-rollup.toml +46 -46
- package/config/jobs/workspace-health.toml +63 -63
- package/config/mcp.toml +16 -16
- package/config/output-contracts.toml +17 -17
- package/config/peers.toml +32 -32
- package/config/peers.toml.example +32 -32
- package/config/policies/memory-default.toml +10 -10
- package/config/policies/memory-heartbeat.toml +5 -5
- package/config/policies/memory-ops.toml +10 -10
- package/config/policies/tools-heartbeat-minimal.toml +8 -8
- package/config/policies/tools-interactive-safe.toml +8 -8
- package/config/policies/tools-ops-bounded.toml +8 -8
- package/config/policies/tools-orchestrator.toml +7 -7
- package/config/providers/anthropic.toml +15 -15
- package/config/providers/ollama.toml +5 -5
- package/config/providers/openai-codex.toml +9 -9
- package/config/providers/openrouter.toml +5 -5
- package/config/router.toml +22 -22
- package/config/runtime.toml +114 -114
- package/config/skills/self-improvement.toml +15 -15
- package/config/skills/telegram-onboarding-spec.md +240 -240
- package/config/skills/workspace-monitor.toml +15 -15
- package/config/task-router.toml +42 -42
- package/install.sh +50 -50
- package/package.json +91 -90
- package/src/auth/auth-profiles.js +169 -169
- package/src/auth/openai-codex-oauth.js +285 -285
- package/src/battle.js +449 -449
- package/src/cli/help.js +265 -265
- package/src/cli/output-filter.js +49 -49
- package/src/cli/runtime-control.js +704 -704
- package/src/cli-main.js +2763 -2763
- package/src/cli.js +78 -78
- package/src/config/loader.js +332 -332
- package/src/config/schema-validator.js +214 -214
- package/src/config/toml-lite.js +8 -8
- package/src/daemon/action-handlers.js +71 -71
- package/src/daemon/healing-tick.js +87 -87
- package/src/daemon/health-probes.js +90 -90
- package/src/daemon/notifier.js +57 -57
- package/src/daemon/nurse.js +218 -218
- package/src/daemon/repair-log.js +106 -106
- package/src/daemon/rule-staging.js +90 -90
- package/src/daemon/rules.js +29 -29
- package/src/daemon/telegram-commands.js +54 -54
- package/src/daemon/updater.js +85 -85
- package/src/jobs/job-runner.js +78 -78
- package/src/mcp/consumer.js +129 -129
- package/src/memory/active-recall.js +171 -171
- package/src/memory/backend-manager.js +97 -97
- package/src/memory/backends/file-backend.js +38 -38
- package/src/memory/backends/qmd-backend.js +219 -219
- package/src/memory/embedding-guards.js +24 -24
- package/src/memory/embedding-index.js +118 -118
- package/src/memory/embedding-service.js +179 -179
- package/src/memory/file-index.js +177 -177
- package/src/memory/memory-signature.js +5 -5
- package/src/memory/memory-store.js +648 -648
- package/src/memory/retrieval-planner.js +66 -66
- package/src/memory/scoring.js +145 -145
- package/src/memory/simhash.js +78 -78
- package/src/memory/sqlite-active-store.js +824 -824
- package/src/memory/write-policy.js +36 -36
- package/src/onboarding/aliases.js +33 -33
- package/src/onboarding/auth/api-key.js +224 -224
- package/src/onboarding/auth/ollama-detect.js +42 -42
- package/src/onboarding/clack-prompter.js +77 -77
- package/src/onboarding/doctor.js +530 -530
- package/src/onboarding/lock.js +42 -42
- package/src/onboarding/model-catalog.js +344 -344
- package/src/onboarding/phases/auth.js +576 -589
- package/src/onboarding/phases/build.js +130 -130
- package/src/onboarding/phases/choose.js +82 -82
- package/src/onboarding/phases/detect.js +98 -98
- package/src/onboarding/phases/hatch.js +216 -216
- package/src/onboarding/phases/identity.js +79 -79
- package/src/onboarding/phases/ollama.js +345 -345
- package/src/onboarding/phases/scaffold.js +99 -99
- package/src/onboarding/phases/telegram.js +377 -377
- package/src/onboarding/phases/validate.js +204 -204
- package/src/onboarding/phases/verify.js +206 -206
- package/src/onboarding/platform.js +482 -482
- package/src/onboarding/status-bar.js +95 -95
- package/src/onboarding/templates.js +794 -794
- package/src/onboarding/toml-writer.js +38 -38
- package/src/onboarding/tui.js +250 -250
- package/src/onboarding/uninstall.js +153 -153
- package/src/onboarding/wizard.js +516 -499
- package/src/providers/anthropic.js +168 -168
- package/src/providers/base.js +247 -247
- package/src/providers/circuit-breaker.js +136 -136
- package/src/providers/ollama.js +163 -163
- package/src/providers/openai-codex.js +149 -149
- package/src/providers/openrouter.js +136 -136
- package/src/providers/registry.js +36 -36
- package/src/providers/router.js +16 -16
- package/src/runtime/bootstrap-cache.js +47 -47
- package/src/runtime/capabilities-prompt.js +25 -25
- package/src/runtime/completion-ping.js +99 -99
- package/src/runtime/config-validator.js +121 -121
- package/src/runtime/context-ledger.js +360 -360
- package/src/runtime/cutover-readiness.js +42 -42
- package/src/runtime/daemon.js +729 -729
- package/src/runtime/delivery-ack.js +195 -195
- package/src/runtime/delivery-adapters/local-file.js +41 -41
- package/src/runtime/delivery-adapters/openclaw-cli.js +94 -94
- package/src/runtime/delivery-adapters/openclaw-peer.js +98 -98
- package/src/runtime/delivery-adapters/shadow.js +13 -13
- package/src/runtime/delivery-adapters/standalone-http.js +98 -98
- package/src/runtime/delivery-adapters/telegram.js +104 -104
- package/src/runtime/delivery-adapters/tui.js +128 -128
- package/src/runtime/delivery-manager.js +807 -807
- package/src/runtime/delivery-store.js +168 -168
- package/src/runtime/dependency-health.js +118 -118
- package/src/runtime/envelope.js +114 -114
- package/src/runtime/evaluation.js +1089 -1089
- package/src/runtime/exec-approvals.js +216 -216
- package/src/runtime/executor.js +500 -500
- package/src/runtime/failure-ping.js +67 -67
- package/src/runtime/flows.js +83 -83
- package/src/runtime/guards.js +45 -45
- package/src/runtime/handoff.js +51 -51
- package/src/runtime/identity-cache.js +28 -28
- package/src/runtime/improvement-engine.js +109 -109
- package/src/runtime/improvement-harness.js +581 -581
- package/src/runtime/input-sanitiser.js +72 -72
- package/src/runtime/interaction-contract.js +347 -347
- package/src/runtime/lane-readiness.js +226 -226
- package/src/runtime/migration.js +323 -323
- package/src/runtime/model-resolution.js +78 -78
- package/src/runtime/network.js +64 -64
- package/src/runtime/notification-store.js +97 -97
- package/src/runtime/notifier.js +256 -256
- package/src/runtime/orchestrator.js +53 -53
- package/src/runtime/orphan-reaper.js +41 -41
- package/src/runtime/output-contract-schema.js +139 -139
- package/src/runtime/output-contract-validator.js +439 -439
- package/src/runtime/peer-readiness.js +69 -69
- package/src/runtime/peer-registry.js +133 -133
- package/src/runtime/pilot-status.js +108 -108
- package/src/runtime/prompt-builder.js +261 -261
- package/src/runtime/provider-attempt.js +582 -582
- package/src/runtime/report-fallback.js +71 -71
- package/src/runtime/result-normalizer.js +183 -183
- package/src/runtime/retention.js +74 -74
- package/src/runtime/review.js +244 -244
- package/src/runtime/route-job.js +15 -15
- package/src/runtime/run-store.js +38 -38
- package/src/runtime/schedule.js +88 -88
- package/src/runtime/scheduler-state.js +434 -434
- package/src/runtime/scheduler.js +656 -656
- package/src/runtime/session-compactor.js +182 -182
- package/src/runtime/session-search.js +155 -155
- package/src/runtime/slack-inbound.js +249 -249
- package/src/runtime/ssrf.js +102 -102
- package/src/runtime/status-aggregator.js +330 -330
- package/src/runtime/task-contract.js +140 -140
- package/src/runtime/task-packet.js +107 -107
- package/src/runtime/task-router.js +140 -140
- package/src/runtime/telegram-inbound.js +1565 -1565
- package/src/runtime/token-counter.js +134 -134
- package/src/runtime/token-estimator.js +59 -59
- package/src/runtime/tool-loop.js +200 -200
- package/src/runtime/transport-server.js +311 -311
- package/src/runtime/tui-server.js +411 -411
- package/src/runtime/ulid.js +44 -44
- package/src/security/ssrf-check.js +197 -197
- package/src/setup.js +369 -369
- package/src/shadow/bridge.js +303 -303
- package/src/skills/loader.js +84 -84
- package/src/tools/catalog.json +49 -49
- package/src/tools/cli-delegate.js +44 -44
- package/src/tools/mcp-client.js +106 -106
- package/src/tools/micro/cancel-task.js +6 -6
- package/src/tools/micro/complete-task.js +6 -6
- package/src/tools/micro/fail-task.js +6 -6
- package/src/tools/micro/http-fetch.js +74 -74
- package/src/tools/micro/index.js +36 -36
- package/src/tools/micro/lcm-recall.js +60 -60
- package/src/tools/micro/list-dir.js +17 -17
- package/src/tools/micro/list-skills.js +46 -46
- package/src/tools/micro/load-skill.js +38 -38
- package/src/tools/micro/memory-search.js +45 -45
- package/src/tools/micro/read-file.js +11 -11
- package/src/tools/micro/session-search.js +54 -54
- package/src/tools/micro/shell-exec.js +43 -43
- package/src/tools/micro/trigger-job.js +79 -79
- package/src/tools/micro/web-search.js +58 -58
- package/src/tools/micro/workspace-paths.js +39 -39
- package/src/tools/micro/write-file.js +14 -14
- package/src/tools/micro/write-memory.js +41 -41
- package/src/tools/registry.js +348 -348
- package/src/tools/tool-result-contract.js +36 -36
- package/src/tui/chat.js +835 -835
- package/src/tui/renderer.js +175 -175
- package/src/tui/socket-client.js +217 -217
- package/src/utils/canonical-json.js +29 -29
- package/src/utils/compaction.js +30 -30
- package/src/utils/env-loader.js +5 -5
- package/src/utils/errors.js +80 -80
- package/src/utils/fs.js +101 -101
- package/src/utils/ids.js +5 -5
- package/src/utils/model-context-limits.js +30 -30
- package/src/utils/token-budget.js +74 -74
- package/src/utils/usage-cost.js +25 -25
- package/src/utils/usage-metrics.js +14 -14
|
@@ -1,66 +1,66 @@
|
|
|
1
|
-
const STOPWORDS = new Set([
|
|
2
|
-
"the",
|
|
3
|
-
"and",
|
|
4
|
-
"with",
|
|
5
|
-
"that",
|
|
6
|
-
"this",
|
|
7
|
-
"from",
|
|
8
|
-
"into",
|
|
9
|
-
"your",
|
|
10
|
-
"have",
|
|
11
|
-
"will",
|
|
12
|
-
"would",
|
|
13
|
-
"should",
|
|
14
|
-
"could",
|
|
15
|
-
"through",
|
|
16
|
-
"using",
|
|
17
|
-
"use",
|
|
18
|
-
"for",
|
|
19
|
-
"job",
|
|
20
|
-
"type",
|
|
21
|
-
"bounded",
|
|
22
|
-
"runtime",
|
|
23
|
-
"overhead",
|
|
24
|
-
"execute",
|
|
25
|
-
"keep",
|
|
26
|
-
"only",
|
|
27
|
-
"what",
|
|
28
|
-
"deserves"
|
|
29
|
-
]);
|
|
30
|
-
|
|
31
|
-
const TASK_HINTS = {
|
|
32
|
-
heartbeat: ["heartbeat", "memory", "status", "check", "triage"],
|
|
33
|
-
workspace_health: ["workspace", "health", "status", "calendar", "issues", "weather", "blockers", "deltas"],
|
|
34
|
-
memory_rollup: ["memory", "summary", "notes", "rollup", "handoff", "inbox", "projects", "backlog", "update"]
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
function tokenize(text) {
|
|
38
|
-
return String(text || "")
|
|
39
|
-
.toLowerCase()
|
|
40
|
-
.split(/[^a-z0-9]+/i)
|
|
41
|
-
.filter(Boolean)
|
|
42
|
-
.filter((token) => token.length > 2 && !STOPWORDS.has(token));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function unique(tokens) {
|
|
46
|
-
return [...new Set(tokens)];
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function planRetrievalQueries({ objective, taskType, purpose, identity, reportGuidance = null }) {
|
|
50
|
-
const objectiveTokens = tokenize(objective);
|
|
51
|
-
const purposeTokens = tokenize(purpose || identity?.purpose || "").slice(0, 12);
|
|
52
|
-
const taskTokens = TASK_HINTS[taskType] || tokenize(String(taskType || "").replaceAll("_", " "));
|
|
53
|
-
const guidanceTokens = tokenize(
|
|
54
|
-
[reportGuidance?.focus || [], reportGuidance?.qualityChecks || []]
|
|
55
|
-
.flat()
|
|
56
|
-
.join(" ")
|
|
57
|
-
).slice(0, 12);
|
|
58
|
-
|
|
59
|
-
const fileQuery = unique([...objectiveTokens, ...taskTokens, ...purposeTokens, ...guidanceTokens]).slice(0, 22).join(" ");
|
|
60
|
-
const qmdQuery = unique([...taskTokens, ...guidanceTokens, ...purposeTokens, ...objectiveTokens]).slice(0, 10).join(" ");
|
|
61
|
-
|
|
62
|
-
return {
|
|
63
|
-
fileQuery: fileQuery || objective,
|
|
64
|
-
qmdQuery: qmdQuery || fileQuery || objective
|
|
65
|
-
};
|
|
66
|
-
}
|
|
1
|
+
const STOPWORDS = new Set([
|
|
2
|
+
"the",
|
|
3
|
+
"and",
|
|
4
|
+
"with",
|
|
5
|
+
"that",
|
|
6
|
+
"this",
|
|
7
|
+
"from",
|
|
8
|
+
"into",
|
|
9
|
+
"your",
|
|
10
|
+
"have",
|
|
11
|
+
"will",
|
|
12
|
+
"would",
|
|
13
|
+
"should",
|
|
14
|
+
"could",
|
|
15
|
+
"through",
|
|
16
|
+
"using",
|
|
17
|
+
"use",
|
|
18
|
+
"for",
|
|
19
|
+
"job",
|
|
20
|
+
"type",
|
|
21
|
+
"bounded",
|
|
22
|
+
"runtime",
|
|
23
|
+
"overhead",
|
|
24
|
+
"execute",
|
|
25
|
+
"keep",
|
|
26
|
+
"only",
|
|
27
|
+
"what",
|
|
28
|
+
"deserves"
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const TASK_HINTS = {
|
|
32
|
+
heartbeat: ["heartbeat", "memory", "status", "check", "triage"],
|
|
33
|
+
workspace_health: ["workspace", "health", "status", "calendar", "issues", "weather", "blockers", "deltas"],
|
|
34
|
+
memory_rollup: ["memory", "summary", "notes", "rollup", "handoff", "inbox", "projects", "backlog", "update"]
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
function tokenize(text) {
|
|
38
|
+
return String(text || "")
|
|
39
|
+
.toLowerCase()
|
|
40
|
+
.split(/[^a-z0-9]+/i)
|
|
41
|
+
.filter(Boolean)
|
|
42
|
+
.filter((token) => token.length > 2 && !STOPWORDS.has(token));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function unique(tokens) {
|
|
46
|
+
return [...new Set(tokens)];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function planRetrievalQueries({ objective, taskType, purpose, identity, reportGuidance = null }) {
|
|
50
|
+
const objectiveTokens = tokenize(objective);
|
|
51
|
+
const purposeTokens = tokenize(purpose || identity?.purpose || "").slice(0, 12);
|
|
52
|
+
const taskTokens = TASK_HINTS[taskType] || tokenize(String(taskType || "").replaceAll("_", " "));
|
|
53
|
+
const guidanceTokens = tokenize(
|
|
54
|
+
[reportGuidance?.focus || [], reportGuidance?.qualityChecks || []]
|
|
55
|
+
.flat()
|
|
56
|
+
.join(" ")
|
|
57
|
+
).slice(0, 12);
|
|
58
|
+
|
|
59
|
+
const fileQuery = unique([...objectiveTokens, ...taskTokens, ...purposeTokens, ...guidanceTokens]).slice(0, 22).join(" ");
|
|
60
|
+
const qmdQuery = unique([...taskTokens, ...guidanceTokens, ...purposeTokens, ...objectiveTokens]).slice(0, 10).join(" ");
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
fileQuery: fileQuery || objective,
|
|
64
|
+
qmdQuery: qmdQuery || fileQuery || objective
|
|
65
|
+
};
|
|
66
|
+
}
|
package/src/memory/scoring.js
CHANGED
|
@@ -1,145 +1,145 @@
|
|
|
1
|
-
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
2
|
-
|
|
3
|
-
const TYPE_WEIGHT = {
|
|
4
|
-
fact: 1,
|
|
5
|
-
summary: 0.9,
|
|
6
|
-
event: 0.55,
|
|
7
|
-
scratchpad: 0.75
|
|
8
|
-
};
|
|
9
|
-
|
|
10
|
-
const SYNONYMS = {
|
|
11
|
-
heartbeat: ["check", "status", "pulse", "monitor"],
|
|
12
|
-
memory: ["notes", "context", "recall", "summary"],
|
|
13
|
-
workspace: ["vault", "project", "repo", "files"],
|
|
14
|
-
health: ["status", "stability", "signal", "check"],
|
|
15
|
-
purpose: ["mission", "role", "goal"],
|
|
16
|
-
soul: ["identity", "voice", "values"],
|
|
17
|
-
cron: ["schedule", "job", "automation"]
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export const DEFAULT_RETRIEVAL_BLEND = {
|
|
21
|
-
lexicalWeight: 0.36,
|
|
22
|
-
embeddingWeight: 0.3,
|
|
23
|
-
recencyWeight: 0.14,
|
|
24
|
-
salienceWeight: 0.14,
|
|
25
|
-
typeWeight: 0.06,
|
|
26
|
-
semanticRescueBonus: 0.06,
|
|
27
|
-
shadowSnapshotPenalty: 0.12
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
function tokenize(text) {
|
|
31
|
-
return String(text || "")
|
|
32
|
-
.toLowerCase()
|
|
33
|
-
.split(/[^a-z0-9]+/i)
|
|
34
|
-
.filter(Boolean);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function expandTokens(tokens) {
|
|
38
|
-
const expanded = new Set(tokens);
|
|
39
|
-
for (const token of tokens) {
|
|
40
|
-
const synonyms = SYNONYMS[token];
|
|
41
|
-
if (!synonyms) continue;
|
|
42
|
-
for (const synonym of synonyms) expanded.add(synonym);
|
|
43
|
-
}
|
|
44
|
-
return [...expanded];
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function computeLexicalScore(query, text) {
|
|
48
|
-
const queryTokens = new Set(expandTokens(tokenize(query)));
|
|
49
|
-
if (queryTokens.size === 0) return 0;
|
|
50
|
-
const textTokens = tokenize(text);
|
|
51
|
-
if (textTokens.length === 0) return 0;
|
|
52
|
-
let hits = 0;
|
|
53
|
-
for (const token of textTokens) {
|
|
54
|
-
if (queryTokens.has(token)) hits += 1;
|
|
55
|
-
}
|
|
56
|
-
return Math.min(1, hits / Math.max(queryTokens.size, 1));
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export function computeRecencyScore(timestamp, now = Date.now(), halfLifeDays = 14) {
|
|
60
|
-
if (!timestamp) return 0.2;
|
|
61
|
-
const ageDays = Math.max(0, now - new Date(timestamp).getTime()) / DAY_MS;
|
|
62
|
-
return Math.pow(0.5, ageDays / halfLifeDays);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export function scoreMemory(item, query, now = Date.now(), blend = {}) {
|
|
66
|
-
const weights = {
|
|
67
|
-
...DEFAULT_RETRIEVAL_BLEND,
|
|
68
|
-
...blend
|
|
69
|
-
};
|
|
70
|
-
const text = [item.title, item.content, item.summary, item.reason, item.category]
|
|
71
|
-
.filter(Boolean)
|
|
72
|
-
.join(" ");
|
|
73
|
-
const lexicalScore = Number(item.lexicalScore ?? computeLexicalScore(query, text));
|
|
74
|
-
const embeddingSimilarity = Number(item.embeddingSimilarity ?? 0);
|
|
75
|
-
const recencyScore = computeRecencyScore(item.timestamp, now);
|
|
76
|
-
const salienceScore = Math.max(0, Math.min(1, Number(item.salience ?? 0.5)));
|
|
77
|
-
const typeScore = TYPE_WEIGHT[item.type] ?? 0.5;
|
|
78
|
-
const shadowSnapshotPenalty =
|
|
79
|
-
item.sourceKind === "shadow_snapshot" || String(item.title || "").startsWith("shadow snapshot")
|
|
80
|
-
? weights.shadowSnapshotPenalty
|
|
81
|
-
: 0;
|
|
82
|
-
const semanticRescueBonus =
|
|
83
|
-
lexicalScore < 0.12 && embeddingSimilarity >= 0.35 && item.embeddingFreshness === "fresh"
|
|
84
|
-
? weights.semanticRescueBonus
|
|
85
|
-
: 0;
|
|
86
|
-
|
|
87
|
-
const blendedScore =
|
|
88
|
-
lexicalScore * weights.lexicalWeight +
|
|
89
|
-
embeddingSimilarity * weights.embeddingWeight +
|
|
90
|
-
recencyScore * weights.recencyWeight +
|
|
91
|
-
salienceScore * weights.salienceWeight +
|
|
92
|
-
typeScore * weights.typeWeight +
|
|
93
|
-
semanticRescueBonus -
|
|
94
|
-
shadowSnapshotPenalty;
|
|
95
|
-
|
|
96
|
-
return {
|
|
97
|
-
score: blendedScore,
|
|
98
|
-
lexicalScore,
|
|
99
|
-
embeddingSimilarity,
|
|
100
|
-
recencyScore,
|
|
101
|
-
salienceScore,
|
|
102
|
-
typeScore,
|
|
103
|
-
semanticRescueBonus,
|
|
104
|
-
shadowSnapshotPenalty
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Compatibility helper retained for tests and older call sites.
|
|
109
|
-
export function computeDenseSimilarity(query, text, dimensions = 64) {
|
|
110
|
-
const queryVector = vectorize(expandTokens(tokenize(query)), dimensions);
|
|
111
|
-
const textVector = vectorize(expandTokens(tokenize(text)), dimensions);
|
|
112
|
-
return cosineSimilarity(queryVector, textVector);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function vectorize(tokens, dimensions) {
|
|
116
|
-
const vector = new Array(dimensions).fill(0);
|
|
117
|
-
for (const token of tokens) {
|
|
118
|
-
const hash = stableHash(token);
|
|
119
|
-
const index = Math.abs(hash) % dimensions;
|
|
120
|
-
const sign = hash % 2 === 0 ? 1 : -1;
|
|
121
|
-
vector[index] += sign * (1 + token.length / 10);
|
|
122
|
-
}
|
|
123
|
-
return vector;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function stableHash(text) {
|
|
127
|
-
let hash = 0;
|
|
128
|
-
for (let i = 0; i < text.length; i += 1) {
|
|
129
|
-
hash = (hash * 31 + text.charCodeAt(i)) | 0;
|
|
130
|
-
}
|
|
131
|
-
return hash;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function cosineSimilarity(a, b) {
|
|
135
|
-
let dot = 0;
|
|
136
|
-
let normA = 0;
|
|
137
|
-
let normB = 0;
|
|
138
|
-
for (let i = 0; i < a.length; i += 1) {
|
|
139
|
-
dot += a[i] * b[i];
|
|
140
|
-
normA += a[i] * a[i];
|
|
141
|
-
normB += b[i] * b[i];
|
|
142
|
-
}
|
|
143
|
-
if (normA === 0 || normB === 0) return 0;
|
|
144
|
-
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
145
|
-
}
|
|
1
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
2
|
+
|
|
3
|
+
const TYPE_WEIGHT = {
|
|
4
|
+
fact: 1,
|
|
5
|
+
summary: 0.9,
|
|
6
|
+
event: 0.55,
|
|
7
|
+
scratchpad: 0.75
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const SYNONYMS = {
|
|
11
|
+
heartbeat: ["check", "status", "pulse", "monitor"],
|
|
12
|
+
memory: ["notes", "context", "recall", "summary"],
|
|
13
|
+
workspace: ["vault", "project", "repo", "files"],
|
|
14
|
+
health: ["status", "stability", "signal", "check"],
|
|
15
|
+
purpose: ["mission", "role", "goal"],
|
|
16
|
+
soul: ["identity", "voice", "values"],
|
|
17
|
+
cron: ["schedule", "job", "automation"]
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const DEFAULT_RETRIEVAL_BLEND = {
|
|
21
|
+
lexicalWeight: 0.36,
|
|
22
|
+
embeddingWeight: 0.3,
|
|
23
|
+
recencyWeight: 0.14,
|
|
24
|
+
salienceWeight: 0.14,
|
|
25
|
+
typeWeight: 0.06,
|
|
26
|
+
semanticRescueBonus: 0.06,
|
|
27
|
+
shadowSnapshotPenalty: 0.12
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function tokenize(text) {
|
|
31
|
+
return String(text || "")
|
|
32
|
+
.toLowerCase()
|
|
33
|
+
.split(/[^a-z0-9]+/i)
|
|
34
|
+
.filter(Boolean);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function expandTokens(tokens) {
|
|
38
|
+
const expanded = new Set(tokens);
|
|
39
|
+
for (const token of tokens) {
|
|
40
|
+
const synonyms = SYNONYMS[token];
|
|
41
|
+
if (!synonyms) continue;
|
|
42
|
+
for (const synonym of synonyms) expanded.add(synonym);
|
|
43
|
+
}
|
|
44
|
+
return [...expanded];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function computeLexicalScore(query, text) {
|
|
48
|
+
const queryTokens = new Set(expandTokens(tokenize(query)));
|
|
49
|
+
if (queryTokens.size === 0) return 0;
|
|
50
|
+
const textTokens = tokenize(text);
|
|
51
|
+
if (textTokens.length === 0) return 0;
|
|
52
|
+
let hits = 0;
|
|
53
|
+
for (const token of textTokens) {
|
|
54
|
+
if (queryTokens.has(token)) hits += 1;
|
|
55
|
+
}
|
|
56
|
+
return Math.min(1, hits / Math.max(queryTokens.size, 1));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function computeRecencyScore(timestamp, now = Date.now(), halfLifeDays = 14) {
|
|
60
|
+
if (!timestamp) return 0.2;
|
|
61
|
+
const ageDays = Math.max(0, now - new Date(timestamp).getTime()) / DAY_MS;
|
|
62
|
+
return Math.pow(0.5, ageDays / halfLifeDays);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function scoreMemory(item, query, now = Date.now(), blend = {}) {
|
|
66
|
+
const weights = {
|
|
67
|
+
...DEFAULT_RETRIEVAL_BLEND,
|
|
68
|
+
...blend
|
|
69
|
+
};
|
|
70
|
+
const text = [item.title, item.content, item.summary, item.reason, item.category]
|
|
71
|
+
.filter(Boolean)
|
|
72
|
+
.join(" ");
|
|
73
|
+
const lexicalScore = Number(item.lexicalScore ?? computeLexicalScore(query, text));
|
|
74
|
+
const embeddingSimilarity = Number(item.embeddingSimilarity ?? 0);
|
|
75
|
+
const recencyScore = computeRecencyScore(item.timestamp, now);
|
|
76
|
+
const salienceScore = Math.max(0, Math.min(1, Number(item.salience ?? 0.5)));
|
|
77
|
+
const typeScore = TYPE_WEIGHT[item.type] ?? 0.5;
|
|
78
|
+
const shadowSnapshotPenalty =
|
|
79
|
+
item.sourceKind === "shadow_snapshot" || String(item.title || "").startsWith("shadow snapshot")
|
|
80
|
+
? weights.shadowSnapshotPenalty
|
|
81
|
+
: 0;
|
|
82
|
+
const semanticRescueBonus =
|
|
83
|
+
lexicalScore < 0.12 && embeddingSimilarity >= 0.35 && item.embeddingFreshness === "fresh"
|
|
84
|
+
? weights.semanticRescueBonus
|
|
85
|
+
: 0;
|
|
86
|
+
|
|
87
|
+
const blendedScore =
|
|
88
|
+
lexicalScore * weights.lexicalWeight +
|
|
89
|
+
embeddingSimilarity * weights.embeddingWeight +
|
|
90
|
+
recencyScore * weights.recencyWeight +
|
|
91
|
+
salienceScore * weights.salienceWeight +
|
|
92
|
+
typeScore * weights.typeWeight +
|
|
93
|
+
semanticRescueBonus -
|
|
94
|
+
shadowSnapshotPenalty;
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
score: blendedScore,
|
|
98
|
+
lexicalScore,
|
|
99
|
+
embeddingSimilarity,
|
|
100
|
+
recencyScore,
|
|
101
|
+
salienceScore,
|
|
102
|
+
typeScore,
|
|
103
|
+
semanticRescueBonus,
|
|
104
|
+
shadowSnapshotPenalty
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Compatibility helper retained for tests and older call sites.
|
|
109
|
+
export function computeDenseSimilarity(query, text, dimensions = 64) {
|
|
110
|
+
const queryVector = vectorize(expandTokens(tokenize(query)), dimensions);
|
|
111
|
+
const textVector = vectorize(expandTokens(tokenize(text)), dimensions);
|
|
112
|
+
return cosineSimilarity(queryVector, textVector);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function vectorize(tokens, dimensions) {
|
|
116
|
+
const vector = new Array(dimensions).fill(0);
|
|
117
|
+
for (const token of tokens) {
|
|
118
|
+
const hash = stableHash(token);
|
|
119
|
+
const index = Math.abs(hash) % dimensions;
|
|
120
|
+
const sign = hash % 2 === 0 ? 1 : -1;
|
|
121
|
+
vector[index] += sign * (1 + token.length / 10);
|
|
122
|
+
}
|
|
123
|
+
return vector;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function stableHash(text) {
|
|
127
|
+
let hash = 0;
|
|
128
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
129
|
+
hash = (hash * 31 + text.charCodeAt(i)) | 0;
|
|
130
|
+
}
|
|
131
|
+
return hash;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function cosineSimilarity(a, b) {
|
|
135
|
+
let dot = 0;
|
|
136
|
+
let normA = 0;
|
|
137
|
+
let normB = 0;
|
|
138
|
+
for (let i = 0; i < a.length; i += 1) {
|
|
139
|
+
dot += a[i] * b[i];
|
|
140
|
+
normA += a[i] * a[i];
|
|
141
|
+
normB += b[i] * b[i];
|
|
142
|
+
}
|
|
143
|
+
if (normA === 0 || normB === 0) return 0;
|
|
144
|
+
return dot / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
145
|
+
}
|
package/src/memory/simhash.js
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
const STOP_WORDS = new Set([
|
|
2
|
-
"a", "an", "the", "is", "was", "are", "were", "be", "been", "being",
|
|
3
|
-
"have", "has", "had", "do", "does", "did", "will", "would", "shall",
|
|
4
|
-
"should", "may", "might", "can", "could", "must", "ought",
|
|
5
|
-
"i", "me", "my", "we", "our", "you", "your", "he", "him", "his",
|
|
6
|
-
"she", "her", "it", "its", "they", "them", "their",
|
|
7
|
-
"this", "that", "these", "those",
|
|
8
|
-
"in", "on", "at", "to", "for", "of", "with", "by", "from",
|
|
9
|
-
"and", "but", "or", "not", "no", "nor", "so", "yet",
|
|
10
|
-
"if", "then", "else", "when", "where", "how", "what", "which", "who",
|
|
11
|
-
"all", "each", "every", "both", "few", "more", "most", "some", "any",
|
|
12
|
-
"just", "very", "also", "too", "only"
|
|
13
|
-
]);
|
|
14
|
-
|
|
15
|
-
// FNV-1a 64-bit hash
|
|
16
|
-
const FNV_OFFSET = 0xcbf29ce484222325n;
|
|
17
|
-
const FNV_PRIME = 0x100000001b3n;
|
|
18
|
-
const MASK_64 = (1n << 64n) - 1n;
|
|
19
|
-
|
|
20
|
-
function fnv1a64(str) {
|
|
21
|
-
let hash = FNV_OFFSET;
|
|
22
|
-
for (let i = 0; i < str.length; i++) {
|
|
23
|
-
hash ^= BigInt(str.charCodeAt(i));
|
|
24
|
-
hash = (hash * FNV_PRIME) & MASK_64;
|
|
25
|
-
}
|
|
26
|
-
return hash;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function tokenize(text) {
|
|
30
|
-
return String(text || "")
|
|
31
|
-
.toLowerCase()
|
|
32
|
-
.split(/[^a-z0-9]+/)
|
|
33
|
-
.filter((token) => token.length > 0 && !STOP_WORDS.has(token));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function computeSimhash(text) {
|
|
37
|
-
const tokens = tokenize(text);
|
|
38
|
-
if (tokens.length === 0) return 0n;
|
|
39
|
-
|
|
40
|
-
const weights = new Int32Array(64);
|
|
41
|
-
|
|
42
|
-
for (const token of tokens) {
|
|
43
|
-
const hash = fnv1a64(token);
|
|
44
|
-
for (let i = 0; i < 64; i++) {
|
|
45
|
-
if ((hash >> BigInt(i)) & 1n) {
|
|
46
|
-
weights[i] += 1;
|
|
47
|
-
} else {
|
|
48
|
-
weights[i] -= 1;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
let result = 0n;
|
|
54
|
-
for (let i = 0; i < 64; i++) {
|
|
55
|
-
if (weights[i] > 0) {
|
|
56
|
-
result |= (1n << BigInt(i));
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
return result;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function hammingDistance(a, b) {
|
|
63
|
-
let xor = a ^ b;
|
|
64
|
-
let count = 0;
|
|
65
|
-
while (xor > 0n) {
|
|
66
|
-
count += Number(xor & 1n);
|
|
67
|
-
xor >>= 1n;
|
|
68
|
-
}
|
|
69
|
-
return count;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export function simhashToHex(hash) {
|
|
73
|
-
return hash.toString(16).padStart(16, "0");
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function hexToSimhash(hex) {
|
|
77
|
-
return BigInt(`0x${hex}`);
|
|
78
|
-
}
|
|
1
|
+
const STOP_WORDS = new Set([
|
|
2
|
+
"a", "an", "the", "is", "was", "are", "were", "be", "been", "being",
|
|
3
|
+
"have", "has", "had", "do", "does", "did", "will", "would", "shall",
|
|
4
|
+
"should", "may", "might", "can", "could", "must", "ought",
|
|
5
|
+
"i", "me", "my", "we", "our", "you", "your", "he", "him", "his",
|
|
6
|
+
"she", "her", "it", "its", "they", "them", "their",
|
|
7
|
+
"this", "that", "these", "those",
|
|
8
|
+
"in", "on", "at", "to", "for", "of", "with", "by", "from",
|
|
9
|
+
"and", "but", "or", "not", "no", "nor", "so", "yet",
|
|
10
|
+
"if", "then", "else", "when", "where", "how", "what", "which", "who",
|
|
11
|
+
"all", "each", "every", "both", "few", "more", "most", "some", "any",
|
|
12
|
+
"just", "very", "also", "too", "only"
|
|
13
|
+
]);
|
|
14
|
+
|
|
15
|
+
// FNV-1a 64-bit hash
|
|
16
|
+
const FNV_OFFSET = 0xcbf29ce484222325n;
|
|
17
|
+
const FNV_PRIME = 0x100000001b3n;
|
|
18
|
+
const MASK_64 = (1n << 64n) - 1n;
|
|
19
|
+
|
|
20
|
+
function fnv1a64(str) {
|
|
21
|
+
let hash = FNV_OFFSET;
|
|
22
|
+
for (let i = 0; i < str.length; i++) {
|
|
23
|
+
hash ^= BigInt(str.charCodeAt(i));
|
|
24
|
+
hash = (hash * FNV_PRIME) & MASK_64;
|
|
25
|
+
}
|
|
26
|
+
return hash;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function tokenize(text) {
|
|
30
|
+
return String(text || "")
|
|
31
|
+
.toLowerCase()
|
|
32
|
+
.split(/[^a-z0-9]+/)
|
|
33
|
+
.filter((token) => token.length > 0 && !STOP_WORDS.has(token));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function computeSimhash(text) {
|
|
37
|
+
const tokens = tokenize(text);
|
|
38
|
+
if (tokens.length === 0) return 0n;
|
|
39
|
+
|
|
40
|
+
const weights = new Int32Array(64);
|
|
41
|
+
|
|
42
|
+
for (const token of tokens) {
|
|
43
|
+
const hash = fnv1a64(token);
|
|
44
|
+
for (let i = 0; i < 64; i++) {
|
|
45
|
+
if ((hash >> BigInt(i)) & 1n) {
|
|
46
|
+
weights[i] += 1;
|
|
47
|
+
} else {
|
|
48
|
+
weights[i] -= 1;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let result = 0n;
|
|
54
|
+
for (let i = 0; i < 64; i++) {
|
|
55
|
+
if (weights[i] > 0) {
|
|
56
|
+
result |= (1n << BigInt(i));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return result;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function hammingDistance(a, b) {
|
|
63
|
+
let xor = a ^ b;
|
|
64
|
+
let count = 0;
|
|
65
|
+
while (xor > 0n) {
|
|
66
|
+
count += Number(xor & 1n);
|
|
67
|
+
xor >>= 1n;
|
|
68
|
+
}
|
|
69
|
+
return count;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function simhashToHex(hash) {
|
|
73
|
+
return hash.toString(16).padStart(16, "0");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function hexToSimhash(hex) {
|
|
77
|
+
return BigInt(`0x${hex}`);
|
|
78
|
+
}
|