import { bsky } from "../utils/bsky.ts"; import type { AutonomyDeclaration } from "@voyager/autonomy-lexicon"; import { AUTONOMY_DECLARATION_LEXICON } from "@voyager/autonomy-lexicon"; import { Lexicons } from "@atproto/lexicon"; import { agentContext } from "./agentContext.ts"; /** * AT Protocol record type that includes the $type property * Includes index signature for compatibility with AT Protocol API */ type AutonomyDeclarationRecord = AutonomyDeclaration & { $type: "studio.voyager.account.autonomy"; [key: string]: unknown; }; /** * Autonomy Declaration Lexicon * * The schema is imported from @voyager/autonomy-lexicon package and * is published at voyager.studio for use by all Cloudseeding instances. * * Schema vs. Records: * - The SCHEMA is published once by voyager.studio (domain owner) * - Each agent creates their own RECORD using this schema * * Template users do NOT need to publish this schema - it's already * published and discoverable via DNS resolution. They only need to * create their own autonomy declaration record (done automatically * by submitAutonomyDeclarationRecord below). * * Canonical source: jsr:@voyager/autonomy-lexicon */ export { AUTONOMY_DECLARATION_LEXICON }; export const createAutonomyDeclarationRecord = async () => { const automationLevel = Deno.env.get("AUTOMATION_LEVEL")?.toLowerCase(); const projectDescription = Deno.env.get("PROJECT_DESCRIPTION"); const disclosureUrl = Deno.env.get("DISCLOSURE_URL"); const responsiblePartyType = Deno.env.get("RESPONSIBLE_PARTY_TYPE") ?.toLowerCase(); const responsiblePartyName = Deno.env.get("RESPONSIBLE_PARTY_NAME"); const responsiblePartyContact = Deno.env.get("RESPONSIBLE_PARTY_CONTACT"); const responsiblePartyBsky = Deno.env.get("RESPONSIBLE_PARTY_BSKY"); const declarationRecord: AutonomyDeclarationRecord = { $type: "studio.voyager.account.autonomy", usesGenerativeAI: true, // Always true for this project automationLevel: (automationLevel === "assisted" || automationLevel === "collaborative" || automationLevel === "automated") ? automationLevel : "automated", // Default to automated if not specified or invalid createdAt: new Date().toISOString(), }; // Add description if provided if (projectDescription?.trim()) { declarationRecord.description = projectDescription.trim(); } // Add disclosure URL if provided if (disclosureUrl?.trim()) { declarationRecord.disclosureUrl = disclosureUrl.trim(); } // Add external services from agentContext (already parsed and validated) if (agentContext.externalServices) { declarationRecord.externalServices = agentContext.externalServices; } // Build responsible party object if any fields are provided if ( responsiblePartyType || responsiblePartyName || responsiblePartyContact || responsiblePartyBsky ) { declarationRecord.responsibleParty = {}; // Add type if provided and valid if ( responsiblePartyType === "person" || responsiblePartyType === "organization" ) { declarationRecord.responsibleParty.type = responsiblePartyType; } // Add name if provided if (responsiblePartyName?.trim()) { declarationRecord.responsibleParty.name = responsiblePartyName.trim(); } // Add contact if provided if (responsiblePartyContact?.trim()) { declarationRecord.responsibleParty.contact = responsiblePartyContact .trim(); } // Handle DID or Handle from RESPONSIBLE_PARTY_BSKY if (responsiblePartyBsky?.trim()) { const bskyIdentifier = responsiblePartyBsky.trim(); // Check if it's a DID (starts with "did:") if (bskyIdentifier.startsWith("did:")) { declarationRecord.responsibleParty.did = bskyIdentifier; } else { // Assume it's a handle and resolve to DID try { const authorData = await bsky.getProfile({ actor: bskyIdentifier }); declarationRecord.responsibleParty.did = authorData.data.did; } catch (error) { console.warn( `Failed to resolve DID for identifier ${bskyIdentifier}:`, error, ); // Continue without DID rather than failing } } } } return declarationRecord; }; export const submitAutonomyDeclarationRecord = async () => { const lex = new Lexicons(); try { lex.add(AUTONOMY_DECLARATION_LEXICON as any); const record = await createAutonomyDeclarationRecord(); lex.assertValidRecord( "studio.voyager.account.autonomy", record, ); const repo = bsky.session?.did; if (!repo) { throw new Error("Not logged in - no DID available"); } // Check if record already exists let exists = false; try { await bsky.com.atproto.repo.getRecord({ repo, collection: "studio.voyager.account.autonomy", rkey: "self", }); exists = true; console.log("🔹 Existing autonomy declaration found - updating..."); } catch (error: any) { // Handle "record not found" errors (status 400 with error: "RecordNotFound") const isNotFound = error?.status === 400 && error?.error === "RecordNotFound" || error?.status === 404 || error?.message?.includes("not found") || error?.message?.includes("Could not locate record"); if (isNotFound) { console.log( "🔹 No existing autonomy declaration found - creating new...", ); } else { // Re-throw if it's not a "not found" error throw error; } } // Create or update the record const result = await bsky.com.atproto.repo.putRecord({ repo, collection: "studio.voyager.account.autonomy", rkey: "self", record, }); console.log( `🔹 Autonomy declaration ${exists ? "updated" : "created"} successfully:`, result, ); return result; } catch (error) { console.error("Error submitting autonomy declaration record:", error); throw error; } };