mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
feat(durable,chord): publish commits at adoption, address-following watches, synchronous observers
Durable publishes a stored commit inside Storage.commit's adopt: the
telemetry events, its own observers (document watches, conversation
views, task graph, scheduler, waits), and a dispatch of host
subscribeCommits listeners one microtask later, before every wait and
abort the commit triggers. A commit the storage rejects after adoption is
published before the Session fails; a throwing telemetry adapter is
reported as host_error "telemetry" instead of rejecting a durable commit.
Chord's watch listeners take one change object, { value, ops, retired }
({ value } for native watches); retirement is a flag, not an end.
Durable's document watches follow their address, and a task document's
watch ends after its retirement call. New synchronous observers for
adapters: observeDoc, observeConversation, observeTaskGraph; the watches
are built on them. The env file watcher's change type is renamed
FileChange.
This commit is contained in:
parent
363ad78bf0
commit
8b0caefc22
53 changed files with 3389 additions and 554 deletions
|
|
@ -32,7 +32,7 @@ console.log("watch baseline:", notesWatch.value);
|
|||
const busy = Promise.withResolvers<void>();
|
||||
const done = Promise.withResolvers<void>();
|
||||
let calls = 0;
|
||||
notesWatch.start(async (value, ops, _deliveryContext) => {
|
||||
notesWatch.start(async ({ value, ops }, _deliveryContext) => {
|
||||
console.log("watch update:", value, JSON.stringify(ops));
|
||||
// The first call stays busy, as a slow consumer would, while three more commits land.
|
||||
if (++calls === 1) await busy.promise;
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ if (process.env.OPENAI_API_KEY === undefined) {
|
|||
process.stdout.write(text.slice(printed.length));
|
||||
printed = text;
|
||||
};
|
||||
liveWatch.start(async (value) => {
|
||||
liveWatch.start(async ({ value }) => {
|
||||
const block = value?.generation?.message?.content.find((content) => content.type === "text");
|
||||
if (block?.type === "text") printText(block.text);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -82,14 +82,14 @@ let stop: () => Promise<unknown>;
|
|||
if (mode === "events") {
|
||||
const stream = await watchEvents(harness, root.id, context);
|
||||
print(stream.snapshot);
|
||||
stream.start(async (events) => {
|
||||
stream.start(async ({ value: events }) => {
|
||||
for (const event of events) print(event);
|
||||
});
|
||||
stop = () => stream.stop();
|
||||
} else {
|
||||
const watch = await root.watch(context);
|
||||
print({ view: watch.value });
|
||||
watch.start(async (_value, ops) => print({ ops }));
|
||||
watch.start(async ({ ops }) => print({ ops }));
|
||||
stop = () => watch.stop();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import { Type } from "@earendil-works/pi-ai";
|
|||
import { createModels } from "@earendil-works/pi-ai/models";
|
||||
import { fauxAssistantMessage, fauxProvider, fauxText, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import {
|
||||
type AgentEvent,
|
||||
createRegistry,
|
||||
defineExtension,
|
||||
defineTool,
|
||||
|
|
@ -63,7 +62,7 @@ console.log(
|
|||
view.value.entries.map((entry) => entry.kind),
|
||||
);
|
||||
console.log("view tool slot:", live.tools?.[0]?.status, JSON.stringify(live.tools?.[0]?.output));
|
||||
view.start(async (value) => {
|
||||
view.start(async ({ value }) => {
|
||||
const slot = (value.docs["pi.live"] as LiveState).tools?.[0];
|
||||
if (slot?.status === "running") console.log("view output now:", JSON.stringify(slot.output));
|
||||
});
|
||||
|
|
@ -75,7 +74,7 @@ console.log(
|
|||
stream.snapshot.tools.map((slot) => `${slot.name} ${slot.status}`),
|
||||
);
|
||||
let output = stream.snapshot.tools[0]?.output ?? "";
|
||||
stream.start(async (events: readonly AgentEvent[]) => {
|
||||
stream.start(async ({ value: events }) => {
|
||||
for (const event of events) {
|
||||
if (event.type === "tool_execution_update" && event.output !== undefined) {
|
||||
output =
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@ const attached = new Set<ConversationId>();
|
|||
const attach = async (id: ConversationId, indent: string): Promise<void> => {
|
||||
attached.add(id);
|
||||
const stream = await watchEvents(harness, id, context);
|
||||
stream.start(async (events) => {
|
||||
stream.start(async ({ value: events }) => {
|
||||
for (const event of events) {
|
||||
print(indent, event);
|
||||
if (event.type !== "tool_execution_update") continue;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,6 @@ import { createModels } from "@earendil-works/pi-ai/models";
|
|||
import { fauxAssistantMessage, fauxProvider, fauxText, fauxToolCall } from "@earendil-works/pi-ai/providers/faux";
|
||||
import { openaiProvider } from "@earendil-works/pi-ai/providers/openai";
|
||||
import {
|
||||
type AgentEvent,
|
||||
AssistantEntry,
|
||||
type ConversationId,
|
||||
configure,
|
||||
|
|
@ -285,7 +284,7 @@ function contentText(content: string | readonly { type: string; text?: string }[
|
|||
/** Print the main conversation's messages as they are committed. Subagents work in their own conversations. */
|
||||
async function follow(harness: Harness, id: ConversationId): Promise<void> {
|
||||
const stream = await watchEvents(harness, id, context);
|
||||
stream.start(async (events: readonly AgentEvent[]) => {
|
||||
stream.start(async ({ value: events }) => {
|
||||
for (const event of events) {
|
||||
if (event.type !== "message_end") continue;
|
||||
const message = event.entry.model?.[0];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue