/** Snapshot-loaded schedules, pause cases, and durable admission across SIGKILL. */ import { BACKGROUND_CONTEXT as context } from '@earendil-works/chord/context' import { createModels } from '@earendil-works/pi-ai/models' import { fauxProvider,fauxAssistantMessage,fauxToolCall } from '@earendil-works/pi-ai/providers/faux' import { Harness,createRegistry } from '@earendil-works/pi-durable' import { openStorage } from './sqlite' import { loadExtensions,fs } from './extensions' import { cases,assert,until } from './automations-test-support' import { clean } from './automations-pause-fixture' import { emit,defineEvent } from './events' const models=createModels(),faux=fauxProvider({tokensPerSecond:100000});models.setProvider(faux.provider) const registry=createRegistry(),storage=await openStorage(globalThis.storagePath) const harness=await Harness.open(storage,{models,registry,now:()=>Date.now()+(globalThis.automationMode==='restart'?7200000:0),settings:{get extensions(){return registry.snapshot().installed()}}},context) const root=await harness.root(context,{agent:{model:{provider:'faux',modelId:'faux-1'}}}) const loader=await loadExtensions(harness,registry,storage,models,context) assert(!loader.state().error,loader.state().error) globalThis.automationBase=`file:///run/sausage/extensions/${loader.state().good}/extensions` const {Automations}=await import(/* @vite-ignore */ `${globalThis.automationBase}/automations/server.ts`) const call=async(args:any,conversation=root)=>{ faux.setResponses([fauxAssistantMessage([fauxToolCall('automation',args)],{stopReason:'toolUse'}),fauxAssistantMessage('done')]) await (await conversation.submit({type:'input',content:'Configure automation'},context)).wait(context) const result=(await conversation.context(context)).entries.filter((entry:any)=>entry.kind==='pi.tool-result').at(-1) assert(result && !result.model?.[0]?.isError,JSON.stringify(result)) } harness.resume() if(globalThis.automationMode==='prepare') { await call({action:'create',name:'restart once',schedule:`at ${new Date(Date.now()+3600000).toISOString()}`,subscribers:[root.id]}) await until(async()=>Object.values((await harness.snapshot(Automations,context)).automations).length===1) const a=Object.values((await harness.snapshot(Automations,context)).automations)[0] as any await until(async()=>(await harness.getTask(a.scheduler,context)).state.status==='sleeping') await fs.write('/automation-ready','ready') await new Promise(()=>{}) } else if(globalThis.automationMode==='restart') { faux.setResponses([fauxAssistantMessage('woken once')]) await until(async()=>(await harness.snapshot(Automations,context)).automations.A1.fires===1) await until(async()=>(await root.context(context)).entries.some((entry:any)=>entry.model?.some((m:any)=>typeof m.content==='string'&&m.content.includes('[automation A1 fired]')))) await harness.waitForIdle(context) const ledger=await harness.snapshot(Automations,context) assert(ledger.automations.A1.fires===1&&!ledger.automations.A1.enabled,'Exactly one durable fire') const entries=(await root.context(context)).entries.filter((entry:any)=>entry.model?.some((m:any)=>m.role==='user'&&typeof m.content==='string'&&m.content.includes('[automation A1 fired]'))) assert(entries.length===1,'Exactly one subscriber input') await loader.close();await harness.close(context);await storage.close() } else if(globalThis.automationMode==='triggers') { await call({action:'create',name:'event',event:{name:'workspaces.landed',where:{title:'ship'}},subscribers:[root.id]}) await harness.commit((tx:any)=>emit(tx,defineEvent('workspaces.landed'),{sha:'abcdef',title:'ship'}),context) await until(async()=>(await harness.snapshot(Automations,context)).automations.A1.fires===1) await until(async()=>(await root.context(context)).entries.some((entry:any)=>entry.model?.some((m:any)=>typeof m.content==='string'&&m.content.includes('[automation A1 fired]')))) await harness.waitForIdle(context) await call({action:'create',name:'context',condition:{metric:'context',of:`#${root.id}`,above:75,cooldownMs:0},subscribers:[root.id]}) const {ContextUsageDoc}=await import(/* @vite-ignore */ `${globalThis.automationBase}/automations/triggers.ts`) await harness.commit(async(tx:any)=>Object.assign(await tx.doc(ContextUsageDoc,root.id),{percent:80,tokens:800,window:1000}),context) await until(async()=>(await harness.snapshot(Automations,context)).automations.A2.fires===1) await until(async()=>(await root.context(context)).entries.some((entry:any)=>entry.model?.some((m:any)=>typeof m.content==='string'&&m.content.includes('[automation A2 fired]')))) await harness.waitForIdle(context) await loader.close();await harness.close(context);await storage.close() } else if(globalThis.automationMode==='grant') { for(const path of ['/src/sausage/automations','/src/sausage/automations/A1']) await fs.mkdir(path) await fs.write('/src/sausage/automations/A1/script.sh',`curl http://host.sausage/whoami\nprintf '\\n'\ncurl http://127.0.0.1:${globalThis.automationPort}/token\nprintf '\\nFIRE delivered\\n'\nprintf 'FIRE ignored\\n' >&2\nprintf 'DONE\\n'`) const subscriber=await harness.createConversation({ownership:{kind:'ownerless'}},context) await subscriber.configure({model:{provider:'faux',modelId:'faux-1'}},context) const {Started}=await import(/* @vite-ignore */ `${globalThis.automationBase}/messages/addresses.ts`) const entry=await harness.commit((tx:any)=>tx.appendEntry(subscriber.id,{kind:'automation.origin',data:{}}),context) const creatorRecord=await harness.commit(async(tx:any)=>{ const fork=await tx.forkConversation(subscriber.id,entry.id,{ownership:{kind:'ownerless'}}) ;(await tx.doc(Started,fork.id)).by=root.id return fork },context) const creator=await harness.conversation(creatorRecord.id,context) await call({action:'create',name:'creator grant',ask:'Fetch and fire',subscribers:[subscriber.id]},creator) await until(async()=>(await harness.snapshot(Automations,context))?.automations.A1?.script?.state==='done') await until(async()=>(await subscriber.context(context)).entries.some((entry:any)=>entry.model?.some((m:any)=>typeof m.content==='string'&&m.content.includes('[automation A1 fired]')))) await harness.waitForIdle(context) const ledger=await harness.snapshot(Automations,context) assert(ledger.automations.A1.fires===1,'Only stdout fires') assert(ledger.automations.A1.executionConversation===creator.id,'The creator owns execution') const log=await fs.read('/src/sausage/automations/A1/log') assert(log.includes('accepted'),'The creator chain reaches the starter grant') assert(JSON.stringify(JSON.parse(log.split('\n')[0]).conversations)===JSON.stringify([String(creator.id),String(subscriber.id),String(root.id)]),'The host appends the starter after fork ancestry') const entries=(await root.context(context)).entries.filter((entry:any)=>entry.model?.some((m:any)=>m.role==='user'&&typeof m.content==='string'&&m.content.includes('[automation A1 fired]'))) assert(entries.length===0,'The creator is not a subscriber') const delivered=(await subscriber.context(context)).entries.filter((entry:any)=>entry.model?.some((m:any)=>m.role==='user'&&typeof m.content==='string'&&m.content.includes('[automation A1 fired]'))) assert(delivered.length===1,'Exactly one script subscriber input') const replacement=await harness.createConversation({ownership:{kind:'ownerless'}},context) await call({action:'subscribe',id:'A1',to:`#${replacement.id}`}) await call({action:'unsubscribe',id:'A1',to:`#${subscriber.id}`}) const changed=(await harness.snapshot(Automations,context)).automations.A1 assert(changed.executionConversation===creator.id,'Subscription changes retain the creator') assert(JSON.stringify(changed.subscribers)===JSON.stringify([replacement.id]),'Subscription ids normalize') await loader.close();await harness.close(context);await storage.close() } else { await loader.close();await harness.close(context);await storage.close() const zones=Intl.supportedValuesOf('timeZone') assert(zones.includes('Europe/Vienna') && zones.includes('America/New_York') && zones.includes('Asia/Tokyo'),'V8 provides IANA zone data') console.log(`IANA zones: ${zones.length}`) await import('./automations-schedule-cases') await import('./automations-pause-cases') for(const test of cases) { try {await test.run();console.log(`PASS ${test.name}`)} finally {await clean()} } }