feat: unify onboarding + config schema
This commit is contained in:
@@ -146,11 +146,8 @@ export class GatewayClient {
|
||||
const pending = this.pending.get(parsed.id);
|
||||
if (!pending) return;
|
||||
// If the payload is an ack with status accepted, keep waiting for final.
|
||||
const payload = parsed.payload;
|
||||
const status =
|
||||
payload && typeof payload === "object" && "status" in payload
|
||||
? (payload as { status?: unknown }).status
|
||||
: undefined;
|
||||
const payload = parsed.payload as { status?: unknown } | undefined;
|
||||
const status = payload?.status;
|
||||
if (pending.expectFinal && status === "accepted") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -11,6 +11,10 @@ import {
|
||||
ChatSendParamsSchema,
|
||||
type ConfigGetParams,
|
||||
ConfigGetParamsSchema,
|
||||
type ConfigSchemaParams,
|
||||
ConfigSchemaParamsSchema,
|
||||
type ConfigSchemaResponse,
|
||||
ConfigSchemaResponseSchema,
|
||||
type ConfigSetParams,
|
||||
ConfigSetParamsSchema,
|
||||
type ConnectParams,
|
||||
@@ -105,6 +109,22 @@ import {
|
||||
WebLoginStartParamsSchema,
|
||||
type WebLoginWaitParams,
|
||||
WebLoginWaitParamsSchema,
|
||||
type WizardCancelParams,
|
||||
WizardCancelParamsSchema,
|
||||
type WizardNextParams,
|
||||
WizardNextParamsSchema,
|
||||
type WizardNextResult,
|
||||
WizardNextResultSchema,
|
||||
type WizardStartParams,
|
||||
WizardStartParamsSchema,
|
||||
type WizardStartResult,
|
||||
WizardStartResultSchema,
|
||||
type WizardStatusParams,
|
||||
WizardStatusParamsSchema,
|
||||
type WizardStatusResult,
|
||||
WizardStatusResultSchema,
|
||||
type WizardStep,
|
||||
WizardStepSchema,
|
||||
} from "./schema.js";
|
||||
|
||||
const ajv = new (
|
||||
@@ -174,6 +194,21 @@ export const validateConfigGetParams = ajv.compile<ConfigGetParams>(
|
||||
export const validateConfigSetParams = ajv.compile<ConfigSetParams>(
|
||||
ConfigSetParamsSchema,
|
||||
);
|
||||
export const validateConfigSchemaParams = ajv.compile<ConfigSchemaParams>(
|
||||
ConfigSchemaParamsSchema,
|
||||
);
|
||||
export const validateWizardStartParams = ajv.compile<WizardStartParams>(
|
||||
WizardStartParamsSchema,
|
||||
);
|
||||
export const validateWizardNextParams = ajv.compile<WizardNextParams>(
|
||||
WizardNextParamsSchema,
|
||||
);
|
||||
export const validateWizardCancelParams = ajv.compile<WizardCancelParams>(
|
||||
WizardCancelParamsSchema,
|
||||
);
|
||||
export const validateWizardStatusParams = ajv.compile<WizardStatusParams>(
|
||||
WizardStatusParamsSchema,
|
||||
);
|
||||
export const validateTalkModeParams =
|
||||
ajv.compile<TalkModeParams>(TalkModeParamsSchema);
|
||||
export const validateProvidersStatusParams = ajv.compile<ProvidersStatusParams>(
|
||||
@@ -258,6 +293,16 @@ export {
|
||||
SessionsCompactParamsSchema,
|
||||
ConfigGetParamsSchema,
|
||||
ConfigSetParamsSchema,
|
||||
ConfigSchemaParamsSchema,
|
||||
ConfigSchemaResponseSchema,
|
||||
WizardStartParamsSchema,
|
||||
WizardNextParamsSchema,
|
||||
WizardCancelParamsSchema,
|
||||
WizardStatusParamsSchema,
|
||||
WizardStepSchema,
|
||||
WizardNextResultSchema,
|
||||
WizardStartResultSchema,
|
||||
WizardStatusResultSchema,
|
||||
ProvidersStatusParamsSchema,
|
||||
WebLoginStartParamsSchema,
|
||||
WebLoginWaitParamsSchema,
|
||||
@@ -304,6 +349,16 @@ export type {
|
||||
NodePairApproveParams,
|
||||
ConfigGetParams,
|
||||
ConfigSetParams,
|
||||
ConfigSchemaParams,
|
||||
ConfigSchemaResponse,
|
||||
WizardStartParams,
|
||||
WizardNextParams,
|
||||
WizardCancelParams,
|
||||
WizardStatusParams,
|
||||
WizardStep,
|
||||
WizardNextResult,
|
||||
WizardStartResult,
|
||||
WizardStatusResult,
|
||||
TalkModeParams,
|
||||
ProvidersStatusParams,
|
||||
WebLoginStartParams,
|
||||
|
||||
@@ -342,6 +342,157 @@ export const ConfigSetParamsSchema = Type.Object(
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const ConfigSchemaParamsSchema = Type.Object(
|
||||
{},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const ConfigUiHintSchema = Type.Object(
|
||||
{
|
||||
label: Type.Optional(Type.String()),
|
||||
help: Type.Optional(Type.String()),
|
||||
group: Type.Optional(Type.String()),
|
||||
order: Type.Optional(Type.Integer()),
|
||||
advanced: Type.Optional(Type.Boolean()),
|
||||
sensitive: Type.Optional(Type.Boolean()),
|
||||
placeholder: Type.Optional(Type.String()),
|
||||
itemTemplate: Type.Optional(Type.Unknown()),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const ConfigSchemaResponseSchema = Type.Object(
|
||||
{
|
||||
schema: Type.Unknown(),
|
||||
uiHints: Type.Record(Type.String(), ConfigUiHintSchema),
|
||||
version: NonEmptyString,
|
||||
generatedAt: NonEmptyString,
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const WizardStartParamsSchema = Type.Object(
|
||||
{
|
||||
mode: Type.Optional(
|
||||
Type.Union([Type.Literal("local"), Type.Literal("remote")]),
|
||||
),
|
||||
workspace: Type.Optional(Type.String()),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const WizardAnswerSchema = Type.Object(
|
||||
{
|
||||
stepId: NonEmptyString,
|
||||
value: Type.Optional(Type.Unknown()),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const WizardNextParamsSchema = Type.Object(
|
||||
{
|
||||
sessionId: NonEmptyString,
|
||||
answer: Type.Optional(WizardAnswerSchema),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const WizardCancelParamsSchema = Type.Object(
|
||||
{
|
||||
sessionId: NonEmptyString,
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const WizardStatusParamsSchema = Type.Object(
|
||||
{
|
||||
sessionId: NonEmptyString,
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const WizardStepOptionSchema = Type.Object(
|
||||
{
|
||||
value: Type.Unknown(),
|
||||
label: NonEmptyString,
|
||||
hint: Type.Optional(Type.String()),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const WizardStepSchema = Type.Object(
|
||||
{
|
||||
id: NonEmptyString,
|
||||
type: Type.Union([
|
||||
Type.Literal("note"),
|
||||
Type.Literal("select"),
|
||||
Type.Literal("text"),
|
||||
Type.Literal("confirm"),
|
||||
Type.Literal("multiselect"),
|
||||
Type.Literal("progress"),
|
||||
Type.Literal("action"),
|
||||
]),
|
||||
title: Type.Optional(Type.String()),
|
||||
message: Type.Optional(Type.String()),
|
||||
options: Type.Optional(Type.Array(WizardStepOptionSchema)),
|
||||
initialValue: Type.Optional(Type.Unknown()),
|
||||
placeholder: Type.Optional(Type.String()),
|
||||
sensitive: Type.Optional(Type.Boolean()),
|
||||
executor: Type.Optional(
|
||||
Type.Union([Type.Literal("gateway"), Type.Literal("client")]),
|
||||
),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const WizardNextResultSchema = Type.Object(
|
||||
{
|
||||
done: Type.Boolean(),
|
||||
step: Type.Optional(WizardStepSchema),
|
||||
status: Type.Optional(
|
||||
Type.Union([
|
||||
Type.Literal("running"),
|
||||
Type.Literal("done"),
|
||||
Type.Literal("cancelled"),
|
||||
Type.Literal("error"),
|
||||
]),
|
||||
),
|
||||
error: Type.Optional(Type.String()),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const WizardStartResultSchema = Type.Object(
|
||||
{
|
||||
sessionId: NonEmptyString,
|
||||
done: Type.Boolean(),
|
||||
step: Type.Optional(WizardStepSchema),
|
||||
status: Type.Optional(
|
||||
Type.Union([
|
||||
Type.Literal("running"),
|
||||
Type.Literal("done"),
|
||||
Type.Literal("cancelled"),
|
||||
Type.Literal("error"),
|
||||
]),
|
||||
),
|
||||
error: Type.Optional(Type.String()),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const WizardStatusResultSchema = Type.Object(
|
||||
{
|
||||
status: Type.Union([
|
||||
Type.Literal("running"),
|
||||
Type.Literal("done"),
|
||||
Type.Literal("cancelled"),
|
||||
Type.Literal("error"),
|
||||
]),
|
||||
error: Type.Optional(Type.String()),
|
||||
},
|
||||
{ additionalProperties: false },
|
||||
);
|
||||
|
||||
export const TalkModeParamsSchema = Type.Object(
|
||||
{
|
||||
enabled: Type.Boolean(),
|
||||
@@ -680,6 +831,16 @@ export const ProtocolSchemas: Record<string, TSchema> = {
|
||||
SessionsCompactParams: SessionsCompactParamsSchema,
|
||||
ConfigGetParams: ConfigGetParamsSchema,
|
||||
ConfigSetParams: ConfigSetParamsSchema,
|
||||
ConfigSchemaParams: ConfigSchemaParamsSchema,
|
||||
ConfigSchemaResponse: ConfigSchemaResponseSchema,
|
||||
WizardStartParams: WizardStartParamsSchema,
|
||||
WizardNextParams: WizardNextParamsSchema,
|
||||
WizardCancelParams: WizardCancelParamsSchema,
|
||||
WizardStatusParams: WizardStatusParamsSchema,
|
||||
WizardStep: WizardStepSchema,
|
||||
WizardNextResult: WizardNextResultSchema,
|
||||
WizardStartResult: WizardStartResultSchema,
|
||||
WizardStatusResult: WizardStatusResultSchema,
|
||||
TalkModeParams: TalkModeParamsSchema,
|
||||
ProvidersStatusParams: ProvidersStatusParamsSchema,
|
||||
WebLoginStartParams: WebLoginStartParamsSchema,
|
||||
@@ -737,6 +898,16 @@ export type SessionsDeleteParams = Static<typeof SessionsDeleteParamsSchema>;
|
||||
export type SessionsCompactParams = Static<typeof SessionsCompactParamsSchema>;
|
||||
export type ConfigGetParams = Static<typeof ConfigGetParamsSchema>;
|
||||
export type ConfigSetParams = Static<typeof ConfigSetParamsSchema>;
|
||||
export type ConfigSchemaParams = Static<typeof ConfigSchemaParamsSchema>;
|
||||
export type ConfigSchemaResponse = Static<typeof ConfigSchemaResponseSchema>;
|
||||
export type WizardStartParams = Static<typeof WizardStartParamsSchema>;
|
||||
export type WizardNextParams = Static<typeof WizardNextParamsSchema>;
|
||||
export type WizardCancelParams = Static<typeof WizardCancelParamsSchema>;
|
||||
export type WizardStatusParams = Static<typeof WizardStatusParamsSchema>;
|
||||
export type WizardStep = Static<typeof WizardStepSchema>;
|
||||
export type WizardNextResult = Static<typeof WizardNextResultSchema>;
|
||||
export type WizardStartResult = Static<typeof WizardStartResultSchema>;
|
||||
export type WizardStatusResult = Static<typeof WizardStatusResultSchema>;
|
||||
export type TalkModeParams = Static<typeof TalkModeParamsSchema>;
|
||||
export type ProvidersStatusParams = Static<typeof ProvidersStatusParamsSchema>;
|
||||
export type WebLoginStartParams = Static<typeof WebLoginStartParamsSchema>;
|
||||
|
||||
@@ -152,7 +152,10 @@ vi.mock("../config/sessions.js", async () => {
|
||||
}),
|
||||
};
|
||||
});
|
||||
vi.mock("../config/config.js", () => {
|
||||
vi.mock("../config/config.js", async () => {
|
||||
const actual = await vi.importActual<typeof import("../config/config.js")>(
|
||||
"../config/config.js",
|
||||
);
|
||||
const resolveConfigPath = () =>
|
||||
path.join(os.homedir(), ".clawdis", "clawdis.json");
|
||||
|
||||
@@ -222,6 +225,7 @@ vi.mock("../config/config.js", () => {
|
||||
});
|
||||
|
||||
return {
|
||||
...actual,
|
||||
CONFIG_PATH_CLAWDIS: resolveConfigPath(),
|
||||
STATE_DIR_CLAWDIS: path.dirname(resolveConfigPath()),
|
||||
get isNixMode() {
|
||||
@@ -381,7 +385,10 @@ function onceMessage<T = unknown>(
|
||||
});
|
||||
}
|
||||
|
||||
async function startServerWithClient(token?: string) {
|
||||
async function startServerWithClient(
|
||||
token?: string,
|
||||
opts?: Parameters<typeof startGatewayServer>[1],
|
||||
) {
|
||||
const port = await getFreePort();
|
||||
const prev = process.env.CLAWDIS_GATEWAY_TOKEN;
|
||||
if (token === undefined) {
|
||||
@@ -389,7 +396,7 @@ async function startServerWithClient(token?: string) {
|
||||
} else {
|
||||
process.env.CLAWDIS_GATEWAY_TOKEN = token;
|
||||
}
|
||||
const server = await startGatewayServer(port);
|
||||
const server = await startGatewayServer(port, opts);
|
||||
const ws = new WebSocket(`ws://127.0.0.1:${port}`);
|
||||
await new Promise<void>((resolve) => ws.once("open", resolve));
|
||||
return { server, ws, port, prevToken: prev };
|
||||
@@ -2299,6 +2306,110 @@ describe("gateway server", () => {
|
||||
},
|
||||
);
|
||||
|
||||
test("config.schema returns schema + hints", async () => {
|
||||
const { server, ws } = await startServerWithClient();
|
||||
await connectOk(ws);
|
||||
|
||||
const res = await rpcReq<{
|
||||
schema?: { properties?: { gateway?: unknown } };
|
||||
uiHints?: { gateway?: { label?: string } };
|
||||
}>(ws, "config.schema", {});
|
||||
expect(res.ok).toBe(true);
|
||||
expect(res.payload?.schema?.properties?.gateway).toBeTruthy();
|
||||
expect(res.payload?.uiHints?.gateway?.label).toBe("Gateway");
|
||||
|
||||
ws.close();
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test("wizard.start and wizard.next drive steps", async () => {
|
||||
const { server, ws } = await startServerWithClient(undefined, {
|
||||
wizardRunner: async (_opts, _runtime, prompter) => {
|
||||
await prompter.note("Welcome");
|
||||
const name = await prompter.text({ message: "Name" });
|
||||
await prompter.note(`Hello ${name}`);
|
||||
},
|
||||
});
|
||||
await connectOk(ws);
|
||||
|
||||
const startRes = await rpcReq<{
|
||||
sessionId?: string;
|
||||
step?: { id?: string; type?: string };
|
||||
}>(ws, "wizard.start", {});
|
||||
expect(startRes.ok).toBe(true);
|
||||
const sessionId = startRes.payload?.sessionId ?? "";
|
||||
const firstStep = startRes.payload?.step;
|
||||
expect(sessionId).not.toBe("");
|
||||
expect(firstStep?.type).toBe("note");
|
||||
|
||||
const runningRes = await rpcReq(ws, "wizard.start", {});
|
||||
expect(runningRes.ok).toBe(false);
|
||||
expect(runningRes.error?.message).toMatch(/wizard already running/i);
|
||||
|
||||
const nextOne = await rpcReq<{
|
||||
step?: { id?: string; type?: string };
|
||||
done?: boolean;
|
||||
}>(ws, "wizard.next", {
|
||||
sessionId,
|
||||
answer: { stepId: firstStep?.id, value: null },
|
||||
});
|
||||
expect(nextOne.ok).toBe(true);
|
||||
const textStep = nextOne.payload?.step;
|
||||
expect(textStep?.type).toBe("text");
|
||||
|
||||
const nextTwo = await rpcReq<{
|
||||
step?: { id?: string; type?: string };
|
||||
done?: boolean;
|
||||
}>(ws, "wizard.next", {
|
||||
sessionId,
|
||||
answer: { stepId: textStep?.id, value: "Peter" },
|
||||
});
|
||||
expect(nextTwo.ok).toBe(true);
|
||||
const finalStep = nextTwo.payload?.step;
|
||||
expect(finalStep?.type).toBe("note");
|
||||
|
||||
const done = await rpcReq<{
|
||||
done?: boolean;
|
||||
status?: string;
|
||||
}>(ws, "wizard.next", {
|
||||
sessionId,
|
||||
answer: { stepId: finalStep?.id, value: null },
|
||||
});
|
||||
expect(done.ok).toBe(true);
|
||||
expect(done.payload?.done).toBe(true);
|
||||
expect(done.payload?.status).toBe("done");
|
||||
|
||||
ws.close();
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test("wizard.cancel ends the session", async () => {
|
||||
const { server, ws } = await startServerWithClient(undefined, {
|
||||
wizardRunner: async (_opts, _runtime, prompter) => {
|
||||
await prompter.note("Welcome");
|
||||
await prompter.text({ message: "Name" });
|
||||
},
|
||||
});
|
||||
await connectOk(ws);
|
||||
|
||||
const startRes = await rpcReq<{
|
||||
sessionId?: string;
|
||||
step?: { id?: string; type?: string };
|
||||
}>(ws, "wizard.start", {});
|
||||
expect(startRes.ok).toBe(true);
|
||||
const sessionId = startRes.payload?.sessionId ?? "";
|
||||
expect(sessionId).not.toBe("");
|
||||
|
||||
const cancelRes = await rpcReq<{ status?: string }>(ws, "wizard.cancel", {
|
||||
sessionId,
|
||||
});
|
||||
expect(cancelRes.ok).toBe(true);
|
||||
expect(cancelRes.payload?.status).toBe("cancelled");
|
||||
|
||||
ws.close();
|
||||
await server.close();
|
||||
});
|
||||
|
||||
test("providers.status returns snapshot without probe", async () => {
|
||||
const prevToken = process.env.TELEGRAM_BOT_TOKEN;
|
||||
delete process.env.TELEGRAM_BOT_TOKEN;
|
||||
|
||||
@@ -62,6 +62,7 @@ import {
|
||||
validateConfigObject,
|
||||
writeConfigFile,
|
||||
} from "../config/config.js";
|
||||
import { buildConfigSchema } from "../config/schema.js";
|
||||
import {
|
||||
buildGroupDisplayName,
|
||||
loadSessionStore,
|
||||
@@ -170,6 +171,8 @@ import type { WebProviderStatus } from "../web/auto-reply.js";
|
||||
import { startWebLoginWithQr, waitForWebLogin } from "../web/login-qr.js";
|
||||
import { sendMessageWhatsApp } from "../web/outbound.js";
|
||||
import { getWebAuthAgeMs, logoutWeb, readWebSelfId } from "../web/session.js";
|
||||
import { runOnboardingWizard } from "../wizard/onboarding.js";
|
||||
import { WizardSession } from "../wizard/session.js";
|
||||
import {
|
||||
assertGatewayAuthConfigured,
|
||||
authorizeGatewayConnect,
|
||||
@@ -392,6 +395,7 @@ import {
|
||||
validateChatHistoryParams,
|
||||
validateChatSendParams,
|
||||
validateConfigGetParams,
|
||||
validateConfigSchemaParams,
|
||||
validateConfigSetParams,
|
||||
validateConnectParams,
|
||||
validateCronAddParams,
|
||||
@@ -426,6 +430,10 @@ import {
|
||||
validateWakeParams,
|
||||
validateWebLoginStartParams,
|
||||
validateWebLoginWaitParams,
|
||||
validateWizardCancelParams,
|
||||
validateWizardNextParams,
|
||||
validateWizardStartParams,
|
||||
validateWizardStatusParams,
|
||||
} from "./protocol/index.js";
|
||||
import { DEFAULT_WS_SLOW_MS, getGatewayWsLogStyle } from "./ws-logging.js";
|
||||
|
||||
@@ -504,6 +512,11 @@ const METHODS = [
|
||||
"status",
|
||||
"config.get",
|
||||
"config.set",
|
||||
"config.schema",
|
||||
"wizard.start",
|
||||
"wizard.next",
|
||||
"wizard.cancel",
|
||||
"wizard.status",
|
||||
"talk.mode",
|
||||
"models.list",
|
||||
"skills.status",
|
||||
@@ -602,6 +615,14 @@ export type GatewayServerOptions = {
|
||||
* Test-only: allow canvas host startup even when NODE_ENV/VITEST would disable it.
|
||||
*/
|
||||
allowCanvasHostInTests?: boolean;
|
||||
/**
|
||||
* Test-only: override the onboarding wizard runner.
|
||||
*/
|
||||
wizardRunner?: (
|
||||
opts: import("../commands/onboard-types.js").OnboardOptions,
|
||||
runtime: import("../runtime.js").RuntimeEnv,
|
||||
prompter: import("../wizard/prompts.js").WizardPrompter,
|
||||
) => Promise<void>;
|
||||
};
|
||||
|
||||
function isLoopbackAddress(ip: string | undefined): boolean {
|
||||
@@ -1432,6 +1453,23 @@ export async function startGatewayServer(
|
||||
);
|
||||
}
|
||||
|
||||
const wizardRunner = opts.wizardRunner ?? runOnboardingWizard;
|
||||
const wizardSessions = new Map<string, WizardSession>();
|
||||
|
||||
const findRunningWizard = (): string | null => {
|
||||
for (const [id, session] of wizardSessions) {
|
||||
if (session.getStatus() === "running") return id;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const purgeWizardSession = (id: string) => {
|
||||
const session = wizardSessions.get(id);
|
||||
if (!session) return;
|
||||
if (session.getStatus() === "running") return;
|
||||
wizardSessions.delete(id);
|
||||
};
|
||||
|
||||
const normalizeHookHeaders = (req: IncomingMessage) => {
|
||||
const headers: Record<string, string> = {};
|
||||
for (const [key, value] of Object.entries(req.headers)) {
|
||||
@@ -2801,6 +2839,20 @@ export async function startGatewayServer(
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
return { ok: true, payloadJSON: JSON.stringify(snapshot) };
|
||||
}
|
||||
case "config.schema": {
|
||||
const params = parseParams();
|
||||
if (!validateConfigSchemaParams(params)) {
|
||||
return {
|
||||
ok: false,
|
||||
error: {
|
||||
code: ErrorCodes.INVALID_REQUEST,
|
||||
message: `invalid config.schema params: ${formatValidationErrors(validateConfigSchemaParams.errors)}`,
|
||||
},
|
||||
};
|
||||
}
|
||||
const schema = buildConfigSchema();
|
||||
return { ok: true, payloadJSON: JSON.stringify(schema) };
|
||||
}
|
||||
case "config.set": {
|
||||
const params = parseParams();
|
||||
if (!validateConfigSetParams(params)) {
|
||||
@@ -5306,6 +5358,23 @@ export async function startGatewayServer(
|
||||
respond(true, snapshot, undefined);
|
||||
break;
|
||||
}
|
||||
case "config.schema": {
|
||||
const params = (req.params ?? {}) as Record<string, unknown>;
|
||||
if (!validateConfigSchemaParams(params)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
`invalid config.schema params: ${formatValidationErrors(validateConfigSchemaParams.errors)}`,
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
const schema = buildConfigSchema();
|
||||
respond(true, schema, undefined);
|
||||
break;
|
||||
}
|
||||
case "config.set": {
|
||||
const params = (req.params ?? {}) as Record<string, unknown>;
|
||||
if (!validateConfigSetParams(params)) {
|
||||
@@ -5363,6 +5432,171 @@ export async function startGatewayServer(
|
||||
);
|
||||
break;
|
||||
}
|
||||
case "wizard.start": {
|
||||
const params = (req.params ?? {}) as Record<string, unknown>;
|
||||
if (!validateWizardStartParams(params)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
`invalid wizard.start params: ${formatValidationErrors(validateWizardStartParams.errors)}`,
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
const running = findRunningWizard();
|
||||
if (running) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.UNAVAILABLE, "wizard already running"),
|
||||
);
|
||||
break;
|
||||
}
|
||||
const sessionId = randomUUID();
|
||||
const opts = {
|
||||
mode: params.mode as "local" | "remote" | undefined,
|
||||
workspace:
|
||||
typeof params.workspace === "string"
|
||||
? params.workspace
|
||||
: undefined,
|
||||
};
|
||||
const session = new WizardSession((prompter) =>
|
||||
wizardRunner(opts, defaultRuntime, prompter),
|
||||
);
|
||||
wizardSessions.set(sessionId, session);
|
||||
const result = await session.next();
|
||||
if (result.done) {
|
||||
purgeWizardSession(sessionId);
|
||||
}
|
||||
respond(true, { sessionId, ...result }, undefined);
|
||||
break;
|
||||
}
|
||||
case "wizard.next": {
|
||||
const params = (req.params ?? {}) as Record<string, unknown>;
|
||||
if (!validateWizardNextParams(params)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
`invalid wizard.next params: ${formatValidationErrors(validateWizardNextParams.errors)}`,
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
const sessionId = params.sessionId as string;
|
||||
const session = wizardSessions.get(sessionId);
|
||||
if (!session) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.INVALID_REQUEST, "wizard not found"),
|
||||
);
|
||||
break;
|
||||
}
|
||||
const answer = params.answer as
|
||||
| { stepId?: string; value?: unknown }
|
||||
| undefined;
|
||||
if (answer) {
|
||||
if (session.getStatus() !== "running") {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
"wizard not running",
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
try {
|
||||
await session.answer(
|
||||
String(answer.stepId ?? ""),
|
||||
answer.value,
|
||||
);
|
||||
} catch (err) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.INVALID_REQUEST, formatForLog(err)),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const result = await session.next();
|
||||
if (result.done) {
|
||||
purgeWizardSession(sessionId);
|
||||
}
|
||||
respond(true, result, undefined);
|
||||
break;
|
||||
}
|
||||
case "wizard.cancel": {
|
||||
const params = (req.params ?? {}) as Record<string, unknown>;
|
||||
if (!validateWizardCancelParams(params)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
`invalid wizard.cancel params: ${formatValidationErrors(validateWizardCancelParams.errors)}`,
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
const sessionId = params.sessionId as string;
|
||||
const session = wizardSessions.get(sessionId);
|
||||
if (!session) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.INVALID_REQUEST, "wizard not found"),
|
||||
);
|
||||
break;
|
||||
}
|
||||
session.cancel();
|
||||
const status = {
|
||||
status: session.getStatus(),
|
||||
error: session.getError(),
|
||||
};
|
||||
wizardSessions.delete(sessionId);
|
||||
respond(true, status, undefined);
|
||||
break;
|
||||
}
|
||||
case "wizard.status": {
|
||||
const params = (req.params ?? {}) as Record<string, unknown>;
|
||||
if (!validateWizardStatusParams(params)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(
|
||||
ErrorCodes.INVALID_REQUEST,
|
||||
`invalid wizard.status params: ${formatValidationErrors(validateWizardStatusParams.errors)}`,
|
||||
),
|
||||
);
|
||||
break;
|
||||
}
|
||||
const sessionId = params.sessionId as string;
|
||||
const session = wizardSessions.get(sessionId);
|
||||
if (!session) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
errorShape(ErrorCodes.INVALID_REQUEST, "wizard not found"),
|
||||
);
|
||||
break;
|
||||
}
|
||||
const status = {
|
||||
status: session.getStatus(),
|
||||
error: session.getError(),
|
||||
};
|
||||
if (status.status !== "running") {
|
||||
wizardSessions.delete(sessionId);
|
||||
}
|
||||
respond(true, status, undefined);
|
||||
break;
|
||||
}
|
||||
case "talk.mode": {
|
||||
if (
|
||||
client &&
|
||||
|
||||
Reference in New Issue
Block a user