Merge v3-t51: automations and pause as world extensions, host.chain (T51, P3-n)

This commit is contained in:
2026-11-07 14:51:59 +00:00
29 changed files with 37148 additions and 403 deletions
+54 -15
View File
@@ -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();
}
})();