@lensmcp/protocol-types 1.25.5 → 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";
@@ -656,6 +656,19 @@ export interface DashDiagnostics {
656
656
  slow: number;
657
657
  warnings: number;
658
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
+ };
659
672
  }
660
673
  /** The problem kinds that are "slow or wasteful" — Performance's findings and the slow counter. */
661
674
  export declare const DASH_PERF_KINDS: readonly DashProblemKind[];
@@ -742,6 +755,11 @@ export interface DashErrors {
742
755
  recentOccurrences: number;
743
756
  /** Hidden by hand, and still hidden: nothing of them has been seen since. Listed so they can be restored. */
744
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;
745
763
  }
746
764
  /** `POST /api/errors/dismiss` and `/api/errors/restore` — the body, and the answer. */
747
765
  export interface DashErrorIds {
@@ -798,8 +816,55 @@ export type DashStreamEvent = {
798
816
  } | {
799
817
  type: 'errors';
800
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>;
801
829
  };
802
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
+ }
803
868
  /**
804
869
  * What one run of a route touched, by key, with how many times:
805
870
  * `svc:<callee>` a service it called · `ext:<host>` a host outside the cluster · `sql:<VERB> <table>`
@@ -816,6 +881,8 @@ export interface DashRouteRun {
816
881
  /** Who called it: another service by name, else `client`. */
817
882
  caller?: string;
818
883
  touches: DashTouches;
884
+ /** Its request and response as they came in and went out (an `ingress` exchange, `/api/exchange?id=`). */
885
+ exchangeId?: string;
819
886
  }
820
887
  export type DashRouteChangeKind = 'new-external' | 'new-service' | 'new-query' | 'new-queue' | 'new-cache' | 'new-error' | 'gone' | 'gone-error' | 'count' | 'status' | 'duration';
821
888
  export interface DashRouteChange {
@@ -1051,8 +1118,13 @@ export interface DashExternalCall {
1051
1118
  /** The request it was made in — the calls that share it are one request's steps. */
1052
1119
  requestId?: string;
1053
1120
  }
1054
- /** 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
+ */
1055
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';
1056
1128
  host: string;
1057
1129
  call: DashExternalCall;
1058
1130
  /** Absent: not captured (capture off, headers rotated away) or withheld (`withheld` says why). */
@@ -1166,6 +1238,14 @@ export interface DashSqlFinding {
1166
1238
  tool: string;
1167
1239
  at: number;
1168
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
+ }
1169
1249
  /**
1170
1250
  * A place in the services' own code where something ran — what a coding agent opens. From the stack the tap took at
1171
1251
  * the call: the service's nearest frame, and the callers above it.
@@ -1210,6 +1290,8 @@ export interface DashSqlTemplateSummary {
1210
1290
  sites?: DashCodeSite[];
1211
1291
  /** An unnamed statement its named version replaced (edited as it was named): that statement's id — not one to name. */
1212
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;
1213
1295
  /** SELECT · INSERT · UPDATE · DELETE · …; a WITH reads as the statement it leads to. */
1214
1296
  verb: string;
1215
1297
  tables: string[];
@@ -1926,12 +2008,18 @@ export interface DashAiAnalyzer {
1926
2008
  folder?: string;
1927
2009
  /** Its runs in this checkout. */
1928
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
+ */
1929
2016
  guide?: {
1930
2017
  id: string;
1931
2018
  title: string;
1932
2019
  version: number;
1933
2020
  sha: string;
1934
- text: string;
2021
+ text?: string;
2022
+ chars?: number;
1935
2023
  };
1936
2024
  indicators: Array<{
1937
2025
  id: string;
@@ -1954,10 +2042,14 @@ export interface DashAiAnalyzer {
1954
2042
  path: string;
1955
2043
  why: string;
1956
2044
  }>;
1957
- /** 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
+ */
1958
2049
  preview: {
1959
- system: string;
2050
+ system?: string;
1960
2051
  tokens: number;
2052
+ chars?: number;
1961
2053
  };
1962
2054
  lastRun?: {
1963
2055
  at: number;
@@ -1978,6 +2070,21 @@ export interface DashAiAnalyzer {
1978
2070
  model: string;
1979
2071
  };
1980
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
+ }
1981
2088
  /** How hard a frontier model thinks (Claude 4.6 and later; other models are sent none). */
1982
2089
  export type DashAiEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max';
1983
2090
  export interface DashAiAnalyzers {
@@ -2045,8 +2152,13 @@ export interface DashAiRun {
2045
2152
  analyzerName: string;
2046
2153
  question: string;
2047
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
+ */
2048
2160
  target: {
2049
- kind: 'sql' | 'route' | 'error' | 'flow' | 'ui' | 'kv' | 'queue';
2161
+ kind: 'sql' | 'route' | 'error' | 'flow' | 'ui' | 'kv' | 'queue' | 'db' | 'dbtable' | 'kvstore' | 'queuestore' | 'files' | 'external' | 'log';
2050
2162
  id: string;
2051
2163
  label: string;
2052
2164
  name?: string;
@@ -2144,7 +2256,13 @@ export interface DashAiRun {
2144
2256
  instructions: string;
2145
2257
  };
2146
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;
2147
2264
  }
2265
+ export type DashAiRefusal = 'no-key' | 'over-daily-cap' | 'no-room';
2148
2266
  /**
2149
2267
  * An analysis running or waiting now, in the workspace's shared queue: one per `key` (the analyzer and its target),
2150
2268
  * at most the workspace's limit running together, the rest waiting in the order they came.
@@ -2230,11 +2348,12 @@ export interface DashReviewStatus {
2230
2348
  turns?: number;
2231
2349
  tools?: number;
2232
2350
  };
2233
- /** 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). */
2234
2352
  failed?: {
2235
2353
  runId: string;
2236
2354
  at: number;
2237
2355
  error: string;
2356
+ refused?: DashAiRefusal;
2238
2357
  };
2239
2358
  }
2240
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.5",
3
+ "version": "1.25.6",
4
4
  "type": "module",
5
5
  "main": "./index.js",
6
6
  "module": "./index.js",