@plur-ai/core 0.9.7 → 0.9.8
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/index.d.ts +27 -0
- package/dist/index.js +129 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -2273,6 +2273,33 @@ declare class Plur {
|
|
|
2273
2273
|
* Fast-path hash dedup returns existing on exact match.
|
|
2274
2274
|
*/
|
|
2275
2275
|
learn(statement: string, context?: LearnContext): Engram;
|
|
2276
|
+
/**
|
|
2277
|
+
* Async learn that returns the canonical engram — server-assigned ID
|
|
2278
|
+
* for remote-routed writes, locally-built engram for local writes.
|
|
2279
|
+
*
|
|
2280
|
+
* Use this from async callers (MCP handlers, OpenClaw plugins, etc.)
|
|
2281
|
+
* when the user later needs to reference the engram by ID (forget,
|
|
2282
|
+
* feedback, history). The sync `learn()` returns a local-placeholder
|
|
2283
|
+
* ID for remote-routed writes — the actual server engram has a
|
|
2284
|
+
* different ID, so feedback/forget against the placeholder fails.
|
|
2285
|
+
*
|
|
2286
|
+
* Local writes: just delegates to sync learn(). Same dedup, same
|
|
2287
|
+
* history append, same return shape.
|
|
2288
|
+
*
|
|
2289
|
+
* Remote writes: bypasses local YAML entirely. POSTs to the remote's
|
|
2290
|
+
* /api/v1/engrams, awaits the server's response, and returns an
|
|
2291
|
+
* Engram with the server-assigned id. Throws on remote failure
|
|
2292
|
+
* (caller knows the write didn't land — better UX than a fire-and-
|
|
2293
|
+
* forget that pretends success and leaves the user with a phantom ID).
|
|
2294
|
+
*/
|
|
2295
|
+
learnRouted(statement: string, context?: LearnContext): Promise<Engram>;
|
|
2296
|
+
/**
|
|
2297
|
+
* Build an Engram object without persisting it. Used by learnRouted to
|
|
2298
|
+
* give callers a fully-shaped Engram with the server's ID after the
|
|
2299
|
+
* remote POST completes. Mirrors the construction in learn() but
|
|
2300
|
+
* doesn't acquire the lock or touch disk.
|
|
2301
|
+
*/
|
|
2302
|
+
private _buildEngramShape;
|
|
2276
2303
|
/** Build deps for learn-async module. */
|
|
2277
2304
|
private _learnAsyncDeps;
|
|
2278
2305
|
/** Async learn with LLM-driven deduplication (Ideas 1+2+19). */
|
package/dist/index.js
CHANGED
|
@@ -1617,8 +1617,23 @@ var RemoteStore = class {
|
|
|
1617
1617
|
* Append a single engram to the remote store. POST /api/v1/engrams
|
|
1618
1618
|
* carries statement + scope + domain + type — the server handles
|
|
1619
1619
|
* ID assignment, content_hash, status.
|
|
1620
|
+
*
|
|
1621
|
+
* Returns void to satisfy the EngramStore interface contract. Callers
|
|
1622
|
+
* that need the server-assigned ID (e.g. so the user can later
|
|
1623
|
+
* forget/feedback on it) should use `appendAndGetServerId()` instead.
|
|
1620
1624
|
*/
|
|
1621
1625
|
async append(engram) {
|
|
1626
|
+
await this.appendAndGetServerId(engram);
|
|
1627
|
+
}
|
|
1628
|
+
/**
|
|
1629
|
+
* Like append() but returns the server-assigned ID. Required because
|
|
1630
|
+
* the server picks its own ID (e.g. ENG-2026-05-06-007) and ignores
|
|
1631
|
+
* any id we'd send. Without this, callers see the local placeholder
|
|
1632
|
+
* ID (e.g. ENG-2026-0506-017) and a later `forget(id)` against that
|
|
1633
|
+
* placeholder will fail — the engram only exists on the server with
|
|
1634
|
+
* the server's ID.
|
|
1635
|
+
*/
|
|
1636
|
+
async appendAndGetServerId(engram) {
|
|
1622
1637
|
const body = JSON.stringify({
|
|
1623
1638
|
statement: engram.statement,
|
|
1624
1639
|
scope: engram.scope,
|
|
@@ -1634,7 +1649,12 @@ var RemoteStore = class {
|
|
|
1634
1649
|
const text = await r.text().catch(() => "");
|
|
1635
1650
|
throw new Error(`Remote store append failed: ${r.status} ${text}`);
|
|
1636
1651
|
}
|
|
1652
|
+
const data = await r.json().catch(() => ({}));
|
|
1653
|
+
if (!data.id) {
|
|
1654
|
+
throw new Error(`Remote store append succeeded but server returned no id`);
|
|
1655
|
+
}
|
|
1637
1656
|
this.cache = null;
|
|
1657
|
+
return { id: data.id };
|
|
1638
1658
|
}
|
|
1639
1659
|
/**
|
|
1640
1660
|
* `save(all)` — used by migrations to bulk-replace. Not supported
|
|
@@ -3643,6 +3663,115 @@ var Plur = class {
|
|
|
3643
3663
|
return engram;
|
|
3644
3664
|
});
|
|
3645
3665
|
}
|
|
3666
|
+
/**
|
|
3667
|
+
* Async learn that returns the canonical engram — server-assigned ID
|
|
3668
|
+
* for remote-routed writes, locally-built engram for local writes.
|
|
3669
|
+
*
|
|
3670
|
+
* Use this from async callers (MCP handlers, OpenClaw plugins, etc.)
|
|
3671
|
+
* when the user later needs to reference the engram by ID (forget,
|
|
3672
|
+
* feedback, history). The sync `learn()` returns a local-placeholder
|
|
3673
|
+
* ID for remote-routed writes — the actual server engram has a
|
|
3674
|
+
* different ID, so feedback/forget against the placeholder fails.
|
|
3675
|
+
*
|
|
3676
|
+
* Local writes: just delegates to sync learn(). Same dedup, same
|
|
3677
|
+
* history append, same return shape.
|
|
3678
|
+
*
|
|
3679
|
+
* Remote writes: bypasses local YAML entirely. POSTs to the remote's
|
|
3680
|
+
* /api/v1/engrams, awaits the server's response, and returns an
|
|
3681
|
+
* Engram with the server-assigned id. Throws on remote failure
|
|
3682
|
+
* (caller knows the write didn't land — better UX than a fire-and-
|
|
3683
|
+
* forget that pretends success and leaves the user with a phantom ID).
|
|
3684
|
+
*/
|
|
3685
|
+
async learnRouted(statement, context) {
|
|
3686
|
+
if (!this.config.allow_secrets) {
|
|
3687
|
+
const secrets = detectSecrets(statement);
|
|
3688
|
+
if (secrets.length > 0) {
|
|
3689
|
+
throw new Error(`Secret detected in statement: ${secrets[0].pattern}. Use config.allow_secrets to override.`);
|
|
3690
|
+
}
|
|
3691
|
+
}
|
|
3692
|
+
const scope = context?.scope ?? "global";
|
|
3693
|
+
const remoteDriver = this._resolveRemoteStoreForScope(scope);
|
|
3694
|
+
if (!remoteDriver) {
|
|
3695
|
+
return this.learn(statement, context);
|
|
3696
|
+
}
|
|
3697
|
+
const allEngrams = this._loadAllEngrams();
|
|
3698
|
+
const hashMatch = this._hashDedup(statement, allEngrams);
|
|
3699
|
+
if (hashMatch) return hashMatch;
|
|
3700
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3701
|
+
const localPlaceholder = this._buildEngramShape(statement, scope, context, now);
|
|
3702
|
+
const { id: serverId } = await remoteDriver.appendAndGetServerId(localPlaceholder);
|
|
3703
|
+
const serverEngram = { ...localPlaceholder, id: serverId };
|
|
3704
|
+
appendHistory(this.paths.root, {
|
|
3705
|
+
event: "engram_created",
|
|
3706
|
+
engram_id: serverId,
|
|
3707
|
+
timestamp: now,
|
|
3708
|
+
data: { type: serverEngram.type, scope: serverEngram.scope, source: serverEngram.source, routed_to: "remote" }
|
|
3709
|
+
});
|
|
3710
|
+
return serverEngram;
|
|
3711
|
+
}
|
|
3712
|
+
/**
|
|
3713
|
+
* Build an Engram object without persisting it. Used by learnRouted to
|
|
3714
|
+
* give callers a fully-shaped Engram with the server's ID after the
|
|
3715
|
+
* remote POST completes. Mirrors the construction in learn() but
|
|
3716
|
+
* doesn't acquire the lock or touch disk.
|
|
3717
|
+
*/
|
|
3718
|
+
_buildEngramShape(statement, scope, context, now) {
|
|
3719
|
+
const type = context?.type ?? "behavioral";
|
|
3720
|
+
const cogLevel = TYPE_TO_COGNITIVE2[type] ?? "remember";
|
|
3721
|
+
const TYPE_TO_MEMORY_CLASS3 = {
|
|
3722
|
+
behavioral: "semantic",
|
|
3723
|
+
terminological: "semantic",
|
|
3724
|
+
procedural: "procedural",
|
|
3725
|
+
architectural: "semantic"
|
|
3726
|
+
};
|
|
3727
|
+
const memoryClass = context?.memory_class ?? TYPE_TO_MEMORY_CLASS3[type] ?? "semantic";
|
|
3728
|
+
const commitment = context?.commitment ?? "leaning";
|
|
3729
|
+
return {
|
|
3730
|
+
// Placeholder id — overwritten by the server's assigned id before return.
|
|
3731
|
+
// Any consumer that observes this id directly (rather than via learnRouted's
|
|
3732
|
+
// return value) is doing it wrong — log says so.
|
|
3733
|
+
id: "__pending__",
|
|
3734
|
+
version: 2,
|
|
3735
|
+
status: "active",
|
|
3736
|
+
consolidated: false,
|
|
3737
|
+
type,
|
|
3738
|
+
scope,
|
|
3739
|
+
visibility: context?.visibility ?? (context?.domain ? "public" : "private"),
|
|
3740
|
+
statement,
|
|
3741
|
+
rationale: context?.rationale,
|
|
3742
|
+
source: context?.source,
|
|
3743
|
+
domain: context?.domain,
|
|
3744
|
+
activation: {
|
|
3745
|
+
retrieval_strength: 0.7,
|
|
3746
|
+
storage_strength: 1,
|
|
3747
|
+
frequency: 0,
|
|
3748
|
+
last_accessed: now.slice(0, 10)
|
|
3749
|
+
},
|
|
3750
|
+
feedback_signals: { positive: 0, negative: 0, neutral: 0 },
|
|
3751
|
+
knowledge_type: { memory_class: memoryClass, cognitive_level: cogLevel },
|
|
3752
|
+
knowledge_anchors: (context?.knowledge_anchors ?? []).map((a) => ({
|
|
3753
|
+
path: a.path,
|
|
3754
|
+
relevance: a.relevance ?? "supporting",
|
|
3755
|
+
snippet: a.snippet
|
|
3756
|
+
})),
|
|
3757
|
+
associations: [],
|
|
3758
|
+
derivation_count: 1,
|
|
3759
|
+
tags: context?.tags ?? [],
|
|
3760
|
+
pack: null,
|
|
3761
|
+
abstract: context?.abstract ?? null,
|
|
3762
|
+
derived_from: context?.derived_from ?? null,
|
|
3763
|
+
dual_coding: context?.dual_coding,
|
|
3764
|
+
polarity: null,
|
|
3765
|
+
content_hash: computeContentHash(statement),
|
|
3766
|
+
commitment,
|
|
3767
|
+
locked_at: commitment === "locked" ? now : void 0,
|
|
3768
|
+
locked_reason: commitment === "locked" ? context?.locked_reason : void 0,
|
|
3769
|
+
summary: autoSummary(statement, void 0),
|
|
3770
|
+
engram_version: 1,
|
|
3771
|
+
episode_ids: context?.session_episode_id ? [context.session_episode_id] : [],
|
|
3772
|
+
pinned: context?.pinned === true ? true : void 0
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3646
3775
|
/** Build deps for learn-async module. */
|
|
3647
3776
|
_learnAsyncDeps() {
|
|
3648
3777
|
return {
|