Pass cached conversation ancestry to bash execution

This commit is contained in:
2026-11-07 12:28:46 +00:00
parent ad6aa97ee1
commit e003fd6b40
8 changed files with 22810 additions and 8 deletions
+29 -2
View File
@@ -53126,6 +53126,31 @@ async function openStorage(path3) {
}
// environment.ts
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 record = await harness2.commit((tx) => tx.conversation(id), context);
const up = record?.owner?.conversationId ?? record?.parent?.conversationId ?? (await harness2.snapshot(Started, id, context))?.by;
return [String(id), ...up === void 0 ? [] : await chain2(up)];
})();
cache.set(id, result);
}
return result;
};
return chain2;
}
var ops = Deno.core.ops;
var ok = (value2) => ({ ok: true, value: value2 });
var path2 = (cwd, input) => {
@@ -53137,6 +53162,7 @@ var path2 = (cwd, input) => {
return "/" + parts.join("/");
};
function environment(cwd = "/", conversation) {
const conversations = Array.isArray(conversation) ? conversation : conversation === void 0 ? [] : [String(conversation)];
const call = (kind, p, bytes2 = []) => ops.op_file(kind, path2(cwd, p), bytes2);
const env = {
id: "world",
@@ -53215,7 +53241,7 @@ function environment(cwd = "/", conversation) {
}
};
try {
const rid = ops.op_exec(command, options?.cwd ?? cwd, options?.timeout, conversation === void 0 ? void 0 : String(conversation));
const rid = ops.op_exec(command, options?.cwd ?? cwd, options?.timeout, void 0, void 0, conversations);
const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
let result;
while (true) {
@@ -54219,8 +54245,9 @@ var harness = await Harness.open(storage, {
settings: { followUpMode: "all", get extensions() {
return registry.snapshot().installed();
} },
env: (target) => environment(target.cwd ?? "/", target.conversationId)
env: async (target) => environment(target.cwd ?? "/", await chain(target.conversationId))
}, BACKGROUND_CONTEXT);
var chain = conversationChains(harness, BACKGROUND_CONTEXT);
var root = await harness.root(BACKGROUND_CONTEXT, { agent: { model: { provider: "openai", modelId: model2.id }, thinkingLevel: "low", cwd: "/" } });
var storedAgent = await harness.snapshot(AgentDoc, root.id, BACKGROUND_CONTEXT);
if (Array.isArray(storedAgent?.extensions) && storedAgent.extensions.length === 2 && [CodingTools, CredentialRequests].every((extension) => storedAgent.extensions.includes(extension.name))) {
File diff suppressed because it is too large Load Diff
+81
View File
@@ -0,0 +1,81 @@
// Real bash tools and extension commands retain the nearest-first grant ancestry (D47, D63).
import { BACKGROUND_CONTEXT as context } from '@earendil-works/chord/context';
import { createModels } from '@earendil-works/pi-ai/models';
import { fauxProvider, fauxAssistantMessage, fauxToolCall } from '@earendil-works/pi-ai/providers/faux';
import { Harness, createRegistry, defineDoc, defineExtension, defineTask } from '@earendil-works/pi-durable';
import { CodingTools } from '@earendil-works/pi-durable/tools';
import { environment, conversationChains } from './environment';
import { exec, fs } from './extensions';
import { openStorage } from './sqlite';
const assert = (condition: any, message: string) => { if (!condition) throw new Error(message); };
const equal = (a: any, b: any) => assert(JSON.stringify(a) === JSON.stringify(b), 'Wrong conversation chain');
const Started = defineDoc<{ by?: number }>({ kind: 'board.started', version: 1, checkpointWhen: () => false,
scope: 'conversation', history: 'latest', fork: 'initial', initial: () => ({}) });
const Anchor = defineTask({ name: 'chain.anchor', version: 1, initial: () => ({ phase: 'done' }),
phases: { done: (_task: any, runtime: any, ctx: any) => runtime.commit(() => ({ status: 'terminal', outcome: { status: 'completed', result: null } }), ctx) } });
const models = createModels(), faux = fauxProvider({ tokensPerSecond: 100000 }); models.setProvider(faux.provider);
const registry = createRegistry(); registry.install(CodingTools); registry.install(defineExtension({ name: 'chain', tasks: [Anchor] }));
const harness = await Harness.open(await openStorage(globalThis.storagePath), { models, registry,
env: async (target: any) => environment('/', await chain(target.conversationId)) }, context);
const chain = conversationChains(harness, context);
const agent = { model: { provider: 'faux', modelId: 'faux-1' }, extensions: [CodingTools], cwd: '/' };
const root = await harness.root(context, { agent });
const owned = async (parent: any) => parent.commit(async (tx: any) => {
const taskId = await tx.createTask(Anchor, null, { ownership: { kind: 'conversation' } });
return tx.createConversation({ ownership: { kind: 'task', taskId } });
}, context);
let records: any;
if (!globalThis.chainReady) {
const parentRecord = await owned(root);
const parent = await harness.conversation(parentRecord.id, context);
const child = await owned(parent);
const entry = await parent.commit((tx: any) => tx.appendEntry(parent.id, { kind: 'chain.origin', data: {} }), context);
const fork = await parent.commit((tx: any) => tx.forkConversation(parent.id, entry.id, { ownership: { kind: 'ownerless' } }), context);
const unrelated = await harness.commit((tx: any) => tx.createConversation({ ownership: { kind: 'ownerless' } }), context);
const branch = await harness.commit(async (tx: any) => {
const record = await tx.createConversation({ ownership: { kind: 'ownerless' } });
(await tx.doc(Started, record.id)).by = parent.id;
return record;
}, context);
const ownedFork = await root.commit(async (tx: any) => {
const taskId = await tx.createTask(Anchor, null, { ownership: { kind: 'conversation' } });
(await tx.doc(Started, fork.id)).by = unrelated.id;
return tx.forkConversation(parent.id, fork.parent.at, { ownership: { kind: 'task', taskId } });
}, context);
records = { parent: parentRecord, child, fork, branch, unrelated, ownedFork };
await fs.write('/chain-ids', JSON.stringify(records));
await harness.close(context);
} else {
records = JSON.parse(await fs.read('/chain-ids'));
const { parent, child, fork, branch, unrelated, ownedFork } = records;
harness.resume();
try {
for (const record of [child, fork, branch, unrelated]) {
const conversation = await harness.conversation(record.id, context);
await conversation.configure(agent, context);
faux.setResponses([
fauxAssistantMessage([fauxToolCall('bash', { command: `curl http://host.sausage/whoami` }, { id: `identity-${record.id}` })], { stopReason: 'toolUse' }),
fauxAssistantMessage([fauxToolCall('bash', { command: `curl -i http://127.0.0.1:${globalThis.chainPort}/token` }, { id: `fetch-${record.id}` })], { stopReason: 'toolUse' }),
fauxAssistantMessage('done'),
]);
await (await conversation.submit({ type: 'input', content: 'Check inherited access' }, context)).wait(context);
const entries = (await conversation.context(context)).entries;
const tools = entries.filter((entry: any) => entry.kind === 'pi.tool-result');
const output = (entry: any) => entry.model.flatMap((message: any) => message.content ?? []).filter((block: any) => block.type === 'text').map((block: any) => block.text).join('');
const identity = JSON.parse(output(tools[0]));
equal(identity.conversations, record.id === unrelated.id ? [String(record.id)] : [String(record.id), String(parent.id), String(root.id)]);
const response = output(tools[1]);
assert(response.includes(record.id === unrelated.id ? '403' : 'accepted'), 'Wrong inherited authorization');
}
equal(await chain(ownedFork.id), [String(ownedFork.id), String(root.id)]);
assert(await harness.snapshot(Started, unrelated.id, context) === undefined, 'Missing starter must remain absent');
assert(chain(child.id) === chain(child.id), 'The chain must use its cached promise');
const denied = await exec(['curl', '-i', `http://127.0.0.1:${globalThis.chainPort}/unrelated`], { cwd: '/', conversations: await chain(child.id) });
assert(denied.stdout.includes('403'), 'An unrelated grant must not cover the child');
const delegated = await exec(['curl', 'http://host.sausage/whoami'], { cwd: '/', conversations: await chain(child.id) });
assert(delegated.code === 0, 'Extension exec failed');
equal(JSON.parse(delegated.stdout).conversations, [String(child.id), String(parent.id), String(root.id)]);
const cleared = await exec(['curl', 'http://host.sausage/whoami'], { cwd: '/', conversations: [] });
assert(cleared.code === 0, 'Explicit empty chain failed'); equal(JSON.parse(cleared.stdout).conversations, []);
} finally { await harness.close(context); }
}
+25 -2
View File
@@ -1,5 +1,27 @@
// The coding tools see only world paths. Rust owns the VFS and process execution.
import { LineScanner } from '@earendil-works/pi-durable/env';
import { defineDoc } from '@earendil-works/pi-durable';
// D63: read the optional branch starter without creating a document.
const Started = defineDoc<{ by?: number }>({ kind: 'board.started', version: 1,
checkpointWhen: () => false, scope: 'conversation', history: 'latest', fork: 'initial', initial: () => ({}) });
export function conversationChains(harness: any, context: any) {
const cache = new Map<number, Promise<string[]>>();
const chain = (id: number): Promise<string[]> => {
let result = cache.get(id);
if (!result) {
result = (async () => {
const record = await harness.commit((tx: any) => tx.conversation(id), context);
const up = record?.owner?.conversationId ?? record?.parent?.conversationId ??
(await harness.snapshot(Started, id, context))?.by;
return [String(id), ...(up === undefined ? [] : await chain(up))];
})();
cache.set(id, result);
}
return result;
};
return chain;
}
const ops = Deno.core.ops;
const ok = (value: any) => ({ ok: true, value });
const path = (cwd: string, input: string) => {
@@ -10,7 +32,8 @@ const path = (cwd: string, input: string) => {
}
return '/' + parts.join('/');
};
export function environment(cwd = '/', conversation?: string | number) {
export function environment(cwd = '/', conversation?: string | number | string[]) {
const conversations = Array.isArray(conversation) ? conversation : conversation === undefined ? [] : [String(conversation)];
const call = (kind: string, p: string, bytes: number[] = []) => ops.op_file(kind, path(cwd, p), bytes);
const env: any = {
id: 'world', cwd,
@@ -87,7 +110,7 @@ export function environment(cwd = '/', conversation?: string | number) {
}
};
try {
const rid = ops.op_exec(command, options?.cwd ?? cwd, options?.timeout, conversation === undefined ? undefined : String(conversation));
const rid = ops.op_exec(command, options?.cwd ?? cwd, options?.timeout, undefined, undefined, conversations);
const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
let result;
while (true) {
+3 -2
View File
@@ -5,7 +5,7 @@ import { openaiProvider } from '@earendil-works/pi-ai/providers/openai';
import { Harness, AgentDoc, createRegistry } from '@earendil-works/pi-durable';
import { CodingTools } from '@earendil-works/pi-durable/tools';
import { openStorage } from './sqlite';
import { environment } from './environment';
import { environment, conversationChains } from './environment';
import { CredentialRequests, startRequests } from './credentials';
import { serve } from './input';
import { loadExtensions } from './extensions';
@@ -32,8 +32,9 @@ registry.install(CredentialRequests);
export const storage = await openStorage(globalThis.storagePath);
const harness = await Harness.open(storage, {
models, registry, settings: { followUpMode: 'all', get extensions() { return registry.snapshot().installed(); } },
env: (target: any) => environment(target.cwd ?? '/', target.conversationId),
env: async (target: any) => environment(target.cwd ?? '/', await chain(target.conversationId)),
}, context);
const chain = conversationChains(harness, context);
const root = await harness.root(context, { agent: { model: { provider: 'openai', modelId: model.id }, thinkingLevel: 'low', cwd: '/' } });
// T32 stored this exact built-in list in the console root. Use registry defaults now.
const storedAgent = await harness.snapshot(AgentDoc, root.id, context);
+11 -1
View File
@@ -21249,6 +21249,15 @@ function createCodingTools(options = {}) {
var CodingTools = createCodingTools();
// environment.ts
var Started = defineDoc({
kind: "board.started",
version: 1,
checkpointWhen: () => false,
scope: "conversation",
history: "latest",
fork: "initial",
initial: () => ({})
});
var ops = Deno.core.ops;
var ok = (value2) => ({ ok: true, value: value2 });
var path = (cwd, input) => {
@@ -21260,6 +21269,7 @@ var path = (cwd, input) => {
return "/" + parts.join("/");
};
function environment(cwd = "/", conversation) {
const conversations = Array.isArray(conversation) ? conversation : conversation === void 0 ? [] : [String(conversation)];
const call = (kind, p, bytes2 = []) => ops.op_file(kind, path(cwd, p), bytes2);
const env = {
id: "world",
@@ -21338,7 +21348,7 @@ function environment(cwd = "/", conversation) {
}
};
try {
const rid = ops.op_exec(command, options?.cwd ?? cwd, options?.timeout, conversation === void 0 ? void 0 : String(conversation));
const rid = ops.op_exec(command, options?.cwd ?? cwd, options?.timeout, void 0, void 0, conversations);
const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
let result;
while (true) {
+11 -1
View File
@@ -21249,6 +21249,15 @@ function createCodingTools(options = {}) {
var CodingTools = createCodingTools();
// environment.ts
var Started = defineDoc({
kind: "board.started",
version: 1,
checkpointWhen: () => false,
scope: "conversation",
history: "latest",
fork: "initial",
initial: () => ({})
});
var ops = Deno.core.ops;
var ok = (value2) => ({ ok: true, value: value2 });
var path = (cwd, input) => {
@@ -21260,6 +21269,7 @@ var path = (cwd, input) => {
return "/" + parts.join("/");
};
function environment(cwd = "/", conversation) {
const conversations = Array.isArray(conversation) ? conversation : conversation === void 0 ? [] : [String(conversation)];
const call = (kind, p, bytes2 = []) => ops.op_file(kind, path(cwd, p), bytes2);
const env = {
id: "world",
@@ -21338,7 +21348,7 @@ function environment(cwd = "/", conversation) {
}
};
try {
const rid = ops.op_exec(command, options?.cwd ?? cwd, options?.timeout, conversation === void 0 ? void 0 : String(conversation));
const rid = ops.op_exec(command, options?.cwd ?? cwd, options?.timeout, void 0, void 0, conversations);
const decoders = { stdout: new TextDecoder(), stderr: new TextDecoder() };
let result2;
while (true) {