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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Ask, type Asset, type Comment, type CommentQuery, type CommitReplyInput, type CreateAssetInput, type CreateCommentInput, type CreateMockInput, type CreatePostInput, type CreateSessionInput, type Draft, type Knobs, type Mock, type MockKind, type Post, type PostQuery, type PostStatus, type PostVersion, type Session, type
|
|
1
|
+
import { type Ask, type Asset, type Comment, type CommentQuery, type CommitReplyInput, type CreateAssetInput, type CreateCommentInput, type CreateMockInput, type CreatePostInput, type CreateSessionInput, type Draft, type Knobs, type Mock, type MockKind, type Post, type PostQuery, type PostStatus, type PostVersion, type Session, type Embed, type Flow, type FlowStep, type ForkRef, type SqlStorage, type Store, type Surface, type UpdateMockInput, type UpdatePostInput } from "./types.ts";
|
|
2
2
|
export declare class SqlStore implements Store {
|
|
3
3
|
private sql;
|
|
4
4
|
private assetRefCache;
|
|
@@ -26,7 +26,7 @@ export declare class SqlStore implements Store {
|
|
|
26
26
|
private rowToPost;
|
|
27
27
|
private rowToAsset;
|
|
28
28
|
private rowToComment;
|
|
29
|
-
listSessions(): Promise<Session[]>;
|
|
29
|
+
listSessions(project?: string): Promise<Session[]>;
|
|
30
30
|
getSession(id: string): Promise<Session | null>;
|
|
31
31
|
getSessionByKey(key: string): Promise<Session | null>;
|
|
32
32
|
createSession(input: CreateSessionInput): Promise<Session>;
|
|
@@ -34,6 +34,8 @@ export declare class SqlStore implements Store {
|
|
|
34
34
|
removeSession(id: string): Promise<boolean>;
|
|
35
35
|
private touch;
|
|
36
36
|
markAgentSeen(sessionId: string, seq: number): Promise<void>;
|
|
37
|
+
touchAgent(sessionId: string): Promise<void>;
|
|
38
|
+
handOff(sessionId: string, seen: Pick<Session, "agentSeq" | "agentActiveAt">, toSeq: number): Promise<boolean>;
|
|
37
39
|
getSetting(key: string): Promise<string | null>;
|
|
38
40
|
setSetting(key: string, value: string): Promise<void>;
|
|
39
41
|
listProjects(): Promise<import("./types.ts").ProjectSummary[]>;
|
|
@@ -43,6 +45,10 @@ export declare class SqlStore implements Store {
|
|
|
43
45
|
createMock(input: CreateMockInput): Promise<Mock>;
|
|
44
46
|
updateMock(id: string, patch: UpdateMockInput): Promise<Mock | null>;
|
|
45
47
|
removeMock(id: string): Promise<boolean>;
|
|
48
|
+
private flowsOf;
|
|
49
|
+
private writeFlow;
|
|
50
|
+
listFlows(project: string): Promise<Flow[]>;
|
|
51
|
+
placeFlowStep(project: string, name: string, screen: FlowStep, step: number): Promise<Flow | null>;
|
|
46
52
|
putDraft(mockId: string, draft: Draft | null): Promise<{
|
|
47
53
|
draft: Draft | null;
|
|
48
54
|
id: string;
|
|
@@ -53,7 +59,9 @@ export declare class SqlStore implements Store {
|
|
|
53
59
|
states: string[];
|
|
54
60
|
asks: Ask[];
|
|
55
61
|
knobs: Knobs;
|
|
62
|
+
widths: number[];
|
|
56
63
|
sessionId: string | null;
|
|
64
|
+
resolvedAt: string | null;
|
|
57
65
|
createdAt: string;
|
|
58
66
|
updatedAt: string;
|
|
59
67
|
} | null>;
|
|
@@ -65,13 +73,14 @@ export declare class SqlStore implements Store {
|
|
|
65
73
|
createPost(input: CreatePostInput): Promise<Post | null>;
|
|
66
74
|
updatePost(id: string, patch: UpdatePostInput): Promise<{
|
|
67
75
|
author?: string | undefined;
|
|
68
|
-
|
|
76
|
+
embeds: Embed[];
|
|
69
77
|
state: string | null;
|
|
70
78
|
version: number;
|
|
71
79
|
updatedAt: string;
|
|
72
80
|
history: PostVersion[];
|
|
73
81
|
from: number;
|
|
74
82
|
prompt: string;
|
|
83
|
+
note?: string | undefined;
|
|
75
84
|
knobs?: Knobs | undefined;
|
|
76
85
|
title: string;
|
|
77
86
|
surfaces: Surface[];
|
|
@@ -81,9 +90,13 @@ export declare class SqlStore implements Store {
|
|
|
81
90
|
variant: string;
|
|
82
91
|
status: PostStatus;
|
|
83
92
|
createdAt: string;
|
|
93
|
+
forkedFrom?: ForkRef;
|
|
94
|
+
starred?: true;
|
|
95
|
+
acceptedVersion?: number;
|
|
96
|
+
hiddenVersions?: number[];
|
|
84
97
|
} | null>;
|
|
85
98
|
removePost(id: string): Promise<boolean>;
|
|
86
|
-
setPostStatus(id: string, status: PostStatus): Promise<{
|
|
99
|
+
setPostStatus(id: string, status: PostStatus, acceptedVersion?: number): Promise<{
|
|
87
100
|
status: PostStatus;
|
|
88
101
|
id: string;
|
|
89
102
|
sessionId: string;
|
|
@@ -97,11 +110,85 @@ export declare class SqlStore implements Store {
|
|
|
97
110
|
version: number;
|
|
98
111
|
history: PostVersion[];
|
|
99
112
|
knobs?: Knobs;
|
|
100
|
-
|
|
113
|
+
note?: string;
|
|
114
|
+
embeds: Embed[];
|
|
115
|
+
forkedFrom?: ForkRef;
|
|
116
|
+
starred?: true;
|
|
117
|
+
hiddenVersions?: number[];
|
|
118
|
+
from?: number;
|
|
119
|
+
prompt?: string;
|
|
120
|
+
author?: string;
|
|
121
|
+
} | {
|
|
122
|
+
status: PostStatus;
|
|
123
|
+
acceptedVersion: number;
|
|
124
|
+
id: string;
|
|
125
|
+
sessionId: string;
|
|
126
|
+
mock: string;
|
|
127
|
+
state: string | null;
|
|
128
|
+
variant: string;
|
|
129
|
+
title: string;
|
|
130
|
+
surfaces: Surface[];
|
|
131
|
+
createdAt: string;
|
|
132
|
+
updatedAt: string;
|
|
133
|
+
version: number;
|
|
134
|
+
history: PostVersion[];
|
|
135
|
+
knobs?: Knobs;
|
|
136
|
+
note?: string;
|
|
137
|
+
embeds: Embed[];
|
|
138
|
+
forkedFrom?: ForkRef;
|
|
139
|
+
starred?: true;
|
|
140
|
+
hiddenVersions?: number[];
|
|
141
|
+
from?: number;
|
|
142
|
+
prompt?: string;
|
|
143
|
+
author?: string;
|
|
144
|
+
} | null>;
|
|
145
|
+
setPostStarred(id: string, starred: boolean): Promise<{
|
|
146
|
+
id: string;
|
|
147
|
+
sessionId: string;
|
|
148
|
+
mock: string;
|
|
149
|
+
state: string | null;
|
|
150
|
+
variant: string;
|
|
151
|
+
status: PostStatus;
|
|
152
|
+
title: string;
|
|
153
|
+
surfaces: Surface[];
|
|
154
|
+
createdAt: string;
|
|
155
|
+
updatedAt: string;
|
|
156
|
+
version: number;
|
|
157
|
+
history: PostVersion[];
|
|
158
|
+
knobs?: Knobs;
|
|
159
|
+
note?: string;
|
|
160
|
+
embeds: Embed[];
|
|
161
|
+
forkedFrom?: ForkRef;
|
|
162
|
+
acceptedVersion?: number;
|
|
163
|
+
hiddenVersions?: number[];
|
|
164
|
+
from?: number;
|
|
165
|
+
prompt?: string;
|
|
166
|
+
author?: string;
|
|
167
|
+
} | {
|
|
168
|
+
starred: true;
|
|
169
|
+
id: string;
|
|
170
|
+
sessionId: string;
|
|
171
|
+
mock: string;
|
|
172
|
+
state: string | null;
|
|
173
|
+
variant: string;
|
|
174
|
+
status: PostStatus;
|
|
175
|
+
title: string;
|
|
176
|
+
surfaces: Surface[];
|
|
177
|
+
createdAt: string;
|
|
178
|
+
updatedAt: string;
|
|
179
|
+
version: number;
|
|
180
|
+
history: PostVersion[];
|
|
181
|
+
knobs?: Knobs;
|
|
182
|
+
note?: string;
|
|
183
|
+
embeds: Embed[];
|
|
184
|
+
forkedFrom?: ForkRef;
|
|
185
|
+
acceptedVersion?: number;
|
|
186
|
+
hiddenVersions?: number[];
|
|
101
187
|
from?: number;
|
|
102
188
|
prompt?: string;
|
|
103
189
|
author?: string;
|
|
104
190
|
} | null>;
|
|
191
|
+
setVersionHidden(id: string, version: number, hidden: boolean): Promise<Post | null>;
|
|
105
192
|
listComments(query: CommentQuery): Promise<Comment[]>;
|
|
106
193
|
private insertComment;
|
|
107
194
|
createComment(input: CreateCommentInput): Promise<Comment | null>;
|
package/dist/server/sqlStore.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { collectAssetIds, DEFAULT_PROJECT, DEFAULT_VARIANT, hashAssetId, HISTORY_LIMIT, htmlSurface, MAX_WORKSPACE_ASSET_BYTES, newId, normalizeSurfaceIds, projectFromCwd, reservedAgent, selectEvictions, slugify, stripNul, summarizeProjects, } from "./types.js";
|
|
1
|
+
import { collectAssetIds, collectAttachedIds, DEFAULT_PROJECT, DEFAULT_VARIANT, hashAssetId, HISTORY_LIMIT, htmlSurface, MAX_WORKSPACE_ASSET_BYTES, newId, normalizeSurfaceIds, projectFromCwd, reservedAgent, sameScreen, selectEvictions, slugify, stripNul, summarizeProjects, } from "./types.js";
|
|
2
2
|
function parseJson(raw, fallback) {
|
|
3
3
|
if (typeof raw !== "string" || !raw)
|
|
4
4
|
return fallback;
|
|
@@ -14,7 +14,7 @@ const POST_COLUMNS = `
|
|
|
14
14
|
variant TEXT NOT NULL, status TEXT NOT NULL DEFAULT 'open', title TEXT NOT NULL,
|
|
15
15
|
surfaces TEXT NOT NULL, knobs TEXT, slots TEXT NOT NULL DEFAULT '[]',
|
|
16
16
|
createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL, version INTEGER NOT NULL,
|
|
17
|
-
curFrom INTEGER, curPrompt TEXT, curAuthor TEXT`;
|
|
17
|
+
curFrom INTEGER, curPrompt TEXT, curAuthor TEXT, note TEXT`;
|
|
18
18
|
// Store implementation on SQLite — a Durable Object's `ctx.storage.sql` in the
|
|
19
19
|
// Worker, or node:sqlite via an adapter on Node (see server/sqliteStorage.ts).
|
|
20
20
|
// One workspace = one database, so plain SQL with no tenant columns.
|
|
@@ -65,13 +65,14 @@ export class SqlStore {
|
|
|
65
65
|
CREATE TABLE IF NOT EXISTS sessions (
|
|
66
66
|
id TEXT PRIMARY KEY, agent TEXT NOT NULL, title TEXT, cwd TEXT,
|
|
67
67
|
createdAt TEXT NOT NULL, lastActiveAt TEXT NOT NULL,
|
|
68
|
-
agentSeq INTEGER NOT NULL DEFAULT 0, project TEXT, key TEXT
|
|
68
|
+
agentSeq INTEGER NOT NULL DEFAULT 0, project TEXT, key TEXT, agentActiveAt TEXT
|
|
69
69
|
);
|
|
70
70
|
CREATE TABLE IF NOT EXISTS mocks (
|
|
71
71
|
id TEXT PRIMARY KEY, project TEXT NOT NULL, slug TEXT NOT NULL, title TEXT NOT NULL,
|
|
72
72
|
kind TEXT NOT NULL DEFAULT 'component', states TEXT NOT NULL DEFAULT '[]',
|
|
73
73
|
asks TEXT NOT NULL DEFAULT '[]', knobs TEXT NOT NULL DEFAULT '{}', draft TEXT,
|
|
74
|
-
sessionId TEXT, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL
|
|
74
|
+
sessionId TEXT, createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL,
|
|
75
|
+
widths TEXT NOT NULL DEFAULT '[]'
|
|
75
76
|
);
|
|
76
77
|
CREATE TABLE IF NOT EXISTS posts (${POST_COLUMNS});
|
|
77
78
|
CREATE TABLE IF NOT EXISTS post_versions (
|
|
@@ -95,14 +96,34 @@ export class SqlStore {
|
|
|
95
96
|
CREATE TABLE IF NOT EXISTS settings (
|
|
96
97
|
key TEXT PRIMARY KEY, value TEXT NOT NULL
|
|
97
98
|
);
|
|
99
|
+
CREATE TABLE IF NOT EXISTS flows (
|
|
100
|
+
project TEXT NOT NULL, name TEXT NOT NULL, steps TEXT NOT NULL DEFAULT '[]',
|
|
101
|
+
createdAt TEXT NOT NULL, updatedAt TEXT NOT NULL,
|
|
102
|
+
PRIMARY KEY (project, name)
|
|
103
|
+
);
|
|
98
104
|
`);
|
|
99
105
|
this.addMissing("sessions", {
|
|
100
106
|
agentSeq: "INTEGER NOT NULL DEFAULT 0",
|
|
101
107
|
project: "TEXT",
|
|
102
108
|
key: "TEXT",
|
|
109
|
+
agentActiveAt: "TEXT",
|
|
103
110
|
});
|
|
111
|
+
this.addMissing("mocks", { widths: "TEXT NOT NULL DEFAULT '[]'", resolvedAt: "TEXT" });
|
|
104
112
|
if (legacy)
|
|
105
113
|
this.migrateLegacy();
|
|
114
|
+
// After the legacy lift, which rebuilds posts from POST_COLUMNS alone.
|
|
115
|
+
const pinned = this.columns("posts").has("acceptedVersion");
|
|
116
|
+
this.addMissing("posts", {
|
|
117
|
+
note: "TEXT",
|
|
118
|
+
forkedFrom: "TEXT",
|
|
119
|
+
starred: "INTEGER NOT NULL DEFAULT 0",
|
|
120
|
+
acceptedVersion: "INTEGER",
|
|
121
|
+
hiddenVersions: "TEXT",
|
|
122
|
+
});
|
|
123
|
+
// Before the pin, accepted meant "its latest": freeze that as the version in use.
|
|
124
|
+
if (!pinned) {
|
|
125
|
+
this.sql.exec("UPDATE posts SET acceptedVersion = version WHERE status = 'accepted'");
|
|
126
|
+
}
|
|
106
127
|
this.createIndexes();
|
|
107
128
|
}
|
|
108
129
|
createIndexes() {
|
|
@@ -381,6 +402,8 @@ export class SqlStore {
|
|
|
381
402
|
lastActiveAt: r.lastActiveAt,
|
|
382
403
|
agentSeq: r.agentSeq ?? 0,
|
|
383
404
|
project: r.project ?? null,
|
|
405
|
+
agentActiveAt: r.agentActiveAt ?? null,
|
|
406
|
+
keyed: r.key != null,
|
|
384
407
|
};
|
|
385
408
|
}
|
|
386
409
|
rowToMock(r) {
|
|
@@ -393,14 +416,17 @@ export class SqlStore {
|
|
|
393
416
|
states: parseJson(r.states, []),
|
|
394
417
|
asks: parseJson(r.asks, []),
|
|
395
418
|
knobs: parseJson(r.knobs, {}),
|
|
419
|
+
widths: parseJson(r.widths, []),
|
|
396
420
|
draft: parseJson(r.draft, null),
|
|
397
421
|
sessionId: r.sessionId ?? null,
|
|
422
|
+
resolvedAt: r.resolvedAt ?? null,
|
|
398
423
|
createdAt: r.createdAt,
|
|
399
424
|
updatedAt: r.updatedAt,
|
|
400
425
|
};
|
|
401
426
|
}
|
|
402
427
|
rowToPost(r, history) {
|
|
403
428
|
const knobs = parseJson(r.knobs, null);
|
|
429
|
+
const hidden = parseJson(r.hiddenVersions, []);
|
|
404
430
|
return {
|
|
405
431
|
id: r.id,
|
|
406
432
|
sessionId: r.sessionId,
|
|
@@ -415,7 +441,15 @@ export class SqlStore {
|
|
|
415
441
|
version: r.version,
|
|
416
442
|
history,
|
|
417
443
|
...(knobs ? { knobs } : {}),
|
|
418
|
-
|
|
444
|
+
...(r.note == null ? {} : { note: r.note }),
|
|
445
|
+
// The column keeps its first name, from when embeds were called slots.
|
|
446
|
+
embeds: parseJson(r.slots, []),
|
|
447
|
+
...(r.forkedFrom == null ? {} : { forkedFrom: parseJson(r.forkedFrom, null) }),
|
|
448
|
+
...(r.starred ? { starred: true } : {}),
|
|
449
|
+
...(r.status === "accepted" && r.acceptedVersion != null
|
|
450
|
+
? { acceptedVersion: r.acceptedVersion }
|
|
451
|
+
: {}),
|
|
452
|
+
...(hidden.length ? { hiddenVersions: hidden } : {}),
|
|
419
453
|
...(r.curFrom == null ? {} : { from: r.curFrom }),
|
|
420
454
|
...(r.curPrompt == null ? {} : { prompt: r.curPrompt }),
|
|
421
455
|
...(r.curAuthor == null ? {} : { author: r.curAuthor }),
|
|
@@ -458,11 +492,13 @@ export class SqlStore {
|
|
|
458
492
|
};
|
|
459
493
|
}
|
|
460
494
|
// --- sessions ---
|
|
461
|
-
async listSessions() {
|
|
462
|
-
|
|
463
|
-
.exec("SELECT * FROM sessions ORDER BY lastActiveAt DESC")
|
|
464
|
-
.
|
|
465
|
-
|
|
495
|
+
async listSessions(project) {
|
|
496
|
+
const rows = project === undefined
|
|
497
|
+
? this.sql.exec("SELECT * FROM sessions ORDER BY lastActiveAt DESC").toArray()
|
|
498
|
+
: this.sql
|
|
499
|
+
.exec("SELECT * FROM sessions WHERE project = ? ORDER BY lastActiveAt DESC", project)
|
|
500
|
+
.toArray();
|
|
501
|
+
return rows.map((r) => this.rowToSession(r));
|
|
466
502
|
}
|
|
467
503
|
async getSession(id) {
|
|
468
504
|
const rows = this.sql.exec("SELECT * FROM sessions WHERE id = ?", id).toArray();
|
|
@@ -485,8 +521,10 @@ export class SqlStore {
|
|
|
485
521
|
lastActiveAt: now,
|
|
486
522
|
agentSeq: 0,
|
|
487
523
|
project: stripNul(input.project)?.trim() || projectFromCwd(stripNul(input.cwd ?? null)) || null,
|
|
524
|
+
agentActiveAt: now,
|
|
525
|
+
keyed: !!stripNul(input.key)?.trim(),
|
|
488
526
|
};
|
|
489
|
-
this.sql.exec("INSERT INTO sessions (id, agent, title, cwd, createdAt, lastActiveAt, agentSeq, project, key) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?)", session.id, session.agent, session.title, session.cwd, session.createdAt, session.lastActiveAt, session.project, stripNul(input.key)?.trim() || null);
|
|
527
|
+
this.sql.exec("INSERT INTO sessions (id, agent, title, cwd, createdAt, lastActiveAt, agentSeq, project, key, agentActiveAt) VALUES (?, ?, ?, ?, ?, ?, 0, ?, ?, ?)", session.id, session.agent, session.title, session.cwd, session.createdAt, session.lastActiveAt, session.project, stripNul(input.key)?.trim() || null, session.agentActiveAt);
|
|
490
528
|
return session;
|
|
491
529
|
}
|
|
492
530
|
async renameSession(id, title) {
|
|
@@ -528,6 +566,14 @@ export class SqlStore {
|
|
|
528
566
|
async markAgentSeen(sessionId, seq) {
|
|
529
567
|
this.sql.exec("UPDATE sessions SET agentSeq = ? WHERE id = ? AND agentSeq < ?", seq, sessionId, seq);
|
|
530
568
|
}
|
|
569
|
+
async touchAgent(sessionId) {
|
|
570
|
+
this.sql.exec("UPDATE sessions SET agentActiveAt = ? WHERE id = ?", new Date().toISOString(), sessionId);
|
|
571
|
+
}
|
|
572
|
+
async handOff(sessionId, seen, toSeq) {
|
|
573
|
+
return (this.sql
|
|
574
|
+
.exec("UPDATE sessions SET agentSeq = ? WHERE id = ? AND agentSeq = ? AND agentActiveAt IS ? AND agentSeq < ? RETURNING id", toSeq, sessionId, seen.agentSeq, seen.agentActiveAt, toSeq)
|
|
575
|
+
.toArray().length > 0);
|
|
576
|
+
}
|
|
531
577
|
// --- settings ---
|
|
532
578
|
async getSetting(key) {
|
|
533
579
|
return this.settingSync(key);
|
|
@@ -570,12 +616,14 @@ export class SqlStore {
|
|
|
570
616
|
states: (input.states ?? []).map((s) => stripNul(s)),
|
|
571
617
|
asks: [],
|
|
572
618
|
knobs: input.knobs ?? {},
|
|
619
|
+
widths: input.widths ?? [],
|
|
573
620
|
draft: null,
|
|
574
621
|
sessionId: input.sessionId ?? null,
|
|
622
|
+
resolvedAt: null,
|
|
575
623
|
createdAt: now,
|
|
576
624
|
updatedAt: now,
|
|
577
625
|
};
|
|
578
|
-
this.sql.exec("INSERT INTO mocks (id, project, slug, title, kind, states, asks, knobs, draft, sessionId, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, '[]', ?, NULL, ?, ?, ?)", mock.id, mock.project, mock.slug, mock.title, mock.kind, JSON.stringify(mock.states), JSON.stringify(mock.knobs), mock.sessionId, mock.createdAt, mock.updatedAt);
|
|
626
|
+
this.sql.exec("INSERT INTO mocks (id, project, slug, title, kind, states, asks, knobs, widths, draft, sessionId, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?, '[]', ?, ?, NULL, ?, ?, ?)", mock.id, mock.project, mock.slug, mock.title, mock.kind, JSON.stringify(mock.states), JSON.stringify(mock.knobs), JSON.stringify(mock.widths), mock.sessionId, mock.createdAt, mock.updatedAt);
|
|
579
627
|
return mock;
|
|
580
628
|
}
|
|
581
629
|
async updateMock(id, patch) {
|
|
@@ -589,29 +637,86 @@ export class SqlStore {
|
|
|
589
637
|
...(patch.states !== undefined && { states: patch.states.map((s) => stripNul(s)) }),
|
|
590
638
|
...(patch.asks !== undefined && { asks: patch.asks }),
|
|
591
639
|
...(patch.knobs !== undefined && { knobs: patch.knobs }),
|
|
640
|
+
...(patch.widths !== undefined && { widths: patch.widths }),
|
|
592
641
|
...(patch.sessionId !== undefined && { sessionId: patch.sessionId }),
|
|
642
|
+
...(patch.resolvedAt !== undefined && { resolvedAt: patch.resolvedAt }),
|
|
593
643
|
updatedAt: new Date().toISOString(),
|
|
594
644
|
};
|
|
595
|
-
this.sql.exec("UPDATE mocks SET title = ?, kind = ?, states = ?, asks = ?, knobs = ?, sessionId = ?, updatedAt = ? WHERE id = ?", next.title, next.kind, JSON.stringify(next.states), JSON.stringify(next.asks), JSON.stringify(next.knobs), next.sessionId, next.updatedAt, id);
|
|
645
|
+
this.sql.exec("UPDATE mocks SET title = ?, kind = ?, states = ?, asks = ?, knobs = ?, widths = ?, sessionId = ?, resolvedAt = ?, updatedAt = ? WHERE id = ?", next.title, next.kind, JSON.stringify(next.states), JSON.stringify(next.asks), JSON.stringify(next.knobs), JSON.stringify(next.widths), next.sessionId, next.resolvedAt, next.updatedAt, id);
|
|
646
|
+
if (patch.asks !== undefined)
|
|
647
|
+
this.invalidateAssetRefs();
|
|
596
648
|
return next;
|
|
597
649
|
}
|
|
598
650
|
async removeMock(id) {
|
|
599
|
-
|
|
651
|
+
const mock = await this.getMock(id);
|
|
652
|
+
if (!mock)
|
|
600
653
|
return false;
|
|
601
654
|
this.atomic(() => {
|
|
602
655
|
this.sql.exec("DELETE FROM comments WHERE mockId = ?", id);
|
|
603
656
|
this.sql.exec("DELETE FROM post_versions WHERE postId IN (SELECT id FROM posts WHERE mockId = ?)", id);
|
|
604
657
|
this.sql.exec("DELETE FROM posts WHERE mockId = ?", id);
|
|
605
658
|
this.sql.exec("DELETE FROM mocks WHERE id = ?", id);
|
|
659
|
+
for (const flow of this.flowsOf(mock.project)) {
|
|
660
|
+
const steps = flow.steps.filter((x) => x.mock !== mock.slug);
|
|
661
|
+
if (steps.length !== flow.steps.length)
|
|
662
|
+
this.writeFlow(flow, steps);
|
|
663
|
+
}
|
|
606
664
|
});
|
|
607
665
|
this.invalidateAssetRefs();
|
|
608
666
|
return true;
|
|
609
667
|
}
|
|
668
|
+
// --- flows ---
|
|
669
|
+
flowsOf(project) {
|
|
670
|
+
return this.sql
|
|
671
|
+
.exec("SELECT * FROM flows WHERE project = ? ORDER BY createdAt, rowid", project)
|
|
672
|
+
.toArray()
|
|
673
|
+
.map((r) => ({
|
|
674
|
+
project: r.project,
|
|
675
|
+
name: r.name,
|
|
676
|
+
steps: parseJson(r.steps, []),
|
|
677
|
+
createdAt: r.createdAt,
|
|
678
|
+
updatedAt: r.updatedAt,
|
|
679
|
+
}));
|
|
680
|
+
}
|
|
681
|
+
// An emptied flow is deleted rather than kept as a name with nothing to walk.
|
|
682
|
+
writeFlow(flow, steps) {
|
|
683
|
+
if (steps.length === 0) {
|
|
684
|
+
this.sql.exec("DELETE FROM flows WHERE project = ? AND name = ?", flow.project, flow.name);
|
|
685
|
+
return null;
|
|
686
|
+
}
|
|
687
|
+
const next = { ...flow, steps, updatedAt: new Date().toISOString() };
|
|
688
|
+
this.sql.exec("INSERT INTO flows (project, name, steps, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?) ON CONFLICT(project, name) DO UPDATE SET steps = excluded.steps, updatedAt = excluded.updatedAt", next.project, next.name, JSON.stringify(next.steps), next.createdAt, next.updatedAt);
|
|
689
|
+
return next;
|
|
690
|
+
}
|
|
691
|
+
async listFlows(project) {
|
|
692
|
+
return this.flowsOf(project);
|
|
693
|
+
}
|
|
694
|
+
async placeFlowStep(project, name, screen, step) {
|
|
695
|
+
const clean = { mock: stripNul(screen.mock), state: stripNul(screen.state) };
|
|
696
|
+
const label = stripNul(name);
|
|
697
|
+
return this.atomic(() => {
|
|
698
|
+
const now = new Date().toISOString();
|
|
699
|
+
const flow = this.flowsOf(project).find((f) => f.name === label) ?? {
|
|
700
|
+
project,
|
|
701
|
+
name: label,
|
|
702
|
+
steps: [],
|
|
703
|
+
createdAt: now,
|
|
704
|
+
updatedAt: now,
|
|
705
|
+
};
|
|
706
|
+
const steps = flow.steps.filter((x) => !sameScreen(x, clean));
|
|
707
|
+
if (step > 0)
|
|
708
|
+
steps.splice(Math.min(step, steps.length + 1) - 1, 0, clean);
|
|
709
|
+
if (steps.length === 0 && flow.steps.length === 0)
|
|
710
|
+
return null;
|
|
711
|
+
return this.writeFlow(flow, steps);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
610
714
|
async putDraft(mockId, draft) {
|
|
611
715
|
const mock = await this.getMock(mockId);
|
|
612
716
|
if (!mock)
|
|
613
717
|
return null;
|
|
614
718
|
this.sql.exec("UPDATE mocks SET draft = ? WHERE id = ?", draft ? JSON.stringify(draft) : null, mockId);
|
|
719
|
+
this.invalidateAssetRefs();
|
|
615
720
|
return { ...mock, draft };
|
|
616
721
|
}
|
|
617
722
|
touchMock(mockId, at) {
|
|
@@ -674,12 +779,14 @@ export class SqlStore {
|
|
|
674
779
|
version: 1,
|
|
675
780
|
history: [],
|
|
676
781
|
...(input.knobs ? { knobs: input.knobs } : {}),
|
|
677
|
-
|
|
782
|
+
...(stripNul(input.note ?? "").trim() ? { note: stripNul(input.note ?? "").trim() } : {}),
|
|
783
|
+
embeds: input.embeds ?? [],
|
|
784
|
+
...(input.forkedFrom ? { forkedFrom: input.forkedFrom } : {}),
|
|
678
785
|
...(input.from === undefined ? {} : { from: input.from }),
|
|
679
786
|
...(input.prompt === undefined ? {} : { prompt: stripNul(input.prompt) }),
|
|
680
787
|
...(input.author === undefined ? {} : { author: stripNul(input.author) }),
|
|
681
788
|
};
|
|
682
|
-
this.sql.exec("INSERT INTO posts (id, sessionId, mockId, state, variant, status, title, surfaces, knobs, slots, createdAt, updatedAt, version, curFrom, curPrompt, curAuthor) VALUES (?, ?, ?, ?, ?, 'open', ?, ?, ?, ?, ?, ?, 1, ?, ?, ?)", post.id, post.sessionId, post.mock, post.state, post.variant, post.title, JSON.stringify(post.surfaces), post.knobs ? JSON.stringify(post.knobs) : null, JSON.stringify(post.
|
|
789
|
+
this.sql.exec("INSERT INTO posts (id, sessionId, mockId, state, variant, status, title, surfaces, knobs, slots, createdAt, updatedAt, version, curFrom, curPrompt, curAuthor, note, forkedFrom) VALUES (?, ?, ?, ?, ?, 'open', ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?, ?)", post.id, post.sessionId, post.mock, post.state, post.variant, post.title, JSON.stringify(post.surfaces), post.knobs ? JSON.stringify(post.knobs) : null, JSON.stringify(post.embeds), post.createdAt, post.updatedAt, post.from ?? null, post.prompt ?? null, post.author ?? null, post.note ?? null, post.forkedFrom ? JSON.stringify(post.forkedFrom) : null);
|
|
683
790
|
this.touch(input.sessionId);
|
|
684
791
|
this.touchMock(post.mock, now);
|
|
685
792
|
this.addAssetRefs(post.surfaces);
|
|
@@ -701,13 +808,17 @@ export class SqlStore {
|
|
|
701
808
|
...(post.prompt === undefined ? {} : { prompt: post.prompt }),
|
|
702
809
|
...(post.author === undefined ? {} : { author: post.author }),
|
|
703
810
|
};
|
|
811
|
+
// The version in use is kept past the limit: export and embeds resolve to it.
|
|
812
|
+
const pin = post.acceptedVersion ?? -1;
|
|
704
813
|
const history = [...post.history, archived];
|
|
705
|
-
|
|
706
|
-
|
|
814
|
+
const prunable = history.filter((h) => h.version !== pin);
|
|
815
|
+
if (prunable.length > HISTORY_LIMIT)
|
|
816
|
+
history.splice(history.indexOf(prunable[0]), 1);
|
|
707
817
|
const title = patch.title !== undefined ? stripNul(patch.title).trim() || post.title : post.title;
|
|
708
818
|
const surfaces = patch.surfaces !== undefined ? normalizeSurfaceIds(patch.surfaces) : post.surfaces;
|
|
709
819
|
const knobs = patch.knobs === undefined ? post.knobs : (patch.knobs ?? undefined);
|
|
710
|
-
const
|
|
820
|
+
const note = patch.note === undefined ? post.note : stripNul(patch.note).trim() || undefined;
|
|
821
|
+
const embeds = patch.embeds ?? post.embeds;
|
|
711
822
|
const state = patch.state === undefined ? post.state : patch.state;
|
|
712
823
|
const version = post.version + 1;
|
|
713
824
|
const updatedAt = new Date().toISOString();
|
|
@@ -715,22 +826,23 @@ export class SqlStore {
|
|
|
715
826
|
const from = patch.from ?? post.version;
|
|
716
827
|
const prompt = stripNul(patch.prompt ?? "");
|
|
717
828
|
const author = patch.author === undefined ? null : stripNul(patch.author);
|
|
718
|
-
this.sql.exec("UPDATE posts SET title = ?, surfaces = ?, knobs = ?, slots = ?, state = ?, updatedAt = ?, version = ?, curFrom = ?, curPrompt = ?, curAuthor = ? WHERE id = ? AND version = ?", title, JSON.stringify(surfaces), knobs ? JSON.stringify(knobs) : null, JSON.stringify(
|
|
829
|
+
this.sql.exec("UPDATE posts SET title = ?, surfaces = ?, knobs = ?, note = ?, slots = ?, state = ?, updatedAt = ?, version = ?, curFrom = ?, curPrompt = ?, curAuthor = ? WHERE id = ? AND version = ?", title, JSON.stringify(surfaces), knobs ? JSON.stringify(knobs) : null, note ?? null, JSON.stringify(embeds), state, updatedAt, version, from, prompt, author, id, post.version);
|
|
719
830
|
const affected = this.sql.exec("SELECT changes() AS n").one().n;
|
|
720
831
|
if (affected > 0) {
|
|
721
832
|
this.insertVersion(id, archived);
|
|
722
|
-
this.sql.exec("DELETE FROM post_versions WHERE postId = ? AND version <= ?", id, version - 1 - HISTORY_LIMIT);
|
|
833
|
+
this.sql.exec("DELETE FROM post_versions WHERE postId = ? AND version <= ? AND version != ?", id, version - 1 - HISTORY_LIMIT, pin);
|
|
723
834
|
this.touch(post.sessionId);
|
|
724
835
|
this.touchMock(post.mock, updatedAt);
|
|
725
836
|
if (patch.surfaces !== undefined)
|
|
726
837
|
this.addAssetRefs(surfaces);
|
|
727
|
-
const { knobs: _knobs, author: _author, ...rest } = post;
|
|
838
|
+
const { knobs: _knobs, note: _note, author: _author, ...rest } = post;
|
|
728
839
|
return {
|
|
729
840
|
...rest,
|
|
730
841
|
title,
|
|
731
842
|
surfaces,
|
|
732
843
|
...(knobs ? { knobs } : {}),
|
|
733
|
-
|
|
844
|
+
...(note ? { note } : {}),
|
|
845
|
+
embeds,
|
|
734
846
|
state,
|
|
735
847
|
version,
|
|
736
848
|
updatedAt,
|
|
@@ -754,12 +866,36 @@ export class SqlStore {
|
|
|
754
866
|
this.invalidateAssetRefs();
|
|
755
867
|
return true;
|
|
756
868
|
}
|
|
757
|
-
async setPostStatus(id, status) {
|
|
869
|
+
async setPostStatus(id, status, acceptedVersion) {
|
|
870
|
+
const post = await this.getPost(id);
|
|
871
|
+
if (!post)
|
|
872
|
+
return null;
|
|
873
|
+
const pin = status === "accepted" ? (acceptedVersion ?? post.version) : null;
|
|
874
|
+
this.sql.exec("UPDATE posts SET status = ?, acceptedVersion = ? WHERE id = ?", status, pin, id);
|
|
875
|
+
const { acceptedVersion: _, ...rest } = post;
|
|
876
|
+
return pin === null ? { ...rest, status } : { ...rest, status, acceptedVersion: pin };
|
|
877
|
+
}
|
|
878
|
+
async setPostStarred(id, starred) {
|
|
758
879
|
const post = await this.getPost(id);
|
|
759
880
|
if (!post)
|
|
760
881
|
return null;
|
|
761
|
-
this.sql.exec("UPDATE posts SET
|
|
762
|
-
|
|
882
|
+
this.sql.exec("UPDATE posts SET starred = ? WHERE id = ?", starred ? 1 : 0, id);
|
|
883
|
+
const { starred: _, ...rest } = post;
|
|
884
|
+
return starred ? { ...rest, starred: true } : rest;
|
|
885
|
+
}
|
|
886
|
+
async setVersionHidden(id, version, hidden) {
|
|
887
|
+
const post = await this.getPost(id);
|
|
888
|
+
if (!post)
|
|
889
|
+
return null;
|
|
890
|
+
const set = new Set(post.hiddenVersions ?? []);
|
|
891
|
+
if (hidden)
|
|
892
|
+
set.add(version);
|
|
893
|
+
else
|
|
894
|
+
set.delete(version);
|
|
895
|
+
const list = [...set].sort((a, b) => a - b);
|
|
896
|
+
this.sql.exec("UPDATE posts SET hiddenVersions = ? WHERE id = ?", list.length ? JSON.stringify(list) : null, id);
|
|
897
|
+
const { hiddenVersions: _, ...rest } = post;
|
|
898
|
+
return list.length ? { ...rest, hiddenVersions: list } : rest;
|
|
763
899
|
}
|
|
764
900
|
// --- comments ---
|
|
765
901
|
async listComments(query) {
|
|
@@ -826,6 +962,8 @@ export class SqlStore {
|
|
|
826
962
|
return null;
|
|
827
963
|
const comment = this.rowToComment(rows[0]);
|
|
828
964
|
this.sql.exec("DELETE FROM comments WHERE id = ?", id);
|
|
965
|
+
if (comment.kind === "reply")
|
|
966
|
+
this.invalidateAssetRefs();
|
|
829
967
|
this.touch(comment.sessionId);
|
|
830
968
|
return comment;
|
|
831
969
|
}
|
|
@@ -844,13 +982,22 @@ export class SqlStore {
|
|
|
844
982
|
postVersion: input.payload.version,
|
|
845
983
|
payload: input.payload,
|
|
846
984
|
});
|
|
847
|
-
this.sql.exec(
|
|
848
|
-
|
|
849
|
-
|
|
985
|
+
this.sql.exec(input.keepDraft
|
|
986
|
+
? "UPDATE mocks SET asks = ? WHERE id = ?"
|
|
987
|
+
: "UPDATE mocks SET asks = ?, draft = NULL WHERE id = ?", JSON.stringify(input.asks), input.mockId);
|
|
988
|
+
if (input.resolvedAt !== undefined) {
|
|
989
|
+
this.sql.exec("UPDATE mocks SET resolvedAt = ?, updatedAt = ? WHERE id = ?", input.resolvedAt, input.resolvedAt, input.mockId);
|
|
990
|
+
}
|
|
991
|
+
for (const { id, version } of input.accept) {
|
|
992
|
+
this.sql.exec("UPDATE posts SET status = 'accepted', acceptedVersion = ? WHERE id = ? AND mockId = ?", version, id, input.mockId);
|
|
850
993
|
}
|
|
851
994
|
for (const id of input.archive) {
|
|
852
|
-
this.sql.exec("UPDATE posts SET status = 'archived' WHERE id = ? AND mockId = ?", id, input.mockId);
|
|
995
|
+
this.sql.exec("UPDATE posts SET status = 'archived', acceptedVersion = NULL WHERE id = ? AND mockId = ?", id, input.mockId);
|
|
853
996
|
}
|
|
997
|
+
for (const id of input.drop ?? []) {
|
|
998
|
+
this.sql.exec("UPDATE posts SET status = 'dropped', acceptedVersion = NULL WHERE id = ? AND mockId = ?", id, input.mockId);
|
|
999
|
+
}
|
|
1000
|
+
this.invalidateAssetRefs();
|
|
854
1001
|
return comment;
|
|
855
1002
|
});
|
|
856
1003
|
}
|
|
@@ -865,6 +1012,17 @@ export class SqlStore {
|
|
|
865
1012
|
for (const r of this.sql.exec("SELECT surfaces FROM post_versions").toArray()) {
|
|
866
1013
|
collectAssetIds(parseJson(r.surfaces, []), out);
|
|
867
1014
|
}
|
|
1015
|
+
// The user's attachments: in a draft, in a sent reply, on an answered ask.
|
|
1016
|
+
for (const r of this.sql.exec("SELECT asks, draft FROM mocks").toArray()) {
|
|
1017
|
+
collectAttachedIds(parseJson(r.draft, null), out);
|
|
1018
|
+
for (const ask of parseJson(r.asks, []))
|
|
1019
|
+
collectAttachedIds(ask, out);
|
|
1020
|
+
}
|
|
1021
|
+
for (const r of this.sql
|
|
1022
|
+
.exec("SELECT payload FROM comments WHERE kind = 'reply' AND payload IS NOT NULL")
|
|
1023
|
+
.toArray()) {
|
|
1024
|
+
collectAttachedIds(parseJson(r.payload, null), out);
|
|
1025
|
+
}
|
|
868
1026
|
// An installed icon set is referenced by its project's design, not by a
|
|
869
1027
|
// surface; evicting it would blank that project's icons.
|
|
870
1028
|
for (const r of this.sql
|