Expose canonical host chains and validate automation ancestry

This commit is contained in:
2026-11-07 14:50:06 +00:00
parent d349d20524
commit 02f6341bf7
14 changed files with 1189 additions and 906 deletions
+4 -2
View File
@@ -32,8 +32,10 @@ External script effects can repeat after a crash.
## Credentials
The conversation that creates an automation remains its execution conversation.
Every script launch uses that conversation's T44 chain.
The chain includes ownership, fork ancestry, and the optional branch starter.
Every script launch uses `host.chain(creator)`.
The host delegates to `conversationChains` in `host/js/environment.ts`.
The host appends the branch starter's chain after the ownership or fork ancestry.
The host removes duplicate ids without changing their order.
Subscribe and unsubscribe change notification recipients only.
They never change the execution conversation.
Built-in automations without a creator use an empty chain.
+2 -2
View File
@@ -8,7 +8,7 @@ import {
import type { ConversationId, Cursor, Tx } from '@earendil-works/pi-durable'
type ExtensionHost = any
import { SubagentsApi } from '../subagents/api.ts'
import { discover } from '../subagents/definitions.ts'
import { ModelsApi } from '../models/api.ts'
import { nextAt } from './schedule.ts'
import type { Automation, Run } from './types.ts'
import { RUN_TASK } from './types.ts'
@@ -26,7 +26,7 @@ const terminal = {
}
async function definition(host: ExtensionHost, name: string) {
const found = (
await discover(host)
await host.need(ModelsApi).discover('/src/sausage')
).definitions.find((d) => d.name === name)
if (!found) throw new Error(`Unknown agent: ${name}`)
return found
+2 -3
View File
@@ -6,7 +6,6 @@ import { SubagentsApi } from '../subagents/api.ts'
import { scheduler } from './scheduler.ts'
import { triggers, TRIGGER_EVENTS } from './triggers.ts'
import { parseSchedule } from './schedule.ts'
import { executionChain } from './chain.ts'
import { fireText, doneText, trimLog } from './types.ts'
import type { Args, Automation, Subscriber } from './types.ts'
export type { Automation, Run } from './types.ts'
@@ -62,7 +61,7 @@ export default function automations(host:any) {
execute:async(task,runtime,ctx)=>{
const a = (await host.harness().snapshot(Automations,ctx))!.automations[task.input.id]
if (!a?.enabled || !a.script || a.scheduler!==task.id) return runtime.commit(()=>terminal,ctx)
const conversations = await executionChain(host,a.executionConversation)
const conversations = a.executionConversation === undefined ? [] : await host.chain(a.executionConversation)
let queue = Promise.resolve(), log='', done=false, sequence=0
const pending:Record<string,string> = {stdout:'',stderr:''}
function line(text:string,stream:string) {
@@ -79,7 +78,7 @@ export default function automations(host:any) {
receipts[key]=true
},ctx))
}
const result=await host.exec(['sh',path(a.id)],{cwd:directory(a.id),conversations,timeout:3600,signal:runtime.signal,onOutput:(text:string,stream:string)=>{
const result=await host.exec(['sh',path(a.id)],{cwd:directory(a.id),conversations,timeout:3600,signal:runtime.signal,task:task.id,onOutput:(text:string,stream:string)=>{
pending[stream]+=text
const lines=pending[stream].split('\n'); pending[stream]=lines.pop()!
for(const text of lines) line(text.replace(/\r$/,''),stream)
File diff suppressed because it is too large Load Diff
+18 -8
View File
@@ -16,10 +16,10 @@ 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)=>{
const call=async(args:any,conversation=root)=>{
faux.setResponses([fauxAssistantMessage([fauxToolCall('automation',args)],{stopReason:'toolUse'}),fauxAssistantMessage('done')])
await (await root.submit({type:'input',content:'Configure automation'},context)).wait(context)
const result=(await root.context(context)).entries.filter((entry:any)=>entry.kind==='pi.tool-result').at(-1)
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()
@@ -55,17 +55,27 @@ if(globalThis.automationMode==='prepare') {
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://127.0.0.1:${globalThis.automationPort}/token\nprintf '\\nFIRE delivered\\n'\nprintf 'FIRE ignored\\n' >&2\nprintf 'DONE\\n'`)
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)
await call({action:'create',name:'creator grant',ask:'Fetch and fire',subscribers:[subscriber.id]})
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===root.id,'The creator owns execution')
assert((await fs.read('/src/sausage/automations/A1/log')).includes('accepted'),'The creator grant reaches curl')
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]')))
@@ -74,7 +84,7 @@ if(globalThis.automationMode==='prepare') {
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===root.id,'Subscription changes retain the creator')
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 {
+2
View File
@@ -54213,6 +54213,7 @@ function registerModules() {
async function loadExtensions(harness2, registry2, storage2, models2, context, builtins = []) {
registerModules();
const eventService = eventLog(() => harness2);
const chain2 = conversationChains(harness2, context);
const repo = "/src/sausage";
let providers = /* @__PURE__ */ new Map();
const resources = /* @__PURE__ */ new Map();
@@ -54264,6 +54265,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
fs,
exec: (command, options) => exec(command, { ...options, extension: name }),
git,
chain: chain2,
implement: (api, value2) => {
staged.set(api.name, value2);
},
+109 -70
View File
@@ -8946,27 +8946,27 @@ var REPLACE_TAKE = { kind: "replace" };
var PendingOps = class {
#state = EMPTY;
/** Queue the ops of one commit. */
push(ops2) {
if (ops2.length === 0)
push(ops3) {
if (ops3.length === 0)
return;
const state = this.#state;
if (ops2[0][0] === "r") {
this.#state = { kind: "batch", ops: ops2 };
if (ops3[0][0] === "r") {
this.#state = { kind: "batch", ops: ops3 };
return;
}
switch (state.kind) {
case "empty":
this.#state = { kind: "batch", ops: ops2 };
this.#state = { kind: "batch", ops: ops3 };
return;
case "batch": {
const folding = { kind: "folding", ops: [], leaves: /* @__PURE__ */ new Map() };
this.#state = folding;
if (this.#fold(state.ops, folding))
this.#fold(ops2, folding);
this.#fold(ops3, folding);
return;
}
case "folding":
this.#fold(ops2, state);
this.#fold(ops3, state);
return;
case "replace":
return;
@@ -8997,8 +8997,8 @@ var PendingOps = class {
}
}
/** Fold `ops` into `folding`; false once the queue overflowed into `replace`. */
#fold(ops2, folding) {
for (const op2 of ops2) {
#fold(ops3, folding) {
for (const op2 of ops3) {
switch (op2[0]) {
case "r":
folding.ops.length = 0;
@@ -9267,12 +9267,12 @@ var PathError = class extends Error {
this.name = "PathError";
}
};
function apply(target, ops2) {
return applyOps(target, ops2);
function apply(target, ops3) {
return applyOps(target, ops3);
}
function applyOps(target, ops2) {
function applyOps(target, ops3) {
let root = target;
for (const op2 of ops2) {
for (const op2 of ops3) {
assertValidOp(op2);
if (op2[0] === "r") {
root = op2[1];
@@ -9341,14 +9341,14 @@ function applyOps(target, ops2) {
}
return root;
}
function applyImmutable(target, ops2) {
return applyImmutableBatches(target, [ops2]);
function applyImmutable(target, ops3) {
return applyImmutableBatches(target, [ops3]);
}
function applyImmutableBatches(target, batches) {
let root = target;
const owned = /* @__PURE__ */ new WeakSet();
for (const ops2 of batches) {
for (const op2 of ops2) {
for (const ops3 of batches) {
for (const op2 of ops3) {
assertValidOp(op2);
if (op2[0] === "r") {
root = op2[1];
@@ -9462,10 +9462,10 @@ function encoder() {
let nextId = 0;
let previous;
return {
encode(ops2) {
encode(ops3) {
previous = void 0;
const out = [];
for (const op2 of ops2) {
for (const op2 of ops3) {
if (op2[0] === "r") {
out.push(op2);
seen.clear();
@@ -9715,9 +9715,9 @@ var Source = class {
return [];
}
};
function unbox(ops2, value2) {
function unbox(ops3, value2) {
const result = [];
for (const op2 of ops2) {
for (const op2 of ops3) {
if (op2[0] === "r" || op2[1].length === 0)
return [["r", value2]];
const path = op2[1].slice(1);
@@ -9811,8 +9811,8 @@ var Cell = class extends Source {
throw notReady(`state ${this.key} (its init has not run)`);
const before = this.value;
const diff = options.diff ?? sharesStructure(before, value2);
const ops2 = diff ? diffRevisions(before, value2) : void 0;
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "ops", true, ops2);
const ops3 = diff ? diffRevisions(before, value2) : void 0;
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "ops", true, ops3);
}
/**
* Installs a migrated value and its schema (reload commit). The caller validated the value and the derivations
@@ -9823,7 +9823,7 @@ var Cell = class extends Source {
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "reset", false);
}
/** Adopts this revision and every derived one, then queues all notifications (cell first, then derivations). */
#publish(prepared, type, validate2 = true, ops2) {
#publish(prepared, type, validate2 = true, ops3) {
if (prepared.ops.length === 0 && type === "ops") {
this.#tracker.adopt(prepared);
return;
@@ -9840,7 +9840,7 @@ var Cell = class extends Source {
}
holdPublications(() => {
this.#tracker.adopt(prepared);
const published = ops2 !== void 0 && ops2.length > 0 ? ops2 : unbox(prepared.ops, value2);
const published = ops3 !== void 0 && ops3.length > 0 ? ops3 : unbox(prepared.ops, value2);
this.fanout.emit(type === "reset" ? { type, value: value2 } : { type, ops: published, value: value2 });
for (const [derived, next] of candidates)
derived.install(next);
@@ -9953,9 +9953,9 @@ var Derived = class extends Source {
install(next) {
const before = this.#value;
this.#value = next;
const ops2 = before === void 0 || next === void 0 ? [["r", next ?? null]] : diffRevisions(before, next);
if (ops2.length > 0)
this.fanout.emit({ type: "ops", ops: ops2, value: next });
const ops3 = before === void 0 || next === void 0 ? [["r", next ?? null]] : diffRevisions(before, next);
if (ops3.length > 0)
this.fanout.emit({ type: "ops", ops: ops3, value: next });
}
};
var isSource = (value2) => value2 instanceof Source;
@@ -14008,10 +14008,10 @@ var RemoteBinding = class {
const target = pending?.values.get(name) ?? this.members.get(name);
if (target === void 0)
return;
const ops2 = target.decoder.decode(frame.ops ?? []);
target.value = applyImmutable(target.value, ops2);
const ops3 = target.decoder.decode(frame.ops ?? []);
target.value = applyImmutable(target.value, ops3);
if (target === this.members.get(name))
this.#deliver(name, { type: "ops", ops: ops2, value: target.value });
this.#deliver(name, { type: "ops", ops: ops3, value: target.value });
return;
}
const fresh = { value: frame.value, decoder: decoder() };
@@ -15835,8 +15835,8 @@ var Watch = class extends WatchLifecycle {
this.#retired = false;
if (lone !== void 0)
return lone;
const ops2 = batch.kind === "ops" ? batch.ops : [["r", value2]];
return { kind: "value", value: value2, ops: ops2, retired };
const ops3 = batch.kind === "ops" ? batch.ops : [["r", value2]];
return { kind: "value", value: value2, ops: ops3, retired };
}
/** The pending failure, or the value after one as a root replacement (no ops were kept since the failure). */
#nextAfterFailure() {
@@ -17601,7 +17601,7 @@ function describe(plan, record2) {
const change = plan.change;
if (change === void 0 || !publishes(plan))
return void 0;
const ops2 = change.loaded === void 0 ? EMPTY_OPERATIONS : change.prepared.ops;
const ops3 = change.loaded === void 0 ? EMPTY_OPERATIONS : change.prepared.ops;
return {
type: "document",
kind: "change",
@@ -17609,11 +17609,11 @@ function describe(plan, record2) {
conversationId,
version: change.version,
value: change.prepared.value,
ops: ops2
ops: ops3
};
}
function storeHeld(hold, ops2, value2, version) {
hold.pending.push(ops2);
function storeHeld(hold, ops3, value2, version) {
hold.pending.push(ops3);
const taken = hold.pending.take();
if (taken.kind === "replace")
return { content: { version, kind: "base", value: value2 }, ops: [["r", value2]] };
@@ -18694,7 +18694,7 @@ var SessionImpl = class {
return { kind: "adoption", error };
}
const deadline = this.#clock() + withinMs;
for (const { loaded, ops: ops2, base } of held2) {
for (const { loaded, ops: ops3, base } of held2) {
const id = loaded.record.id;
let hold = this.#holds.get(id);
if (hold === void 0) {
@@ -18702,7 +18702,7 @@ var SessionImpl = class {
hold = { loaded, pending: new PendingOps(), base: false, deadline, unpin };
this.#holds.set(id, hold);
}
hold.pending.push(ops2);
hold.pending.push(ops3);
if (base)
hold.base = true;
if (deadline < hold.deadline)
@@ -26917,14 +26917,14 @@ var TaskGraphView = class {
const mount = this.#mount;
if (mount === void 0)
return;
const ops2 = [];
const ops3 = [];
for (const [key, record2] of mount.records) {
const blocked = this.#blocking.blocked(record2);
if (blocked === mount.value.tasks[key]?.blocked)
continue;
ops2.push(blocked === void 0 ? ["d", ["tasks", key, "blocked"]] : ["s", ["tasks", key, "blocked"], blocked]);
ops3.push(blocked === void 0 ? ["d", ["tasks", key, "blocked"]] : ["s", ["tasks", key, "blocked"], blocked]);
}
publish(mount, ops2, BACKGROUND_CONTEXT, (error) => this.#session.report(error));
publish(mount, ops3, BACKGROUND_CONTEXT, (error) => this.#session.report(error));
}).catch(() => {
});
}
@@ -26948,7 +26948,7 @@ var TaskGraphView = class {
}
};
function advance(mount, publication, context, blocked, report2) {
const ops2 = [];
const ops3 = [];
const changed = /* @__PURE__ */ new Map();
const node = (key) => changed.has(key) ? changed.get(key) : mount.value.tasks[key];
for (const change of publication.changes) {
@@ -26961,7 +26961,7 @@ function advance(mount, publication, context, blocked, report2) {
mount.records.delete(key);
if (previous === void 0)
continue;
ops2.push(["d", ["tasks", key]]);
ops3.push(["d", ["tasks", key]]);
changed.set(key, void 0);
continue;
}
@@ -26970,7 +26970,7 @@ function advance(mount, publication, context, blocked, report2) {
const next = nodeOf2(record2, previous?.conversations ?? [], blocked(subject));
if (previous !== void 0 && JSON.stringify(next) === JSON.stringify(previous))
continue;
ops2.push(["s", ["tasks", key], next]);
ops3.push(["s", ["tasks", key], next]);
changed.set(key, next);
}
const created = /* @__PURE__ */ new Map();
@@ -26983,16 +26983,16 @@ function advance(mount, publication, context, blocked, report2) {
}
for (const [key, ids] of created) {
const conversations = [...node(key).conversations, ...ids].sort((a, b) => a - b);
ops2.push(["s", ["tasks", key, "conversations"], conversations]);
ops3.push(["s", ["tasks", key, "conversations"], conversations]);
}
publish(mount, ops2, context, report2);
publish(mount, ops3, context, report2);
}
function publish(mount, ops2, context, report2) {
if (ops2.length === 0)
function publish(mount, ops3, context, report2) {
if (ops3.length === 0)
return;
mount.value = applyImmutable(mount.value, ops2);
mount.value = applyImmutable(mount.value, ops3);
const frameContext = withoutAbortSignal(context);
const call = { kind: "value", value: mount.value, ops: ops2, retired: false };
const call = { kind: "value", value: mount.value, ops: ops3, retired: false };
for (const observer of [...mount.observers])
contained(() => observer.advance(call, frameContext), report2);
}
@@ -27218,19 +27218,19 @@ function advance2(id, mount, publication, context, report2) {
const previous = windowed.value;
let entries2 = previous.entries;
let before = previous.before;
let ops2 = docOps;
let ops3 = docOps;
if (appended.length > 0) {
ops2 = [...docOps, ["p", ["entries"], entries2.length, 0, inserted]];
ops3 = [...docOps, ["p", ["entries"], entries2.length, 0, inserted]];
entries2 = entries2.concat(appended);
if (entries2.length > windowed.max) {
const drop = entries2.length - windowed.min;
before = entries2[drop - 1].id;
entries2 = entries2.slice(drop);
ops2.push(["p", ["entries"], 0, drop, []], ["s", ["before"], before]);
ops3.push(["p", ["entries"], 0, drop, []], ["s", ["before"], before]);
}
}
windowed.value = view(mount.conversation, entries2, before, mount.docs);
const call = { kind: "value", value: windowed.value, ops: ops2, retired: false };
const call = { kind: "value", value: windowed.value, ops: ops3, retired: false };
contained(() => windowed.observer.advance(call, frameContext), report2);
}
}
@@ -31368,14 +31368,51 @@ function eventLog(harness2) {
}
}
// extensions.ts
// environment.ts
var CodingTools2 = { ...CodingTools, tools: CodingTools.tools.map((tool) => ({
...tool,
execute: (args, api, context) => tool.execute(args, { ...api, env: api.env.forTool(api.callId, tool.name) }, context)
})) };
var Started = defineDoc({
kind: "board.started",
version: 1,
checkpointWhen: () => false,
scope: "conversation",
history: "latest",
fork: "initial",
initial: () => ({})
});
function conversationChains(harness2, context) {
const cache = /* @__PURE__ */ new Map();
const chain = (id) => {
let result = cache.get(id);
if (!result) {
result = (async () => {
const record2 = await harness2.commit((tx) => tx.conversation(id), context);
const up = record2?.owner?.conversationId ?? record2?.parent?.conversationId;
const starter = (await harness2.snapshot(Started, id, context))?.by;
return [.../* @__PURE__ */ new Set([
String(id),
...up === void 0 ? [] : await chain(up),
...starter === void 0 ? [] : await chain(starter)
])];
})();
cache.set(id, result);
}
return result;
};
return chain;
}
var ops = Deno.core.ops;
// extensions.ts
var ops2 = Deno.core.ops;
var encoder4 = new TextEncoder();
var decoder4 = new TextDecoder();
var ExtensionState = defineDoc({ kind: "sausage.extensions", version: 1, scope: "session", checkpointWhen: () => false, initial: () => ({ good: null, current: null, error: null }) });
var Metadata = defineDoc({ kind: "sausage.metadata", version: 1, scope: "conversation", history: "latest", fork: "current", initial: () => ({ label: "", title: "" }), checkpointWhen: () => false });
async function file(kind, path, bytes2 = []) {
const result = await ops.op_file(kind, path, bytes2);
const result = await ops2.op_file(kind, path, bytes2);
if (!result.ok) throw Object.assign(new Error(result.error.message), result.error);
return result.value;
}
@@ -31398,14 +31435,14 @@ async function exec(command, options) {
program: `extension:${options.extension}`,
conversations: options.conversations ?? []
} : void 0;
const rid = ops.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const rid = ops2.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const abort = () => Deno.core.close(rid);
options.signal?.addEventListener("abort", abort, { once: true });
const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
const result = { code: 0, stdout: "", stderr: "" };
try {
for (; ; ) {
const chunk = await ops.op_exec_next(rid);
const chunk = await ops2.op_exec_next(rid);
if (!chunk.stream) {
if (chunk.error) throw new Error(chunk.error);
result.code = chunk.exitCode;
@@ -31432,11 +31469,12 @@ async function git(cwd, ...args) {
function registerModules() {
const modules = { "@earendil-works/sausage-events": events_exports, "@earendil-works/pi-schema": dist_exports, "@earendil-works/pi-durable": dist_exports3, "@earendil-works/pi-durable/tools": tools_exports, "@earendil-works/pi-ai": dist_exports4, "@earendil-works/pi-ai/models": models_exports, "@earendil-works/chord": dist_exports2, "@earendil-works/chord/context": context_exports };
globalThis.__sausage = { modules };
ops.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
ops2.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
}
async function loadExtensions(harness2, registry2, storage2, models2, context, builtins = []) {
registerModules();
const eventService = eventLog(() => harness2);
const chain = conversationChains(harness2, context);
const repo = "/src/sausage";
let providers = /* @__PURE__ */ new Map();
const resources = /* @__PURE__ */ new Map();
@@ -31447,7 +31485,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
let queue2 = Promise.resolve();
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
};
const dispose = async (entries2) => {
@@ -31455,7 +31493,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await cleanup();
} catch (error) {
ops.op_output(`Extension cleanup: ${String(error)}
ops2.op_output(`Extension cleanup: ${String(error)}
`);
}
}
@@ -31488,6 +31526,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
fs,
exec: (command, options) => exec(command, { ...options, extension: name }),
git,
chain,
implement: (api, value2) => {
staged.set(api.name, value2);
},
@@ -31510,12 +31549,12 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
onClose: (cb) => close.push(cb),
notice: (message) => {
state.notice = `${name}: ${message}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${name}: ${message}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${name}: ${message}
`);
if (!notices.has(message)) {
notices.add(message);
void harness2.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops.op_output(`Recording a notice failed: ${String(error)}
void harness2.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops2.op_output(`Recording a notice failed: ${String(error)}
`));
}
}
@@ -31594,7 +31633,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
tip = await git(repo, "rev-parse", "refs/heads/main");
} catch (error) {
state.error = String(error);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
return;
}
try {
@@ -31603,8 +31642,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
if (error.code === "not_found") settings = {};
else {
state.notice = `Settings: ${String(error)}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${state.notice}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${state.notice}
`);
}
}
@@ -31620,11 +31659,11 @@ Last good: ${fallback}` : failed;
await publish2();
};
const enqueue2 = () => queue2 = queue2.then(reload);
const rid = ops.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
const rid = ops2.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
await enqueue2();
const pending = (async () => {
while (active) {
await ops.op_watch_next(rid);
await ops2.op_watch_next(rid);
if (active) await enqueue2();
}
})();
+109 -70
View File
@@ -6190,27 +6190,27 @@ var REPLACE_TAKE = { kind: "replace" };
var PendingOps = class {
#state = EMPTY;
/** Queue the ops of one commit. */
push(ops2) {
if (ops2.length === 0)
push(ops3) {
if (ops3.length === 0)
return;
const state = this.#state;
if (ops2[0][0] === "r") {
this.#state = { kind: "batch", ops: ops2 };
if (ops3[0][0] === "r") {
this.#state = { kind: "batch", ops: ops3 };
return;
}
switch (state.kind) {
case "empty":
this.#state = { kind: "batch", ops: ops2 };
this.#state = { kind: "batch", ops: ops3 };
return;
case "batch": {
const folding = { kind: "folding", ops: [], leaves: /* @__PURE__ */ new Map() };
this.#state = folding;
if (this.#fold(state.ops, folding))
this.#fold(ops2, folding);
this.#fold(ops3, folding);
return;
}
case "folding":
this.#fold(ops2, state);
this.#fold(ops3, state);
return;
case "replace":
return;
@@ -6241,8 +6241,8 @@ var PendingOps = class {
}
}
/** Fold `ops` into `folding`; false once the queue overflowed into `replace`. */
#fold(ops2, folding) {
for (const op2 of ops2) {
#fold(ops3, folding) {
for (const op2 of ops3) {
switch (op2[0]) {
case "r":
folding.ops.length = 0;
@@ -6511,12 +6511,12 @@ var PathError = class extends Error {
this.name = "PathError";
}
};
function apply(target, ops2) {
return applyOps(target, ops2);
function apply(target, ops3) {
return applyOps(target, ops3);
}
function applyOps(target, ops2) {
function applyOps(target, ops3) {
let root = target;
for (const op2 of ops2) {
for (const op2 of ops3) {
assertValidOp(op2);
if (op2[0] === "r") {
root = op2[1];
@@ -6585,14 +6585,14 @@ function applyOps(target, ops2) {
}
return root;
}
function applyImmutable(target, ops2) {
return applyImmutableBatches(target, [ops2]);
function applyImmutable(target, ops3) {
return applyImmutableBatches(target, [ops3]);
}
function applyImmutableBatches(target, batches) {
let root = target;
const owned = /* @__PURE__ */ new WeakSet();
for (const ops2 of batches) {
for (const op2 of ops2) {
for (const ops3 of batches) {
for (const op2 of ops3) {
assertValidOp(op2);
if (op2[0] === "r") {
root = op2[1];
@@ -6706,10 +6706,10 @@ function encoder() {
let nextId = 0;
let previous;
return {
encode(ops2) {
encode(ops3) {
previous = void 0;
const out = [];
for (const op2 of ops2) {
for (const op2 of ops3) {
if (op2[0] === "r") {
out.push(op2);
seen.clear();
@@ -6959,9 +6959,9 @@ var Source = class {
return [];
}
};
function unbox(ops2, value2) {
function unbox(ops3, value2) {
const result = [];
for (const op2 of ops2) {
for (const op2 of ops3) {
if (op2[0] === "r" || op2[1].length === 0)
return [["r", value2]];
const path = op2[1].slice(1);
@@ -7055,8 +7055,8 @@ var Cell = class extends Source {
throw notReady(`state ${this.key} (its init has not run)`);
const before = this.value;
const diff = options.diff ?? sharesStructure(before, value2);
const ops2 = diff ? diffRevisions(before, value2) : void 0;
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "ops", true, ops2);
const ops3 = diff ? diffRevisions(before, value2) : void 0;
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "ops", true, ops3);
}
/**
* Installs a migrated value and its schema (reload commit). The caller validated the value and the derivations
@@ -7067,7 +7067,7 @@ var Cell = class extends Source {
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "reset", false);
}
/** Adopts this revision and every derived one, then queues all notifications (cell first, then derivations). */
#publish(prepared, type, validate2 = true, ops2) {
#publish(prepared, type, validate2 = true, ops3) {
if (prepared.ops.length === 0 && type === "ops") {
this.#tracker.adopt(prepared);
return;
@@ -7084,7 +7084,7 @@ var Cell = class extends Source {
}
holdPublications(() => {
this.#tracker.adopt(prepared);
const published = ops2 !== void 0 && ops2.length > 0 ? ops2 : unbox(prepared.ops, value2);
const published = ops3 !== void 0 && ops3.length > 0 ? ops3 : unbox(prepared.ops, value2);
this.fanout.emit(type === "reset" ? { type, value: value2 } : { type, ops: published, value: value2 });
for (const [derived, next] of candidates)
derived.install(next);
@@ -7197,9 +7197,9 @@ var Derived = class extends Source {
install(next) {
const before = this.#value;
this.#value = next;
const ops2 = before === void 0 || next === void 0 ? [["r", next ?? null]] : diffRevisions(before, next);
if (ops2.length > 0)
this.fanout.emit({ type: "ops", ops: ops2, value: next });
const ops3 = before === void 0 || next === void 0 ? [["r", next ?? null]] : diffRevisions(before, next);
if (ops3.length > 0)
this.fanout.emit({ type: "ops", ops: ops3, value: next });
}
};
var isSource = (value2) => value2 instanceof Source;
@@ -11252,10 +11252,10 @@ var RemoteBinding = class {
const target = pending?.values.get(name) ?? this.members.get(name);
if (target === void 0)
return;
const ops2 = target.decoder.decode(frame.ops ?? []);
target.value = applyImmutable(target.value, ops2);
const ops3 = target.decoder.decode(frame.ops ?? []);
target.value = applyImmutable(target.value, ops3);
if (target === this.members.get(name))
this.#deliver(name, { type: "ops", ops: ops2, value: target.value });
this.#deliver(name, { type: "ops", ops: ops3, value: target.value });
return;
}
const fresh = { value: frame.value, decoder: decoder() };
@@ -13079,8 +13079,8 @@ var Watch = class extends WatchLifecycle {
this.#retired = false;
if (lone !== void 0)
return lone;
const ops2 = batch.kind === "ops" ? batch.ops : [["r", value2]];
return { kind: "value", value: value2, ops: ops2, retired };
const ops3 = batch.kind === "ops" ? batch.ops : [["r", value2]];
return { kind: "value", value: value2, ops: ops3, retired };
}
/** The pending failure, or the value after one as a root replacement (no ops were kept since the failure). */
#nextAfterFailure() {
@@ -14845,7 +14845,7 @@ function describe(plan, record2) {
const change = plan.change;
if (change === void 0 || !publishes(plan))
return void 0;
const ops2 = change.loaded === void 0 ? EMPTY_OPERATIONS : change.prepared.ops;
const ops3 = change.loaded === void 0 ? EMPTY_OPERATIONS : change.prepared.ops;
return {
type: "document",
kind: "change",
@@ -14853,11 +14853,11 @@ function describe(plan, record2) {
conversationId,
version: change.version,
value: change.prepared.value,
ops: ops2
ops: ops3
};
}
function storeHeld(hold, ops2, value2, version) {
hold.pending.push(ops2);
function storeHeld(hold, ops3, value2, version) {
hold.pending.push(ops3);
const taken = hold.pending.take();
if (taken.kind === "replace")
return { content: { version, kind: "base", value: value2 }, ops: [["r", value2]] };
@@ -15938,7 +15938,7 @@ var SessionImpl = class {
return { kind: "adoption", error };
}
const deadline = this.#clock() + withinMs;
for (const { loaded, ops: ops2, base } of held2) {
for (const { loaded, ops: ops3, base } of held2) {
const id = loaded.record.id;
let hold = this.#holds.get(id);
if (hold === void 0) {
@@ -15946,7 +15946,7 @@ var SessionImpl = class {
hold = { loaded, pending: new PendingOps(), base: false, deadline, unpin };
this.#holds.set(id, hold);
}
hold.pending.push(ops2);
hold.pending.push(ops3);
if (base)
hold.base = true;
if (deadline < hold.deadline)
@@ -25033,14 +25033,14 @@ var TaskGraphView = class {
const mount = this.#mount;
if (mount === void 0)
return;
const ops2 = [];
const ops3 = [];
for (const [key, record2] of mount.records) {
const blocked = this.#blocking.blocked(record2);
if (blocked === mount.value.tasks[key]?.blocked)
continue;
ops2.push(blocked === void 0 ? ["d", ["tasks", key, "blocked"]] : ["s", ["tasks", key, "blocked"], blocked]);
ops3.push(blocked === void 0 ? ["d", ["tasks", key, "blocked"]] : ["s", ["tasks", key, "blocked"], blocked]);
}
publish(mount, ops2, BACKGROUND_CONTEXT, (error) => this.#session.report(error));
publish(mount, ops3, BACKGROUND_CONTEXT, (error) => this.#session.report(error));
}).catch(() => {
});
}
@@ -25064,7 +25064,7 @@ var TaskGraphView = class {
}
};
function advance(mount, publication, context, blocked, report2) {
const ops2 = [];
const ops3 = [];
const changed = /* @__PURE__ */ new Map();
const node = (key) => changed.has(key) ? changed.get(key) : mount.value.tasks[key];
for (const change of publication.changes) {
@@ -25077,7 +25077,7 @@ function advance(mount, publication, context, blocked, report2) {
mount.records.delete(key);
if (previous === void 0)
continue;
ops2.push(["d", ["tasks", key]]);
ops3.push(["d", ["tasks", key]]);
changed.set(key, void 0);
continue;
}
@@ -25086,7 +25086,7 @@ function advance(mount, publication, context, blocked, report2) {
const next = nodeOf2(record2, previous?.conversations ?? [], blocked(subject));
if (previous !== void 0 && JSON.stringify(next) === JSON.stringify(previous))
continue;
ops2.push(["s", ["tasks", key], next]);
ops3.push(["s", ["tasks", key], next]);
changed.set(key, next);
}
const created = /* @__PURE__ */ new Map();
@@ -25099,16 +25099,16 @@ function advance(mount, publication, context, blocked, report2) {
}
for (const [key, ids] of created) {
const conversations = [...node(key).conversations, ...ids].sort((a, b) => a - b);
ops2.push(["s", ["tasks", key, "conversations"], conversations]);
ops3.push(["s", ["tasks", key, "conversations"], conversations]);
}
publish(mount, ops2, context, report2);
publish(mount, ops3, context, report2);
}
function publish(mount, ops2, context, report2) {
if (ops2.length === 0)
function publish(mount, ops3, context, report2) {
if (ops3.length === 0)
return;
mount.value = applyImmutable(mount.value, ops2);
mount.value = applyImmutable(mount.value, ops3);
const frameContext = withoutAbortSignal(context);
const call = { kind: "value", value: mount.value, ops: ops2, retired: false };
const call = { kind: "value", value: mount.value, ops: ops3, retired: false };
for (const observer of [...mount.observers])
contained(() => observer.advance(call, frameContext), report2);
}
@@ -25334,19 +25334,19 @@ function advance2(id, mount, publication, context, report2) {
const previous = windowed.value;
let entries2 = previous.entries;
let before = previous.before;
let ops2 = docOps;
let ops3 = docOps;
if (appended.length > 0) {
ops2 = [...docOps, ["p", ["entries"], entries2.length, 0, inserted]];
ops3 = [...docOps, ["p", ["entries"], entries2.length, 0, inserted]];
entries2 = entries2.concat(appended);
if (entries2.length > windowed.max) {
const drop = entries2.length - windowed.min;
before = entries2[drop - 1].id;
entries2 = entries2.slice(drop);
ops2.push(["p", ["entries"], 0, drop, []], ["s", ["before"], before]);
ops3.push(["p", ["entries"], 0, drop, []], ["s", ["before"], before]);
}
}
windowed.value = view(mount.conversation, entries2, before, mount.docs);
const call = { kind: "value", value: windowed.value, ops: ops2, retired: false };
const call = { kind: "value", value: windowed.value, ops: ops3, retired: false };
contained(() => windowed.observer.advance(call, frameContext), report2);
}
}
@@ -31368,14 +31368,51 @@ function StringEnum(values, options) {
};
}
// extensions.ts
// environment.ts
var CodingTools2 = { ...CodingTools, tools: CodingTools.tools.map((tool) => ({
...tool,
execute: (args, api, context) => tool.execute(args, { ...api, env: api.env.forTool(api.callId, tool.name) }, context)
})) };
var Started = defineDoc({
kind: "board.started",
version: 1,
checkpointWhen: () => false,
scope: "conversation",
history: "latest",
fork: "initial",
initial: () => ({})
});
function conversationChains(harness2, context) {
const cache = /* @__PURE__ */ new Map();
const chain = (id) => {
let result = cache.get(id);
if (!result) {
result = (async () => {
const record2 = await harness2.commit((tx) => tx.conversation(id), context);
const up = record2?.owner?.conversationId ?? record2?.parent?.conversationId;
const starter = (await harness2.snapshot(Started, id, context))?.by;
return [.../* @__PURE__ */ new Set([
String(id),
...up === void 0 ? [] : await chain(up),
...starter === void 0 ? [] : await chain(starter)
])];
})();
cache.set(id, result);
}
return result;
};
return chain;
}
var ops = Deno.core.ops;
// extensions.ts
var ops2 = Deno.core.ops;
var encoder4 = new TextEncoder();
var decoder4 = new TextDecoder();
var ExtensionState = defineDoc({ kind: "sausage.extensions", version: 1, scope: "session", checkpointWhen: () => false, initial: () => ({ good: null, current: null, error: null }) });
var Metadata = defineDoc({ kind: "sausage.metadata", version: 1, scope: "conversation", history: "latest", fork: "current", initial: () => ({ label: "", title: "" }), checkpointWhen: () => false });
async function file(kind, path, bytes2 = []) {
const result = await ops.op_file(kind, path, bytes2);
const result = await ops2.op_file(kind, path, bytes2);
if (!result.ok) throw Object.assign(new Error(result.error.message), result.error);
return result.value;
}
@@ -31398,14 +31435,14 @@ async function exec(command, options) {
program: `extension:${options.extension}`,
conversations: options.conversations ?? []
} : void 0;
const rid = ops.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const rid = ops2.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const abort = () => Deno.core.close(rid);
options.signal?.addEventListener("abort", abort, { once: true });
const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
const result = { code: 0, stdout: "", stderr: "" };
try {
for (; ; ) {
const chunk = await ops.op_exec_next(rid);
const chunk = await ops2.op_exec_next(rid);
if (!chunk.stream) {
if (chunk.error) throw new Error(chunk.error);
result.code = chunk.exitCode;
@@ -31432,11 +31469,12 @@ async function git(cwd, ...args) {
function registerModules() {
const modules = { "@earendil-works/sausage-events": events_exports, "@earendil-works/pi-schema": dist_exports, "@earendil-works/pi-durable": dist_exports3, "@earendil-works/pi-durable/tools": tools_exports, "@earendil-works/pi-ai": dist_exports4, "@earendil-works/pi-ai/models": models_exports, "@earendil-works/chord": dist_exports2, "@earendil-works/chord/context": context_exports };
globalThis.__sausage = { modules };
ops.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
ops2.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
}
async function loadExtensions(harness2, registry2, storage2, models2, context, builtins = []) {
registerModules();
const eventService = eventLog(() => harness2);
const chain = conversationChains(harness2, context);
const repo = "/src/sausage";
let providers = /* @__PURE__ */ new Map();
const resources = /* @__PURE__ */ new Map();
@@ -31447,7 +31485,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
let queue2 = Promise.resolve();
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
};
const dispose = async (entries2) => {
@@ -31455,7 +31493,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await cleanup();
} catch (error) {
ops.op_output(`Extension cleanup: ${String(error)}
ops2.op_output(`Extension cleanup: ${String(error)}
`);
}
}
@@ -31488,6 +31526,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
fs,
exec: (command, options) => exec(command, { ...options, extension: name }),
git,
chain,
implement: (api, value2) => {
staged.set(api.name, value2);
},
@@ -31510,12 +31549,12 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
onClose: (cb) => close.push(cb),
notice: (message) => {
state.notice = `${name}: ${message}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${name}: ${message}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${name}: ${message}
`);
if (!notices.has(message)) {
notices.add(message);
void harness2.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops.op_output(`Recording a notice failed: ${String(error)}
void harness2.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops2.op_output(`Recording a notice failed: ${String(error)}
`));
}
}
@@ -31594,7 +31633,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
tip = await git(repo, "rev-parse", "refs/heads/main");
} catch (error) {
state.error = String(error);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
return;
}
try {
@@ -31603,8 +31642,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
if (error.code === "not_found") settings = {};
else {
state.notice = `Settings: ${String(error)}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${state.notice}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${state.notice}
`);
}
}
@@ -31620,11 +31659,11 @@ Last good: ${fallback}` : failed;
await publish2();
};
const enqueue2 = () => queue2 = queue2.then(reload);
const rid = ops.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
const rid = ops2.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
await enqueue2();
const pending = (async () => {
while (active) {
await ops.op_watch_next(rid);
await ops2.op_watch_next(rid);
if (active) await enqueue2();
}
})();
+109 -70
View File
@@ -8946,27 +8946,27 @@ var REPLACE_TAKE = { kind: "replace" };
var PendingOps = class {
#state = EMPTY;
/** Queue the ops of one commit. */
push(ops2) {
if (ops2.length === 0)
push(ops3) {
if (ops3.length === 0)
return;
const state = this.#state;
if (ops2[0][0] === "r") {
this.#state = { kind: "batch", ops: ops2 };
if (ops3[0][0] === "r") {
this.#state = { kind: "batch", ops: ops3 };
return;
}
switch (state.kind) {
case "empty":
this.#state = { kind: "batch", ops: ops2 };
this.#state = { kind: "batch", ops: ops3 };
return;
case "batch": {
const folding = { kind: "folding", ops: [], leaves: /* @__PURE__ */ new Map() };
this.#state = folding;
if (this.#fold(state.ops, folding))
this.#fold(ops2, folding);
this.#fold(ops3, folding);
return;
}
case "folding":
this.#fold(ops2, state);
this.#fold(ops3, state);
return;
case "replace":
return;
@@ -8997,8 +8997,8 @@ var PendingOps = class {
}
}
/** Fold `ops` into `folding`; false once the queue overflowed into `replace`. */
#fold(ops2, folding) {
for (const op2 of ops2) {
#fold(ops3, folding) {
for (const op2 of ops3) {
switch (op2[0]) {
case "r":
folding.ops.length = 0;
@@ -9267,12 +9267,12 @@ var PathError = class extends Error {
this.name = "PathError";
}
};
function apply(target, ops2) {
return applyOps(target, ops2);
function apply(target, ops3) {
return applyOps(target, ops3);
}
function applyOps(target, ops2) {
function applyOps(target, ops3) {
let root2 = target;
for (const op2 of ops2) {
for (const op2 of ops3) {
assertValidOp(op2);
if (op2[0] === "r") {
root2 = op2[1];
@@ -9341,14 +9341,14 @@ function applyOps(target, ops2) {
}
return root2;
}
function applyImmutable(target, ops2) {
return applyImmutableBatches(target, [ops2]);
function applyImmutable(target, ops3) {
return applyImmutableBatches(target, [ops3]);
}
function applyImmutableBatches(target, batches) {
let root2 = target;
const owned = /* @__PURE__ */ new WeakSet();
for (const ops2 of batches) {
for (const op2 of ops2) {
for (const ops3 of batches) {
for (const op2 of ops3) {
assertValidOp(op2);
if (op2[0] === "r") {
root2 = op2[1];
@@ -9462,10 +9462,10 @@ function encoder() {
let nextId = 0;
let previous;
return {
encode(ops2) {
encode(ops3) {
previous = void 0;
const out = [];
for (const op2 of ops2) {
for (const op2 of ops3) {
if (op2[0] === "r") {
out.push(op2);
seen.clear();
@@ -9715,9 +9715,9 @@ var Source = class {
return [];
}
};
function unbox(ops2, value2) {
function unbox(ops3, value2) {
const result = [];
for (const op2 of ops2) {
for (const op2 of ops3) {
if (op2[0] === "r" || op2[1].length === 0)
return [["r", value2]];
const path = op2[1].slice(1);
@@ -9811,8 +9811,8 @@ var Cell = class extends Source {
throw notReady(`state ${this.key} (its init has not run)`);
const before = this.value;
const diff = options.diff ?? sharesStructure(before, value2);
const ops2 = diff ? diffRevisions(before, value2) : void 0;
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "ops", true, ops2);
const ops3 = diff ? diffRevisions(before, value2) : void 0;
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "ops", true, ops3);
}
/**
* Installs a migrated value and its schema (reload commit). The caller validated the value and the derivations
@@ -9823,7 +9823,7 @@ var Cell = class extends Source {
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "reset", false);
}
/** Adopts this revision and every derived one, then queues all notifications (cell first, then derivations). */
#publish(prepared, type, validate2 = true, ops2) {
#publish(prepared, type, validate2 = true, ops3) {
if (prepared.ops.length === 0 && type === "ops") {
this.#tracker.adopt(prepared);
return;
@@ -9840,7 +9840,7 @@ var Cell = class extends Source {
}
holdPublications(() => {
this.#tracker.adopt(prepared);
const published = ops2 !== void 0 && ops2.length > 0 ? ops2 : unbox(prepared.ops, value2);
const published = ops3 !== void 0 && ops3.length > 0 ? ops3 : unbox(prepared.ops, value2);
this.fanout.emit(type === "reset" ? { type, value: value2 } : { type, ops: published, value: value2 });
for (const [derived, next] of candidates)
derived.install(next);
@@ -9953,9 +9953,9 @@ var Derived = class extends Source {
install(next) {
const before = this.#value;
this.#value = next;
const ops2 = before === void 0 || next === void 0 ? [["r", next ?? null]] : diffRevisions(before, next);
if (ops2.length > 0)
this.fanout.emit({ type: "ops", ops: ops2, value: next });
const ops3 = before === void 0 || next === void 0 ? [["r", next ?? null]] : diffRevisions(before, next);
if (ops3.length > 0)
this.fanout.emit({ type: "ops", ops: ops3, value: next });
}
};
var isSource = (value2) => value2 instanceof Source;
@@ -14008,10 +14008,10 @@ var RemoteBinding = class {
const target = pending?.values.get(name) ?? this.members.get(name);
if (target === void 0)
return;
const ops2 = target.decoder.decode(frame.ops ?? []);
target.value = applyImmutable(target.value, ops2);
const ops3 = target.decoder.decode(frame.ops ?? []);
target.value = applyImmutable(target.value, ops3);
if (target === this.members.get(name))
this.#deliver(name, { type: "ops", ops: ops2, value: target.value });
this.#deliver(name, { type: "ops", ops: ops3, value: target.value });
return;
}
const fresh = { value: frame.value, decoder: decoder() };
@@ -15835,8 +15835,8 @@ var Watch = class extends WatchLifecycle {
this.#retired = false;
if (lone !== void 0)
return lone;
const ops2 = batch.kind === "ops" ? batch.ops : [["r", value2]];
return { kind: "value", value: value2, ops: ops2, retired };
const ops3 = batch.kind === "ops" ? batch.ops : [["r", value2]];
return { kind: "value", value: value2, ops: ops3, retired };
}
/** The pending failure, or the value after one as a root replacement (no ops were kept since the failure). */
#nextAfterFailure() {
@@ -17601,7 +17601,7 @@ function describe(plan, record2) {
const change = plan.change;
if (change === void 0 || !publishes(plan))
return void 0;
const ops2 = change.loaded === void 0 ? EMPTY_OPERATIONS : change.prepared.ops;
const ops3 = change.loaded === void 0 ? EMPTY_OPERATIONS : change.prepared.ops;
return {
type: "document",
kind: "change",
@@ -17609,11 +17609,11 @@ function describe(plan, record2) {
conversationId,
version: change.version,
value: change.prepared.value,
ops: ops2
ops: ops3
};
}
function storeHeld(hold, ops2, value2, version) {
hold.pending.push(ops2);
function storeHeld(hold, ops3, value2, version) {
hold.pending.push(ops3);
const taken = hold.pending.take();
if (taken.kind === "replace")
return { content: { version, kind: "base", value: value2 }, ops: [["r", value2]] };
@@ -18694,7 +18694,7 @@ var SessionImpl = class {
return { kind: "adoption", error };
}
const deadline = this.#clock() + withinMs;
for (const { loaded, ops: ops2, base } of held2) {
for (const { loaded, ops: ops3, base } of held2) {
const id = loaded.record.id;
let hold = this.#holds.get(id);
if (hold === void 0) {
@@ -18702,7 +18702,7 @@ var SessionImpl = class {
hold = { loaded, pending: new PendingOps(), base: false, deadline, unpin };
this.#holds.set(id, hold);
}
hold.pending.push(ops2);
hold.pending.push(ops3);
if (base)
hold.base = true;
if (deadline < hold.deadline)
@@ -26917,14 +26917,14 @@ var TaskGraphView = class {
const mount = this.#mount;
if (mount === void 0)
return;
const ops2 = [];
const ops3 = [];
for (const [key, record2] of mount.records) {
const blocked = this.#blocking.blocked(record2);
if (blocked === mount.value.tasks[key]?.blocked)
continue;
ops2.push(blocked === void 0 ? ["d", ["tasks", key, "blocked"]] : ["s", ["tasks", key, "blocked"], blocked]);
ops3.push(blocked === void 0 ? ["d", ["tasks", key, "blocked"]] : ["s", ["tasks", key, "blocked"], blocked]);
}
publish(mount, ops2, BACKGROUND_CONTEXT, (error) => this.#session.report(error));
publish(mount, ops3, BACKGROUND_CONTEXT, (error) => this.#session.report(error));
}).catch(() => {
});
}
@@ -26948,7 +26948,7 @@ var TaskGraphView = class {
}
};
function advance(mount, publication, context, blocked, report2) {
const ops2 = [];
const ops3 = [];
const changed = /* @__PURE__ */ new Map();
const node = (key) => changed.has(key) ? changed.get(key) : mount.value.tasks[key];
for (const change of publication.changes) {
@@ -26961,7 +26961,7 @@ function advance(mount, publication, context, blocked, report2) {
mount.records.delete(key);
if (previous === void 0)
continue;
ops2.push(["d", ["tasks", key]]);
ops3.push(["d", ["tasks", key]]);
changed.set(key, void 0);
continue;
}
@@ -26970,7 +26970,7 @@ function advance(mount, publication, context, blocked, report2) {
const next = nodeOf2(record2, previous?.conversations ?? [], blocked(subject));
if (previous !== void 0 && JSON.stringify(next) === JSON.stringify(previous))
continue;
ops2.push(["s", ["tasks", key], next]);
ops3.push(["s", ["tasks", key], next]);
changed.set(key, next);
}
const created = /* @__PURE__ */ new Map();
@@ -26983,16 +26983,16 @@ function advance(mount, publication, context, blocked, report2) {
}
for (const [key, ids] of created) {
const conversations = [...node(key).conversations, ...ids].sort((a, b) => a - b);
ops2.push(["s", ["tasks", key, "conversations"], conversations]);
ops3.push(["s", ["tasks", key, "conversations"], conversations]);
}
publish(mount, ops2, context, report2);
publish(mount, ops3, context, report2);
}
function publish(mount, ops2, context, report2) {
if (ops2.length === 0)
function publish(mount, ops3, context, report2) {
if (ops3.length === 0)
return;
mount.value = applyImmutable(mount.value, ops2);
mount.value = applyImmutable(mount.value, ops3);
const frameContext = withoutAbortSignal(context);
const call = { kind: "value", value: mount.value, ops: ops2, retired: false };
const call = { kind: "value", value: mount.value, ops: ops3, retired: false };
for (const observer of [...mount.observers])
contained(() => observer.advance(call, frameContext), report2);
}
@@ -27218,19 +27218,19 @@ function advance2(id, mount, publication, context, report2) {
const previous = windowed.value;
let entries2 = previous.entries;
let before = previous.before;
let ops2 = docOps;
let ops3 = docOps;
if (appended.length > 0) {
ops2 = [...docOps, ["p", ["entries"], entries2.length, 0, inserted]];
ops3 = [...docOps, ["p", ["entries"], entries2.length, 0, inserted]];
entries2 = entries2.concat(appended);
if (entries2.length > windowed.max) {
const drop = entries2.length - windowed.min;
before = entries2[drop - 1].id;
entries2 = entries2.slice(drop);
ops2.push(["p", ["entries"], 0, drop, []], ["s", ["before"], before]);
ops3.push(["p", ["entries"], 0, drop, []], ["s", ["before"], before]);
}
}
windowed.value = view(mount.conversation, entries2, before, mount.docs);
const call = { kind: "value", value: windowed.value, ops: ops2, retired: false };
const call = { kind: "value", value: windowed.value, ops: ops3, retired: false };
contained(() => windowed.observer.advance(call, frameContext), report2);
}
}
@@ -31368,14 +31368,51 @@ function eventLog(harness2) {
}
}
// extensions.ts
// environment.ts
var CodingTools2 = { ...CodingTools, tools: CodingTools.tools.map((tool) => ({
...tool,
execute: (args, api, context) => tool.execute(args, { ...api, env: api.env.forTool(api.callId, tool.name) }, context)
})) };
var Started = defineDoc({
kind: "board.started",
version: 1,
checkpointWhen: () => false,
scope: "conversation",
history: "latest",
fork: "initial",
initial: () => ({})
});
function conversationChains(harness2, context) {
const cache = /* @__PURE__ */ new Map();
const chain = (id) => {
let result = cache.get(id);
if (!result) {
result = (async () => {
const record2 = await harness2.commit((tx) => tx.conversation(id), context);
const up = record2?.owner?.conversationId ?? record2?.parent?.conversationId;
const starter = (await harness2.snapshot(Started, id, context))?.by;
return [.../* @__PURE__ */ new Set([
String(id),
...up === void 0 ? [] : await chain(up),
...starter === void 0 ? [] : await chain(starter)
])];
})();
cache.set(id, result);
}
return result;
};
return chain;
}
var ops = Deno.core.ops;
// extensions.ts
var ops2 = Deno.core.ops;
var encoder4 = new TextEncoder();
var decoder4 = new TextDecoder();
var ExtensionState = defineDoc({ kind: "sausage.extensions", version: 1, scope: "session", checkpointWhen: () => false, initial: () => ({ good: null, current: null, error: null }) });
var Metadata = defineDoc({ kind: "sausage.metadata", version: 1, scope: "conversation", history: "latest", fork: "current", initial: () => ({ label: "", title: "" }), checkpointWhen: () => false });
async function file(kind, path, bytes2 = []) {
const result = await ops.op_file(kind, path, bytes2);
const result = await ops2.op_file(kind, path, bytes2);
if (!result.ok) throw Object.assign(new Error(result.error.message), result.error);
return result.value;
}
@@ -31398,14 +31435,14 @@ async function exec(command, options) {
program: `extension:${options.extension}`,
conversations: options.conversations ?? []
} : void 0;
const rid = ops.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const rid = ops2.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const abort = () => Deno.core.close(rid);
options.signal?.addEventListener("abort", abort, { once: true });
const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
const result = { code: 0, stdout: "", stderr: "" };
try {
for (; ; ) {
const chunk = await ops.op_exec_next(rid);
const chunk = await ops2.op_exec_next(rid);
if (!chunk.stream) {
if (chunk.error) throw new Error(chunk.error);
result.code = chunk.exitCode;
@@ -31432,11 +31469,12 @@ async function git(cwd, ...args) {
function registerModules() {
const modules = { "@earendil-works/sausage-events": events_exports, "@earendil-works/pi-schema": dist_exports, "@earendil-works/pi-durable": dist_exports3, "@earendil-works/pi-durable/tools": tools_exports, "@earendil-works/pi-ai": dist_exports4, "@earendil-works/pi-ai/models": models_exports, "@earendil-works/chord": dist_exports2, "@earendil-works/chord/context": context_exports };
globalThis.__sausage = { modules };
ops.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
ops2.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
}
async function loadExtensions(harness2, registry2, storage2, models2, context, builtins = []) {
registerModules();
const eventService = eventLog(() => harness2);
const chain = conversationChains(harness2, context);
const repo = "/src/sausage";
let providers = /* @__PURE__ */ new Map();
const resources = /* @__PURE__ */ new Map();
@@ -31447,7 +31485,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
let queue2 = Promise.resolve();
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
};
const dispose = async (entries2) => {
@@ -31455,7 +31493,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await cleanup();
} catch (error) {
ops.op_output(`Extension cleanup: ${String(error)}
ops2.op_output(`Extension cleanup: ${String(error)}
`);
}
}
@@ -31488,6 +31526,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
fs,
exec: (command, options) => exec(command, { ...options, extension: name }),
git,
chain,
implement: (api, value2) => {
staged.set(api.name, value2);
},
@@ -31510,12 +31549,12 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
onClose: (cb) => close.push(cb),
notice: (message) => {
state.notice = `${name}: ${message}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${name}: ${message}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${name}: ${message}
`);
if (!notices.has(message)) {
notices.add(message);
void harness2.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops.op_output(`Recording a notice failed: ${String(error)}
void harness2.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops2.op_output(`Recording a notice failed: ${String(error)}
`));
}
}
@@ -31594,7 +31633,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
tip = await git(repo, "rev-parse", "refs/heads/main");
} catch (error) {
state.error = String(error);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
return;
}
try {
@@ -31603,8 +31642,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
if (error.code === "not_found") settings = {};
else {
state.notice = `Settings: ${String(error)}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${state.notice}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${state.notice}
`);
}
}
@@ -31620,11 +31659,11 @@ Last good: ${fallback}` : failed;
await publish2();
};
const enqueue2 = () => queue2 = queue2.then(reload);
const rid = ops.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
const rid = ops2.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
await enqueue2();
const pending = (async () => {
while (active) {
await ops.op_watch_next(rid);
await ops2.op_watch_next(rid);
if (active) await enqueue2();
}
})();
+4 -1
View File
@@ -7,6 +7,7 @@ import * as chord from '@earendil-works/chord';
import * as contextApi from '@earendil-works/chord/context';
import * as schema from '@earendil-works/pi-schema';
import * as events from './events';
import { conversationChains } from './environment';
const ops = Deno.core.ops;
const encoder = new TextEncoder();
const decoder = new TextDecoder();
@@ -16,6 +17,7 @@ export type Host = events.EventHost & {
harness(): durable.Harness; storage: Pick<durable.Storage, 'scanSubmissions'>;
models(): modelApi.Models; settings(): durable.JsonObject; metadata: typeof Metadata;
fs: typeof fs; exec: typeof exec; git: typeof git;
chain(conversationId:number):Promise<string[]>;
implement<S>(api: {name:string;readonly service?:S}, value:NoInfer<S>):void;
need<S>(api:{name:string;readonly service?:S}):S; want<S>(api:{name:string;readonly service?:S}):S|undefined;
resource<T>(key:string, create:()=>T):T;
@@ -73,6 +75,7 @@ export function registerModules() {
export async function loadExtensions(harness:any, registry:any, storage:any, models:any, context:any, builtins:any[]=[]) {
registerModules();
const eventService = events.eventLog(()=>harness);
const chain = conversationChains(harness,context);
const repo = '/src/sausage';
let providers = new Map<string,any>();
const resources = new Map<string,any>();
@@ -104,7 +107,7 @@ export async function loadExtensions(harness:any, registry:any, storage:any, mod
}});
const host:Host = {
harness:()=>harness, storage, models:()=>models, settings:()=>settings, metadata:Metadata, fs,
exec:(command,options)=>exec(command,{...options,extension:name}),git,
exec:(command,options)=>exec(command,{...options,extension:name}),git,chain,
implement:(api,value)=>{staged.set(api.name,value);}, need:handle,
want:(api)=>providers.has(api.name)?handle(api):undefined,
resource:(key,create)=>{const id=`${name}/${key}`;if(!resources.has(id))resources.set(id,create());return resources.get(id);},
+54 -52
View File
@@ -30428,14 +30428,51 @@ function eventLog(harness2) {
}
}
// extensions.ts
// environment.ts
var CodingTools2 = { ...CodingTools, tools: CodingTools.tools.map((tool2) => ({
...tool2,
execute: (args, api, context) => tool2.execute(args, { ...api, env: api.env.forTool(api.callId, tool2.name) }, context)
})) };
var Started = defineDoc({
kind: "board.started",
version: 1,
checkpointWhen: () => false,
scope: "conversation",
history: "latest",
fork: "initial",
initial: () => ({})
});
function conversationChains(harness2, context) {
const cache = /* @__PURE__ */ new Map();
const chain = (id) => {
let result = cache.get(id);
if (!result) {
result = (async () => {
const record2 = await harness2.commit((tx) => tx.conversation(id), context);
const up = record2?.owner?.conversationId ?? record2?.parent?.conversationId;
const starter = (await harness2.snapshot(Started, id, context))?.by;
return [.../* @__PURE__ */ new Set([
String(id),
...up === void 0 ? [] : await chain(up),
...starter === void 0 ? [] : await chain(starter)
])];
})();
cache.set(id, result);
}
return result;
};
return chain;
}
var ops = Deno.core.ops;
// extensions.ts
var ops2 = Deno.core.ops;
var encoder4 = new TextEncoder();
var decoder4 = new TextDecoder();
var ExtensionState = defineDoc({ kind: "sausage.extensions", version: 1, scope: "session", checkpointWhen: () => false, initial: () => ({ good: null, current: null, error: null }) });
var Metadata = defineDoc({ kind: "sausage.metadata", version: 1, scope: "conversation", history: "latest", fork: "current", initial: () => ({ label: "", title: "" }), checkpointWhen: () => false });
async function file(kind, path, bytes2 = []) {
const result = await ops.op_file(kind, path, bytes2);
const result = await ops2.op_file(kind, path, bytes2);
if (!result.ok) throw Object.assign(new Error(result.error.message), result.error);
return result.value;
}
@@ -30458,14 +30495,14 @@ async function exec(command, options) {
program: `extension:${options.extension}`,
conversations: options.conversations ?? []
} : void 0;
const rid = ops.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const rid = ops2.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const abort = () => Deno.core.close(rid);
options.signal?.addEventListener("abort", abort, { once: true });
const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
const result = { code: 0, stdout: "", stderr: "" };
try {
for (; ; ) {
const chunk = await ops.op_exec_next(rid);
const chunk = await ops2.op_exec_next(rid);
if (!chunk.stream) {
if (chunk.error) throw new Error(chunk.error);
result.code = chunk.exitCode;
@@ -30492,11 +30529,12 @@ async function git(cwd, ...args) {
function registerModules() {
const modules = { "@earendil-works/sausage-events": events_exports, "@earendil-works/pi-schema": dist_exports, "@earendil-works/pi-durable": dist_exports3, "@earendil-works/pi-durable/tools": tools_exports, "@earendil-works/pi-ai": dist_exports4, "@earendil-works/pi-ai/models": models_exports, "@earendil-works/chord": dist_exports2, "@earendil-works/chord/context": context_exports };
globalThis.__sausage = { modules };
ops.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
ops2.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
}
async function loadExtensions(harness2, registry2, storage2, models2, context, builtins = []) {
registerModules();
const eventService = eventLog(() => harness2);
const chain = conversationChains(harness2, context);
const repo = "/src/sausage";
let providers = /* @__PURE__ */ new Map();
const resources = /* @__PURE__ */ new Map();
@@ -30507,7 +30545,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
let queue2 = Promise.resolve();
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
};
const dispose = async (entries3) => {
@@ -30515,7 +30553,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await cleanup();
} catch (error) {
ops.op_output(`Extension cleanup: ${String(error)}
ops2.op_output(`Extension cleanup: ${String(error)}
`);
}
}
@@ -30548,6 +30586,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
fs,
exec: (command, options) => exec(command, { ...options, extension: name }),
git,
chain,
implement: (api, value2) => {
staged.set(api.name, value2);
},
@@ -30570,12 +30609,12 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
onClose: (cb) => close.push(cb),
notice: (message) => {
state.notice = `${name}: ${message}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${name}: ${message}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${name}: ${message}
`);
if (!notices.has(message)) {
notices.add(message);
void harness2.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops.op_output(`Recording a notice failed: ${String(error)}
void harness2.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops2.op_output(`Recording a notice failed: ${String(error)}
`));
}
}
@@ -30654,7 +30693,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
tip = await git(repo, "rev-parse", "refs/heads/main");
} catch (error) {
state.error = String(error);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
return;
}
try {
@@ -30663,8 +30702,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
if (error.code === "not_found") settings = {};
else {
state.notice = `Settings: ${String(error)}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${state.notice}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${state.notice}
`);
}
}
@@ -30680,11 +30719,11 @@ Last good: ${fallback}` : failed;
await publish2();
};
const enqueue2 = () => queue2 = queue2.then(reload);
const rid = ops.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
const rid = ops2.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
await enqueue2();
const pending = (async () => {
while (active) {
await ops.op_watch_next(rid);
await ops2.op_watch_next(rid);
if (active) await enqueue2();
}
})();
@@ -30697,43 +30736,6 @@ Last good: ${fallback}` : failed;
} };
}
// environment.ts
var CodingTools2 = { ...CodingTools, tools: CodingTools.tools.map((tool2) => ({
...tool2,
execute: (args, api, context) => tool2.execute(args, { ...api, env: api.env.forTool(api.callId, tool2.name) }, context)
})) };
var Started = defineDoc({
kind: "board.started",
version: 1,
checkpointWhen: () => false,
scope: "conversation",
history: "latest",
fork: "initial",
initial: () => ({})
});
function conversationChains(harness2, context) {
const cache = /* @__PURE__ */ new Map();
const chain = (id) => {
let result = cache.get(id);
if (!result) {
result = (async () => {
const record2 = await harness2.commit((tx) => tx.conversation(id), context);
const up = record2?.owner?.conversationId ?? record2?.parent?.conversationId;
const starter = (await harness2.snapshot(Started, id, context))?.by;
return [.../* @__PURE__ */ new Set([
String(id),
...up === void 0 ? [] : await chain(up),
...starter === void 0 ? [] : await chain(starter)
])];
})();
cache.set(id, result);
}
return result;
};
return chain;
}
var ops2 = Deno.core.ops;
// node_modules/@earendil-works/chord/dist/sqlite/schema.js
function validateSqliteSchemaPrefix(prefix) {
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(prefix)) {
+109 -70
View File
@@ -7915,27 +7915,27 @@ var REPLACE_TAKE = { kind: "replace" };
var PendingOps = class {
#state = EMPTY;
/** Queue the ops of one commit. */
push(ops2) {
if (ops2.length === 0)
push(ops3) {
if (ops3.length === 0)
return;
const state = this.#state;
if (ops2[0][0] === "r") {
this.#state = { kind: "batch", ops: ops2 };
if (ops3[0][0] === "r") {
this.#state = { kind: "batch", ops: ops3 };
return;
}
switch (state.kind) {
case "empty":
this.#state = { kind: "batch", ops: ops2 };
this.#state = { kind: "batch", ops: ops3 };
return;
case "batch": {
const folding = { kind: "folding", ops: [], leaves: /* @__PURE__ */ new Map() };
this.#state = folding;
if (this.#fold(state.ops, folding))
this.#fold(ops2, folding);
this.#fold(ops3, folding);
return;
}
case "folding":
this.#fold(ops2, state);
this.#fold(ops3, state);
return;
case "replace":
return;
@@ -7966,8 +7966,8 @@ var PendingOps = class {
}
}
/** Fold `ops` into `folding`; false once the queue overflowed into `replace`. */
#fold(ops2, folding) {
for (const op2 of ops2) {
#fold(ops3, folding) {
for (const op2 of ops3) {
switch (op2[0]) {
case "r":
folding.ops.length = 0;
@@ -8236,12 +8236,12 @@ var PathError = class extends Error {
this.name = "PathError";
}
};
function apply(target, ops2) {
return applyOps(target, ops2);
function apply(target, ops3) {
return applyOps(target, ops3);
}
function applyOps(target, ops2) {
function applyOps(target, ops3) {
let root = target;
for (const op2 of ops2) {
for (const op2 of ops3) {
assertValidOp(op2);
if (op2[0] === "r") {
root = op2[1];
@@ -8310,14 +8310,14 @@ function applyOps(target, ops2) {
}
return root;
}
function applyImmutable(target, ops2) {
return applyImmutableBatches(target, [ops2]);
function applyImmutable(target, ops3) {
return applyImmutableBatches(target, [ops3]);
}
function applyImmutableBatches(target, batches) {
let root = target;
const owned = /* @__PURE__ */ new WeakSet();
for (const ops2 of batches) {
for (const op2 of ops2) {
for (const ops3 of batches) {
for (const op2 of ops3) {
assertValidOp(op2);
if (op2[0] === "r") {
root = op2[1];
@@ -8431,10 +8431,10 @@ function encoder() {
let nextId = 0;
let previous;
return {
encode(ops2) {
encode(ops3) {
previous = void 0;
const out = [];
for (const op2 of ops2) {
for (const op2 of ops3) {
if (op2[0] === "r") {
out.push(op2);
seen.clear();
@@ -8684,9 +8684,9 @@ var Source = class {
return [];
}
};
function unbox(ops2, value2) {
function unbox(ops3, value2) {
const result = [];
for (const op2 of ops2) {
for (const op2 of ops3) {
if (op2[0] === "r" || op2[1].length === 0)
return [["r", value2]];
const path = op2[1].slice(1);
@@ -8780,8 +8780,8 @@ var Cell = class extends Source {
throw notReady(`state ${this.key} (its init has not run)`);
const before = this.value;
const diff = options.diff ?? sharesStructure(before, value2);
const ops2 = diff ? diffRevisions(before, value2) : void 0;
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "ops", true, ops2);
const ops3 = diff ? diffRevisions(before, value2) : void 0;
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "ops", true, ops3);
}
/**
* Installs a migrated value and its schema (reload commit). The caller validated the value and the derivations
@@ -8792,7 +8792,7 @@ var Cell = class extends Source {
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "reset", false);
}
/** Adopts this revision and every derived one, then queues all notifications (cell first, then derivations). */
#publish(prepared, type, validate2 = true, ops2) {
#publish(prepared, type, validate2 = true, ops3) {
if (prepared.ops.length === 0 && type === "ops") {
this.#tracker.adopt(prepared);
return;
@@ -8809,7 +8809,7 @@ var Cell = class extends Source {
}
holdPublications(() => {
this.#tracker.adopt(prepared);
const published = ops2 !== void 0 && ops2.length > 0 ? ops2 : unbox(prepared.ops, value2);
const published = ops3 !== void 0 && ops3.length > 0 ? ops3 : unbox(prepared.ops, value2);
this.fanout.emit(type === "reset" ? { type, value: value2 } : { type, ops: published, value: value2 });
for (const [derived, next] of candidates)
derived.install(next);
@@ -8922,9 +8922,9 @@ var Derived = class extends Source {
install(next) {
const before = this.#value;
this.#value = next;
const ops2 = before === void 0 || next === void 0 ? [["r", next ?? null]] : diffRevisions(before, next);
if (ops2.length > 0)
this.fanout.emit({ type: "ops", ops: ops2, value: next });
const ops3 = before === void 0 || next === void 0 ? [["r", next ?? null]] : diffRevisions(before, next);
if (ops3.length > 0)
this.fanout.emit({ type: "ops", ops: ops3, value: next });
}
};
var isSource = (value2) => value2 instanceof Source;
@@ -12977,10 +12977,10 @@ var RemoteBinding = class {
const target = pending?.values.get(name) ?? this.members.get(name);
if (target === void 0)
return;
const ops2 = target.decoder.decode(frame.ops ?? []);
target.value = applyImmutable(target.value, ops2);
const ops3 = target.decoder.decode(frame.ops ?? []);
target.value = applyImmutable(target.value, ops3);
if (target === this.members.get(name))
this.#deliver(name, { type: "ops", ops: ops2, value: target.value });
this.#deliver(name, { type: "ops", ops: ops3, value: target.value });
return;
}
const fresh = { value: frame.value, decoder: decoder() };
@@ -14804,8 +14804,8 @@ var Watch = class extends WatchLifecycle {
this.#retired = false;
if (lone !== void 0)
return lone;
const ops2 = batch.kind === "ops" ? batch.ops : [["r", value2]];
return { kind: "value", value: value2, ops: ops2, retired };
const ops3 = batch.kind === "ops" ? batch.ops : [["r", value2]];
return { kind: "value", value: value2, ops: ops3, retired };
}
/** The pending failure, or the value after one as a root replacement (no ops were kept since the failure). */
#nextAfterFailure() {
@@ -16570,7 +16570,7 @@ function describe(plan, record2) {
const change = plan.change;
if (change === void 0 || !publishes(plan))
return void 0;
const ops2 = change.loaded === void 0 ? EMPTY_OPERATIONS : change.prepared.ops;
const ops3 = change.loaded === void 0 ? EMPTY_OPERATIONS : change.prepared.ops;
return {
type: "document",
kind: "change",
@@ -16578,11 +16578,11 @@ function describe(plan, record2) {
conversationId,
version: change.version,
value: change.prepared.value,
ops: ops2
ops: ops3
};
}
function storeHeld(hold, ops2, value2, version) {
hold.pending.push(ops2);
function storeHeld(hold, ops3, value2, version) {
hold.pending.push(ops3);
const taken = hold.pending.take();
if (taken.kind === "replace")
return { content: { version, kind: "base", value: value2 }, ops: [["r", value2]] };
@@ -17663,7 +17663,7 @@ var SessionImpl = class {
return { kind: "adoption", error };
}
const deadline = this.#clock() + withinMs;
for (const { loaded, ops: ops2, base } of held2) {
for (const { loaded, ops: ops3, base } of held2) {
const id = loaded.record.id;
let hold = this.#holds.get(id);
if (hold === void 0) {
@@ -17671,7 +17671,7 @@ var SessionImpl = class {
hold = { loaded, pending: new PendingOps(), base: false, deadline, unpin };
this.#holds.set(id, hold);
}
hold.pending.push(ops2);
hold.pending.push(ops3);
if (base)
hold.base = true;
if (deadline < hold.deadline)
@@ -26442,14 +26442,14 @@ var TaskGraphView = class {
const mount = this.#mount;
if (mount === void 0)
return;
const ops2 = [];
const ops3 = [];
for (const [key, record2] of mount.records) {
const blocked = this.#blocking.blocked(record2);
if (blocked === mount.value.tasks[key]?.blocked)
continue;
ops2.push(blocked === void 0 ? ["d", ["tasks", key, "blocked"]] : ["s", ["tasks", key, "blocked"], blocked]);
ops3.push(blocked === void 0 ? ["d", ["tasks", key, "blocked"]] : ["s", ["tasks", key, "blocked"], blocked]);
}
publish(mount, ops2, BACKGROUND_CONTEXT, (error) => this.#session.report(error));
publish(mount, ops3, BACKGROUND_CONTEXT, (error) => this.#session.report(error));
}).catch(() => {
});
}
@@ -26473,7 +26473,7 @@ var TaskGraphView = class {
}
};
function advance(mount, publication, context, blocked, report2) {
const ops2 = [];
const ops3 = [];
const changed = /* @__PURE__ */ new Map();
const node = (key) => changed.has(key) ? changed.get(key) : mount.value.tasks[key];
for (const change of publication.changes) {
@@ -26486,7 +26486,7 @@ function advance(mount, publication, context, blocked, report2) {
mount.records.delete(key);
if (previous === void 0)
continue;
ops2.push(["d", ["tasks", key]]);
ops3.push(["d", ["tasks", key]]);
changed.set(key, void 0);
continue;
}
@@ -26495,7 +26495,7 @@ function advance(mount, publication, context, blocked, report2) {
const next = nodeOf2(record2, previous?.conversations ?? [], blocked(subject));
if (previous !== void 0 && JSON.stringify(next) === JSON.stringify(previous))
continue;
ops2.push(["s", ["tasks", key], next]);
ops3.push(["s", ["tasks", key], next]);
changed.set(key, next);
}
const created = /* @__PURE__ */ new Map();
@@ -26508,16 +26508,16 @@ function advance(mount, publication, context, blocked, report2) {
}
for (const [key, ids] of created) {
const conversations = [...node(key).conversations, ...ids].sort((a, b) => a - b);
ops2.push(["s", ["tasks", key, "conversations"], conversations]);
ops3.push(["s", ["tasks", key, "conversations"], conversations]);
}
publish(mount, ops2, context, report2);
publish(mount, ops3, context, report2);
}
function publish(mount, ops2, context, report2) {
if (ops2.length === 0)
function publish(mount, ops3, context, report2) {
if (ops3.length === 0)
return;
mount.value = applyImmutable(mount.value, ops2);
mount.value = applyImmutable(mount.value, ops3);
const frameContext = withoutAbortSignal(context);
const call = { kind: "value", value: mount.value, ops: ops2, retired: false };
const call = { kind: "value", value: mount.value, ops: ops3, retired: false };
for (const observer of [...mount.observers])
contained(() => observer.advance(call, frameContext), report2);
}
@@ -26743,19 +26743,19 @@ function advance2(id, mount, publication, context, report2) {
const previous = windowed.value;
let entries2 = previous.entries;
let before = previous.before;
let ops2 = docOps;
let ops3 = docOps;
if (appended.length > 0) {
ops2 = [...docOps, ["p", ["entries"], entries2.length, 0, inserted]];
ops3 = [...docOps, ["p", ["entries"], entries2.length, 0, inserted]];
entries2 = entries2.concat(appended);
if (entries2.length > windowed.max) {
const drop = entries2.length - windowed.min;
before = entries2[drop - 1].id;
entries2 = entries2.slice(drop);
ops2.push(["p", ["entries"], 0, drop, []], ["s", ["before"], before]);
ops3.push(["p", ["entries"], 0, drop, []], ["s", ["before"], before]);
}
}
windowed.value = view(mount.conversation, entries2, before, mount.docs);
const call = { kind: "value", value: windowed.value, ops: ops2, retired: false };
const call = { kind: "value", value: windowed.value, ops: ops3, retired: false };
contained(() => windowed.observer.advance(call, frameContext), report2);
}
}
@@ -30428,14 +30428,51 @@ function eventLog(harness) {
}
}
// extensions.ts
// environment.ts
var CodingTools2 = { ...CodingTools, tools: CodingTools.tools.map((tool) => ({
...tool,
execute: (args, api, context) => tool.execute(args, { ...api, env: api.env.forTool(api.callId, tool.name) }, context)
})) };
var Started = defineDoc({
kind: "board.started",
version: 1,
checkpointWhen: () => false,
scope: "conversation",
history: "latest",
fork: "initial",
initial: () => ({})
});
function conversationChains(harness, context) {
const cache = /* @__PURE__ */ new Map();
const chain = (id) => {
let result = cache.get(id);
if (!result) {
result = (async () => {
const record2 = await harness.commit((tx) => tx.conversation(id), context);
const up = record2?.owner?.conversationId ?? record2?.parent?.conversationId;
const starter = (await harness.snapshot(Started, id, context))?.by;
return [.../* @__PURE__ */ new Set([
String(id),
...up === void 0 ? [] : await chain(up),
...starter === void 0 ? [] : await chain(starter)
])];
})();
cache.set(id, result);
}
return result;
};
return chain;
}
var ops = Deno.core.ops;
// extensions.ts
var ops2 = Deno.core.ops;
var encoder4 = new TextEncoder();
var decoder4 = new TextDecoder();
var ExtensionState = defineDoc({ kind: "sausage.extensions", version: 1, scope: "session", checkpointWhen: () => false, initial: () => ({ good: null, current: null, error: null }) });
var Metadata = defineDoc({ kind: "sausage.metadata", version: 1, scope: "conversation", history: "latest", fork: "current", initial: () => ({ label: "", title: "" }), checkpointWhen: () => false });
async function file(kind, path, bytes2 = []) {
const result = await ops.op_file(kind, path, bytes2);
const result = await ops2.op_file(kind, path, bytes2);
if (!result.ok) throw Object.assign(new Error(result.error.message), result.error);
return result.value;
}
@@ -30458,14 +30495,14 @@ async function exec(command, options) {
program: `extension:${options.extension}`,
conversations: options.conversations ?? []
} : void 0;
const rid = ops.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const rid = ops2.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const abort = () => Deno.core.close(rid);
options.signal?.addEventListener("abort", abort, { once: true });
const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
const result = { code: 0, stdout: "", stderr: "" };
try {
for (; ; ) {
const chunk = await ops.op_exec_next(rid);
const chunk = await ops2.op_exec_next(rid);
if (!chunk.stream) {
if (chunk.error) throw new Error(chunk.error);
result.code = chunk.exitCode;
@@ -30492,11 +30529,12 @@ async function git(cwd, ...args) {
function registerModules() {
const modules = { "@earendil-works/sausage-events": events_exports, "@earendil-works/pi-schema": dist_exports, "@earendil-works/pi-durable": dist_exports3, "@earendil-works/pi-durable/tools": tools_exports, "@earendil-works/pi-ai": dist_exports4, "@earendil-works/pi-ai/models": models_exports, "@earendil-works/chord": dist_exports2, "@earendil-works/chord/context": context_exports };
globalThis.__sausage = { modules };
ops.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
ops2.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
}
async function loadExtensions(harness, registry, storage, models, context, builtins = []) {
registerModules();
const eventService = eventLog(() => harness);
const chain = conversationChains(harness, context);
const repo = "/src/sausage";
let providers = /* @__PURE__ */ new Map();
const resources = /* @__PURE__ */ new Map();
@@ -30507,7 +30545,7 @@ async function loadExtensions(harness, registry, storage, models, context, built
let queue2 = Promise.resolve();
const publish2 = async () => {
await harness.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
};
const dispose = async (entries2) => {
@@ -30515,7 +30553,7 @@ async function loadExtensions(harness, registry, storage, models, context, built
try {
await cleanup();
} catch (error) {
ops.op_output(`Extension cleanup: ${String(error)}
ops2.op_output(`Extension cleanup: ${String(error)}
`);
}
}
@@ -30548,6 +30586,7 @@ async function loadExtensions(harness, registry, storage, models, context, built
fs,
exec: (command, options) => exec(command, { ...options, extension: name }),
git,
chain,
implement: (api, value2) => {
staged.set(api.name, value2);
},
@@ -30570,12 +30609,12 @@ async function loadExtensions(harness, registry, storage, models, context, built
onClose: (cb) => close.push(cb),
notice: (message) => {
state.notice = `${name}: ${message}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${name}: ${message}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${name}: ${message}
`);
if (!notices.has(message)) {
notices.add(message);
void harness.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops.op_output(`Recording a notice failed: ${String(error)}
void harness.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops2.op_output(`Recording a notice failed: ${String(error)}
`));
}
}
@@ -30654,7 +30693,7 @@ async function loadExtensions(harness, registry, storage, models, context, built
tip = await git(repo, "rev-parse", "refs/heads/main");
} catch (error) {
state.error = String(error);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
return;
}
try {
@@ -30663,8 +30702,8 @@ async function loadExtensions(harness, registry, storage, models, context, built
if (error.code === "not_found") settings = {};
else {
state.notice = `Settings: ${String(error)}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${state.notice}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${state.notice}
`);
}
}
@@ -30680,11 +30719,11 @@ Last good: ${fallback}` : failed;
await publish2();
};
const enqueue2 = () => queue2 = queue2.then(reload);
const rid = ops.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
const rid = ops2.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
await enqueue2();
const pending = (async () => {
while (active) {
await ops.op_watch_next(rid);
await ops2.op_watch_next(rid);
if (active) await enqueue2();
}
})();
+111 -72
View File
@@ -8946,27 +8946,27 @@ var REPLACE_TAKE = { kind: "replace" };
var PendingOps = class {
#state = EMPTY;
/** Queue the ops of one commit. */
push(ops2) {
if (ops2.length === 0)
push(ops3) {
if (ops3.length === 0)
return;
const state = this.#state;
if (ops2[0][0] === "r") {
this.#state = { kind: "batch", ops: ops2 };
if (ops3[0][0] === "r") {
this.#state = { kind: "batch", ops: ops3 };
return;
}
switch (state.kind) {
case "empty":
this.#state = { kind: "batch", ops: ops2 };
this.#state = { kind: "batch", ops: ops3 };
return;
case "batch": {
const folding = { kind: "folding", ops: [], leaves: /* @__PURE__ */ new Map() };
this.#state = folding;
if (this.#fold(state.ops, folding))
this.#fold(ops2, folding);
this.#fold(ops3, folding);
return;
}
case "folding":
this.#fold(ops2, state);
this.#fold(ops3, state);
return;
case "replace":
return;
@@ -8997,8 +8997,8 @@ var PendingOps = class {
}
}
/** Fold `ops` into `folding`; false once the queue overflowed into `replace`. */
#fold(ops2, folding) {
for (const op2 of ops2) {
#fold(ops3, folding) {
for (const op2 of ops3) {
switch (op2[0]) {
case "r":
folding.ops.length = 0;
@@ -9267,12 +9267,12 @@ var PathError = class extends Error {
this.name = "PathError";
}
};
function apply(target, ops2) {
return applyOps(target, ops2);
function apply(target, ops3) {
return applyOps(target, ops3);
}
function applyOps(target, ops2) {
function applyOps(target, ops3) {
let root2 = target;
for (const op2 of ops2) {
for (const op2 of ops3) {
assertValidOp(op2);
if (op2[0] === "r") {
root2 = op2[1];
@@ -9341,14 +9341,14 @@ function applyOps(target, ops2) {
}
return root2;
}
function applyImmutable(target, ops2) {
return applyImmutableBatches(target, [ops2]);
function applyImmutable(target, ops3) {
return applyImmutableBatches(target, [ops3]);
}
function applyImmutableBatches(target, batches) {
let root2 = target;
const owned = /* @__PURE__ */ new WeakSet();
for (const ops2 of batches) {
for (const op2 of ops2) {
for (const ops3 of batches) {
for (const op2 of ops3) {
assertValidOp(op2);
if (op2[0] === "r") {
root2 = op2[1];
@@ -9462,10 +9462,10 @@ function encoder() {
let nextId = 0;
let previous;
return {
encode(ops2) {
encode(ops3) {
previous = void 0;
const out = [];
for (const op2 of ops2) {
for (const op2 of ops3) {
if (op2[0] === "r") {
out.push(op2);
seen.clear();
@@ -9715,9 +9715,9 @@ var Source = class {
return [];
}
};
function unbox(ops2, value2) {
function unbox(ops3, value2) {
const result = [];
for (const op2 of ops2) {
for (const op2 of ops3) {
if (op2[0] === "r" || op2[1].length === 0)
return [["r", value2]];
const path = op2[1].slice(1);
@@ -9811,8 +9811,8 @@ var Cell = class extends Source {
throw notReady(`state ${this.key} (its init has not run)`);
const before = this.value;
const diff = options.diff ?? sharesStructure(before, value2);
const ops2 = diff ? diffRevisions(before, value2) : void 0;
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "ops", true, ops2);
const ops3 = diff ? diffRevisions(before, value2) : void 0;
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "ops", true, ops3);
}
/**
* Installs a migrated value and its schema (reload commit). The caller validated the value and the derivations
@@ -9823,7 +9823,7 @@ var Cell = class extends Source {
this.#publish(this.#tracker.prepareReplace({ v: value2 }), "reset", false);
}
/** Adopts this revision and every derived one, then queues all notifications (cell first, then derivations). */
#publish(prepared, type, validate2 = true, ops2) {
#publish(prepared, type, validate2 = true, ops3) {
if (prepared.ops.length === 0 && type === "ops") {
this.#tracker.adopt(prepared);
return;
@@ -9840,7 +9840,7 @@ var Cell = class extends Source {
}
holdPublications(() => {
this.#tracker.adopt(prepared);
const published = ops2 !== void 0 && ops2.length > 0 ? ops2 : unbox(prepared.ops, value2);
const published = ops3 !== void 0 && ops3.length > 0 ? ops3 : unbox(prepared.ops, value2);
this.fanout.emit(type === "reset" ? { type, value: value2 } : { type, ops: published, value: value2 });
for (const [derived, next] of candidates)
derived.install(next);
@@ -9953,9 +9953,9 @@ var Derived = class extends Source {
install(next) {
const before = this.#value;
this.#value = next;
const ops2 = before === void 0 || next === void 0 ? [["r", next ?? null]] : diffRevisions(before, next);
if (ops2.length > 0)
this.fanout.emit({ type: "ops", ops: ops2, value: next });
const ops3 = before === void 0 || next === void 0 ? [["r", next ?? null]] : diffRevisions(before, next);
if (ops3.length > 0)
this.fanout.emit({ type: "ops", ops: ops3, value: next });
}
};
var isSource = (value2) => value2 instanceof Source;
@@ -14008,10 +14008,10 @@ var RemoteBinding = class {
const target = pending?.values.get(name) ?? this.members.get(name);
if (target === void 0)
return;
const ops2 = target.decoder.decode(frame.ops ?? []);
target.value = applyImmutable(target.value, ops2);
const ops3 = target.decoder.decode(frame.ops ?? []);
target.value = applyImmutable(target.value, ops3);
if (target === this.members.get(name))
this.#deliver(name, { type: "ops", ops: ops2, value: target.value });
this.#deliver(name, { type: "ops", ops: ops3, value: target.value });
return;
}
const fresh = { value: frame.value, decoder: decoder() };
@@ -15835,8 +15835,8 @@ var Watch = class extends WatchLifecycle {
this.#retired = false;
if (lone !== void 0)
return lone;
const ops2 = batch.kind === "ops" ? batch.ops : [["r", value2]];
return { kind: "value", value: value2, ops: ops2, retired };
const ops3 = batch.kind === "ops" ? batch.ops : [["r", value2]];
return { kind: "value", value: value2, ops: ops3, retired };
}
/** The pending failure, or the value after one as a root replacement (no ops were kept since the failure). */
#nextAfterFailure() {
@@ -17601,7 +17601,7 @@ function describe(plan, record2) {
const change = plan.change;
if (change === void 0 || !publishes(plan))
return void 0;
const ops2 = change.loaded === void 0 ? EMPTY_OPERATIONS : change.prepared.ops;
const ops3 = change.loaded === void 0 ? EMPTY_OPERATIONS : change.prepared.ops;
return {
type: "document",
kind: "change",
@@ -17609,11 +17609,11 @@ function describe(plan, record2) {
conversationId,
version: change.version,
value: change.prepared.value,
ops: ops2
ops: ops3
};
}
function storeHeld(hold, ops2, value2, version) {
hold.pending.push(ops2);
function storeHeld(hold, ops3, value2, version) {
hold.pending.push(ops3);
const taken = hold.pending.take();
if (taken.kind === "replace")
return { content: { version, kind: "base", value: value2 }, ops: [["r", value2]] };
@@ -18694,7 +18694,7 @@ var SessionImpl = class {
return { kind: "adoption", error };
}
const deadline = this.#clock() + withinMs;
for (const { loaded, ops: ops2, base: base2 } of held2) {
for (const { loaded, ops: ops3, base: base2 } of held2) {
const id = loaded.record.id;
let hold = this.#holds.get(id);
if (hold === void 0) {
@@ -18702,7 +18702,7 @@ var SessionImpl = class {
hold = { loaded, pending: new PendingOps(), base: false, deadline, unpin };
this.#holds.set(id, hold);
}
hold.pending.push(ops2);
hold.pending.push(ops3);
if (base2)
hold.base = true;
if (deadline < hold.deadline)
@@ -26917,14 +26917,14 @@ var TaskGraphView = class {
const mount = this.#mount;
if (mount === void 0)
return;
const ops2 = [];
const ops3 = [];
for (const [key, record2] of mount.records) {
const blocked = this.#blocking.blocked(record2);
if (blocked === mount.value.tasks[key]?.blocked)
continue;
ops2.push(blocked === void 0 ? ["d", ["tasks", key, "blocked"]] : ["s", ["tasks", key, "blocked"], blocked]);
ops3.push(blocked === void 0 ? ["d", ["tasks", key, "blocked"]] : ["s", ["tasks", key, "blocked"], blocked]);
}
publish(mount, ops2, BACKGROUND_CONTEXT, (error) => this.#session.report(error));
publish(mount, ops3, BACKGROUND_CONTEXT, (error) => this.#session.report(error));
}).catch(() => {
});
}
@@ -26948,7 +26948,7 @@ var TaskGraphView = class {
}
};
function advance(mount, publication, context, blocked, report2) {
const ops2 = [];
const ops3 = [];
const changed = /* @__PURE__ */ new Map();
const node = (key) => changed.has(key) ? changed.get(key) : mount.value.tasks[key];
for (const change of publication.changes) {
@@ -26961,7 +26961,7 @@ function advance(mount, publication, context, blocked, report2) {
mount.records.delete(key);
if (previous === void 0)
continue;
ops2.push(["d", ["tasks", key]]);
ops3.push(["d", ["tasks", key]]);
changed.set(key, void 0);
continue;
}
@@ -26970,7 +26970,7 @@ function advance(mount, publication, context, blocked, report2) {
const next = nodeOf2(record2, previous?.conversations ?? [], blocked(subject));
if (previous !== void 0 && JSON.stringify(next) === JSON.stringify(previous))
continue;
ops2.push(["s", ["tasks", key], next]);
ops3.push(["s", ["tasks", key], next]);
changed.set(key, next);
}
const created = /* @__PURE__ */ new Map();
@@ -26983,16 +26983,16 @@ function advance(mount, publication, context, blocked, report2) {
}
for (const [key, ids] of created) {
const conversations = [...node(key).conversations, ...ids].sort((a, b) => a - b);
ops2.push(["s", ["tasks", key, "conversations"], conversations]);
ops3.push(["s", ["tasks", key, "conversations"], conversations]);
}
publish(mount, ops2, context, report2);
publish(mount, ops3, context, report2);
}
function publish(mount, ops2, context, report2) {
if (ops2.length === 0)
function publish(mount, ops3, context, report2) {
if (ops3.length === 0)
return;
mount.value = applyImmutable(mount.value, ops2);
mount.value = applyImmutable(mount.value, ops3);
const frameContext = withoutAbortSignal(context);
const call2 = { kind: "value", value: mount.value, ops: ops2, retired: false };
const call2 = { kind: "value", value: mount.value, ops: ops3, retired: false };
for (const observer of [...mount.observers])
contained(() => observer.advance(call2, frameContext), report2);
}
@@ -27218,19 +27218,19 @@ function advance2(id, mount, publication, context, report2) {
const previous = windowed.value;
let entries2 = previous.entries;
let before = previous.before;
let ops2 = docOps;
let ops3 = docOps;
if (appended.length > 0) {
ops2 = [...docOps, ["p", ["entries"], entries2.length, 0, inserted]];
ops3 = [...docOps, ["p", ["entries"], entries2.length, 0, inserted]];
entries2 = entries2.concat(appended);
if (entries2.length > windowed.max) {
const drop = entries2.length - windowed.min;
before = entries2[drop - 1].id;
entries2 = entries2.slice(drop);
ops2.push(["p", ["entries"], 0, drop, []], ["s", ["before"], before]);
ops3.push(["p", ["entries"], 0, drop, []], ["s", ["before"], before]);
}
}
windowed.value = view(mount.conversation, entries2, before, mount.docs);
const call2 = { kind: "value", value: windowed.value, ops: ops2, retired: false };
const call2 = { kind: "value", value: windowed.value, ops: ops3, retired: false };
contained(() => windowed.observer.advance(call2, frameContext), report2);
}
}
@@ -31368,14 +31368,51 @@ function eventLog(harness2) {
}
}
// extensions.ts
// environment.ts
var CodingTools2 = { ...CodingTools, tools: CodingTools.tools.map((tool) => ({
...tool,
execute: (args, api, context) => tool.execute(args, { ...api, env: api.env.forTool(api.callId, tool.name) }, context)
})) };
var Started = defineDoc({
kind: "board.started",
version: 1,
checkpointWhen: () => false,
scope: "conversation",
history: "latest",
fork: "initial",
initial: () => ({})
});
function conversationChains(harness2, context) {
const cache = /* @__PURE__ */ new Map();
const chain = (id) => {
let result = cache.get(id);
if (!result) {
result = (async () => {
const record2 = await harness2.commit((tx) => tx.conversation(id), context);
const up = record2?.owner?.conversationId ?? record2?.parent?.conversationId;
const starter = (await harness2.snapshot(Started, id, context))?.by;
return [.../* @__PURE__ */ new Set([
String(id),
...up === void 0 ? [] : await chain(up),
...starter === void 0 ? [] : await chain(starter)
])];
})();
cache.set(id, result);
}
return result;
};
return chain;
}
var ops = Deno.core.ops;
// extensions.ts
var ops2 = Deno.core.ops;
var encoder4 = new TextEncoder();
var decoder4 = new TextDecoder();
var ExtensionState = defineDoc({ kind: "sausage.extensions", version: 1, scope: "session", checkpointWhen: () => false, initial: () => ({ good: null, current: null, error: null }) });
var Metadata = defineDoc({ kind: "sausage.metadata", version: 1, scope: "conversation", history: "latest", fork: "current", initial: () => ({ label: "", title: "" }), checkpointWhen: () => false });
async function file(kind, path, bytes2 = []) {
const result = await ops.op_file(kind, path, bytes2);
const result = await ops2.op_file(kind, path, bytes2);
if (!result.ok) throw Object.assign(new Error(result.error.message), result.error);
return result.value;
}
@@ -31398,14 +31435,14 @@ async function exec(command, options) {
program: `extension:${options.extension}`,
conversations: options.conversations ?? []
} : void 0;
const rid = ops.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const rid = ops2.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const abort = () => Deno.core.close(rid);
options.signal?.addEventListener("abort", abort, { once: true });
const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
const result = { code: 0, stdout: "", stderr: "" };
try {
for (; ; ) {
const chunk = await ops.op_exec_next(rid);
const chunk = await ops2.op_exec_next(rid);
if (!chunk.stream) {
if (chunk.error) throw new Error(chunk.error);
result.code = chunk.exitCode;
@@ -31432,11 +31469,12 @@ async function git(cwd, ...args) {
function registerModules() {
const modules = { "@earendil-works/sausage-events": events_exports, "@earendil-works/pi-schema": dist_exports, "@earendil-works/pi-durable": dist_exports3, "@earendil-works/pi-durable/tools": tools_exports, "@earendil-works/pi-ai": dist_exports4, "@earendil-works/pi-ai/models": models_exports, "@earendil-works/chord": dist_exports2, "@earendil-works/chord/context": context_exports };
globalThis.__sausage = { modules };
ops.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
ops2.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
}
async function loadExtensions(harness2, registry2, storage2, models2, context, builtins = []) {
registerModules();
const eventService = eventLog(() => harness2);
const chain = conversationChains(harness2, context);
const repo = "/src/sausage";
let providers = /* @__PURE__ */ new Map();
const resources = /* @__PURE__ */ new Map();
@@ -31447,7 +31485,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
let queue2 = Promise.resolve();
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
};
const dispose = async (entries2) => {
@@ -31455,7 +31493,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await cleanup();
} catch (error) {
ops.op_output(`Extension cleanup: ${String(error)}
ops2.op_output(`Extension cleanup: ${String(error)}
`);
}
}
@@ -31488,6 +31526,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
fs,
exec: (command, options) => exec(command, { ...options, extension: name }),
git,
chain,
implement: (api, value2) => {
staged.set(api.name, value2);
},
@@ -31510,12 +31549,12 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
onClose: (cb) => close.push(cb),
notice: (message) => {
state.notice = `${name}: ${message}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${name}: ${message}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${name}: ${message}
`);
if (!notices.has(message)) {
notices.add(message);
void harness2.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops.op_output(`Recording a notice failed: ${String(error)}
void harness2.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops2.op_output(`Recording a notice failed: ${String(error)}
`));
}
}
@@ -31594,7 +31633,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
tip = await git(repo, "rev-parse", "refs/heads/main");
} catch (error) {
state.error = String(error);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
return;
}
try {
@@ -31603,8 +31642,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
if (error.code === "not_found") settings = {};
else {
state.notice = `Settings: ${String(error)}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${state.notice}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${state.notice}
`);
}
}
@@ -31620,11 +31659,11 @@ Last good: ${fallback}` : failed;
await publish2();
};
const enqueue2 = () => queue2 = queue2.then(reload);
const rid = ops.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
const rid = ops2.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
await enqueue2();
const pending = (async () => {
while (active) {
await ops.op_watch_next(rid);
await ops2.op_watch_next(rid);
if (active) await enqueue2();
}
})();
@@ -31671,7 +31710,7 @@ var todosModule = await import(
/* @vite-ignore */
`${base}/todos/server.ts`
);
var { Plan, TaskIndex, Branch, Started } = planModule;
var { Plan, TaskIndex, Branch, Started: Started2 } = planModule;
var { Todos, Standing, add, mark, noteFor, DONE_LIMIT } = todosModule;
var call = async (args, id = root.id, name = "plan") => {
const conversation = await harness.conversation(id, BACKGROUND_CONTEXT);
@@ -31776,7 +31815,7 @@ try {
}, BACKGROUND_CONTEXT);
assert(Object.keys((await harness.snapshot(Plan, branch.id, BACKGROUND_CONTEXT)).epics).length === 1, "Fork inherited plan");
equal(JSON.parse(text(await call({ action: "show" }, branch.id))), Plan.definition.initial());
assert((await harness.snapshot(Started, branch.id, BACKGROUND_CONTEXT)).started, "Branch started");
assert((await harness.snapshot(Started2, branch.id, BACKGROUND_CONTEXT)).started, "Branch started");
const ULTIMATE = 5;
const fakeTx = () => {
const docs = /* @__PURE__ */ new Map();
+54 -52
View File
@@ -30428,14 +30428,51 @@ function eventLog(harness2) {
}
}
// extensions.ts
// environment.ts
var CodingTools2 = { ...CodingTools, tools: CodingTools.tools.map((tool) => ({
...tool,
execute: (args, api, context) => tool.execute(args, { ...api, env: api.env.forTool(api.callId, tool.name) }, context)
})) };
var Started = defineDoc({
kind: "board.started",
version: 1,
checkpointWhen: () => false,
scope: "conversation",
history: "latest",
fork: "initial",
initial: () => ({})
});
function conversationChains(harness2, context) {
const cache = /* @__PURE__ */ new Map();
const chain2 = (id) => {
let result = cache.get(id);
if (!result) {
result = (async () => {
const record2 = await harness2.commit((tx) => tx.conversation(id), context);
const up = record2?.owner?.conversationId ?? record2?.parent?.conversationId;
const starter = (await harness2.snapshot(Started, id, context))?.by;
return [.../* @__PURE__ */ new Set([
String(id),
...up === void 0 ? [] : await chain2(up),
...starter === void 0 ? [] : await chain2(starter)
])];
})();
cache.set(id, result);
}
return result;
};
return chain2;
}
var ops = Deno.core.ops;
// extensions.ts
var ops2 = Deno.core.ops;
var encoder4 = new TextEncoder();
var decoder4 = new TextDecoder();
var ExtensionState = defineDoc({ kind: "sausage.extensions", version: 1, scope: "session", checkpointWhen: () => false, initial: () => ({ good: null, current: null, error: null }) });
var Metadata = defineDoc({ kind: "sausage.metadata", version: 1, scope: "conversation", history: "latest", fork: "current", initial: () => ({ label: "", title: "" }), checkpointWhen: () => false });
async function file(kind, path, bytes2 = []) {
const result = await ops.op_file(kind, path, bytes2);
const result = await ops2.op_file(kind, path, bytes2);
if (!result.ok) throw Object.assign(new Error(result.error.message), result.error);
return result.value;
}
@@ -30458,14 +30495,14 @@ async function exec(command, options) {
program: `extension:${options.extension}`,
conversations: options.conversations ?? []
} : void 0;
const rid = ops.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const rid = ops2.op_exec(command, options.cwd, options.timeout, void 0, options.env, options.conversations, tag);
const abort = () => Deno.core.close(rid);
options.signal?.addEventListener("abort", abort, { once: true });
const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
const result = { code: 0, stdout: "", stderr: "" };
try {
for (; ; ) {
const chunk = await ops.op_exec_next(rid);
const chunk = await ops2.op_exec_next(rid);
if (!chunk.stream) {
if (chunk.error) throw new Error(chunk.error);
result.code = chunk.exitCode;
@@ -30492,11 +30529,12 @@ async function git(cwd, ...args) {
function registerModules() {
const modules = { "@earendil-works/sausage-events": events_exports, "@earendil-works/pi-schema": dist_exports, "@earendil-works/pi-durable": dist_exports3, "@earendil-works/pi-durable/tools": tools_exports, "@earendil-works/pi-ai": dist_exports4, "@earendil-works/pi-ai/models": models_exports, "@earendil-works/chord": dist_exports2, "@earendil-works/chord/context": context_exports };
globalThis.__sausage = { modules };
ops.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
ops2.op_modules(Object.fromEntries(Object.entries(modules).map(([name, value2]) => [name, Object.keys(value2)])));
}
async function loadExtensions(harness2, registry2, storage2, models2, context, builtins = []) {
registerModules();
const eventService = eventLog(() => harness2);
const chain2 = conversationChains(harness2, context);
const repo = "/src/sausage";
let providers = /* @__PURE__ */ new Map();
const resources = /* @__PURE__ */ new Map();
@@ -30507,7 +30545,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
let queue2 = Promise.resolve();
const publish2 = async () => {
await harness2.commit(async (tx) => Object.assign(await tx.doc(ExtensionState), state), context);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
await fs.write("/src/sausage/.git/sausage-last-good", state.good ?? "");
};
const dispose = async (entries2) => {
@@ -30515,7 +30553,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
try {
await cleanup();
} catch (error) {
ops.op_output(`Extension cleanup: ${String(error)}
ops2.op_output(`Extension cleanup: ${String(error)}
`);
}
}
@@ -30548,6 +30586,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
fs,
exec: (command, options) => exec(command, { ...options, extension: name }),
git,
chain: chain2,
implement: (api, value2) => {
staged.set(api.name, value2);
},
@@ -30570,12 +30609,12 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
onClose: (cb) => close.push(cb),
notice: (message) => {
state.notice = `${name}: ${message}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${name}: ${message}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${name}: ${message}
`);
if (!notices.has(message)) {
notices.add(message);
void harness2.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops.op_output(`Recording a notice failed: ${String(error)}
void harness2.commit((tx) => eventService.emit(tx, NoticeEvent, { extension: name, message, level: "warning" }), context).catch((error) => ops2.op_output(`Recording a notice failed: ${String(error)}
`));
}
}
@@ -30654,7 +30693,7 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
tip = await git(repo, "rev-parse", "refs/heads/main");
} catch (error) {
state.error = String(error);
ops.op_extension_status(JSON.stringify(state));
ops2.op_extension_status(JSON.stringify(state));
return;
}
try {
@@ -30663,8 +30702,8 @@ async function loadExtensions(harness2, registry2, storage2, models2, context, b
if (error.code === "not_found") settings = {};
else {
state.notice = `Settings: ${String(error)}`;
ops.op_extension_status(JSON.stringify(state));
ops.op_output(`${state.notice}
ops2.op_extension_status(JSON.stringify(state));
ops2.op_output(`${state.notice}
`);
}
}
@@ -30680,11 +30719,11 @@ Last good: ${fallback}` : failed;
await publish2();
};
const enqueue2 = () => queue2 = queue2.then(reload);
const rid = ops.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
const rid = ops2.op_watch([`${repo}/.git/refs/heads/main`, `${repo}/.git/packed-refs`, `${repo}/settings.json`]);
await enqueue2();
const pending = (async () => {
while (active) {
await ops.op_watch_next(rid);
await ops2.op_watch_next(rid);
if (active) await enqueue2();
}
})();
@@ -30697,43 +30736,6 @@ Last good: ${fallback}` : failed;
} };
}
// environment.ts
var CodingTools2 = { ...CodingTools, tools: CodingTools.tools.map((tool) => ({
...tool,
execute: (args, api, context) => tool.execute(args, { ...api, env: api.env.forTool(api.callId, tool.name) }, context)
})) };
var Started = defineDoc({
kind: "board.started",
version: 1,
checkpointWhen: () => false,
scope: "conversation",
history: "latest",
fork: "initial",
initial: () => ({})
});
function conversationChains(harness2, context) {
const cache = /* @__PURE__ */ new Map();
const chain2 = (id) => {
let result = cache.get(id);
if (!result) {
result = (async () => {
const record2 = await harness2.commit((tx) => tx.conversation(id), context);
const up = record2?.owner?.conversationId ?? record2?.parent?.conversationId;
const starter = (await harness2.snapshot(Started, id, context))?.by;
return [.../* @__PURE__ */ new Set([
String(id),
...up === void 0 ? [] : await chain2(up),
...starter === void 0 ? [] : await chain2(starter)
])];
})();
cache.set(id, result);
}
return result;
};
return chain2;
}
var ops2 = Deno.core.ops;
// node_modules/@earendil-works/chord/dist/sqlite/schema.js
function validateSqliteSchemaPrefix(prefix) {
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(prefix)) {