agent-embassy 1.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 (145) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/CONTRIBUTING.md +122 -0
  3. package/LICENSE +21 -0
  4. package/README.md +185 -0
  5. package/README.zh-CN.md +185 -0
  6. package/SECURITY.md +224 -0
  7. package/assets/live-dashboard/app.css +1610 -0
  8. package/assets/mark-seal.svg +7 -0
  9. package/assets/mark.svg +7 -0
  10. package/assets/social-preview.png +0 -0
  11. package/assets/vendor/react/LICENSE +21 -0
  12. package/assets/vendor/react/react-dom.production.min.js +267 -0
  13. package/assets/vendor/react/react.production.min.js +31 -0
  14. package/dist/src/errors.d.ts +5 -0
  15. package/dist/src/errors.js +11 -0
  16. package/dist/src/errors.js.map +1 -0
  17. package/dist/src/gateway/claude-compatibility-scratch.d.ts +18 -0
  18. package/dist/src/gateway/claude-compatibility-scratch.js +158 -0
  19. package/dist/src/gateway/claude-compatibility-scratch.js.map +1 -0
  20. package/dist/src/gateway/claude-helper-client.d.ts +43 -0
  21. package/dist/src/gateway/claude-helper-client.js +212 -0
  22. package/dist/src/gateway/claude-helper-client.js.map +1 -0
  23. package/dist/src/gateway/claude-helper-protocol.d.ts +172 -0
  24. package/dist/src/gateway/claude-helper-protocol.js +373 -0
  25. package/dist/src/gateway/claude-helper-protocol.js.map +1 -0
  26. package/dist/src/gateway/claude-helper-supervisor.d.ts +74 -0
  27. package/dist/src/gateway/claude-helper-supervisor.js +497 -0
  28. package/dist/src/gateway/claude-helper-supervisor.js.map +1 -0
  29. package/dist/src/gateway/claude-helper.d.ts +1 -0
  30. package/dist/src/gateway/claude-helper.js +269 -0
  31. package/dist/src/gateway/claude-helper.js.map +1 -0
  32. package/dist/src/gateway/claude-peer.d.ts +273 -0
  33. package/dist/src/gateway/claude-peer.js +2359 -0
  34. package/dist/src/gateway/claude-peer.js.map +1 -0
  35. package/dist/src/gateway/claude-runtime.d.ts +40 -0
  36. package/dist/src/gateway/claude-runtime.js +302 -0
  37. package/dist/src/gateway/claude-runtime.js.map +1 -0
  38. package/dist/src/gateway/cli-copy.d.ts +8 -0
  39. package/dist/src/gateway/cli-copy.en.d.ts +13 -0
  40. package/dist/src/gateway/cli-copy.en.js +43 -0
  41. package/dist/src/gateway/cli-copy.en.js.map +1 -0
  42. package/dist/src/gateway/cli-copy.js +19 -0
  43. package/dist/src/gateway/cli-copy.js.map +1 -0
  44. package/dist/src/gateway/cli-copy.zh-CN.d.ts +13 -0
  45. package/dist/src/gateway/cli-copy.zh-CN.js +43 -0
  46. package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -0
  47. package/dist/src/gateway/cli.d.ts +44 -0
  48. package/dist/src/gateway/cli.js +969 -0
  49. package/dist/src/gateway/cli.js.map +1 -0
  50. package/dist/src/gateway/codex-app-server.d.ts +310 -0
  51. package/dist/src/gateway/codex-app-server.js +1746 -0
  52. package/dist/src/gateway/codex-app-server.js.map +1 -0
  53. package/dist/src/gateway/codex-local-transport.d.ts +81 -0
  54. package/dist/src/gateway/codex-local-transport.js +617 -0
  55. package/dist/src/gateway/codex-local-transport.js.map +1 -0
  56. package/dist/src/gateway/codex-registration-generation.d.ts +5 -0
  57. package/dist/src/gateway/codex-registration-generation.js +19 -0
  58. package/dist/src/gateway/codex-registration-generation.js.map +1 -0
  59. package/dist/src/gateway/codex-registration-succession.d.ts +209 -0
  60. package/dist/src/gateway/codex-registration-succession.js +588 -0
  61. package/dist/src/gateway/codex-registration-succession.js.map +1 -0
  62. package/dist/src/gateway/compatibility.d.ts +70 -0
  63. package/dist/src/gateway/compatibility.js +252 -0
  64. package/dist/src/gateway/compatibility.js.map +1 -0
  65. package/dist/src/gateway/config.d.ts +24 -0
  66. package/dist/src/gateway/config.js +130 -0
  67. package/dist/src/gateway/config.js.map +1 -0
  68. package/dist/src/gateway/control.d.ts +308 -0
  69. package/dist/src/gateway/control.js +1573 -0
  70. package/dist/src/gateway/control.js.map +1 -0
  71. package/dist/src/gateway/dashboard-copy.d.ts +7 -0
  72. package/dist/src/gateway/dashboard-copy.en.d.ts +506 -0
  73. package/dist/src/gateway/dashboard-copy.en.js +507 -0
  74. package/dist/src/gateway/dashboard-copy.en.js.map +1 -0
  75. package/dist/src/gateway/dashboard-copy.js +516 -0
  76. package/dist/src/gateway/dashboard-copy.js.map +1 -0
  77. package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +506 -0
  78. package/dist/src/gateway/dashboard-copy.zh-CN.js +507 -0
  79. package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -0
  80. package/dist/src/gateway/dashboard-model.d.ts +207 -0
  81. package/dist/src/gateway/dashboard-model.js +720 -0
  82. package/dist/src/gateway/dashboard-model.js.map +1 -0
  83. package/dist/src/gateway/dashboard.d.ts +26 -0
  84. package/dist/src/gateway/dashboard.js +941 -0
  85. package/dist/src/gateway/dashboard.js.map +1 -0
  86. package/dist/src/gateway/delivery-machine.d.ts +235 -0
  87. package/dist/src/gateway/delivery-machine.js +540 -0
  88. package/dist/src/gateway/delivery-machine.js.map +1 -0
  89. package/dist/src/gateway/instance-lease.d.ts +21 -0
  90. package/dist/src/gateway/instance-lease.js +549 -0
  91. package/dist/src/gateway/instance-lease.js.map +1 -0
  92. package/dist/src/gateway/live-dashboard-app/app.js +2914 -0
  93. package/dist/src/gateway/live-dashboard-assets.d.ts +10 -0
  94. package/dist/src/gateway/live-dashboard-assets.js +73 -0
  95. package/dist/src/gateway/live-dashboard-assets.js.map +1 -0
  96. package/dist/src/gateway/live-dashboard-bootstrap.d.ts +36 -0
  97. package/dist/src/gateway/live-dashboard-bootstrap.js +176 -0
  98. package/dist/src/gateway/live-dashboard-bootstrap.js.map +1 -0
  99. package/dist/src/gateway/live-dashboard-command.d.ts +54 -0
  100. package/dist/src/gateway/live-dashboard-command.js +324 -0
  101. package/dist/src/gateway/live-dashboard-command.js.map +1 -0
  102. package/dist/src/gateway/live-dashboard-http.d.ts +41 -0
  103. package/dist/src/gateway/live-dashboard-http.js +445 -0
  104. package/dist/src/gateway/live-dashboard-http.js.map +1 -0
  105. package/dist/src/gateway/live-dashboard-protocol.d.ts +38 -0
  106. package/dist/src/gateway/live-dashboard-protocol.js +156 -0
  107. package/dist/src/gateway/live-dashboard-protocol.js.map +1 -0
  108. package/dist/src/gateway/live-dashboard-server.d.ts +33 -0
  109. package/dist/src/gateway/live-dashboard-server.js +130 -0
  110. package/dist/src/gateway/live-dashboard-server.js.map +1 -0
  111. package/dist/src/gateway/live-dashboard-stream.d.ts +46 -0
  112. package/dist/src/gateway/live-dashboard-stream.js +233 -0
  113. package/dist/src/gateway/live-dashboard-stream.js.map +1 -0
  114. package/dist/src/gateway/live-dashboard.d.ts +31 -0
  115. package/dist/src/gateway/live-dashboard.js +190 -0
  116. package/dist/src/gateway/live-dashboard.js.map +1 -0
  117. package/dist/src/gateway/locale.d.ts +4 -0
  118. package/dist/src/gateway/locale.js +10 -0
  119. package/dist/src/gateway/locale.js.map +1 -0
  120. package/dist/src/gateway/progress-watch-machine.d.ts +97 -0
  121. package/dist/src/gateway/progress-watch-machine.js +172 -0
  122. package/dist/src/gateway/progress-watch-machine.js.map +1 -0
  123. package/dist/src/gateway/providers.d.ts +287 -0
  124. package/dist/src/gateway/providers.js +2451 -0
  125. package/dist/src/gateway/providers.js.map +1 -0
  126. package/dist/src/gateway/server.d.ts +57 -0
  127. package/dist/src/gateway/server.js +264 -0
  128. package/dist/src/gateway/server.js.map +1 -0
  129. package/dist/src/gateway/service.d.ts +449 -0
  130. package/dist/src/gateway/service.js +4831 -0
  131. package/dist/src/gateway/service.js.map +1 -0
  132. package/dist/src/gateway/store.d.ts +340 -0
  133. package/dist/src/gateway/store.js +4047 -0
  134. package/dist/src/gateway/store.js.map +1 -0
  135. package/dist/src/gateway/types.d.ts +514 -0
  136. package/dist/src/gateway/types.js +414 -0
  137. package/dist/src/gateway/types.js.map +1 -0
  138. package/dist/src/mutex.d.ts +4 -0
  139. package/dist/src/mutex.js +23 -0
  140. package/dist/src/mutex.js.map +1 -0
  141. package/docs/DESIGN.md +134 -0
  142. package/docs/GATEWAY-ARCHITECTURE.md +858 -0
  143. package/package.json +80 -0
  144. package/skills/embassy-peer/SKILL.md +198 -0
  145. package/skills/embassy-peer/agents/openai.yaml +4 -0
@@ -0,0 +1,941 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { lstat, open, realpath, rename, unlink, } from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { formatDashboardCopy, getDashboardCopy, } from "./dashboard-copy.js";
5
+ import { buildDashboardViewModel, DASHBOARD_MODEL_LIMITS, } from "./dashboard-model.js";
6
+ export const DASHBOARD_FILE_NAME = "gateway-dashboard.html";
7
+ export const DASHBOARD_ZH_CN_FILE_NAME = "gateway-dashboard.zh-CN.html";
8
+ /** Retained as an API compatibility constant. Static snapshots never refresh. */
9
+ export const DASHBOARD_DEFAULT_REFRESH_SECONDS = 15;
10
+ export const DASHBOARD_MESSAGE_LIMIT = DASHBOARD_MODEL_LIMITS.messages;
11
+ export const DASHBOARD_MESSAGE_HISTORY_LIMIT = DASHBOARD_MODEL_LIMITS.messageEvents;
12
+ export const DASHBOARD_CONNECTOR_LIMIT = DASHBOARD_MODEL_LIMITS.connectors;
13
+ export const DASHBOARD_AVAILABLE_PEER_LIMIT = DASHBOARD_MODEL_LIMITS.availablePeers;
14
+ export const DASHBOARD_AVAILABLE_PEER_INPUT_LIMIT = 256;
15
+ export const DASHBOARD_ROUTE_LIMIT = DASHBOARD_MODEL_LIMITS.routes;
16
+ export const DASHBOARD_ALERT_LIMIT = DASHBOARD_MODEL_LIMITS.alerts;
17
+ export const DASHBOARD_MAX_HTML_BYTES = 256 * 1024;
18
+ const HTML_ESCAPE_PATTERN = /[&<>"']/g;
19
+ const HTML_ESCAPES = {
20
+ "&": "&amp;",
21
+ "<": "&lt;",
22
+ ">": "&gt;",
23
+ '"': "&quot;",
24
+ "'": "&#39;",
25
+ };
26
+ export function escapeDashboardHtml(value) {
27
+ return value.replace(HTML_ESCAPE_PATTERN, (character) => HTML_ESCAPES[character] ?? "");
28
+ }
29
+ function t(context, key, values = {}) {
30
+ return escapeDashboardHtml(formatDashboardCopy(context.copy, key, values));
31
+ }
32
+ function normalizeDashboardLocale(value) {
33
+ if (value === undefined || value === "en")
34
+ return "en";
35
+ if (value === "zh-CN")
36
+ return "zh-CN";
37
+ throw new Error("DASHBOARD_LOCALE_UNSUPPORTED");
38
+ }
39
+ function formatInteger(value) {
40
+ return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
41
+ }
42
+ function renderCount(context, value, lowerBound) {
43
+ const count = formatInteger(value);
44
+ return lowerBound
45
+ ? t(context, "count.atLeast", { count })
46
+ : count;
47
+ }
48
+ function formatDuration(milliseconds) {
49
+ if (milliseconds === undefined)
50
+ return "—";
51
+ if (milliseconds < 1_000)
52
+ return `${milliseconds} ms`;
53
+ if (milliseconds < 60_000) {
54
+ const seconds = milliseconds / 1_000;
55
+ return `${seconds < 10 ? seconds.toFixed(1) : Math.round(seconds)} s`;
56
+ }
57
+ const minutes = Math.floor(milliseconds / 60_000);
58
+ const seconds = Math.floor((milliseconds % 60_000) / 1_000);
59
+ if (minutes < 60)
60
+ return `${minutes}m ${seconds}s`;
61
+ const hours = Math.floor(minutes / 60);
62
+ return `${hours}h ${minutes % 60}m`;
63
+ }
64
+ function formatQueueAge(milliseconds, locale) {
65
+ if (milliseconds === undefined)
66
+ return undefined;
67
+ const seconds = Math.floor(milliseconds / 1_000);
68
+ if (seconds < 60)
69
+ return locale === "zh-CN" ? `${seconds} 秒` : `${seconds}s`;
70
+ const minutes = Math.floor(seconds / 60);
71
+ if (minutes < 60) {
72
+ return locale === "zh-CN"
73
+ ? `${minutes} 分 ${seconds % 60} 秒`
74
+ : `${minutes}m ${seconds % 60}s`;
75
+ }
76
+ const hours = Math.floor(minutes / 60);
77
+ if (hours < 48) {
78
+ return locale === "zh-CN"
79
+ ? `${hours} 小时 ${minutes % 60} 分`
80
+ : `${hours}h ${minutes % 60}m`;
81
+ }
82
+ return locale === "zh-CN"
83
+ ? `${Math.floor(hours / 24)} 天 ${hours % 24} 小时`
84
+ : `${Math.floor(hours / 24)}d ${hours % 24}h`;
85
+ }
86
+ function formatBytes(bytes) {
87
+ if (bytes < 1_024)
88
+ return `${bytes} B`;
89
+ if (bytes < 1_048_576) {
90
+ const kibibytes = bytes / 1_024;
91
+ return `${kibibytes < 10 ? kibibytes.toFixed(1) : Math.round(kibibytes)} KiB`;
92
+ }
93
+ const mebibytes = bytes / 1_048_576;
94
+ return `${mebibytes < 10 ? mebibytes.toFixed(1) : Math.round(mebibytes)} MiB`;
95
+ }
96
+ function formatShortAge(milliseconds, locale) {
97
+ const seconds = Math.max(0, Math.floor(milliseconds / 1_000));
98
+ if (seconds < 60)
99
+ return locale === "zh-CN" ? `${seconds} 秒` : `${seconds}s`;
100
+ const minutes = Math.floor(seconds / 60);
101
+ if (minutes < 60)
102
+ return locale === "zh-CN" ? `${minutes} 分` : `${minutes}m`;
103
+ const hours = Math.floor(minutes / 60);
104
+ if (hours < 48)
105
+ return locale === "zh-CN" ? `${hours} 小时` : `${hours}h`;
106
+ const days = Math.floor(hours / 24);
107
+ return locale === "zh-CN" ? `${days} 天` : `${days}d`;
108
+ }
109
+ function renderTimestampAtSnapshot(context, timestamp) {
110
+ if (timestamp === undefined) {
111
+ return `<span class="quiet">${t(context, "time.unavailable")}</span>`;
112
+ }
113
+ if (context.model.generatedAt === undefined) {
114
+ return `<time datetime="${timestamp}" title="${timestamp}">${escapeDashboardHtml(timestamp.slice(0, 19).replace("T", " "))} UTC</time>`;
115
+ }
116
+ const difference = Date.parse(context.model.generatedAt) - Date.parse(timestamp);
117
+ let label;
118
+ if (Math.abs(difference) < 1_000) {
119
+ label = t(context, "time.atSnapshot");
120
+ }
121
+ else if (difference > 0) {
122
+ label = t(context, "time.beforeSnapshot", {
123
+ age: formatShortAge(difference, context.locale),
124
+ });
125
+ }
126
+ else {
127
+ label = t(context, "time.afterSnapshot", {
128
+ age: formatShortAge(-difference, context.locale),
129
+ });
130
+ }
131
+ return `<time datetime="${timestamp}" title="${timestamp}">${label}</time>`;
132
+ }
133
+ function toneForOverall(overall) {
134
+ return overall === "ready" ? "good" : overall === "attention" ? "danger" : "info";
135
+ }
136
+ function toneForParty(status) {
137
+ if (status === "ready")
138
+ return "good";
139
+ if (status === "busy")
140
+ return "info";
141
+ if (status === "waiting")
142
+ return "warning";
143
+ if (status === "attention")
144
+ return "danger";
145
+ return "quiet";
146
+ }
147
+ function toneForHealth(health) {
148
+ if (health === "healthy")
149
+ return "good";
150
+ if (health === "connecting")
151
+ return "info";
152
+ if (health === "degraded")
153
+ return "warning";
154
+ return "danger";
155
+ }
156
+ function toneForCompatibility(compatibility) {
157
+ if (compatibility === "compatible")
158
+ return "good";
159
+ if (compatibility === "unknown")
160
+ return "quiet";
161
+ if (compatibility === "expired")
162
+ return "warning";
163
+ return "danger";
164
+ }
165
+ function toneForRoute(state) {
166
+ if (state === "idle")
167
+ return "good";
168
+ if (state === "busy")
169
+ return "info";
170
+ if (state === "awaiting_approval")
171
+ return "warning";
172
+ if (state === "disabled")
173
+ return "quiet";
174
+ return "danger";
175
+ }
176
+ function toneForPeer(state) {
177
+ if (state === "idle")
178
+ return "good";
179
+ if (state === "busy")
180
+ return "info";
181
+ if (state === "awaiting_approval")
182
+ return "warning";
183
+ return "danger";
184
+ }
185
+ function toneForDelivery(state, safeErrorCode) {
186
+ if (state === "rejected" && safeErrorCode === "SENDER_NOT_PAIRED") {
187
+ return "quiet";
188
+ }
189
+ if (state === "delivered")
190
+ return "good";
191
+ if (state === "queued" ||
192
+ state === "dispatching" ||
193
+ state === "transport_written") {
194
+ return "info";
195
+ }
196
+ if (state === "held" || state === "unconfirmed" || state === "ambiguous") {
197
+ return "warning";
198
+ }
199
+ if (state === "duplicate")
200
+ return "quiet";
201
+ return "danger";
202
+ }
203
+ function statusPill(label, tone) {
204
+ return `<span class="status status--${tone}"><span class="status__dot" aria-hidden="true"></span>${label}</span>`;
205
+ }
206
+ function overallLabel(context) {
207
+ return t(context, context.model.overall === "ready"
208
+ ? "overall.ready"
209
+ : context.model.overall === "attention"
210
+ ? "overall.attention"
211
+ : "overall.setup");
212
+ }
213
+ function partyStatusLabel(context, status) {
214
+ const key = status === "ready"
215
+ ? "status.ready"
216
+ : status === "busy"
217
+ ? "status.busy"
218
+ : status === "waiting"
219
+ ? "status.waiting"
220
+ : status === "attention"
221
+ ? "status.attention"
222
+ : "status.missing";
223
+ return t(context, key);
224
+ }
225
+ function nextActionLabel(context, action) {
226
+ const key = action === "discover_claude"
227
+ ? "next.discoverClaude"
228
+ : action === "select_claude"
229
+ ? "next.selectClaude"
230
+ : action === "pair_routes"
231
+ ? "next.pairRoutes"
232
+ : action === "restore_claude"
233
+ ? "next.restoreClaude"
234
+ : action === "repair_claude_inventory"
235
+ ? "next.repairClaude"
236
+ : action === "register_codex"
237
+ ? "next.registerCodex"
238
+ : action === "restore_codex"
239
+ ? "next.restoreCodex"
240
+ : "next.none";
241
+ return t(context, key);
242
+ }
243
+ function renderParty(context, party) {
244
+ const isClaude = party.kind === "claude";
245
+ const ready = renderCount(context, party.ready, party.countIsLowerBound);
246
+ const total = renderCount(context, party.total, party.countIsLowerBound);
247
+ const selectable = renderCount(context, party.selectable ?? 0, party.countIsLowerBound);
248
+ return `<article class="exchange-party exchange-party--${party.kind}" data-semantic-party="${party.kind}">
249
+ <div class="party-heading">
250
+ <div><h3>${t(context, isClaude ? "exchange.claude.title" : "exchange.codex.title")}</h3><p>${t(context, isClaude ? "exchange.claude.note" : "exchange.codex.note")}</p></div>
251
+ ${statusPill(partyStatusLabel(context, party.status), toneForParty(party.status))}
252
+ </div>
253
+ <p class="party-count">${t(context, isClaude ? "exchange.count.claude" : "exchange.count.codex", { ready, selectable, total })}</p>
254
+ ${party.primaryAlias === undefined ? "" : `<p class="party-alias">${escapeDashboardHtml(party.primaryAlias)}</p>`}
255
+ <p class="next-action"><strong>${t(context, "next.label")}:</strong> ${nextActionLabel(context, party.nextAction)}</p>
256
+ </article>`;
257
+ }
258
+ function renderPairGraph(context) {
259
+ const graph = context.model.graph;
260
+ const summary = t(context, "app.routes.pairSummary", {
261
+ ready: renderCount(context, graph.readyPairCount, graph.pairCountIsLowerBound),
262
+ total: renderCount(context, graph.pairCount, graph.pairCountIsLowerBound),
263
+ });
264
+ const unpaired = t(context, "app.routes.unpairedSummary", {
265
+ claude: String(graph.unpairedReadyClaude),
266
+ codex: String(graph.unpairedReadyCodex),
267
+ });
268
+ const rows = context.model.pairs.length === 0
269
+ ? `<p class="quiet">${t(context, "app.routes.noPairInline")}</p>`
270
+ : `<ul class="pair-list">${context.model.pairs.map((pair) => {
271
+ const label = t(context, "app.routes.pairDescription", {
272
+ claude: escapeDashboardHtml(pair.claudeAlias),
273
+ codex: escapeDashboardHtml(pair.codexAlias),
274
+ });
275
+ const stateLabel = pair.state === "ready"
276
+ ? t(context, "status.ready")
277
+ : pair.state === "degraded"
278
+ ? t(context, "status.attention")
279
+ : t(context, "status.missing");
280
+ const tone = pair.state === "ready"
281
+ ? "good"
282
+ : pair.state === "degraded"
283
+ ? "warning"
284
+ : "danger";
285
+ return `<li><span>${label}</span>${statusPill(stateLabel, tone)}</li>`;
286
+ }).join("")}</ul>`;
287
+ const omitted = context.model.omissions.pairs === 0
288
+ ? ""
289
+ : `<p class="quiet">${t(context, "app.omitted.pairs", {
290
+ count: String(context.model.omissions.pairs),
291
+ })}</p>`;
292
+ return `<section class="pair-graph" aria-labelledby="pair-graph-title">
293
+ <div class="pair-graph__heading"><h3 id="pair-graph-title">${t(context, "app.routes.pairs")}</h3><strong>${summary}</strong></div>
294
+ <p>${unpaired}</p>
295
+ ${rows}
296
+ ${omitted}
297
+ </section>`;
298
+ }
299
+ function renderExchange(context) {
300
+ const age = formatQueueAge(context.model.exchange.oldestQueueAgeMs, context.locale);
301
+ const pouch = context.model.exchange.queuedMessages === 0
302
+ ? t(context, "exchange.pouch.empty")
303
+ : t(context, "exchange.pouch.queued", {
304
+ count: renderCount(context, context.model.exchange.queuedMessages, context.model.exchange.queueCountIsLowerBound),
305
+ });
306
+ const paired = context.model.inboundMode === "paired";
307
+ const hasPair = context.model.graph.readyPairCount > 0;
308
+ const policyBody = paired
309
+ ? t(context, hasPair ? "inbound.paired.body" : "inbound.noPair.body")
310
+ : t(context, "inbound.open.body");
311
+ return `<section class="section exchange" aria-labelledby="exchange-title">
312
+ <div class="section-heading">
313
+ <div><p class="eyebrow">${t(context, "exchange.eyebrow")}</p><h2 id="exchange-title">${t(context, "exchange.title")}</h2></div>
314
+ <p>${t(context, "exchange.note")}</p>
315
+ </div>
316
+ <div class="inbound-policy" data-inbound-mode="${context.model.inboundMode}">
317
+ ${statusPill(t(context, paired ? "inbound.paired.badge" : "inbound.open.badge"), paired ? "quiet" : "warning")}
318
+ <p>${policyBody}</p>
319
+ </div>
320
+ <div class="exchange-board">
321
+ ${renderParty(context, context.model.exchange.claude)}
322
+ <div class="pouch" aria-label="${t(context, "exchange.pouch.title")}">
323
+ <span class="pouch__line" aria-hidden="true"></span>
324
+ <div class="seal-mark" aria-hidden="true"><span></span></div>
325
+ <p class="pouch__title">${t(context, "exchange.pouch.title")}</p>
326
+ <strong>${pouch}</strong>
327
+ ${age === undefined ? "" : `<small>${t(context, "exchange.pouch.oldest", { age })}</small>`}
328
+ </div>
329
+ ${renderParty(context, context.model.exchange.codex)}
330
+ </div>
331
+ <div class="direction-key" aria-label="${t(context, "exchange.title")}"><span>Codex → Claude</span><span>Claude → Codex</span></div>
332
+ ${renderPairGraph(context)}
333
+ </section>`;
334
+ }
335
+ function severityLabel(context, severity) {
336
+ return t(context, severity === "error"
337
+ ? "severity.error"
338
+ : severity === "warning"
339
+ ? "severity.warning"
340
+ : "severity.info");
341
+ }
342
+ function alertTone(severity) {
343
+ return severity === "error" ? "danger" : severity === "warning" ? "warning" : "info";
344
+ }
345
+ function guidanceKeys(guidance) {
346
+ switch (guidance) {
347
+ case "reobserve_claude":
348
+ return ["guidance.reobserveClaude.title", "guidance.reobserveClaude.body", "guidance.reobserveClaude.action"];
349
+ case "reobserve_codex":
350
+ return ["guidance.reobserveCodex.title", "guidance.reobserveCodex.body", "guidance.reobserveCodex.action"];
351
+ case "claude_not_observed":
352
+ return ["guidance.claudeNotObserved.title", "guidance.claudeNotObserved.body", "guidance.claudeNotObserved.action"];
353
+ case "codex_stale":
354
+ return ["guidance.codexStale.title", "guidance.codexStale.body", "guidance.codexStale.action"];
355
+ case "connector_offline":
356
+ return ["guidance.connectorOffline.title", "guidance.connectorOffline.body", "guidance.connectorOffline.action"];
357
+ case "route_stale":
358
+ return ["guidance.routeStale.title", "guidance.routeStale.body", "guidance.routeStale.action"];
359
+ case "queue_stalled":
360
+ return ["guidance.queueStalled.title", "guidance.queueStalled.body", "guidance.queueStalled.action"];
361
+ case "unconfirmed":
362
+ return ["guidance.unconfirmed.title", "guidance.unconfirmed.body", "guidance.unconfirmed.action"];
363
+ case "degraded":
364
+ return ["guidance.degraded.title", "guidance.degraded.body", "guidance.degraded.action"];
365
+ case "codex_succession_busy":
366
+ return ["guidance.codexSuccessionBusy.title", "guidance.codexSuccessionBusy.body", "guidance.codexSuccessionBusy.action"];
367
+ case "codex_succession_recovery":
368
+ return ["guidance.codexSuccessionRecovery.title", "guidance.codexSuccessionRecovery.body", "guidance.codexSuccessionRecovery.action"];
369
+ case "progress_watch":
370
+ return ["guidance.progressWatch.title", "guidance.progressWatch.body", "guidance.progressWatch.action"];
371
+ default:
372
+ return ["guidance.generic.title", "guidance.generic.body", "guidance.generic.action"];
373
+ }
374
+ }
375
+ function renderAttention(context) {
376
+ const hasOmissions = context.model.omissions.upstreamAlerts > 0 ||
377
+ context.model.omissions.attentionItems > 0;
378
+ if (context.model.attention.length === 0 && !hasOmissions)
379
+ return "";
380
+ return `<section class="section attention" aria-labelledby="attention-title">
381
+ <div class="section-heading"><div><p class="eyebrow">${t(context, "attention.eyebrow")}</p><h2 id="attention-title">${t(context, "attention.title")}</h2></div><p>${t(context, hasOmissions ? "attention.countVisible" : "attention.count", { count: formatInteger(context.model.attention.length) })}</p></div>
382
+ ${context.model.attention.length === 0 ? `<p class="attention-projection-note">${t(context, "attention.projectionOnly")}</p>` : `<ol class="attention-list">${context.model.attention
383
+ .map((item) => {
384
+ const [titleKey, bodyKey, actionKey] = guidanceKeys(item.guidance);
385
+ const scope = [
386
+ item.provider === undefined
387
+ ? undefined
388
+ : t(context, item.provider === "claude" ? "provider.claude" : "provider.codex"),
389
+ item.alias === undefined ? undefined : escapeDashboardHtml(item.alias),
390
+ item.host === undefined ? undefined : escapeDashboardHtml(item.host),
391
+ ].filter((value) => value !== undefined);
392
+ return `<li class="attention-item attention-item--${alertTone(item.severity)}">
393
+ <div class="attention-item__meta">${statusPill(severityLabel(context, item.severity), alertTone(item.severity))}${item.code === undefined ? "" : `<code>${item.code}</code>`}${renderTimestampAtSnapshot(context, item.timestamp)}</div>
394
+ <h3>${t(context, titleKey)}</h3><p>${t(context, bodyKey)}</p>
395
+ ${scope.length === 0 ? "" : `<p class="scope"><strong>${t(context, "attention.scope")}:</strong> ${scope.join(" · ")}</p>`}
396
+ <p class="next-action"><strong>${t(context, "next.label")}:</strong> ${t(context, actionKey)}</p>
397
+ </li>`;
398
+ })
399
+ .join("")}</ol>`}
400
+ </section>`;
401
+ }
402
+ function renderTransit(context) {
403
+ const age = formatQueueAge(context.model.transit.oldestQueueAgeMs, context.locale);
404
+ return `<section class="section transit" aria-labelledby="transit-title">
405
+ <div class="section-heading"><div><p class="eyebrow">${t(context, "transit.eyebrow")}</p><h2 id="transit-title">${t(context, "transit.title")}</h2></div>${context.model.transit.queuedMessages === 0 ? `<p>${t(context, "transit.empty")}</p>` : ""}</div>
406
+ <dl class="ledger-strip">
407
+ <div><dt>${t(context, "transit.queued")}</dt><dd>${renderCount(context, context.model.transit.queuedMessages, context.model.transit.queueCountIsLowerBound)}</dd></div>
408
+ <div><dt>${t(context, "transit.active")}</dt><dd>${renderCount(context, context.model.transit.activeDeliveries, context.model.transit.activeCountIsLowerBound)}</dd></div>
409
+ <div><dt>${t(context, "transit.oldest")}</dt><dd>${age === undefined ? t(context, context.model.transit.queuedMessages === 0 ? "time.unavailable" : "transit.unavailable") : `<time datetime="${context.model.transit.oldestQueuedAt}">${escapeDashboardHtml(age)}</time>`}</dd></div>
410
+ </dl>
411
+ </section>`;
412
+ }
413
+ function deliveryLabelKey(state) {
414
+ switch (state) {
415
+ case "queued": return "delivery.queued";
416
+ case "duplicate": return "delivery.duplicate";
417
+ case "dispatching": return "delivery.dispatching";
418
+ case "transport_written": return "delivery.transportWritten";
419
+ case "held": return "delivery.held";
420
+ case "delivered": return "delivery.delivered";
421
+ case "unconfirmed": return "delivery.unconfirmed";
422
+ case "failed": return "delivery.failed";
423
+ case "ambiguous": return "delivery.ambiguous";
424
+ case "expired": return "delivery.expired";
425
+ case "cancelled": return "delivery.cancelled";
426
+ case "abandoned": return "delivery.abandoned";
427
+ case "rejected": return "delivery.rejected";
428
+ }
429
+ }
430
+ function deliveryMeaningKey(state, direction, safeErrorCode) {
431
+ if (state === "rejected" && safeErrorCode === "SENDER_NOT_PAIRED") {
432
+ return "activity.meaning.senderNotPaired";
433
+ }
434
+ if (state === "delivered") {
435
+ return direction === "codex_to_claude"
436
+ ? "activity.meaning.delivered.codexToClaude"
437
+ : "activity.meaning.delivered.claudeToCodex";
438
+ }
439
+ if (state === "unconfirmed")
440
+ return "activity.meaning.unconfirmed";
441
+ if (state === "ambiguous")
442
+ return "activity.meaning.ambiguous";
443
+ return "activity.meaning.other";
444
+ }
445
+ function renderMessageHistory(context, message) {
446
+ if (message.events.length <= 1) {
447
+ return `<span class="quiet">${t(context, "activity.history.one")}</span>`;
448
+ }
449
+ return `<details class="history"><summary>${t(context, "activity.history.many", { count: formatInteger(message.events.length) })}</summary><ol>${message.events
450
+ .map((event) => `<li data-dashboard-row="message-event">${renderTimestampAtSnapshot(context, event.timestamp)} ${statusPill(t(context, deliveryLabelKey(event.state)), toneForDelivery(event.state, event.safeErrorCode))}${event.safeErrorCode === undefined ? "" : `<code>${event.safeErrorCode}</code>`}</li>`)
451
+ .join("")}</ol></details>`;
452
+ }
453
+ function renderActivity(context) {
454
+ const rows = context.model.activity.length === 0
455
+ ? `<tr class="empty-row"><td colspan="7">${t(context, "activity.empty")}</td></tr>`
456
+ : context.model.activity
457
+ .map((message) => `<tr data-dashboard-row="message-summary" data-delivery-state="${message.state}">
458
+ <td data-label="${t(context, "activity.column.updated")}">${renderTimestampAtSnapshot(context, message.timestamp)}</td>
459
+ <td data-label="${t(context, "activity.column.route")}" class="route-cell"><strong>${t(context, message.direction === "claude_to_codex" ? "direction.claudeToCodex" : "direction.codexToClaude")}${message.steer === true ? ' <span class="pill quiet">STEER</span>' : ""}</strong><span>${escapeDashboardHtml(message.sourceAlias)} → ${escapeDashboardHtml(message.targetAlias)}</span></td>
460
+ <td data-label="${t(context, "activity.column.id")}"><code>…${message.messageIdSuffix ?? "—"}</code>${message.conversationIdSuffix === undefined ? "" : `<span class="cell-note"><code>conv …${escapeDashboardHtml(message.conversationIdSuffix)}</code></span>`}</td>
461
+ <td data-label="${t(context, "activity.column.result")}">${statusPill(t(context, deliveryLabelKey(message.state)), toneForDelivery(message.state, message.safeErrorCode))}<span class="cell-note">${t(context, deliveryMeaningKey(message.state, message.direction, message.safeErrorCode))}</span>${message.safeErrorCode === undefined ? "" : `<code class="cell-code">${message.safeErrorCode}</code>`}</td>
462
+ <td data-label="${t(context, "activity.column.elapsed")}" class="numeric">${formatDuration(message.latencyMs)}</td>
463
+ <td data-label="${t(context, "activity.column.size")}" class="numeric">${formatBytes(message.bytes)}</td>
464
+ <td data-label="${t(context, "activity.column.history")}">${renderMessageHistory(context, message)}</td>
465
+ </tr>`)
466
+ .join("");
467
+ const operationCopyKeys = {
468
+ discovery_refreshed: "app.activity.operation.discoveryRefreshed",
469
+ claude_selected: "app.activity.operation.claudeSelected",
470
+ claude_unselected: "app.activity.operation.claudeUnselected",
471
+ codex_registered: "app.activity.operation.codexRegistered",
472
+ codex_succeeded: "app.activity.operation.codexSucceeded",
473
+ codex_unregistered: "app.activity.operation.codexUnregistered",
474
+ routes_paired: "app.activity.operation.routesPaired",
475
+ routes_unpaired: "app.activity.operation.routesUnpaired",
476
+ watch_ended: "app.activity.operation.watchEnded",
477
+ };
478
+ const operations = context.model.brokerActivity.length === 0
479
+ ? ""
480
+ : `<details class="history"><summary>${t(context, "app.activity.kinds.operation")} (${formatInteger(context.model.brokerActivity.length)})</summary><ol>${context.model.brokerActivity
481
+ .slice()
482
+ .reverse()
483
+ .map((event) => `<li data-dashboard-row="operator-action">${renderTimestampAtSnapshot(context, event.timestamp)} ${escapeDashboardHtml(t(context, operationCopyKeys[event.action]))} ${statusPill(t(context, event.outcome === "accepted" ? "app.activity.operation.accepted" : "app.activity.operation.rejected"), event.outcome === "accepted" ? "good" : "warning")}${event.aliases.length === 0 ? "" : ` <code>${event.aliases.map(escapeDashboardHtml).join(" · ")}</code>`}${event.safeErrorCode === undefined ? "" : ` <code>${event.safeErrorCode}</code>`}</li>`)
484
+ .join("")}</ol></details>`;
485
+ return `<section class="section" aria-labelledby="activity-title">
486
+ <div class="section-heading"><div><p class="eyebrow">${t(context, "activity.eyebrow")}</p><h2 id="activity-title">${t(context, "activity.title")}</h2></div><p>${t(context, "activity.note")}</p></div>
487
+ <div class="table-wrap" tabindex="0" role="region" aria-labelledby="activity-title"><table class="responsive-table"><caption>${t(context, "activity.note")}</caption><thead><tr><th>${t(context, "activity.column.updated")}</th><th>${t(context, "activity.column.route")}</th><th>${t(context, "activity.column.id")}</th><th>${t(context, "activity.column.result")}</th><th class="numeric">${t(context, "activity.column.elapsed")}</th><th class="numeric">${t(context, "activity.column.size")}</th><th>${t(context, "activity.column.history")}</th></tr></thead><tbody>${rows}</tbody></table></div>${operations}
488
+ </section>`;
489
+ }
490
+ function renderProgressWatches(context) {
491
+ const rows = context.model.watches.length === 0
492
+ ? `<tr class="empty-row"><td colspan="7">${t(context, "watches.empty")}</td></tr>`
493
+ : context.model.watches
494
+ .map((watch) => `<tr data-dashboard-row="progress-watch" data-watch-phase="${watch.phase}">
495
+ <th scope="row" data-label="${t(context, "watches.column.conversation")}"><code>…${escapeDashboardHtml(watch.conversationIdSuffix)}</code></th>
496
+ <td data-label="${t(context, "watches.column.parties")}" class="route-cell"><strong>${escapeDashboardHtml(watch.ownerAlias)} → ${escapeDashboardHtml(watch.workerAlias)}</strong>${watch.workerReportedComplete ? `<span class="cell-note">${t(context, "watches.workerComplete")}</span>` : ""}</td>
497
+ <td data-label="${t(context, "watches.column.phase")}">${statusPill(t(context, watch.phase === "episode" ? "watches.phase.episode" : "watches.phase.quiet"), watch.phase === "episode" ? "warning" : "quiet")}</td>
498
+ <td data-label="${t(context, "watches.column.quietFor")}" class="numeric">${formatDuration(watch.idleForMs)}</td>
499
+ <td data-label="${t(context, "watches.column.nextAction")}" class="numeric">${formatDuration(watch.dueInMs)}</td>
500
+ <td data-label="${t(context, "watches.column.nudges")}" class="numeric">${formatInteger(watch.nudgeCount)}</td>
501
+ <td data-label="${t(context, "watches.column.capability")}">${statusPill(t(context, watch.capability === "route" ? "watches.capability.route" : "watches.capability.conversation"), watch.capability === "route" ? "warning" : "info")}</td>
502
+ </tr>`)
503
+ .join("");
504
+ return `<section class="section" aria-labelledby="watches-title">
505
+ <div class="section-heading"><div><p class="eyebrow">${t(context, "watches.eyebrow")}</p><h2 id="watches-title">${t(context, "watches.title")}</h2></div><p>${t(context, "watches.note")}</p></div>
506
+ <div class="table-wrap" tabindex="0" role="region" aria-labelledby="watches-title"><table class="responsive-table"><caption>${t(context, "watches.note")}</caption><thead><tr><th>${t(context, "watches.column.conversation")}</th><th>${t(context, "watches.column.parties")}</th><th>${t(context, "watches.column.phase")}</th><th class="numeric">${t(context, "watches.column.quietFor")}</th><th class="numeric">${t(context, "watches.column.nextAction")}</th><th class="numeric">${t(context, "watches.column.nudges")}</th><th>${t(context, "watches.column.capability")}</th></tr></thead><tbody>${rows}</tbody></table></div>
507
+ </section>`;
508
+ }
509
+ function providerLabel(context, provider) {
510
+ return t(context, provider === "claude" ? "provider.claude" : "provider.codex");
511
+ }
512
+ function healthLabel(context, health) {
513
+ return t(context, `health.${health}`);
514
+ }
515
+ function compatibilityLabel(context, compatibility) {
516
+ return t(context, `compatibility.${compatibility}`);
517
+ }
518
+ function routeStateLabel(context, state) {
519
+ const suffix = state === "awaiting_approval" ? "awaitingApproval" : state;
520
+ return t(context, `route.${suffix}`);
521
+ }
522
+ function peerStateLabel(context, state) {
523
+ const suffix = state === "awaiting_approval" ? "awaitingApproval" : state;
524
+ return t(context, `peer.${suffix}`);
525
+ }
526
+ function peerSelectionGuidance(context, guidance) {
527
+ const key = guidance === "alias_collision"
528
+ ? "peer.reason.aliasCollision"
529
+ : guidance === "session_collision"
530
+ ? "peer.reason.sessionCollision"
531
+ : guidance === "discovery_incomplete"
532
+ ? "peer.reason.discoveryIncomplete"
533
+ : guidance === "offline"
534
+ ? "peer.reason.offline"
535
+ : "peer.reason.incompatible";
536
+ return t(context, key);
537
+ }
538
+ function renderSessions(context) {
539
+ const peerRows = context.model.peers.length === 0
540
+ ? `<tr class="empty-row"><td colspan="7">${t(context, "sessions.peers.empty")}</td></tr>`
541
+ : context.model.peers.map((peer) => `<tr><th scope="row" data-label="${t(context, "column.alias")}">${escapeDashboardHtml(peer.alias)}</th><td data-label="${t(context, "column.host")}">${escapeDashboardHtml(peer.host)}</td><td data-label="${t(context, "column.state")}">${statusPill(peerStateLabel(context, peer.state), toneForPeer(peer.state))}</td><td data-label="${t(context, "column.compatibility")}">${statusPill(compatibilityLabel(context, peer.compatibility), toneForCompatibility(peer.compatibility))}</td><td data-label="${t(context, "column.validation")}">${statusPill(t(context, peer.validated ? "status.validated" : "status.validationRejected"), peer.validated ? "good" : "warning")}</td><td data-label="${t(context, "column.selection")}">${peer.selectable ? statusPill(t(context, peer.selected ? "status.selected" : "status.available"), peer.selected ? "good" : "quiet") : `${statusPill(t(context, "status.notSelectable"), "warning")}${peer.selectionGuidance === undefined ? "" : `<span class="cell-note">${peerSelectionGuidance(context, peer.selectionGuidance)}</span>`}`}</td><td data-label="${t(context, "column.observed")}">${renderTimestampAtSnapshot(context, peer.lastSeenAt)}</td><td data-label="${t(context, "column.issue")}">${peer.safeErrorCode === undefined ? "—" : `<code>${peer.safeErrorCode}</code>`}</td></tr>`).join("");
542
+ const routeRows = context.model.routes.length === 0
543
+ ? `<tr class="empty-row"><td colspan="8">${t(context, "sessions.routes.empty")}</td></tr>`
544
+ : context.model.routes.map((route) => `<tr><th scope="row" data-label="${t(context, "column.alias")}">${escapeDashboardHtml(route.alias)}</th><td data-label="${t(context, "column.provider")}">${providerLabel(context, route.provider)}</td><td data-label="${t(context, "column.host")}">${escapeDashboardHtml(route.host)}</td><td data-label="${t(context, "column.state")}">${statusPill(routeStateLabel(context, route.enabled ? route.state : "disabled"), toneForRoute(route.enabled ? route.state : "disabled"))}</td><td data-label="${t(context, "column.compatibility")}">${statusPill(compatibilityLabel(context, route.compatibility), toneForCompatibility(route.compatibility))}</td><td data-label="${t(context, "column.queue")}">${formatInteger(route.queueDepth)}${route.queueAgeMs === undefined ? "" : `<span class="cell-note">${escapeDashboardHtml(formatQueueAge(route.queueAgeMs, context.locale) ?? "")}</span>`}</td><td data-label="${t(context, "column.observed")}">${renderTimestampAtSnapshot(context, route.lastSeenAt)}</td><td data-label="${t(context, "column.issue")}">${route.safeErrorCode === undefined ? "—" : `<code>${route.safeErrorCode}</code>`}</td></tr>`).join("");
545
+ return `<section class="section" aria-labelledby="sessions-title">
546
+ <div class="section-heading"><div><p class="eyebrow">${t(context, "sessions.eyebrow")}</p><h2 id="sessions-title">${t(context, "sessions.title")}</h2></div><p>${t(context, "sessions.note")}</p></div>
547
+ <div class="register-grid">
548
+ <section aria-labelledby="peers-title"><h3 id="peers-title">${t(context, "sessions.peers.title")}</h3><div class="table-wrap" tabindex="0" role="region" aria-labelledby="peers-title"><table class="responsive-table"><caption>${t(context, "sessions.peers.caption")}</caption><thead><tr><th>${t(context, "column.alias")}</th><th>${t(context, "column.host")}</th><th>${t(context, "column.state")}</th><th>${t(context, "column.compatibility")}</th><th>${t(context, "column.validation")}</th><th>${t(context, "column.selection")}</th><th>${t(context, "column.observed")}</th><th>${t(context, "column.issue")}</th></tr></thead><tbody>${peerRows}</tbody></table></div></section>
549
+ <section aria-labelledby="routes-title"><h3 id="routes-title">${t(context, "sessions.routes.title")}</h3><div class="table-wrap" tabindex="0" role="region" aria-labelledby="routes-title"><table class="responsive-table"><caption>${t(context, "sessions.routes.caption")}</caption><thead><tr><th>${t(context, "column.alias")}</th><th>${t(context, "column.provider")}</th><th>${t(context, "column.host")}</th><th>${t(context, "column.state")}</th><th>${t(context, "column.compatibility")}</th><th>${t(context, "column.queue")}</th><th>${t(context, "column.observed")}</th><th>${t(context, "column.issue")}</th></tr></thead><tbody>${routeRows}</tbody></table></div></section>
550
+ </div>
551
+ </section>`;
552
+ }
553
+ function renderDiagnostics(context) {
554
+ const compatibilityRows = context.model.compatibilityChecks.length === 0
555
+ ? `<tr class="empty-row"><td colspan="8">${t(context, "diagnostics.compatibilityChecks.empty")}</td></tr>`
556
+ : context.model.compatibilityChecks.map((check) => {
557
+ const tone = check.tier === "certified"
558
+ ? "good"
559
+ : check.tier === "schema_attested"
560
+ ? "info"
561
+ : "danger";
562
+ const certification = check.certificationOutcome === undefined
563
+ ? "—"
564
+ : `<code>${check.certificationOutcome} / ${check.certificationDepth}</code>`;
565
+ const issue = check.certificationSafeErrorCode ?? check.safeErrorCode;
566
+ return `<tr><th scope="row" data-label="${t(context, "column.provider")}">${providerLabel(context, check.surface)}</th><td data-label="${t(context, "diagnostics.version")}"><code>${escapeDashboardHtml(check.version)}</code></td><td data-label="${t(context, "diagnostics.tier")}">${statusPill(t(context, `compatibilityTier.${check.tier}`), tone)}</td><td data-label="${t(context, "diagnostics.checkedAt")}">${renderTimestampAtSnapshot(context, check.checkedAt)}</td><td data-label="${t(context, "diagnostics.certification")}">${certification}</td><td data-label="${t(context, "diagnostics.certifiedAt")}">${check.certifiedAt === undefined ? "—" : renderTimestampAtSnapshot(context, check.certifiedAt)}</td><td data-label="${t(context, "diagnostics.failedProbe")}">${check.failedProbe === undefined ? "—" : `<code>${escapeDashboardHtml(check.failedProbe)}</code>`}</td><td data-label="${t(context, "column.issue")}">${issue === undefined ? "—" : `<code>${issue}</code>`}</td></tr>`;
567
+ }).join("");
568
+ const connectorRows = context.model.connectors.length === 0
569
+ ? `<tr class="empty-row"><td colspan="7">${t(context, "diagnostics.connectors.empty")}</td></tr>`
570
+ : context.model.connectors.map((connector) => `<tr><th scope="row" data-label="${t(context, "column.provider")}">${providerLabel(context, connector.provider)}</th><td data-label="${t(context, "column.host")}">${escapeDashboardHtml(connector.host)}</td><td data-label="${t(context, "diagnostics.health")}">${statusPill(healthLabel(context, connector.health), toneForHealth(connector.health))}</td><td data-label="${t(context, "column.compatibility")}">${statusPill(compatibilityLabel(context, connector.compatibility), toneForCompatibility(connector.compatibility))}</td><td data-label="${t(context, "diagnostics.protocol")}"><code>${escapeDashboardHtml([connector.protocol, connector.protocolVersion].filter(Boolean).join(" ") || "—")}</code></td><td data-label="${t(context, "column.observed")}">${renderTimestampAtSnapshot(context, connector.lastSeenAt)}</td><td data-label="${t(context, "column.issue")}">${connector.safeErrorCode === undefined ? "—" : `<code>${connector.safeErrorCode}</code>`}</td></tr>`).join("");
571
+ const omissions = context.model.omissions;
572
+ const omissionEntries = [
573
+ [omissions.connectors, "diagnostics.omissions.connectors"],
574
+ [omissions.availablePeers, "diagnostics.omissions.peers"],
575
+ [omissions.routes, "diagnostics.omissions.routes"],
576
+ [omissions.pairs, "diagnostics.omissions.pairs"],
577
+ [omissions.progressWatches, "diagnostics.omissions.progressWatches"],
578
+ [
579
+ omissions.upstreamProgressWatchEvents,
580
+ "diagnostics.omissions.upstreamProgressWatchEvents",
581
+ ],
582
+ [
583
+ omissions.progressWatchEvents,
584
+ "diagnostics.omissions.progressWatchEvents",
585
+ ],
586
+ [
587
+ omissions.upstreamMessageEvents,
588
+ "diagnostics.omissions.upstreamMessageEvents",
589
+ ],
590
+ [omissions.messageGroups, "diagnostics.omissions.messageGroups"],
591
+ [omissions.messageEvents, "diagnostics.omissions.messageEvents"],
592
+ [omissions.upstreamAlerts, "diagnostics.omissions.upstreamAlerts"],
593
+ [omissions.attentionItems, "diagnostics.omissions.attentionItems"],
594
+ [
595
+ omissions.upstreamActivityEvents,
596
+ "diagnostics.omissions.upstreamActivityEvents",
597
+ ],
598
+ [omissions.activityEvents, "diagnostics.omissions.activityEvents"],
599
+ ];
600
+ const visibleOmissions = omissionEntries.filter(([count]) => count > 0);
601
+ const omissionText = visibleOmissions.length === 0
602
+ ? t(context, "diagnostics.omissions.none")
603
+ : visibleOmissions
604
+ .map(([count, key]) => t(context, key, { count: formatInteger(count) }))
605
+ .join(" · ");
606
+ const accounting = context.model.accounting;
607
+ const deadline = context.model.deadlinePressure;
608
+ const deadlineBucketKeys = {
609
+ under_1m: "app.diag.deadline.bucket.under1m",
610
+ "1m_to_5m": "app.diag.deadline.bucket.1to5m",
611
+ "5m_to_15m": "app.diag.deadline.bucket.5to15m",
612
+ "15m_to_60m": "app.diag.deadline.bucket.15to60m",
613
+ over_60m: "app.diag.deadline.bucket.over60m",
614
+ };
615
+ const deadlineSection = deadline === undefined
616
+ ? ""
617
+ : `<section aria-labelledby="deadline-pressure-title"><h3 id="deadline-pressure-title">${t(context, "app.diag.deadline.title")}</h3><p>${t(context, "app.diag.deadline.retained", { terminal: formatInteger(deadline.terminalEvents), expired: formatInteger(deadline.expiredEvents), deadline: formatDuration(deadline.configuredDeadlineMs) })}</p><dl class="accounting">${deadline.buckets.map((bucket) => `<div><dt>${t(context, deadlineBucketKeys[bucket.bucket])}</dt><dd>${formatInteger(bucket.settled)} · ${formatInteger(bucket.expired)} ${t(context, "delivery.expired")}</dd></div>`).join("")}</dl></section>`;
618
+ return `<details class="section diagnostics" aria-labelledby="diagnostics-title" aria-describedby="diagnostics-note">
619
+ <summary><h2 id="diagnostics-title"><span class="disclosure-icon" aria-hidden="true"><span class="disclosure-icon__closed">+</span><span class="disclosure-icon__open">−</span></span>${t(context, "diagnostics.title")}</h2></summary>
620
+ <div class="diagnostics__body">
621
+ <p id="diagnostics-note" class="diagnostics__note">${t(context, "diagnostics.note")}</p>
622
+ <section aria-labelledby="compatibility-checks-title"><h3 id="compatibility-checks-title">${t(context, "diagnostics.compatibilityChecks")}</h3><div class="table-wrap" tabindex="0" role="region" aria-labelledby="compatibility-checks-title"><table class="responsive-table"><caption>${t(context, "diagnostics.compatibilityChecks.caption")}</caption><thead><tr><th>${t(context, "column.provider")}</th><th>${t(context, "diagnostics.version")}</th><th>${t(context, "diagnostics.tier")}</th><th>${t(context, "diagnostics.checkedAt")}</th><th>${t(context, "diagnostics.certification")}</th><th>${t(context, "diagnostics.certifiedAt")}</th><th>${t(context, "diagnostics.failedProbe")}</th><th>${t(context, "column.issue")}</th></tr></thead><tbody>${compatibilityRows}</tbody></table></div></section>
623
+ <section aria-labelledby="connectors-title"><h3 id="connectors-title">${t(context, "diagnostics.connectors")}</h3><div class="table-wrap" tabindex="0" role="region" aria-labelledby="connectors-title"><table class="responsive-table"><caption>${t(context, "diagnostics.connectors.caption")}</caption><thead><tr><th>${t(context, "column.provider")}</th><th>${t(context, "column.host")}</th><th>${t(context, "diagnostics.health")}</th><th>${t(context, "column.compatibility")}</th><th>${t(context, "diagnostics.protocol")}</th><th>${t(context, "column.observed")}</th><th>${t(context, "column.issue")}</th></tr></thead><tbody>${connectorRows}</tbody></table></div></section>
624
+ ${deadlineSection}
625
+ <section aria-labelledby="accounting-title"><h3 id="accounting-title">${t(context, "diagnostics.accounting")}</h3><dl class="accounting"><div><dt>${t(context, "diagnostics.accepted")}</dt><dd>${formatInteger(accounting.accepted)}</dd></div><div><dt>${t(context, "diagnostics.duplicates")}</dt><dd>${formatInteger(accounting.duplicates)}</dd></div><div><dt>${t(context, "diagnostics.delivered")}</dt><dd>${formatInteger(accounting.delivered)}</dd></div><div><dt>${t(context, "diagnostics.unconfirmed")}</dt><dd>${formatInteger(accounting.unconfirmed)}</dd></div><div><dt>${t(context, "diagnostics.ambiguous")}</dt><dd>${formatInteger(accounting.ambiguous)}</dd></div><div><dt>${t(context, "diagnostics.failed")}</dt><dd>${formatInteger(accounting.failed)}</dd></div><div><dt>${t(context, "diagnostics.expired")}</dt><dd>${formatInteger(accounting.expired)}</dd></div><div><dt>${t(context, "diagnostics.cancelled")}</dt><dd>${formatInteger(accounting.cancelled)}</dd></div><div><dt>${t(context, "diagnostics.abandoned")}</dt><dd>${formatInteger(accounting.abandoned)}</dd></div><div><dt>${t(context, "diagnostics.rejected")}</dt><dd>${formatInteger(accounting.rejected)}</dd></div><div><dt>${t(context, "diagnostics.bytesAccepted")}</dt><dd>${formatBytes(accounting.bytesAccepted)}</dd></div><div><dt>${t(context, "diagnostics.queuedBytes")}</dt><dd>${formatBytes(accounting.queuedBytes)}</dd></div></dl></section>
626
+ <p class="omissions"><strong>${t(context, "diagnostics.omissions")}:</strong> ${omissionText}</p>
627
+ </div>
628
+ </details>`;
629
+ }
630
+ function languageNavigation(context) {
631
+ const en = context.locale === "en"
632
+ ? `<span lang="en" aria-current="page">${t(context, "language.en")}</span>`
633
+ : `<a lang="en" hreflang="en" href="./${DASHBOARD_FILE_NAME}">${t(context, "language.en")}</a>`;
634
+ const zh = context.locale === "zh-CN"
635
+ ? `<span lang="zh-CN" aria-current="page">${t(context, "language.zhCn")}</span>`
636
+ : `<a lang="zh-CN" hreflang="zh-CN" href="./${DASHBOARD_ZH_CN_FILE_NAME}">${t(context, "language.zhCn")}</a>`;
637
+ return `<nav class="language" aria-label="${t(context, "language.label")}">${en}<span aria-hidden="true">/</span>${zh}</nav>`;
638
+ }
639
+ const DASHBOARD_STYLES = `
640
+ :root {
641
+ color-scheme: light;
642
+ --paper: #f5f0e6;
643
+ --paper-deep: #ebe3d4;
644
+ --sheet: #fffdf7;
645
+ --ink: #28251f;
646
+ --muted: #5f594f;
647
+ --hairline: #cbc2b2;
648
+ --hairline-strong: #9e9587;
649
+ --seal: #b63a2c;
650
+ --seal-soft: #f7e5df;
651
+ --good: #256348;
652
+ --good-soft: #e4f0e8;
653
+ --info: #315f78;
654
+ --info-soft: #e5eef1;
655
+ --warning: #8a5a18;
656
+ --warning-soft: #f5ebd7;
657
+ --danger: #a13232;
658
+ --danger-soft: #f6e3df;
659
+ --quiet: #5c574f;
660
+ --quiet-soft: #ece7de;
661
+ }
662
+ * { box-sizing: border-box; }
663
+ html { background: var(--paper); scroll-behavior: smooth; }
664
+ body { margin: 0; color: var(--ink); background: linear-gradient(90deg, transparent 0 49.92%, rgba(78,67,52,.035) 49.92% 50.08%, transparent 50.08%), var(--paper); font: 15px/1.55 ui-serif, Georgia, "Times New Roman", serif; }
665
+ a { color: inherit; text-underline-offset: .2em; }
666
+ a:focus-visible, summary:focus-visible, .table-wrap:focus-visible { outline: 3px solid var(--seal); outline-offset: 3px; }
667
+ .skip-link { position: fixed; z-index: 10; left: 1rem; top: -6rem; padding: .65rem .9rem; color: var(--sheet); background: var(--ink); }
668
+ .skip-link:focus { top: 1rem; }
669
+ main { width: min(100% - 2rem, 1240px); margin: 0 auto; padding: 2rem 0 3rem; }
670
+ .masthead { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 2rem; align-items: start; padding: 1.35rem 0 2.4rem; border-top: 1px solid var(--ink); border-bottom: 1px solid var(--ink); }
671
+ .brand-row { display: flex; gap: 1.15rem; align-items: center; }
672
+ .brand-mark, .seal-mark { position: relative; display: grid; place-items: center; flex: 0 0 auto; border: 1px solid var(--ink); border-radius: 50%; }
673
+ .brand-mark { width: 4.4rem; height: 4.4rem; }
674
+ .brand-mark::before, .brand-mark::after, .seal-mark::before, .seal-mark::after { content: ""; position: absolute; top: 23%; bottom: 23%; width: 23%; border-radius: 50%; }
675
+ .brand-mark::before, .seal-mark::before { left: 20%; border-right: 1px solid var(--ink); }
676
+ .brand-mark::after, .seal-mark::after { right: 20%; border-left: 1px solid var(--ink); }
677
+ .brand-mark span, .seal-mark span { position: relative; z-index: 1; width: 24%; aspect-ratio: 1; border-radius: 50%; background: var(--seal); }
678
+ .eyebrow { margin: 0 0 .28rem; color: var(--seal); font: 700 .72rem/1.2 ui-sans-serif, system-ui, sans-serif; letter-spacing: .14em; text-transform: uppercase; }
679
+ h1, h2, h3, p { margin-top: 0; }
680
+ h1 { margin-bottom: .25rem; font-size: clamp(2.5rem, 7vw, 5.7rem); font-weight: 500; letter-spacing: -.055em; line-height: .9; }
681
+ h2 { margin-bottom: 0; font-size: clamp(1.55rem, 3vw, 2.25rem); font-weight: 500; letter-spacing: -.025em; line-height: 1.05; }
682
+ h3 { margin-bottom: .28rem; font-size: 1.03rem; }
683
+ .subtitle { max-width: 44rem; margin: .65rem 0 0; color: var(--muted); font-size: 1.03rem; }
684
+ .masthead__meta { display: grid; justify-items: end; gap: .8rem; max-width: 23rem; text-align: right; }
685
+ .masthead__meta p { margin: 0; color: var(--muted); font-size: .82rem; }
686
+ .language { display: inline-flex; gap: .5rem; align-items: center; font: 700 .76rem/1 ui-sans-serif, system-ui, sans-serif; }
687
+ .language [aria-current="page"] { color: var(--seal); }
688
+ .status { display: inline-flex; align-items: center; gap: .42rem; padding: .23rem .52rem; border: 1px solid currentColor; border-radius: 999px; font: 720 .72rem/1.2 ui-sans-serif, system-ui, sans-serif; white-space: nowrap; }
689
+ .status__dot { width: .42rem; height: .42rem; border-radius: 50%; background: currentColor; }
690
+ .status--good { color: var(--good); background: var(--good-soft); }
691
+ .status--info { color: var(--info); background: var(--info-soft); }
692
+ .status--warning { color: var(--warning); background: var(--warning-soft); }
693
+ .status--danger { color: var(--danger); background: var(--danger-soft); }
694
+ .status--quiet { color: var(--quiet); background: var(--quiet-soft); }
695
+ .section { padding: 2.4rem 0; border-bottom: 1px solid var(--hairline-strong); }
696
+ .section-heading { display: grid; grid-template-columns: minmax(0, 1fr) minmax(14rem, .72fr); gap: 2rem; align-items: end; margin-bottom: 1.25rem; }
697
+ .section-heading > p { margin: 0; color: var(--muted); font-size: .88rem; }
698
+ .exchange-board { display: grid; grid-template-columns: minmax(0, 1fr) minmax(11rem, .38fr) minmax(0, 1fr); align-items: stretch; border: 1px solid var(--ink); background: var(--sheet); }
699
+ .inbound-policy { display: flex; align-items: center; gap: .75rem; margin: 0 0 1rem; padding: .65rem .8rem; border: 1px solid var(--hairline); background: var(--paper-deep); }
700
+ .inbound-policy p { margin: 0; }
701
+ .inbound-policy[data-inbound-mode="open"] { border-color: var(--warning); background: var(--warning-soft); }
702
+ .exchange-party { min-width: 0; padding: 1.3rem; }
703
+ .exchange-party--claude { border-right: 1px solid var(--hairline); }
704
+ .exchange-party--codex { border-left: 1px solid var(--hairline); }
705
+ .party-heading { display: flex; justify-content: space-between; gap: 1rem; align-items: flex-start; }
706
+ .party-heading p, .party-count { color: var(--muted); font-size: .82rem; }
707
+ .party-count { margin: 1.8rem 0 .3rem; font-family: ui-sans-serif, system-ui, sans-serif; }
708
+ .party-alias { margin: 0; font: 650 .85rem/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; overflow-wrap: anywhere; }
709
+ .next-action { margin: 1.15rem 0 0; padding-top: .85rem; border-top: 1px solid var(--hairline); color: var(--muted); font-size: .81rem; }
710
+ .next-action strong { color: var(--ink); }
711
+ .pouch { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; min-height: 15rem; padding: 1rem; text-align: center; overflow: hidden; }
712
+ .pouch__line { position: absolute; left: -10%; right: -10%; top: 50%; height: 1px; background: var(--hairline-strong); }
713
+ .seal-mark { width: 3.7rem; height: 3.7rem; margin-bottom: .75rem; background: var(--paper); }
714
+ .pouch p, .pouch strong, .pouch small { position: relative; z-index: 1; background: var(--sheet); }
715
+ .pouch__title { margin: 0; padding: 0 .35rem; color: var(--muted); font-size: .74rem; text-transform: uppercase; letter-spacing: .1em; }
716
+ .pouch strong { padding: .1rem .35rem; font: 720 .88rem/1.4 ui-sans-serif, system-ui, sans-serif; }
717
+ .pouch small { padding: 0 .35rem; color: var(--muted); }
718
+ .direction-key { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; padding-top: .55rem; color: var(--muted); font-size: .74rem; }
719
+ .direction-key span:last-child { text-align: right; }
720
+ .pair-graph { margin-top: 1.2rem; padding: 1rem; border: 1px solid var(--hairline); background: var(--sheet); }
721
+ .pair-graph__heading { display: flex; flex-wrap: wrap; justify-content: space-between; gap: .7rem 1rem; align-items: baseline; }
722
+ .pair-graph__heading h3, .pair-graph p { margin-bottom: .55rem; }
723
+ .pair-graph__heading strong, .pair-graph p { color: var(--muted); font: .78rem/1.45 ui-sans-serif, system-ui, sans-serif; }
724
+ .pair-list { display: grid; gap: .45rem; margin: .8rem 0 0; padding: 0; list-style: none; }
725
+ .pair-list li { display: flex; justify-content: space-between; gap: .8rem; align-items: center; padding-top: .45rem; border-top: 1px solid var(--hairline); overflow-wrap: anywhere; }
726
+ .attention { border-bottom-color: var(--danger); }
727
+ .attention-list { display: grid; gap: 0; margin: 0; padding: 0; list-style: none; border-top: 1px solid var(--hairline); }
728
+ .attention-projection-note { margin: 0; padding: 1rem; border-left: 3px solid var(--warning); background: var(--warning-soft); }
729
+ .attention-item { padding: 1.1rem 0 1.1rem 1rem; border-bottom: 1px solid var(--hairline); border-left: 3px solid currentColor; }
730
+ .attention-item--danger { color: var(--danger); }
731
+ .attention-item--warning { color: var(--warning); }
732
+ .attention-item--info { color: var(--info); }
733
+ .attention-item h3, .attention-item p { color: var(--ink); }
734
+ .attention-item__meta { display: flex; flex-wrap: wrap; gap: .55rem; align-items: center; margin-bottom: .7rem; color: var(--muted); font-size: .75rem; }
735
+ .scope { margin-bottom: 0; color: var(--muted) !important; font-size: .78rem; }
736
+ .ledger-strip, .accounting { display: grid; grid-template-columns: repeat(3, 1fr); margin: 0; border-top: 1px solid var(--ink); border-bottom: 1px solid var(--ink); }
737
+ .ledger-strip > div, .accounting > div { padding: .9rem 1rem; border-right: 1px solid var(--hairline); }
738
+ .ledger-strip > div:last-child, .accounting > div:nth-child(3n) { border-right: 0; }
739
+ dt { color: var(--muted); font: 700 .7rem/1.2 ui-sans-serif, system-ui, sans-serif; letter-spacing: .07em; text-transform: uppercase; }
740
+ dd { margin: .25rem 0 0; font-size: 1.45rem; font-variant-numeric: tabular-nums; }
741
+ .table-wrap { max-width: 100%; overflow: auto; border: 1px solid var(--hairline); background: var(--sheet); }
742
+ table { width: 100%; border-collapse: collapse; font: .79rem/1.4 ui-sans-serif, system-ui, sans-serif; font-variant-numeric: tabular-nums; }
743
+ caption { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; border: 0; }
744
+ th, td { padding: .68rem .75rem; border-bottom: 1px solid var(--hairline); text-align: left; vertical-align: top; white-space: nowrap; }
745
+ thead th { color: var(--muted); background: var(--paper-deep); font-size: .66rem; letter-spacing: .06em; text-transform: uppercase; }
746
+ tbody tr:last-child > * { border-bottom: 0; }
747
+ tbody th { font-weight: 720; }
748
+ code { font: .76rem/1.4 ui-monospace, SFMono-Regular, Menlo, monospace; }
749
+ .route-cell { white-space: normal; min-width: 16rem; }
750
+ .route-cell span, .cell-note, .cell-code { display: block; margin-top: .2rem; color: var(--muted); font-size: .72rem; white-space: normal; }
751
+ .numeric { text-align: right; }
752
+ .history summary, .diagnostics summary { cursor: pointer; }
753
+ .history ol { min-width: 22rem; margin: .55rem 0 0; padding-left: 1.1rem; }
754
+ .history li { margin: .3rem 0; }
755
+ .history .status { margin: 0 .4rem; }
756
+ .empty-row td { padding: 1.2rem; color: var(--muted); text-align: center; }
757
+ .register-grid { display: grid; gap: 1.4rem; }
758
+ .register-grid > section { min-width: 0; }
759
+ .register-grid h3, .diagnostics__body h3 { margin: 0 0 .55rem; }
760
+ .diagnostics { padding-bottom: 0; }
761
+ .diagnostics > summary { list-style: none; padding-bottom: 2rem; }
762
+ .diagnostics > summary::-webkit-details-marker { display: none; }
763
+ .diagnostics > summary h2 { display: flex; align-items: center; gap: .4rem; margin: 0; font-size: 1.05rem; }
764
+ .disclosure-icon { display: inline-grid; width: 1.5rem; color: var(--seal); }
765
+ .disclosure-icon > span { grid-area: 1 / 1; }
766
+ .disclosure-icon__open { visibility: hidden; }
767
+ .diagnostics[open] .disclosure-icon__closed { visibility: hidden; }
768
+ .diagnostics[open] .disclosure-icon__open { visibility: visible; }
769
+ .diagnostics__body { display: grid; gap: 1.5rem; padding-bottom: 2.2rem; }
770
+ .diagnostics__note { margin: 0; color: var(--muted); font-size: .8rem; }
771
+ .accounting { grid-template-columns: repeat(6, 1fr); }
772
+ .accounting > div { border-bottom: 0; }
773
+ .accounting dd { font-size: 1.1rem; }
774
+ .omissions { margin: 0; color: var(--muted); font-size: .8rem; }
775
+ .quiet { color: var(--muted); }
776
+ footer { padding-top: 1rem; color: var(--muted); font-size: .74rem; text-align: center; }
777
+ @media (max-width: 860px) {
778
+ .masthead, .section-heading { grid-template-columns: 1fr; gap: 1rem; }
779
+ .masthead__meta { justify-items: start; max-width: none; text-align: left; }
780
+ .exchange-board { grid-template-columns: 1fr; }
781
+ .exchange-party--claude { border-right: 0; border-bottom: 1px solid var(--hairline); }
782
+ .exchange-party--codex { border-left: 0; border-top: 1px solid var(--hairline); }
783
+ .pouch { min-height: 10rem; }
784
+ .direction-key { display: none; }
785
+ .accounting { grid-template-columns: repeat(3, 1fr); }
786
+ .accounting > div:nth-child(3) { border-right: 0; }
787
+ }
788
+ @media (max-width: 560px) {
789
+ main { width: min(100% - 1rem, 1240px); padding-top: .5rem; }
790
+ .brand-row { align-items: flex-start; }
791
+ .brand-mark { width: 3.3rem; height: 3.3rem; }
792
+ .party-heading { flex-direction: column; }
793
+ .ledger-strip { grid-template-columns: 1fr; }
794
+ .ledger-strip > div { border-right: 0; border-bottom: 1px solid var(--hairline); }
795
+ .ledger-strip > div:last-child { border-bottom: 0; }
796
+ .responsive-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
797
+ .responsive-table, .responsive-table tbody, .responsive-table tr { display: block; width: 100%; }
798
+ .responsive-table tbody tr:not(.empty-row) { padding: .5rem .7rem; border-bottom: 1px solid var(--hairline); }
799
+ .responsive-table tbody th, .responsive-table tbody td { display: grid; grid-template-columns: minmax(6.7rem, .42fr) minmax(0, 1fr); gap: .55rem; padding: .3rem 0; border: 0; white-space: normal; text-align: left; }
800
+ .responsive-table tbody th::before, .responsive-table tbody td::before { content: attr(data-label); color: var(--muted); font-size: .65rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
801
+ .responsive-table .empty-row td { display: block; }
802
+ .route-cell { min-width: 0; }
803
+ .history ol { min-width: 0; }
804
+ .accounting { grid-template-columns: repeat(2, 1fr); }
805
+ .accounting > div, .accounting > div:nth-child(3n) { border-right: 1px solid var(--hairline); border-bottom: 1px solid var(--hairline); }
806
+ .accounting > div:nth-child(2n) { border-right: 0; }
807
+ }
808
+ @media (prefers-color-scheme: dark) {
809
+ :root { color-scheme: dark; --paper: #201e1a; --paper-deep: #2b2823; --sheet: #25221d; --ink: #eee7da; --muted: #b5ac9e; --hairline: #514a40; --hairline-strong: #776f62; --seal: #e06b55; --seal-soft: #452822; --good: #8bc5a7; --good-soft: #20362b; --info: #96bdcf; --info-soft: #24343b; --warning: #e2b26b; --warning-soft: #3d3020; --danger: #ef958c; --danger-soft: #412725; --quiet: #b5aca0; --quiet-soft: #34302a; }
810
+ }
811
+ @media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }
812
+ @media (forced-colors: active) {
813
+ .exchange-board, .table-wrap, .ledger-strip, .accounting, .status, .brand-mark, .seal-mark, .attention-item { border-color: CanvasText; }
814
+ .status__dot, .brand-mark span, .seal-mark span { background: CanvasText; }
815
+ a:focus-visible, summary:focus-visible, .table-wrap:focus-visible { outline-color: Highlight; }
816
+ }
817
+ @media print { body { background: white; } }
818
+ `;
819
+ export function renderGatewayDashboard(snapshot, options = {}) {
820
+ const locale = normalizeDashboardLocale(options.locale);
821
+ const context = {
822
+ locale,
823
+ copy: getDashboardCopy(locale),
824
+ model: buildDashboardViewModel(snapshot),
825
+ };
826
+ const languageFile = locale === "en" ? DASHBOARD_FILE_NAME : DASHBOARD_ZH_CN_FILE_NAME;
827
+ const generatedAt = context.model.generatedAt === undefined
828
+ ? t(context, "time.unavailable")
829
+ : `<time datetime="${context.model.generatedAt}">${escapeDashboardHtml(context.model.generatedAt.slice(0, 19).replace("T", " "))} UTC</time>`;
830
+ const html = `<!doctype html>
831
+ <html lang="${locale}">
832
+ <head>
833
+ <meta charset="utf-8">
834
+ <meta name="viewport" content="width=device-width, initial-scale=1">
835
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; img-src 'none'; font-src 'none'; media-src 'none'; connect-src 'none'; object-src 'none'; frame-src 'none'; worker-src 'none'; manifest-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'">
836
+ <meta name="referrer" content="no-referrer">
837
+ <meta name="color-scheme" content="light dark">
838
+ <title>${t(context, "meta.title")}</title>
839
+ <style>${DASHBOARD_STYLES}</style>
840
+ </head>
841
+ <body data-dashboard-locale="${locale}" data-dashboard-file="${languageFile}">
842
+ <a class="skip-link" href="#main">${t(context, "skip")}</a>
843
+ <main id="main">
844
+ <header class="masthead">
845
+ <div class="brand-row"><div class="brand-mark" aria-hidden="true"><span></span></div><div><p class="eyebrow">${t(context, "brand.eyebrow")}</p><h1>${t(context, "brand.title")}</h1><p class="subtitle">${t(context, "brand.subtitle")}</p></div></div>
846
+ <div class="masthead__meta">${languageNavigation(context)}${statusPill(overallLabel(context), toneForOverall(context.model.overall))}<p><strong>${t(context, "snapshot.asOf", { time: "" })}</strong>${generatedAt}<br>${t(context, "snapshot.static")}</p></div>
847
+ </header>
848
+ ${renderExchange(context)}
849
+ ${renderAttention(context)}
850
+ ${renderTransit(context)}
851
+ ${renderProgressWatches(context)}
852
+ ${renderActivity(context)}
853
+ ${renderSessions(context)}
854
+ ${renderDiagnostics(context)}
855
+ <footer>${t(context, "footer")}</footer>
856
+ </main>
857
+ </body>
858
+ </html>
859
+ `;
860
+ if (Buffer.byteLength(html, "utf8") > DASHBOARD_MAX_HTML_BYTES) {
861
+ throw new Error("DASHBOARD_SNAPSHOT_TOO_LARGE");
862
+ }
863
+ return html;
864
+ }
865
+ export const renderDashboardHtml = renderGatewayDashboard;
866
+ async function validatePrivateStateDirectory(stateDirectory) {
867
+ const resolved = path.resolve(stateDirectory);
868
+ const info = await lstat(resolved);
869
+ if (info.isSymbolicLink() || !info.isDirectory()) {
870
+ throw new Error("DASHBOARD_STATE_DIRECTORY_UNSAFE");
871
+ }
872
+ if ((info.mode & 0o777) !== 0o700) {
873
+ throw new Error("DASHBOARD_STATE_DIRECTORY_UNSAFE");
874
+ }
875
+ if (typeof process.getuid === "function" && info.uid !== process.getuid()) {
876
+ throw new Error("DASHBOARD_STATE_DIRECTORY_UNSAFE");
877
+ }
878
+ if ((await realpath(resolved)) !== resolved) {
879
+ throw new Error("DASHBOARD_STATE_DIRECTORY_UNSAFE");
880
+ }
881
+ return resolved;
882
+ }
883
+ async function validateExistingDashboard(outputPath) {
884
+ try {
885
+ const info = await lstat(outputPath);
886
+ if (info.isSymbolicLink() || !info.isFile()) {
887
+ throw new Error("DASHBOARD_TARGET_UNSAFE");
888
+ }
889
+ if (typeof process.getuid === "function" && info.uid !== process.getuid()) {
890
+ throw new Error("DASHBOARD_TARGET_UNSAFE");
891
+ }
892
+ }
893
+ catch (error) {
894
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
895
+ return;
896
+ }
897
+ throw error;
898
+ }
899
+ }
900
+ export async function publishGatewayDashboard(stateDirectory, snapshot, _options = {}) {
901
+ const directory = await validatePrivateStateDirectory(stateDirectory);
902
+ const artifacts = [
903
+ {
904
+ outputPath: path.join(directory, DASHBOARD_FILE_NAME),
905
+ temporaryPath: path.join(directory, `.${DASHBOARD_FILE_NAME}.${process.pid}.${randomUUID()}.tmp`),
906
+ html: renderGatewayDashboard(snapshot, { locale: "en" }),
907
+ },
908
+ {
909
+ outputPath: path.join(directory, DASHBOARD_ZH_CN_FILE_NAME),
910
+ temporaryPath: path.join(directory, `.${DASHBOARD_ZH_CN_FILE_NAME}.${process.pid}.${randomUUID()}.tmp`),
911
+ html: renderGatewayDashboard(snapshot, { locale: "zh-CN" }),
912
+ },
913
+ ];
914
+ for (const artifact of artifacts) {
915
+ await validateExistingDashboard(artifact.outputPath);
916
+ }
917
+ try {
918
+ for (const artifact of artifacts) {
919
+ artifact.handle = await open(artifact.temporaryPath, "wx", 0o600);
920
+ await artifact.handle.writeFile(artifact.html, "utf8");
921
+ await artifact.handle.chmod(0o600);
922
+ await artifact.handle.sync();
923
+ await artifact.handle.close();
924
+ artifact.handle = undefined;
925
+ }
926
+ for (const artifact of artifacts) {
927
+ await rename(artifact.temporaryPath, artifact.outputPath);
928
+ }
929
+ return artifacts[0].outputPath;
930
+ }
931
+ finally {
932
+ for (const artifact of artifacts) {
933
+ if (artifact.handle !== undefined) {
934
+ await artifact.handle.close().catch(() => undefined);
935
+ }
936
+ await unlink(artifact.temporaryPath).catch(() => undefined);
937
+ }
938
+ }
939
+ }
940
+ export const writeDashboardSnapshot = publishGatewayDashboard;
941
+ //# sourceMappingURL=dashboard.js.map