chapterhouse 0.3.25 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/api/server-runtime.js +1 -1
  2. package/dist/api/server.js +13 -1
  3. package/dist/api/server.test.js +68 -54
  4. package/dist/api/sse.integration.test.js +4 -46
  5. package/dist/api/turn-sse.integration.test.js +20 -47
  6. package/dist/config.js +81 -1
  7. package/dist/config.test.js +123 -0
  8. package/dist/copilot/agents.js +27 -4
  9. package/dist/copilot/agents.test.js +7 -0
  10. package/dist/copilot/oneshot.js +54 -0
  11. package/dist/copilot/orchestrator.js +228 -4
  12. package/dist/copilot/orchestrator.test.js +373 -1
  13. package/dist/copilot/system-message.js +4 -0
  14. package/dist/copilot/system-message.test.js +24 -0
  15. package/dist/copilot/tools.agent.test.js +23 -0
  16. package/dist/copilot/tools.js +350 -4
  17. package/dist/copilot/tools.memory.test.js +248 -0
  18. package/dist/copilot/turn-event-log-env.test.js +19 -0
  19. package/dist/copilot/turn-event-log.js +22 -23
  20. package/dist/copilot/turn-event-log.test.js +61 -2
  21. package/dist/memory/active-scope.js +69 -0
  22. package/dist/memory/active-scope.test.js +76 -0
  23. package/dist/memory/checkpoint-prompt.js +71 -0
  24. package/dist/memory/checkpoint.js +257 -0
  25. package/dist/memory/checkpoint.test.js +255 -0
  26. package/dist/memory/decisions.js +53 -0
  27. package/dist/memory/decisions.test.js +92 -0
  28. package/dist/memory/entities.js +59 -0
  29. package/dist/memory/entities.test.js +65 -0
  30. package/dist/memory/eot.js +219 -0
  31. package/dist/memory/eot.test.js +263 -0
  32. package/dist/memory/hot-tier.js +187 -0
  33. package/dist/memory/hot-tier.test.js +197 -0
  34. package/dist/memory/housekeeping.js +352 -0
  35. package/dist/memory/housekeeping.test.js +280 -0
  36. package/dist/memory/inbox.js +73 -0
  37. package/dist/memory/index.js +11 -0
  38. package/dist/memory/observations.js +46 -0
  39. package/dist/memory/observations.test.js +86 -0
  40. package/dist/memory/recall.js +197 -0
  41. package/dist/memory/recall.test.js +196 -0
  42. package/dist/memory/scopes.js +89 -0
  43. package/dist/memory/scopes.test.js +201 -0
  44. package/dist/memory/tiering.js +193 -0
  45. package/dist/memory/types.js +2 -0
  46. package/dist/paths.js +7 -1
  47. package/dist/store/db.js +423 -17
  48. package/dist/store/db.test.js +94 -7
  49. package/dist/test/api-server.js +50 -0
  50. package/dist/test/api-server.test.js +57 -0
  51. package/dist/test/setup-env.js +25 -0
  52. package/dist/test/setup-env.test.js +38 -0
  53. package/package.json +1 -1
  54. package/web/dist/assets/{index-BRPJa1DK.js → index-DmYLALt0.js} +70 -70
  55. package/web/dist/assets/index-DmYLALt0.js.map +1 -0
  56. package/web/dist/index.html +1 -1
  57. package/web/dist/assets/index-BRPJa1DK.js.map +0 -1
@@ -0,0 +1,193 @@
1
+ import { config } from "../config.js";
2
+ import { getDb } from "../store/db.js";
3
+ import { childLogger } from "../util/logger.js";
4
+ const log = childLogger("memory.tiering");
5
+ const TABLES = {
6
+ observation: "mem_observations",
7
+ decision: "mem_decisions",
8
+ entity: "mem_entities",
9
+ };
10
+ function dbTable(table) {
11
+ return TABLES[table];
12
+ }
13
+ function passSummary(pass, examined = 0, modified = 0, errors = []) {
14
+ return { pass, examined, modified, errors };
15
+ }
16
+ function isRecent(value, days) {
17
+ if (!value) {
18
+ return false;
19
+ }
20
+ const parsed = new Date(value.includes("T") ? value : value.replace(" ", "T"));
21
+ if (Number.isNaN(parsed.getTime())) {
22
+ return false;
23
+ }
24
+ return parsed.getTime() >= Date.now() - days * 24 * 60 * 60 * 1000;
25
+ }
26
+ export function inferTierFromSignals(row) {
27
+ if (row.archived_at || row.superseded_by) {
28
+ return "cold";
29
+ }
30
+ if (row.tier === "glacier") {
31
+ return "cold";
32
+ }
33
+ if (row.confidence !== undefined && row.confidence !== null && row.confidence > 0.7) {
34
+ const timestamp = row.created_at ?? row.decided_at ?? row.updated_at;
35
+ if (isRecent(timestamp, 30)) {
36
+ return "hot";
37
+ }
38
+ }
39
+ return row.tier === "hot" || row.tier === "cold" ? row.tier : "warm";
40
+ }
41
+ function setTier(table, id, tier, reason, manual) {
42
+ const result = getDb().prepare(`
43
+ UPDATE ${dbTable(table)}
44
+ SET tier = ?, tier_reason = ?, tier_pinned_at = ${manual ? "CURRENT_TIMESTAMP" : "tier_pinned_at"}
45
+ WHERE id = ?
46
+ `).run(tier, reason, id);
47
+ if (result.changes === 0) {
48
+ throw new Error(`Unknown memory ${table} id '${id}'.`);
49
+ }
50
+ log.info({ table, id, tier, reason, manual }, "memory.tier.set");
51
+ }
52
+ export function promoteToHot(table, id, reason) {
53
+ setTier(table, id, "hot", reason, true);
54
+ }
55
+ export function demoteToWarm(table, id, reason) {
56
+ setTier(table, id, "warm", reason, true);
57
+ }
58
+ export function demoteToCold(table, id, reason) {
59
+ setTier(table, id, "cold", reason, true);
60
+ }
61
+ function updateTier(sql, params) {
62
+ return getDb().prepare(sql).run(...params).changes;
63
+ }
64
+ export function tieringPass(scopeId) {
65
+ if (!config.memoryTieringEnabled) {
66
+ return passSummary("tieringPass");
67
+ }
68
+ try {
69
+ const db = getDb();
70
+ const counts = db.prepare(`
71
+ SELECT
72
+ (SELECT COUNT(*) FROM mem_observations WHERE scope_id = ?) AS observations,
73
+ (SELECT COUNT(*) FROM mem_decisions WHERE scope_id = ?) AS decisions,
74
+ (SELECT COUNT(*) FROM mem_entities WHERE scope_id = ?) AS entities
75
+ `).get(scopeId, scopeId, scopeId);
76
+ let modified = 0;
77
+ const tx = db.transaction(() => {
78
+ modified += updateTier(`
79
+ UPDATE mem_observations
80
+ SET tier = 'cold', tier_reason = 'archived or superseded'
81
+ WHERE scope_id = ?
82
+ AND tier != 'cold'
83
+ AND tier_pinned_at IS NULL
84
+ AND (archived_at IS NOT NULL OR superseded_by IS NOT NULL)
85
+ `, [scopeId]);
86
+ modified += updateTier(`
87
+ UPDATE mem_decisions
88
+ SET tier = 'cold', tier_reason = 'archived or superseded'
89
+ WHERE scope_id = ?
90
+ AND tier != 'cold'
91
+ AND tier_pinned_at IS NULL
92
+ AND (archived_at IS NOT NULL OR superseded_by IS NOT NULL)
93
+ `, [scopeId]);
94
+ modified += updateTier(`
95
+ UPDATE mem_observations
96
+ SET tier = 'hot', tier_reason = 'referenced by recent entity decision'
97
+ WHERE scope_id = ?
98
+ AND tier != 'hot'
99
+ AND tier_pinned_at IS NULL
100
+ AND archived_at IS NULL
101
+ AND superseded_by IS NULL
102
+ AND entity_id IS NOT NULL
103
+ AND EXISTS (
104
+ SELECT 1
105
+ FROM mem_decisions d
106
+ WHERE d.scope_id = mem_observations.scope_id
107
+ AND d.entity_id = mem_observations.entity_id
108
+ AND d.archived_at IS NULL
109
+ AND d.superseded_by IS NULL
110
+ AND datetime(d.decided_at) >= datetime('now', '-7 days')
111
+ )
112
+ `, [scopeId]);
113
+ modified += updateTier(`
114
+ UPDATE mem_decisions
115
+ SET tier = 'hot', tier_reason = 'recent decision restatement'
116
+ WHERE scope_id = ?
117
+ AND tier != 'hot'
118
+ AND tier_pinned_at IS NULL
119
+ AND archived_at IS NULL
120
+ AND (
121
+ datetime(decided_at) >= datetime('now', '-7 days')
122
+ OR EXISTS (
123
+ SELECT 1
124
+ FROM mem_decisions old
125
+ WHERE old.superseded_by = mem_decisions.id
126
+ AND datetime(mem_decisions.decided_at) >= datetime('now', '-7 days')
127
+ )
128
+ )
129
+ `, [scopeId]);
130
+ modified += updateTier(`
131
+ UPDATE mem_entities
132
+ SET tier = 'hot', tier_reason = 'referenced by three recent observations'
133
+ WHERE scope_id = ?
134
+ AND tier != 'hot'
135
+ AND tier_pinned_at IS NULL
136
+ AND (
137
+ SELECT COUNT(*)
138
+ FROM mem_observations o
139
+ WHERE o.entity_id = mem_entities.id
140
+ AND o.archived_at IS NULL
141
+ AND o.superseded_by IS NULL
142
+ AND datetime(o.created_at) >= datetime('now', '-7 days')
143
+ ) >= 3
144
+ `, [scopeId]);
145
+ modified += updateTier(`
146
+ UPDATE mem_observations
147
+ SET tier = 'warm', tier_reason = 'hot age threshold without recall'
148
+ WHERE scope_id = ?
149
+ AND tier = 'hot'
150
+ AND tier_pinned_at IS NULL
151
+ AND archived_at IS NULL
152
+ AND superseded_by IS NULL
153
+ AND last_recalled_at IS NULL
154
+ AND datetime(created_at) < datetime('now', ?)
155
+ `, [scopeId, `-${config.memoryHotAgeDays} days`]);
156
+ modified += updateTier(`
157
+ UPDATE mem_decisions
158
+ SET tier = 'warm', tier_reason = 'hot age threshold without recall'
159
+ WHERE scope_id = ?
160
+ AND tier = 'hot'
161
+ AND tier_pinned_at IS NULL
162
+ AND archived_at IS NULL
163
+ AND superseded_by IS NULL
164
+ AND last_recalled_at IS NULL
165
+ AND datetime(decided_at) < datetime('now', ?)
166
+ `, [scopeId, `-${config.memoryHotAgeDays} days`]);
167
+ modified += updateTier(`
168
+ UPDATE mem_observations
169
+ SET tier = 'cold', tier_reason = 'stale low confidence'
170
+ WHERE scope_id = ?
171
+ AND tier = 'warm'
172
+ AND tier_pinned_at IS NULL
173
+ AND confidence < 0.3
174
+ AND datetime(created_at) < datetime('now', '-60 days')
175
+ `, [scopeId]);
176
+ modified += updateTier(`
177
+ UPDATE mem_entities
178
+ SET tier = 'cold', tier_reason = 'stale low confidence'
179
+ WHERE scope_id = ?
180
+ AND tier = 'warm'
181
+ AND tier_pinned_at IS NULL
182
+ AND confidence < 0.3
183
+ AND datetime(updated_at) < datetime('now', '-60 days')
184
+ `, [scopeId]);
185
+ });
186
+ tx();
187
+ return passSummary("tieringPass", counts.observations + counts.decisions + counts.entities, modified);
188
+ }
189
+ catch (error) {
190
+ return passSummary("tieringPass", 0, 0, [error instanceof Error ? error.message : String(error)]);
191
+ }
192
+ }
193
+ //# sourceMappingURL=tiering.js.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
package/dist/paths.js CHANGED
@@ -13,8 +13,14 @@ function resolveChapterhouseHome() {
13
13
  : join(configuredHome, ".chapterhouse");
14
14
  }
15
15
  export const CHAPTERHOUSE_HOME = resolveChapterhouseHome();
16
+ export function getChapterhouseHome() {
17
+ return resolveChapterhouseHome();
18
+ }
16
19
  /** Path to the SQLite database */
17
20
  export const DB_PATH = join(CHAPTERHOUSE_HOME, "chapterhouse.db");
21
+ export function getDbPath() {
22
+ return join(resolveChapterhouseHome(), "chapterhouse.db");
23
+ }
18
24
  /** Path to the user .env file */
19
25
  export const ENV_PATH = join(CHAPTERHOUSE_HOME, ".env");
20
26
  /** Path to user-local skills */
@@ -36,6 +42,6 @@ export function resolveWikiRelativePath(relativePath) {
36
42
  }
37
43
  /** Ensure ~/.chapterhouse/ exists */
38
44
  export function ensureChapterhouseHome() {
39
- mkdirSync(CHAPTERHOUSE_HOME, { recursive: true });
45
+ mkdirSync(resolveChapterhouseHome(), { recursive: true });
40
46
  }
41
47
  //# sourceMappingURL=paths.js.map