59 lines
3.8 KiB
TypeScript
59 lines
3.8 KiB
TypeScript
/** Dream requests and prepared handoffs in the current world. No storage chapters or host restart. */
|
|
import { BACKGROUND_CONTEXT as context } from '@earendil-works/chord/context'
|
|
import { defineDoc, defineExtension, defineTool, ROOT_CONVERSATION_ID as ROOT } from '@earendil-works/pi-durable'
|
|
import { arr, obj, opt, str } from '@earendil-works/pi-schema'
|
|
import { Fired } from '../automations/refs.ts'
|
|
import { MainAgent, Started } from '../subagents/api.ts'
|
|
import { BuiltInDoc, PauseDoc, DREAM_ASK, ensureAutomation, fired } from './built-ins.ts'
|
|
import { deliver, deliverTask } from './delivery.ts'
|
|
export const DreamApi={name:'dream'}
|
|
export const Next=defineDoc<any>({kind:'dream.next',version:1,scope:'conversation',history:'latest',fork:'initial',checkpointWhen:()=>false,initial:()=>({})})
|
|
const Receipts=defineDoc<Record<string,any>>({kind:'dream.tool-receipts',version:1,scope:'task',checkpointWhen:()=>false,initial:()=>({})})
|
|
const plain=(value:any)=>JSON.parse(JSON.stringify(value))
|
|
export function prepare(args:any,now:number) {
|
|
if(!Array.isArray(args?.learned)||args.learned.length!==3) throw new Error('learned must be 3 lines')
|
|
const text=(value:any,name:string,max:number)=>{
|
|
if(typeof value!=='string'||!value.trim()) throw new Error(`${name} must be nonempty text`)
|
|
if(value.trim().length>max) throw new Error(`${name} must be at most ${max} characters`)
|
|
return value.trim()
|
|
}
|
|
return {learned:args.learned.map((line:any)=>text(line,'learned',200)),handoff:text(args.handoff,'handoff',12000),at:now}
|
|
}
|
|
export default function dream(host:any) {
|
|
const queue=host.resource('dream.ensure',()=>({last:Promise.resolve()}))
|
|
const ensure=()=>{
|
|
const run=queue.last.then(()=>ensureAutomation(host))
|
|
queue.last=run.catch(()=>undefined)
|
|
return run.catch((error:any)=>host.notice(`Dream automations: ${String(error)}`))
|
|
}
|
|
host.onOpen(ensure)
|
|
host.react('mainAgent-started',[Started],(_tx:any,{data}:any)=>{
|
|
// Creation commits run after this reactor transaction completes.
|
|
if(data.kind==='mainAgent') void ensure()
|
|
},{from:'now'})
|
|
host.react('requested',[Fired],(tx:any,{data}:any)=>fired(host,tx,data.id,data.fire),{from:'now'})
|
|
async function change(tx:any,args:any) {
|
|
const mainAgent=(await tx.doc(MainAgent,ROOT))['$main-agent']
|
|
if(mainAgent===undefined) throw new Error('the main agent has no conversation yet')
|
|
if(args.action==='start') {
|
|
await deliver(host,tx,mainAgent,{type:'input',requestId:`dream.requested:${crypto.randomUUID()}`,kind:'dream.requested',data:{},content:`The user requested a dream. ${DREAM_ASK}`})
|
|
return {requested:true}
|
|
}
|
|
if(args.action!=='prepare') throw new Error(`Unknown dream action ${args.action}`)
|
|
const prepared=prepare(args,Date.now())
|
|
;(await tx.doc(Next,ROOT)).prepared=prepared
|
|
await deliver(host,tx,mainAgent,{type:'write',requestId:`dream.prepared:${crypto.randomUUID()}`,kind:'dream.prepared',data:prepared,content:`Prepared dream handoff:\n${prepared.learned.join('\n')}\n\n${prepared.handoff}\nContinue in this world. No chapter switch is available.`})
|
|
return prepared
|
|
}
|
|
host.implement(DreamApi,{operate:(args:any)=>host.harness().commit((tx:any)=>change(tx,args),context)})
|
|
return defineExtension({name:'dream',tasks:[deliverTask(host)],tools:[defineTool({name:'dream',description:'Request a read-only dream branch, or prepare three learned lines and a handoff in the current world. No chapter switch.',parameters:obj({action:str(),learned:opt(arr(str())),handoff:opt(str())}),execute:async(args,api,ctx)=>{
|
|
const result=await api.commit(async(tx:any)=>{
|
|
const receipts=await tx.doc(Receipts,api.taskId)
|
|
if(receipts[api.callId]===undefined) receipts[api.callId]=await change(tx,args)
|
|
return plain(receipts[api.callId])
|
|
},ctx)
|
|
return {content:[{type:'text',text:JSON.stringify(result)}]}
|
|
}})]})
|
|
}
|
|
export const docs=[Next,BuiltInDoc,PauseDoc]
|