agent-embassy 2.0.1 → 3.0.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.
Files changed (144) hide show
  1. package/CHANGELOG.md +56 -0
  2. package/CONTRIBUTING.md +19 -34
  3. package/README.md +110 -228
  4. package/SECURITY.md +55 -89
  5. package/dist/src/errors.d.ts +10 -1
  6. package/dist/src/errors.js +3 -1
  7. package/dist/src/errors.js.map +1 -1
  8. package/dist/src/gateway/claude-helper-protocol.d.ts +8 -11
  9. package/dist/src/gateway/claude-helper-protocol.js +12 -11
  10. package/dist/src/gateway/claude-helper-protocol.js.map +1 -1
  11. package/dist/src/gateway/claude-helper-supervisor.d.ts +1 -5
  12. package/dist/src/gateway/claude-helper-supervisor.js +10 -9
  13. package/dist/src/gateway/claude-helper-supervisor.js.map +1 -1
  14. package/dist/src/gateway/claude-helper.js +6 -6
  15. package/dist/src/gateway/claude-helper.js.map +1 -1
  16. package/dist/src/gateway/claude-peer.d.ts +0 -3
  17. package/dist/src/gateway/claude-peer.js +6 -18
  18. package/dist/src/gateway/claude-peer.js.map +1 -1
  19. package/dist/src/gateway/cli.d.ts +17 -6
  20. package/dist/src/gateway/cli.js +912 -261
  21. package/dist/src/gateway/cli.js.map +1 -1
  22. package/dist/src/gateway/codex-socket-holder.d.ts +26 -0
  23. package/dist/src/gateway/codex-socket-holder.js +76 -0
  24. package/dist/src/gateway/codex-socket-holder.js.map +1 -0
  25. package/dist/src/gateway/codex-stateless-transport.js +1 -1
  26. package/dist/src/gateway/codex-stateless-transport.js.map +1 -1
  27. package/dist/src/gateway/config.d.ts +1 -10
  28. package/dist/src/gateway/config.js +4 -10
  29. package/dist/src/gateway/config.js.map +1 -1
  30. package/dist/src/gateway/control.d.ts +45 -77
  31. package/dist/src/gateway/control.js +56 -141
  32. package/dist/src/gateway/control.js.map +1 -1
  33. package/dist/src/gateway/federation-nodes.d.ts +29 -2
  34. package/dist/src/gateway/federation-nodes.js +177 -7
  35. package/dist/src/gateway/federation-nodes.js.map +1 -1
  36. package/dist/src/gateway/peer-client.d.ts +4 -3
  37. package/dist/src/gateway/peer-client.js +22 -13
  38. package/dist/src/gateway/peer-client.js.map +1 -1
  39. package/dist/src/gateway/peer-protocol.d.ts +8 -11
  40. package/dist/src/gateway/peer-protocol.js +5 -12
  41. package/dist/src/gateway/peer-protocol.js.map +1 -1
  42. package/dist/src/gateway/provenance-envelope.d.ts +0 -1
  43. package/dist/src/gateway/provenance-envelope.js +4 -19
  44. package/dist/src/gateway/provenance-envelope.js.map +1 -1
  45. package/dist/src/gateway/providers.d.ts +7 -4
  46. package/dist/src/gateway/providers.js +16 -20
  47. package/dist/src/gateway/providers.js.map +1 -1
  48. package/dist/src/gateway/server.d.ts +3 -12
  49. package/dist/src/gateway/server.js +31 -50
  50. package/dist/src/gateway/server.js.map +1 -1
  51. package/dist/src/gateway/service-agent.d.ts +187 -0
  52. package/dist/src/gateway/service-agent.js +758 -0
  53. package/dist/src/gateway/service-agent.js.map +1 -0
  54. package/dist/src/gateway/service.d.ts +114 -31
  55. package/dist/src/gateway/service.js +461 -566
  56. package/dist/src/gateway/service.js.map +1 -1
  57. package/dist/src/gateway/status-view.d.ts +167 -0
  58. package/dist/src/gateway/status-view.js +488 -0
  59. package/dist/src/gateway/status-view.js.map +1 -0
  60. package/dist/src/gateway/store.d.ts +103 -21
  61. package/dist/src/gateway/store.js +454 -529
  62. package/dist/src/gateway/store.js.map +1 -1
  63. package/dist/src/gateway/types.d.ts +48 -99
  64. package/dist/src/gateway/types.js +15 -52
  65. package/dist/src/gateway/types.js.map +1 -1
  66. package/docs/CONFIGURATION.md +173 -44
  67. package/docs/DELIVERY.md +11 -11
  68. package/docs/GATEWAY-ARCHITECTURE.md +277 -375
  69. package/package.json +4 -12
  70. package/skills/embassy-peer/SKILL.md +65 -90
  71. package/skills/embassy-peer/agents/openai.yaml +1 -1
  72. package/README.zh-CN.md +0 -275
  73. package/assets/live-dashboard/app.css +0 -1619
  74. package/assets/vendor/react/LICENSE +0 -21
  75. package/assets/vendor/react/react-dom.production.min.js +0 -267
  76. package/assets/vendor/react/react.production.min.js +0 -31
  77. package/dist/src/gateway/acp-client.d.ts +0 -110
  78. package/dist/src/gateway/acp-client.js +0 -407
  79. package/dist/src/gateway/acp-client.js.map +0 -1
  80. package/dist/src/gateway/acp-provider.d.ts +0 -66
  81. package/dist/src/gateway/acp-provider.js +0 -275
  82. package/dist/src/gateway/acp-provider.js.map +0 -1
  83. package/dist/src/gateway/cli-copy.d.ts +0 -8
  84. package/dist/src/gateway/cli-copy.en.d.ts +0 -22
  85. package/dist/src/gateway/cli-copy.en.js +0 -62
  86. package/dist/src/gateway/cli-copy.en.js.map +0 -1
  87. package/dist/src/gateway/cli-copy.js +0 -27
  88. package/dist/src/gateway/cli-copy.js.map +0 -1
  89. package/dist/src/gateway/cli-copy.zh-CN.d.ts +0 -22
  90. package/dist/src/gateway/cli-copy.zh-CN.js +0 -62
  91. package/dist/src/gateway/cli-copy.zh-CN.js.map +0 -1
  92. package/dist/src/gateway/codex-doctor.d.ts +0 -36
  93. package/dist/src/gateway/codex-doctor.js +0 -127
  94. package/dist/src/gateway/codex-doctor.js.map +0 -1
  95. package/dist/src/gateway/dashboard-copy.d.ts +0 -7
  96. package/dist/src/gateway/dashboard-copy.en.d.ts +0 -504
  97. package/dist/src/gateway/dashboard-copy.en.js +0 -505
  98. package/dist/src/gateway/dashboard-copy.en.js.map +0 -1
  99. package/dist/src/gateway/dashboard-copy.js +0 -514
  100. package/dist/src/gateway/dashboard-copy.js.map +0 -1
  101. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +0 -504
  102. package/dist/src/gateway/dashboard-copy.zh-CN.js +0 -505
  103. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +0 -1
  104. package/dist/src/gateway/dashboard-model.d.ts +0 -343
  105. package/dist/src/gateway/dashboard-model.js +0 -1061
  106. package/dist/src/gateway/dashboard-model.js.map +0 -1
  107. package/dist/src/gateway/dashboard.d.ts +0 -20
  108. package/dist/src/gateway/dashboard.js +0 -874
  109. package/dist/src/gateway/dashboard.js.map +0 -1
  110. package/dist/src/gateway/deepseek-detect.d.ts +0 -14
  111. package/dist/src/gateway/deepseek-detect.js +0 -41
  112. package/dist/src/gateway/deepseek-detect.js.map +0 -1
  113. package/dist/src/gateway/live-dashboard-app/app.js +0 -2385
  114. package/dist/src/gateway/live-dashboard-assets.d.ts +0 -10
  115. package/dist/src/gateway/live-dashboard-assets.js +0 -74
  116. package/dist/src/gateway/live-dashboard-assets.js.map +0 -1
  117. package/dist/src/gateway/live-dashboard-command.d.ts +0 -60
  118. package/dist/src/gateway/live-dashboard-command.js +0 -334
  119. package/dist/src/gateway/live-dashboard-command.js.map +0 -1
  120. package/dist/src/gateway/live-dashboard-http.d.ts +0 -39
  121. package/dist/src/gateway/live-dashboard-http.js +0 -383
  122. package/dist/src/gateway/live-dashboard-http.js.map +0 -1
  123. package/dist/src/gateway/live-dashboard-protocol.d.ts +0 -34
  124. package/dist/src/gateway/live-dashboard-protocol.js +0 -114
  125. package/dist/src/gateway/live-dashboard-protocol.js.map +0 -1
  126. package/dist/src/gateway/live-dashboard-server.d.ts +0 -33
  127. package/dist/src/gateway/live-dashboard-server.js +0 -144
  128. package/dist/src/gateway/live-dashboard-server.js.map +0 -1
  129. package/dist/src/gateway/live-dashboard-stream.d.ts +0 -46
  130. package/dist/src/gateway/live-dashboard-stream.js +0 -234
  131. package/dist/src/gateway/live-dashboard-stream.js.map +0 -1
  132. package/dist/src/gateway/live-dashboard.d.ts +0 -28
  133. package/dist/src/gateway/live-dashboard.js +0 -154
  134. package/dist/src/gateway/live-dashboard.js.map +0 -1
  135. package/dist/src/gateway/locale.d.ts +0 -4
  136. package/dist/src/gateway/locale.js +0 -10
  137. package/dist/src/gateway/locale.js.map +0 -1
  138. package/dist/src/gateway/progress-watch-machine.d.ts +0 -45
  139. package/dist/src/gateway/progress-watch-machine.js +0 -70
  140. package/dist/src/gateway/progress-watch-machine.js.map +0 -1
  141. package/docs/CONFIGURATION.zh-CN.md +0 -97
  142. package/docs/DASHBOARD.md +0 -98
  143. package/docs/DASHBOARD.zh-CN.md +0 -49
  144. package/docs/DELIVERY.zh-CN.md +0 -55
@@ -1,1061 +0,0 @@
1
- import { arePublicAvailablePeerSnapshots, gatewayProviders, isPublicRegistryObservationSnapshot, parseDirection, } from "./types.js";
2
- export const DASHBOARD_MODEL_LIMITS = Object.freeze({
3
- connectors: 16,
4
- availablePeers: 64,
5
- routes: 128,
6
- consentEdges: 64,
7
- messages: 50,
8
- messageEvents: 60,
9
- progressWatches: 64,
10
- progressWatchEvents: 64,
11
- activityEvents: 64,
12
- alerts: 32,
13
- });
14
- const dashboardActivityKinds = [
15
- "discovery",
16
- "selection",
17
- "registration",
18
- "pairing",
19
- "watch",
20
- ];
21
- const dashboardActivityActions = [
22
- "discovery_refreshed",
23
- "claude_selected",
24
- "claude_unselected",
25
- "codex_registered",
26
- "codex_succeeded",
27
- "codex_unregistered",
28
- "routes_paired",
29
- "routes_unpaired",
30
- "watch_ended",
31
- ];
32
- function isDashboardActivityKind(value) {
33
- return dashboardActivityKinds.some((candidate) => candidate === value);
34
- }
35
- function isDashboardActivityAction(value) {
36
- return dashboardActivityActions.some((candidate) => candidate === value);
37
- }
38
- const presentation = (values) => values;
39
- const statePresentation = {
40
- delivery: presentation({
41
- queued: { tone: "info", chip: "progress" },
42
- duplicate: { tone: "quiet", chip: "inert" },
43
- dispatching: { tone: "info", chip: "progress" },
44
- transport_written: { tone: "info", chip: "progress" },
45
- held: { tone: "warning", chip: "progress" },
46
- delivered: { tone: "good", chip: "positive" },
47
- unconfirmed: { tone: "warning", chip: "indeterminate" },
48
- failed: { tone: "danger", chip: "failure" },
49
- ambiguous: { tone: "warning", chip: "indeterminate" },
50
- expired: { tone: "danger", chip: "failure" },
51
- cancelled: { tone: "danger", chip: "inert" },
52
- abandoned: { tone: "danger", chip: "inert" },
53
- rejected: { tone: "danger", chip: "warning" },
54
- }),
55
- route: presentation({
56
- stale: { tone: "danger", chip: "failure" },
57
- idle: { tone: "good", chip: "progress" },
58
- busy: { tone: "info", chip: "active" },
59
- awaiting_approval: { tone: "warning", chip: "warning" },
60
- offline: { tone: "danger", chip: "failure" },
61
- disabled: { tone: "quiet", chip: "inert" },
62
- }),
63
- peer: presentation({
64
- idle: { tone: "good", chip: "progress" },
65
- busy: { tone: "info", chip: "active" },
66
- awaiting_approval: { tone: "warning", chip: "warning" },
67
- offline: { tone: "danger", chip: "inert" },
68
- }),
69
- health: presentation({
70
- healthy: { tone: "good", chip: "positive" },
71
- connecting: { tone: "info", chip: "progress" },
72
- degraded: { tone: "warning", chip: "warning" },
73
- offline: { tone: "danger", chip: "failure" },
74
- }),
75
- overall: presentation({
76
- ready: { tone: "good", chip: "positive" },
77
- setup: { tone: "info", chip: "progress" },
78
- attention: { tone: "danger", chip: "failure" },
79
- }),
80
- party: presentation({
81
- ready: { tone: "good", chip: "positive" },
82
- busy: { tone: "info", chip: "active" },
83
- waiting: { tone: "warning", chip: "warning" },
84
- missing: { tone: "quiet", chip: "inert" },
85
- attention: { tone: "danger", chip: "failure" },
86
- }),
87
- severity: presentation({
88
- info: { tone: "info", chip: "active" },
89
- warning: { tone: "warning", chip: "warning" },
90
- error: { tone: "danger", chip: "failure" },
91
- }),
92
- connection: presentation({
93
- connected: { tone: "good", chip: "positive" },
94
- connecting: { tone: "info", chip: "progress" },
95
- paused: { tone: "quiet", chip: "inert" },
96
- unavailable: { tone: "warning", chip: "warning" },
97
- capacity: { tone: "warning", chip: "warning" },
98
- disconnected: { tone: "warning", chip: "warning" },
99
- stopped: { tone: "warning", chip: "warning" },
100
- }),
101
- };
102
- const guidanceCopyKeys = {
103
- reobserve_claude: "reobserveClaude",
104
- consent_edge_unavailable: "consentEdgeUnavailable",
105
- claude_not_observed: "claudeNotObserved",
106
- connector_offline: "connectorOffline",
107
- route_stale: "routeStale",
108
- queue_stalled: "queueStalled",
109
- recipient_waiting_input: "recipientWaitingInput",
110
- unconfirmed: "unconfirmed",
111
- degraded: "degraded",
112
- progress_watch: "progressWatch",
113
- registry_empty: "registryEmpty",
114
- registry_rejected: "registryRejected",
115
- generic: "generic",
116
- };
117
- const nextActionCopyKeys = {
118
- discover_claude: "next.discoverClaude",
119
- select_claude: "next.selectClaude",
120
- pair_routes: "next.pairRoutes",
121
- restore_claude: "next.restoreClaude",
122
- repair_claude_inventory: "next.repairClaude",
123
- register_codex: "next.registerCodex",
124
- none: "next.none",
125
- };
126
- const attentionCommands = {
127
- reobserve_claude: "embassy select-claude --alias {alias}",
128
- consent_edge_unavailable: "embassy refresh-dashboard",
129
- claude_not_observed: "embassy select-claude --alias {alias}",
130
- connector_offline: "embassy status",
131
- route_stale: "embassy status",
132
- queue_stalled: "embassy status",
133
- recipient_waiting_input: "embassy status",
134
- unconfirmed: "embassy status",
135
- degraded: "embassy status",
136
- progress_watch: "embassy status",
137
- registry_empty: "embassy refresh-dashboard",
138
- registry_rejected: "embassy status",
139
- generic: "embassy status",
140
- };
141
- const deliveryMeaningKeys = {
142
- queued: "activity.meaning.queued",
143
- duplicate: "activity.meaning.duplicate",
144
- dispatching: "activity.meaning.dispatching",
145
- transport_written: "activity.meaning.transportWritten",
146
- held: "activity.meaning.held",
147
- delivered: "activity.meaning.delivered",
148
- unconfirmed: "activity.meaning.unconfirmed",
149
- failed: "activity.meaning.failed",
150
- ambiguous: "activity.meaning.ambiguous",
151
- expired: "activity.meaning.expired",
152
- cancelled: "activity.meaning.cancelled",
153
- abandoned: "activity.meaning.abandoned.generic",
154
- rejected: "activity.meaning.rejected",
155
- };
156
- /** JSON-safe semantics embedded once in the live dashboard boot payload. */
157
- export const DASHBOARD_SEMANTICS = Object.freeze({
158
- statePresentation,
159
- deliveryChipByTargetProvider: {
160
- delivered: { claude: "qualified" },
161
- },
162
- deliveryChipBySafeErrorCode: {
163
- rejected: { SENDER_NOT_PAIRED: "inert" },
164
- },
165
- deliveryToneBySafeErrorCode: {
166
- rejected: { SENDER_NOT_PAIRED: "quiet" },
167
- },
168
- guidanceCopyKeys,
169
- nextActionCopyKeys,
170
- attentionCommands,
171
- attentionCommandFallbacks: {},
172
- deliveryMeaningKeys,
173
- deliveryMeaningByTargetProvider: {
174
- claude: "activity.meaning.delivered.toClaude",
175
- codex: "activity.meaning.delivered.toCodex",
176
- deepseek: "activity.meaning.delivered.toDeepSeek",
177
- grok: "activity.meaning.delivered.toGrok",
178
- peer: "activity.meaning.delivered.toPeer",
179
- },
180
- deliveryMeaningBySafeErrorCode: {
181
- SENDER_NOT_PAIRED: "activity.meaning.senderNotPaired",
182
- CONTROLLER_RESTARTED: "activity.meaning.abandoned.controllerRestarted",
183
- TRANSIENT_BODY_UNAVAILABLE: "activity.meaning.abandoned.transientBody",
184
- ROUTE_UNREGISTERED: "activity.meaning.abandoned.routeTerminated",
185
- MESSAGE_EXPIRED: "activity.meaning.abandoned.routeTerminated",
186
- },
187
- });
188
- function semanticPresentation(domain, state) {
189
- const table = DASHBOARD_SEMANTICS.statePresentation[domain];
190
- return Object.prototype.hasOwnProperty.call(table, state)
191
- ? table[state]
192
- : undefined;
193
- }
194
- export function dashboardTone(domain, state, safeErrorCode) {
195
- if (domain === "delivery" && safeErrorCode !== undefined) {
196
- const byCode = DASHBOARD_SEMANTICS.deliveryToneBySafeErrorCode;
197
- const override = byCode[state]?.[safeErrorCode];
198
- if (override !== undefined)
199
- return override;
200
- }
201
- return semanticPresentation(domain, state)?.tone ?? "danger";
202
- }
203
- export function deliveryMeaningKey(state, direction, safeErrorCode) {
204
- if (state === "rejected" && safeErrorCode === "SENDER_NOT_PAIRED") {
205
- return DASHBOARD_SEMANTICS.deliveryMeaningBySafeErrorCode.SENDER_NOT_PAIRED;
206
- }
207
- if (state === "delivered" && direction !== undefined) {
208
- const target = parseDirection(direction)?.targetProvider;
209
- if (target !== undefined) {
210
- return DASHBOARD_SEMANTICS.deliveryMeaningByTargetProvider[target];
211
- }
212
- }
213
- if (state === "abandoned" && safeErrorCode !== undefined) {
214
- const byCode = DASHBOARD_SEMANTICS.deliveryMeaningBySafeErrorCode;
215
- return byCode[safeErrorCode] ?? deliveryMeaningKeys.abandoned;
216
- }
217
- const byState = DASHBOARD_SEMANTICS.deliveryMeaningKeys;
218
- return byState[state] ?? "activity.meaning.other";
219
- }
220
- export function guidanceCopyKey(guidance) {
221
- return DASHBOARD_SEMANTICS.guidanceCopyKeys[guidance];
222
- }
223
- export function nextActionCopyKey(action) {
224
- return DASHBOARD_SEMANTICS.nextActionCopyKeys[action];
225
- }
226
- export function attentionCommand(item) {
227
- const fallbacks = DASHBOARD_SEMANTICS.attentionCommandFallbacks;
228
- return DASHBOARD_SEMANTICS.attentionCommands[item.guidance].replaceAll("{alias}", item.alias ?? fallbacks[item.guidance] ?? "<alias>");
229
- }
230
- const SAFE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
231
- const SAFE_PROTOCOL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+:/-]{0,47}$/;
232
- const OPAQUE_SUFFIX_PATTERN = /^[a-f0-9]{6,12}$/i;
233
- const CONVERSATION_SUFFIX_PATTERN = /^[A-Za-z0-9_-]{8}$/;
234
- function boundedText(value, maximumCharacters = 96) {
235
- if (typeof value !== "string")
236
- return "";
237
- const characters = Array.from(value);
238
- return characters.length <= maximumCharacters
239
- ? value
240
- : `${characters.slice(0, maximumCharacters - 1).join("")}\u2026`;
241
- }
242
- function safeCode(value) {
243
- return typeof value === "string" && SAFE_CODE_PATTERN.test(value)
244
- ? value
245
- : undefined;
246
- }
247
- function safeProtocol(value) {
248
- return typeof value === "string" && SAFE_PROTOCOL_PATTERN.test(value)
249
- ? value
250
- : undefined;
251
- }
252
- function normalizedInteger(value) {
253
- return typeof value === "number" &&
254
- Number.isSafeInteger(value) &&
255
- value >= 0
256
- ? value
257
- : undefined;
258
- }
259
- function normalizedTimestamp(value) {
260
- if (typeof value !== "string")
261
- return undefined;
262
- const milliseconds = Date.parse(value);
263
- return Number.isFinite(milliseconds)
264
- ? new Date(milliseconds).toISOString()
265
- : undefined;
266
- }
267
- function compareText(left, right) {
268
- return left < right ? -1 : left > right ? 1 : 0;
269
- }
270
- function boundedAdd(left, right) {
271
- return Math.min(Number.MAX_SAFE_INTEGER, left + right);
272
- }
273
- function normalizedCounters(counters) {
274
- return {
275
- accepted: normalizedInteger(counters?.accepted) ?? 0,
276
- delivered: normalizedInteger(counters?.delivered) ?? 0,
277
- unconfirmed: normalizedInteger(counters?.unconfirmed) ?? 0,
278
- failed: normalizedInteger(counters?.failed) ?? 0,
279
- ambiguous: normalizedInteger(counters?.ambiguous) ?? 0,
280
- expired: normalizedInteger(counters?.expired) ?? 0,
281
- cancelled: normalizedInteger(counters?.cancelled) ?? 0,
282
- abandoned: normalizedInteger(counters?.abandoned) ?? 0,
283
- rejected: normalizedInteger(counters?.rejected) ?? 0,
284
- bytesAccepted: normalizedInteger(counters?.bytesAccepted) ?? 0,
285
- };
286
- }
287
- export function buildDashboardViewModel(snapshot) {
288
- return buildProjectedDashboardViewModel(snapshot, false);
289
- }
290
- export function buildLiveDashboardViewModel(snapshot) {
291
- return buildProjectedDashboardViewModel(snapshot, true);
292
- }
293
- function routeIsReady(route) {
294
- return (route.enabled &&
295
- (route.state === "idle" || route.state === "busy"));
296
- }
297
- function peerSelectionGuidance(peer) {
298
- if (peer.safeErrorCode === "PEER_ALIAS_COLLISION")
299
- return "alias_collision";
300
- if (peer.safeErrorCode === "PEER_SESSION_COLLISION")
301
- return "session_collision";
302
- if (peer.safeErrorCode === "PEER_DISCOVERY_INCOMPLETE") {
303
- return "discovery_incomplete";
304
- }
305
- if (peer.state === "offline")
306
- return "offline";
307
- return undefined;
308
- }
309
- function peerIsSelectable(peer) {
310
- return peer.validated && peerSelectionGuidance(peer) === undefined;
311
- }
312
- function partyStatus(routes, ready) {
313
- if (routes.length === 0)
314
- return "missing";
315
- if (ready === 0) {
316
- return routes.some((route) => route.state === "awaiting_approval")
317
- ? "waiting"
318
- : "attention";
319
- }
320
- return routes.some((route) => route.state === "busy") ? "busy" : "ready";
321
- }
322
- function routePriority(route) {
323
- if (!route.enabled || route.state === "offline") {
324
- return 0;
325
- }
326
- if (route.state === "stale")
327
- return 1;
328
- if (route.state === "awaiting_approval")
329
- return 2;
330
- if (route.state === "busy")
331
- return 3;
332
- return 4;
333
- }
334
- function alertPriority(severity) {
335
- return severity === "error" ? 0 : severity === "warning" ? 1 : 2;
336
- }
337
- function guidanceFor(code) {
338
- switch (code) {
339
- case "PEER_NOT_OBSERVED":
340
- case "CLAUDE_PEER_NOT_OBSERVED":
341
- return "claude_not_observed";
342
- case "CONNECTOR_OFFLINE":
343
- return "connector_offline";
344
- case "ROUTE_STALE":
345
- return "route_stale";
346
- case "QUEUE_STALLED":
347
- return "queue_stalled";
348
- case "CLAUDE_NATIVE_ACK_UNAVAILABLE":
349
- case "CLAUDE_RECEIPT_UNCONFIRMED":
350
- return "unconfirmed";
351
- case "ADAPTER_DEGRADED":
352
- case "ROUTE_DEGRADED":
353
- return "degraded";
354
- default:
355
- return "generic";
356
- }
357
- }
358
- function recipientIsUnobserved(route) {
359
- return (route.provider === "claude" &&
360
- (route.safeErrorCode === "CLAUDE_PEER_NOT_OBSERVED" ||
361
- route.safeErrorCode === "PEER_NOT_OBSERVED"));
362
- }
363
- function queueAge(depth, oldestQueuedAt, generatedAt) {
364
- if (depth === 0 || oldestQueuedAt === undefined || generatedAt === undefined) {
365
- return undefined;
366
- }
367
- const elapsed = Date.parse(generatedAt) - Date.parse(oldestQueuedAt);
368
- return Number.isSafeInteger(elapsed) && elapsed >= 0 ? elapsed : undefined;
369
- }
370
- function messageKey(message) {
371
- return [
372
- message.direction,
373
- boundedText(message.sourceAlias),
374
- boundedText(message.targetAlias),
375
- typeof message.messageIdSuffix === "string" ? message.messageIdSuffix : "",
376
- ].join("\0");
377
- }
378
- function buildMessageGroups(messages, includeBodies) {
379
- const grouped = new Map();
380
- for (const message of messages) {
381
- const key = messageKey(message);
382
- const events = grouped.get(key);
383
- if (events === undefined)
384
- grouped.set(key, [message]);
385
- else
386
- events.push(message);
387
- }
388
- const all = [...grouped.values()]
389
- .map((events) => {
390
- const ordered = [...events].sort((left, right) => {
391
- const byTime = compareText(normalizedTimestamp(left.timestamp) ?? "", normalizedTimestamp(right.timestamp) ?? "");
392
- return byTime ||
393
- (normalizedInteger(left.sequence) ?? 0) -
394
- (normalizedInteger(right.sequence) ?? 0);
395
- });
396
- const latest = ordered[ordered.length - 1];
397
- const allEvents = ordered.map((event) => ({
398
- sequence: normalizedInteger(event.sequence) ?? 0,
399
- ...(normalizedTimestamp(event.timestamp) === undefined
400
- ? {}
401
- : { timestamp: normalizedTimestamp(event.timestamp) }),
402
- state: event.state,
403
- ...(normalizedInteger(event.latencyMs) === undefined
404
- ? {}
405
- : { latencyMs: normalizedInteger(event.latencyMs) }),
406
- ...(safeCode(event.safeErrorCode) === undefined
407
- ? {}
408
- : { safeErrorCode: safeCode(event.safeErrorCode) }),
409
- ...(typeof event.conversationIdSuffix === "string" &&
410
- CONVERSATION_SUFFIX_PATTERN.test(event.conversationIdSuffix)
411
- ? { conversationIdSuffix: event.conversationIdSuffix }
412
- : {}),
413
- ...(event.steer === true ? { steer: true } : {}),
414
- }));
415
- return {
416
- direction: latest.direction,
417
- sourceAlias: boundedText(latest.sourceAlias),
418
- targetAlias: boundedText(latest.targetAlias),
419
- ...(typeof latest.messageIdSuffix === "string" &&
420
- OPAQUE_SUFFIX_PATTERN.test(latest.messageIdSuffix)
421
- ? { messageIdSuffix: latest.messageIdSuffix.toLowerCase() }
422
- : {}),
423
- ...(typeof latest.conversationIdSuffix === "string" &&
424
- CONVERSATION_SUFFIX_PATTERN.test(latest.conversationIdSuffix)
425
- ? { conversationIdSuffix: latest.conversationIdSuffix }
426
- : {}),
427
- state: latest.state,
428
- ...(normalizedTimestamp(latest.timestamp) === undefined
429
- ? {}
430
- : { timestamp: normalizedTimestamp(latest.timestamp) }),
431
- ...(normalizedInteger(latest.latencyMs) === undefined
432
- ? {}
433
- : { latencyMs: normalizedInteger(latest.latencyMs) }),
434
- bytes: normalizedInteger(latest.bytes) ?? 0,
435
- ...(safeCode(latest.safeErrorCode) === undefined
436
- ? {}
437
- : { safeErrorCode: safeCode(latest.safeErrorCode) }),
438
- ...(latest.steer === true ? { steer: true } : {}),
439
- // Bodies are first-class observable data (CO #36); the display copy
440
- // is bounded so one 16 KiB message cannot dominate the view model.
441
- ...(includeBodies &&
442
- typeof latest.body === "string" && latest.body.length > 0
443
- ? { body: boundedText(latest.body, 4096) }
444
- : {}),
445
- events: allEvents,
446
- };
447
- })
448
- .sort((left, right) => compareText(right.timestamp ?? "", left.timestamp ?? "") ||
449
- compareText(left.sourceAlias, right.sourceAlias));
450
- let eventBudget = DASHBOARD_MODEL_LIMITS.messageEvents;
451
- let omittedEvents = 0;
452
- const displayedGroups = all.slice(0, DASHBOARD_MODEL_LIMITS.messages);
453
- const visibleGroups = displayedGroups.map((group, index) => {
454
- const groupsAfterThis = displayedGroups.length - index - 1;
455
- const availableForThis = Math.max(1, eventBudget - groupsAfterThis);
456
- const retained = Math.min(availableForThis, group.events.length);
457
- eventBudget -= retained;
458
- omittedEvents += group.events.length - retained;
459
- return {
460
- ...group,
461
- events: retained === 0 ? [] : group.events.slice(-retained),
462
- };
463
- });
464
- return {
465
- groups: visibleGroups,
466
- omittedGroups: Math.max(0, all.length - DASHBOARD_MODEL_LIMITS.messages),
467
- omittedEvents,
468
- activeGroups: all.filter((message) => ["queued", "dispatching", "transport_written", "held"].includes(message.state)).length,
469
- };
470
- }
471
- function buildProjectedDashboardViewModel(snapshot, includeBodies) {
472
- const generatedAt = normalizedTimestamp(snapshot.generatedAt);
473
- const inboundMode = snapshot.inboundMode === "open" ? "open" : "paired";
474
- const validPeers = arePublicAvailablePeerSnapshots(snapshot.availablePeers)
475
- ? snapshot.availablePeers
476
- : [];
477
- const peers = validPeers
478
- .map((peer) => ({
479
- alias: boundedText(peer.alias),
480
- host: boundedText(peer.host),
481
- state: peer.state,
482
- validated: peer.validated,
483
- selected: peer.selected,
484
- selectable: peerIsSelectable(peer),
485
- ...(peerSelectionGuidance(peer) === undefined
486
- ? {}
487
- : { selectionGuidance: peerSelectionGuidance(peer) }),
488
- ...(normalizedTimestamp(peer.lastSeenAt) === undefined
489
- ? {}
490
- : { lastSeenAt: normalizedTimestamp(peer.lastSeenAt) }),
491
- ...(safeCode(peer.safeErrorCode) === undefined
492
- ? {}
493
- : { safeErrorCode: safeCode(peer.safeErrorCode) }),
494
- }))
495
- .sort((left, right) => {
496
- if (left.selected !== right.selected)
497
- return left.selected ? -1 : 1;
498
- return compareText(left.alias, right.alias);
499
- })
500
- .slice(0, DASHBOARD_MODEL_LIMITS.availablePeers);
501
- const allBrokerActivity = (snapshot.activityEvents ?? []).flatMap((event) => {
502
- const timestamp = normalizedTimestamp(event.timestamp);
503
- const sequence = normalizedInteger(event.sequence);
504
- if (timestamp === undefined ||
505
- sequence === undefined ||
506
- sequence < 1 ||
507
- !isDashboardActivityKind(event.kind) ||
508
- !isDashboardActivityAction(event.action)) {
509
- return [];
510
- }
511
- return [
512
- {
513
- sequence,
514
- timestamp,
515
- kind: event.kind,
516
- action: event.action,
517
- outcome: event.outcome,
518
- aliases: event.aliases.map((alias) => boundedText(alias)).slice(0, 2),
519
- operatorAction: event.operatorAction,
520
- ...(safeCode(event.safeErrorCode) === undefined
521
- ? {}
522
- : { safeErrorCode: safeCode(event.safeErrorCode) }),
523
- },
524
- ];
525
- });
526
- const brokerActivity = allBrokerActivity.slice(-DASHBOARD_MODEL_LIMITS.activityEvents);
527
- const allRoutes = snapshot.routes.map((route) => {
528
- const depth = normalizedInteger(route.queueDepth) ?? 0;
529
- const oldestQueuedAt = normalizedTimestamp(route.oldestQueuedAt);
530
- const age = queueAge(depth, oldestQueuedAt, generatedAt);
531
- return {
532
- alias: boundedText(route.alias),
533
- provider: route.provider,
534
- host: boundedText(route.host),
535
- enabled: Boolean(route.enabled),
536
- state: route.state,
537
- queueDepth: depth,
538
- ...(oldestQueuedAt === undefined ? {} : { oldestQueuedAt }),
539
- ...(age === undefined ? {} : { queueAgeMs: age }),
540
- counters: normalizedCounters(route.counters),
541
- ...(normalizedTimestamp(route.lastSeenAt) === undefined
542
- ? {}
543
- : { lastSeenAt: normalizedTimestamp(route.lastSeenAt) }),
544
- ...(safeCode(route.safeErrorCode) === undefined
545
- ? {}
546
- : { safeErrorCode: safeCode(route.safeErrorCode) }),
547
- mutable: route.mutable !== false,
548
- };
549
- });
550
- const routes = [...allRoutes]
551
- .sort((left, right) => routePriority(left) - routePriority(right) ||
552
- compareText(left.alias, right.alias))
553
- .slice(0, DASHBOARD_MODEL_LIMITS.routes);
554
- const routeByEndpoint = new Map(allRoutes.map((route) => [`${route.provider}\0${route.alias}`, route]));
555
- const allConsentEdges = snapshot.consentEdges
556
- .map((edge) => {
557
- const endpoints = edge.endpoints.map((endpoint) => ({
558
- alias: boundedText(endpoint.alias),
559
- provider: endpoint.provider,
560
- }));
561
- const routes = endpoints.map((endpoint) => routeByEndpoint.get(`${endpoint.provider}\0${endpoint.alias}`));
562
- const bothPresent = routes.every((route) => route !== undefined);
563
- return {
564
- endpoints,
565
- host: boundedText(edge.host),
566
- state: bothPresent &&
567
- routes.every((route) => route !== undefined && routeIsReady(route))
568
- ? "ready"
569
- : bothPresent
570
- ? "degraded"
571
- : "unavailable",
572
- counters: normalizedCounters(edge.counters),
573
- mutable: edge.mutable !== false,
574
- };
575
- })
576
- .sort((left, right) => compareText(left.endpoints[0].provider, right.endpoints[0].provider) ||
577
- compareText(left.endpoints[0].alias, right.endpoints[0].alias) ||
578
- compareText(left.endpoints[1].provider, right.endpoints[1].provider) ||
579
- compareText(left.endpoints[1].alias, right.endpoints[1].alias));
580
- const consentEdges = allConsentEdges.slice(0, DASHBOARD_MODEL_LIMITS.consentEdges);
581
- const allWatches = (snapshot.progressWatches ?? [])
582
- .flatMap((watch) => {
583
- const lastActivityAt = normalizedTimestamp(watch.lastActivityAt);
584
- const nextActionAt = normalizedTimestamp(watch.nextActionAt);
585
- const generatedAtMs = generatedAt === undefined ? undefined : Date.parse(generatedAt);
586
- if (!CONVERSATION_SUFFIX_PATTERN.test(watch.conversationIdSuffix) ||
587
- lastActivityAt === undefined ||
588
- nextActionAt === undefined ||
589
- generatedAtMs === undefined) {
590
- return [];
591
- }
592
- if (watch.nudgeCount !== 0 &&
593
- watch.nudgeCount !== 1 &&
594
- watch.nudgeCount !== 2) {
595
- return [];
596
- }
597
- return [
598
- {
599
- conversationIdSuffix: watch.conversationIdSuffix,
600
- ownerAlias: boundedText(watch.ownerAlias),
601
- workerAlias: boundedText(watch.workerAlias),
602
- lastActivityAt,
603
- nextActionAt,
604
- idleForMs: Math.max(0, generatedAtMs - Date.parse(lastActivityAt)),
605
- dueInMs: Math.max(0, Date.parse(nextActionAt) - generatedAtMs),
606
- nudgeCount: watch.nudgeCount,
607
- },
608
- ];
609
- })
610
- .sort((left, right) => compareText(left.nextActionAt, right.nextActionAt) ||
611
- compareText(left.ownerAlias, right.ownerAlias) ||
612
- compareText(left.workerAlias, right.workerAlias));
613
- const watches = allWatches.slice(0, DASHBOARD_MODEL_LIMITS.progressWatches);
614
- const allWatchEvents = (snapshot.progressWatchEvents ?? [])
615
- .flatMap((event) => {
616
- const timestamp = normalizedTimestamp(event.timestamp);
617
- if (timestamp === undefined ||
618
- !CONVERSATION_SUFFIX_PATTERN.test(event.conversationIdSuffix) ||
619
- normalizedInteger(event.sequence) === undefined) {
620
- return [];
621
- }
622
- return [
623
- {
624
- sequence: event.sequence,
625
- timestamp,
626
- conversationIdSuffix: event.conversationIdSuffix,
627
- ownerAlias: boundedText(event.ownerAlias),
628
- workerAlias: boundedText(event.workerAlias),
629
- kind: event.kind,
630
- actor: event.actor,
631
- ...(event.reason === undefined ? {} : { reason: event.reason }),
632
- },
633
- ];
634
- })
635
- .sort((left, right) => right.sequence - left.sequence);
636
- const watchEvents = allWatchEvents.slice(0, DASHBOARD_MODEL_LIMITS.progressWatchEvents);
637
- const connectors = snapshot.connectors
638
- .map((connector) => {
639
- const registry = isPublicRegistryObservationSnapshot(connector.registry)
640
- ? connector.registry
641
- : undefined;
642
- return {
643
- provider: connector.provider,
644
- host: boundedText(connector.host),
645
- health: connector.health,
646
- ...(safeProtocol(connector.protocol) === undefined
647
- ? {}
648
- : { protocol: safeProtocol(connector.protocol) }),
649
- ...(safeProtocol(connector.protocolVersion) === undefined
650
- ? {}
651
- : { protocolVersion: safeProtocol(connector.protocolVersion) }),
652
- ...(normalizedTimestamp(connector.lastSeenAt) === undefined
653
- ? {}
654
- : { lastSeenAt: normalizedTimestamp(connector.lastSeenAt) }),
655
- ...(connector.codexDoctor === undefined
656
- ? {}
657
- : {
658
- codexDoctor: {
659
- conditions: [...connector.codexDoctor.conditions],
660
- },
661
- }),
662
- ...(safeCode(connector.safeErrorCode) === undefined
663
- ? {}
664
- : { safeErrorCode: safeCode(connector.safeErrorCode) }),
665
- ...(registry === undefined
666
- ? {}
667
- : {
668
- registry: {
669
- entriesScanned: registry.entriesScanned,
670
- parseableRecords: registry.parseableRecords,
671
- parseableRecordSeenSinceBoot: registry.parseableRecordSeenSinceBoot,
672
- rejected: registry.rejected.map((row) => ({ ...row })),
673
- rejectedCodesOmitted: registry.rejectedCodesOmitted,
674
- },
675
- }),
676
- };
677
- })
678
- .sort((left, right) => compareText(`${left.provider}\0${left.host}`, `${right.provider}\0${right.host}`))
679
- .slice(0, DASHBOARD_MODEL_LIMITS.connectors);
680
- const messages = buildMessageGroups(snapshot.messages, includeBodies);
681
- const queuedMessages = allRoutes.reduce((total, route) => boundedAdd(total, route.queueDepth), 0);
682
- const queuedWithAge = allRoutes
683
- .filter((route) => route.oldestQueuedAt !== undefined && route.queueAgeMs !== undefined)
684
- .sort((left, right) => right.queueAgeMs - left.queueAgeMs);
685
- const oldest = queuedWithAge[0];
686
- const activeDeliveries = messages.activeGroups;
687
- const routesByProvider = Object.fromEntries(gatewayProviders.map((provider) => [
688
- provider,
689
- allRoutes.filter((route) => route.provider === provider),
690
- ]));
691
- const readyByProvider = Object.fromEntries(gatewayProviders.map((provider) => [
692
- provider,
693
- routesByProvider[provider].filter(routeIsReady).length,
694
- ]));
695
- const pairedAliasesByProvider = Object.fromEntries(gatewayProviders.map((provider) => [provider, new Set()]));
696
- for (const edge of allConsentEdges) {
697
- for (const endpoint of edge.endpoints) {
698
- pairedAliasesByProvider[endpoint.provider].add(endpoint.alias);
699
- }
700
- }
701
- const readyConsentEdges = allConsentEdges.filter((edge) => edge.state === "ready");
702
- const consentEdgeCountIsLowerBound = (normalizedInteger(snapshot.truncation.consentEdges) ?? 0) > 0;
703
- const hasConsentEvidence = allConsentEdges.length > 0 || consentEdgeCountIsLowerBound;
704
- const graph = {
705
- consentEdgeCount: allConsentEdges.length,
706
- readyConsentEdgeCount: readyConsentEdges.length,
707
- consentEdgeCountIsLowerBound,
708
- unpairedReadyByProvider: Object.fromEntries(gatewayProviders.map((provider) => [
709
- provider,
710
- routesByProvider[provider].filter((route) => routeIsReady(route) &&
711
- !pairedAliasesByProvider[provider].has(route.alias)).length,
712
- ])),
713
- };
714
- const selectedPeers = validPeers.filter((peer) => peer.selected && peerIsSelectable(peer));
715
- const selectablePeers = validPeers.filter(peerIsSelectable);
716
- const selectedClaudeCount = Math.max(selectedPeers.filter((peer) => peer.provider === "claude").length, readyByProvider.claude);
717
- const readyPairedByProvider = Object.fromEntries(gatewayProviders.map((provider) => [
718
- provider,
719
- routesByProvider[provider].some((route) => routeIsReady(route) &&
720
- pairedAliasesByProvider[provider].has(route.alias)),
721
- ]));
722
- const claudeNextAction = selectedClaudeCount > 0 &&
723
- (readyPairedByProvider.claude ||
724
- (consentEdgeCountIsLowerBound && readyByProvider.claude > 0))
725
- ? "none"
726
- : readyByProvider.claude > 0
727
- ? "pair_routes"
728
- : validPeers.length === 0
729
- ? "discover_claude"
730
- : selectablePeers.length > 0
731
- ? !hasConsentEvidence
732
- ? "pair_routes"
733
- : "restore_claude"
734
- : selectedPeers.length > 0
735
- ? "restore_claude"
736
- : "repair_claude_inventory";
737
- const codexNextAction = readyPairedByProvider.codex ||
738
- (consentEdgeCountIsLowerBound && readyByProvider.codex > 0)
739
- ? "none"
740
- : readyByProvider.codex > 0
741
- ? "pair_routes"
742
- : routesByProvider.codex.length === 0
743
- ? "register_codex"
744
- : "none";
745
- const parties = gatewayProviders.map((provider) => {
746
- const providerPeers = validPeers.filter((peer) => peer.provider === provider);
747
- const providerRoutes = routesByProvider[provider];
748
- const ready = provider === "claude"
749
- ? selectedClaudeCount
750
- : readyByProvider[provider];
751
- const nextAction = provider === "claude"
752
- ? claudeNextAction
753
- : provider === "codex"
754
- ? codexNextAction
755
- : readyPairedByProvider[provider]
756
- ? "none"
757
- : ready > 0
758
- ? "pair_routes"
759
- : "none";
760
- return {
761
- kind: provider,
762
- status: provider === "claude" && ready > 0 && partyStatus(providerRoutes, ready) === "missing"
763
- ? "attention"
764
- : partyStatus(providerRoutes, ready),
765
- total: provider === "claude" ? providerPeers.length : providerRoutes.length,
766
- ready,
767
- ...(provider === "claude"
768
- ? { selectable: selectablePeers.filter((peer) => peer.provider === provider).length }
769
- : {}),
770
- countIsLowerBound: (normalizedInteger(snapshot.truncation.routes) ?? 0) > 0 ||
771
- (provider === "claude" &&
772
- (normalizedInteger(snapshot.truncation.availablePeers) ?? 0) > 0),
773
- ...(providerPeers.find((peer) => peer.selected)?.alias === undefined
774
- ? providerRoutes[0]?.alias === undefined
775
- ? {}
776
- : { primaryAlias: boundedText(providerRoutes[0].alias) }
777
- : { primaryAlias: boundedText(providerPeers.find((peer) => peer.selected).alias) }),
778
- nextAction,
779
- };
780
- });
781
- const explicitAlerts = snapshot.alerts
782
- .map((alert) => {
783
- const code = safeCode(alert.code);
784
- const route = code === "QUEUE_STALLED" && typeof alert.alias === "string" &&
785
- alert.provider !== undefined
786
- ? routeByEndpoint.get(`${alert.provider}\0${alert.alias}`)
787
- : undefined;
788
- return {
789
- kind: "alert",
790
- ...(code === undefined ? {} : { code }),
791
- severity: alert.severity === "error" ||
792
- alert.severity === "warning" ||
793
- alert.severity === "info"
794
- ? alert.severity
795
- : "warning",
796
- ...(normalizedTimestamp(alert.timestamp) === undefined
797
- ? {}
798
- : { timestamp: normalizedTimestamp(alert.timestamp) }),
799
- ...(alert.provider === undefined ? {} : { provider: alert.provider }),
800
- ...(typeof alert.alias === "string"
801
- ? { alias: boundedText(alert.alias) }
802
- : {}),
803
- ...(typeof alert.host === "string"
804
- ? { host: boundedText(alert.host) }
805
- : {}),
806
- ...(alert.provider === "codex" &&
807
- typeof alert.host === "string" &&
808
- route !== undefined &&
809
- alert.host === route.host &&
810
- route.provider === "codex" &&
811
- route.state === "busy" &&
812
- route.queueDepth > 0
813
- ? { queueDepth: route.queueDepth }
814
- : {}),
815
- guidance: guidanceFor(code),
816
- };
817
- })
818
- .sort((left, right) => alertPriority(left.severity) - alertPriority(right.severity) ||
819
- compareText(right.timestamp ?? "", left.timestamp ?? ""));
820
- const attentionCandidates = [...explicitAlerts];
821
- const unavailableConsentEdgeScopes = new Set();
822
- for (const edge of allConsentEdges) {
823
- if (edge.state !== "unavailable")
824
- continue;
825
- for (const endpoint of edge.endpoints) {
826
- if (routeByEndpoint.has(`${endpoint.provider}\0${endpoint.alias}`))
827
- continue;
828
- const scope = `${endpoint.provider}\0${endpoint.alias}\0${edge.host}`;
829
- if (unavailableConsentEdgeScopes.has(scope))
830
- continue;
831
- unavailableConsentEdgeScopes.add(scope);
832
- attentionCandidates.push({
833
- kind: "route",
834
- severity: "warning",
835
- provider: endpoint.provider,
836
- alias: endpoint.alias,
837
- host: edge.host,
838
- guidance: "consent_edge_unavailable",
839
- });
840
- }
841
- }
842
- for (const route of allRoutes) {
843
- const writes = messages.groups.filter(({ direction, targetAlias, state, timestamp }) => parseDirection(direction)?.targetProvider === "claude" &&
844
- targetAlias === route.alias && state === "delivered" &&
845
- timestamp !== undefined);
846
- // Groups arrive newest-first. The oldest unconsumed write owns the notice:
847
- // a fresh write to the same recipient is not evidence that an hours-old one
848
- // was ever read.
849
- const write = writes[writes.length - 1];
850
- if (generatedAt === undefined || write?.timestamp === undefined ||
851
- !recipientIsUnobserved(route) ||
852
- Date.parse(generatedAt) - Date.parse(write.timestamp) <
853
- 120_000)
854
- continue;
855
- const item = {
856
- kind: "route",
857
- code: route.safeErrorCode,
858
- severity: "warning",
859
- ...(route.lastSeenAt === undefined ? {} : { timestamp: route.lastSeenAt }),
860
- provider: route.provider,
861
- alias: route.alias,
862
- host: route.host,
863
- guidance: "recipient_waiting_input",
864
- };
865
- const existingIndex = attentionCandidates.findIndex((candidate) => candidate.provider === item.provider &&
866
- candidate.alias === item.alias &&
867
- (candidate.code === "CLAUDE_PEER_NOT_OBSERVED" ||
868
- candidate.code === "PEER_NOT_OBSERVED"));
869
- if (existingIndex === -1)
870
- attentionCandidates.push(item);
871
- else
872
- attentionCandidates[existingIndex] = item;
873
- }
874
- for (const watch of allWatches) {
875
- if (watch.nudgeCount === 0)
876
- continue;
877
- attentionCandidates.push({
878
- kind: "watch",
879
- code: "PROGRESS_WATCH_QUIET",
880
- severity: "warning",
881
- timestamp: watch.lastActivityAt,
882
- alias: watch.workerAlias,
883
- guidance: "progress_watch",
884
- });
885
- }
886
- const representedScopes = new Set(attentionCandidates.map((item) => `${item.provider ?? ""}\0${item.alias ?? ""}\0${item.host ?? ""}`));
887
- for (const route of allRoutes) {
888
- if (routeIsReady(route)) {
889
- continue;
890
- }
891
- const scope = `${route.provider}\0${route.alias}\0${route.host}`;
892
- if (representedScopes.has(scope))
893
- continue;
894
- attentionCandidates.push({
895
- kind: "route",
896
- ...(route.safeErrorCode === undefined ? {} : { code: route.safeErrorCode }),
897
- severity: route.state === "offline" ? "error" : "warning",
898
- ...(route.lastSeenAt === undefined ? {} : { timestamp: route.lastSeenAt }),
899
- provider: route.provider,
900
- alias: route.alias,
901
- host: route.host,
902
- guidance: "route_stale",
903
- });
904
- representedScopes.add(scope);
905
- }
906
- for (const connector of connectors) {
907
- if (connector.health === "healthy")
908
- continue;
909
- const scope = `${connector.provider}\0\0${connector.host}`;
910
- if (representedScopes.has(scope))
911
- continue;
912
- attentionCandidates.push({
913
- kind: "connector",
914
- ...(connector.safeErrorCode === undefined
915
- ? {}
916
- : { code: connector.safeErrorCode }),
917
- severity: connector.health === "degraded" ? "warning" : "error",
918
- ...(connector.lastSeenAt === undefined
919
- ? {}
920
- : { timestamp: connector.lastSeenAt }),
921
- provider: connector.provider,
922
- host: connector.host,
923
- guidance: connector.health === "offline"
924
- ? "connector_offline"
925
- : "degraded",
926
- });
927
- representedScopes.add(scope);
928
- }
929
- for (const connector of connectors) {
930
- const observation = connector.registry;
931
- if (observation === undefined)
932
- continue;
933
- if (observation.rejected.length > 0 ||
934
- observation.rejectedCodesOmitted > 0) {
935
- const item = {
936
- kind: "connector",
937
- code: observation.rejected.some((row) => row.safeErrorCode === "CLAUDE_REGISTRY_UNAVAILABLE")
938
- ? "CLAUDE_REGISTRY_UNAVAILABLE"
939
- : "CLAUDE_REGISTRY_RECORDS_REJECTED",
940
- severity: "warning",
941
- provider: "claude",
942
- host: connector.host,
943
- guidance: "registry_rejected",
944
- };
945
- const duplicateIndex = attentionCandidates.findIndex((candidate) => candidate.code === item.code &&
946
- candidate.provider === item.provider &&
947
- candidate.alias === item.alias &&
948
- candidate.host === item.host);
949
- if (duplicateIndex === -1)
950
- attentionCandidates.push(item);
951
- else
952
- attentionCandidates[duplicateIndex] = item;
953
- continue;
954
- }
955
- if (observation.parseableRecordSeenSinceBoot)
956
- continue;
957
- attentionCandidates.push({
958
- kind: "connector",
959
- code: observation.entriesScanned === 0
960
- ? "CLAUDE_REGISTRY_EMPTY_SINCE_BOOT"
961
- : "CLAUDE_REGISTRY_NO_PARSEABLE_RECORD_SINCE_BOOT",
962
- severity: "warning",
963
- provider: "claude",
964
- host: connector.host,
965
- guidance: "registry_empty",
966
- });
967
- }
968
- if ((snapshot.health === "degraded" || snapshot.health === "offline") &&
969
- !attentionCandidates.some((item) => item.kind === "broker")) {
970
- attentionCandidates.push({
971
- kind: "broker",
972
- severity: snapshot.health === "degraded" ? "warning" : "error",
973
- guidance: snapshot.health === "offline" ? "connector_offline" : "degraded",
974
- });
975
- }
976
- const attention = attentionCandidates.slice(0, DASHBOARD_MODEL_LIMITS.alerts);
977
- const setupComplete = hasConsentEvidence;
978
- const hasDegradedConsentEdge = allConsentEdges.some((edge) => edge.state !== "ready");
979
- const overall = attentionCandidates.length > 0 ||
980
- (normalizedInteger(snapshot.truncation.alerts) ?? 0) > 0 ||
981
- hasDegradedConsentEdge
982
- ? "attention"
983
- : setupComplete
984
- ? "ready"
985
- : "setup";
986
- const omissions = {
987
- connectors: boundedAdd(normalizedInteger(snapshot.truncation.connectors) ?? 0, Math.max(0, snapshot.connectors.length - DASHBOARD_MODEL_LIMITS.connectors)),
988
- availablePeers: boundedAdd(normalizedInteger(snapshot.truncation.availablePeers) ?? 0, Math.max(0, validPeers.length - DASHBOARD_MODEL_LIMITS.availablePeers)),
989
- routes: boundedAdd(normalizedInteger(snapshot.truncation.routes) ?? 0, Math.max(0, allRoutes.length - DASHBOARD_MODEL_LIMITS.routes)),
990
- consentEdges: boundedAdd(normalizedInteger(snapshot.truncation.consentEdges) ?? 0, Math.max(0, allConsentEdges.length - DASHBOARD_MODEL_LIMITS.consentEdges)),
991
- progressWatches: boundedAdd(normalizedInteger(snapshot.truncation.progressWatches) ?? 0, Math.max(0, allWatches.length - DASHBOARD_MODEL_LIMITS.progressWatches)),
992
- upstreamProgressWatchEvents: normalizedInteger(snapshot.truncation.progressWatchEvents) ?? 0,
993
- progressWatchEvents: Math.max(0, allWatchEvents.length - DASHBOARD_MODEL_LIMITS.progressWatchEvents),
994
- upstreamMessageEvents: normalizedInteger(snapshot.truncation.messages) ?? 0,
995
- messageGroups: messages.omittedGroups,
996
- messageEvents: messages.omittedEvents,
997
- upstreamAlerts: normalizedInteger(snapshot.truncation.alerts) ?? 0,
998
- attentionItems: Math.max(0, attentionCandidates.length - DASHBOARD_MODEL_LIMITS.alerts),
999
- upstreamActivityEvents: normalizedInteger(snapshot.truncation.activityEvents) ?? 0,
1000
- activityEvents: Math.max(0, allBrokerActivity.length - DASHBOARD_MODEL_LIMITS.activityEvents),
1001
- };
1002
- return {
1003
- schemaVersion: 2,
1004
- ...(generatedAt === undefined ? {} : { generatedAt }),
1005
- inboundMode,
1006
- health: snapshot.health,
1007
- overall,
1008
- exchange: {
1009
- parties,
1010
- queuedMessages,
1011
- queueCountIsLowerBound: (normalizedInteger(snapshot.truncation.routes) ?? 0) > 0,
1012
- ...(oldest === undefined
1013
- ? {}
1014
- : {
1015
- oldestQueueAgeMs: oldest.queueAgeMs,
1016
- oldestQueuedAt: oldest.oldestQueuedAt,
1017
- }),
1018
- },
1019
- attention,
1020
- transit: {
1021
- queuedMessages,
1022
- activeDeliveries,
1023
- queueCountIsLowerBound: (normalizedInteger(snapshot.truncation.routes) ?? 0) > 0,
1024
- activeCountIsLowerBound: (normalizedInteger(snapshot.truncation.messages) ?? 0) > 0,
1025
- ...(oldest === undefined
1026
- ? {}
1027
- : {
1028
- oldestQueueAgeMs: oldest.queueAgeMs,
1029
- oldestQueuedAt: oldest.oldestQueuedAt,
1030
- }),
1031
- },
1032
- activity: messages.groups,
1033
- brokerActivity,
1034
- peers,
1035
- routes,
1036
- consentEdges,
1037
- watches,
1038
- watchEvents,
1039
- graph,
1040
- connectors,
1041
- accounting: {
1042
- accepted: normalizedInteger(snapshot.accounting.accepted) ?? 0,
1043
- duplicates: normalizedInteger(snapshot.accounting.duplicates) ?? 0,
1044
- delivered: normalizedInteger(snapshot.accounting.delivered) ?? 0,
1045
- unconfirmed: normalizedInteger(snapshot.accounting.unconfirmed) ?? 0,
1046
- failed: normalizedInteger(snapshot.accounting.failed) ?? 0,
1047
- ambiguous: normalizedInteger(snapshot.accounting.ambiguous) ?? 0,
1048
- expired: normalizedInteger(snapshot.accounting.expired) ?? 0,
1049
- cancelled: normalizedInteger(snapshot.accounting.cancelled) ?? 0,
1050
- abandoned: normalizedInteger(snapshot.accounting.abandoned) ?? 0,
1051
- rejected: normalizedInteger(snapshot.accounting.rejected) ?? 0,
1052
- bytesAccepted: normalizedInteger(snapshot.accounting.bytesAccepted) ?? 0,
1053
- queuedBytes: normalizedInteger(snapshot.accounting.queuedBytes) ?? 0,
1054
- },
1055
- ...(snapshot.deadlinePressure === undefined
1056
- ? {}
1057
- : { deadlinePressure: snapshot.deadlinePressure }),
1058
- omissions,
1059
- };
1060
- }
1061
- //# sourceMappingURL=dashboard-model.js.map