@lorekit/cli 1.57.0 → 1.58.0
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/package.json +1 -1
- package/src/shared/mcp.mjs +104 -6
- package/src/shared/mirror-pairs.mjs +6 -0
package/package.json
CHANGED
package/src/shared/mcp.mjs
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
// Minimal MCP-over-HTTP (JSON-RPC 2.0) client for the LoreKit endpoint.
|
|
2
|
-
// Zero dependencies — uses the global fetch (Node 18+).
|
|
2
|
+
// Zero EXTERNAL dependencies — uses the global fetch (Node 18+). Imports
|
|
3
|
+
// below are same-package sibling modules (`./origin.mjs`), not npm deps.
|
|
4
|
+
|
|
5
|
+
import { prNumberFromEnv, isValidRepo } from './origin.mjs';
|
|
3
6
|
|
|
4
7
|
// Split a configured server URL like ".../mcp?token=lk_rw_x" into
|
|
5
8
|
// { endpoint: ".../mcp", token: "lk_rw_x" }.
|
|
@@ -170,6 +173,94 @@ export function normalizeCorrelationId(raw) {
|
|
|
170
173
|
return /^[A-Za-z0-9_\-./:#@]+$/.test(t) ? t : null;
|
|
171
174
|
}
|
|
172
175
|
|
|
176
|
+
/**
|
|
177
|
+
* The bounded `session_kind` vocabulary (migration 00082) — sent via
|
|
178
|
+
* `X-LoreKit-Session-Kind`, validated edge-side by the CROSS-LANGUAGE twin of
|
|
179
|
+
* this file's derivation, `packages/mcp-core/src/telemetry/session-kind.ts`
|
|
180
|
+
* (`parseSessionKind`). Kept here rather than imported: this package has no
|
|
181
|
+
* dependency on `@lorekit/core`, and the two are guarded for behavioural
|
|
182
|
+
* parity by `session-kind-parity.spec.ts` rather than a byte comparison,
|
|
183
|
+
* which is what a cross-language pair (this `.mjs` vs that `.ts`) needs.
|
|
184
|
+
*/
|
|
185
|
+
const SESSION_KINDS = ['local', 'ci', 'pr', 'unknown'];
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Derive `{ correlationId, sessionKind }` from the ambient environment, for
|
|
189
|
+
* every call site that does not have an EXPLICIT `LOREKIT_CORRELATION_ID` —
|
|
190
|
+
* the caller checks that first and skips this entirely when it is set, since
|
|
191
|
+
* an explicit value always wins.
|
|
192
|
+
*
|
|
193
|
+
* Precedence, first match wins:
|
|
194
|
+
* 1. PR context — `prNumberFromEnv` (LOREKIT_PR / GITHUB_REF / GITHUB_PR_NUMBER,
|
|
195
|
+
* see `origin.mjs`) resolves a PR number AND a repo is known → `pr` +
|
|
196
|
+
* `pr:<owner>/<repo>#<n>`.
|
|
197
|
+
* 2. CI environment (`GITHUB_ACTIONS`/`CI`) — `ci` always; a correlation id
|
|
198
|
+
* of `ci:<owner>/<repo>#<run_id>` when both a repo and GITHUB_RUN_ID are
|
|
199
|
+
* known, otherwise no correlation id (still `ci` — the session KIND is
|
|
200
|
+
* known even when a stable id to group by is not).
|
|
201
|
+
* 3. A host-provided session id (`LOREKIT_SESSION_ID`, or the handful of
|
|
202
|
+
* well-known agent-host env vars below) — `local` +
|
|
203
|
+
* `session:<id>`. The raw id itself is never logged or stored anywhere
|
|
204
|
+
* beyond this derived correlation id.
|
|
205
|
+
* 4. Otherwise `unknown`, no correlation id — never a guess.
|
|
206
|
+
*
|
|
207
|
+
* TOTAL and fail-safe: reads only `env` (never throws on a missing/odd
|
|
208
|
+
* value), and every branch degrades to the next rather than throwing. A
|
|
209
|
+
* derived value that fails `normalizeCorrelationId`'s charset/length check is
|
|
210
|
+
* dropped (session_kind is still reported; only the drill-down id is not).
|
|
211
|
+
*/
|
|
212
|
+
export function deriveSessionContext(env = process.env) {
|
|
213
|
+
const repo = isValidRepo(env.GITHUB_REPOSITORY);
|
|
214
|
+
const prNumber = prNumberFromEnv(env);
|
|
215
|
+
|
|
216
|
+
if (prNumber !== null && repo) {
|
|
217
|
+
const correlationId = normalizeCorrelationId(`pr:${repo}#${prNumber}`);
|
|
218
|
+
if (correlationId) return { correlationId, sessionKind: 'pr' };
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const isCI = env.GITHUB_ACTIONS === 'true' || env.CI === 'true' || env.CI === '1';
|
|
222
|
+
if (isCI) {
|
|
223
|
+
const runId = typeof env.GITHUB_RUN_ID === 'string' ? env.GITHUB_RUN_ID.trim() : '';
|
|
224
|
+
if (repo && runId) {
|
|
225
|
+
const correlationId = normalizeCorrelationId(`ci:${repo}#${runId}`);
|
|
226
|
+
if (correlationId) return { correlationId, sessionKind: 'ci' };
|
|
227
|
+
}
|
|
228
|
+
return { correlationId: null, sessionKind: 'ci' };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Well-known agent-host session id env vars. Best-effort: hosts differ and
|
|
232
|
+
// this is not an exhaustive registry, so an unrecognised host still falls
|
|
233
|
+
// through to `unknown` rather than fabricating an id.
|
|
234
|
+
const sessionId = firstNonEmptyEnv(env, ['LOREKIT_SESSION_ID', 'CLAUDE_SESSION_ID']);
|
|
235
|
+
if (sessionId) {
|
|
236
|
+
// A local session IS known even when the specific id fails the
|
|
237
|
+
// correlation-id charset/length check — report the kind either way, and
|
|
238
|
+
// let the id itself degrade to null rather than losing the whole reading.
|
|
239
|
+
return { correlationId: normalizeCorrelationId(`session:${sessionId}`), sessionKind: 'local' };
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return { correlationId: null, sessionKind: 'unknown' };
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function firstNonEmptyEnv(env, keys) {
|
|
246
|
+
for (const key of keys) {
|
|
247
|
+
const v = env[key];
|
|
248
|
+
if (typeof v === 'string' && v.trim() !== '') return v.trim();
|
|
249
|
+
}
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Validate a `session_kind` value against the closed vocabulary. Total and
|
|
255
|
+
* fail-safe — mirrors `parseSessionKind`'s behaviour (never used to VALIDATE
|
|
256
|
+
* an incoming value here, since this process only ever sends a value it just
|
|
257
|
+
* derived itself, but kept as the single place the vocabulary is spelled out
|
|
258
|
+
* so `deriveSessionContext` and any future caller cannot drift from it).
|
|
259
|
+
*/
|
|
260
|
+
export function isSessionKind(value) {
|
|
261
|
+
return SESSION_KINDS.includes(value);
|
|
262
|
+
}
|
|
263
|
+
|
|
173
264
|
/**
|
|
174
265
|
* Normalise a deployment-environment marker restFetch attaches as
|
|
175
266
|
* X-LoreKit-Deployment-Environment when DEPLOYMENT_ENVIRONMENT (or
|
|
@@ -219,11 +310,17 @@ export async function restFetch(baseUrl, token, path, { method = 'GET', body, ti
|
|
|
219
310
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
220
311
|
try {
|
|
221
312
|
const url = `${baseUrl}${path}`;
|
|
222
|
-
//
|
|
223
|
-
// job or a hook
|
|
224
|
-
//
|
|
225
|
-
//
|
|
226
|
-
|
|
313
|
+
// Usage correlation: an EXPLICIT LOREKIT_CORRELATION_ID always wins (e.g. a
|
|
314
|
+
// CI job or a hook hand-setting a PR/session id). Otherwise, derive one
|
|
315
|
+
// from the ambient environment (CI/PR/session — see `deriveSessionContext`)
|
|
316
|
+
// so GET /memories/usage?correlation_id=… and the session_kind dimension
|
|
317
|
+
// are populated without anyone having to export anything by hand. Both
|
|
318
|
+
// stay unset only when neither an explicit value nor a derivable one
|
|
319
|
+
// exists (`sessionKind: 'unknown'`, no correlationId).
|
|
320
|
+
const explicitCorrelationId = normalizeCorrelationId(process.env.LOREKIT_CORRELATION_ID);
|
|
321
|
+
const derived = explicitCorrelationId ? null : deriveSessionContext(process.env);
|
|
322
|
+
const correlationId = explicitCorrelationId ?? derived?.correlationId ?? null;
|
|
323
|
+
const sessionKind = derived?.sessionKind ?? null;
|
|
227
324
|
// Opt-in test-run marker: when DEPLOYMENT_ENVIRONMENT is set (a deploy/CI
|
|
228
325
|
// smoke sets it to `test`), tell the edge to report that
|
|
229
326
|
// `deployment.environment.name` for this request so Dash0 can filter synthetic
|
|
@@ -238,6 +335,7 @@ export async function restFetch(baseUrl, token, path, { method = 'GET', body, ti
|
|
|
238
335
|
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
239
336
|
...(traceparent ? { traceparent } : {}),
|
|
240
337
|
...(correlationId ? { 'x-lorekit-correlation-id': correlationId } : {}),
|
|
338
|
+
...(sessionKind ? { 'x-lorekit-session-kind': sessionKind } : {}),
|
|
241
339
|
...(runEnv ? { 'x-lorekit-deployment-environment': runEnv } : {}),
|
|
242
340
|
// Name the calling surface so usage analytics can tell a CLI read from a
|
|
243
341
|
// dashboard one. Not cosmetic: `GET /memories/read-activity` EXCLUDES the
|
|
@@ -54,6 +54,12 @@ export const mirrorPairs = [
|
|
|
54
54
|
{ core: 'packages/mcp-core/src/webhook/github-app-jwt.ts', edge: 'supabase/functions/mcp/github-app-jwt.ts', driftChecked: true },
|
|
55
55
|
{ core: 'packages/mcp-core/src/telemetry/trace-context.ts', edge: 'supabase/functions/_shared/telemetry/trace-context.ts', driftChecked: true },
|
|
56
56
|
{ core: 'packages/mcp-core/src/rest/rest-tool-name.ts', edge: 'supabase/functions/_shared/rest/rest-tool-name.ts', driftChecked: true },
|
|
57
|
+
// The `X-LoreKit-Session-Kind` validator (migration 00082). Also has a
|
|
58
|
+
// SECOND, cross-LANGUAGE twin — the CLI's `deriveSessionContext` in
|
|
59
|
+
// `packages/cli/src/shared/mcp.mjs` — guarded behaviourally by
|
|
60
|
+
// `packages/cli/test/session-context.test.mjs`, the same split
|
|
61
|
+
// `lesson-rank.ts` uses below.
|
|
62
|
+
{ core: 'packages/mcp-core/src/telemetry/session-kind.ts', edge: 'supabase/functions/_shared/telemetry/session-kind.ts', driftChecked: true },
|
|
57
63
|
// Has a SECOND, cross-LANGUAGE twin no byte comparison can cover — the
|
|
58
64
|
// CLI's own `lessons-pure.mjs` — guarded behaviourally by
|
|
59
65
|
// `lesson-rank-parity.spec.ts` instead.
|