@inixiative/foundry-core 0.2.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.
- package/package.json +1 -6
- package/src/action-prompt.ts +47 -28
- package/src/adapters/file-memory.ts +314 -122
- package/src/adapters/http-memory.ts +19 -24
- package/src/adapters/index.ts +4 -4
- package/src/adapters/markdown-docs.ts +15 -15
- package/src/adapters/sqlite-memory.ts +29 -27
- package/src/base-agent.ts +5 -7
- package/src/cache-lifecycle.ts +14 -14
- package/src/capability.ts +76 -83
- package/src/clarifier.ts +46 -0
- package/src/classifier.ts +6 -9
- package/src/context-layer.ts +125 -78
- package/src/context-stack.ts +15 -14
- package/src/credentials.ts +4 -1
- package/src/decider.ts +7 -8
- package/src/delivery-evidence.ts +206 -55
- package/src/event-stream.ts +34 -20
- package/src/executor.ts +81 -43
- package/src/harness.ts +225 -126
- package/src/hooks.ts +26 -26
- package/src/hydrator.ts +6 -8
- package/src/id.ts +3 -3
- package/src/index.ts +248 -218
- package/src/intervention.ts +21 -12
- package/src/messages.ts +73 -62
- package/src/middleware.ts +9 -19
- package/src/model-registry.ts +1587 -0
- package/src/native-evidence.ts +124 -30
- package/src/permission-middleware.ts +16 -28
- package/src/retry.ts +6 -6
- package/src/router.ts +5 -5
- package/src/signal.ts +16 -11
- package/src/thread.ts +138 -61
- package/src/token-counts.ts +9 -1
- package/src/token-tracker.ts +43 -34
- package/src/tools.ts +138 -95
- package/src/trace.ts +44 -23
- package/src/types.ts +33 -24
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { ContextSource } from
|
|
2
|
-
import type {
|
|
3
|
-
import type { Signal } from
|
|
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(
|
|
52
|
-
if (ct.includes(
|
|
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 ===
|
|
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:
|
|
66
|
-
headers: { ...this._headers,
|
|
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(
|
|
76
|
-
if (ct.includes(
|
|
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 ===
|
|
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 =
|
|
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 =
|
|
132
|
-
const http = this;
|
|
127
|
+
signalWriter(path: string = '/signals') {
|
|
133
128
|
return async (signal: Signal): Promise<void> => {
|
|
134
129
|
try {
|
|
135
|
-
await
|
|
130
|
+
await this.post(path, signal);
|
|
136
131
|
} catch (err) {
|
|
137
132
|
console.warn(`[HttpMemory] signal forwarding failed:`, (err as Error).message);
|
|
138
133
|
}
|
package/src/adapters/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { FileMemory,
|
|
2
|
-
export {
|
|
3
|
-
export {
|
|
4
|
-
export {
|
|
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 {
|
|
2
|
-
import type { ContextSource } from
|
|
3
|
-
import type {
|
|
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 =
|
|
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(
|
|
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(
|
|
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:
|
|
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(
|
|
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
|
|
2
|
-
import type { ContextSource } from
|
|
3
|
-
import type {
|
|
4
|
-
import type { Signal } from
|
|
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 =
|
|
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
|
-
[
|
|
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(
|
|
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(
|
|
75
|
-
: this.db.query(
|
|
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(
|
|
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(
|
|
100
|
-
: this.db.query(
|
|
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(
|
|
109
|
-
: this.db.query(
|
|
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:
|
|
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
|
-
|
|
155
|
+
this.write({
|
|
152
156
|
id: signal.id,
|
|
153
157
|
kind: signal.kind,
|
|
154
158
|
content:
|
|
155
|
-
typeof signal.content ===
|
|
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
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
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
|
/**
|
package/src/cache-lifecycle.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ContextLayer,
|
|
2
|
-
import { ContextStack } from
|
|
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:
|
|
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(
|
|
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:
|
|
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(
|
|
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
|
}
|