feat(iteration): implement sdk v1 miniapp runtime loop

This commit is contained in:
2026-08-05 18:30:29 +08:00
parent 486280bec0
commit d2a2fd0aff
37 changed files with 2135 additions and 60 deletions
@@ -30,6 +30,7 @@ export type MountedChatApp = {
messages: HTMLElement;
loginView: HTMLElement;
chatView: HTMLElement;
appSessions: HTMLElement;
presence: HTMLElement;
rendererContext: TrustedDOMRendererContext;
rendererRegistry: RendererRegistry<TrustedDOMRendererContext>;
@@ -51,6 +52,7 @@ export function mountChatApp(
const messages = query<HTMLElement>(root, "#messages");
const loginView = query<HTMLElement>(root, "#login-view");
const chatView = query<HTMLElement>(root, "#chat-view");
const appSessions = query<HTMLElement>(root, "#app-sessions");
const presence = query<HTMLElement>(root, "#presence");
const rendererContext = new TrustedDOMRendererContext(
messages,
@@ -68,6 +70,7 @@ export function mountChatApp(
messages,
loginView,
chatView,
appSessions,
presence,
rendererContext,
rendererRegistry,
+1
View File
@@ -19,6 +19,7 @@ export function mountChatShell(root: HTMLElement): void {
</section>
<section id="chat-view" class="chat-view" hidden>
<header class="chat-header"><div class="agent"><span class="avatar">✦</span><div><strong>AI Agent</strong><small id="presence">正在连接…</small></div></div><div><button id="surface-demo" class="quiet" type="button">启用任务面板</button><button id="logout" class="quiet">退出</button></div></header>
<section id="app-sessions" class="app-sessions" aria-label="应用子会话"></section>
<section id="messages" class="messages" aria-live="polite"></section>
<form id="message-form" class="composer"><textarea id="message-input" rows="1" placeholder="问问你的 AI 搭档…"></textarea><button id="send" type="submit">发送</button></form>
</section>
File diff suppressed because one or more lines are too long
@@ -12,6 +12,7 @@ import type {
ConversationItem,
DeliveryState,
InputForm,
NoticeDefinition,
JsonObject,
ToolCallRequest,
} from "@/runtime/protocol/lineup-v1";
@@ -168,6 +169,10 @@ export class TrustedDOMRendererContext {
public renderToolCall(item: Extract<ConversationItem, { kind: "tool-call" }>): void {
const record = this.toolInteractions?.read(item.call.call_id);
if (item.call.tool === "notice" && item.call.notice) {
this.renderNoticeCard(item.call, item.call.notice);
return;
}
if (item.call.tool === "choice" && item.call.action_group) {
this.renderActionGroup(item.call, item.call.action_group, record);
return;
@@ -183,6 +188,30 @@ export class TrustedDOMRendererContext {
this.renderSystemMessage("收到无法安全展示的标准交互请求。");
}
private renderNoticeCard(call: ToolCallRequest, notice: NoticeDefinition): void {
const card = document.createElement("article");
card.className = "tool-card notice-card";
card.dataset.callId = call.call_id;
const heading = document.createElement("strong");
heading.textContent = call.title || "提示";
const detail = document.createElement("p");
detail.textContent = notice.message;
const status = document.createElement("small");
status.className = "tool-card-status";
status.textContent = notice.dismiss_label ?? "已收到";
card.append(heading, detail, status);
this.toolCards.set(call.call_id, { card, status, controls: [] });
this.messages.append(card);
this.scrollLatest();
// Toast is only an additional short reminder; the durable IM card above remains.
const toast = document.createElement("div");
toast.className = "notice-toast";
toast.textContent = notice.message;
toast.setAttribute("role", "status");
this.messages.parentElement?.append(toast);
globalThis.setTimeout(() => toast.remove(), 4_000);
}
public renderToolResult(item: Extract<ConversationItem, { kind: "tool-result" }>): void {
const entry = this.toolCards.get(item.result.call_id);
if (!entry) return;