@unson/brainbase-mcp 0.41.3 → 0.41.6
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/judgment-history-http.js +52 -11
- package/dist/judgment-history.d.ts +70 -2
- package/dist/judgment-history.js +381 -122
- package/package.json +2 -2
- package/ui/brainbase-tokens.css +4 -0
- package/ui/foundation-overview.js +30 -11
- package/ui/judgment-history.css +250 -23
- package/ui/judgment-history.js +497 -115
- package/ui/local-web-shell.js +4 -1
- package/ui/people-and-companies-view.css +143 -0
- package/ui/people-and-companies-view.js +87 -11
- package/ui/workspace-kit.css +43 -0
- package/ui/workspace-kit.js +131 -4
package/dist/judgment-history.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
1
2
|
import { createHash } from 'node:crypto';
|
|
2
3
|
import { lstat, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
4
|
import { join, resolve } from 'node:path';
|
|
@@ -12,6 +13,23 @@ export const JUDGMENT_HISTORY_ENTRYPOINTS = Object.freeze([
|
|
|
12
13
|
'company_os',
|
|
13
14
|
'unknown'
|
|
14
15
|
]);
|
|
16
|
+
const judgmentHistoryTimingSlots = new AsyncLocalStorage();
|
|
17
|
+
/** Isolates Server-Timing for one HTTP request from other in-flight reads. */
|
|
18
|
+
export function withJudgmentHistoryRequestTimings(fn) {
|
|
19
|
+
return judgmentHistoryTimingSlots.run({ current: null }, fn);
|
|
20
|
+
}
|
|
21
|
+
/** Timings published by the in-flight request, when one is bound. */
|
|
22
|
+
export function judgmentHistoryRequestTimings() {
|
|
23
|
+
const slot = judgmentHistoryTimingSlots.getStore();
|
|
24
|
+
if (!slot)
|
|
25
|
+
return { bound: false, timings: null };
|
|
26
|
+
return { bound: true, timings: slot.current };
|
|
27
|
+
}
|
|
28
|
+
function publishJudgmentHistoryReadTimings(timings) {
|
|
29
|
+
const slot = judgmentHistoryTimingSlots.getStore();
|
|
30
|
+
if (slot)
|
|
31
|
+
slot.current = timings;
|
|
32
|
+
}
|
|
15
33
|
export class JudgmentHistoryReaderError extends Error {
|
|
16
34
|
statusCode;
|
|
17
35
|
code;
|
|
@@ -22,6 +40,82 @@ export class JudgmentHistoryReaderError extends Error {
|
|
|
22
40
|
this.name = 'JudgmentHistoryReaderError';
|
|
23
41
|
}
|
|
24
42
|
}
|
|
43
|
+
export const DEFAULT_LOCAL_JUDGMENT_HISTORY_READ_PARALLEL = 8;
|
|
44
|
+
export const MAX_JUDGMENT_HISTORY_RESOLUTION_BATCH = 100;
|
|
45
|
+
/** Parsed artifacts kept for one local source. The oldest entry is dropped past this. */
|
|
46
|
+
export const DEFAULT_JUDGMENT_HISTORY_JOURNAL_CACHE_LIMIT = 4_096;
|
|
47
|
+
/**
|
|
48
|
+
* Remembers parsed journal artifacts for one local source.
|
|
49
|
+
* The key is (session, turn, file name, mtime). A later read of the same turn
|
|
50
|
+
* misses when that file's mtime changes, and the previous bytes are not reused.
|
|
51
|
+
* A lookup refreshes recency. Past `limit`, the least recently used entry is dropped.
|
|
52
|
+
*/
|
|
53
|
+
export class JudgmentHistoryJournalScanCache {
|
|
54
|
+
#files = new Map();
|
|
55
|
+
#limit;
|
|
56
|
+
#hits = 0;
|
|
57
|
+
#misses = 0;
|
|
58
|
+
constructor(limit = DEFAULT_JUDGMENT_HISTORY_JOURNAL_CACHE_LIMIT) {
|
|
59
|
+
if (!Number.isInteger(limit) || limit < 1)
|
|
60
|
+
throw new TypeError('cache limit must be a positive integer');
|
|
61
|
+
this.#limit = limit;
|
|
62
|
+
}
|
|
63
|
+
get limit() {
|
|
64
|
+
return this.#limit;
|
|
65
|
+
}
|
|
66
|
+
get size() {
|
|
67
|
+
return this.#files.size;
|
|
68
|
+
}
|
|
69
|
+
lookup(session, turn, fileName, mtimeNs) {
|
|
70
|
+
const key = `${session}\0${turn}\0${fileName}`;
|
|
71
|
+
const entry = this.#files.get(key);
|
|
72
|
+
if (!entry || entry.mtimeNs !== mtimeNs) {
|
|
73
|
+
if (entry)
|
|
74
|
+
this.#files.delete(key);
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
this.#files.delete(key);
|
|
78
|
+
this.#files.set(key, entry);
|
|
79
|
+
this.#hits += 1;
|
|
80
|
+
return entry.result;
|
|
81
|
+
}
|
|
82
|
+
store(session, turn, fileName, mtimeNs, result) {
|
|
83
|
+
const key = `${session}\0${turn}\0${fileName}`;
|
|
84
|
+
this.#files.delete(key);
|
|
85
|
+
this.#files.set(key, { mtimeNs, result });
|
|
86
|
+
while (this.#files.size > this.#limit) {
|
|
87
|
+
const oldest = this.#files.keys().next().value;
|
|
88
|
+
if (oldest === undefined)
|
|
89
|
+
break;
|
|
90
|
+
this.#files.delete(oldest);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
noteMiss() {
|
|
94
|
+
this.#misses += 1;
|
|
95
|
+
}
|
|
96
|
+
get hits() {
|
|
97
|
+
return this.#hits;
|
|
98
|
+
}
|
|
99
|
+
get misses() {
|
|
100
|
+
return this.#misses;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/** Runs `map` with at most `limit` calls in flight. Results keep input order. */
|
|
104
|
+
export async function mapWithConcurrencyLimit(items, limit, map) {
|
|
105
|
+
if (!Number.isInteger(limit) || limit < 1)
|
|
106
|
+
throw new TypeError('parallel limit must be a positive integer');
|
|
107
|
+
const results = new Array(items.length);
|
|
108
|
+
let next = 0;
|
|
109
|
+
const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
|
|
110
|
+
while (next < items.length) {
|
|
111
|
+
const index = next;
|
|
112
|
+
next += 1;
|
|
113
|
+
results[index] = await map(items[index], index);
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
await Promise.all(workers);
|
|
117
|
+
return results;
|
|
118
|
+
}
|
|
25
119
|
export class JudgmentHistoryFeedbackError extends Error {
|
|
26
120
|
statusCode;
|
|
27
121
|
code;
|
|
@@ -945,21 +1039,12 @@ function mergeRecordWithFinal(adoptionRecord, final, sessionRef, key, turnInput,
|
|
|
945
1039
|
};
|
|
946
1040
|
return { ...baseRecord, missing_fields: missingFields(baseRecord, firstPresent(blocks, 'missing_fields')) };
|
|
947
1041
|
}
|
|
948
|
-
async function
|
|
1042
|
+
async function loadNewestJournalGroups(root, maxFiles, maxRecordBytes, cache, readParallel) {
|
|
949
1043
|
const sessions = await readdir(root, { withFileTypes: true });
|
|
950
1044
|
const groups = new Map();
|
|
1045
|
+
const discovered = [];
|
|
951
1046
|
let invalidCount = 0;
|
|
952
|
-
|
|
953
|
-
let capped = false;
|
|
954
|
-
const stop = () => {
|
|
955
|
-
if (candidateCount >= maxFiles) {
|
|
956
|
-
capped = true;
|
|
957
|
-
return true;
|
|
958
|
-
}
|
|
959
|
-
candidateCount += 1;
|
|
960
|
-
return false;
|
|
961
|
-
};
|
|
962
|
-
outer: for (const session of sessions.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
1047
|
+
for (const session of sessions.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
963
1048
|
if (session.isSymbolicLink()) {
|
|
964
1049
|
invalidCount += 1;
|
|
965
1050
|
continue;
|
|
@@ -975,7 +1060,6 @@ async function readLocalRecords(root, configuredEntrypoint, maxFiles, maxRecordB
|
|
|
975
1060
|
invalidCount += 1;
|
|
976
1061
|
continue;
|
|
977
1062
|
}
|
|
978
|
-
const groupMap = new Map();
|
|
979
1063
|
const recognized = names.map((entry) => {
|
|
980
1064
|
const name = entry.name;
|
|
981
1065
|
if (ADOPTION_FILE.test(name))
|
|
@@ -1002,12 +1086,9 @@ async function readLocalRecords(root, configuredEntrypoint, maxFiles, maxRecordB
|
|
|
1002
1086
|
invalidCount += 1;
|
|
1003
1087
|
continue;
|
|
1004
1088
|
}
|
|
1005
|
-
if (stop())
|
|
1006
|
-
break outer;
|
|
1007
|
-
const group = groupMap.get(key) ?? groupFor(groups, session.name, key);
|
|
1008
|
-
groupMap.set(key, group);
|
|
1009
1089
|
const path = join(sessionPath, candidate.entry.name);
|
|
1010
1090
|
if (candidate.kind === 'events') {
|
|
1091
|
+
const group = groupFor(groups, session.name, key);
|
|
1011
1092
|
group.eventDirectorySeen = true;
|
|
1012
1093
|
if (candidate.entry.isSymbolicLink() || !candidate.entry.isDirectory()) {
|
|
1013
1094
|
group.invalid += 1;
|
|
@@ -1024,33 +1105,106 @@ async function readLocalRecords(root, configuredEntrypoint, maxFiles, maxRecordB
|
|
|
1024
1105
|
for (const eventEntry of eventEntries.sort((left, right) => left.name.localeCompare(right.name))) {
|
|
1025
1106
|
if (!eventEntry.name.endsWith('.json'))
|
|
1026
1107
|
continue;
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1108
|
+
discovered.push({
|
|
1109
|
+
session: session.name,
|
|
1110
|
+
key,
|
|
1111
|
+
kind: 'event',
|
|
1112
|
+
path: join(path, eventEntry.name),
|
|
1113
|
+
name: eventEntry.name,
|
|
1114
|
+
eventKey: eventEntry.name.slice(0, -5),
|
|
1115
|
+
mtimeNs: 0n
|
|
1116
|
+
});
|
|
1036
1117
|
}
|
|
1037
1118
|
continue;
|
|
1038
1119
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1120
|
+
discovered.push({
|
|
1121
|
+
session: session.name,
|
|
1122
|
+
key,
|
|
1123
|
+
kind: candidate.kind,
|
|
1124
|
+
path,
|
|
1125
|
+
name: candidate.entry.name,
|
|
1126
|
+
mtimeNs: 0n
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
const stated = await mapWithConcurrencyLimit(discovered, readParallel, async (candidate) => {
|
|
1131
|
+
let mtimeNs = 0n;
|
|
1132
|
+
try {
|
|
1133
|
+
mtimeNs = (await lstat(candidate.path, { bigint: true })).mtimeNs;
|
|
1134
|
+
}
|
|
1135
|
+
catch {
|
|
1136
|
+
mtimeNs = 0n;
|
|
1137
|
+
}
|
|
1138
|
+
return { ...candidate, mtimeNs };
|
|
1139
|
+
});
|
|
1140
|
+
const newestFirst = [...stated].sort((left, right) => {
|
|
1141
|
+
if (left.mtimeNs === right.mtimeNs)
|
|
1142
|
+
return left.path.localeCompare(right.path);
|
|
1143
|
+
return left.mtimeNs > right.mtimeNs ? -1 : 1;
|
|
1144
|
+
});
|
|
1145
|
+
const capped = newestFirst.length > maxFiles;
|
|
1146
|
+
const selectedPaths = new Set(newestFirst.slice(0, maxFiles).map((candidate) => candidate.path));
|
|
1147
|
+
const selected = stated.filter((candidate) => selectedPaths.has(candidate.path));
|
|
1148
|
+
let cacheHits = 0;
|
|
1149
|
+
let cacheMisses = 0;
|
|
1150
|
+
const loaded = await mapWithConcurrencyLimit(selected, readParallel, async (candidate) => {
|
|
1151
|
+
const hit = cache.lookup(candidate.session, candidate.key, candidate.name, candidate.mtimeNs);
|
|
1152
|
+
if (hit) {
|
|
1153
|
+
cacheHits += 1;
|
|
1154
|
+
return { path: candidate.path, result: hit };
|
|
1155
|
+
}
|
|
1156
|
+
cacheMisses += 1;
|
|
1157
|
+
cache.noteMiss();
|
|
1158
|
+
const result = await readBoundedJson(candidate.path, maxRecordBytes);
|
|
1159
|
+
cache.store(candidate.session, candidate.key, candidate.name, candidate.mtimeNs, result);
|
|
1160
|
+
return { path: candidate.path, result };
|
|
1161
|
+
});
|
|
1162
|
+
const byPath = new Map(loaded.map((item) => [item.path, item.result]));
|
|
1163
|
+
for (const candidate of selected) {
|
|
1164
|
+
const group = groupFor(groups, candidate.session, candidate.key);
|
|
1165
|
+
const result = byPath.get(candidate.path);
|
|
1166
|
+
if (!result) {
|
|
1167
|
+
group.invalid += 1;
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
if (candidate.kind === 'event') {
|
|
1171
|
+
group.eventDirectorySeen = true;
|
|
1172
|
+
if (result.invalid || !result.value || !validJournalEvent(result.value, candidate.session, candidate.key, candidate.eventKey)) {
|
|
1041
1173
|
group.invalid += 1;
|
|
1042
1174
|
continue;
|
|
1043
1175
|
}
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
else
|
|
1051
|
-
group.turnInput = result.value;
|
|
1176
|
+
group.events.push(result.value);
|
|
1177
|
+
continue;
|
|
1178
|
+
}
|
|
1179
|
+
if (result.invalid || !result.value) {
|
|
1180
|
+
group.invalid += 1;
|
|
1181
|
+
continue;
|
|
1052
1182
|
}
|
|
1183
|
+
if (candidate.kind === 'adoption')
|
|
1184
|
+
group.adoption = result.value;
|
|
1185
|
+
else if (candidate.kind === 'final')
|
|
1186
|
+
group.final = result.value;
|
|
1187
|
+
else if (candidate.kind === 'episode')
|
|
1188
|
+
group.episode = result.value;
|
|
1189
|
+
else
|
|
1190
|
+
group.turnInput = result.value;
|
|
1053
1191
|
}
|
|
1192
|
+
return { groups, invalidCount, candidateCount: selected.length, capped, cacheHits, cacheMisses };
|
|
1193
|
+
}
|
|
1194
|
+
async function readLocalRecords(root, configuredEntrypoint, maxFiles, maxRecordBytes, cache, readParallel) {
|
|
1195
|
+
const loaded = await loadNewestJournalGroups(root, maxFiles, maxRecordBytes, cache, readParallel);
|
|
1196
|
+
const groups = loaded.groups;
|
|
1197
|
+
let invalidCount = loaded.invalidCount;
|
|
1198
|
+
let candidateCount = loaded.candidateCount;
|
|
1199
|
+
let capped = loaded.capped;
|
|
1200
|
+
const stop = () => {
|
|
1201
|
+
if (candidateCount >= maxFiles) {
|
|
1202
|
+
capped = true;
|
|
1203
|
+
return true;
|
|
1204
|
+
}
|
|
1205
|
+
candidateCount += 1;
|
|
1206
|
+
return false;
|
|
1207
|
+
};
|
|
1054
1208
|
const records = [];
|
|
1055
1209
|
const seen = new Set();
|
|
1056
1210
|
for (const group of groups.values()) {
|
|
@@ -1139,7 +1293,7 @@ async function readLocalRecords(root, configuredEntrypoint, maxFiles, maxRecordB
|
|
|
1139
1293
|
}
|
|
1140
1294
|
records.sort(compareRecords);
|
|
1141
1295
|
const contentDigest = sha256(canonicalJson({ records, candidateCount, invalidCount, capped }));
|
|
1142
|
-
return { records, invalidCount, candidateCount, capped, contentDigest };
|
|
1296
|
+
return { records, invalidCount, candidateCount, capped, contentDigest, cacheHits: loaded.cacheHits, cacheMisses: loaded.cacheMisses };
|
|
1143
1297
|
}
|
|
1144
1298
|
function compareRecords(left, right) {
|
|
1145
1299
|
const dateOrder = right.recorded_at.localeCompare(left.recorded_at);
|
|
@@ -1171,30 +1325,50 @@ export function createLocalJudgmentHistorySource(options) {
|
|
|
1171
1325
|
throw new TypeError('max_files must be a positive integer');
|
|
1172
1326
|
if (!Number.isInteger(maxRecordBytes) || maxRecordBytes < 1)
|
|
1173
1327
|
throw new TypeError('max_record_bytes must be a positive integer');
|
|
1328
|
+
const readParallel = options.read_parallel ?? DEFAULT_LOCAL_JUDGMENT_HISTORY_READ_PARALLEL;
|
|
1329
|
+
if (!Number.isInteger(readParallel) || readParallel < 1)
|
|
1330
|
+
throw new TypeError('read_parallel must be a positive integer');
|
|
1331
|
+
const cache = options.cache ?? new JudgmentHistoryJournalScanCache();
|
|
1174
1332
|
const root = resolve(options.root);
|
|
1333
|
+
let lastTimings = null;
|
|
1175
1334
|
return {
|
|
1176
1335
|
storage: 'local',
|
|
1336
|
+
timings: () => lastTimings,
|
|
1177
1337
|
async read() {
|
|
1338
|
+
const started = performance.now();
|
|
1339
|
+
const finish = (scanned, cacheHits = 0, cacheMisses = 0, snapshot) => {
|
|
1340
|
+
const journalMs = performance.now() - started;
|
|
1341
|
+
const timings = {
|
|
1342
|
+
total_ms: journalMs,
|
|
1343
|
+
journal_ms: journalMs,
|
|
1344
|
+
page_ms: 0,
|
|
1345
|
+
cache_hits: cacheHits,
|
|
1346
|
+
cache_misses: cacheMisses,
|
|
1347
|
+
parallel_limit: readParallel,
|
|
1348
|
+
scanned_files: scanned
|
|
1349
|
+
};
|
|
1350
|
+
lastTimings = timings;
|
|
1351
|
+
return { ...snapshot, timings };
|
|
1352
|
+
};
|
|
1178
1353
|
try {
|
|
1179
1354
|
const rootStats = await lstat(root);
|
|
1180
1355
|
if (rootStats.isSymbolicLink()) {
|
|
1181
|
-
return { status: 'unavailable', records: null, coverage: unavailableCoverage('local', 'journal_symlink_rejected') };
|
|
1356
|
+
return finish(0, 0, 0, { status: 'unavailable', records: null, coverage: unavailableCoverage('local', 'journal_symlink_rejected') });
|
|
1357
|
+
}
|
|
1358
|
+
if (!rootStats.isDirectory()) {
|
|
1359
|
+
return finish(0, 0, 0, { status: 'unavailable', records: null, coverage: unavailableCoverage('local', 'journal_not_directory') });
|
|
1182
1360
|
}
|
|
1183
|
-
if (!rootStats.isDirectory())
|
|
1184
|
-
return { status: 'unavailable', records: null, coverage: unavailableCoverage('local', 'journal_not_directory') };
|
|
1185
1361
|
}
|
|
1186
1362
|
catch (error) {
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
}
|
|
1190
|
-
return { status: 'unavailable', records: null, coverage: unavailableCoverage('local', 'journal_unreadable') };
|
|
1363
|
+
const reason = error.code === 'ENOENT' ? 'journal_unavailable' : 'journal_unreadable';
|
|
1364
|
+
return finish(0, 0, 0, { status: 'unavailable', records: null, coverage: unavailableCoverage('local', reason) });
|
|
1191
1365
|
}
|
|
1192
1366
|
let result;
|
|
1193
1367
|
try {
|
|
1194
|
-
result = await readLocalRecords(root, configuredEntrypoint, maxFiles, maxRecordBytes);
|
|
1368
|
+
result = await readLocalRecords(root, configuredEntrypoint, maxFiles, maxRecordBytes, cache, readParallel);
|
|
1195
1369
|
}
|
|
1196
1370
|
catch {
|
|
1197
|
-
return { status: 'unavailable', records: null, coverage: unavailableCoverage('local', 'journal_unreadable') };
|
|
1371
|
+
return finish(0, 0, 0, { status: 'unavailable', records: null, coverage: unavailableCoverage('local', 'journal_unreadable') });
|
|
1198
1372
|
}
|
|
1199
1373
|
const status = result.invalidCount > 0 || result.capped ? 'partial' : 'available';
|
|
1200
1374
|
const reason = result.capped ? 'journal_scan_capped' : result.invalidCount > 0 ? 'some_journal_records_unreadable' : null;
|
|
@@ -1211,7 +1385,7 @@ export function createLocalJudgmentHistorySource(options) {
|
|
|
1211
1385
|
sources,
|
|
1212
1386
|
total: status === 'available' ? result.records.length : null
|
|
1213
1387
|
};
|
|
1214
|
-
return {
|
|
1388
|
+
return finish(result.candidateCount, result.cacheHits, result.cacheMisses, {
|
|
1215
1389
|
status,
|
|
1216
1390
|
records: result.records,
|
|
1217
1391
|
coverage,
|
|
@@ -1224,7 +1398,7 @@ export function createLocalJudgmentHistorySource(options) {
|
|
|
1224
1398
|
capped: result.capped,
|
|
1225
1399
|
content_digest: result.contentDigest
|
|
1226
1400
|
})
|
|
1227
|
-
};
|
|
1401
|
+
});
|
|
1228
1402
|
}
|
|
1229
1403
|
};
|
|
1230
1404
|
}
|
|
@@ -1417,105 +1591,190 @@ export function createJudgmentHistoryReader(options) {
|
|
|
1417
1591
|
if (!options?.source || typeof options.source.read !== 'function')
|
|
1418
1592
|
throw new TypeError('source is required');
|
|
1419
1593
|
const now = options.now ?? (() => new Date());
|
|
1594
|
+
let lastTimings = null;
|
|
1595
|
+
const takeSource = async () => {
|
|
1596
|
+
const snapshot = await options.source.read();
|
|
1597
|
+
return {
|
|
1598
|
+
source: buildSourceSnapshot(snapshot),
|
|
1599
|
+
sourceTimings: snapshot.timings ?? options.source.timings?.() ?? null
|
|
1600
|
+
};
|
|
1601
|
+
};
|
|
1602
|
+
const remember = (readStarted, pageStarted, sourceTimings) => {
|
|
1603
|
+
const finished = performance.now();
|
|
1604
|
+
const timings = {
|
|
1605
|
+
total_ms: finished - readStarted,
|
|
1606
|
+
journal_ms: sourceTimings?.journal_ms ?? Math.max(0, pageStarted - readStarted),
|
|
1607
|
+
page_ms: Math.max(0, finished - pageStarted),
|
|
1608
|
+
cache_hits: sourceTimings?.cache_hits ?? 0,
|
|
1609
|
+
cache_misses: sourceTimings?.cache_misses ?? 0,
|
|
1610
|
+
parallel_limit: sourceTimings?.parallel_limit ?? 0,
|
|
1611
|
+
scanned_files: sourceTimings?.scanned_files ?? 0
|
|
1612
|
+
};
|
|
1613
|
+
lastTimings = timings;
|
|
1614
|
+
publishJudgmentHistoryReadTimings(timings);
|
|
1615
|
+
};
|
|
1420
1616
|
return {
|
|
1617
|
+
timings: () => lastTimings,
|
|
1421
1618
|
async home(inputQuery = {}) {
|
|
1422
1619
|
const query = normalizeQuery(inputQuery);
|
|
1423
1620
|
if (query.project !== null && options.authorizeProject && !(await options.authorizeProject(query.project))) {
|
|
1424
1621
|
throw new JudgmentHistoryReaderError(403, 'project_scope_forbidden', 'project is outside the trusted scope');
|
|
1425
1622
|
}
|
|
1426
|
-
const
|
|
1427
|
-
|
|
1428
|
-
const
|
|
1429
|
-
|
|
1430
|
-
|
|
1623
|
+
const readStarted = performance.now();
|
|
1624
|
+
const loaded = await takeSource();
|
|
1625
|
+
const source = loaded.source;
|
|
1626
|
+
const pageStarted = performance.now();
|
|
1627
|
+
try {
|
|
1628
|
+
let records = source.records;
|
|
1629
|
+
const filters = { period: query.period, project: query.project, entrypoint: query.entrypoint };
|
|
1630
|
+
if (source.status === 'unavailable' || records === null) {
|
|
1631
|
+
return {
|
|
1632
|
+
contract_version: JUDGMENT_HISTORY_CONTRACT_VERSION,
|
|
1633
|
+
status: source.status,
|
|
1634
|
+
records: null,
|
|
1635
|
+
coverage: { ...source.coverage, total: null },
|
|
1636
|
+
pagination: { cursor: query.cursor, next_cursor: null, limit: query.limit, has_more: false },
|
|
1637
|
+
filters
|
|
1638
|
+
};
|
|
1639
|
+
}
|
|
1640
|
+
let scopeUnverified = false;
|
|
1641
|
+
if (query.project === null && options.authorizeProject) {
|
|
1642
|
+
const visible = [];
|
|
1643
|
+
for (const record of records) {
|
|
1644
|
+
// A record without a trusted project binding must never enter a
|
|
1645
|
+
// scoped aggregate, because its owner cannot be authorized.
|
|
1646
|
+
if (record.project_code === null) {
|
|
1647
|
+
scopeUnverified = true;
|
|
1648
|
+
continue;
|
|
1649
|
+
}
|
|
1650
|
+
if (await options.authorizeProject(record.project_code))
|
|
1651
|
+
visible.push(record);
|
|
1652
|
+
}
|
|
1653
|
+
records = visible;
|
|
1654
|
+
}
|
|
1655
|
+
const effectiveNow = now();
|
|
1656
|
+
const start = periodStart(query.period, effectiveNow);
|
|
1657
|
+
const anchor = query.period === 'all' ? null : startOfJstDay(effectiveNow);
|
|
1658
|
+
const status = scopeUnverified && source.status === 'available' ? 'partial' : source.status;
|
|
1659
|
+
const baseCoverage = scopeUnverified
|
|
1660
|
+
? { ...source.coverage, complete: false, reason: 'project_scope_unverified', total: null }
|
|
1661
|
+
: { ...source.coverage, total: null };
|
|
1662
|
+
const base = {
|
|
1431
1663
|
contract_version: JUDGMENT_HISTORY_CONTRACT_VERSION,
|
|
1432
|
-
status
|
|
1664
|
+
status,
|
|
1433
1665
|
records: null,
|
|
1434
|
-
coverage:
|
|
1666
|
+
coverage: baseCoverage,
|
|
1435
1667
|
pagination: { cursor: query.cursor, next_cursor: null, limit: query.limit, has_more: false },
|
|
1436
1668
|
filters
|
|
1437
1669
|
};
|
|
1670
|
+
const filtered = records.filter((record) => {
|
|
1671
|
+
if (query.project !== null && record.project_code !== query.project)
|
|
1672
|
+
return false;
|
|
1673
|
+
if (query.entrypoint !== null && record.entrypoint !== query.entrypoint)
|
|
1674
|
+
return false;
|
|
1675
|
+
if (start && Date.parse(record.recorded_at) < start.getTime())
|
|
1676
|
+
return false;
|
|
1677
|
+
return true;
|
|
1678
|
+
});
|
|
1679
|
+
const filter = filterKey(query, start, anchor);
|
|
1680
|
+
const offset = query.cursor ? decodeCursor(query.cursor, filter, source.snapshot_id) : 0;
|
|
1681
|
+
const page = filtered.slice(offset, offset + query.limit);
|
|
1682
|
+
const hasMore = offset + page.length < filtered.length;
|
|
1683
|
+
const nextCursor = hasMore ? encodeCursor({ filter, snapshot: source.snapshot_id, offset: offset + page.length }) : null;
|
|
1684
|
+
const coverage = {
|
|
1685
|
+
...baseCoverage,
|
|
1686
|
+
total: baseCoverage.complete ? filtered.length : null
|
|
1687
|
+
};
|
|
1688
|
+
return {
|
|
1689
|
+
...base,
|
|
1690
|
+
records: page,
|
|
1691
|
+
coverage,
|
|
1692
|
+
pagination: { cursor: query.cursor, next_cursor: nextCursor, limit: query.limit, has_more: hasMore }
|
|
1693
|
+
};
|
|
1438
1694
|
}
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
const visible = [];
|
|
1442
|
-
for (const record of records) {
|
|
1443
|
-
// A record without a trusted project binding must never enter a
|
|
1444
|
-
// scoped aggregate, because its owner cannot be authorized.
|
|
1445
|
-
if (record.project_code === null) {
|
|
1446
|
-
scopeUnverified = true;
|
|
1447
|
-
continue;
|
|
1448
|
-
}
|
|
1449
|
-
if (await options.authorizeProject(record.project_code))
|
|
1450
|
-
visible.push(record);
|
|
1451
|
-
}
|
|
1452
|
-
records = visible;
|
|
1695
|
+
finally {
|
|
1696
|
+
remember(readStarted, pageStarted, loaded.sourceTimings);
|
|
1453
1697
|
}
|
|
1454
|
-
const effectiveNow = now();
|
|
1455
|
-
const start = periodStart(query.period, effectiveNow);
|
|
1456
|
-
const anchor = query.period === 'all' ? null : startOfJstDay(effectiveNow);
|
|
1457
|
-
const status = scopeUnverified && source.status === 'available' ? 'partial' : source.status;
|
|
1458
|
-
const baseCoverage = scopeUnverified
|
|
1459
|
-
? { ...source.coverage, complete: false, reason: 'project_scope_unverified', total: null }
|
|
1460
|
-
: { ...source.coverage, total: null };
|
|
1461
|
-
const base = {
|
|
1462
|
-
contract_version: JUDGMENT_HISTORY_CONTRACT_VERSION,
|
|
1463
|
-
status,
|
|
1464
|
-
records: null,
|
|
1465
|
-
coverage: baseCoverage,
|
|
1466
|
-
pagination: { cursor: query.cursor, next_cursor: null, limit: query.limit, has_more: false },
|
|
1467
|
-
filters
|
|
1468
|
-
};
|
|
1469
|
-
const filtered = records.filter((record) => {
|
|
1470
|
-
if (query.project !== null && record.project_code !== query.project)
|
|
1471
|
-
return false;
|
|
1472
|
-
if (query.entrypoint !== null && record.entrypoint !== query.entrypoint)
|
|
1473
|
-
return false;
|
|
1474
|
-
if (start && Date.parse(record.recorded_at) < start.getTime())
|
|
1475
|
-
return false;
|
|
1476
|
-
return true;
|
|
1477
|
-
});
|
|
1478
|
-
const filter = filterKey(query, start, anchor);
|
|
1479
|
-
const offset = query.cursor ? decodeCursor(query.cursor, filter, source.snapshot_id) : 0;
|
|
1480
|
-
const page = filtered.slice(offset, offset + query.limit);
|
|
1481
|
-
const hasMore = offset + page.length < filtered.length;
|
|
1482
|
-
const nextCursor = hasMore ? encodeCursor({ filter, snapshot: source.snapshot_id, offset: offset + page.length }) : null;
|
|
1483
|
-
const coverage = {
|
|
1484
|
-
...baseCoverage,
|
|
1485
|
-
total: baseCoverage.complete ? filtered.length : null
|
|
1486
|
-
};
|
|
1487
|
-
return {
|
|
1488
|
-
...base,
|
|
1489
|
-
records: page,
|
|
1490
|
-
coverage,
|
|
1491
|
-
pagination: { cursor: query.cursor, next_cursor: nextCursor, limit: query.limit, has_more: hasMore }
|
|
1492
|
-
};
|
|
1493
1698
|
},
|
|
1494
1699
|
async detail(recordId) {
|
|
1495
1700
|
const safeRecordId = safeId(recordId);
|
|
1496
1701
|
if (!safeRecordId)
|
|
1497
1702
|
throw new JudgmentHistoryReaderError(400, 'invalid_record_id', 'record_id is invalid');
|
|
1498
|
-
const
|
|
1499
|
-
|
|
1703
|
+
const readStarted = performance.now();
|
|
1704
|
+
const loaded = await takeSource();
|
|
1705
|
+
const source = loaded.source;
|
|
1706
|
+
const pageStarted = performance.now();
|
|
1707
|
+
try {
|
|
1708
|
+
if (source.status === 'unavailable' || source.records === null) {
|
|
1709
|
+
return {
|
|
1710
|
+
contract_version: JUDGMENT_HISTORY_CONTRACT_VERSION,
|
|
1711
|
+
status: source.status,
|
|
1712
|
+
record: null,
|
|
1713
|
+
coverage: source.coverage
|
|
1714
|
+
};
|
|
1715
|
+
}
|
|
1716
|
+
const record = source.records.find((candidate) => candidate.record_id === safeRecordId) ?? null;
|
|
1717
|
+
if (record && options.authorizeProject) {
|
|
1718
|
+
if (record.project_code === null || !(await options.authorizeProject(record.project_code))) {
|
|
1719
|
+
throw new JudgmentHistoryReaderError(403, 'project_scope_forbidden', 'record is outside the trusted scope');
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1500
1722
|
return {
|
|
1501
1723
|
contract_version: JUDGMENT_HISTORY_CONTRACT_VERSION,
|
|
1502
1724
|
status: source.status,
|
|
1503
|
-
record
|
|
1504
|
-
coverage: source.coverage
|
|
1725
|
+
record,
|
|
1726
|
+
coverage: { ...source.coverage, total: source.coverage.complete ? source.records.length : null }
|
|
1505
1727
|
};
|
|
1506
1728
|
}
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1729
|
+
finally {
|
|
1730
|
+
remember(readStarted, pageStarted, loaded.sourceTimings);
|
|
1731
|
+
}
|
|
1732
|
+
},
|
|
1733
|
+
async findByResolutionIds(resolutionIds) {
|
|
1734
|
+
if (!Array.isArray(resolutionIds) || resolutionIds.length < 1 || resolutionIds.length > MAX_JUDGMENT_HISTORY_RESOLUTION_BATCH) {
|
|
1735
|
+
throw new JudgmentHistoryReaderError(400, 'invalid_resolution_ids', `resolution ids must contain between 1 and ${MAX_JUDGMENT_HISTORY_RESOLUTION_BATCH} ids`);
|
|
1736
|
+
}
|
|
1737
|
+
const ids = resolutionIds.map((id) => {
|
|
1738
|
+
const safe = safeId(id);
|
|
1739
|
+
if (!safe)
|
|
1740
|
+
throw new JudgmentHistoryReaderError(400, 'invalid_resolution_id', 'resolution id is invalid');
|
|
1741
|
+
return safe;
|
|
1742
|
+
});
|
|
1743
|
+
const readStarted = performance.now();
|
|
1744
|
+
const loaded = await takeSource();
|
|
1745
|
+
const source = loaded.source;
|
|
1746
|
+
const pageStarted = performance.now();
|
|
1747
|
+
try {
|
|
1748
|
+
if (source.status === 'unavailable' || source.records === null) {
|
|
1749
|
+
return {
|
|
1750
|
+
contract_version: JUDGMENT_HISTORY_CONTRACT_VERSION,
|
|
1751
|
+
status: source.status,
|
|
1752
|
+
records: ids.map(() => null),
|
|
1753
|
+
coverage: source.coverage
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
const byId = new Map(source.records.map((record) => [record.record_id, record]));
|
|
1757
|
+
const records = [];
|
|
1758
|
+
for (const id of ids) {
|
|
1759
|
+
const record = byId.get(id) ?? null;
|
|
1760
|
+
if (!record || !options.authorizeProject) {
|
|
1761
|
+
records.push(record);
|
|
1762
|
+
continue;
|
|
1763
|
+
}
|
|
1764
|
+
// A forbidden or unbound project is indistinguishable from a missing id.
|
|
1765
|
+
const allowed = record.project_code !== null && await options.authorizeProject(record.project_code);
|
|
1766
|
+
records.push(allowed ? record : null);
|
|
1511
1767
|
}
|
|
1768
|
+
return {
|
|
1769
|
+
contract_version: JUDGMENT_HISTORY_CONTRACT_VERSION,
|
|
1770
|
+
status: source.status,
|
|
1771
|
+
records,
|
|
1772
|
+
coverage: { ...source.coverage, total: source.coverage.complete ? source.records.length : null }
|
|
1773
|
+
};
|
|
1774
|
+
}
|
|
1775
|
+
finally {
|
|
1776
|
+
remember(readStarted, pageStarted, loaded.sourceTimings);
|
|
1512
1777
|
}
|
|
1513
|
-
return {
|
|
1514
|
-
contract_version: JUDGMENT_HISTORY_CONTRACT_VERSION,
|
|
1515
|
-
status: source.status,
|
|
1516
|
-
record,
|
|
1517
|
-
coverage: { ...source.coverage, total: source.coverage.complete ? source.records.length : null }
|
|
1518
|
-
};
|
|
1519
1778
|
}
|
|
1520
1779
|
};
|
|
1521
1780
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unson/brainbase-mcp",
|
|
3
|
-
"version": "0.41.
|
|
3
|
+
"version": "0.41.6",
|
|
4
4
|
"description": "Local-first Brainbase judgment OS that lets supported AI agents reuse a user's approved goals, priorities, decisions, and judgment criteria.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -483,5 +483,5 @@
|
|
|
483
483
|
"engines": {
|
|
484
484
|
"node": ">=20"
|
|
485
485
|
},
|
|
486
|
-
"gitHead": "
|
|
486
|
+
"gitHead": "0c67baf7d7c57f4f671c966c8053705af65ba4d0"
|
|
487
487
|
}
|
package/ui/brainbase-tokens.css
CHANGED
|
@@ -62,6 +62,10 @@
|
|
|
62
62
|
--bb-color-notice-control-border: #ffffff45;
|
|
63
63
|
--bb-color-inline-error: #9b3d3d;
|
|
64
64
|
--bb-color-record-missing: #80601c;
|
|
65
|
+
--bb-color-drawer-overlay: rgb(13 25 46 / 6%);
|
|
66
|
+
--bb-color-drawer-overlay-clear: rgb(13 25 46 / 0%);
|
|
67
|
+
--bb-shadow-drawer: -18px 0 48px rgb(13 25 46 / 10%);
|
|
68
|
+
--bb-shadow-popover: 0 8px 28px rgb(13 25 46 / 14%);
|
|
65
69
|
--bb-color-dialog-overlay: #0d192e66;
|
|
66
70
|
--bb-color-scrollbar-rgb: 36,50,75;
|
|
67
71
|
--bb-color-scrollbar-dark-rgb: 203,212,225;
|