@equationalapplications/core-llm-wiki 5.5.1 → 6.0.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/README.md +3 -31
- package/dist/{chunk-JQRA6BYQ.mjs → chunk-HREQ6F6Z.mjs} +168 -39
- package/dist/chunk-HREQ6F6Z.mjs.map +1 -0
- package/dist/index.d.mts +18 -3
- package/dist/index.d.ts +18 -3
- package/dist/index.js +168 -36
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/dist/{testing-D0RnZjyW.d.mts → testing-DgqVB29I.d.mts} +72 -6
- package/dist/{testing-D0RnZjyW.d.ts → testing-DgqVB29I.d.ts} +72 -6
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/testing.js +165 -36
- package/dist/testing.js.map +1 -1
- package/dist/testing.mjs +1 -1
- package/package.json +2 -2
- package/dist/chunk-JQRA6BYQ.mjs.map +0 -1
|
@@ -101,6 +101,20 @@ interface OntologyBackfillResult {
|
|
|
101
101
|
* Stamped with the recheck cooldown, so they reappear as `deferred` next pass. */
|
|
102
102
|
skipped: number;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* L3 heal-success record: a fact the model emitted a verdict for under a
|
|
106
|
+
* truncated view of its own body. `originalBodyChars` lets an operator flag
|
|
107
|
+
* the fact for re-inspection; `truncatedBodyChars` is the L3 cap actually
|
|
108
|
+
* applied. Emitted by `PromptService.buildHealPrompt`, surfaced via
|
|
109
|
+
* `HealResult.degraded`, mutually exclusive with `HealResult.skipped` on
|
|
110
|
+
* `id` (a fact can be either healed-under-degradation or dropped — never
|
|
111
|
+
* both).
|
|
112
|
+
*/
|
|
113
|
+
interface DegradedRecord {
|
|
114
|
+
id: string;
|
|
115
|
+
originalBodyChars: number;
|
|
116
|
+
truncatedBodyChars: number;
|
|
117
|
+
}
|
|
104
118
|
/** Result of a single heal run. */
|
|
105
119
|
interface HealResult {
|
|
106
120
|
/**
|
|
@@ -118,8 +132,32 @@ interface HealResult {
|
|
|
118
132
|
deleted: number;
|
|
119
133
|
/** New facts synthesized by heal. */
|
|
120
134
|
newFactsCreated: number;
|
|
121
|
-
/**
|
|
122
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Candidates that could not converge after the helper's full escalation
|
|
137
|
+
* path. `reason` distinguishes genuine non-convergence
|
|
138
|
+
* (`'non_convergent'` — terminal give-up at attemptLevel 3, parse error,
|
|
139
|
+
* or model-config error) from transient provider failures
|
|
140
|
+
* (`'call_error'` — non-truncation error originating in `call()` at
|
|
141
|
+
* `batch.length === 1`). `doRunHeal` excludes `'call_error'` ids from
|
|
142
|
+
* `markHealChecked`'s cooldown stamp so a momentary provider hiccup does
|
|
143
|
+
* not lock the fact out for `HEAL_RECHECK_MS`. Mutually exclusive with
|
|
144
|
+
* `degraded` on `id`.
|
|
145
|
+
*/
|
|
146
|
+
skipped: Array<{
|
|
147
|
+
id: string;
|
|
148
|
+
reason: 'non_convergent' | 'call_error';
|
|
149
|
+
}>;
|
|
150
|
+
/**
|
|
151
|
+
* L3 heal successes: facts the model emitted a verdict for under a
|
|
152
|
+
* truncated view of their own body. Each entry carries the truncation
|
|
153
|
+
* magnitude so an operator can flag the fact for re-inspection. The
|
|
154
|
+
* corresponding log line `[WikiMemory] heal healed under degraded
|
|
155
|
+
* context ...` fires for each entry. Mutually exclusive with `skipped`
|
|
156
|
+
* on `id` — the reconciliation step in doRunHeal drops any record
|
|
157
|
+
* whose id also appears in `skipped` (a contradiction: degraded means
|
|
158
|
+
* healed, skipped means dropped).
|
|
159
|
+
*/
|
|
160
|
+
degraded: Array<DegradedRecord>;
|
|
123
161
|
/**
|
|
124
162
|
* Heal candidates still eligible after this run — convergence signal: loop while > 0.
|
|
125
163
|
*
|
|
@@ -1530,9 +1568,34 @@ declare class PromptService {
|
|
|
1530
1568
|
systemPrompt: string;
|
|
1531
1569
|
userPrompt: string;
|
|
1532
1570
|
};
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1571
|
+
/**
|
|
1572
|
+
* Heal-prompt level interpretation for the `attemptLevel` ladder.
|
|
1573
|
+
*
|
|
1574
|
+
* Caller contract: `documentAnchors` may be a slice sized for `batch.length`
|
|
1575
|
+
* or a larger set (e.g. a cache hit from `_selectHealAnchors`). This function
|
|
1576
|
+
* applies the prompt-side anchor cap `min(HEAL_MAX_ANCHORS=50, batch.length
|
|
1577
|
+
* * HEAL_ANCHORS_PER_CANDIDATE=4)` so the rendered prompt is bounded
|
|
1578
|
+
* regardless of caller input. `HEAL_MAX_ANCHORS` and
|
|
1579
|
+
* `HEAL_ANCHORS_PER_CANDIDATE` live here too — keeping the formula
|
|
1580
|
+
* co-located with its application avoids a "MaintenanceService policy"
|
|
1581
|
+
* import cycle (`PromptService` is constructed before `MaintenanceService`
|
|
1582
|
+
* exists) and makes the cap testable without a `MaintenanceService`
|
|
1583
|
+
* instance. Task 3 exports the same two constants from `MaintenanceService`
|
|
1584
|
+
* for caller-side overfetch sizing; the values must match.
|
|
1585
|
+
*
|
|
1586
|
+
* Level semantics:
|
|
1587
|
+
* - L0: allTasks + recentEvents + full candidate bodies; anchors re-capped
|
|
1588
|
+
* - L1: drop allTasks; recentEvents present; candidate bodies full
|
|
1589
|
+
* - L2: drop allTasks and recentEvents; candidate bodies full
|
|
1590
|
+
* - L3: drop allTasks and recentEvents; truncate each candidate body to
|
|
1591
|
+
* `bodyTruncationChars` and emit a `degraded` record per truncated fact
|
|
1592
|
+
*/
|
|
1593
|
+
buildHealPrompt(healCandidates: unknown[], documentAnchors: unknown[], allTasks: unknown[], recentEvents: unknown[], runtimeOverride: string | undefined, attemptLevel: 0 | 1 | 2 | 3, bodyTruncationChars?: number): {
|
|
1594
|
+
prompts: {
|
|
1595
|
+
systemPrompt: string;
|
|
1596
|
+
userPrompt: string;
|
|
1597
|
+
};
|
|
1598
|
+
degraded: DegradedRecord[];
|
|
1536
1599
|
};
|
|
1537
1600
|
buildOntologyBackfillPrompt(facts: unknown[], runtimeOverride?: string, ontologyContext?: OntologyPromptContext | null): {
|
|
1538
1601
|
systemPrompt: string;
|
|
@@ -1748,6 +1811,7 @@ declare class EventRepository extends BaseRepository {
|
|
|
1748
1811
|
declare const ONTOLOGY_BACKFILL_BATCH_SIZE = 25;
|
|
1749
1812
|
declare const ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS = 40000;
|
|
1750
1813
|
declare const ONTOLOGY_BACKFILL_RECHECK_MS: number;
|
|
1814
|
+
|
|
1751
1815
|
/**
|
|
1752
1816
|
* Heal candidates fetched per pass. Bounds the pool runBatched draws from, not
|
|
1753
1817
|
* the size of an individual LLM call — runBatched still packs ~10 candidates per
|
|
@@ -1787,6 +1851,7 @@ declare class MaintenanceService {
|
|
|
1787
1851
|
runHeal(entityId: string, options?: {
|
|
1788
1852
|
promptOverride?: string;
|
|
1789
1853
|
batchSize?: number;
|
|
1854
|
+
bodyTruncationChars?: number;
|
|
1790
1855
|
}): Promise<HealResult>;
|
|
1791
1856
|
runOntologyBackfill(entityId: string, options?: {
|
|
1792
1857
|
promptOverride?: string;
|
|
@@ -1840,6 +1905,7 @@ declare class MaintenanceService {
|
|
|
1840
1905
|
doRunHeal(entityId: string, options?: {
|
|
1841
1906
|
promptOverride?: string;
|
|
1842
1907
|
batchSize?: number;
|
|
1908
|
+
bodyTruncationChars?: number;
|
|
1843
1909
|
}): Promise<HealResult>;
|
|
1844
1910
|
/** Core ontology backfill pass (locks handled by {@link runOntologyBackfill}). Package-internal orchestration hook. */
|
|
1845
1911
|
doRunOntologyBackfill(entityId: string, options?: {
|
|
@@ -2304,4 +2370,4 @@ declare class WikiMemory {
|
|
|
2304
2370
|
setGeneratedByTask(taskId: string, entityId: string, actor: string): Promise<void>;
|
|
2305
2371
|
}
|
|
2306
2372
|
|
|
2307
|
-
export {
|
|
2373
|
+
export { WikiSourceRefHashCollision as $, WikiBusyError as A, type WikiBusyOperation as B, type ChunkFailure as C, type DegradedRecord as D, type EntityStatus as E, type FormatContextOptions as F, type GraphNeighborhood as G, HEAL_BATCH_SIZE as H, type IngestDocumentResult as I, type WikiCheckpoint as J, type WikiConfig as K, type LLMProvider as L, type MemoryBundle as M, WikiDuplicateHashError as N, type OntologyManifest as O, type PromptOverrides as P, type WikiEdge as Q, type ReadOptions as R, type SQLiteAdapter as S, type WikiEvent as T, type WikiFact as U, type VectorRanker as V, type WikiOptions as W, WikiIngestEmptyError as X, type WikiMemoryTestAccess as Y, type WikiOutboxEvent as Z, WikiParseError as _, type MemoryDump as a, WikiStrictOntologyViolation as a0, type WikiTask as a1, WikiTransactionError as a2, EmbeddingService as a3, ImportExportService as a4, IngestionService as a5, JobManager as a6, MaintenanceService as a7, RetrievalService as a8, SearchService as a9, WriteService as aa, type FormattedMemoryDump as b, WikiMemory as c, type ExtractedFact as d, type ExtractedFactEdge as e, type ExtractedFactWithOntology as f, type ExtractedTask as g, type GraphTraversalOptions as h, HEAL_RECHECK_MS as i, HOOK_TIMEOUT_MARKER as j, type HealResult as k, ONTOLOGY_BACKFILL_BATCH_SIZE as l, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS as m, ONTOLOGY_BACKFILL_RECHECK_MS as n, type OntologyBackfillResult as o, type OntologyConfig as p, type OntologyEdgeType as q, type OntologyMode as r, type OntologyNodeType as s, type OntologyPromptContext as t, type OntologyUpdates as u, PromptService as v, PrunePartialFailureError as w, type VectorRankerFallback as x, type VectorRankerRankArgs as y, type VectorRankerSemanticResult as z };
|
|
@@ -101,6 +101,20 @@ interface OntologyBackfillResult {
|
|
|
101
101
|
* Stamped with the recheck cooldown, so they reappear as `deferred` next pass. */
|
|
102
102
|
skipped: number;
|
|
103
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* L3 heal-success record: a fact the model emitted a verdict for under a
|
|
106
|
+
* truncated view of its own body. `originalBodyChars` lets an operator flag
|
|
107
|
+
* the fact for re-inspection; `truncatedBodyChars` is the L3 cap actually
|
|
108
|
+
* applied. Emitted by `PromptService.buildHealPrompt`, surfaced via
|
|
109
|
+
* `HealResult.degraded`, mutually exclusive with `HealResult.skipped` on
|
|
110
|
+
* `id` (a fact can be either healed-under-degradation or dropped — never
|
|
111
|
+
* both).
|
|
112
|
+
*/
|
|
113
|
+
interface DegradedRecord {
|
|
114
|
+
id: string;
|
|
115
|
+
originalBodyChars: number;
|
|
116
|
+
truncatedBodyChars: number;
|
|
117
|
+
}
|
|
104
118
|
/** Result of a single heal run. */
|
|
105
119
|
interface HealResult {
|
|
106
120
|
/**
|
|
@@ -118,8 +132,32 @@ interface HealResult {
|
|
|
118
132
|
deleted: number;
|
|
119
133
|
/** New facts synthesized by heal. */
|
|
120
134
|
newFactsCreated: number;
|
|
121
|
-
/**
|
|
122
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Candidates that could not converge after the helper's full escalation
|
|
137
|
+
* path. `reason` distinguishes genuine non-convergence
|
|
138
|
+
* (`'non_convergent'` — terminal give-up at attemptLevel 3, parse error,
|
|
139
|
+
* or model-config error) from transient provider failures
|
|
140
|
+
* (`'call_error'` — non-truncation error originating in `call()` at
|
|
141
|
+
* `batch.length === 1`). `doRunHeal` excludes `'call_error'` ids from
|
|
142
|
+
* `markHealChecked`'s cooldown stamp so a momentary provider hiccup does
|
|
143
|
+
* not lock the fact out for `HEAL_RECHECK_MS`. Mutually exclusive with
|
|
144
|
+
* `degraded` on `id`.
|
|
145
|
+
*/
|
|
146
|
+
skipped: Array<{
|
|
147
|
+
id: string;
|
|
148
|
+
reason: 'non_convergent' | 'call_error';
|
|
149
|
+
}>;
|
|
150
|
+
/**
|
|
151
|
+
* L3 heal successes: facts the model emitted a verdict for under a
|
|
152
|
+
* truncated view of their own body. Each entry carries the truncation
|
|
153
|
+
* magnitude so an operator can flag the fact for re-inspection. The
|
|
154
|
+
* corresponding log line `[WikiMemory] heal healed under degraded
|
|
155
|
+
* context ...` fires for each entry. Mutually exclusive with `skipped`
|
|
156
|
+
* on `id` — the reconciliation step in doRunHeal drops any record
|
|
157
|
+
* whose id also appears in `skipped` (a contradiction: degraded means
|
|
158
|
+
* healed, skipped means dropped).
|
|
159
|
+
*/
|
|
160
|
+
degraded: Array<DegradedRecord>;
|
|
123
161
|
/**
|
|
124
162
|
* Heal candidates still eligible after this run — convergence signal: loop while > 0.
|
|
125
163
|
*
|
|
@@ -1530,9 +1568,34 @@ declare class PromptService {
|
|
|
1530
1568
|
systemPrompt: string;
|
|
1531
1569
|
userPrompt: string;
|
|
1532
1570
|
};
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1571
|
+
/**
|
|
1572
|
+
* Heal-prompt level interpretation for the `attemptLevel` ladder.
|
|
1573
|
+
*
|
|
1574
|
+
* Caller contract: `documentAnchors` may be a slice sized for `batch.length`
|
|
1575
|
+
* or a larger set (e.g. a cache hit from `_selectHealAnchors`). This function
|
|
1576
|
+
* applies the prompt-side anchor cap `min(HEAL_MAX_ANCHORS=50, batch.length
|
|
1577
|
+
* * HEAL_ANCHORS_PER_CANDIDATE=4)` so the rendered prompt is bounded
|
|
1578
|
+
* regardless of caller input. `HEAL_MAX_ANCHORS` and
|
|
1579
|
+
* `HEAL_ANCHORS_PER_CANDIDATE` live here too — keeping the formula
|
|
1580
|
+
* co-located with its application avoids a "MaintenanceService policy"
|
|
1581
|
+
* import cycle (`PromptService` is constructed before `MaintenanceService`
|
|
1582
|
+
* exists) and makes the cap testable without a `MaintenanceService`
|
|
1583
|
+
* instance. Task 3 exports the same two constants from `MaintenanceService`
|
|
1584
|
+
* for caller-side overfetch sizing; the values must match.
|
|
1585
|
+
*
|
|
1586
|
+
* Level semantics:
|
|
1587
|
+
* - L0: allTasks + recentEvents + full candidate bodies; anchors re-capped
|
|
1588
|
+
* - L1: drop allTasks; recentEvents present; candidate bodies full
|
|
1589
|
+
* - L2: drop allTasks and recentEvents; candidate bodies full
|
|
1590
|
+
* - L3: drop allTasks and recentEvents; truncate each candidate body to
|
|
1591
|
+
* `bodyTruncationChars` and emit a `degraded` record per truncated fact
|
|
1592
|
+
*/
|
|
1593
|
+
buildHealPrompt(healCandidates: unknown[], documentAnchors: unknown[], allTasks: unknown[], recentEvents: unknown[], runtimeOverride: string | undefined, attemptLevel: 0 | 1 | 2 | 3, bodyTruncationChars?: number): {
|
|
1594
|
+
prompts: {
|
|
1595
|
+
systemPrompt: string;
|
|
1596
|
+
userPrompt: string;
|
|
1597
|
+
};
|
|
1598
|
+
degraded: DegradedRecord[];
|
|
1536
1599
|
};
|
|
1537
1600
|
buildOntologyBackfillPrompt(facts: unknown[], runtimeOverride?: string, ontologyContext?: OntologyPromptContext | null): {
|
|
1538
1601
|
systemPrompt: string;
|
|
@@ -1748,6 +1811,7 @@ declare class EventRepository extends BaseRepository {
|
|
|
1748
1811
|
declare const ONTOLOGY_BACKFILL_BATCH_SIZE = 25;
|
|
1749
1812
|
declare const ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS = 40000;
|
|
1750
1813
|
declare const ONTOLOGY_BACKFILL_RECHECK_MS: number;
|
|
1814
|
+
|
|
1751
1815
|
/**
|
|
1752
1816
|
* Heal candidates fetched per pass. Bounds the pool runBatched draws from, not
|
|
1753
1817
|
* the size of an individual LLM call — runBatched still packs ~10 candidates per
|
|
@@ -1787,6 +1851,7 @@ declare class MaintenanceService {
|
|
|
1787
1851
|
runHeal(entityId: string, options?: {
|
|
1788
1852
|
promptOverride?: string;
|
|
1789
1853
|
batchSize?: number;
|
|
1854
|
+
bodyTruncationChars?: number;
|
|
1790
1855
|
}): Promise<HealResult>;
|
|
1791
1856
|
runOntologyBackfill(entityId: string, options?: {
|
|
1792
1857
|
promptOverride?: string;
|
|
@@ -1840,6 +1905,7 @@ declare class MaintenanceService {
|
|
|
1840
1905
|
doRunHeal(entityId: string, options?: {
|
|
1841
1906
|
promptOverride?: string;
|
|
1842
1907
|
batchSize?: number;
|
|
1908
|
+
bodyTruncationChars?: number;
|
|
1843
1909
|
}): Promise<HealResult>;
|
|
1844
1910
|
/** Core ontology backfill pass (locks handled by {@link runOntologyBackfill}). Package-internal orchestration hook. */
|
|
1845
1911
|
doRunOntologyBackfill(entityId: string, options?: {
|
|
@@ -2304,4 +2370,4 @@ declare class WikiMemory {
|
|
|
2304
2370
|
setGeneratedByTask(taskId: string, entityId: string, actor: string): Promise<void>;
|
|
2305
2371
|
}
|
|
2306
2372
|
|
|
2307
|
-
export {
|
|
2373
|
+
export { WikiSourceRefHashCollision as $, WikiBusyError as A, type WikiBusyOperation as B, type ChunkFailure as C, type DegradedRecord as D, type EntityStatus as E, type FormatContextOptions as F, type GraphNeighborhood as G, HEAL_BATCH_SIZE as H, type IngestDocumentResult as I, type WikiCheckpoint as J, type WikiConfig as K, type LLMProvider as L, type MemoryBundle as M, WikiDuplicateHashError as N, type OntologyManifest as O, type PromptOverrides as P, type WikiEdge as Q, type ReadOptions as R, type SQLiteAdapter as S, type WikiEvent as T, type WikiFact as U, type VectorRanker as V, type WikiOptions as W, WikiIngestEmptyError as X, type WikiMemoryTestAccess as Y, type WikiOutboxEvent as Z, WikiParseError as _, type MemoryDump as a, WikiStrictOntologyViolation as a0, type WikiTask as a1, WikiTransactionError as a2, EmbeddingService as a3, ImportExportService as a4, IngestionService as a5, JobManager as a6, MaintenanceService as a7, RetrievalService as a8, SearchService as a9, WriteService as aa, type FormattedMemoryDump as b, WikiMemory as c, type ExtractedFact as d, type ExtractedFactEdge as e, type ExtractedFactWithOntology as f, type ExtractedTask as g, type GraphTraversalOptions as h, HEAL_RECHECK_MS as i, HOOK_TIMEOUT_MARKER as j, type HealResult as k, ONTOLOGY_BACKFILL_BATCH_SIZE as l, ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS as m, ONTOLOGY_BACKFILL_RECHECK_MS as n, type OntologyBackfillResult as o, type OntologyConfig as p, type OntologyEdgeType as q, type OntologyMode as r, type OntologyNodeType as s, type OntologyPromptContext as t, type OntologyUpdates as u, PromptService as v, PrunePartialFailureError as w, type VectorRankerFallback as x, type VectorRankerRankArgs as y, type VectorRankerSemanticResult as z };
|
package/dist/testing.d.mts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { a3 as EmbeddingService, a4 as ImportExportService, a5 as IngestionService, a6 as JobManager, a6 as JobManagerType, a7 as MaintenanceService, a8 as RetrievalService, a9 as SearchService, a9 as SearchServiceType, Y as WikiMemoryTestAccess, aa as WriteService } from './testing-DgqVB29I.mjs';
|
|
2
2
|
import '@equationalapplications/core-okf';
|
|
3
3
|
import 'minisearch';
|
package/dist/testing.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { a3 as EmbeddingService, a4 as ImportExportService, a5 as IngestionService, a6 as JobManager, a6 as JobManagerType, a7 as MaintenanceService, a8 as RetrievalService, a9 as SearchService, a9 as SearchServiceType, Y as WikiMemoryTestAccess, aa as WriteService } from './testing-DgqVB29I.js';
|
|
2
2
|
import '@equationalapplications/core-okf';
|
|
3
3
|
import 'minisearch';
|
package/dist/testing.js
CHANGED
|
@@ -1270,6 +1270,12 @@ Return ONLY a valid JSON object matching this schema:
|
|
|
1270
1270
|
If no manifest type fits a fact, omit that fact from "classifications" entirely \u2014 do not guess.
|
|
1271
1271
|
When echoing an existing fact's title verbatim into "target_title", preserve every JSON escape sequence (\\", \\n, \\\\, \\/) exactly as it appeared in the input body \u2014 do not strip backslashes, do not add unescaped quotes. Do not return markdown, just raw JSON.`;
|
|
1272
1272
|
|
|
1273
|
+
// src/utils/healConstants.ts
|
|
1274
|
+
var HEAL_MAX_ANCHORS = 50;
|
|
1275
|
+
var HEAL_ANCHORS_PER_CANDIDATE = 4;
|
|
1276
|
+
var HEAL_MAX_FACT_BODY_CHARS_L3 = 4e3;
|
|
1277
|
+
var HEAL_MAX_TASKS = 50;
|
|
1278
|
+
|
|
1273
1279
|
// src/services/PromptService.ts
|
|
1274
1280
|
var PromptService = class {
|
|
1275
1281
|
constructor(globalOverrides) {
|
|
@@ -1337,25 +1343,67 @@ Current Facts:
|
|
|
1337
1343
|
${JSON.stringify(currentFacts, null, 2)}`
|
|
1338
1344
|
};
|
|
1339
1345
|
}
|
|
1340
|
-
|
|
1346
|
+
/**
|
|
1347
|
+
* Heal-prompt level interpretation for the `attemptLevel` ladder.
|
|
1348
|
+
*
|
|
1349
|
+
* Caller contract: `documentAnchors` may be a slice sized for `batch.length`
|
|
1350
|
+
* or a larger set (e.g. a cache hit from `_selectHealAnchors`). This function
|
|
1351
|
+
* applies the prompt-side anchor cap `min(HEAL_MAX_ANCHORS=50, batch.length
|
|
1352
|
+
* * HEAL_ANCHORS_PER_CANDIDATE=4)` so the rendered prompt is bounded
|
|
1353
|
+
* regardless of caller input. `HEAL_MAX_ANCHORS` and
|
|
1354
|
+
* `HEAL_ANCHORS_PER_CANDIDATE` live here too — keeping the formula
|
|
1355
|
+
* co-located with its application avoids a "MaintenanceService policy"
|
|
1356
|
+
* import cycle (`PromptService` is constructed before `MaintenanceService`
|
|
1357
|
+
* exists) and makes the cap testable without a `MaintenanceService`
|
|
1358
|
+
* instance. Task 3 exports the same two constants from `MaintenanceService`
|
|
1359
|
+
* for caller-side overfetch sizing; the values must match.
|
|
1360
|
+
*
|
|
1361
|
+
* Level semantics:
|
|
1362
|
+
* - L0: allTasks + recentEvents + full candidate bodies; anchors re-capped
|
|
1363
|
+
* - L1: drop allTasks; recentEvents present; candidate bodies full
|
|
1364
|
+
* - L2: drop allTasks and recentEvents; candidate bodies full
|
|
1365
|
+
* - L3: drop allTasks and recentEvents; truncate each candidate body to
|
|
1366
|
+
* `bodyTruncationChars` and emit a `degraded` record per truncated fact
|
|
1367
|
+
*/
|
|
1368
|
+
buildHealPrompt(healCandidates, documentAnchors, allTasks, recentEvents, runtimeOverride, attemptLevel, bodyTruncationChars = HEAL_MAX_FACT_BODY_CHARS_L3) {
|
|
1369
|
+
const effectiveTasks = attemptLevel >= 1 ? [] : allTasks;
|
|
1370
|
+
const effectiveEvents = attemptLevel >= 2 ? [] : recentEvents;
|
|
1371
|
+
const maxAnchors = Math.max(1, Math.min(HEAL_MAX_ANCHORS, healCandidates.length * HEAL_ANCHORS_PER_CANDIDATE));
|
|
1372
|
+
const effectiveAnchors = documentAnchors.slice(0, maxAnchors);
|
|
1373
|
+
const { shapedCandidates, degraded } = applyBodyTruncation(
|
|
1374
|
+
healCandidates,
|
|
1375
|
+
attemptLevel,
|
|
1376
|
+
bodyTruncationChars
|
|
1377
|
+
);
|
|
1341
1378
|
const template = runtimeOverride ?? this.globalOverrides?.healSystemPrompt ?? HEAL_SYSTEM_PROMPT;
|
|
1342
1379
|
if (/\{\{\s*healCandidates\s*\}\}/.test(template) || /\{\{\s*documentAnchors\s*\}\}/.test(template) || /\{\{\s*allTasks\s*\}\}/.test(template) || /\{\{\s*recentEvents\s*\}\}/.test(template)) {
|
|
1343
1380
|
return {
|
|
1344
|
-
|
|
1345
|
-
|
|
1381
|
+
prompts: {
|
|
1382
|
+
systemPrompt: this.hydrate(template, {
|
|
1383
|
+
healCandidates: shapedCandidates,
|
|
1384
|
+
documentAnchors: effectiveAnchors,
|
|
1385
|
+
allTasks: effectiveTasks,
|
|
1386
|
+
recentEvents: effectiveEvents
|
|
1387
|
+
}),
|
|
1388
|
+
userPrompt: "Please heal the memory graph."
|
|
1389
|
+
},
|
|
1390
|
+
degraded
|
|
1346
1391
|
};
|
|
1347
1392
|
}
|
|
1348
1393
|
return {
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1394
|
+
prompts: {
|
|
1395
|
+
systemPrompt: template,
|
|
1396
|
+
userPrompt: `Heal Candidates:
|
|
1397
|
+
${JSON.stringify(shapedCandidates, null, 2)}
|
|
1352
1398
|
Document Anchors (DO NOT MODIFY OR DELETE):
|
|
1353
|
-
${JSON.stringify(
|
|
1399
|
+
${JSON.stringify(effectiveAnchors, null, 2)}
|
|
1354
1400
|
All Tasks:
|
|
1355
|
-
${JSON.stringify(
|
|
1401
|
+
${JSON.stringify(effectiveTasks, null, 2)}
|
|
1356
1402
|
Recent Events:
|
|
1357
|
-
${JSON.stringify(
|
|
1403
|
+
${JSON.stringify(effectiveEvents, null, 2)}
|
|
1358
1404
|
The following document anchors are provided for contradiction detection only. Do not include them in \`downgraded\`, \`deleted\`, or \`newFacts\`.`
|
|
1405
|
+
},
|
|
1406
|
+
degraded
|
|
1359
1407
|
};
|
|
1360
1408
|
}
|
|
1361
1409
|
buildOntologyBackfillPrompt(facts, runtimeOverride, ontologyContext) {
|
|
@@ -1375,6 +1423,36 @@ ${JSON.stringify(facts, null, 2)}`
|
|
|
1375
1423
|
};
|
|
1376
1424
|
}
|
|
1377
1425
|
};
|
|
1426
|
+
function applyBodyTruncation(candidates, attemptLevel, bodyTruncationChars) {
|
|
1427
|
+
if (attemptLevel < 3) {
|
|
1428
|
+
return { shapedCandidates: candidates, degraded: [] };
|
|
1429
|
+
}
|
|
1430
|
+
const shapedCandidates = [];
|
|
1431
|
+
const degraded = [];
|
|
1432
|
+
for (const c of candidates) {
|
|
1433
|
+
if (typeof c !== "object" || c === null) {
|
|
1434
|
+
shapedCandidates.push(c);
|
|
1435
|
+
continue;
|
|
1436
|
+
}
|
|
1437
|
+
const fact = c;
|
|
1438
|
+
const body = typeof fact.body === "string" ? fact.body : "";
|
|
1439
|
+
if (body.length <= bodyTruncationChars) {
|
|
1440
|
+
shapedCandidates.push(c);
|
|
1441
|
+
continue;
|
|
1442
|
+
}
|
|
1443
|
+
if (typeof fact.id !== "string") {
|
|
1444
|
+
shapedCandidates.push(c);
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
const originalBodyChars = body.length;
|
|
1448
|
+
const prefix = safeSlice(body, 0, bodyTruncationChars);
|
|
1449
|
+
const truncatedBodyChars = prefix.length;
|
|
1450
|
+
const truncated = `${prefix}\u2026[truncated at ${truncatedBodyChars} chars, original was ${originalBodyChars}]`;
|
|
1451
|
+
shapedCandidates.push({ ...fact, body: truncated });
|
|
1452
|
+
degraded.push({ id: fact.id, originalBodyChars, truncatedBodyChars });
|
|
1453
|
+
}
|
|
1454
|
+
return { shapedCandidates, degraded };
|
|
1455
|
+
}
|
|
1378
1456
|
|
|
1379
1457
|
// src/utils/chunkingDefaults.ts
|
|
1380
1458
|
var DEFAULT_MAX_CHUNK_LENGTH = 12e3;
|
|
@@ -1885,16 +1963,24 @@ var TRUNCATION_PATTERNS = [
|
|
|
1885
1963
|
/finish[_ ]?reason/i
|
|
1886
1964
|
];
|
|
1887
1965
|
var EXCEEDS_LIMIT_PATTERN = /exceed[a-z]*[^.]{0,40}\b(model|context)?[ _-]?limit/i;
|
|
1888
|
-
function
|
|
1889
|
-
let message;
|
|
1966
|
+
function getErrorMessage(err) {
|
|
1890
1967
|
try {
|
|
1891
|
-
|
|
1968
|
+
return err instanceof Error ? err.message : String(err ?? "");
|
|
1892
1969
|
} catch {
|
|
1893
|
-
return
|
|
1970
|
+
return void 0;
|
|
1894
1971
|
}
|
|
1972
|
+
}
|
|
1973
|
+
function isTruncationError(err) {
|
|
1974
|
+
const message = getErrorMessage(err);
|
|
1975
|
+
if (message === void 0) return false;
|
|
1895
1976
|
if (EXCEEDS_LIMIT_PATTERN.test(message)) return false;
|
|
1896
1977
|
return TRUNCATION_PATTERNS.some((pattern) => pattern.test(message));
|
|
1897
1978
|
}
|
|
1979
|
+
function isConfigError(err) {
|
|
1980
|
+
const message = getErrorMessage(err);
|
|
1981
|
+
if (message === void 0) return false;
|
|
1982
|
+
return EXCEEDS_LIMIT_PATTERN.test(message);
|
|
1983
|
+
}
|
|
1898
1984
|
function initialBatchSize(maxOutputTokens) {
|
|
1899
1985
|
if (!maxOutputTokens || !Number.isFinite(maxOutputTokens) || maxOutputTokens <= 0) {
|
|
1900
1986
|
return DEFAULT_BATCH_SIZE;
|
|
@@ -1936,14 +2022,18 @@ async function runBatched(args) {
|
|
|
1936
2022
|
const single = candidate.slice(0, 1);
|
|
1937
2023
|
return { batch: single, prompts: await buildPrompt(single) };
|
|
1938
2024
|
};
|
|
1939
|
-
const onFailure = async (batch, err) => {
|
|
1940
|
-
if (batch.length
|
|
1941
|
-
if (
|
|
1942
|
-
|
|
2025
|
+
const onFailure = async (batch, err, attemptLevel, fromCall) => {
|
|
2026
|
+
if (batch.length === 1) {
|
|
2027
|
+
if (fromCall && isTruncationError(err) && attemptLevel < 3) {
|
|
2028
|
+
await attempt(batch, void 0, attemptLevel + 1);
|
|
2029
|
+
} else {
|
|
2030
|
+
const reason = fromCall && !isTruncationError(err) && !isConfigError(err) ? "call_error" : "non_convergent";
|
|
2031
|
+
skipped.push({ item: batch[0], reason });
|
|
1943
2032
|
onSkip?.(batch[0], err);
|
|
1944
2033
|
}
|
|
1945
2034
|
return;
|
|
1946
2035
|
}
|
|
2036
|
+
if (fromCall && !isTruncationError(err)) throw err;
|
|
1947
2037
|
const mid = Math.ceil(batch.length / 2);
|
|
1948
2038
|
if (mid < batchSize) batchSize = mid;
|
|
1949
2039
|
let i = 0;
|
|
@@ -1954,23 +2044,22 @@ async function runBatched(args) {
|
|
|
1954
2044
|
i += trimmed.batch.length;
|
|
1955
2045
|
}
|
|
1956
2046
|
};
|
|
1957
|
-
const attempt = async (batch, prebuilt) => {
|
|
2047
|
+
const attempt = async (batch, prebuilt, attemptLevel = 0) => {
|
|
1958
2048
|
if (batch.length === 0) return;
|
|
1959
|
-
const prompts = prebuilt
|
|
2049
|
+
const prompts = prebuilt && attemptLevel === 0 ? prebuilt : await buildPrompt(batch, attemptLevel);
|
|
1960
2050
|
batches++;
|
|
1961
2051
|
let responseText;
|
|
1962
2052
|
try {
|
|
1963
2053
|
responseText = await call(prompts);
|
|
1964
2054
|
} catch (err) {
|
|
1965
|
-
|
|
1966
|
-
await onFailure(batch, err);
|
|
2055
|
+
await onFailure(batch, err, attemptLevel, true);
|
|
1967
2056
|
return;
|
|
1968
2057
|
}
|
|
1969
2058
|
let result;
|
|
1970
2059
|
try {
|
|
1971
2060
|
result = parse(responseText, batch);
|
|
1972
2061
|
} catch (err) {
|
|
1973
|
-
await onFailure(batch, err);
|
|
2062
|
+
await onFailure(batch, err, attemptLevel, false);
|
|
1974
2063
|
return;
|
|
1975
2064
|
}
|
|
1976
2065
|
results.push(result);
|
|
@@ -1979,7 +2068,7 @@ async function runBatched(args) {
|
|
|
1979
2068
|
while (index < items.length) {
|
|
1980
2069
|
const { batch, prompts } = await trim(items.slice(index, index + batchSize));
|
|
1981
2070
|
index += batch.length;
|
|
1982
|
-
await attempt(batch, prompts);
|
|
2071
|
+
await attempt(batch, prompts, 0);
|
|
1983
2072
|
}
|
|
1984
2073
|
return { results, skipped, batches };
|
|
1985
2074
|
}
|
|
@@ -1990,7 +2079,6 @@ var MIN_TOKENS_TO_QUALIFY = 3;
|
|
|
1990
2079
|
var ONTOLOGY_BACKFILL_BATCH_SIZE = 25;
|
|
1991
2080
|
var ONTOLOGY_BACKFILL_MAX_PROMPT_CHARS = 4e4;
|
|
1992
2081
|
var ONTOLOGY_BACKFILL_RECHECK_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
1993
|
-
var HEAL_MAX_ANCHORS = 50;
|
|
1994
2082
|
var HEAL_ANCHOR_SEARCH_OVERFETCH = 4;
|
|
1995
2083
|
var HEAL_MAX_PROMPT_CHARS = 4e4;
|
|
1996
2084
|
var HEAL_BATCH_SIZE = 25;
|
|
@@ -2421,6 +2509,10 @@ var MaintenanceService = class {
|
|
|
2421
2509
|
if (!Number.isInteger(batchSize) || batchSize < 1) {
|
|
2422
2510
|
throw new Error("Invalid batchSize: must be an integer >= 1");
|
|
2423
2511
|
}
|
|
2512
|
+
const bodyTruncationChars = options?.bodyTruncationChars ?? HEAL_MAX_FACT_BODY_CHARS_L3;
|
|
2513
|
+
if (!Number.isInteger(bodyTruncationChars) || bodyTruncationChars < 1) {
|
|
2514
|
+
throw new Error("Invalid bodyTruncationChars: must be an integer >= 1");
|
|
2515
|
+
}
|
|
2424
2516
|
const now = Date.now();
|
|
2425
2517
|
const recheckCutoff = now - HEAL_RECHECK_MS;
|
|
2426
2518
|
const orphanAfterDays = this.options.config?.orphanAfterDays !== void 0 ? this.options.config?.orphanAfterDays : 30;
|
|
@@ -2461,29 +2553,44 @@ var MaintenanceService = class {
|
|
|
2461
2553
|
downgraded: staleDowngradedIds.length,
|
|
2462
2554
|
deleted: orphanedIds.length,
|
|
2463
2555
|
newFactsCreated: 0,
|
|
2464
|
-
skipped:
|
|
2556
|
+
skipped: [],
|
|
2557
|
+
degraded: [],
|
|
2465
2558
|
remaining: counts2.eligible,
|
|
2466
2559
|
deferred: counts2.deferred
|
|
2467
2560
|
};
|
|
2468
2561
|
}
|
|
2469
|
-
const allTasks = await this.taskRepo.findAllPending([entityId]);
|
|
2562
|
+
const allTasks = await this.taskRepo.findAllPending([entityId], HEAL_MAX_TASKS);
|
|
2470
2563
|
const recentEvents = await this.eventRepo.getRecent(entityId, 20);
|
|
2471
2564
|
const toPromptShape = (f) => {
|
|
2472
2565
|
const { embedding: _embedding, embedding_blob: _blob, ...rest } = f;
|
|
2473
2566
|
return { ...rest, tags: typeof rest.tags === "string" ? JSON.parse(rest.tags) : rest.tags };
|
|
2474
2567
|
};
|
|
2475
2568
|
const anchorCache = /* @__PURE__ */ new Map();
|
|
2569
|
+
const degraded = [];
|
|
2476
2570
|
const outcome = await runBatched({
|
|
2477
2571
|
items: healCandidates,
|
|
2478
|
-
buildPrompt: async (batch) => {
|
|
2479
|
-
const documentAnchors = await this._selectHealAnchors(
|
|
2480
|
-
|
|
2572
|
+
buildPrompt: async (batch, attemptLevel = 0) => {
|
|
2573
|
+
const documentAnchors = await this._selectHealAnchors(
|
|
2574
|
+
entityId,
|
|
2575
|
+
batch,
|
|
2576
|
+
// Per-batch anchor cap: `batch.length * HEAL_ANCHORS_PER_CANDIDATE` (capped at
|
|
2577
|
+
// HEAL_MAX_ANCHORS) right-sizes the anchor lookup so a 1-fact batch does
|
|
2578
|
+
// not overfetch the same 200 keyword hits a 25-fact batch once did.
|
|
2579
|
+
// buildHealPrompt applies the matching cap on its side — values must match.
|
|
2580
|
+
Math.min(HEAL_MAX_ANCHORS, batch.length * HEAL_ANCHORS_PER_CANDIDATE),
|
|
2581
|
+
anchorCache
|
|
2582
|
+
);
|
|
2583
|
+
const { prompts, degraded: batchDegraded } = await this.promptService.buildHealPrompt(
|
|
2481
2584
|
batch.map(toPromptShape),
|
|
2482
2585
|
documentAnchors,
|
|
2483
2586
|
allTasks,
|
|
2484
2587
|
recentEvents,
|
|
2485
|
-
promptOverride
|
|
2588
|
+
promptOverride,
|
|
2589
|
+
attemptLevel,
|
|
2590
|
+
bodyTruncationChars
|
|
2486
2591
|
);
|
|
2592
|
+
degraded.push(...batchDegraded);
|
|
2593
|
+
return prompts;
|
|
2487
2594
|
},
|
|
2488
2595
|
call: (prompts) => this.options.llmProvider.generateText(prompts),
|
|
2489
2596
|
parse: (responseText, batch) => {
|
|
@@ -2557,8 +2664,14 @@ var MaintenanceService = class {
|
|
|
2557
2664
|
insertedFacts.push({ id, entity_id: entityId, title: fact.title, body: fact.body, tags: JSON.stringify(fact.tags) });
|
|
2558
2665
|
healFactsForDedupe.push({ id, title: fact.title });
|
|
2559
2666
|
}
|
|
2667
|
+
const callErrorIds = new Set(
|
|
2668
|
+
outcome.skipped.filter((s) => s.reason === "call_error").map((s) => s.item.id)
|
|
2669
|
+
);
|
|
2560
2670
|
await this.entryRepo.markHealChecked(
|
|
2561
|
-
[
|
|
2671
|
+
[
|
|
2672
|
+
...healCandidates.map((f) => f.id).filter((id) => !callErrorIds.has(id)),
|
|
2673
|
+
...insertedFacts.map((f) => f.id)
|
|
2674
|
+
],
|
|
2562
2675
|
entityId,
|
|
2563
2676
|
now,
|
|
2564
2677
|
tx
|
|
@@ -2576,6 +2689,13 @@ var MaintenanceService = class {
|
|
|
2576
2689
|
await this.embeddingService.embedFact(fact);
|
|
2577
2690
|
}
|
|
2578
2691
|
this.searchService.evictCache(entityId);
|
|
2692
|
+
const skippedIds = new Set(outcome.skipped.map(({ item }) => item.id));
|
|
2693
|
+
const healedDegraded = degraded.filter((d) => !skippedIds.has(d.id));
|
|
2694
|
+
for (const d of healedDegraded) {
|
|
2695
|
+
console.warn(
|
|
2696
|
+
`[WikiMemory] heal healed under degraded context ${entityId}/${d.id}: body truncated from ${d.originalBodyChars} to ${d.truncatedBodyChars} chars`
|
|
2697
|
+
);
|
|
2698
|
+
}
|
|
2579
2699
|
let scanned = outcome.skipped.length;
|
|
2580
2700
|
for (const batchResult of outcome.results) scanned += batchResult.batch.length;
|
|
2581
2701
|
const allDowngraded = /* @__PURE__ */ new Set([...staleDowngradedIds, ...safeDowngraded]);
|
|
@@ -2586,7 +2706,8 @@ var MaintenanceService = class {
|
|
|
2586
2706
|
downgraded: allDowngraded.size,
|
|
2587
2707
|
deleted: allDeleted.size,
|
|
2588
2708
|
newFactsCreated: insertedFacts.length,
|
|
2589
|
-
skipped: outcome.skipped.
|
|
2709
|
+
skipped: outcome.skipped.map(({ item, reason }) => ({ id: item.id, reason })),
|
|
2710
|
+
degraded: healedDegraded,
|
|
2590
2711
|
remaining: counts.eligible,
|
|
2591
2712
|
deferred: counts.deferred
|
|
2592
2713
|
};
|
|
@@ -2670,7 +2791,15 @@ var MaintenanceService = class {
|
|
|
2670
2791
|
};
|
|
2671
2792
|
}
|
|
2672
2793
|
if (outcome.skipped.length > 0) {
|
|
2673
|
-
|
|
2794
|
+
const callErrorIds = new Set(
|
|
2795
|
+
outcome.skipped.filter((s) => s.reason === "call_error").map((s) => s.item.id)
|
|
2796
|
+
);
|
|
2797
|
+
await this.entryRepo.markOntologyChecked(
|
|
2798
|
+
outcome.skipped.filter(({ item }) => !callErrorIds.has(item.id)).map(({ item }) => item.id),
|
|
2799
|
+
entityId,
|
|
2800
|
+
now,
|
|
2801
|
+
this.db
|
|
2802
|
+
);
|
|
2674
2803
|
}
|
|
2675
2804
|
this.searchService.evictCache(entityId);
|
|
2676
2805
|
const counts = await this.entryRepo.countUntypedByEntityId(entityId, recheckCutoff);
|
|
@@ -2786,7 +2915,7 @@ var MaintenanceService = class {
|
|
|
2786
2915
|
* batches that reduce to the same query share one lookup. Caller-owned and
|
|
2787
2916
|
* per-pass — see the call site in doRunHeal.
|
|
2788
2917
|
*/
|
|
2789
|
-
async _selectHealAnchors(entityId, batch, cache) {
|
|
2918
|
+
async _selectHealAnchors(entityId, batch, cap = HEAL_MAX_ANCHORS, cache) {
|
|
2790
2919
|
const query = batch.map((f) => f.title).join(" ").trim();
|
|
2791
2920
|
if (!query) return [];
|
|
2792
2921
|
const cached = cache?.get(query);
|
|
@@ -2794,7 +2923,7 @@ var MaintenanceService = class {
|
|
|
2794
2923
|
const hits = this.searchService.searchKeyword(
|
|
2795
2924
|
query,
|
|
2796
2925
|
[entityId],
|
|
2797
|
-
|
|
2926
|
+
cap * HEAL_ANCHOR_SEARCH_OVERFETCH
|
|
2798
2927
|
);
|
|
2799
2928
|
const hitIds = hits.map((h) => h.id);
|
|
2800
2929
|
const anchors = [];
|
|
@@ -2805,7 +2934,7 @@ var MaintenanceService = class {
|
|
|
2805
2934
|
const row = byId.get(id);
|
|
2806
2935
|
if (!row) continue;
|
|
2807
2936
|
anchors.push(row);
|
|
2808
|
-
if (anchors.length >=
|
|
2937
|
+
if (anchors.length >= cap) break;
|
|
2809
2938
|
}
|
|
2810
2939
|
}
|
|
2811
2940
|
cache?.set(query, anchors);
|