mockpit 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -6
- package/bin/mockpit.js +139 -18
- package/dist/mcp/server.js +30 -10
- package/dist/server/apiViews.d.ts +129 -25
- package/dist/server/apiViews.js +86 -23
- package/dist/server/app.d.ts +2 -1
- package/dist/server/app.js +1258 -200
- package/dist/server/appIcon.d.ts +22 -0
- package/dist/server/appIcon.js +43 -0
- package/dist/server/appIconPng.d.ts +1 -0
- package/dist/server/appIconPng.js +6 -0
- package/dist/server/askNudges.d.ts +3 -0
- package/dist/server/askNudges.js +40 -0
- package/dist/server/base64.d.ts +1 -0
- package/dist/server/base64.js +8 -0
- package/dist/server/decisions.d.ts +23 -0
- package/dist/server/decisions.js +99 -0
- package/dist/server/designGuide.js +53 -16
- package/dist/server/embeds.d.ts +25 -0
- package/dist/server/embeds.js +118 -0
- package/dist/server/eventLog.js +2 -1
- package/dist/server/feedbackBatch.d.ts +28 -5
- package/dist/server/feedbackBatch.js +78 -11
- package/dist/server/fills.d.ts +41 -0
- package/dist/server/fills.js +328 -0
- package/dist/server/handoff.d.ts +6 -0
- package/dist/server/handoff.js +38 -0
- package/dist/server/index.js +15 -1
- package/dist/server/knobs.d.ts +6 -2
- package/dist/server/knobs.js +13 -4
- package/dist/server/mcpHttp.d.ts +5 -2
- package/dist/server/mcpHttp.js +14 -5
- package/dist/server/mcpSpec.d.ts +21 -13
- package/dist/server/mcpSpec.js +99 -9
- package/dist/server/postSurfaces.js +3 -12
- package/dist/server/richRender.js +3 -1
- package/dist/server/run.js +6 -2
- package/dist/server/runApi.d.ts +1 -1
- package/dist/server/runApi.js +5 -4
- package/dist/server/sqlStore.d.ts +92 -5
- package/dist/server/sqlStore.js +188 -30
- package/dist/server/surfacePage.d.ts +6 -2
- package/dist/server/surfacePage.js +142 -10
- package/dist/server/themes.d.ts +2 -0
- package/dist/server/themes.js +23 -3
- package/dist/server/types.d.ts +95 -10
- package/dist/server/types.js +89 -6
- package/dist/server/viewerRequest.d.ts +2 -0
- package/dist/server/viewerRequest.js +14 -0
- package/extensions/mockpit.js +1 -0
- package/guide/topics/asks.md +69 -10
- package/guide/topics/feedback.md +104 -16
- package/guide/topics/html.md +5 -1
- package/guide/topics/http.md +2 -1
- package/guide/topics/knobs.md +4 -3
- package/guide/topics/surfaces.md +32 -16
- package/package.json +4 -13
- package/skills/launch-video/SKILL.md +1 -1
- package/skills/mockpit/SKILL.md +14 -4
- package/viewer/dist/index.html +9 -9
- package/dist/server/slots.d.ts +0 -14
- package/dist/server/slots.js +0 -71
package/dist/server/knobs.d.ts
CHANGED
|
@@ -10,6 +10,10 @@ export declare function explicitKnob(config: KnobConfig): ExplicitKnob;
|
|
|
10
10
|
export declare function checkKnobConfig(path: string, raw: unknown): Checked<KnobConfig>;
|
|
11
11
|
export declare function checkKnobs(raw: unknown): Checked<Knobs>;
|
|
12
12
|
export declare function checkKnobValue(path: string, config: KnobConfig, value: unknown): Checked<KnobValue>;
|
|
13
|
-
export declare function knobFor(path: string, mock: Pick<Mock, "knobs">, posts: Pick<Post, "knobs">
|
|
14
|
-
|
|
13
|
+
export declare function knobFor(path: string, mock: Pick<Mock, "knobs">, posts: (Pick<Post, "knobs"> & {
|
|
14
|
+
surfaces?: Post["surfaces"];
|
|
15
|
+
})[]): KnobConfig | undefined;
|
|
16
|
+
export declare function checkKnobValues(raw: unknown, mock: Pick<Mock, "knobs">, posts: (Pick<Post, "knobs"> & {
|
|
17
|
+
surfaces?: Post["surfaces"];
|
|
18
|
+
})[], what: string): Checked<Record<string, KnobValue>>;
|
|
15
19
|
export declare function discreteChoices(config: KnobConfig): number | null;
|
package/dist/server/knobs.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Knob schema and value validation (plan Q3: values are checked against the
|
|
2
2
|
// declared schema before they are stored or used). Runtime-agnostic. The config
|
|
3
3
|
// shape mirrors tunekit's usePane config; see the knob types in types.ts.
|
|
4
|
+
import { slotKnobs, slotsInSurfaces } from "./fills.js";
|
|
4
5
|
const MAX_KNOBS = 100;
|
|
5
6
|
const MAX_OPTIONS = 100;
|
|
6
7
|
const MAX_STRING = 500;
|
|
@@ -292,13 +293,22 @@ export function checkKnobValue(path, config, value) {
|
|
|
292
293
|
}
|
|
293
294
|
}
|
|
294
295
|
}
|
|
295
|
-
// The declared knob for a path: the mock's own, else a variant's override
|
|
296
|
+
// The declared knob for a path: the mock's own, else a variant's override,
|
|
297
|
+
// else the knob a variant's slot implies (fills.ts), so an option can `set` a
|
|
298
|
+
// slot's fill without the agent declaring anything.
|
|
296
299
|
export function knobFor(path, mock, posts) {
|
|
297
300
|
if (Object.hasOwn(mock.knobs, path))
|
|
298
301
|
return mock.knobs[path];
|
|
299
302
|
for (const p of posts)
|
|
300
303
|
if (p.knobs && Object.hasOwn(p.knobs, path))
|
|
301
304
|
return p.knobs[path];
|
|
305
|
+
for (const p of posts) {
|
|
306
|
+
if (!p.surfaces)
|
|
307
|
+
continue;
|
|
308
|
+
const implied = slotKnobs(slotsInSurfaces(p.surfaces));
|
|
309
|
+
if (Object.hasOwn(implied, path))
|
|
310
|
+
return implied[path];
|
|
311
|
+
}
|
|
302
312
|
return undefined;
|
|
303
313
|
}
|
|
304
314
|
export function checkKnobValues(raw, mock, posts, what) {
|
|
@@ -320,11 +330,10 @@ export function checkKnobValues(raw, mock, posts, what) {
|
|
|
320
330
|
return { ok: true, value: out };
|
|
321
331
|
}
|
|
322
332
|
// How many discrete choices a knob offers, or null when it is continuous. A knob
|
|
323
|
-
// with only a handful is usually a decision dressed as a control (plan D12).
|
|
333
|
+
// with only a handful is usually a decision dressed as a control (plan D12). A
|
|
334
|
+
// toggle is null: on/off reads fine on one render.
|
|
324
335
|
export function discreteChoices(config) {
|
|
325
336
|
const knob = explicitKnob(config);
|
|
326
|
-
if (knob.type === "toggle")
|
|
327
|
-
return 2;
|
|
328
337
|
if (knob.type === "select")
|
|
329
338
|
return knob.options.length;
|
|
330
339
|
if (knob.type === "image" && knob.options)
|
package/dist/server/mcpHttp.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ export interface McpFlows {
|
|
|
8
8
|
list: Flow<[query: {
|
|
9
9
|
project?: string;
|
|
10
10
|
}]>;
|
|
11
|
+
tree: Flow<[query: {
|
|
12
|
+
project?: string;
|
|
13
|
+
}]>;
|
|
11
14
|
get: Flow<[ref: unknown, query: {
|
|
12
15
|
project?: unknown;
|
|
13
16
|
body?: boolean;
|
|
@@ -23,11 +26,11 @@ export interface McpFlows {
|
|
|
23
26
|
},
|
|
24
27
|
ctx: FlowContext
|
|
25
28
|
]>;
|
|
26
|
-
say: Flow<[ref: unknown, body: any]>;
|
|
29
|
+
say: Flow<[ref: unknown, body: any, ctx: FlowContext]>;
|
|
27
30
|
feedback: Flow<[query: {
|
|
28
31
|
session?: unknown;
|
|
29
32
|
project?: unknown;
|
|
30
|
-
}]>;
|
|
33
|
+
}, ctx: FlowContext]>;
|
|
31
34
|
}
|
|
32
35
|
export interface McpDeps {
|
|
33
36
|
store: Store;
|
package/dist/server/mcpHttp.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { decodeBase64 } from "./base64.js";
|
|
2
2
|
import { isGuideTopic, unknownTopicMessage } from "./designGuide.js";
|
|
3
|
-
import { HTTP_MCP_TOOLS, HTTP_RUN_TOOLS, MCP_INSTRUCTIONS, MCP_SERVER_INFO, runToolResult, toolResult, } from "./mcpSpec.js";
|
|
3
|
+
import { HTTP_MCP_TOOLS, HTTP_RUN_TOOLS, MCP_INSTRUCTIONS, MCP_SERVER_INFO, runToolResult, toolResult, withFeedbackImages, } from "./mcpSpec.js";
|
|
4
4
|
import { RUN_INSTRUCTIONS } from "./runApi.js";
|
|
5
5
|
// A flow's error status becomes a tool error carrying the same JSON body the
|
|
6
6
|
// REST tier returns, so the agent sees the hint (states, variants) either way.
|
|
@@ -24,6 +24,10 @@ export function registerMcp(app, deps) {
|
|
|
24
24
|
const projects = await store.listProjects();
|
|
25
25
|
return projects[0]?.name ?? "workspace";
|
|
26
26
|
}
|
|
27
|
+
async function loadImage(ref) {
|
|
28
|
+
const asset = await store.getAsset(ref.id);
|
|
29
|
+
return asset ? { data: asset.data, mimeType: asset.contentType } : null;
|
|
30
|
+
}
|
|
27
31
|
async function callTool(name, args, ctx) {
|
|
28
32
|
switch (name) {
|
|
29
33
|
case "publish":
|
|
@@ -31,6 +35,9 @@ export function registerMcp(app, deps) {
|
|
|
31
35
|
case "ask":
|
|
32
36
|
return unwrap(await flows.ask(args.mock, args, ctx));
|
|
33
37
|
case "read":
|
|
38
|
+
if (args.mock === undefined && args.tree === true) {
|
|
39
|
+
return unwrap(await flows.tree({ project: args.project }));
|
|
40
|
+
}
|
|
34
41
|
if (args.mock === undefined)
|
|
35
42
|
return unwrap(await flows.list({ project: args.project }));
|
|
36
43
|
return unwrap(await flows.get(args.mock, {
|
|
@@ -39,9 +46,9 @@ export function registerMcp(app, deps) {
|
|
|
39
46
|
history: args.history === true,
|
|
40
47
|
}));
|
|
41
48
|
case "feedback":
|
|
42
|
-
return unwrap(await flows.feedback({ session: args.session, project: args.project }));
|
|
49
|
+
return unwrap(await flows.feedback({ session: args.session, project: args.project }, ctx));
|
|
43
50
|
case "say":
|
|
44
|
-
return unwrap(await flows.say(args.mock, args));
|
|
51
|
+
return unwrap(await flows.say(args.mock, args, ctx));
|
|
45
52
|
case "export":
|
|
46
53
|
return unwrap(await flows.exportMock(args.mock, args, ctx));
|
|
47
54
|
case "upload": {
|
|
@@ -124,9 +131,11 @@ export function registerMcp(app, deps) {
|
|
|
124
131
|
const name = msg.params?.name;
|
|
125
132
|
if (name === "run") {
|
|
126
133
|
const args = msg.params?.arguments ?? {};
|
|
127
|
-
|
|
134
|
+
const envelope = unwrap(await deps.run(args, ctx));
|
|
135
|
+
return rpc(msg.id, await withFeedbackImages(runToolResult(envelope), envelope, loadImage));
|
|
128
136
|
}
|
|
129
|
-
|
|
137
|
+
const value = await callTool(name, msg.params?.arguments ?? {}, ctx);
|
|
138
|
+
return rpc(msg.id, await withFeedbackImages(toolResult(name, value), value, loadImage));
|
|
130
139
|
}
|
|
131
140
|
catch (err) {
|
|
132
141
|
return rpc(msg.id, {
|
package/dist/server/mcpSpec.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as z from "zod/v4";
|
|
2
|
+
import { type ImageRef } from "./feedbackBatch.ts";
|
|
2
3
|
export declare const MCP_SERVER_INFO: {
|
|
3
4
|
name: string;
|
|
4
5
|
version: string;
|
|
@@ -52,7 +53,7 @@ type Param = {
|
|
|
52
53
|
d?: string;
|
|
53
54
|
req?: boolean;
|
|
54
55
|
} | {
|
|
55
|
-
t: "surfaces" | "asks" | "object";
|
|
56
|
+
t: "surfaces" | "asks" | "object" | "integers";
|
|
56
57
|
d?: string;
|
|
57
58
|
req?: boolean;
|
|
58
59
|
};
|
|
@@ -130,9 +131,9 @@ export declare const STDIO_MCP_TOOLS: ({
|
|
|
130
131
|
_meta: {
|
|
131
132
|
"anthropic/alwaysLoad": boolean;
|
|
132
133
|
};
|
|
133
|
-
outputSchema?: z.ZodObject<
|
|
134
|
-
[
|
|
135
|
-
}
|
|
134
|
+
outputSchema?: z.ZodObject<{
|
|
135
|
+
[x: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
136
|
+
}, z.core.$loose> | undefined;
|
|
136
137
|
name: string;
|
|
137
138
|
description: string;
|
|
138
139
|
inputSchema: Readonly<{
|
|
@@ -140,9 +141,9 @@ export declare const STDIO_MCP_TOOLS: ({
|
|
|
140
141
|
}>;
|
|
141
142
|
} | {
|
|
142
143
|
_meta?: undefined;
|
|
143
|
-
outputSchema?: z.ZodObject<
|
|
144
|
-
[
|
|
145
|
-
}
|
|
144
|
+
outputSchema?: z.ZodObject<{
|
|
145
|
+
[x: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
146
|
+
}, z.core.$loose> | undefined;
|
|
146
147
|
name: string;
|
|
147
148
|
description: string;
|
|
148
149
|
inputSchema: Readonly<{
|
|
@@ -153,9 +154,9 @@ export declare const STDIO_RUN_TOOLS: ({
|
|
|
153
154
|
_meta: {
|
|
154
155
|
"anthropic/alwaysLoad": boolean;
|
|
155
156
|
};
|
|
156
|
-
outputSchema?: z.ZodObject<
|
|
157
|
-
[
|
|
158
|
-
}
|
|
157
|
+
outputSchema?: z.ZodObject<{
|
|
158
|
+
[x: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
159
|
+
}, z.core.$loose> | undefined;
|
|
159
160
|
name: string;
|
|
160
161
|
description: string;
|
|
161
162
|
inputSchema: Readonly<{
|
|
@@ -163,9 +164,9 @@ export declare const STDIO_RUN_TOOLS: ({
|
|
|
163
164
|
}>;
|
|
164
165
|
} | {
|
|
165
166
|
_meta?: undefined;
|
|
166
|
-
outputSchema?: z.ZodObject<
|
|
167
|
-
[
|
|
168
|
-
}
|
|
167
|
+
outputSchema?: z.ZodObject<{
|
|
168
|
+
[x: string]: z.core.$ZodType<unknown, unknown, z.core.$ZodTypeInternals<unknown, unknown>>;
|
|
169
|
+
}, z.core.$loose> | undefined;
|
|
169
170
|
name: string;
|
|
170
171
|
description: string;
|
|
171
172
|
inputSchema: Readonly<{
|
|
@@ -180,6 +181,13 @@ export declare function toolResult(name: string, value: unknown): {
|
|
|
180
181
|
text: string;
|
|
181
182
|
}[];
|
|
182
183
|
};
|
|
184
|
+
export declare const MAX_RESULT_IMAGES = 8;
|
|
185
|
+
export declare function withFeedbackImages<T extends {
|
|
186
|
+
content: unknown[];
|
|
187
|
+
}>(result: T, value: unknown, load: (ref: ImageRef) => Promise<{
|
|
188
|
+
data: Uint8Array;
|
|
189
|
+
mimeType: string;
|
|
190
|
+
} | null>): Promise<T>;
|
|
183
191
|
export declare function runToolResult(envelope: unknown): {
|
|
184
192
|
isError?: boolean | undefined;
|
|
185
193
|
structuredContent?: Record<string, unknown> | undefined;
|
package/dist/server/mcpSpec.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import * as z from "zod/v4";
|
|
2
|
+
import { encodeBase64 } from "./base64.js";
|
|
2
3
|
import { GUIDE_TOPICS } from "./designGuide.js";
|
|
4
|
+
import { feedbackImages } from "./feedbackBatch.js";
|
|
3
5
|
import { KIT_IDS } from "./kits.js";
|
|
4
6
|
import { RUN_DESCRIPTION } from "./runApi.js";
|
|
5
7
|
import { SURFACE_KINDS } from "./types.js";
|
|
@@ -26,7 +28,12 @@ const field = {
|
|
|
26
28
|
knobs: 'Global knobs (tunekit usePane shape) keyed by path: [default,min,max,step], boolean, {type:"select",options}, "#hex"',
|
|
27
29
|
variantKnobs: "Knobs this variant alone has",
|
|
28
30
|
from: "Branch from this version",
|
|
31
|
+
fork: 'Start this new variant from a sibling: "variant" or "variant@N"; alone it copies it, with parts/fills it edits the copy',
|
|
29
32
|
prompt: "What prompted this version; usually omit",
|
|
33
|
+
note: "What this variant is; shown beside it, never in the html",
|
|
34
|
+
widths: "Widths it ships at, side by side: [390,1280]",
|
|
35
|
+
flow: "Flow this screen is a step of; alone, only places it",
|
|
36
|
+
step: "1-based place; 0 removes",
|
|
30
37
|
kits: `Opt-in bundles: ${KIT_IDS.join("|")}`,
|
|
31
38
|
};
|
|
32
39
|
const diffFileSchema = z.object({
|
|
@@ -73,10 +80,15 @@ const askSchema = z.object({
|
|
|
73
80
|
state: z.string().optional(),
|
|
74
81
|
part: z.string().optional().describe("data-part name, for scope part"),
|
|
75
82
|
multi: z.boolean().optional(),
|
|
83
|
+
after: z
|
|
84
|
+
.string()
|
|
85
|
+
.optional()
|
|
86
|
+
.describe("Id of an earlier ask this depends on; it opens once that one is answered"),
|
|
76
87
|
options: z.array(z.object({
|
|
77
88
|
id: z.string().optional(),
|
|
78
89
|
label: z.string(),
|
|
79
90
|
variant: z.string().optional().describe("Bind to a published variant"),
|
|
91
|
+
version: z.number().int().optional().describe("And one version of it"),
|
|
80
92
|
set: z.record(z.string(), z.unknown()).optional().describe("Or to knob values {path: value}"),
|
|
81
93
|
})),
|
|
82
94
|
});
|
|
@@ -98,6 +110,9 @@ function zodParam(p) {
|
|
|
98
110
|
case "integer":
|
|
99
111
|
s = z.number().int();
|
|
100
112
|
break;
|
|
113
|
+
case "integers":
|
|
114
|
+
s = z.array(z.number().int());
|
|
115
|
+
break;
|
|
101
116
|
default:
|
|
102
117
|
s = p.t === "string" ? z.string() : p.t === "number" ? z.number() : z.boolean();
|
|
103
118
|
}
|
|
@@ -116,7 +131,7 @@ const VARIANT_REF = { mock: P.mock, state: P.state, variant: P.variant, project:
|
|
|
116
131
|
export const MCP_TOOL_DEFS = [
|
|
117
132
|
{
|
|
118
133
|
name: "publish",
|
|
119
|
-
description: "Create or version one variant of one state of a mock. One of html, surfaces (the full ordered list) or
|
|
134
|
+
description: "Create or version one variant of one state of a mock. One of html, surfaces (the full ordered list), parts or fills (splice the latest version). Returns parts per state, nudges, feedback.",
|
|
120
135
|
params: {
|
|
121
136
|
...VARIANT_REF,
|
|
122
137
|
title: { t: "string", d: field.title },
|
|
@@ -127,10 +142,19 @@ export const MCP_TOOL_DEFS = [
|
|
|
127
142
|
d: "Full ordered list: {id} keeps a surface, a missing id removes it",
|
|
128
143
|
},
|
|
129
144
|
parts: { t: "object", d: '{"name"|"name#key": outer html}; splices marked parts' },
|
|
145
|
+
fills: {
|
|
146
|
+
t: "object",
|
|
147
|
+
d: '{"part#fill": outer html}; replaces that fill of a slot, or adds it after the others',
|
|
148
|
+
},
|
|
130
149
|
knobs: { t: "object", d: field.knobs },
|
|
131
150
|
variantKnobs: { t: "object", d: field.variantKnobs },
|
|
132
151
|
from: { t: "integer", d: field.from },
|
|
152
|
+
fork: { t: "string", d: field.fork },
|
|
133
153
|
prompt: { t: "string", d: field.prompt },
|
|
154
|
+
note: { t: "string", d: field.note },
|
|
155
|
+
widths: { t: "integers", d: field.widths },
|
|
156
|
+
flow: { t: "string", d: field.flow },
|
|
157
|
+
step: { t: "integer", d: field.step },
|
|
134
158
|
session: P.session,
|
|
135
159
|
sessionTitle: { t: "string", d: field.sessionTitle },
|
|
136
160
|
agent: { t: "string", d: field.agent },
|
|
@@ -150,17 +174,21 @@ export const MCP_TOOL_DEFS = [
|
|
|
150
174
|
},
|
|
151
175
|
{
|
|
152
176
|
name: "read",
|
|
153
|
-
description: "Without mock: every mock (slug, states, variants, open asks) plus pending. With mock: its states, variants, asks with answers, parts, knobs and
|
|
177
|
+
description: "Without mock: every mock (slug, states, variants, open asks), flows, plus pending. With mock: its states, variants, asks with answers, parts, knobs and settled values (every answer's set and tuning sent so far), plus pending. Never consumes feedback.",
|
|
154
178
|
params: {
|
|
155
179
|
mock: { t: "string", d: field.mock },
|
|
156
180
|
project: P.project,
|
|
157
181
|
body: { t: "boolean", d: "Include each variant's surfaces" },
|
|
158
182
|
history: { t: "boolean", d: "Include version rows" },
|
|
183
|
+
tree: {
|
|
184
|
+
t: "boolean",
|
|
185
|
+
d: "Without mock: the project as a tree (each mock's variants with status and forkedFrom, the mocks it uses and is used in)",
|
|
186
|
+
},
|
|
159
187
|
},
|
|
160
188
|
},
|
|
161
189
|
{
|
|
162
190
|
name: "feedback",
|
|
163
|
-
description: "Returns at once: what the user sent since you last heard (one batch per mock: reply answers, mix, tuned knobs, part comments, comments, accepted/archived), delivered once, plus pending (viewer open, draft progress).",
|
|
191
|
+
description: "Returns at once: what the user sent since you last heard (one batch per mock: reply answers, mix, tuned knobs, part comments, comments, accepted/archived/dropped, or resolved with the asks it dismissed), delivered once, plus pending (viewer open, draft progress).",
|
|
164
192
|
params: {
|
|
165
193
|
project: P.project,
|
|
166
194
|
session: { t: "string", d: "Session id returned by publish", req: true },
|
|
@@ -179,7 +207,7 @@ export const MCP_TOOL_DEFS = [
|
|
|
179
207
|
},
|
|
180
208
|
{
|
|
181
209
|
name: "export",
|
|
182
|
-
description: "The
|
|
210
|
+
description: "The html in use (else current) per state, version history, screenshot URL, knobs, the settled values and the last reply.",
|
|
183
211
|
params: VARIANT_REF,
|
|
184
212
|
},
|
|
185
213
|
{
|
|
@@ -233,7 +261,9 @@ const nullStr = z.string().nullable();
|
|
|
233
261
|
const strs = z.array(z.string());
|
|
234
262
|
const bag = z.record(z.string(), z.unknown());
|
|
235
263
|
const variantRef = o({ state: nullStr, variant: str });
|
|
264
|
+
const inUseRef = o({ state: nullStr, variant: str, version: num });
|
|
236
265
|
const partsByState = z.array(o({ state: nullStr, parts: z.array(o({ name: str })) }));
|
|
266
|
+
const flowSchema = o({ name: str, steps: z.array(o({ mock: str, state: nullStr })) });
|
|
237
267
|
const feedbackSchema = o({
|
|
238
268
|
mock: nullStr,
|
|
239
269
|
reply: o({
|
|
@@ -245,18 +275,29 @@ const feedbackSchema = o({
|
|
|
245
275
|
mix: bag,
|
|
246
276
|
tuned: bag,
|
|
247
277
|
comments: z.array(o({ part: nullStr, text: str })),
|
|
278
|
+
reactions: strs.optional().describe('One-tap reactions, e.g. 👍 part "hero" (variant B)'),
|
|
248
279
|
})
|
|
249
280
|
.nullable()
|
|
250
281
|
.describe("The user's Send; null if only comments"),
|
|
251
282
|
comments: z.array(o({ seq: num, text: str })),
|
|
252
|
-
accepted: z.array(
|
|
283
|
+
accepted: z.array(inUseRef).describe("Accepted, each at the version put in use"),
|
|
253
284
|
archived: z.array(variantRef),
|
|
285
|
+
dropped: z.array(variantRef),
|
|
286
|
+
resolved: z
|
|
287
|
+
.literal(true)
|
|
288
|
+
.optional()
|
|
289
|
+
.describe("The user resolved the mock: its open asks were closed unanswered"),
|
|
290
|
+
dismissed: z
|
|
291
|
+
.array(o({ ask: str, text: str }))
|
|
292
|
+
.optional()
|
|
293
|
+
.describe("The asks the resolution closed; don't ask them again"),
|
|
254
294
|
}).meta({ id: "Feedback" });
|
|
255
295
|
// Typed once, on feedback: repeating it on every write would cost more than
|
|
256
296
|
// the rest of the write's output schema.
|
|
257
297
|
const feedbackRide = z.array(bag).describe("Feedback batches as the feedback tool returns");
|
|
258
298
|
const pendingSchema = o({
|
|
259
299
|
mock: str,
|
|
300
|
+
resolved: z.literal(true).optional(),
|
|
260
301
|
viewerOpen: z.boolean(),
|
|
261
302
|
draft: o({ answered: num, of: num, comments: num, touchedAt: str }).nullable(),
|
|
262
303
|
}).meta({ id: "Pending" });
|
|
@@ -268,6 +309,7 @@ const writeOutput = {
|
|
|
268
309
|
partChanges: o({ vanished: strs, renamed: z.array(o({ from: str, to: str })) }).optional(),
|
|
269
310
|
nudges: strs.optional(),
|
|
270
311
|
suggestedAsk: bag.optional().describe("Send with ask when a nudge names it"),
|
|
312
|
+
flow: flowSchema.optional(),
|
|
271
313
|
feedback: feedbackRide,
|
|
272
314
|
};
|
|
273
315
|
const OUTPUT_SCHEMAS = {
|
|
@@ -288,7 +330,13 @@ const OUTPUT_SCHEMAS = {
|
|
|
288
330
|
session: nullStr,
|
|
289
331
|
},
|
|
290
332
|
publish: writeOutput,
|
|
291
|
-
ask: {
|
|
333
|
+
ask: {
|
|
334
|
+
asks: z.array(o({ id: str, text: str })),
|
|
335
|
+
open: num,
|
|
336
|
+
url: str,
|
|
337
|
+
nudges: strs.optional(),
|
|
338
|
+
feedback: feedbackRide,
|
|
339
|
+
},
|
|
292
340
|
feedback: {
|
|
293
341
|
feedback: z.array(feedbackSchema).describe("One batch per mock; empty when nothing is new"),
|
|
294
342
|
pending: z.array(pendingSchema),
|
|
@@ -298,13 +346,39 @@ const OUTPUT_SCHEMAS = {
|
|
|
298
346
|
mocks: z.array(o({ slug: str, states: strs, variants: num, open: num })).optional(),
|
|
299
347
|
states: strs.optional(),
|
|
300
348
|
asks: z.array(o({ id: str, text: str })).optional(),
|
|
301
|
-
variants: z
|
|
349
|
+
variants: z
|
|
350
|
+
.array(o({
|
|
351
|
+
state: nullStr,
|
|
352
|
+
variant: str,
|
|
353
|
+
status: str,
|
|
354
|
+
version: num,
|
|
355
|
+
acceptedVersion: num.optional().describe("Set when accepted: the version in use"),
|
|
356
|
+
history: z
|
|
357
|
+
.array(o({ version: num, hidden: z.boolean().optional() }))
|
|
358
|
+
.optional()
|
|
359
|
+
.describe("With history: version rows; hidden ones the user put away are flagged"),
|
|
360
|
+
}))
|
|
361
|
+
.optional(),
|
|
362
|
+
inUse: z
|
|
363
|
+
.array(inUseRef)
|
|
364
|
+
.optional()
|
|
365
|
+
.describe("Per state, the version the user put in use; export and embeds resolve to it"),
|
|
302
366
|
knobs: bag.optional(),
|
|
303
|
-
|
|
367
|
+
settled: bag.optional().describe("Every knob value the user has sent, later replies winning"),
|
|
368
|
+
tree: z.array(bag).optional(),
|
|
369
|
+
flows: z.array(flowSchema).optional(),
|
|
304
370
|
pending: z.union([z.array(pendingSchema), pendingSchema]),
|
|
305
371
|
},
|
|
306
372
|
export: {
|
|
307
|
-
states: z.array(o({
|
|
373
|
+
states: z.array(o({
|
|
374
|
+
state: nullStr,
|
|
375
|
+
variant: str,
|
|
376
|
+
version: num,
|
|
377
|
+
latest: num.optional().describe("Your newer version, not in use yet"),
|
|
378
|
+
html: str,
|
|
379
|
+
markdown: str,
|
|
380
|
+
})),
|
|
381
|
+
settled: bag,
|
|
308
382
|
reply: bag.nullable(),
|
|
309
383
|
},
|
|
310
384
|
};
|
|
@@ -394,6 +468,22 @@ export function toolResult(name, value) {
|
|
|
394
468
|
...(structured ? { structuredContent: value } : {}),
|
|
395
469
|
};
|
|
396
470
|
}
|
|
471
|
+
// The images a result's feedback carries, inlined as image content so the
|
|
472
|
+
// model sees what the user attached instead of only its URL. Capped per result:
|
|
473
|
+
// the rest stay reachable by URL in the JSON.
|
|
474
|
+
export const MAX_RESULT_IMAGES = 8;
|
|
475
|
+
export async function withFeedbackImages(result, value, load) {
|
|
476
|
+
const batches = value?.feedback;
|
|
477
|
+
if (!Array.isArray(batches))
|
|
478
|
+
return result;
|
|
479
|
+
const images = [];
|
|
480
|
+
for (const ref of feedbackImages(batches).slice(0, MAX_RESULT_IMAGES)) {
|
|
481
|
+
const bytes = await load(ref).catch(() => null);
|
|
482
|
+
if (bytes)
|
|
483
|
+
images.push({ type: "image", data: encodeBase64(bytes.data), mimeType: bytes.mimeType });
|
|
484
|
+
}
|
|
485
|
+
return images.length ? { ...result, content: [...result.content, ...images] } : result;
|
|
486
|
+
}
|
|
397
487
|
// A run that failed is still a full envelope (calls, feedback); isError tells
|
|
398
488
|
// the client the script did not finish.
|
|
399
489
|
export function runToolResult(envelope) {
|
|
@@ -6,23 +6,14 @@ const MERMAID_PARSE_NEXT_STEPS = [
|
|
|
6
6
|
'Keep the diagram declaration on the first non-comment line (for example, "flowchart TD").',
|
|
7
7
|
"Reduce the diagram to a minimal valid form, then add statements back until the failing line is isolated.",
|
|
8
8
|
];
|
|
9
|
-
const requiredString = (name) => z.string({
|
|
10
|
-
required_error: `requires string "${name}"`,
|
|
11
|
-
invalid_type_error: `requires string "${name}"`,
|
|
12
|
-
});
|
|
9
|
+
const requiredString = (name) => z.string({ error: `requires string "${name}"` });
|
|
13
10
|
const optionalLooseString = z.preprocess((v) => (typeof v === "string" ? v : undefined), z.string().optional());
|
|
14
11
|
const looseLayout = z.preprocess((v) => (v === "unified" || v === "split" ? v : undefined), z.enum(["unified", "split"]).optional());
|
|
15
12
|
const optionalLooseNumber = z.preprocess((v) => (typeof v === "number" && Number.isFinite(v) ? v : undefined), z.number().optional());
|
|
16
13
|
const strictDiffFile = z.object({
|
|
17
14
|
filename: requiredString("filename"),
|
|
18
|
-
before: z.string({
|
|
19
|
-
|
|
20
|
-
invalid_type_error: 'requires string "before" and "after"',
|
|
21
|
-
}),
|
|
22
|
-
after: z.string({
|
|
23
|
-
required_error: 'requires string "before" and "after"',
|
|
24
|
-
invalid_type_error: 'requires string "before" and "after"',
|
|
25
|
-
}),
|
|
15
|
+
before: z.string({ error: 'requires string "before" and "after"' }),
|
|
16
|
+
after: z.string({ error: 'requires string "before" and "after"' }),
|
|
26
17
|
language: z.string().optional(),
|
|
27
18
|
});
|
|
28
19
|
const looseDiffFile = z
|
|
@@ -139,7 +139,9 @@ blockquote {
|
|
|
139
139
|
color: var(--muted);
|
|
140
140
|
}
|
|
141
141
|
table { border-collapse: collapse; font-size: 13px; }
|
|
142
|
-
|
|
142
|
+
/* break-word, not the body's anywhere: anywhere lets a column shrink to one
|
|
143
|
+
letter, so a long cell beside it squeezes "Topic" into "Top/ic". */
|
|
144
|
+
th, td { border: 0.5px solid var(--border); padding: 4px 8px; text-align: left; overflow-wrap: break-word; }
|
|
143
145
|
th { background: var(--hover); }
|
|
144
146
|
img { max-width: 100%; height: auto; border-radius: 6px; }
|
|
145
147
|
hr { border: none; border-top: 0.5px solid var(--border); margin: 1em 0; }
|
package/dist/server/run.js
CHANGED
|
@@ -153,6 +153,10 @@ export function createRunFlow(deps) {
|
|
|
153
153
|
return [await deps.guide("", topic), String(topic)];
|
|
154
154
|
},
|
|
155
155
|
async read([mock, opts]) {
|
|
156
|
+
if ((mock === undefined || mock === null) && plain(opts).tree === true) {
|
|
157
|
+
const value = await take(flows.tree({ project: await projectOf() }));
|
|
158
|
+
return [value, `${value.tree.length} mocks`];
|
|
159
|
+
}
|
|
156
160
|
if (mock === undefined || mock === null) {
|
|
157
161
|
const value = await take(flows.list({ project: await projectOf() }));
|
|
158
162
|
return [value, `${value.mocks.length} mocks`];
|
|
@@ -170,13 +174,13 @@ export function createRunFlow(deps) {
|
|
|
170
174
|
return [value, `${value.mock} ${value.asks.length} ask(s)`];
|
|
171
175
|
},
|
|
172
176
|
async feedback() {
|
|
173
|
-
const value = await take(flows.feedback(await mine({})));
|
|
177
|
+
const value = await take(flows.feedback(await mine({}), flowCtx));
|
|
174
178
|
const n = value.feedback.length;
|
|
175
179
|
return [value, n ? `${n} batch(es)` : "no feedback"];
|
|
176
180
|
},
|
|
177
181
|
async say([v, message]) {
|
|
178
182
|
const r = plain(v);
|
|
179
|
-
await take(flows.say(r.mock, { ...(await mine(r)), message }));
|
|
183
|
+
await take(flows.say(r.mock, { ...(await mine(r)), message }, flowCtx));
|
|
180
184
|
return [null, String(r.mock ?? "?")];
|
|
181
185
|
},
|
|
182
186
|
async upload([data, opts]) {
|
package/dist/server/runApi.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const RUN_API = "// The body of an async function, in plain JavaScript (no TypeScript syntax).\n// `await mockpit.*`, `return` a JSON value, print() adds an output line.\n// No network, timers, imports or process. Session, project and agent are fixed\n// for the run; pass the returned session to the next run.\ntype Ref = { mock: string; state?: string; variant?: string };\ntype Knobs = Record<string, unknown>; // path \u2192 [default,min,max,step] | boolean | {type:\"select\",options} | \"#hex\"\ntype Surface =\n | { kind: \"html\"; html: string; kits?: string[] }\n | { kind: \"markdown\"; markdown: string }\n | { kind: \"diff\"; patch?: string; files?: { filename: string; before: string; after: string }[]; layout?: \"unified\" | \"split\" }\n | { kind: \"image\"; assetId: string; alt?: string; caption?: string }\n | { kind: \"terminal\"; text: string; cols?: number; title?: string }\n | { kind: \"mermaid\"; mermaid: string }\n | { kind: \"json\"; data: unknown; title?: string }\n | { kind: \"code\"; code: string; language?: string; lineStart?: number };\ntype Ask = {\n id?: string; text: string; scope?: \"mock\" | \"state\" | \"part\"; state?: string; part?: string; multi?: boolean;\n options: { id?: string; label: string; variant?: string; set?: Knobs }[];\n};\ntype Written = {\n post: { state: string | null; variant: string; version: number }; url: string;\n parts: { state: string | null; parts: { name: string }[] }[];\n partChanges?: { vanished: string[]; renamed: { from: string; to: string }[] };\n nudges?: string[]; suggestedAsk?: Ask; // send it with ask when nudged\n};\ntype Feedback = {\n mock: string | null;\n reply: null | { asks: { ask: string; chosen: { id: string; label: string; other?: true }[]; note?: string }[]; mix: Knobs; tuned: Knobs; comments: { part: string | null; text: string }[] };\n comments: { text: string; state: string | null; variant: string | null }[];\n accepted: { state: string | null; variant: string }[]; archived: { state: string | null; variant: string }[];\n};\ntype Pending = { mock: string; viewerOpen: boolean; draft: null | { answered: number; of: number; comments: number; touchedAt: string } };\ndeclare const mockpit: {\n guide(topic?: \"knobs\" | \"asks\" | \"surfaces\" | \"html\" | \"feedback\" | \"http\" | \"scripts\"): Promise<string>;\n /** No mock: every mock plus pending. With one: its states, variants, asks, parts, knobs. */\n read(mock?: string, opts?: { body?: boolean; history?: boolean }): Promise<unknown>;\n /** Creates or versions (mock, state, variant). One of: html; surfaces, the full ordered list\n * ({id} alone keeps that surface, a missing id is removed); parts {\"name\"|\"name#key\": outer html}. */\n publish(v: Ref & { title?: string; kind?: \"component\" | \"page\"; html?: string; surfaces?: (Surface | { id: string })[]; parts?: Record<string, string>; knobs?: Knobs; variantKnobs?: Knobs; from?: number }): Promise<Written>;\n ask(mock: string, asks: Ask[]): Promise<{ asks: { id: string; text: string }[]; url: string }>;\n /** Never waits: what the user sent since you last heard, and what they are doing now. */\n feedback(): Promise<{ feedback: Feedback[]; pending: Pending[] }>;\n say(v: Ref, message: string): Promise<void>;\n export(v: Ref): Promise<{ states: { state: string | null; variant: string; version: number; html: string }[]; reply: unknown }>;\n /** data: base64 bytes. Use id as an image surface's assetId. */\n upload(data: string, opts: { contentType: string; filename?: string; kind?: \"image\" | \"file\" }): Promise<{ id: string; url: string }>;\n};\ndeclare function print(...values: unknown[]): void;";
|
|
1
|
+
export declare const RUN_API = "// The body of an async function, in plain JavaScript (no TypeScript syntax).\n// `await mockpit.*`, `return` a JSON value, print() adds an output line.\n// No network, timers, imports or process. Session, project and agent are fixed\n// for the run; pass the returned session to the next run.\ntype Ref = { mock: string; state?: string; variant?: string };\ntype Knobs = Record<string, unknown>; // path \u2192 [default,min,max,step] | boolean | {type:\"select\",options} | \"#hex\"\ntype Surface =\n | { kind: \"html\"; html: string; kits?: string[] }\n | { kind: \"markdown\"; markdown: string }\n | { kind: \"diff\"; patch?: string; files?: { filename: string; before: string; after: string }[]; layout?: \"unified\" | \"split\" }\n | { kind: \"image\"; assetId: string; alt?: string; caption?: string }\n | { kind: \"terminal\"; text: string; cols?: number; title?: string }\n | { kind: \"mermaid\"; mermaid: string }\n | { kind: \"json\"; data: unknown; title?: string }\n | { kind: \"code\"; code: string; language?: string; lineStart?: number };\ntype Ask = {\n id?: string; text: string; scope?: \"mock\" | \"state\" | \"part\"; state?: string; part?: string; multi?: boolean;\n options: { id?: string; label: string; variant?: string; set?: Knobs }[];\n};\ntype Written = {\n post: { state: string | null; variant: string; version: number }; url: string;\n parts: { state: string | null; parts: { name: string }[] }[];\n partChanges?: { vanished: string[]; renamed: { from: string; to: string }[] };\n nudges?: string[]; suggestedAsk?: Ask; // send it with ask when nudged\n};\ntype Feedback = {\n mock: string | null;\n reply: null | { asks: { ask: string; chosen: { id: string; label: string; other?: true }[]; note?: string }[]; mix: Knobs; tuned: Knobs; comments: { part: string | null; text: string }[] };\n comments: { text: string; state: string | null; variant: string | null }[];\n accepted: { state: string | null; variant: string }[]; archived: { state: string | null; variant: string }[];\n};\ntype Pending = { mock: string; viewerOpen: boolean; draft: null | { answered: number; of: number; comments: number; touchedAt: string } };\ndeclare const mockpit: {\n guide(topic?: \"knobs\" | \"asks\" | \"surfaces\" | \"html\" | \"feedback\" | \"http\" | \"scripts\"): Promise<string>;\n /** No mock: every mock, the flows, plus pending. With one: its states, variants, asks, parts, knobs. */\n read(mock?: string, opts?: { body?: boolean; history?: boolean }): Promise<unknown>;\n /** Creates or versions (mock, state, variant). One of: html; surfaces, the full ordered list\n * ({id} alone keeps that surface, a missing id is removed); parts {\"name\"|\"name#key\": outer html};\n * fills {\"part#fill\": outer html}, replacing or adding one alternative of a slot. */\n publish(v: Ref & { title?: string; kind?: \"component\" | \"page\"; html?: string; surfaces?: (Surface | { id: string })[]; parts?: Record<string, string>; fills?: Record<string, string>; knobs?: Knobs; variantKnobs?: Knobs; from?: number; prompt?: string; note?: string; widths?: number[]; flow?: string; step?: number }): Promise<Written>;\n ask(mock: string, asks: Ask[]): Promise<{ asks: { id: string; text: string }[]; url: string; nudges?: string[] }>;\n /** Never waits: what the user sent since you last heard, and what they are doing now. */\n feedback(): Promise<{ feedback: Feedback[]; pending: Pending[] }>;\n say(v: Ref, message: string): Promise<void>;\n export(v: Ref): Promise<{ states: { state: string | null; variant: string; version: number; html: string }[]; reply: unknown }>;\n /** data: base64 bytes. Use id as an image surface's assetId. */\n upload(data: string, opts: { contentType: string; filename?: string; kind?: \"image\" | \"file\" }): Promise<{ id: string; url: string }>;\n};\ndeclare function print(...values: unknown[]): void;";
|
|
2
2
|
export declare const RUN_FUNCTIONS: readonly ["guide", "read", "publish", "ask", "feedback", "say", "export", "upload"];
|
|
3
3
|
export type RunFunction = (typeof RUN_FUNCTIONS)[number];
|
|
4
4
|
export declare const RUN_EXAMPLE = "const html = (tone) => `<section data-part=\"hero\" class=\"${tone}\"><h1>Writer</h1></section>`;\nawait mockpit.publish({ mock: \"writer\", variant: \"calm\", html: html(\"calm\") });\nawait mockpit.publish({ mock: \"writer\", variant: \"bold\", html: html(\"bold\") });\nconst { url } = await mockpit.ask(\"writer\", [{ id: \"look\", text: \"Which look?\",\n options: [{ label: \"Calm\", variant: \"calm\" }, { label: \"Bold\", variant: \"bold\" }] }]);\n// Tell the user where to look and end the turn; a later run reads mockpit.feedback().\nreturn url;";
|
package/dist/server/runApi.js
CHANGED
|
@@ -38,12 +38,13 @@ type Feedback = {
|
|
|
38
38
|
type Pending = { mock: string; viewerOpen: boolean; draft: null | { answered: number; of: number; comments: number; touchedAt: string } };
|
|
39
39
|
declare const mockpit: {
|
|
40
40
|
guide(topic?: "knobs" | "asks" | "surfaces" | "html" | "feedback" | "http" | "scripts"): Promise<string>;
|
|
41
|
-
/** No mock: every mock plus pending. With one: its states, variants, asks, parts, knobs. */
|
|
41
|
+
/** No mock: every mock, the flows, plus pending. With one: its states, variants, asks, parts, knobs. */
|
|
42
42
|
read(mock?: string, opts?: { body?: boolean; history?: boolean }): Promise<unknown>;
|
|
43
43
|
/** Creates or versions (mock, state, variant). One of: html; surfaces, the full ordered list
|
|
44
|
-
* ({id} alone keeps that surface, a missing id is removed); parts {"name"|"name#key": outer html}
|
|
45
|
-
|
|
46
|
-
|
|
44
|
+
* ({id} alone keeps that surface, a missing id is removed); parts {"name"|"name#key": outer html};
|
|
45
|
+
* fills {"part#fill": outer html}, replacing or adding one alternative of a slot. */
|
|
46
|
+
publish(v: Ref & { title?: string; kind?: "component" | "page"; html?: string; surfaces?: (Surface | { id: string })[]; parts?: Record<string, string>; fills?: Record<string, string>; knobs?: Knobs; variantKnobs?: Knobs; from?: number; prompt?: string; note?: string; widths?: number[]; flow?: string; step?: number }): Promise<Written>;
|
|
47
|
+
ask(mock: string, asks: Ask[]): Promise<{ asks: { id: string; text: string }[]; url: string; nudges?: string[] }>;
|
|
47
48
|
/** Never waits: what the user sent since you last heard, and what they are doing now. */
|
|
48
49
|
feedback(): Promise<{ feedback: Feedback[]; pending: Pending[] }>;
|
|
49
50
|
say(v: Ref, message: string): Promise<void>;
|