@lorekit/cli 1.33.1 → 1.33.3

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/bin/lorekit.mjs CHANGED
@@ -718,8 +718,38 @@ async function main() {
718
718
  }
719
719
 
720
720
  main()
721
- .then((code) => process.exit(code ?? 0))
721
+ .then((code) => flushThenExit(code ?? 0))
722
722
  .catch((e) => {
723
723
  err(`${c.red('Error:')} ${e && e.stack ? e.stack : e}`);
724
- process.exit(1);
724
+ flushThenExit(1);
725
725
  });
726
+
727
+ // Exit only after stdout/stderr have drained.
728
+ //
729
+ // `process.exit()` truncates any output still buffered for a PIPE (the shape a
730
+ // spawned child's stdout has), because pipe writes are asynchronous. `lorekit
731
+ // mcp` streams newline-delimited JSON-RPC frames to stdout, and a large frame —
732
+ // e.g. a `memory.list` result for a big scope — overflows the pipe buffer, so
733
+ // exiting the instant `main()` resolves drops the tail of that write and the
734
+ // client sees a silent "no response" (this reproduced deterministically once a
735
+ // scope's payload crossed ~½ MB). Flushing first makes the final frame whole.
736
+ // The unref'd safety timer guarantees the process still exits if a stream never
737
+ // drains, so this can never turn a finished command into a hang.
738
+ function flushThenExit(code) {
739
+ let pending = 2;
740
+ const done = () => {
741
+ pending -= 1;
742
+ if (pending === 0) process.exit(code);
743
+ };
744
+ const safety = setTimeout(() => process.exit(code), 2000);
745
+ safety.unref?.();
746
+ for (const stream of [process.stdout, process.stderr]) {
747
+ try {
748
+ // An empty write's callback fires only after every previously-queued write
749
+ // has flushed to the fd, so it is a reliable drain barrier.
750
+ stream.write('', done);
751
+ } catch {
752
+ done();
753
+ }
754
+ }
755
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lorekit/cli",
3
- "version": "1.33.1",
3
+ "version": "1.33.3",
4
4
  "description": "Install the LoreKit shared-memory skill and run health checks for the LoreKit MCP server.",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/mcp.mjs CHANGED
@@ -138,6 +138,26 @@ export function normalizeCorrelationId(raw) {
138
138
  return /^[A-Za-z0-9_\-./:#@]+$/.test(t) ? t : null;
139
139
  }
140
140
 
141
+ /**
142
+ * Normalise a deployment-environment marker restFetch attaches as
143
+ * X-LoreKit-Deployment-Environment when DEPLOYMENT_ENVIRONMENT (or
144
+ * OTEL_DEPLOYMENT_ENVIRONMENT) is set. It lets a smoke/test run tell the edge to
145
+ * report `deployment.environment.name` for that request — the edge honours only
146
+ * the synthetic `test` value, so real traffic (no env set) is never tagged and a
147
+ * caller can never relabel itself as another real environment. This is the SAME
148
+ * value `resolveDeploymentEnvironment` (packages/cli/src/telemetry.mjs) puts on
149
+ * the CLI's own resource, so one `DEPLOYMENT_ENVIRONMENT=test` marks both the CLI
150
+ * span and its downstream edge spans. Bounded + charset-restricted (fail-safe: a
151
+ * bad value is not sent). Zero-dep, so the small regex is duplicated
152
+ * intentionally, like `normalizeCorrelationId`.
153
+ */
154
+ export function normalizeRunEnvironment(raw) {
155
+ if (typeof raw !== 'string') return null;
156
+ const t = raw.trim();
157
+ if (!t || t.length > 64) return null;
158
+ return /^[A-Za-z0-9_.\-:]+$/.test(t) ? t : null;
159
+ }
160
+
141
161
  export async function restFetch(baseUrl, token, path, { method = 'GET', body, timeoutMs = 10000, traceparent } = {}) {
142
162
  const controller = new AbortController();
143
163
  const timer = setTimeout(() => controller.abort(), timeoutMs);
@@ -148,12 +168,21 @@ export async function restFetch(baseUrl, token, path, { method = 'GET', body, ti
148
168
  // /memories/usage?correlation_id=… can report "usage for this PR". Absent env
149
169
  // ⇒ no header ⇒ existing behaviour unchanged.
150
170
  const correlationId = normalizeCorrelationId(process.env.LOREKIT_CORRELATION_ID);
171
+ // Opt-in test-run marker: when DEPLOYMENT_ENVIRONMENT is set (a deploy/CI
172
+ // smoke sets it to `test`), tell the edge to report that
173
+ // `deployment.environment.name` for this request so Dash0 can filter synthetic
174
+ // smoke traffic apart from real usage. Absent env ⇒ no header ⇒ existing
175
+ // behaviour unchanged. The edge honours only `test` (see otel.ts).
176
+ const runEnv = normalizeRunEnvironment(
177
+ process.env.DEPLOYMENT_ENVIRONMENT ?? process.env.OTEL_DEPLOYMENT_ENVIRONMENT,
178
+ );
151
179
  const headers = {
152
180
  accept: 'application/json',
153
181
  ...(body !== undefined ? { 'content-type': 'application/json' } : {}),
154
182
  ...(token ? { authorization: `Bearer ${token}` } : {}),
155
183
  ...(traceparent ? { traceparent } : {}),
156
184
  ...(correlationId ? { 'x-lorekit-correlation-id': correlationId } : {}),
185
+ ...(runEnv ? { 'x-lorekit-deployment-environment': runEnv } : {}),
157
186
  // Name the calling surface so usage analytics can tell a CLI read from a
158
187
  // dashboard one. Not cosmetic: `GET /memories/read-activity` EXCLUDES the
159
188
  // `dashboard` client (a human browsing lore is not consuming it), so a