mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
Each branch is a top-level conversation forked from another at an entry; the session-scoped p2.main document lists them and names the current one. Earlier branches are read-only. The main agent's per-agent state (subagents, artifacts, voice and chat mode, pending move) stays on conversation 1 for every branch. - tree plugin: SessionTree (tree, navigate, select); the worker reports main and branches to the catalog (Sessions v4, catalog schema 4) - TUI: /tree and double Escape (pi's default "tree"), a tree selector adapted from coding-agent - web: Edit on user messages, Branch from here on answers, branch rows under the session in the sidebar - main-agent checks use the branch list instead of conversation 1 - search indexes each branch's own entries Design: apps/p2/docs/tree.md
65 lines
2.2 KiB
TypeScript
65 lines
2.2 KiB
TypeScript
// Native catalog operations beneath the copied mobile Session sheet.
|
|
import { BACKGROUND_CONTEXT, liveIn, whenLive } from "@p2/app/v1";
|
|
import {
|
|
closeLayers,
|
|
conversationRoute,
|
|
MoreIcon,
|
|
openLayer,
|
|
useComposerState,
|
|
useHost,
|
|
useNavigation,
|
|
useObject,
|
|
useReadable,
|
|
useRoute,
|
|
useSheet,
|
|
} from "@p2/app/v1/web";
|
|
import { directoryPickerLayer } from "../envs/contract.web.tsx";
|
|
import { Sessions } from "./contract.ts";
|
|
import { SessionSheet } from "./SessionSheet.tsx";
|
|
export function SessionOptions() {
|
|
const host = useHost(),
|
|
nav = useNavigation(),
|
|
route = useRoute();
|
|
const service = useObject(Sessions, {}, { variant: "owner" }),
|
|
rows = useReadable(service?.sessions);
|
|
const session = rows?.find((row) => row.id === route.session),
|
|
sessionSheet = useSheet("session");
|
|
const { setError } = useComposerState();
|
|
const perform = (operation: Promise<unknown> | undefined) =>
|
|
void operation?.catch((error) => setError(error instanceof Error ? error.message : String(error)));
|
|
if (!service || !session || route.conversation !== session.main) return null;
|
|
return (
|
|
<>
|
|
<button
|
|
type="button"
|
|
className="icon-button"
|
|
aria-label="Session options"
|
|
onClick={() => openLayer({ kind: "sheet", name: "session" })}
|
|
>
|
|
<MoreIcon />
|
|
</button>
|
|
{sessionSheet && (
|
|
<SessionSheet
|
|
session={session}
|
|
onClose={closeLayers}
|
|
onRename={(title) => perform(service?.rename({ id: session.id, title }, BACKGROUND_CONTEXT))}
|
|
onPin={() => perform(service?.pin({ id: session.id, pinned: !session.pinned }, BACKGROUND_CONTEXT))}
|
|
onShare={() => openLayer({ kind: "sheet", name: "share" })}
|
|
onCwd={() => openLayer(directoryPickerLayer(session.env ?? null, session.cwd))}
|
|
onArchive={() => {
|
|
closeLayers();
|
|
void nav.replace(conversationRoute(route, undefined));
|
|
// Leaving the session unmounts this view and releases `service`, which cancels its calls: the
|
|
// archive holds a reference of its own until it is done.
|
|
const archiving = host.hold(Sessions, {}, BACKGROUND_CONTEXT);
|
|
perform(
|
|
whenLive(archiving)
|
|
.then(() => liveIn(archiving, "owner")?.archive({ id: session.id }, BACKGROUND_CONTEXT))
|
|
.finally(() => archiving.release()),
|
|
);
|
|
}}
|
|
/>
|
|
)}
|
|
</>
|
|
);
|
|
}
|