@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.
@@ -1,8 +1,19 @@
1
- import { JUDGMENT_HISTORY_ENTRYPOINTS, createJudgmentHistoryReader, JudgmentHistoryFeedbackConflictError } from './judgment-history.js';
1
+ import { JUDGMENT_HISTORY_ENTRYPOINTS, MAX_JUDGMENT_HISTORY_RESOLUTION_BATCH, createJudgmentHistoryReader, judgmentHistoryRequestTimings, JudgmentHistoryFeedbackConflictError, withJudgmentHistoryRequestTimings } from './judgment-history.js';
2
2
  import { LocalWebHttpError, readJsonObjectBody, writeJson } from './local-web-security.js';
3
3
  export const JUDGMENT_HISTORY_HTTP_VERSION = 'judgment-history-http.v1';
4
4
  export const JUDGMENT_HISTORY_HTTP_PREFIX = '/api/judgment-history';
5
5
  export const JUDGMENT_HISTORY_FEEDBACK_BODY_LIMIT_BYTES = 64 * 1024;
6
+ function serverTimingHeader(timings, handlerMs) {
7
+ const parts = [`total;dur=${handlerMs.toFixed(1)}`];
8
+ if (timings) {
9
+ parts.push(`journal;dur=${timings.journal_ms.toFixed(1)}`);
10
+ parts.push(`page;dur=${timings.page_ms.toFixed(1)}`);
11
+ parts.push(`cache;desc="hits=${timings.cache_hits},misses=${timings.cache_misses}"`);
12
+ parts.push(`parallel;desc="${timings.parallel_limit}"`);
13
+ }
14
+ parts.push('order;desc="newest-first"');
15
+ return parts.join(', ');
16
+ }
6
17
  function methodNotAllowed() {
7
18
  const error = new Error('Use GET or POST');
8
19
  error.statusCode = 405;
@@ -14,6 +25,8 @@ function matchRoute(pathname, basePath) {
14
25
  return { name: 'home' };
15
26
  if (pathname === `${basePath}/feedback`)
16
27
  return { name: 'feedback' };
28
+ if (pathname === `${basePath}/resolutions`)
29
+ return { name: 'resolutions' };
17
30
  if (!pathname.startsWith(`${basePath}/records/`))
18
31
  return null;
19
32
  const encoded = pathname.slice(`${basePath}/records/`.length);
@@ -48,6 +61,17 @@ function readerError(statusCode, code, message) {
48
61
  error.code = code;
49
62
  return error;
50
63
  }
64
+ function parseResolutionIds(url) {
65
+ for (const key of url.searchParams.keys()) {
66
+ if (key !== 'id')
67
+ throw readerError(400, 'unknown_query', `Unknown query parameter: ${key}`);
68
+ }
69
+ const ids = url.searchParams.getAll('id');
70
+ if (ids.length < 1 || ids.length > MAX_JUDGMENT_HISTORY_RESOLUTION_BATCH) {
71
+ throw readerError(400, 'invalid_resolution_ids', `id must be repeated between 1 and ${MAX_JUDGMENT_HISTORY_RESOLUTION_BATCH} times`);
72
+ }
73
+ return ids;
74
+ }
51
75
  function parseQuery(url) {
52
76
  const allowed = new Set(['period', 'project', 'entrypoint', 'cursor', 'limit']);
53
77
  for (const key of url.searchParams.keys()) {
@@ -90,7 +114,18 @@ export function createJudgmentHistoryHttpHandler(options) {
90
114
  }
91
115
  const basePath = (options.basePath ?? JUDGMENT_HISTORY_HTTP_PREFIX).replace(/\/+$/u, '');
92
116
  const reader = options.reader;
93
- return async (request, response) => {
117
+ return (request, response) => withJudgmentHistoryRequestTimings(async () => {
118
+ const started = performance.now();
119
+ const writeTimed = (statusCode, body) => {
120
+ if (!response.headersSent) {
121
+ const scoped = judgmentHistoryRequestTimings();
122
+ const timings = scoped.bound
123
+ ? scoped.timings
124
+ : (typeof reader.timings === 'function' ? reader.timings() : null);
125
+ response.setHeader('Server-Timing', serverTimingHeader(timings, performance.now() - started));
126
+ }
127
+ writeJson(response, statusCode, body);
128
+ };
94
129
  const url = new URL(request.url ?? '/', 'http://localhost');
95
130
  let route;
96
131
  try {
@@ -98,7 +133,7 @@ export function createJudgmentHistoryHttpHandler(options) {
98
133
  }
99
134
  catch (error) {
100
135
  const normalized = toError(error);
101
- writeJson(response, normalized.statusCode, {
136
+ writeTimed(normalized.statusCode, {
102
137
  error: { code: normalized.code, message: normalized.message }
103
138
  });
104
139
  return true;
@@ -125,24 +160,30 @@ export function createJudgmentHistoryHttpHandler(options) {
125
160
  feedback_event: result.event,
126
161
  ...result.event
127
162
  };
128
- writeJson(response, result.created ? 201 : 200, responseBody);
163
+ writeTimed(result.created ? 201 : 200, responseBody);
129
164
  }
130
165
  else {
131
166
  if (request.method !== 'GET')
132
167
  throw methodNotAllowed();
133
- if (route.name === 'home') {
134
- writeJson(response, 200, await reader.home(parseQuery(url)));
168
+ if (route.name === 'resolutions') {
169
+ if (typeof reader.findByResolutionIds !== 'function') {
170
+ throw readerError(501, 'resolution_batch_unavailable', 'This reader cannot look up resolution ids in one read');
171
+ }
172
+ writeTimed(200, await reader.findByResolutionIds(parseResolutionIds(url)));
173
+ }
174
+ else if (route.name === 'home') {
175
+ writeTimed(200, await reader.home(parseQuery(url)));
135
176
  }
136
177
  else {
137
178
  const result = await reader.detail(route.recordId);
138
179
  if (result.status === 'unavailable') {
139
- writeJson(response, 200, result);
180
+ writeTimed(200, result);
140
181
  }
141
182
  else if (result.status === 'available' && !result.record) {
142
183
  throw readerError(404, 'judgment_history_not_found', 'No judgment history record matches this id');
143
184
  }
144
185
  else {
145
- writeJson(response, 200, result);
186
+ writeTimed(200, result);
146
187
  }
147
188
  }
148
189
  }
@@ -150,19 +191,19 @@ export function createJudgmentHistoryHttpHandler(options) {
150
191
  catch (error) {
151
192
  const normalized = toError(error);
152
193
  if (error instanceof JudgmentHistoryFeedbackConflictError) {
153
- writeJson(response, normalized.statusCode, {
194
+ writeTimed(normalized.statusCode, {
154
195
  error: { code: normalized.code, message: normalized.message },
155
196
  feedback_event: error.existing
156
197
  });
157
198
  }
158
199
  else {
159
- writeJson(response, normalized.statusCode, {
200
+ writeTimed(normalized.statusCode, {
160
201
  error: { code: normalized.code, message: normalized.message }
161
202
  });
162
203
  }
163
204
  }
164
205
  return true;
165
- };
206
+ });
166
207
  }
167
208
  /** Convenience composition seam for adapters that want the HTTP contract without a local journal. */
168
209
  export function createJudgmentHistoryHttpReader(options) {
@@ -85,11 +85,34 @@ export interface JudgmentHistorySourceSnapshot {
85
85
  readonly coverage: JudgmentHistoryCoverage;
86
86
  /** Stable source revision used to bind pagination cursors. */
87
87
  readonly snapshot_id?: string;
88
+ /** Measurements for this read. Concurrent reads each carry their own. */
89
+ readonly timings?: JudgmentHistoryReadTimings;
90
+ }
91
+ export interface JudgmentHistoryReadTimings {
92
+ /** Wall time of the latest home, detail, or batch read, including paging. */
93
+ readonly total_ms: number;
94
+ /** Time spent discovering and reading journal artifacts. */
95
+ readonly journal_ms: number;
96
+ /** Time spent filtering and slicing the newest-first page. */
97
+ readonly page_ms: number;
98
+ readonly cache_hits: number;
99
+ readonly cache_misses: number;
100
+ readonly parallel_limit: number;
101
+ readonly scanned_files: number;
88
102
  }
89
103
  export interface JudgmentHistorySource {
90
104
  readonly storage: JudgmentHistoryStorage;
91
105
  readonly read: () => Promise<JudgmentHistorySourceSnapshot>;
92
- }
106
+ /** Timings of the latest completed `read`. Overlapping reads use `snapshot.timings`. */
107
+ readonly timings?: () => JudgmentHistoryReadTimings | null;
108
+ }
109
+ /** Isolates Server-Timing for one HTTP request from other in-flight reads. */
110
+ export declare function withJudgmentHistoryRequestTimings<T>(fn: () => Promise<T>): Promise<T>;
111
+ /** Timings published by the in-flight request, when one is bound. */
112
+ export declare function judgmentHistoryRequestTimings(): {
113
+ readonly bound: boolean;
114
+ readonly timings: JudgmentHistoryReadTimings | null;
115
+ };
93
116
  export interface JudgmentHistoryQuery {
94
117
  readonly period?: JudgmentHistoryPeriod;
95
118
  readonly project?: string;
@@ -128,9 +151,18 @@ export interface JudgmentHistoryReaderOptions {
128
151
  /** Organization adapters provide trusted scope authorization here. */
129
152
  readonly authorizeProject?: (project: string) => boolean | Promise<boolean>;
130
153
  }
154
+ export interface JudgmentHistoryResolutionBatch {
155
+ readonly contract_version: typeof JUDGMENT_HISTORY_CONTRACT_VERSION;
156
+ readonly status: JudgmentHistoryStatus;
157
+ readonly records: readonly (JudgmentHistoryRecord | null)[];
158
+ readonly coverage: JudgmentHistoryCoverage;
159
+ }
131
160
  export interface JudgmentHistoryReader {
132
161
  readonly home: (query?: JudgmentHistoryQuery) => Promise<JudgmentHistoryHome>;
133
162
  readonly detail: (recordId: string) => Promise<JudgmentHistoryDetail>;
163
+ /** One journal read for many resolution ids. `records` keeps the request order. */
164
+ readonly findByResolutionIds: (resolutionIds: readonly string[]) => Promise<JudgmentHistoryResolutionBatch>;
165
+ readonly timings: () => JudgmentHistoryReadTimings | null;
134
166
  }
135
167
  export declare class JudgmentHistoryReaderError extends Error {
136
168
  readonly statusCode: number;
@@ -144,7 +176,34 @@ export interface LocalJudgmentHistorySourceOptions {
144
176
  readonly max_files?: number;
145
177
  /** Maximum size of one journal artifact, in bytes. */
146
178
  readonly max_record_bytes?: number;
147
- }
179
+ /** Maximum journal artifact reads in flight. */
180
+ readonly read_parallel?: number;
181
+ /** Reuses parsed artifacts while (session, turn, mtime) is unchanged. */
182
+ readonly cache?: JudgmentHistoryJournalScanCache;
183
+ }
184
+ export declare const DEFAULT_LOCAL_JUDGMENT_HISTORY_READ_PARALLEL = 8;
185
+ export declare const MAX_JUDGMENT_HISTORY_RESOLUTION_BATCH = 100;
186
+ /** Parsed artifacts kept for one local source. The oldest entry is dropped past this. */
187
+ export declare const DEFAULT_JUDGMENT_HISTORY_JOURNAL_CACHE_LIMIT = 4096;
188
+ /**
189
+ * Remembers parsed journal artifacts for one local source.
190
+ * The key is (session, turn, file name, mtime). A later read of the same turn
191
+ * misses when that file's mtime changes, and the previous bytes are not reused.
192
+ * A lookup refreshes recency. Past `limit`, the least recently used entry is dropped.
193
+ */
194
+ export declare class JudgmentHistoryJournalScanCache {
195
+ #private;
196
+ constructor(limit?: number);
197
+ get limit(): number;
198
+ get size(): number;
199
+ lookup(session: string, turn: string, fileName: string, mtimeNs: bigint): BoundedJsonResult | null;
200
+ store(session: string, turn: string, fileName: string, mtimeNs: bigint, result: BoundedJsonResult): void;
201
+ noteMiss(): void;
202
+ get hits(): number;
203
+ get misses(): number;
204
+ }
205
+ /** Runs `map` with at most `limit` calls in flight. Results keep input order. */
206
+ export declare function mapWithConcurrencyLimit<T, R>(items: readonly T[], limit: number, map: (item: T, index: number) => Promise<R>): Promise<R[]>;
148
207
  export interface LocalJudgmentHistoryFeedbackWriterOptions {
149
208
  readonly root: string;
150
209
  readonly reader: JudgmentHistoryReader;
@@ -160,9 +219,17 @@ export declare class JudgmentHistoryFeedbackConflictError extends JudgmentHistor
160
219
  readonly existing: JudgmentHistoryFeedbackEvent;
161
220
  constructor(existing: JudgmentHistoryFeedbackEvent);
162
221
  }
222
+ interface JsonObject {
223
+ [key: string]: unknown;
224
+ }
163
225
  export declare const DEFAULT_LOCAL_JUDGMENT_HISTORY_SCAN_CAP = 4096;
164
226
  export declare const DEFAULT_LOCAL_JUDGMENT_HISTORY_RECORD_BYTES: number;
165
227
  export declare function canonicalJson(value: unknown): string;
228
+ interface BoundedJsonResult {
229
+ readonly value: JsonObject | null;
230
+ readonly missing: boolean;
231
+ readonly invalid: boolean;
232
+ }
166
233
  /** Reads only the configured local owner journal; it never searches or uploads another journal. */
167
234
  export declare function createLocalJudgmentHistorySource(options: LocalJudgmentHistorySourceOptions): JudgmentHistorySource;
168
235
  /**
@@ -176,3 +243,4 @@ export declare function appendLocalJudgmentHistoryFeedback(options: LocalJudgmen
176
243
  }): Promise<JudgmentHistoryFeedbackWriteResult>;
177
244
  /** Reader shared by local, organization, Mana, and Company OS adapters. */
178
245
  export declare function createJudgmentHistoryReader(options: JudgmentHistoryReaderOptions): JudgmentHistoryReader;
246
+ export {};