mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-23 17:52:49 +00:00
* 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>
87 lines
3.1 KiB
TypeScript
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;
|
|
}
|
|
}
|