@loopbacktools/server 0.0.0-stage → 0.4.1
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/LICENSE +21 -0
- package/README.md +26 -2
- package/dist/package.json +57 -0
- package/dist/src/domain/ask.d.ts +78 -0
- package/dist/src/domain/ask.js +161 -0
- package/dist/src/domain/errors.d.ts +70 -0
- package/dist/src/domain/errors.js +57 -0
- package/dist/src/generated/app.gen.d.ts +2311 -0
- package/dist/src/generated/app.gen.js +400 -0
- package/dist/src/generated/runtime.gen.d.ts +357 -0
- package/dist/src/generated/runtime.gen.js +298 -0
- package/dist/src/generated/schemas.gen.d.ts +6416 -0
- package/dist/src/generated/schemas.gen.js +306 -0
- package/dist/src/generated/wire-contract.gen.d.ts +77 -0
- package/dist/src/generated/wire-contract.gen.js +3 -0
- package/dist/src/index.d.ts +11 -0
- package/dist/src/index.js +8 -0
- package/dist/src/loopback.d.ts +27 -0
- package/dist/src/loopback.js +16 -0
- package/dist/src/space/arguments.d.ts +8 -0
- package/dist/src/space/arguments.js +52 -0
- package/dist/src/space/migrations.d.ts +10 -0
- package/dist/src/space/migrations.js +106 -0
- package/dist/src/space/object.d.ts +30 -0
- package/dist/src/space/object.js +169 -0
- package/dist/src/space/operations.d.ts +149 -0
- package/dist/src/space/operations.js +568 -0
- package/dist/src/space/read.d.ts +8 -0
- package/dist/src/space/read.js +33 -0
- package/dist/src/space/rpc.d.ts +61 -0
- package/dist/src/space/rpc.js +96 -0
- package/dist/src/space/services.d.ts +41 -0
- package/dist/src/space/services.js +58 -0
- package/dist/src/space/store.d.ts +87 -0
- package/dist/src/space/store.js +276 -0
- package/dist/src/version.d.ts +2 -0
- package/dist/src/version.js +3 -0
- package/dist/src/worker/env.d.ts +19 -0
- package/dist/src/worker/env.js +3 -0
- package/dist/src/worker/files.d.ts +21 -0
- package/dist/src/worker/files.js +42 -0
- package/dist/src/worker/gate.d.ts +55 -0
- package/dist/src/worker/gate.js +83 -0
- package/dist/src/worker/http.d.ts +2092 -0
- package/dist/src/worker/http.js +291 -0
- package/dist/src/worker/jwt.d.ts +49 -0
- package/dist/src/worker/jwt.js +142 -0
- package/dist/src/worker/mcp.d.ts +15 -0
- package/dist/src/worker/mcp.js +401 -0
- package/dist/src/worker/run.d.ts +45 -0
- package/dist/src/worker/run.js +71 -0
- package/dist/src/worker/spaces.d.ts +24 -0
- package/dist/src/worker/spaces.js +64 -0
- package/package.json +51 -3
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
// GENERATED by typespec-http-zod from the TypeSpec service definition. DO NOT EDIT.
|
|
2
|
+
// Regenerate with: pnpm generate
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { SPEC_VOCABULARIES } from "@loopbacktools/protocol";
|
|
5
|
+
/** Wire text as the JSON value it spells that the schema accepts: a number, a boolean, or the text. */
|
|
6
|
+
function wireRead(raw, schema) {
|
|
7
|
+
if (typeof raw !== "string")
|
|
8
|
+
return raw;
|
|
9
|
+
const readings = [];
|
|
10
|
+
if (/^-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?$/.test(raw) && Number.isFinite(Number(raw))) {
|
|
11
|
+
readings.push(Number(raw));
|
|
12
|
+
}
|
|
13
|
+
if (raw === "true" || raw === "false")
|
|
14
|
+
readings.push(raw === "true");
|
|
15
|
+
for (const reading of [...readings, raw])
|
|
16
|
+
if (schema.safeParse(reading).success)
|
|
17
|
+
return reading;
|
|
18
|
+
return readings[0] ?? raw;
|
|
19
|
+
}
|
|
20
|
+
export const problemSchema = z.strictObject({
|
|
21
|
+
code: z.string().describe("A stable, machine-readable reason, such as `ask.closed`."),
|
|
22
|
+
message: z.string(),
|
|
23
|
+
}).describe("Why a request was refused.");
|
|
24
|
+
export const actorSchema = z.strictObject({
|
|
25
|
+
id: z.string().describe("Stable within the deployment, as its gate names them."),
|
|
26
|
+
name: z.string().exactOptional().describe("How to show them, when the gate knows."),
|
|
27
|
+
}).describe("Someone a deployment's gate recognised: a person, or the agent they authorised.");
|
|
28
|
+
export const whoamiSchema = z.strictObject({
|
|
29
|
+
space: z.string(),
|
|
30
|
+
actor: actorSchema,
|
|
31
|
+
}).describe("Who the caller is.");
|
|
32
|
+
export const eventTicketSchema = z.strictObject({
|
|
33
|
+
ticket: z.string(),
|
|
34
|
+
expiresAt: z.iso.datetime({ offset: true }),
|
|
35
|
+
}).describe("A one-time pass onto the event socket, for a client that cannot send headers there, as a browser\ncannot. Offer it as the subprotocol `loopback.ticket.<ticket>` alongside `loopback.v1`. It works\nonce, within a minute, so a copy that turns up in a log is of no use to anyone.");
|
|
36
|
+
export const choiceSchema = z.strictObject({
|
|
37
|
+
label: z.string().min(1).max(80),
|
|
38
|
+
description: z.string().exactOptional(),
|
|
39
|
+
}).describe("A choice offered with a question.");
|
|
40
|
+
export const questionPromptSchema = z.strictObject({
|
|
41
|
+
question: z.string(),
|
|
42
|
+
options: z.array(choiceSchema),
|
|
43
|
+
multiple: z.boolean(),
|
|
44
|
+
context: z.string().exactOptional(),
|
|
45
|
+
}).describe("A question as it is shown to the person.");
|
|
46
|
+
export const askStatusSchema = z.enum(SPEC_VOCABULARIES.AskStatus).describe("Where an ask stands. While it is open, it is either the person's move or the agent's.");
|
|
47
|
+
export const answeredSchema = z.strictObject({
|
|
48
|
+
outcome: z.literal("answered"),
|
|
49
|
+
selected: z.array(z.string()),
|
|
50
|
+
text: z.string().exactOptional(),
|
|
51
|
+
}).describe("An answer to a question: the choices picked, words of their own, or both.");
|
|
52
|
+
export const approvedSchema = z.strictObject({
|
|
53
|
+
outcome: z.literal("approved"),
|
|
54
|
+
note: z.string().exactOptional(),
|
|
55
|
+
}).describe("A review approved, with an optional note.");
|
|
56
|
+
export const changesRequestedSchema = z.strictObject({
|
|
57
|
+
outcome: z.literal("changes_requested"),
|
|
58
|
+
feedback: z.string().min(1),
|
|
59
|
+
}).describe("A review sent back with feedback.");
|
|
60
|
+
export const followUpSchema = z.strictObject({
|
|
61
|
+
outcome: z.literal("follow_up"),
|
|
62
|
+
question: z.string().min(1),
|
|
63
|
+
}).describe("A question asked back before deciding. The ask stays open until the agent answers.");
|
|
64
|
+
export const dismissedSchema = z.strictObject({
|
|
65
|
+
outcome: z.literal("dismissed"),
|
|
66
|
+
}).describe("Declined without an answer.");
|
|
67
|
+
export const pointReplySchema = z.strictObject({
|
|
68
|
+
point: z.string().min(1),
|
|
69
|
+
text: z.string().exactOptional().describe("What they said: a question, a push-back or a comment."),
|
|
70
|
+
resolved: z.boolean().exactOptional().describe("Whether they are done with the point, and everything beneath it."),
|
|
71
|
+
}).describe("The person on one point of a briefing: words, resolved, or both.");
|
|
72
|
+
export const respondedSchema = z.strictObject({
|
|
73
|
+
outcome: z.literal("responded"),
|
|
74
|
+
points: z.array(pointReplySchema).min(1),
|
|
75
|
+
}).describe("Replies on the points of a briefing. The agent answers every point written on and left unresolved;\nthe briefing closes once every point is resolved.");
|
|
76
|
+
export const replySchema = z.union([answeredSchema, approvedSchema, changesRequestedSchema, followUpSchema, dismissedSchema, respondedSchema]).describe("What a person replies. A `follow_up` hands the move to the agent; so does a `responded` that\nleaves any point unresolved. Every other reply closes the ask.");
|
|
77
|
+
export const humanEntrySchema = z.strictObject({
|
|
78
|
+
author: z.literal("human"),
|
|
79
|
+
reply: replySchema,
|
|
80
|
+
by: actorSchema.exactOptional().describe("Who replied, when the deployment knows."),
|
|
81
|
+
at: z.iso.datetime({ offset: true }),
|
|
82
|
+
}).describe("A reply the person gave.");
|
|
83
|
+
export const markdownBlockSchema = z.strictObject({
|
|
84
|
+
type: z.literal("markdown"),
|
|
85
|
+
text: z.string().min(1),
|
|
86
|
+
}).describe("Prose, copy or a plan, as GitHub-flavoured markdown.");
|
|
87
|
+
export const codeBlockSchema = z.strictObject({
|
|
88
|
+
type: z.literal("code"),
|
|
89
|
+
code: z.string().min(1),
|
|
90
|
+
language: z.string().exactOptional().describe("A language name the highlighter knows, such as `ts` or `python`."),
|
|
91
|
+
filename: z.string().exactOptional(),
|
|
92
|
+
}).describe("Source code, shown highlighted.");
|
|
93
|
+
export const diffBlockSchema = z.strictObject({
|
|
94
|
+
type: z.literal("diff"),
|
|
95
|
+
diff: z.string().min(1),
|
|
96
|
+
filename: z.string().exactOptional(),
|
|
97
|
+
}).describe("A unified diff, as `git diff` prints it.");
|
|
98
|
+
export const imageBlockSchema = z.strictObject({
|
|
99
|
+
type: z.literal("image"),
|
|
100
|
+
uploadId: z.string().min(1),
|
|
101
|
+
caption: z.string().exactOptional(),
|
|
102
|
+
}).describe("An image uploaded beforehand through an upload ticket.");
|
|
103
|
+
export const blockSchema = z.union([markdownBlockSchema, codeBlockSchema, diffBlockSchema, imageBlockSchema]).describe("One piece of what is shown to the person.");
|
|
104
|
+
export const pointSchema = z.strictObject({
|
|
105
|
+
id: z.string().describe("`3` for the third point of the briefing, `3.1` for the first beneath it."),
|
|
106
|
+
title: z.string().min(1).max(200).describe("The point in one line, shown while it is folded."),
|
|
107
|
+
detail: z.string().exactOptional().describe("The point explained, as markdown, shown when the person opens it."),
|
|
108
|
+
content: z.array(blockSchema).exactOptional().describe("Anything to look at with it: code, a diff or an image."),
|
|
109
|
+
}).describe("A point of a briefing as it is shown, with the id replies name it by.");
|
|
110
|
+
export const agentEntrySchema = z.strictObject({
|
|
111
|
+
author: z.literal("agent"),
|
|
112
|
+
answer: z.string(),
|
|
113
|
+
content: z.array(blockSchema).exactOptional(),
|
|
114
|
+
point: z.string().exactOptional().describe("The briefing point this answers."),
|
|
115
|
+
points: z.array(pointSchema).exactOptional().describe("Sub-points the answer broke the point into, with their ids."),
|
|
116
|
+
at: z.iso.datetime({ offset: true }),
|
|
117
|
+
}).describe("An answer the agent gave: to a question asked back, or on one point of a briefing.");
|
|
118
|
+
export const entrySchema = z.union([humanEntrySchema, agentEntrySchema]).describe("One turn in an ask's thread after it was opened.");
|
|
119
|
+
export const questionAskSchema = z.strictObject({
|
|
120
|
+
kind: z.literal("question"),
|
|
121
|
+
prompt: questionPromptSchema,
|
|
122
|
+
id: z.string(),
|
|
123
|
+
status: askStatusSchema,
|
|
124
|
+
waiting: z.boolean().describe("Whether an agent is following this ask right now: waiting on its call, or polling it as a background task."),
|
|
125
|
+
project: z.string().exactOptional(),
|
|
126
|
+
client: z.string().exactOptional().describe("The MCP client that asked, such as `claude-code 2.1.0`."),
|
|
127
|
+
raisedBy: actorSchema.exactOptional().describe("Whose agent asked: the person its credential belongs to, when the deployment knows."),
|
|
128
|
+
thread: z.array(entrySchema).describe("Every turn after the ask was opened, oldest first."),
|
|
129
|
+
createdAt: z.iso.datetime({ offset: true }),
|
|
130
|
+
updatedAt: z.iso.datetime({ offset: true }),
|
|
131
|
+
closedAt: z.iso.datetime({ offset: true }).exactOptional(),
|
|
132
|
+
}).describe("A question an agent asked.");
|
|
133
|
+
export const reviewPromptSchema = z.strictObject({
|
|
134
|
+
title: z.string(),
|
|
135
|
+
content: z.array(blockSchema),
|
|
136
|
+
summary: z.string().exactOptional(),
|
|
137
|
+
}).describe("A review as it is shown to the person.");
|
|
138
|
+
export const reviewAskSchema = z.strictObject({
|
|
139
|
+
kind: z.literal("review"),
|
|
140
|
+
prompt: reviewPromptSchema,
|
|
141
|
+
revises: z.string().exactOptional().describe("The id of the review this one revises."),
|
|
142
|
+
id: z.string(),
|
|
143
|
+
status: askStatusSchema,
|
|
144
|
+
waiting: z.boolean().describe("Whether an agent is following this ask right now: waiting on its call, or polling it as a background task."),
|
|
145
|
+
project: z.string().exactOptional(),
|
|
146
|
+
client: z.string().exactOptional().describe("The MCP client that asked, such as `claude-code 2.1.0`."),
|
|
147
|
+
raisedBy: actorSchema.exactOptional().describe("Whose agent asked: the person its credential belongs to, when the deployment knows."),
|
|
148
|
+
thread: z.array(entrySchema).describe("Every turn after the ask was opened, oldest first."),
|
|
149
|
+
createdAt: z.iso.datetime({ offset: true }),
|
|
150
|
+
updatedAt: z.iso.datetime({ offset: true }),
|
|
151
|
+
closedAt: z.iso.datetime({ offset: true }).exactOptional(),
|
|
152
|
+
}).describe("Work an agent put up for review.");
|
|
153
|
+
export const briefingPromptSectionSchema = z.strictObject({
|
|
154
|
+
title: z.string().exactOptional(),
|
|
155
|
+
points: z.array(pointSchema),
|
|
156
|
+
}).describe("A list of a briefing's points as it is shown.");
|
|
157
|
+
export const briefingPromptSchema = z.strictObject({
|
|
158
|
+
title: z.string(),
|
|
159
|
+
summary: z.string().exactOptional(),
|
|
160
|
+
sections: z.array(briefingPromptSectionSchema),
|
|
161
|
+
}).describe("A briefing as it is shown to the person, before any sub-points the agent adds while answering.");
|
|
162
|
+
export const briefingAskSchema = z.strictObject({
|
|
163
|
+
kind: z.literal("briefing"),
|
|
164
|
+
prompt: briefingPromptSchema,
|
|
165
|
+
id: z.string(),
|
|
166
|
+
status: askStatusSchema,
|
|
167
|
+
waiting: z.boolean().describe("Whether an agent is following this ask right now: waiting on its call, or polling it as a background task."),
|
|
168
|
+
project: z.string().exactOptional(),
|
|
169
|
+
client: z.string().exactOptional().describe("The MCP client that asked, such as `claude-code 2.1.0`."),
|
|
170
|
+
raisedBy: actorSchema.exactOptional().describe("Whose agent asked: the person its credential belongs to, when the deployment knows."),
|
|
171
|
+
thread: z.array(entrySchema).describe("Every turn after the ask was opened, oldest first."),
|
|
172
|
+
createdAt: z.iso.datetime({ offset: true }),
|
|
173
|
+
updatedAt: z.iso.datetime({ offset: true }),
|
|
174
|
+
closedAt: z.iso.datetime({ offset: true }).exactOptional(),
|
|
175
|
+
}).describe("Points an agent explained, for the person to reply on one at a time.");
|
|
176
|
+
export const askSchema = z.union([questionAskSchema, reviewAskSchema, briefingAskSchema]).describe("Something an agent needs from a person: a short thread that ends with a reply.");
|
|
177
|
+
export const snapshotSchema = z.strictObject({
|
|
178
|
+
open: z.array(askSchema),
|
|
179
|
+
recent: z.array(askSchema),
|
|
180
|
+
}).describe("Open asks, newest first, and the most recently closed.");
|
|
181
|
+
export const askPageSchema = z.strictObject({
|
|
182
|
+
items: z.array(askSchema),
|
|
183
|
+
nextBefore: z.iso.datetime({ offset: true }).exactOptional().describe("Pass as `before` for the next page. Absent on the last page."),
|
|
184
|
+
}).describe("A page of closed asks, newest first.");
|
|
185
|
+
export const imageTypeSchema = z.enum(["image/png", "image/jpeg", "image/webp", "image/gif"]).describe("Image formats an agent may upload for a review.");
|
|
186
|
+
export const uploadReceiptSchema = z.strictObject({
|
|
187
|
+
uploadId: z.string(),
|
|
188
|
+
contentType: imageTypeSchema,
|
|
189
|
+
size: z.int32(),
|
|
190
|
+
}).describe("A stored upload.");
|
|
191
|
+
export const revisionsSchema = z.enum(SPEC_VOCABULARIES.Revisions).describe("The contract's revisions, oldest first. Change the shape of any message and declare it here as a\nnew revision, marking what changed with `@added`, `@removed`, `@renamedFrom` or `@typeChangedFrom`:\nevery part of a deployment names the revision it writes, and newer code upgrades older messages.\nNever edit a revision that has shipped; `revisions/` holds each one frozen, and a test fails when\nthe contract no longer emits it unchanged. See docs/design/versioned-messages.md.");
|
|
192
|
+
export const askKindSchema = z.enum(SPEC_VOCABULARIES.AskKind).describe("What an agent is asking for: an answer to a question, a verdict on something it produced, or the\nperson's word on each point of something it explained.");
|
|
193
|
+
export const briefingPointSchema = z.strictObject({
|
|
194
|
+
title: z.string().min(1).max(200).describe("The point in one line, shown while it is folded."),
|
|
195
|
+
detail: z.string().exactOptional().describe("The point explained, as markdown, shown when the person opens it."),
|
|
196
|
+
content: z.array(blockSchema).exactOptional().describe("Anything to look at with it: code, a diff or an image."),
|
|
197
|
+
}).describe("One point of a briefing: a line the person sees folded, and what opens beneath it.");
|
|
198
|
+
export const briefingSectionSchema = z.strictObject({
|
|
199
|
+
title: z.string().exactOptional(),
|
|
200
|
+
points: z.array(briefingPointSchema).min(1),
|
|
201
|
+
}).describe("A list of points under an optional heading.");
|
|
202
|
+
export const questionInputSchema = z.strictObject({
|
|
203
|
+
question: z.string().min(1).describe("The question, as markdown."),
|
|
204
|
+
options: z.array(choiceSchema).min(2).max(6).exactOptional().describe("Between two and six choices, offered as buttons."),
|
|
205
|
+
multiple: z.boolean().exactOptional().describe("Whether the person may pick more than one choice."),
|
|
206
|
+
context: z.string().exactOptional().describe("Background the person needs to answer, as markdown."),
|
|
207
|
+
project: z.string().min(1).describe("The project you are working in: the name of the folder at the root of its repository (the\nlast part of `git rev-parse --show-toplevel`), or of the working directory outside one. The\nperson works on many projects at once from one inbox; this is how they know who is asking."),
|
|
208
|
+
}).describe("A question for the person, with optional choices. They may always answer in their own words. All\nof it together may be at most 256 KiB.");
|
|
209
|
+
export const reviewInputSchema = z.strictObject({
|
|
210
|
+
title: z.string().min(1).max(200),
|
|
211
|
+
content: z.array(blockSchema).min(1).describe("What is being reviewed, in order."),
|
|
212
|
+
summary: z.string().exactOptional().describe("What changed, or what to look at."),
|
|
213
|
+
revises: z.string().exactOptional().describe("The id of the review this one revises, after changes were requested."),
|
|
214
|
+
project: z.string().min(1).describe("The project you are working in: the name of the folder at the root of its repository (the\nlast part of `git rev-parse --show-toplevel`), or of the working directory outside one. The\nperson works on many projects at once from one inbox; this is how they know who is asking."),
|
|
215
|
+
}).describe("Work put in front of the person for approval or feedback. All of it together may be at most\n256 KiB: show the part that matters, or split it into several reviews.");
|
|
216
|
+
export const briefingInputSchema = z.strictObject({
|
|
217
|
+
title: z.string().min(1).max(200),
|
|
218
|
+
summary: z.string().exactOptional().describe("What this explains, and why, as markdown."),
|
|
219
|
+
sections: z.array(briefingSectionSchema).min(1).describe("The points, in lists under optional headings."),
|
|
220
|
+
project: z.string().min(1).describe("The project you are working in: the name of the folder at the root of its repository (the\nlast part of `git rev-parse --show-toplevel`), or of the working directory outside one. The\nperson works on many projects at once from one inbox; this is how they know who is asking."),
|
|
221
|
+
}).describe("Something explained as points the person can reply on one at a time: findings, options, steps, a\nlist of changes. They may write on any point, or mark it resolved, and you answer on each point\nthey wrote on until every point is resolved. Points are numbered for you, straight through every\nsection: at most fifty. All of it together may be at most 256 KiB.");
|
|
222
|
+
export const pointAnswerSchema = z.strictObject({
|
|
223
|
+
point: z.string().min(1).describe("The point's id, such as `3` or `3.1`."),
|
|
224
|
+
answer: z.string().min(1).describe("The answer, as markdown."),
|
|
225
|
+
content: z.array(blockSchema).exactOptional().describe("Anything new to look at alongside the answer."),
|
|
226
|
+
points: z.array(briefingPointSchema).exactOptional().describe("Sub-points this point breaks into, numbered beneath it (`3.1`, `3.2`, \u2026), each of which the\nperson can reply on in turn. Points go at most three levels deep."),
|
|
227
|
+
}).describe("Your answer on one point of a briefing.");
|
|
228
|
+
export const briefingAnswerInputSchema = z.strictObject({
|
|
229
|
+
askId: z.string().min(1),
|
|
230
|
+
answers: z.array(pointAnswerSchema).min(1),
|
|
231
|
+
}).describe("Your answers on a briefing, one per point: every point the person wrote on without resolving it,\nand any other open point you have more to say on. All of it together may be at most 256 KiB.");
|
|
232
|
+
export const followUpInputSchema = z.strictObject({
|
|
233
|
+
askId: z.string().min(1),
|
|
234
|
+
answer: z.string().min(1).describe("The answer, as markdown."),
|
|
235
|
+
content: z.array(blockSchema).exactOptional().describe("Anything new to look at alongside the answer."),
|
|
236
|
+
}).describe("The agent's answer to a question the person asked back, with its content at most 256 KiB.");
|
|
237
|
+
export const uploadRequestSchema = z.strictObject({
|
|
238
|
+
contentType: imageTypeSchema,
|
|
239
|
+
}).describe("A request for somewhere to upload an image.");
|
|
240
|
+
export const uploadTicketSchema = z.strictObject({
|
|
241
|
+
uploadId: z.string(),
|
|
242
|
+
uploadUrl: z.string().describe("Send the image here with `PUT` and its `Content-Type`. No credentials needed."),
|
|
243
|
+
expiresAt: z.iso.datetime({ offset: true }),
|
|
244
|
+
maxBytes: z.int32(),
|
|
245
|
+
command: z.string().describe("A ready-to-run command that performs the upload; replace `FILE` with the image's path."),
|
|
246
|
+
}).describe("Where to upload an image, once, before it expires.");
|
|
247
|
+
export const inboxRequestSchema = z.strictObject({
|
|
248
|
+
askId: z.string().exactOptional().describe("An ask to open directly. Leave it out for the inbox itself."),
|
|
249
|
+
}).describe("A request for the address of the person's inbox.");
|
|
250
|
+
export const inboxLinkSchema = z.strictObject({
|
|
251
|
+
url: z.string(),
|
|
252
|
+
}).describe("Where the person reads and answers everything agents ask.");
|
|
253
|
+
export const askCommonSchema = z.strictObject({
|
|
254
|
+
id: z.string(),
|
|
255
|
+
status: askStatusSchema,
|
|
256
|
+
waiting: z.boolean().describe("Whether an agent is following this ask right now: waiting on its call, or polling it as a background task."),
|
|
257
|
+
project: z.string().exactOptional(),
|
|
258
|
+
client: z.string().exactOptional().describe("The MCP client that asked, such as `claude-code 2.1.0`."),
|
|
259
|
+
raisedBy: actorSchema.exactOptional().describe("Whose agent asked: the person its credential belongs to, when the deployment knows."),
|
|
260
|
+
thread: z.array(entrySchema).describe("Every turn after the ask was opened, oldest first."),
|
|
261
|
+
createdAt: z.iso.datetime({ offset: true }),
|
|
262
|
+
updatedAt: z.iso.datetime({ offset: true }),
|
|
263
|
+
closedAt: z.iso.datetime({ offset: true }).exactOptional(),
|
|
264
|
+
}).describe("What every ask carries, whatever its kind.");
|
|
265
|
+
export const toolReplySchema = z.strictObject({
|
|
266
|
+
askId: z.string(),
|
|
267
|
+
reply: replySchema,
|
|
268
|
+
}).describe("What a blocking MCP tool returns: the reply that ended its wait.");
|
|
269
|
+
export const snapshotEventSchema = z.strictObject({
|
|
270
|
+
type: z.literal("snapshot"),
|
|
271
|
+
snapshot: snapshotSchema,
|
|
272
|
+
}).describe("The first frame on the event socket, and the state to start from.");
|
|
273
|
+
export const askEventSchema = z.strictObject({
|
|
274
|
+
type: z.enum(["ask.opened", "ask.updated"]),
|
|
275
|
+
ask: askSchema,
|
|
276
|
+
}).describe("An ask was opened, or changed: a new turn, a close, or an agent starting or stopping following it.");
|
|
277
|
+
export const serverEventSchema = z.union([snapshotEventSchema, askEventSchema]).describe("A frame on the event socket.");
|
|
278
|
+
export const Session_whoamiResponses = [{ status: 200, schema: whoamiSchema, contentTypes: ["application/json"] }, { status: 401, schema: problemSchema, contentTypes: ["application/json"] }];
|
|
279
|
+
export const EventTickets_issueResponses = [{ status: 201, schema: eventTicketSchema, contentTypes: ["application/json"] }, { status: 401, schema: problemSchema, contentTypes: ["application/json"] }];
|
|
280
|
+
export const Snapshots_readResponses = [{ status: 200, schema: snapshotSchema, contentTypes: ["application/json"] }, { status: 401, schema: problemSchema, contentTypes: ["application/json"] }];
|
|
281
|
+
export const Asks_listQuery = z.object({
|
|
282
|
+
before: z.iso.datetime({ offset: true }).exactOptional().describe("Only asks closed before this moment."),
|
|
283
|
+
limit: z.preprocess((raw) => wireRead(raw, z.int32().min(1).max(100).exactOptional()), z.int32().min(1).max(100).exactOptional()),
|
|
284
|
+
});
|
|
285
|
+
export const Asks_listResponses = [{ status: 200, schema: askPageSchema, contentTypes: ["application/json"] }, { status: 401, schema: problemSchema, contentTypes: ["application/json"] }];
|
|
286
|
+
export const Asks_readPath = z.object({
|
|
287
|
+
id: z.string(),
|
|
288
|
+
});
|
|
289
|
+
export const Asks_readResponses = [{ status: 200, schema: askSchema, contentTypes: ["application/json"] }, { status: 401, schema: problemSchema, contentTypes: ["application/json"] }, { status: 404, schema: problemSchema, contentTypes: ["application/json"] }];
|
|
290
|
+
export const Asks_replyPath = z.object({
|
|
291
|
+
id: z.string(),
|
|
292
|
+
});
|
|
293
|
+
export const Asks_replyResponses = [{ status: 200, schema: askSchema, contentTypes: ["application/json"] }, { status: 401, schema: problemSchema, contentTypes: ["application/json"] }, { status: 404, schema: problemSchema, contentTypes: ["application/json"] }, { status: 409, schema: problemSchema, contentTypes: ["application/json"] }, { status: 422, schema: problemSchema, contentTypes: ["application/json"] }];
|
|
294
|
+
export const Files_readPath = z.object({
|
|
295
|
+
uploadId: z.string(),
|
|
296
|
+
});
|
|
297
|
+
export const Files_readResponse200 = z.unknown();
|
|
298
|
+
export const Files_readResponses = [{ status: 200, schema: Files_readResponse200, contentTypes: ["image/png", "image/jpeg", "image/webp", "image/gif"] }, { status: 401, schema: problemSchema, contentTypes: ["application/json"] }, { status: 404, schema: problemSchema, contentTypes: ["application/json"] }];
|
|
299
|
+
export const Uploads_storePath = z.object({
|
|
300
|
+
spaceId: z.string(),
|
|
301
|
+
token: z.string(),
|
|
302
|
+
});
|
|
303
|
+
export const Uploads_storeHeader = z.object({
|
|
304
|
+
"Content-Type": z.string(),
|
|
305
|
+
});
|
|
306
|
+
export const Uploads_storeResponses = [{ status: 201, schema: uploadReceiptSchema, contentTypes: ["application/json"] }, { status: 404, schema: problemSchema, contentTypes: ["application/json"] }, { status: 410, schema: problemSchema, contentTypes: ["application/json"] }, { status: 413, schema: problemSchema, contentTypes: ["application/json"] }, { status: 422, schema: problemSchema, contentTypes: ["application/json"] }];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { z } from "zod";
|
|
2
|
+
import type * as Contracts from "@loopbacktools/protocol";
|
|
3
|
+
import type { replySchema, whoamiSchema, problemSchema, eventTicketSchema, askSchema, snapshotSchema, askPageSchema, uploadReceiptSchema } from "./schemas.gen.js";
|
|
4
|
+
/**
|
|
5
|
+
* Exact structural equality. The naive `A extends B ? B extends A` form reports two object types
|
|
6
|
+
* equal when one has an optional property the other lacks, which is the whole class of drift this
|
|
7
|
+
* exists to catch; the identity-function comparison does not.
|
|
8
|
+
*/
|
|
9
|
+
type Identical<A, B> = (<T>() => T extends A ? 1 : 2) extends <T>() => (T extends B ? 1 : 2) ? true : ["the emitted validator and the shared wire type describe different shapes"];
|
|
10
|
+
/**
|
|
11
|
+
* A shape with its index signatures removed, at every depth.
|
|
12
|
+
*
|
|
13
|
+
* **The two sides of this contract differ on exactly one axis, deliberately.** For an open model the
|
|
14
|
+
* validator's output carries `[key: string]: unknown`, because a loose parse really does pass
|
|
15
|
+
* unknown keys through. The shared wire type does not, because it is a FLOOR - the properties a
|
|
16
|
+
* producer must supply - and an index signature there is an obligation on the producer rather than a
|
|
17
|
+
* description of the payload. TypeScript gives an interface no implicit index signature, so carrying
|
|
18
|
+
* it made the published shape unsatisfiable by most domain types.
|
|
19
|
+
*
|
|
20
|
+
* So this comparison is over DECLARED PROPERTIES, the only claim both sides make. **That the
|
|
21
|
+
* validator is open wherever the document is open has not stopped being checked** - it is asserted
|
|
22
|
+
* directly by the emitter's own suite now, instead of falling out of this comparison as a side
|
|
23
|
+
* effect. Every other kind of drift still fails here exactly as before.
|
|
24
|
+
*
|
|
25
|
+
* Applied to the INFERRED side only. If the shared type ever grew an index signature of its own that
|
|
26
|
+
* is a real disagreement, and this still catches it.
|
|
27
|
+
*
|
|
28
|
+
* Deep, because openness recurs: a nested open model puts one at every level, which is why a single
|
|
29
|
+
* spread never repaired the shape it was supposed to repair.
|
|
30
|
+
*
|
|
31
|
+
* **An index signature arrives two ways and only one of them is openness.** A model spreading
|
|
32
|
+
* `...Record<unknown>` gains one because its validator is loose - a fact about the MODEL, which the
|
|
33
|
+
* contract type does not state, so it is stripped. A property declared `Record<unknown>` gains one
|
|
34
|
+
* because that IS its type, stated identically in both artefacts, and stripping it destroys the
|
|
35
|
+
* property: `Declared<Record<string, unknown>>` became `{}`, and any spec with a dictionary
|
|
36
|
+
* property emitted an assertion that does not compile.
|
|
37
|
+
*
|
|
38
|
+
* The two are told apart the same way `types.ts` already tells them apart: **a string indexer means
|
|
39
|
+
* "dictionary" only when there are no declared properties beside it.** So the index signature is
|
|
40
|
+
* dropped from a shape that has other keys, and kept - with its value type still mapped - from one
|
|
41
|
+
* that does not.
|
|
42
|
+
*/
|
|
43
|
+
type DeclaredKeyOf<T> = keyof {
|
|
44
|
+
[K in keyof T as string extends K ? never : number extends K ? never : symbol extends K ? never : K]: 0;
|
|
45
|
+
};
|
|
46
|
+
type Declared<T> = T extends (...args: never[]) => unknown ? T : T extends readonly unknown[] ? {
|
|
47
|
+
[K in keyof T]: Declared<T[K]>;
|
|
48
|
+
} : T extends object ? [DeclaredKeyOf<T>] extends [never] ? [T[keyof T]] extends [never] ? {} : {
|
|
49
|
+
[K in keyof T]: Declared<T[K]>;
|
|
50
|
+
} : {
|
|
51
|
+
[K in keyof T as string extends K ? never : number extends K ? never : symbol extends K ? never : K]: Declared<T[K]>;
|
|
52
|
+
} : T;
|
|
53
|
+
/**
|
|
54
|
+
* **The contract type with its intersections written out**, as `Declared` writes out the validator's:
|
|
55
|
+
* `(A) & (Inner)` - a redeclaration merged with a `@oneOf` union it cannot be merged into key by key
|
|
56
|
+
* - and the one object type a mapping makes of it are one shape, which the identity check below tells
|
|
57
|
+
* apart by spelling. Mapped and nothing dropped, so an index signature the contract states is still
|
|
58
|
+
* compared.
|
|
59
|
+
*/
|
|
60
|
+
type Flat<T> = T extends (...args: never[]) => unknown ? T : T extends object ? [keyof T] extends [never] ? {} : {
|
|
61
|
+
[K in keyof T]: Flat<T[K]>;
|
|
62
|
+
} : T;
|
|
63
|
+
/**
|
|
64
|
+
* **The constraint is what fails the build.** `Identical<A, B> & true` merely produces an odd
|
|
65
|
+
* type when the two disagree, and a type alias to an odd type compiles fine - an assertion that
|
|
66
|
+
* asserts nothing. Only a constraint violation is an error.
|
|
67
|
+
*/
|
|
68
|
+
type MustHold<T extends true> = T;
|
|
69
|
+
export type AssertReply = MustHold<Identical<Declared<z.input<typeof replySchema>>, Flat<Contracts.Reply>>>;
|
|
70
|
+
export type AssertWhoami = MustHold<Identical<Declared<z.input<typeof whoamiSchema>>, Flat<Contracts.Whoami>>>;
|
|
71
|
+
export type AssertProblem = MustHold<Identical<Declared<z.input<typeof problemSchema>>, Flat<Contracts.Problem>>>;
|
|
72
|
+
export type AssertEventTicket = MustHold<Identical<Declared<z.input<typeof eventTicketSchema>>, Flat<Contracts.EventTicket>>>;
|
|
73
|
+
export type AssertAsk = MustHold<Identical<Declared<z.input<typeof askSchema>>, Flat<Contracts.Ask>>>;
|
|
74
|
+
export type AssertSnapshot = MustHold<Identical<Declared<z.input<typeof snapshotSchema>>, Flat<Contracts.Snapshot>>>;
|
|
75
|
+
export type AssertAskPage = MustHold<Identical<Declared<z.input<typeof askPageSchema>>, Flat<Contracts.AskPage>>>;
|
|
76
|
+
export type AssertUploadReceipt = MustHold<Identical<Declared<z.input<typeof uploadReceiptSchema>>, Flat<Contracts.UploadReceipt>>>;
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { createLoopback, type LoopbackOptions } from "./loopback.ts";
|
|
2
|
+
export { VERSION } from "./version.ts";
|
|
3
|
+
export { Space } from "./space/object.ts";
|
|
4
|
+
export { Declined, Unauthorized } from "./domain/errors.ts";
|
|
5
|
+
export type { Declinable, SpaceClient } from "./space/rpc.ts";
|
|
6
|
+
export { eraseSpace, Spaces } from "./worker/spaces.ts";
|
|
7
|
+
export type { LoopbackBindings, WorkerBindings } from "./worker/env.ts";
|
|
8
|
+
export { WorkerEnv } from "./worker/env.ts";
|
|
9
|
+
export { type Access, credentialOf, DEFAULT_SPACE, Gate, NotConfigured, TokenGate, } from "./worker/gate.ts";
|
|
10
|
+
export { ConfiguredGate, jwtGate, type JwtGateOptions } from "./worker/jwt.ts";
|
|
11
|
+
export type { InboxLink } from "./worker/mcp.ts";
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createLoopback } from "./loopback.js";
|
|
2
|
+
export { VERSION } from "./version.js";
|
|
3
|
+
export { Space } from "./space/object.js";
|
|
4
|
+
export { Declined, Unauthorized } from "./domain/errors.js";
|
|
5
|
+
export { eraseSpace, Spaces } from "./worker/spaces.js";
|
|
6
|
+
export { WorkerEnv } from "./worker/env.js";
|
|
7
|
+
export { credentialOf, DEFAULT_SPACE, Gate, NotConfigured, TokenGate, } from "./worker/gate.js";
|
|
8
|
+
export { ConfiguredGate, jwtGate } from "./worker/jwt.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Layer } from "effect";
|
|
2
|
+
import type { LoopbackBindings, WorkerEnv } from "./worker/env.ts";
|
|
3
|
+
import type { Gate } from "./worker/gate.ts";
|
|
4
|
+
import { type InboxLink } from "./worker/mcp.ts";
|
|
5
|
+
import { Spaces } from "./worker/spaces.ts";
|
|
6
|
+
export interface LoopbackOptions {
|
|
7
|
+
/** Decides who each request is from and which space it opens. */
|
|
8
|
+
readonly gate: Layer.Layer<Gate, never, WorkerEnv>;
|
|
9
|
+
/**
|
|
10
|
+
* How requests reach their spaces. The Worker's own `Space` objects unless said otherwise: a host
|
|
11
|
+
* wraps `Spaces.layer` to meter what its tenants open, or to decline it with a reason.
|
|
12
|
+
*/
|
|
13
|
+
readonly spaces?: Layer.Layer<Spaces, never, WorkerEnv>;
|
|
14
|
+
/**
|
|
15
|
+
* Where the person reads and answers a space's asks, which `open_inbox` gives agents. This
|
|
16
|
+
* Worker's own inbox unless said otherwise: a host whose frontend lives elsewhere, or routes by
|
|
17
|
+
* space, says where.
|
|
18
|
+
*/
|
|
19
|
+
readonly inboxUrl?: (link: InboxLink) => string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A Loopback Worker's `fetch` handler. The deployment exports it as its default, beside the `Space`
|
|
23
|
+
* Durable Object class declared with SQLite storage, and binds `FILES`.
|
|
24
|
+
*/
|
|
25
|
+
export declare const createLoopback: (options: LoopbackOptions) => {
|
|
26
|
+
fetch: (request: Request, env: LoopbackBindings, ctx: ExecutionContext) => Promise<Response> | Response;
|
|
27
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { makeApp } from "./worker/http.js";
|
|
2
|
+
import { defaultInboxUrl } from "./worker/mcp.js";
|
|
3
|
+
import { runtimes } from "./worker/run.js";
|
|
4
|
+
import { Spaces } from "./worker/spaces.js";
|
|
5
|
+
/**
|
|
6
|
+
* A Loopback Worker's `fetch` handler. The deployment exports it as its default, beside the `Space`
|
|
7
|
+
* Durable Object class declared with SQLite storage, and binds `FILES`.
|
|
8
|
+
*/
|
|
9
|
+
export const createLoopback = (options) => {
|
|
10
|
+
const app = makeApp(runtimes(options.gate, options.spaces ?? Spaces.layer), options.inboxUrl ?? defaultInboxUrl);
|
|
11
|
+
return {
|
|
12
|
+
// Any environment that declares Loopback's bindings: the host's own type, such as the one
|
|
13
|
+
// `wrangler types` writes, need not say it holds nothing else.
|
|
14
|
+
fetch: (request, env, ctx) => app.fetch(request, env, ctx),
|
|
15
|
+
};
|
|
16
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { Effect } from "effect";
|
|
2
|
+
import type { Operations } from "./operations.ts";
|
|
3
|
+
/**
|
|
4
|
+
* A call's arguments, decoded for its operation. Arguments that don't decode, or that hold something
|
|
5
|
+
* from a newer revision, are a fault in the caller: the operation never runs on them, and nothing is
|
|
6
|
+
* kept. A caller on a newer revision is refused as `Outdated` before this.
|
|
7
|
+
*/
|
|
8
|
+
export declare const readArguments: <K extends keyof Operations>(method: K, args: readonly unknown[]) => Effect.Effect<Parameters<Operations[K]>>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as Contract from "@loopbacktools/protocol/schema";
|
|
2
|
+
import { Effect, Result, Schema } from "effect";
|
|
3
|
+
import { read } from "./read.js";
|
|
4
|
+
/**
|
|
5
|
+
* What each space operation takes, as the space decodes it before running anything: the contract's
|
|
6
|
+
* messages with the schemas generated from it, and the ids and numbers beside them. Nothing a call
|
|
7
|
+
* passes is run unread. See docs/design/versioned-messages.md.
|
|
8
|
+
*/
|
|
9
|
+
const Id = Schema.String;
|
|
10
|
+
const Count = Schema.Int.check(Schema.isGreaterThanOrEqualTo(0));
|
|
11
|
+
const opening = (kind, input) => Schema.Struct({
|
|
12
|
+
kind: Schema.Literal(kind),
|
|
13
|
+
input,
|
|
14
|
+
client: Schema.optionalKey(Schema.String),
|
|
15
|
+
actor: Contract.Actor,
|
|
16
|
+
});
|
|
17
|
+
const OpenRequest = Schema.Union([
|
|
18
|
+
opening("question", Contract.QuestionInput),
|
|
19
|
+
opening("review", Contract.ReviewInput),
|
|
20
|
+
opening("briefing", Contract.BriefingInput),
|
|
21
|
+
]);
|
|
22
|
+
const taking = {
|
|
23
|
+
open: Schema.Tuple([OpenRequest]),
|
|
24
|
+
answerFollowUp: Schema.Tuple([Contract.FollowUpInput]),
|
|
25
|
+
answerBriefing: Schema.Tuple([Contract.BriefingAnswerInput]),
|
|
26
|
+
awaitReply: Schema.Tuple([Id, Count]),
|
|
27
|
+
markDelivered: Schema.Tuple([Id, Count]),
|
|
28
|
+
followTask: Schema.Tuple([Id, Count]),
|
|
29
|
+
cancelTask: Schema.Tuple([Id, Count]),
|
|
30
|
+
reply: Schema.Tuple([Id, Contract.Reply, Contract.Actor]),
|
|
31
|
+
get: Schema.Tuple([Id]),
|
|
32
|
+
snapshot: Schema.Tuple([]),
|
|
33
|
+
history: Schema.Tuple([Schema.UndefinedOr(Schema.String), Count]),
|
|
34
|
+
createUpload: Schema.Tuple([Contract.ImageType]),
|
|
35
|
+
claimUpload: Schema.Tuple([Schema.String, Schema.String]),
|
|
36
|
+
completeUpload: Schema.Tuple([Id, Count]),
|
|
37
|
+
findUpload: Schema.Tuple([Id]),
|
|
38
|
+
issueTicket: Schema.Tuple([Id]),
|
|
39
|
+
redeemTicket: Schema.Tuple([Schema.String]),
|
|
40
|
+
};
|
|
41
|
+
/** The compiler's check that every operation has a schema, and that none refuses what it accepts. */
|
|
42
|
+
const everyOperationRead = true;
|
|
43
|
+
void everyOperationRead;
|
|
44
|
+
/**
|
|
45
|
+
* A call's arguments, decoded for its operation. Arguments that don't decode, or that hold something
|
|
46
|
+
* from a newer revision, are a fault in the caller: the operation never runs on them, and nothing is
|
|
47
|
+
* kept. A caller on a newer revision is refused as `Outdated` before this.
|
|
48
|
+
*/
|
|
49
|
+
export const readArguments = (method, args) => Result.match(read(taking[method], args), {
|
|
50
|
+
onSuccess: (decoded) => Effect.succeed(decoded),
|
|
51
|
+
onFailure: (why) => Effect.die(new Error(`Space.${method} can't read its arguments: ${why}`)),
|
|
52
|
+
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { SqliteMigrator } from "@effect/sql-sqlite-do";
|
|
2
|
+
import { SqlClient } from "effect/sql";
|
|
3
|
+
/**
|
|
4
|
+
* Gives every review kept without content a note saying so. A space still on code from before
|
|
5
|
+
* briefings, reached during a deploy by a Worker that knew them, kept a briefing as a review with
|
|
6
|
+
* no content: a record the contract refuses, which no inbox could show and no reply could return.
|
|
7
|
+
*/
|
|
8
|
+
export declare const REPAIR_CONTENTLESS_REVIEWS = "\n\tUPDATE asks\n\tSET prompt = json_set(\n\t\tprompt,\n\t\t'$.content',\n\t\tjson('[{\"type\":\"markdown\",\"text\":\"This arrived while Loopback was being updated, and what it showed was lost. Dismiss it, and the agent can send it again.\"}]')\n\t)\n\tWHERE kind = 'review' AND json_extract(prompt, '$.content') IS NULL\n";
|
|
9
|
+
/** The space's schema, applied in order when its object starts. Never edit one that has shipped. */
|
|
10
|
+
export declare const migrations: import("effect/Layer").Layer<never, SqliteMigrator.MigrationError | import("effect/sql/SqlError").SqlError, SqlClient.SqlClient>;
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { SqliteMigrator } from "@effect/sql-sqlite-do";
|
|
2
|
+
import { Effect } from "effect";
|
|
3
|
+
import { SqlClient, Migrator } from "effect/sql";
|
|
4
|
+
/**
|
|
5
|
+
* Gives every review kept without content a note saying so. A space still on code from before
|
|
6
|
+
* briefings, reached during a deploy by a Worker that knew them, kept a briefing as a review with
|
|
7
|
+
* no content: a record the contract refuses, which no inbox could show and no reply could return.
|
|
8
|
+
*/
|
|
9
|
+
export const REPAIR_CONTENTLESS_REVIEWS = `
|
|
10
|
+
UPDATE asks
|
|
11
|
+
SET prompt = json_set(
|
|
12
|
+
prompt,
|
|
13
|
+
'$.content',
|
|
14
|
+
json('[{"type":"markdown","text":"This arrived while Loopback was being updated, and what it showed was lost. Dismiss it, and the agent can send it again."}]')
|
|
15
|
+
)
|
|
16
|
+
WHERE kind = 'review' AND json_extract(prompt, '$.content') IS NULL
|
|
17
|
+
`;
|
|
18
|
+
/** The space's schema, applied in order when its object starts. Never edit one that has shipped. */
|
|
19
|
+
export const migrations = SqliteMigrator.layer({
|
|
20
|
+
loader: Migrator.fromRecord({
|
|
21
|
+
"0001_asks": Effect.gen(function* () {
|
|
22
|
+
const sql = yield* SqlClient.SqlClient;
|
|
23
|
+
yield* sql `
|
|
24
|
+
CREATE TABLE asks (
|
|
25
|
+
id TEXT PRIMARY KEY,
|
|
26
|
+
kind TEXT NOT NULL,
|
|
27
|
+
key TEXT NOT NULL,
|
|
28
|
+
status TEXT NOT NULL,
|
|
29
|
+
prompt TEXT NOT NULL,
|
|
30
|
+
project TEXT,
|
|
31
|
+
client TEXT,
|
|
32
|
+
revises TEXT,
|
|
33
|
+
created_at TEXT NOT NULL,
|
|
34
|
+
updated_at TEXT NOT NULL,
|
|
35
|
+
closed_at TEXT,
|
|
36
|
+
last_waited_at TEXT
|
|
37
|
+
)
|
|
38
|
+
`;
|
|
39
|
+
yield* sql `CREATE INDEX asks_by_key ON asks (key)`;
|
|
40
|
+
yield* sql `CREATE INDEX asks_by_status ON asks (status, created_at)`;
|
|
41
|
+
yield* sql `CREATE INDEX asks_by_closed_at ON asks (closed_at)`;
|
|
42
|
+
yield* sql `
|
|
43
|
+
CREATE TABLE entries (
|
|
44
|
+
ask_id TEXT NOT NULL REFERENCES asks (id),
|
|
45
|
+
seq INTEGER NOT NULL,
|
|
46
|
+
author TEXT NOT NULL,
|
|
47
|
+
body TEXT NOT NULL,
|
|
48
|
+
delivered_at TEXT,
|
|
49
|
+
PRIMARY KEY (ask_id, seq)
|
|
50
|
+
)
|
|
51
|
+
`;
|
|
52
|
+
yield* sql `
|
|
53
|
+
CREATE TABLE uploads (
|
|
54
|
+
id TEXT PRIMARY KEY,
|
|
55
|
+
token_hash TEXT NOT NULL UNIQUE,
|
|
56
|
+
content_type TEXT NOT NULL,
|
|
57
|
+
max_bytes INTEGER NOT NULL,
|
|
58
|
+
status TEXT NOT NULL,
|
|
59
|
+
size INTEGER,
|
|
60
|
+
created_at TEXT NOT NULL,
|
|
61
|
+
expires_at TEXT NOT NULL
|
|
62
|
+
)
|
|
63
|
+
`;
|
|
64
|
+
}),
|
|
65
|
+
"0002_task_cancellations": Effect.gen(function* () {
|
|
66
|
+
const sql = yield* SqlClient.SqlClient;
|
|
67
|
+
// An agent's background task follows the reply to one of its turns. Cancelling it is
|
|
68
|
+
// remembered, so the task keeps answering "cancelled" rather than going unknown.
|
|
69
|
+
yield* sql `
|
|
70
|
+
CREATE TABLE task_cancellations (
|
|
71
|
+
ask_id TEXT NOT NULL REFERENCES asks (id),
|
|
72
|
+
after_seq INTEGER NOT NULL,
|
|
73
|
+
cancelled_at TEXT NOT NULL,
|
|
74
|
+
PRIMARY KEY (ask_id, after_seq)
|
|
75
|
+
)
|
|
76
|
+
`;
|
|
77
|
+
}),
|
|
78
|
+
"0003_event_tickets": Effect.gen(function* () {
|
|
79
|
+
const sql = yield* SqlClient.SqlClient;
|
|
80
|
+
// One-time passes onto the event socket, for browsers, which cannot send headers there.
|
|
81
|
+
yield* sql `
|
|
82
|
+
CREATE TABLE event_tickets (
|
|
83
|
+
token_hash TEXT PRIMARY KEY,
|
|
84
|
+
actor_id TEXT NOT NULL,
|
|
85
|
+
expires_at TEXT NOT NULL
|
|
86
|
+
)
|
|
87
|
+
`;
|
|
88
|
+
}),
|
|
89
|
+
"0004_raised_by": Effect.gen(function* () {
|
|
90
|
+
const sql = yield* SqlClient.SqlClient;
|
|
91
|
+
// Whose agent asked, as the gate named them; asks opened before it was recorded have none.
|
|
92
|
+
yield* sql `ALTER TABLE asks ADD COLUMN raised_by TEXT`;
|
|
93
|
+
}),
|
|
94
|
+
"0005_repair_contentless_reviews": Effect.gen(function* () {
|
|
95
|
+
const sql = yield* SqlClient.SqlClient;
|
|
96
|
+
yield* sql.unsafe(REPAIR_CONTENTLESS_REVIEWS);
|
|
97
|
+
}),
|
|
98
|
+
"0006_revisions": Effect.gen(function* () {
|
|
99
|
+
const sql = yield* SqlClient.SqlClient;
|
|
100
|
+
// The revision of the contract each ask and entry was written at. Rows from before it was
|
|
101
|
+
// recorded are revision 1, as is anything code from before then writes after a rollback.
|
|
102
|
+
yield* sql `ALTER TABLE asks ADD COLUMN revision INTEGER NOT NULL DEFAULT 1`;
|
|
103
|
+
yield* sql `ALTER TABLE entries ADD COLUMN revision INTEGER NOT NULL DEFAULT 1`;
|
|
104
|
+
}),
|
|
105
|
+
}),
|
|
106
|
+
});
|