@soleri/core 7.0.0 → 8.1.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 (294) hide show
  1. package/dist/agency/agency-manager.d.ts +27 -1
  2. package/dist/agency/agency-manager.d.ts.map +1 -1
  3. package/dist/agency/agency-manager.js +180 -9
  4. package/dist/agency/agency-manager.js.map +1 -1
  5. package/dist/agency/default-rules.d.ts +7 -0
  6. package/dist/agency/default-rules.d.ts.map +1 -0
  7. package/dist/agency/default-rules.js +79 -0
  8. package/dist/agency/default-rules.js.map +1 -0
  9. package/dist/agency/types.d.ts +48 -0
  10. package/dist/agency/types.d.ts.map +1 -1
  11. package/dist/brain/brain.d.ts +17 -2
  12. package/dist/brain/brain.d.ts.map +1 -1
  13. package/dist/brain/brain.js +118 -8
  14. package/dist/brain/brain.js.map +1 -1
  15. package/dist/brain/knowledge-synthesizer.d.ts +37 -0
  16. package/dist/brain/knowledge-synthesizer.d.ts.map +1 -0
  17. package/dist/brain/knowledge-synthesizer.js +159 -0
  18. package/dist/brain/knowledge-synthesizer.js.map +1 -0
  19. package/dist/brain/learning-radar.d.ts +96 -0
  20. package/dist/brain/learning-radar.d.ts.map +1 -0
  21. package/dist/brain/learning-radar.js +202 -0
  22. package/dist/brain/learning-radar.js.map +1 -0
  23. package/dist/brain/types.d.ts +15 -0
  24. package/dist/brain/types.d.ts.map +1 -1
  25. package/dist/context/context-engine.d.ts.map +1 -1
  26. package/dist/context/context-engine.js +82 -17
  27. package/dist/context/context-engine.js.map +1 -1
  28. package/dist/context/types.d.ts +5 -0
  29. package/dist/context/types.d.ts.map +1 -1
  30. package/dist/control/intent-router.d.ts +12 -1
  31. package/dist/control/intent-router.d.ts.map +1 -1
  32. package/dist/control/intent-router.js +68 -0
  33. package/dist/control/intent-router.js.map +1 -1
  34. package/dist/control/types.d.ts +17 -0
  35. package/dist/control/types.d.ts.map +1 -1
  36. package/dist/curator/classifier.d.ts +18 -0
  37. package/dist/curator/classifier.d.ts.map +1 -0
  38. package/dist/curator/classifier.js +59 -0
  39. package/dist/curator/classifier.js.map +1 -0
  40. package/dist/curator/quality-gate.d.ts +29 -0
  41. package/dist/curator/quality-gate.d.ts.map +1 -0
  42. package/dist/curator/quality-gate.js +86 -0
  43. package/dist/curator/quality-gate.js.map +1 -0
  44. package/dist/domain-packs/index.d.ts +0 -3
  45. package/dist/domain-packs/index.d.ts.map +1 -1
  46. package/dist/domain-packs/index.js +0 -3
  47. package/dist/domain-packs/index.js.map +1 -1
  48. package/dist/domain-packs/loader.d.ts.map +1 -1
  49. package/dist/domain-packs/loader.js +20 -4
  50. package/dist/domain-packs/loader.js.map +1 -1
  51. package/dist/domain-packs/pack-runtime.d.ts +5 -5
  52. package/dist/domain-packs/pack-runtime.d.ts.map +1 -1
  53. package/dist/domain-packs/pack-runtime.js +2 -2
  54. package/dist/domain-packs/pack-runtime.js.map +1 -1
  55. package/dist/domain-packs/types.d.ts +8 -2
  56. package/dist/domain-packs/types.d.ts.map +1 -1
  57. package/dist/domain-packs/types.js.map +1 -1
  58. package/dist/engine/bin/soleri-engine.js +13 -2
  59. package/dist/engine/bin/soleri-engine.js.map +1 -1
  60. package/dist/engine/index.d.ts +2 -0
  61. package/dist/engine/index.d.ts.map +1 -1
  62. package/dist/engine/index.js +1 -0
  63. package/dist/engine/index.js.map +1 -1
  64. package/dist/engine/module-manifest.d.ts +28 -0
  65. package/dist/engine/module-manifest.d.ts.map +1 -0
  66. package/dist/engine/module-manifest.js +85 -0
  67. package/dist/engine/module-manifest.js.map +1 -0
  68. package/dist/engine/register-engine.d.ts +19 -0
  69. package/dist/engine/register-engine.d.ts.map +1 -1
  70. package/dist/engine/register-engine.js +15 -2
  71. package/dist/engine/register-engine.js.map +1 -1
  72. package/dist/events/event-bus.d.ts +30 -0
  73. package/dist/events/event-bus.d.ts.map +1 -0
  74. package/dist/events/event-bus.js +51 -0
  75. package/dist/events/event-bus.js.map +1 -0
  76. package/dist/flows/chain-runner.d.ts +46 -0
  77. package/dist/flows/chain-runner.d.ts.map +1 -0
  78. package/dist/flows/chain-runner.js +271 -0
  79. package/dist/flows/chain-runner.js.map +1 -0
  80. package/dist/flows/chain-types.d.ts +103 -0
  81. package/dist/flows/chain-types.d.ts.map +1 -0
  82. package/dist/flows/chain-types.js +23 -0
  83. package/dist/flows/chain-types.js.map +1 -0
  84. package/dist/health/doctor-checks.d.ts +15 -0
  85. package/dist/health/doctor-checks.d.ts.map +1 -0
  86. package/dist/health/doctor-checks.js +98 -0
  87. package/dist/health/doctor-checks.js.map +1 -0
  88. package/dist/index.d.ts +0 -1
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +0 -1
  91. package/dist/index.js.map +1 -1
  92. package/dist/intake/content-classifier.d.ts.map +1 -1
  93. package/dist/intake/content-classifier.js +0 -2
  94. package/dist/intake/content-classifier.js.map +1 -1
  95. package/dist/intake/text-ingester.d.ts +52 -0
  96. package/dist/intake/text-ingester.d.ts.map +1 -0
  97. package/dist/intake/text-ingester.js +181 -0
  98. package/dist/intake/text-ingester.js.map +1 -0
  99. package/dist/llm/llm-client.d.ts.map +1 -1
  100. package/dist/llm/llm-client.js +45 -5
  101. package/dist/llm/llm-client.js.map +1 -1
  102. package/dist/llm/oauth-discovery.d.ts +18 -0
  103. package/dist/llm/oauth-discovery.d.ts.map +1 -0
  104. package/dist/llm/oauth-discovery.js +130 -0
  105. package/dist/llm/oauth-discovery.js.map +1 -0
  106. package/dist/llm/types.d.ts +4 -2
  107. package/dist/llm/types.d.ts.map +1 -1
  108. package/dist/packs/pack-installer.d.ts +2 -1
  109. package/dist/packs/pack-installer.d.ts.map +1 -1
  110. package/dist/packs/pack-installer.js +10 -1
  111. package/dist/packs/pack-installer.js.map +1 -1
  112. package/dist/persistence/index.d.ts +0 -1
  113. package/dist/persistence/index.d.ts.map +1 -1
  114. package/dist/persistence/index.js +0 -1
  115. package/dist/persistence/index.js.map +1 -1
  116. package/dist/persistence/types.d.ts +2 -6
  117. package/dist/persistence/types.d.ts.map +1 -1
  118. package/dist/planning/evidence-collector.d.ts +41 -0
  119. package/dist/planning/evidence-collector.d.ts.map +1 -0
  120. package/dist/planning/evidence-collector.js +194 -0
  121. package/dist/planning/evidence-collector.js.map +1 -0
  122. package/dist/planning/planner.d.ts +4 -0
  123. package/dist/planning/planner.d.ts.map +1 -1
  124. package/dist/planning/planner.js +11 -0
  125. package/dist/planning/planner.js.map +1 -1
  126. package/dist/plugins/index.d.ts +4 -0
  127. package/dist/plugins/index.d.ts.map +1 -1
  128. package/dist/plugins/index.js +4 -0
  129. package/dist/plugins/index.js.map +1 -1
  130. package/dist/plugins/plugin-registry.d.ts +4 -0
  131. package/dist/plugins/plugin-registry.d.ts.map +1 -1
  132. package/dist/plugins/plugin-registry.js +4 -0
  133. package/dist/plugins/plugin-registry.js.map +1 -1
  134. package/dist/plugins/types.d.ts +32 -27
  135. package/dist/plugins/types.d.ts.map +1 -1
  136. package/dist/plugins/types.js +6 -3
  137. package/dist/plugins/types.js.map +1 -1
  138. package/dist/queue/job-queue.d.ts +92 -0
  139. package/dist/queue/job-queue.d.ts.map +1 -0
  140. package/dist/queue/job-queue.js +180 -0
  141. package/dist/queue/job-queue.js.map +1 -0
  142. package/dist/queue/pipeline-runner.d.ts +62 -0
  143. package/dist/queue/pipeline-runner.d.ts.map +1 -0
  144. package/dist/queue/pipeline-runner.js +126 -0
  145. package/dist/queue/pipeline-runner.js.map +1 -0
  146. package/dist/runtime/admin-setup-ops.d.ts +20 -0
  147. package/dist/runtime/admin-setup-ops.d.ts.map +1 -0
  148. package/dist/runtime/admin-setup-ops.js +583 -0
  149. package/dist/runtime/admin-setup-ops.js.map +1 -0
  150. package/dist/runtime/chain-ops.d.ts +9 -0
  151. package/dist/runtime/chain-ops.d.ts.map +1 -0
  152. package/dist/runtime/chain-ops.js +107 -0
  153. package/dist/runtime/chain-ops.js.map +1 -0
  154. package/dist/runtime/claude-md-helpers.d.ts +56 -0
  155. package/dist/runtime/claude-md-helpers.d.ts.map +1 -0
  156. package/dist/runtime/claude-md-helpers.js +160 -0
  157. package/dist/runtime/claude-md-helpers.js.map +1 -0
  158. package/dist/runtime/curator-extra-ops.d.ts +3 -2
  159. package/dist/runtime/curator-extra-ops.d.ts.map +1 -1
  160. package/dist/runtime/curator-extra-ops.js +81 -3
  161. package/dist/runtime/curator-extra-ops.js.map +1 -1
  162. package/dist/runtime/facades/admin-facade.d.ts.map +1 -1
  163. package/dist/runtime/facades/admin-facade.js +5 -2
  164. package/dist/runtime/facades/admin-facade.js.map +1 -1
  165. package/dist/runtime/facades/agency-facade.d.ts.map +1 -1
  166. package/dist/runtime/facades/agency-facade.js +64 -0
  167. package/dist/runtime/facades/agency-facade.js.map +1 -1
  168. package/dist/runtime/facades/brain-facade.d.ts.map +1 -1
  169. package/dist/runtime/facades/brain-facade.js +122 -1
  170. package/dist/runtime/facades/brain-facade.js.map +1 -1
  171. package/dist/runtime/facades/control-facade.d.ts.map +1 -1
  172. package/dist/runtime/facades/control-facade.js +42 -0
  173. package/dist/runtime/facades/control-facade.js.map +1 -1
  174. package/dist/runtime/facades/memory-facade.d.ts.map +1 -1
  175. package/dist/runtime/facades/memory-facade.js +20 -2
  176. package/dist/runtime/facades/memory-facade.js.map +1 -1
  177. package/dist/runtime/facades/plan-facade.d.ts.map +1 -1
  178. package/dist/runtime/facades/plan-facade.js +2 -0
  179. package/dist/runtime/facades/plan-facade.js.map +1 -1
  180. package/dist/runtime/facades/vault-facade.d.ts.map +1 -1
  181. package/dist/runtime/facades/vault-facade.js +25 -5
  182. package/dist/runtime/facades/vault-facade.js.map +1 -1
  183. package/dist/runtime/intake-ops.d.ts +7 -5
  184. package/dist/runtime/intake-ops.d.ts.map +1 -1
  185. package/dist/runtime/intake-ops.js +98 -5
  186. package/dist/runtime/intake-ops.js.map +1 -1
  187. package/dist/runtime/memory-extra-ops.d.ts +6 -3
  188. package/dist/runtime/memory-extra-ops.d.ts.map +1 -1
  189. package/dist/runtime/memory-extra-ops.js +292 -4
  190. package/dist/runtime/memory-extra-ops.js.map +1 -1
  191. package/dist/runtime/pack-ops.d.ts +3 -0
  192. package/dist/runtime/pack-ops.d.ts.map +1 -1
  193. package/dist/runtime/pack-ops.js +18 -1
  194. package/dist/runtime/pack-ops.js.map +1 -1
  195. package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
  196. package/dist/runtime/planning-extra-ops.js +85 -0
  197. package/dist/runtime/planning-extra-ops.js.map +1 -1
  198. package/dist/runtime/playbook-ops.js +1 -1
  199. package/dist/runtime/playbook-ops.js.map +1 -1
  200. package/dist/runtime/plugin-ops.d.ts.map +1 -1
  201. package/dist/runtime/plugin-ops.js +3 -0
  202. package/dist/runtime/plugin-ops.js.map +1 -1
  203. package/dist/runtime/runtime.d.ts.map +1 -1
  204. package/dist/runtime/runtime.js +143 -2
  205. package/dist/runtime/runtime.js.map +1 -1
  206. package/dist/runtime/session-briefing.d.ts +23 -0
  207. package/dist/runtime/session-briefing.d.ts.map +1 -0
  208. package/dist/runtime/session-briefing.js +154 -0
  209. package/dist/runtime/session-briefing.js.map +1 -0
  210. package/dist/runtime/types.d.ts +23 -0
  211. package/dist/runtime/types.d.ts.map +1 -1
  212. package/dist/runtime/vault-linking-ops.d.ts.map +1 -1
  213. package/dist/runtime/vault-linking-ops.js +3 -7
  214. package/dist/runtime/vault-linking-ops.js.map +1 -1
  215. package/dist/vault/vault.d.ts +34 -0
  216. package/dist/vault/vault.d.ts.map +1 -1
  217. package/dist/vault/vault.js +89 -3
  218. package/dist/vault/vault.js.map +1 -1
  219. package/package.json +6 -4
  220. package/src/__tests__/admin-setup-ops.test.ts +355 -0
  221. package/src/__tests__/async-infrastructure.test.ts +307 -0
  222. package/src/__tests__/cognee-client-gaps.test.ts +6 -2
  223. package/src/__tests__/cognee-hybrid-search.test.ts +49 -35
  224. package/src/__tests__/cognee-sync-manager-deep.test.ts +89 -65
  225. package/src/__tests__/curator-extra-ops.test.ts +6 -2
  226. package/src/__tests__/curator-pipeline-e2e.test.ts +545 -0
  227. package/src/__tests__/memory-extra-ops.test.ts +2 -2
  228. package/src/__tests__/module-manifest-drift.test.ts +59 -0
  229. package/src/__tests__/planning-extra-ops.test.ts +2 -2
  230. package/src/__tests__/second-brain-features.test.ts +583 -0
  231. package/src/agency/agency-manager.ts +217 -9
  232. package/src/agency/default-rules.ts +83 -0
  233. package/src/agency/types.ts +61 -0
  234. package/src/brain/brain.ts +110 -8
  235. package/src/brain/knowledge-synthesizer.ts +216 -0
  236. package/src/brain/learning-radar.ts +340 -0
  237. package/src/brain/types.ts +16 -0
  238. package/src/context/context-engine.ts +114 -15
  239. package/src/context/types.ts +5 -0
  240. package/src/control/intent-router.ts +107 -0
  241. package/src/control/types.ts +10 -0
  242. package/src/curator/classifier.ts +86 -0
  243. package/src/curator/quality-gate.ts +127 -0
  244. package/src/domain-packs/index.ts +0 -6
  245. package/src/domain-packs/loader.ts +25 -5
  246. package/src/domain-packs/pack-runtime.ts +6 -6
  247. package/src/domain-packs/types.ts +8 -2
  248. package/src/engine/bin/soleri-engine.ts +18 -2
  249. package/src/engine/index.ts +2 -0
  250. package/src/engine/module-manifest.ts +99 -0
  251. package/src/engine/register-engine.ts +21 -2
  252. package/src/events/event-bus.ts +58 -0
  253. package/src/flows/chain-runner.ts +369 -0
  254. package/src/flows/chain-types.ts +57 -0
  255. package/src/index.ts +0 -1
  256. package/src/intake/content-classifier.ts +0 -2
  257. package/src/intake/text-ingester.ts +234 -0
  258. package/src/llm/llm-client.ts +50 -7
  259. package/src/llm/oauth-discovery.ts +151 -0
  260. package/src/llm/types.ts +4 -2
  261. package/src/packs/pack-installer.ts +16 -1
  262. package/src/persistence/index.ts +0 -1
  263. package/src/persistence/types.ts +2 -6
  264. package/src/planning/evidence-collector.ts +247 -0
  265. package/src/planning/planner.ts +11 -0
  266. package/src/plugins/index.ts +4 -0
  267. package/src/plugins/plugin-registry.ts +6 -1
  268. package/src/plugins/types.ts +10 -5
  269. package/src/queue/job-queue.ts +281 -0
  270. package/src/queue/pipeline-runner.ts +149 -0
  271. package/src/runtime/admin-setup-ops.ts +664 -0
  272. package/src/runtime/chain-ops.ts +121 -0
  273. package/src/runtime/claude-md-helpers.ts +218 -0
  274. package/src/runtime/curator-extra-ops.ts +86 -3
  275. package/src/runtime/facades/admin-facade.ts +5 -2
  276. package/src/runtime/facades/agency-facade.ts +68 -0
  277. package/src/runtime/facades/brain-facade.ts +142 -1
  278. package/src/runtime/facades/control-facade.ts +45 -0
  279. package/src/runtime/facades/memory-facade.ts +20 -2
  280. package/src/runtime/facades/plan-facade.ts +2 -0
  281. package/src/runtime/facades/vault-facade.ts +28 -5
  282. package/src/runtime/intake-ops.ts +107 -5
  283. package/src/runtime/memory-extra-ops.ts +312 -4
  284. package/src/runtime/pack-ops.ts +26 -1
  285. package/src/runtime/planning-extra-ops.ts +94 -0
  286. package/src/runtime/playbook-ops.ts +1 -1
  287. package/src/runtime/plugin-ops.ts +3 -0
  288. package/src/runtime/runtime.ts +138 -2
  289. package/src/runtime/session-briefing.ts +175 -0
  290. package/src/runtime/types.ts +23 -0
  291. package/src/runtime/vault-linking-ops.ts +3 -7
  292. package/src/vault/vault.ts +105 -4
  293. package/src/__tests__/postgres-provider.test.ts +0 -116
  294. package/src/persistence/postgres-provider.ts +0 -310
@@ -60,8 +60,6 @@ export async function classifyChunk(
60
60
  ): Promise<ClassifiedItem[]> {
61
61
  try {
62
62
  const result = await llm.complete({
63
- provider: 'openai',
64
- model: 'gpt-4o-mini',
65
63
  systemPrompt: CLASSIFICATION_PROMPT,
66
64
  userPrompt: chunkText,
67
65
  maxTokens: 4096,
@@ -0,0 +1,234 @@
1
+ /**
2
+ * Text Ingester — ingest articles, transcripts, and plain text into the vault.
3
+ *
4
+ * Reuses existing content-classifier (LLM extraction) and dedup-gate (TF-IDF).
5
+ * No new dependencies — fetch() is built-in, HTML stripping is regex-based.
6
+ */
7
+
8
+ import type { Vault } from '../vault/vault.js';
9
+ import type { LLMClient } from '../llm/llm-client.js';
10
+ import type { IntelligenceEntry } from '../intelligence/types.js';
11
+ import type { ClassifiedItem } from './types.js';
12
+ import { classifyChunk } from './content-classifier.js';
13
+ import { dedupItems } from './dedup-gate.js';
14
+
15
+ // ─── Types ───────────────────────────────────────────────────────────
16
+
17
+ export interface IngestSource {
18
+ type: 'article' | 'transcript' | 'notes' | 'documentation';
19
+ title: string;
20
+ url?: string;
21
+ author?: string;
22
+ }
23
+
24
+ export interface IngestOptions {
25
+ domain?: string;
26
+ tags?: string[];
27
+ /** Max chars per chunk for LLM classification. Default 4000. */
28
+ chunkSize?: number;
29
+ }
30
+
31
+ export interface IngestResult {
32
+ source: IngestSource;
33
+ ingested: number;
34
+ duplicates: number;
35
+ entries: Array<{ id: string; title: string; type: string }>;
36
+ }
37
+
38
+ // ─── Constants ───────────────────────────────────────────────────────
39
+
40
+ const DEFAULT_CHUNK_SIZE = 4000;
41
+ const FETCH_TIMEOUT_MS = 15000;
42
+
43
+ // ─── Class ───────────────────────────────────────────────────────────
44
+
45
+ export class TextIngester {
46
+ private vault: Vault;
47
+ private llm: LLMClient | null;
48
+
49
+ constructor(vault: Vault, llm: LLMClient | null) {
50
+ this.vault = vault;
51
+ this.llm = llm;
52
+ }
53
+
54
+ /**
55
+ * Ingest a URL — fetch, strip HTML, classify, dedup, store.
56
+ */
57
+ async ingestUrl(url: string, opts?: IngestOptions): Promise<IngestResult> {
58
+ if (!this.llm) {
59
+ return { source: { type: 'article', title: url }, ingested: 0, duplicates: 0, entries: [] };
60
+ }
61
+
62
+ let text: string;
63
+ let title = url;
64
+ try {
65
+ const response = await fetch(url, {
66
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
67
+ headers: { 'User-Agent': 'Soleri/1.0 (knowledge ingestion)' },
68
+ });
69
+ if (!response.ok) {
70
+ return { source: { type: 'article', title }, ingested: 0, duplicates: 0, entries: [] };
71
+ }
72
+ const html = await response.text();
73
+ title = extractTitle(html) ?? url;
74
+ text = stripHtml(html);
75
+ } catch {
76
+ return { source: { type: 'article', title }, ingested: 0, duplicates: 0, entries: [] };
77
+ }
78
+
79
+ if (text.length < 50) {
80
+ return { source: { type: 'article', title }, ingested: 0, duplicates: 0, entries: [] };
81
+ }
82
+
83
+ const source: IngestSource = { type: 'article', title, url };
84
+ return this.ingestText(text, source, opts);
85
+ }
86
+
87
+ /**
88
+ * Ingest raw text — classify, dedup, store.
89
+ */
90
+ async ingestText(
91
+ text: string,
92
+ source: IngestSource,
93
+ opts?: IngestOptions,
94
+ ): Promise<IngestResult> {
95
+ if (!this.llm) {
96
+ return { source, ingested: 0, duplicates: 0, entries: [] };
97
+ }
98
+
99
+ const chunkSize = opts?.chunkSize ?? DEFAULT_CHUNK_SIZE;
100
+ const chunks = splitIntoChunks(text, chunkSize);
101
+ const domain = opts?.domain ?? 'general';
102
+ const extraTags = opts?.tags ?? [];
103
+
104
+ // Classify all chunks
105
+ const allItems: ClassifiedItem[] = [];
106
+ for (const chunk of chunks) {
107
+ const items = await classifyChunk(this.llm, chunk, `${source.type}: ${source.title}`);
108
+ allItems.push(...items);
109
+ }
110
+
111
+ if (allItems.length === 0) {
112
+ return { source, ingested: 0, duplicates: 0, entries: [] };
113
+ }
114
+
115
+ // Dedup against vault
116
+ const dedupResults = dedupItems(allItems, this.vault);
117
+ const unique = dedupResults.filter((r) => !r.isDuplicate).map((r) => r.item);
118
+ const duplicateCount = dedupResults.filter((r) => r.isDuplicate).length;
119
+
120
+ // Build source attribution for context field
121
+ const attribution = buildAttribution(source);
122
+
123
+ // Store in vault
124
+ const entries: IntelligenceEntry[] = unique.map((item, i) => ({
125
+ id: `ingest-${source.type}-${Date.now()}-${i}-${Math.random().toString(36).slice(2, 6)}`,
126
+ type: mapType(item.type),
127
+ domain,
128
+ title: item.title,
129
+ description: item.description,
130
+ severity: mapSeverity(item.severity),
131
+ tags: [...(item.tags ?? []), ...extraTags, 'ingested', source.type],
132
+ context: attribution,
133
+ origin: 'user' as const,
134
+ }));
135
+
136
+ if (entries.length > 0) {
137
+ this.vault.seed(entries);
138
+ }
139
+
140
+ return {
141
+ source,
142
+ ingested: entries.length,
143
+ duplicates: duplicateCount,
144
+ entries: entries.map((e) => ({ id: e.id, title: e.title, type: e.type })),
145
+ };
146
+ }
147
+
148
+ /**
149
+ * Ingest multiple items in sequence.
150
+ */
151
+ async ingestBatch(
152
+ items: Array<{ text: string; source: IngestSource; opts?: IngestOptions }>,
153
+ ): Promise<IngestResult[]> {
154
+ const results: IngestResult[] = [];
155
+ for (const item of items) {
156
+ const result = await this.ingestText(item.text, item.source, item.opts);
157
+ results.push(result);
158
+ }
159
+ return results;
160
+ }
161
+ }
162
+
163
+ // ─── Helpers ─────────────────────────────────────────────────────────
164
+
165
+ function stripHtml(html: string): string {
166
+ return (
167
+ html
168
+ // Remove script and style blocks
169
+ .replace(/<script[\s\S]*?<\/script>/gi, '')
170
+ .replace(/<style[\s\S]*?<\/style>/gi, '')
171
+ // Remove nav, header, footer, aside
172
+ .replace(/<(nav|header|footer|aside)[\s\S]*?<\/\1>/gi, '')
173
+ // Remove all HTML tags
174
+ .replace(/<[^>]+>/g, ' ')
175
+ // Decode common entities
176
+ .replace(/&amp;/g, '&')
177
+ .replace(/&lt;/g, '<')
178
+ .replace(/&gt;/g, '>')
179
+ .replace(/&quot;/g, '"')
180
+ .replace(/&#39;/g, "'")
181
+ .replace(/&nbsp;/g, ' ')
182
+ // Collapse whitespace
183
+ .replace(/\s+/g, ' ')
184
+ .trim()
185
+ );
186
+ }
187
+
188
+ function extractTitle(html: string): string | null {
189
+ const match = html.match(/<title[^>]*>(.*?)<\/title>/i);
190
+ if (match) {
191
+ return match[1].replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>').trim();
192
+ }
193
+ return null;
194
+ }
195
+
196
+ function splitIntoChunks(text: string, chunkSize: number): string[] {
197
+ if (text.length <= chunkSize) return [text];
198
+
199
+ const chunks: string[] = [];
200
+ let start = 0;
201
+ while (start < text.length) {
202
+ let end = start + chunkSize;
203
+ // Try to break at a sentence boundary
204
+ if (end < text.length) {
205
+ const lastPeriod = text.lastIndexOf('. ', end);
206
+ if (lastPeriod > start + chunkSize * 0.5) {
207
+ end = lastPeriod + 2;
208
+ }
209
+ }
210
+ chunks.push(text.slice(start, end).trim());
211
+ start = end;
212
+ }
213
+ return chunks.filter((c) => c.length > 0);
214
+ }
215
+
216
+ function buildAttribution(source: IngestSource): string {
217
+ const parts = [`Source: ${source.type}`];
218
+ if (source.title) parts.push(`Title: ${source.title}`);
219
+ if (source.url) parts.push(`URL: ${source.url}`);
220
+ if (source.author) parts.push(`Author: ${source.author}`);
221
+ return parts.join(' | ');
222
+ }
223
+
224
+ function mapType(type: string): IntelligenceEntry['type'] {
225
+ if (type === 'pattern') return 'pattern';
226
+ if (type === 'anti-pattern') return 'anti-pattern';
227
+ return 'rule';
228
+ }
229
+
230
+ function mapSeverity(severity: string | undefined): IntelligenceEntry['severity'] {
231
+ if (severity === 'critical') return 'critical';
232
+ if (severity === 'warning') return 'warning';
233
+ return 'suggestion';
234
+ }
@@ -25,8 +25,45 @@ const OPENAI_API_URL = 'https://api.openai.com/v1/chat/completions';
25
25
  // =============================================================================
26
26
 
27
27
  function loadRoutingConfig(agentId: string): RoutingConfig {
28
+ // Default task→model routing: cheap models for routine, powerful for reasoning.
29
+ // Anthropic routes use extended thinking for quality decisions when available.
30
+ // Agents can override via ~/.{agentId}/model-routing.json.
31
+ const defaultRoutes: RouteEntry[] = [
32
+ // OpenAI routes (default — works without Anthropic key)
33
+ { caller: 'quality-gate', task: 'evaluate', model: 'gpt-4o', provider: 'openai' },
34
+ { caller: 'classifier', task: 'classify', model: 'gpt-4o-mini', provider: 'openai' },
35
+ { caller: 'knowledge-synthesizer', task: 'synthesize', model: 'gpt-4o', provider: 'openai' },
36
+ { caller: 'content-classifier', model: 'gpt-4o-mini', provider: 'openai' },
37
+ { caller: 'vault-linking', task: 'evaluate-links', model: 'gpt-4o-mini', provider: 'openai' },
38
+ // Anthropic routes (higher quality when key available — extended thinking capable)
39
+ {
40
+ caller: 'quality-gate-anthropic',
41
+ task: 'evaluate',
42
+ model: 'claude-sonnet-4-20250514',
43
+ provider: 'anthropic',
44
+ },
45
+ {
46
+ caller: 'contradiction-evaluator',
47
+ task: 'evaluate',
48
+ model: 'claude-sonnet-4-20250514',
49
+ provider: 'anthropic',
50
+ },
51
+ {
52
+ caller: 'knowledge-synthesizer-anthropic',
53
+ task: 'synthesize',
54
+ model: 'claude-sonnet-4-20250514',
55
+ provider: 'anthropic',
56
+ },
57
+ {
58
+ caller: 'classifier-anthropic',
59
+ task: 'classify',
60
+ model: 'claude-haiku-4-5-20251001',
61
+ provider: 'anthropic',
62
+ },
63
+ ];
64
+
28
65
  const defaultConfig: RoutingConfig = {
29
- routes: [],
66
+ routes: defaultRoutes,
30
67
  defaultOpenAIModel: 'gpt-4o-mini',
31
68
  defaultAnthropicModel: 'claude-sonnet-4-20250514',
32
69
  };
@@ -123,6 +160,8 @@ interface AnthropicClient {
123
160
  };
124
161
  }
125
162
 
163
+ type ResolvedLLMOptions = LLMCallOptions & { model: string; provider: 'openai' | 'anthropic' };
164
+
126
165
  export class LLMClient {
127
166
  private openaiKeyPool: KeyPool;
128
167
  private anthropicKeyPool: KeyPool;
@@ -144,11 +183,15 @@ export class LLMClient {
144
183
 
145
184
  async complete(options: LLMCallOptions): Promise<LLMCallResult> {
146
185
  const routed = this.router.resolve(options.caller, options.task, options.model);
147
- const resolvedOptions = { ...options, model: routed.model, provider: routed.provider };
186
+ const resolved: ResolvedLLMOptions = {
187
+ ...options,
188
+ model: options.model ?? routed.model,
189
+ provider: options.provider ?? routed.provider,
190
+ };
148
191
 
149
- return resolvedOptions.provider === 'anthropic'
150
- ? this.callAnthropic(resolvedOptions)
151
- : this.callOpenAI(resolvedOptions);
192
+ return resolved.provider === 'anthropic'
193
+ ? this.callAnthropic(resolved)
194
+ : this.callOpenAI(resolved);
152
195
  }
153
196
 
154
197
  isAvailable(): { openai: boolean; anthropic: boolean } {
@@ -166,7 +209,7 @@ export class LLMClient {
166
209
  // OPENAI
167
210
  // ===========================================================================
168
211
 
169
- private async callOpenAI(options: LLMCallOptions): Promise<LLMCallResult> {
212
+ private async callOpenAI(options: ResolvedLLMOptions): Promise<LLMCallResult> {
170
213
  const keyPool = this.openaiKeyPool.hasKeys ? this.openaiKeyPool : null;
171
214
 
172
215
  if (!keyPool) {
@@ -238,7 +281,7 @@ export class LLMClient {
238
281
  // ANTHROPIC
239
282
  // ===========================================================================
240
283
 
241
- private async callAnthropic(options: LLMCallOptions): Promise<LLMCallResult> {
284
+ private async callAnthropic(options: ResolvedLLMOptions): Promise<LLMCallResult> {
242
285
  const client = await this.getAnthropicClient();
243
286
  if (!client) {
244
287
  throw new LLMError('Anthropic API key not configured', { retryable: false });
@@ -0,0 +1,151 @@
1
+ /**
2
+ * OAuth Token Discovery — find Claude Code OAuth tokens on macOS and Linux.
3
+ *
4
+ * Priority:
5
+ * 1. ANTHROPIC_API_KEY env var (explicit, highest priority)
6
+ * 2. Claude Code credentials file (~/.claude/.credentials.json or similar)
7
+ * 3. macOS Keychain (security find-generic-password)
8
+ * 4. Linux GNOME Keyring (secret-tool lookup)
9
+ * 5. null (graceful fallback → use OpenAI or no LLM)
10
+ *
11
+ * Cached for 5 minutes to avoid repeated I/O.
12
+ */
13
+
14
+ import { execFileSync } from 'node:child_process';
15
+ import { readFileSync, existsSync } from 'node:fs';
16
+ import { join } from 'node:path';
17
+ import { homedir, platform } from 'node:os';
18
+
19
+ // ─── Cache ───────────────────────────────────────────────────────────
20
+
21
+ let cachedToken: string | null = null;
22
+ let cacheTimestamp = 0;
23
+ const CACHE_TTL_MS = 5 * 60 * 1000;
24
+
25
+ // ─── Public API ──────────────────────────────────────────────────────
26
+
27
+ /**
28
+ * Discover an Anthropic API token. Returns null if none found.
29
+ * Results cached for 5 minutes.
30
+ */
31
+ export function discoverAnthropicToken(): string | null {
32
+ if (cachedToken && Date.now() - cacheTimestamp < CACHE_TTL_MS) {
33
+ return cachedToken;
34
+ }
35
+
36
+ const token = tryEnvVar() ?? tryCredentialsFile() ?? tryPlatformKeychain();
37
+
38
+ if (token) {
39
+ cachedToken = token;
40
+ cacheTimestamp = Date.now();
41
+ }
42
+
43
+ return token;
44
+ }
45
+
46
+ // ─── Discovery Methods ───────────────────────────────────────────────
47
+
48
+ function tryEnvVar(): string | null {
49
+ return process.env.ANTHROPIC_API_KEY ?? null;
50
+ }
51
+
52
+ function tryCredentialsFile(): string | null {
53
+ const candidates = [
54
+ join(homedir(), '.claude', '.credentials.json'),
55
+ join(homedir(), '.claude', 'credentials.json'),
56
+ join(homedir(), '.config', 'claude', 'credentials.json'),
57
+ ];
58
+
59
+ for (const path of candidates) {
60
+ try {
61
+ if (!existsSync(path)) continue;
62
+ const raw = readFileSync(path, 'utf-8');
63
+ const parsed = JSON.parse(raw) as Record<string, unknown>;
64
+
65
+ // Claude Code OAuth format: { claudeAiOauth: { accessToken: "..." } }
66
+ const oauth = parsed.claudeAiOauth as Record<string, unknown> | undefined;
67
+ if (oauth?.accessToken && typeof oauth.accessToken === 'string') {
68
+ return oauth.accessToken;
69
+ }
70
+
71
+ // Alternative: direct token field
72
+ if (parsed.accessToken && typeof parsed.accessToken === 'string') {
73
+ return parsed.accessToken as string;
74
+ }
75
+
76
+ // Alternative: API key field
77
+ if (parsed.apiKey && typeof parsed.apiKey === 'string') {
78
+ return parsed.apiKey as string;
79
+ }
80
+ } catch {
81
+ continue;
82
+ }
83
+ }
84
+
85
+ return null;
86
+ }
87
+
88
+ function tryPlatformKeychain(): string | null {
89
+ const os = platform();
90
+
91
+ if (os === 'darwin') return tryMacKeychain();
92
+ if (os === 'linux') return tryLinuxKeyring();
93
+
94
+ return null;
95
+ }
96
+
97
+ function tryMacKeychain(): string | null {
98
+ try {
99
+ const raw = execFileSync(
100
+ 'security',
101
+ ['find-generic-password', '-s', 'Claude Code-credentials', '-w'],
102
+ { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] },
103
+ ).trim();
104
+
105
+ if (!raw) return null;
106
+
107
+ // Try JSON parse
108
+ try {
109
+ const parsed = JSON.parse(raw) as Record<string, unknown>;
110
+ const oauth = parsed.claudeAiOauth as Record<string, unknown> | undefined;
111
+ if (oauth?.accessToken && typeof oauth.accessToken === 'string') {
112
+ return oauth.accessToken;
113
+ }
114
+ } catch {
115
+ // JSON might be truncated — try regex fallback
116
+ const match = raw.match(/"accessToken"\s*:\s*"([^"]+)"/);
117
+ if (match) return match[1];
118
+ }
119
+ } catch {
120
+ // Keychain not available or no entry
121
+ }
122
+
123
+ return null;
124
+ }
125
+
126
+ function tryLinuxKeyring(): string | null {
127
+ try {
128
+ // GNOME Keyring via secret-tool
129
+ const token = execFileSync(
130
+ 'secret-tool',
131
+ ['lookup', 'service', 'Claude Code', 'type', 'credentials'],
132
+ { encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe'] },
133
+ ).trim();
134
+
135
+ if (token) {
136
+ // May be JSON or raw token
137
+ try {
138
+ const parsed = JSON.parse(token) as Record<string, unknown>;
139
+ const oauth = parsed.claudeAiOauth as Record<string, unknown> | undefined;
140
+ if (oauth?.accessToken) return oauth.accessToken as string;
141
+ } catch {
142
+ // Treat as raw token
143
+ if (token.length > 20) return token;
144
+ }
145
+ }
146
+ } catch {
147
+ // secret-tool not available or no entry
148
+ }
149
+
150
+ return null;
151
+ }
package/src/llm/types.ts CHANGED
@@ -43,8 +43,10 @@ export class LLMError extends Error {
43
43
  }
44
44
 
45
45
  export interface LLMCallOptions {
46
- provider: 'openai' | 'anthropic';
47
- model: string;
46
+ /** Provider override. If omitted, the model router selects based on caller/task. */
47
+ provider?: 'openai' | 'anthropic';
48
+ /** Model override. If omitted, the model router selects based on caller/task. */
49
+ model?: string;
48
50
  systemPrompt: string;
49
51
  userPrompt: string;
50
52
  temperature?: number;
@@ -21,6 +21,7 @@ import { loadIntelligenceData } from '../intelligence/loader.js';
21
21
  import type { Vault } from '../vault/vault.js';
22
22
  import type { PluginRegistry } from '../plugins/plugin-registry.js';
23
23
  import type { PluginContext } from '../plugins/types.js';
24
+ import type { PackRuntime } from '../domain-packs/pack-runtime.js';
24
25
 
25
26
  const MANIFEST_FILENAME = 'soleri-pack.json';
26
27
 
@@ -108,7 +109,11 @@ export class PackInstaller {
108
109
  /**
109
110
  * Install a knowledge pack from a directory.
110
111
  */
111
- async install(packDir: string, runtimeCtx?: unknown): Promise<InstallResult> {
112
+ async install(
113
+ packDir: string,
114
+ runtimeCtx?: unknown,
115
+ packRuntime?: PackRuntime,
116
+ ): Promise<InstallResult> {
112
117
  // Validate first
113
118
  const validation = this.validate(packDir);
114
119
  if (!validation.valid || !validation.manifest) {
@@ -174,6 +179,16 @@ export class PackInstaller {
174
179
  }
175
180
 
176
181
  const ctx: PluginContext = {
182
+ packRuntime:
183
+ packRuntime ??
184
+ ({
185
+ vault: {},
186
+ getProject: () => undefined,
187
+ listProjects: () => [],
188
+ createCheck: () => '',
189
+ validateCheck: () => null,
190
+ validateAndConsume: () => null,
191
+ } as unknown as PackRuntime),
177
192
  runtime: runtimeCtx ?? {},
178
193
  manifest: pluginLoaded.manifest,
179
194
  directory: packDir,
@@ -6,4 +6,3 @@ export type {
6
6
  FtsSearchOptions,
7
7
  } from './types.js';
8
8
  export { SQLitePersistenceProvider } from './sqlite-provider.js';
9
- export { PostgresPersistenceProvider, translateSql } from './postgres-provider.js';
@@ -35,7 +35,7 @@ export interface PersistenceProvider {
35
35
  transaction<T>(fn: () => T): T;
36
36
 
37
37
  /** Identifies the backend engine. */
38
- readonly backend: 'sqlite' | 'postgres';
38
+ readonly backend: 'sqlite';
39
39
 
40
40
  /** Full-text search abstraction. */
41
41
  ftsSearch<T = Record<string, unknown>>(
@@ -52,12 +52,8 @@ export interface PersistenceProvider {
52
52
  }
53
53
 
54
54
  export interface PersistenceConfig {
55
- type: 'sqlite' | 'postgres';
55
+ type: 'sqlite';
56
56
  path: string;
57
- /** PostgreSQL connection string. */
58
- connectionString?: string;
59
- /** PostgreSQL pool size. */
60
- poolSize?: number;
61
57
  }
62
58
 
63
59
  export interface FtsSearchOptions {