trellis 2.0.13 → 2.1.2
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/dist/cli/index.js +1 -1
- package/dist/embeddings/index.js +1 -1
- package/dist/{index-7gvjxt27.js → index-2917tjd8.js} +1 -1
- package/package.json +2 -10
- package/dist/transformers.node-bx3q9d7k.js +0 -33130
- package/src/cli/index.ts +0 -3356
- package/src/core/agents/harness.ts +0 -380
- package/src/core/agents/index.ts +0 -18
- package/src/core/agents/types.ts +0 -90
- package/src/core/index.ts +0 -118
- package/src/core/kernel/middleware.ts +0 -44
- package/src/core/kernel/trellis-kernel.ts +0 -593
- package/src/core/ontology/builtins.ts +0 -248
- package/src/core/ontology/index.ts +0 -34
- package/src/core/ontology/registry.ts +0 -209
- package/src/core/ontology/types.ts +0 -124
- package/src/core/ontology/validator.ts +0 -382
- package/src/core/persist/backend.ts +0 -74
- package/src/core/persist/sqlite-backend.ts +0 -298
- package/src/core/plugins/index.ts +0 -17
- package/src/core/plugins/registry.ts +0 -322
- package/src/core/plugins/types.ts +0 -126
- package/src/core/query/datalog.ts +0 -188
- package/src/core/query/engine.ts +0 -370
- package/src/core/query/index.ts +0 -34
- package/src/core/query/parser.ts +0 -481
- package/src/core/query/types.ts +0 -200
- package/src/core/store/eav-store.ts +0 -467
- package/src/decisions/auto-capture.ts +0 -136
- package/src/decisions/hooks.ts +0 -163
- package/src/decisions/index.ts +0 -261
- package/src/decisions/types.ts +0 -103
- package/src/embeddings/auto-embed.ts +0 -248
- package/src/embeddings/chunker.ts +0 -327
- package/src/embeddings/index.ts +0 -48
- package/src/embeddings/model.ts +0 -112
- package/src/embeddings/search.ts +0 -305
- package/src/embeddings/store.ts +0 -313
- package/src/embeddings/types.ts +0 -92
- package/src/engine.ts +0 -1125
- package/src/garden/cluster.ts +0 -330
- package/src/garden/garden.ts +0 -306
- package/src/garden/index.ts +0 -29
- package/src/git/git-exporter.ts +0 -286
- package/src/git/git-importer.ts +0 -329
- package/src/git/git-reader.ts +0 -189
- package/src/git/index.ts +0 -22
- package/src/identity/governance.ts +0 -211
- package/src/identity/identity.ts +0 -224
- package/src/identity/index.ts +0 -30
- package/src/identity/signing-middleware.ts +0 -97
- package/src/index.ts +0 -29
- package/src/links/index.ts +0 -49
- package/src/links/lifecycle.ts +0 -400
- package/src/links/parser.ts +0 -484
- package/src/links/ref-index.ts +0 -186
- package/src/links/resolver.ts +0 -314
- package/src/links/types.ts +0 -108
- package/src/mcp/index.ts +0 -22
- package/src/mcp/server.ts +0 -1278
- package/src/semantic/csharp-parser.ts +0 -493
- package/src/semantic/go-parser.ts +0 -585
- package/src/semantic/index.ts +0 -34
- package/src/semantic/java-parser.ts +0 -456
- package/src/semantic/python-parser.ts +0 -659
- package/src/semantic/ruby-parser.ts +0 -446
- package/src/semantic/rust-parser.ts +0 -784
- package/src/semantic/semantic-merge.ts +0 -210
- package/src/semantic/ts-parser.ts +0 -681
- package/src/semantic/types.ts +0 -175
- package/src/sync/http-transport.ts +0 -144
- package/src/sync/index.ts +0 -43
- package/src/sync/memory-transport.ts +0 -66
- package/src/sync/multi-repo.ts +0 -200
- package/src/sync/reconciler.ts +0 -237
- package/src/sync/sync-engine.ts +0 -258
- package/src/sync/types.ts +0 -104
- package/src/sync/ws-transport.ts +0 -145
- package/src/ui/client.html +0 -695
- package/src/ui/server.ts +0 -419
- package/src/vcs/blob-store.ts +0 -124
- package/src/vcs/branch.ts +0 -150
- package/src/vcs/checkpoint.ts +0 -64
- package/src/vcs/decompose.ts +0 -469
- package/src/vcs/diff.ts +0 -409
- package/src/vcs/engine-context.ts +0 -26
- package/src/vcs/index.ts +0 -23
- package/src/vcs/issue.ts +0 -800
- package/src/vcs/merge.ts +0 -425
- package/src/vcs/milestone.ts +0 -124
- package/src/vcs/ops.ts +0 -59
- package/src/vcs/types.ts +0 -213
- package/src/vcs/vcs-middleware.ts +0 -81
- package/src/watcher/fs-watcher.ts +0 -255
- package/src/watcher/index.ts +0 -9
- package/src/watcher/ingestion.ts +0 -116
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SQLite-backed Kernel Backend
|
|
3
|
-
*
|
|
4
|
-
* Replaces the P0 JsonOpLog with a proper WAL-mode SQLite database.
|
|
5
|
-
* Stores ops, snapshots, and blobs in a single database file.
|
|
6
|
-
*
|
|
7
|
-
* @module trellis/core
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { Database } from 'bun:sqlite';
|
|
11
|
-
import type { KernelOp, KernelBackend } from './backend.js';
|
|
12
|
-
|
|
13
|
-
// ---------------------------------------------------------------------------
|
|
14
|
-
// Schema
|
|
15
|
-
// ---------------------------------------------------------------------------
|
|
16
|
-
|
|
17
|
-
const SCHEMA_SQL = `
|
|
18
|
-
CREATE TABLE IF NOT EXISTS ops (
|
|
19
|
-
hash TEXT PRIMARY KEY,
|
|
20
|
-
kind TEXT NOT NULL,
|
|
21
|
-
timestamp TEXT NOT NULL,
|
|
22
|
-
agent_id TEXT NOT NULL,
|
|
23
|
-
previous_hash TEXT,
|
|
24
|
-
payload TEXT NOT NULL
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
CREATE TABLE IF NOT EXISTS snapshots (
|
|
28
|
-
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
29
|
-
last_op_hash TEXT NOT NULL,
|
|
30
|
-
data TEXT NOT NULL,
|
|
31
|
-
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
CREATE TABLE IF NOT EXISTS blobs (
|
|
35
|
-
hash TEXT PRIMARY KEY,
|
|
36
|
-
content BLOB NOT NULL
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
CREATE INDEX IF NOT EXISTS idx_ops_kind ON ops(kind);
|
|
40
|
-
CREATE INDEX IF NOT EXISTS idx_ops_timestamp ON ops(timestamp);
|
|
41
|
-
CREATE INDEX IF NOT EXISTS idx_ops_agent ON ops(agent_id);
|
|
42
|
-
CREATE INDEX IF NOT EXISTS idx_ops_previous ON ops(previous_hash);
|
|
43
|
-
CREATE INDEX IF NOT EXISTS idx_snapshots_op ON snapshots(last_op_hash);
|
|
44
|
-
`;
|
|
45
|
-
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
// Implementation
|
|
48
|
-
// ---------------------------------------------------------------------------
|
|
49
|
-
|
|
50
|
-
export class SqliteKernelBackend implements KernelBackend {
|
|
51
|
-
private db: Database;
|
|
52
|
-
private _stmts: {
|
|
53
|
-
insert: ReturnType<Database['prepare']>;
|
|
54
|
-
readAll: ReturnType<Database['prepare']>;
|
|
55
|
-
readUntil: ReturnType<Database['prepare']>;
|
|
56
|
-
readAfter: ReturnType<Database['prepare']>;
|
|
57
|
-
getByHash: ReturnType<Database['prepare']>;
|
|
58
|
-
getLast: ReturnType<Database['prepare']>;
|
|
59
|
-
count: ReturnType<Database['prepare']>;
|
|
60
|
-
saveSnapshot: ReturnType<Database['prepare']>;
|
|
61
|
-
loadSnapshot: ReturnType<Database['prepare']>;
|
|
62
|
-
putBlob: ReturnType<Database['prepare']>;
|
|
63
|
-
getBlob: ReturnType<Database['prepare']>;
|
|
64
|
-
hasBlob: ReturnType<Database['prepare']>;
|
|
65
|
-
} | null = null;
|
|
66
|
-
|
|
67
|
-
constructor(private dbPath: string) {
|
|
68
|
-
this.db = new Database(dbPath);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
init(): void {
|
|
72
|
-
this.db.exec('PRAGMA journal_mode=WAL;');
|
|
73
|
-
this.db.exec('PRAGMA foreign_keys=ON;');
|
|
74
|
-
this.db.exec('PRAGMA synchronous=NORMAL;');
|
|
75
|
-
this.db.exec(SCHEMA_SQL);
|
|
76
|
-
this._prepareStatements();
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
private _prepareStatements(): void {
|
|
80
|
-
this._stmts = {
|
|
81
|
-
insert: this.db.prepare(`
|
|
82
|
-
INSERT OR IGNORE INTO ops (hash, kind, timestamp, agent_id, previous_hash, payload)
|
|
83
|
-
VALUES ($hash, $kind, $timestamp, $agentId, $previousHash, $payload)
|
|
84
|
-
`),
|
|
85
|
-
readAll: this.db.prepare(`
|
|
86
|
-
SELECT hash, kind, timestamp, agent_id, previous_hash, payload
|
|
87
|
-
FROM ops ORDER BY rowid ASC
|
|
88
|
-
`),
|
|
89
|
-
readUntil: this.db.prepare(`
|
|
90
|
-
SELECT hash, kind, timestamp, agent_id, previous_hash, payload
|
|
91
|
-
FROM ops WHERE rowid <= (SELECT rowid FROM ops WHERE hash = $hash)
|
|
92
|
-
ORDER BY rowid ASC
|
|
93
|
-
`),
|
|
94
|
-
readAfter: this.db.prepare(`
|
|
95
|
-
SELECT hash, kind, timestamp, agent_id, previous_hash, payload
|
|
96
|
-
FROM ops WHERE rowid > (SELECT rowid FROM ops WHERE hash = $hash)
|
|
97
|
-
ORDER BY rowid ASC
|
|
98
|
-
`),
|
|
99
|
-
getByHash: this.db.prepare(`
|
|
100
|
-
SELECT hash, kind, timestamp, agent_id, previous_hash, payload
|
|
101
|
-
FROM ops WHERE hash = $hash
|
|
102
|
-
`),
|
|
103
|
-
getLast: this.db.prepare(`
|
|
104
|
-
SELECT hash, kind, timestamp, agent_id, previous_hash, payload
|
|
105
|
-
FROM ops ORDER BY rowid DESC LIMIT 1
|
|
106
|
-
`),
|
|
107
|
-
count: this.db.prepare('SELECT COUNT(*) as cnt FROM ops'),
|
|
108
|
-
saveSnapshot: this.db.prepare(`
|
|
109
|
-
INSERT INTO snapshots (last_op_hash, data)
|
|
110
|
-
VALUES ($lastOpHash, $data)
|
|
111
|
-
`),
|
|
112
|
-
loadSnapshot: this.db.prepare(`
|
|
113
|
-
SELECT last_op_hash, data FROM snapshots
|
|
114
|
-
ORDER BY id DESC LIMIT 1
|
|
115
|
-
`),
|
|
116
|
-
putBlob: this.db.prepare(`
|
|
117
|
-
INSERT OR IGNORE INTO blobs (hash, content) VALUES ($hash, $content)
|
|
118
|
-
`),
|
|
119
|
-
getBlob: this.db.prepare(`
|
|
120
|
-
SELECT content FROM blobs WHERE hash = $hash
|
|
121
|
-
`),
|
|
122
|
-
hasBlob: this.db.prepare(`
|
|
123
|
-
SELECT 1 FROM blobs WHERE hash = $hash
|
|
124
|
-
`),
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
// -------------------------------------------------------------------------
|
|
129
|
-
// Op operations
|
|
130
|
-
// -------------------------------------------------------------------------
|
|
131
|
-
|
|
132
|
-
append(op: KernelOp): void {
|
|
133
|
-
const payload = JSON.stringify({
|
|
134
|
-
facts: op.facts,
|
|
135
|
-
links: op.links,
|
|
136
|
-
...(op.deleteFacts?.length ? { deleteFacts: op.deleteFacts } : {}),
|
|
137
|
-
...(op.deleteLinks?.length ? { deleteLinks: op.deleteLinks } : {}),
|
|
138
|
-
...((op as any).vcs ? { vcs: (op as any).vcs } : {}),
|
|
139
|
-
...((op as any).signature ? { signature: (op as any).signature } : {}),
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
this._stmts!.insert.run({
|
|
143
|
-
$hash: op.hash,
|
|
144
|
-
$kind: op.kind,
|
|
145
|
-
$timestamp: op.timestamp,
|
|
146
|
-
$agentId: op.agentId,
|
|
147
|
-
$previousHash: op.previousHash ?? null,
|
|
148
|
-
$payload: payload,
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
appendBatch(ops: KernelOp[]): void {
|
|
153
|
-
if (ops.length === 0) return;
|
|
154
|
-
this.db.transaction(() => {
|
|
155
|
-
for (const op of ops) {
|
|
156
|
-
this.append(op);
|
|
157
|
-
}
|
|
158
|
-
})();
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
readAll(): KernelOp[] {
|
|
162
|
-
const rows = this._stmts!.readAll.all() as any[];
|
|
163
|
-
return rows.map(rowToOp);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
readUntil(hash: string): KernelOp[] {
|
|
167
|
-
const rows = this._stmts!.readUntil.all({ $hash: hash }) as any[];
|
|
168
|
-
return rows.map(rowToOp);
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
readAfter(hash: string): KernelOp[] {
|
|
172
|
-
const rows = this._stmts!.readAfter.all({ $hash: hash }) as any[];
|
|
173
|
-
return rows.map(rowToOp);
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
readUntilTimestamp(isoTimestamp: string): KernelOp[] {
|
|
177
|
-
const rows = this.db
|
|
178
|
-
.prepare(
|
|
179
|
-
`SELECT hash, kind, timestamp, agent_id, previous_hash, payload
|
|
180
|
-
FROM ops WHERE timestamp <= $ts ORDER BY rowid ASC`,
|
|
181
|
-
)
|
|
182
|
-
.all({ $ts: isoTimestamp }) as any[];
|
|
183
|
-
return rows.map(rowToOp);
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
getLastOp(): KernelOp | undefined {
|
|
187
|
-
const row = this._stmts!.getLast.get() as any;
|
|
188
|
-
return row ? rowToOp(row) : undefined;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
getOpByHash(hash: string): KernelOp | undefined {
|
|
192
|
-
const row = this._stmts!.getByHash.get({ $hash: hash }) as any;
|
|
193
|
-
return row ? rowToOp(row) : undefined;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
count(): number {
|
|
197
|
-
const row = this._stmts!.count.get() as any;
|
|
198
|
-
return row?.cnt ?? 0;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/**
|
|
202
|
-
* Find the common ancestor op of two op hashes by walking
|
|
203
|
-
* previousHash chains until they converge.
|
|
204
|
-
*/
|
|
205
|
-
findCommonAncestor(hashA: string, hashB: string): KernelOp | undefined {
|
|
206
|
-
// Collect ancestors of A
|
|
207
|
-
const ancestorsA = new Set<string>();
|
|
208
|
-
let cursor: string | undefined = hashA;
|
|
209
|
-
while (cursor) {
|
|
210
|
-
ancestorsA.add(cursor);
|
|
211
|
-
const op = this.getOpByHash(cursor);
|
|
212
|
-
cursor = op?.previousHash;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Walk B's chain until we find a common ancestor
|
|
216
|
-
cursor = hashB;
|
|
217
|
-
while (cursor) {
|
|
218
|
-
if (ancestorsA.has(cursor)) {
|
|
219
|
-
return this.getOpByHash(cursor);
|
|
220
|
-
}
|
|
221
|
-
const op = this.getOpByHash(cursor);
|
|
222
|
-
cursor = op?.previousHash;
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return undefined;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// -------------------------------------------------------------------------
|
|
229
|
-
// Snapshot operations
|
|
230
|
-
// -------------------------------------------------------------------------
|
|
231
|
-
|
|
232
|
-
saveSnapshot(lastOpHash: string, data: any): void {
|
|
233
|
-
this._stmts!.saveSnapshot.run({
|
|
234
|
-
$lastOpHash: lastOpHash,
|
|
235
|
-
$data: JSON.stringify(data),
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
loadLatestSnapshot(): { lastOpHash: string; data: any } | undefined {
|
|
240
|
-
const row = this._stmts!.loadSnapshot.get() as any;
|
|
241
|
-
if (!row) return undefined;
|
|
242
|
-
return {
|
|
243
|
-
lastOpHash: row.last_op_hash,
|
|
244
|
-
data: JSON.parse(row.data),
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// -------------------------------------------------------------------------
|
|
249
|
-
// Blob operations
|
|
250
|
-
// -------------------------------------------------------------------------
|
|
251
|
-
|
|
252
|
-
putBlob(hash: string, content: Uint8Array): void {
|
|
253
|
-
this._stmts!.putBlob.run({
|
|
254
|
-
$hash: hash,
|
|
255
|
-
$content: Buffer.from(content),
|
|
256
|
-
});
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
getBlob(hash: string): Uint8Array | undefined {
|
|
260
|
-
const row = this._stmts!.getBlob.get({ $hash: hash }) as any;
|
|
261
|
-
if (!row) return undefined;
|
|
262
|
-
return new Uint8Array(row.content);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
hasBlob(hash: string): boolean {
|
|
266
|
-
return !!this._stmts!.hasBlob.get({ $hash: hash });
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// -------------------------------------------------------------------------
|
|
270
|
-
// Lifecycle
|
|
271
|
-
// -------------------------------------------------------------------------
|
|
272
|
-
|
|
273
|
-
close(): void {
|
|
274
|
-
this.db.close();
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
// ---------------------------------------------------------------------------
|
|
279
|
-
// Helpers
|
|
280
|
-
// ---------------------------------------------------------------------------
|
|
281
|
-
|
|
282
|
-
function rowToOp(row: any): KernelOp {
|
|
283
|
-
const payload = JSON.parse(row.payload);
|
|
284
|
-
const op: any = {
|
|
285
|
-
hash: row.hash,
|
|
286
|
-
kind: row.kind,
|
|
287
|
-
timestamp: row.timestamp,
|
|
288
|
-
agentId: row.agent_id,
|
|
289
|
-
};
|
|
290
|
-
if (row.previous_hash) op.previousHash = row.previous_hash;
|
|
291
|
-
if (payload.facts) op.facts = payload.facts;
|
|
292
|
-
if (payload.links) op.links = payload.links;
|
|
293
|
-
if (payload.deleteFacts) op.deleteFacts = payload.deleteFacts;
|
|
294
|
-
if (payload.deleteLinks) op.deleteLinks = payload.deleteLinks;
|
|
295
|
-
if (payload.vcs) op.vcs = payload.vcs;
|
|
296
|
-
if (payload.signature) op.signature = payload.signature;
|
|
297
|
-
return op;
|
|
298
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugin System — Public API Surface
|
|
3
|
-
*
|
|
4
|
-
* @module trellis/core/plugins
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
export { PluginRegistry, EventBus } from './registry.js';
|
|
8
|
-
|
|
9
|
-
export type {
|
|
10
|
-
PluginDef,
|
|
11
|
-
PluginContext,
|
|
12
|
-
PluginManifest,
|
|
13
|
-
EventCallback,
|
|
14
|
-
EventHandler,
|
|
15
|
-
WellKnownEvent,
|
|
16
|
-
WorkspaceConfig,
|
|
17
|
-
} from './types.js';
|
|
@@ -1,322 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Plugin Registry — Load, register, and manage plugins.
|
|
3
|
-
*
|
|
4
|
-
* Handles plugin lifecycle, dependency resolution, event dispatching,
|
|
5
|
-
* and workspace configuration.
|
|
6
|
-
*
|
|
7
|
-
* @module trellis/core/plugins
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import type { TrellisKernel } from '../kernel/trellis-kernel.js';
|
|
11
|
-
import type { OntologyRegistry } from '../ontology/registry.js';
|
|
12
|
-
import type { QueryEngine } from '../query/engine.js';
|
|
13
|
-
import type {
|
|
14
|
-
PluginDef,
|
|
15
|
-
PluginContext,
|
|
16
|
-
PluginManifest,
|
|
17
|
-
EventCallback,
|
|
18
|
-
EventHandler,
|
|
19
|
-
WorkspaceConfig,
|
|
20
|
-
} from './types.js';
|
|
21
|
-
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// Event Bus
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
export class EventBus {
|
|
27
|
-
private handlers: Map<string, Set<EventCallback>> = new Map();
|
|
28
|
-
|
|
29
|
-
on(event: string, handler: EventCallback): void {
|
|
30
|
-
const set = this.handlers.get(event) ?? new Set();
|
|
31
|
-
set.add(handler);
|
|
32
|
-
this.handlers.set(event, set);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
off(event: string, handler: EventCallback): void {
|
|
36
|
-
const set = this.handlers.get(event);
|
|
37
|
-
if (set) {
|
|
38
|
-
set.delete(handler);
|
|
39
|
-
if (set.size === 0) this.handlers.delete(event);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async emit(event: string, data?: unknown): Promise<void> {
|
|
44
|
-
// Exact match
|
|
45
|
-
const exact = this.handlers.get(event);
|
|
46
|
-
if (exact) {
|
|
47
|
-
for (const h of exact) await h(data);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Wildcard match (e.g. "op:*" matches "op:applied")
|
|
51
|
-
for (const [pattern, handlers] of this.handlers) {
|
|
52
|
-
if (pattern === event) continue; // Already handled
|
|
53
|
-
if (pattern.endsWith('*') && event.startsWith(pattern.slice(0, -1))) {
|
|
54
|
-
for (const h of handlers) await h(data);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
listEvents(): string[] {
|
|
60
|
-
return [...this.handlers.keys()];
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
clear(): void {
|
|
64
|
-
this.handlers.clear();
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// ---------------------------------------------------------------------------
|
|
69
|
-
// Plugin Registry
|
|
70
|
-
// ---------------------------------------------------------------------------
|
|
71
|
-
|
|
72
|
-
export class PluginRegistry {
|
|
73
|
-
private plugins: Map<string, { def: PluginDef; loaded: boolean }> = new Map();
|
|
74
|
-
private eventBus: EventBus = new EventBus();
|
|
75
|
-
private workspaceConfig: WorkspaceConfig = {};
|
|
76
|
-
private logs: Array<{ pluginId: string; message: string; timestamp: string }> = [];
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Register a plugin definition. Does not load it yet.
|
|
80
|
-
*/
|
|
81
|
-
register(def: PluginDef): void {
|
|
82
|
-
if (this.plugins.has(def.id)) {
|
|
83
|
-
throw new Error(`Plugin "${def.id}" is already registered.`);
|
|
84
|
-
}
|
|
85
|
-
this.plugins.set(def.id, { def, loaded: false });
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Unregister a plugin. Unloads it first if loaded.
|
|
90
|
-
*/
|
|
91
|
-
async unregister(id: string): Promise<void> {
|
|
92
|
-
const entry = this.plugins.get(id);
|
|
93
|
-
if (!entry) return;
|
|
94
|
-
if (entry.loaded) await this.unload(id);
|
|
95
|
-
this.plugins.delete(id);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Load a plugin (call onLoad, register middleware/ontologies/rules/events).
|
|
100
|
-
* Resolves dependencies first.
|
|
101
|
-
*/
|
|
102
|
-
async load(
|
|
103
|
-
id: string,
|
|
104
|
-
kernel?: TrellisKernel,
|
|
105
|
-
ontologyRegistry?: OntologyRegistry,
|
|
106
|
-
queryEngine?: QueryEngine,
|
|
107
|
-
): Promise<void> {
|
|
108
|
-
const entry = this.plugins.get(id);
|
|
109
|
-
if (!entry) throw new Error(`Plugin "${id}" is not registered.`);
|
|
110
|
-
if (entry.loaded) return;
|
|
111
|
-
|
|
112
|
-
// Check dependencies
|
|
113
|
-
if (entry.def.dependencies) {
|
|
114
|
-
for (const dep of entry.def.dependencies) {
|
|
115
|
-
const depEntry = this.plugins.get(dep);
|
|
116
|
-
if (!depEntry) {
|
|
117
|
-
throw new Error(`Plugin "${id}" depends on "${dep}" which is not registered.`);
|
|
118
|
-
}
|
|
119
|
-
if (!depEntry.loaded) {
|
|
120
|
-
await this.load(dep, kernel, ontologyRegistry, queryEngine);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Register middleware
|
|
126
|
-
if (entry.def.middleware && kernel) {
|
|
127
|
-
for (const mw of entry.def.middleware) {
|
|
128
|
-
kernel.addMiddleware(mw);
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Register ontologies
|
|
133
|
-
if (entry.def.ontologies && ontologyRegistry) {
|
|
134
|
-
for (const schema of entry.def.ontologies) {
|
|
135
|
-
try { ontologyRegistry.register(schema); } catch {}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Register rules
|
|
140
|
-
if (entry.def.rules && queryEngine) {
|
|
141
|
-
for (const rule of entry.def.rules) {
|
|
142
|
-
queryEngine.addRule(rule);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
// Register event handlers
|
|
147
|
-
if (entry.def.eventHandlers) {
|
|
148
|
-
for (const eh of entry.def.eventHandlers) {
|
|
149
|
-
this.eventBus.on(eh.event, eh.handler);
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Build plugin context
|
|
154
|
-
const ctx = this._buildContext(id);
|
|
155
|
-
|
|
156
|
-
// Call onLoad
|
|
157
|
-
if (entry.def.onLoad) {
|
|
158
|
-
await entry.def.onLoad(ctx);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
entry.loaded = true;
|
|
162
|
-
await this.eventBus.emit('plugin:loaded', { pluginId: id });
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Unload a plugin (call onUnload, remove middleware/events).
|
|
167
|
-
*/
|
|
168
|
-
async unload(id: string): Promise<void> {
|
|
169
|
-
const entry = this.plugins.get(id);
|
|
170
|
-
if (!entry || !entry.loaded) return;
|
|
171
|
-
|
|
172
|
-
const ctx = this._buildContext(id);
|
|
173
|
-
if (entry.def.onUnload) {
|
|
174
|
-
await entry.def.onUnload(ctx);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Remove event handlers
|
|
178
|
-
if (entry.def.eventHandlers) {
|
|
179
|
-
for (const eh of entry.def.eventHandlers) {
|
|
180
|
-
this.eventBus.off(eh.event, eh.handler);
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
entry.loaded = false;
|
|
185
|
-
await this.eventBus.emit('plugin:unloaded', { pluginId: id });
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Load all registered plugins in dependency order.
|
|
190
|
-
*/
|
|
191
|
-
async loadAll(
|
|
192
|
-
kernel?: TrellisKernel,
|
|
193
|
-
ontologyRegistry?: OntologyRegistry,
|
|
194
|
-
queryEngine?: QueryEngine,
|
|
195
|
-
): Promise<void> {
|
|
196
|
-
const order = this._resolveDependencyOrder();
|
|
197
|
-
for (const id of order) {
|
|
198
|
-
await this.load(id, kernel, ontologyRegistry, queryEngine);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* Unload all plugins in reverse order.
|
|
204
|
-
*/
|
|
205
|
-
async unloadAll(): Promise<void> {
|
|
206
|
-
const order = this._resolveDependencyOrder().reverse();
|
|
207
|
-
for (const id of order) {
|
|
208
|
-
await this.unload(id);
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// -------------------------------------------------------------------------
|
|
213
|
-
// Queries
|
|
214
|
-
// -------------------------------------------------------------------------
|
|
215
|
-
|
|
216
|
-
get(id: string): PluginDef | undefined {
|
|
217
|
-
return this.plugins.get(id)?.def;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
isLoaded(id: string): boolean {
|
|
221
|
-
return this.plugins.get(id)?.loaded ?? false;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
list(): Array<{ def: PluginDef; loaded: boolean }> {
|
|
225
|
-
return [...this.plugins.values()];
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
listLoaded(): PluginDef[] {
|
|
229
|
-
return [...this.plugins.values()]
|
|
230
|
-
.filter((e) => e.loaded)
|
|
231
|
-
.map((e) => e.def);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// -------------------------------------------------------------------------
|
|
235
|
-
// Event bus access
|
|
236
|
-
// -------------------------------------------------------------------------
|
|
237
|
-
|
|
238
|
-
getEventBus(): EventBus {
|
|
239
|
-
return this.eventBus;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
async emit(event: string, data?: unknown): Promise<void> {
|
|
243
|
-
await this.eventBus.emit(event, data);
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
on(event: string, handler: EventCallback): void {
|
|
247
|
-
this.eventBus.on(event, handler);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
// -------------------------------------------------------------------------
|
|
251
|
-
// Workspace config
|
|
252
|
-
// -------------------------------------------------------------------------
|
|
253
|
-
|
|
254
|
-
getWorkspaceConfig(): WorkspaceConfig {
|
|
255
|
-
return this.workspaceConfig;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
setWorkspaceConfig(config: WorkspaceConfig): void {
|
|
259
|
-
this.workspaceConfig = config;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
getConfigValue(key: string): unknown {
|
|
263
|
-
return this.workspaceConfig.settings?.[key];
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
setConfigValue(key: string, value: unknown): void {
|
|
267
|
-
if (!this.workspaceConfig.settings) this.workspaceConfig.settings = {};
|
|
268
|
-
this.workspaceConfig.settings[key] = value;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// -------------------------------------------------------------------------
|
|
272
|
-
// Logs
|
|
273
|
-
// -------------------------------------------------------------------------
|
|
274
|
-
|
|
275
|
-
getLogs(pluginId?: string): typeof this.logs {
|
|
276
|
-
if (pluginId) return this.logs.filter((l) => l.pluginId === pluginId);
|
|
277
|
-
return [...this.logs];
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// -------------------------------------------------------------------------
|
|
281
|
-
// Internal
|
|
282
|
-
// -------------------------------------------------------------------------
|
|
283
|
-
|
|
284
|
-
private _buildContext(pluginId: string): PluginContext {
|
|
285
|
-
return {
|
|
286
|
-
pluginId,
|
|
287
|
-
on: (event, handler) => this.eventBus.on(event, handler),
|
|
288
|
-
emit: (event, data) => { this.eventBus.emit(event, data); },
|
|
289
|
-
getConfig: (key) => this.getConfigValue(key),
|
|
290
|
-
log: (message) => {
|
|
291
|
-
this.logs.push({ pluginId, message, timestamp: new Date().toISOString() });
|
|
292
|
-
},
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
private _resolveDependencyOrder(): string[] {
|
|
297
|
-
const visited = new Set<string>();
|
|
298
|
-
const order: string[] = [];
|
|
299
|
-
|
|
300
|
-
const visit = (id: string, stack: Set<string>) => {
|
|
301
|
-
if (visited.has(id)) return;
|
|
302
|
-
if (stack.has(id)) throw new Error(`Circular dependency detected: ${[...stack, id].join(' → ')}`);
|
|
303
|
-
|
|
304
|
-
stack.add(id);
|
|
305
|
-
const entry = this.plugins.get(id);
|
|
306
|
-
if (entry?.def.dependencies) {
|
|
307
|
-
for (const dep of entry.def.dependencies) {
|
|
308
|
-
visit(dep, stack);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
stack.delete(id);
|
|
312
|
-
visited.add(id);
|
|
313
|
-
order.push(id);
|
|
314
|
-
};
|
|
315
|
-
|
|
316
|
-
for (const id of this.plugins.keys()) {
|
|
317
|
-
visit(id, new Set());
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
return order;
|
|
321
|
-
}
|
|
322
|
-
}
|