feat(hololake): complete public runtime loop

This commit is contained in:
冰朔 2026-09-03 19:44:19 +08:00
commit 92cce27973
26 changed files with 2626 additions and 79 deletions

View file

@ -1,12 +1,14 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { check } from "@tauri-apps/plugin-updater";
import { relaunch } from "@tauri-apps/plugin-process";
import { listen } from "@tauri-apps/api/event";
import { Icon } from "./icons";
import * as api from "./runtime";
import type {
KnowledgeDocument,
KnowledgeSummary,
SourceKind,
RuntimeOverview,
SystemSnapshot,
} from "./types";
import {
@ -34,6 +36,7 @@ const nav: [View, string, string][] = [
const labels: Record<SourceKind, string> = {
USER_MESSAGE: "用户语言",
PERSONA_RESPONSE: "人格回应",
EXTERNAL_AI_MESSAGE: "外部 AI",
SYSTEM_CONTEXT: "系统环境",
PROTOCOL_EVENT: "协议运行",
AGENT_ACTION: "Agent 执行",
@ -43,6 +46,7 @@ const labels: Record<SourceKind, string> = {
export default function App() {
const [data, setData] = useState<SystemSnapshot | null>(null),
[runtime, setRuntime] = useState<RuntimeOverview | null>(null),
[view, setView] = useState<View>("channel"),
[error, setError] = useState(""),
[busy, setBusy] = useState(false);
@ -51,8 +55,23 @@ export default function App() {
.snapshot()
.then(setData)
.catch((e) => setError(String(e)));
useEffect(() => {
const refreshRuntime = () =>
api
.runtimeOverview()
.then(setRuntime)
.catch((e) => setError(String(e)));
const refreshAll = () => {
refresh();
refreshRuntime();
};
useEffect(() => {
refreshAll();
if (!("__TAURI_INTERNALS__" in window)) return;
let unlisten: (() => void) | undefined;
listen("hololake-runtime-event", () => refreshAll()).then((dispose) => {
unlisten = dispose;
});
return () => unlisten?.();
}, []);
if (!data)
return (
@ -61,7 +80,7 @@ export default function App() {
HoloLake
</div>
);
if (!data.channel) return <Onboarding onDone={refresh} error={error} />;
if (!data.channel) return <Onboarding onDone={refreshAll} error={error} />;
return (
<div className="app-shell">
<div className="titlebar">
@ -104,7 +123,8 @@ export default function App() {
{view === "channel" && (
<Channel
data={data}
refresh={refresh}
runtime={runtime}
refresh={refreshAll}
openKnowledge={() => setView("knowledge")}
/>
)}{" "}
@ -115,7 +135,8 @@ export default function App() {
{view === "settings" && (
<Settings
data={data}
refresh={refresh}
runtime={runtime}
refresh={refreshAll}
busy={busy}
setBusy={setBusy}
setError={setError}
@ -171,10 +192,12 @@ function Onboarding({ onDone, error }: { onDone: () => void; error: string }) {
function Channel({
data,
runtime,
refresh,
openKnowledge,
}: {
data: SystemSnapshot;
runtime: RuntimeOverview | null;
refresh: () => void;
openKnowledge: () => void;
}) {
@ -210,14 +233,19 @@ function Channel({
.filter(
(e) =>
e.sourceKind === "USER_MESSAGE" ||
e.sourceKind === "PERSONA_RESPONSE",
e.sourceKind === "PERSONA_RESPONSE" ||
e.sourceKind === "EXTERNAL_AI_MESSAGE",
)
.slice(0, 8)
.reverse()
.map((e) => (
<article
className={
e.sourceKind === "USER_MESSAGE" ? "human" : "persona"
e.sourceKind === "USER_MESSAGE"
? "human"
: e.sourceKind === "PERSONA_RESPONSE"
? "persona"
: "external-ai"
}
key={e.eventId}
>
@ -226,6 +254,52 @@ function Channel({
</article>
))}
</section>
{runtime &&
runtime.proposals.some(
(proposal) => proposal.state === "PENDING_HUMAN_APPROVAL",
) && (
<section className="approval-queue">
<h2> Agent </h2>
{runtime.proposals
.filter(
(proposal) => proposal.state === "PENDING_HUMAN_APPROVAL",
)
.map((proposal) => (
<article key={proposal.proposalId}>
<div>
<strong>{proposal.gir.programId}</strong>
<p>
{proposal.gir.actions
.map((action) => action.operation)
.join(" → ")}
</p>
<small>
{proposal.clientId} · {proposal.proposalId}
</small>
</div>
<div>
<button
className="soft"
onClick={async () => {
await api.rejectProposal(proposal.proposalId);
refresh();
}}
>
</button>
<button
onClick={async () => {
await api.approveProposal(proposal.proposalId);
refresh();
}}
>
</button>
</div>
</article>
))}
</section>
)}
<ReceiptStream data={data} />
<div className="composer">
<textarea
@ -256,6 +330,23 @@ function Channel({
<strong></strong>
<p className="path">{data.channel?.privateGitPath}</p>
</article>
<article>
<h2></h2>
<strong>
{runtime?.persona.personas[0]?.displayName ?? "尚未建立"}
</strong>
<p>
{runtime?.persona.personas[0]?.state ?? "不会自动生成默认人格"}
</p>
</article>
<article>
<h2>GLP </h2>
<strong>
{runtime?.realtime.state ?? "正在启动"} ·{" "}
{runtime?.realtime.connectedClients ?? 0}
</strong>
<p>{runtime?.realtime.endpoint ?? "仅监听本机回环地址"}</p>
</article>
<article>
<h2></h2>
<strong>
@ -276,7 +367,9 @@ function ReceiptStream({ data }: { data: SystemSnapshot }) {
const rows = data.timeline
.filter(
(e) =>
e.sourceKind !== "USER_MESSAGE" && e.sourceKind !== "PERSONA_RESPONSE",
e.sourceKind !== "USER_MESSAGE" &&
e.sourceKind !== "PERSONA_RESPONSE" &&
e.sourceKind !== "EXTERNAL_AI_MESSAGE",
)
.slice(0, 5);
return (
@ -839,18 +932,22 @@ function Portal() {
}
function Settings({
data,
runtime,
refresh,
busy,
setBusy,
setError,
}: {
data: SystemSnapshot;
runtime: RuntimeOverview | null;
refresh: () => void;
busy: boolean;
setBusy: (v: boolean) => void;
setError: (v: string) => void;
}) {
const [bridgeName, setBridgeName] = useState("");
const [bridgeName, setBridgeName] = useState(""),
[personaName, setPersonaName] = useState(""),
[runtimeMessage, setRuntimeMessage] = useState("");
const update = async () => {
setBusy(true);
try {
@ -894,7 +991,11 @@ function Settings({
<article className="bridge">
<div>
<h2> AI</h2>
<p></p>
<p>
{runtime?.realtime.endpoint ?? "本机实时桥正在启动"} ·{" "}
{runtime?.realtime.connectedClients ?? 0}{" "}
</p>
</div>
<div>
<input
@ -915,17 +1016,95 @@ function Settings({
</button>
</div>
</article>
<article className="bridge">
<div>
<h2></h2>
<p>
{runtime?.persona.personas.length
? `${runtime.persona.personas.length} 个本频道试用人格;既有人格验证数 ${runtime.persona.verifiedExistingPersonaCount}`
: "建立一个本频道可逆试用人格,再由外部 AI 宿主实时承载。"}
</p>
</div>
<div>
<input
value={personaName}
onChange={(e) => setPersonaName(e.target.value)}
placeholder="给试用人格起名"
/>
<button
onClick={async () => {
if (personaName.trim()) {
const persona = await api.registerPersona(personaName.trim());
setPersonaName("");
setRuntimeMessage(
`${persona.displayName} 已建立;尚未验证为既有历史人格。`,
);
refresh();
}
}}
>
</button>
</div>
</article>
<article>
<div>
<h2></h2>
<p>
{runtimeMessage ||
"先建立一个外部 AI 入口,然后复制本机 GLP 接入命令给外部编程 AI。"}
</p>
</div>
<button
className="soft"
disabled={!data.externalAiBridges.length}
onClick={async () => {
const invitation = await api.realtimeInvitation();
const bridge = data.externalAiBridges[0];
const persona = runtime?.persona.personas[0];
const command = `${invitation.connectorCommand} --bridge-id ${bridge.bridgeId}${persona ? ` --persona-id ${persona.personaId}` : ""}`;
await navigator.clipboard.writeText(command);
setRuntimeMessage(
"实时接入命令已复制。令牌只在本机 descriptor 文件中。",
);
}}
>
</button>
</article>
{data.externalAiBridges.map((b) => (
<article key={b.bridgeId}>
<div>
<h2>{b.displayName}</h2>
<p>
{b.bridgeId} · {b.inboxPath}
{b.bridgeId} ·{" "}
{runtime?.realtime.protocol ?? "GLP_LOCAL_REALTIME/1"}
</p>
</div>
<span className="status">{b.state}</span>
</article>
))}
{runtime?.persona.personas.map((persona) => (
<article key={persona.personaId}>
<div>
<h2>{persona.displayName}</h2>
<p>
{persona.personaId} · {persona.state}
</p>
</div>
<button
className="soft danger"
onClick={async () => {
if (confirm(`删除试用人格 ${persona.personaId}`)) {
await api.deleteTrialPersona(persona.personaId);
refresh();
}
}}
>
</button>
</article>
))}
</section>
</div>
);

View file

@ -1,53 +1,293 @@
import { invoke } from "@tauri-apps/api/core";
import type { Channel, ExternalAiBridge, KnowledgeDocument, MutationReceipt, SystemSnapshot, TimelineEvent } from "./types";
import type {
AgentProposal,
AgentReceipt,
Channel,
ExternalAiBridge,
KnowledgeDocument,
MutationReceipt,
PublicPersona,
RealtimeInvitation,
RuntimeOverview,
SystemSnapshot,
TimelineEvent,
} from "./types";
const tauri = () => "__TAURI_INTERNALS__" in window;
const now = () => new Date().toISOString();
const key = "hololake.clean.browser-state.v1";
const runtimeKey = "hololake.clean.browser-runtime.v1";
type BrowserState = { channel: Channel | null; timeline: TimelineEvent[]; documents: KnowledgeDocument[]; bridges: ExternalAiBridge[] };
const empty: BrowserState = { channel: null, timeline: [], documents: [], bridges: [] };
const load = (): BrowserState => JSON.parse(localStorage.getItem(key) ?? JSON.stringify(empty));
const save = (state: BrowserState) => localStorage.setItem(key, JSON.stringify(state));
const event = (sourceKind: TimelineEvent["sourceKind"], title: string, content: string, state: TimelineEvent["state"] = "SUCCEEDED"): TimelineEvent => ({ eventId: crypto.randomUUID(), sourceKind, title, content, occurredAt: now(), state });
type BrowserState = {
channel: Channel | null;
timeline: TimelineEvent[];
documents: KnowledgeDocument[];
bridges: ExternalAiBridge[];
};
const empty: BrowserState = {
channel: null,
timeline: [],
documents: [],
bridges: [],
};
const load = (): BrowserState =>
JSON.parse(localStorage.getItem(key) ?? JSON.stringify(empty));
const save = (state: BrowserState) =>
localStorage.setItem(key, JSON.stringify(state));
const loadRuntime = (): { personas: PublicPersona[] } =>
JSON.parse(localStorage.getItem(runtimeKey) ?? '{"personas":[]}');
const saveRuntime = (state: { personas: PublicPersona[] }) =>
localStorage.setItem(runtimeKey, JSON.stringify(state));
const event = (
sourceKind: TimelineEvent["sourceKind"],
title: string,
content: string,
state: TimelineEvent["state"] = "SUCCEEDED",
): TimelineEvent => ({
eventId: crypto.randomUUID(),
sourceKind,
title,
content,
occurredAt: now(),
state,
});
export async function snapshot(): Promise<SystemSnapshot> {
if (tauri()) return invoke("system_snapshot");
const s = load();
return { appVersion: "1.0.0-browser", channel: s.channel, timeline: s.timeline, documents: s.documents.map(({ path, title, updatedAt, sha256 }) => ({ path, title, updatedAt, sha256 })), moduleRegistryVersion: "1", publicUpdateEndpoint: "https://guanghulab.com/hololake/releases/latest.json", publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE", externalAiBridges: s.bridges };
return {
appVersion: "1.1.0-browser",
channel: s.channel,
timeline: s.timeline,
documents: s.documents.map(({ path, title, updatedAt, sha256 }) => ({
path,
title,
updatedAt,
sha256,
})),
moduleRegistryVersion: "1",
publicUpdateEndpoint:
"https://guanghulab.com/hololake/releases/latest.json",
publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE",
externalAiBridges: s.bridges,
};
}
export async function createChannel(name: string): Promise<Channel> {
if (tauri()) return invoke("create_channel", { name });
const s = load(); const channel: Channel = { channelId: `HL-CH-${crypto.randomUUID().slice(0,8).toUpperCase()}`, name, createdAt: now(), privateGitPath: "浏览器预览不写入磁盘", publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE" };
s.channel = channel; s.timeline.unshift(event("SYSTEM_RECEIPT", "频道已建立", `${channel.channelId} 已登记。`)); save(s); return channel;
const s = load();
const channel: Channel = {
channelId: `HL-CH-${crypto.randomUUID().slice(0, 8).toUpperCase()}`,
name,
createdAt: now(),
privateGitPath: "浏览器预览不写入磁盘",
publicDistributionState: "SIGNED_UPDATE_FEED_READY_NO_RELEASE",
};
s.channel = channel;
s.timeline.unshift(
event("SYSTEM_RECEIPT", "频道已建立", `${channel.channelId} 已登记。`),
);
save(s);
return channel;
}
export async function submitMessage(content: string): Promise<MutationReceipt> {
if (tauri()) return invoke("submit_user_message", { content });
const s = load(); const e = event("USER_MESSAGE", "用户语言", content, "RECORDED"); s.timeline.unshift(e, event("SYSTEM_RECEIPT", "系统回执", "用户语言已分流保存;等待人格体接入,不伪造人格回应。", "WAITING")); save(s);
return { receiptId: crypto.randomUUID(), state: "WAITING_PERSONA", message: "用户语言已记录", gitCommit: null, event: e };
const s = load();
const e = event("USER_MESSAGE", "用户语言", content, "RECORDED");
s.timeline.unshift(
e,
event(
"SYSTEM_RECEIPT",
"系统回执",
"用户语言已分流保存;等待人格体接入,不伪造人格回应。",
"WAITING",
),
);
save(s);
return {
receiptId: crypto.randomUUID(),
state: "WAITING_PERSONA",
message: "用户语言已记录",
gitCommit: null,
event: e,
};
}
export async function readDocument(path: string): Promise<KnowledgeDocument> {
if (tauri()) return invoke("read_knowledge_document", { path });
const doc = load().documents.find(d => d.path === path); if (!doc) throw new Error("文档不存在"); return doc;
const doc = load().documents.find((d) => d.path === path);
if (!doc) throw new Error("文档不存在");
return doc;
}
export async function createDocument(title: string): Promise<MutationReceipt> {
if (tauri()) return invoke("create_knowledge_document", { title });
const s = load(); const path = `${title.trim().replace(/[\\/:*?\"<>|]/g,"-")}.md`; const d = { path, title: title.trim(), body: `# ${title.trim()}\n\n`, updatedAt: now(), sha256: "browser-preview" }; s.documents.unshift(d); const e=event("AGENT_ACTION","Agent 执行",`已创建 ${path}`); s.timeline.unshift(e); save(s); return {receiptId:crypto.randomUUID(),state:"SUCCEEDED",message:"文档已创建",gitCommit:"browser-preview",event:e};
const s = load();
const path = `${title.trim().replace(/[\\/:*?\"<>|]/g, "-")}.md`;
const d = {
path,
title: title.trim(),
body: `# ${title.trim()}\n\n`,
updatedAt: now(),
sha256: "browser-preview",
};
s.documents.unshift(d);
const e = event("AGENT_ACTION", "Agent 执行", `已创建 ${path}`);
s.timeline.unshift(e);
save(s);
return {
receiptId: crypto.randomUUID(),
state: "SUCCEEDED",
message: "文档已创建",
gitCommit: "browser-preview",
event: e,
};
}
export async function saveDocument(path: string, body: string): Promise<MutationReceipt> {
export async function saveDocument(
path: string,
body: string,
): Promise<MutationReceipt> {
if (tauri()) return invoke("save_knowledge_document", { path, body });
const s=load(); const d=s.documents.find(x=>x.path===path); if(!d) throw new Error("文档不存在"); d.body=body; d.updatedAt=now(); const e=event("SYSTEM_RECEIPT","系统回执",`${path} 已保存到私人 Git 预览层。`); s.timeline.unshift(e); save(s); return {receiptId:crypto.randomUUID(),state:"SUCCEEDED",message:"保存完成",gitCommit:"browser-preview",event:e};
const s = load();
const d = s.documents.find((x) => x.path === path);
if (!d) throw new Error("文档不存在");
d.body = body;
d.updatedAt = now();
const e = event(
"SYSTEM_RECEIPT",
"系统回执",
`${path} 已保存到私人 Git 预览层。`,
);
s.timeline.unshift(e);
save(s);
return {
receiptId: crypto.randomUUID(),
state: "SUCCEEDED",
message: "保存完成",
gitCommit: "browser-preview",
event: e,
};
}
export async function deleteDocument(path: string): Promise<MutationReceipt> {
if (tauri()) return invoke("delete_knowledge_document", { path });
const s=load(); s.documents=s.documents.filter(x=>x.path!==path); const e=event("SYSTEM_RECEIPT","系统回执",`${path} 已移入回收站。`); s.timeline.unshift(e); save(s); return {receiptId:crypto.randomUUID(),state:"SUCCEEDED",message:"已移入回收站",gitCommit:"browser-preview",event:e};
const s = load();
s.documents = s.documents.filter((x) => x.path !== path);
const e = event("SYSTEM_RECEIPT", "系统回执", `${path} 已移入回收站。`);
s.timeline.unshift(e);
save(s);
return {
receiptId: crypto.randomUUID(),
state: "SUCCEEDED",
message: "已移入回收站",
gitCommit: "browser-preview",
event: e,
};
}
export async function importFolder(): Promise<MutationReceipt | null> { if (!tauri()) throw new Error("文件夹导入请在已安装的 HoloLake 中使用"); return invoke("import_knowledge_folder"); }
export async function exportDocument(path: string): Promise<string | null> { if (!tauri()) throw new Error("下载请在已安装的 HoloLake 中使用"); return invoke("export_knowledge_document", { path }); }
export async function registerBridge(displayName: string): Promise<ExternalAiBridge> {
export async function importFolder(): Promise<MutationReceipt | null> {
if (!tauri()) throw new Error("文件夹导入请在已安装的 HoloLake 中使用");
return invoke("import_knowledge_folder");
}
export async function exportDocument(path: string): Promise<string | null> {
if (!tauri()) throw new Error("下载请在已安装的 HoloLake 中使用");
return invoke("export_knowledge_document", { path });
}
export async function registerBridge(
displayName: string,
): Promise<ExternalAiBridge> {
if (tauri()) return invoke("register_external_ai_bridge", { displayName });
const s=load(); const b={bridgeId:`HL-AI-${crypto.randomUUID().slice(0,8).toUpperCase()}`,displayName,inboxPath:"浏览器预览",outboxPath:"浏览器预览",state:"LOCAL_EXPRESSION_ONLY"}; s.bridges.push(b); save(s); return b;
const s = load();
const b = {
bridgeId: `HL-AI-${crypto.randomUUID().slice(0, 8).toUpperCase()}`,
displayName,
inboxPath: "浏览器预览",
outboxPath: "浏览器预览",
state: "LOCAL_EXPRESSION_ONLY",
};
s.bridges.push(b);
save(s);
return b;
}
export async function runtimeOverview(): Promise<RuntimeOverview> {
if (tauri()) return invoke("get_runtime_overview");
const preview = loadRuntime();
return {
realtime: {
schema: "preview",
state: "LISTENING",
protocol: "GLP_LOCAL_REALTIME/1",
endpoint: "tcp://127.0.0.1:39281",
connectedClients: 0,
loopbackOnly: true,
transportGrantsAuthority: false,
},
persona: {
schema: "preview",
state: preview.personas.length
? "TRIAL_PERSONA_READY"
: "READY_NO_PERSONA",
activePersonaId: preview.personas[0]?.personaId ?? null,
personas: preview.personas,
verifiedExistingPersonaCount: 0,
},
proposals: [],
};
}
export async function realtimeInvitation(): Promise<RealtimeInvitation> {
if (tauri()) return invoke("get_realtime_invitation");
return {
schema: "preview",
protocol: "GLP_LOCAL_REALTIME/1",
endpoint: "tcp://127.0.0.1:39281",
token: "browser-preview",
descriptorPath: "browser-preview",
connectorCommand: "仅已安装 App 可生成接入命令",
warning: "令牌不证明人格身份。",
};
}
export async function registerPersona(
displayName: string,
): Promise<PublicPersona> {
if (tauri()) return invoke("register_public_persona", { displayName });
const persona = {
personaId: `HL-PERSONA-${crypto.randomUUID().slice(0, 10).toUpperCase()}`,
displayName,
state: "LOCAL_TRIAL_UNVERIFIED_EXTERNAL_HOST_REQUIRED",
createdAt: now(),
reversibleUntilUnixMs: Date.now() + 30 * 86400000,
};
const preview = loadRuntime();
preview.personas.push(persona);
saveRuntime(preview);
return persona;
}
export async function deleteTrialPersona(
personaId: string,
): Promise<RuntimeOverview["persona"]> {
if (tauri())
return invoke("delete_trial_persona", {
personaId,
exactConfirmation: `删除试用人格 ${personaId}`,
});
const preview = loadRuntime();
preview.personas = preview.personas.filter(
(persona) => persona.personaId !== personaId,
);
saveRuntime(preview);
return {
schema: "preview",
state: preview.personas.length ? "TRIAL_PERSONA_READY" : "READY_NO_PERSONA",
activePersonaId: preview.personas[0]?.personaId ?? null,
personas: preview.personas,
verifiedExistingPersonaCount: 0,
};
}
export async function approveProposal(
proposalId: string,
): Promise<AgentReceipt> {
return invoke("approve_agent_proposal", { proposalId });
}
export async function rejectProposal(
proposalId: string,
): Promise<AgentProposal> {
return invoke("reject_agent_proposal", { proposalId });
}

View file

@ -353,6 +353,9 @@ main {
.conversation article.persona span {
color: #c5a6ff;
}
.conversation article.external-ai span {
color: #72d6ca;
}
.conversation article p {
margin: 0;
line-height: 1.7;
@ -365,6 +368,47 @@ main {
border-radius: 18px;
background: rgba(4, 16, 30, 0.74);
}
.approval-queue {
margin-top: 18px;
padding: 18px 20px;
border: 1px solid rgba(240, 197, 107, 0.38);
border-radius: 18px;
background: rgba(40, 29, 12, 0.36);
}
.approval-queue > h2 {
margin: 0 0 12px;
color: #f4d895;
font-size: 13px;
}
.approval-queue article {
display: flex;
align-items: center;
justify-content: space-between;
gap: 18px;
padding: 12px 13px;
border-radius: 11px;
background: rgba(6, 18, 32, 0.7);
}
.approval-queue article + article {
margin-top: 8px;
}
.approval-queue strong {
font-size: 13px;
}
.approval-queue p {
margin: 5px 0;
color: #b7c8d8;
font-size: 11px;
}
.approval-queue small {
color: #6f879e;
font-size: 9px;
}
.approval-queue article > div:last-child {
display: flex;
flex: none;
gap: 7px;
}
.receipt-stream h2 {
font-size: 13px;
font-weight: 570;

View file

@ -1,9 +1,128 @@
export type SourceKind = "USER_MESSAGE" | "PERSONA_RESPONSE" | "SYSTEM_CONTEXT" | "PROTOCOL_EVENT" | "AGENT_ACTION" | "TOOL_RESULT" | "SYSTEM_RECEIPT";
export type SourceKind =
| "USER_MESSAGE"
| "PERSONA_RESPONSE"
| "EXTERNAL_AI_MESSAGE"
| "SYSTEM_CONTEXT"
| "PROTOCOL_EVENT"
| "AGENT_ACTION"
| "TOOL_RESULT"
| "SYSTEM_RECEIPT";
export interface Channel { channelId: string; name: string; createdAt: string; privateGitPath: string; publicDistributionState: string; }
export interface TimelineEvent { eventId: string; sourceKind: SourceKind; title: string; content: string; occurredAt: string; state: "RECORDED" | "WAITING" | "RUNNING" | "SUCCEEDED" | "FAILED"; }
export interface KnowledgeDocument { path: string; title: string; body: string; updatedAt: string; sha256: string; }
export interface KnowledgeSummary { path: string; title: string; updatedAt: string; sha256: string; }
export interface SystemSnapshot { appVersion: string; channel: Channel | null; timeline: TimelineEvent[]; documents: KnowledgeSummary[]; moduleRegistryVersion: string; publicUpdateEndpoint: string; publicDistributionState: string; externalAiBridges: ExternalAiBridge[]; }
export interface ExternalAiBridge { bridgeId: string; displayName: string; inboxPath: string; outboxPath: string; state: string; }
export interface MutationReceipt { receiptId: string; state: string; message: string; gitCommit: string | null; event: TimelineEvent; }
export interface Channel {
channelId: string;
name: string;
createdAt: string;
privateGitPath: string;
publicDistributionState: string;
}
export interface TimelineEvent {
eventId: string;
sourceKind: SourceKind;
title: string;
content: string;
occurredAt: string;
state: "RECORDED" | "WAITING" | "RUNNING" | "SUCCEEDED" | "FAILED";
}
export interface KnowledgeDocument {
path: string;
title: string;
body: string;
updatedAt: string;
sha256: string;
}
export interface KnowledgeSummary {
path: string;
title: string;
updatedAt: string;
sha256: string;
}
export interface SystemSnapshot {
appVersion: string;
channel: Channel | null;
timeline: TimelineEvent[];
documents: KnowledgeSummary[];
moduleRegistryVersion: string;
publicUpdateEndpoint: string;
publicDistributionState: string;
externalAiBridges: ExternalAiBridge[];
}
export interface ExternalAiBridge {
bridgeId: string;
displayName: string;
inboxPath: string;
outboxPath: string;
state: string;
}
export interface MutationReceipt {
receiptId: string;
state: string;
message: string;
gitCommit: string | null;
event: TimelineEvent;
}
export interface PublicPersona {
personaId: string;
displayName: string;
state: string;
createdAt: string;
reversibleUntilUnixMs: number;
}
export interface GirAction {
actionId: string;
operation: string;
input: Record<string, unknown>;
}
export interface GirProgram {
schema: string;
programId: string;
channelId: string;
sourceSha256: string;
compilerId: string;
unresolvedNaturalLanguage: boolean;
actions: GirAction[];
}
export interface AgentProposal {
proposalId: string;
clientId: string;
personaId: string | null;
state: string;
createdAt: string;
gir: GirProgram;
}
export interface AgentReceipt {
schema: string;
receiptId: string;
proposalId: string;
state: string;
actionResults: unknown[];
gitCommit: string | null;
targetReadbackSha256: string;
}
export interface RuntimeOverview {
realtime: {
schema: string;
state: string;
protocol: string;
endpoint: string;
connectedClients: number;
loopbackOnly: boolean;
transportGrantsAuthority: boolean;
};
persona: {
schema: string;
state: string;
activePersonaId: string | null;
personas: PublicPersona[];
verifiedExistingPersonaCount: number;
};
proposals: AgentProposal[];
}
export interface RealtimeInvitation {
schema: string;
protocol: string;
endpoint: string;
token: string;
descriptorPath: string;
connectorCommand: string;
warning: string;
}