@charzhu/openjaw-agent 0.2.0 → 0.2.1
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/README.md +1 -1
- package/dist/main.js +143 -59
- package/dist/main.js.map +3 -3
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -51,7 +51,7 @@ Use the default command for the new TUI, `--legacy-ui` to A/B test against the p
|
|
|
51
51
|
|
|
52
52
|
### Prerequisites
|
|
53
53
|
|
|
54
|
-
- **Node.js
|
|
54
|
+
- **Node.js 22.5+** — [Download](https://nodejs.org). Uses the built-in `node:sqlite` module. Node 23.5+ recommended (its bundled SQLite includes FTS5 for faster memory search; older builds fall back to a LIKE-based scan).
|
|
55
55
|
- **Windows 10/11** recommended (some automation tools require Windows; cross-platform basics work elsewhere)
|
|
56
56
|
- **LLM access** — Maestro proxy, Anthropic/OpenAI API keys, or GitHub Copilot via `/connect`
|
|
57
57
|
|
package/dist/main.js
CHANGED
|
@@ -8978,6 +8978,19 @@ import { DatabaseSync } from "node:sqlite";
|
|
|
8978
8978
|
import { join as join12 } from "node:path";
|
|
8979
8979
|
import { homedir as homedir9 } from "node:os";
|
|
8980
8980
|
import { mkdirSync as mkdirSync8, existsSync as existsSync10 } from "node:fs";
|
|
8981
|
+
function hasFts5() {
|
|
8982
|
+
return _hasFts5;
|
|
8983
|
+
}
|
|
8984
|
+
function detectFts5(database) {
|
|
8985
|
+
const probe = `__fts5_probe_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
8986
|
+
try {
|
|
8987
|
+
database.exec(`CREATE VIRTUAL TABLE temp.${probe} USING fts5(x)`);
|
|
8988
|
+
database.exec(`DROP TABLE temp.${probe}`);
|
|
8989
|
+
return true;
|
|
8990
|
+
} catch {
|
|
8991
|
+
return false;
|
|
8992
|
+
}
|
|
8993
|
+
}
|
|
8981
8994
|
function getMemoryDb() {
|
|
8982
8995
|
if (!db) {
|
|
8983
8996
|
if (!existsSync10(DB_DIR)) {
|
|
@@ -8987,12 +9000,12 @@ function getMemoryDb() {
|
|
|
8987
9000
|
db.exec("PRAGMA journal_mode=WAL");
|
|
8988
9001
|
db.exec("PRAGMA busy_timeout=5000");
|
|
8989
9002
|
ensureSchema(db);
|
|
8990
|
-
logger_default.debug("Memory database opened", { path: DB_PATH });
|
|
9003
|
+
logger_default.debug("Memory database opened", { path: DB_PATH, hasFts5: _hasFts5 });
|
|
8991
9004
|
}
|
|
8992
9005
|
return db;
|
|
8993
9006
|
}
|
|
8994
|
-
function ensureSchema(
|
|
8995
|
-
|
|
9007
|
+
function ensureSchema(database) {
|
|
9008
|
+
database.exec(`
|
|
8996
9009
|
CREATE TABLE IF NOT EXISTS memories (
|
|
8997
9010
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8998
9011
|
content TEXT NOT NULL,
|
|
@@ -9002,36 +9015,49 @@ function ensureSchema(db2) {
|
|
|
9002
9015
|
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
9003
9016
|
)
|
|
9004
9017
|
`);
|
|
9005
|
-
|
|
9006
|
-
|
|
9007
|
-
|
|
9008
|
-
|
|
9009
|
-
|
|
9010
|
-
|
|
9011
|
-
|
|
9012
|
-
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9018
|
+
_hasFts5 = detectFts5(database);
|
|
9019
|
+
if (_hasFts5) {
|
|
9020
|
+
database.exec(`
|
|
9021
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(
|
|
9022
|
+
content,
|
|
9023
|
+
content='memories',
|
|
9024
|
+
content_rowid='id',
|
|
9025
|
+
tokenize='unicode61'
|
|
9026
|
+
)
|
|
9027
|
+
`);
|
|
9028
|
+
database.exec(`
|
|
9029
|
+
CREATE TRIGGER IF NOT EXISTS memories_ai AFTER INSERT ON memories BEGIN
|
|
9030
|
+
INSERT INTO memories_fts(rowid, content) VALUES (new.id, new.content);
|
|
9031
|
+
END
|
|
9032
|
+
`);
|
|
9033
|
+
database.exec(`
|
|
9034
|
+
CREATE TRIGGER IF NOT EXISTS memories_ad AFTER DELETE ON memories BEGIN
|
|
9035
|
+
INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.id, old.content);
|
|
9036
|
+
END
|
|
9037
|
+
`);
|
|
9038
|
+
database.exec(`
|
|
9039
|
+
CREATE TRIGGER IF NOT EXISTS memories_au AFTER UPDATE ON memories BEGIN
|
|
9040
|
+
INSERT INTO memories_fts(memories_fts, rowid, content) VALUES ('delete', old.id, old.content);
|
|
9041
|
+
INSERT INTO memories_fts(rowid, content) VALUES (new.id, new.content);
|
|
9042
|
+
END
|
|
9043
|
+
`);
|
|
9044
|
+
try {
|
|
9045
|
+
database.exec(`INSERT INTO memories_fts(memories_fts) VALUES('rebuild')`);
|
|
9046
|
+
} catch (err) {
|
|
9047
|
+
logger_default.warn("FTS5 rebuild failed (non-fatal)", { error: String(err) });
|
|
9048
|
+
}
|
|
9049
|
+
} else {
|
|
9050
|
+
database.exec(`DROP TRIGGER IF EXISTS memories_ai`);
|
|
9051
|
+
database.exec(`DROP TRIGGER IF EXISTS memories_ad`);
|
|
9052
|
+
database.exec(`DROP TRIGGER IF EXISTS memories_au`);
|
|
9053
|
+
logger_default.warn("FTS5 not available in this SQLite build \u2014 memory search will use LIKE-based fallback. For best performance, upgrade to Node 23.5+ (bundled SQLite includes FTS5 by default).");
|
|
9054
|
+
}
|
|
9029
9055
|
try {
|
|
9030
|
-
|
|
9056
|
+
database.exec("ALTER TABLE memories ADD COLUMN hrr_vector BLOB");
|
|
9031
9057
|
} catch {
|
|
9032
9058
|
}
|
|
9033
9059
|
}
|
|
9034
|
-
var DB_DIR, DB_PATH, db;
|
|
9060
|
+
var DB_DIR, DB_PATH, db, _hasFts5;
|
|
9035
9061
|
var init_db = __esm({
|
|
9036
9062
|
"../openjaw-mcp/dist/memory/db.js"() {
|
|
9037
9063
|
"use strict";
|
|
@@ -9039,6 +9065,9 @@ var init_db = __esm({
|
|
|
9039
9065
|
DB_DIR = join12(homedir9(), ".openjaw");
|
|
9040
9066
|
DB_PATH = join12(DB_DIR, "memory.db");
|
|
9041
9067
|
db = null;
|
|
9068
|
+
_hasFts5 = false;
|
|
9069
|
+
__name(hasFts5, "hasFts5");
|
|
9070
|
+
__name(detectFts5, "detectFts5");
|
|
9042
9071
|
__name(getMemoryDb, "getMemoryDb");
|
|
9043
9072
|
__name(ensureSchema, "ensureSchema");
|
|
9044
9073
|
}
|
|
@@ -9135,11 +9164,15 @@ var init_hrr = __esm({
|
|
|
9135
9164
|
});
|
|
9136
9165
|
|
|
9137
9166
|
// ../openjaw-mcp/dist/memory/retrieval.js
|
|
9138
|
-
|
|
9167
|
+
function escapeLike(token) {
|
|
9168
|
+
return `%${token.replace(/[\\%_]/g, "\\$&")}%`;
|
|
9169
|
+
}
|
|
9170
|
+
var STOPWORDS, MAX_LIKE_TERMS, DEFAULT_WEIGHTS_FTS, DEFAULT_WEIGHTS_NO_FTS, HybridRetriever;
|
|
9139
9171
|
var init_retrieval = __esm({
|
|
9140
9172
|
"../openjaw-mcp/dist/memory/retrieval.js"() {
|
|
9141
9173
|
"use strict";
|
|
9142
9174
|
init_hrr();
|
|
9175
|
+
init_db();
|
|
9143
9176
|
init_logger();
|
|
9144
9177
|
STOPWORDS = /* @__PURE__ */ new Set([
|
|
9145
9178
|
"a",
|
|
@@ -9201,23 +9234,30 @@ var init_retrieval = __esm({
|
|
|
9201
9234
|
"should",
|
|
9202
9235
|
"could"
|
|
9203
9236
|
]);
|
|
9204
|
-
|
|
9237
|
+
MAX_LIKE_TERMS = 16;
|
|
9238
|
+
__name(escapeLike, "escapeLike");
|
|
9239
|
+
DEFAULT_WEIGHTS_FTS = {
|
|
9205
9240
|
fts: 0.4,
|
|
9206
9241
|
jaccard: 0.3,
|
|
9207
9242
|
hrr: 0.3
|
|
9208
9243
|
};
|
|
9244
|
+
DEFAULT_WEIGHTS_NO_FTS = {
|
|
9245
|
+
fts: 0,
|
|
9246
|
+
jaccard: 0.5,
|
|
9247
|
+
hrr: 0.5
|
|
9248
|
+
};
|
|
9209
9249
|
HybridRetriever = class {
|
|
9210
9250
|
static {
|
|
9211
9251
|
__name(this, "HybridRetriever");
|
|
9212
9252
|
}
|
|
9213
9253
|
db;
|
|
9214
9254
|
weights;
|
|
9215
|
-
constructor(db2, weights
|
|
9255
|
+
constructor(db2, weights) {
|
|
9216
9256
|
this.db = db2;
|
|
9217
|
-
this.weights = weights;
|
|
9257
|
+
this.weights = weights ?? (hasFts5() ? DEFAULT_WEIGHTS_FTS : DEFAULT_WEIGHTS_NO_FTS);
|
|
9218
9258
|
}
|
|
9219
9259
|
/**
|
|
9220
|
-
* Hybrid search: FTS5 candidate retrieval → Jaccard + HRR re-ranking.
|
|
9260
|
+
* Hybrid search: FTS5 (or LIKE fallback) candidate retrieval → Jaccard + HRR re-ranking.
|
|
9221
9261
|
*/
|
|
9222
9262
|
search(query, limit = 10) {
|
|
9223
9263
|
const queryTokens = query.toLowerCase().match(/[\w]+/g);
|
|
@@ -9228,27 +9268,47 @@ var init_retrieval = __esm({
|
|
|
9228
9268
|
return [];
|
|
9229
9269
|
const queryTokenSet = new Set(filtered);
|
|
9230
9270
|
const candidateLimit = Math.max(limit * 5, 50);
|
|
9231
|
-
const ftsQuery = filtered.join(" OR ");
|
|
9232
9271
|
let hasHrrColumn = true;
|
|
9233
9272
|
try {
|
|
9234
9273
|
this.db.prepare("SELECT hrr_vector FROM memories LIMIT 0").all();
|
|
9235
9274
|
} catch {
|
|
9236
9275
|
hasHrrColumn = false;
|
|
9237
9276
|
}
|
|
9238
|
-
|
|
9239
|
-
|
|
9240
|
-
|
|
9241
|
-
|
|
9242
|
-
|
|
9243
|
-
|
|
9244
|
-
|
|
9245
|
-
|
|
9246
|
-
|
|
9247
|
-
|
|
9248
|
-
|
|
9249
|
-
|
|
9250
|
-
|
|
9251
|
-
|
|
9277
|
+
let candidates = [];
|
|
9278
|
+
if (hasFts5()) {
|
|
9279
|
+
const selectCols = hasHrrColumn ? "m.id, m.content, m.source, m.created_at, m.hrr_vector, fts.rank" : "m.id, m.content, m.source, m.created_at, fts.rank";
|
|
9280
|
+
const ftsQuery = filtered.join(" OR ");
|
|
9281
|
+
try {
|
|
9282
|
+
candidates = this.db.prepare(`
|
|
9283
|
+
SELECT ${selectCols}
|
|
9284
|
+
FROM memories_fts fts
|
|
9285
|
+
JOIN memories m ON m.id = fts.rowid
|
|
9286
|
+
WHERE memories_fts MATCH ?
|
|
9287
|
+
ORDER BY fts.rank
|
|
9288
|
+
LIMIT ?
|
|
9289
|
+
`).all(ftsQuery, candidateLimit);
|
|
9290
|
+
} catch (err) {
|
|
9291
|
+
logger_default.warn("FTS5 search failed in hybrid retriever", { query: ftsQuery, error: String(err) });
|
|
9292
|
+
return [];
|
|
9293
|
+
}
|
|
9294
|
+
} else {
|
|
9295
|
+
const terms = Array.from(new Set(filtered)).slice(0, MAX_LIKE_TERMS);
|
|
9296
|
+
const selectCols = hasHrrColumn ? "m.id, m.content, m.source, m.created_at, m.hrr_vector, 0 AS rank" : "m.id, m.content, m.source, m.created_at, 0 AS rank";
|
|
9297
|
+
const wheres = terms.map(() => `m.content LIKE ? ESCAPE '\\'`).join(" OR ");
|
|
9298
|
+
const params = terms.map(escapeLike);
|
|
9299
|
+
params.push(candidateLimit);
|
|
9300
|
+
try {
|
|
9301
|
+
candidates = this.db.prepare(`
|
|
9302
|
+
SELECT ${selectCols}
|
|
9303
|
+
FROM memories m
|
|
9304
|
+
WHERE ${wheres}
|
|
9305
|
+
ORDER BY m.created_at DESC, m.id DESC
|
|
9306
|
+
LIMIT ?
|
|
9307
|
+
`).all(...params);
|
|
9308
|
+
} catch (err) {
|
|
9309
|
+
logger_default.warn("LIKE-fallback search failed in hybrid retriever", { error: String(err) });
|
|
9310
|
+
return [];
|
|
9311
|
+
}
|
|
9252
9312
|
}
|
|
9253
9313
|
if (candidates.length === 0)
|
|
9254
9314
|
return [];
|
|
@@ -19380,6 +19440,9 @@ import { homedir as homedir11 } from "node:os";
|
|
|
19380
19440
|
function setMemoryPrefetchQuery(query) {
|
|
19381
19441
|
currentQuery = query;
|
|
19382
19442
|
}
|
|
19443
|
+
function escapeLike2(token) {
|
|
19444
|
+
return `%${token.replace(/[\\%_]/g, "\\$&")}%`;
|
|
19445
|
+
}
|
|
19383
19446
|
function getMemorySection() {
|
|
19384
19447
|
if (!currentQuery) return null;
|
|
19385
19448
|
try {
|
|
@@ -19396,15 +19459,34 @@ function getMemorySection() {
|
|
|
19396
19459
|
if (!tokens || tokens.length === 0) return null;
|
|
19397
19460
|
const filtered = tokens.filter((t) => !STOPWORDS2.has(t));
|
|
19398
19461
|
if (filtered.length === 0) return null;
|
|
19399
|
-
|
|
19400
|
-
const
|
|
19401
|
-
|
|
19402
|
-
|
|
19403
|
-
|
|
19404
|
-
|
|
19405
|
-
|
|
19406
|
-
|
|
19407
|
-
|
|
19462
|
+
let rows = [];
|
|
19463
|
+
const ftsQuery = filtered.join(" OR ");
|
|
19464
|
+
try {
|
|
19465
|
+
rows = db2.prepare(`
|
|
19466
|
+
SELECT m.content, m.source, m.created_at
|
|
19467
|
+
FROM memories_fts fts
|
|
19468
|
+
JOIN memories m ON m.id = fts.rowid
|
|
19469
|
+
WHERE memories_fts MATCH ?
|
|
19470
|
+
ORDER BY fts.rank
|
|
19471
|
+
LIMIT ?
|
|
19472
|
+
`).all(ftsQuery, MAX_PREFETCH_RESULTS);
|
|
19473
|
+
} catch {
|
|
19474
|
+
const terms = Array.from(new Set(filtered)).slice(0, MAX_LIKE_TERMS2);
|
|
19475
|
+
const wheres = terms.map(() => `content LIKE ? ESCAPE '\\'`).join(" OR ");
|
|
19476
|
+
const params = terms.map(escapeLike2);
|
|
19477
|
+
params.push(MAX_PREFETCH_RESULTS);
|
|
19478
|
+
try {
|
|
19479
|
+
rows = db2.prepare(`
|
|
19480
|
+
SELECT content, source, created_at
|
|
19481
|
+
FROM memories
|
|
19482
|
+
WHERE ${wheres}
|
|
19483
|
+
ORDER BY created_at DESC, id DESC
|
|
19484
|
+
LIMIT ?
|
|
19485
|
+
`).all(...params);
|
|
19486
|
+
} catch {
|
|
19487
|
+
return null;
|
|
19488
|
+
}
|
|
19489
|
+
}
|
|
19408
19490
|
if (rows.length === 0) return null;
|
|
19409
19491
|
let result = "# Relevant Memories\n\nRecalled from your memory database based on your current query:\n\n";
|
|
19410
19492
|
for (const row of rows) {
|
|
@@ -19421,12 +19503,13 @@ function getMemorySection() {
|
|
|
19421
19503
|
return null;
|
|
19422
19504
|
}
|
|
19423
19505
|
}
|
|
19424
|
-
var MAX_PREFETCH_RESULTS, MAX_PREFETCH_CHARS, STOPWORDS2, currentQuery;
|
|
19506
|
+
var MAX_PREFETCH_RESULTS, MAX_PREFETCH_CHARS, MAX_LIKE_TERMS2, STOPWORDS2, currentQuery;
|
|
19425
19507
|
var init_memory2 = __esm({
|
|
19426
19508
|
"src/prompts/memory.ts"() {
|
|
19427
19509
|
"use strict";
|
|
19428
19510
|
MAX_PREFETCH_RESULTS = 10;
|
|
19429
19511
|
MAX_PREFETCH_CHARS = 4e3;
|
|
19512
|
+
MAX_LIKE_TERMS2 = 16;
|
|
19430
19513
|
STOPWORDS2 = /* @__PURE__ */ new Set([
|
|
19431
19514
|
"a",
|
|
19432
19515
|
"an",
|
|
@@ -19489,6 +19572,7 @@ var init_memory2 = __esm({
|
|
|
19489
19572
|
]);
|
|
19490
19573
|
currentQuery = null;
|
|
19491
19574
|
__name(setMemoryPrefetchQuery, "setMemoryPrefetchQuery");
|
|
19575
|
+
__name(escapeLike2, "escapeLike");
|
|
19492
19576
|
__name(getMemorySection, "getMemorySection");
|
|
19493
19577
|
}
|
|
19494
19578
|
});
|