@camox/api-contract 0.62.0 → 0.65.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/dist/analytics.d.ts +1 -1
- package/dist/drafts.d.ts +50 -2
- package/dist/drafts.js +57 -3
- package/dist/layout-routes.d.ts +20 -0
- package/dist/layout-routes.js +43 -0
- package/dist/locales.d.ts +2 -0
- package/dist/locales.js +7 -1
- package/dist/patrol.d.ts +116 -8
- package/dist/patrol.js +33 -1
- package/dist/router.d.ts +7166 -3317
- package/dist/threads.d.ts +69 -12
- package/dist/threads.js +70 -8
- package/package.json +8 -2
- package/src/drafts.ts +67 -2
- package/src/layout-routes.ts +51 -0
- package/src/locales.ts +7 -0
- package/src/patrol.ts +129 -9
- package/src/threads.ts +95 -4
package/dist/threads.d.ts
CHANGED
|
@@ -10,6 +10,14 @@ import { Message } from "@earendil-works/pi-ai";
|
|
|
10
10
|
* studio as they sent it (`editorTextOf`).
|
|
11
11
|
*/
|
|
12
12
|
export declare const EDITOR_MESSAGE_ENTRY = "camox.editor-message";
|
|
13
|
+
/**
|
|
14
|
+
* The kind of the transcript entry recording a Connected Agent's message (ADR 0030), with its
|
|
15
|
+
* member and client as data (`connectedAgentMessageDataSchema`). The model reads it as a user
|
|
16
|
+
* message, as it does an editor's: its first text part is what the Connected Agent wrote, and a
|
|
17
|
+
* second one, which only the model reads, says who sent it. Studios show it as the member's AI,
|
|
18
|
+
* never as the member.
|
|
19
|
+
*/
|
|
20
|
+
export declare const CONNECTED_AGENT_MESSAGE_ENTRY = "camox.connected-agent-message";
|
|
13
21
|
/** The kind of the transcript entry recording that an editor stopped a run, with them as data. */
|
|
14
22
|
export declare const RUN_STOPPED_ENTRY = "camox.run-stopped";
|
|
15
23
|
/**
|
|
@@ -27,7 +35,8 @@ export declare const RECONCILE_ENTRY = "camox.reconcile";
|
|
|
27
35
|
/**
|
|
28
36
|
* The kind of the transcript entry opening a draft's thread a Live thread handed a request off
|
|
29
37
|
* to: the Brief, which the model reads and editors see as from Camox, ahead of the editor's
|
|
30
|
-
* messages.
|
|
38
|
+
* messages. In a draft Camox opened, such as its Patrol Draft, Camox's own messages are Briefs
|
|
39
|
+
* too, with their `operationId` as data, and start the agent's run, which acts for Camox.
|
|
31
40
|
*/
|
|
32
41
|
export declare const BRIEF_ENTRY = "camox.brief";
|
|
33
42
|
/**
|
|
@@ -45,8 +54,8 @@ export declare const PUSH_ENTRY = "camox.push";
|
|
|
45
54
|
/** How a pushed head's build ended: built, built with type errors, or built but not previewed. */
|
|
46
55
|
export declare const pushOutcomeSchema: z.ZodEnum<{
|
|
47
56
|
built: "built";
|
|
48
|
-
typeErrors: "typeErrors";
|
|
49
57
|
notPreviewed: "notPreviewed";
|
|
58
|
+
typeErrors: "typeErrors";
|
|
50
59
|
}>;
|
|
51
60
|
export type PushOutcome = z.infer<typeof pushOutcomeSchema>;
|
|
52
61
|
/**
|
|
@@ -56,6 +65,8 @@ export type PushOutcome = z.infer<typeof pushOutcomeSchema>;
|
|
|
56
65
|
export declare const START_DRAFT_TOOL = "startDraft";
|
|
57
66
|
/** The agent's tool building its code changes mid-turn, which the end of a turn also does. */
|
|
58
67
|
export declare const BUILD_TOOL = "build";
|
|
68
|
+
/** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
|
|
69
|
+
export declare const LOOK_TOOL = "look";
|
|
59
70
|
/**
|
|
60
71
|
* The tools whose calls change a draft: the content tools that write, and the code tools that
|
|
61
72
|
* write files. The model calls the code tools, and code mode scripts call both. A test in the API
|
|
@@ -103,6 +114,32 @@ export declare const threadSenderSchema: z.ZodObject<{
|
|
|
103
114
|
export type ThreadSender = z.infer<typeof threadSenderSchema>;
|
|
104
115
|
/** Whether `data` names a sender. */
|
|
105
116
|
export declare const isSender: (data: unknown) => data is ThreadSender;
|
|
117
|
+
/**
|
|
118
|
+
* The app a Connected Agent runs in, by the name it gave: verified when it comes from a Client ID
|
|
119
|
+
* Metadata Document, self-asserted otherwise.
|
|
120
|
+
*/
|
|
121
|
+
export declare const connectedAgentClientSchema: z.ZodObject<{
|
|
122
|
+
name: z.ZodString;
|
|
123
|
+
verified: z.ZodBoolean;
|
|
124
|
+
}, z.core.$strip>;
|
|
125
|
+
export type ConnectedAgentClient = z.infer<typeof connectedAgentClientSchema>;
|
|
126
|
+
/**
|
|
127
|
+
* What a Connected Agent's message entry records: the member it acts as, its client, and the
|
|
128
|
+
* operation id it was sent with.
|
|
129
|
+
*/
|
|
130
|
+
export declare const connectedAgentMessageDataSchema: z.ZodObject<{
|
|
131
|
+
member: z.ZodObject<{
|
|
132
|
+
id: z.ZodString;
|
|
133
|
+
name: z.ZodString;
|
|
134
|
+
image: z.ZodNullable<z.ZodString>;
|
|
135
|
+
}, z.core.$strip>;
|
|
136
|
+
client: z.ZodObject<{
|
|
137
|
+
name: z.ZodString;
|
|
138
|
+
verified: z.ZodBoolean;
|
|
139
|
+
}, z.core.$strip>;
|
|
140
|
+
operationId: z.ZodOptional<z.ZodString>;
|
|
141
|
+
}, z.core.$strip>;
|
|
142
|
+
export type ConnectedAgentMessageData = z.infer<typeof connectedAgentMessageDataSchema>;
|
|
106
143
|
/** What a push's entry records: who pushed, their commits' subjects, oldest first, and the build. */
|
|
107
144
|
export declare const pushDataSchema: z.ZodObject<{
|
|
108
145
|
pusher: z.ZodObject<{
|
|
@@ -113,8 +150,8 @@ export declare const pushDataSchema: z.ZodObject<{
|
|
|
113
150
|
subjects: z.ZodArray<z.ZodString>;
|
|
114
151
|
outcome: z.ZodEnum<{
|
|
115
152
|
built: "built";
|
|
116
|
-
typeErrors: "typeErrors";
|
|
117
153
|
notPreviewed: "notPreviewed";
|
|
154
|
+
typeErrors: "typeErrors";
|
|
118
155
|
}>;
|
|
119
156
|
}, z.core.$strip>;
|
|
120
157
|
export type PushData = z.infer<typeof pushDataSchema>;
|
|
@@ -142,18 +179,18 @@ export type StartedDraft = z.infer<typeof startedDraftSchema>;
|
|
|
142
179
|
/** How a write touched what it names. */
|
|
143
180
|
export declare const touchKindSchema: z.ZodEnum<{
|
|
144
181
|
created: "created";
|
|
182
|
+
deleted: "deleted";
|
|
145
183
|
edited: "edited";
|
|
146
184
|
moved: "moved";
|
|
147
|
-
deleted: "deleted";
|
|
148
185
|
settings: "settings";
|
|
149
186
|
}>;
|
|
150
187
|
export type TouchKind = z.infer<typeof touchKindSchema>;
|
|
151
188
|
export declare const touchSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
152
189
|
kind: z.ZodEnum<{
|
|
153
190
|
created: "created";
|
|
191
|
+
deleted: "deleted";
|
|
154
192
|
edited: "edited";
|
|
155
193
|
moved: "moved";
|
|
156
|
-
deleted: "deleted";
|
|
157
194
|
settings: "settings";
|
|
158
195
|
}>;
|
|
159
196
|
locale: z.ZodOptional<z.ZodString>;
|
|
@@ -163,9 +200,9 @@ export declare const touchSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
163
200
|
}, z.core.$strip>, z.ZodObject<{
|
|
164
201
|
kind: z.ZodEnum<{
|
|
165
202
|
created: "created";
|
|
203
|
+
deleted: "deleted";
|
|
166
204
|
edited: "edited";
|
|
167
205
|
moved: "moved";
|
|
168
|
-
deleted: "deleted";
|
|
169
206
|
settings: "settings";
|
|
170
207
|
}>;
|
|
171
208
|
locale: z.ZodOptional<z.ZodString>;
|
|
@@ -174,9 +211,9 @@ export declare const touchSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
174
211
|
}, z.core.$strip>, z.ZodObject<{
|
|
175
212
|
kind: z.ZodEnum<{
|
|
176
213
|
created: "created";
|
|
214
|
+
deleted: "deleted";
|
|
177
215
|
edited: "edited";
|
|
178
216
|
moved: "moved";
|
|
179
|
-
deleted: "deleted";
|
|
180
217
|
settings: "settings";
|
|
181
218
|
}>;
|
|
182
219
|
locale: z.ZodOptional<z.ZodString>;
|
|
@@ -194,9 +231,9 @@ export declare const touchedBatchSchema: z.ZodObject<{
|
|
|
194
231
|
touches: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
195
232
|
kind: z.ZodEnum<{
|
|
196
233
|
created: "created";
|
|
234
|
+
deleted: "deleted";
|
|
197
235
|
edited: "edited";
|
|
198
236
|
moved: "moved";
|
|
199
|
-
deleted: "deleted";
|
|
200
237
|
settings: "settings";
|
|
201
238
|
}>;
|
|
202
239
|
locale: z.ZodOptional<z.ZodString>;
|
|
@@ -206,9 +243,9 @@ export declare const touchedBatchSchema: z.ZodObject<{
|
|
|
206
243
|
}, z.core.$strip>, z.ZodObject<{
|
|
207
244
|
kind: z.ZodEnum<{
|
|
208
245
|
created: "created";
|
|
246
|
+
deleted: "deleted";
|
|
209
247
|
edited: "edited";
|
|
210
248
|
moved: "moved";
|
|
211
|
-
deleted: "deleted";
|
|
212
249
|
settings: "settings";
|
|
213
250
|
}>;
|
|
214
251
|
locale: z.ZodOptional<z.ZodString>;
|
|
@@ -217,9 +254,9 @@ export declare const touchedBatchSchema: z.ZodObject<{
|
|
|
217
254
|
}, z.core.$strip>, z.ZodObject<{
|
|
218
255
|
kind: z.ZodEnum<{
|
|
219
256
|
created: "created";
|
|
257
|
+
deleted: "deleted";
|
|
220
258
|
edited: "edited";
|
|
221
259
|
moved: "moved";
|
|
222
|
-
deleted: "deleted";
|
|
223
260
|
settings: "settings";
|
|
224
261
|
}>;
|
|
225
262
|
locale: z.ZodOptional<z.ZodString>;
|
|
@@ -236,6 +273,17 @@ export type ThreadMessage = {
|
|
|
236
273
|
sender: ThreadSender;
|
|
237
274
|
text: string;
|
|
238
275
|
} |
|
|
276
|
+
/**
|
|
277
|
+
* A member's Connected Agent's message: the member's own AI, in its client, writing as itself.
|
|
278
|
+
* Never shown as the member's own words.
|
|
279
|
+
*/
|
|
280
|
+
{
|
|
281
|
+
id: string;
|
|
282
|
+
from: "connectedAgent";
|
|
283
|
+
member: ThreadSender;
|
|
284
|
+
client: ConnectedAgentClient;
|
|
285
|
+
text: string;
|
|
286
|
+
} |
|
|
239
287
|
/** Camox's own message, starting a turn it began itself: why it did. */
|
|
240
288
|
{
|
|
241
289
|
id: string;
|
|
@@ -291,10 +339,12 @@ export type ThreadMessage = {
|
|
|
291
339
|
* - `changing`: a call of that round changes the draft (`CHANGING_TOOLS`), as soon as it's written
|
|
292
340
|
* or, in a code mode script, as soon as the script calls one.
|
|
293
341
|
* - `building`: its `build` call runs, or the end of the turn builds its code changes.
|
|
342
|
+
* - `checking`: it looks at what it designed, from writing a `look` call until the model is
|
|
343
|
+
* called again.
|
|
294
344
|
* - `fixing`: the end of the turn's build failed, and the agent fixes it.
|
|
295
345
|
* - `startingDraft`: on Live, its `startDraft` call runs.
|
|
296
346
|
*/
|
|
297
|
-
export type AgentActivity = "thinking" | "exploring" | "changing" | "building" | "fixing" | "startingDraft";
|
|
347
|
+
export type AgentActivity = "thinking" | "exploring" | "changing" | "building" | "checking" | "fixing" | "startingDraft";
|
|
298
348
|
/** Everything the studio shows for a thread. */
|
|
299
349
|
export type ThreadView = {
|
|
300
350
|
messages: readonly ThreadMessage[];
|
|
@@ -324,6 +374,8 @@ export type ThreadState = {
|
|
|
324
374
|
* or making it until the model is called again.
|
|
325
375
|
*/
|
|
326
376
|
changing?: boolean;
|
|
377
|
+
/** Whether the model's latest round calls `look`: from writing the call until it's called again. */
|
|
378
|
+
checking?: boolean;
|
|
327
379
|
/** The drafts the thread's cards name, as the thread last looked them up. */
|
|
328
380
|
drafts?: DraftCardStates;
|
|
329
381
|
};
|
|
@@ -337,6 +389,11 @@ type ThreadStep = {
|
|
|
337
389
|
text: string;
|
|
338
390
|
/** The draft the message went to, as the agent on Live handed the request off. */
|
|
339
391
|
handedOffTo?: string;
|
|
392
|
+
} | {
|
|
393
|
+
type: "connectedAgent";
|
|
394
|
+
member: ThreadSender;
|
|
395
|
+
client: ConnectedAgentClient;
|
|
396
|
+
text: string;
|
|
340
397
|
} |
|
|
341
398
|
/** Camox starting a turn of its own, saying why. */
|
|
342
399
|
{
|
|
@@ -509,7 +566,7 @@ export declare const editorTextOf: (content: Message["content"]) => string;
|
|
|
509
566
|
* run is over, or a later message started another run after its last model call came back
|
|
510
567
|
* unfinished. While Pi retries a failed call, the run goes on, so the turn shows as working.
|
|
511
568
|
*/
|
|
512
|
-
export declare function threadViewOf({ steps, running, toolCalls, callingTools, changing, drafts }: ThreadState): ThreadView;
|
|
569
|
+
export declare function threadViewOf({ steps, running, toolCalls, callingTools, changing, checking, drafts }: ThreadState): ThreadView;
|
|
513
570
|
/** Folds one of Pi's agent events into the thread's state. */
|
|
514
571
|
export declare function reduceThreadState(state: ThreadState, event: AgentEvent): ThreadState;
|
|
515
572
|
/** Folds a batch of Pi's agent events into the thread's state. */
|
package/dist/threads.js
CHANGED
|
@@ -8,6 +8,14 @@ import { z } from "zod";
|
|
|
8
8
|
* studio as they sent it (`editorTextOf`).
|
|
9
9
|
*/
|
|
10
10
|
const EDITOR_MESSAGE_ENTRY = "camox.editor-message";
|
|
11
|
+
/**
|
|
12
|
+
* The kind of the transcript entry recording a Connected Agent's message (ADR 0030), with its
|
|
13
|
+
* member and client as data (`connectedAgentMessageDataSchema`). The model reads it as a user
|
|
14
|
+
* message, as it does an editor's: its first text part is what the Connected Agent wrote, and a
|
|
15
|
+
* second one, which only the model reads, says who sent it. Studios show it as the member's AI,
|
|
16
|
+
* never as the member.
|
|
17
|
+
*/
|
|
18
|
+
const CONNECTED_AGENT_MESSAGE_ENTRY = "camox.connected-agent-message";
|
|
11
19
|
/** The kind of the transcript entry recording that an editor stopped a run, with them as data. */
|
|
12
20
|
const RUN_STOPPED_ENTRY = "camox.run-stopped";
|
|
13
21
|
/**
|
|
@@ -25,7 +33,8 @@ const RECONCILE_ENTRY = "camox.reconcile";
|
|
|
25
33
|
/**
|
|
26
34
|
* The kind of the transcript entry opening a draft's thread a Live thread handed a request off
|
|
27
35
|
* to: the Brief, which the model reads and editors see as from Camox, ahead of the editor's
|
|
28
|
-
* messages.
|
|
36
|
+
* messages. In a draft Camox opened, such as its Patrol Draft, Camox's own messages are Briefs
|
|
37
|
+
* too, with their `operationId` as data, and start the agent's run, which acts for Camox.
|
|
29
38
|
*/
|
|
30
39
|
const BRIEF_ENTRY = "camox.brief";
|
|
31
40
|
/**
|
|
@@ -53,6 +62,8 @@ const pushOutcomeSchema = z.enum([
|
|
|
53
62
|
const START_DRAFT_TOOL = "startDraft";
|
|
54
63
|
/** The agent's tool building its code changes mid-turn, which the end of a turn also does. */
|
|
55
64
|
const BUILD_TOOL = "build";
|
|
65
|
+
/** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
|
|
66
|
+
const LOOK_TOOL = "look";
|
|
56
67
|
/**
|
|
57
68
|
* The tools whose calls change a draft: the content tools that write, and the code tools that
|
|
58
69
|
* write files. The model calls the code tools, and code mode scripts call both. A test in the API
|
|
@@ -61,6 +72,7 @@ const BUILD_TOOL = "build";
|
|
|
61
72
|
const CHANGING_TOOLS = /* @__PURE__ */ new Set([
|
|
62
73
|
"addBackToLocale",
|
|
63
74
|
"addLocale",
|
|
75
|
+
"applyFindingFix",
|
|
64
76
|
"createBlock",
|
|
65
77
|
"createCollectionRecord",
|
|
66
78
|
"createPage",
|
|
@@ -97,6 +109,23 @@ const threadSenderSchema = z.object({
|
|
|
97
109
|
});
|
|
98
110
|
/** Whether `data` names a sender. */
|
|
99
111
|
const isSender = (data) => threadSenderSchema.safeParse(data).success;
|
|
112
|
+
/**
|
|
113
|
+
* The app a Connected Agent runs in, by the name it gave: verified when it comes from a Client ID
|
|
114
|
+
* Metadata Document, self-asserted otherwise.
|
|
115
|
+
*/
|
|
116
|
+
const connectedAgentClientSchema = z.object({
|
|
117
|
+
name: z.string(),
|
|
118
|
+
verified: z.boolean()
|
|
119
|
+
});
|
|
120
|
+
/**
|
|
121
|
+
* What a Connected Agent's message entry records: the member it acts as, its client, and the
|
|
122
|
+
* operation id it was sent with.
|
|
123
|
+
*/
|
|
124
|
+
const connectedAgentMessageDataSchema = z.object({
|
|
125
|
+
member: threadSenderSchema,
|
|
126
|
+
client: connectedAgentClientSchema,
|
|
127
|
+
operationId: z.string().optional()
|
|
128
|
+
});
|
|
100
129
|
/** What a push's entry records: who pushed, their commits' subjects, oldest first, and the build. */
|
|
101
130
|
const pushDataSchema = z.object({
|
|
102
131
|
pusher: threadSenderSchema,
|
|
@@ -291,6 +320,19 @@ function threadStepOf(entry) {
|
|
|
291
320
|
...handedOffTo && { handedOffTo }
|
|
292
321
|
};
|
|
293
322
|
}
|
|
323
|
+
if (entry.kind === "camox.connected-agent-message") {
|
|
324
|
+
const data = connectedAgentMessageDataSchema.safeParse(entry.data).data;
|
|
325
|
+
return data && {
|
|
326
|
+
id,
|
|
327
|
+
type: "connectedAgent",
|
|
328
|
+
member: senderOf(data.member),
|
|
329
|
+
client: {
|
|
330
|
+
name: data.client.name,
|
|
331
|
+
verified: data.client.verified
|
|
332
|
+
},
|
|
333
|
+
text: editorTextOf(message.content)
|
|
334
|
+
};
|
|
335
|
+
}
|
|
294
336
|
if (entry.kind === "camox.brief") return {
|
|
295
337
|
id,
|
|
296
338
|
type: "camox",
|
|
@@ -331,7 +373,7 @@ function threadStepOf(entry) {
|
|
|
331
373
|
* run is over, or a later message started another run after its last model call came back
|
|
332
374
|
* unfinished. While Pi retries a failed call, the run goes on, so the turn shows as working.
|
|
333
375
|
*/
|
|
334
|
-
function threadViewOf({ steps, running, toolCalls = [], callingTools = false, changing = false, drafts = {} }) {
|
|
376
|
+
function threadViewOf({ steps, running, toolCalls = [], callingTools = false, changing = false, checking = false, drafts = {} }) {
|
|
335
377
|
const messages = [];
|
|
336
378
|
let waiting;
|
|
337
379
|
let unfinished = false;
|
|
@@ -374,6 +416,17 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
|
|
|
374
416
|
waiting = step.id;
|
|
375
417
|
break;
|
|
376
418
|
}
|
|
419
|
+
case "connectedAgent":
|
|
420
|
+
if (unfinished) fail();
|
|
421
|
+
messages.push({
|
|
422
|
+
id: step.id,
|
|
423
|
+
from: "connectedAgent",
|
|
424
|
+
member: step.member,
|
|
425
|
+
client: step.client,
|
|
426
|
+
text: step.text
|
|
427
|
+
});
|
|
428
|
+
waiting = step.id;
|
|
429
|
+
break;
|
|
377
430
|
case "camox":
|
|
378
431
|
if (unfinished) fail();
|
|
379
432
|
messages.push({
|
|
@@ -458,14 +511,16 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
|
|
|
458
511
|
status,
|
|
459
512
|
toolCalls,
|
|
460
513
|
callingTools,
|
|
461
|
-
changing
|
|
514
|
+
changing,
|
|
515
|
+
checking
|
|
462
516
|
})
|
|
463
517
|
};
|
|
464
518
|
}
|
|
465
519
|
/** What the agent is doing during a run, from where the turn's build stands and its tool calls. */
|
|
466
|
-
function activityOf({ status, toolCalls, callingTools, changing }) {
|
|
520
|
+
function activityOf({ status, toolCalls, callingTools, changing, checking }) {
|
|
467
521
|
const calling = (name) => toolCalls.some((call) => call.name === name);
|
|
468
522
|
if (status === "building" || calling("build")) return "building";
|
|
523
|
+
if (checking) return "checking";
|
|
469
524
|
if (status === "fixing") return "fixing";
|
|
470
525
|
if (calling("startDraft")) return "startingDraft";
|
|
471
526
|
if (changing) return "changing";
|
|
@@ -476,6 +531,8 @@ function activityOf({ status, toolCalls, callingTools, changing }) {
|
|
|
476
531
|
const changes = (name) => CHANGING_TOOLS.has(name);
|
|
477
532
|
/** Whether a block of the model's message is a call of a tool that changes the draft. */
|
|
478
533
|
const callsChangingTool = (block) => block.type === "toolCall" && changes(block.name);
|
|
534
|
+
/** Whether a block of the model's message is a call of `look`. */
|
|
535
|
+
const callsLook = (block) => block.type === "toolCall" && block.name === "look";
|
|
479
536
|
const append = (state, entry) => {
|
|
480
537
|
const step = entry && threadStepOf(entry);
|
|
481
538
|
if (!step || state.steps.some((known) => known.id === step.id)) return state;
|
|
@@ -499,6 +556,7 @@ function reduceThreadState(state, event) {
|
|
|
499
556
|
running: event.run !== void 0,
|
|
500
557
|
toolCalls,
|
|
501
558
|
callingTools: generated.some((part) => part.type === "toolCall"),
|
|
559
|
+
checking: generated.some(callsLook) || toolCalls.some((call) => call.name === "look"),
|
|
502
560
|
changing: generated.some(callsChangingTool) || toolCalls.some((call) => changes(call.name)) || event.nestedTools.some((nested) => changes(nested.name) && toolCalls.some((call) => nested.parentCallId === call.callId || nested.parentCallId.startsWith(`${call.callId}/`)))
|
|
503
561
|
};
|
|
504
562
|
}
|
|
@@ -511,12 +569,14 @@ function reduceThreadState(state, event) {
|
|
|
511
569
|
running: false,
|
|
512
570
|
toolCalls: [],
|
|
513
571
|
callingTools: false,
|
|
514
|
-
changing: false
|
|
572
|
+
changing: false,
|
|
573
|
+
checking: false
|
|
515
574
|
};
|
|
516
575
|
case "message_start": return event.message.role === "assistant" ? {
|
|
517
576
|
...state,
|
|
518
577
|
callingTools: false,
|
|
519
|
-
changing: false
|
|
578
|
+
changing: false,
|
|
579
|
+
checking: false
|
|
520
580
|
} : state;
|
|
521
581
|
case "message_update": {
|
|
522
582
|
const calls = event.changes.flatMap((change) => change.type === "toolcall_start" || change.type === "block" ? [change.block] : change.type === "message" ? change.message.content : []);
|
|
@@ -524,7 +584,8 @@ function reduceThreadState(state, event) {
|
|
|
524
584
|
return {
|
|
525
585
|
...state,
|
|
526
586
|
callingTools: state.callingTools || calls.some((block) => block.type === "toolCall"),
|
|
527
|
-
changing: state.changing || calls.some(callsChangingTool)
|
|
587
|
+
changing: state.changing || calls.some(callsChangingTool),
|
|
588
|
+
checking: state.checking || calls.some(callsLook)
|
|
528
589
|
};
|
|
529
590
|
}
|
|
530
591
|
case "tool_execution_start": {
|
|
@@ -536,6 +597,7 @@ function reduceThreadState(state, event) {
|
|
|
536
597
|
return {
|
|
537
598
|
...state,
|
|
538
599
|
changing,
|
|
600
|
+
checking: state.checking || event.toolName === "look",
|
|
539
601
|
toolCalls: [...state.toolCalls ?? [], {
|
|
540
602
|
callId: event.toolCallId,
|
|
541
603
|
name: event.toolName
|
|
@@ -566,4 +628,4 @@ function reduceThreadFrame(state, frame) {
|
|
|
566
628
|
}
|
|
567
629
|
|
|
568
630
|
//#endregion
|
|
569
|
-
export { BRIEF_ENTRY, BUILD_STATUS_ENTRY, BUILD_TOOL, CHANGING_TOOLS, DRAFT_ENDED_ENTRY, EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, PUSH_ENTRY, RECONCILE_ENTRY, RUN_STOPPED_ENTRY, START_DRAFT_TOOL, THREAD_ENDED_CLOSE_CODE, buildStatusDataSchema, buildStatusSchema, editorMessageDataSchema, editorSelectionSchema, editorTextOf, editorWhereSchema, isSender, pushDataSchema, pushOutcomeSchema, reconcileDataSchema, reduceThreadEvents, reduceThreadFrame, reduceThreadState, startedDraftCardSchema, startedDraftSchema, textOf, threadClientMessageSchema, threadSenderSchema, threadViewOf, touchKindSchema, touchSchema, touchedBatchSchema };
|
|
631
|
+
export { BRIEF_ENTRY, BUILD_STATUS_ENTRY, BUILD_TOOL, CHANGING_TOOLS, CONNECTED_AGENT_MESSAGE_ENTRY, DRAFT_ENDED_ENTRY, EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, LOOK_TOOL, PUSH_ENTRY, RECONCILE_ENTRY, RUN_STOPPED_ENTRY, START_DRAFT_TOOL, THREAD_ENDED_CLOSE_CODE, buildStatusDataSchema, buildStatusSchema, connectedAgentClientSchema, connectedAgentMessageDataSchema, editorMessageDataSchema, editorSelectionSchema, editorTextOf, editorWhereSchema, isSender, pushDataSchema, pushOutcomeSchema, reconcileDataSchema, reduceThreadEvents, reduceThreadFrame, reduceThreadState, startedDraftCardSchema, startedDraftSchema, textOf, threadClientMessageSchema, threadSenderSchema, threadViewOf, touchKindSchema, touchSchema, touchedBatchSchema };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@camox/api-contract",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.65.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -51,6 +51,10 @@
|
|
|
51
51
|
"./router": {
|
|
52
52
|
"types": "./dist/router.d.ts",
|
|
53
53
|
"default": "./dist/router.js"
|
|
54
|
+
},
|
|
55
|
+
"./layout-routes": {
|
|
56
|
+
"types": "./dist/layout-routes.d.ts",
|
|
57
|
+
"default": "./dist/layout-routes.js"
|
|
54
58
|
}
|
|
55
59
|
},
|
|
56
60
|
"dependencies": {
|
|
@@ -60,7 +64,8 @@
|
|
|
60
64
|
},
|
|
61
65
|
"devDependencies": {
|
|
62
66
|
"@earendil-works/pi-ai": "1.3.0",
|
|
63
|
-
"@earendil-works/pi-durable": "1.3.0"
|
|
67
|
+
"@earendil-works/pi-durable": "1.3.0",
|
|
68
|
+
"tsrs": "0.20.1-ts7.1.0-beta"
|
|
64
69
|
},
|
|
65
70
|
"peerDependencies": {
|
|
66
71
|
"@earendil-works/pi-ai": "1.3.0",
|
|
@@ -85,6 +90,7 @@
|
|
|
85
90
|
"^production",
|
|
86
91
|
"{workspaceRoot}/apps/api/src/**/*",
|
|
87
92
|
"{workspaceRoot}/packages/ai-tools/src/**/*",
|
|
93
|
+
"{workspaceRoot}/tsconfig.dts.api-contract.json",
|
|
88
94
|
"!{workspaceRoot}/**/*.test.ts"
|
|
89
95
|
]
|
|
90
96
|
}
|
package/src/drafts.ts
CHANGED
|
@@ -25,6 +25,46 @@ export const PUBLISHING_DRAFT_HEADER = "x-camox-publishing-draft";
|
|
|
25
25
|
*/
|
|
26
26
|
export const PUBLISHING_DRAFT_VAR = "CAMOX_PUBLISHING_DRAFT";
|
|
27
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Request header carrying a look token (ADR 0032): a short-lived, read-only credential the API
|
|
30
|
+
* signs for the agent's `look` tool, naming one project and one draft. A request carrying a valid
|
|
31
|
+
* one reads that draft's content, named with `DRAFT_HEADER`, as a member would, without a session.
|
|
32
|
+
* It writes nothing, reads no other draft, and reads none of Live's private data.
|
|
33
|
+
*/
|
|
34
|
+
export const LOOK_TOKEN_HEADER = "x-camox-look-token";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The cookie the browser `look` drives carries the look token in, set for the website's host
|
|
38
|
+
* only, so third-party requests never see it. The website forwards it as `LOOK_TOKEN_HEADER`.
|
|
39
|
+
*/
|
|
40
|
+
export const LOOK_TOKEN_COOKIE = "camox_look";
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The website's lightweight document the browser `look` drives loads (the SDK runtime's
|
|
44
|
+
* `RUNTIME_ROUTE_PATHS.look`): one page of the draft `DRAFT_SEARCH_PARAM` names, at the path
|
|
45
|
+
* `LOOK_PATH_PARAM` gives in the locale `LOOK_LOCALE_PARAM` names, its Default Locale without.
|
|
46
|
+
*/
|
|
47
|
+
export const LOOK_DOCUMENT_PATH = "/_camox/look";
|
|
48
|
+
export const LOOK_PATH_PARAM = "path";
|
|
49
|
+
export const LOOK_LOCALE_PARAM = "locale";
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The look document's Block Definition mode: instead of a page, the block of the Block Definition
|
|
53
|
+
* `LOOK_BLOCK_DEFINITION_PARAM` names, alone in the site's shell, with its default content and
|
|
54
|
+
* Block Settings and the overrides `LOOK_CONTENT_PARAM` and `LOOK_SETTINGS_PARAM` give as JSON, in
|
|
55
|
+
* the shape the content tools take.
|
|
56
|
+
*/
|
|
57
|
+
export const LOOK_BLOCK_DEFINITION_PARAM = "blockDefinition";
|
|
58
|
+
export const LOOK_CONTENT_PARAM = "content";
|
|
59
|
+
export const LOOK_SETTINGS_PARAM = "settings";
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* The id of a block shown without being placed, as the look document's Block Definition mode
|
|
63
|
+
* shows it: never a stored block's, which are UUIDs. Its element is
|
|
64
|
+
* `[data-camox-viewport-block="unplaced"]`.
|
|
65
|
+
*/
|
|
66
|
+
export const UNPLACED_BLOCK_ID = "unplaced";
|
|
67
|
+
|
|
28
68
|
/**
|
|
29
69
|
* Request header asking the API to create the draft named by `DRAFT_HEADER` if it doesn't
|
|
30
70
|
* exist yet. Its value is `"1"`.
|
|
@@ -87,19 +127,34 @@ export const PUBLISH_BLOCKED_ERROR = "PUBLISH_BLOCKED";
|
|
|
87
127
|
|
|
88
128
|
/**
|
|
89
129
|
* oRPC error code for a publish refused because the draft still has pending translations: every
|
|
90
|
-
* locale goes live together,
|
|
130
|
+
* locale goes live together, and there is no publishing anyway (ADR 0033).
|
|
91
131
|
*/
|
|
92
132
|
export const TRANSLATIONS_PENDING_ERROR = "TRANSLATIONS_PENDING";
|
|
93
133
|
|
|
134
|
+
/**
|
|
135
|
+
* oRPC error code for a publish refused because the draft has open findings, each to fix or for a
|
|
136
|
+
* member to dismiss: there is no publishing past them (ADR 0033).
|
|
137
|
+
*/
|
|
138
|
+
export const FINDINGS_OPEN_ERROR = "FINDINGS_OPEN";
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* oRPC error code for a publish refused because a patroller's check at publish hasn't read the
|
|
142
|
+
* draft's latest changes yet: publishing waits for it (ADR 0033).
|
|
143
|
+
*/
|
|
144
|
+
export const CHECKS_RUNNING_ERROR = "CHECKS_RUNNING";
|
|
145
|
+
|
|
94
146
|
/**
|
|
95
147
|
* Why the publish tool published nothing of a draft with changes, and returned a fresh preview to
|
|
96
148
|
* review again, by the error code of each: Live changed since the preview, the draft has blockers,
|
|
97
|
-
* translations are still running,
|
|
149
|
+
* translations are still running, the draft has open findings, a check at publish is still
|
|
150
|
+
* running, or the draft's code changed since the preview.
|
|
98
151
|
*/
|
|
99
152
|
export const NOT_PUBLISHED_CODES = {
|
|
100
153
|
conflictsChanged: CONFLICTS_CHANGED_ERROR,
|
|
101
154
|
blocked: PUBLISH_BLOCKED_ERROR,
|
|
102
155
|
translating: TRANSLATIONS_PENDING_ERROR,
|
|
156
|
+
findings: FINDINGS_OPEN_ERROR,
|
|
157
|
+
checking: CHECKS_RUNNING_ERROR,
|
|
103
158
|
revisionChanged: REVISION_CHANGED_ERROR,
|
|
104
159
|
} as const;
|
|
105
160
|
|
|
@@ -133,6 +188,7 @@ const BLOCK_WRITES: Record<keyof Router["blocks"], boolean> = {
|
|
|
133
188
|
getUsageCounts: false,
|
|
134
189
|
getPagesUsing: false,
|
|
135
190
|
getSyncedContent: false,
|
|
191
|
+
getUnplaced: false,
|
|
136
192
|
create: true,
|
|
137
193
|
updateContent: true,
|
|
138
194
|
updateSettings: true,
|
|
@@ -208,6 +264,14 @@ const LOCALE_WRITES: Record<keyof Router["locales"], boolean> = {
|
|
|
208
264
|
addBack: true,
|
|
209
265
|
};
|
|
210
266
|
|
|
267
|
+
// Applying a finding's fix edits content as the member would; dismissing one is the project's,
|
|
268
|
+
// never content (ADR 0034).
|
|
269
|
+
const PATROL_WRITES: Record<keyof Router["patrol"], boolean> = {
|
|
270
|
+
get: false,
|
|
271
|
+
applyFix: true,
|
|
272
|
+
dismissFinding: false,
|
|
273
|
+
};
|
|
274
|
+
|
|
211
275
|
// `definitions` is absent: its sync is a release, never a studio content write.
|
|
212
276
|
const CONTENT_WRITES: Partial<Record<keyof Router, Record<string, boolean>>> = {
|
|
213
277
|
blocks: BLOCK_WRITES,
|
|
@@ -215,6 +279,7 @@ const CONTENT_WRITES: Partial<Record<keyof Router, Record<string, boolean>>> = {
|
|
|
215
279
|
collectionDefinitions: COLLECTION_WRITES,
|
|
216
280
|
files: FILE_WRITES,
|
|
217
281
|
pages: PAGE_WRITES,
|
|
282
|
+
patrol: PATROL_WRITES,
|
|
218
283
|
repeatableItems: ITEM_WRITES,
|
|
219
284
|
};
|
|
220
285
|
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** How a layout's pages come about: authored by editors, derived from data, or one fixed page. */
|
|
2
|
+
export type LayoutKind = "curated" | "derived" | "singleton";
|
|
3
|
+
|
|
4
|
+
/** A layout as route matching reads it: its id, which is its file route, and its kind. */
|
|
5
|
+
export type LayoutRoute = { id: string; kind: LayoutKind };
|
|
6
|
+
|
|
7
|
+
/** The segments of a layout's file route, such as `blog.$slug`: `$` segments are params. */
|
|
8
|
+
export function routeSegments(id: string) {
|
|
9
|
+
return id.split(".");
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The singleton or derived layout whose route matches `pathname`, a path in its Locale, with its
|
|
14
|
+
* params, or null. The route with the fewest params wins, so a singleton's fixed route wins over
|
|
15
|
+
* a derived route that would match it too. The SDK renders pages and the API checks them with it.
|
|
16
|
+
*/
|
|
17
|
+
export function matchLayoutRoute<L>(
|
|
18
|
+
layouts: readonly L[],
|
|
19
|
+
pathname: string,
|
|
20
|
+
routeOf: (layout: L) => LayoutRoute,
|
|
21
|
+
): { layout: L; params: Record<string, string> } | null {
|
|
22
|
+
const path = pathname.replace(/^\/|\/$/g, "").split("/");
|
|
23
|
+
const paramCount = (layout: L) =>
|
|
24
|
+
routeSegments(routeOf(layout).id).filter((s) => s.startsWith("$")).length;
|
|
25
|
+
const candidates = layouts
|
|
26
|
+
.filter((layout) => routeOf(layout).kind !== "curated")
|
|
27
|
+
.sort((a, b) => paramCount(a) - paramCount(b));
|
|
28
|
+
for (const layout of candidates) {
|
|
29
|
+
const segments = routeSegments(routeOf(layout).id);
|
|
30
|
+
if (segments.length !== path.length) continue;
|
|
31
|
+
const params: Record<string, string> = {};
|
|
32
|
+
let matches = true;
|
|
33
|
+
for (const [index, segment] of segments.entries()) {
|
|
34
|
+
let value: string;
|
|
35
|
+
try {
|
|
36
|
+
value = decodeURIComponent(path[index]!);
|
|
37
|
+
} catch {
|
|
38
|
+
matches = false;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
if (segment.startsWith("$") && value && !value.includes("/"))
|
|
42
|
+
params[segment.slice(1)] = value;
|
|
43
|
+
else if (segment !== value) {
|
|
44
|
+
matches = false;
|
|
45
|
+
break;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
if (matches) return { layout, params };
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
package/src/locales.ts
CHANGED
|
@@ -15,6 +15,13 @@ export function localePrefix(locale: LocaleView): string {
|
|
|
15
15
|
return locale.isDefault ? "" : `/${locale.tag.toLowerCase()}`;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
/** A locale-relative path under its locale's prefix, keeping its search and hash: `/` is `/de`. */
|
|
19
|
+
export function localizedPath(path: string, locale: LocaleView): string {
|
|
20
|
+
const prefix = localePrefix(locale);
|
|
21
|
+
if (!prefix) return path;
|
|
22
|
+
return path === "/" || /^\/[?#]/.test(path) ? `${prefix}${path.slice(1)}` : `${prefix}${path}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
18
25
|
/**
|
|
19
26
|
* The locale a runtime path names, and the path after its prefix, without search or hash. A first
|
|
20
27
|
* segment that isn't a non-default locale's prefix leaves the path the default locale's. A
|