refactor: extract runtime Core into scripts/core.ts; runtime.mjs becomes a thin shell
Core exposes buildIndex/searchText/executeQuery/executeAttune as the single shared implementation for all transports. First TypeScript module, run via Node type stripping in dev. Adds typescript-eslint. lint/typecheck green, test 107/107.
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
// Obelisk Core (see docs/adr/0003-core-typescript-esm-precompiled.md).
|
||||
//
|
||||
// The single shared implementation behind every transport. runtime.mjs (skill),
|
||||
// and later the CLI and MCP server, are thin shells over these four functions;
|
||||
// none of them re-implement retrieval or own the DB lifecycle.
|
||||
//
|
||||
// Authored in TypeScript with erasable-only syntax so Node can run it directly
|
||||
// via type stripping in development, while the skill artifact ships the tsc
|
||||
// output (Phase 6). The heavy internals (db/indexer/query) remain .mjs for now
|
||||
// and are migrated in later phases; Core is the typed seam over them.
|
||||
|
||||
import { createContext, runInNewContext } from 'node:vm';
|
||||
|
||||
import { DB_PATH, openDb } from './db.mjs';
|
||||
import { buildIndex } from './indexer.mjs';
|
||||
import { createQueryApi, createAttuneApi } from './query.mjs';
|
||||
|
||||
export { buildIndex, DB_PATH };
|
||||
|
||||
type SandboxApi = Record<string, unknown>;
|
||||
|
||||
// Run a user-supplied CodeAct script inside the query/attune sandbox. The script
|
||||
// body runs as an async IIFE with a 30s timeout; its `return` value is resolved.
|
||||
function runInSandbox(api: SandboxApi, scriptContent: string): Promise<unknown> {
|
||||
const sandbox = {
|
||||
...api, JSON, Math, Array, Object, Set, Map, Date, RegExp,
|
||||
parseInt, parseFloat, String, Number, Boolean, Error, Promise, console, setTimeout,
|
||||
};
|
||||
const ctx = createContext(sandbox);
|
||||
return runInNewContext(`(async()=>{${scriptContent}})()`, ctx, { timeout: 30000 });
|
||||
}
|
||||
|
||||
// FTS search over indexed message text. Refreshes the index, then queries.
|
||||
export function searchText(text: string, opts?: Record<string, unknown>): unknown {
|
||||
buildIndex();
|
||||
const db = openDb();
|
||||
try {
|
||||
return createQueryApi(db).search(text, opts);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Execute a read-only CodeAct query script and resolve its returned value.
|
||||
export async function executeQuery(scriptContent: string): Promise<unknown> {
|
||||
buildIndex();
|
||||
const db = openDb();
|
||||
try {
|
||||
return await runInSandbox(createQueryApi(db), scriptContent);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
// Execute a memory-mutation CodeAct script (remember/forget only).
|
||||
export async function executeAttune(scriptContent: string): Promise<unknown> {
|
||||
buildIndex();
|
||||
const db = openDb();
|
||||
try {
|
||||
return await runInSandbox(createAttuneApi(db), scriptContent);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
+11
-44
@@ -1,32 +1,16 @@
|
||||
#!/usr/bin/env node
|
||||
// Skill transport: a thin CLI shell over Obelisk Core (scripts/core.ts).
|
||||
// It only parses args, reads script files, prints JSON, and owns the uniform
|
||||
// { error, stack } + exit-1 error envelope. All logic lives in Core.
|
||||
|
||||
import { createRequire } from 'node:module';
|
||||
const require = createRequire(import.meta.url);
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const vm = require('node:vm');
|
||||
|
||||
import { DB_PATH, openDb } from './db.mjs';
|
||||
import { buildIndex } from './indexer.mjs';
|
||||
import { createQueryApi, createAttuneApi } from './query.mjs';
|
||||
import { DB_PATH, buildIndex, searchText, executeQuery, executeAttune } from './core.ts';
|
||||
|
||||
function executeScript(api, scriptContent) {
|
||||
const sandbox = {
|
||||
...api, JSON, Math, Array, Object, Set, Map, Date, RegExp,
|
||||
parseInt, parseFloat, String, Number, Boolean, Error, Promise, console, setTimeout,
|
||||
};
|
||||
const ctx = vm.createContext(sandbox);
|
||||
return vm.runInNewContext(`(async()=>{${scriptContent}})()`, ctx, { timeout: 30000 });
|
||||
}
|
||||
|
||||
function executeQuery(db, scriptContent) {
|
||||
return executeScript(createQueryApi(db), scriptContent);
|
||||
}
|
||||
|
||||
function executeAttune(db, scriptContent) {
|
||||
return executeScript(createAttuneApi(db), scriptContent);
|
||||
}
|
||||
|
||||
function main() {
|
||||
async function main() {
|
||||
const args = process.argv.slice(2);
|
||||
// Uniform error envelope across all four verbs: a failure is reported as
|
||||
// { error, stack } on stdout with exit code 1, never a raw crash on stderr.
|
||||
@@ -34,6 +18,8 @@ function main() {
|
||||
process.stdout.write(JSON.stringify({ error: e.message, stack: e.stack }) + '\n');
|
||||
process.exitCode = 1;
|
||||
};
|
||||
const emit = (r) => process.stdout.write(JSON.stringify(r, null, 2) + '\n');
|
||||
|
||||
if (args[0] === '--build') {
|
||||
try {
|
||||
buildIndex({ force: true });
|
||||
@@ -42,34 +28,15 @@ function main() {
|
||||
return;
|
||||
}
|
||||
if (args[0] === '--search' && args[1]) {
|
||||
let db;
|
||||
try {
|
||||
buildIndex();
|
||||
db = openDb();
|
||||
process.stdout.write(JSON.stringify(createQueryApi(db).search(args.slice(1).join(' ')), null, 2) + '\n');
|
||||
} catch (e) {
|
||||
fail(e);
|
||||
} finally {
|
||||
if (db) db.close();
|
||||
}
|
||||
try { emit(searchText(args.slice(1).join(' '))); } catch (e) { fail(e); }
|
||||
return;
|
||||
}
|
||||
if (args[0] === '--query' && args[1]) {
|
||||
buildIndex();
|
||||
const db = openDb();
|
||||
const script = fs.readFileSync(path.resolve(args[1]), 'utf8');
|
||||
executeQuery(db, script)
|
||||
.then(r => { process.stdout.write(JSON.stringify(r, null, 2) + '\n'); db.close(); })
|
||||
.catch(e => { process.stdout.write(JSON.stringify({ error: e.message, stack: e.stack }) + '\n'); db.close(); process.exitCode = 1; });
|
||||
try { emit(await executeQuery(fs.readFileSync(path.resolve(args[1]), 'utf8'))); } catch (e) { fail(e); }
|
||||
return;
|
||||
}
|
||||
if (args[0] === '--attune' && args[1]) {
|
||||
buildIndex();
|
||||
const db = openDb();
|
||||
const script = fs.readFileSync(path.resolve(args[1]), 'utf8');
|
||||
executeAttune(db, script)
|
||||
.then(r => { process.stdout.write(JSON.stringify(r, null, 2) + '\n'); db.close(); })
|
||||
.catch(e => { process.stdout.write(JSON.stringify({ error: e.message, stack: e.stack }) + '\n'); db.close(); process.exitCode = 1; });
|
||||
try { emit(await executeAttune(fs.readFileSync(path.resolve(args[1]), 'utf8'))); } catch (e) { fail(e); }
|
||||
return;
|
||||
}
|
||||
process.stderr.write('Usage:\n node runtime.mjs --build\n node runtime.mjs --search "text"\n node runtime.mjs --query <file.js>\n node runtime.mjs --attune <file.js>\n');
|
||||
|
||||
Reference in New Issue
Block a user