mockpit 1.3.0 → 3.0.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 +66 -202
- package/bin/demoData.js +5 -5
- package/bin/initDesign.js +6 -6
- package/bin/mockpit.js +687 -192
- package/bin/poster.js +377 -0
- package/dist/mcp/server.js +110 -19
- package/dist/server/answerAdditions.d.ts +7 -0
- package/dist/server/answerAdditions.js +76 -0
- package/dist/server/apiViews.d.ts +563 -41
- package/dist/server/apiViews.js +223 -74
- package/dist/server/app.d.ts +10 -2
- package/dist/server/app.js +3284 -782
- package/dist/server/appIcon.js +15 -5
- package/dist/server/appIconPng.js +3 -3
- package/dist/server/askNudges.js +1 -1
- package/dist/server/cdnInline.d.ts +8 -0
- package/dist/server/cdnInline.js +638 -0
- package/dist/server/codeWorker.js +2 -1
- package/dist/server/decisionList.d.ts +45 -0
- package/dist/server/decisionList.js +302 -0
- package/dist/server/decisions.d.ts +8 -8
- package/dist/server/decisions.js +18 -17
- package/dist/server/designGuide.d.ts +10 -6
- package/dist/server/designGuide.js +173 -78
- package/dist/server/embeds.d.ts +4 -1
- package/dist/server/embeds.js +57 -6
- package/dist/server/eventLog.d.ts +2 -2
- package/dist/server/eventLog.js +11 -11
- package/dist/server/events.d.ts +9 -6
- package/dist/server/feedbackBatch.d.ts +38 -16
- package/dist/server/feedbackBatch.js +121 -50
- package/dist/server/fills.d.ts +4 -0
- package/dist/server/fills.js +55 -1
- package/dist/server/guidance.d.ts +23 -0
- package/dist/server/guidance.js +71 -0
- package/dist/server/handoff.d.ts +4 -1
- package/dist/server/handoff.js +8 -2
- package/dist/server/icons.js +1 -1
- package/dist/server/index.js +49 -11
- package/dist/server/jump.d.ts +21 -0
- package/dist/server/jump.js +175 -0
- package/dist/server/knobs.d.ts +3 -3
- package/dist/server/knobs.js +6 -6
- package/dist/server/mcpHttp.d.ts +8 -1
- package/dist/server/mcpHttp.js +44 -7
- package/dist/server/mcpSpec.d.ts +2 -1
- package/dist/server/mcpSpec.js +172 -50
- package/dist/server/parts.js +9 -1
- package/dist/server/postSurfaces.js +3 -0
- package/dist/server/run.d.ts +1 -1
- package/dist/server/run.js +31 -17
- package/dist/server/runApi.d.ts +3 -3
- package/dist/server/runApi.js +34 -24
- package/dist/server/sqlStore.d.ts +117 -94
- package/dist/server/sqlStore.js +1089 -141
- package/dist/server/standings.d.ts +25 -0
- package/dist/server/standings.js +113 -0
- package/dist/server/surfacePage.d.ts +6 -9
- package/dist/server/surfacePage.js +361 -86
- package/dist/server/types.d.ts +252 -69
- package/dist/server/types.js +87 -69
- package/extensions/mockpit.js +245 -65
- package/guide/AGENT_SETUP.md +13 -1
- package/guide/packs/design-craft.md +34 -0
- package/guide/topics/asks.md +117 -69
- package/guide/topics/feedback.md +156 -119
- package/guide/topics/html.md +19 -32
- package/guide/topics/http.md +43 -40
- package/guide/topics/knobs.md +7 -10
- package/guide/topics/scripts.md +24 -35
- package/guide/topics/surfaces.md +50 -41
- package/opencode/mockpit.js +120 -0
- package/opencode/package.json +8 -0
- package/package.json +4 -6
- package/skills/launch-video/SKILL.md +12 -12
- package/skills/mockpit/SKILL.md +35 -23
- package/viewer/dist/index.html +9 -8
package/dist/server/mcpSpec.js
CHANGED
|
@@ -4,23 +4,23 @@ import { GUIDE_TOPICS } from "./designGuide.js";
|
|
|
4
4
|
import { feedbackImages } from "./feedbackBatch.js";
|
|
5
5
|
import { KIT_IDS } from "./kits.js";
|
|
6
6
|
import { RUN_DESCRIPTION } from "./runApi.js";
|
|
7
|
-
import { SURFACE_KINDS } from "./types.js";
|
|
7
|
+
import { MAX_NOTES, SURFACE_KINDS } from "./types.js";
|
|
8
8
|
export const MCP_SERVER_INFO = { name: "mockpit", version: "0.2.0" };
|
|
9
9
|
// The `kind` enum both MCP transports advertise — derived from the one canonical
|
|
10
10
|
// list (types.ts) so the MCP tier can never fall behind what REST/CLI accept.
|
|
11
11
|
const SURFACE_KIND_ENUM = [...SURFACE_KINDS];
|
|
12
|
-
export const MCP_INSTRUCTIONS = "Mockpit shows design work to the user: project >
|
|
13
|
-
"each variant (name states in the user's words;
|
|
14
|
-
"revise. A choice is several variants plus one
|
|
12
|
+
export const MCP_INSTRUCTIONS = "Mockpit shows design work to the user: project > design > state > variant > version. publish " +
|
|
13
|
+
"each variant (name states in the user's words; declare parts with data-part); publish again to " +
|
|
14
|
+
"revise. A choice is several variants plus one question that binds them (ask). One render plus a " +
|
|
15
15
|
"control: declare a knob. Say where to look, then end your turn; nothing waits. When the " +
|
|
16
16
|
"user says they answered, call feedback. Writes return feedback too; it is delivered once. " +
|
|
17
17
|
"Fetch guide before html.";
|
|
18
18
|
const field = {
|
|
19
|
-
|
|
19
|
+
design: "Design slug, title or id",
|
|
20
20
|
state: "State label; omit if single-state",
|
|
21
21
|
variant: 'Variant label; default "default"',
|
|
22
22
|
project: "Default: the session's project",
|
|
23
|
-
title: "
|
|
23
|
+
title: "Design title",
|
|
24
24
|
session: "Session id from an earlier publish",
|
|
25
25
|
sessionTitle: "Task name; used only when creating the session",
|
|
26
26
|
agent: "Agent name for a new session",
|
|
@@ -28,13 +28,19 @@ const field = {
|
|
|
28
28
|
knobs: 'Global knobs (tunekit usePane shape) keyed by path: [default,min,max,step], boolean, {type:"select",options}, "#hex"',
|
|
29
29
|
variantKnobs: "Knobs this variant alone has",
|
|
30
30
|
from: "Branch from this version",
|
|
31
|
+
amend: "Replace the latest version in place: a fix before the user acts on it",
|
|
32
|
+
withdraw: "Take the latest version back, before the user acts on it",
|
|
31
33
|
fork: 'Start this new variant from a sibling: "variant" or "variant@N"; alone it copies it, with parts/fills it edits the copy',
|
|
32
34
|
prompt: "What prompted this version; usually omit",
|
|
33
|
-
|
|
35
|
+
description: "What this variant is; shown beside it, never in the html",
|
|
34
36
|
widths: "Widths it ships at, side by side: [390,1280]",
|
|
35
|
-
|
|
37
|
+
kind: "Only when building the product's screens: screen, component (a reusable design screens embed). With no html it plans one, drawn by a later publish. Omit for ideas, diagrams and reviews",
|
|
38
|
+
area: 'Product area in the project\'s words; Home groups by it. "" clears',
|
|
39
|
+
source: 'Code path implementing it. "" clears',
|
|
40
|
+
flow: "Flow this state is a step of; alone, only places it",
|
|
36
41
|
step: "1-based place; 0 removes",
|
|
37
42
|
kits: `Opt-in bundles: ${KIT_IDS.join("|")}`,
|
|
43
|
+
poster: "Still of the page (asset id) shown instead of running it; resend when the page changes",
|
|
38
44
|
};
|
|
39
45
|
const diffFileSchema = z.object({
|
|
40
46
|
filename: z.string(),
|
|
@@ -53,6 +59,7 @@ export const mcpSurfaceSchema = z
|
|
|
53
59
|
kind: z.enum(SURFACE_KIND_ENUM).optional(),
|
|
54
60
|
html: z.string().optional().describe(field.html),
|
|
55
61
|
kits: z.array(z.string()).optional().describe(field.kits),
|
|
62
|
+
poster: z.string().optional().describe(field.poster),
|
|
56
63
|
markdown: z.string().optional().describe("Prose; raw HTML is escaped"),
|
|
57
64
|
mermaid: z.string().optional().describe("Diagram source; do not set colors"),
|
|
58
65
|
patch: z.string().optional().describe("Preferred over files"),
|
|
@@ -74,10 +81,14 @@ export const mcpSurfaceSchema = z
|
|
|
74
81
|
description: "Set kind and its field: html, markdown, mermaid, patch|files (diff), assetId (image), text (terminal), data (json), code",
|
|
75
82
|
});
|
|
76
83
|
const askSchema = z.object({
|
|
77
|
-
id: z.string().optional().describe("Stable id; reusing one replaces that
|
|
84
|
+
id: z.string().optional().describe("Stable id; reusing one replaces that question"),
|
|
78
85
|
text: z.string().describe("The question, one sentence"),
|
|
79
|
-
scope: z.enum(["
|
|
86
|
+
scope: z.enum(["design", "state", "part"]).optional(),
|
|
80
87
|
state: z.string().optional(),
|
|
88
|
+
states: z
|
|
89
|
+
.array(z.string())
|
|
90
|
+
.optional()
|
|
91
|
+
.describe("Design-wide question: the states its answers can be seen on, best first"),
|
|
81
92
|
part: z.string().optional().describe("data-part name, for scope part"),
|
|
82
93
|
multi: z.boolean().optional(),
|
|
83
94
|
after: z
|
|
@@ -90,6 +101,10 @@ const askSchema = z.object({
|
|
|
90
101
|
variant: z.string().optional().describe("Bind to a published variant"),
|
|
91
102
|
version: z.number().int().optional().describe("And one version of it"),
|
|
92
103
|
set: z.record(z.string(), z.unknown()).optional().describe("Or to knob values {path: value}"),
|
|
104
|
+
style: z.string().optional().describe("With a set of a new value: its CSS"),
|
|
105
|
+
fill: z.string().optional().describe("With a set of {part: new fill}: its html"),
|
|
106
|
+
html: z.string().optional().describe("Or a whole page"),
|
|
107
|
+
recommended: z.boolean().optional().describe("The one you would pick; one per ask"),
|
|
93
108
|
})),
|
|
94
109
|
});
|
|
95
110
|
function zodParam(p) {
|
|
@@ -113,6 +128,9 @@ function zodParam(p) {
|
|
|
113
128
|
case "integers":
|
|
114
129
|
s = z.array(z.number().int());
|
|
115
130
|
break;
|
|
131
|
+
case "strings":
|
|
132
|
+
s = z.array(z.string());
|
|
133
|
+
break;
|
|
116
134
|
default:
|
|
117
135
|
s = p.t === "string" ? z.string() : p.t === "number" ? z.number() : z.boolean();
|
|
118
136
|
}
|
|
@@ -121,37 +139,42 @@ function zodParam(p) {
|
|
|
121
139
|
return p.req ? s : s.optional();
|
|
122
140
|
}
|
|
123
141
|
const P = {
|
|
124
|
-
|
|
142
|
+
design: { t: "string", d: field.design, req: true },
|
|
125
143
|
state: { t: "string", d: field.state },
|
|
126
144
|
variant: { t: "string", d: field.variant },
|
|
127
145
|
project: { t: "string", d: field.project },
|
|
128
146
|
session: { t: "string", d: field.session },
|
|
129
147
|
};
|
|
130
|
-
const VARIANT_REF = {
|
|
148
|
+
const VARIANT_REF = { design: P.design, state: P.state, variant: P.variant, project: P.project };
|
|
131
149
|
export const MCP_TOOL_DEFS = [
|
|
132
150
|
{
|
|
133
151
|
name: "publish",
|
|
134
|
-
description: "Create or version one variant of one state of a
|
|
152
|
+
description: "Create or version one variant of one state of a design. One of html, surfaces (the full ordered list), parts or fills (splice the latest version); or kind screen or component and none of them, to plan it. Returns parts per state, nudges, feedback.",
|
|
135
153
|
params: {
|
|
136
154
|
...VARIANT_REF,
|
|
137
155
|
title: { t: "string", d: field.title },
|
|
138
|
-
kind: { t: "enum", values: ["component", "
|
|
156
|
+
kind: { t: "enum", values: ["screen", "component", "idea"], d: field.kind },
|
|
157
|
+
area: { t: "string", d: field.area },
|
|
158
|
+
source: { t: "string", d: field.source },
|
|
139
159
|
html: { t: "string", d: field.html },
|
|
160
|
+
poster: { t: "string", d: field.poster },
|
|
140
161
|
surfaces: {
|
|
141
162
|
t: "surfaces",
|
|
142
163
|
d: "Full ordered list: {id} keeps a surface, a missing id removes it",
|
|
143
164
|
},
|
|
144
|
-
parts: { t: "object", d: '{"name"|"name#key": outer html}; splices
|
|
165
|
+
parts: { t: "object", d: '{"name"|"name#key": outer html}; splices declared parts' },
|
|
145
166
|
fills: {
|
|
146
167
|
t: "object",
|
|
147
|
-
d: '{"part#fill": outer html}; replaces that fill of a
|
|
168
|
+
d: '{"part#fill": outer html}; replaces that fill of a part, or adds it after its others',
|
|
148
169
|
},
|
|
149
170
|
knobs: { t: "object", d: field.knobs },
|
|
150
171
|
variantKnobs: { t: "object", d: field.variantKnobs },
|
|
151
172
|
from: { t: "integer", d: field.from },
|
|
152
173
|
fork: { t: "string", d: field.fork },
|
|
174
|
+
amend: { t: "boolean", d: field.amend },
|
|
175
|
+
withdraw: { t: "boolean", d: field.withdraw },
|
|
153
176
|
prompt: { t: "string", d: field.prompt },
|
|
154
|
-
|
|
177
|
+
description: { t: "string", d: field.description },
|
|
155
178
|
widths: { t: "integers", d: field.widths },
|
|
156
179
|
flow: { t: "string", d: field.flow },
|
|
157
180
|
step: { t: "integer", d: field.step },
|
|
@@ -163,9 +186,9 @@ export const MCP_TOOL_DEFS = [
|
|
|
163
186
|
},
|
|
164
187
|
{
|
|
165
188
|
name: "ask",
|
|
166
|
-
description: "Ask structured questions on a
|
|
189
|
+
description: "Ask structured questions on a design. A choice is several variants plus one question that binds them; options may also set knob values. Reusing a question id replaces it.",
|
|
167
190
|
params: {
|
|
168
|
-
|
|
191
|
+
design: P.design,
|
|
169
192
|
project: P.project,
|
|
170
193
|
asks: { t: "asks", d: "Questions with options", req: true },
|
|
171
194
|
session: P.session,
|
|
@@ -174,21 +197,25 @@ export const MCP_TOOL_DEFS = [
|
|
|
174
197
|
},
|
|
175
198
|
{
|
|
176
199
|
name: "read",
|
|
177
|
-
description: "Without
|
|
200
|
+
description: "Read this first. Both forms carry decisionList: what the user already settled in this project; it stands unless they say otherwise. Without design: every design (slug, states, variants, open questions), flows, plus pending. With design: its notes (a spec; the user's edits stand), states, variants, questions with answers, parts, knobs and settled values (every answer's set and tuning sent so far), plus pending. Never consumes feedback.",
|
|
178
201
|
params: {
|
|
179
|
-
|
|
202
|
+
design: { t: "string", d: field.design },
|
|
180
203
|
project: P.project,
|
|
181
204
|
body: { t: "boolean", d: "Include each variant's surfaces" },
|
|
182
205
|
history: { t: "boolean", d: "Include version rows" },
|
|
183
206
|
tree: {
|
|
184
207
|
t: "boolean",
|
|
185
|
-
d: "Without
|
|
208
|
+
d: "Without design: the project as a tree (each design's variants with status and forkedFrom, the designs it embeds and is embedded in)",
|
|
209
|
+
},
|
|
210
|
+
decisions: {
|
|
211
|
+
t: "boolean",
|
|
212
|
+
d: "Only the whole decision list, when decisionList says it was cut",
|
|
186
213
|
},
|
|
187
214
|
},
|
|
188
215
|
},
|
|
189
216
|
{
|
|
190
217
|
name: "feedback",
|
|
191
|
-
description: "Returns at once: what the user sent since you last heard (one batch per
|
|
218
|
+
description: "Returns at once: what the user sent since you last heard (one batch per design: reply answers, borrowed parts, tuned knobs, part comments, verdict; thread messages; inUse/archived), delivered once, plus pending (viewer open, draft progress).",
|
|
192
219
|
params: {
|
|
193
220
|
project: P.project,
|
|
194
221
|
session: { t: "string", d: "Session id returned by publish", req: true },
|
|
@@ -197,7 +224,7 @@ export const MCP_TOOL_DEFS = [
|
|
|
197
224
|
},
|
|
198
225
|
{
|
|
199
226
|
name: "say",
|
|
200
|
-
description: "Post a short plain-text message in a
|
|
227
|
+
description: "Post a short plain-text message in a design's thread. Returns feedback.",
|
|
201
228
|
params: {
|
|
202
229
|
...VARIANT_REF,
|
|
203
230
|
message: { t: "string", d: "Plain text", req: true },
|
|
@@ -205,11 +232,51 @@ export const MCP_TOOL_DEFS = [
|
|
|
205
232
|
},
|
|
206
233
|
httpOnly: ["session"],
|
|
207
234
|
},
|
|
235
|
+
{
|
|
236
|
+
name: "move",
|
|
237
|
+
description: "Tidy up variants: to or toState moves them there and out of here; archive takes them out of view; restore brings them back; a group goes along; one in use stays. Only when the user asks: hideOlder hides each variant's older versions (variants optional: all of them), and deleteOlder deletes them for good, skipping any that a reply, comment, question, embed, copy or fork still points at, or that is in use. Confirm a delete first: it can't be undone. Returns feedback.",
|
|
238
|
+
params: {
|
|
239
|
+
design: P.design,
|
|
240
|
+
state: P.state,
|
|
241
|
+
variants: { t: "strings", d: "Variant names; required except with hideOlder or deleteOlder" },
|
|
242
|
+
to: { t: "string", d: "Target design; default this one" },
|
|
243
|
+
toState: { t: "string", d: "Target state; created if new" },
|
|
244
|
+
archive: { t: "boolean" },
|
|
245
|
+
restore: { t: "boolean" },
|
|
246
|
+
hideOlder: { t: "boolean", d: "Hide all but each variant's newest visible version" },
|
|
247
|
+
deleteOlder: { t: "boolean", d: "Delete them instead; permanent, skips what's referenced" },
|
|
248
|
+
project: P.project,
|
|
249
|
+
session: P.session,
|
|
250
|
+
},
|
|
251
|
+
httpOnly: ["session"],
|
|
252
|
+
},
|
|
208
253
|
{
|
|
209
254
|
name: "export",
|
|
210
255
|
description: "The html in use (else current) per state, version history, screenshot URL, knobs, the settled values and the last reply.",
|
|
211
256
|
params: VARIANT_REF,
|
|
212
257
|
},
|
|
258
|
+
{
|
|
259
|
+
name: "edit",
|
|
260
|
+
description: "Change a design without a new version; a new slug rewrites every reference to the old one. Without design, rename renames the project and areaOrder orders its areas. Returns feedback.",
|
|
261
|
+
params: {
|
|
262
|
+
design: { t: "string", d: field.design },
|
|
263
|
+
project: P.project,
|
|
264
|
+
slug: { t: "string" },
|
|
265
|
+
title: { t: "string" },
|
|
266
|
+
kind: { t: "enum", values: ["screen", "component", "idea"] },
|
|
267
|
+
area: { t: "string" },
|
|
268
|
+
source: { t: "string" },
|
|
269
|
+
firstState: { t: "string", d: "Names a design's one unnamed state, so it can have more" },
|
|
270
|
+
notes: {
|
|
271
|
+
t: "string",
|
|
272
|
+
d: `A short plain-text spec, at most ${MAX_NOTES} characters; "" clears. Not applied over the user's own edit, and the result says so`,
|
|
273
|
+
},
|
|
274
|
+
rename: { t: "string", d: "New project name" },
|
|
275
|
+
areaOrder: { t: "strings", d: "Without design: Home's area order" },
|
|
276
|
+
session: P.session,
|
|
277
|
+
},
|
|
278
|
+
httpOnly: ["session"],
|
|
279
|
+
},
|
|
213
280
|
{
|
|
214
281
|
name: "upload",
|
|
215
282
|
description: "Upload bytes and return id and URL. Reference id as an image surface's assetId.",
|
|
@@ -263,52 +330,69 @@ const bag = z.record(z.string(), z.unknown());
|
|
|
263
330
|
const variantRef = o({ state: nullStr, variant: str });
|
|
264
331
|
const inUseRef = o({ state: nullStr, variant: str, version: num });
|
|
265
332
|
const partsByState = z.array(o({ state: nullStr, parts: z.array(o({ name: str })) }));
|
|
266
|
-
const flowSchema = o({ name: str, steps: z.array(o({
|
|
333
|
+
const flowSchema = o({ name: str, steps: z.array(o({ design: str, state: nullStr })) });
|
|
267
334
|
const feedbackSchema = o({
|
|
268
|
-
|
|
335
|
+
design: nullStr,
|
|
269
336
|
reply: o({
|
|
270
337
|
asks: z.array(o({
|
|
271
338
|
ask: str,
|
|
272
339
|
chosen: z.array(o({ label: str, other: z.literal(true).optional() })),
|
|
273
|
-
|
|
340
|
+
description: str.optional(),
|
|
274
341
|
})),
|
|
275
|
-
|
|
342
|
+
borrowed: bag.describe("Part → the variant to take it from"),
|
|
276
343
|
tuned: bag,
|
|
277
344
|
comments: z.array(o({ part: nullStr, text: str })),
|
|
278
345
|
reactions: strs.optional().describe('One-tap reactions, e.g. 👍 part "hero" (variant B)'),
|
|
346
|
+
verdict: o({ kind: z.enum(["use", "revise"]), state: nullStr, variant: str })
|
|
347
|
+
.optional()
|
|
348
|
+
.describe("Use this or New version on one variant"),
|
|
349
|
+
more: o({
|
|
350
|
+
ask: str,
|
|
351
|
+
text: str,
|
|
352
|
+
existing: strs,
|
|
353
|
+
note: str.optional(),
|
|
354
|
+
answered: z.array(o({ ask: str, text: str, chosen: strs, comment: str.optional() })),
|
|
355
|
+
})
|
|
356
|
+
.optional()
|
|
357
|
+
.describe("The user wants more answers to this question: ask it again, same id, with them"),
|
|
279
358
|
})
|
|
280
359
|
.nullable()
|
|
281
|
-
.describe("The user's Send; null if only
|
|
282
|
-
|
|
283
|
-
|
|
360
|
+
.describe("The user's Send; null if only messages"),
|
|
361
|
+
messages: z.array(o({ seq: num, text: str })).describe("What the user wrote in the thread"),
|
|
362
|
+
inUse: z.array(inUseRef).describe("In use, each at the version the user put in use"),
|
|
284
363
|
archived: z.array(variantRef),
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
.
|
|
288
|
-
.optional()
|
|
289
|
-
|
|
290
|
-
dismissed: z
|
|
291
|
-
.array(o({ ask: str, text: str }))
|
|
364
|
+
edited: o({
|
|
365
|
+
kind: str.optional(),
|
|
366
|
+
area: nullStr.optional(),
|
|
367
|
+
notes: nullStr.optional(),
|
|
368
|
+
})
|
|
292
369
|
.optional()
|
|
293
|
-
.describe("The
|
|
370
|
+
.describe("The user moved the design from Home (its kind or area now) or edited its notes"),
|
|
294
371
|
}).meta({ id: "Feedback" });
|
|
295
372
|
// Typed once, on feedback: repeating it on every write would cost more than
|
|
296
373
|
// the rest of the write's output schema.
|
|
297
374
|
const feedbackRide = z.array(bag).describe("Feedback batches as the feedback tool returns");
|
|
298
375
|
const pendingSchema = o({
|
|
299
|
-
|
|
300
|
-
|
|
376
|
+
design: str,
|
|
377
|
+
archived: z.literal(true).optional(),
|
|
301
378
|
viewerOpen: z.boolean(),
|
|
302
379
|
draft: o({ answered: num, of: num, comments: num, touchedAt: str }).nullable(),
|
|
303
380
|
}).meta({ id: "Pending" });
|
|
304
381
|
const writeOutput = {
|
|
305
|
-
post: o({ state: nullStr, variant: str, version: num })
|
|
382
|
+
post: o({ state: nullStr, variant: str, version: num })
|
|
383
|
+
.optional()
|
|
384
|
+
.describe("Absent when planned: nothing is drawn yet"),
|
|
385
|
+
planned: z.boolean().optional(),
|
|
306
386
|
sessionId: str,
|
|
307
387
|
url: str.describe("Viewer link for the user"),
|
|
308
|
-
parts: partsByState,
|
|
388
|
+
parts: partsByState.optional(),
|
|
309
389
|
partChanges: o({ vanished: strs, renamed: z.array(o({ from: str, to: str })) }).optional(),
|
|
310
390
|
nudges: strs.optional(),
|
|
311
391
|
suggestedAsk: bag.optional().describe("Send with ask when a nudge names it"),
|
|
392
|
+
suggestedFillAsks: z
|
|
393
|
+
.array(bag)
|
|
394
|
+
.optional()
|
|
395
|
+
.describe("A question per part with fills that none sets; send with ask"),
|
|
312
396
|
flow: flowSchema.optional(),
|
|
313
397
|
feedback: feedbackRide,
|
|
314
398
|
};
|
|
@@ -338,21 +422,43 @@ const OUTPUT_SCHEMAS = {
|
|
|
338
422
|
feedback: feedbackRide,
|
|
339
423
|
},
|
|
340
424
|
feedback: {
|
|
341
|
-
feedback: z.array(feedbackSchema).describe("One batch per
|
|
425
|
+
feedback: z.array(feedbackSchema).describe("One batch per design; empty when nothing is new"),
|
|
342
426
|
pending: z.array(pendingSchema),
|
|
343
427
|
},
|
|
344
428
|
say: { feedback: feedbackRide },
|
|
429
|
+
move: {
|
|
430
|
+
variants: z.array(o({ state: str.nullable(), variant: str, status: str })),
|
|
431
|
+
versions: z
|
|
432
|
+
.array(o({ state: str.nullable(), variant: str, version: num }))
|
|
433
|
+
.optional()
|
|
434
|
+
.describe("With hideOlder or deleteOlder: what was hidden or deleted"),
|
|
435
|
+
skipped: z.array(o({ state: str.nullable().optional(), variant: str, version: num.optional(), why: str })),
|
|
436
|
+
to: o({ design: str, title: str, state: str.nullable() }).optional(),
|
|
437
|
+
saved: z
|
|
438
|
+
.array(o({ state: str.nullable(), variant: str, already: z.boolean() }))
|
|
439
|
+
.optional()
|
|
440
|
+
.describe("What each is called there"),
|
|
441
|
+
url: str,
|
|
442
|
+
feedback: feedbackRide,
|
|
443
|
+
},
|
|
444
|
+
edit: {
|
|
445
|
+
renamed: o({ from: str, to: str }).optional(),
|
|
446
|
+
areaOrder: strs.optional(),
|
|
447
|
+
url: str,
|
|
448
|
+
nudges: strs.optional(),
|
|
449
|
+
feedback: feedbackRide,
|
|
450
|
+
},
|
|
345
451
|
read: {
|
|
346
|
-
|
|
452
|
+
designs: z.array(o({ slug: str, states: strs, variants: num, open: num })).optional(),
|
|
347
453
|
states: strs.optional(),
|
|
348
454
|
asks: z.array(o({ id: str, text: str })).optional(),
|
|
349
455
|
variants: z
|
|
350
456
|
.array(o({
|
|
351
457
|
state: nullStr,
|
|
352
458
|
variant: str,
|
|
353
|
-
status:
|
|
459
|
+
status: z.enum(["open", "inUse", "archived"]),
|
|
354
460
|
version: num,
|
|
355
|
-
|
|
461
|
+
inUseVersion: num.optional().describe("Set while in use: the version in use"),
|
|
356
462
|
history: z
|
|
357
463
|
.array(o({ version: num, hidden: z.boolean().optional() }))
|
|
358
464
|
.optional()
|
|
@@ -367,7 +473,20 @@ const OUTPUT_SCHEMAS = {
|
|
|
367
473
|
settled: bag.optional().describe("Every knob value the user has sent, later replies winning"),
|
|
368
474
|
tree: z.array(bag).optional(),
|
|
369
475
|
flows: z.array(flowSchema).optional(),
|
|
370
|
-
|
|
476
|
+
decisionList: o({
|
|
477
|
+
total: num,
|
|
478
|
+
lines: z.array(o({
|
|
479
|
+
design: str,
|
|
480
|
+
text: str,
|
|
481
|
+
here: z.boolean().optional().describe("From the design read"),
|
|
482
|
+
edited: z.boolean().optional(),
|
|
483
|
+
archived: z.boolean().optional(),
|
|
484
|
+
})),
|
|
485
|
+
more: str.optional(),
|
|
486
|
+
}).optional(),
|
|
487
|
+
total: num.optional(),
|
|
488
|
+
lines: z.array(bag).optional(),
|
|
489
|
+
pending: z.union([z.array(pendingSchema), pendingSchema]).optional(),
|
|
371
490
|
},
|
|
372
491
|
export: {
|
|
373
492
|
states: z.array(o({
|
|
@@ -375,7 +494,8 @@ const OUTPUT_SCHEMAS = {
|
|
|
375
494
|
variant: str,
|
|
376
495
|
version: num,
|
|
377
496
|
latest: num.optional().describe("Your newer version, not in use yet"),
|
|
378
|
-
html: str,
|
|
497
|
+
html: str.describe("With fills: only the chosen ones"),
|
|
498
|
+
allFills: str.optional().describe("With fills: the html with every fill"),
|
|
379
499
|
markdown: str,
|
|
380
500
|
})),
|
|
381
501
|
settled: bag,
|
|
@@ -395,7 +515,9 @@ function shapeFor(tool, transport) {
|
|
|
395
515
|
shape[key] =
|
|
396
516
|
transport === "stdio" && key === "html" && tool.name === "publish"
|
|
397
517
|
? z.string().optional().describe(`${field.html}; or a file path`)
|
|
398
|
-
:
|
|
518
|
+
: transport === "stdio" && key === "poster" && tool.name === "publish"
|
|
519
|
+
? z.string().optional().describe(`${p.d}; or an image file path`)
|
|
520
|
+
: zodParam(p);
|
|
399
521
|
}
|
|
400
522
|
return shape;
|
|
401
523
|
}
|
package/dist/server/parts.js
CHANGED
|
@@ -48,8 +48,16 @@ export function partsInHtml(html) {
|
|
|
48
48
|
}
|
|
49
49
|
return [...byName.values()];
|
|
50
50
|
}
|
|
51
|
+
// Cached per surfaces array, which a stored post never mutates: a design's view
|
|
52
|
+
// asks for every variant's parts on every read. Callers must not change the result.
|
|
53
|
+
const bySurfaces = new WeakMap();
|
|
51
54
|
export function partsInSurfaces(surfaces) {
|
|
52
|
-
|
|
55
|
+
const hit = bySurfaces.get(surfaces);
|
|
56
|
+
if (hit)
|
|
57
|
+
return hit;
|
|
58
|
+
const parts = mergeParts(surfaces.flatMap((s) => (s.kind === "html" ? [partsInHtml(s.html)] : [])));
|
|
59
|
+
bySurfaces.set(surfaces, parts);
|
|
60
|
+
return parts;
|
|
53
61
|
}
|
|
54
62
|
// Union several part lists by name, first occurrence first.
|
|
55
63
|
export function mergeParts(lists) {
|
|
@@ -44,17 +44,20 @@ const strictHtmlSurface = z.object({
|
|
|
44
44
|
kind: z.literal("html"),
|
|
45
45
|
html: requiredString("html"),
|
|
46
46
|
kits: z.array(z.string()).optional(),
|
|
47
|
+
poster: z.string().min(1).optional(),
|
|
47
48
|
});
|
|
48
49
|
const looseHtmlSurface = z
|
|
49
50
|
.object({
|
|
50
51
|
kind: z.literal("html"),
|
|
51
52
|
html: requiredString("html"),
|
|
52
53
|
kits: z.unknown().optional(),
|
|
54
|
+
poster: optionalLooseString,
|
|
53
55
|
})
|
|
54
56
|
.transform((p) => ({
|
|
55
57
|
kind: "html",
|
|
56
58
|
html: p.html,
|
|
57
59
|
...(Array.isArray(p.kits) ? { kits: p.kits.filter((k) => typeof k === "string") } : {}),
|
|
60
|
+
...(p.poster ? { poster: p.poster } : {}),
|
|
58
61
|
}));
|
|
59
62
|
// Strict mode rejects an unknown id with the valid set, so a CLI/REST typo is a
|
|
60
63
|
// clean 400; loose mode filters unknown ids out rather than dropping the whole
|
package/dist/server/run.d.ts
CHANGED
|
@@ -77,6 +77,6 @@ export interface RunDeps {
|
|
|
77
77
|
status: number;
|
|
78
78
|
}>;
|
|
79
79
|
}
|
|
80
|
-
export declare const RUN_UNAVAILABLE = "run is not available on this deployment: it has no script sandbox. Use the
|
|
80
|
+
export declare const RUN_UNAVAILABLE = "run is not available on this deployment: it has no script sandbox. Use the design tools (or REST) instead";
|
|
81
81
|
export declare function cut(s: string, max: number): string;
|
|
82
82
|
export declare function createRunFlow(deps: RunDeps): (body: any, ctx: FlowContext) => Promise<FlowResult>;
|
package/dist/server/run.js
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// that is counted, bounded and logged.
|
|
10
10
|
import { decodeBase64 } from "./base64.js";
|
|
11
11
|
import { isGuideTopic, unknownTopicMessage } from "./designGuide.js";
|
|
12
|
-
import { unwrap } from "./mcpHttp.js";
|
|
12
|
+
import { editCall, unwrap } from "./mcpHttp.js";
|
|
13
13
|
import { RUN_FUNCTIONS } from "./runApi.js";
|
|
14
14
|
// A script never waits for the user, so the wall clock is only a sandbox guard:
|
|
15
15
|
// 10 s clears a run of publishes with room to spare. 32 MiB and a 256 KiB stack are what the spike showed safe (512 KiB overflows the host
|
|
@@ -26,7 +26,7 @@ export const DEFAULT_RUN_LIMITS = {
|
|
|
26
26
|
maxInflight: 4,
|
|
27
27
|
maxRuns: 4,
|
|
28
28
|
};
|
|
29
|
-
export const RUN_UNAVAILABLE = "run is not available on this deployment: it has no script sandbox. Use the
|
|
29
|
+
export const RUN_UNAVAILABLE = "run is not available on this deployment: it has no script sandbox. Use the design tools (or REST) instead";
|
|
30
30
|
const text = (v, max) => typeof v === "string" && v.trim() ? v.trim().slice(0, max) : undefined;
|
|
31
31
|
const plain = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
32
32
|
// Keep the head and the tail: the start says what a value is, the end is where
|
|
@@ -40,7 +40,7 @@ export function cut(s, max) {
|
|
|
40
40
|
}
|
|
41
41
|
function writeSummary(body) {
|
|
42
42
|
const post = body?.post;
|
|
43
|
-
const slug = body?.
|
|
43
|
+
const slug = body?.design?.slug ?? "?";
|
|
44
44
|
if (!post)
|
|
45
45
|
return slug;
|
|
46
46
|
return `${slug}/${post.state ? `${post.state}/` : ""}${post.variant} v${post.version}`;
|
|
@@ -152,26 +152,30 @@ export function createRunFlow(deps) {
|
|
|
152
152
|
throw new Error(unknownTopicMessage(String(topic)));
|
|
153
153
|
return [await deps.guide("", topic), String(topic)];
|
|
154
154
|
},
|
|
155
|
-
async read([
|
|
156
|
-
if (
|
|
155
|
+
async read([design, opts]) {
|
|
156
|
+
if (plain(opts).decisions === true) {
|
|
157
|
+
const value = await take(flows.decisions({ project: await projectOf() }));
|
|
158
|
+
return [value, `${value.total} decisions`];
|
|
159
|
+
}
|
|
160
|
+
if ((design === undefined || design === null) && plain(opts).tree === true) {
|
|
157
161
|
const value = await take(flows.tree({ project: await projectOf() }));
|
|
158
|
-
return [value, `${value.tree.length}
|
|
162
|
+
return [value, `${value.tree.length} designs`];
|
|
159
163
|
}
|
|
160
|
-
if (
|
|
164
|
+
if (design === undefined || design === null) {
|
|
161
165
|
const value = await take(flows.list({ project: await projectOf() }));
|
|
162
|
-
return [value, `${value.
|
|
166
|
+
return [value, `${value.designs.length} designs`];
|
|
163
167
|
}
|
|
164
168
|
const o = plain(opts);
|
|
165
|
-
const value = await take(flows.get(
|
|
166
|
-
return [value, String(value?.slug ??
|
|
169
|
+
const value = await take(flows.get(design, { project, body: o.body === true, history: o.history === true }));
|
|
170
|
+
return [value, String(value?.slug ?? design)];
|
|
167
171
|
},
|
|
168
172
|
async publish([v]) {
|
|
169
173
|
const value = await take(flows.publish({ ...(await mine(plain(v))), agent }, flowCtx));
|
|
170
174
|
return [value, writeSummary(value)];
|
|
171
175
|
},
|
|
172
|
-
async ask([
|
|
173
|
-
const value = await take(flows.ask(
|
|
174
|
-
return [value, `${value.
|
|
176
|
+
async ask([design, asks]) {
|
|
177
|
+
const value = await take(flows.ask(design, await mine({ asks }), flowCtx));
|
|
178
|
+
return [value, `${value.design} ${value.asks.length} ask(s)`];
|
|
175
179
|
},
|
|
176
180
|
async feedback() {
|
|
177
181
|
const value = await take(flows.feedback(await mine({}), flowCtx));
|
|
@@ -180,8 +184,18 @@ export function createRunFlow(deps) {
|
|
|
180
184
|
},
|
|
181
185
|
async say([v, message]) {
|
|
182
186
|
const r = plain(v);
|
|
183
|
-
await take(flows.say(r.
|
|
184
|
-
return [null, String(r.
|
|
187
|
+
await take(flows.say(r.design, { ...(await mine(r)), message }, flowCtx));
|
|
188
|
+
return [null, String(r.design ?? "?")];
|
|
189
|
+
},
|
|
190
|
+
async move([v]) {
|
|
191
|
+
const r = plain(v);
|
|
192
|
+
const value = await take(flows.move(r.design, await mine(r), flowCtx));
|
|
193
|
+
return [value, `${value.variants.length} moved`];
|
|
194
|
+
},
|
|
195
|
+
async edit([v]) {
|
|
196
|
+
const r = await mine(plain(v));
|
|
197
|
+
const value = await take(editCall(flows, r, flowCtx, project));
|
|
198
|
+
return [value, value.renamed ? `${value.renamed.from} → ${value.renamed.to}` : "edited"];
|
|
185
199
|
},
|
|
186
200
|
async upload([data, opts]) {
|
|
187
201
|
if (typeof data !== "string" || !data)
|
|
@@ -201,8 +215,8 @@ export function createRunFlow(deps) {
|
|
|
201
215
|
},
|
|
202
216
|
async export([v]) {
|
|
203
217
|
const r = plain(v);
|
|
204
|
-
const value = await take(flows.
|
|
205
|
-
return [value, `${value.
|
|
218
|
+
const value = await take(flows.exportDesign(r.design, { ...r, project }, flowCtx));
|
|
219
|
+
return [value, `${value.design} ${value.states.length} state(s)`];
|
|
206
220
|
},
|
|
207
221
|
};
|
|
208
222
|
const call = (name, argsJson) => {
|
package/dist/server/runApi.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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 = {
|
|
2
|
-
export declare const RUN_FUNCTIONS: readonly ["guide", "read", "publish", "ask", "feedback", "say", "export", "upload"];
|
|
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 = { design: 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?: \"design\" | \"state\" | \"part\"; state?: string; part?: string; multi?: boolean;\n options: { id?: string; label: string; variant?: string; set?: Knobs; recommended?: boolean }[];\n};\ntype Written = {\n post?: { state: string | null; variant: string; version: number }; planned?: true; url: string;\n parts?: { state: string | null; parts: { name: string }[] }[];\n partChanges?: { vanished: string[]; renamed: { from: string; to: string }[] };\n nudges?: string[]; suggestedAsk?: Ask; suggestedFillAsks?: Ask[]; // send with ask when nudged\n};\ntype Feedback = {\n design: string | null;\n reply: null | { asks: { ask: string; chosen: { id: string; label: string; other?: true }[]; comment?: string }[]; borrowed: Record<string, string>; tuned: Knobs; comments: { part: string | null; text: string }[]; verdict?: { kind: \"use\" | \"revise\"; state: string | null; variant: string; version?: number } };\n messages: { text: string; state: string | null; variant: string | null }[];\n inUse: { state: string | null; variant: string; version: number }[]; archived: { state: string | null; variant: string }[];\n};\ntype Pending = { design: string; archived?: true; 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 /** Read first: decisionList is what the user already settled; it stands unless they say otherwise.\n * No design: every design, the flows, plus pending. With one: its states, variants, asks, parts, knobs.\n * opts.decisions: only the whole list, when a read cut it. */\n read(design?: string, opts?: { body?: boolean; history?: boolean; decisions?: boolean }): Promise<unknown>;\n /** Creates or versions (design, 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 fill of a part.\n * None of them, with kind screen or component: plans it, for a later publish to draw. */\n publish(v: Ref & { title?: string; kind?: \"screen\" | \"component\" | \"idea\"; area?: string; source?: string; html?: string; poster?: string; surfaces?: (Surface | { id: string })[]; parts?: Record<string, string>; fills?: Record<string, string>; knobs?: Knobs; variantKnobs?: Knobs; from?: number; prompt?: string; description?: string; widths?: number[]; flow?: string; step?: number }): Promise<Written>;\n ask(design: 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 /** to or toState moves variants there; archive hides them; restore brings them back. */\n move(v: Ref & { variants: string[]; to?: string; toState?: string; archive?: boolean; restore?: boolean }): Promise<{ variants: { state: string | null; variant: string; status: string }[]; skipped: { variant: string; why: string }[]; url: string }>;\n /** No new version; \"\" clears. { rename } or { areaOrder } without design edits the project.\n * notes: a short plain-text spec; not applied over the user's own edit (notes.applied says). */\n edit(v: { design?: string; slug?: string; title?: string; kind?: string; area?: string; source?: string; firstState?: string; notes?: string; rename?: string; areaOrder?: string[] }): Promise<{ url: string; notes?: { applied: boolean; text?: string } }>;\n export(v: Ref): Promise<{ states: { state: string | null; variant: string; version: number; html: string; allFills?: 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
|
+
export declare const RUN_FUNCTIONS: readonly ["guide", "read", "publish", "ask", "feedback", "say", "move", "edit", "export", "upload"];
|
|
3
3
|
export type RunFunction = (typeof RUN_FUNCTIONS)[number];
|
|
4
|
-
export declare const RUN_EXAMPLE = "const html = (tone) => `<section data-part=\"hero\" class=\"${tone}\"><h1>Writer</h1></section>`;\nawait mockpit.publish({
|
|
4
|
+
export declare const RUN_EXAMPLE = "const html = (tone) => `<section data-part=\"hero\" class=\"${tone}\"><h1>Writer</h1></section>`;\nawait mockpit.publish({ design: \"writer\", variant: \"calm\", html: html(\"calm\") });\nawait mockpit.publish({ design: \"writer\", variant: \"bold\", html: html(\"bold\") });\nconst { url } = await mockpit.ask(\"writer\", [{ id: \"tone\", text: \"Calm or bold?\",\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;";
|
|
5
5
|
export declare const RUN_DESCRIPTION: string;
|
|
6
6
|
export declare const RUN_INSTRUCTIONS: string;
|