@lensmcp/protocol-types 1.25.3 → 1.25.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/index.d.ts CHANGED
@@ -19,3 +19,4 @@ export * from './lib/ai-content.js';
19
19
  export * from './lib/http-exchange.js';
20
20
  export * from './lib/pod-memory.js';
21
21
  export * from './lib/perf-rules.js';
22
+ export * from './lib/sources.js';
package/index.js CHANGED
@@ -1 +1 @@
1
- "use strict";export*from"./lib/schema-version.js";export*from"./lib/source-location.js";export*from"./lib/context.js";export*from"./lib/event.js";export*from"./lib/node.js";export*from"./lib/edge.js";export*from"./lib/attributes.js";export*from"./lib/producer-health.js";export*from"./lib/resources.js";export*from"./lib/tokens.js";export*from"./lib/capture-demand.js";export*from"./lib/bounded-clone.js";export*from"./lib/bounded-map.js";export*from"./lib/flood-guard.js";export*from"./lib/dashboard.js";export*from"./lib/log-query.js";export*from"./lib/error-dismissal.js";export*from"./lib/ai-content.js";export*from"./lib/http-exchange.js";export*from"./lib/pod-memory.js";export*from"./lib/perf-rules.js";
1
+ "use strict";export*from"./lib/schema-version.js";export*from"./lib/source-location.js";export*from"./lib/context.js";export*from"./lib/event.js";export*from"./lib/node.js";export*from"./lib/edge.js";export*from"./lib/attributes.js";export*from"./lib/producer-health.js";export*from"./lib/resources.js";export*from"./lib/tokens.js";export*from"./lib/capture-demand.js";export*from"./lib/bounded-clone.js";export*from"./lib/bounded-map.js";export*from"./lib/flood-guard.js";export*from"./lib/dashboard.js";export*from"./lib/log-query.js";export*from"./lib/error-dismissal.js";export*from"./lib/ai-content.js";export*from"./lib/http-exchange.js";export*from"./lib/pod-memory.js";export*from"./lib/perf-rules.js";export*from"./lib/sources.js";
@@ -106,7 +106,8 @@ export interface DashService {
106
106
  min?: number;
107
107
  max?: number;
108
108
  };
109
- workers: string[];
109
+ /** Its `serve` workers — each declared one, with what its devserver has said of it. */
110
+ workers: DashWorker[];
110
111
  lastColdStartMs?: number;
111
112
  restarts: number;
112
113
  buildFailed: boolean;
@@ -141,6 +142,40 @@ export interface DashService {
141
142
  /** What its running pods weigh, from their own post-GC reports (`pod-memory` events). Absent until one reports. */
142
143
  memory?: DashServiceMemory;
143
144
  }
145
+ /**
146
+ * A service's `serve` worker (issues/closed/foodguard-80): a queue consumer or a background loop its devserver runs
147
+ * beside the pods, started again when it crashes. One that is not `running` is work nobody is doing.
148
+ */
149
+ export interface DashWorker {
150
+ name: string;
151
+ /**
152
+ * `running` · `restarting` — it crashed and starts again at `nextAt` · `crash-looping` — it keeps crashing, each wait
153
+ * longer · `exited` — it ended on its own, or its bundle is missing: not started again until the next build ·
154
+ * `stopped` — its service stopped · `unknown` — declared, and its devserver has said nothing of it (one from before
155
+ * 1.25.3 says nothing).
156
+ */
157
+ state: 'running' | 'restarting' | 'crash-looping' | 'exited' | 'stopped' | 'unknown';
158
+ pid?: number;
159
+ /** When it entered this state. */
160
+ since?: number;
161
+ /** Crashes this session. */
162
+ crashes: number;
163
+ /** Crashes in a row (restarting / crash-looping). */
164
+ inARow?: number;
165
+ /** When it is started again. */
166
+ nextAt?: number;
167
+ /** Why it is not running (exited / stopped). */
168
+ reason?: string;
169
+ /** The last time it ended unasked: how, and what it printed last. */
170
+ lastCrash?: {
171
+ at: number;
172
+ exitCode: number | null;
173
+ signal: string | null;
174
+ reading?: string;
175
+ external?: boolean;
176
+ lines: string[];
177
+ };
178
+ }
144
179
  /** A service's pods, summed: what the Cluster tab's memory column shows. Bytes throughout. */
145
180
  export interface DashServiceMemory {
146
181
  /** Heap after each pod's latest full GC. */
@@ -621,6 +656,19 @@ export interface DashDiagnostics {
621
656
  slow: number;
622
657
  warnings: number;
623
658
  };
659
+ /**
660
+ * Set on the stream's frame, which carries only what a page lists: the findings of a {@link DASH_PERF_KINDS} kind
661
+ * (failures are the Errors screen's, from `DashErrors`), the open ones and then the recently resolved ones, each up
662
+ * to a cap. These are their numbers before the cap — `open - problems.filter(open).length` is a list's «N more».
663
+ * Absent from older servers, whose frame held every problem.
664
+ */
665
+ totals?: {
666
+ open: number;
667
+ resolved: number;
668
+ high: number;
669
+ medium: number;
670
+ low: number;
671
+ };
624
672
  }
625
673
  /** The problem kinds that are "slow or wasteful" — Performance's findings and the slow counter. */
626
674
  export declare const DASH_PERF_KINDS: readonly DashProblemKind[];
@@ -707,6 +755,11 @@ export interface DashErrors {
707
755
  recentOccurrences: number;
708
756
  /** Hidden by hand, and still hidden: nothing of them has been seen since. Listed so they can be restored. */
709
757
  dismissed: DashErrorGroup[];
758
+ /**
759
+ * Set on the stream's frame: each group carries at most this many `occurrences`, newest first (and the newest with a
760
+ * flow when none of those has one) — what Errors shows of a group. The error log keeps more; `count` is all of them.
761
+ */
762
+ occurrencesShown?: number;
710
763
  }
711
764
  /** `POST /api/errors/dismiss` and `/api/errors/restore` — the body, and the answer. */
712
765
  export interface DashErrorIds {
@@ -763,8 +816,55 @@ export type DashStreamEvent = {
763
816
  } | {
764
817
  type: 'errors';
765
818
  data: DashErrors;
819
+ }
820
+ /** The shell's numbers, on every tab whatever page it shows. */
821
+ | {
822
+ type: 'summary';
823
+ data: DashSummary;
824
+ }
825
+ /** Every 15 s on every connection — what tells a tab its stream is alive when its page's frames are quiet. */
826
+ | {
827
+ type: 'ping';
828
+ data: Record<string, never>;
766
829
  };
767
830
  export type DashStreamEventType = DashStreamEvent['type'];
831
+ /**
832
+ * What a tab asks the stream for: `GET /api/stream?topics=cluster,flows` — the frames its page reads. `hello` and
833
+ * `ping` go to every tab, `summary` too (asked or not); `logs-resync` goes with `logs`, and the log backlog is sent
834
+ * only to a tab that asks for `logs`. A tab that sends no `topics` gets every frame (a UI from before topics).
835
+ */
836
+ export type DashStreamTopic = 'summary' | 'session' | 'cluster' | 'kpis' | 'flows' | 'flow-rev' | 'logs' | 'diagnostics' | 'errors';
837
+ export declare const DASH_STREAM_TOPICS: readonly DashStreamTopic[];
838
+ /**
839
+ * The shell's numbers — the sidebar's badges, the top bar's counters, the scope card — for a tab whose page does not
840
+ * take the frame they come from. Counted by the same code as those frames, from what their producers last built.
841
+ * A part is absent until its producer first ran.
842
+ */
843
+ export interface DashSummary {
844
+ /** `DashErrors.recentGroups`, and how many groups it lists (`groups.length`). */
845
+ errors?: {
846
+ recentGroups: number;
847
+ groups: number;
848
+ };
849
+ /** `DashDiagnostics.counts`, and each warning's title (`warnings[].title`) — the top bar's tooltip. */
850
+ diagnostics?: {
851
+ slow: number;
852
+ warnings: number;
853
+ warningTitles: string[];
854
+ };
855
+ /** Each listed flow's `lastSeenAt`, newest first, as many as `flows` lists: the «new flows» badge counts those after the tab last looked. */
856
+ flows?: {
857
+ lastSeenAt: number[];
858
+ };
859
+ /** Services down, wedged or crash-looping; and the scope card's `DashCluster.instance` and `worktrees`. */
860
+ cluster?: {
861
+ troubled: number;
862
+ instance?: DashCluster['instance'];
863
+ worktrees?: DashWorktree[];
864
+ };
865
+ /** `DashSession.setupIssues.length`. */
866
+ setupIssues?: number;
867
+ }
768
868
  /**
769
869
  * What one run of a route touched, by key, with how many times:
770
870
  * `svc:<callee>` a service it called · `ext:<host>` a host outside the cluster · `sql:<VERB> <table>`
@@ -781,6 +881,8 @@ export interface DashRouteRun {
781
881
  /** Who called it: another service by name, else `client`. */
782
882
  caller?: string;
783
883
  touches: DashTouches;
884
+ /** Its request and response as they came in and went out (an `ingress` exchange, `/api/exchange?id=`). */
885
+ exchangeId?: string;
784
886
  }
785
887
  export type DashRouteChangeKind = 'new-external' | 'new-service' | 'new-query' | 'new-queue' | 'new-cache' | 'new-error' | 'gone' | 'gone-error' | 'count' | 'status' | 'duration';
786
888
  export interface DashRouteChange {
@@ -1016,8 +1118,13 @@ export interface DashExternalCall {
1016
1118
  /** The request it was made in — the calls that share it are one request's steps. */
1017
1119
  requestId?: string;
1018
1120
  }
1019
- /** One call opened (Data › External APIs' Request & response panel): the call, its exchange, its request's other steps. */
1121
+ /**
1122
+ * One exchange opened in the Request & response panel: an outbound call (Data › External APIs, a flow's step) or a
1123
+ * route's run (Routes › a route › Runs) — the call or the run, its exchange, its request's other steps.
1124
+ */
1020
1125
  export interface DashExchangeView {
1126
+ /** What was opened: an outbound call (`host` is its host), or a route's run (`host` is the service that served it). */
1127
+ of?: 'call' | 'run';
1021
1128
  host: string;
1022
1129
  call: DashExternalCall;
1023
1130
  /** Absent: not captured (capture off, headers rotated away) or withheld (`withheld` says why). */
@@ -1131,6 +1238,14 @@ export interface DashSqlFinding {
1131
1238
  tool: string;
1132
1239
  at: number;
1133
1240
  }
1241
+ /**
1242
+ * Why a statement is test SQL: every site it ran from is a test file (`sites`), its name starts with `test.` (`name`), or
1243
+ * nx.json → `lensmcp.sql.test` lists it (`listed`).
1244
+ */
1245
+ export interface DashSqlTestMark {
1246
+ by: 'sites' | 'name' | 'listed';
1247
+ why: string;
1248
+ }
1134
1249
  /**
1135
1250
  * A place in the services' own code where something ran — what a coding agent opens. From the stack the tap took at
1136
1251
  * the call: the service's nearest frame, and the callers above it.
@@ -1175,6 +1290,8 @@ export interface DashSqlTemplateSummary {
1175
1290
  sites?: DashCodeSite[];
1176
1291
  /** An unnamed statement its named version replaced (edited as it was named): that statement's id — not one to name. */
1177
1292
  replacedBy?: string;
1293
+ /** Test SQL — written in a test, not in the services' code (apps/catalog sql-test.ts): not linted, named or reviewed by itself. */
1294
+ test?: DashSqlTestMark;
1178
1295
  /** SELECT · INSERT · UPDATE · DELETE · …; a WITH reads as the statement it leads to. */
1179
1296
  verb: string;
1180
1297
  tables: string[];
@@ -1891,12 +2008,18 @@ export interface DashAiAnalyzer {
1891
2008
  folder?: string;
1892
2009
  /** Its runs in this checkout. */
1893
2010
  runs: number;
2011
+ /**
2012
+ * LensMCP's guide it reviews by. `/api/ai/analyzers` leaves its `text` out and says its length (`chars`):
2013
+ * `/api/ai/analyzers/guide?id=` ({@link DashAiAnalyzerGuide}) returns it when Settings shows it — six guides and
2014
+ * six previews in every list were 1 MB a read (FoodGuard, 2026-10-20).
2015
+ */
1894
2016
  guide?: {
1895
2017
  id: string;
1896
2018
  title: string;
1897
2019
  version: number;
1898
2020
  sha: string;
1899
- text: string;
2021
+ text?: string;
2022
+ chars?: number;
1900
2023
  };
1901
2024
  indicators: Array<{
1902
2025
  id: string;
@@ -1919,10 +2042,14 @@ export interface DashAiAnalyzer {
1919
2042
  path: string;
1920
2043
  why: string;
1921
2044
  }>;
1922
- /** The instructions every run of it sends now, before the evidence. */
2045
+ /**
2046
+ * The instructions every run of it sends now, before the evidence: their size in the list (`tokens`, `chars`); the
2047
+ * text itself (`system`) from `/api/ai/analyzers/guide?id=`, like the guide's.
2048
+ */
1923
2049
  preview: {
1924
- system: string;
2050
+ system?: string;
1925
2051
  tokens: number;
2052
+ chars?: number;
1926
2053
  };
1927
2054
  lastRun?: {
1928
2055
  at: number;
@@ -1943,6 +2070,21 @@ export interface DashAiAnalyzer {
1943
2070
  model: string;
1944
2071
  };
1945
2072
  }
2073
+ /** `GET /api/ai/analyzers/guide?id=<analyzer>`: one analyzer's guide and what a run of it sends — fetched when Settings shows them. */
2074
+ export interface DashAiAnalyzerGuide {
2075
+ id: string;
2076
+ guide?: {
2077
+ id: string;
2078
+ title: string;
2079
+ version: number;
2080
+ sha: string;
2081
+ text: string;
2082
+ };
2083
+ preview: {
2084
+ system: string;
2085
+ tokens: number;
2086
+ };
2087
+ }
1946
2088
  /** How hard a frontier model thinks (Claude 4.6 and later; other models are sent none). */
1947
2089
  export type DashAiEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
1948
2090
  export interface DashAiAnalyzers {
@@ -2010,8 +2152,13 @@ export interface DashAiRun {
2010
2152
  analyzerName: string;
2011
2153
  question: string;
2012
2154
  /** What was reviewed; `name` is a statement's name (its leading comment) — the same across its versions. */
2155
+ /**
2156
+ * `db`: one database (`<store>/<database>`, the health check); `dbtable`: one table (`<store>/<database>/<schema>.<table>`);
2157
+ * `kvstore` / `queuestore`: one Redis store's keys / BullMQ queues (`<store>`); `files`: the services' file access
2158
+ * (`files`); `external`: the external APIs the services call (`external`); `log`: one log line (its `seq`).
2159
+ */
2013
2160
  target: {
2014
- kind: 'sql' | 'route' | 'error' | 'flow' | 'ui' | 'kv' | 'queue';
2161
+ kind: 'sql' | 'route' | 'error' | 'flow' | 'ui' | 'kv' | 'queue' | 'db' | 'dbtable' | 'kvstore' | 'queuestore' | 'files' | 'external' | 'log';
2015
2162
  id: string;
2016
2163
  label: string;
2017
2164
  name?: string;
@@ -2109,7 +2256,13 @@ export interface DashAiRun {
2109
2256
  instructions: string;
2110
2257
  };
2111
2258
  error?: string;
2259
+ /**
2260
+ * The run was never sent, so nothing was reviewed: no key could pay (`no-key`), the trigger's daily allowance was
2261
+ * spent (`over-daily-cap`), or every key was out of rate-limit room (`no-room`). A refusal, not a failed review.
2262
+ */
2263
+ refused?: DashAiRefusal;
2112
2264
  }
2265
+ export type DashAiRefusal = 'no-key' | 'over-daily-cap' | 'no-room';
2113
2266
  /**
2114
2267
  * An analysis running or waiting now, in the workspace's shared queue: one per `key` (the analyzer and its target),
2115
2268
  * at most the workspace's limit running together, the rest waiting in the order they came.
@@ -2195,11 +2348,12 @@ export interface DashReviewStatus {
2195
2348
  turns?: number;
2196
2349
  tools?: number;
2197
2350
  };
2198
- /** The newest run, when it failed: when, and its error. */
2351
+ /** The newest run, when it failed: when, and its error — `refused` when it was never sent (DashAiRun.refused). */
2199
2352
  failed?: {
2200
2353
  runId: string;
2201
2354
  at: number;
2202
2355
  error: string;
2356
+ refused?: DashAiRefusal;
2203
2357
  };
2204
2358
  }
2205
2359
  /** A finished review in a few lines: when, its score, what it is about, the changes it asks for first. */
package/lib/dashboard.js CHANGED
@@ -1 +1 @@
1
- "use strict";export const DASHBOARD_PROTOCOL=1,DASH_PERF_KINDS=["n-plus-one","db-in-loop","render-storm","slow-path","slow-route","slow-query","missing-index","leak","egress-slow","duplicate-requests","perf-rule","db-connection","other"],DASH_AI_TRIGGERS=["auto","click","agent"];
1
+ "use strict";export const DASHBOARD_PROTOCOL=1,DASH_PERF_KINDS=["n-plus-one","db-in-loop","render-storm","slow-path","slow-route","slow-query","missing-index","leak","egress-slow","duplicate-requests","perf-rule","db-connection","other"],DASH_STREAM_TOPICS=["summary","session","cluster","kpis","flows","flow-rev","logs","diagnostics","errors"],DASH_AI_TRIGGERS=["auto","click","agent"];
@@ -1,5 +1,6 @@
1
1
  /**
2
- * One HTTP exchange the lens captured (R6): a service's outbound call as it went over the wire — its request and
2
+ * One HTTP exchange the lens captured (R6): a service's outbound call (`egress`), or a request it served (`ingress`
3
+ * — a route's run, as it came in and went out), as it went over the wire — its request and
3
4
  * response headers (redacted), the query string and the bodies (redacted, the first 64 KB of each), and for a
4
5
  * server-sent-events response every event with the moment it arrived. Kept OFF the bus, one file per exchange under
5
6
  * `.lensmcp/http/`; the bus event carries its `exchangeId`. Capture: `LENSMCP_HTTP_CAPTURE=off | headers | bodies`.
@@ -17,8 +18,11 @@ export interface HttpExchangeBody {
17
18
  /** A binary body: its digest instead of its bytes (when it was small enough to hash). */
18
19
  sha256?: string;
19
20
  truncated: boolean;
20
- /** Why there is no text: `binary`, `form-data`, `stream` (a request body sent as a stream), `ai`. */
21
- omitted?: 'binary' | 'form-data' | 'stream' | 'ai';
21
+ /**
22
+ * Why there is no text: `binary`, `form-data`, `stream` (a request body sent as a stream), `ai`, `encoded` (a
23
+ * compressed answer too big to unpack).
24
+ */
25
+ omitted?: 'binary' | 'form-data' | 'stream' | 'ai' | 'encoded';
22
26
  }
23
27
  export interface HttpExchangeStreamEvent {
24
28
  /** Since the request started. */
@@ -32,7 +36,7 @@ export interface HttpExchange {
32
36
  schemaVersion: 1;
33
37
  id: string;
34
38
  capturedAt: number;
35
- side: 'egress';
39
+ side: 'egress' | 'ingress';
36
40
  service?: string;
37
41
  flowId?: string;
38
42
  requestId?: string;
@@ -0,0 +1,225 @@
1
+ /**
2
+ * CUSTOM SOURCES (issues/open/foodguard-81) — an application REPORTS what the lens cannot observe, as one of a closed
3
+ * set of typed kinds the lens knows how to draw, threshold and explain to an agent: `queue` · `loop` · `store` ·
4
+ * `traffic`. Phase 1 reads `queue` only — work a service keeps waiting in its OWN storage (an outbox table), which no
5
+ * event stream can show: FoodGuard's lens showed its BullMQ queues nearly empty while 6,389 outbox rows waited.
6
+ *
7
+ * Three hops, each with its own shape:
8
+ *
9
+ * 1. The WIRE — the snapshot a service answers on the dev route it declares in project.json (`cluster.sources`):
10
+ * `{ schema: 'lensmcp.queue/v1', at, truncated?: { groups }, groups: [{ key, labels?, pending, inFlight?, failed?,
11
+ * oldestPendingAt?, completedInWindow? }] }`. The service owns its query; the lens never connects to an app's
12
+ * database. `@lensmcp/cluster` parses it (`source-poller.ts`) — a snapshot that does not parse is `malformed`, never
13
+ * zero.
14
+ * 2. The BUS — the gateway's poller reports each poll as a `source: 'gateway'`, `category: 'cluster'` event whose
15
+ * `raw.kind` is `source-poll` ({@link SourcePollRaw}), and each service's declaration as `source-declaration`
16
+ * ({@link SourceDeclarationRaw}). A group's `key` travels as `group`: the bus redacts any field NAMED `key`
17
+ * (`@lensmcp/core` `isSecretParam`), which would have turned every group into `[REDACTED]`. The cluster MIRRORS
18
+ * these shapes (it does not depend on this package); a parity fixture pins both sides.
19
+ * 3. The READ — `apps/agent` folds them into `process://sources` ({@link SourcesReport}); the `observe.sources` and
20
+ * `source.get` tools, the status signal and the dashboard read that.
21
+ *
22
+ * Everything here is REPORTED by the app, never measured by the lens: every read carries `provenance: 'reported'`.
23
+ * Additive only.
24
+ */
25
+ /** The closed set of source kinds. */
26
+ export declare const SOURCE_KINDS: readonly ["queue", "loop", "store", "traffic"];
27
+ export type SourceKind = (typeof SOURCE_KINDS)[number];
28
+ /** The kinds this release reads; a declared kind outside it is said, not polled. */
29
+ export declare const SOURCE_KINDS_READ: readonly ["queue"];
30
+ export type SourceKindRead = (typeof SOURCE_KINDS_READ)[number];
31
+ /** Every snapshot's schema: `lensmcp.<kind>/v1`. */
32
+ export declare const sourceWireSchema: (kind: SourceKind) => string;
33
+ export declare const QUEUE_WIRE_SCHEMA = "lensmcp.queue/v1";
34
+ /** The resource the lens folds the polls into. */
35
+ export declare const URI_SOURCES = "process://sources";
36
+ /** Said on every read of a source: the app's claim, not the lens's measurement. */
37
+ export declare const SOURCE_PROVENANCE: "reported";
38
+ /** The finding's rule id: a queue group whose oldest pending item has waited longer than `thresholds.oldestWaitMin`. */
39
+ export declare const QUEUE_OLDEST_WAIT_RULE = "queue/oldest-wait";
40
+ export declare const SOURCE_EVENT_KINDS: readonly ["source-poll", "source-declaration"];
41
+ export type SourceEventKind = (typeof SOURCE_EVENT_KINDS)[number];
42
+ /** What ONE poll found. */
43
+ export declare const SOURCE_POLL_STATES: readonly ["current", "unreachable", "refused", "malformed", "not-running"];
44
+ export type SourcePollState = (typeof SOURCE_POLL_STATES)[number];
45
+ /**
46
+ * What the lens says of a source when it is READ: a poll's state; `stale` — its newest report is overdue (the gateway
47
+ * stopped polling, or its polls are held up), so whatever it last said is no longer known; or `never-polled` —
48
+ * declared, and no poll has reported yet.
49
+ */
50
+ export declare const SOURCE_STATES: readonly ["current", "unreachable", "refused", "malformed", "not-running", "stale", "never-polled"];
51
+ export type SourceState = (typeof SOURCE_STATES)[number];
52
+ export declare const SOURCE_DEFAULT_INTERVAL_SEC = 10;
53
+ export declare const SOURCE_MIN_INTERVAL_SEC = 2;
54
+ export declare const QUEUE_DEFAULT_OLDEST_WAIT_MIN = 10;
55
+ /** A snapshot holds at most this many groups; a producer with more caps it and says `truncated: { groups }`. */
56
+ export declare const SOURCE_MAX_GROUPS = 500;
57
+ export declare const SOURCE_MAX_BYTES: number;
58
+ /** A report is overdue — `stale` — once it is this many report periods old (and never sooner than 30 s). */
59
+ export declare const SOURCE_STALE_PERIODS = 3;
60
+ export declare const SOURCE_STALE_MIN_MS = 30000;
61
+ /** When a source's newest report stops being trusted. */
62
+ export declare function sourceStaleAfterMs(reportEveryMs: number): number;
63
+ /** A queue source's thresholds. `stalledMin` is accepted now and judged once the lens keeps a history (phase 2). */
64
+ export interface QueueThresholds {
65
+ oldestWaitMin: number;
66
+ stalledMin?: number;
67
+ }
68
+ /** One declared source, as the gateway polls it. */
69
+ export interface SourceDeclRaw {
70
+ sourceKind: SourceKindRead;
71
+ name: string;
72
+ path: string;
73
+ intervalSec: number;
74
+ thresholds: QueueThresholds;
75
+ }
76
+ /** One group of a queue snapshot, as the bus carries it. */
77
+ export interface QueueGroupRaw {
78
+ /** The service's own key for the group (`tenant/kind`) — the wire's `key`. */
79
+ group: string;
80
+ labels?: Record<string, string>;
81
+ pending: number;
82
+ inFlight?: number;
83
+ failed?: number;
84
+ /** When the oldest pending item of the group was written (ISO). `null`/absent: the service did not say. */
85
+ oldestPendingAt?: string | null;
86
+ completedInWindow?: number;
87
+ }
88
+ export interface QueueTotals {
89
+ groups: number;
90
+ pending: number;
91
+ inFlight: number;
92
+ failed: number;
93
+ /** Present when any group said it. */
94
+ completedInWindow?: number;
95
+ }
96
+ /** A queue snapshot that parsed. */
97
+ export interface QueueSnapshotRaw {
98
+ /** When the SERVICE measured it (its `at`, ISO) — a group's wait is measured at this time. */
99
+ at: string;
100
+ /** When the gateway received it (epoch ms) — the snapshot's age is measured from this. */
101
+ receivedAt: number;
102
+ totals: QueueTotals;
103
+ /** Every group the service answered (at most {@link SOURCE_MAX_GROUPS}), the oldest-waiting first. */
104
+ groups: QueueGroupRaw[];
105
+ /** The service capped its answer: this many groups were dropped, and are in no total. */
106
+ truncated?: {
107
+ groups: number;
108
+ };
109
+ }
110
+ /** One poll of one declared source (`raw.kind: 'source-poll'`). */
111
+ export interface SourcePollRaw extends SourceDeclRaw {
112
+ kind: 'source-poll';
113
+ provenance: typeof SOURCE_PROVENANCE;
114
+ /** The Nx project that declares it. */
115
+ project: string;
116
+ state: SourcePollState;
117
+ /** Why, for every state but `current`: the error, the status line, what did not parse, why the service was not asked. */
118
+ detail?: string;
119
+ /** The HTTP status of a `refused` answer. */
120
+ status?: number;
121
+ polledAt: number;
122
+ durationMs?: number;
123
+ /** The gateway reports this source again within this many ms — what decides when the lens calls it `stale`. */
124
+ reportEveryMs: number;
125
+ /** Polls in a row that failed (`unreachable`/`refused`/`malformed`) — the gateway backs off while it grows. */
126
+ failures?: number;
127
+ /** The LAST GOOD snapshot: this poll's when `current`, else the one before (kept, with its age), if any. */
128
+ snapshot?: QueueSnapshotRaw;
129
+ /** The owning workspace (stamped by the gateway on every service event). */
130
+ wsKey?: string;
131
+ floodSuppressed?: number;
132
+ }
133
+ /** A service's `cluster.sources`, as the gateway read it (`raw.kind: 'source-declaration'`). */
134
+ export interface SourceDeclarationRaw {
135
+ kind: 'source-declaration';
136
+ project: string;
137
+ /** The entries it polls. */
138
+ sources: SourceDeclRaw[];
139
+ /** Each entry it does NOT poll, and why — an unknown kind or a malformed entry is said, never silently dropped. */
140
+ problems: string[];
141
+ wsKey?: string;
142
+ }
143
+ /** A `source-poll` event's `raw`, checked just enough to be read safely (telemetry is only partly trusted). */
144
+ export declare function asSourcePollRaw(raw: unknown): SourcePollRaw | undefined;
145
+ /** A `source-declaration` event's `raw`, checked just enough to be read safely. */
146
+ export declare function asSourceDeclarationRaw(raw: unknown): SourceDeclarationRaw | undefined;
147
+ /** One queue group, as the lens shows it. */
148
+ export interface QueueGroupView extends QueueGroupRaw {
149
+ /** How long its oldest pending item had waited when the service measured it (`at − oldestPendingAt`). */
150
+ oldestWaitMs?: number;
151
+ /** That wait is longer than the source's `thresholds.oldestWaitMin`. */
152
+ overThreshold: boolean;
153
+ }
154
+ /** A queue source's headline numbers. */
155
+ export interface QueueHeadline extends QueueTotals {
156
+ /** The longest wait among its groups, as of the snapshot. */
157
+ oldestWaitMs?: number;
158
+ /** Groups whose oldest wait is over the threshold. */
159
+ overThreshold: number;
160
+ }
161
+ /** One declared source, as the lens shows it. */
162
+ export interface SourceView extends SourceDeclRaw {
163
+ provenance: typeof SOURCE_PROVENANCE;
164
+ project: string;
165
+ state: SourceState;
166
+ /** Why, for every state but `current`. */
167
+ detail?: string;
168
+ status?: number;
169
+ /** When the gateway last polled it (absent: `never-polled`). */
170
+ polledAt?: number;
171
+ failures?: number;
172
+ /** The last good snapshot's age — present whenever one is held, whatever the state. */
173
+ ageMs?: number;
174
+ /** When the service measured the last good snapshot (its `at`). */
175
+ snapshotAt?: string;
176
+ headline?: QueueHeadline;
177
+ /** The oldest-waiting first. Empty with `state: 'current'` is a real «nothing pending». */
178
+ groups: QueueGroupView[];
179
+ truncated?: {
180
+ groups: number;
181
+ };
182
+ }
183
+ /** A queue group whose oldest pending item has waited longer than its source allows. */
184
+ export interface SourceFinding {
185
+ rule: typeof QUEUE_OLDEST_WAIT_RULE;
186
+ provenance: typeof SOURCE_PROVENANCE;
187
+ project: string;
188
+ name: string;
189
+ sourceKind: SourceKindRead;
190
+ group: string;
191
+ labels?: Record<string, string>;
192
+ pending: number;
193
+ oldestWaitMs: number;
194
+ thresholdMin: number;
195
+ title: string;
196
+ fix: string[];
197
+ verify: string;
198
+ }
199
+ /** `process://sources`. */
200
+ export interface SourcesReport {
201
+ schemaVersion?: string;
202
+ revision?: number;
203
+ updatedAt?: string;
204
+ provenance: typeof SOURCE_PROVENANCE;
205
+ /** `none` until a service declares a source: nothing reports what the taps cannot see — not «nothing waits». */
206
+ producer: 'connected' | 'none';
207
+ detail?: string;
208
+ /** The clock every age here was measured against. */
209
+ readAt: number;
210
+ sources: SourceView[];
211
+ /** Groups over their threshold, from `current` snapshots — the longest wait first. */
212
+ findings: SourceFinding[];
213
+ /** Declaration entries the gateway does not poll, and why. */
214
+ problems: Array<{
215
+ project: string;
216
+ problem: string;
217
+ }>;
218
+ counts: Record<SourceState, number>;
219
+ /** Over the `current` queue snapshots only. */
220
+ totals: {
221
+ pending: number;
222
+ inFlight: number;
223
+ failed: number;
224
+ };
225
+ }
package/lib/sources.js ADDED
@@ -0,0 +1 @@
1
+ "use strict";var E=Object.defineProperty;var n=(r,t)=>E(r,"name",{value:t,configurable:!0});var f=Object.defineProperty,e=n((r,t)=>f(r,"name",{value:t,configurable:!0}),"n");export const SOURCE_KINDS=["queue","loop","store","traffic"],SOURCE_KINDS_READ=["queue"],sourceWireSchema=e(r=>`lensmcp.${r}/v1`,"sourceWireSchema"),QUEUE_WIRE_SCHEMA="lensmcp.queue/v1",URI_SOURCES="process://sources",SOURCE_PROVENANCE="reported",QUEUE_OLDEST_WAIT_RULE="queue/oldest-wait",SOURCE_EVENT_KINDS=["source-poll","source-declaration"],SOURCE_POLL_STATES=["current","unreachable","refused","malformed","not-running"],SOURCE_STATES=[...SOURCE_POLL_STATES,"stale","never-polled"],SOURCE_DEFAULT_INTERVAL_SEC=10,SOURCE_MIN_INTERVAL_SEC=2,QUEUE_DEFAULT_OLDEST_WAIT_MIN=10,SOURCE_MAX_GROUPS=500,SOURCE_MAX_BYTES=256*1024,SOURCE_STALE_PERIODS=3,SOURCE_STALE_MIN_MS=3e4;export function sourceStaleAfterMs(r){return Math.max(SOURCE_STALE_MIN_MS,SOURCE_STALE_PERIODS*Math.max(0,r))}n(sourceStaleAfterMs,"sourceStaleAfterMs"),e(sourceStaleAfterMs,"sourceStaleAfterMs");const o=e(r=>typeof r=="object"&&r!==null&&!Array.isArray(r),"isObj"),i=e(r=>typeof r=="number"&&Number.isInteger(r)&&r>=0,"isCount"),c=e(r=>typeof r=="number"&&Number.isFinite(r)&&r>0,"isTime"),a=e((r,t)=>typeof r=="string"&&r.length>0&&r.length<=t,"isStr");function l(r){if(!SOURCE_KINDS_READ.includes(r.sourceKind)||!a(r.name,200)||typeof r.path!="string"||typeof r.intervalSec!="number")return!1;const t=r.thresholds;return o(t)&&typeof t.oldestWaitMin=="number"&&t.oldestWaitMin>0}n(l,"f"),e(l,"asDecl");function p(r){if(!o(r)||!a(r.group,1e3)||!i(r.pending))return!1;for(const u of["inFlight","failed","completedInWindow"])if(r[u]!==void 0&&!i(r[u]))return!1;const t=r.oldestPendingAt;if(t!=null&&typeof t!="string")return!1;const s=r.labels;return s===void 0||o(s)&&Object.values(s).every(u=>typeof u=="string")}n(p,"l"),e(p,"asGroup");function S(r){if(!o(r)||typeof r.at!="string"||!c(r.receivedAt)||!Array.isArray(r.groups))return!1;const t=r.totals;if(!o(t)||!i(t.groups)||!i(t.pending)||!i(t.inFlight)||!i(t.failed))return!1;const s=r.truncated;return s!==void 0&&!(o(s)&&i(s.groups))?!1:r.groups.length<=SOURCE_MAX_GROUPS&&r.groups.every(p)}n(S,"d"),e(S,"asSnapshot");export function asSourcePollRaw(r){if(!(!o(r)||r.kind!=="source-poll"||!a(r.project,200)||!l(r))&&SOURCE_POLL_STATES.includes(r.state)&&!(!c(r.polledAt)||!c(r.reportEveryMs))&&!(r.snapshot!==void 0&&!S(r.snapshot))&&!(r.state==="current"&&r.snapshot===void 0))return r}n(asSourcePollRaw,"asSourcePollRaw"),e(asSourcePollRaw,"asSourcePollRaw");export function asSourceDeclarationRaw(r){if(!(!o(r)||r.kind!=="source-declaration"||!a(r.project,200))&&!(!Array.isArray(r.sources)||!r.sources.every(t=>o(t)&&l(t)))&&!(!Array.isArray(r.problems)||!r.problems.every(t=>typeof t=="string")))return r}n(asSourceDeclarationRaw,"asSourceDeclarationRaw"),e(asSourceDeclarationRaw,"asSourceDeclarationRaw");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lensmcp/protocol-types",
3
- "version": "1.25.3",
3
+ "version": "1.25.6",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",