@remnic/core 9.62.0 → 9.62.1
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/access-admin-ops-surface.js +11 -11
- package/dist/access-authorization-probe.js +12 -12
- package/dist/access-boundary.js +11 -11
- package/dist/access-cli.js +43 -43
- package/dist/access-extraction-force-flush.js +11 -11
- package/dist/access-http-query.js +11 -11
- package/dist/access-http.js +20 -20
- package/dist/access-identity-continuity-surface.js +11 -11
- package/dist/access-lcm-surface.js +11 -11
- package/dist/access-mcp.js +16 -16
- package/dist/access-namespace-preflight.js +11 -11
- package/dist/access-observe-write-surface.js +11 -11
- package/dist/access-operations-batch.js +12 -12
- package/dist/access-operations.js +15 -15
- package/dist/access-recall-concurrency.js +11 -11
- package/dist/access-recall-response.js +11 -11
- package/dist/access-recall-surface.js +11 -11
- package/dist/access-service.js +11 -11
- package/dist/access-surface-catalog.js +13 -13
- package/dist/active-memory-bridge.js +2 -2
- package/dist/active-recall.js +3 -3
- package/dist/briefing.js +4 -4
- package/dist/causal-consolidation.js +5 -5
- package/dist/{chunk-KAB7TZN3.js → chunk-3G6YU5YD.js} +2 -2
- package/dist/{chunk-XD4VGLDL.js → chunk-3XG4QEA2.js} +35 -31
- package/dist/chunk-3XG4QEA2.js.map +1 -0
- package/dist/{chunk-HEEEMJ26.js → chunk-4E67XCT2.js} +3 -3
- package/dist/{chunk-H32ANIFP.js → chunk-4G45LJJP.js} +2 -2
- package/dist/{chunk-4XAVQLBR.js → chunk-4Y2RHVCV.js} +2 -2
- package/dist/{chunk-S3B3UIQ3.js → chunk-67MKNKJQ.js} +2 -2
- package/dist/{chunk-PA5OPZX2.js → chunk-75U2FR7U.js} +2 -2
- package/dist/{chunk-I67HKRBF.js → chunk-7JAXC526.js} +2 -2
- package/dist/{chunk-3GPBFDXH.js → chunk-7PVTOZR4.js} +2 -2
- package/dist/{chunk-DSA47WBN.js → chunk-BHO3IVAN.js} +2 -2
- package/dist/{chunk-MYZ4NA43.js → chunk-DYDACAK2.js} +2 -2
- package/dist/{chunk-4JGSG7QN.js → chunk-ETCVSZLG.js} +2 -2
- package/dist/{chunk-EIQFIJDM.js → chunk-EU74U4GT.js} +2 -2
- package/dist/{chunk-L33N3BCA.js → chunk-GZRDOVQN.js} +2 -2
- package/dist/{chunk-AWRJBAIH.js → chunk-HKYNBEL4.js} +6 -6
- package/dist/{chunk-MJ4UGUR6.js → chunk-IIRK5V5Z.js} +2 -2
- package/dist/{chunk-2RIDW23Y.js → chunk-IXJ5U6ZL.js} +8 -8
- package/dist/{chunk-QRKYABS6.js → chunk-JDJOAL4B.js} +2 -2
- package/dist/{chunk-GN7HO2LA.js → chunk-JF6YSHJM.js} +7 -7
- package/dist/{chunk-D5HDJWNN.js → chunk-JLMH5YMU.js} +3 -3
- package/dist/{chunk-FW73HN33.js → chunk-K4WIUTNH.js} +2 -2
- package/dist/{chunk-TE4MKFT2.js → chunk-KCFZOSR6.js} +4 -4
- package/dist/{chunk-VWDTV6SH.js → chunk-KYI3CM42.js} +2 -2
- package/dist/{chunk-CIA6ZUBA.js → chunk-LAM6UDFV.js} +2 -2
- package/dist/{chunk-W4Q54HM3.js → chunk-LLLY5IBK.js} +2 -2
- package/dist/{chunk-CM4SZZBN.js → chunk-MJFDJTDC.js} +2 -2
- package/dist/{chunk-VOELBUS4.js → chunk-NXH34IPL.js} +2 -2
- package/dist/{chunk-MU3TPZOV.js → chunk-PBH2Z2NY.js} +2 -2
- package/dist/chunk-PJKTFXE2.js +119 -0
- package/dist/chunk-PJKTFXE2.js.map +1 -0
- package/dist/{chunk-G3YBWFUC.js → chunk-QN7XQEIT.js} +34 -17
- package/dist/chunk-QN7XQEIT.js.map +1 -0
- package/dist/{chunk-HQBVQICL.js → chunk-QNMNNFKD.js} +2 -2
- package/dist/{chunk-EGW4O32Z.js → chunk-QRA3XVJX.js} +7 -4
- package/dist/chunk-QRA3XVJX.js.map +1 -0
- package/dist/{chunk-L4DLLKPN.js → chunk-SZ4HFZKB.js} +2 -2
- package/dist/{chunk-N4OYPGZS.js → chunk-TLUSSS2F.js} +2 -2
- package/dist/{chunk-N4EPIKSY.js → chunk-U27G4E5G.js} +3 -3
- package/dist/{chunk-W3Y6FYRK.js → chunk-UIRU3MIJ.js} +2 -2
- package/dist/{chunk-URN7XWGC.js → chunk-WANOCU3K.js} +2 -2
- package/dist/{chunk-XDK6ZFLL.js → chunk-WTMG5GAK.js} +2 -2
- package/dist/{chunk-C3R732UY.js → chunk-XIABRKF6.js} +37 -32
- package/dist/chunk-XIABRKF6.js.map +1 -0
- package/dist/{chunk-D4OCJSLV.js → chunk-YDPAE2RD.js} +2 -2
- package/dist/{chunk-FQSDU634.js → chunk-YG56A6XY.js} +2 -2
- package/dist/{chunk-XBR5BAKW.js → chunk-YSUSUZR4.js} +3 -3
- package/dist/{chunk-YUIBXMZY.js → chunk-ZJKLGS76.js} +2 -2
- package/dist/cli.js +32 -32
- package/dist/compounding/engine.js +4 -4
- package/dist/config.js +3 -3
- package/dist/connectors/codex-materialize-runner.js +4 -4
- package/dist/connectors/index.js +4 -4
- package/dist/convergence-refresh.js +6 -6
- package/dist/corpus-watermark.js +6 -6
- package/dist/entity-retrieval.js +4 -4
- package/dist/external-wiki-access.js +12 -12
- package/dist/extraction-source-grounding.js +3 -3
- package/dist/extraction.js +7 -7
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +56 -43
- package/dist/maintenance/memory-governance.js +4 -4
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +4 -4
- package/dist/maintenance/rebuild-memory-projection.js +5 -5
- package/dist/namespaces/migrate.js +5 -5
- package/dist/namespaces/storage.js +4 -4
- package/dist/operator-doctor-corpus.js +7 -7
- package/dist/operator-toolkit.js +12 -12
- package/dist/orchestration/maintenance.js +9 -9
- package/dist/orchestrator.js +43 -43
- package/dist/provenance.js +2 -2
- package/dist/resume-bundles.js +4 -4
- package/dist/semantic-consolidation.js +5 -5
- package/dist/semantic-rule-promotion.js +4 -4
- package/dist/semantic-rule-verifier.js +4 -4
- package/dist/source-agent-qualifier.js +6 -6
- package/dist/storage.js +3 -3
- package/dist/support-passport/index.js +14 -14
- package/dist/verified-recall.js +4 -4
- package/dist/whitespace.d.ts +26 -1
- package/dist/whitespace.js +11 -3
- package/package.json +2 -2
- package/src/cli.ts +14 -13
- package/src/entity-retrieval.ts +4 -7
- package/src/harmonic-construction.test.ts +187 -2
- package/src/harmonic-retrieval.ts +54 -32
- package/src/index.ts +3 -4
- package/src/orchestration/recall-section-coordinator.ts +20 -14
- package/src/whitespace.test.ts +178 -0
- package/src/whitespace.ts +119 -0
- package/dist/chunk-6MKAMLQL.js +0 -16
- package/dist/chunk-6MKAMLQL.js.map +0 -1
- package/dist/chunk-C3R732UY.js.map +0 -1
- package/dist/chunk-EGW4O32Z.js.map +0 -1
- package/dist/chunk-G3YBWFUC.js.map +0 -1
- package/dist/chunk-XD4VGLDL.js.map +0 -1
- /package/dist/{chunk-KAB7TZN3.js.map → chunk-3G6YU5YD.js.map} +0 -0
- /package/dist/{chunk-HEEEMJ26.js.map → chunk-4E67XCT2.js.map} +0 -0
- /package/dist/{chunk-H32ANIFP.js.map → chunk-4G45LJJP.js.map} +0 -0
- /package/dist/{chunk-4XAVQLBR.js.map → chunk-4Y2RHVCV.js.map} +0 -0
- /package/dist/{chunk-S3B3UIQ3.js.map → chunk-67MKNKJQ.js.map} +0 -0
- /package/dist/{chunk-PA5OPZX2.js.map → chunk-75U2FR7U.js.map} +0 -0
- /package/dist/{chunk-I67HKRBF.js.map → chunk-7JAXC526.js.map} +0 -0
- /package/dist/{chunk-3GPBFDXH.js.map → chunk-7PVTOZR4.js.map} +0 -0
- /package/dist/{chunk-DSA47WBN.js.map → chunk-BHO3IVAN.js.map} +0 -0
- /package/dist/{chunk-MYZ4NA43.js.map → chunk-DYDACAK2.js.map} +0 -0
- /package/dist/{chunk-4JGSG7QN.js.map → chunk-ETCVSZLG.js.map} +0 -0
- /package/dist/{chunk-EIQFIJDM.js.map → chunk-EU74U4GT.js.map} +0 -0
- /package/dist/{chunk-L33N3BCA.js.map → chunk-GZRDOVQN.js.map} +0 -0
- /package/dist/{chunk-AWRJBAIH.js.map → chunk-HKYNBEL4.js.map} +0 -0
- /package/dist/{chunk-MJ4UGUR6.js.map → chunk-IIRK5V5Z.js.map} +0 -0
- /package/dist/{chunk-2RIDW23Y.js.map → chunk-IXJ5U6ZL.js.map} +0 -0
- /package/dist/{chunk-QRKYABS6.js.map → chunk-JDJOAL4B.js.map} +0 -0
- /package/dist/{chunk-GN7HO2LA.js.map → chunk-JF6YSHJM.js.map} +0 -0
- /package/dist/{chunk-D5HDJWNN.js.map → chunk-JLMH5YMU.js.map} +0 -0
- /package/dist/{chunk-FW73HN33.js.map → chunk-K4WIUTNH.js.map} +0 -0
- /package/dist/{chunk-TE4MKFT2.js.map → chunk-KCFZOSR6.js.map} +0 -0
- /package/dist/{chunk-VWDTV6SH.js.map → chunk-KYI3CM42.js.map} +0 -0
- /package/dist/{chunk-CIA6ZUBA.js.map → chunk-LAM6UDFV.js.map} +0 -0
- /package/dist/{chunk-W4Q54HM3.js.map → chunk-LLLY5IBK.js.map} +0 -0
- /package/dist/{chunk-CM4SZZBN.js.map → chunk-MJFDJTDC.js.map} +0 -0
- /package/dist/{chunk-VOELBUS4.js.map → chunk-NXH34IPL.js.map} +0 -0
- /package/dist/{chunk-MU3TPZOV.js.map → chunk-PBH2Z2NY.js.map} +0 -0
- /package/dist/{chunk-HQBVQICL.js.map → chunk-QNMNNFKD.js.map} +0 -0
- /package/dist/{chunk-L4DLLKPN.js.map → chunk-SZ4HFZKB.js.map} +0 -0
- /package/dist/{chunk-N4OYPGZS.js.map → chunk-TLUSSS2F.js.map} +0 -0
- /package/dist/{chunk-N4EPIKSY.js.map → chunk-U27G4E5G.js.map} +0 -0
- /package/dist/{chunk-W3Y6FYRK.js.map → chunk-UIRU3MIJ.js.map} +0 -0
- /package/dist/{chunk-URN7XWGC.js.map → chunk-WANOCU3K.js.map} +0 -0
- /package/dist/{chunk-XDK6ZFLL.js.map → chunk-WTMG5GAK.js.map} +0 -0
- /package/dist/{chunk-D4OCJSLV.js.map → chunk-YDPAE2RD.js.map} +0 -0
- /package/dist/{chunk-FQSDU634.js.map → chunk-YG56A6XY.js.map} +0 -0
- /package/dist/{chunk-XBR5BAKW.js.map → chunk-YSUSUZR4.js.map} +0 -0
- /package/dist/{chunk-YUIBXMZY.js.map → chunk-ZJKLGS76.js.map} +0 -0
package/src/cli.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { resolveNamespaceCapabilities,
|
|
|
12
12
|
import { ThreadingManager } from "./threading.js";
|
|
13
13
|
import { bumpMemoryCorpusVersionForDir } from "./memory-corpus-version.js";
|
|
14
14
|
import { utcDayRange } from "./transcript.js";
|
|
15
|
+
import { padEndDisplay, truncateGraphemeSafe } from "./whitespace.js";
|
|
15
16
|
import { runWearablesCliCommand } from "./wearables/cli.js";
|
|
16
17
|
import { registerMeetingsCommands } from "./cli/meetings-commands.js";
|
|
17
18
|
import { registerResearchStatusCommands } from "./cli/research-status-commands.js";
|
|
@@ -5672,7 +5673,7 @@ export function registerCli(
|
|
|
5672
5673
|
console.log(` raw excerpts (${r.rawExcerpts.length}):`);
|
|
5673
5674
|
for (const ex of r.rawExcerpts) {
|
|
5674
5675
|
console.log(
|
|
5675
|
-
` [turn ${ex.turnIndex}, ${ex.role}] ${ex.content
|
|
5676
|
+
` [turn ${ex.turnIndex}, ${ex.role}] ${truncateGraphemeSafe(ex.content, 200)}`,
|
|
5676
5677
|
);
|
|
5677
5678
|
}
|
|
5678
5679
|
}
|
|
@@ -7614,7 +7615,7 @@ export function registerCli(
|
|
|
7614
7615
|
console.log(` ${r.path} (score: ${r.score.toFixed(3)})`);
|
|
7615
7616
|
if (r.snippet) {
|
|
7616
7617
|
console.log(
|
|
7617
|
-
` ${r.snippet
|
|
7618
|
+
` ${truncateGraphemeSafe(r.snippet, 150).replace(/\n/g, " ")}`,
|
|
7618
7619
|
);
|
|
7619
7620
|
}
|
|
7620
7621
|
console.log();
|
|
@@ -7640,7 +7641,7 @@ export function registerCli(
|
|
|
7640
7641
|
console.log(`\n=== Text Search Fallback: "${query}" (${matches.length} results) ===\n`);
|
|
7641
7642
|
console.log(`QMD status: ${qmdStatus}\n`);
|
|
7642
7643
|
for (const m of matches.slice(0, maxResults)) {
|
|
7643
|
-
console.log(` [${m.frontmatter.category}] ${m.content
|
|
7644
|
+
console.log(` [${m.frontmatter.category}] ${truncateGraphemeSafe(m.content, 120)}`);
|
|
7644
7645
|
}
|
|
7645
7646
|
}
|
|
7646
7647
|
});
|
|
@@ -7783,12 +7784,12 @@ export function registerCli(
|
|
|
7783
7784
|
for (const cluster of result.clusters) {
|
|
7784
7785
|
console.log(`\n Category: ${cluster.category} (${cluster.memories.length} memories, overlap=${cluster.overlapScore.toFixed(2)})`);
|
|
7785
7786
|
for (const m of cluster.memories) {
|
|
7786
|
-
const preview = m.content.length > 80 ? m.content
|
|
7787
|
+
const preview = m.content.length > 80 ? `${truncateGraphemeSafe(m.content, 80)}...` : m.content;
|
|
7787
7788
|
console.log(` - ${m.frontmatter.id}: ${preview}`);
|
|
7788
7789
|
}
|
|
7789
7790
|
if (cluster.canonicalContent) {
|
|
7790
7791
|
const preview = cluster.canonicalContent.length > 120
|
|
7791
|
-
? cluster.canonicalContent
|
|
7792
|
+
? `${truncateGraphemeSafe(cluster.canonicalContent, 120)}...`
|
|
7792
7793
|
: cluster.canonicalContent;
|
|
7793
7794
|
console.log(` → Canonical: ${preview}`);
|
|
7794
7795
|
}
|
|
@@ -8018,7 +8019,7 @@ export function registerCli(
|
|
|
8018
8019
|
const lastAccessed = m.frontmatter.lastAccessed
|
|
8019
8020
|
? new Date(m.frontmatter.lastAccessed).toLocaleDateString()
|
|
8020
8021
|
: "unknown";
|
|
8021
|
-
console.log(` ${m.frontmatter.accessCount}x [${m.frontmatter.category}] ${m.content
|
|
8022
|
+
console.log(` ${m.frontmatter.accessCount}x [${m.frontmatter.category}] ${truncateGraphemeSafe(m.content, 80)}`);
|
|
8022
8023
|
console.log(` Last accessed: ${lastAccessed} ID: ${m.frontmatter.id}`);
|
|
8023
8024
|
console.log();
|
|
8024
8025
|
}
|
|
@@ -8071,7 +8072,7 @@ export function registerCli(
|
|
|
8071
8072
|
console.log("\n=== Importance Distribution ===\n");
|
|
8072
8073
|
for (const [level, count] of Object.entries(levelCounts)) {
|
|
8073
8074
|
const bar = "█".repeat(Math.min(count, 50));
|
|
8074
|
-
console.log(` ${level
|
|
8075
|
+
console.log(` ${padEndDisplay(level, 10)} ${count.toString().padStart(4)} ${bar}`);
|
|
8075
8076
|
}
|
|
8076
8077
|
console.log(`\n Total scored: ${withImportance.length} / ${memories.length} memories\n`);
|
|
8077
8078
|
|
|
@@ -8104,7 +8105,7 @@ export function registerCli(
|
|
|
8104
8105
|
console.log(
|
|
8105
8106
|
` ${imp.score.toFixed(2)} [${imp.level}] [${m.frontmatter.category}]`,
|
|
8106
8107
|
);
|
|
8107
|
-
console.log(` ${m.content
|
|
8108
|
+
console.log(` ${truncateGraphemeSafe(m.content, 100)}`);
|
|
8108
8109
|
if (imp.keywords.length > 0) {
|
|
8109
8110
|
console.log(` Keywords: ${imp.keywords.join(", ")}`);
|
|
8110
8111
|
}
|
|
@@ -8131,7 +8132,7 @@ export function registerCli(
|
|
|
8131
8132
|
|
|
8132
8133
|
for (const topic of topics.slice(0, top)) {
|
|
8133
8134
|
const bar = "█".repeat(Math.min(Math.round(topic.score * 10), 30));
|
|
8134
|
-
console.log(` ${topic.term
|
|
8135
|
+
console.log(` ${padEndDisplay(topic.term, 20)} ${topic.score.toFixed(3)} (${topic.count}x) ${bar}`);
|
|
8135
8136
|
}
|
|
8136
8137
|
});
|
|
8137
8138
|
|
|
@@ -8163,7 +8164,7 @@ export function registerCli(
|
|
|
8163
8164
|
console.log(` Time range: ${summary.timeRangeStart.slice(0, 10)} to ${summary.timeRangeEnd.slice(0, 10)}`);
|
|
8164
8165
|
console.log(` Source memories: ${summary.sourceEpisodeIds.length}`);
|
|
8165
8166
|
console.log(` Key facts: ${summary.keyFacts.length}`);
|
|
8166
|
-
console.log(`\n Summary: ${summary.summaryText
|
|
8167
|
+
console.log(`\n Summary: ${truncateGraphemeSafe(summary.summaryText, 200)}...`);
|
|
8167
8168
|
console.log();
|
|
8168
8169
|
}
|
|
8169
8170
|
});
|
|
@@ -8255,13 +8256,13 @@ export function registerCli(
|
|
|
8255
8256
|
const parent = memories.find((m) => m.frontmatter.id === parentId);
|
|
8256
8257
|
console.log(`\n=== Chunks for ${parentId} ===\n`);
|
|
8257
8258
|
if (parent) {
|
|
8258
|
-
console.log(`Parent: ${parent.content
|
|
8259
|
+
console.log(`Parent: ${truncateGraphemeSafe(parent.content, 100)}...`);
|
|
8259
8260
|
console.log();
|
|
8260
8261
|
}
|
|
8261
8262
|
|
|
8262
8263
|
for (const chunk of chunks) {
|
|
8263
8264
|
console.log(
|
|
8264
|
-
` [${(chunk.frontmatter.chunkIndex ?? 0) + 1}/${chunk.frontmatter.chunkTotal}] ${chunk.content
|
|
8265
|
+
` [${(chunk.frontmatter.chunkIndex ?? 0) + 1}/${chunk.frontmatter.chunkTotal}] ${truncateGraphemeSafe(chunk.content, 80)}...`,
|
|
8265
8266
|
);
|
|
8266
8267
|
}
|
|
8267
8268
|
return;
|
|
@@ -9408,7 +9409,7 @@ function formatTranscript(entries: TranscriptEntry[]): string {
|
|
|
9408
9409
|
return entries
|
|
9409
9410
|
.map((e) => {
|
|
9410
9411
|
const time = e.timestamp.slice(11, 16); // HH:MM
|
|
9411
|
-
return `[${time}] ${e.role}: ${e.content
|
|
9412
|
+
return `[${time}] ${e.role}: ${truncateGraphemeSafe(e.content, 200)}${e.content.length > 200 ? "..." : ""}`;
|
|
9412
9413
|
})
|
|
9413
9414
|
.join("\n");
|
|
9414
9415
|
}
|
package/src/entity-retrieval.ts
CHANGED
|
@@ -8,12 +8,9 @@ import { collectNativeKnowledgeChunks, type NativeKnowledgeChunk } from "./nativ
|
|
|
8
8
|
import { compareEntityTimestamps, normalizeEntityName, type StorageManager } from "./storage.js";
|
|
9
9
|
import { normalizeEntityText, resolveRequestedEntitySectionKeys } from "./entity-schema.js";
|
|
10
10
|
import type { EntityStructuredSection, MemoryFile, PluginConfig, TranscriptEntry } from "./types.js";
|
|
11
|
+
import { truncateGraphemeSafe } from "./whitespace.js";
|
|
11
12
|
import { containsPhrase } from "./entity-retrieval-boundaries.js";
|
|
12
|
-
import {
|
|
13
|
-
buildOriginStructuredSections,
|
|
14
|
-
sanitizeOriginatedFacts,
|
|
15
|
-
type EntityOriginStructuredSection,
|
|
16
|
-
} from "./entity-origin-fields.js";
|
|
13
|
+
import { buildOriginStructuredSections, sanitizeOriginatedFacts, type EntityOriginStructuredSection } from "./entity-origin-fields.js";
|
|
17
14
|
import {
|
|
18
15
|
checkEntityRecallAbort,
|
|
19
16
|
entityRecallSectionAbsent,
|
|
@@ -106,7 +103,7 @@ function uniqueStrings(values: string[]): string[] {
|
|
|
106
103
|
function compactLine(value: string, maxLength: number = 220): string {
|
|
107
104
|
const normalized = value.replace(/\s+/g, " ").trim();
|
|
108
105
|
if (normalized.length <= maxLength) return normalized;
|
|
109
|
-
return `${normalized
|
|
106
|
+
return `${truncateGraphemeSafe(normalized, maxLength - 1).trimEnd()}…`;
|
|
110
107
|
}
|
|
111
108
|
function dedupeHintSnippetsByText(snippets: EntityHintSnippet[]): EntityHintSnippet[] {
|
|
112
109
|
const deduped = new Map<string, EntityHintSnippet>();
|
|
@@ -1052,7 +1049,7 @@ function formatEntityHintSection(
|
|
|
1052
1049
|
|
|
1053
1050
|
let result = lines.join("\n");
|
|
1054
1051
|
if (result.length > maxChars) {
|
|
1055
|
-
result = `${result
|
|
1052
|
+
result = `${truncateGraphemeSafe(result, Math.max(0, maxChars - 15)).trimEnd()}\n\n...(trimmed)\n`;
|
|
1056
1053
|
}
|
|
1057
1054
|
return result.trim().length > 0 ? result.trimEnd() : null;
|
|
1058
1055
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { appendFile, mkdir, mkdtemp, open, readFile, rm, stat, unlink, utimes, writeFile } from "node:fs/promises";
|
|
3
|
+
import { appendFile, mkdir, mkdtemp, open, readdir, readFile, rm, stat, unlink, utimes, writeFile } from "node:fs/promises";
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import test from "node:test";
|
|
7
7
|
import {
|
|
8
|
+
HARMONIC_SOURCE_MEMORY_INSERTED_AT_KEY,
|
|
8
9
|
getAbstractionNodeStoreStatus,
|
|
9
10
|
upsertAbstractionNode,
|
|
10
11
|
upsertAbstractionNodes,
|
|
@@ -26,12 +27,14 @@ import {
|
|
|
26
27
|
deriveHarmonicRecords,
|
|
27
28
|
normalizeCueAnchorInputs,
|
|
28
29
|
} from "./harmonic-construction.js";
|
|
29
|
-
import { searchHarmonicRetrieval } from "./harmonic-retrieval.js";
|
|
30
|
+
import { searchHarmonicRetrieval, type HarmonicRetrievalResult } from "./harmonic-retrieval.js";
|
|
30
31
|
import { readJsonFile, withJsonStoreMutationLock } from "./json-store.js";
|
|
31
32
|
import {
|
|
32
33
|
filterHarmonicEntityMentions,
|
|
33
34
|
persistConstructedHarmonicRecords,
|
|
34
35
|
} from "./orchestration/harmonic-construction-persist.js";
|
|
36
|
+
import { compareDeterministicStrings } from "./deterministic-order.js";
|
|
37
|
+
import { stripAttributesSuffix } from "./structured-attributes.js";
|
|
35
38
|
import { Orchestrator } from "./orchestrator.js";
|
|
36
39
|
import { ExtractedFactSchema, ExtractionResultSchema } from "./schemas.js";
|
|
37
40
|
import type { ExtractionResult, MemoryFile } from "./types.js";
|
|
@@ -1259,6 +1262,188 @@ test("harmonic anchors stay scoped to projected active sources in a mixed episod
|
|
|
1259
1262
|
}
|
|
1260
1263
|
});
|
|
1261
1264
|
|
|
1265
|
+
async function persistRecencyFixture(memoryDir: string): Promise<{
|
|
1266
|
+
storageDir: string;
|
|
1267
|
+
persistedIds: string[];
|
|
1268
|
+
contentById: Map<string, string>;
|
|
1269
|
+
}> {
|
|
1270
|
+
const config = parseConfig({
|
|
1271
|
+
openaiApiKey: "sk-test",
|
|
1272
|
+
memoryDir,
|
|
1273
|
+
workspaceDir: path.join(memoryDir, "workspace"),
|
|
1274
|
+
qmdEnabled: false,
|
|
1275
|
+
embeddingFallbackEnabled: false,
|
|
1276
|
+
chunkingEnabled: false,
|
|
1277
|
+
harmonicRetrievalEnabled: true,
|
|
1278
|
+
abstractionAnchorsEnabled: false,
|
|
1279
|
+
});
|
|
1280
|
+
const orchestrator = new Orchestrator(config) as unknown as PersistenceHarness;
|
|
1281
|
+
const storage = await orchestrator.getStorage("default");
|
|
1282
|
+
await storage.ensureDirectories();
|
|
1283
|
+
const contents = [
|
|
1284
|
+
"Atlas fact zero of the oldest insertion pair.",
|
|
1285
|
+
"Atlas fact one of the oldest insertion pair.",
|
|
1286
|
+
"Atlas fact two at the middle insertion time.",
|
|
1287
|
+
"Atlas fact three of the newest insertion pair.",
|
|
1288
|
+
"Atlas fact four of the newest insertion pair.",
|
|
1289
|
+
];
|
|
1290
|
+
const { persistedIds } = await orchestrator.persistExtraction(
|
|
1291
|
+
{
|
|
1292
|
+
facts: contents.map((content) => ({
|
|
1293
|
+
category: "fact",
|
|
1294
|
+
content,
|
|
1295
|
+
confidence: 0.99,
|
|
1296
|
+
tags: ["atlas"],
|
|
1297
|
+
entityRef: "atlas-project",
|
|
1298
|
+
cueAnchors: [],
|
|
1299
|
+
})),
|
|
1300
|
+
entities: [{ name: "atlas-project", type: "company", facts: [] }],
|
|
1301
|
+
profileUpdates: [],
|
|
1302
|
+
questions: [],
|
|
1303
|
+
relationships: [],
|
|
1304
|
+
episodeTitle: "Atlas insertion ordering",
|
|
1305
|
+
},
|
|
1306
|
+
storage,
|
|
1307
|
+
null,
|
|
1308
|
+
{ sessionKey: "session:harmonic-recency" }
|
|
1309
|
+
);
|
|
1310
|
+
const expectedContents = new Set(contents);
|
|
1311
|
+
const contentById = new Map<string, string>();
|
|
1312
|
+
const factIds: string[] = [];
|
|
1313
|
+
for (const persistedId of persistedIds) {
|
|
1314
|
+
const memory = await storage.getMemoryById(persistedId);
|
|
1315
|
+
if (!memory) continue;
|
|
1316
|
+
const content = memory.frontmatter.structuredAttributes
|
|
1317
|
+
? stripAttributesSuffix(memory.content)
|
|
1318
|
+
: memory.content;
|
|
1319
|
+
if (!expectedContents.has(content)) continue;
|
|
1320
|
+
factIds.push(persistedId);
|
|
1321
|
+
contentById.set(persistedId, content);
|
|
1322
|
+
}
|
|
1323
|
+
assert.equal(factIds.length, 5, `expected the five fact memories, got ${persistedIds.length} persisted`);
|
|
1324
|
+
return { storageDir: storage.dir, persistedIds: factIds, contentById };
|
|
1325
|
+
}
|
|
1326
|
+
|
|
1327
|
+
async function rewriteTopicInsertedAt(
|
|
1328
|
+
storageDir: string,
|
|
1329
|
+
insertedAtById: Record<string, string> | null,
|
|
1330
|
+
recordedAt?: string
|
|
1331
|
+
): Promise<void> {
|
|
1332
|
+
const nodesDir = path.join(storageDir, "state", "abstraction-nodes", "nodes");
|
|
1333
|
+
for (const dayEntry of await readdir(nodesDir, { withFileTypes: true })) {
|
|
1334
|
+
if (!dayEntry.isDirectory()) continue;
|
|
1335
|
+
for (const entryName of await readdir(path.join(nodesDir, dayEntry.name))) {
|
|
1336
|
+
if (!entryName.endsWith(".json")) continue;
|
|
1337
|
+
const filePath = path.join(nodesDir, dayEntry.name, entryName);
|
|
1338
|
+
const node = JSON.parse(await readFile(filePath, "utf8")) as Record<string, unknown>;
|
|
1339
|
+
if (node.kind !== "topic") continue;
|
|
1340
|
+
const metadata = { ...(node.metadata as Record<string, string>) };
|
|
1341
|
+
if (insertedAtById === null) {
|
|
1342
|
+
delete metadata[HARMONIC_SOURCE_MEMORY_INSERTED_AT_KEY];
|
|
1343
|
+
} else {
|
|
1344
|
+
metadata[HARMONIC_SOURCE_MEMORY_INSERTED_AT_KEY] = JSON.stringify(insertedAtById);
|
|
1345
|
+
}
|
|
1346
|
+
await writeFile(
|
|
1347
|
+
filePath,
|
|
1348
|
+
`${JSON.stringify({ ...node, metadata, ...(recordedAt ? { recordedAt } : {}) }, null, 2)}\n`
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
async function findProjectedTopic(
|
|
1355
|
+
storageDir: string
|
|
1356
|
+
): Promise<HarmonicRetrievalResult | undefined> {
|
|
1357
|
+
const results = await searchHarmonicRetrieval({
|
|
1358
|
+
memoryDir: storageDir,
|
|
1359
|
+
query: "atlas",
|
|
1360
|
+
maxResults: 5,
|
|
1361
|
+
anchorsEnabled: false,
|
|
1362
|
+
});
|
|
1363
|
+
return results.find((result) => result.node.kind === "topic");
|
|
1364
|
+
}
|
|
1365
|
+
|
|
1366
|
+
test("harmonic projection orders sources newest-first with deterministic tiebreak", async () => {
|
|
1367
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-harmonic-recency-"));
|
|
1368
|
+
try {
|
|
1369
|
+
const { storageDir, persistedIds, contentById } = await persistRecencyFixture(memoryDir);
|
|
1370
|
+
const ascendingIds = [...persistedIds].sort(compareDeterministicStrings);
|
|
1371
|
+
const [first, second, third, fourth, fifth] = ascendingIds.map((memoryId) => memoryId);
|
|
1372
|
+
assert.ok(first && second && third && fourth && fifth);
|
|
1373
|
+
const oldest = "2026-01-01T00:00:00.000Z";
|
|
1374
|
+
const middle = "2026-02-01T00:00:00.000Z";
|
|
1375
|
+
const newest = "2026-03-01T00:00:00.000Z";
|
|
1376
|
+
const insertedAtByRank = [oldest, oldest, middle, newest, newest];
|
|
1377
|
+
await rewriteTopicInsertedAt(
|
|
1378
|
+
storageDir,
|
|
1379
|
+
Object.fromEntries(ascendingIds.map((memoryId, index) => [memoryId, insertedAtByRank[index]]))
|
|
1380
|
+
);
|
|
1381
|
+
|
|
1382
|
+
const topic = await findProjectedTopic(storageDir);
|
|
1383
|
+
assert.ok(topic, "topic node must stay retrievable");
|
|
1384
|
+
assert.deepEqual(topic.node.sourceMemoryIds, [fourth, fifth, third, first, second]);
|
|
1385
|
+
assert.equal(topic.node.title, contentById.get(fourth));
|
|
1386
|
+
assert.equal(
|
|
1387
|
+
topic.node.summary,
|
|
1388
|
+
[fourth, fifth, third].map((memoryId) => contentById.get(memoryId)).join("; ")
|
|
1389
|
+
);
|
|
1390
|
+
} finally {
|
|
1391
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
1392
|
+
}
|
|
1393
|
+
});
|
|
1394
|
+
|
|
1395
|
+
test("harmonic projection keeps ascending id order for equal or missing insertion timestamps", async () => {
|
|
1396
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-harmonic-recency-equal-"));
|
|
1397
|
+
try {
|
|
1398
|
+
const { storageDir, persistedIds, contentById } = await persistRecencyFixture(memoryDir);
|
|
1399
|
+
const ascendingIds = [...persistedIds].sort(compareDeterministicStrings);
|
|
1400
|
+
const [first, second, third, fourth, fifth] = ascendingIds.map((memoryId) => memoryId);
|
|
1401
|
+
assert.ok(first && second && third && fourth && fifth);
|
|
1402
|
+
const expectedSummary = [first, second, third]
|
|
1403
|
+
.map((memoryId) => contentById.get(memoryId))
|
|
1404
|
+
.join("; ");
|
|
1405
|
+
|
|
1406
|
+
await rewriteTopicInsertedAt(
|
|
1407
|
+
storageDir,
|
|
1408
|
+
Object.fromEntries(ascendingIds.map((memoryId) => [memoryId, "2026-01-01T00:00:00.000Z"]))
|
|
1409
|
+
);
|
|
1410
|
+
let topic = await findProjectedTopic(storageDir);
|
|
1411
|
+
assert.ok(topic);
|
|
1412
|
+
assert.deepEqual(topic.node.sourceMemoryIds, ascendingIds);
|
|
1413
|
+
assert.equal(topic.node.title, contentById.get(first));
|
|
1414
|
+
assert.equal(topic.node.summary, expectedSummary);
|
|
1415
|
+
|
|
1416
|
+
await rewriteTopicInsertedAt(storageDir, null);
|
|
1417
|
+
topic = await findProjectedTopic(storageDir);
|
|
1418
|
+
assert.ok(topic);
|
|
1419
|
+
assert.deepEqual(topic.node.sourceMemoryIds, ascendingIds);
|
|
1420
|
+
assert.equal(topic.node.title, contentById.get(first));
|
|
1421
|
+
assert.equal(topic.node.summary, expectedSummary);
|
|
1422
|
+
|
|
1423
|
+
await rewriteTopicInsertedAt(
|
|
1424
|
+
storageDir,
|
|
1425
|
+
{
|
|
1426
|
+
[first]: "not-a-timestamp",
|
|
1427
|
+
[second]: "2026-01-01T00:00:00.000Z",
|
|
1428
|
+
[third]: "2026-02-01T00:00:00.000Z",
|
|
1429
|
+
[fourth]: "definitely not a date",
|
|
1430
|
+
[fifth]: "2026-03-01T00:00:00.000Z",
|
|
1431
|
+
},
|
|
1432
|
+
"2026-06-01T00:00:00.000Z"
|
|
1433
|
+
);
|
|
1434
|
+
topic = await findProjectedTopic(storageDir);
|
|
1435
|
+
assert.ok(topic);
|
|
1436
|
+
assert.deepEqual(topic.node.sourceMemoryIds, [first, fourth, fifth, third, second]);
|
|
1437
|
+
assert.equal(topic.node.title, contentById.get(first));
|
|
1438
|
+
assert.equal(
|
|
1439
|
+
topic.node.summary,
|
|
1440
|
+
[first, fourth, fifth].map((memoryId) => contentById.get(memoryId)).join("; ")
|
|
1441
|
+
);
|
|
1442
|
+
} finally {
|
|
1443
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
1444
|
+
}
|
|
1445
|
+
});
|
|
1446
|
+
|
|
1262
1447
|
test("non-default namespace construction ignores the default store override", async () => {
|
|
1263
1448
|
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-harmonic-namespace-"));
|
|
1264
1449
|
try {
|
|
@@ -13,6 +13,7 @@ import { compareDeterministicStrings } from "./deterministic-order.js";
|
|
|
13
13
|
import { countRecallTokenOverlap, normalizeRecallTokens } from "./recall-tokenization.js";
|
|
14
14
|
import { stripAttributesSuffix } from "./structured-attributes.js";
|
|
15
15
|
import { isValidityExpiredNow } from "./temporal-validity.js";
|
|
16
|
+
import { isRecord } from "./store-contract.js";
|
|
16
17
|
import type { MemoryFile } from "./types.js";
|
|
17
18
|
|
|
18
19
|
type SourceMemoryMap = Map<string, MemoryFile>;
|
|
@@ -42,6 +43,22 @@ async function readSourceMemories(options: {
|
|
|
42
43
|
return sourceMemories;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
function parseSourceMemoryInsertedAt(node: AbstractionNode): Map<string, string> {
|
|
47
|
+
const insertedAtById = new Map<string, string>();
|
|
48
|
+
const insertedAtRaw = node.metadata?.[HARMONIC_SOURCE_MEMORY_INSERTED_AT_KEY];
|
|
49
|
+
if (!insertedAtRaw) return insertedAtById;
|
|
50
|
+
try {
|
|
51
|
+
const parsed = JSON.parse(insertedAtRaw) as unknown;
|
|
52
|
+
if (!isRecord(parsed)) return insertedAtById;
|
|
53
|
+
for (const [memoryId, value] of Object.entries(parsed)) {
|
|
54
|
+
if (typeof value === "string") insertedAtById.set(memoryId, value);
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// Malformed insertion metadata leaves ordering on the recordedAt fallback.
|
|
58
|
+
}
|
|
59
|
+
return insertedAtById;
|
|
60
|
+
}
|
|
61
|
+
|
|
45
62
|
function projectSourceBackedNode(
|
|
46
63
|
node: AbstractionNode,
|
|
47
64
|
sourceMemories: SourceMemoryMap,
|
|
@@ -62,6 +79,19 @@ function projectSourceBackedNode(
|
|
|
62
79
|
});
|
|
63
80
|
if (activeSourceMemoryIds.length === 0) return null;
|
|
64
81
|
|
|
82
|
+
// Newest-first source order keeps the projected title and summary on the most
|
|
83
|
+
// recently inserted facts. Equal or missing timestamps fall back to ascending
|
|
84
|
+
// id order, which matches the stored sourceMemoryIds order.
|
|
85
|
+
const insertedAtById = parseSourceMemoryInsertedAt(node);
|
|
86
|
+
const insertedAtMs = (memoryId: string): number => {
|
|
87
|
+
const parsedMs = Date.parse(insertedAtById.get(memoryId) ?? "");
|
|
88
|
+
return Number.isFinite(parsedMs) ? parsedMs : Date.parse(node.recordedAt);
|
|
89
|
+
};
|
|
90
|
+
activeSourceMemoryIds.sort(
|
|
91
|
+
(left, right) =>
|
|
92
|
+
insertedAtMs(right) - insertedAtMs(left) || compareDeterministicStrings(left, right)
|
|
93
|
+
);
|
|
94
|
+
|
|
65
95
|
const activeMemories = activeSourceMemoryIds.flatMap((memoryId) => {
|
|
66
96
|
const memory = sourceMemories.get(memoryId);
|
|
67
97
|
return memory ? [memory] : [];
|
|
@@ -69,40 +99,32 @@ function projectSourceBackedNode(
|
|
|
69
99
|
const activeContents = activeMemories.map((memory) =>
|
|
70
100
|
memory.frontmatter.structuredAttributes ? stripAttributesSuffix(memory.content) : memory.content
|
|
71
101
|
);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (activeInsertedAt.length > 0) {
|
|
82
|
-
metadata = {
|
|
83
|
-
[HARMONIC_SOURCE_MEMORY_INSERTED_AT_KEY]: JSON.stringify(Object.fromEntries(activeInsertedAt)),
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
} catch {
|
|
87
|
-
metadata = undefined;
|
|
88
|
-
}
|
|
102
|
+
let metadata: Record<string, string> | undefined;
|
|
103
|
+
const activeInsertedAt = activeSourceMemoryIds.flatMap((memoryId) => {
|
|
104
|
+
const value = insertedAtById.get(memoryId);
|
|
105
|
+
return typeof value === "string" ? [[memoryId, value] as const] : [];
|
|
106
|
+
});
|
|
107
|
+
if (activeInsertedAt.length > 0) {
|
|
108
|
+
metadata = {
|
|
109
|
+
[HARMONIC_SOURCE_MEMORY_INSERTED_AT_KEY]: JSON.stringify(Object.fromEntries(activeInsertedAt)),
|
|
110
|
+
};
|
|
89
111
|
}
|
|
90
112
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
113
|
+
return {
|
|
114
|
+
...node,
|
|
115
|
+
sourceMemoryIds: activeSourceMemoryIds,
|
|
116
|
+
title: activeContents[0]?.slice(0, 80) ?? node.title,
|
|
117
|
+
summary: activeContents.slice(0, 3).join("; ").slice(0, 400),
|
|
118
|
+
tags: [...new Set(activeMemories.flatMap((memory) => memory.frontmatter.tags))].sort(compareDeterministicStrings),
|
|
119
|
+
entityRefs: [
|
|
120
|
+
...new Set(
|
|
121
|
+
activeMemories.flatMap((memory) =>
|
|
122
|
+
typeof memory.frontmatter.entityRef === "string" ? [memory.frontmatter.entityRef] : []
|
|
123
|
+
)
|
|
124
|
+
),
|
|
125
|
+
].sort(compareDeterministicStrings),
|
|
126
|
+
metadata,
|
|
127
|
+
};
|
|
106
128
|
}
|
|
107
129
|
|
|
108
130
|
function anchorMatchesProjectedNode(
|
package/src/index.ts
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
*
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
+
export { collapseWhitespace, displayWidth, graphemeUnits, padEndDisplay, truncateCodePointSafe, truncateGraphemeSafe } from "./whitespace.js";
|
|
15
|
+
|
|
14
16
|
// ---------------------------------------------------------------------------
|
|
15
17
|
// Plugin entry resolution
|
|
16
18
|
// ---------------------------------------------------------------------------
|
|
@@ -1446,10 +1448,7 @@ export {
|
|
|
1446
1448
|
|
|
1447
1449
|
// Coding-graph engine contract (#1551 PR1), implemented by optional @remnic/coding-graph.
|
|
1448
1450
|
|
|
1449
|
-
export {
|
|
1450
|
-
CODING_GRAPH_ENGINE_VERSION,
|
|
1451
|
-
TIER_1_LANGUAGES,
|
|
1452
|
-
} from "./coding/coding-graph-types.js";
|
|
1451
|
+
export { CODING_GRAPH_ENGINE_VERSION, TIER_1_LANGUAGES } from "./coding/coding-graph-types.js";
|
|
1453
1452
|
|
|
1454
1453
|
export type {
|
|
1455
1454
|
CodingGraphEngine,
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
|
|
20
20
|
import type { LastRecallBudgetSummary } from "../recall-state.js";
|
|
21
21
|
import { estimateTokenCount } from "../token-estimate.js";
|
|
22
|
+
import { graphemeUnits, truncateGraphemeSafe } from "../whitespace.js";
|
|
22
23
|
import type { PluginConfig, RecallSectionConfig } from "../types.js";
|
|
23
24
|
|
|
24
25
|
export interface RecallSectionAppendOptions {
|
|
@@ -145,7 +146,7 @@ export class RecallSectionCoordinator {
|
|
|
145
146
|
finalContent =
|
|
146
147
|
sectionId === "profile"
|
|
147
148
|
? this.truncateProfileToBoundary(finalContent, maxChars)
|
|
148
|
-
: `${finalContent
|
|
149
|
+
: `${truncateGraphemeSafe(finalContent, maxChars)}\n\n...(trimmed)\n`;
|
|
149
150
|
}
|
|
150
151
|
if (finalContent.length === 0) return false;
|
|
151
152
|
const existing = sectionBuckets.get(sectionId) ?? [];
|
|
@@ -174,11 +175,16 @@ export class RecallSectionCoordinator {
|
|
|
174
175
|
const contentLimit = maxChars - suffix.length;
|
|
175
176
|
if (contentLimit <= 0) {
|
|
176
177
|
const boundary = content.lastIndexOf("\n", maxChars);
|
|
177
|
-
|
|
178
|
+
const clipped = boundary > 0
|
|
179
|
+
? content.slice(0, boundary)
|
|
180
|
+
: truncateGraphemeSafe(content, maxChars);
|
|
181
|
+
return clipped.trimEnd();
|
|
178
182
|
}
|
|
179
183
|
const boundary = content.lastIndexOf("\n", contentLimit);
|
|
180
|
-
const
|
|
181
|
-
|
|
184
|
+
const clipped = boundary > 0
|
|
185
|
+
? content.slice(0, boundary)
|
|
186
|
+
: truncateGraphemeSafe(content, contentLimit);
|
|
187
|
+
return `${clipped.trimEnd()}${suffix}`;
|
|
182
188
|
}
|
|
183
189
|
truncateRecallSectionToBudget(
|
|
184
190
|
content: string,
|
|
@@ -191,20 +197,20 @@ export class RecallSectionCoordinator {
|
|
|
191
197
|
content.length <= maxChars
|
|
192
198
|
? content
|
|
193
199
|
: maxChars <= suffix.length
|
|
194
|
-
? content
|
|
195
|
-
: `${content
|
|
200
|
+
? truncateGraphemeSafe(content, maxChars)
|
|
201
|
+
: `${truncateGraphemeSafe(content, maxChars - suffix.length)}${suffix}`;
|
|
196
202
|
if (maxTokens === undefined || estimateTokenCount(charBounded) <= maxTokens) {
|
|
197
203
|
return charBounded;
|
|
198
204
|
}
|
|
199
205
|
const markerFits =
|
|
200
206
|
maxChars > suffix.length && estimateTokenCount(suffix) <= maxTokens;
|
|
201
|
-
const tokenSource = content.length > maxChars ? content
|
|
202
|
-
const
|
|
207
|
+
const tokenSource = content.length > maxChars ? truncateGraphemeSafe(content, maxChars) : content;
|
|
208
|
+
const units = graphemeUnits(tokenSource);
|
|
203
209
|
let low = 0;
|
|
204
|
-
let high =
|
|
210
|
+
let high = units.length;
|
|
205
211
|
while (low < high) {
|
|
206
212
|
const mid = Math.ceil((low + high) / 2);
|
|
207
|
-
const prefix =
|
|
213
|
+
const prefix = units.slice(0, mid).join("");
|
|
208
214
|
const candidate = markerFits ? `${prefix}${suffix}` : prefix;
|
|
209
215
|
if (candidate.length <= maxChars && estimateTokenCount(candidate) <= maxTokens) {
|
|
210
216
|
low = mid;
|
|
@@ -214,19 +220,19 @@ export class RecallSectionCoordinator {
|
|
|
214
220
|
}
|
|
215
221
|
if (markerFits && low === 0) {
|
|
216
222
|
low = 0;
|
|
217
|
-
high =
|
|
223
|
+
high = units.length;
|
|
218
224
|
while (low < high) {
|
|
219
225
|
const mid = Math.ceil((low + high) / 2);
|
|
220
|
-
const prefix =
|
|
226
|
+
const prefix = units.slice(0, mid).join("");
|
|
221
227
|
if (prefix.length <= maxChars && estimateTokenCount(prefix) <= maxTokens) {
|
|
222
228
|
low = mid;
|
|
223
229
|
} else {
|
|
224
230
|
high = mid - 1;
|
|
225
231
|
}
|
|
226
232
|
}
|
|
227
|
-
return
|
|
233
|
+
return units.slice(0, low).join("");
|
|
228
234
|
}
|
|
229
|
-
const prefix =
|
|
235
|
+
const prefix = units.slice(0, low).join("");
|
|
230
236
|
return markerFits ? `${prefix}${suffix}` : prefix;
|
|
231
237
|
}
|
|
232
238
|
|