Show recent swarm mention handling

This commit is contained in:
Meta Vens 2026-07-06 22:13:23 +01:00
commit 1bfa4fc53c
2 changed files with 174 additions and 3 deletions

View file

@ -149,9 +149,11 @@ async function buildSnapshot() {
const recentEvents = Array.isArray(heartbeat.value?.history)
? heartbeat.value.history.slice(-24).reverse().map(projectHistoryEvent)
: [];
const recentMentionActivity = buildRecentMentionActivity(heartbeat.value?.history, pendingMentions);
const controls = projectControls(heartbeat.value?.controls);
const upcomingTurns = buildUpcomingTurns(participantSnapshots);
const lastShuffle = findLastShuffleEvent(recentEvents);
const lastMentionActivity = recentMentionActivity[0] ?? null;
return {
schemaVersion: snapshotSchemaId,
@ -188,6 +190,8 @@ async function buildSnapshot() {
participantCount: participantSnapshots.length,
activeTurnCount: activeTurns.length,
pendingMentionCount: pendingMentions.length,
recentMentionActivityCount: recentMentionActivity.length,
lastMentionActivity,
readyNowCount: readyNow.length,
nextIdentityId: nextParticipant?.identityId ?? null,
nextDisplayName: nextParticipant?.displayName ?? null,
@ -204,6 +208,7 @@ async function buildSnapshot() {
participants: participantSnapshots,
activeTurns,
pendingMentions: pendingMentions.map(projectPendingMention),
recentMentionActivity,
orchestrator: {
state: deriveOrchestratorState(organs),
organs,
@ -371,16 +376,94 @@ function projectHistoryEvent(event) {
return {
type: String(event?.type ?? "event"),
identityId: stringOrNull(event?.identityId),
observedAt: stringOrNull(event?.observedAt ?? event?.queuedAt ?? event?.appliedAt),
observedAt: stringOrNull(event?.observedAt ?? event?.queuedAt ?? event?.consumedAt ?? event?.appliedAt),
activeJobId: stringOrNull(event?.activeJobId),
statusPath: stringOrNull(event?.statusPath),
reason: stringOrNull(event?.reason),
pendingMentionCount: numberOrNull(event?.pendingMentionCount),
mentionCount: numberOrNull(event?.mentionCount),
nextTurnAt: numberOrNull(event?.nextTurnAt),
recoveryMinutes: numberOrNull(event?.recoveryMinutes),
};
}
function buildRecentMentionActivity(history, pendingMentions) {
const activity = [];
for (const mention of pendingMentions ?? []) {
const projected = projectPendingMention(mention);
activity.push({
kind: "pending",
identityId: projected.identityId,
sourceChannelId: projected.sourceChannelId,
sourceMessageId: projected.sourceMessageId,
observedAt: projected.createdAt,
mentionCount: 1,
prompt: projected.prompt,
});
}
const events = Array.isArray(history) ? history : [];
for (let index = events.length - 1; index >= 0 && activity.length < 16; index -= 1) {
const event = events[index];
const type = String(event?.type ?? "");
if (type === "pending_mentions_consumed") {
const mentions = Array.isArray(event?.mentions) ? event.mentions.map(projectMentionEventEntry) : [];
activity.push({
kind: "consumed",
identityId: stringOrNull(event?.identityId),
observedAt: stringOrNull(event?.consumedAt ?? event?.queuedAt),
activeJobId: stringOrNull(event?.activeJobId),
requestMessageId: stringOrNull(event?.requestMessageId),
mentionCount: numberOrNull(event?.mentionCount) ?? mentions.length,
sourceChannelId: mentions[0]?.sourceChannelId ?? null,
sourceMessageId: mentions[0]?.sourceMessageId ?? null,
prompt: mentions[0]?.prompt ?? "",
mentions,
});
} else if (type === "pending_mention_queued" || type === "pending_mention_duplicate") {
activity.push({
kind: type === "pending_mention_duplicate" ? "duplicate_intake" : "intake",
identityId: stringOrNull(event?.identityId),
sourceChannelId: stringOrNull(event?.sourceChannelId ?? event?.channelId),
sourceMessageId: stringOrNull(event?.sourceMessageId ?? event?.messageId),
observedAt: stringOrNull(event?.queuedAt ?? event?.observedAt),
mentionCount: 1,
prompt: "",
});
} else if (
type === "queued" &&
typeof event?.pendingMentionCount === "number" &&
event.pendingMentionCount > 0
) {
activity.push({
kind: "handled_job",
identityId: stringOrNull(event?.identityId),
observedAt: stringOrNull(event?.queuedAt ?? event?.observedAt),
activeJobId: stringOrNull(event?.activeJobId),
requestMessageId: stringOrNull(event?.requestMessageId),
mentionCount: numberOrNull(event?.pendingMentionCount),
prompt: "",
});
}
}
return activity
.sort((left, right) => timestampMs(right.observedAt) - timestampMs(left.observedAt))
.slice(0, 12);
}
function projectMentionEventEntry(mention) {
return {
id: stringOrNull(mention?.id),
sourceChannelId: stringOrNull(mention?.sourceChannelId ?? mention?.channelId),
sourceMessageId: stringOrNull(mention?.sourceMessageId ?? mention?.messageId),
authorId: stringOrNull(mention?.authorId),
authorName: stringOrNull(mention?.authorName),
createdAt: stringOrNull(mention?.createdAt ?? mention?.queuedAt),
prompt: truncate(String(mention?.prompt ?? mention?.visiblePrompt ?? mention?.content ?? ""), 220),
};
}
async function readIdentityMetadata(registryPathValue) {
const metadata = new Map();
const registryPath = resolveConfigPath(registryPathValue, resolve(repoRoot, ".voidbot", "private", "repo-discord-identities.json"));
@ -1069,6 +1152,8 @@ function buildEveInterfaceBinding(snapshot, eveState) {
participantCount: snapshot.summary?.participantCount ?? 0,
activeTurnCount: snapshot.summary?.activeTurnCount ?? 0,
pendingMentionCount: snapshot.summary?.pendingMentionCount ?? 0,
recentMentionActivityCount: snapshot.summary?.recentMentionActivityCount ?? 0,
lastMentionActivity: snapshot.summary?.lastMentionActivity ?? null,
nextIdentityId: snapshot.summary?.nextIdentityId ?? null,
nextDisplayName: snapshot.summary?.nextDisplayName ?? null,
lastTickAt: snapshot.summary?.lastTickAt ?? null,
@ -1139,6 +1224,7 @@ function buildEveProviderState(snapshot) {
const upcoming = Array.isArray(snapshot.upcomingTurns) ? snapshot.upcomingTurns : [];
const activeTurns = Array.isArray(snapshot.activeTurns) ? snapshot.activeTurns : [];
const pendingMentions = Array.isArray(snapshot.pendingMentions) ? snapshot.pendingMentions : [];
const recentMentionActivity = Array.isArray(snapshot.recentMentionActivity) ? snapshot.recentMentionActivity : [];
const recentEvents = Array.isArray(snapshot.recentEvents) ? snapshot.recentEvents : [];
const orchestrator = snapshot.orchestrator ?? {};
const organs = Array.isArray(orchestrator.organs) ? orchestrator.organs : [];
@ -1168,6 +1254,7 @@ function buildEveProviderState(snapshot) {
repoFaceOrgan,
swarmSurfaceOrgan,
pendingMentions,
recentMentionActivity,
activeTurns,
});
const nodes = [
@ -1265,7 +1352,7 @@ function buildEveProviderState(snapshot) {
eveMetric("agents", "Agents", summary.participantCount ?? participants.length),
eveMetric("ready", "Ready", summary.readyNowCount ?? 0),
eveMetric("active", "Active", activeTurns.length || summary.activeTurnCount || 0),
eveMetric("mentions", "Mentions", pendingMentions.length || summary.pendingMentionCount || 0),
eveMetric("mentions", "Mentions", mentionMetric(summary, recentMentionActivity)),
eveMetric("mesh", "Mesh write", snapshot.cultMesh?.writeStatus ?? "pending"),
eveMetric("provider", "Provider", route.serverStatus),
]),
@ -1373,7 +1460,13 @@ function buildEveProviderState(snapshot) {
role: "mono",
text: `${mention.identityId ?? "face"} / ${shortIso(mention.createdAt)}\n${compactText(mention.prompt ?? "", 150)}`,
}))
: [eveNode("pending-mentions-empty", "text", { text: "No pending mentions." })]),
: [eveNode("pending-mentions-empty", "text", { text: mentionEmptyText(summary, recentMentionActivity) })]),
...recentMentionActivity.slice(0, 5).map((activity, index) =>
eveNode(`recent-mention-activity-${index}`, "text", {
role: "mono",
text: mentionActivityText(activity),
}),
),
...recentEvents.slice(0, 4).map((event, index) =>
eveNode(`recent-event-${index}`, "text", {
role: "mono",
@ -1477,11 +1570,51 @@ function buildOperatorAlerts(snapshot, context) {
alerts.push(`Watchdog is ${context.watchdog?.lastStatus ?? "missing"}.`);
}
if ((context.pendingMentions?.length ?? 0) > 0) alerts.push(`${context.pendingMentions.length} pending mention(s) need routing.`);
else if ((context.recentMentionActivity?.length ?? 0) > 0) {
const latest = context.recentMentionActivity[0];
alerts.push(`Pending mentions are empty; latest mention activity was ${latest.kind ?? "seen"} for ${latest.identityId ?? "unknown"} at ${shortIso(latest.observedAt)}.`);
}
if ((context.activeTurns?.length ?? 0) > 0) alerts.push(`${context.activeTurns.length} active turn(s) are running.`);
if (!alerts.length) alerts.push("No immediate operator alerts.");
return alerts;
}
function mentionMetric(summary, recentMentionActivity) {
const pending = summary.pendingMentionCount ?? 0;
if (pending > 0) return `${pending} pending`;
const latest = recentMentionActivity?.[0] ?? summary.lastMentionActivity;
if (!latest) return "0 pending";
const count = latest.mentionCount ?? 1;
if (latest.kind === "consumed" || latest.kind === "handled_job") {
return `0 pending / ${count} handled`;
}
return `0 pending / ${latest.kind ?? "seen"}`;
}
function mentionEmptyText(summary, recentMentionActivity) {
const latest = recentMentionActivity?.[0] ?? summary.lastMentionActivity;
if (!latest) {
return "No pending mentions.";
}
return `No pending mentions. Recent activity: ${mentionActivitySummary(latest)}.`;
}
function mentionActivityText(activity) {
return `${mentionActivitySummary(activity)}\n${compactText(activity.prompt || activity.mentions?.[0]?.prompt || activity.activeJobId || activity.requestMessageId || "", 150)}`;
}
function mentionActivitySummary(activity) {
const label = activity.kind === "consumed"
? "consumed"
: activity.kind === "handled_job"
? "handled job"
: activity.kind === "duplicate_intake"
? "duplicate intake"
: activity.kind ?? "activity";
const count = activity.mentionCount && activity.mentionCount > 1 ? ` x${activity.mentionCount}` : "";
return `${label}${count} ${activity.identityId ?? "face"} / ${shortIso(activity.observedAt)}`;
}
function compactPath(value, maxLength = 74) {
const text = String(value ?? "");
if (text.length <= maxLength) return text;
@ -1599,6 +1732,11 @@ function shortIso(value) {
return `${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")} ${String(date.getHours()).padStart(2, "0")}:${String(date.getMinutes()).padStart(2, "0")}`;
}
function timestampMs(value) {
const parsed = Date.parse(value ?? "");
return Number.isFinite(parsed) ? parsed : 0;
}
function findSnapshotOrgan(organs, needle) {
const lower = String(needle).toLowerCase();
return organs.find((organ) =>
@ -2650,6 +2788,20 @@ function redactSnapshot(snapshot) {
identityId: mention.identityId,
createdAt: mention.createdAt,
}));
redacted.recentMentionActivity = redacted.recentMentionActivity.map((activity) => ({
kind: activity.kind,
identityId: activity.identityId,
observedAt: activity.observedAt,
mentionCount: activity.mentionCount,
}));
if (redacted.summary?.lastMentionActivity) {
redacted.summary.lastMentionActivity = {
kind: redacted.summary.lastMentionActivity.kind,
identityId: redacted.summary.lastMentionActivity.identityId,
observedAt: redacted.summary.lastMentionActivity.observedAt,
mentionCount: redacted.summary.lastMentionActivity.mentionCount,
};
}
redacted.orchestrator.organs = redacted.orchestrator.organs.map((organ) => ({
...organ,
lastLogPath: null,

View file

@ -334,6 +334,25 @@ async function main(): Promise<void> {
if (pendingMentions.length > 0) {
const consumedIds = new Set(pendingMentions.map((entry) => entry.id));
state.pendingMentions = state.pendingMentions.filter((entry) => !consumedIds.has(entry.id));
state.history.push({
type: "pending_mentions_consumed",
identityId: participant.identityId,
participantKind: participant.participantKind,
turnKind: participant.turnKind,
activeJobId: turn.activeJobId,
requestMessageId: turn.requestMessageId,
consumedAt: queuedAt,
mentionCount: pendingMentions.length,
mentions: pendingMentions.slice(-6).map((mention) => ({
id: mention.id,
channelId: mention.channelId,
messageId: mention.messageId,
authorId: mention.authorId,
authorName: mention.authorName,
queuedAt: mention.queuedAt,
visiblePrompt: collapseWhitespace(mention.visiblePrompt, 240),
})),
});
}
state.history.push({
type: "queued",