Files
paseo/plugins/muse-provider/server/questions.ts
T
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

87 lines
3.1 KiB
TypeScript

import type { ProviderEvent, ProviderPermissionResponse } from "@getpaseo/plugin/server/provider";
import type { z } from "zod";
import { MspConnection } from "./connection.js";
import { MuseError } from "./errors.js";
import { ackSchema, questionSchema, questionAnswersSchema, settledQuestionSchema } from "./wire.js";
export class Questions {
private readonly pending = new Map<string, z.infer<typeof questionSchema>>();
private readonly settled = new Set<string>();
constructor(
private readonly host: MspConnection,
private readonly sessionId: string,
private readonly id: string,
private readonly emit: (event: ProviderEvent) => void,
) {}
requested(input: unknown): void {
const request = questionSchema.parse(input);
if (this.settled.has(request.userInputId)) return;
this.pending.set(request.userInputId, request);
this.emit({
type: "session.permission",
sessionId: this.id,
request: {
id: request.userInputId,
name: request.toolName,
kind: "question",
input: {
questions: request.questions.map((question) => ({
id: question.id,
header: question.header,
question: question.question,
options: question.options,
multiSelect: question.selection.mode === "multiple",
allowOther: true,
allowEmpty: question.selection.minSelections === 0,
})),
},
},
});
}
resolved(input: unknown): void {
const { userInputId } = settledQuestionSchema.parse(input);
this.settled.add(userInputId);
this.pending.delete(userInputId);
this.emit({
type: "session.permission_resolved",
sessionId: this.id,
permissionId: userInputId,
});
}
async answer(id: string, response: ProviderPermissionResponse): Promise<boolean> {
const request = this.pending.get(id);
if (!request) return false;
if (response.behavior === "deny") {
await this.host.command(
"userInput/cancel",
{ sessionId: this.sessionId, userInputId: id, reason: response.message },
ackSchema,
);
return true;
}
const supplied = questionAnswersSchema.parse(response.updatedInput);
const answers = request.questions.map((question) => {
const text = supplied.answers[question.header];
if (text === undefined) throw new MuseError("missingAnswer", `Answer ${question.header}`);
if (question.selection.mode === "single")
return question.options.some((option) => option.label === text)
? { questionId: question.id, selectedLabel: text }
: { questionId: question.id, freeText: text };
const labels = text
.split(",")
.map((label) => label.trim())
.filter(Boolean);
if (labels.every((label) => question.options.some((option) => option.label === label)))
return { questionId: question.id, selectedLabels: labels };
return { questionId: question.id, freeText: text };
});
await this.host.command(
"userInput/answer",
{ sessionId: this.sessionId, userInputId: id, answers },
ackSchema,
);
return true;
}
}