Files
Mohamed Boudraandmillerben95 8332f4d1ff feat(muse): add Muse Code as a built-in provider plugin (#5719)
* test(muse): record real host protocol fixtures

* feat(muse): add built-in MSP provider

Co-authored-by: millerben95 <millerben95@users.noreply.github.com>

* fix(muse): expose provider settings in the app

The daemon registered host settings without a client contribution, leaving sandbox, network, and workspace trust unreachable. Share the definition with a settings screen and expose it through Command Center even when installed plugins are disabled.

* docs(muse): publish setup and provider support

* test: correct runtime fixtures and restart readiness

* fix(muse): hide reminder housekeeping from the timeline

* fix(muse): use the official Muse Code mark

Source: https://muse.ai/images/favicon/app-squiggle.svg

Preserve the official silhouette path verbatim. Remove its gradient definition and use currentColor, scaling the original 100-unit coordinates into the 24-unit viewBox. Black silhouette renders match pixel-for-pixel.

* fix(muse): adapt to current plugin APIs

Use the screen API introduced with sidebar plugin items. Usage reports now come from source account discovery; remove the retired agent usage-reference request and headline marker, and exercise discovery through the existing provider fixtures.

---------

Co-authored-by: millerben95 <millerben95@users.noreply.github.com>
2026-09-30 14:56:41 +01:00

95 lines
2.9 KiB
TypeScript

import type { ProviderEvent } from "@getpaseo/plugin/server/provider";
import { MspConnection, type Notification } from "./connection.js";
import { Timeline } from "./timeline.js";
import {
childSessionSchema,
pageSchema,
itemNotificationSchema,
notificationSchema,
deltaSchema,
type WireItem,
} from "./wire.js";
interface Child {
timeline: Timeline;
cursor: string | undefined;
}
export class Children {
private readonly children = new Map<string, Child>();
constructor(
private readonly host: MspConnection,
private readonly parentId: string,
private readonly cwd: string,
private readonly emit: (event: ProviderEvent) => void,
) {}
async update(item: WireItem): Promise<void> {
if (
!item.childSessionId ||
!(
item.kind === "subagent" ||
item.kind === "workflow" ||
item.tool === "workflow" ||
item.tool.startsWith("subagent_")
)
)
return;
const id = item.childSessionId;
if (!this.children.has(id)) {
const { session } = await this.host.request(
"session/read",
{ sessionId: id },
childSessionSchema,
);
this.emit({
type: "session.opened",
sessionId: id,
parentSessionId: this.parentId,
toolCallId: item.callId ?? item.itemId,
capabilities: [],
restoration: "parent",
cwd: session.workspaceRoot ?? this.cwd,
title: session.title,
});
this.children.set(id, {
timeline: new Timeline(this.host, id, id, this.emit),
cursor: undefined,
});
}
await this.page(id);
}
async notify(id: string, event: Notification): Promise<void> {
const child = this.children.get(id);
if (!child) return;
if (["item/started", "item/updated", "item/completed"].includes(event.method)) {
const { item } = itemNotificationSchema.parse(event.params);
await child.timeline.fold(item);
} else if (event.method === "item/delta") child.timeline.delta(deltaSchema.parse(event.params));
}
async refresh(): Promise<void> {
for (const id of this.children.keys()) await this.page(id);
}
private async page(id: string): Promise<void> {
const child = this.children.get(id)!;
let next: string | null;
do {
const page = await this.host.request(
"view/page",
{ sessionId: id, cursor: child.cursor, limit: 1000, direction: "forward" },
pageSchema,
);
for (const event of page.events) {
await this.notify(id, event);
const envelope = notificationSchema.parse(event.params);
if (envelope.viewCursor) child.cursor = envelope.viewCursor;
}
next = page.nextCursor;
if (next !== null) child.cursor = next;
} while (next !== null);
}
close(): void {
for (const sessionId of this.children.keys()) this.emit({ type: "session.closed", sessionId });
this.children.clear();
}
}