agent-embassy 1.4.0 → 1.5.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 +32 -0
- package/CONTRIBUTING.md +61 -6
- package/README.md +6 -5
- package/README.zh-CN.md +4 -4
- package/SECURITY.md +128 -24
- package/dist/src/gateway/claude-peer.d.ts +8 -5
- package/dist/src/gateway/claude-peer.js +81 -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 +3 -7
- package/dist/src/gateway/cli.js.map +1 -1
- package/dist/src/gateway/codex-app-server.d.ts +2 -1
- package/dist/src/gateway/codex-app-server.js +1 -1
- 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 +11 -0
- package/dist/src/gateway/compatibility.js +104 -22
- 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 +36 -17
- package/dist/src/gateway/dashboard-copy.en.js +50 -31
- package/dist/src/gateway/dashboard-copy.en.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.js +36 -17
- package/dist/src/gateway/dashboard-copy.js.map +1 -1
- package/dist/src/gateway/dashboard-copy.zh-CN.d.ts +36 -17
- package/dist/src/gateway/dashboard-copy.zh-CN.js +50 -31
- package/dist/src/gateway/dashboard-copy.zh-CN.js.map +1 -1
- package/dist/src/gateway/dashboard-model.d.ts +176 -8
- package/dist/src/gateway/dashboard-model.js +420 -70
- package/dist/src/gateway/dashboard-model.js.map +1 -1
- package/dist/src/gateway/dashboard.js +48 -135
- package/dist/src/gateway/dashboard.js.map +1 -1
- package/dist/src/gateway/live-dashboard-app/app.js +148 -247
- 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 +69 -15
- package/dist/src/gateway/providers.js +456 -109
- package/dist/src/gateway/providers.js.map +1 -1
- package/dist/src/gateway/server.d.ts +8 -5
- package/dist/src/gateway/server.js +94 -13
- package/dist/src/gateway/server.js.map +1 -1
- package/dist/src/gateway/service.d.ts +25 -1
- package/dist/src/gateway/service.js +252 -69
- 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 +38 -11
- package/dist/src/gateway/types.js +141 -3
- 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 +29 -6
- package/docs/DASHBOARD.zh-CN.md +1 -1
- package/docs/GATEWAY-ARCHITECTURE.md +143 -57
- package/package.json +1 -1
- package/skills/embassy-peer/SKILL.md +2 -2
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { arePublicAvailablePeerSnapshots } from "./types.js";
|
|
2
|
-
import { isCompatibilityAttestation, } from "./compatibility.js";
|
|
1
|
+
import { arePublicAvailablePeerSnapshots, isPublicCompatibilityCheckSnapshot, isPublicRegistryObservationSnapshot, } from "./types.js";
|
|
3
2
|
export const DASHBOARD_MODEL_LIMITS = Object.freeze({
|
|
4
3
|
connectors: 16,
|
|
5
4
|
availablePeers: 64,
|
|
@@ -12,6 +11,235 @@ export const DASHBOARD_MODEL_LIMITS = Object.freeze({
|
|
|
12
11
|
activityEvents: 64,
|
|
13
12
|
alerts: 32,
|
|
14
13
|
});
|
|
14
|
+
const presentation = (values) => values;
|
|
15
|
+
const statePresentation = {
|
|
16
|
+
delivery: presentation({
|
|
17
|
+
queued: { tone: "info", chip: "progress" },
|
|
18
|
+
duplicate: { tone: "quiet", chip: "inert" },
|
|
19
|
+
dispatching: { tone: "info", chip: "progress" },
|
|
20
|
+
transport_written: { tone: "info", chip: "progress" },
|
|
21
|
+
held: { tone: "warning", chip: "progress" },
|
|
22
|
+
delivered: { tone: "good", chip: "positive" },
|
|
23
|
+
unconfirmed: { tone: "warning", chip: "indeterminate" },
|
|
24
|
+
failed: { tone: "danger", chip: "failure" },
|
|
25
|
+
ambiguous: { tone: "warning", chip: "indeterminate" },
|
|
26
|
+
expired: { tone: "danger", chip: "failure" },
|
|
27
|
+
cancelled: { tone: "danger", chip: "inert" },
|
|
28
|
+
abandoned: { tone: "danger", chip: "inert" },
|
|
29
|
+
rejected: { tone: "danger", chip: "warning" },
|
|
30
|
+
}),
|
|
31
|
+
route: presentation({
|
|
32
|
+
stale: { tone: "danger", chip: "failure" },
|
|
33
|
+
idle: { tone: "good", chip: "progress" },
|
|
34
|
+
busy: { tone: "info", chip: "active" },
|
|
35
|
+
awaiting_approval: { tone: "warning", chip: "warning" },
|
|
36
|
+
offline: { tone: "danger", chip: "failure" },
|
|
37
|
+
incompatible: { tone: "danger", chip: "failure" },
|
|
38
|
+
disabled: { tone: "quiet", chip: "inert" },
|
|
39
|
+
}),
|
|
40
|
+
peer: presentation({
|
|
41
|
+
idle: { tone: "good", chip: "progress" },
|
|
42
|
+
busy: { tone: "info", chip: "active" },
|
|
43
|
+
awaiting_approval: { tone: "warning", chip: "warning" },
|
|
44
|
+
offline: { tone: "danger", chip: "inert" },
|
|
45
|
+
incompatible: { tone: "danger", chip: "failure" },
|
|
46
|
+
}),
|
|
47
|
+
health: presentation({
|
|
48
|
+
healthy: { tone: "good", chip: "positive" },
|
|
49
|
+
connecting: { tone: "info", chip: "progress" },
|
|
50
|
+
degraded: { tone: "warning", chip: "warning" },
|
|
51
|
+
offline: { tone: "danger", chip: "failure" },
|
|
52
|
+
incompatible: { tone: "danger", chip: "failure" },
|
|
53
|
+
}),
|
|
54
|
+
compatibility: presentation({
|
|
55
|
+
compatible: { tone: "good", chip: "positive" },
|
|
56
|
+
unknown: { tone: "quiet", chip: "inert" },
|
|
57
|
+
expired: { tone: "warning", chip: "warning" },
|
|
58
|
+
incompatible: { tone: "danger", chip: "failure" },
|
|
59
|
+
}),
|
|
60
|
+
overall: presentation({
|
|
61
|
+
ready: { tone: "good", chip: "positive" },
|
|
62
|
+
setup: { tone: "info", chip: "progress" },
|
|
63
|
+
attention: { tone: "danger", chip: "failure" },
|
|
64
|
+
}),
|
|
65
|
+
party: presentation({
|
|
66
|
+
ready: { tone: "good", chip: "positive" },
|
|
67
|
+
busy: { tone: "info", chip: "active" },
|
|
68
|
+
waiting: { tone: "warning", chip: "warning" },
|
|
69
|
+
missing: { tone: "quiet", chip: "inert" },
|
|
70
|
+
attention: { tone: "danger", chip: "failure" },
|
|
71
|
+
}),
|
|
72
|
+
severity: presentation({
|
|
73
|
+
info: { tone: "info", chip: "active" },
|
|
74
|
+
warning: { tone: "warning", chip: "warning" },
|
|
75
|
+
error: { tone: "danger", chip: "failure" },
|
|
76
|
+
}),
|
|
77
|
+
connection: presentation({
|
|
78
|
+
connected: { tone: "good", chip: "positive" },
|
|
79
|
+
connecting: { tone: "info", chip: "progress" },
|
|
80
|
+
paused: { tone: "quiet", chip: "inert" },
|
|
81
|
+
unavailable: { tone: "warning", chip: "warning" },
|
|
82
|
+
capacity: { tone: "warning", chip: "warning" },
|
|
83
|
+
disconnected: { tone: "warning", chip: "warning" },
|
|
84
|
+
stopped: { tone: "warning", chip: "warning" },
|
|
85
|
+
}),
|
|
86
|
+
};
|
|
87
|
+
const guidanceCopyKeys = {
|
|
88
|
+
reobserve_claude: "reobserveClaude",
|
|
89
|
+
reobserve_codex: "reobserveCodex",
|
|
90
|
+
codex_reactivation_required: "codexReactivationRequired",
|
|
91
|
+
consent_edge_unavailable: "consentEdgeUnavailable",
|
|
92
|
+
claude_not_observed: "claudeNotObserved",
|
|
93
|
+
codex_stale: "codexStale",
|
|
94
|
+
connector_offline: "connectorOffline",
|
|
95
|
+
route_stale: "routeStale",
|
|
96
|
+
queue_stalled: "queueStalled",
|
|
97
|
+
recipient_waiting_input: "recipientWaitingInput",
|
|
98
|
+
unconfirmed: "unconfirmed",
|
|
99
|
+
degraded: "degraded",
|
|
100
|
+
codex_succession_busy: "codexSuccessionBusy",
|
|
101
|
+
codex_succession_recovery: "codexSuccessionRecovery",
|
|
102
|
+
progress_watch: "progressWatch",
|
|
103
|
+
registry_empty: "registryEmpty",
|
|
104
|
+
registry_rejected: "registryRejected",
|
|
105
|
+
provider_incompatible: "providerIncompatible",
|
|
106
|
+
generic: "generic",
|
|
107
|
+
};
|
|
108
|
+
const nextActionCopyKeys = {
|
|
109
|
+
discover_claude: "next.discoverClaude",
|
|
110
|
+
select_claude: "next.selectClaude",
|
|
111
|
+
pair_routes: "next.pairRoutes",
|
|
112
|
+
restore_claude: "next.restoreClaude",
|
|
113
|
+
repair_claude_inventory: "next.repairClaude",
|
|
114
|
+
register_codex: "next.registerCodex",
|
|
115
|
+
restore_codex: "next.restoreCodex",
|
|
116
|
+
review_compatibility: "next.reviewCompatibility",
|
|
117
|
+
none: "next.none",
|
|
118
|
+
};
|
|
119
|
+
const attentionCommands = {
|
|
120
|
+
reobserve_claude: "embassy select-claude --alias {alias}",
|
|
121
|
+
reobserve_codex: "embassy register-codex --alias {alias}",
|
|
122
|
+
codex_reactivation_required: "embassy register-codex --alias {alias}",
|
|
123
|
+
consent_edge_unavailable: "embassy refresh-dashboard",
|
|
124
|
+
claude_not_observed: "embassy select-claude --alias {alias}",
|
|
125
|
+
codex_stale: "embassy register-codex --alias {alias}",
|
|
126
|
+
connector_offline: "embassy status",
|
|
127
|
+
route_stale: "embassy status",
|
|
128
|
+
queue_stalled: "embassy status",
|
|
129
|
+
recipient_waiting_input: "embassy status",
|
|
130
|
+
unconfirmed: "embassy status",
|
|
131
|
+
degraded: "embassy status",
|
|
132
|
+
codex_succession_busy: "embassy register-codex --alias <new> --succeeds {alias}",
|
|
133
|
+
codex_succession_recovery: "embassy register-codex --alias <new> --succeeds {alias}",
|
|
134
|
+
progress_watch: "embassy status",
|
|
135
|
+
registry_empty: "embassy refresh-dashboard",
|
|
136
|
+
registry_rejected: "embassy status",
|
|
137
|
+
provider_incompatible: "embassy refresh-dashboard",
|
|
138
|
+
generic: "embassy status",
|
|
139
|
+
};
|
|
140
|
+
const deliveryMeaningKeys = {
|
|
141
|
+
queued: "activity.meaning.queued",
|
|
142
|
+
duplicate: "activity.meaning.duplicate",
|
|
143
|
+
dispatching: "activity.meaning.dispatching",
|
|
144
|
+
transport_written: "activity.meaning.transportWritten",
|
|
145
|
+
held: "activity.meaning.held",
|
|
146
|
+
delivered: "activity.meaning.delivered",
|
|
147
|
+
unconfirmed: "activity.meaning.unconfirmed",
|
|
148
|
+
failed: "activity.meaning.failed",
|
|
149
|
+
ambiguous: "activity.meaning.ambiguous",
|
|
150
|
+
expired: "activity.meaning.expired",
|
|
151
|
+
cancelled: "activity.meaning.cancelled",
|
|
152
|
+
abandoned: "activity.meaning.abandoned.generic",
|
|
153
|
+
rejected: "activity.meaning.rejected",
|
|
154
|
+
};
|
|
155
|
+
/** JSON-safe semantics embedded once in the live dashboard boot payload. */
|
|
156
|
+
export const DASHBOARD_SEMANTICS = Object.freeze({
|
|
157
|
+
statePresentation,
|
|
158
|
+
deliveryChipByDirection: {
|
|
159
|
+
delivered: { codex_to_claude: "qualified" },
|
|
160
|
+
},
|
|
161
|
+
deliveryChipBySafeErrorCode: {
|
|
162
|
+
rejected: { SENDER_NOT_PAIRED: "inert" },
|
|
163
|
+
},
|
|
164
|
+
deliveryToneBySafeErrorCode: {
|
|
165
|
+
rejected: { SENDER_NOT_PAIRED: "quiet" },
|
|
166
|
+
},
|
|
167
|
+
guidanceCopyKeys,
|
|
168
|
+
nextActionCopyKeys,
|
|
169
|
+
attentionCommands,
|
|
170
|
+
attentionCommandFallbacks: {
|
|
171
|
+
codex_succession_busy: "<old>",
|
|
172
|
+
codex_succession_recovery: "<old>",
|
|
173
|
+
},
|
|
174
|
+
deliveryMeaningKeys,
|
|
175
|
+
deliveryMeaningByDirection: {
|
|
176
|
+
codex_to_claude: "activity.meaning.delivered.codexToClaude",
|
|
177
|
+
claude_to_codex: "activity.meaning.delivered.claudeToCodex",
|
|
178
|
+
},
|
|
179
|
+
deliveryMeaningBySafeErrorCode: {
|
|
180
|
+
SENDER_NOT_PAIRED: "activity.meaning.senderNotPaired",
|
|
181
|
+
CONTROLLER_RESTARTED: "activity.meaning.abandoned.controllerRestarted",
|
|
182
|
+
TRANSIENT_BODY_UNAVAILABLE: "activity.meaning.abandoned.transientBody",
|
|
183
|
+
ROUTE_UNREGISTERED: "activity.meaning.abandoned.routeTerminated",
|
|
184
|
+
MESSAGE_EXPIRED: "activity.meaning.abandoned.routeTerminated",
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
function semanticPresentation(domain, state) {
|
|
188
|
+
const table = DASHBOARD_SEMANTICS.statePresentation[domain];
|
|
189
|
+
return Object.prototype.hasOwnProperty.call(table, state)
|
|
190
|
+
? table[state]
|
|
191
|
+
: undefined;
|
|
192
|
+
}
|
|
193
|
+
export function dashboardChipKind(domain, state, direction, safeErrorCode) {
|
|
194
|
+
if (domain === "delivery") {
|
|
195
|
+
const byCode = DASHBOARD_SEMANTICS.deliveryChipBySafeErrorCode;
|
|
196
|
+
const byDirection = DASHBOARD_SEMANTICS.deliveryChipByDirection;
|
|
197
|
+
if (safeErrorCode !== undefined) {
|
|
198
|
+
const override = byCode[state]?.[safeErrorCode];
|
|
199
|
+
if (override !== undefined)
|
|
200
|
+
return override;
|
|
201
|
+
}
|
|
202
|
+
if (direction !== undefined) {
|
|
203
|
+
const override = byDirection[state]?.[direction];
|
|
204
|
+
if (override !== undefined)
|
|
205
|
+
return override;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return semanticPresentation(domain, state)?.chip ?? "unknown";
|
|
209
|
+
}
|
|
210
|
+
export function dashboardTone(domain, state, safeErrorCode) {
|
|
211
|
+
if (domain === "delivery" && safeErrorCode !== undefined) {
|
|
212
|
+
const byCode = DASHBOARD_SEMANTICS.deliveryToneBySafeErrorCode;
|
|
213
|
+
const override = byCode[state]?.[safeErrorCode];
|
|
214
|
+
if (override !== undefined)
|
|
215
|
+
return override;
|
|
216
|
+
}
|
|
217
|
+
return semanticPresentation(domain, state)?.tone ?? "danger";
|
|
218
|
+
}
|
|
219
|
+
export function deliveryMeaningKey(state, direction, safeErrorCode) {
|
|
220
|
+
if (state === "rejected" && safeErrorCode === "SENDER_NOT_PAIRED") {
|
|
221
|
+
return DASHBOARD_SEMANTICS.deliveryMeaningBySafeErrorCode.SENDER_NOT_PAIRED;
|
|
222
|
+
}
|
|
223
|
+
if (state === "delivered" && direction !== undefined) {
|
|
224
|
+
return DASHBOARD_SEMANTICS.deliveryMeaningByDirection[direction];
|
|
225
|
+
}
|
|
226
|
+
if (state === "abandoned" && safeErrorCode !== undefined) {
|
|
227
|
+
const byCode = DASHBOARD_SEMANTICS.deliveryMeaningBySafeErrorCode;
|
|
228
|
+
return byCode[safeErrorCode] ?? deliveryMeaningKeys.abandoned;
|
|
229
|
+
}
|
|
230
|
+
const byState = DASHBOARD_SEMANTICS.deliveryMeaningKeys;
|
|
231
|
+
return byState[state] ?? "activity.meaning.other";
|
|
232
|
+
}
|
|
233
|
+
export function guidanceCopyKey(guidance) {
|
|
234
|
+
return DASHBOARD_SEMANTICS.guidanceCopyKeys[guidance];
|
|
235
|
+
}
|
|
236
|
+
export function nextActionCopyKey(action) {
|
|
237
|
+
return DASHBOARD_SEMANTICS.nextActionCopyKeys[action];
|
|
238
|
+
}
|
|
239
|
+
export function attentionCommand(item) {
|
|
240
|
+
const fallbacks = DASHBOARD_SEMANTICS.attentionCommandFallbacks;
|
|
241
|
+
return DASHBOARD_SEMANTICS.attentionCommands[item.guidance].replaceAll("{alias}", item.alias ?? fallbacks[item.guidance] ?? "<alias>");
|
|
242
|
+
}
|
|
15
243
|
const SAFE_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;
|
|
16
244
|
const SAFE_PROTOCOL_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._+:/-]{0,47}$/;
|
|
17
245
|
const OPAQUE_SUFFIX_PATTERN = /^[a-f0-9]{6,12}$/i;
|
|
@@ -69,8 +297,15 @@ function normalizedCounters(counters) {
|
|
|
69
297
|
bytesAccepted: normalizedInteger(counters?.bytesAccepted) ?? 0,
|
|
70
298
|
};
|
|
71
299
|
}
|
|
300
|
+
export function buildDashboardViewModel(snapshot) {
|
|
301
|
+
return buildProjectedDashboardViewModel(snapshot, false);
|
|
302
|
+
}
|
|
303
|
+
export function buildLiveDashboardViewModel(snapshot) {
|
|
304
|
+
return buildProjectedDashboardViewModel(snapshot, true);
|
|
305
|
+
}
|
|
72
306
|
function routeIsReady(route) {
|
|
73
307
|
return (route.enabled &&
|
|
308
|
+
route.safeErrorCode !== "CODEX_WRITES_DISABLED" &&
|
|
74
309
|
route.compatibility === "compatible" &&
|
|
75
310
|
(route.state === "idle" || route.state === "busy"));
|
|
76
311
|
}
|
|
@@ -151,6 +386,14 @@ function guidanceFor(code, provider) {
|
|
|
151
386
|
case "ADAPTER_DEGRADED":
|
|
152
387
|
case "ROUTE_DEGRADED":
|
|
153
388
|
return "degraded";
|
|
389
|
+
case "CLAUDE_PEER_VERSION_UNSUPPORTED":
|
|
390
|
+
case "CLAUDE_VERSION_UNPARSEABLE":
|
|
391
|
+
case "CLAUDE_VERSION_CHECK_FAILED":
|
|
392
|
+
case "CLAUDE_VERSION_EVIDENCE_CONFLICT":
|
|
393
|
+
case "CLAUDE_VERSION_EVIDENCE_TOO_LARGE":
|
|
394
|
+
case "CODEX_APP_SERVER_VERSION_UNSUPPORTED":
|
|
395
|
+
case "CODEX_APP_SERVER_VERSION_UNPARSEABLE":
|
|
396
|
+
return "provider_incompatible";
|
|
154
397
|
default:
|
|
155
398
|
return "generic";
|
|
156
399
|
}
|
|
@@ -219,7 +462,7 @@ function messageKey(message) {
|
|
|
219
462
|
typeof message.messageIdSuffix === "string" ? message.messageIdSuffix : "",
|
|
220
463
|
].join("\0");
|
|
221
464
|
}
|
|
222
|
-
function buildMessageGroups(messages) {
|
|
465
|
+
function buildMessageGroups(messages, includeBodies) {
|
|
223
466
|
const grouped = new Map();
|
|
224
467
|
for (const message of messages) {
|
|
225
468
|
const key = messageKey(message);
|
|
@@ -282,7 +525,8 @@ function buildMessageGroups(messages) {
|
|
|
282
525
|
...(latest.steer === true ? { steer: true } : {}),
|
|
283
526
|
// Bodies are first-class observable data (CO #36); the display copy
|
|
284
527
|
// is bounded so one 16 KiB message cannot dominate the view model.
|
|
285
|
-
...(
|
|
528
|
+
...(includeBodies &&
|
|
529
|
+
typeof latest.body === "string" && latest.body.length > 0
|
|
286
530
|
? { body: boundedText(latest.body, 4096) }
|
|
287
531
|
: {}),
|
|
288
532
|
events: allEvents,
|
|
@@ -311,12 +555,23 @@ function buildMessageGroups(messages) {
|
|
|
311
555
|
activeGroups: all.filter((message) => ["queued", "dispatching", "transport_written", "held"].includes(message.state)).length,
|
|
312
556
|
};
|
|
313
557
|
}
|
|
314
|
-
|
|
558
|
+
function buildProjectedDashboardViewModel(snapshot, includeBodies) {
|
|
315
559
|
const generatedAt = normalizedTimestamp(snapshot.generatedAt);
|
|
316
560
|
const inboundMode = snapshot.inboundMode === "open" ? "open" : "paired";
|
|
317
561
|
const validPeers = arePublicAvailablePeerSnapshots(snapshot.availablePeers)
|
|
318
562
|
? snapshot.availablePeers
|
|
319
563
|
: [];
|
|
564
|
+
const quarantinedProviders = new Set([
|
|
565
|
+
...snapshot.connectors
|
|
566
|
+
.filter((connector) => connector.compatibility === "incompatible")
|
|
567
|
+
.map((connector) => connector.provider),
|
|
568
|
+
...(Array.isArray(snapshot.compatibilityChecks)
|
|
569
|
+
? snapshot.compatibilityChecks
|
|
570
|
+
.filter(isPublicCompatibilityCheckSnapshot)
|
|
571
|
+
.filter((check) => check.tier === "incompatible")
|
|
572
|
+
.map((check) => check.surface)
|
|
573
|
+
: []),
|
|
574
|
+
]);
|
|
320
575
|
const peers = validPeers
|
|
321
576
|
.map((peer) => ({
|
|
322
577
|
alias: boundedText(peer.alias),
|
|
@@ -403,7 +658,11 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
403
658
|
claudeAlias,
|
|
404
659
|
codexAlias,
|
|
405
660
|
host: boundedText(pair.host),
|
|
406
|
-
state: bothPresent &&
|
|
661
|
+
state: bothPresent &&
|
|
662
|
+
!quarantinedProviders.has("claude") &&
|
|
663
|
+
!quarantinedProviders.has("codex") &&
|
|
664
|
+
routeIsReady(claudeRoute) &&
|
|
665
|
+
routeIsReady(codexRoute)
|
|
407
666
|
? "ready"
|
|
408
667
|
: bothPresent
|
|
409
668
|
? "degraded"
|
|
@@ -425,9 +684,9 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
425
684
|
generatedAtMs === undefined) {
|
|
426
685
|
return [];
|
|
427
686
|
}
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
687
|
+
if (watch.nudgeCount !== 0 &&
|
|
688
|
+
watch.nudgeCount !== 1 &&
|
|
689
|
+
watch.nudgeCount !== 2) {
|
|
431
690
|
return [];
|
|
432
691
|
}
|
|
433
692
|
return [
|
|
@@ -435,11 +694,8 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
435
694
|
conversationIdSuffix: watch.conversationIdSuffix,
|
|
436
695
|
ownerAlias: boundedText(watch.ownerAlias),
|
|
437
696
|
workerAlias: boundedText(watch.workerAlias),
|
|
438
|
-
phase: watch.phase,
|
|
439
|
-
capability: watch.capability,
|
|
440
697
|
lastActivityAt,
|
|
441
698
|
nextActionAt,
|
|
442
|
-
idleMs,
|
|
443
699
|
idleForMs: Math.max(0, generatedAtMs - Date.parse(lastActivityAt)),
|
|
444
700
|
dueInMs: Math.max(0, Date.parse(nextActionAt) - generatedAtMs),
|
|
445
701
|
nudgeCount: watch.nudgeCount,
|
|
@@ -466,37 +722,52 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
466
722
|
ownerAlias: boundedText(event.ownerAlias),
|
|
467
723
|
workerAlias: boundedText(event.workerAlias),
|
|
468
724
|
kind: event.kind,
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
: { nudgeNumber: event.nudgeNumber }),
|
|
725
|
+
actor: event.actor,
|
|
726
|
+
...(event.reason === undefined ? {} : { reason: event.reason }),
|
|
472
727
|
},
|
|
473
728
|
];
|
|
474
729
|
})
|
|
475
730
|
.sort((left, right) => right.sequence - left.sequence);
|
|
476
731
|
const watchEvents = allWatchEvents.slice(0, DASHBOARD_MODEL_LIMITS.progressWatchEvents);
|
|
477
732
|
const connectors = snapshot.connectors
|
|
478
|
-
.map((connector) =>
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
:
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
733
|
+
.map((connector) => {
|
|
734
|
+
const registry = isPublicRegistryObservationSnapshot(connector.registry)
|
|
735
|
+
? connector.registry
|
|
736
|
+
: undefined;
|
|
737
|
+
return {
|
|
738
|
+
provider: connector.provider,
|
|
739
|
+
host: boundedText(connector.host),
|
|
740
|
+
health: connector.health,
|
|
741
|
+
compatibility: connector.compatibility,
|
|
742
|
+
...(safeProtocol(connector.protocol) === undefined
|
|
743
|
+
? {}
|
|
744
|
+
: { protocol: safeProtocol(connector.protocol) }),
|
|
745
|
+
...(safeProtocol(connector.protocolVersion) === undefined
|
|
746
|
+
? {}
|
|
747
|
+
: { protocolVersion: safeProtocol(connector.protocolVersion) }),
|
|
748
|
+
...(normalizedTimestamp(connector.lastSeenAt) === undefined
|
|
749
|
+
? {}
|
|
750
|
+
: { lastSeenAt: normalizedTimestamp(connector.lastSeenAt) }),
|
|
751
|
+
...(safeCode(connector.safeErrorCode) === undefined
|
|
752
|
+
? {}
|
|
753
|
+
: { safeErrorCode: safeCode(connector.safeErrorCode) }),
|
|
754
|
+
...(registry === undefined
|
|
755
|
+
? {}
|
|
756
|
+
: {
|
|
757
|
+
registry: {
|
|
758
|
+
entriesScanned: registry.entriesScanned,
|
|
759
|
+
parseableRecords: registry.parseableRecords,
|
|
760
|
+
parseableRecordSeenSinceBoot: registry.parseableRecordSeenSinceBoot,
|
|
761
|
+
rejected: registry.rejected.map((row) => ({ ...row })),
|
|
762
|
+
rejectedCodesOmitted: registry.rejectedCodesOmitted,
|
|
763
|
+
},
|
|
764
|
+
}),
|
|
765
|
+
};
|
|
766
|
+
})
|
|
496
767
|
.sort((left, right) => compareText(`${left.provider}\0${left.host}`, `${right.provider}\0${right.host}`))
|
|
497
768
|
.slice(0, DASHBOARD_MODEL_LIMITS.connectors);
|
|
498
769
|
const compatibilityChecks = (Array.isArray(snapshot.compatibilityChecks)
|
|
499
|
-
? snapshot.compatibilityChecks.filter(
|
|
770
|
+
? snapshot.compatibilityChecks.filter(isPublicCompatibilityCheckSnapshot)
|
|
500
771
|
: [])
|
|
501
772
|
.map((attestation) => {
|
|
502
773
|
const failed = attestation.probes.find((probe) => probe.outcome === "fail");
|
|
@@ -504,6 +775,8 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
504
775
|
return {
|
|
505
776
|
surface: attestation.surface,
|
|
506
777
|
version: boundedText(attestation.version),
|
|
778
|
+
testedVersion: boundedText(attestation.testedVersion),
|
|
779
|
+
supportedMajor: boundedText(attestation.supportedMajor),
|
|
507
780
|
tier: attestation.tier,
|
|
508
781
|
checkedAt: attestation.checkedAt,
|
|
509
782
|
...(failed === undefined ? {} : { failure: failed.name }),
|
|
@@ -512,7 +785,7 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
512
785
|
})
|
|
513
786
|
.sort((left, right) => compareText(left.surface, right.surface))
|
|
514
787
|
.slice(0, 2);
|
|
515
|
-
const messages = buildMessageGroups(snapshot.messages);
|
|
788
|
+
const messages = buildMessageGroups(snapshot.messages, includeBodies);
|
|
516
789
|
const queuedMessages = allRoutes.reduce((total, route) => boundedAdd(total, route.queueDepth), 0);
|
|
517
790
|
const queuedWithAge = allRoutes
|
|
518
791
|
.filter((route) => route.oldestQueuedAt !== undefined && route.queueAgeMs !== undefined)
|
|
@@ -521,52 +794,63 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
521
794
|
const activeDeliveries = messages.activeGroups;
|
|
522
795
|
const claudeRoutes = allRoutes.filter((route) => route.provider === "claude");
|
|
523
796
|
const codexRoutes = allRoutes.filter((route) => route.provider === "codex");
|
|
524
|
-
const
|
|
525
|
-
const
|
|
797
|
+
const monitorOnlyCodexRoutes = codexRoutes.filter((route) => route.safeErrorCode === "CODEX_WRITES_DISABLED");
|
|
798
|
+
const routeIsExchangeReady = (route) => !quarantinedProviders.has(route.provider) && routeIsReady(route);
|
|
799
|
+
const readyClaude = claudeRoutes.filter(routeIsExchangeReady).length;
|
|
800
|
+
const readyCodex = codexRoutes.filter(routeIsExchangeReady).length;
|
|
526
801
|
const readyPairs = allPairs.filter((pair) => pair.state === "ready");
|
|
527
802
|
const pairedClaude = new Set(allPairs.map((pair) => pair.claudeAlias));
|
|
528
803
|
const pairedCodex = new Set(allPairs.map((pair) => pair.codexAlias));
|
|
529
|
-
const readyPairedClaude = claudeRoutes.some((route) =>
|
|
530
|
-
const readyPairedCodex = codexRoutes.some((route) =>
|
|
804
|
+
const readyPairedClaude = claudeRoutes.some((route) => routeIsExchangeReady(route) && pairedClaude.has(route.alias));
|
|
805
|
+
const readyPairedCodex = codexRoutes.some((route) => routeIsExchangeReady(route) && pairedCodex.has(route.alias));
|
|
531
806
|
const pairCountIsLowerBound = (normalizedInteger(snapshot.truncation.pairs) ?? 0) > 0;
|
|
532
807
|
const hasPairEvidence = allPairs.length > 0 || pairCountIsLowerBound;
|
|
533
808
|
const graph = {
|
|
534
809
|
pairCount: allPairs.length,
|
|
535
810
|
readyPairCount: readyPairs.length,
|
|
536
811
|
pairCountIsLowerBound,
|
|
537
|
-
unpairedReadyClaude: claudeRoutes.filter((route) =>
|
|
538
|
-
unpairedReadyCodex: codexRoutes.filter((route) =>
|
|
812
|
+
unpairedReadyClaude: claudeRoutes.filter((route) => routeIsExchangeReady(route) && !pairedClaude.has(route.alias)).length,
|
|
813
|
+
unpairedReadyCodex: codexRoutes.filter((route) => routeIsExchangeReady(route) && !pairedCodex.has(route.alias)).length,
|
|
539
814
|
};
|
|
540
815
|
const selectedPeers = validPeers.filter((peer) => peer.selected && peerIsSelectable(peer));
|
|
541
816
|
const selectablePeers = validPeers.filter(peerIsSelectable);
|
|
542
|
-
const selectedClaudeCount =
|
|
817
|
+
const selectedClaudeCount = quarantinedProviders.has("claude")
|
|
818
|
+
? 0
|
|
819
|
+
: Math.max(selectedPeers.length, readyClaude);
|
|
543
820
|
const claudeStatus = partyStatus(claudeRoutes, readyClaude);
|
|
544
821
|
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
|
-
|
|
822
|
+
const claudeNextAction = quarantinedProviders.has("claude")
|
|
823
|
+
? "review_compatibility"
|
|
824
|
+
: selectedClaudeCount > 0 &&
|
|
825
|
+
(readyPairedClaude || (pairCountIsLowerBound && readyClaude > 0))
|
|
826
|
+
? "none"
|
|
827
|
+
: readyClaude > 0
|
|
828
|
+
? "pair_routes"
|
|
829
|
+
: validPeers.length === 0
|
|
830
|
+
? "discover_claude"
|
|
831
|
+
: selectablePeers.length > 0
|
|
832
|
+
? !hasPairEvidence
|
|
833
|
+
? "pair_routes"
|
|
834
|
+
: "restore_claude"
|
|
835
|
+
: selectedPeers.length > 0
|
|
836
|
+
? "restore_claude"
|
|
837
|
+
: "repair_claude_inventory";
|
|
838
|
+
const codexNextAction = quarantinedProviders.has("codex") || monitorOnlyCodexRoutes.length > 0
|
|
839
|
+
? "review_compatibility"
|
|
840
|
+
: readyPairedCodex || (pairCountIsLowerBound && readyCodex > 0)
|
|
841
|
+
? "none"
|
|
842
|
+
: readyCodex > 0
|
|
843
|
+
? "pair_routes"
|
|
844
|
+
: codexRoutes.length === 0
|
|
845
|
+
? "register_codex"
|
|
846
|
+
: "restore_codex";
|
|
566
847
|
const explicitAlerts = coalesceCodexReactivationAlerts(snapshot.alerts
|
|
567
848
|
.filter((alert) => alert.code !== "COMPATIBILITY_CERTIFICATION_FAILED")
|
|
568
849
|
.map((alert) => {
|
|
569
850
|
const code = safeCode(alert.code);
|
|
851
|
+
const route = code === "QUEUE_STALLED" && typeof alert.alias === "string"
|
|
852
|
+
? routeByAlias.get(alert.alias)
|
|
853
|
+
: undefined;
|
|
570
854
|
return {
|
|
571
855
|
kind: "alert",
|
|
572
856
|
...(code === undefined ? {} : { code }),
|
|
@@ -587,7 +871,19 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
587
871
|
...(typeof alert.host === "string"
|
|
588
872
|
? { host: boundedText(alert.host) }
|
|
589
873
|
: {}),
|
|
590
|
-
|
|
874
|
+
...(alert.provider === "codex" &&
|
|
875
|
+
typeof alert.host === "string" &&
|
|
876
|
+
route !== undefined &&
|
|
877
|
+
alert.host === route.host &&
|
|
878
|
+
route.provider === "codex" &&
|
|
879
|
+
route.state === "busy" &&
|
|
880
|
+
route.queueDepth > 0
|
|
881
|
+
? { queueDepth: route.queueDepth }
|
|
882
|
+
: {}),
|
|
883
|
+
guidance: alert.provider !== undefined &&
|
|
884
|
+
quarantinedProviders.has(alert.provider)
|
|
885
|
+
? "provider_incompatible"
|
|
886
|
+
: guidanceFor(code, alert.provider),
|
|
591
887
|
};
|
|
592
888
|
}), allRoutes).sort((left, right) => alertPriority(left.severity) - alertPriority(right.severity) ||
|
|
593
889
|
compareText(right.timestamp ?? "", left.timestamp ?? ""));
|
|
@@ -649,13 +945,11 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
649
945
|
attentionCandidates[existingIndex] = item;
|
|
650
946
|
}
|
|
651
947
|
for (const watch of allWatches) {
|
|
652
|
-
if (watch.
|
|
948
|
+
if (watch.nudgeCount === 0)
|
|
653
949
|
continue;
|
|
654
950
|
attentionCandidates.push({
|
|
655
951
|
kind: "watch",
|
|
656
|
-
code:
|
|
657
|
-
? "PROGRESS_WATCH_CAPABILITY_DEGRADED"
|
|
658
|
-
: "PROGRESS_WATCH_QUIET",
|
|
952
|
+
code: "PROGRESS_WATCH_QUIET",
|
|
659
953
|
severity: "warning",
|
|
660
954
|
timestamp: watch.lastActivityAt,
|
|
661
955
|
alias: watch.workerAlias,
|
|
@@ -664,7 +958,9 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
664
958
|
}
|
|
665
959
|
const representedScopes = new Set(attentionCandidates.map((item) => `${item.provider ?? ""}\0${item.alias ?? ""}\0${item.host ?? ""}`));
|
|
666
960
|
for (const route of allRoutes) {
|
|
667
|
-
if (routeIsReady(route)
|
|
961
|
+
if (routeIsReady(route) ||
|
|
962
|
+
quarantinedProviders.has(route.provider) ||
|
|
963
|
+
route.safeErrorCode === "CODEX_WRITES_DISABLED") {
|
|
668
964
|
continue;
|
|
669
965
|
}
|
|
670
966
|
const scope = `${route.provider}\0${route.alias}\0${route.host}`;
|
|
@@ -709,13 +1005,64 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
709
1005
|
: { timestamp: connector.lastSeenAt }),
|
|
710
1006
|
provider: connector.provider,
|
|
711
1007
|
host: connector.host,
|
|
712
|
-
guidance: connector.health === "offline"
|
|
1008
|
+
guidance: connector.health === "offline"
|
|
1009
|
+
? "connector_offline"
|
|
1010
|
+
: quarantinedProviders.has(connector.provider) ||
|
|
1011
|
+
connector.safeErrorCode === "CLAUDE_PEER_VERSION_UNSUPPORTED" ||
|
|
1012
|
+
connector.safeErrorCode === "CLAUDE_VERSION_UNPARSEABLE" ||
|
|
1013
|
+
connector.safeErrorCode === "CLAUDE_VERSION_CHECK_FAILED" ||
|
|
1014
|
+
connector.safeErrorCode === "CLAUDE_VERSION_EVIDENCE_CONFLICT" ||
|
|
1015
|
+
connector.safeErrorCode === "CLAUDE_VERSION_EVIDENCE_TOO_LARGE" ||
|
|
1016
|
+
connector.safeErrorCode === "CODEX_APP_SERVER_VERSION_UNSUPPORTED" ||
|
|
1017
|
+
connector.safeErrorCode === "CODEX_APP_SERVER_VERSION_UNPARSEABLE"
|
|
1018
|
+
? "provider_incompatible"
|
|
1019
|
+
: "degraded",
|
|
713
1020
|
});
|
|
714
1021
|
representedScopes.add(scope);
|
|
715
1022
|
}
|
|
1023
|
+
for (const connector of connectors) {
|
|
1024
|
+
const observation = connector.registry;
|
|
1025
|
+
if (observation === undefined)
|
|
1026
|
+
continue;
|
|
1027
|
+
if (observation.rejected.length > 0 ||
|
|
1028
|
+
observation.rejectedCodesOmitted > 0) {
|
|
1029
|
+
const item = {
|
|
1030
|
+
kind: "connector",
|
|
1031
|
+
code: observation.rejected.some((row) => row.safeErrorCode === "CLAUDE_REGISTRY_UNAVAILABLE")
|
|
1032
|
+
? "CLAUDE_REGISTRY_UNAVAILABLE"
|
|
1033
|
+
: "CLAUDE_REGISTRY_RECORDS_REJECTED",
|
|
1034
|
+
severity: "warning",
|
|
1035
|
+
provider: "claude",
|
|
1036
|
+
host: connector.host,
|
|
1037
|
+
guidance: "registry_rejected",
|
|
1038
|
+
};
|
|
1039
|
+
const duplicateIndex = attentionCandidates.findIndex((candidate) => candidate.code === item.code &&
|
|
1040
|
+
candidate.provider === item.provider &&
|
|
1041
|
+
candidate.alias === item.alias &&
|
|
1042
|
+
candidate.host === item.host);
|
|
1043
|
+
if (duplicateIndex === -1)
|
|
1044
|
+
attentionCandidates.push(item);
|
|
1045
|
+
else
|
|
1046
|
+
attentionCandidates[duplicateIndex] = item;
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
if (observation.parseableRecordSeenSinceBoot)
|
|
1050
|
+
continue;
|
|
1051
|
+
attentionCandidates.push({
|
|
1052
|
+
kind: "connector",
|
|
1053
|
+
code: observation.entriesScanned === 0
|
|
1054
|
+
? "CLAUDE_REGISTRY_EMPTY_SINCE_BOOT"
|
|
1055
|
+
: "CLAUDE_REGISTRY_NO_PARSEABLE_RECORD_SINCE_BOOT",
|
|
1056
|
+
severity: "warning",
|
|
1057
|
+
provider: "claude",
|
|
1058
|
+
host: connector.host,
|
|
1059
|
+
guidance: "registry_empty",
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
716
1062
|
if ((snapshot.health === "degraded" ||
|
|
717
1063
|
snapshot.health === "offline" ||
|
|
718
1064
|
snapshot.health === "incompatible") &&
|
|
1065
|
+
!(snapshot.health === "degraded" && quarantinedProviders.size > 0) &&
|
|
719
1066
|
!attentionCandidates.some((item) => item.kind === "broker")) {
|
|
720
1067
|
attentionCandidates.push({
|
|
721
1068
|
kind: "broker",
|
|
@@ -778,6 +1125,9 @@ export function buildDashboardViewModel(snapshot) {
|
|
|
778
1125
|
status: codexStatus,
|
|
779
1126
|
total: codexRoutes.length,
|
|
780
1127
|
ready: readyCodex,
|
|
1128
|
+
...(monitorOnlyCodexRoutes.length === 0
|
|
1129
|
+
? {}
|
|
1130
|
+
: { monitorOnly: monitorOnlyCodexRoutes.length }),
|
|
781
1131
|
countIsLowerBound: (normalizedInteger(snapshot.truncation.routes) ?? 0) > 0,
|
|
782
1132
|
...(codexRoutes[0]?.alias === undefined
|
|
783
1133
|
? {}
|