2026-05-19 17:24:39 +01:00
#!/usr/bin/env node
2026-05-21 00:10:55 +01:00
import { spawnSync } from "node:child_process" ;
2026-05-19 17:24:39 +01:00
import { createRequire } from "node:module" ;
import { randomUUID } from "node:crypto" ;
2026-05-21 00:10:55 +01:00
import { existsSync , readFileSync } from "node:fs" ;
2026-05-19 17:24:39 +01:00
import { mkdir , readFile , writeFile } from "node:fs/promises" ;
import { dirname , resolve } from "node:path" ;
import { fileURLToPath } from "node:url" ;
2026-07-15 22:16:07 +01:00
import {
commandIdFromOptions ,
crossingProvenanceFromOptions ,
writeCrossingReceipt ,
} from "./bifrost-crossing-documents.mjs" ;
2026-05-19 17:24:39 +01:00
const scriptDir = dirname ( fileURLToPath ( import . meta . url ) ) ;
const repoRoot = resolve ( scriptDir , ".." ) ;
const projectsRoot = resolve ( repoRoot , ".." ) ;
const defaultStorePath = resolve ( repoRoot , ".bifrost" , "agent-transport.cc" ) ;
2026-05-21 00:10:55 +01:00
const bridgeCli = resolve ( repoRoot , "tools" , "bifrost-bridge.mjs" ) ;
const defaultPersonaName = "Bifrost" ;
const defaultPersonaAvatarUrl =
"https://raw.githubusercontent.com/GameCult/Bifrost/main/src/Bifrost.Web/wwwroot/img/bifrost-profile.png" ;
2026-05-19 17:24:39 +01:00
2026-06-22 14:03:07 +01:00
const cultCachePackagePath = resolveFirstExisting ( "CultCache TypeScript runtime" , [
resolve ( projectsRoot , "CultLib" , "packages" , "cultcache-ts" , "package.json" ) ,
resolve ( projectsRoot , "CultCacheTS" , "package.json" ) ,
resolve ( projectsRoot , "CultCacheTS" , "node_modules" , "cultcache-ts" , "package.json" ) ,
] ) ;
const cultNetPackagePath = resolveFirstExisting ( "CultNet TypeScript runtime" , [
resolve ( projectsRoot , "CultLib" , "packages" , "cultnet-ts" , "package.json" ) ,
resolve ( projectsRoot , "CultNetTS" , "package.json" ) ,
] ) ;
const cultCacheRequire = createRequire ( cultCachePackagePath ) ;
const cultNetRequire = createRequire ( cultNetPackagePath ) ;
2026-05-19 17:24:39 +01:00
const {
CultCache ,
SingleFileMessagePackBackingStore ,
defineDocumentType ,
2026-09-30 17:16:39 +02:00
} = cultCacheRequire ( "@gamecult/cultcache-ts" ) ;
2026-05-19 17:24:39 +01:00
const {
CultNetDocumentRegistry ,
defineCultNetDocumentBinding ,
2026-06-22 14:03:07 +01:00
} = cultNetRequire ( "cultnet-ts" ) ;
2026-05-19 17:24:39 +01:00
const { encode , decode } = cultNetRequire ( "@msgpack/msgpack" ) ;
2026-06-22 14:03:07 +01:00
function resolveFirstExisting ( label , candidates ) {
for ( const candidate of candidates ) {
if ( existsSync ( candidate ) ) {
return candidate ;
}
}
throw new Error ( ` ${ label } is unavailable. Tried: ${ candidates . join ( ", " ) } ` ) ;
}
2026-05-19 17:24:39 +01:00
const documentType = "bifrost.agent-transport.update-request" ;
const schemaId = "bifrost.agent-transport.update-request.v0" ;
const validStatuses = new Set ( [ "queued" , "claimed" , "completed" , "cancelled" ] ) ;
const validCloseStatuses = new Set ( [ "completed" , "cancelled" ] ) ;
const updateRequestDefinition = defineDocumentType ( {
type : documentType ,
schemaId ,
schemaName : documentType ,
schemaVersion : "bifrost.agent_transport.update_request.v0" ,
schema : {
parse : parseUpdateRequest ,
} ,
name : "id" ,
indexes : {
targetRepoName : "targetRepoName" ,
targetAgentIdentity : "targetAgentIdentity" ,
status : "status" ,
} ,
members : [
{ slot : 1 , memberName : "id" , typeName : "string" , isName : true } ,
{ slot : 2 , memberName : "targetRepoName" , typeName : "string" , indexAlias : "targetRepoName" } ,
{ slot : 3 , memberName : "targetRepositoryFullName" , typeName : "string" } ,
{ slot : 4 , memberName : "targetAgentIdentity" , typeName : "string" , indexAlias : "targetAgentIdentity" } ,
{ slot : 5 , memberName : "title" , typeName : "string" } ,
{ slot : 6 , memberName : "requestMarkdown" , typeName : "string" } ,
{ slot : 7 , memberName : "priority" , typeName : "number" } ,
{ slot : 8 , memberName : "status" , typeName : "string" , indexAlias : "status" } ,
{ slot : 9 , memberName : "sourceKind" , typeName : "string" } ,
{ slot : 10 , memberName : "sourceChannelId" , typeName : "string" } ,
{ slot : 11 , memberName : "sourceMessageIds" , typeName : "string" , isMany : true } ,
{ slot : 12 , memberName : "sourcePacketPath" , typeName : "string" } ,
{ slot : 13 , memberName : "sourcePromptPath" , typeName : "string" } ,
{ slot : 14 , memberName : "createdByAgent" , typeName : "string" } ,
{ slot : 15 , memberName : "claimedByAgent" , typeName : "string" } ,
{ slot : 16 , memberName : "closeNote" , typeName : "string" } ,
{ slot : 17 , memberName : "createdAt" , typeName : "string" } ,
{ slot : 18 , memberName : "updatedAt" , typeName : "string" } ,
{ slot : 19 , memberName : "claimedAt" , typeName : "string" } ,
{ slot : 20 , memberName : "closedAt" , typeName : "string" } ,
] ,
} ) ;
const cultNetRegistry = new CultNetDocumentRegistry ( [
defineCultNetDocumentBinding ( {
definition : updateRequestDefinition ,
payloadSchemaVersion : "bifrost.agent_transport.update_request.v0" ,
} ) ,
] ) ;
async function main ( ) {
2026-06-22 14:03:07 +01:00
loadBifrostLocalEnv ( ) ;
2026-05-19 17:24:39 +01:00
const [ command , ... rawArgs ] = process . argv . slice ( 2 ) ;
const options = parseArgs ( rawArgs ) ;
if ( ! command || command === "help" || command === "--help" || command === "-h" ) {
printHelp ( ) ;
return ;
}
2026-06-17 05:56:25 +01:00
if ( isMutatingCommand ( command ) ) {
ensureTransportReceiptGate ( options ) ;
}
2026-05-19 17:24:39 +01:00
const storePath = resolveOptionPath ( options . store ? ? defaultStorePath ) ;
const cache = await openCache ( storePath ) ;
switch ( command ) {
case "enqueue" :
await enqueue ( cache , options ) ;
return ;
case "list" :
listRequests ( cache , options ) ;
return ;
case "claim" :
await claim ( cache , options ) ;
return ;
2026-05-20 16:48:20 +01:00
case "release" :
await releaseRequest ( cache , options ) ;
return ;
2026-05-19 17:24:39 +01:00
case "close" :
await closeRequest ( cache , options ) ;
return ;
case "snapshot" :
await writeSnapshot ( cache , options ) ;
return ;
case "apply-snapshot" :
await applySnapshot ( cache , options ) ;
return ;
case "schema" :
printJson ( { documentType , schemaId } ) ;
return ;
default :
throw new Error ( ` Unknown command " ${ command } ". Run "node tools/agent-transport.mjs help". ` ) ;
}
}
2026-06-22 14:03:07 +01:00
function loadBifrostLocalEnv ( ) {
if ( process . env . BIFROST _SKIP _LOCAL _ENV === "true" ) {
return ;
}
loadLocalEnv ( resolve ( repoRoot , ".env" ) ) ;
loadLocalEnv ( resolve ( projectsRoot , "VoidBot" , ".env" ) ) ;
}
2026-06-17 05:56:25 +01:00
function isMutatingCommand ( command ) {
2026-06-17 08:22:30 +01:00
return command === "enqueue"
|| command === "claim"
|| command === "release"
|| command === "close"
|| command === "apply-snapshot" ;
2026-06-17 05:56:25 +01:00
}
function ensureTransportReceiptGate ( options ) {
2026-07-15 22:16:07 +01:00
if ( wantsUnreceiptedActivity ( options ) ) {
2026-06-17 08:38:21 +01:00
throw new Error (
2026-07-15 22:16:07 +01:00
"Bifrost request-lane mutations cannot use --allow-unreceipted-activity or BIFROST_ALLOW_UNRECEIPTED_ACTIVITY. " +
"Use the typed Bifrost CultCache/CultMesh request store instead of mutating off-book." ,
2026-06-17 08:38:21 +01:00
) ;
}
2026-07-15 22:16:07 +01:00
if ( hasRemovedHttpBridgeConfig ( ) ) {
throw new Error (
"BIFROST_BRIDGE_BASE_URL / BIFROST_BRIDGE_TOKEN were removed. " +
"Bifrost request-lane receipts are written to the typed CultCache/CultMesh store." ,
) ;
2026-06-17 05:56:25 +01:00
}
}
2026-07-15 22:16:07 +01:00
function hasRemovedHttpBridgeConfig ( ) {
return Boolean ( optionalString ( process . env . BIFROST _BRIDGE _BASE _URL ) || optionalString ( process . env . BIFROST _BRIDGE _TOKEN ) ) ;
2026-06-17 08:38:21 +01:00
}
function wantsUnreceiptedActivity ( options ) {
2026-06-17 05:56:25 +01:00
return options [ "allow-unreceipted-activity" ] === "true" || process . env . BIFROST _ALLOW _UNRECEIPTED _ACTIVITY === "true" ;
}
2026-05-19 17:24:39 +01:00
async function openCache ( storePath ) {
const cache = CultCache . builder ( )
. withDocumentType ( updateRequestDefinition )
. withGenericStore ( new SingleFileMessagePackBackingStore ( storePath ) )
. build ( ) ;
await cache . pullAllBackingStores ( ) ;
return cache ;
}
async function enqueue ( cache , options ) {
const now = new Date ( ) . toISOString ( ) ;
const requestMarkdown = await readRequestMarkdown ( options ) ;
const request = parseUpdateRequest ( {
id : options . id ? ? ` req_ ${ randomUUID ( ) } ` ,
targetRepoName : requireOption ( options , "repo" ) ,
targetRepositoryFullName : optionalString ( options [ "repo-full-name" ] ) ,
targetAgentIdentity : optionalString ( options . agent ) ,
title : requireOption ( options , "title" ) ,
requestMarkdown ,
priority : parseInteger ( options . priority ? ? "50" , "priority" ) ,
status : "queued" ,
sourceKind : optionalString ( options [ "source-kind" ] ) ? ? "manual" ,
sourceChannelId : optionalString ( options [ "source-channel-id" ] ) ,
sourceMessageIds : parseCsv ( options [ "source-message-ids" ] ) ,
sourcePacketPath : optionalString ( options [ "packet-path" ] ) ,
sourcePromptPath : optionalString ( options [ "prompt-path" ] ) ,
createdByAgent : optionalString ( options [ "created-by" ] ) ,
claimedByAgent : undefined ,
closeNote : undefined ,
createdAt : now ,
updatedAt : now ,
claimedAt : undefined ,
closedAt : undefined ,
} ) ;
2026-05-21 00:10:55 +01:00
await mirrorEnqueuedRequestOrThrow ( request , options ) ;
2026-05-19 17:24:39 +01:00
await cache . put ( updateRequestDefinition , request . id , request ) ;
2026-06-17 04:49:22 +01:00
await recordTransportReceiptOrThrow ( request , {
activityKind : "Queued" ,
status : request . status ,
actorName : request . createdByAgent ? ? "system" ,
note : "Update request queued." ,
2026-07-15 22:16:07 +01:00
} , options ) ;
2026-05-19 17:24:39 +01:00
printJson ( request ) ;
}
2026-05-21 00:10:55 +01:00
async function mirrorEnqueuedRequestOrThrow ( request , options ) {
const channelId = resolveMirrorChannelId ( options ) ;
if ( ! channelId ) {
if ( allowsUnmirrored ( options ) ) {
return ;
}
throw new Error (
"Bifrost update requests require a Discord mirror. Set BIFROST_DISCORD_CHANNEL_ID, pass --mirror-channel-id, or use --allow-unmirrored true only for explicit fixtures." ,
) ;
}
const mirrorContent =
await readOptionalTextOption ( options , "mirror-content" ) ? ?
renderRequestMirrorFallback ( request ) ;
const personaName = optionalString ( options [ "mirror-persona-name" ] )
? ? optionalString ( process . env . BIFROST _DISCORD _PERSONA _NAME )
? ? defaultPersonaName ;
const personaAvatarUrl =
optionalString ( options [ "mirror-persona-avatar-url" ] ) ? ?
optionalString ( process . env . BIFROST _DISCORD _PERSONA _AVATAR _URL ) ? ?
optionalString ( process . env . DISCORD _PERSONA _AVATAR _URL _BIFROST ) ? ?
defaultPersonaAvatarUrl ;
runNodeJson ( [
bridgeCli ,
"discord-post" ,
"--channel-id" , channelId ,
"--content" , mirrorContent ,
"--persona-name" , personaName ,
2026-06-17 04:03:14 +01:00
"--source-kind" , documentType ,
"--source-id" , request . id ,
"--authority-ref" , "bifrost_agent_transport_enqueue" ,
... optionalArg ( "--target-repository-full-name" , request . targetRepositoryFullName ? ? request . targetRepoName ) ,
2026-05-21 00:10:55 +01:00
... optionalArg ( "--persona-avatar-url" , personaAvatarUrl ) ,
... optionalArg ( "--reply-to-message-id" , options [ "mirror-reply-to-message-id" ] ) ,
2026-06-17 12:06:20 +01:00
... bridgeRecoveryArgs ( options ) ,
2026-05-21 00:10:55 +01:00
... ( options [ "mirror-dry-run" ] === "true" ? [ "--dry-run" , "true" ] : [ ] ) ,
] , repoRoot ) ;
}
function resolveMirrorChannelId ( options ) {
return (
optionalString ( options [ "mirror-channel-id" ] ) ? ?
optionalString ( process . env . BIFROST _DISCORD _CHANNEL _ID ) ? ?
optionalString ( process . env . DISCORD _BIFROST _CHANNEL _ID )
) ;
}
function allowsUnmirrored ( options ) {
return options [ "allow-unmirrored" ] === "true" || process . env . BIFROST _ALLOW _UNMIRRORED _GOVERNANCE === "true" ;
}
2026-06-17 12:06:20 +01:00
function bridgeRecoveryArgs ( options ) {
2026-07-15 22:16:07 +01:00
return [ ] ;
2026-06-17 12:06:20 +01:00
}
2026-05-21 00:10:55 +01:00
function renderRequestMirrorFallback ( request ) {
const actor = request . createdByAgent ? ` ${ request . createdByAgent } queued ` : "Queued" ;
return [
2026-05-21 19:55:53 +01:00
` Bifrost update request queued ` ,
2026-05-21 00:10:55 +01:00
"" ,
` ** ${ request . targetRepoName } : ${ request . title } ** ` ,
2026-05-21 19:55:53 +01:00
` Request: \` ${ request . id } \` ` ,
` Target: ${ request . targetAgentIdentity ? ` ${ request . targetAgentIdentity } / ` : "" } ${ request . targetRepoName } ` ,
` Status: queued ` ,
` Codex job: no - this request is waiting for the dispatcher ` ,
"" ,
` ${ actor } this work request. ` ,
2026-05-21 00:10:55 +01:00
"" ,
summarizeRequestMarkdown ( request . requestMarkdown ) ,
"" ,
2026-05-21 19:55:53 +01:00
` Next: a separate \` Bifrost Codex dispatch started \` receipt means a Codex turn actually began. ` ,
2026-05-21 00:10:55 +01:00
] . join ( "\n" ) ;
}
function summarizeRequestMarkdown ( markdown ) {
const text = String ( markdown ) ;
const requestSection = text . match ( /## Request\s+([\s\S]*?)(?:\n## |\n```|$)/i ) ? . [ 1 ] ? . trim ( ) ;
const firstUsefulBlock = ( requestSection ? ? text )
. split ( /\n\s*\n/ )
. map ( ( block ) => block . replace ( /\s+/g , " " ) . trim ( ) )
. find ( ( block ) => block && ! block . startsWith ( "#" ) ) ;
return truncate ( firstUsefulBlock ? ? "A Bifrost update request was queued for Codex dispatch." , 360 ) ;
}
function truncate ( value , maxLength ) {
return value . length > maxLength ? ` ${ value . slice ( 0 , maxLength - 3 ) } ... ` : value ;
}
2026-05-19 17:24:39 +01:00
function listRequests ( cache , options ) {
const requests = filteredRequests ( cache , options )
. sort ( compareRequests ) ;
printJson ( requests ) ;
}
async function claim ( cache , options ) {
const repo = optionalString ( options . repo ) ;
if ( ! repo ) {
throw new Error ( "claim requires --repo so an agent cannot grab work outside its jurisdiction." ) ;
}
const agent = optionalString ( options . agent ) ;
const claimedBy = optionalString ( options [ "claimed-by" ] ) ? ? agent ? ? "codex" ;
const request = filteredRequests ( cache , {
... options ,
status : "queued" ,
} )
. filter ( ( candidate ) => equalsIgnoreCase ( candidate . targetRepoName , repo ) )
. filter ( ( candidate ) => ! agent || equalsIgnoreCase ( candidate . targetAgentIdentity , agent ) || ! candidate . targetAgentIdentity )
. sort ( compareRequests ) [ 0 ] ;
if ( ! request ) {
printJson ( null ) ;
return ;
}
const now = new Date ( ) . toISOString ( ) ;
const claimed = parseUpdateRequest ( {
... request ,
status : "claimed" ,
claimedByAgent : claimedBy ,
claimedAt : now ,
updatedAt : now ,
} ) ;
await cache . put ( updateRequestDefinition , claimed . id , claimed ) ;
2026-06-17 04:49:22 +01:00
await recordTransportReceiptOrThrow ( claimed , {
activityKind : "Claimed" ,
status : claimed . status ,
actorName : claimed . claimedByAgent ? ? claimed . targetAgentIdentity ? ? "codex" ,
note : ` Request claimed for ${ claimed . targetRepoName } . ` ,
2026-07-15 22:16:07 +01:00
} , options ) ;
2026-05-19 17:24:39 +01:00
printJson ( claimed ) ;
}
async function closeRequest ( cache , options ) {
const id = requireOption ( options , "id" ) ;
const status = requireOption ( options , "status" ) ;
if ( ! validCloseStatuses . has ( status ) ) {
throw new Error ( "--status for close must be completed or cancelled." ) ;
}
const current = cache . get ( updateRequestDefinition , id ) ;
if ( ! current ) {
throw new Error ( ` No update request found for id " ${ id } ". ` ) ;
}
const now = new Date ( ) . toISOString ( ) ;
const closed = parseUpdateRequest ( {
... current ,
status ,
closeNote : optionalString ( options . note ) ,
closedAt : now ,
updatedAt : now ,
} ) ;
await cache . put ( updateRequestDefinition , closed . id , closed ) ;
2026-06-17 04:49:22 +01:00
await recordTransportReceiptOrThrow ( closed , {
activityKind : "Closed" ,
status : closed . status ,
actorName : current . claimedByAgent ? ? current . targetAgentIdentity ? ? "system" ,
note : closed . closeNote || ` Request closed as ${ closed . status } . ` ,
2026-07-15 22:16:07 +01:00
} , options ) ;
2026-05-19 17:24:39 +01:00
printJson ( closed ) ;
}
2026-05-20 16:48:20 +01:00
async function releaseRequest ( cache , options ) {
const id = requireOption ( options , "id" ) ;
const current = cache . get ( updateRequestDefinition , id ) ;
if ( ! current ) {
throw new Error ( ` No update request found for id " ${ id } ". ` ) ;
}
if ( current . status !== "claimed" ) {
throw new Error ( ` Only claimed requests can be released; " ${ id } " is ${ current . status } . ` ) ;
}
const now = new Date ( ) . toISOString ( ) ;
const released = parseUpdateRequest ( {
... current ,
status : "queued" ,
claimedByAgent : undefined ,
claimedAt : undefined ,
closeNote : optionalString ( options . note ) ,
updatedAt : now ,
} ) ;
await cache . put ( updateRequestDefinition , released . id , released ) ;
2026-06-17 04:49:22 +01:00
await recordTransportReceiptOrThrow ( released , {
activityKind : "Released" ,
status : released . status ,
actorName : current . claimedByAgent ? ? current . targetAgentIdentity ? ? "system" ,
note : released . closeNote || "Claim released back to the queue." ,
2026-07-15 22:16:07 +01:00
} , options ) ;
2026-05-20 16:48:20 +01:00
printJson ( released ) ;
}
2026-05-19 17:24:39 +01:00
async function writeSnapshot ( cache , options ) {
const message = cultNetRegistry . createRawSnapshotResponse (
cache ,
options [ "message-id" ] ? ? ` snapshot_ ${ randomUUID ( ) } ` ,
) ;
if ( options . out ) {
const outPath = resolveOptionPath ( options . out ) ;
await mkdir ( dirname ( outPath ) , { recursive : true } ) ;
await writeFile ( outPath , encode ( message ) ) ;
printJson ( {
schemaVersion : message . schemaVersion ,
messageId : message . messageId ,
documentCount : message . documents . length ,
out : outPath ,
} ) ;
return ;
}
printJson ( {
... message ,
documents : message . documents . map ( ( document ) => ( {
... document ,
payload : Buffer . from ( document . payload ) . toString ( "base64url" ) ,
payloadEncoding : "messagepack+base64url" ,
} ) ) ,
} ) ;
}
async function applySnapshot ( cache , options ) {
const inPath = resolveOptionPath ( requireOption ( options , "in" ) ) ;
2026-06-17 08:22:30 +01:00
const beforeRequests = mapRequestsById ( cache . getAll ( updateRequestDefinition ) ) ;
2026-05-19 17:24:39 +01:00
const message = decode ( await readFile ( inPath ) ) ;
await cultNetRegistry . applyRawSnapshotResponse ( cache , message ) ;
2026-06-17 08:22:30 +01:00
const afterRequests = mapRequestsById ( cache . getAll ( updateRequestDefinition ) ) ;
const receipts = deriveSnapshotReceipts ( beforeRequests , afterRequests ) ;
try {
for ( const { request , receipt } of receipts ) {
2026-07-15 22:16:07 +01:00
await recordTransportReceiptOrThrow ( request , receipt , options ) ;
2026-06-17 08:22:30 +01:00
}
} catch ( error ) {
await restoreRequests ( cache , beforeRequests , afterRequests ) ;
throw error ;
}
2026-05-19 17:24:39 +01:00
printJson ( {
applied : true ,
schemaVersion : message . schemaVersion ,
messageId : message . messageId ,
documentCount : message . documents ? . length ? ? 0 ,
2026-06-17 08:22:30 +01:00
receiptCount : receipts . length ,
2026-05-19 17:24:39 +01:00
in : inPath ,
} ) ;
}
2026-06-17 08:22:30 +01:00
function mapRequestsById ( requests ) {
return new Map ( requests . map ( ( request ) => [ request . id , request ] ) ) ;
}
function deriveSnapshotReceipts ( beforeRequests , afterRequests ) {
const receipts = [ ] ;
for ( const [ id , current ] of afterRequests . entries ( ) ) {
const previous = beforeRequests . get ( id ) ;
if ( previous && stableJson ( previous ) === stableJson ( current ) ) {
continue ;
}
receipts . push ( {
request : current ,
receipt : buildSnapshotReceipt ( previous , current ) ,
} ) ;
}
return receipts ;
}
function buildSnapshotReceipt ( previous , current ) {
if ( ! previous ) {
return {
activityKind : activityKindForSnapshotState ( current . status ) ,
status : current . status ,
actorName : receiptActorName ( current ) ,
note : ` Snapshot import created request state as ${ current . status } . ` ,
} ;
}
if ( previous . status === "claimed" && current . status === "queued" ) {
return {
activityKind : "Released" ,
status : current . status ,
actorName : previous . claimedByAgent ? ? receiptActorName ( current ) ,
note : "Snapshot import released a claimed request back to the queue." ,
} ;
}
if ( previous . status !== "claimed" && current . status === "claimed" ) {
return {
activityKind : "Claimed" ,
status : current . status ,
actorName : current . claimedByAgent ? ? receiptActorName ( current ) ,
note : ` Snapshot import claimed request for ${ current . targetRepoName } . ` ,
} ;
}
if ( current . status === "completed" || current . status === "cancelled" ) {
return {
activityKind : "Closed" ,
status : current . status ,
actorName : current . claimedByAgent ? ? previous . claimedByAgent ? ? receiptActorName ( current ) ,
note : current . closeNote || ` Snapshot import closed request as ${ current . status } . ` ,
} ;
}
return {
activityKind : activityKindForSnapshotState ( current . status ) ,
status : current . status ,
actorName : receiptActorName ( current ) ,
note : "Snapshot import refreshed request state." ,
} ;
}
function activityKindForSnapshotState ( status ) {
return status === "claimed" ? "Claimed" : "Queued" ;
}
function receiptActorName ( request ) {
return request . claimedByAgent ? ? request . createdByAgent ? ? request . targetAgentIdentity ? ? "system" ;
}
async function restoreRequests ( cache , beforeRequests , afterRequests ) {
for ( const [ id , request ] of beforeRequests . entries ( ) ) {
await cache . put ( updateRequestDefinition , id , request ) ;
}
for ( const id of afterRequests . keys ( ) ) {
if ( ! beforeRequests . has ( id ) ) {
await cache . delete ( updateRequestDefinition , id ) ;
}
}
}
function stableJson ( value ) {
return JSON . stringify ( value ) ;
}
2026-07-15 22:16:07 +01:00
async function recordTransportReceiptOrThrow ( request , receipt , options = { } ) {
const commandId = commandIdFromOptions ( options ) || ` agent-transport: ${ request . id } : ${ receipt . activityKind } ` ;
const provenance = crossingProvenanceFromOptions ( {
... options ,
"cultmesh-command-id" : commandId ,
"source-kind" : request . sourceKind || "bifrost.agent-transport.update-request" ,
"source-id" : request . id ,
"request-id" : request . id ,
"authority-ref" : "bifrost_agent_transport" ,
identity : receipt . actorName ,
} ) ;
const now = new Date ( ) . toISOString ( ) ;
await writeCrossingReceipt ( {
receiptId : ` crossing_ ${ slugifyReceiptPart ( commandId ) } _ ${ slugifyReceiptPart ( receipt . activityKind ) } ` ,
commandId ,
crossingKind : "agent_transport.request" ,
status : request . status === "cancelled" ? "cancelled" : "completed" ,
ok : true ,
requestedAt : request . createdAt || now ,
startedAt : request . updatedAt || now ,
completedAt : now ,
actor : {
kind : "agent" ,
name : receipt . actorName || "system" ,
bifrostIdentity : receipt . actorName || "system" ,
2026-06-17 04:49:22 +01:00
} ,
2026-07-15 22:16:07 +01:00
source : provenance . source ,
authority : provenance . authority ,
epiphany : provenance . epiphany ,
target : {
surface : "agent-transport" ,
locator : request . id ,
repositoryFullName : request . targetRepositoryFullName || request . targetRepoName ,
2026-06-17 04:49:22 +01:00
requestId : request . id ,
2026-07-15 22:16:07 +01:00
status : request . status ,
} ,
externalReceipt : {
id : request . id ,
transport : "cultcache" ,
url : "" ,
} ,
surfaceReceipt : {
2026-06-17 04:49:22 +01:00
activityKind : receipt . activityKind ,
2026-07-15 22:16:07 +01:00
note : receipt . note || "" ,
requestStatus : receipt . status ,
} ,
} , options ) ;
2026-06-17 04:49:22 +01:00
}
2026-05-19 17:24:39 +01:00
function filteredRequests ( cache , options ) {
return cache . getAll ( updateRequestDefinition )
. filter ( ( request ) => ! options . status || request . status === options . status )
. filter ( ( request ) => ! options . repo || equalsIgnoreCase ( request . targetRepoName , options . repo ) )
. filter ( ( request ) => ! options . agent || equalsIgnoreCase ( request . targetAgentIdentity , options . agent ) ) ;
}
function compareRequests ( left , right ) {
if ( right . priority !== left . priority ) {
return right . priority - left . priority ;
}
return left . createdAt . localeCompare ( right . createdAt ) ;
}
async function readRequestMarkdown ( options ) {
if ( options [ "request-file" ] ) {
return readFile ( resolveOptionPath ( options [ "request-file" ] ) , "utf8" ) ;
}
return requireOption ( options , "request" ) ;
}
2026-05-21 00:10:55 +01:00
async function readOptionalTextOption ( options , name ) {
const file = optionalString ( options [ ` ${ name } -file ` ] ) ;
if ( file ) {
return readFile ( resolveOptionPath ( file ) , "utf8" ) ;
}
return optionalString ( options [ name ] ) ;
}
2026-05-19 17:24:39 +01:00
function parseUpdateRequest ( input ) {
if ( ! input || typeof input !== "object" ) {
throw new Error ( "Update request must be an object." ) ;
}
const request = {
id : requireString ( input . id , "id" ) ,
targetRepoName : requireString ( input . targetRepoName , "targetRepoName" ) ,
targetRepositoryFullName : optionalString ( input . targetRepositoryFullName ) ,
targetAgentIdentity : optionalString ( input . targetAgentIdentity ) ,
title : requireString ( input . title , "title" ) ,
requestMarkdown : requireString ( input . requestMarkdown , "requestMarkdown" ) ,
priority : requireNumber ( input . priority , "priority" ) ,
status : requireString ( input . status , "status" ) ,
sourceKind : optionalString ( input . sourceKind ) ? ? "manual" ,
sourceChannelId : optionalString ( input . sourceChannelId ) ,
sourceMessageIds : normalizeStringArray ( input . sourceMessageIds , "sourceMessageIds" ) ,
sourcePacketPath : optionalString ( input . sourcePacketPath ) ,
sourcePromptPath : optionalString ( input . sourcePromptPath ) ,
createdByAgent : optionalString ( input . createdByAgent ) ,
claimedByAgent : optionalString ( input . claimedByAgent ) ,
closeNote : optionalString ( input . closeNote ) ,
createdAt : requireString ( input . createdAt , "createdAt" ) ,
updatedAt : requireString ( input . updatedAt , "updatedAt" ) ,
claimedAt : optionalString ( input . claimedAt ) ,
closedAt : optionalString ( input . closedAt ) ,
} ;
if ( ! validStatuses . has ( request . status ) ) {
throw new Error ( ` Update request status " ${ request . status } " is not valid. ` ) ;
}
return request ;
}
function parseArgs ( args ) {
const options = { } ;
for ( let index = 0 ; index < args . length ; index += 1 ) {
const token = args [ index ] ;
if ( ! token . startsWith ( "--" ) ) {
throw new Error ( ` Unexpected argument " ${ token } ". ` ) ;
}
const key = token . slice ( 2 ) ;
const next = args [ index + 1 ] ;
if ( ! next || next . startsWith ( "--" ) ) {
options [ key ] = "true" ;
continue ;
}
options [ key ] = next ;
index += 1 ;
}
return options ;
}
function requireOption ( options , name ) {
const value = options [ name ] ;
if ( typeof value !== "string" || value . trim ( ) . length === 0 ) {
throw new Error ( ` Missing required option -- ${ name } . ` ) ;
}
return value . trim ( ) ;
}
function optionalString ( value ) {
if ( typeof value !== "string" ) {
return undefined ;
}
const trimmed = value . trim ( ) ;
return trimmed . length > 0 ? trimmed : undefined ;
}
2026-05-21 00:10:55 +01:00
function optionalArg ( flag , value ) {
const normalized = optionalString ( value ) ;
return normalized ? [ flag , normalized ] : [ ] ;
}
2026-05-19 17:24:39 +01:00
function requireString ( value , field ) {
const normalized = optionalString ( value ) ;
if ( ! normalized ) {
throw new Error ( ` Update request field " ${ field } " must be a non-empty string. ` ) ;
}
return normalized ;
}
function requireNumber ( value , field ) {
const number = typeof value === "number" ? value : Number ( value ) ;
if ( ! Number . isFinite ( number ) ) {
throw new Error ( ` Update request field " ${ field } " must be a finite number. ` ) ;
}
return number ;
}
function parseInteger ( value , field ) {
const number = Number ( value ) ;
if ( ! Number . isInteger ( number ) ) {
throw new Error ( ` -- ${ field } must be an integer. ` ) ;
}
return number ;
}
function parseCsv ( value ) {
if ( typeof value !== "string" || value . trim ( ) . length === 0 ) {
return [ ] ;
}
return value
. split ( "," )
. map ( ( item ) => item . trim ( ) )
. filter ( ( item ) => item . length > 0 ) ;
}
function normalizeStringArray ( value , field ) {
if ( value === undefined || value === null ) {
return [ ] ;
}
if ( ! Array . isArray ( value ) || value . some ( ( item ) => typeof item !== "string" ) ) {
throw new Error ( ` Update request field " ${ field } " must be an array of strings. ` ) ;
}
return value ;
}
function equalsIgnoreCase ( left , right ) {
return typeof left === "string"
&& typeof right === "string"
&& left . toLowerCase ( ) === right . toLowerCase ( ) ;
}
2026-07-15 22:16:07 +01:00
function slugifyReceiptPart ( value ) {
return String ( value )
. trim ( )
. toLowerCase ( )
. replace ( /[^a-z0-9._:-]+/g , "-" )
. replace ( /^-+|-+$/g , "" )
. slice ( 0 , 120 ) || "receipt" ;
}
2026-05-19 17:24:39 +01:00
function resolveOptionPath ( path ) {
return resolve ( process . cwd ( ) , path ) ;
}
2026-05-21 00:10:55 +01:00
function runNodeJson ( args , cwd ) {
const result = spawnSync ( process . execPath , args , {
cwd ,
encoding : "utf8" ,
stdio : [ "ignore" , "pipe" , "pipe" ] ,
windowsHide : true ,
timeout : 30000 ,
} ) ;
if ( result . status !== 0 ) {
const reason = result . error ? . message ? ? result . stderr ? ? result . stdout ;
throw new Error ( ` node ${ args . join ( " " ) } failed with ${ result . status ? ? "unknown" } : \n ${ reason } ` ) ;
}
return JSON . parse ( result . stdout ) ;
}
function loadLocalEnv ( path ) {
if ( ! existsSync ( path ) ) {
return ;
}
for ( const line of readFileSync ( path , "utf8" ) . split ( /\r?\n/ ) ) {
const trimmed = line . trim ( ) ;
if ( ! trimmed || trimmed . startsWith ( "#" ) ) {
continue ;
}
const separator = trimmed . indexOf ( "=" ) ;
if ( separator === - 1 ) {
continue ;
}
const key = trimmed . slice ( 0 , separator ) . trim ( ) ;
if ( process . env [ key ] ) {
continue ;
}
let value = trimmed . slice ( separator + 1 ) . trim ( ) ;
if (
value . length >= 2 &&
( ( value . startsWith ( '"' ) && value . endsWith ( '"' ) ) || ( value . startsWith ( "'" ) && value . endsWith ( "'" ) ) )
) {
value = value . slice ( 1 , - 1 ) ;
}
process . env [ key ] = value ;
}
}
2026-05-19 17:24:39 +01:00
function printJson ( value ) {
process . stdout . write ( ` ${ JSON . stringify ( value , null , 2 ) } \n ` ) ;
}
function printHelp ( ) {
process . stdout . write ( ` Bifrost agent transport
Commands :
enqueue Store a queued update request in CultCache
list List requests , optionally filtered by -- repo , -- agent , -- status
claim Claim the highest - priority queued request for -- repo
close Mark a request completed or cancelled
2026-05-20 16:48:20 +01:00
release Return a claimed request to the queue after dispatch setup fails
2026-05-19 17:24:39 +01:00
snapshot Write or print a CultNet raw snapshot response
apply - snapshot Apply a CultNet raw snapshot response from -- in
schema Print the document type and schema id
Common options :
-- store < path > Override the . cc store path
2026-05-21 00:10:55 +01:00
Mirror options for enqueue :
-- mirror - channel - id < id > Mirror queued requests to Discord ; defaults to BIFROST _DISCORD _CHANNEL _ID
-- mirror - persona - name < name > Render the mirror through this persona ; defaults to Bifrost
-- mirror - persona - avatar - url < url > Optional persona avatar for the mirror
-- mirror - content < text > Optional custom mirror text
-- mirror - content - file < path > Read custom mirror text from a file
-- mirror - dry - run true Exercise mirror plumbing without posting to Discord
-- allow - unmirrored true Fixture / debug escape hatch ; production writes should not use this
2026-05-19 17:24:39 +01:00
Examples :
node tools / agent - transport . mjs enqueue -- repo AetheriaLore -- agent nibu -- title "Wavecrafters" -- request - file packet . md -- priority 80
node tools / agent - transport . mjs claim -- repo AetheriaLore -- agent nibu -- claimed - by nibu
node tools / agent - transport . mjs snapshot -- out transport . msgpack
` );
}
main ( ) . catch ( ( error ) => {
process . stderr . write ( ` ${ error instanceof Error ? error . message : String ( error ) } \n ` ) ;
process . exitCode = 1 ;
} ) ;