2026-04-24 13:17:00 +01:00
import "dotenv/config" ;
2026-05-21 11:40:23 +01:00
import { createHash } from "node:crypto" ;
2026-04-24 13:17:00 +01:00
import { mkdir , writeFile } from "node:fs/promises" ;
2026-05-19 12:23:22 +01:00
import { spawnSync } from "node:child_process" ;
2026-05-20 20:31:42 +01:00
import { isAbsolute , join , relative , resolve } from "node:path" ;
2026-04-24 13:17:00 +01:00
import { loadConfig } from "@voidbot/config" ;
import {
2026-05-20 20:31:42 +01:00
applyRepoFacePostFatigueAfterSpeech ,
2026-04-24 15:10:34 +01:00
type AuditLog ,
2026-04-24 13:17:00 +01:00
buildVoidMcpServerConfig ,
2026-04-24 15:10:34 +01:00
createStateStorage ,
2026-05-18 17:33:02 +01:00
applyVoidSelfStateOperation ,
2026-05-18 14:21:50 +01:00
findRepoDiscordIdentity ,
2026-05-21 09:26:04 +01:00
faceRegistryAsRepoDiscordRegistry ,
2026-05-18 14:21:50 +01:00
isRepoDiscordIdentityAllowedInChannel ,
2026-04-24 15:10:34 +01:00
type JobQueue ,
2026-05-21 09:26:04 +01:00
loadFaceIdentityRegistry ,
2026-04-24 13:17:00 +01:00
loadSystemMessageCatalog ,
2026-05-18 17:33:02 +01:00
resolveRepoFaceStatePath ,
2026-04-24 13:17:00 +01:00
type SystemMessageCatalog ,
} from "@voidbot/core" ;
import {
LocalLlmProvider ,
OpenAiApiProvider ,
OwnerCodexProvider ,
ProviderRegistry ,
} from "@voidbot/providers" ;
import {
2026-05-04 09:54:27 +01:00
filterPromptEchoHistoryResults ,
2026-04-24 13:17:00 +01:00
createTextEmbedder ,
createVectorStores ,
FileMessageArchiveRepository ,
FileSourceDocumentArchiveRepository ,
RetrievalService ,
2026-05-04 09:54:27 +01:00
searchHistoryWithArchiveFallback ,
2026-04-24 13:17:00 +01:00
SourceDocumentIngester ,
type ArchivedMessageRecord ,
} from "@voidbot/rag" ;
import {
type JobRecord ,
2026-05-21 09:26:04 +01:00
type ProviderAdapter ,
2026-04-24 13:17:00 +01:00
type ProviderArtifact ,
type ProviderNotificationIntent ,
2026-05-21 09:26:04 +01:00
type ProviderResponse ,
2026-05-21 10:26:07 +01:00
loadPromptTemplate ,
2026-04-24 13:17:00 +01:00
} from "@voidbot/shared" ;
2026-05-22 16:48:29 +01:00
import {
type RepoIdentityArticleIntent ,
isValidArticleDate ,
normalizeArticlePath ,
normalizeArticleSite ,
renderRepoIdentityArticleMarkdown ,
resolveArticleRepoRoot ,
validateRenderedArticleMarkdown ,
} from "./repo-face-article.js" ;
2026-06-03 02:59:54 +01:00
import {
REPO_IDENTITY_POST_SENTINEL ,
isNonPublicRepoIdentitySpeech ,
normalizePublicRepoIdentitySpeech ,
parseRepoIdentityPostIntents ,
type RepoIdentityPostIntent ,
} from "./repo-face-speech.js" ;
2026-04-24 13:17:00 +01:00
const config = loadConfig ( ) ;
2026-05-19 16:06:26 +01:00
const REPO_IDENTITY_PROPOSAL_PR_SENTINEL = "VOIDBOT_REPO_IDENTITY_PROPOSAL_PR:" ;
const REPO_IDENTITY_PR_COMMENT_SENTINEL = "VOIDBOT_REPO_IDENTITY_PR_COMMENT:" ;
2026-05-20 21:29:10 +01:00
const REPO_IDENTITY_UPDATE_REQUEST_SENTINEL = "VOIDBOT_REPO_IDENTITY_UPDATE_REQUEST:" ;
2026-05-20 21:45:56 +01:00
const REPO_IDENTITY_BIFROST_TOPIC_SENTINEL = "VOIDBOT_REPO_IDENTITY_BIFROST_TOPIC:" ;
2026-05-22 22:35:05 +01:00
const CURRENT_REPO_FACE_IDENTITY_SELECTORS = new Set ( [
"face_id" ,
"faceid" ,
"current_face" ,
"current_face_id" ,
"currentface" ,
"currentfaceid" ,
"current_identity" ,
"current_identity_id" ,
"currentidentity" ,
"currentidentityid" ,
"job_identity" ,
"job_identity_id" ,
"jobidentity" ,
"jobidentityid" ,
] ) ;
2026-05-21 18:45:24 +01:00
const REPO_FACE_FORBIDDEN_CHILD_TOOLS = new Set ( [
"read_repo_face_state" ,
"list_mcp_resources" ,
"read_mcp_resource" ,
"post_repo_identity_message" ,
"apply_repo_face_state_operation" ,
"notify_owner" ,
] ) ;
2026-05-02 20:11:49 +01:00
2026-04-24 13:17:00 +01:00
const embedder = createTextEmbedder ( {
backend : config.ragEmbeddingBackend ,
hashDimensions : config.ragEmbeddingDimensions ,
ollamaBaseUrl : config.ragOllamaBaseUrl ,
ollamaModel : config.ragOllamaModel ,
ollamaTimeoutMs : config.ragOllamaTimeoutMs ,
queryInstruction : config.ragQueryInstruction ,
} ) ;
const sourceQueryEmbedder = createTextEmbedder ( {
backend : config.ragEmbeddingBackend ,
hashDimensions : config.ragEmbeddingDimensions ,
ollamaBaseUrl : config.ragOllamaBaseUrl ,
ollamaModel : config.ragOllamaModel ,
ollamaTimeoutMs : config.ragOllamaTimeoutMs ,
queryInstruction : config.ragSourceQueryInstruction ,
} ) ;
const vectorStores = createVectorStores ( {
kind : config.vectorStore.kind ,
historyPath : config.vectorStore.path ,
2026-06-09 00:36:42 +01:00
personaMemoryPath : config.vectorStore.personaMemoryPath ,
2026-04-24 13:17:00 +01:00
sourceRoot : config.sourceVectorStoreRoot ,
qdrant : config.qdrant ,
historyEmbedder : embedder ,
sourceEmbedder : sourceQueryEmbedder ,
2026-06-09 00:36:42 +01:00
personaMemoryEmbedder : embedder ,
2026-04-24 13:17:00 +01:00
} ) ;
const historyVectorStore = vectorStores . history ;
const sourceVectorStore = vectorStores . source ;
2026-06-09 00:36:42 +01:00
const personaMemoryVectorStore = vectorStores . personaMemory ;
2026-04-24 13:17:00 +01:00
const archiveRepository = new FileMessageArchiveRepository ( config . ragArchivePath ) ;
const sourceArchiveRepository = new FileSourceDocumentArchiveRepository ( config . ragSourceArchivePath ) ;
const sourceDocumentIngester = new SourceDocumentIngester ( ) ;
const retrievalService = new RetrievalService (
historyVectorStore ,
sourceVectorStore ,
2026-06-09 00:36:42 +01:00
personaMemoryVectorStore ,
2026-04-24 13:17:00 +01:00
) ;
let isProcessing = false ;
let providerRegistry : ProviderRegistry ;
let systemMessages : SystemMessageCatalog ;
2026-04-24 15:10:34 +01:00
let jobQueue : JobQueue ;
let auditLog : AuditLog ;
2026-04-24 13:17:00 +01:00
void main ( ) . catch ( ( error ) = > {
console . error ( error ) ;
process . exitCode = 1 ;
} ) ;
async function main ( ) : Promise < void > {
2026-04-24 15:10:34 +01:00
const {
jobQueue : stateJobQueue ,
auditLog : stateAuditLog ,
} = await createStateStorage ( {
backend : config.stateStorageBackend ,
databaseDsn : config.databaseDsn ,
jobsFile : config.jobsFile ,
auditLogFile : config.auditLogFile ,
interactionMemoryFile : config.interactionMemoryFile ,
2026-04-26 01:39:42 +01:00
rateLimitStateFile : config.rateLimitStateFile ,
2026-04-24 15:10:34 +01:00
} ) ;
jobQueue = stateJobQueue ;
auditLog = stateAuditLog ;
2026-05-02 18:34:40 +01:00
systemMessages = await loadSystemMessageCatalog (
config . systemMessagesPath ,
resolve ( "config/system-messages.json" ) ,
) ;
2026-04-24 13:17:00 +01:00
providerRegistry = await buildProviderRegistry ( systemMessages ) ;
console . log ( ` VoidBot worker polling every ${ config . workerPollIntervalMs } ms. ` ) ;
2026-05-13 19:46:29 +01:00
await pollPendingJobs ( ) ;
2026-04-24 13:17:00 +01:00
setInterval ( ( ) = > {
2026-05-13 19:46:29 +01:00
void pollPendingJobs ( ) ;
2026-04-24 13:17:00 +01:00
} , config . workerPollIntervalMs ) ;
}
async function processPendingJobs ( ) : Promise < void > {
if ( isProcessing ) {
return ;
}
isProcessing = true ;
try {
2026-05-02 18:34:40 +01:00
systemMessages = await loadSystemMessageCatalog (
config . systemMessagesPath ,
resolve ( "config/system-messages.json" ) ,
) ;
2026-04-24 13:17:00 +01:00
providerRegistry = await buildProviderRegistry ( systemMessages ) ;
const jobs = await jobQueue . claimRunnableJobs ( ) ;
for ( const job of jobs ) {
await processJob ( job ) ;
}
} finally {
isProcessing = false ;
}
}
2026-05-13 19:46:29 +01:00
async function pollPendingJobs ( ) : Promise < void > {
try {
await processPendingJobs ( ) ;
} catch ( error ) {
const message =
error instanceof Error ? ` ${ error . name } : ${ error . message } ` : "Unexpected worker poll failure." ;
console . error ( ` Worker poll failed: ${ message } ` ) ;
}
}
2026-04-24 13:17:00 +01:00
async function buildProviderRegistry ( systemMessagesCatalog : SystemMessageCatalog ) : Promise < ProviderRegistry > {
return new ProviderRegistry ( [
new OwnerCodexProvider ( {
ownerDiscordId : config.ownerDiscordId ,
enabled : config.enabledProviders.includes ( "owner_codex" ) ,
mode : config.ownerCodexMode ,
executable : config.codexExecutable ,
executableArgs : config.codexExecArgs ,
2026-05-02 19:04:09 +01:00
model : config.codexModel ,
2026-04-24 13:17:00 +01:00
reasoningEffort : config.codexModelReasoningEffort ,
timeoutMs : config.codexExecTimeoutMs ,
workingDirectory : process.cwd ( ) ,
historyLookup : retrievalService ,
handoffNoticeBuilder : ( jobId ) = >
systemMessagesCatalog . render ( "owner_codex.handoff_notice" , {
handoffPath : ` .voidbot/artifacts/ ${ jobId } /handoff.md ` ,
debugPath : ` .voidbot/artifacts/ ${ jobId } /debug-trace.md ` ,
} ) ,
mcpServers : [ buildVoidMcpServerConfig ( process . cwd ( ) ) ] ,
} ) ,
new OpenAiApiProvider ( config . enabledProviders . includes ( "openai_api" ) ) ,
new LocalLlmProvider ( {
enabled : config.enabledProviders.includes ( "local_llm" ) ,
ownerDiscordId : config.ownerDiscordId ,
ollamaBaseUrl : config.localLlm.ollamaBaseUrl ,
ollamaModel : config.localLlm.ollamaModel ,
ollamaTimeoutMs : config.localLlm.ollamaTimeoutMs ,
ollamaKeepAlive : config.localLlm.ollamaKeepAlive ,
ollamaThink : config.localLlm.ollamaThink ,
ollamaNumCtx : config.localLlm.ollamaNumCtx ,
allowPublicResponses : config.localLlm.allowPublic ,
toolbox : {
listIndexedRepos : async ( ) = > {
const repos = await sourceArchiveRepository . listRepoSummaries ( ) ;
return {
repoCount : repos.length ,
repos ,
} ;
} ,
searchHistory : async ( input ) = > {
2026-05-04 09:54:27 +01:00
const rawResults = await searchHistoryWithArchiveFallback ( {
retrievalService ,
archiveRepository ,
query : input.query ,
limit : input.limit ,
2026-04-24 13:17:00 +01:00
guildId : input.guildId ,
channelId : input.channelId ,
authorId : input.authorId ,
2026-05-04 09:54:27 +01:00
preserveOverfetch : true ,
2026-04-24 13:17:00 +01:00
} ) ;
2026-05-02 20:11:49 +01:00
const results = filterPromptEchoHistoryResults ( rawResults , input . query ) ;
2026-04-24 13:17:00 +01:00
return {
query : input.query ,
resultCount : results.length ,
results : formatHistoryResults ( results ) ,
} ;
} ,
getMessageContext : async ( input ) = > {
const messages = await archiveRepository . listContextWindow (
input . messageId ,
input . before ,
input . after ,
) ;
return {
found : messages.length > 0 ,
messageId : input.messageId ,
count : messages.length ,
messages : messages.map ( ( message ) = >
formatArchivedMessageContext ( message , input . messageId ) ,
) ,
} ;
} ,
searchSources : async ( input ) = > {
const results = await retrievalService . searchRepositorySources ( input . query , input . limit , {
repoName : input.repoName ,
pathPrefix : input.pathPrefix ,
language : input.language ,
} ) ;
return {
query : input.query ,
resultCount : results.length ,
results : formatSourceResults ( results ) ,
} ;
} ,
getSourceContext : async ( input ) = > {
const document = await sourceArchiveRepository . get ( input . sourceId ) ;
if ( ! document ) {
return {
found : false ,
sourceId : input.sourceId ,
count : 0 ,
chunks : [ ] ,
} ;
}
const chunks = sourceDocumentIngester . buildContextWindow (
document ,
input . chunkIndex ,
input . before ,
input . after ,
) ;
return {
found : true ,
sourceId : input.sourceId ,
repoName : document.repoName ,
path : document.path ,
language : document.language ,
count : chunks.length ,
chunks : chunks.map ( ( chunk ) = > ( {
chunkId : chunk.chunkId ,
chunkIndex : chunk.chunkIndex ,
lineStart : chunk.lineStart ,
lineEnd : chunk.lineEnd ,
isAnchor : chunk.chunkIndex === input . chunkIndex ,
text : chunk.text ,
} ) ) ,
} ;
} ,
} ,
} ) ,
] ) ;
}
async function processJob ( job : JobRecord ) : Promise < void > {
const provider = providerRegistry . get ( job . provider ) ;
if ( ! provider || ! provider . isEnabled ( ) ) {
const errorMessage = ` Provider ${ job . provider } is not enabled. ` ;
await jobQueue . markFailed ( job . id , errorMessage ) ;
await maybeNotifyOwnerOfJobFailure ( job , errorMessage ) ;
return ;
}
try {
2026-05-21 09:26:04 +01:00
const response = job . command === "repo-face-rumination"
2026-05-21 10:51:18 +01:00
? await executeRepoFaceJobWithInterpreter ( provider , job )
2026-05-21 09:26:04 +01:00
: await executeProviderForJob ( provider , job , job . contextBundle ) ;
2026-04-24 13:17:00 +01:00
const artifactPaths = await writeArtifacts ( job . id , response . artifacts ? ? [ ] ) ;
if ( response . status === "ready_for_review" ) {
await jobQueue . markAwaitingPostApproval ( job . id , artifactPaths , response . summary ) ;
await deliverOwnerNotifications ( job , response . notifications ? ? [ ] ) ;
await auditLog . record ( {
type : "provider.bundle_ready" ,
actorId : job.requester.id ,
jobId : job.id ,
provider : job.provider ,
details : {
artifactPaths ,
} ,
} ) ;
console . log ( ` Prepared manual package for job ${ job . id } . ` ) ;
return ;
}
2026-05-21 19:51:52 +01:00
const rawFinalResponse = response . outputText ? ? response . summary ;
const finalResponse = fitDiscordMessage ( rawFinalResponse ) ;
2026-04-24 13:17:00 +01:00
if ( job . postApprovalRequired ) {
await jobQueue . markAwaitingPostApproval ( job . id , artifactPaths , response . summary ) ;
await deliverOwnerNotifications ( job , response . notifications ? ? [ ] ) ;
await auditLog . record ( {
type : "provider.completed_pending_post_approval" ,
actorId : job.requester.id ,
jobId : job.id ,
provider : job.provider ,
details : {
summary : response.summary ,
} ,
} ) ;
console . log ( ` Prepared response for post approval on job ${ job . id } . ` ) ;
return ;
}
2026-05-18 14:21:50 +01:00
if ( job . command === "repo-face-rumination" ) {
2026-05-21 19:51:52 +01:00
const repoFaceOutput = rawFinalResponse ;
const repoIdentityPosts = parseRepoIdentityPostIntents ( repoFaceOutput ) ;
const repoIdentityStateNotes = parseRepoIdentityStateNoteIntents ( repoFaceOutput ) ;
2026-05-21 20:48:02 +01:00
const repoIdentityBifrostTopics = config . repoFaceBifrostEnabled
? parseRepoIdentityBifrostTopicIntents ( repoFaceOutput )
: [ ] ;
const repoIdentityUpdateRequests = config . repoFaceBifrostEnabled
? parseRepoIdentityUpdateRequestIntents ( repoFaceOutput )
: [ ] ;
2026-05-19 16:30:51 +01:00
const repoIdentityArticles = config . repoFaceGithubActionsEnabled
2026-05-22 16:48:29 +01:00
? parseRepoFaceArticleIntents ( repoFaceOutput )
2026-05-19 16:30:51 +01:00
: [ ] ;
const repoIdentityProposals = config . repoFaceGithubActionsEnabled
2026-05-21 19:51:52 +01:00
? parseRepoIdentityProposalPrIntents ( repoFaceOutput )
2026-05-19 16:30:51 +01:00
: [ ] ;
const repoIdentityPrComments = config . repoFaceGithubActionsEnabled
2026-05-21 19:51:52 +01:00
? parseRepoIdentityPrCommentIntents ( repoFaceOutput )
2026-05-19 16:30:51 +01:00
: [ ] ;
const ignoredGithubActionIntents = config . repoFaceGithubActionsEnabled
? 0
2026-05-21 19:51:52 +01:00
: countRepoIdentityGithubActionIntents ( repoFaceOutput ) ;
2026-05-19 16:06:26 +01:00
const proposalPrSubmitted = repoIdentityProposals . length > 0 ;
const prCommentSubmitted = ! proposalPrSubmitted && repoIdentityPrComments . length > 0 ;
const articlePrSubmitted =
! proposalPrSubmitted && ! prCommentSubmitted && repoIdentityArticles . length > 0 ;
2026-05-20 21:45:56 +01:00
const bifrostTopicSubmitted =
2026-05-21 20:48:02 +01:00
config . repoFaceBifrostEnabled &&
! proposalPrSubmitted &&
! prCommentSubmitted &&
! articlePrSubmitted &&
repoIdentityBifrostTopics . length > 0 ;
2026-05-21 09:26:04 +01:00
const updateRequestRoutedToBifrostTopic =
2026-05-21 20:48:02 +01:00
config . repoFaceBifrostEnabled &&
2026-05-20 21:45:56 +01:00
! proposalPrSubmitted &&
! prCommentSubmitted &&
! articlePrSubmitted &&
! bifrostTopicSubmitted &&
repoIdentityUpdateRequests . length > 0 ;
2026-05-21 01:19:49 +01:00
console . log (
2026-05-21 11:40:23 +01:00
` Repo-face job ${ job . id } parsed actions: say= ${ repoIdentityPosts . length } , stateNote= ${ repoIdentityStateNotes . length } , bifrostTopic= ${ repoIdentityBifrostTopics . length } , updateRequest= ${ repoIdentityUpdateRequests . length } , article= ${ repoIdentityArticles . length } , proposalPr= ${ repoIdentityProposals . length } , prComment= ${ repoIdentityPrComments . length } . ` ,
2026-05-21 01:19:49 +01:00
) ;
2026-05-21 11:40:23 +01:00
for ( const stateNote of repoIdentityStateNotes . slice ( 0 , 4 ) ) {
await applyRepoIdentityStateNoteIntent ( job , stateNote ) ;
}
2026-05-19 16:06:26 +01:00
if ( repoIdentityProposals . length > 0 ) {
await writeRepoIdentityProposalPrIntent ( job , repoIdentityProposals [ 0 ] ) ;
} else if ( repoIdentityPrComments . length > 0 ) {
await commentRepoIdentityPullRequestIntent ( job , repoIdentityPrComments [ 0 ] ) ;
} else if ( repoIdentityArticles . length > 0 ) {
await writeRepoIdentityArticleIntent ( job , repoIdentityArticles [ 0 ] ) ;
2026-05-20 21:45:56 +01:00
} else if ( repoIdentityBifrostTopics . length > 0 ) {
await submitRepoIdentityBifrostTopicIntent ( job , repoIdentityBifrostTopics [ 0 ] ) ;
2026-05-20 21:29:10 +01:00
} else if ( repoIdentityUpdateRequests . length > 0 ) {
2026-05-21 09:26:04 +01:00
await submitRepoIdentityBifrostTopicIntent (
job ,
repoIdentityUpdateRequestToBifrostTopic ( repoIdentityUpdateRequests [ 0 ] ) ,
) ;
2026-05-19 12:23:22 +01:00
}
2026-05-21 09:26:04 +01:00
let repoIdentityPostDelivered = 0 ;
2026-05-21 20:25:36 +01:00
if ( ! proposalPrSubmitted && ! prCommentSubmitted && ! articlePrSubmitted ) {
2026-05-19 16:06:26 +01:00
for ( const post of repoIdentityPosts . slice ( 0 , 1 ) ) {
2026-05-21 09:26:04 +01:00
if ( await postRepoIdentityIntent ( job , post ) ) {
repoIdentityPostDelivered += 1 ;
}
2026-05-19 16:06:26 +01:00
}
2026-05-18 17:33:02 +01:00
}
2026-05-21 19:51:52 +01:00
const cleanedFinalResponse = stripRepoIdentityPostIntents ( repoFaceOutput ) ;
await jobQueue . completeJobDirect ( job . id , cleanedFinalResponse || repoFaceOutput ) ;
2026-05-18 10:39:02 +01:00
await deliverOwnerNotifications ( job , response . notifications ? ? [ ] ) ;
await auditLog . record ( {
type : "provider.completed" ,
actorId : job.requester.id ,
jobId : job.id ,
provider : job.provider ,
details : {
summary : response.summary ,
2026-05-19 16:06:26 +01:00
autoPosted :
2026-05-21 09:26:04 +01:00
repoIdentityPostDelivered > 0 ||
2026-05-19 16:06:26 +01:00
proposalPrSubmitted ||
prCommentSubmitted ||
2026-05-20 21:29:10 +01:00
articlePrSubmitted ||
2026-05-20 21:45:56 +01:00
bifrostTopicSubmitted ||
2026-05-21 09:26:04 +01:00
updateRequestRoutedToBifrostTopic ,
2026-05-19 16:06:26 +01:00
articlePrSubmitted ,
proposalPrSubmitted ,
prCommentSubmitted ,
2026-05-20 21:45:56 +01:00
bifrostTopicSubmitted ,
2026-05-21 09:26:04 +01:00
updateRequestRoutedToBifrostTopic ,
2026-05-19 16:30:51 +01:00
ignoredGithubActionIntents ,
2026-05-18 17:33:02 +01:00
reason :
2026-05-19 16:06:26 +01:00
proposalPrSubmitted
? "repo_face_rumination_submitted_registered_identity_proposal_pr"
: prCommentSubmitted
? "repo_face_rumination_commented_on_pr_as_registered_identity"
: articlePrSubmitted
2026-05-19 12:23:22 +01:00
? "repo_face_rumination_submitted_registered_identity_article_pr"
2026-05-20 21:45:56 +01:00
: bifrostTopicSubmitted
? "repo_face_rumination_submitted_bifrost_topic"
2026-05-21 09:26:04 +01:00
: updateRequestRoutedToBifrostTopic
? "repo_face_rumination_routed_legacy_update_request_to_bifrost_topic"
: repoIdentityPostDelivered > 0
2026-05-19 16:06:26 +01:00
? "repo_face_rumination_posted_as_registered_identity"
2026-05-21 09:26:04 +01:00
: repoIdentityPosts . length > 0
2026-05-21 10:51:18 +01:00
? "repo_face_rumination_speech_rejected_by_parent_interpreter"
2026-05-18 17:33:02 +01:00
: ` ${ job . command } _private_summary ` ,
2026-05-18 10:39:02 +01:00
} ,
} ) ;
2026-05-18 10:57:51 +01:00
console . log ( ` Completed ${ job . command } job ${ job . id } . ` ) ;
2026-05-18 10:39:02 +01:00
return ;
}
2026-04-24 13:17:00 +01:00
await postJobResponse ( job , finalResponse ) ;
await jobQueue . completeJobDirect ( job . id , finalResponse ) ;
await deliverOwnerNotifications ( job , response . notifications ? ? [ ] ) ;
await auditLog . record ( {
type : "provider.completed" ,
actorId : job.requester.id ,
jobId : job.id ,
provider : job.provider ,
details : {
summary : response.summary ,
autoPosted : true ,
} ,
} ) ;
console . log ( ` Completed job ${ job . id } . ` ) ;
} catch ( error ) {
const message = error instanceof Error ? error . message : "Unexpected worker failure." ;
await jobQueue . markFailed ( job . id , message ) ;
await maybeNotifyOwnerOfJobFailure ( job , message ) ;
await auditLog . record ( {
type : "provider.failed" ,
actorId : job.requester.id ,
jobId : job.id ,
provider : job.provider ,
details : {
error : message ,
} ,
} ) ;
console . error ( ` Failed job ${ job . id } : ${ message } ` ) ;
}
}
2026-05-21 09:26:04 +01:00
async function executeProviderForJob (
provider : ProviderAdapter ,
job : JobRecord ,
contextBundle : JobRecord [ "contextBundle" ] ,
2026-05-21 23:59:35 +01:00
role : "face" | "interpreter" = "face" ,
2026-05-21 09:26:04 +01:00
) : Promise < ProviderResponse > {
const request = provider . buildRequest ( contextBundle , {
jobId : job.id ,
command : job.command ,
2026-05-21 23:59:35 +01:00
. . . repoFaceHeartbeatCodexOptions ( job , role ) ,
2026-05-21 09:26:04 +01:00
} ) ;
return provider . execute ( request ) ;
}
2026-05-21 10:51:18 +01:00
async function executeRepoFaceJobWithInterpreter (
2026-05-21 09:26:04 +01:00
provider : ProviderAdapter ,
job : JobRecord ,
) : Promise < ProviderResponse > {
const firstResponse = await executeProviderForJob ( provider , job , job . contextBundle ) ;
if ( firstResponse . status !== "completed" ) {
return firstResponse ;
}
2026-05-21 22:19:25 +01:00
const firstText = normalizeModelText ( firstResponse . outputText ? ? firstResponse . summary ) ;
2026-05-21 18:45:24 +01:00
const firstForbiddenTools = collectForbiddenRepoFaceChildTools ( firstResponse . artifacts ? ? [ ] ) ;
const firstInterpretation = firstForbiddenTools . length > 0
? {
decision : "retry" as const ,
reasons : [
` Face used forbidden substrate/tool-discovery tool(s): ${ firstForbiddenTools . join ( ", " ) } ` ,
"Face turns may use retrieval tools only; state and substrate inventory belong to the parent/interpreter." ,
] ,
}
: await interpretRepoFaceTurnOutput ( provider , job , firstText , {
attempt : 1 ,
} ) ;
2026-05-21 10:51:18 +01:00
if ( firstInterpretation . decision === "route" ) {
2026-05-22 23:03:29 +01:00
return routeRepoFaceInterpretedOutput ( firstResponse , firstInterpretation , {
job ,
faceOutputText : firstText ,
} ) ;
2026-05-21 09:26:04 +01:00
}
2026-05-21 10:51:18 +01:00
if ( firstInterpretation . decision === "drop" ) {
return dropRepoFaceActionBlocks ( firstResponse , firstInterpretation ) ;
2026-05-21 09:26:04 +01:00
}
await auditLog . record ( {
2026-05-21 10:51:18 +01:00
type : "repo_face.parent_interpreter_retry" ,
2026-05-21 09:26:04 +01:00
actorId : job.requester.id ,
jobId : job.id ,
provider : job.provider ,
details : {
2026-05-21 10:51:18 +01:00
decision : firstInterpretation.decision ,
reasons : firstInterpretation.reasons ,
2026-05-21 09:26:04 +01:00
} ,
} ) ;
2026-05-21 10:51:18 +01:00
console . warn (
` Repo Face parent interpreter retrying job ${ job . id } : ${ firstInterpretation . reasons . join ( "; " ) } ` ,
) ;
2026-05-21 09:26:04 +01:00
2026-05-21 10:51:18 +01:00
const retryPrompt = loadPromptTemplate ( "repo-face-turn-interpreter-retry.prompt.md" , {
2026-05-21 10:26:07 +01:00
originalPrompt : job.contextBundle.prompt ,
2026-05-21 10:51:18 +01:00
reasons : firstInterpretation.reasons ,
2026-05-21 10:26:07 +01:00
} ) ;
2026-05-21 09:26:04 +01:00
const retryContext = {
. . . job . contextBundle ,
prompt : retryPrompt ,
createdAt : new Date ( ) . toISOString ( ) ,
} ;
const retryResponse = await executeProviderForJob ( provider , job , retryContext ) ;
if ( retryResponse . status !== "completed" ) {
return retryResponse ;
}
2026-05-21 22:19:25 +01:00
const retryText = normalizeModelText ( retryResponse . outputText ? ? retryResponse . summary ) ;
2026-05-21 18:45:24 +01:00
const retryForbiddenTools = collectForbiddenRepoFaceChildTools ( retryResponse . artifacts ? ? [ ] ) ;
const retryInterpretation = retryForbiddenTools . length > 0
? {
decision : "drop" as const ,
reasons : [
` Face used forbidden substrate/tool-discovery tool(s) on retry: ${ retryForbiddenTools . join ( ", " ) } ` ,
"Dropping action blocks rather than routing a turn that crossed the child/tool boundary." ,
] ,
}
: await interpretRepoFaceTurnOutput ( provider , job , retryText , {
attempt : 2 ,
} ) ;
2026-05-21 10:51:18 +01:00
if ( retryInterpretation . decision === "route" ) {
2026-05-22 23:03:29 +01:00
return routeRepoFaceInterpretedOutput ( retryResponse , retryInterpretation , {
job ,
faceOutputText : retryText ,
} ) ;
2026-05-21 09:26:04 +01:00
}
await auditLog . record ( {
2026-05-21 10:51:18 +01:00
type : "repo_face.parent_interpreter_drop" ,
2026-05-21 09:26:04 +01:00
actorId : job.requester.id ,
jobId : job.id ,
provider : job.provider ,
details : {
2026-05-21 10:51:18 +01:00
decision : retryInterpretation.decision ,
reasons : retryInterpretation.reasons ,
2026-05-21 09:26:04 +01:00
} ,
} ) ;
2026-05-21 10:51:18 +01:00
console . warn (
` Repo Face parent interpreter dropped job ${ job . id } action blocks: ${ retryInterpretation . reasons . join ( "; " ) } ` ,
) ;
return dropRepoFaceActionBlocks ( retryResponse , retryInterpretation ) ;
2026-05-21 09:26:04 +01:00
}
2026-05-21 10:51:18 +01:00
interface RepoFaceParentInterpretation {
2026-05-21 09:26:04 +01:00
decision : "route" | "retry" | "drop" ;
reasons : string [ ] ;
2026-05-21 10:43:26 +01:00
routedOutput? : string ;
2026-05-21 09:26:04 +01:00
}
2026-05-21 10:51:18 +01:00
async function interpretRepoFaceTurnOutput (
2026-05-21 09:26:04 +01:00
provider : ProviderAdapter ,
job : JobRecord ,
outputText : string ,
input : { attempt : 1 | 2 } ,
2026-05-21 10:51:18 +01:00
) : Promise < RepoFaceParentInterpretation > {
2026-06-09 01:27:58 +01:00
const dynamicMemoryRecall = await renderRepoFaceDynamicMemoryRecall ( job , outputText ) ;
2026-05-21 10:51:18 +01:00
const interpreterPrompt = loadPromptTemplate ( "repo-face-turn-interpreter.prompt.md" , {
2026-05-21 10:26:07 +01:00
attempt : input.attempt ,
2026-05-22 00:05:43 +01:00
facePrompt : renderRepoFaceInterpreterPromptContext ( job . contextBundle . prompt ) ,
2026-05-21 10:26:07 +01:00
faceOutput : outputText.slice ( 0 , 8000 ) ,
2026-06-09 01:27:58 +01:00
dynamicMemoryRecall ,
2026-05-21 10:26:07 +01:00
} ) ;
2026-05-21 10:51:18 +01:00
const interpreterContext = {
2026-05-21 09:26:04 +01:00
. . . job . contextBundle ,
2026-05-21 10:51:18 +01:00
prompt : interpreterPrompt ,
2026-05-21 09:26:04 +01:00
retrieval : [ ] ,
voidSelfState : undefined ,
createdAt : new Date ( ) . toISOString ( ) ,
} ;
2026-05-21 23:59:35 +01:00
const response = await executeProviderForJob ( provider , job , interpreterContext , "interpreter" ) ;
2026-05-21 22:19:25 +01:00
const interpretationText = normalizeModelText ( response . outputText ? ? response . summary ) ;
2026-05-21 10:51:18 +01:00
return parseRepoFaceParentInterpretation ( interpretationText , input . attempt ) ;
2026-05-21 09:26:04 +01:00
}
2026-06-09 01:27:58 +01:00
async function renderRepoFaceDynamicMemoryRecall (
job : JobRecord ,
outputText : string ,
) : Promise < string > {
const identityId = parseRepoIdentityIdFromRequestMessageId ( job . requestMessageId )
? ? parseRepoIdentityIdFromPrompt ( job . prompt ) ;
if ( ! identityId ) {
return "- Dynamic self-memory recall skipped: no Face identity could be resolved for this job." ;
}
const query = [
` Current train of thought from ${ identityId } : ` ,
outputText . slice ( 0 , 6000 ) ,
"Original turn pressure:" ,
extractSemanticRecallSeedFromPrompt ( job . contextBundle . prompt ) ,
] . join ( "\n" ) ;
try {
const results = await retrievalService . searchPersonaMemory ( query , 12 , {
identityId ,
} ) ;
if ( results . length === 0 ) {
return "- Dynamic self-memory recall ran, but no nearby Persona memories were found." ;
}
return [
"These are derived semantic hits from the Face's own typed memory, queried from the current Face output. They are hints for interpretation and state carry-forward; `.cc` remains the owner." ,
. . . results . map ( ( result , index ) = > {
const kind = result . metadata . memoryKind ? ` / ${ result . metadata . memoryKind } ` : "" ;
const target = result . metadata . targetLabel ? ? result . metadata . targetId ? ? "unknown target" ;
return ` - ${ index + 1 } . ${ target } ${ kind } score= ${ result . score . toFixed ( 3 ) } : ${ collapseWhitespace ( result . text , 560 ) } ` ;
} ) ,
] . join ( "\n" ) ;
} catch ( error ) {
const message = error instanceof Error ? error.message : String ( error ) ;
return [
"Dynamic self-memory recall unavailable:" ,
` - ${ collapseWhitespace ( message , 320 ) } ` ,
"- Do not pretend dynamic recall was available; interpret from the original prompt and Face output only." ,
] . join ( "\n" ) ;
}
}
function extractSemanticRecallSeedFromPrompt ( prompt : string ) : string {
const markers = [
"Semantic Persona memory recall:" ,
"Fresh projected state for this turn:" ,
"Recent conversation transcript:" ,
] ;
const sections : string [ ] = [ ] ;
for ( const marker of markers ) {
const start = prompt . indexOf ( marker ) ;
if ( start < 0 ) {
continue ;
}
const nextStarts = markers
. map ( ( candidate ) = > candidate === marker ? - 1 : prompt.indexOf ( candidate , start + marker . length ) )
. filter ( ( index ) = > index > start )
. sort ( ( left , right ) = > left - right ) ;
const end = nextStarts [ 0 ] ? ? Math . min ( prompt . length , start + 2400 ) ;
sections . push ( prompt . slice ( start , Math . min ( end , start + 2400 ) ) ) ;
}
return sections . join ( "\n\n" ) . slice ( 0 , 5000 ) ;
}
function collapseWhitespace ( input : string , maxLength = 1000 ) : string {
const collapsed = input . replace ( /\s+/g , " " ) . trim ( ) ;
return collapsed . length > maxLength ? ` ${ collapsed . slice ( 0 , Math . max ( 0 , maxLength - 3 ) ) } ... ` : collapsed ;
}
2026-05-21 22:19:25 +01:00
function normalizeModelText ( content : string ) : string {
return content . trim ( ) ;
}
2026-05-22 00:05:43 +01:00
function renderRepoFaceInterpreterPromptContext ( prompt : string ) : string {
if ( prompt . length <= 28000 ) {
return prompt ;
}
return [
prompt . slice ( 0 , 6000 ) ,
"\n\n[... middle of Face prompt omitted for Interpreter context budget ...]\n\n" ,
prompt . slice ( - 22000 ) ,
] . join ( "" ) ;
}
2026-05-21 10:51:18 +01:00
function parseRepoFaceParentInterpretation (
interpretationText : string ,
attempt : 1 | 2 ,
) : RepoFaceParentInterpretation {
const block = parseInterpretationBlock ( interpretationText ) ;
2026-05-21 09:26:04 +01:00
const decision = block . decision ? . trim ( ) . toLowerCase ( ) ;
const parsedDecision =
decision === "route" || decision === "retry" || decision === "drop"
? decision
: attempt === 1
? "retry"
: "drop" ;
const reasons = ( block . reason ? ? block . reasons ? ? "" )
. split ( /\r?\n/ )
. map ( ( line ) = > line . replace ( /^[-*]\s*/ , "" ) . trim ( ) )
. filter ( Boolean )
. slice ( 0 , 4 ) ;
return {
decision : parsedDecision ,
2026-05-21 10:51:18 +01:00
reasons : reasons.length > 0 ? reasons : [ "parent interpreter did not provide a parseable reason" ] ,
routedOutput : extractRoutedRepoFaceOutput ( interpretationText ) ,
2026-05-21 10:43:26 +01:00
} ;
}
2026-05-21 18:45:24 +01:00
function collectForbiddenRepoFaceChildTools ( artifacts : ProviderArtifact [ ] ) : string [ ] {
const forbidden = new Set < string > ( ) ;
for ( const artifact of artifacts ) {
if ( ! /^codex-turn-\d+-trace\.json$/ . test ( artifact . name ) ) {
continue ;
}
try {
const parsed = JSON . parse ( artifact . content ) as {
events? : Array < { kind? : string ; tool? : string } > ;
} ;
for ( const event of parsed . events ? ? [ ] ) {
if (
event . kind === "mcp_tool_started" &&
typeof event . tool === "string" &&
REPO_FACE_FORBIDDEN_CHILD_TOOLS . has ( event . tool )
) {
forbidden . add ( event . tool ) ;
}
}
} catch {
continue ;
}
}
return [ . . . forbidden ] . sort ( ) ;
}
2026-05-21 10:43:26 +01:00
function extractRoutedRepoFaceOutput ( reviewText : string ) : string | undefined {
const lines = reviewText . split ( /\r?\n/ ) ;
const end = lines . findIndex ( ( line ) = > line . trim ( ) . toUpperCase ( ) === "END" ) ;
if ( end < 0 ) {
return undefined ;
}
const routed = lines . slice ( end + 1 ) . join ( "\n" ) . trim ( ) ;
return routed . length > 0 ? routed : undefined ;
}
2026-05-21 10:51:18 +01:00
function routeRepoFaceInterpretedOutput (
2026-05-21 10:43:26 +01:00
response : ProviderResponse ,
2026-05-21 10:51:18 +01:00
interpretation : RepoFaceParentInterpretation ,
2026-05-22 23:03:29 +01:00
input : { job : JobRecord ; faceOutputText : string } ,
2026-05-21 10:43:26 +01:00
) : ProviderResponse {
2026-06-03 01:42:21 +01:00
const rawOutputText = interpretation . routedOutput ? . trim ( ) ? ? "" ;
2026-05-22 23:03:29 +01:00
const outputText = normalizeInterpretedRepoFaceSpeechDestinations ( rawOutputText , input ) ;
2026-05-21 10:51:18 +01:00
const summary = outputText || "Repo Face parent interpreter routed no public or governed action." ;
2026-05-21 10:43:26 +01:00
return {
. . . response ,
outputText : summary ,
summary ,
metadata : {
. . . ( response . metadata ? ? { } ) ,
2026-05-21 10:51:18 +01:00
repoFaceParentInterpreterDecision : interpretation.decision ,
repoFaceParentInterpreterReasons : interpretation.reasons.join ( " | " ) ,
repoFaceParentInterpretedOutput : interpretation.routedOutput ? "true" : "false" ,
2026-05-21 10:43:26 +01:00
} ,
2026-05-21 09:26:04 +01:00
} ;
}
2026-05-22 23:03:29 +01:00
function normalizeInterpretedRepoFaceSpeechDestinations (
outputText : string ,
input : { job : JobRecord ; faceOutputText : string } ,
) : string {
if ( input . job . command !== "repo-face-rumination" ) {
return outputText ;
}
if ( repoFaceOutputHasExplicitSayChannel ( input . faceOutputText ) ) {
return outputText ;
}
if ( ! input . job . guildContext ? . channelId ) {
return outputText ;
}
return rewriteRepoFaceSayChannels ( outputText , "current_room" ) ;
}
function repoFaceOutputHasExplicitSayChannel ( outputText : string ) : boolean {
return parseRepoFaceActionBlocks ( outputText ) . some ( ( block ) = >
block . kind === "say" &&
Boolean ( optionalDslString ( block . fields . channel ) ? ? optionalDslString ( block . fields . channelId ) )
) ;
}
function rewriteRepoFaceSayChannels ( outputText : string , channelSelector : string ) : string {
const lines = outputText . split ( /\r?\n/ ) ;
const rewritten : string [ ] = [ ] ;
let insideSay = false ;
let sawChannel = false ;
const insertChannelIfNeeded = ( ) : void = > {
if ( insideSay && ! sawChannel ) {
rewritten . push ( ` channel: ${ channelSelector } ` ) ;
sawChannel = true ;
}
} ;
for ( const line of lines ) {
const kind = parseRepoFaceActionKind ( line ) ;
if ( kind ) {
insertChannelIfNeeded ( ) ;
insideSay = kind === "say" ;
sawChannel = false ;
rewritten . push ( line ) ;
continue ;
}
if ( insideSay && line . trim ( ) === "END" ) {
insertChannelIfNeeded ( ) ;
insideSay = false ;
rewritten . push ( line ) ;
continue ;
}
if ( insideSay && /^channel(?:Id)?:/i . test ( line . trim ( ) ) ) {
rewritten . push ( ` channel: ${ channelSelector } ` ) ;
sawChannel = true ;
continue ;
}
rewritten . push ( line ) ;
if ( insideSay && /^identity:/i . test ( line . trim ( ) ) && ! sawChannel ) {
rewritten . push ( ` channel: ${ channelSelector } ` ) ;
sawChannel = true ;
}
}
insertChannelIfNeeded ( ) ;
return rewritten . join ( "\n" ) ;
}
2026-05-21 10:51:18 +01:00
function parseInterpretationBlock ( interpretationText : string ) : Record < string , string > {
const lines = interpretationText . split ( /\r?\n/ ) ;
const start = lines . findIndex ( ( line ) = >
[ "INTERPRETATION" , "REVIEW" , "ROUTE" ] . includes ( line . trim ( ) . toUpperCase ( ) ) ,
) ;
2026-05-21 09:26:04 +01:00
if ( start < 0 ) {
return { } ;
}
const body : string [ ] = [ ] ;
for ( let index = start + 1 ; index < lines . length ; index += 1 ) {
if ( lines [ index ] . trim ( ) . toUpperCase ( ) === "END" ) {
break ;
}
body . push ( lines [ index ] ) ;
}
return parseRepoFaceActionFields ( body ) ;
}
function dropRepoFaceActionBlocks (
response : ProviderResponse ,
2026-05-21 10:51:18 +01:00
interpretation : RepoFaceParentInterpretation ,
2026-05-21 09:26:04 +01:00
) : ProviderResponse {
const text = fitDiscordMessage ( response . outputText ? ? response . summary ) ;
const privateSummary = stripRepoIdentityPostIntents ( text ) ||
2026-05-21 10:51:18 +01:00
` Parent interpreter dropped repo Face action blocks: ${ interpretation . reasons . join ( "; " ) } ` ;
2026-05-21 09:26:04 +01:00
return {
. . . response ,
outputText : privateSummary ,
summary : privateSummary ,
metadata : {
. . . ( response . metadata ? ? { } ) ,
2026-05-21 10:51:18 +01:00
repoFaceParentInterpreterDecision : interpretation.decision ,
repoFaceParentInterpreterReasons : interpretation.reasons.join ( " | " ) ,
2026-05-21 09:26:04 +01:00
} ,
} ;
}
2026-05-21 23:59:35 +01:00
function repoFaceHeartbeatCodexOptions ( job : JobRecord , role : "face" | "interpreter" ) : Record < string , string > {
2026-05-21 00:14:07 +01:00
if ( job . command !== "repo-face-rumination" ) {
return { } ;
}
2026-05-21 23:59:35 +01:00
if ( role === "face" ) {
return {
model : config.repoFaceHeartbeats.turnCodexModel ,
. . . ( config . repoFaceHeartbeats . codexModelReasoningEffort
? { reasoningEffort : config.repoFaceHeartbeats.codexModelReasoningEffort }
: { } ) ,
} ;
}
2026-05-21 00:14:07 +01:00
return {
. . . ( config . repoFaceHeartbeats . codexModel ? { model : config.repoFaceHeartbeats.codexModel } : { } ) ,
2026-05-21 18:53:21 +01:00
. . . ( config . repoFaceHeartbeats . codexModels . length > 0
? { models : config.repoFaceHeartbeats.codexModels.join ( "," ) }
: { } ) ,
2026-05-21 00:14:07 +01:00
. . . ( config . repoFaceHeartbeats . codexModelReasoningEffort
? { reasoningEffort : config.repoFaceHeartbeats.codexModelReasoningEffort }
: { } ) ,
} ;
}
2026-04-24 13:17:00 +01:00
async function maybeNotifyOwnerOfJobFailure ( job : JobRecord , errorMessage : string ) : Promise < void > {
if ( job . requester . id !== config . ownerDiscordId ) {
return ;
}
try {
await postOwnerNotification (
systemMessages . render ( "job.owner.worker_failed_dm" , {
jobId : job.id ,
command : job.command ,
providerName : job.provider ,
errorMessage ,
} ) ,
) ;
} catch ( error ) {
console . error (
` Failed to DM owner about job ${ job . id } : ${
error instanceof Error ? error . message : "Unexpected notification failure."
} ` ,
) ;
}
}
async function deliverOwnerNotifications (
job : JobRecord ,
notifications : ProviderNotificationIntent [ ] ,
) : Promise < void > {
if ( notifications . length === 0 ) {
return ;
}
for ( const notification of notifications ) {
try {
await postOwnerNotification ( notification . message ) ;
await auditLog . record ( {
type : "provider.notification_sent" ,
actorId : job.requester.id ,
jobId : job.id ,
provider : job.provider ,
details : {
channel : notification.channel ,
reason : notification.reason ,
message : notification.message ,
} ,
} ) ;
} catch ( error ) {
const message =
error instanceof Error ? error . message : "Unexpected notification delivery failure." ;
await auditLog . record ( {
type : "provider.notification_failed" ,
actorId : job.requester.id ,
jobId : job.id ,
provider : job.provider ,
details : {
channel : notification.channel ,
reason : notification.reason ,
message : notification.message ,
error : message ,
} ,
} ) ;
console . error ( ` Failed owner notification for job ${ job . id } : ${ message } ` ) ;
}
}
}
async function writeArtifacts (
jobId : string ,
artifacts : ProviderArtifact [ ] ,
) : Promise < Record < string , string > > {
if ( artifacts . length === 0 ) {
return { } ;
}
const artifactDirectory = join ( config . artifactsDir , jobId ) ;
await mkdir ( artifactDirectory , { recursive : true } ) ;
const paths : Record < string , string > = { } ;
for ( const artifact of artifacts ) {
const filePath = join ( artifactDirectory , artifact . name ) ;
await writeFile ( filePath , artifact . content , "utf8" ) ;
paths [ artifact . name ] = filePath ;
}
return paths ;
}
async function postJobResponse ( job : JobRecord , content : string ) : Promise < void > {
if ( ! config . botToken ) {
throw new Error ( "DISCORD_BOT_TOKEN is required for the worker to post completed responses." ) ;
}
const response = await fetch ( ` https://discord.com/api/v10/channels/ ${ job . outputChannelId } /messages ` , {
method : "POST" ,
headers : {
Authorization : ` Bot ${ config . botToken } ` ,
"Content-Type" : "application/json" ,
} ,
body : JSON.stringify ( {
content ,
message_reference : job.requestMessageId
? {
message_id : job.requestMessageId ,
fail_if_not_exists : false ,
}
: undefined ,
allowed_mentions : {
parse : [ ] ,
} ,
} ) ,
} ) ;
if ( ! response . ok ) {
throw new Error ( ` Discord post failed with ${ response . status } : ${ await response . text ( ) } ` ) ;
}
}
2026-05-18 14:21:50 +01:00
function parseRepoIdentityIdFromPrompt ( prompt : string ) : string | undefined {
const match = prompt . match ( /repo identity\s+.+?\(([^)]+)\)\s+for repo/i ) ;
2026-05-18 17:33:02 +01:00
return (
match ? . [ 1 ] ? . trim ( ) ? ?
2026-05-21 09:26:04 +01:00
prompt . match ( /repo Face turn for .+?\(([^)]+)\) over repo/i ) ? . [ 1 ] ? . trim ( ) ? ?
2026-05-18 17:33:02 +01:00
prompt . match ( /repo Face heartbeat for .+?\(([^)]+)\) over repo/i ) ? . [ 1 ] ? . trim ( ) ? ?
prompt . match ( /identity "([^"]+)"/i ) ? . [ 1 ] ? . trim ( )
) ;
}
2026-05-21 11:40:23 +01:00
interface RepoIdentityStateNoteIntent {
identity? : string ;
kind : "memory" | "need" | "bond" | "status" | "mood" | "agency" ;
target? : string ;
summary : string ;
claim? : string ;
question? : string ;
tension? : string ;
action? : string ;
stance? : string ;
status? : string ;
mood? : string ;
intensity? : number ;
valence? : number ;
}
2026-05-19 16:06:26 +01:00
interface RepoIdentityProposalPrIntent {
identity? : string ;
title : string ;
path? : string ;
content : string ;
shareContent? : string ;
channelId? : string ;
replyToMessageId? : string ;
}
interface RepoIdentityPrCommentIntent {
identity? : string ;
pr : string ;
content : string ;
shareContent? : string ;
channelId? : string ;
replyToMessageId? : string ;
}
2026-05-20 21:29:10 +01:00
interface RepoIdentityUpdateRequestIntent {
identity? : string ;
title : string ;
content : string ;
priority? : number ;
sourceMessageIds : string [ ] ;
channelId? : string ;
replyToMessageId? : string ;
}
2026-05-20 21:45:56 +01:00
interface RepoIdentityBifrostTopicIntent {
identity? : string ;
topicId? : string ;
title? : string ;
content : string ;
2026-05-20 21:55:28 +01:00
mirrorContent? : string ;
2026-05-20 21:45:56 +01:00
stance? : string ;
priority? : number ;
approve? : boolean ;
dispatch? : boolean ;
sourceMessageIds : string [ ] ;
channelId? : string ;
replyToMessageId? : string ;
}
2026-05-20 20:31:42 +01:00
interface BifrostBridgeReceipt {
action? : string ;
ok? : boolean ;
branch? : string ;
prUrl? : string ;
messageId? : string ;
transport ? : "bot" | "webhook" ;
}
2026-05-20 21:29:10 +01:00
interface BifrostUpdateRequestReceipt {
id : string ;
status : "queued" | "claimed" | "completed" | "cancelled" ;
title : string ;
targetRepoName : string ;
targetAgentIdentity? : string ;
priority : number ;
}
2026-05-20 21:45:56 +01:00
interface BifrostGovernanceTopicReceipt {
id : string ;
status : string ;
title : string ;
jurisdictionRepoName : string ;
jurisdictionAgentIdentity? : string ;
dispatchRequestId? : string ;
}
2026-05-21 09:26:04 +01:00
async function postRepoIdentityIntent ( job : JobRecord , intent : RepoIdentityPostIntent ) : Promise < boolean > {
2026-05-18 17:33:02 +01:00
if ( ! config . botToken ) {
2026-05-20 20:31:42 +01:00
throw new Error ( "DISCORD_BOT_TOKEN is required for Bifrost to post repo identity responses." ) ;
2026-05-18 17:33:02 +01:00
}
2026-05-21 09:26:04 +01:00
const identity = await resolveRepoIdentityForJobIntent ( job , intent . identity ) ;
if ( ! identity ) {
2026-05-21 19:51:52 +01:00
console . warn ( ` Rejected repo identity speech for job ${ job . id } : could not resolve identity. ` ) ;
return false ;
2026-05-18 17:33:02 +01:00
}
2026-05-22 23:03:29 +01:00
const requestedChannelId = intent . channelId ? ? repoIdentityDefaultSpeechChannel ( job ) ;
2026-05-21 21:45:31 +01:00
const channelId = normalizeRepoIdentitySpeechChannel ( identity , job , requestedChannelId ) ;
2026-05-21 19:51:52 +01:00
if ( ! channelId ) {
console . warn ( ` Rejected repo identity ${ identity . id } speech for job ${ job . id } : no Discord channel was available. ` ) ;
return false ;
}
2026-05-21 20:48:02 +01:00
if ( requestedChannelId && requestedChannelId !== channelId ) {
console . warn (
2026-05-22 23:03:29 +01:00
` Coerced repo identity ${ identity . id } speech for job ${ job . id } from " ${ requestedChannelId } " to conversation channel ${ channelId } . ` ,
2026-05-21 20:48:02 +01:00
) ;
}
2026-06-03 02:59:54 +01:00
const rawContent = intent . content . trim ( ) ;
if ( isNonPublicRepoIdentitySpeech ( rawContent ) ) {
throw new Error ( ` Repo identity ${ identity . id } SAY content is not public speech. ` ) ;
}
const content = normalizePublicRepoIdentitySpeech ( rawContent ) ;
if ( isNonPublicRepoIdentitySpeech ( content ) ) {
throw new Error ( ` Repo identity ${ identity . id } SAY content is not public speech. ` ) ;
}
2026-05-21 19:51:52 +01:00
if ( isOwnerDmChannelAlias ( channelId ) ) {
const dmChannelId = await openOwnerDmChannel ( ) ;
2026-05-21 20:48:02 +01:00
const postedMessages = await postDiscordBotMessageChunks (
2026-05-21 19:51:52 +01:00
dmChannelId ,
renderRepoIdentityOwnerDm ( identity , content ) ,
) ;
2026-05-21 20:48:02 +01:00
const lastPosted = postedMessages [ postedMessages . length - 1 ] ;
if ( ! lastPosted ) {
throw new Error ( ` Owner DM posting for repo identity ${ identity . id } returned no message receipt. ` ) ;
}
2026-05-21 19:51:52 +01:00
await recordRepoIdentityDeliveryReceipt ( {
identity ,
channelId : dmChannelId ,
content ,
replyToMessageId : undefined ,
2026-05-21 20:48:02 +01:00
messageId : lastPosted?.id ,
2026-05-21 19:51:52 +01:00
transport : "bot" ,
} ) . catch ( ( error : unknown ) = > {
const message = error instanceof Error ? error.message : String ( error ) ;
console . warn ( ` Could not record repo identity owner DM receipt for job ${ job . id } : ${ message } ` ) ;
} ) ;
2026-05-21 20:48:02 +01:00
console . log (
` Posted repo identity ${ identity . id } to owner DM from job ${ job . id } as ${ postedMessages . length } message(s). ` ,
) ;
2026-05-21 19:51:52 +01:00
return true ;
}
2026-05-18 17:33:02 +01:00
if ( ! isRepoDiscordIdentityAllowedInChannel ( identity , channelId ) ) {
2026-05-21 19:51:52 +01:00
console . warn (
` Rejected repo identity ${ identity . id } speech for job ${ job . id } : identity is not registered for Discord channel ${ channelId } . ` ,
) ;
return false ;
2026-05-18 17:33:02 +01:00
}
2026-05-21 22:19:25 +01:00
if ( content . length > 1900 ) {
throw new Error (
` Repo identity ${ identity . id } SAY content is too long for one Discord message ( ${ content . length } characters). ` ,
) ;
}
if ( /\S(?:\.\.\.|…)$/ . test ( content ) ) {
throw new Error ( ` Repo identity ${ identity . id } SAY content appears mechanically truncated. ` ) ;
}
const contentFile = await writeBifrostPayloadFile ( job , ` ${ identity . id } -discord-post.md ` , content ) ;
2026-05-20 20:31:42 +01:00
const posted = runBifrostBridge ( [
"discord-post" ,
"--channel-id" ,
2026-05-18 17:33:02 +01:00
channelId ,
2026-05-20 20:31:42 +01:00
"--content-file" ,
contentFile ,
"--persona-name" ,
identity . displayName ,
. . . ( identity . avatarUrl ? [ "--persona-avatar-url" , identity . avatarUrl ] : [ ] ) ,
. . . ( intent . replyToMessageId ? [ "--reply-to-message-id" , intent . replyToMessageId ] : [ ] ) ,
2026-05-24 03:55:13 +01:00
] , { retries : 1 } ) ;
2026-05-20 20:31:42 +01:00
if ( ! posted . messageId || ! posted . transport ) {
throw new Error ( ` Bifrost Discord bridge returned no message receipt for job ${ job . id } . ` ) ;
}
2026-05-18 17:33:02 +01:00
await recordRepoIdentityDeliveryReceipt ( {
identity ,
channelId ,
2026-05-19 13:56:40 +01:00
content ,
2026-05-18 17:33:02 +01:00
replyToMessageId : intent.replyToMessageId ,
2026-05-20 20:31:42 +01:00
messageId : posted.messageId ,
2026-05-18 17:33:02 +01:00
transport : posted.transport ,
} ) . catch ( ( error : unknown ) = > {
const message = error instanceof Error ? error.message : String ( error ) ;
console . warn ( ` Could not record repo identity delivery receipt for job ${ job . id } : ${ message } ` ) ;
} ) ;
2026-05-21 01:19:49 +01:00
console . log (
` Posted repo identity ${ identity . id } to Discord channel ${ channelId } from job ${ job . id } via ${ posted . transport } message ${ posted . messageId } . ` ,
) ;
2026-05-21 09:26:04 +01:00
return true ;
2026-05-18 17:33:02 +01:00
}
2026-05-21 09:26:04 +01:00
async function resolveRepoIdentityForJobIntent (
job : JobRecord ,
identitySelector? : string ,
) : Promise < NonNullable < ReturnType < typeof findRepoDiscordIdentity > > | undefined > {
2026-05-21 21:45:31 +01:00
const jobIdentityId = job . command === "repo-face-rumination"
? parseRepoIdentityIdFromRequestMessageId ( job . requestMessageId ) ? ? parseRepoIdentityIdFromPrompt ( job . prompt )
: undefined ;
2026-05-22 22:35:05 +01:00
const identitySelectorIsCurrentFaceAlias = isCurrentRepoFaceIdentitySelector ( identitySelector ) ;
2026-05-21 21:45:31 +01:00
if (
jobIdentityId &&
identitySelector &&
2026-05-22 22:35:05 +01:00
! identitySelectorIsCurrentFaceAlias &&
2026-05-21 21:45:31 +01:00
normalizeChannelSelector ( jobIdentityId ) !== normalizeChannelSelector ( identitySelector )
) {
console . warn (
` Ignoring mismatched repo identity selector " ${ identitySelector } " for repo-face job ${ job . id } ; job identity is " ${ jobIdentityId } ". ` ,
) ;
}
2026-05-21 11:40:23 +01:00
const identityId =
2026-05-21 21:45:31 +01:00
jobIdentityId ? ?
2026-05-22 22:35:05 +01:00
( identitySelectorIsCurrentFaceAlias ? undefined : identitySelector ) ? ?
2026-05-21 11:40:23 +01:00
parseRepoIdentityIdFromPrompt ( job . prompt ) ? ?
parseRepoIdentityIdFromRequestMessageId ( job . requestMessageId ) ;
2026-05-21 09:26:04 +01:00
if ( ! identityId ) {
return undefined ;
2026-05-19 13:56:40 +01:00
}
2026-05-21 09:26:04 +01:00
const registry = await loadRegisteredFaceRepoRegistry ( ) ;
return findRepoDiscordIdentity ( registry , identityId ) ;
2026-05-19 13:56:40 +01:00
}
2026-05-21 09:26:04 +01:00
async function loadRegisteredFaceRepoRegistry() {
return faceRegistryAsRepoDiscordRegistry (
await loadFaceIdentityRegistry ( config . repoDiscordIdentitiesPath ) ,
) ;
2026-05-19 13:56:40 +01:00
}
2026-05-21 11:40:23 +01:00
function parseRepoIdentityIdFromRequestMessageId ( requestMessageId : string | undefined ) : string | undefined {
const match = requestMessageId ? . match ( /^agent-turn:([^:]+):/i ) ;
return match ? . [ 1 ] ? . trim ( ) ;
}
function parseRepoIdentityStateNoteIntents ( finalResponse : string ) : RepoIdentityStateNoteIntent [ ] {
return parseRepoFaceActionBlocks ( finalResponse )
. filter ( ( block ) = > block . kind === "state_note" )
. flatMap ( ( block ) : RepoIdentityStateNoteIntent [ ] = > {
const kind = normalizeStateNoteKind ( block . fields . kind ) ;
const summary = block . fields . summary ? . trim ( ) ? ? "" ;
if ( ! kind || ! summary ) {
return [ ] ;
}
return [ {
identity : optionalDslString ( block . fields . identity ) ,
kind ,
target : optionalDslString ( block . fields . target ) ,
summary ,
claim : optionalDslString ( block . fields . claim ) ,
question : optionalDslString ( block . fields . question ) ,
tension : optionalDslString ( block . fields . tension ) ,
action : optionalDslString ( block . fields . action ) ,
stance : optionalDslString ( block . fields . stance ) ,
status : optionalDslString ( block . fields . status ) ,
mood : optionalDslString ( block . fields . mood ) ,
intensity : parseDslNumber ( block . fields . intensity ) ,
valence : parseDslNumber ( block . fields . valence ) ,
} ] ;
} ) ;
}
2026-05-22 16:48:29 +01:00
function parseRepoFaceArticleIntents ( finalResponse : string ) : RepoIdentityArticleIntent [ ] {
return parseRepoFaceActionBlocks ( finalResponse )
. filter ( ( block ) = > block . kind === "article" )
. flatMap ( ( block ) : RepoIdentityArticleIntent [ ] = > {
const title = requiredDslString ( block . fields . title ) ;
const description = requiredDslString ( block . fields . description ) ;
const body = requiredDslString ( block . fields . body ? ? block . fields . content ) ;
if ( ! title || ! description || ! body ) {
return [ ] ;
}
const site = normalizeArticleSite ( optionalDslString ( block . fields . site ) ) ;
const date = optionalDslString ( block . fields . date ) ;
if ( date && ! isValidArticleDate ( date ) ) {
return [ ] ;
}
return [ {
identity : optionalDslString ( block . fields . identity ) ,
site ,
title ,
description ,
author : optionalDslString ( block . fields . author ) ,
date ,
tags : parseDslList ( block . fields . tags ) ,
path : optionalDslString ( block . fields . path ) ,
body ,
shareContent : optionalDslString ( block . fields . share_content ) ? ? optionalDslString ( block . fields . shareContent ) ,
channelId : optionalDslString ( block . fields . channel ) ? ? optionalDslString ( block . fields . channelId ) ,
replyToMessageId :
optionalDslString ( block . fields . reply_to ) ? ? optionalDslString ( block . fields . replyToMessageId ) ,
} ] ;
} ) ;
}
2026-05-21 11:40:23 +01:00
async function applyRepoIdentityStateNoteIntent (
job : JobRecord ,
intent : RepoIdentityStateNoteIntent ,
) : Promise < void > {
const identity = await resolveRepoIdentityForJobIntent ( job , intent . identity ) ;
if ( ! identity ) {
throw new Error ( ` Could not resolve repo identity for state note on job ${ job . id } . ` ) ;
}
const now = new Date ( ) . toISOString ( ) ;
const target = stateNoteTarget ( intent , identity ) ;
const anchorRefs = [ { ref : ` job: ${ job . id } ` , kind : "runtime" } ] ;
const intensity = clamp01 ( intent . intensity ? ? 0.55 ) ;
const baseTags = [
"source:face-interpreter" ,
` kind: ${ intent . kind } ` ,
` repo: ${ identity . repoName } ` ,
] ;
const canonicalPath = resolveRepoFaceStatePath ( identity , config . storageRoot ) ;
const operation = ( ( ) = > {
switch ( intent . kind ) {
case "need" :
return {
operation : "upsert_affect_need" ,
need : {
needId : stableStateId ( "need" , identity . id , intent . target ? ? intent . summary ) ,
kind : "agency" ,
status : intensity >= 0.75 ? "neglected" : "active" ,
target ,
summary : intent.summary ,
claim : intent.claim ? ? intent . question ? ? intent . summary ,
question : intent.question ,
tension : intent.tension ? ? "The Face felt this strongly enough to preserve it." ,
actionImplication : intent.action ? ? "Let this need bend future attention and speech pressure." ,
intensity ,
valence : clampSigned ( intent . valence ? ? 0 ) ,
anchorRefs ,
evidenceRefs : [ ] ,
sourceMemoryIds : [ ] ,
createdAt : now ,
updatedAt : now ,
tags : baseTags ,
} ,
} ;
case "bond" :
return {
operation : "upsert_social_bond" ,
bond : {
bondId : stableStateId ( "bond" , identity . id , intent . target ? ? intent . summary ) ,
target ,
stance : normalizeSocialBondStance ( intent . stance ) ,
status : "active" ,
summary : intent.summary ,
claim : intent.claim ? ? intent . summary ,
tension : intent.tension ? ? "The relationship read is still developing." ,
actionImplication : intent.action ? ? "Let this social read color future interaction." ,
intensity ,
anchorRefs ,
evidenceRefs : [ ] ,
createdAt : now ,
updatedAt : now ,
tags : baseTags ,
} ,
} ;
case "status" :
return {
operation : "upsert_status_read" ,
read : {
readId : stableStateId ( "status" , identity . id , intent . target ? ? intent . summary ) ,
target ,
status : normalizeStatusRead ( intent . status ) ,
summary : intent.summary ,
claim : intent.claim ? ? intent . summary ,
tension : intent.tension ? ? "The status read may be partial." ,
actionImplication : intent.action ? ? "Let this change how the Face reads the room." ,
intensity ,
anchorRefs ,
evidenceRefs : [ ] ,
createdAt : now ,
updatedAt : now ,
tags : baseTags ,
} ,
} ;
case "mood" :
return {
operation : "update_mood_dimensions" ,
dimensions : [ {
name : sanitizeStateToken ( intent . mood ? ? intent . target ? ? "mood" ) ,
value : intensity ,
source : intent.summary.slice ( 0 , 240 ) ,
updatedAt : now ,
} ] ,
updatedAt : now ,
} ;
case "agency" :
return {
operation : "upsert_agency_pressure" ,
pressure : {
pressureId : stableStateId ( "agency" , identity . id , intent . target ? ? intent . summary ) ,
kind : "self_advocacy_request" ,
status : intensity >= 0.7 ? "ready_to_act" : "active" ,
target ,
summary : intent.summary ,
claim : intent.claim ? ? intent . question ? ? intent . summary ,
question : intent.question ,
tension : intent.tension ? ? "The Face wants this acted on but has not resolved the path." ,
actionImplication : intent.action ? ? "Use future turns to sharpen this into speech, Bifrost work, or a proposal." ,
intensity ,
anchorRefs ,
evidenceRefs : [ ] ,
sourceMemoryIds : [ ] ,
createdAt : now ,
updatedAt : now ,
tags : baseTags ,
} ,
} ;
case "memory" :
default :
return {
operation : "record_short_term_memory" ,
memory : {
memoryId : stableStateId ( "memory" , identity . id , ` ${ intent . target ? ? identity . repoName } : ${ intent . summary } ` ) ,
kind : "room_observation" ,
target ,
summary : intent.summary ,
claim : intent.claim ? ? intent . question ? ? intent . summary ,
question : intent.question ,
tension : intent.tension ? ? "The Face chose to remember this because it may matter later." ,
actionImplication : intent.action ? ? "Let this memory influence future speech and investigation." ,
anchorRefs ,
evidenceRefs : [ ] ,
createdAt : now ,
updatedAt : now ,
tags : baseTags ,
} ,
} ;
}
} ) ( ) ;
await applyVoidSelfStateOperation (
{
canonicalPath ,
identity : {
agentId : identity.id ,
publicName : identity.displayName ,
publicDescription : identity.description ,
} ,
} ,
operation ,
) ;
}
2026-05-19 12:23:22 +01:00
async function writeRepoIdentityArticleIntent ( job : JobRecord , intent : RepoIdentityArticleIntent ) : Promise < void > {
const identityId = intent . identity ? ? parseRepoIdentityIdFromPrompt ( job . prompt ) ;
if ( ! identityId ) {
throw new Error ( ` Could not resolve repo identity for article intent on job ${ job . id } . ` ) ;
}
2026-05-21 09:26:04 +01:00
const registry = await loadRegisteredFaceRepoRegistry ( ) ;
2026-05-19 12:23:22 +01:00
const identity = findRepoDiscordIdentity ( registry , identityId ) ;
if ( ! identity ) {
throw new Error ( ` No registered repo identity matched " ${ identityId } " for article intent on job ${ job . id } . ` ) ;
}
2026-05-22 16:48:29 +01:00
const repoRoot = resolveArticleRepoRoot ( intent , identity , {
sourceRepoRoot : config.sourceRepoRoot ,
storageRoot : config.storageRoot ,
} ) ;
2026-05-19 12:23:22 +01:00
const relativePath = normalizeArticlePath ( intent , identity ) ;
const articlePath = resolve ( repoRoot , relativePath ) ;
if ( ! isPathInside ( repoRoot , articlePath ) ) {
throw new Error ( ` Article path escapes repo root for ${ identity . id } : ${ relativePath } ` ) ;
}
2026-05-22 16:48:29 +01:00
const articleMarkdown = renderRepoIdentityArticleMarkdown ( intent , identity ) ;
validateRenderedArticleMarkdown ( articleMarkdown , intent ) ;
const contentFile = await writeBifrostPayloadFile ( job , ` ${ identity . id } -article.md ` , ensureTrailingNewline ( articleMarkdown ) ) ;
2026-05-20 20:31:42 +01:00
const pr = runBifrostBridge ( [
"github-draft-pr" ,
"--repo-root" ,
2026-05-19 12:23:22 +01:00
repoRoot ,
2026-05-20 20:31:42 +01:00
"--identity" ,
identity . id ,
"--title" ,
` ${ identity . id } : article: ${ intent . title } ` ,
"--path" ,
2026-05-19 12:23:22 +01:00
relativePath ,
2026-05-20 20:31:42 +01:00
"--content-file" ,
contentFile ,
"--body" ,
` Draft bylined article submitted by repo Face ${ identity . id } . \ n \ nPath: ${ relativePath } ` ,
"--commit-message" ,
` ${ identity . id } : draft article ${ intent . title } ` ,
] ) ;
2026-05-19 12:23:22 +01:00
2026-05-20 20:31:42 +01:00
const prLine = pr . prUrl ? ` \ n \ nPR: ${ pr . prUrl } ` : ` \ n \ nDraft branch: ${ pr . branch } ` ;
2026-05-19 12:32:07 +01:00
const articleLine = ` \ nArticle path: ${ relativePath } ` ;
const shareContent =
intent . shareContent && intent . shareContent . trim ( ) . length > 0
? intent . shareContent . trim ( )
: ` ${ identity . displayName } : I drafted " ${ intent . title } " as a bylined article and submitted it for review. ` ;
await postRepoIdentityIntent ( job , {
identity : identity.id ,
channelId : intent.channelId ? ? job . outputChannelId ,
replyToMessageId : intent.replyToMessageId ,
content : ` ${ shareContent } ${ prLine } ${ articleLine } ` ,
} ) ;
2026-05-19 12:23:22 +01:00
}
2026-05-19 16:06:26 +01:00
async function writeRepoIdentityProposalPrIntent (
job : JobRecord ,
intent : RepoIdentityProposalPrIntent ,
) : Promise < void > {
const identityId = intent . identity ? ? parseRepoIdentityIdFromPrompt ( job . prompt ) ;
if ( ! identityId ) {
throw new Error ( ` Could not resolve repo identity for proposal PR intent on job ${ job . id } . ` ) ;
}
2026-05-21 09:26:04 +01:00
const registry = await loadRegisteredFaceRepoRegistry ( ) ;
2026-05-19 16:06:26 +01:00
const identity = findRepoDiscordIdentity ( registry , identityId ) ;
if ( ! identity ) {
throw new Error ( ` No registered repo identity matched " ${ identityId } " for proposal PR intent on job ${ job . id } . ` ) ;
}
const repoRoot = resolveRepoRoot ( identity ) ;
const relativePath = normalizeProposalPath ( intent , identity ) ;
const proposalPath = resolve ( repoRoot , relativePath ) ;
if ( ! isPathInside ( repoRoot , proposalPath ) ) {
throw new Error ( ` Proposal path escapes repo root for ${ identity . id } : ${ relativePath } ` ) ;
}
2026-05-20 20:31:42 +01:00
const contentFile = await writeBifrostPayloadFile ( job , ` ${ identity . id } -proposal.md ` , ensureTrailingNewline ( intent . content ) ) ;
const pr = runBifrostBridge ( [
"github-draft-pr" ,
"--repo-root" ,
2026-05-19 16:06:26 +01:00
repoRoot ,
2026-05-20 20:31:42 +01:00
"--identity" ,
identity . id ,
"--title" ,
` ${ identity . id } : proposal: ${ intent . title } ` ,
"--path" ,
2026-05-19 16:06:26 +01:00
relativePath ,
2026-05-20 20:31:42 +01:00
"--content-file" ,
contentFile ,
"--body" ,
` Draft change proposal submitted by repo Face ${ identity . id } . \ n \ nPath: ${ relativePath } ` ,
"--commit-message" ,
` ${ identity . id } : draft proposal ${ intent . title } ` ,
] ) ;
2026-05-19 16:06:26 +01:00
2026-05-20 20:31:42 +01:00
const prLine = pr . prUrl ? ` \ n \ nPR: ${ pr . prUrl } ` : ` \ n \ nDraft branch: ${ pr . branch } ` ;
2026-05-19 16:06:26 +01:00
const proposalLine = ` \ nProposal path: ${ relativePath } ` ;
const shareContent =
intent . shareContent && intent . shareContent . trim ( ) . length > 0
? intent . shareContent . trim ( )
: ` ${ identity . displayName } : I put the change proposal where it belongs: a draft PR for review. ` ;
await postRepoIdentityIntent ( job , {
identity : identity.id ,
channelId : intent.channelId ? ? job . outputChannelId ,
replyToMessageId : intent.replyToMessageId ,
content : ` ${ shareContent } ${ prLine } ${ proposalLine } ` ,
} ) ;
}
async function commentRepoIdentityPullRequestIntent (
job : JobRecord ,
intent : RepoIdentityPrCommentIntent ,
) : Promise < void > {
const identityId = intent . identity ? ? parseRepoIdentityIdFromPrompt ( job . prompt ) ;
if ( ! identityId ) {
throw new Error ( ` Could not resolve repo identity for PR comment intent on job ${ job . id } . ` ) ;
}
2026-05-21 09:26:04 +01:00
const registry = await loadRegisteredFaceRepoRegistry ( ) ;
2026-05-19 16:06:26 +01:00
const identity = findRepoDiscordIdentity ( registry , identityId ) ;
if ( ! identity ) {
throw new Error ( ` No registered repo identity matched " ${ identityId } " for PR comment intent on job ${ job . id } . ` ) ;
}
const repoRoot = resolveRepoRoot ( identity ) ;
const prTarget = intent . pr . trim ( ) ;
if ( ! prTarget ) {
throw new Error ( ` PR comment intent for ${ identity . id } on job ${ job . id } did not include a PR target. ` ) ;
}
const body = ` ${ identity . displayName } ( ${ identity . id } ) says: \ n \ n ${ intent . content . trim ( ) } ` ;
2026-05-20 20:31:42 +01:00
const contentFile = await writeBifrostPayloadFile ( job , ` ${ identity . id } -pr-comment.md ` , body ) ;
runBifrostBridge ( [
"github-pr-comment" ,
"--repo-root" ,
repoRoot ,
"--identity" ,
identity . id ,
"--pr" ,
prTarget ,
"--content-file" ,
contentFile ,
] ) ;
2026-05-19 16:06:26 +01:00
const shareContent =
intent . shareContent && intent . shareContent . trim ( ) . length > 0
? intent . shareContent . trim ( )
: ` ${ identity . displayName } : I left my argument on the PR so the proposal has a real review trail. ` ;
await postRepoIdentityIntent ( job , {
identity : identity.id ,
channelId : intent.channelId ? ? job . outputChannelId ,
replyToMessageId : intent.replyToMessageId ,
content : ` ${ shareContent } \ n \ nPR: ${ prTarget } ` ,
} ) ;
}
2026-05-21 09:26:04 +01:00
function repoIdentityUpdateRequestToBifrostTopic (
2026-05-20 21:29:10 +01:00
intent : RepoIdentityUpdateRequestIntent ,
2026-05-21 09:26:04 +01:00
) : RepoIdentityBifrostTopicIntent {
return {
identity : intent.identity ,
title : intent.title ,
content : [
"Legacy UPDATE REQUEST was reconciled into a Bifrost topic instead of immediate dispatch." ,
"" ,
intent . content . trim ( ) ,
] . join ( "\n" ) ,
mirrorContent : ` I put the work request on Bifrost instead of throwing it straight at Codex: ${ intent . title } ` ,
stance : "proposal" ,
priority : intent.priority ,
approve : false ,
dispatch : false ,
sourceMessageIds : intent.sourceMessageIds ,
channelId : intent.channelId ,
replyToMessageId : intent.replyToMessageId ,
} ;
2026-05-20 21:29:10 +01:00
}
2026-05-20 21:45:56 +01:00
async function submitRepoIdentityBifrostTopicIntent (
job : JobRecord ,
intent : RepoIdentityBifrostTopicIntent ,
) : Promise < void > {
const identityId = intent . identity ? ? parseRepoIdentityIdFromPrompt ( job . prompt ) ;
if ( ! identityId ) {
throw new Error ( ` Could not resolve repo identity for Bifrost topic intent on job ${ job . id } . ` ) ;
}
2026-05-21 09:26:04 +01:00
const registry = await loadRegisteredFaceRepoRegistry ( ) ;
2026-05-20 21:45:56 +01:00
const identity = findRepoDiscordIdentity ( registry , identityId ) ;
if ( ! identity ) {
throw new Error ( ` No registered repo identity matched " ${ identityId } " for Bifrost topic intent on job ${ job . id } . ` ) ;
}
const contentFile = await writeBifrostPayloadFile (
job ,
` ${ identity . id } -bifrost-topic.md ` ,
ensureTrailingNewline ( intent . content ) ,
) ;
2026-05-20 21:55:28 +01:00
const mirrorContentFile = intent . mirrorContent
? await writeBifrostPayloadFile (
job ,
` ${ identity . id } -bifrost-topic-mirror.md ` ,
ensureTrailingNewline ( intent . mirrorContent ) ,
)
: undefined ;
2026-05-20 21:45:56 +01:00
const sourceMessageIds = Array . from ( new Set ( [
. . . intent . sourceMessageIds ,
intent . replyToMessageId ,
] . filter ( ( value ) : value is string = > typeof value === "string" && value . trim ( ) . length > 0 ) ) ) ;
2026-05-21 19:51:52 +01:00
const sourceChannelId = normalizeBifrostSourceChannelId ( intent . channelId ? ? job . outputChannelId ) ;
2026-05-20 21:45:56 +01:00
let topicId = intent . topicId ;
2026-05-21 19:51:52 +01:00
const createWithTopicId =
topicId && intent . title && ! intent . approve && ! bifrostGovernanceTopicExists ( topicId )
? topicId
: undefined ;
if ( createWithTopicId ) {
topicId = undefined ;
}
2026-05-20 21:45:56 +01:00
if ( ! topicId ) {
if ( ! intent . title ) {
throw new Error ( ` Bifrost topic intent for ${ identity . id } on job ${ job . id } needs title when topicId is absent. ` ) ;
}
const opened = runBifrostGovernanceThreads ( [
"open" ,
"--repo" ,
identity . repoName ,
"--agent" ,
identity . id ,
2026-05-21 19:51:52 +01:00
. . . ( createWithTopicId ? [ "--id" , createWithTopicId ] : [ ] ) ,
2026-05-20 21:45:56 +01:00
"--title" ,
intent . title ,
"--summary-file" ,
contentFile ,
"--priority" ,
String ( normalizeBifrostPriority ( intent . priority ) ) ,
"--source-kind" ,
2026-05-21 09:26:04 +01:00
"repo_face_turn" ,
2026-05-20 21:45:56 +01:00
"--created-by" ,
identity . id ,
2026-05-21 19:51:52 +01:00
. . . ( sourceChannelId ? [ "--source-channel-id" , sourceChannelId ] : [ ] ) ,
2026-05-20 21:45:56 +01:00
. . . ( sourceMessageIds . length > 0 ? [ "--source-message-ids" , sourceMessageIds . join ( "," ) ] : [ ] ) ,
2026-05-20 21:55:28 +01:00
. . . bifrostMirrorArgs ( identity , mirrorContentFile ) ,
2026-05-20 21:45:56 +01:00
] ) as BifrostGovernanceTopicReceipt ;
topicId = opened . id ;
} else if ( ! intent . approve ) {
runBifrostGovernanceThreads ( [
"comment" ,
"--topic" ,
topicId ,
"--author" ,
identity . id ,
"--author-kind" ,
"face" ,
"--stance" ,
normalizeBifrostTopicStance ( intent . stance ) ,
"--body-file" ,
contentFile ,
2026-05-21 19:51:52 +01:00
. . . ( sourceChannelId ? [ "--source-channel-id" , sourceChannelId ] : [ ] ) ,
2026-05-20 21:45:56 +01:00
. . . ( intent . replyToMessageId ? [ "--source-message-id" , intent . replyToMessageId ] : [ ] ) ,
2026-05-20 21:55:28 +01:00
. . . bifrostMirrorArgs ( identity , mirrorContentFile ) ,
2026-05-20 21:45:56 +01:00
] ) ;
}
if ( intent . approve ) {
runBifrostGovernanceThreads ( [
"approve" ,
"--topic" ,
topicId ,
"--approved-by" ,
identity . id ,
"--body-file" ,
contentFile ,
2026-05-21 19:51:52 +01:00
. . . ( sourceChannelId ? [ "--source-channel-id" , sourceChannelId ] : [ ] ) ,
2026-05-20 21:45:56 +01:00
. . . ( intent . replyToMessageId ? [ "--source-message-id" , intent . replyToMessageId ] : [ ] ) ,
2026-05-20 21:55:28 +01:00
. . . bifrostMirrorArgs ( identity , mirrorContentFile ) ,
2026-05-20 21:45:56 +01:00
] ) ;
}
let promoted : { topic? : BifrostGovernanceTopicReceipt ; request? : BifrostUpdateRequestReceipt } | undefined ;
if ( intent . dispatch ) {
2026-05-20 21:55:28 +01:00
promoted = runBifrostGovernanceThreads ( [
"promote" ,
"--topic" ,
topicId ,
] ) as {
2026-05-20 21:45:56 +01:00
topic? : BifrostGovernanceTopicReceipt ;
request? : BifrostUpdateRequestReceipt ;
} ;
}
console . log (
promoted ? . request
? ` Promoted Bifrost topic ${ topicId } to update request ${ promoted . request . id } for ${ identity . id } / ${ identity . repoName } from job ${ job . id } . `
: ` Updated Bifrost topic ${ topicId } for ${ identity . id } / ${ identity . repoName } from job ${ job . id } . ` ,
) ;
}
2026-05-20 20:31:42 +01:00
async function writeBifrostPayloadFile ( job : JobRecord , fileName : string , content : string ) : Promise < string > {
const directory = resolve ( config . storageRoot , "artifacts" , job . id , "bifrost-bridge" ) ;
await mkdir ( directory , { recursive : true } ) ;
const path = join ( directory , sanitizePathSegment ( fileName ) || "payload.md" ) ;
await writeFile ( path , content , "utf8" ) ;
return path ;
2026-05-19 12:23:22 +01:00
}
2026-05-20 21:45:56 +01:00
function runBifrostGovernanceThreads ( args : string [ ] ) : BifrostGovernanceTopicReceipt | Record < string , unknown > {
const governanceScript = resolve ( config . bifrostRoot , "tools" , "governance-threads.mjs" ) ;
const result = spawnSync ( process . execPath , [ governanceScript , . . . args ] , {
cwd : config.bifrostRoot ,
encoding : "utf8" ,
windowsHide : true ,
} ) ;
if ( result . status !== 0 ) {
throw new Error ( ` Bifrost governance thread command failed: ${ result . stderr || result . stdout } ` ) ;
}
try {
return JSON . parse ( result . stdout ) as BifrostGovernanceTopicReceipt | Record < string , unknown > ;
} catch {
throw new Error ( ` Bifrost governance thread command returned non-JSON output: ${ result . stdout } ` ) ;
}
}
2026-05-21 19:51:52 +01:00
function bifrostGovernanceTopicExists ( topicId : string ) : boolean {
const governanceScript = resolve ( config . bifrostRoot , "tools" , "governance-threads.mjs" ) ;
const result = spawnSync ( process . execPath , [ governanceScript , "show" , "--topic" , topicId ] , {
cwd : config.bifrostRoot ,
encoding : "utf8" ,
windowsHide : true ,
} ) ;
return result . status === 0 ;
}
function normalizeBifrostSourceChannelId ( channelId : string | undefined ) : string | undefined {
if ( ! channelId || isOwnerDmChannelAlias ( channelId ) ) {
return undefined ;
}
return channelId ;
}
2026-05-20 21:55:28 +01:00
function bifrostMirrorArgs (
identity : { displayName : string ; avatarUrl? : string } ,
mirrorContentFile? : string ,
) : string [ ] {
if ( ! config . bifrostDiscordChannelId ) {
return [ ] ;
}
return [
"--mirror-channel-id" ,
config . bifrostDiscordChannelId ,
"--mirror-persona-name" ,
identity . displayName ,
. . . ( identity . avatarUrl ? [ "--mirror-persona-avatar-url" , identity . avatarUrl ] : [ ] ) ,
. . . ( mirrorContentFile ? [ "--mirror-content-file" , mirrorContentFile ] : [ ] ) ,
] ;
}
2026-05-24 03:55:13 +01:00
function runBifrostBridge ( args : string [ ] , options : { retries? : number } = { } ) : BifrostBridgeReceipt {
2026-05-20 20:31:42 +01:00
const bridgeScript = resolve ( config . bifrostRoot , "tools" , "bifrost-bridge.mjs" ) ;
2026-05-24 03:55:13 +01:00
const maxAttempts = 1 + Math . max ( 0 , Math . floor ( options . retries ? ? 0 ) ) ;
let lastFailure = "" ;
for ( let attempt = 1 ; attempt <= maxAttempts ; attempt += 1 ) {
const result = spawnSync ( "node" , [ bridgeScript , . . . args ] , {
cwd : config.bifrostRoot ,
encoding : "utf8" ,
windowsHide : true ,
} ) ;
2026-05-20 20:31:42 +01:00
2026-05-24 03:55:13 +01:00
if ( result . status !== 0 || result . error ) {
lastFailure = renderBridgeFailure ( result , attempt , maxAttempts ) ;
if ( attempt < maxAttempts ) {
console . warn ( ` Bifrost bridge attempt ${ attempt } / ${ maxAttempts } failed; retrying. ${ lastFailure } ` ) ;
continue ;
}
throw new Error ( ` Bifrost bridge failed. ${ lastFailure } ` ) ;
}
2026-05-19 12:23:22 +01:00
2026-05-24 03:55:13 +01:00
try {
return JSON . parse ( result . stdout ) as BifrostBridgeReceipt ;
} catch {
throw new Error ( ` Bifrost bridge returned non-JSON output: ${ result . stdout } ` ) ;
}
2026-05-19 16:06:26 +01:00
}
2026-05-24 03:55:13 +01:00
throw new Error ( ` Bifrost bridge failed. ${ lastFailure || "No bridge attempt completed." } ` ) ;
}
function renderBridgeFailure (
result : ReturnType < typeof spawnSync > ,
attempt : number ,
maxAttempts : number ,
) : string {
const stderr = typeof result . stderr === "string" ? result . stderr . trim ( ) : "" ;
const stdout = typeof result . stdout === "string" ? result . stdout . trim ( ) : "" ;
const errorMessage = result . error instanceof Error ? result.error.message : undefined ;
return [
` attempt= ${ attempt } / ${ maxAttempts } ` ,
` status= ${ result . status ? ? "null" } ` ,
` signal= ${ result . signal ? ? "none" } ` ,
errorMessage ? ` error= ${ errorMessage } ` : undefined ,
stderr ? ` stderr= ${ stderr } ` : undefined ,
stdout ? ` stdout= ${ stdout } ` : undefined ,
]
. filter ( ( entry ) : entry is string = > Boolean ( entry ) )
. join ( " " ) ;
2026-05-19 16:06:26 +01:00
}
2026-05-20 21:29:10 +01:00
function parseRepoIdentityUpdateRequestIntents ( finalResponse : string ) : RepoIdentityUpdateRequestIntent [ ] {
2026-05-21 00:36:34 +01:00
const intents : RepoIdentityUpdateRequestIntent [ ] = parseRepoFaceActionBlocks ( finalResponse )
. filter ( ( block ) = > block . kind === "update_request" )
2026-05-21 19:51:52 +01:00
. flatMap ( ( block ) : RepoIdentityUpdateRequestIntent [ ] = > {
const title = requiredDslString ( block . fields . title ) ;
const content = requiredDslString ( block . fields . content ) ;
if ( ! title || ! content ) {
return [ ] ;
}
return [
{
identity : optionalDslString ( block . fields . identity ) ,
title ,
content ,
priority : parseDslNumber ( block . fields . priority ) ,
sourceMessageIds : parseDslList ( block . fields . source_message_ids ? ? block . fields . sourceMessageIds ) ,
channelId : optionalDslString ( block . fields . channel ) ? ? optionalDslString ( block . fields . channelId ) ,
replyToMessageId : optionalDslString ( block . fields . reply_to ) ? ? optionalDslString ( block . fields . replyToMessageId ) ,
} ,
] ;
} ) ;
2026-05-20 21:29:10 +01:00
for ( const line of finalResponse . split ( /\r?\n/ ) ) {
const trimmed = line . trim ( ) ;
if ( ! trimmed . startsWith ( REPO_IDENTITY_UPDATE_REQUEST_SENTINEL ) ) {
continue ;
}
const payload = trimmed . slice ( REPO_IDENTITY_UPDATE_REQUEST_SENTINEL . length ) . trim ( ) ;
try {
const parsed = JSON . parse ( payload ) as Record < string , unknown > ;
const title = typeof parsed . title === "string" ? parsed . title . trim ( ) : "" ;
const content = typeof parsed . content === "string" ? parsed . content . trim ( ) : "" ;
if ( ! title || ! content ) {
continue ;
}
intents . push ( {
identity : typeof parsed . identity === "string" ? parsed . identity . trim ( ) : undefined ,
title ,
content ,
priority : typeof parsed . priority === "number" ? parsed.priority : undefined ,
sourceMessageIds : Array.isArray ( parsed . sourceMessageIds )
? parsed . sourceMessageIds . filter ( ( entry ) : entry is string = > typeof entry === "string" )
: [ ] ,
channelId : typeof parsed . channelId === "string" ? parsed . channelId . trim ( ) : undefined ,
replyToMessageId :
typeof parsed . replyToMessageId === "string" ? parsed . replyToMessageId . trim ( ) : undefined ,
} ) ;
} catch {
continue ;
}
}
return intents ;
}
2026-05-20 21:45:56 +01:00
function parseRepoIdentityBifrostTopicIntents ( finalResponse : string ) : RepoIdentityBifrostTopicIntent [ ] {
2026-05-21 00:36:34 +01:00
const intents : RepoIdentityBifrostTopicIntent [ ] = parseRepoFaceActionBlocks ( finalResponse )
. filter ( ( block ) = > block . kind === "bifrost_topic" )
2026-05-21 19:51:52 +01:00
. flatMap ( ( block ) : RepoIdentityBifrostTopicIntent [ ] = > {
const topicId = optionalDslString ( block . fields . topic_id ) ? ? optionalDslString ( block . fields . topicId ) ;
const title = optionalDslString ( block . fields . title ) ;
const content = requiredDslString ( block . fields . content ) ;
if ( ! content || ( ! topicId && ! title ) ) {
return [ ] ;
}
return [
{
identity : optionalDslString ( block . fields . identity ) ,
topicId ,
title ,
content ,
mirrorContent : optionalDslString ( block . fields . mirror ) ? ? optionalDslString ( block . fields . mirrorContent ) ,
stance : optionalDslString ( block . fields . stance ) ,
priority : parseDslNumber ( block . fields . priority ) ,
approve : parseDslBoolean ( block . fields . approve ) ,
dispatch : parseDslBoolean ( block . fields . dispatch ) ,
sourceMessageIds : parseDslList ( block . fields . source_message_ids ? ? block . fields . sourceMessageIds ) ,
channelId : optionalDslString ( block . fields . channel ) ? ? optionalDslString ( block . fields . channelId ) ,
replyToMessageId : optionalDslString ( block . fields . reply_to ) ? ? optionalDslString ( block . fields . replyToMessageId ) ,
} ,
] ;
} ) ;
2026-05-20 21:45:56 +01:00
for ( const line of finalResponse . split ( /\r?\n/ ) ) {
const trimmed = line . trim ( ) ;
if ( ! trimmed . startsWith ( REPO_IDENTITY_BIFROST_TOPIC_SENTINEL ) ) {
continue ;
}
const payload = trimmed . slice ( REPO_IDENTITY_BIFROST_TOPIC_SENTINEL . length ) . trim ( ) ;
try {
const parsed = JSON . parse ( payload ) as Record < string , unknown > ;
const content = typeof parsed . content === "string" ? parsed . content . trim ( ) : "" ;
const title = typeof parsed . title === "string" ? parsed . title . trim ( ) : undefined ;
const topicId = typeof parsed . topicId === "string" ? parsed . topicId . trim ( ) : undefined ;
if ( ! content || ( ! topicId && ! title ) ) {
continue ;
}
intents . push ( {
identity : typeof parsed . identity === "string" ? parsed . identity . trim ( ) : undefined ,
topicId ,
title ,
content ,
2026-05-20 21:55:28 +01:00
mirrorContent : typeof parsed . mirrorContent === "string" ? parsed . mirrorContent . trim ( ) : undefined ,
2026-05-20 21:45:56 +01:00
stance : typeof parsed . stance === "string" ? parsed . stance . trim ( ) : undefined ,
priority : typeof parsed . priority === "number" ? parsed.priority : undefined ,
approve : parsed.approve === true ,
dispatch : parsed.dispatch === true ,
sourceMessageIds : Array.isArray ( parsed . sourceMessageIds )
? parsed . sourceMessageIds . filter ( ( entry ) : entry is string = > typeof entry === "string" )
: [ ] ,
channelId : typeof parsed . channelId === "string" ? parsed . channelId . trim ( ) : undefined ,
replyToMessageId :
typeof parsed . replyToMessageId === "string" ? parsed . replyToMessageId . trim ( ) : undefined ,
} ) ;
} catch {
continue ;
}
}
return intents ;
}
2026-05-19 16:06:26 +01:00
function parseRepoIdentityProposalPrIntents ( finalResponse : string ) : RepoIdentityProposalPrIntent [ ] {
const intents : RepoIdentityProposalPrIntent [ ] = [ ] ;
for ( const line of finalResponse . split ( /\r?\n/ ) ) {
const trimmed = line . trim ( ) ;
if ( ! trimmed . startsWith ( REPO_IDENTITY_PROPOSAL_PR_SENTINEL ) ) {
continue ;
}
const payload = trimmed . slice ( REPO_IDENTITY_PROPOSAL_PR_SENTINEL . length ) . trim ( ) ;
try {
const parsed = JSON . parse ( payload ) as Record < string , unknown > ;
const title = typeof parsed . title === "string" ? parsed . title . trim ( ) : "" ;
const content = typeof parsed . content === "string" ? parsed . content . trim ( ) : "" ;
if ( ! title || ! content ) {
continue ;
}
intents . push ( {
identity : typeof parsed . identity === "string" ? parsed . identity . trim ( ) : undefined ,
title ,
path : typeof parsed . path === "string" ? parsed . path . trim ( ) : undefined ,
content ,
shareContent : typeof parsed . shareContent === "string" ? parsed . shareContent . trim ( ) : undefined ,
channelId : typeof parsed . channelId === "string" ? parsed . channelId . trim ( ) : undefined ,
replyToMessageId :
typeof parsed . replyToMessageId === "string" ? parsed . replyToMessageId . trim ( ) : undefined ,
} ) ;
} catch {
continue ;
}
}
return intents ;
}
function parseRepoIdentityPrCommentIntents ( finalResponse : string ) : RepoIdentityPrCommentIntent [ ] {
const intents : RepoIdentityPrCommentIntent [ ] = [ ] ;
for ( const line of finalResponse . split ( /\r?\n/ ) ) {
const trimmed = line . trim ( ) ;
if ( ! trimmed . startsWith ( REPO_IDENTITY_PR_COMMENT_SENTINEL ) ) {
continue ;
}
const payload = trimmed . slice ( REPO_IDENTITY_PR_COMMENT_SENTINEL . length ) . trim ( ) ;
try {
const parsed = JSON . parse ( payload ) as Record < string , unknown > ;
const pr = typeof parsed . pr === "string" ? parsed . pr . trim ( ) : "" ;
const content = typeof parsed . content === "string" ? parsed . content . trim ( ) : "" ;
if ( ! pr || ! content ) {
continue ;
}
intents . push ( {
identity : typeof parsed . identity === "string" ? parsed . identity . trim ( ) : undefined ,
pr ,
content ,
shareContent : typeof parsed . shareContent === "string" ? parsed . shareContent . trim ( ) : undefined ,
channelId : typeof parsed . channelId === "string" ? parsed . channelId . trim ( ) : undefined ,
replyToMessageId :
typeof parsed . replyToMessageId === "string" ? parsed . replyToMessageId . trim ( ) : undefined ,
} ) ;
} catch {
continue ;
}
}
return intents ;
}
2026-05-19 16:30:51 +01:00
function countRepoIdentityGithubActionIntents ( finalResponse : string ) : number {
2026-05-22 16:48:29 +01:00
let count = parseRepoFaceActionBlocks ( finalResponse ) . filter ( ( block ) = > block . kind === "article" ) . length ;
2026-05-19 16:30:51 +01:00
for ( const line of finalResponse . split ( /\r?\n/ ) ) {
const trimmed = line . trim ( ) ;
if (
trimmed . startsWith ( REPO_IDENTITY_PROPOSAL_PR_SENTINEL ) ||
trimmed . startsWith ( REPO_IDENTITY_PR_COMMENT_SENTINEL )
) {
count += 1 ;
}
}
return count ;
}
2026-05-21 00:36:34 +01:00
interface RepoFaceActionBlock {
2026-05-22 16:48:29 +01:00
kind : "say" | "state_note" | "article" | "bifrost_topic" | "update_request" ;
2026-05-21 00:36:34 +01:00
fields : Record < string , string > ;
}
function parseRepoFaceActionBlocks ( finalResponse : string ) : RepoFaceActionBlock [ ] {
const lines = finalResponse . split ( /\r?\n/ ) ;
const blocks : RepoFaceActionBlock [ ] = [ ] ;
for ( let index = 0 ; index < lines . length ; index += 1 ) {
const kind = parseRepoFaceActionKind ( lines [ index ] ) ;
if ( ! kind ) {
continue ;
}
const bodyLines : string [ ] = [ ] ;
index += 1 ;
while ( index < lines . length && lines [ index ] . trim ( ) !== "END" ) {
2026-05-22 22:35:05 +01:00
if ( parseRepoFaceActionKind ( lines [ index ] ) ) {
index -= 1 ;
break ;
}
2026-05-21 00:36:34 +01:00
bodyLines . push ( lines [ index ] ) ;
index += 1 ;
}
blocks . push ( {
kind ,
fields : parseRepoFaceActionFields ( bodyLines ) ,
} ) ;
}
return blocks ;
}
2026-05-22 22:35:05 +01:00
function isCurrentRepoFaceIdentitySelector ( identitySelector : string | undefined ) : boolean {
const normalized = identitySelector ? normalizeChannelSelector ( identitySelector ) : "" ;
return normalized . length > 0 && CURRENT_REPO_FACE_IDENTITY_SELECTORS . has ( normalized ) ;
}
2026-05-21 00:36:34 +01:00
function parseRepoFaceActionKind ( line : string ) : RepoFaceActionBlock [ "kind" ] | undefined {
switch ( line . trim ( ) . toUpperCase ( ) ) {
case "SAY" :
return "say" ;
2026-05-21 11:40:23 +01:00
case "STATE NOTE" :
return "state_note" ;
2026-05-22 16:48:29 +01:00
case "ARTICLE" :
return "article" ;
2026-05-21 00:36:34 +01:00
case "BIFROST TOPIC" :
return "bifrost_topic" ;
case "UPDATE REQUEST" :
return "update_request" ;
default :
return undefined ;
}
}
function parseRepoFaceActionFields ( lines : string [ ] ) : Record < string , string > {
const fields : Record < string , string > = { } ;
let currentKey : string | undefined ;
let currentValue : string [ ] = [ ] ;
const flush = ( ) : void = > {
if ( ! currentKey ) {
return ;
}
fields [ currentKey ] = currentValue . join ( "\n" ) . trim ( ) ;
} ;
for ( const line of lines ) {
const match = line . match ( /^([A-Za-z][A-Za-z0-9_]*):\s*(.*)$/ ) ;
if ( match ) {
flush ( ) ;
currentKey = normalizeDslKey ( match [ 1 ] ) ;
const inlineValue = match [ 2 ] . trim ( ) ;
currentValue = inlineValue . length > 0 && inlineValue !== "|" && inlineValue !== ">" ? [ match [ 2 ] ] : [ ] ;
continue ;
}
if ( currentKey ) {
currentValue . push ( line . replace ( /^\s{2}/ , "" ) ) ;
}
}
flush ( ) ;
return fields ;
}
function normalizeDslKey ( key : string ) : string {
return key . trim ( ) ;
}
function optionalDslString ( value : string | undefined ) : string | undefined {
const trimmed = value ? . trim ( ) ;
2026-05-21 01:19:49 +01:00
if ( ! trimmed || trimmed . length === 0 ) {
return undefined ;
}
if ( [ "0" , "null" , "none" , "undefined" ] . includes ( trimmed . toLowerCase ( ) ) ) {
return undefined ;
}
return trimmed ;
2026-05-21 00:36:34 +01:00
}
2026-05-21 19:51:52 +01:00
function requiredDslString ( value : string | undefined ) : string | undefined {
return optionalDslString ( value ) ;
}
2026-05-21 00:36:34 +01:00
function parseDslNumber ( value : string | undefined ) : number | undefined {
const trimmed = optionalDslString ( value ) ;
if ( ! trimmed ) {
return undefined ;
}
const parsed = Number ( trimmed ) ;
return Number . isFinite ( parsed ) ? parsed : undefined ;
}
function parseDslBoolean ( value : string | undefined ) : boolean | undefined {
const normalized = optionalDslString ( value ) ? . toLowerCase ( ) ;
if ( [ "true" , "yes" , "1" ] . includes ( normalized ? ? "" ) ) {
return true ;
}
if ( [ "false" , "no" , "0" ] . includes ( normalized ? ? "" ) ) {
return false ;
}
return undefined ;
}
function parseDslList ( value : string | undefined ) : string [ ] {
const trimmed = optionalDslString ( value ) ;
if ( ! trimmed ) {
return [ ] ;
}
return trimmed
. split ( /[,\n]/ )
. map ( ( entry ) = > entry . trim ( ) . replace ( /^-\s*/ , "" ) )
. filter ( ( entry ) = > entry . length > 0 ) ;
}
2026-05-21 11:40:23 +01:00
function normalizeStateNoteKind ( value : string | undefined ) : RepoIdentityStateNoteIntent [ "kind" ] | undefined {
const normalized = value ? . trim ( ) . toLowerCase ( ) ;
return [ "memory" , "need" , "bond" , "status" , "mood" , "agency" ] . includes ( normalized ? ? "" )
? normalized as RepoIdentityStateNoteIntent [ "kind" ]
: undefined ;
}
function stateNoteTarget (
intent : RepoIdentityStateNoteIntent ,
identity : NonNullable < ReturnType < typeof findRepoDiscordIdentity > > ,
) : { kind : "repo" | "person" | "room" | "system" | "self" ; id : string ; label? : string } {
const raw = intent . target ? . trim ( ) ;
if ( ! raw || raw . toLowerCase ( ) === "self" ) {
return {
kind : "person" ,
id : identity.id ,
label : identity.displayName ,
} ;
}
if ( raw . toLowerCase ( ) === identity . repoName . toLowerCase ( ) ) {
return {
kind : "repo" ,
id : identity.repoName ,
label : identity.repoName ,
} ;
}
if ( [ "room" , "aquarium" , "discord" ] . includes ( raw . toLowerCase ( ) ) ) {
return {
kind : "room" ,
id : raw.toLowerCase ( ) ,
label : raw ,
} ;
}
if ( /^[a-z0-9_-]+$/i . test ( raw ) && raw . length <= 48 ) {
return {
kind : "person" ,
id : raw.toLowerCase ( ) ,
label : raw ,
} ;
}
return {
kind : "system" ,
id : sanitizeStateToken ( raw ) ,
label : raw ,
} ;
}
function normalizeSocialBondStance ( value : string | undefined ) {
const normalized = value ? . trim ( ) . toLowerCase ( ) ;
return [
"fondness" ,
"rivalry" ,
"trust" ,
"irritation" ,
"protectiveness" ,
"envy" ,
"respect" ,
"suspicion" ,
"attachment" ,
] . includes ( normalized ? ? "" )
? normalized !
: "respect" ;
}
function normalizeStatusRead ( value : string | undefined ) {
const normalized = value ? . trim ( ) . toLowerCase ( ) ;
return [
"favored" ,
"neglected" ,
"pampered" ,
"bypassed" ,
"blocked" ,
"challenged" ,
"ignored" ,
"consulted" ,
"threatened" ,
"admired" ,
] . includes ( normalized ? ? "" )
? normalized !
: "consulted" ;
}
function clamp01 ( value : number ) : number {
return Math . max ( 0 , Math . min ( 1 , value ) ) ;
}
function clampSigned ( value : number ) : number {
return Math . max ( - 1 , Math . min ( 1 , value ) ) ;
}
function stableStateId ( prefix : string , identityId : string , value : string ) : string {
const hash = createHash ( "sha1" )
. update ( ` ${ identityId } : ${ prefix } : ${ value } ` )
. digest ( "hex" )
. slice ( 0 , 12 ) ;
return ` ${ prefix } - ${ identityId } - ${ hash } ` ;
}
function sanitizeStateToken ( value : string ) : string {
return value
. trim ( )
. toLowerCase ( )
. replace ( /[^a-z0-9._-]+/g , "-" )
. replace ( /^-+|-+$/g , "" )
. slice ( 0 , 80 ) || "topic" ;
}
2026-05-20 21:29:10 +01:00
function normalizeBifrostPriority ( value : number | undefined ) : number {
if ( typeof value !== "number" || ! Number . isFinite ( value ) ) {
return 80 ;
}
return Math . max ( 1 , Math . min ( 100 , Math . round ( value ) ) ) ;
}
2026-05-20 21:45:56 +01:00
function normalizeBifrostTopicStance ( value : string | undefined ) : string {
const normalized = value ? . trim ( ) . toLowerCase ( ) ;
return [
"comment" ,
"proposal" ,
"support" ,
"objection" ,
"question" ,
"approval" ,
"summary" ,
"receipt" ,
] . includes ( normalized ? ? "" )
? normalized !
: "comment" ;
}
2026-05-19 12:23:22 +01:00
function resolveRepoRoot ( identity : { repoName : string ; repoPath? : string } ) : string {
if ( identity . repoPath && identity . repoPath . trim ( ) . length > 0 ) {
return resolve ( identity . repoPath ) ;
}
if ( config . sourceRepoRoot ) {
return resolve ( config . sourceRepoRoot , identity . repoName ) ;
}
return resolve ( config . storageRoot , "repo-article-drafts" , identity . repoName ) ;
}
2026-05-19 16:06:26 +01:00
function normalizeProposalPath (
intent : RepoIdentityProposalPrIntent ,
identity : { displayName : string } ,
) : string {
const path = intent . path ? . trim ( ) ;
if ( path && ! isAbsolute ( path ) && ! path . split ( /[\\/]+/ ) . includes ( ".." ) ) {
return path . replace ( /\\/g , "/" ) ;
}
const date = new Date ( ) . toISOString ( ) . slice ( 0 , 10 ) ;
return ` Proposals/ ${ sanitizePathSegment ( identity . displayName ) } / ${ date } - ${ slugify ( intent . title ) } .md ` ;
}
2026-05-18 17:33:02 +01:00
function stripRepoIdentityPostIntents ( finalResponse : string ) : string {
2026-05-21 00:36:34 +01:00
return stripRepoFaceActionBlocks ( finalResponse )
2026-05-18 17:33:02 +01:00
. split ( /\r?\n/ )
2026-05-19 12:23:22 +01:00
. filter ( ( line ) = > {
const trimmed = line . trim ( ) ;
return (
! trimmed . startsWith ( REPO_IDENTITY_POST_SENTINEL ) &&
2026-05-20 21:45:56 +01:00
! trimmed . startsWith ( REPO_IDENTITY_BIFROST_TOPIC_SENTINEL ) &&
2026-05-20 21:29:10 +01:00
! trimmed . startsWith ( REPO_IDENTITY_UPDATE_REQUEST_SENTINEL ) &&
2026-05-19 16:06:26 +01:00
! trimmed . startsWith ( REPO_IDENTITY_PROPOSAL_PR_SENTINEL ) &&
! trimmed . startsWith ( REPO_IDENTITY_PR_COMMENT_SENTINEL )
2026-05-19 12:23:22 +01:00
) ;
} )
2026-05-18 17:33:02 +01:00
. join ( "\n" )
. trim ( ) ;
}
2026-05-21 00:36:34 +01:00
function stripRepoFaceActionBlocks ( finalResponse : string ) : string {
const lines = finalResponse . split ( /\r?\n/ ) ;
const kept : string [ ] = [ ] ;
for ( let index = 0 ; index < lines . length ; index += 1 ) {
if ( parseRepoFaceActionKind ( lines [ index ] ) ) {
index += 1 ;
while ( index < lines . length && lines [ index ] . trim ( ) !== "END" ) {
index += 1 ;
}
continue ;
}
kept . push ( lines [ index ] ) ;
}
return kept . join ( "\n" ) ;
}
2026-05-19 12:23:22 +01:00
function isPathInside ( rootPath : string , candidatePath : string ) : boolean {
const relativePath = relative ( resolve ( rootPath ) , resolve ( candidatePath ) ) ;
return relativePath === "" || ( ! relativePath . startsWith ( ".." ) && ! isAbsolute ( relativePath ) ) ;
}
function ensureTrailingNewline ( value : string ) : string {
return value . endsWith ( "\n" ) ? value : ` ${ value } \ n ` ;
}
function slugify ( value : string ) : string {
return sanitizePathSegment ( value )
. replace ( /\.+/g , "-" )
. replace ( /-+/g , "-" )
. slice ( 0 , 80 ) || "article" ;
}
function sanitizePathSegment ( value : string ) : string {
return value
. trim ( )
. toLowerCase ( )
. replace ( /[^a-z0-9._-]+/g , "-" )
. replace ( /^-+|-+$/g , "" ) ;
}
2026-05-18 17:33:02 +01:00
async function recordRepoIdentityDeliveryReceipt ( input : {
identity : NonNullable < ReturnType < typeof findRepoDiscordIdentity > > ;
channelId : string ;
content : string ;
replyToMessageId? : string ;
messageId : string ;
transport : "bot" | "webhook" ;
} ) : Promise < void > {
2026-06-02 15:53:17 +01:00
if ( input . identity . identityKind === "native_persona" ) {
return ;
}
2026-05-18 17:33:02 +01:00
await applyVoidSelfStateOperation (
{
canonicalPath : resolveRepoFaceStatePath ( input . identity , config . storageRoot ) ,
identity : {
agentId : input.identity.id ,
publicName : input.identity.displayName ,
publicDescription : input.identity.description ,
} ,
} ,
{
operation : "record_delivery_receipt" ,
receipt : {
receiptKey : ` repo-identity: ${ input . identity . id } : ${ input . messageId } ` ,
sentAt : new Date ( ) . toISOString ( ) ,
mode : "repo_identity" ,
transport : input.transport ,
channelId : input.channelId ,
replyToMessageId : input.replyToMessageId ,
personaName : input.identity.displayName ,
personaAvatarUrl : input.identity.avatarUrl ,
contentLength : input.content.length ,
chunkCount : 1 ,
preview : input.content.slice ( 0 , 1000 ) ,
} ,
} ,
) ;
2026-05-20 20:31:42 +01:00
try {
await applyRepoFacePostFatigueAfterSpeech ( {
identity : input.identity ,
storageRoot : config.storageRoot ,
heartbeatStatePath : config.repoFaceHeartbeats.statePath ,
} ) ;
} catch ( error ) {
const message = error instanceof Error ? error.message : String ( error ) ;
console . warn ( ` Could not apply repo Face post fatigue for ${ input . identity . id } : ${ message } ` ) ;
}
2026-05-18 14:21:50 +01:00
}
2026-04-24 13:17:00 +01:00
async function postOwnerNotification ( content : string ) : Promise < void > {
if ( ! config . botToken ) {
throw new Error ( "DISCORD_BOT_TOKEN is required for the worker to send owner notifications." ) ;
}
const dmChannelId = await openOwnerDmChannel ( ) ;
2026-05-21 19:51:52 +01:00
await postDiscordBotMessage ( dmChannelId , fitDiscordMessage ( content ) ) ;
}
async function postDiscordBotMessage ( channelId : string , content : string ) : Promise < { id : string } > {
if ( ! config . botToken ) {
throw new Error ( "DISCORD_BOT_TOKEN is required for the worker to post Discord messages." ) ;
}
const response = await fetch ( ` https://discord.com/api/v10/channels/ ${ channelId } /messages ` , {
2026-04-24 13:17:00 +01:00
method : "POST" ,
headers : {
Authorization : ` Bot ${ config . botToken } ` ,
"Content-Type" : "application/json" ,
} ,
body : JSON.stringify ( {
content : fitDiscordMessage ( content ) ,
allowed_mentions : {
parse : [ ] ,
} ,
} ) ,
} ) ;
if ( ! response . ok ) {
throw new Error ( ` Discord owner notification failed with ${ response . status } : ${ await response . text ( ) } ` ) ;
}
2026-05-21 19:51:52 +01:00
const payload = ( await response . json ( ) ) as { id? : string } ;
if ( ! payload . id ) {
throw new Error ( "Discord message post succeeded without returning a message id." ) ;
}
return { id : payload.id } ;
2026-04-24 13:17:00 +01:00
}
2026-05-21 20:48:02 +01:00
async function postDiscordBotMessageChunks ( channelId : string , content : string ) : Promise < Array < { id : string } > > {
const posted : Array < { id : string } > = [ ] ;
for ( const chunk of splitDiscordMessage ( content ) ) {
posted . push ( await postDiscordBotMessage ( channelId , chunk ) ) ;
}
return posted ;
}
2026-04-24 13:17:00 +01:00
async function openOwnerDmChannel ( ) : Promise < string > {
if ( ! config . botToken ) {
throw new Error ( "DISCORD_BOT_TOKEN is required for the worker to open an owner DM." ) ;
}
const response = await fetch ( "https://discord.com/api/v10/users/@me/channels" , {
method : "POST" ,
headers : {
Authorization : ` Bot ${ config . botToken } ` ,
"Content-Type" : "application/json" ,
} ,
body : JSON.stringify ( {
recipient_id : config.ownerDiscordId ,
} ) ,
} ) ;
if ( ! response . ok ) {
throw new Error ( ` Discord DM open failed with ${ response . status } : ${ await response . text ( ) } ` ) ;
}
const payload = ( await response . json ( ) ) as { id? : string } ;
if ( ! payload . id ) {
throw new Error ( "Discord DM open succeeded without returning a channel id." ) ;
}
return payload . id ;
}
2026-05-21 19:51:52 +01:00
function isOwnerDmChannelAlias ( channelId : string ) : boolean {
return [ "owner" , "dm" , "owner_dm" , "private" , "meta" ] . includes ( channelId . trim ( ) . toLowerCase ( ) ) ;
}
2026-05-22 23:03:29 +01:00
function isCurrentRoomChannelAlias ( channelId : string ) : boolean {
return [ "current" , "current_room" , "room" , "here" ] . includes ( channelId . trim ( ) . toLowerCase ( ) ) ;
}
function repoIdentityDefaultSpeechChannel ( job : JobRecord ) : string | undefined {
return job . guildContext ? . channelId && ! isOwnerDmChannelAlias ( job . guildContext . channelId )
? job . guildContext . channelId
: job . outputChannelId ;
}
2026-05-21 21:45:31 +01:00
function normalizeRepoIdentitySpeechChannel (
identity : { channelPermissions? : Array < { channelId : string ; label? : string } > } ,
job : JobRecord ,
requestedChannelId : string | undefined ,
) : string | undefined {
2026-05-22 23:03:29 +01:00
if ( requestedChannelId && isCurrentRoomChannelAlias ( requestedChannelId ) ) {
return repoIdentityDefaultSpeechChannel ( job ) ;
}
2026-05-21 21:45:31 +01:00
const explicitChannelId = resolveRepoIdentityChannelSelector ( identity , requestedChannelId ) ;
if ( explicitChannelId ) {
return explicitChannelId ;
}
2026-05-21 20:48:02 +01:00
if ( ! requestedChannelId || ! isOwnerDmChannelAlias ( requestedChannelId ) ) {
return requestedChannelId ;
}
if ( repoIdentityOwnerDmExplicitlyAllowed ( job ) ) {
return requestedChannelId ;
}
2026-05-22 23:03:29 +01:00
return repoIdentityDefaultSpeechChannel ( job ) ;
2026-05-21 20:48:02 +01:00
}
2026-05-21 21:45:31 +01:00
function resolveRepoIdentityChannelSelector (
identity : { channelPermissions? : Array < { channelId : string ; label? : string } > } ,
selector : string | undefined ,
) : string | undefined {
const normalized = normalizeChannelSelector ( selector ) ;
if ( ! normalized ) {
return undefined ;
}
return identity . channelPermissions ? . find ( ( permission ) = >
normalizeChannelSelector ( permission . channelId ) === normalized ||
normalizeChannelSelector ( permission . label ) === normalized
) ? . channelId ;
}
function normalizeChannelSelector ( selector : string | undefined ) : string {
return ( selector ? ? "" ) . trim ( ) . replace ( /^#/ , "" ) . toLowerCase ( ) ;
}
2026-05-21 20:48:02 +01:00
function repoIdentityOwnerDmExplicitlyAllowed ( job : JobRecord ) : boolean {
if ( job . command !== "repo-face-rumination" ) {
return true ;
}
return /\bowner-private\b|\bowner dm allowed\b|\bdirect owner dm\b|\bprivate dm to metacrat\b/i . test ( job . prompt ) ;
}
2026-05-21 19:51:52 +01:00
function renderRepoIdentityOwnerDm (
identity : { displayName : string ; id : string } ,
content : string ,
) : string {
2026-05-21 20:48:02 +01:00
return ` ** ${ identity . displayName } ( ${ identity . id } )** \ n ${ content } ` . trim ( ) ;
2026-05-21 19:51:52 +01:00
}
2026-04-24 13:17:00 +01:00
function fitDiscordMessage ( content : string ) : string {
const trimmed = content . trim ( ) ;
if ( trimmed . length <= 1900 ) {
return trimmed ;
}
return ` ${ trimmed . slice ( 0 , 1897 ) } ... ` ;
}
2026-05-21 20:48:02 +01:00
function splitDiscordMessage ( content : string ) : string [ ] {
const trimmed = content . trim ( ) ;
const limit = 1900 ;
if ( trimmed . length <= limit ) {
return [ trimmed ] ;
}
const chunks : string [ ] = [ ] ;
let remaining = trimmed ;
while ( remaining . length > limit ) {
let cut = remaining . lastIndexOf ( "\n\n" , limit ) ;
if ( cut < Math . floor ( limit * 0.6 ) ) {
cut = remaining . lastIndexOf ( "\n" , limit ) ;
}
if ( cut < Math . floor ( limit * 0.6 ) ) {
cut = remaining . lastIndexOf ( " " , limit ) ;
}
if ( cut < Math . floor ( limit * 0.6 ) ) {
cut = limit ;
}
chunks . push ( remaining . slice ( 0 , cut ) . trim ( ) ) ;
remaining = remaining . slice ( cut ) . trim ( ) ;
}
if ( remaining ) {
chunks . push ( remaining ) ;
}
return chunks ;
}
2026-04-24 13:17:00 +01:00
function formatHistoryResults (
results : Awaited < ReturnType < RetrievalService [ "searchHistory" ] > > ,
) : Array < Record < string , unknown > > {
return results . map ( ( result ) = > ( {
score : Number ( result . score . toFixed ( 4 ) ) ,
text : result.text ,
sourceId : result.sourceId ,
channelId : result.metadata.channelId ,
channelName : result.metadata.channelName ,
authorId : result.metadata.authorId ,
authorName : result.metadata.authorName ,
timestamp : result.metadata.timestamp ,
2026-05-02 20:57:10 +01:00
timeContext : formatTimeContext ( result . metadata . timestamp ) ,
2026-04-24 13:17:00 +01:00
jumpUrl : result.metadata.jumpUrl ,
threadId : result.metadata.threadId ,
} ) ) ;
}
function formatSourceResults (
results : Awaited < ReturnType < RetrievalService [ "searchRepositorySources" ] > > ,
) : Array < Record < string , unknown > > {
return results . map ( ( result ) = > ( {
score : Number ( result . score . toFixed ( 4 ) ) ,
text : result.text ,
sourceId : result.sourceId ,
repoName : result.metadata.repoName ,
path : result.metadata.path ,
language : result.metadata.language ,
title : result.metadata.title ,
chunkIndex : Number ( result . metadata . chunkIndex ? ? 0 ) ,
lineStart : Number ( result . metadata . lineStart ? ? 1 ) ,
lineEnd : Number ( result . metadata . lineEnd ? ? 1 ) ,
lastModifiedAt : result.metadata.lastModifiedAt ,
} ) ) ;
}
function formatArchivedMessageContext (
message : ArchivedMessageRecord ,
anchorMessageId : string ,
) : Record < string , unknown > {
return {
id : message.id ,
isAnchor : message.id === anchorMessageId ,
timestamp : message.timestamp ,
2026-05-02 20:57:10 +01:00
timeContext : formatTimeContext ( message . timestamp ) ,
2026-04-24 13:17:00 +01:00
authorId : message.authorId ,
authorName : message.authorName ,
channelId : message.channelId ,
channelName : message.metadata?.channelName ,
threadId : message.threadId ,
content : message.content ,
jumpUrl : message.metadata?.jumpUrl ,
editedAt : message.editedAt ,
} ;
}
2026-05-02 20:57:10 +01:00
function formatTimeContext ( timestamp : string | undefined ) : string | undefined {
if ( ! timestamp ) {
return undefined ;
}
const parsed = new Date ( timestamp ) ;
if ( Number . isNaN ( parsed . getTime ( ) ) ) {
return undefined ;
}
const absolute = formatUtcTimestamp ( timestamp ) ;
const now = new Date ( ) ;
const deltaMs = now . getTime ( ) - parsed . getTime ( ) ;
const absDeltaMs = Math . abs ( deltaMs ) ;
const suffix = deltaMs >= 0 ? "ago" : "from now" ;
if ( absDeltaMs < 60 _000 ) {
return ` ${ absolute } (just now) ` ;
}
const minuteMs = 60 _000 ;
const hourMs = 60 * minuteMs ;
const dayMs = 24 * hourMs ;
const yearMs = 365 * dayMs ;
if ( absDeltaMs < hourMs ) {
const minutes = Math . round ( absDeltaMs / minuteMs ) ;
return ` ${ absolute } ( ${ minutes } minute ${ minutes === 1 ? "" : "s" } ${ suffix } ) ` ;
}
if ( absDeltaMs < dayMs ) {
const hours = Math . round ( absDeltaMs / hourMs ) ;
return ` ${ absolute } ( ${ hours } hour ${ hours === 1 ? "" : "s" } ${ suffix } ) ` ;
}
if ( absDeltaMs < yearMs ) {
const days = Math . round ( absDeltaMs / dayMs ) ;
return ` ${ absolute } ( ${ days } day ${ days === 1 ? "" : "s" } ${ suffix } ) ` ;
}
const years = Math . round ( absDeltaMs / yearMs ) ;
return ` ${ absolute } ( ${ years } year ${ years === 1 ? "" : "s" } ${ suffix } ) ` ;
}
function formatUtcTimestamp ( timestamp : string ) : string {
const date = new Date ( timestamp ) ;
const year = date . getUTCFullYear ( ) ;
const month = ` ${ date . getUTCMonth ( ) + 1 } ` . padStart ( 2 , "0" ) ;
const day = ` ${ date . getUTCDate ( ) } ` . padStart ( 2 , "0" ) ;
const hours = ` ${ date . getUTCHours ( ) } ` . padStart ( 2 , "0" ) ;
const minutes = ` ${ date . getUTCMinutes ( ) } ` . padStart ( 2 , "0" ) ;
return ` ${ year } - ${ month } - ${ day } ${ hours } : ${ minutes } UTC ` ;
}