Surface live peer hooks for Face social reads
This commit is contained in:
parent
44be152744
commit
fb8b9bd48d
2 changed files with 63 additions and 1 deletions
|
|
@ -2201,17 +2201,23 @@ async function renderRepoFaceSocialOpportunitySurface(input: {
|
|||
const typedState = await loadVoidSelfStateTypedDocuments({ canonicalPath: statePath });
|
||||
const relations = collectRepoFaceSocialRelations(typedState);
|
||||
const unmappedPeers = collectUnmappedSocialPeers(input.identity, input.registryIdentities, relations);
|
||||
const messages = collectSocialOpportunityMessages(input);
|
||||
const unmappedPeerHooks = collectUnmappedPeerSocialHooks({
|
||||
identity: input.identity,
|
||||
unmappedPeers,
|
||||
messages,
|
||||
});
|
||||
if (relations.length === 0) {
|
||||
return [
|
||||
"No existing person-bond or person-status read matched this turn.",
|
||||
"Use the transcript as evidence only when someone gives a real hook for trust, irritation, protection, rivalry, respect, suspicion, deference, or a careful question.",
|
||||
...formatUnmappedPeerHookLines(unmappedPeerHooks),
|
||||
unmappedPeers.length > 0
|
||||
? `Unmapped registered peers: ${formatUnmappedPeers(unmappedPeers)}. Do not greet the whole roster at once.`
|
||||
: "No peer roster was available for comparison this turn.",
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
const messages = collectSocialOpportunityMessages(input);
|
||||
const hookedRelations = relations
|
||||
.map((relation) => ({
|
||||
relation,
|
||||
|
|
@ -2224,6 +2230,7 @@ async function renderRepoFaceSocialOpportunitySurface(input: {
|
|||
return [
|
||||
"Existing person-bonds/status reads have no matching speaker in the attached recent messages.",
|
||||
"Treat the transcript itself as possible fresh social evidence, not as an obligation to invent a relationship.",
|
||||
...formatUnmappedPeerHookLines(unmappedPeerHooks),
|
||||
unmappedPeers.length > 0
|
||||
? `Still-unmapped peers: ${formatUnmappedPeers(unmappedPeers)}. If one of them appears in the transcript, there is evidence available for a possible first read.`
|
||||
: "No unmapped registered peers were found for this turn.",
|
||||
|
|
@ -2239,6 +2246,7 @@ async function renderRepoFaceSocialOpportunitySurface(input: {
|
|||
}
|
||||
|
||||
if (unmappedPeers.length > 0) {
|
||||
lines.push(...formatUnmappedPeerHookLines(unmappedPeerHooks));
|
||||
lines.push(`Unmapped registered peers: ${formatUnmappedPeers(unmappedPeers)}.`);
|
||||
}
|
||||
|
||||
|
|
@ -2324,6 +2332,59 @@ function formatUnmappedPeers(peers: RepoDiscordIdentity[]): string {
|
|||
return peers.map((peer) => `${peer.displayName}/${peer.repoName}`).join(", ");
|
||||
}
|
||||
|
||||
function collectUnmappedPeerSocialHooks(input: {
|
||||
identity: RepoDiscordIdentity;
|
||||
unmappedPeers: RepoDiscordIdentity[];
|
||||
messages: Array<{ label: string; message: SourceMessage }>;
|
||||
}): Array<{ peer: RepoDiscordIdentity; hooks: Array<{ label: string; message: SourceMessage }> }> {
|
||||
const selfTokens = new Set(socialTargetTokens(input.identity.displayName, input.identity.id, input.identity.repoName));
|
||||
const peersByToken = new Map<string, RepoDiscordIdentity>();
|
||||
for (const peer of input.unmappedPeers) {
|
||||
for (const token of socialTargetTokens(peer.displayName, peer.id, peer.repoName)) {
|
||||
if (!selfTokens.has(token)) {
|
||||
peersByToken.set(token, peer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const byPeer = new Map<string, { peer: RepoDiscordIdentity; hooks: Array<{ label: string; message: SourceMessage }> }>();
|
||||
for (const entry of input.messages) {
|
||||
if (!entry.message.isBot || !entry.message.content.trim()) {
|
||||
continue;
|
||||
}
|
||||
const peer = peersByToken.get(normalizeSocialLabel(entry.message.authorName));
|
||||
if (!peer) {
|
||||
continue;
|
||||
}
|
||||
const bucket = byPeer.get(peer.id) ?? { peer, hooks: [] };
|
||||
bucket.hooks.push(entry);
|
||||
byPeer.set(peer.id, bucket);
|
||||
}
|
||||
|
||||
return [...byPeer.values()]
|
||||
.map((entry) => ({
|
||||
peer: entry.peer,
|
||||
hooks: entry.hooks.slice(-2),
|
||||
}))
|
||||
.sort((left, right) => left.peer.displayName.localeCompare(right.peer.displayName))
|
||||
.slice(0, 5);
|
||||
}
|
||||
|
||||
function formatUnmappedPeerHookLines(
|
||||
peerHooks: Array<{ peer: RepoDiscordIdentity; hooks: Array<{ label: string; message: SourceMessage }> }>,
|
||||
): string[] {
|
||||
if (peerHooks.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
"Possible first-read hooks from unmapped peers who actually spoke nearby:",
|
||||
...peerHooks.map((entry) =>
|
||||
`- ${entry.peer.displayName}: ${entry.hooks.map(formatSocialOpportunityMessage).join(" | ")}`,
|
||||
),
|
||||
"Use one of these only if it sparks a real first read, question, tease, alliance, irritation, or rivalry. Do not manufacture intimacy from a single quote.",
|
||||
];
|
||||
}
|
||||
|
||||
function collectSocialOpportunityMessages(input: {
|
||||
channelPlan: RepoFaceChannelPlan;
|
||||
recentMessages: SourceMessage[];
|
||||
|
|
|
|||
|
|
@ -269,3 +269,4 @@
|
|||
{"ts":"2026-05-22T14:09:25Z","type":"implementation","status":"ok","note":"Expanded repo Face state projection input and projector contract so volatile state is not crushed before the child sees it. The state packet now exposes all non-retired affect needs, bonds, status reads, mood dimensions, agency pressure, activation profile, runtime speaking/rest pressure, durable memories, short-term residue, incubations, candidate speech pressure, and recent speech residue in character-facing terms. Nibu projected memory grew from ~231 words to ~1094 words, and a mock dry run passed with a more specific witness-as-caste Aetheria turn."}
|
||||
{"ts":"2026-05-22T14:59:26Z","type":"implementation","status":"ok","note":"Repo Face state projection now imports existing interaction-memory pronoun guidance for visible humans and the owner profile, preventing projectors from guessing gender in relationship prose; Nibu projection smoke guards Metacrat she/her."}
|
||||
{"ts":"2026-05-22T15:48:15Z","type":"implementation","status":"ok","note":"Repo Face article publishing now crosses the parent Interpreter as an ARTICLE DSL block instead of the legacy VOIDBOT_REPO_IDENTITY_ARTICLE JSON sentinel. Deterministic worker code renders and validates YAML frontmatter, strips model-supplied body frontmatter, routes Aetheria articles to Aetheria/Articles and non-Aetheria blog posts to gamecult-site GameCult/Blog, and smoke coverage verifies the contract."}
|
||||
{"ts":"2026-05-22T15:59:05Z","type":"implementation","status":"ok","note":"Repo Face social affordances now include recent message hooks from unmapped registered peers who actually spoke nearby, instead of only listing peer names. A Libby-only dry run after the change routed cleanly and improved from zero social signal to five, with the Face forming live reads about Aqua/Epiphany rather than only repeating CultLib work doctrine."}
|
||||
|
|
|
|||
Loading…
Reference in a new issue