agent-embassy 1.4.1 → 1.6.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.
- package/CHANGELOG.md +40 -1
- package/CONTRIBUTING.md +69 -6
- package/README.md +8 -5
- package/README.zh-CN.md +6 -4
- package/SECURITY.md +152 -27
- package/dist/src/gateway/claude-peer.d.ts +7 -4
- package/dist/src/gateway/claude-peer.js +80 -36
- package/dist/src/gateway/claude-peer.js.map +1 -1
- package/dist/src/gateway/claude-runtime.d.ts +4 -0
- package/dist/src/gateway/claude-runtime.js +50 -39
- package/dist/src/gateway/claude-runtime.js.map +1 -1
- package/dist/src/gateway/cli-copy.d.ts +1 -1
- package/dist/src/gateway/cli-copy.en.d.ts +1 -1
- package/dist/src/gateway/cli-copy.en.js +1 -1
- package/dist/src/gateway/cli-copy.en.js.map +1 -1
- package/dist/src/gateway/cli-copy.js +1 -1
- package/dist/src/gateway/cli-copy.js.map +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.d.ts +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.js +1 -1
- package/dist/src/gateway/cli-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/cli.d.ts +1 -1
- package/dist/src/gateway/cli.js +6 -7
- package/dist/src/gateway/cli.js.map +1 -1
- package/dist/src/gateway/codex-app-server.d.ts +44 -2
- package/dist/src/gateway/codex-app-server.js +536 -2
- package/dist/src/gateway/codex-app-server.js.map +1 -1
- package/dist/src/gateway/codex-local-transport.d.ts +10 -6
- package/dist/src/gateway/codex-local-transport.js +101 -56
- package/dist/src/gateway/codex-local-transport.js.map +1 -1
- package/dist/src/gateway/compatibility.d.ts +42 -3
- package/dist/src/gateway/compatibility.js +190 -37
- package/dist/src/gateway/compatibility.js.map +1 -1
- package/dist/src/gateway/control.js +28 -30
- package/dist/src/gateway/control.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.d.ts +1 -1
- package/dist/src/gateway/dashboard-copy.en.d.ts +42 -17
- package/dist/src/gateway/dashboard-copy.en.js +56 -31
- package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.js +42 -17
- package/dist/src/gateway/dashboard-copy.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +42 -17
- package/dist/src/gateway/dashboard-copy.zh-CN.js +56 -31
- package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/dashboard-model.d.ts +199 -11
- package/dist/src/gateway/dashboard-model.js +478 -75
- package/dist/src/gateway/dashboard-model.js.map +1 -1
- package/dist/src/gateway/dashboard.d.ts +3 -0
- package/dist/src/gateway/dashboard.js +62 -137
- package/dist/src/gateway/dashboard.js.map +1 -1
- package/dist/src/gateway/deepseek-detect.d.ts +13 -0
- package/dist/src/gateway/deepseek-detect.js +125 -0
- package/dist/src/gateway/deepseek-detect.js.map +1 -0
- package/dist/src/gateway/live-dashboard-app/app.js +158 -256
- package/dist/src/gateway/live-dashboard-assets.js +2 -1
- package/dist/src/gateway/live-dashboard-assets.js.map +1 -1
- package/dist/src/gateway/live-dashboard-stream.d.ts +2 -2
- package/dist/src/gateway/live-dashboard-stream.js +2 -2
- package/dist/src/gateway/live-dashboard-stream.js.map +1 -1
- package/dist/src/gateway/progress-watch-machine.d.ts +50 -64
- package/dist/src/gateway/progress-watch-machine.js +42 -133
- package/dist/src/gateway/progress-watch-machine.js.map +1 -1
- package/dist/src/gateway/provenance-envelope.d.ts +2 -0
- package/dist/src/gateway/provenance-envelope.js +13 -3
- package/dist/src/gateway/provenance-envelope.js.map +1 -1
- package/dist/src/gateway/providers.d.ts +80 -16
- package/dist/src/gateway/providers.js +782 -115
- package/dist/src/gateway/providers.js.map +1 -1
- package/dist/src/gateway/server.d.ts +10 -5
- package/dist/src/gateway/server.js +100 -13
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service.d.ts +46 -3
- package/dist/src/gateway/service.js +363 -101
- package/dist/src/gateway/service.js.map +1 -1
- package/dist/src/gateway/store.d.ts +23 -28
- package/dist/src/gateway/store.js +479 -467
- package/dist/src/gateway/store.js.map +1 -1
- package/dist/src/gateway/types.d.ts +40 -12
- package/dist/src/gateway/types.js +145 -4
- package/dist/src/gateway/types.js.map +1 -1
- package/docs/CONFIGURATION.md +8 -8
- package/docs/CONFIGURATION.zh-CN.md +8 -8
- package/docs/DASHBOARD.md +35 -6
- package/docs/DASHBOARD.zh-CN.md +1 -1
- package/docs/GATEWAY-ARCHITECTURE.md +173 -61
- package/package.json +1 -1
- package/skills/embassy-peer/SKILL.md +2 -2
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { arePublicAvailablePeerSnapshots } from "./types.js";
|
|
2
|
-
import {
|
|
1
|
+
import { arePublicAvailablePeerSnapshots, isPublicCompatibilityCheckSnapshot, isPublicRegistryObservationSnapshot, } from "./types.js";
|
|
2
|
+
import { compatibilitySurfaceDefinitions, } from "./compatibility.js";
|
|
3
3
|
export const DASHBOARD_MODEL_LIMITS = Object.freeze({
|
|
4
4
|
connectors: 16,
|
|
5
5
|
availablePeers: 64,
|
|
@@ -12,6 +12,237 @@ export const DASHBOARD_MODEL_LIMITS = Object.freeze({
|
|
|
12
12
|
activityEvents: 64,
|
|
13
13
|
alerts: 32,
|
|
14
14
|
});
|
|
15
|
+
const presentation = (values) => values;
|
|
16
|
+
const statePresentation = {
|
|
17
|
+
delivery: presentation({
|
|
18
|
+
queued: { tone: "info", chip: "progress" },
|
|
19
|
+
duplicate: { tone: "quiet", chip: "inert" },
|
|
20
|
+
dispatching: { tone: "info", chip: "progress" },
|
|
21
|
+
transport_written: { tone: "info", chip: "progress" },
|
|
22
|
+
held: { tone: "warning", chip: "progress" },
|
|
23
|
+
delivered: { tone: "good", chip: "positive" },
|
|
24
|
+
unconfirmed: { tone: "warning", chip: "indeterminate" },
|
|
25
|
+
failed: { tone: "danger", chip: "failure" },
|
|
26
|
+
ambiguous: { tone: "warning", chip: "indeterminate" },
|
|
27
|
+
expired: { tone: "danger", chip: "failure" },
|
|
28
|
+
cancelled: { tone: "danger", chip: "inert" },
|
|
29
|
+
abandoned: { tone: "danger", chip: "inert" },
|
|
30
|
+
rejected: { tone: "danger", chip: "warning" },
|
|
31
|
+
}),
|
|
32
|
+
route: presentation({
|
|
33
|
+
stale: { tone: "danger", chip: "failure" },
|
|
34
|
+
idle: { tone: "good", chip: "progress" },
|
|
35
|
+
busy: { tone: "info", chip: "active" },
|
|
36
|
+
awaiting_approval: { tone: "warning", chip: "warning" },
|
|
37
|
+
offline: { tone: "danger", chip: "failure" },
|
|
38
|
+
incompatible: { tone: "danger", chip: "failure" },
|
|
39
|
+
disabled: { tone: "quiet", chip: "inert" },
|
|
40
|
+
}),
|
|
41
|
+
peer: presentation({
|
|
42
|
+
idle: { tone: "good", chip: "progress" },
|
|
43
|
+
busy: { tone: "info", chip: "active" },
|
|
44
|
+
awaiting_approval: { tone: "warning", chip: "warning" },
|
|
45
|
+
offline: { tone: "danger", chip: "inert" },
|
|
46
|
+
incompatible: { tone: "danger", chip: "failure" },
|
|
47
|
+
}),
|
|
48
|
+
health: presentation({
|
|
49
|
+
healthy: { tone: "good", chip: "positive" },
|
|
50
|
+
connecting: { tone: "info", chip: "progress" },
|
|
51
|
+
degraded: { tone: "warning", chip: "warning" },
|
|
52
|
+
offline: { tone: "danger", chip: "failure" },
|
|
53
|
+
incompatible: { tone: "danger", chip: "failure" },
|
|
54
|
+
}),
|
|
55
|
+
compatibility: presentation({
|
|
56
|
+
compatible: { tone: "good", chip: "positive" },
|
|
57
|
+
unknown: { tone: "quiet", chip: "inert" },
|
|
58
|
+
expired: { tone: "warning", chip: "warning" },
|
|
59
|
+
incompatible: { tone: "danger", chip: "failure" },
|
|
60
|
+
}),
|
|
61
|
+
overall: presentation({
|
|
62
|
+
ready: { tone: "good", chip: "positive" },
|
|
63
|
+
setup: { tone: "info", chip: "progress" },
|
|
64
|
+
attention: { tone: "danger", chip: "failure" },
|
|
65
|
+
}),
|
|
66
|
+
party: presentation({
|
|
67
|
+
ready: { tone: "good", chip: "positive" },
|
|
68
|
+
busy: { tone: "info", chip: "active" },
|
|
69
|
+
waiting: { tone: "warning", chip: "warning" },
|
|
70
|
+
missing: { tone: "quiet", chip: "inert" },
|
|
71
|
+
attention: { tone: "danger", chip: "failure" },
|
|
72
|
+
}),
|
|
73
|
+
severity: presentation({
|
|
74
|
+
info: { tone: "info", chip: "active" },
|
|
75
|
+
warning: { tone: "warning", chip: "warning" },
|
|
76
|
+
error: { tone: "danger", chip: "failure" },
|
|
77
|
+
}),
|
|
78
|
+
connection: presentation({
|
|
79
|
+
connected: { tone: "good", chip: "positive" },
|
|
80
|
+
connecting: { tone: "info", chip: "progress" },
|
|
81
|
+
paused: { tone: "quiet", chip: "inert" },
|
|
82
|
+
unavailable: { tone: "warning", chip: "warning" },
|
|
83
|
+
capacity: { tone: "warning", chip: "warning" },
|
|
84
|
+
disconnected: { tone: "warning", chip: "warning" },
|
|
85
|
+
stopped: { tone: "warning", chip: "warning" },
|
|
86
|
+
}),
|
|
87
|
+
};
|
|
88
|
+
const guidanceCopyKeys = {
|
|
89
|
+
reobserve_claude: "reobserveClaude",
|
|
90
|
+
reobserve_codex: "reobserveCodex",
|
|
91
|
+
codex_reactivation_required: "codexReactivationRequired",
|
|
92
|
+
consent_edge_unavailable: "consentEdgeUnavailable",
|
|
93
|
+
claude_not_observed: "claudeNotObserved",
|
|
94
|
+
codex_stale: "codexStale",
|
|
95
|
+
codex_app_reconnect_required: "codexAppReconnectRequired",
|
|
96
|
+
connector_offline: "connectorOffline",
|
|
97
|
+
route_stale: "routeStale",
|
|
98
|
+
queue_stalled: "queueStalled",
|
|
99
|
+
recipient_waiting_input: "recipientWaitingInput",
|
|
100
|
+
unconfirmed: "unconfirmed",
|
|
101
|
+
degraded: "degraded",
|
|
102
|
+
codex_succession_busy: "codexSuccessionBusy",
|
|
103
|
+
codex_succession_recovery: "codexSuccessionRecovery",
|
|
104
|
+
progress_watch: "progressWatch",
|
|
105
|
+
registry_empty: "registryEmpty",
|
|
106
|
+
registry_rejected: "registryRejected",
|
|
107
|
+
provider_incompatible: "providerIncompatible",
|
|
108
|
+
generic: "generic",
|
|
109
|
+
};
|
|
110
|
+
const nextActionCopyKeys = {
|
|
111
|
+
discover_claude: "next.discoverClaude",
|
|
112
|
+
select_claude: "next.selectClaude",
|
|
113
|
+
pair_routes: "next.pairRoutes",
|
|
114
|
+
restore_claude: "next.restoreClaude",
|
|
115
|
+
repair_claude_inventory: "next.repairClaude",
|
|
116
|
+
register_codex: "next.registerCodex",
|
|
117
|
+
restore_codex: "next.restoreCodex",
|
|
118
|
+
review_compatibility: "next.reviewCompatibility",
|
|
119
|
+
none: "next.none",
|
|
120
|
+
};
|
|
121
|
+
const attentionCommands = {
|
|
122
|
+
reobserve_claude: "embassy select-claude --alias {alias}",
|
|
123
|
+
reobserve_codex: "embassy register-codex --alias {alias}",
|
|
124
|
+
codex_reactivation_required: "embassy register-codex --alias {alias}",
|
|
125
|
+
consent_edge_unavailable: "embassy refresh-dashboard",
|
|
126
|
+
claude_not_observed: "embassy select-claude --alias {alias}",
|
|
127
|
+
codex_stale: "embassy register-codex --alias {alias}",
|
|
128
|
+
codex_app_reconnect_required: "/usr/bin/open --env CODEX_APP_SERVER_USE_LOCAL_DAEMON=1 -a ChatGPT",
|
|
129
|
+
connector_offline: "embassy status",
|
|
130
|
+
route_stale: "embassy status",
|
|
131
|
+
queue_stalled: "embassy status",
|
|
132
|
+
recipient_waiting_input: "embassy status",
|
|
133
|
+
unconfirmed: "embassy status",
|
|
134
|
+
degraded: "embassy status",
|
|
135
|
+
codex_succession_busy: "embassy register-codex --alias <new> --succeeds {alias}",
|
|
136
|
+
codex_succession_recovery: "embassy register-codex --alias <new> --succeeds {alias}",
|
|
137
|
+
progress_watch: "embassy status",
|
|
138
|
+
registry_empty: "embassy refresh-dashboard",
|
|
139
|
+
registry_rejected: "embassy status",
|
|
140
|
+
provider_incompatible: "embassy refresh-dashboard",
|
|
141
|
+
generic: "embassy status",
|
|
142
|
+
};
|
|
143
|
+
const deliveryMeaningKeys = {
|
|
144
|
+
queued: "activity.meaning.queued",
|
|
145
|
+
duplicate: "activity.meaning.duplicate",
|
|
146
|
+
dispatching: "activity.meaning.dispatching",
|
|
147
|
+
transport_written: "activity.meaning.transportWritten",
|
|
148
|
+
held: "activity.meaning.held",
|
|
149
|
+
delivered: "activity.meaning.delivered",
|
|
150
|
+
unconfirmed: "activity.meaning.unconfirmed",
|
|
151
|
+
failed: "activity.meaning.failed",
|
|
152
|
+
ambiguous: "activity.meaning.ambiguous",
|
|
153
|
+
expired: "activity.meaning.expired",
|
|
154
|
+
cancelled: "activity.meaning.cancelled",
|
|
155
|
+
abandoned: "activity.meaning.abandoned.generic",
|
|
156
|
+
rejected: "activity.meaning.rejected",
|
|
157
|
+
};
|
|
158
|
+
/** JSON-safe semantics embedded once in the live dashboard boot payload. */
|
|
159
|
+
export const DASHBOARD_SEMANTICS = Object.freeze({
|
|
160
|
+
statePresentation,
|
|
161
|
+
deliveryChipByDirection: {
|
|
162
|
+
delivered: { codex_to_claude: "qualified" },
|
|
163
|
+
},
|
|
164
|
+
deliveryChipBySafeErrorCode: {
|
|
165
|
+
rejected: { SENDER_NOT_PAIRED: "inert" },
|
|
166
|
+
},
|
|
167
|
+
deliveryToneBySafeErrorCode: {
|
|
168
|
+
rejected: { SENDER_NOT_PAIRED: "quiet" },
|
|
169
|
+
},
|
|
170
|
+
guidanceCopyKeys,
|
|
171
|
+
nextActionCopyKeys,
|
|
172
|
+
attentionCommands,
|
|
173
|
+
attentionCommandFallbacks: {
|
|
174
|
+
codex_succession_busy: "<old>",
|
|
175
|
+
codex_succession_recovery: "<old>",
|
|
176
|
+
},
|
|
177
|
+
deliveryMeaningKeys,
|
|
178
|
+
deliveryMeaningByDirection: {
|
|
179
|
+
codex_to_claude: "activity.meaning.delivered.codexToClaude",
|
|
180
|
+
claude_to_codex: "activity.meaning.delivered.claudeToCodex",
|
|
181
|
+
},
|
|
182
|
+
deliveryMeaningBySafeErrorCode: {
|
|
183
|
+
SENDER_NOT_PAIRED: "activity.meaning.senderNotPaired",
|
|
184
|
+
CONTROLLER_RESTARTED: "activity.meaning.abandoned.controllerRestarted",
|
|
185
|
+
TRANSIENT_BODY_UNAVAILABLE: "activity.meaning.abandoned.transientBody",
|
|
186
|
+
ROUTE_UNREGISTERED: "activity.meaning.abandoned.routeTerminated",
|
|
187
|
+
MESSAGE_EXPIRED: "activity.meaning.abandoned.routeTerminated",
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
function semanticPresentation(domain, state) {
|
|
191
|
+
const table = DASHBOARD_SEMANTICS.statePresentation[domain];
|
|
192
|
+
return Object.prototype.hasOwnProperty.call(table, state)
|
|
193
|
+
? table[state]
|
|
194
|
+
: undefined;
|
|
195
|
+
}
|
|
196
|
+
export function dashboardChipKind(domain, state, direction, safeErrorCode) {
|
|
197
|
+
if (domain === "delivery") {
|
|
198
|
+
const byCode = DASHBOARD_SEMANTICS.deliveryChipBySafeErrorCode;
|
|
199
|
+
const byDirection = DASHBOARD_SEMANTICS.deliveryChipByDirection;
|
|
200
|
+
if (safeErrorCode !== undefined) {
|
|
201
|
+
const override = byCode[state]?.[safeErrorCode];
|
|
202
|
+
if (override !== undefined)
|
|
203
|
+
return override;
|
|
204
|
+
}
|
|
205
|
+
if (direction !== undefined) {
|
|
206
|
+
const override = byDirection[state]?.[direction];
|
|
207
|
+
if (override !== undefined)
|
|
208
|
+
return override;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return semanticPresentation(domain, state)?.chip ?? "unknown";
|
|
212
|
+
}
|
|
213
|
+
export function dashboardTone(domain, state, safeErrorCode) {
|
|
214
|
+
if (domain === "delivery" && safeErrorCode !== undefined) {
|
|
215
|
+
const byCode = DASHBOARD_SEMANTICS.deliveryToneBySafeErrorCode;
|
|
216
|
+
const override = byCode[state]?.[safeErrorCode];
|
|
217
|
+
if (override !== undefined)
|
|
218
|
+
return override;
|
|
219
|
+
}
|
|
220
|
+
return semanticPresentation(domain, state)?.tone ?? "danger";
|
|
221
|
+
}
|
|
222
|
+
export function deliveryMeaningKey(state, direction, safeErrorCode) {
|
|
223
|
+
if (state === "rejected" && safeErrorCode === "SENDER_NOT_PAIRED") {
|
|
224
|
+
return DASHBOARD_SEMANTICS.deliveryMeaningBySafeErrorCode.SENDER_NOT_PAIRED;
|
|
225
|
+
}
|
|
226
|
+
if (state === "delivered" && direction !== undefined) {
|
|
227
|
+
return DASHBOARD_SEMANTICS.deliveryMeaningByDirection[direction];
|
|
228
|
+
}
|
|
229
|
+
if (state === "abandoned" && safeErrorCode !== undefined) {
|
|
230
|
+
const byCode = DASHBOARD_SEMANTICS.deliveryMeaningBySafeErrorCode;
|
|
231
|
+
return byCode[safeErrorCode] ?? deliveryMeaningKeys.abandoned;
|
|
232
|
+
}
|
|
233
|
+
const byState = DASHBOARD_SEMANTICS.deliveryMeaningKeys;
|
|
234
|
+
return byState[state] ?? "activity.meaning.other";
|
|
235
|
+
}
|
|
236
|
+
export function guidanceCopyKey(guidance) {
|
|
237
|
+
return DASHBOARD_SEMANTICS.guidanceCopyKeys[guidance];
|
|
238
|
+
}
|
|
239
|
+
export function nextActionCopyKey(action) {
|
|
240
|
+
return DASHBOARD_SEMANTICS.nextActionCopyKeys[action];
|
|
241
|
+
}
|
|
242
|
+
export function attentionCommand(item) {
|
|
243
|
+
const fallbacks = DASHBOARD_SEMANTICS.attentionCommandFallbacks;
|
|
244
|
+
return DASHBOARD_SEMANTICS.attentionCommands[item.guidance].replaceAll("{alias}", item.alias ?? fallbacks[item.guidance] ?? "<alias>");
|
|
245
|
+
}
|
|
15
246
|
const SAFE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
16
247
|
const SAFE_PROTOCOL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+:/-]{0,47}$/;
|
|
17
248
|
const OPAQUE_SUFFIX_PATTERN = /^[a-f0-9]{6,12}$/i;
|
|
@@ -69,8 +300,15 @@ function normalizedCounters(counters) {
|
|
|
69
300
|
bytesAccepted: normalizedInteger(counters?.bytesAccepted) ?? 0,
|
|
70
301
|
};
|
|
71
302
|
}
|
|
303
|
+
export function buildDashboardViewModel(snapshot, options = {}) {
|
|
304
|
+
return buildProjectedDashboardViewModel(snapshot, false, options);
|
|
305
|
+
}
|
|
306
|
+
export function buildLiveDashboardViewModel(snapshot, options = {}) {
|
|
307
|
+
return buildProjectedDashboardViewModel(snapshot, true, options);
|
|
308
|
+
}
|
|
72
309
|
function routeIsReady(route) {
|
|
73
310
|
return (route.enabled &&
|
|
311
|
+
route.safeErrorCode !== "CODEX_WRITES_DISABLED" &&
|
|
74
312
|
route.compatibility === "compatible" &&
|
|
75
313
|
(route.state === "idle" || route.state === "busy"));
|
|
76
314
|
}
|
|
@@ -116,6 +354,22 @@ function routePriority(route) {
|
|
|
116
354
|
return 3;
|
|
117
355
|
return 4;
|
|
118
356
|
}
|
|
357
|
+
const CODEX_APP_RECONNECT_GUIDANCE_AFTER_MS = 2_000;
|
|
358
|
+
function needsCodexAppReconnect(route, connectors, generatedAt) {
|
|
359
|
+
if (route.provider !== "codex" ||
|
|
360
|
+
route.state !== "stale" ||
|
|
361
|
+
route.safeErrorCode !== "CODEX_ROUTE_STALE" ||
|
|
362
|
+
route.lastSeenAt === undefined ||
|
|
363
|
+
generatedAt === undefined ||
|
|
364
|
+
Date.parse(generatedAt) - Date.parse(route.lastSeenAt) <
|
|
365
|
+
CODEX_APP_RECONNECT_GUIDANCE_AFTER_MS) {
|
|
366
|
+
return false;
|
|
367
|
+
}
|
|
368
|
+
return connectors.some((connector) => connector.provider === "codex" &&
|
|
369
|
+
connector.host === route.host &&
|
|
370
|
+
connector.health === "healthy" &&
|
|
371
|
+
connector.compatibility === "compatible");
|
|
372
|
+
}
|
|
119
373
|
function alertPriority(severity) {
|
|
120
374
|
return severity === "error" ? 0 : severity === "warning" ? 1 : 2;
|
|
121
375
|
}
|
|
@@ -151,6 +405,14 @@ function guidanceFor(code, provider) {
|
|
|
151
405
|
case "ADAPTER_DEGRADED":
|
|
152
406
|
case "ROUTE_DEGRADED":
|
|
153
407
|
return "degraded";
|
|
408
|
+
case "CLAUDE_PEER_VERSION_UNSUPPORTED":
|
|
409
|
+
case "CLAUDE_VERSION_UNPARSEABLE":
|
|
410
|
+
case "CLAUDE_VERSION_CHECK_FAILED":
|
|
411
|
+
case "CLAUDE_VERSION_EVIDENCE_CONFLICT":
|
|
412
|
+
case "CLAUDE_VERSION_EVIDENCE_TOO_LARGE":
|
|
413
|
+
case "CODEX_APP_SERVER_VERSION_UNSUPPORTED":
|
|
414
|
+
case "CODEX_APP_SERVER_VERSION_UNPARSEABLE":
|
|
415
|
+
return "provider_incompatible";
|
|
154
416
|
default:
|
|
155
417
|
return "generic";
|
|
156
418
|
}
|
|
@@ -219,7 +481,7 @@ function messageKey(message) {
|
|
|
219
481
|
typeof message.messageIdSuffix === "string" ? message.messageIdSuffix : "",
|
|
220
482
|
].join("\0");
|
|
221
483
|
}
|
|
222
|
-
function buildMessageGroups(messages) {
|
|
484
|
+
function buildMessageGroups(messages, includeBodies) {
|
|
223
485
|
const grouped = new Map();
|
|
224
486
|
for (const message of messages) {
|
|
225
487
|
const key = messageKey(message);
|
|
@@ -282,7 +544,8 @@ function buildMessageGroups(messages) {
|
|
|
282
544
|
...(latest.steer === true ? { steer: true } : {}),
|
|
283
545
|
// Bodies are first-class observable data (CO #36); the display copy
|
|
284
546
|
// is bounded so one 16 KiB message cannot dominate the view model.
|
|
285
|
-
...(
|
|
547
|
+
...(includeBodies &&
|
|
548
|
+
typeof latest.body === "string" && latest.body.length > 0
|
|
286
549
|
? { body: boundedText(latest.body, 4096) }
|
|
287
550
|
: {}),
|
|
288
551
|
events: allEvents,
|
|
@@ -311,12 +574,29 @@ function buildMessageGroups(messages) {
|
|
|
311
574
|
activeGroups: all.filter((message) => ["queued", "dispatching", "transport_written", "held"].includes(message.state)).length,
|
|
312
575
|
};
|
|
313
576
|
}
|
|
314
|
-
|
|
577
|
+
function buildProjectedDashboardViewModel(snapshot, includeBodies, options) {
|
|
578
|
+
const surfaceDefinitions = options.compatibilitySurfaceDefinitions ?? compatibilitySurfaceDefinitions;
|
|
315
579
|
const generatedAt = normalizedTimestamp(snapshot.generatedAt);
|
|
316
580
|
const inboundMode = snapshot.inboundMode === "open" ? "open" : "paired";
|
|
317
581
|
const validPeers = arePublicAvailablePeerSnapshots(snapshot.availablePeers)
|
|
318
582
|
? snapshot.availablePeers
|
|
319
583
|
: [];
|
|
584
|
+
const writesCoveredProviders = new Set((Array.isArray(snapshot.compatibilityChecks)
|
|
585
|
+
? snapshot.compatibilityChecks.filter(isPublicCompatibilityCheckSnapshot)
|
|
586
|
+
: []).filter((check) => check.writesCovered).map((check) => check.surface));
|
|
587
|
+
const quarantinedProviders = new Set([
|
|
588
|
+
...snapshot.connectors
|
|
589
|
+
.filter((connector) => connector.compatibility === "incompatible")
|
|
590
|
+
.map((connector) => connector.provider),
|
|
591
|
+
...(Array.isArray(snapshot.compatibilityChecks)
|
|
592
|
+
? snapshot.compatibilityChecks
|
|
593
|
+
.filter(isPublicCompatibilityCheckSnapshot)
|
|
594
|
+
.filter((check) => check.tier === "incompatible")
|
|
595
|
+
.flatMap((check) => check.surface === "claude" || check.surface === "codex"
|
|
596
|
+
? [check.surface]
|
|
597
|
+
: [])
|
|
598
|
+
: []),
|
|
599
|
+
]);
|
|
320
600
|
const peers = validPeers
|
|
321
601
|
.map((peer) => ({
|
|
322
602
|
alias: boundedText(peer.alias),
|
|
@@ -403,7 +683,11 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
403
683
|
claudeAlias,
|
|
404
684
|
codexAlias,
|
|
405
685
|
host: boundedText(pair.host),
|
|
406
|
-
state: bothPresent &&
|
|
686
|
+
state: bothPresent &&
|
|
687
|
+
!quarantinedProviders.has("claude") &&
|
|
688
|
+
!quarantinedProviders.has("codex") &&
|
|
689
|
+
routeIsReady(claudeRoute) &&
|
|
690
|
+
routeIsReady(codexRoute)
|
|
407
691
|
? "ready"
|
|
408
692
|
: bothPresent
|
|
409
693
|
? "degraded"
|
|
@@ -425,9 +709,9 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
425
709
|
generatedAtMs === undefined) {
|
|
426
710
|
return [];
|
|
427
711
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
712
|
+
if (watch.nudgeCount !== 0 &&
|
|
713
|
+
watch.nudgeCount !== 1 &&
|
|
714
|
+
watch.nudgeCount !== 2) {
|
|
431
715
|
return [];
|
|
432
716
|
}
|
|
433
717
|
return [
|
|
@@ -435,11 +719,8 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
435
719
|
conversationIdSuffix: watch.conversationIdSuffix,
|
|
436
720
|
ownerAlias: boundedText(watch.ownerAlias),
|
|
437
721
|
workerAlias: boundedText(watch.workerAlias),
|
|
438
|
-
phase: watch.phase,
|
|
439
|
-
capability: watch.capability,
|
|
440
722
|
lastActivityAt,
|
|
441
723
|
nextActionAt,
|
|
442
|
-
idleMs,
|
|
443
724
|
idleForMs: Math.max(0, generatedAtMs - Date.parse(lastActivityAt)),
|
|
444
725
|
dueInMs: Math.max(0, Date.parse(nextActionAt) - generatedAtMs),
|
|
445
726
|
nudgeCount: watch.nudgeCount,
|
|
@@ -466,37 +747,52 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
466
747
|
ownerAlias: boundedText(event.ownerAlias),
|
|
467
748
|
workerAlias: boundedText(event.workerAlias),
|
|
468
749
|
kind: event.kind,
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
: { nudgeNumber: event.nudgeNumber }),
|
|
750
|
+
actor: event.actor,
|
|
751
|
+
...(event.reason === undefined ? {} : { reason: event.reason }),
|
|
472
752
|
},
|
|
473
753
|
];
|
|
474
754
|
})
|
|
475
755
|
.sort((left, right) => right.sequence - left.sequence);
|
|
476
756
|
const watchEvents = allWatchEvents.slice(0, DASHBOARD_MODEL_LIMITS.progressWatchEvents);
|
|
477
757
|
const connectors = snapshot.connectors
|
|
478
|
-
.map((connector) =>
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
:
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
758
|
+
.map((connector) => {
|
|
759
|
+
const registry = isPublicRegistryObservationSnapshot(connector.registry)
|
|
760
|
+
? connector.registry
|
|
761
|
+
: undefined;
|
|
762
|
+
return {
|
|
763
|
+
provider: connector.provider,
|
|
764
|
+
host: boundedText(connector.host),
|
|
765
|
+
health: connector.health,
|
|
766
|
+
compatibility: connector.compatibility,
|
|
767
|
+
...(safeProtocol(connector.protocol) === undefined
|
|
768
|
+
? {}
|
|
769
|
+
: { protocol: safeProtocol(connector.protocol) }),
|
|
770
|
+
...(safeProtocol(connector.protocolVersion) === undefined
|
|
771
|
+
? {}
|
|
772
|
+
: { protocolVersion: safeProtocol(connector.protocolVersion) }),
|
|
773
|
+
...(normalizedTimestamp(connector.lastSeenAt) === undefined
|
|
774
|
+
? {}
|
|
775
|
+
: { lastSeenAt: normalizedTimestamp(connector.lastSeenAt) }),
|
|
776
|
+
...(safeCode(connector.safeErrorCode) === undefined
|
|
777
|
+
? {}
|
|
778
|
+
: { safeErrorCode: safeCode(connector.safeErrorCode) }),
|
|
779
|
+
...(registry === undefined
|
|
780
|
+
? {}
|
|
781
|
+
: {
|
|
782
|
+
registry: {
|
|
783
|
+
entriesScanned: registry.entriesScanned,
|
|
784
|
+
parseableRecords: registry.parseableRecords,
|
|
785
|
+
parseableRecordSeenSinceBoot: registry.parseableRecordSeenSinceBoot,
|
|
786
|
+
rejected: registry.rejected.map((row) => ({ ...row })),
|
|
787
|
+
rejectedCodesOmitted: registry.rejectedCodesOmitted,
|
|
788
|
+
},
|
|
789
|
+
}),
|
|
790
|
+
};
|
|
791
|
+
})
|
|
496
792
|
.sort((left, right) => compareText(`${left.provider}\0${left.host}`, `${right.provider}\0${right.host}`))
|
|
497
793
|
.slice(0, DASHBOARD_MODEL_LIMITS.connectors);
|
|
498
|
-
const
|
|
499
|
-
? snapshot.compatibilityChecks.filter(
|
|
794
|
+
const detectedCompatibilityChecks = (Array.isArray(snapshot.compatibilityChecks)
|
|
795
|
+
? snapshot.compatibilityChecks.filter(isPublicCompatibilityCheckSnapshot)
|
|
500
796
|
: [])
|
|
501
797
|
.map((attestation) => {
|
|
502
798
|
const failed = attestation.probes.find((probe) => probe.outcome === "fail");
|
|
@@ -504,15 +800,30 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
504
800
|
return {
|
|
505
801
|
surface: attestation.surface,
|
|
506
802
|
version: boundedText(attestation.version),
|
|
803
|
+
...(attestation.testedVersion === undefined
|
|
804
|
+
? {}
|
|
805
|
+
: { testedVersion: boundedText(attestation.testedVersion) }),
|
|
806
|
+
...(attestation.supportedMajor === undefined
|
|
807
|
+
? {}
|
|
808
|
+
: { supportedMajor: boundedText(attestation.supportedMajor) }),
|
|
507
809
|
tier: attestation.tier,
|
|
810
|
+
writesCovered: attestation.writesCovered,
|
|
508
811
|
checkedAt: attestation.checkedAt,
|
|
509
812
|
...(failed === undefined ? {} : { failure: failed.name }),
|
|
510
813
|
...(issue === undefined ? {} : { safeErrorCode: issue }),
|
|
511
814
|
};
|
|
512
815
|
})
|
|
816
|
+
.sort((left, right) => compareText(left.surface, right.surface));
|
|
817
|
+
const detectedSurfaces = new Set(detectedCompatibilityChecks.map(({ surface }) => surface));
|
|
818
|
+
const compatibilityChecks = [
|
|
819
|
+
...detectedCompatibilityChecks,
|
|
820
|
+
...surfaceDefinitions
|
|
821
|
+
.filter(({ required, surface }) => !required && !detectedSurfaces.has(surface))
|
|
822
|
+
.map(({ surface }) => ({ surface, notDetected: true })),
|
|
823
|
+
]
|
|
513
824
|
.sort((left, right) => compareText(left.surface, right.surface))
|
|
514
|
-
.slice(0,
|
|
515
|
-
const messages = buildMessageGroups(snapshot.messages);
|
|
825
|
+
.slice(0, surfaceDefinitions.length);
|
|
826
|
+
const messages = buildMessageGroups(snapshot.messages, includeBodies);
|
|
516
827
|
const queuedMessages = allRoutes.reduce((total, route) => boundedAdd(total, route.queueDepth), 0);
|
|
517
828
|
const queuedWithAge = allRoutes
|
|
518
829
|
.filter((route) => route.oldestQueuedAt !== undefined && route.queueAgeMs !== undefined)
|
|
@@ -521,52 +832,68 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
521
832
|
const activeDeliveries = messages.activeGroups;
|
|
522
833
|
const claudeRoutes = allRoutes.filter((route) => route.provider === "claude");
|
|
523
834
|
const codexRoutes = allRoutes.filter((route) => route.provider === "codex");
|
|
524
|
-
const
|
|
525
|
-
const
|
|
835
|
+
const monitorOnlyCodexRoutes = codexRoutes.filter((route) => route.safeErrorCode === "CODEX_WRITES_DISABLED");
|
|
836
|
+
const routeIsExchangeReady = (route) => !quarantinedProviders.has(route.provider) && routeIsReady(route);
|
|
837
|
+
const readyClaude = claudeRoutes.filter(routeIsExchangeReady).length;
|
|
838
|
+
const readyCodex = codexRoutes.filter(routeIsExchangeReady).length;
|
|
526
839
|
const readyPairs = allPairs.filter((pair) => pair.state === "ready");
|
|
527
840
|
const pairedClaude = new Set(allPairs.map((pair) => pair.claudeAlias));
|
|
528
841
|
const pairedCodex = new Set(allPairs.map((pair) => pair.codexAlias));
|
|
529
|
-
const readyPairedClaude = claudeRoutes.some((route) =>
|
|
530
|
-
const readyPairedCodex = codexRoutes.some((route) =>
|
|
842
|
+
const readyPairedClaude = claudeRoutes.some((route) => routeIsExchangeReady(route) && pairedClaude.has(route.alias));
|
|
843
|
+
const readyPairedCodex = codexRoutes.some((route) => routeIsExchangeReady(route) && pairedCodex.has(route.alias));
|
|
531
844
|
const pairCountIsLowerBound = (normalizedInteger(snapshot.truncation.pairs) ?? 0) > 0;
|
|
532
845
|
const hasPairEvidence = allPairs.length > 0 || pairCountIsLowerBound;
|
|
533
846
|
const graph = {
|
|
534
847
|
pairCount: allPairs.length,
|
|
535
848
|
readyPairCount: readyPairs.length,
|
|
536
849
|
pairCountIsLowerBound,
|
|
537
|
-
unpairedReadyClaude: claudeRoutes.filter((route) =>
|
|
538
|
-
unpairedReadyCodex: codexRoutes.filter((route) =>
|
|
850
|
+
unpairedReadyClaude: claudeRoutes.filter((route) => routeIsExchangeReady(route) && !pairedClaude.has(route.alias)).length,
|
|
851
|
+
unpairedReadyCodex: codexRoutes.filter((route) => routeIsExchangeReady(route) && !pairedCodex.has(route.alias)).length,
|
|
539
852
|
};
|
|
540
853
|
const selectedPeers = validPeers.filter((peer) => peer.selected && peerIsSelectable(peer));
|
|
541
854
|
const selectablePeers = validPeers.filter(peerIsSelectable);
|
|
542
|
-
const selectedClaudeCount =
|
|
855
|
+
const selectedClaudeCount = quarantinedProviders.has("claude")
|
|
856
|
+
? 0
|
|
857
|
+
: Math.max(selectedPeers.length, readyClaude);
|
|
543
858
|
const claudeStatus = partyStatus(claudeRoutes, readyClaude);
|
|
544
859
|
const codexStatus = partyStatus(codexRoutes, readyCodex);
|
|
545
|
-
const claudeNextAction =
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
? "
|
|
550
|
-
:
|
|
551
|
-
? "
|
|
552
|
-
:
|
|
553
|
-
?
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
:
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
860
|
+
const claudeNextAction = quarantinedProviders.has("claude")
|
|
861
|
+
? "review_compatibility"
|
|
862
|
+
: selectedClaudeCount > 0 &&
|
|
863
|
+
(readyPairedClaude || (pairCountIsLowerBound && readyClaude > 0))
|
|
864
|
+
? "none"
|
|
865
|
+
: readyClaude > 0
|
|
866
|
+
? "pair_routes"
|
|
867
|
+
: validPeers.length === 0
|
|
868
|
+
? "discover_claude"
|
|
869
|
+
: selectablePeers.length > 0
|
|
870
|
+
? !hasPairEvidence
|
|
871
|
+
? "pair_routes"
|
|
872
|
+
: "restore_claude"
|
|
873
|
+
: selectedPeers.length > 0
|
|
874
|
+
? "restore_claude"
|
|
875
|
+
: "repair_claude_inventory";
|
|
876
|
+
const codexNextAction = quarantinedProviders.has("codex") || monitorOnlyCodexRoutes.length > 0
|
|
877
|
+
? "review_compatibility"
|
|
878
|
+
: readyPairedCodex || (pairCountIsLowerBound && readyCodex > 0)
|
|
879
|
+
? "none"
|
|
880
|
+
: readyCodex > 0
|
|
881
|
+
? "pair_routes"
|
|
882
|
+
: codexRoutes.length === 0
|
|
883
|
+
? "register_codex"
|
|
884
|
+
: "restore_codex";
|
|
566
885
|
const explicitAlerts = coalesceCodexReactivationAlerts(snapshot.alerts
|
|
567
886
|
.filter((alert) => alert.code !== "COMPATIBILITY_CERTIFICATION_FAILED")
|
|
568
887
|
.map((alert) => {
|
|
569
888
|
const code = safeCode(alert.code);
|
|
889
|
+
const providerWritesCovered = (alert.provider === "claude" || alert.provider === "codex") &&
|
|
890
|
+
writesCoveredProviders.has(alert.provider);
|
|
891
|
+
const route = code === "QUEUE_STALLED" && typeof alert.alias === "string"
|
|
892
|
+
? routeByAlias.get(alert.alias)
|
|
893
|
+
: undefined;
|
|
894
|
+
const alertRoute = typeof alert.alias === "string"
|
|
895
|
+
? routeByAlias.get(alert.alias)
|
|
896
|
+
: undefined;
|
|
570
897
|
return {
|
|
571
898
|
kind: "alert",
|
|
572
899
|
...(code === undefined ? {} : { code }),
|
|
@@ -587,7 +914,26 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
587
914
|
...(typeof alert.host === "string"
|
|
588
915
|
? { host: boundedText(alert.host) }
|
|
589
916
|
: {}),
|
|
590
|
-
|
|
917
|
+
...(alert.provider === "codex" &&
|
|
918
|
+
typeof alert.host === "string" &&
|
|
919
|
+
route !== undefined &&
|
|
920
|
+
alert.host === route.host &&
|
|
921
|
+
route.provider === "codex" &&
|
|
922
|
+
route.state === "busy" &&
|
|
923
|
+
route.queueDepth > 0
|
|
924
|
+
? { queueDepth: route.queueDepth }
|
|
925
|
+
: {}),
|
|
926
|
+
guidance: alertRoute !== undefined &&
|
|
927
|
+
needsCodexAppReconnect(alertRoute, connectors, generatedAt)
|
|
928
|
+
? "codex_app_reconnect_required"
|
|
929
|
+
: !providerWritesCovered &&
|
|
930
|
+
alert.provider !== undefined &&
|
|
931
|
+
quarantinedProviders.has(alert.provider)
|
|
932
|
+
? "provider_incompatible"
|
|
933
|
+
: providerWritesCovered &&
|
|
934
|
+
guidanceFor(code, alert.provider) === "provider_incompatible"
|
|
935
|
+
? "degraded"
|
|
936
|
+
: guidanceFor(code, alert.provider),
|
|
591
937
|
};
|
|
592
938
|
}), allRoutes).sort((left, right) => alertPriority(left.severity) - alertPriority(right.severity) ||
|
|
593
939
|
compareText(right.timestamp ?? "", left.timestamp ?? ""));
|
|
@@ -649,13 +995,11 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
649
995
|
attentionCandidates[existingIndex] = item;
|
|
650
996
|
}
|
|
651
997
|
for (const watch of allWatches) {
|
|
652
|
-
if (watch.
|
|
998
|
+
if (watch.nudgeCount === 0)
|
|
653
999
|
continue;
|
|
654
1000
|
attentionCandidates.push({
|
|
655
1001
|
kind: "watch",
|
|
656
|
-
code:
|
|
657
|
-
? "PROGRESS_WATCH_CAPABILITY_DEGRADED"
|
|
658
|
-
: "PROGRESS_WATCH_QUIET",
|
|
1002
|
+
code: "PROGRESS_WATCH_QUIET",
|
|
659
1003
|
severity: "warning",
|
|
660
1004
|
timestamp: watch.lastActivityAt,
|
|
661
1005
|
alias: watch.workerAlias,
|
|
@@ -664,7 +1008,9 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
664
1008
|
}
|
|
665
1009
|
const representedScopes = new Set(attentionCandidates.map((item) => `${item.provider ?? ""}\0${item.alias ?? ""}\0${item.host ?? ""}`));
|
|
666
1010
|
for (const route of allRoutes) {
|
|
667
|
-
if (routeIsReady(route)
|
|
1011
|
+
if (routeIsReady(route) ||
|
|
1012
|
+
quarantinedProviders.has(route.provider) ||
|
|
1013
|
+
route.safeErrorCode === "CODEX_WRITES_DISABLED") {
|
|
668
1014
|
continue;
|
|
669
1015
|
}
|
|
670
1016
|
const scope = `${route.provider}\0${route.alias}\0${route.host}`;
|
|
@@ -684,9 +1030,11 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
684
1030
|
route.state === "stale" &&
|
|
685
1031
|
route.safeErrorCode === "REOBSERVATION_REQUIRED"
|
|
686
1032
|
? "codex_reactivation_required"
|
|
687
|
-
: route
|
|
688
|
-
? "
|
|
689
|
-
: "
|
|
1033
|
+
: needsCodexAppReconnect(route, connectors, generatedAt)
|
|
1034
|
+
? "codex_app_reconnect_required"
|
|
1035
|
+
: route.provider === "codex" && route.state === "stale"
|
|
1036
|
+
? "codex_stale"
|
|
1037
|
+
: "route_stale",
|
|
690
1038
|
});
|
|
691
1039
|
representedScopes.add(scope);
|
|
692
1040
|
}
|
|
@@ -709,13 +1057,65 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
709
1057
|
: { timestamp: connector.lastSeenAt }),
|
|
710
1058
|
provider: connector.provider,
|
|
711
1059
|
host: connector.host,
|
|
712
|
-
guidance: connector.health === "offline"
|
|
1060
|
+
guidance: connector.health === "offline"
|
|
1061
|
+
? "connector_offline"
|
|
1062
|
+
: !writesCoveredProviders.has(connector.provider) &&
|
|
1063
|
+
(quarantinedProviders.has(connector.provider) ||
|
|
1064
|
+
connector.safeErrorCode === "CLAUDE_PEER_VERSION_UNSUPPORTED" ||
|
|
1065
|
+
connector.safeErrorCode === "CLAUDE_VERSION_UNPARSEABLE" ||
|
|
1066
|
+
connector.safeErrorCode === "CLAUDE_VERSION_CHECK_FAILED" ||
|
|
1067
|
+
connector.safeErrorCode === "CLAUDE_VERSION_EVIDENCE_CONFLICT" ||
|
|
1068
|
+
connector.safeErrorCode === "CLAUDE_VERSION_EVIDENCE_TOO_LARGE" ||
|
|
1069
|
+
connector.safeErrorCode === "CODEX_APP_SERVER_VERSION_UNSUPPORTED" ||
|
|
1070
|
+
connector.safeErrorCode === "CODEX_APP_SERVER_VERSION_UNPARSEABLE")
|
|
1071
|
+
? "provider_incompatible"
|
|
1072
|
+
: "degraded",
|
|
713
1073
|
});
|
|
714
1074
|
representedScopes.add(scope);
|
|
715
1075
|
}
|
|
1076
|
+
for (const connector of connectors) {
|
|
1077
|
+
const observation = connector.registry;
|
|
1078
|
+
if (observation === undefined)
|
|
1079
|
+
continue;
|
|
1080
|
+
if (observation.rejected.length > 0 ||
|
|
1081
|
+
observation.rejectedCodesOmitted > 0) {
|
|
1082
|
+
const item = {
|
|
1083
|
+
kind: "connector",
|
|
1084
|
+
code: observation.rejected.some((row) => row.safeErrorCode === "CLAUDE_REGISTRY_UNAVAILABLE")
|
|
1085
|
+
? "CLAUDE_REGISTRY_UNAVAILABLE"
|
|
1086
|
+
: "CLAUDE_REGISTRY_RECORDS_REJECTED",
|
|
1087
|
+
severity: "warning",
|
|
1088
|
+
provider: "claude",
|
|
1089
|
+
host: connector.host,
|
|
1090
|
+
guidance: "registry_rejected",
|
|
1091
|
+
};
|
|
1092
|
+
const duplicateIndex = attentionCandidates.findIndex((candidate) => candidate.code === item.code &&
|
|
1093
|
+
candidate.provider === item.provider &&
|
|
1094
|
+
candidate.alias === item.alias &&
|
|
1095
|
+
candidate.host === item.host);
|
|
1096
|
+
if (duplicateIndex === -1)
|
|
1097
|
+
attentionCandidates.push(item);
|
|
1098
|
+
else
|
|
1099
|
+
attentionCandidates[duplicateIndex] = item;
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
if (observation.parseableRecordSeenSinceBoot)
|
|
1103
|
+
continue;
|
|
1104
|
+
attentionCandidates.push({
|
|
1105
|
+
kind: "connector",
|
|
1106
|
+
code: observation.entriesScanned === 0
|
|
1107
|
+
? "CLAUDE_REGISTRY_EMPTY_SINCE_BOOT"
|
|
1108
|
+
: "CLAUDE_REGISTRY_NO_PARSEABLE_RECORD_SINCE_BOOT",
|
|
1109
|
+
severity: "warning",
|
|
1110
|
+
provider: "claude",
|
|
1111
|
+
host: connector.host,
|
|
1112
|
+
guidance: "registry_empty",
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
716
1115
|
if ((snapshot.health === "degraded" ||
|
|
717
1116
|
snapshot.health === "offline" ||
|
|
718
1117
|
snapshot.health === "incompatible") &&
|
|
1118
|
+
!(snapshot.health === "degraded" && quarantinedProviders.size > 0) &&
|
|
719
1119
|
!attentionCandidates.some((item) => item.kind === "broker")) {
|
|
720
1120
|
attentionCandidates.push({
|
|
721
1121
|
kind: "broker",
|
|
@@ -778,6 +1178,9 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
778
1178
|
status: codexStatus,
|
|
779
1179
|
total: codexRoutes.length,
|
|
780
1180
|
ready: readyCodex,
|
|
1181
|
+
...(monitorOnlyCodexRoutes.length === 0
|
|
1182
|
+
? {}
|
|
1183
|
+
: { monitorOnly: monitorOnlyCodexRoutes.length }),
|
|
781
1184
|
countIsLowerBound: (normalizedInteger(snapshot.truncation.routes) ?? 0) > 0,
|
|
782
1185
|
...(codexRoutes[0]?.alias === undefined
|
|
783
1186
|
? {}
|