agent-recall-core 3.4.30 → 3.4.35
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 +121 -441
- package/dist/digest/store.d.ts.map +1 -1
- package/dist/digest/store.js +4 -3
- package/dist/digest/store.js.map +1 -1
- package/dist/display/board-render.d.ts +61 -0
- package/dist/display/board-render.d.ts.map +1 -0
- package/dist/display/board-render.js +223 -0
- package/dist/display/board-render.js.map +1 -0
- package/dist/helpers/alignment-patterns.d.ts +10 -0
- package/dist/helpers/alignment-patterns.d.ts.map +1 -1
- package/dist/helpers/alignment-patterns.js +5 -1
- package/dist/helpers/alignment-patterns.js.map +1 -1
- package/dist/helpers/blind-spots.d.ts +86 -0
- package/dist/helpers/blind-spots.d.ts.map +1 -0
- package/dist/helpers/blind-spots.js +205 -0
- package/dist/helpers/blind-spots.js.map +1 -0
- package/dist/helpers/conflict-scan.d.ts +10 -0
- package/dist/helpers/conflict-scan.d.ts.map +1 -1
- package/dist/helpers/conflict-scan.js +3 -3
- package/dist/helpers/conflict-scan.js.map +1 -1
- package/dist/helpers/semantic-match.d.ts +82 -0
- package/dist/helpers/semantic-match.d.ts.map +1 -0
- package/dist/helpers/semantic-match.js +229 -0
- package/dist/helpers/semantic-match.js.map +1 -0
- package/dist/index.d.ts +52 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +48 -9
- package/dist/index.js.map +1 -1
- package/dist/palace/awareness.d.ts +30 -0
- package/dist/palace/awareness.d.ts.map +1 -1
- package/dist/palace/awareness.js +65 -2
- package/dist/palace/awareness.js.map +1 -1
- package/dist/palace/consolidate.d.ts.map +1 -1
- package/dist/palace/consolidate.js +17 -0
- package/dist/palace/consolidate.js.map +1 -1
- package/dist/palace/decay-pass.d.ts +46 -0
- package/dist/palace/decay-pass.d.ts.map +1 -0
- package/dist/palace/decay-pass.js +87 -0
- package/dist/palace/decay-pass.js.map +1 -0
- package/dist/palace/insights-index.d.ts.map +1 -1
- package/dist/palace/insights-index.js +4 -2
- package/dist/palace/insights-index.js.map +1 -1
- package/dist/palace/skills.d.ts +41 -2
- package/dist/palace/skills.d.ts.map +1 -1
- package/dist/palace/skills.js +106 -4
- package/dist/palace/skills.js.map +1 -1
- package/dist/prompts/consolidation-prompt.d.ts +25 -0
- package/dist/prompts/consolidation-prompt.d.ts.map +1 -0
- package/dist/prompts/consolidation-prompt.js +91 -0
- package/dist/prompts/consolidation-prompt.js.map +1 -0
- package/dist/storage/archive-prune.d.ts +56 -0
- package/dist/storage/archive-prune.d.ts.map +1 -0
- package/dist/storage/archive-prune.js +118 -0
- package/dist/storage/archive-prune.js.map +1 -0
- package/dist/storage/archive-write.d.ts +38 -0
- package/dist/storage/archive-write.d.ts.map +1 -0
- package/dist/storage/archive-write.js +98 -0
- package/dist/storage/archive-write.js.map +1 -0
- package/dist/storage/blind-spots-store.d.ts +30 -0
- package/dist/storage/blind-spots-store.d.ts.map +1 -0
- package/dist/storage/blind-spots-store.js +91 -0
- package/dist/storage/blind-spots-store.js.map +1 -0
- package/dist/storage/capture-router.d.ts +60 -0
- package/dist/storage/capture-router.d.ts.map +1 -0
- package/dist/storage/capture-router.js +133 -0
- package/dist/storage/capture-router.js.map +1 -0
- package/dist/storage/classification.d.ts +32 -0
- package/dist/storage/classification.d.ts.map +1 -0
- package/dist/storage/classification.js +74 -0
- package/dist/storage/classification.js.map +1 -0
- package/dist/storage/consolidation-queue.d.ts +39 -0
- package/dist/storage/consolidation-queue.d.ts.map +1 -0
- package/dist/storage/consolidation-queue.js +120 -0
- package/dist/storage/consolidation-queue.js.map +1 -0
- package/dist/storage/content-guard.d.ts +74 -0
- package/dist/storage/content-guard.d.ts.map +1 -0
- package/dist/storage/content-guard.js +179 -0
- package/dist/storage/content-guard.js.map +1 -0
- package/dist/storage/corrections.d.ts +246 -21
- package/dist/storage/corrections.d.ts.map +1 -1
- package/dist/storage/corrections.js +605 -56
- package/dist/storage/corrections.js.map +1 -1
- package/dist/storage/durable-intent.d.ts +49 -0
- package/dist/storage/durable-intent.d.ts.map +1 -0
- package/dist/storage/durable-intent.js +124 -0
- package/dist/storage/durable-intent.js.map +1 -0
- package/dist/storage/filelock.d.ts +6 -0
- package/dist/storage/filelock.d.ts.map +1 -1
- package/dist/storage/filelock.js +6 -1
- package/dist/storage/filelock.js.map +1 -1
- package/dist/storage/memory-protocol.d.ts +19 -0
- package/dist/storage/memory-protocol.d.ts.map +1 -0
- package/dist/storage/memory-protocol.js +101 -0
- package/dist/storage/memory-protocol.js.map +1 -0
- package/dist/storage/paths.d.ts +23 -0
- package/dist/storage/paths.d.ts.map +1 -1
- package/dist/storage/paths.js +39 -0
- package/dist/storage/paths.js.map +1 -1
- package/dist/storage/retention.d.ts +23 -0
- package/dist/storage/retention.d.ts.map +1 -0
- package/dist/storage/retention.js +48 -0
- package/dist/storage/retention.js.map +1 -0
- package/dist/storage/session.d.ts +2 -2
- package/dist/storage/session.d.ts.map +1 -1
- package/dist/storage/session.js +11 -1
- package/dist/storage/session.js.map +1 -1
- package/dist/supabase/config.d.ts +8 -0
- package/dist/supabase/config.d.ts.map +1 -1
- package/dist/supabase/config.js +5 -0
- package/dist/supabase/config.js.map +1 -1
- package/dist/supabase/recall-backend.d.ts +1 -0
- package/dist/supabase/recall-backend.d.ts.map +1 -1
- package/dist/supabase/recall-backend.js +13 -13
- package/dist/supabase/recall-backend.js.map +1 -1
- package/dist/supabase/sync.d.ts.map +1 -1
- package/dist/supabase/sync.js +30 -3
- package/dist/supabase/sync.js.map +1 -1
- package/dist/tools-logic/bootstrap.d.ts +15 -0
- package/dist/tools-logic/bootstrap.d.ts.map +1 -1
- package/dist/tools-logic/bootstrap.js +238 -47
- package/dist/tools-logic/bootstrap.js.map +1 -1
- package/dist/tools-logic/brief.d.ts +84 -0
- package/dist/tools-logic/brief.d.ts.map +1 -0
- package/dist/tools-logic/brief.js +193 -0
- package/dist/tools-logic/brief.js.map +1 -0
- package/dist/tools-logic/check-action.d.ts +9 -0
- package/dist/tools-logic/check-action.d.ts.map +1 -1
- package/dist/tools-logic/check-action.js +31 -2
- package/dist/tools-logic/check-action.js.map +1 -1
- package/dist/tools-logic/check.d.ts +7 -0
- package/dist/tools-logic/check.d.ts.map +1 -1
- package/dist/tools-logic/check.js +24 -2
- package/dist/tools-logic/check.js.map +1 -1
- package/dist/tools-logic/confidence.d.ts +50 -0
- package/dist/tools-logic/confidence.d.ts.map +1 -0
- package/dist/tools-logic/confidence.js +70 -0
- package/dist/tools-logic/confidence.js.map +1 -0
- package/dist/tools-logic/drill-down.d.ts +37 -0
- package/dist/tools-logic/drill-down.d.ts.map +1 -0
- package/dist/tools-logic/drill-down.js +63 -0
- package/dist/tools-logic/drill-down.js.map +1 -0
- package/dist/tools-logic/export-corrections.d.ts +63 -0
- package/dist/tools-logic/export-corrections.d.ts.map +1 -0
- package/dist/tools-logic/export-corrections.js +124 -0
- package/dist/tools-logic/export-corrections.js.map +1 -0
- package/dist/tools-logic/journal-capture.d.ts.map +1 -1
- package/dist/tools-logic/journal-capture.js +4 -1
- package/dist/tools-logic/journal-capture.js.map +1 -1
- package/dist/tools-logic/journal-write.d.ts.map +1 -1
- package/dist/tools-logic/journal-write.js +3 -2
- package/dist/tools-logic/journal-write.js.map +1 -1
- package/dist/tools-logic/memory-query.d.ts +10 -3
- package/dist/tools-logic/memory-query.d.ts.map +1 -1
- package/dist/tools-logic/memory-query.js +31 -7
- package/dist/tools-logic/memory-query.js.map +1 -1
- package/dist/tools-logic/mirror-builder.d.ts +115 -0
- package/dist/tools-logic/mirror-builder.d.ts.map +1 -0
- package/dist/tools-logic/mirror-builder.js +348 -0
- package/dist/tools-logic/mirror-builder.js.map +1 -0
- package/dist/tools-logic/palace-write.d.ts.map +1 -1
- package/dist/tools-logic/palace-write.js +3 -2
- package/dist/tools-logic/palace-write.js.map +1 -1
- package/dist/tools-logic/pipeline-close.d.ts.map +1 -1
- package/dist/tools-logic/pipeline-close.js +2 -1
- package/dist/tools-logic/pipeline-close.js.map +1 -1
- package/dist/tools-logic/pipeline-open.d.ts.map +1 -1
- package/dist/tools-logic/pipeline-open.js +2 -1
- package/dist/tools-logic/pipeline-open.js.map +1 -1
- package/dist/tools-logic/predict-correction.d.ts +47 -0
- package/dist/tools-logic/predict-correction.d.ts.map +1 -0
- package/dist/tools-logic/predict-correction.js +137 -0
- package/dist/tools-logic/predict-correction.js.map +1 -0
- package/dist/tools-logic/prior-builder.d.ts +27 -0
- package/dist/tools-logic/prior-builder.d.ts.map +1 -0
- package/dist/tools-logic/prior-builder.js +57 -0
- package/dist/tools-logic/prior-builder.js.map +1 -0
- package/dist/tools-logic/recognition-builder.d.ts +116 -0
- package/dist/tools-logic/recognition-builder.d.ts.map +1 -0
- package/dist/tools-logic/recognition-builder.js +289 -0
- package/dist/tools-logic/recognition-builder.js.map +1 -0
- package/dist/tools-logic/safety-consolidation.d.ts +104 -0
- package/dist/tools-logic/safety-consolidation.d.ts.map +1 -0
- package/dist/tools-logic/safety-consolidation.js +256 -0
- package/dist/tools-logic/safety-consolidation.js.map +1 -0
- package/dist/tools-logic/session-end-reflect.d.ts +18 -0
- package/dist/tools-logic/session-end-reflect.d.ts.map +1 -1
- package/dist/tools-logic/session-end-reflect.js +68 -29
- package/dist/tools-logic/session-end-reflect.js.map +1 -1
- package/dist/tools-logic/session-end.d.ts +9 -0
- package/dist/tools-logic/session-end.d.ts.map +1 -1
- package/dist/tools-logic/session-end.js +111 -13
- package/dist/tools-logic/session-end.js.map +1 -1
- package/dist/tools-logic/session-start-lite.d.ts +2 -0
- package/dist/tools-logic/session-start-lite.d.ts.map +1 -1
- package/dist/tools-logic/session-start-lite.js +10 -0
- package/dist/tools-logic/session-start-lite.js.map +1 -1
- package/dist/tools-logic/session-start.d.ts +46 -0
- package/dist/tools-logic/session-start.d.ts.map +1 -1
- package/dist/tools-logic/session-start.js +96 -3
- package/dist/tools-logic/session-start.js.map +1 -1
- package/dist/tools-logic/skill-propose.d.ts +27 -0
- package/dist/tools-logic/skill-propose.d.ts.map +1 -0
- package/dist/tools-logic/skill-propose.js +66 -0
- package/dist/tools-logic/skill-propose.js.map +1 -0
- package/dist/tools-logic/skill-recall.d.ts +5 -0
- package/dist/tools-logic/skill-recall.d.ts.map +1 -1
- package/dist/tools-logic/skill-recall.js +14 -1
- package/dist/tools-logic/skill-recall.js.map +1 -1
- package/dist/tools-logic/smart-recall.d.ts +17 -0
- package/dist/tools-logic/smart-recall.d.ts.map +1 -1
- package/dist/tools-logic/smart-recall.js +51 -16
- package/dist/tools-logic/smart-recall.js.map +1 -1
- package/dist/tools-logic/store-doctor.d.ts +71 -0
- package/dist/tools-logic/store-doctor.d.ts.map +1 -0
- package/dist/tools-logic/store-doctor.js +387 -0
- package/dist/tools-logic/store-doctor.js.map +1 -0
- package/dist/tools-logic/store-repair.d.ts +64 -0
- package/dist/tools-logic/store-repair.d.ts.map +1 -0
- package/dist/tools-logic/store-repair.js +243 -0
- package/dist/tools-logic/store-repair.js.map +1 -0
- package/dist/tools-logic/supersession.d.ts +34 -0
- package/dist/tools-logic/supersession.d.ts.map +1 -0
- package/dist/tools-logic/supersession.js +103 -0
- package/dist/tools-logic/supersession.js.map +1 -0
- package/dist/types.d.ts +7 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/vector/local-vector-backend.d.ts.map +1 -1
- package/dist/vector/local-vector-backend.js +14 -17
- package/dist/vector/local-vector-backend.js.map +1 -1
- package/package.json +9 -1
- package/dist/relevance/injector.d.ts +0 -51
- package/dist/relevance/injector.d.ts.map +0 -1
- package/dist/relevance/injector.js +0 -121
- package/dist/relevance/injector.js.map +0 -1
- package/dist/relevance/precision.d.ts +0 -109
- package/dist/relevance/precision.d.ts.map +0 -1
- package/dist/relevance/precision.js +0 -279
- package/dist/relevance/precision.js.map +0 -1
- package/dist/relevance/promoter.d.ts +0 -60
- package/dist/relevance/promoter.d.ts.map +0 -1
- package/dist/relevance/promoter.js +0 -336
- package/dist/relevance/promoter.js.map +0 -1
- package/dist/relevance/stager.d.ts +0 -61
- package/dist/relevance/stager.d.ts.map +0 -1
- package/dist/relevance/stager.js +0 -251
- package/dist/relevance/stager.js.map +0 -1
- package/dist/relevance/surfacer.d.ts +0 -34
- package/dist/relevance/surfacer.d.ts.map +0 -1
- package/dist/relevance/surfacer.js +0 -333
- package/dist/relevance/surfacer.js.map +0 -1
|
@@ -1,336 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Promoter — curates the staging buffer into the palace/journal at session
|
|
3
|
-
* boundaries (v3.5.0 P-A, spec §4 "Promote", §5).
|
|
4
|
-
*
|
|
5
|
-
* Flow:
|
|
6
|
-
* 1. Read up to N most-recent staged exchanges via `readStaging`.
|
|
7
|
-
* 2. Apply a pure-function durability heuristic — `isDurableSignal` — to
|
|
8
|
-
* drop chitchat, single-word turns, tool-only exchanges.
|
|
9
|
-
* 3. Dedupe survivors against the project's recent journal titles
|
|
10
|
-
* (`tooSimilarToExistingJournal`, threshold 0.7).
|
|
11
|
-
* 4. Write the survivors via the existing `journalWrite` tool, then clear
|
|
12
|
-
* the staging file up to the newest promoted timestamp.
|
|
13
|
-
*
|
|
14
|
-
* Design notes:
|
|
15
|
-
* • The heuristic is intentionally cheap and pure. It is the FIRST pass —
|
|
16
|
-
* the in-room model (called separately by `sessionEndReflect`) is the
|
|
17
|
-
* true fine-grain judge. Here we just trim the obvious noise.
|
|
18
|
-
* • `opts.dryRun` runs the whole pipeline without writing the journal or
|
|
19
|
-
* clearing staging — used by tests and the dashboard preview.
|
|
20
|
-
* • Threshold ordering inside `isDurableSignal` is high-confidence first
|
|
21
|
-
* (explicit markers) then weaker signals (length + lexical proxy)
|
|
22
|
-
* (worker done-def rule 3).
|
|
23
|
-
*/
|
|
24
|
-
import { listJournalFiles } from "../helpers/journal-files.js";
|
|
25
|
-
import { journalWrite } from "../tools-logic/journal-write.js";
|
|
26
|
-
import { readStaging, clearStaged } from "./stager.js";
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
// Tunables
|
|
29
|
-
// ---------------------------------------------------------------------------
|
|
30
|
-
/** Hard cap on staging entries we look at per promote() invocation. */
|
|
31
|
-
const PROMOTE_BATCH_SIZE = 200;
|
|
32
|
-
/** How many recent journals we compare titles against for dedup. */
|
|
33
|
-
const DEDUP_RECENT_JOURNALS = 10;
|
|
34
|
-
/** Token-overlap threshold for "duplicate of existing journal". */
|
|
35
|
-
const DEDUP_THRESHOLD = 0.7;
|
|
36
|
-
/** Title is the first sentence, capped to this many chars. */
|
|
37
|
-
const TITLE_MAX_CHARS = 80;
|
|
38
|
-
/** Body slice cap when writing into the journal. */
|
|
39
|
-
const BODY_MAX_CHARS = 500;
|
|
40
|
-
/** Minimum text length before the length-based durability branch triggers. */
|
|
41
|
-
const DURABLE_MIN_LENGTH = 120;
|
|
42
|
-
// Explicit "this matters" markers. Word-bounded, case-insensitive.
|
|
43
|
-
const DURABLE_MARKER_RE = /\b(decision|decided|conclusion|TIL|gotcha|lesson|rule|invariant|never|always|broke|bug|root cause)\b/i;
|
|
44
|
-
// Correction-shape markers — "no … wrong", "actually", "correction", "the right way".
|
|
45
|
-
const CORRECTION_SHAPE_RE = /\b(no\b.*wrong|actually|correction|the right way)\b/i;
|
|
46
|
-
// Wikilinks (`[[…]]`) or fenced code blocks — both are durable artifacts.
|
|
47
|
-
const DURABLE_ARTIFACT_RE = /\[\[[^\]]+\]\]|```/;
|
|
48
|
-
// Length-branch proxies — at least one "important word" (≥ 4 chars, alphabetic)
|
|
49
|
-
// AND at least one action verb. Crude but pure and cheap.
|
|
50
|
-
const ACTION_VERB_RE = /\b(add|added|adds|fix|fixed|fixes|use|used|uses|build|built|builds|ship|shipped|ships|run|ran|runs|write|wrote|writes|migrate|migrated|migrates|refactor|refactored|refactors|implement|implemented|implements|create|created|creates|remove|removed|removes|update|updated|updates|deploy|deployed|deploys|test|tested|tests|merge|merged|merges|commit|committed|commits|switch|switched|switches|choose|chose|chooses|landed|land)\b/i;
|
|
51
|
-
const IMPORTANT_WORD_RE = /\b[a-zA-Z]{4,}\b/;
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
// Pure helpers (visible for testability)
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
/**
|
|
56
|
-
* Decide whether a staged exchange carries a durable signal.
|
|
57
|
-
*
|
|
58
|
-
* Returns TRUE on any of:
|
|
59
|
-
* 1. Explicit marker (`decision`, `bug`, `gotcha`, …).
|
|
60
|
-
* 2. Correction shape (`no … wrong`, `actually`, `correction`, `the right way`).
|
|
61
|
-
* 3. Contains a wikilink or fenced code block (durable artifact).
|
|
62
|
-
* 4. Length ≥ 120 chars AND has both an "important word" and an action verb
|
|
63
|
-
* (noun-verb proxy — catches substantive sentences).
|
|
64
|
-
*
|
|
65
|
-
* Returns FALSE for chitchat ("ok", "thanks"), single-sentence questions,
|
|
66
|
-
* and tool-only turns (no user/assistant text).
|
|
67
|
-
*/
|
|
68
|
-
export function isDurableSignal(e) {
|
|
69
|
-
if (!e || typeof e !== "object")
|
|
70
|
-
return false;
|
|
71
|
-
const text = `${e.assistant ?? ""}\n${e.user ?? ""}`.trim();
|
|
72
|
-
if (!text)
|
|
73
|
-
return false; // tool-only turn
|
|
74
|
-
// High-confidence markers FIRST — threshold ordering rule.
|
|
75
|
-
if (DURABLE_MARKER_RE.test(text))
|
|
76
|
-
return true;
|
|
77
|
-
if (CORRECTION_SHAPE_RE.test(text))
|
|
78
|
-
return true;
|
|
79
|
-
if (DURABLE_ARTIFACT_RE.test(text))
|
|
80
|
-
return true;
|
|
81
|
-
// Length-gated noun-verb proxy. Skip questions ending with `?` and only `?`
|
|
82
|
-
// (single short question is rarely a durable insight on its own).
|
|
83
|
-
if (text.length >= DURABLE_MIN_LENGTH) {
|
|
84
|
-
if (IMPORTANT_WORD_RE.test(text) && ACTION_VERB_RE.test(text)) {
|
|
85
|
-
return true;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return false;
|
|
89
|
-
}
|
|
90
|
-
/**
|
|
91
|
-
* Strip code fences, frontmatter markers, and markdown headers, then take
|
|
92
|
-
* everything up to the first sentence terminator. ≤ TITLE_MAX_CHARS.
|
|
93
|
-
*/
|
|
94
|
-
function firstSentence(text) {
|
|
95
|
-
if (!text)
|
|
96
|
-
return "";
|
|
97
|
-
// Drop leading markdown noise.
|
|
98
|
-
const cleaned = text
|
|
99
|
-
.replace(/^#+\s*/gm, "")
|
|
100
|
-
.replace(/^>+\s*/gm, "")
|
|
101
|
-
.trim();
|
|
102
|
-
// Sentence boundary: . ! ? or newline.
|
|
103
|
-
const m = cleaned.match(/[^.!?\n]+/);
|
|
104
|
-
const head = (m ? m[0] : cleaned).trim();
|
|
105
|
-
return head.slice(0, TITLE_MAX_CHARS);
|
|
106
|
-
}
|
|
107
|
-
/**
|
|
108
|
-
* Lightweight token set — lowercase alphanumeric words ≥ 3 chars.
|
|
109
|
-
* Visible only inside this module; mirrors the Surfacer's tokenizer in spirit
|
|
110
|
-
* but uses ≥ 3 (titles are short — ≥ 4 would drop too much signal).
|
|
111
|
-
*/
|
|
112
|
-
function tokenize(s) {
|
|
113
|
-
const out = new Set();
|
|
114
|
-
if (!s)
|
|
115
|
-
return out;
|
|
116
|
-
const parts = s.toLowerCase().split(/[^a-z0-9]+/);
|
|
117
|
-
for (const p of parts) {
|
|
118
|
-
if (p.length >= 3)
|
|
119
|
-
out.add(p);
|
|
120
|
-
}
|
|
121
|
-
return out;
|
|
122
|
-
}
|
|
123
|
-
/**
|
|
124
|
-
* Containment-style overlap (intersection / smaller-set). Short titles that
|
|
125
|
-
* are subsets of longer ones score high, which is the dedup behaviour we want.
|
|
126
|
-
*/
|
|
127
|
-
function setOverlap(a, b) {
|
|
128
|
-
if (a.size === 0 || b.size === 0)
|
|
129
|
-
return 0;
|
|
130
|
-
let intersect = 0;
|
|
131
|
-
const [smaller, larger] = a.size <= b.size ? [a, b] : [b, a];
|
|
132
|
-
for (const t of smaller) {
|
|
133
|
-
if (larger.has(t))
|
|
134
|
-
intersect++;
|
|
135
|
-
}
|
|
136
|
-
return intersect / Math.min(a.size, b.size);
|
|
137
|
-
}
|
|
138
|
-
/**
|
|
139
|
-
* Return true if `title` overlaps ≥ DEDUP_THRESHOLD with any of the last
|
|
140
|
-
* `DEDUP_RECENT_JOURNALS` journal entries' filenames OR first-line titles.
|
|
141
|
-
*
|
|
142
|
-
* We compare against filenames AND the first non-blank line of each file
|
|
143
|
-
* because journal filenames are date-prefixed (the smart-name slug carries
|
|
144
|
-
* the topical bit, see `journalFileName`).
|
|
145
|
-
*/
|
|
146
|
-
function tooSimilarToExistingJournal(project, title, _content) {
|
|
147
|
-
const incoming = tokenize(title);
|
|
148
|
-
if (incoming.size === 0)
|
|
149
|
-
return false;
|
|
150
|
-
let journals;
|
|
151
|
-
try {
|
|
152
|
-
journals = listJournalFiles(project, /* includeArchive */ false);
|
|
153
|
-
}
|
|
154
|
-
catch {
|
|
155
|
-
return false;
|
|
156
|
-
}
|
|
157
|
-
const recent = journals.slice(0, DEDUP_RECENT_JOURNALS);
|
|
158
|
-
for (const j of recent) {
|
|
159
|
-
// Compare against the filename slug — fast and avoids extra disk reads.
|
|
160
|
-
// The slug part (everything after the date prefix) carries the topic.
|
|
161
|
-
const slugPart = j.file.replace(/^\d{4}-\d{2}-\d{2}/, "").replace(/\.md$/, "");
|
|
162
|
-
const existingTokens = tokenize(slugPart);
|
|
163
|
-
const overlap = setOverlap(incoming, existingTokens);
|
|
164
|
-
if (overlap >= DEDUP_THRESHOLD)
|
|
165
|
-
return true;
|
|
166
|
-
}
|
|
167
|
-
return false;
|
|
168
|
-
}
|
|
169
|
-
// ---------------------------------------------------------------------------
|
|
170
|
-
// Public API
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
/**
|
|
173
|
-
* Promote durable staged exchanges into the journal.
|
|
174
|
-
*
|
|
175
|
-
* Idempotent within a session: once promoted, the corresponding staging
|
|
176
|
-
* entries are cleared (only the newest-promoted timestamp is used as the
|
|
177
|
-
* `beforeTs` cursor, so any post-promote stage() calls survive).
|
|
178
|
-
*
|
|
179
|
-
* `opts.dryRun = true` runs the full pipeline (incl. dedup) but writes
|
|
180
|
-
* nothing and clears nothing. Useful for tests and dashboard previews.
|
|
181
|
-
*/
|
|
182
|
-
export async function promote(project, opts = {}) {
|
|
183
|
-
const staged = readStaging(project, PROMOTE_BATCH_SIZE);
|
|
184
|
-
if (staged.length === 0) {
|
|
185
|
-
return { promoted: 0, skipped: 0, reasons: {} };
|
|
186
|
-
}
|
|
187
|
-
const reasons = {
|
|
188
|
-
no_durable_signal: 0,
|
|
189
|
-
too_short: 0,
|
|
190
|
-
duplicate: 0,
|
|
191
|
-
written: 0,
|
|
192
|
-
write_error: 0,
|
|
193
|
-
};
|
|
194
|
-
// First filter: durability heuristic. Track the "why not" counter.
|
|
195
|
-
const durable = [];
|
|
196
|
-
for (const e of staged) {
|
|
197
|
-
const text = `${e.assistant ?? ""}${e.user ?? ""}`.trim();
|
|
198
|
-
if (!text) {
|
|
199
|
-
// Tool-only turn — treat as "too_short" so it shows up distinctly in
|
|
200
|
-
// the reasons map. Threshold ordering: shortest reason first.
|
|
201
|
-
reasons.too_short++;
|
|
202
|
-
continue;
|
|
203
|
-
}
|
|
204
|
-
if (!isDurableSignal(e)) {
|
|
205
|
-
// Sub-classify short vs no-signal so the dashboard can distinguish
|
|
206
|
-
// "session was chitchat" vs "session had substance but no markers".
|
|
207
|
-
// High-threshold-first: anything < 20 chars is "too_short", else the
|
|
208
|
-
// generic "no_durable_signal".
|
|
209
|
-
if (text.length < 20) {
|
|
210
|
-
reasons.too_short++;
|
|
211
|
-
}
|
|
212
|
-
else {
|
|
213
|
-
reasons.no_durable_signal++;
|
|
214
|
-
}
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
durable.push(e);
|
|
218
|
-
}
|
|
219
|
-
// Must-Fix #7 (reviewer round 1): consolidate ALL promoted exchanges into
|
|
220
|
-
// ONE appended journal section, rather than one journalWrite call per
|
|
221
|
-
// exchange. This fixes two problems the reviewer flagged:
|
|
222
|
-
//
|
|
223
|
-
// 1. Inflated session counts — N durable exchanges previously produced
|
|
224
|
-
// N separate journal entries (or N `### ts` appends, each triggering
|
|
225
|
-
// its own updateIndex/syncToSupabase). The dashboard's "session
|
|
226
|
-
// count" therefore over-reports productivity for any session that
|
|
227
|
-
// staged multiple durable turns.
|
|
228
|
-
//
|
|
229
|
-
// 2. TZ disagreement at day boundaries — Stager files staging entries
|
|
230
|
-
// under `toLocaleDateString('sv')` (LOCAL TZ, worker done-def rule
|
|
231
|
-
// #4), but `journalWrite` derives its date from `todayISO()` which
|
|
232
|
-
// is UTC. A staged entry written at 23:55 local would, after
|
|
233
|
-
// promotion 5 minutes later, land in a journal file dated for
|
|
234
|
-
// tomorrow (UTC). Consolidating into a single section AT
|
|
235
|
-
// journalWrite's date pin means the inconsistency is contained:
|
|
236
|
-
// the appended section is sourced from staging timestamps (each
|
|
237
|
-
// `### ts` header preserves the original local-TZ wallclock) but
|
|
238
|
-
// the file it lands in is journalWrite's TZ choice (UTC). One
|
|
239
|
-
// file, no day-boundary doubling. Documented in the section
|
|
240
|
-
// header itself ("Ambient-promoted exchanges").
|
|
241
|
-
//
|
|
242
|
-
// The consolidation is BATCHED — we still loop the durable[] list to
|
|
243
|
-
// dedupe per-entry against existing journals (same heuristic as before),
|
|
244
|
-
// accumulate the survivors into one Markdown block, then make a single
|
|
245
|
-
// journalWrite call. Dedup runs first so a fully-duplicate batch
|
|
246
|
-
// produces zero writes (the previous loop did the same).
|
|
247
|
-
let promoted = 0;
|
|
248
|
-
let newestPromotedTs;
|
|
249
|
-
const sections = [];
|
|
250
|
-
for (const exchange of durable) {
|
|
251
|
-
const body = (exchange.assistant ?? exchange.user ?? "").trim();
|
|
252
|
-
const title = firstSentence(body);
|
|
253
|
-
const content = body.slice(0, BODY_MAX_CHARS);
|
|
254
|
-
if (tooSimilarToExistingJournal(project, title, content)) {
|
|
255
|
-
reasons.duplicate++;
|
|
256
|
-
continue;
|
|
257
|
-
}
|
|
258
|
-
if (opts.dryRun) {
|
|
259
|
-
// Count would-be writes so dryRun reports look like the real run.
|
|
260
|
-
reasons.written++;
|
|
261
|
-
promoted++;
|
|
262
|
-
// Track ts for cursor parity with the real path; clearStaged is a
|
|
263
|
-
// no-op under dryRun.
|
|
264
|
-
if (!newestPromotedTs || exchange.ts > newestPromotedTs) {
|
|
265
|
-
newestPromotedTs = exchange.ts;
|
|
266
|
-
}
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
// Buffer this exchange — actual journalWrite happens once below.
|
|
270
|
-
sections.push(`### ${exchange.ts}\n\n${content}`);
|
|
271
|
-
promoted++;
|
|
272
|
-
reasons.written++;
|
|
273
|
-
if (!newestPromotedTs || exchange.ts > newestPromotedTs) {
|
|
274
|
-
newestPromotedTs = exchange.ts;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
// Single consolidated write — only if we have something to write AND
|
|
278
|
-
// we're not in dryRun (dryRun already counted above and never touches
|
|
279
|
-
// the journal). The wrapping section header tells future readers (and
|
|
280
|
-
// the dashboard) that these were promoted by the ambient loop, not
|
|
281
|
-
// typed by a human.
|
|
282
|
-
if (!opts.dryRun && sections.length > 0) {
|
|
283
|
-
try {
|
|
284
|
-
const consolidatedBody = [
|
|
285
|
-
"## Ambient-promoted exchanges",
|
|
286
|
-
"",
|
|
287
|
-
"_Promoted from staging at session boundary. Timestamps below are the",
|
|
288
|
-
"local-TZ capture time; the surrounding journal file is dated by_",
|
|
289
|
-
"_`journalWrite` (UTC) — the two intentionally diverge at day boundaries._",
|
|
290
|
-
"",
|
|
291
|
-
sections.join("\n\n"),
|
|
292
|
-
].join("\n");
|
|
293
|
-
await journalWrite({
|
|
294
|
-
project,
|
|
295
|
-
content: consolidatedBody,
|
|
296
|
-
// Leave palace_room undefined — journalWrite's classifier emits a
|
|
297
|
-
// routing_hint we ignore here; the next-worker session-end pass can
|
|
298
|
-
// act on it (or the human can).
|
|
299
|
-
});
|
|
300
|
-
}
|
|
301
|
-
catch {
|
|
302
|
-
// journalWrite shouldn't throw under normal conditions, but if it
|
|
303
|
-
// does we roll back the per-exchange counters so the result honestly
|
|
304
|
-
// reports zero writes. Error path traced (worker done-def rule 1) —
|
|
305
|
-
// every catch updates a reason counter.
|
|
306
|
-
reasons.write_error += sections.length;
|
|
307
|
-
reasons.written -= sections.length;
|
|
308
|
-
promoted -= sections.length;
|
|
309
|
-
// Wipe the cursor so we don't clear staging for writes that didn't
|
|
310
|
-
// actually land — they'll be retried next session.
|
|
311
|
-
newestPromotedTs = undefined;
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
// Cursor clear: everything strictly older than the newest-promoted ts is
|
|
315
|
-
// expunged. Anything that arrived during this promote() call (newer ts)
|
|
316
|
-
// survives for the next pass. dryRun never clears.
|
|
317
|
-
if (!opts.dryRun) {
|
|
318
|
-
if (newestPromotedTs) {
|
|
319
|
-
// Newest promoted is INCLUSIVE — we want it gone too. Use a tick
|
|
320
|
-
// greater than its ts by appending "~" (string > any iso-digit) so
|
|
321
|
-
// the comparison in clearStaged (`e.ts < beforeTs`) drops it.
|
|
322
|
-
clearStaged(project, newestPromotedTs + "~");
|
|
323
|
-
}
|
|
324
|
-
else if (durable.length === 0 && staged.length > 0) {
|
|
325
|
-
// Nothing was durable in the entire batch — clear everything up to
|
|
326
|
-
// the newest staged ts so we don't re-walk the same chitchat next
|
|
327
|
-
// session. Same `~`-suffix trick so the newest entry is included.
|
|
328
|
-
const newestStaged = staged[0]?.ts;
|
|
329
|
-
if (newestStaged)
|
|
330
|
-
clearStaged(project, newestStaged + "~");
|
|
331
|
-
}
|
|
332
|
-
}
|
|
333
|
-
const skipped = staged.length - promoted;
|
|
334
|
-
return { promoted, skipped, reasons };
|
|
335
|
-
}
|
|
336
|
-
//# sourceMappingURL=promoter.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"promoter.js","sourceRoot":"","sources":["../../src/relevance/promoter.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stager — append-only staging buffer for the Ambient Relevance Loop
|
|
3
|
-
* (v3.5.0 P-A, spec §4 "Stage", §5).
|
|
4
|
-
*
|
|
5
|
-
* Continuous capture: every meaningful exchange is appended (JSONL, one object
|
|
6
|
-
* per line) to a per-project, per-day staging file at
|
|
7
|
-
*
|
|
8
|
-
* ~/.agent-recall/projects/<slug>/_staging/<YYYY-MM-DD>.jsonl
|
|
9
|
-
*
|
|
10
|
-
* Design contract:
|
|
11
|
-
* • `stage` is fire-and-forget — it NEVER throws. The loop must not fail a
|
|
12
|
-
* user turn because we couldn't write a log line. Errors are swallowed
|
|
13
|
-
* silently (the Promoter will simply have less material to chew on).
|
|
14
|
-
* • Append-only on a per-process basis (`fs.appendFileSync` w/ utf-8 +
|
|
15
|
-
* trailing `\n`). Concurrent writers are tolerated because each line is
|
|
16
|
-
* self-contained JSON — interleaved appends from two processes still
|
|
17
|
-
* produce parseable lines.
|
|
18
|
-
* • `readStaging` is permissive: malformed/broken lines are skipped, not
|
|
19
|
-
* fatal. The buffer is best-effort log data, not a database.
|
|
20
|
-
* • `clearStaged` is a time-cursor: it rewrites each day-file in place,
|
|
21
|
-
* keeping only entries strictly newer than the supplied timestamp.
|
|
22
|
-
*/
|
|
23
|
-
export interface StagedExchange {
|
|
24
|
-
/** ISO-8601 timestamp. Promoter sorts/filters by this. */
|
|
25
|
-
ts: string;
|
|
26
|
-
user?: string;
|
|
27
|
-
assistant?: string;
|
|
28
|
-
tool_calls?: Array<{
|
|
29
|
-
name: string;
|
|
30
|
-
args_summary: string;
|
|
31
|
-
}>;
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Append one exchange to today's staging file.
|
|
35
|
-
*
|
|
36
|
-
* NEVER THROWS. The loop continues regardless of fs errors (disk full, bad
|
|
37
|
-
* permissions, etc.) — losing a log line is acceptable; killing a user turn
|
|
38
|
-
* is not. Internal errors are swallowed silently.
|
|
39
|
-
*/
|
|
40
|
-
export declare function stage(project: string, exchange: StagedExchange): void;
|
|
41
|
-
/**
|
|
42
|
-
* Read up to `limit` most recent staged exchanges, newest-first.
|
|
43
|
-
*
|
|
44
|
-
* Tolerant of malformed lines (skipped), missing files (returns []), and
|
|
45
|
-
* unreadable directories (returns []). Never throws.
|
|
46
|
-
*/
|
|
47
|
-
export declare function readStaging(project: string, limit?: number): StagedExchange[];
|
|
48
|
-
/**
|
|
49
|
-
* Clear staged entries whose `ts` is strictly less than `beforeTs`. Returns
|
|
50
|
-
* the count removed.
|
|
51
|
-
*
|
|
52
|
-
* If `beforeTs` is omitted, clears EVERYTHING (use with care — typically the
|
|
53
|
-
* Promoter passes the timestamp of the newest entry it just promoted, so any
|
|
54
|
-
* later-arriving exchange survives the sweep).
|
|
55
|
-
*
|
|
56
|
-
* Rewrites each day-file in place (read → filter → atomic-replace via tmp).
|
|
57
|
-
* Errors on any one file are swallowed; the count reflects what was actually
|
|
58
|
-
* removed.
|
|
59
|
-
*/
|
|
60
|
-
export declare function clearStaged(project: string, beforeTs?: string): number;
|
|
61
|
-
//# sourceMappingURL=stager.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"stager.d.ts","sourceRoot":"","sources":["../../src/relevance/stager.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAaH,MAAM,WAAW,cAAc;IAC7B,0DAA0D;IAC1D,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC5D;AAkGD;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI,CAoBrE;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,CAuB7E;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAiDtE"}
|
package/dist/relevance/stager.js
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Stager — append-only staging buffer for the Ambient Relevance Loop
|
|
3
|
-
* (v3.5.0 P-A, spec §4 "Stage", §5).
|
|
4
|
-
*
|
|
5
|
-
* Continuous capture: every meaningful exchange is appended (JSONL, one object
|
|
6
|
-
* per line) to a per-project, per-day staging file at
|
|
7
|
-
*
|
|
8
|
-
* ~/.agent-recall/projects/<slug>/_staging/<YYYY-MM-DD>.jsonl
|
|
9
|
-
*
|
|
10
|
-
* Design contract:
|
|
11
|
-
* • `stage` is fire-and-forget — it NEVER throws. The loop must not fail a
|
|
12
|
-
* user turn because we couldn't write a log line. Errors are swallowed
|
|
13
|
-
* silently (the Promoter will simply have less material to chew on).
|
|
14
|
-
* • Append-only on a per-process basis (`fs.appendFileSync` w/ utf-8 +
|
|
15
|
-
* trailing `\n`). Concurrent writers are tolerated because each line is
|
|
16
|
-
* self-contained JSON — interleaved appends from two processes still
|
|
17
|
-
* produce parseable lines.
|
|
18
|
-
* • `readStaging` is permissive: malformed/broken lines are skipped, not
|
|
19
|
-
* fatal. The buffer is best-effort log data, not a database.
|
|
20
|
-
* • `clearStaged` is a time-cursor: it rewrites each day-file in place,
|
|
21
|
-
* keeping only entries strictly newer than the supplied timestamp.
|
|
22
|
-
*/
|
|
23
|
-
import * as fs from "node:fs";
|
|
24
|
-
import * as path from "node:path";
|
|
25
|
-
import { sanitizeProject } from "../storage/paths.js";
|
|
26
|
-
import { ensureDir } from "../storage/fs-utils.js";
|
|
27
|
-
import { getRoot } from "../types.js";
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// Internal helpers
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
/**
|
|
32
|
-
* Resolve the per-project staging directory.
|
|
33
|
-
* Mirrors the slug grammar used by storage/paths.ts (sanitizeProject) so we
|
|
34
|
-
* land under the same `<root>/projects/<slug>/...` tree as journals/palace.
|
|
35
|
-
*
|
|
36
|
-
* Underscore prefix on `_staging` is intentional — it matches the convention
|
|
37
|
-
* (see project listing's BLOCKED_SLUGS rejecting `_`-prefixed dirs) so the
|
|
38
|
-
* buffer is hidden from project enumeration.
|
|
39
|
-
*/
|
|
40
|
-
function stagingDir(project) {
|
|
41
|
-
const safe = sanitizeProject(project);
|
|
42
|
-
return path.join(getRoot(), "projects", safe, "_staging");
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Today's local-date filename (YYYY-MM-DD).
|
|
46
|
-
*
|
|
47
|
-
* We use `toLocaleDateString('sv')` — Swedish locale yields ISO-8601 calendar
|
|
48
|
-
* format and respects the local timezone, so a staging entry written at
|
|
49
|
-
* 23:55 local doesn't jump into "tomorrow's" UTC file. This matches the
|
|
50
|
-
* journal-naming convention used elsewhere in core (worker done-def #4).
|
|
51
|
-
*/
|
|
52
|
-
function todayLocalDate() {
|
|
53
|
-
return new Date().toLocaleDateString("sv");
|
|
54
|
-
}
|
|
55
|
-
/**
|
|
56
|
-
* Sort comparator — newest-first by `ts`. String comparison is correct for
|
|
57
|
-
* ISO-8601 timestamps lexicographically.
|
|
58
|
-
*/
|
|
59
|
-
function byTsDesc(a, b) {
|
|
60
|
-
if (a.ts === b.ts)
|
|
61
|
-
return 0;
|
|
62
|
-
return a.ts < b.ts ? 1 : -1;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Parse one JSONL file, returning the entries that are valid `StagedExchange`
|
|
66
|
-
* shapes. Bad lines (truncated writes, manual edits) are silently skipped.
|
|
67
|
-
*/
|
|
68
|
-
function readJsonlFile(filePath) {
|
|
69
|
-
if (!fs.existsSync(filePath))
|
|
70
|
-
return [];
|
|
71
|
-
let raw;
|
|
72
|
-
try {
|
|
73
|
-
raw = fs.readFileSync(filePath, "utf-8");
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
return [];
|
|
77
|
-
}
|
|
78
|
-
const out = [];
|
|
79
|
-
const lines = raw.split("\n");
|
|
80
|
-
for (const line of lines) {
|
|
81
|
-
const trimmed = line.trim();
|
|
82
|
-
if (!trimmed)
|
|
83
|
-
continue;
|
|
84
|
-
try {
|
|
85
|
-
const obj = JSON.parse(trimmed);
|
|
86
|
-
if (obj &&
|
|
87
|
-
typeof obj === "object" &&
|
|
88
|
-
typeof obj.ts === "string") {
|
|
89
|
-
out.push(obj);
|
|
90
|
-
}
|
|
91
|
-
// Object missing `ts` is silently dropped — invariant of StagedExchange.
|
|
92
|
-
}
|
|
93
|
-
catch {
|
|
94
|
-
// Malformed line — skip, never throw.
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
return out;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* List all per-day staging files for a project. Returns absolute paths,
|
|
101
|
-
* sorted by filename (which is the date, so chronological ascending).
|
|
102
|
-
*/
|
|
103
|
-
function listStagingFiles(project) {
|
|
104
|
-
const dir = stagingDir(project);
|
|
105
|
-
if (!fs.existsSync(dir))
|
|
106
|
-
return [];
|
|
107
|
-
let names;
|
|
108
|
-
try {
|
|
109
|
-
names = fs.readdirSync(dir);
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
return [];
|
|
113
|
-
}
|
|
114
|
-
return names
|
|
115
|
-
.filter((n) => /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(n))
|
|
116
|
-
.sort()
|
|
117
|
-
.map((n) => path.join(dir, n));
|
|
118
|
-
}
|
|
119
|
-
// ---------------------------------------------------------------------------
|
|
120
|
-
// Public API
|
|
121
|
-
// ---------------------------------------------------------------------------
|
|
122
|
-
/**
|
|
123
|
-
* Append one exchange to today's staging file.
|
|
124
|
-
*
|
|
125
|
-
* NEVER THROWS. The loop continues regardless of fs errors (disk full, bad
|
|
126
|
-
* permissions, etc.) — losing a log line is acceptable; killing a user turn
|
|
127
|
-
* is not. Internal errors are swallowed silently.
|
|
128
|
-
*/
|
|
129
|
-
export function stage(project, exchange) {
|
|
130
|
-
try {
|
|
131
|
-
if (!exchange || typeof exchange !== "object")
|
|
132
|
-
return;
|
|
133
|
-
// Defensive copy + ts default so callers can pass partial shapes.
|
|
134
|
-
const safe = {
|
|
135
|
-
ts: typeof exchange.ts === "string" && exchange.ts ? exchange.ts : new Date().toISOString(),
|
|
136
|
-
user: exchange.user,
|
|
137
|
-
assistant: exchange.assistant,
|
|
138
|
-
tool_calls: exchange.tool_calls,
|
|
139
|
-
};
|
|
140
|
-
const dir = stagingDir(project);
|
|
141
|
-
ensureDir(dir);
|
|
142
|
-
const file = path.join(dir, `${todayLocalDate()}.jsonl`);
|
|
143
|
-
// appendFileSync is atomic at the syscall level on POSIX up to PIPE_BUF
|
|
144
|
-
// (4096 bytes on Linux/macOS) — well above a normal JSONL line. Concurrent
|
|
145
|
-
// appends from two processes interleave at line boundaries, not within.
|
|
146
|
-
fs.appendFileSync(file, JSON.stringify(safe) + "\n", "utf-8");
|
|
147
|
-
}
|
|
148
|
-
catch {
|
|
149
|
-
// Swallow — see contract above.
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
/**
|
|
153
|
-
* Read up to `limit` most recent staged exchanges, newest-first.
|
|
154
|
-
*
|
|
155
|
-
* Tolerant of malformed lines (skipped), missing files (returns []), and
|
|
156
|
-
* unreadable directories (returns []). Never throws.
|
|
157
|
-
*/
|
|
158
|
-
export function readStaging(project, limit) {
|
|
159
|
-
let all = [];
|
|
160
|
-
try {
|
|
161
|
-
const files = listStagingFiles(project);
|
|
162
|
-
// Walk newest day-file first so we can short-circuit once we have enough.
|
|
163
|
-
for (let i = files.length - 1; i >= 0; i--) {
|
|
164
|
-
const entries = readJsonlFile(files[i]);
|
|
165
|
-
all = all.concat(entries);
|
|
166
|
-
if (typeof limit === "number" && limit > 0 && all.length >= limit * 4) {
|
|
167
|
-
// Heuristic break: read enough to sort confidently. Each file is one
|
|
168
|
-
// day; if we already have 4× the requested limit, no earlier day-file
|
|
169
|
-
// can outrank what we have.
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
return [];
|
|
176
|
-
}
|
|
177
|
-
all.sort(byTsDesc);
|
|
178
|
-
if (typeof limit === "number" && limit > 0 && all.length > limit) {
|
|
179
|
-
return all.slice(0, limit);
|
|
180
|
-
}
|
|
181
|
-
return all;
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Clear staged entries whose `ts` is strictly less than `beforeTs`. Returns
|
|
185
|
-
* the count removed.
|
|
186
|
-
*
|
|
187
|
-
* If `beforeTs` is omitted, clears EVERYTHING (use with care — typically the
|
|
188
|
-
* Promoter passes the timestamp of the newest entry it just promoted, so any
|
|
189
|
-
* later-arriving exchange survives the sweep).
|
|
190
|
-
*
|
|
191
|
-
* Rewrites each day-file in place (read → filter → atomic-replace via tmp).
|
|
192
|
-
* Errors on any one file are swallowed; the count reflects what was actually
|
|
193
|
-
* removed.
|
|
194
|
-
*/
|
|
195
|
-
export function clearStaged(project, beforeTs) {
|
|
196
|
-
let removed = 0;
|
|
197
|
-
let files;
|
|
198
|
-
try {
|
|
199
|
-
files = listStagingFiles(project);
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
return 0;
|
|
203
|
-
}
|
|
204
|
-
for (const file of files) {
|
|
205
|
-
let entries;
|
|
206
|
-
try {
|
|
207
|
-
entries = readJsonlFile(file);
|
|
208
|
-
}
|
|
209
|
-
catch {
|
|
210
|
-
continue;
|
|
211
|
-
}
|
|
212
|
-
if (entries.length === 0) {
|
|
213
|
-
// Empty/corrupt file — remove so the dir stays tidy.
|
|
214
|
-
try {
|
|
215
|
-
fs.unlinkSync(file);
|
|
216
|
-
}
|
|
217
|
-
catch {
|
|
218
|
-
/* ignore */
|
|
219
|
-
}
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
const keep = [];
|
|
223
|
-
for (const e of entries) {
|
|
224
|
-
// undefined cursor → clear EVERYTHING (documented contract).
|
|
225
|
-
// Otherwise keep entries whose ts is >= beforeTs (newer survives).
|
|
226
|
-
const shouldClear = beforeTs === undefined || e.ts < beforeTs;
|
|
227
|
-
if (shouldClear) {
|
|
228
|
-
removed++;
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
keep.push(e);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
try {
|
|
235
|
-
if (keep.length === 0) {
|
|
236
|
-
fs.unlinkSync(file);
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
const tmp = `${file}.tmp.${process.pid}`;
|
|
240
|
-
const payload = keep.map((e) => JSON.stringify(e)).join("\n") + "\n";
|
|
241
|
-
fs.writeFileSync(tmp, payload, "utf-8");
|
|
242
|
-
fs.renameSync(tmp, file); // atomic on POSIX
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
catch {
|
|
246
|
-
// Best-effort — count already reflects intended removals.
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
return removed;
|
|
250
|
-
}
|
|
251
|
-
//# sourceMappingURL=stager.js.map
|