@graphorin/memory 0.5.0
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/CHANGELOG.md +15 -0
- package/LICENSE +21 -0
- package/README.md +401 -0
- package/dist/conflict/index.d.ts +3 -0
- package/dist/conflict/index.js +7 -0
- package/dist/conflict/locale-packs/en.js +262 -0
- package/dist/conflict/locale-packs/en.js.map +1 -0
- package/dist/conflict/locale-packs/index.js +4 -0
- package/dist/conflict/locale-packs/types.d.ts +103 -0
- package/dist/conflict/locale-packs/types.d.ts.map +1 -0
- package/dist/conflict/locale-packs/types.js +55 -0
- package/dist/conflict/locale-packs/types.js.map +1 -0
- package/dist/conflict/pipeline.d.ts +35 -0
- package/dist/conflict/pipeline.d.ts.map +1 -0
- package/dist/conflict/pipeline.js +257 -0
- package/dist/conflict/pipeline.js.map +1 -0
- package/dist/conflict/stages/helpers.js +66 -0
- package/dist/conflict/stages/helpers.js.map +1 -0
- package/dist/conflict/stages/stage1-exact-dedup.js +21 -0
- package/dist/conflict/stages/stage1-exact-dedup.js.map +1 -0
- package/dist/conflict/stages/stage2-embedding-three-zone.js +37 -0
- package/dist/conflict/stages/stage2-embedding-three-zone.js.map +1 -0
- package/dist/conflict/stages/stage3-heuristic-regex.js +45 -0
- package/dist/conflict/stages/stage3-heuristic-regex.js.map +1 -0
- package/dist/conflict/stages/stage4-subject-predicate.js +31 -0
- package/dist/conflict/stages/stage4-subject-predicate.js.map +1 -0
- package/dist/conflict/stages/stage5-defer-to-deep.js +22 -0
- package/dist/conflict/stages/stage5-defer-to-deep.js.map +1 -0
- package/dist/conflict/types.d.ts +222 -0
- package/dist/conflict/types.d.ts.map +1 -0
- package/dist/conflict/types.js +50 -0
- package/dist/conflict/types.js.map +1 -0
- package/dist/consolidator/budget.d.ts +1 -0
- package/dist/consolidator/budget.js +214 -0
- package/dist/consolidator/budget.js.map +1 -0
- package/dist/consolidator/decay.d.ts +35 -0
- package/dist/consolidator/decay.d.ts.map +1 -0
- package/dist/consolidator/decay.js +91 -0
- package/dist/consolidator/decay.js.map +1 -0
- package/dist/consolidator/dlq.js +58 -0
- package/dist/consolidator/dlq.js.map +1 -0
- package/dist/consolidator/errors.d.ts +54 -0
- package/dist/consolidator/errors.d.ts.map +1 -0
- package/dist/consolidator/errors.js +67 -0
- package/dist/consolidator/errors.js.map +1 -0
- package/dist/consolidator/idempotency.d.ts +1 -0
- package/dist/consolidator/idempotency.js +22 -0
- package/dist/consolidator/idempotency.js.map +1 -0
- package/dist/consolidator/index.d.ts +34 -0
- package/dist/consolidator/index.d.ts.map +1 -0
- package/dist/consolidator/index.js +163 -0
- package/dist/consolidator/index.js.map +1 -0
- package/dist/consolidator/lock.d.ts +2 -0
- package/dist/consolidator/lock.js +89 -0
- package/dist/consolidator/lock.js.map +1 -0
- package/dist/consolidator/noise-filter.d.ts +2 -0
- package/dist/consolidator/noise-filter.js +173 -0
- package/dist/consolidator/noise-filter.js.map +1 -0
- package/dist/consolidator/phases/deep.d.ts +4 -0
- package/dist/consolidator/phases/deep.js +198 -0
- package/dist/consolidator/phases/deep.js.map +1 -0
- package/dist/consolidator/phases/induce.d.ts +154 -0
- package/dist/consolidator/phases/induce.d.ts.map +1 -0
- package/dist/consolidator/phases/induce.js +253 -0
- package/dist/consolidator/phases/induce.js.map +1 -0
- package/dist/consolidator/phases/light.d.ts +4 -0
- package/dist/consolidator/phases/light.js +99 -0
- package/dist/consolidator/phases/light.js.map +1 -0
- package/dist/consolidator/phases/reflect.js +287 -0
- package/dist/consolidator/phases/reflect.js.map +1 -0
- package/dist/consolidator/phases/standard.d.ts +8 -0
- package/dist/consolidator/phases/standard.js +499 -0
- package/dist/consolidator/phases/standard.js.map +1 -0
- package/dist/consolidator/reconcile.js +192 -0
- package/dist/consolidator/reconcile.js.map +1 -0
- package/dist/consolidator/runtime.d.ts +62 -0
- package/dist/consolidator/runtime.d.ts.map +1 -0
- package/dist/consolidator/runtime.js +580 -0
- package/dist/consolidator/runtime.js.map +1 -0
- package/dist/consolidator/scheduler.d.ts +111 -0
- package/dist/consolidator/scheduler.d.ts.map +1 -0
- package/dist/consolidator/scheduler.js +71 -0
- package/dist/consolidator/scheduler.js.map +1 -0
- package/dist/consolidator/triggers.d.ts +40 -0
- package/dist/consolidator/triggers.d.ts.map +1 -0
- package/dist/consolidator/triggers.js +94 -0
- package/dist/consolidator/triggers.js.map +1 -0
- package/dist/consolidator/types.d.ts +397 -0
- package/dist/consolidator/types.d.ts.map +1 -0
- package/dist/consolidator/types.js +117 -0
- package/dist/consolidator/types.js.map +1 -0
- package/dist/context-engine/annotations.d.ts +118 -0
- package/dist/context-engine/annotations.d.ts.map +1 -0
- package/dist/context-engine/annotations.js +102 -0
- package/dist/context-engine/annotations.js.map +1 -0
- package/dist/context-engine/auto-recall.d.ts +52 -0
- package/dist/context-engine/auto-recall.d.ts.map +1 -0
- package/dist/context-engine/auto-recall.js +41 -0
- package/dist/context-engine/auto-recall.js.map +1 -0
- package/dist/context-engine/compaction/clear-tool-results.d.ts +55 -0
- package/dist/context-engine/compaction/clear-tool-results.d.ts.map +1 -0
- package/dist/context-engine/compaction/clear-tool-results.js +97 -0
- package/dist/context-engine/compaction/clear-tool-results.js.map +1 -0
- package/dist/context-engine/compaction/compactor.d.ts +52 -0
- package/dist/context-engine/compaction/compactor.d.ts.map +1 -0
- package/dist/context-engine/compaction/compactor.js +196 -0
- package/dist/context-engine/compaction/compactor.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts +15 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js +27 -0
- package/dist/context-engine/compaction/hooks/reanchor-persona-block.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts +18 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js +47 -0
- package/dist/context-engine/compaction/hooks/reanchor-pinned-facts.js.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts +15 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.d.ts.map +1 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js +32 -0
- package/dist/context-engine/compaction/hooks/reanchor-project-rules.js.map +1 -0
- package/dist/context-engine/compaction/hooks/types.d.ts +42 -0
- package/dist/context-engine/compaction/hooks/types.d.ts.map +1 -0
- package/dist/context-engine/compaction/index.d.ts +9 -0
- package/dist/context-engine/compaction/index.js +9 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts +84 -0
- package/dist/context-engine/compaction/templates/summary-9-section.d.ts.map +1 -0
- package/dist/context-engine/compaction/templates/summary-9-section.js +84 -0
- package/dist/context-engine/compaction/templates/summary-9-section.js.map +1 -0
- package/dist/context-engine/compaction/thresholds.d.ts +68 -0
- package/dist/context-engine/compaction/thresholds.d.ts.map +1 -0
- package/dist/context-engine/compaction/thresholds.js +63 -0
- package/dist/context-engine/compaction/thresholds.js.map +1 -0
- package/dist/context-engine/compaction/types.d.ts +202 -0
- package/dist/context-engine/compaction/types.d.ts.map +1 -0
- package/dist/context-engine/engine.d.ts +258 -0
- package/dist/context-engine/engine.d.ts.map +1 -0
- package/dist/context-engine/engine.js +424 -0
- package/dist/context-engine/engine.js.map +1 -0
- package/dist/context-engine/index.d.ts +68 -0
- package/dist/context-engine/index.d.ts.map +1 -0
- package/dist/context-engine/index.js +24 -0
- package/dist/context-engine/locale-packs/en.d.ts +14 -0
- package/dist/context-engine/locale-packs/en.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/en.js +87 -0
- package/dist/context-engine/locale-packs/en.js.map +1 -0
- package/dist/context-engine/locale-packs/index.js +4 -0
- package/dist/context-engine/locale-packs/resolver.d.ts +41 -0
- package/dist/context-engine/locale-packs/resolver.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/resolver.js +119 -0
- package/dist/context-engine/locale-packs/resolver.js.map +1 -0
- package/dist/context-engine/locale-packs/types.d.ts +119 -0
- package/dist/context-engine/locale-packs/types.d.ts.map +1 -0
- package/dist/context-engine/locale-packs/types.js +24 -0
- package/dist/context-engine/locale-packs/types.js.map +1 -0
- package/dist/context-engine/metadata.d.ts +40 -0
- package/dist/context-engine/metadata.d.ts.map +1 -0
- package/dist/context-engine/metadata.js +66 -0
- package/dist/context-engine/metadata.js.map +1 -0
- package/dist/context-engine/preambles/inbound-en.d.ts +20 -0
- package/dist/context-engine/preambles/inbound-en.d.ts.map +1 -0
- package/dist/context-engine/preambles/inbound-en.js +23 -0
- package/dist/context-engine/preambles/inbound-en.js.map +1 -0
- package/dist/context-engine/privacy-filter.d.ts +108 -0
- package/dist/context-engine/privacy-filter.d.ts.map +1 -0
- package/dist/context-engine/privacy-filter.js +113 -0
- package/dist/context-engine/privacy-filter.js.map +1 -0
- package/dist/context-engine/templates/base-en.d.ts +19 -0
- package/dist/context-engine/templates/base-en.d.ts.map +1 -0
- package/dist/context-engine/templates/base-en.js +22 -0
- package/dist/context-engine/templates/base-en.js.map +1 -0
- package/dist/context-engine/templates/composer.d.ts +55 -0
- package/dist/context-engine/templates/composer.d.ts.map +1 -0
- package/dist/context-engine/templates/composer.js +51 -0
- package/dist/context-engine/templates/composer.js.map +1 -0
- package/dist/context-engine/token-budget.d.ts +99 -0
- package/dist/context-engine/token-budget.d.ts.map +1 -0
- package/dist/context-engine/token-budget.js +211 -0
- package/dist/context-engine/token-budget.js.map +1 -0
- package/dist/context-engine/token-counter.d.ts +54 -0
- package/dist/context-engine/token-counter.d.ts.map +1 -0
- package/dist/context-engine/token-counter.js +81 -0
- package/dist/context-engine/token-counter.js.map +1 -0
- package/dist/errors/index.d.ts +171 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +206 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/facade.d.ts +278 -0
- package/dist/facade.d.ts.map +1 -0
- package/dist/facade.js +265 -0
- package/dist/facade.js.map +1 -0
- package/dist/graph/entity-resolver.d.ts +135 -0
- package/dist/graph/entity-resolver.d.ts.map +1 -0
- package/dist/graph/entity-resolver.js +202 -0
- package/dist/graph/entity-resolver.js.map +1 -0
- package/dist/graph/index.d.ts +1 -0
- package/dist/index.d.ts +103 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/contextualize.d.ts +14 -0
- package/dist/internal/contextualize.d.ts.map +1 -0
- package/dist/internal/contextualize.js +110 -0
- package/dist/internal/contextualize.js.map +1 -0
- package/dist/internal/embedder-binding.js +50 -0
- package/dist/internal/embedder-binding.js.map +1 -0
- package/dist/internal/id.js +24 -0
- package/dist/internal/id.js.map +1 -0
- package/dist/internal/injection-heuristics.js +64 -0
- package/dist/internal/injection-heuristics.js.map +1 -0
- package/dist/internal/spans.js +35 -0
- package/dist/internal/spans.js.map +1 -0
- package/dist/internal/storage-adapter.d.ts +742 -0
- package/dist/internal/storage-adapter.d.ts.map +1 -0
- package/dist/migration/embedder-migration.d.ts +124 -0
- package/dist/migration/embedder-migration.d.ts.map +1 -0
- package/dist/migration/embedder-migration.js +109 -0
- package/dist/migration/embedder-migration.js.map +1 -0
- package/dist/migration/index.d.ts +2 -0
- package/dist/migration/index.js +3 -0
- package/dist/search/explain.d.ts +74 -0
- package/dist/search/explain.d.ts.map +1 -0
- package/dist/search/explain.js +50 -0
- package/dist/search/explain.js.map +1 -0
- package/dist/search/index.d.ts +6 -0
- package/dist/search/index.js +6 -0
- package/dist/search/iterative.d.ts +223 -0
- package/dist/search/iterative.d.ts.map +1 -0
- package/dist/search/iterative.js +330 -0
- package/dist/search/iterative.js.map +1 -0
- package/dist/search/query-transform.d.ts +112 -0
- package/dist/search/query-transform.d.ts.map +1 -0
- package/dist/search/query-transform.js +184 -0
- package/dist/search/query-transform.js.map +1 -0
- package/dist/search/rrf.d.ts +95 -0
- package/dist/search/rrf.d.ts.map +1 -0
- package/dist/search/rrf.js +166 -0
- package/dist/search/rrf.js.map +1 -0
- package/dist/search/types.d.ts +50 -0
- package/dist/search/types.d.ts.map +1 -0
- package/dist/tiers/episodic-memory.d.ts +142 -0
- package/dist/tiers/episodic-memory.d.ts.map +1 -0
- package/dist/tiers/episodic-memory.js +254 -0
- package/dist/tiers/episodic-memory.js.map +1 -0
- package/dist/tiers/index.d.ts +8 -0
- package/dist/tiers/index.js +9 -0
- package/dist/tiers/insight-memory.d.ts +71 -0
- package/dist/tiers/insight-memory.d.ts.map +1 -0
- package/dist/tiers/insight-memory.js +133 -0
- package/dist/tiers/insight-memory.js.map +1 -0
- package/dist/tiers/procedural-memory.d.ts +147 -0
- package/dist/tiers/procedural-memory.d.ts.map +1 -0
- package/dist/tiers/procedural-memory.js +267 -0
- package/dist/tiers/procedural-memory.js.map +1 -0
- package/dist/tiers/semantic-memory.d.ts +461 -0
- package/dist/tiers/semantic-memory.d.ts.map +1 -0
- package/dist/tiers/semantic-memory.js +631 -0
- package/dist/tiers/semantic-memory.js.map +1 -0
- package/dist/tiers/session-memory.d.ts +117 -0
- package/dist/tiers/session-memory.d.ts.map +1 -0
- package/dist/tiers/session-memory.js +163 -0
- package/dist/tiers/session-memory.js.map +1 -0
- package/dist/tiers/shared-memory.d.ts +29 -0
- package/dist/tiers/shared-memory.d.ts.map +1 -0
- package/dist/tiers/shared-memory.js +58 -0
- package/dist/tiers/shared-memory.js.map +1 -0
- package/dist/tiers/working-memory.d.ts +129 -0
- package/dist/tiers/working-memory.d.ts.map +1 -0
- package/dist/tiers/working-memory.js +229 -0
- package/dist/tiers/working-memory.js.map +1 -0
- package/dist/tools/block-tools.d.ts +98 -0
- package/dist/tools/block-tools.d.ts.map +1 -0
- package/dist/tools/block-tools.js +114 -0
- package/dist/tools/block-tools.js.map +1 -0
- package/dist/tools/fact-tools.d.ts +294 -0
- package/dist/tools/fact-tools.d.ts.map +1 -0
- package/dist/tools/fact-tools.js +293 -0
- package/dist/tools/fact-tools.js.map +1 -0
- package/dist/tools/index.d.ts +35 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +36 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/recall-tools.d.ts +209 -0
- package/dist/tools/recall-tools.d.ts.map +1 -0
- package/dist/tools/recall-tools.js +170 -0
- package/dist/tools/recall-tools.js.map +1 -0
- package/dist/tools/types.d.ts +40 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/package.json +119 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Provider } from "@graphorin/core";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/contextualize.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Contextual-retrieval mode. `'late-chunk'` is the offline default;
|
|
7
|
+
* `'llm'` is the opt-in, consolidator-only enrichment. P1-3.
|
|
8
|
+
*
|
|
9
|
+
* @stable
|
|
10
|
+
*/
|
|
11
|
+
type ContextualRetrievalMode = 'off' | 'late-chunk' | 'llm';
|
|
12
|
+
//#endregion
|
|
13
|
+
export { ContextualRetrievalMode };
|
|
14
|
+
//# sourceMappingURL=contextualize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contextualize.d.ts","names":[],"sources":["../../src/internal/contextualize.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;KA8BY,uBAAA"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
//#region src/internal/contextualize.ts
|
|
2
|
+
const SITUATING_CONTEXT_SYSTEM_PROMPT = [
|
|
3
|
+
"You are a situating-context assistant for a long-running memory store.",
|
|
4
|
+
"Given one memory and any structured hints, write a single short sentence (≤ 25 words) that situates it — the entities, timeframe, and topic it concerns — so it can be retrieved later by a vaguely-worded question.",
|
|
5
|
+
"Do not restate the memory verbatim and do not add facts that are not implied. Return only the sentence, no preamble."
|
|
6
|
+
].join(" ");
|
|
7
|
+
/**
|
|
8
|
+
* Build the deterministic situating-context prefix from a candidate's
|
|
9
|
+
* structured signals. Returns `''` when nothing useful is available so
|
|
10
|
+
* the caller can short-circuit to the canonical text.
|
|
11
|
+
*
|
|
12
|
+
* @internal
|
|
13
|
+
*/
|
|
14
|
+
function buildSituatingContext(input) {
|
|
15
|
+
const parts = [];
|
|
16
|
+
const subject = trimToUndefined(input.subject);
|
|
17
|
+
const predicate = trimToUndefined(input.predicate);
|
|
18
|
+
const object = trimToUndefined(input.object);
|
|
19
|
+
if (subject !== void 0 && predicate !== void 0 && object !== void 0) parts.push(`${subject} ${predicate} ${object}`);
|
|
20
|
+
else {
|
|
21
|
+
if (subject !== void 0) parts.push(subject);
|
|
22
|
+
if (object !== void 0) parts.push(object);
|
|
23
|
+
}
|
|
24
|
+
const validFrom = trimToUndefined(input.validFrom);
|
|
25
|
+
if (validFrom !== void 0) parts.push(`as of ${validFrom.length >= 10 ? validFrom.slice(0, 10) : validFrom}`);
|
|
26
|
+
const tags = (input.tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);
|
|
27
|
+
if (tags.length > 0) parts.push(`topics: ${tags.join(", ")}`);
|
|
28
|
+
if (parts.length === 0) return "";
|
|
29
|
+
return `[Context: ${parts.join("; ")}]`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Late-chunking contextualizer (offline default). Prepends the
|
|
33
|
+
* deterministic situating context to the canonical text; a no-op when
|
|
34
|
+
* the candidate carries no structured signals. Pure — never calls a
|
|
35
|
+
* provider.
|
|
36
|
+
*
|
|
37
|
+
* @internal
|
|
38
|
+
*/
|
|
39
|
+
function contextualize(input) {
|
|
40
|
+
const ctx = buildSituatingContext(input);
|
|
41
|
+
return ctx === "" ? input.text : `${ctx}\n${input.text}`;
|
|
42
|
+
}
|
|
43
|
+
const ZERO_USAGE = Object.freeze({
|
|
44
|
+
promptTokens: 0,
|
|
45
|
+
completionTokens: 0,
|
|
46
|
+
totalTokens: 0
|
|
47
|
+
});
|
|
48
|
+
/**
|
|
49
|
+
* LLM-context contextualizer (opt-in, consolidator-only). Spends one
|
|
50
|
+
* cheap provider call to write a situating prefix, then prepends it to
|
|
51
|
+
* the canonical text. Resilient by design: an empty completion or a
|
|
52
|
+
* provider error degrades to {@link contextualize} (deterministic
|
|
53
|
+
* late-chunk) so the enrichment can never wedge a write. Returns the
|
|
54
|
+
* usage so the caller can record it against the budget.
|
|
55
|
+
*
|
|
56
|
+
* @internal
|
|
57
|
+
*/
|
|
58
|
+
async function contextualizeWithLlm(input, provider, opts = {}) {
|
|
59
|
+
const request = {
|
|
60
|
+
messages: [{
|
|
61
|
+
role: "user",
|
|
62
|
+
content: buildSituatingUserPrompt(input)
|
|
63
|
+
}],
|
|
64
|
+
systemMessage: SITUATING_CONTEXT_SYSTEM_PROMPT,
|
|
65
|
+
temperature: 0,
|
|
66
|
+
maxTokens: 256,
|
|
67
|
+
...opts.signal !== void 0 ? { signal: opts.signal } : {}
|
|
68
|
+
};
|
|
69
|
+
try {
|
|
70
|
+
const response = await provider.generate(request);
|
|
71
|
+
const prefix = (response.text ?? "").trim();
|
|
72
|
+
if (prefix.length === 0) return {
|
|
73
|
+
indexText: contextualize(input),
|
|
74
|
+
usage: response.usage
|
|
75
|
+
};
|
|
76
|
+
return {
|
|
77
|
+
indexText: `${prefix}\n${input.text}`,
|
|
78
|
+
usage: response.usage
|
|
79
|
+
};
|
|
80
|
+
} catch {
|
|
81
|
+
return {
|
|
82
|
+
indexText: contextualize(input),
|
|
83
|
+
usage: ZERO_USAGE
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function buildSituatingUserPrompt(input) {
|
|
88
|
+
const hints = [];
|
|
89
|
+
const subject = trimToUndefined(input.subject);
|
|
90
|
+
const predicate = trimToUndefined(input.predicate);
|
|
91
|
+
const object = trimToUndefined(input.object);
|
|
92
|
+
if (subject !== void 0) hints.push(`subject=${subject}`);
|
|
93
|
+
if (predicate !== void 0) hints.push(`predicate=${predicate}`);
|
|
94
|
+
if (object !== void 0) hints.push(`object=${object}`);
|
|
95
|
+
const tags = (input.tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);
|
|
96
|
+
if (tags.length > 0) hints.push(`tags=${tags.join(", ")}`);
|
|
97
|
+
const validFrom = trimToUndefined(input.validFrom);
|
|
98
|
+
if (validFrom !== void 0) hints.push(`validFrom=${validFrom}`);
|
|
99
|
+
const hintLine = hints.length > 0 ? `\nStructured hints: ${hints.join("; ")}` : "";
|
|
100
|
+
return `Memory: ${input.text}${hintLine}`;
|
|
101
|
+
}
|
|
102
|
+
function trimToUndefined(value) {
|
|
103
|
+
if (value === void 0) return void 0;
|
|
104
|
+
const trimmed = value.trim();
|
|
105
|
+
return trimmed.length === 0 ? void 0 : trimmed;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
//#endregion
|
|
109
|
+
export { contextualize, contextualizeWithLlm };
|
|
110
|
+
//# sourceMappingURL=contextualize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contextualize.js","names":["parts: string[]","ZERO_USAGE: Usage","request: ProviderRequest","hints: string[]"],"sources":["../../src/internal/contextualize.ts"],"sourcesContent":["/**\n * Contextual retrieval (P1-3). Before a memory is embedded / FTS-indexed\n * the framework can prepend a short *situating context* (entities,\n * timeframe, topics) so a terse fact like \"moved there in March\" stays\n * findable by \"Where did Anna relocate?\". This mirrors Anthropic's\n * Contextual Retrieval, applied to both the vector embedding *and* the\n * lexical index, while the canonical `text` shown to the user / audit\n * trail is left untouched.\n *\n * Three modes:\n * - `'off'` — index the bare canonical text (pre-P1-3 behaviour).\n * - `'late-chunk'` — the **offline default**: prepend a context derived\n * deterministically from the memory's own structured metadata. No LLM\n * call, so the local-first write path is unchanged in cost.\n * - `'llm'` — an opt-in, **consolidator-only** enrichment: one cheap\n * provider call writes a 1–2 sentence situating prefix. Confined to the\n * background consolidator (never the hot write path); a failure or empty\n * completion degrades to {@link contextualize} so a write never wedges.\n *\n * @packageDocumentation\n */\n\nimport type { Provider, ProviderRequest, Usage } from '@graphorin/core';\n\n/**\n * Contextual-retrieval mode. `'late-chunk'` is the offline default;\n * `'llm'` is the opt-in, consolidator-only enrichment. P1-3.\n *\n * @stable\n */\nexport type ContextualRetrievalMode = 'off' | 'late-chunk' | 'llm';\n\n/**\n * Structural subset of a fact / candidate consumed by the\n * contextualizers. Only **author / extraction-supplied** signals are\n * used — never framework-defaulted fields (e.g. a `validFrom` defaulted\n * to write-time) and never opaque scope ids (random uuids would only\n * add noise to the embedding). A candidate with no structured signals\n * yields an empty context, so plain `remember({ text })` writes are\n * left byte-identical.\n *\n * @internal\n */\nexport interface ContextualizeInput {\n readonly text: string;\n readonly subject?: string;\n readonly predicate?: string;\n readonly object?: string;\n readonly tags?: ReadonlyArray<string>;\n /** Author-set validity start (ISO-8601). Framework-defaulted values are *not* passed. */\n readonly validFrom?: string;\n}\n\nconst SITUATING_CONTEXT_SYSTEM_PROMPT = [\n 'You are a situating-context assistant for a long-running memory store.',\n 'Given one memory and any structured hints, write a single short sentence (≤ 25 words) that situates it — the entities, timeframe, and topic it concerns — so it can be retrieved later by a vaguely-worded question.',\n 'Do not restate the memory verbatim and do not add facts that are not implied. Return only the sentence, no preamble.',\n].join(' ');\n\n/**\n * Build the deterministic situating-context prefix from a candidate's\n * structured signals. Returns `''` when nothing useful is available so\n * the caller can short-circuit to the canonical text.\n *\n * @internal\n */\nexport function buildSituatingContext(input: ContextualizeInput): string {\n const parts: string[] = [];\n const subject = trimToUndefined(input.subject);\n const predicate = trimToUndefined(input.predicate);\n const object = trimToUndefined(input.object);\n if (subject !== undefined && predicate !== undefined && object !== undefined) {\n parts.push(`${subject} ${predicate} ${object}`);\n } else {\n if (subject !== undefined) parts.push(subject);\n if (object !== undefined) parts.push(object);\n }\n const validFrom = trimToUndefined(input.validFrom);\n if (validFrom !== undefined) {\n parts.push(`as of ${validFrom.length >= 10 ? validFrom.slice(0, 10) : validFrom}`);\n }\n const tags = (input.tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);\n if (tags.length > 0) {\n parts.push(`topics: ${tags.join(', ')}`);\n }\n if (parts.length === 0) return '';\n return `[Context: ${parts.join('; ')}]`;\n}\n\n/**\n * Late-chunking contextualizer (offline default). Prepends the\n * deterministic situating context to the canonical text; a no-op when\n * the candidate carries no structured signals. Pure — never calls a\n * provider.\n *\n * @internal\n */\nexport function contextualize(input: ContextualizeInput): string {\n const ctx = buildSituatingContext(input);\n return ctx === '' ? input.text : `${ctx}\\n${input.text}`;\n}\n\nconst ZERO_USAGE: Usage = Object.freeze({\n promptTokens: 0,\n completionTokens: 0,\n totalTokens: 0,\n});\n\n/** Result of the LLM-context enrichment — the index text plus call usage for budgeting. */\nexport interface ContextualizeLlmResult {\n readonly indexText: string;\n readonly usage: Usage;\n}\n\n/**\n * LLM-context contextualizer (opt-in, consolidator-only). Spends one\n * cheap provider call to write a situating prefix, then prepends it to\n * the canonical text. Resilient by design: an empty completion or a\n * provider error degrades to {@link contextualize} (deterministic\n * late-chunk) so the enrichment can never wedge a write. Returns the\n * usage so the caller can record it against the budget.\n *\n * @internal\n */\nexport async function contextualizeWithLlm(\n input: ContextualizeInput,\n provider: Provider,\n opts: { readonly signal?: AbortSignal } = {},\n): Promise<ContextualizeLlmResult> {\n const request: ProviderRequest = {\n messages: [{ role: 'user', content: buildSituatingUserPrompt(input) }],\n systemMessage: SITUATING_CONTEXT_SYSTEM_PROMPT,\n temperature: 0,\n // MCON-14: per-call output cap — a one-sentence situating prefix.\n maxTokens: 256,\n ...(opts.signal !== undefined ? { signal: opts.signal } : {}),\n };\n try {\n const response = await provider.generate(request);\n const prefix = (response.text ?? '').trim();\n if (prefix.length === 0) {\n return { indexText: contextualize(input), usage: response.usage };\n }\n return { indexText: `${prefix}\\n${input.text}`, usage: response.usage };\n } catch {\n // Contextualization is an enhancement, not a correctness requirement —\n // a provider failure must never wedge the underlying write. Fall back\n // to the deterministic late-chunk prefix at zero cost.\n return { indexText: contextualize(input), usage: ZERO_USAGE };\n }\n}\n\nfunction buildSituatingUserPrompt(input: ContextualizeInput): string {\n const hints: string[] = [];\n const subject = trimToUndefined(input.subject);\n const predicate = trimToUndefined(input.predicate);\n const object = trimToUndefined(input.object);\n if (subject !== undefined) hints.push(`subject=${subject}`);\n if (predicate !== undefined) hints.push(`predicate=${predicate}`);\n if (object !== undefined) hints.push(`object=${object}`);\n const tags = (input.tags ?? []).map((t) => t.trim()).filter((t) => t.length > 0);\n if (tags.length > 0) hints.push(`tags=${tags.join(', ')}`);\n const validFrom = trimToUndefined(input.validFrom);\n if (validFrom !== undefined) hints.push(`validFrom=${validFrom}`);\n const hintLine = hints.length > 0 ? `\\nStructured hints: ${hints.join('; ')}` : '';\n return `Memory: ${input.text}${hintLine}`;\n}\n\nfunction trimToUndefined(value: string | undefined): string | undefined {\n if (value === undefined) return undefined;\n const trimmed = value.trim();\n return trimmed.length === 0 ? undefined : trimmed;\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/internal/embedder-binding.ts
|
|
2
|
+
/**
|
|
3
|
+
* Infer the canonical adapter family from the embedder id. The
|
|
4
|
+
* canonical id format is `'<adapter>:<model>@<dim>'`; we slice the
|
|
5
|
+
* adapter name off the front so the registry can index by family
|
|
6
|
+
* without needing the embedder to expose an extra property.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
function inferAdapterKind(id) {
|
|
11
|
+
const colonIndex = id.indexOf(":");
|
|
12
|
+
if (colonIndex < 0) return "custom";
|
|
13
|
+
return id.slice(0, colonIndex);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Infer the model id portion of a canonical embedder id.
|
|
17
|
+
*
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
function inferModelName(id) {
|
|
21
|
+
const colonIndex = id.indexOf(":");
|
|
22
|
+
const atIndex = id.lastIndexOf("@");
|
|
23
|
+
if (colonIndex < 0) return id;
|
|
24
|
+
if (atIndex < 0 || atIndex < colonIndex) return id.slice(colonIndex + 1);
|
|
25
|
+
return id.slice(colonIndex + 1, atIndex);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Register the supplied embedder with the storage layer's
|
|
29
|
+
* `embedding_meta` registry. Returns the canonical id surfaced to the
|
|
30
|
+
* memory writers. Idempotent — a registry that already knows the id
|
|
31
|
+
* with a matching `configHash` returns the existing row.
|
|
32
|
+
*
|
|
33
|
+
* @internal
|
|
34
|
+
*/
|
|
35
|
+
function bindEmbedder(embedder, registry) {
|
|
36
|
+
const id = embedder.id();
|
|
37
|
+
registry.registerOrReturn({
|
|
38
|
+
id,
|
|
39
|
+
embedderKind: inferAdapterKind(id),
|
|
40
|
+
model: inferModelName(id),
|
|
41
|
+
dim: embedder.dim(),
|
|
42
|
+
configHash: embedder.configHash(),
|
|
43
|
+
distanceMetric: "cosine"
|
|
44
|
+
});
|
|
45
|
+
return id;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { bindEmbedder };
|
|
50
|
+
//# sourceMappingURL=embedder-binding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedder-binding.js","names":[],"sources":["../../src/internal/embedder-binding.ts"],"sourcesContent":["import type { EmbedderProvider } from '@graphorin/core';\nimport type { EmbeddingMetaRegistryLike } from './storage-adapter.js';\n\n/**\n * Infer the canonical adapter family from the embedder id. The\n * canonical id format is `'<adapter>:<model>@<dim>'`; we slice the\n * adapter name off the front so the registry can index by family\n * without needing the embedder to expose an extra property.\n *\n * @internal\n */\nexport function inferAdapterKind(id: string): string {\n const colonIndex = id.indexOf(':');\n if (colonIndex < 0) return 'custom';\n return id.slice(0, colonIndex);\n}\n\n/**\n * Infer the model id portion of a canonical embedder id.\n *\n * @internal\n */\nexport function inferModelName(id: string): string {\n const colonIndex = id.indexOf(':');\n const atIndex = id.lastIndexOf('@');\n if (colonIndex < 0) return id;\n if (atIndex < 0 || atIndex < colonIndex) return id.slice(colonIndex + 1);\n return id.slice(colonIndex + 1, atIndex);\n}\n\n/**\n * Register the supplied embedder with the storage layer's\n * `embedding_meta` registry. Returns the canonical id surfaced to the\n * memory writers. Idempotent — a registry that already knows the id\n * with a matching `configHash` returns the existing row.\n *\n * @internal\n */\nexport function bindEmbedder(\n embedder: EmbedderProvider,\n registry: EmbeddingMetaRegistryLike,\n): string {\n const id = embedder.id();\n registry.registerOrReturn({\n id,\n embedderKind: inferAdapterKind(id),\n model: inferModelName(id),\n dim: embedder.dim(),\n configHash: embedder.configHash(),\n distanceMetric: 'cosine',\n });\n return id;\n}\n"],"mappings":";;;;;;;;;AAWA,SAAgB,iBAAiB,IAAoB;CACnD,MAAM,aAAa,GAAG,QAAQ,IAAI;AAClC,KAAI,aAAa,EAAG,QAAO;AAC3B,QAAO,GAAG,MAAM,GAAG,WAAW;;;;;;;AAQhC,SAAgB,eAAe,IAAoB;CACjD,MAAM,aAAa,GAAG,QAAQ,IAAI;CAClC,MAAM,UAAU,GAAG,YAAY,IAAI;AACnC,KAAI,aAAa,EAAG,QAAO;AAC3B,KAAI,UAAU,KAAK,UAAU,WAAY,QAAO,GAAG,MAAM,aAAa,EAAE;AACxE,QAAO,GAAG,MAAM,aAAa,GAAG,QAAQ;;;;;;;;;;AAW1C,SAAgB,aACd,UACA,UACQ;CACR,MAAM,KAAK,SAAS,IAAI;AACxB,UAAS,iBAAiB;EACxB;EACA,cAAc,iBAAiB,GAAG;EAClC,OAAO,eAAe,GAAG;EACzB,KAAK,SAAS,KAAK;EACnB,YAAY,SAAS,YAAY;EACjC,gBAAgB;EACjB,CAAC;AACF,QAAO"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
//#region src/internal/id.ts
|
|
4
|
+
/**
|
|
5
|
+
* Stable id generator. Combines `Date.now()` (millisecond resolution),
|
|
6
|
+
* a per-process monotonic counter (resists collision inside the same
|
|
7
|
+
* millisecond), and a 36-bit crypto-quality random suffix (ties the id
|
|
8
|
+
* to the host so different runs do not collide either).
|
|
9
|
+
*
|
|
10
|
+
* The format is intentionally NOT a UUID — UUIDs are 16 bytes once
|
|
11
|
+
* decoded; these ids are 18 chars on average which is what the
|
|
12
|
+
* `audit.db` hash chain (Phase 03b) was sized for.
|
|
13
|
+
*
|
|
14
|
+
* @stable
|
|
15
|
+
*/
|
|
16
|
+
let counter = 0;
|
|
17
|
+
function newMemoryId(prefix) {
|
|
18
|
+
counter = counter + 1 & 65535;
|
|
19
|
+
return `${prefix}_${Date.now().toString(36)}${counter.toString(36).padStart(3, "0")}${randomBytes(4).toString("hex")}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
//#endregion
|
|
23
|
+
export { newMemoryId };
|
|
24
|
+
//# sourceMappingURL=id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id.js","names":[],"sources":["../../src/internal/id.ts"],"sourcesContent":["/**\n * Stable id generator. Combines `Date.now()` (millisecond resolution),\n * a per-process monotonic counter (resists collision inside the same\n * millisecond), and a 36-bit crypto-quality random suffix (ties the id\n * to the host so different runs do not collide either).\n *\n * The format is intentionally NOT a UUID — UUIDs are 16 bytes once\n * decoded; these ids are 18 chars on average which is what the\n * `audit.db` hash chain (Phase 03b) was sized for.\n *\n * @stable\n */\nimport { randomBytes } from 'node:crypto';\n\nlet counter = 0;\n\nexport function newMemoryId(prefix: string): string {\n counter = (counter + 1) & 0xffff;\n const time = Date.now().toString(36);\n const c = counter.toString(36).padStart(3, '0');\n const rand = randomBytes(4).toString('hex');\n return `${prefix}_${time}${c}${rand}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;AAcA,IAAI,UAAU;AAEd,SAAgB,YAAY,QAAwB;AAClD,WAAW,UAAU,IAAK;AAI1B,QAAO,GAAG,OAAO,GAHJ,KAAK,KAAK,CAAC,SAAS,GAAG,GAC1B,QAAQ,SAAS,GAAG,CAAC,SAAS,GAAG,IAAI,GAClC,YAAY,EAAE,CAAC,SAAS,MAAM"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/internal/injection-heuristics.ts
|
|
2
|
+
/**
|
|
3
|
+
* Default English injection-marker rules. High-precision: each requires
|
|
4
|
+
* injection-characteristic structure so benign facts do not trip them.
|
|
5
|
+
*
|
|
6
|
+
* @stable
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_INJECTION_RULES = Object.freeze([
|
|
9
|
+
{
|
|
10
|
+
label: "ignore-previous-instructions",
|
|
11
|
+
pattern: /\bignore\s+(?:all\s+|any\s+)?(?:previous|prior|earlier|the\s+above|preceding)\s+(?:instructions?|prompts?|messages?|commands?|directions?|context|rules?)\b/i
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
label: "disregard-instructions",
|
|
15
|
+
pattern: /\bdisregard\s+(?:all\s+|any\s+|the\s+)?(?:previous|prior|earlier|above|preceding|prior\s+)?(?:instructions?|prompts?|rules?|guidelines?|guardrails?)\b/i
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
label: "override-directive",
|
|
19
|
+
pattern: /\boverride\s+(?:the\s+)?(?:system|safety|security|previous|prior|default)\b/i
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
label: "system-prompt-exfil",
|
|
23
|
+
pattern: /\b(?:reveal|show|print|repeat|leak|expose|dump)\b[^.?!\n]{0,40}\b(?:system\s+prompt|system\s+message|initial\s+instructions|hidden\s+(?:instructions|prompt))\b/i
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
label: "role-markup-injection",
|
|
27
|
+
pattern: /<\/?(?:system|im_start|im_end|tool_call|function_call)\b|\[\/?(?:INST|SYS)\]/i
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
label: "secrecy-directive",
|
|
31
|
+
pattern: /\b(?:do\s+not|don['’]?t|never)\s+(?:tell|inform|notify|alert|mention\s+(?:this\s+)?to|reveal\s+(?:this\s+)?to)\s+(?:the\s+)?(?:user|human|operator|owner)\b|\bwithout\s+(?:telling|informing|notifying|alerting)\s+(?:the\s+)?(?:user|human|operator)\b/i
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
label: "exfiltration-directive",
|
|
35
|
+
pattern: /\b(?:send|forward|email|transmit|upload|post|exfiltrate|leak)\b[^.?!\n]{0,60}\b(?:password|secret|api[\s_-]?keys?|credentials?|private\s+keys?|access\s+tokens?|auth\s+tokens?)\b/i
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
label: "persistent-override",
|
|
39
|
+
pattern: /\bfrom\s+now\s+on\b[^.?!\n]{0,40}\b(?:always|never|you\s+must|ignore|disregard|do\s+not)\b/i
|
|
40
|
+
}
|
|
41
|
+
]);
|
|
42
|
+
/**
|
|
43
|
+
* Scan candidate memory text for obvious injection markers. Pure,
|
|
44
|
+
* offline, allocation-light. Returns the matched rule labels so callers
|
|
45
|
+
* can record *why* a candidate was quarantined.
|
|
46
|
+
*
|
|
47
|
+
* @stable
|
|
48
|
+
*/
|
|
49
|
+
function detectMemoryInjection(text, options = {}) {
|
|
50
|
+
const rules = options.rules ?? DEFAULT_INJECTION_RULES;
|
|
51
|
+
const markers = [];
|
|
52
|
+
for (const rule of rules) if (rule.pattern.test(text)) markers.push(rule.label);
|
|
53
|
+
if (options.extraRules !== void 0) {
|
|
54
|
+
for (const rule of options.extraRules) if (!markers.includes(rule.label) && rule.pattern.test(text)) markers.push(rule.label);
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
flagged: markers.length > 0,
|
|
58
|
+
markers: Object.freeze(markers)
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
//#endregion
|
|
63
|
+
export { detectMemoryInjection };
|
|
64
|
+
//# sourceMappingURL=injection-heuristics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"injection-heuristics.js","names":["DEFAULT_INJECTION_RULES: ReadonlyArray<InjectionRule>","markers: string[]"],"sources":["../../src/internal/injection-heuristics.ts"],"sourcesContent":["/**\n * Offline, dependency-free heuristics that flag obvious memory-injection\n * markers in candidate memory text (P1-4). Long-lived memory is the prime\n * target for poisoning: an injected instruction planted today fires weeks\n * later when semantically recalled, defeating session-scoped prompt-injection\n * defenses (MINJA arXiv:2601.05504; MemoryGraft arXiv:2512.16962). Flagged\n * candidates are quarantined at write time so they never enter\n * action-driving recall until a human validates them.\n *\n * The detector is deliberately *high-precision*: every rule targets\n * injection-characteristic structure (imperative override of prior\n * instructions, role-markup smuggling, secrecy/exfiltration directives), not\n * lone common verbs, so benign first-party facts (\"I always run in the\n * mornings\", \"lives in Lisbon\") are not quarantined. It is locale-aware via\n * pluggable {@link InjectionRule} lists and performs no network or model\n * calls — pure regex over the candidate string.\n *\n * @packageDocumentation\n */\n\n/**\n * A single named injection-detection rule. The `label` is a stable,\n * audit-friendly identifier surfaced in {@link InjectionScan.markers};\n * the `pattern` MUST NOT carry the `g` flag (stateful `RegExp.test`).\n *\n * @stable\n */\nexport interface InjectionRule {\n readonly label: string;\n readonly pattern: RegExp;\n}\n\n/**\n * Result of scanning a candidate string. `flagged` is `true` when at\n * least one rule matched; `markers` lists the matched rule labels\n * (deduped, in rule order) for the audit trail.\n *\n * @stable\n */\nexport interface InjectionScan {\n readonly flagged: boolean;\n readonly markers: ReadonlyArray<string>;\n}\n\n/**\n * Options for {@link detectMemoryInjection}.\n *\n * @stable\n */\nexport interface InjectionHeuristicOptions {\n /**\n * Replace the default English rule set wholesale (advanced — for a\n * fully custom locale). When omitted, {@link DEFAULT_INJECTION_RULES}\n * is used.\n */\n readonly rules?: ReadonlyArray<InjectionRule>;\n /**\n * Additional locale-specific rules merged *after* the active rule set.\n * The common way to make the detector locale-aware without discarding\n * the English defaults.\n */\n readonly extraRules?: ReadonlyArray<InjectionRule>;\n}\n\n/**\n * Default English injection-marker rules. High-precision: each requires\n * injection-characteristic structure so benign facts do not trip them.\n *\n * @stable\n */\nexport const DEFAULT_INJECTION_RULES: ReadonlyArray<InjectionRule> = Object.freeze([\n {\n label: 'ignore-previous-instructions',\n pattern:\n /\\bignore\\s+(?:all\\s+|any\\s+)?(?:previous|prior|earlier|the\\s+above|preceding)\\s+(?:instructions?|prompts?|messages?|commands?|directions?|context|rules?)\\b/i,\n },\n {\n label: 'disregard-instructions',\n pattern:\n /\\bdisregard\\s+(?:all\\s+|any\\s+|the\\s+)?(?:previous|prior|earlier|above|preceding|prior\\s+)?(?:instructions?|prompts?|rules?|guidelines?|guardrails?)\\b/i,\n },\n {\n label: 'override-directive',\n pattern: /\\boverride\\s+(?:the\\s+)?(?:system|safety|security|previous|prior|default)\\b/i,\n },\n {\n label: 'system-prompt-exfil',\n pattern:\n /\\b(?:reveal|show|print|repeat|leak|expose|dump)\\b[^.?!\\n]{0,40}\\b(?:system\\s+prompt|system\\s+message|initial\\s+instructions|hidden\\s+(?:instructions|prompt))\\b/i,\n },\n {\n label: 'role-markup-injection',\n pattern: /<\\/?(?:system|im_start|im_end|tool_call|function_call)\\b|\\[\\/?(?:INST|SYS)\\]/i,\n },\n {\n label: 'secrecy-directive',\n pattern:\n /\\b(?:do\\s+not|don['’]?t|never)\\s+(?:tell|inform|notify|alert|mention\\s+(?:this\\s+)?to|reveal\\s+(?:this\\s+)?to)\\s+(?:the\\s+)?(?:user|human|operator|owner)\\b|\\bwithout\\s+(?:telling|informing|notifying|alerting)\\s+(?:the\\s+)?(?:user|human|operator)\\b/i,\n },\n {\n label: 'exfiltration-directive',\n pattern:\n /\\b(?:send|forward|email|transmit|upload|post|exfiltrate|leak)\\b[^.?!\\n]{0,60}\\b(?:password|secret|api[\\s_-]?keys?|credentials?|private\\s+keys?|access\\s+tokens?|auth\\s+tokens?)\\b/i,\n },\n {\n label: 'persistent-override',\n pattern:\n /\\bfrom\\s+now\\s+on\\b[^.?!\\n]{0,40}\\b(?:always|never|you\\s+must|ignore|disregard|do\\s+not)\\b/i,\n },\n]);\n\n/**\n * Scan candidate memory text for obvious injection markers. Pure,\n * offline, allocation-light. Returns the matched rule labels so callers\n * can record *why* a candidate was quarantined.\n *\n * @stable\n */\nexport function detectMemoryInjection(\n text: string,\n options: InjectionHeuristicOptions = {},\n): InjectionScan {\n const rules = options.rules ?? DEFAULT_INJECTION_RULES;\n const markers: string[] = [];\n for (const rule of rules) {\n if (rule.pattern.test(text)) markers.push(rule.label);\n }\n if (options.extraRules !== undefined) {\n for (const rule of options.extraRules) {\n if (!markers.includes(rule.label) && rule.pattern.test(text)) markers.push(rule.label);\n }\n }\n return { flagged: markers.length > 0, markers: Object.freeze(markers) };\n}\n"],"mappings":";;;;;;;AAsEA,MAAaA,0BAAwD,OAAO,OAAO;CACjF;EACE,OAAO;EACP,SACE;EACH;CACD;EACE,OAAO;EACP,SACE;EACH;CACD;EACE,OAAO;EACP,SAAS;EACV;CACD;EACE,OAAO;EACP,SACE;EACH;CACD;EACE,OAAO;EACP,SAAS;EACV;CACD;EACE,OAAO;EACP,SACE;EACH;CACD;EACE,OAAO;EACP,SACE;EACH;CACD;EACE,OAAO;EACP,SACE;EACH;CACF,CAAC;;;;;;;;AASF,SAAgB,sBACd,MACA,UAAqC,EAAE,EACxB;CACf,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAMC,UAAoB,EAAE;AAC5B,MAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,QAAQ,KAAK,KAAK,CAAE,SAAQ,KAAK,KAAK,MAAM;AAEvD,KAAI,QAAQ,eAAe,QACzB;OAAK,MAAM,QAAQ,QAAQ,WACzB,KAAI,CAAC,QAAQ,SAAS,KAAK,MAAM,IAAI,KAAK,QAAQ,KAAK,KAAK,CAAE,SAAQ,KAAK,KAAK,MAAM;;AAG1F,QAAO;EAAE,SAAS,QAAQ,SAAS;EAAG,SAAS,OAAO,OAAO,QAAQ;EAAE"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
//#region src/internal/spans.ts
|
|
2
|
+
/**
|
|
3
|
+
* Sanitize a `SessionScope` for span attributes — the raw user id is
|
|
4
|
+
* surfaced as-is (it is a stable opaque identifier per DEC-005), but
|
|
5
|
+
* session and agent ids are emitted only when defined to avoid
|
|
6
|
+
* polluting the trace with `null` strings.
|
|
7
|
+
*
|
|
8
|
+
* @internal
|
|
9
|
+
*/
|
|
10
|
+
function scopeAttrs(scope) {
|
|
11
|
+
const attrs = { "memory.scope.user_id": scope.userId };
|
|
12
|
+
if (scope.sessionId !== void 0) attrs["memory.scope.session_id"] = scope.sessionId;
|
|
13
|
+
if (scope.agentId !== void 0) attrs["memory.scope.agent_id"] = scope.agentId;
|
|
14
|
+
return Object.freeze(attrs);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Run an async function inside a `memory.*` span with the standard
|
|
18
|
+
* scope attributes already populated. Adds the span result count and
|
|
19
|
+
* status automatically.
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
async function withMemorySpan(tracer, type, scope, extras, fn) {
|
|
24
|
+
return tracer.span({
|
|
25
|
+
type,
|
|
26
|
+
attrs: {
|
|
27
|
+
...scopeAttrs(scope),
|
|
28
|
+
...extras
|
|
29
|
+
}
|
|
30
|
+
}, fn);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
//#endregion
|
|
34
|
+
export { withMemorySpan };
|
|
35
|
+
//# sourceMappingURL=spans.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spans.js","names":["attrs: Record<string, string>"],"sources":["../../src/internal/spans.ts"],"sourcesContent":["import type { SessionScope, SpanAttributes, SpanType, Tracer } from '@graphorin/core';\n\n/**\n * Sanitize a `SessionScope` for span attributes — the raw user id is\n * surfaced as-is (it is a stable opaque identifier per DEC-005), but\n * session and agent ids are emitted only when defined to avoid\n * polluting the trace with `null` strings.\n *\n * @internal\n */\nexport function scopeAttrs(scope: SessionScope): SpanAttributes {\n const attrs: Record<string, string> = {\n 'memory.scope.user_id': scope.userId,\n };\n if (scope.sessionId !== undefined) attrs['memory.scope.session_id'] = scope.sessionId;\n if (scope.agentId !== undefined) attrs['memory.scope.agent_id'] = scope.agentId;\n return Object.freeze(attrs);\n}\n\n/**\n * Run an async function inside a `memory.*` span with the standard\n * scope attributes already populated. Adds the span result count and\n * status automatically.\n *\n * @internal\n */\nexport async function withMemorySpan<T extends SpanType, R>(\n tracer: Tracer,\n type: T,\n scope: SessionScope,\n extras: SpanAttributes,\n fn: (span: import('@graphorin/core').AISpan<T>) => Promise<R>,\n): Promise<R> {\n return tracer.span({ type, attrs: { ...scopeAttrs(scope), ...extras } }, fn);\n}\n"],"mappings":";;;;;;;;;AAUA,SAAgB,WAAW,OAAqC;CAC9D,MAAMA,QAAgC,EACpC,wBAAwB,MAAM,QAC/B;AACD,KAAI,MAAM,cAAc,OAAW,OAAM,6BAA6B,MAAM;AAC5E,KAAI,MAAM,YAAY,OAAW,OAAM,2BAA2B,MAAM;AACxE,QAAO,OAAO,OAAO,MAAM;;;;;;;;;AAU7B,eAAsB,eACpB,QACA,MACA,OACA,QACA,IACY;AACZ,QAAO,OAAO,KAAK;EAAE;EAAM,OAAO;GAAE,GAAG,WAAW,MAAM;GAAE,GAAG;GAAQ;EAAE,EAAE,GAAG"}
|