@inixiative/foundry-core 0.1.0 → 0.3.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.
@@ -1,6 +1,6 @@
1
- import type { ContextSource } from "../context-layer";
2
- import type { HydrationAdapter, ContextRef } from "../hydrator";
3
- import type { Signal } from "../signal";
1
+ import type { ContextSource } from '../context-layer';
2
+ import type { ContextRef, HydrationAdapter } from '../hydrator';
3
+ import type { Signal } from '../signal';
4
4
 
5
5
  /**
6
6
  * HTTP/REST adapter.
@@ -23,9 +23,9 @@ export class HttpMemory {
23
23
  opts?: {
24
24
  headers?: Record<string, string>;
25
25
  timeout?: number;
26
- }
26
+ },
27
27
  ) {
28
- this.baseUrl = baseUrl.replace(/\/$/, "");
28
+ this.baseUrl = baseUrl.replace(/\/$/, '');
29
29
  this._headers = opts?.headers ?? {};
30
30
  this._timeout = opts?.timeout ?? 10_000;
31
31
  }
@@ -48,10 +48,10 @@ export class HttpMemory {
48
48
  throw new Error(`HTTP ${res.status}: ${res.statusText} (${url.pathname})`);
49
49
  }
50
50
 
51
- const ct = res.headers.get("content-type") ?? "";
52
- if (ct.includes("application/json")) {
51
+ const ct = res.headers.get('content-type') ?? '';
52
+ if (ct.includes('application/json')) {
53
53
  const json = await res.json();
54
- return typeof json === "string" ? json : JSON.stringify(json, null, 2);
54
+ return typeof json === 'string' ? json : JSON.stringify(json, null, 2);
55
55
  }
56
56
 
57
57
  return res.text();
@@ -62,8 +62,8 @@ export class HttpMemory {
62
62
  const url = new URL(path, this.baseUrl);
63
63
 
64
64
  const res = await fetch(url.toString(), {
65
- method: "POST",
66
- headers: { ...this._headers, "Content-Type": "application/json" },
65
+ method: 'POST',
66
+ headers: { ...this._headers, 'Content-Type': 'application/json' },
67
67
  body: JSON.stringify(body),
68
68
  signal: AbortSignal.timeout(this._timeout),
69
69
  });
@@ -72,10 +72,10 @@ export class HttpMemory {
72
72
  throw new Error(`HTTP ${res.status}: ${res.statusText} (${url.pathname})`);
73
73
  }
74
74
 
75
- const ct = res.headers.get("content-type") ?? "";
76
- if (ct.includes("application/json")) {
75
+ const ct = res.headers.get('content-type') ?? '';
76
+ if (ct.includes('application/json')) {
77
77
  const json = await res.json();
78
- return typeof json === "string" ? json : JSON.stringify(json, null, 2);
78
+ return typeof json === 'string' ? json : JSON.stringify(json, null, 2);
79
79
  }
80
80
 
81
81
  return res.text();
@@ -85,11 +85,7 @@ export class HttpMemory {
85
85
  * Create a ContextSource that fetches from a GET endpoint.
86
86
  * The response body becomes the context content.
87
87
  */
88
- asSource(
89
- id: string,
90
- path: string,
91
- params?: Record<string, string>
92
- ): ContextSource {
88
+ asSource(id: string, path: string, params?: Record<string, string>): ContextSource {
93
89
  const http = this;
94
90
  return {
95
91
  id,
@@ -98,7 +94,7 @@ export class HttpMemory {
98
94
  return await http.get(path, params);
99
95
  } catch (err) {
100
96
  console.warn(`[HttpMemory] source load failed for "${id}":`, (err as Error).message);
101
- return "";
97
+ return '';
102
98
  }
103
99
  },
104
100
  };
@@ -108,7 +104,7 @@ export class HttpMemory {
108
104
  * Create a HydrationAdapter.
109
105
  * Refs use paths as locators, fetched via GET.
110
106
  */
111
- asAdapter(system: string = "http"): HydrationAdapter {
107
+ asAdapter(system: string = 'http'): HydrationAdapter {
112
108
  const http = this;
113
109
  return {
114
110
  system,
@@ -118,7 +114,7 @@ export class HttpMemory {
118
114
  return await http.get(ref.locator, params);
119
115
  } catch (err) {
120
116
  console.warn(`[HttpMemory] hydrate failed for "${ref.locator}":`, (err as Error).message);
121
- return "";
117
+ return '';
122
118
  }
123
119
  },
124
120
  };
@@ -128,11 +124,10 @@ export class HttpMemory {
128
124
  * Create a signal writer that POSTs signals to an endpoint.
129
125
  * Use this to forward signals to an external system.
130
126
  */
131
- signalWriter(path: string = "/signals") {
132
- const http = this;
127
+ signalWriter(path: string = '/signals') {
133
128
  return async (signal: Signal): Promise<void> => {
134
129
  try {
135
- await http.post(path, signal);
130
+ await this.post(path, signal);
136
131
  } catch (err) {
137
132
  console.warn(`[HttpMemory] signal forwarding failed:`, (err as Error).message);
138
133
  }
@@ -1,4 +1,4 @@
1
- export { FileMemory, inlineSource, fileSource, type MemoryEntry } from "./file-memory";
2
- export { SqliteMemory, type SqliteEntry } from "./sqlite-memory";
3
- export { HttpMemory } from "./http-memory";
4
- export { MarkdownDocs, claudemdSource } from "./markdown-docs";
1
+ export { FileMemory, fileSource, inlineSource, type MemoryEntry } from './file-memory';
2
+ export { HttpMemory } from './http-memory';
3
+ export { claudemdSource, MarkdownDocs } from './markdown-docs';
4
+ export { type SqliteEntry, SqliteMemory } from './sqlite-memory';
@@ -1,6 +1,6 @@
1
- import { join, relative, basename, extname } from "node:path";
2
- import type { ContextSource } from "../context-layer";
3
- import type { HydrationAdapter, ContextRef } from "../hydrator";
1
+ import { basename, extname, join, relative } from 'node:path';
2
+ import type { ContextSource } from '../context-layer';
3
+ import type { ContextRef, HydrationAdapter } from '../hydrator';
4
4
 
5
5
  /**
6
6
  * Markdown directory adapter.
@@ -22,7 +22,7 @@ export class MarkdownDocs {
22
22
  * @param dir — root directory to scan
23
23
  * @param glob — file pattern (default: all .md files recursively)
24
24
  */
25
- constructor(dir: string, glob: string = "**/*.md") {
25
+ constructor(dir: string, glob: string = '**/*.md') {
26
26
  this.dir = dir;
27
27
  this.glob = glob;
28
28
  }
@@ -55,7 +55,7 @@ export class MarkdownDocs {
55
55
  async readFile(relPath: string): Promise<string> {
56
56
  const absPath = join(this.dir, relPath);
57
57
  const file = Bun.file(absPath);
58
- if (!(await file.exists())) return "";
58
+ if (!(await file.exists())) return '';
59
59
  return file.text();
60
60
  }
61
61
 
@@ -79,13 +79,13 @@ export class MarkdownDocs {
79
79
  id,
80
80
  async load() {
81
81
  const files = await docs.load();
82
- if (files.size === 0) return "";
82
+ if (files.size === 0) return '';
83
83
 
84
84
  const parts: string[] = [];
85
85
  for (const [path, content] of files) {
86
86
  parts.push(`## ${path}\n\n${content}`);
87
87
  }
88
- return parts.join("\n\n---\n\n");
88
+ return parts.join('\n\n---\n\n');
89
89
  },
90
90
  };
91
91
  }
@@ -130,25 +130,25 @@ export class MarkdownDocs {
130
130
  id,
131
131
  async load() {
132
132
  const files = await docs.load();
133
- if (files.size === 0) return "";
133
+ if (files.size === 0) return '';
134
134
 
135
135
  const lines: string[] = [];
136
136
  const paths = [...files.keys()].sort();
137
137
 
138
138
  for (const path of paths) {
139
- const content = files.get(path) ?? "";
139
+ const content = files.get(path) ?? '';
140
140
  if (!content.trim()) continue;
141
141
 
142
142
  const { title, headings } = extractHeadings(content, maxHeadings);
143
143
  const displayTitle = title ?? basename(path, extname(path));
144
144
  lines.push(`${path} — ${displayTitle}`);
145
145
  if (headings.length > 0) {
146
- lines.push(` H2: ${headings.join(", ")}`);
146
+ lines.push(` H2: ${headings.join(', ')}`);
147
147
  }
148
- lines.push("");
148
+ lines.push('');
149
149
  }
150
150
 
151
- return lines.join("\n").trimEnd();
151
+ return lines.join('\n').trimEnd();
152
152
  },
153
153
  };
154
154
  }
@@ -157,7 +157,7 @@ export class MarkdownDocs {
157
157
  asAdapter(): HydrationAdapter {
158
158
  const docs = this;
159
159
  return {
160
- system: "markdown",
160
+ system: 'markdown',
161
161
  async hydrate(ref: ContextRef): Promise<string> {
162
162
  return docs.readFile(ref.locator);
163
163
  },
@@ -178,7 +178,7 @@ function extractHeadings(
178
178
  ): { title: string | null; headings: string[] } {
179
179
  let title: string | null = null;
180
180
  const headings: string[] = [];
181
- const lines = content.split("\n");
181
+ const lines = content.split('\n');
182
182
 
183
183
  for (const line of lines) {
184
184
  if (title === null) {
@@ -206,7 +206,7 @@ export function claudemdSource(id: string, path: string): ContextSource {
206
206
  id,
207
207
  async load() {
208
208
  const file = Bun.file(path);
209
- if (!(await file.exists())) return "";
209
+ if (!(await file.exists())) return '';
210
210
  return file.text();
211
211
  },
212
212
  };
@@ -1,7 +1,7 @@
1
- import { Database } from "bun:sqlite";
2
- import type { ContextSource } from "../context-layer";
3
- import type { HydrationAdapter, ContextRef } from "../hydrator";
4
- import type { Signal } from "../signal";
1
+ import { Database } from 'bun:sqlite';
2
+ import type { ContextSource } from '../context-layer';
3
+ import type { ContextRef, HydrationAdapter } from '../hydrator';
4
+ import type { Signal } from '../signal';
5
5
 
6
6
  /**
7
7
  * SQLite-backed memory system.
@@ -12,7 +12,7 @@ import type { Signal } from "../signal";
12
12
  export class SqliteMemory {
13
13
  readonly db: Database;
14
14
 
15
- constructor(path: string = ":memory:") {
15
+ constructor(path: string = ':memory:') {
16
16
  this.db = new Database(path, { create: true });
17
17
  this._init();
18
18
  }
@@ -58,21 +58,28 @@ export class SqliteMemory {
58
58
  write(entry: SqliteEntry): void {
59
59
  this.db.run(
60
60
  `INSERT OR REPLACE INTO entries (id, kind, content, source, timestamp, meta) VALUES (?, ?, ?, ?, ?, ?)`,
61
- [entry.id, entry.kind, entry.content, entry.source ?? null, entry.timestamp, entry.meta ? JSON.stringify(entry.meta) : null]
61
+ [
62
+ entry.id,
63
+ entry.kind,
64
+ entry.content,
65
+ entry.source ?? null,
66
+ entry.timestamp,
67
+ entry.meta ? JSON.stringify(entry.meta) : null,
68
+ ],
62
69
  );
63
70
  }
64
71
 
65
72
  /** Read by id. */
66
73
  get(id: string): SqliteEntry | undefined {
67
- const row = this.db.query("SELECT * FROM entries WHERE id = ?").get(id) as any;
74
+ const row = this.db.query('SELECT * FROM entries WHERE id = ?').get(id) as any;
68
75
  return row ? this._rowToEntry(row) : undefined;
69
76
  }
70
77
 
71
78
  /** Get all entries, optionally filtered by kind. */
72
79
  all(kind?: string): SqliteEntry[] {
73
80
  const query = kind
74
- ? this.db.query("SELECT * FROM entries WHERE kind = ? ORDER BY timestamp DESC")
75
- : this.db.query("SELECT * FROM entries ORDER BY timestamp DESC");
81
+ ? this.db.query('SELECT * FROM entries WHERE kind = ? ORDER BY timestamp DESC')
82
+ : this.db.query('SELECT * FROM entries ORDER BY timestamp DESC');
76
83
  const rows = kind ? query.all(kind) : query.all();
77
84
  return (rows as any[]).map(this._rowToEntry);
78
85
  }
@@ -81,7 +88,7 @@ export class SqliteMemory {
81
88
  search(query: string, limit: number = 20): SqliteEntry[] {
82
89
  const rows = this.db
83
90
  .query(
84
- `SELECT entries.* FROM entries_fts JOIN entries ON entries_fts.id = entries.id WHERE entries_fts MATCH ? ORDER BY rank LIMIT ?`
91
+ `SELECT entries.* FROM entries_fts JOIN entries ON entries_fts.id = entries.id WHERE entries_fts MATCH ? ORDER BY rank LIMIT ?`,
85
92
  )
86
93
  .all(query, limit);
87
94
  return (rows as any[]).map(this._rowToEntry);
@@ -89,15 +96,15 @@ export class SqliteMemory {
89
96
 
90
97
  /** Delete by id. */
91
98
  delete(id: string): boolean {
92
- const result = this.db.run("DELETE FROM entries WHERE id = ?", [id]);
99
+ const result = this.db.run('DELETE FROM entries WHERE id = ?', [id]);
93
100
  return result.changes > 0;
94
101
  }
95
102
 
96
103
  /** Count entries, optionally by kind. */
97
104
  count(kind?: string): number {
98
105
  const query = kind
99
- ? this.db.query("SELECT COUNT(*) as c FROM entries WHERE kind = ?")
100
- : this.db.query("SELECT COUNT(*) as c FROM entries");
106
+ ? this.db.query('SELECT COUNT(*) as c FROM entries WHERE kind = ?')
107
+ : this.db.query('SELECT COUNT(*) as c FROM entries');
101
108
  const row = (kind ? query.get(kind) : query.get()) as any;
102
109
  return row.c;
103
110
  }
@@ -105,8 +112,8 @@ export class SqliteMemory {
105
112
  /** Recent entries. */
106
113
  recent(limit: number = 50, kind?: string): SqliteEntry[] {
107
114
  const query = kind
108
- ? this.db.query("SELECT * FROM entries WHERE kind = ? ORDER BY timestamp DESC LIMIT ?")
109
- : this.db.query("SELECT * FROM entries ORDER BY timestamp DESC LIMIT ?");
115
+ ? this.db.query('SELECT * FROM entries WHERE kind = ? ORDER BY timestamp DESC LIMIT ?')
116
+ : this.db.query('SELECT * FROM entries ORDER BY timestamp DESC LIMIT ?');
110
117
  const rows = kind ? query.all(kind, limit) : query.all(limit);
111
118
  return (rows as any[]).map(this._rowToEntry);
112
119
  }
@@ -118,10 +125,8 @@ export class SqliteMemory {
118
125
  id,
119
126
  async load() {
120
127
  const entries = mem.recent(limit, kind);
121
- if (entries.length === 0) return "";
122
- return entries
123
- .map((e) => `[${e.kind}] ${e.id}: ${e.content}`)
124
- .join("\n");
128
+ if (entries.length === 0) return '';
129
+ return entries.map((e) => `[${e.kind}] ${e.id}: ${e.content}`).join('\n');
125
130
  },
126
131
  };
127
132
  }
@@ -130,15 +135,15 @@ export class SqliteMemory {
130
135
  asAdapter(): HydrationAdapter {
131
136
  const mem = this;
132
137
  return {
133
- system: "sqlite",
138
+ system: 'sqlite',
134
139
  async hydrate(ref: ContextRef): Promise<string> {
135
140
  const entry = mem.get(ref.locator);
136
- return entry ? entry.content : "";
141
+ return entry ? entry.content : '';
137
142
  },
138
143
  async hydrateBatch(refs: ContextRef[]): Promise<string[]> {
139
144
  return refs.map((r) => {
140
145
  const entry = mem.get(r.locator);
141
- return entry ? entry.content : "";
146
+ return entry ? entry.content : '';
142
147
  });
143
148
  },
144
149
  };
@@ -146,15 +151,12 @@ export class SqliteMemory {
146
151
 
147
152
  /** Signal handler that writes signals to SQLite. */
148
153
  signalWriter() {
149
- const mem = this;
150
154
  return async (signal: Signal): Promise<void> => {
151
- mem.write({
155
+ this.write({
152
156
  id: signal.id,
153
157
  kind: signal.kind,
154
158
  content:
155
- typeof signal.content === "string"
156
- ? signal.content
157
- : JSON.stringify(signal.content),
159
+ typeof signal.content === 'string' ? signal.content : JSON.stringify(signal.content),
158
160
  source: signal.source,
159
161
  timestamp: signal.timestamp,
160
162
  meta: { confidence: signal.confidence, refs: signal.refs },
package/src/base-agent.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { TokenCounts } from "./token-counts";
2
- import { computeHash } from "./context-layer";
3
- import { ContextStack, type LayerFilter, type AssembledContext } from "./context-stack";
1
+ import { computeHash } from './context-layer';
2
+ import type { AssembledContext, ContextStack, LayerFilter } from './context-stack';
3
+ import type { TokenCounts } from './token-counts';
4
4
 
5
5
  export interface ExecutionResult<T = unknown> {
6
6
  readonly output: T;
@@ -72,9 +72,7 @@ export abstract class BaseAgent<TPayload = unknown, TResult = unknown> {
72
72
  }
73
73
 
74
74
  getContext(): string {
75
- return this._layerFilter
76
- ? this._stack.slice(this._layerFilter)
77
- : this._stack.merge();
75
+ return this._layerFilter ? this._stack.slice(this._layerFilter) : this._stack.merge();
78
76
  }
79
77
 
80
78
  /**
@@ -99,7 +97,7 @@ export abstract class BaseAgent<TPayload = unknown, TResult = unknown> {
99
97
  abstract run(
100
98
  payload: TPayload,
101
99
  filterOverride?: LayerFilter,
102
- meta?: Record<string, unknown>
100
+ meta?: Record<string, unknown>,
103
101
  ): Promise<ExecutionResult<TResult>>;
104
102
 
105
103
  /**
@@ -1,5 +1,5 @@
1
- import { ContextLayer, type LayerState } from "./context-layer";
2
- import { ContextStack } from "./context-stack";
1
+ import type { ContextLayer, LayerState } from './context-layer';
2
+ import type { ContextStack } from './context-stack';
3
3
 
4
4
  export interface LifecycleEvent {
5
5
  readonly type: string;
@@ -8,19 +8,13 @@ export interface LifecycleEvent {
8
8
  readonly meta?: Record<string, unknown>;
9
9
  }
10
10
 
11
- export type LifecycleHandler = (
12
- event: LifecycleEvent
13
- ) => void | false | Promise<void | false>;
11
+ export type LifecycleHandler = (event: LifecycleEvent) => void | false | Promise<void | false>;
14
12
 
15
13
  export interface LifecycleRule {
16
14
  readonly id: string;
17
15
  readonly triggers: LayerState[];
18
16
  readonly layerIds?: string[];
19
- action(
20
- layer: ContextLayer,
21
- state: LayerState,
22
- stack: ContextStack
23
- ): Promise<void>;
17
+ action(layer: ContextLayer, state: LayerState, stack: ContextStack): Promise<void>;
24
18
  }
25
19
 
26
20
  /**
@@ -156,26 +150,32 @@ export class CacheLifecycle {
156
150
  // Best-effort error event — if this also throws, outer catch handles it
157
151
  try {
158
152
  await this.emit({
159
- type: "rule:error",
153
+ type: 'rule:error',
160
154
  layerId: layer.id,
161
155
  timestamp: Date.now(),
162
156
  meta: { ruleId: rule.id, error: err },
163
157
  });
164
158
  } catch (emitErr) {
165
- console.warn(`[CacheLifecycle] failed to emit rule:error for ${layer.id}:`, (emitErr as Error).message);
159
+ console.warn(
160
+ `[CacheLifecycle] failed to emit rule:error for ${layer.id}:`,
161
+ (emitErr as Error).message,
162
+ );
166
163
  }
167
164
  }
168
165
  }
169
166
  } catch (err) {
170
167
  try {
171
168
  await this.emit({
172
- type: "lifecycle:error",
169
+ type: 'lifecycle:error',
173
170
  layerId: layer.id,
174
171
  timestamp: Date.now(),
175
172
  meta: { error: err },
176
173
  });
177
174
  } catch (emitErr) {
178
- console.warn(`[CacheLifecycle] failed to emit lifecycle:error for ${layer.id}:`, (emitErr as Error).message);
175
+ console.warn(
176
+ `[CacheLifecycle] failed to emit lifecycle:error for ${layer.id}:`,
177
+ (emitErr as Error).message,
178
+ );
179
179
  }
180
180
  }
181
181
  }