@camox/api-contract 0.56.0 → 0.57.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/dist/patrol.d.ts +114 -0
- package/dist/patrol.js +8 -0
- package/dist/query-keys.d.ts +3 -0
- package/dist/query-keys.js +2 -1
- package/dist/router.d.ts +278 -148
- package/dist/threads.d.ts +79 -26
- package/dist/threads.js +90 -24
- package/package.json +5 -1
- package/src/patrol.ts +113 -0
- package/src/query-keys.ts +4 -0
- package/src/threads.ts +131 -34
package/dist/threads.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { DraftOutcome } from "./drafts.js";
|
|
1
2
|
import { z } from "zod";
|
|
2
3
|
import { AgentEvent } from "@earendil-works/pi-durable";
|
|
4
|
+
import { Message } from "@earendil-works/pi-ai";
|
|
3
5
|
//#region src/threads.d.ts
|
|
4
6
|
/**
|
|
5
7
|
* The kind of the transcript entry recording an editor's message, with its sender and the
|
|
@@ -20,6 +22,41 @@ export declare const BUILD_STATUS_ENTRY = "camox.build-status";
|
|
|
20
22
|
* editors see. The model gets Camox's instructions instead.
|
|
21
23
|
*/
|
|
22
24
|
export declare const RECONCILE_ENTRY = "camox.reconcile";
|
|
25
|
+
/**
|
|
26
|
+
* The kind of the transcript entry opening a draft's thread a Live thread handed a request off
|
|
27
|
+
* to: the Brief, which the model reads and editors see as from Camox, ahead of the editor's
|
|
28
|
+
* messages.
|
|
29
|
+
*/
|
|
30
|
+
export declare const BRIEF_ENTRY = "camox.brief";
|
|
31
|
+
/**
|
|
32
|
+
* The kind of the transcript entry telling the agent on Live that a draft it started ended,
|
|
33
|
+
* published or deleted, with the draft's id as data (`{ draftId }`). The model reads
|
|
34
|
+
* it at the end of the conversation; editors never see it, since the draft's card says so.
|
|
35
|
+
*/
|
|
36
|
+
export declare const DRAFT_ENDED_ENTRY = "camox.draft-ended";
|
|
37
|
+
/**
|
|
38
|
+
* The agent's tool on Live handing a request off to a draft it starts (ADR 0025). A run whose
|
|
39
|
+
* call of it went through ends on the draft's card.
|
|
40
|
+
*/
|
|
41
|
+
export declare const START_DRAFT_TOOL = "startDraft";
|
|
42
|
+
/** The draft a `startDraft` call started, as its result's details name it for the card. */
|
|
43
|
+
export declare const startedDraftCardSchema: z.ZodObject<{
|
|
44
|
+
draftId: z.ZodString;
|
|
45
|
+
name: z.ZodString;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
export type StartedDraftCard = z.infer<typeof startedDraftCardSchema>;
|
|
48
|
+
/**
|
|
49
|
+
* Where a draft a Live thread's card names stands, as the thread looks it up whenever it's shown
|
|
50
|
+
* (ADR 0025): its current name, or its last once it ended, and how it ended. Never written into
|
|
51
|
+
* the transcript, so the model's cached prompt keeps up. A name cleared by a rename is null: the
|
|
52
|
+
* card keeps the name the agent gave it.
|
|
53
|
+
*/
|
|
54
|
+
export type DraftCardState = {
|
|
55
|
+
name: string | null;
|
|
56
|
+
ended?: DraftOutcome;
|
|
57
|
+
};
|
|
58
|
+
/** The drafts a Live thread's cards name, by id, as the thread last looked them up. */
|
|
59
|
+
export type DraftCardStates = Readonly<Record<string, DraftCardState>>;
|
|
23
60
|
export declare const reconcileDataSchema: z.ZodObject<{
|
|
24
61
|
message: z.ZodString;
|
|
25
62
|
}, z.core.$strip>;
|
|
@@ -43,17 +80,21 @@ export declare const threadSenderSchema: z.ZodObject<{
|
|
|
43
80
|
export type ThreadSender = z.infer<typeof threadSenderSchema>;
|
|
44
81
|
/** Whether `data` names a sender. */
|
|
45
82
|
export declare const isSender: (data: unknown) => data is ThreadSender;
|
|
46
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* What an editor's message entry records: who sent it, the operation id it was sent with, and for
|
|
85
|
+
* a message sent on Live as the agent handed the request off, the draft it went to with it.
|
|
86
|
+
*/
|
|
47
87
|
export declare const editorMessageDataSchema: z.ZodObject<{
|
|
48
88
|
id: z.ZodString;
|
|
49
89
|
name: z.ZodString;
|
|
50
90
|
image: z.ZodNullable<z.ZodString>;
|
|
51
91
|
operationId: z.ZodOptional<z.ZodString>;
|
|
92
|
+
handedOffTo: z.ZodOptional<z.ZodString>;
|
|
52
93
|
}, z.core.$strip>;
|
|
53
94
|
/**
|
|
54
|
-
* A draft
|
|
55
|
-
*
|
|
56
|
-
*
|
|
95
|
+
* A draft the agent on Live started with `startDraft`, and the operation ids of the messages of
|
|
96
|
+
* the run that started it: the studio that sent one of them enters the draft, if it still shows
|
|
97
|
+
* Live.
|
|
57
98
|
*/
|
|
58
99
|
export declare const startedDraftSchema: z.ZodObject<{
|
|
59
100
|
draftId: z.ZodString;
|
|
@@ -182,20 +223,19 @@ export type ThreadMessage = {
|
|
|
182
223
|
id: string;
|
|
183
224
|
from: "stopped";
|
|
184
225
|
sender: ThreadSender;
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
};
|
|
226
|
+
} |
|
|
227
|
+
/**
|
|
228
|
+
* In place of the agent's message, for a Live turn handed off to a draft the agent started:
|
|
229
|
+
* the draft's card, with its current name, which opens it. Once the draft ended, it has its
|
|
230
|
+
* last name and how it ended, and opens nothing. The turn is finished.
|
|
231
|
+
*/
|
|
232
|
+
({
|
|
233
|
+
id: string;
|
|
234
|
+
from: "draft";
|
|
235
|
+
ended?: DraftOutcome;
|
|
236
|
+
} & StartedDraftCard);
|
|
197
237
|
/** Everything the studio shows for a thread. */
|
|
198
|
-
export type ThreadView =
|
|
238
|
+
export type ThreadView = {
|
|
199
239
|
messages: readonly ThreadMessage[];
|
|
200
240
|
/** Whether a run is going: the agent is working on a turn, and stopping would end it. */
|
|
201
241
|
running: boolean;
|
|
@@ -216,13 +256,15 @@ export declare const EMPTY_THREAD_VIEW: ThreadView;
|
|
|
216
256
|
* and whether a run is going. The studio and the tests fold Pi's events into this, and derive
|
|
217
257
|
* the view from it with `threadViewOf`, so a snapshot and the events that led to it show the same.
|
|
218
258
|
*/
|
|
219
|
-
export type ThreadState =
|
|
259
|
+
export type ThreadState = {
|
|
220
260
|
steps: readonly ThreadStep[];
|
|
221
261
|
running: boolean;
|
|
222
262
|
/** The model's calls still running, by call id. A tool's own calls aren't the agent's. */
|
|
223
263
|
toolCalls?: readonly string[];
|
|
224
264
|
/** Whether the model's latest message makes tool calls: it writes them, then they run. */
|
|
225
265
|
callingTools?: boolean;
|
|
266
|
+
/** The drafts the thread's cards name, as the thread last looked them up. */
|
|
267
|
+
drafts?: DraftCardStates;
|
|
226
268
|
};
|
|
227
269
|
export declare const EMPTY_THREAD_STATE: ThreadState;
|
|
228
270
|
/** One transcript entry, as far as the studio's view is concerned. */
|
|
@@ -232,6 +274,8 @@ type ThreadStep = {
|
|
|
232
274
|
type: "editor";
|
|
233
275
|
sender: ThreadSender;
|
|
234
276
|
text: string;
|
|
277
|
+
/** The draft the message went to, as the agent on Live handed the request off. */
|
|
278
|
+
handedOffTo?: string;
|
|
235
279
|
} |
|
|
236
280
|
/** Camox starting a turn of its own, saying why. */
|
|
237
281
|
{
|
|
@@ -252,6 +296,10 @@ type ThreadStep = {
|
|
|
252
296
|
type: "stopped";
|
|
253
297
|
sender: ThreadSender;
|
|
254
298
|
} |
|
|
299
|
+
/** A `startDraft` call that went through, which ends the turn on the draft's card. */
|
|
300
|
+
({
|
|
301
|
+
type: "draft";
|
|
302
|
+
} & StartedDraftCard) |
|
|
255
303
|
/** Where the build of the turn's code stands, as the turn ends. */
|
|
256
304
|
{
|
|
257
305
|
type: "build";
|
|
@@ -290,18 +338,21 @@ export type ThreadServerMessage =
|
|
|
290
338
|
type: "accepted";
|
|
291
339
|
operationId: string;
|
|
292
340
|
} |
|
|
293
|
-
/**
|
|
341
|
+
/**
|
|
342
|
+
* The agent on Live started a draft with `startDraft`: the studio that sent a message of the
|
|
343
|
+
* run enters it, if it still shows Live. Live only: a snapshot never replays it.
|
|
344
|
+
*/
|
|
294
345
|
{
|
|
295
346
|
type: "started-draft";
|
|
296
347
|
startedDraft: StartedDraft;
|
|
297
348
|
} |
|
|
298
349
|
/**
|
|
299
|
-
*
|
|
300
|
-
* the
|
|
350
|
+
* Where the drafts a Live thread's cards name stand, after each snapshot and whenever one of
|
|
351
|
+
* them is renamed or ends. It replaces what the studio knew of them.
|
|
301
352
|
*/
|
|
302
353
|
{
|
|
303
|
-
type: "
|
|
304
|
-
|
|
354
|
+
type: "drafts";
|
|
355
|
+
drafts: DraftCardStates;
|
|
305
356
|
} |
|
|
306
357
|
/**
|
|
307
358
|
* A code-mode script that wrote something ended, failed or not: what its successful writes
|
|
@@ -321,16 +372,18 @@ export type ThreadServerMessage =
|
|
|
321
372
|
* thread the next time the studio connects.
|
|
322
373
|
*/
|
|
323
374
|
export declare const THREAD_ENDED_CLOSE_CODE = 4000;
|
|
375
|
+
/** The text of a message's content: its text parts, joined. */
|
|
376
|
+
export declare const textOf: (content: Message["content"]) => string;
|
|
324
377
|
/**
|
|
325
378
|
* What the studio shows for a thread. A turn starts with an editor's message, or Camox's own
|
|
326
379
|
* when it began the turn itself, and messages sent
|
|
327
|
-
* while it runs join it. It ends with the agent's final message
|
|
328
|
-
* is never shown. A stopped turn shows who stopped it instead, and whatever its run wrote after
|
|
380
|
+
* while it runs join it. It ends with the agent's final message, or on Live, the card of the
|
|
381
|
+
* draft it handed the request off to; text written beside tool calls is never shown. A stopped turn shows who stopped it instead, and whatever its run wrote after
|
|
329
382
|
* the stop is left out. A turn that ended without either shows a failed line in its place: its
|
|
330
383
|
* run is over, or a later message started another run after its last model call came back
|
|
331
384
|
* unfinished. While Pi retries a failed call, the run goes on, so the turn shows as working.
|
|
332
385
|
*/
|
|
333
|
-
export declare function threadViewOf({ steps, running, toolCalls, callingTools,
|
|
386
|
+
export declare function threadViewOf({ steps, running, toolCalls, callingTools, drafts }: ThreadState): ThreadView;
|
|
334
387
|
/** Folds one of Pi's agent events into the thread's state. */
|
|
335
388
|
export declare function reduceThreadState(state: ThreadState, event: AgentEvent): ThreadState;
|
|
336
389
|
/** Folds a batch of Pi's agent events into the thread's state. */
|
package/dist/threads.js
CHANGED
|
@@ -20,6 +20,28 @@ const BUILD_STATUS_ENTRY = "camox.build-status";
|
|
|
20
20
|
* editors see. The model gets Camox's instructions instead.
|
|
21
21
|
*/
|
|
22
22
|
const RECONCILE_ENTRY = "camox.reconcile";
|
|
23
|
+
/**
|
|
24
|
+
* The kind of the transcript entry opening a draft's thread a Live thread handed a request off
|
|
25
|
+
* to: the Brief, which the model reads and editors see as from Camox, ahead of the editor's
|
|
26
|
+
* messages.
|
|
27
|
+
*/
|
|
28
|
+
const BRIEF_ENTRY = "camox.brief";
|
|
29
|
+
/**
|
|
30
|
+
* The kind of the transcript entry telling the agent on Live that a draft it started ended,
|
|
31
|
+
* published or deleted, with the draft's id as data (`{ draftId }`). The model reads
|
|
32
|
+
* it at the end of the conversation; editors never see it, since the draft's card says so.
|
|
33
|
+
*/
|
|
34
|
+
const DRAFT_ENDED_ENTRY = "camox.draft-ended";
|
|
35
|
+
/**
|
|
36
|
+
* The agent's tool on Live handing a request off to a draft it starts (ADR 0025). A run whose
|
|
37
|
+
* call of it went through ends on the draft's card.
|
|
38
|
+
*/
|
|
39
|
+
const START_DRAFT_TOOL = "startDraft";
|
|
40
|
+
/** The draft a `startDraft` call started, as its result's details name it for the card. */
|
|
41
|
+
const startedDraftCardSchema = z.object({
|
|
42
|
+
draftId: z.string(),
|
|
43
|
+
name: z.string()
|
|
44
|
+
});
|
|
23
45
|
const reconcileDataSchema = z.object({ message: z.string() });
|
|
24
46
|
const buildStatusSchema = z.enum(["building", "fixing"]);
|
|
25
47
|
const buildStatusDataSchema = z.object({ status: buildStatusSchema });
|
|
@@ -31,12 +53,18 @@ const threadSenderSchema = z.object({
|
|
|
31
53
|
});
|
|
32
54
|
/** Whether `data` names a sender. */
|
|
33
55
|
const isSender = (data) => threadSenderSchema.safeParse(data).success;
|
|
34
|
-
/** What an editor's message entry records: who sent it, and the operation id it was sent with. */
|
|
35
|
-
const editorMessageDataSchema = threadSenderSchema.extend({ operationId: z.string().optional() });
|
|
36
56
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
|
|
57
|
+
* What an editor's message entry records: who sent it, the operation id it was sent with, and for
|
|
58
|
+
* a message sent on Live as the agent handed the request off, the draft it went to with it.
|
|
59
|
+
*/
|
|
60
|
+
const editorMessageDataSchema = threadSenderSchema.extend({
|
|
61
|
+
operationId: z.string().optional(),
|
|
62
|
+
handedOffTo: z.string().optional()
|
|
63
|
+
});
|
|
64
|
+
/**
|
|
65
|
+
* A draft the agent on Live started with `startDraft`, and the operation ids of the messages of
|
|
66
|
+
* the run that started it: the studio that sent one of them enters the draft, if it still shows
|
|
67
|
+
* Live.
|
|
40
68
|
*/
|
|
41
69
|
const startedDraftSchema = z.object({
|
|
42
70
|
draftId: z.string(),
|
|
@@ -110,6 +138,7 @@ const threadClientMessageSchema = z.discriminatedUnion("type", [
|
|
|
110
138
|
* thread the next time the studio connects.
|
|
111
139
|
*/
|
|
112
140
|
const THREAD_ENDED_CLOSE_CODE = 4e3;
|
|
141
|
+
/** The text of a message's content: its text parts, joined. */
|
|
113
142
|
const textOf = (content) => typeof content === "string" ? content : content.flatMap((part) => part.type === "text" ? [part.text] : []).join("").trim();
|
|
114
143
|
/** What an assistant message is to the thread: a final answer, a tool round, or neither. */
|
|
115
144
|
function assistantStep(id, message) {
|
|
@@ -154,13 +183,23 @@ function threadStepOf(entry) {
|
|
|
154
183
|
status: build.status
|
|
155
184
|
};
|
|
156
185
|
}
|
|
186
|
+
if (entry.kind === "camox.draft-ended") return void 0;
|
|
157
187
|
const message = entry.model?.find((candidate) => candidate.role !== "system");
|
|
158
188
|
if (!message) return void 0;
|
|
159
189
|
if (message.role === "user") {
|
|
160
|
-
if (entry.kind === "camox.editor-message")
|
|
190
|
+
if (entry.kind === "camox.editor-message") {
|
|
191
|
+
const handedOffTo = editorMessageDataSchema.safeParse(entry.data).data?.handedOffTo;
|
|
192
|
+
return sender && {
|
|
193
|
+
id,
|
|
194
|
+
type: "editor",
|
|
195
|
+
sender,
|
|
196
|
+
text: textOf(message.content),
|
|
197
|
+
...handedOffTo && { handedOffTo }
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (entry.kind === "camox.brief") return {
|
|
161
201
|
id,
|
|
162
|
-
type: "
|
|
163
|
-
sender,
|
|
202
|
+
type: "camox",
|
|
164
203
|
text: textOf(message.content)
|
|
165
204
|
};
|
|
166
205
|
if (entry.kind === "camox.reconcile") {
|
|
@@ -177,21 +216,28 @@ function threadStepOf(entry) {
|
|
|
177
216
|
};
|
|
178
217
|
}
|
|
179
218
|
if (message.role === "assistant") return assistantStep(id, message);
|
|
180
|
-
if (message.role === "toolResult")
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
219
|
+
if (message.role === "toolResult") {
|
|
220
|
+
const card = message.toolName === "startDraft" && !message.isError ? startedDraftCardSchema.safeParse(message.details).data : void 0;
|
|
221
|
+
return card ? {
|
|
222
|
+
id,
|
|
223
|
+
type: "draft",
|
|
224
|
+
...card
|
|
225
|
+
} : {
|
|
226
|
+
id,
|
|
227
|
+
type: "tools"
|
|
228
|
+
};
|
|
229
|
+
}
|
|
184
230
|
}
|
|
185
231
|
/**
|
|
186
232
|
* What the studio shows for a thread. A turn starts with an editor's message, or Camox's own
|
|
187
233
|
* when it began the turn itself, and messages sent
|
|
188
|
-
* while it runs join it. It ends with the agent's final message
|
|
189
|
-
* is never shown. A stopped turn shows who stopped it instead, and whatever its run wrote after
|
|
234
|
+
* while it runs join it. It ends with the agent's final message, or on Live, the card of the
|
|
235
|
+
* draft it handed the request off to; text written beside tool calls is never shown. A stopped turn shows who stopped it instead, and whatever its run wrote after
|
|
190
236
|
* the stop is left out. A turn that ended without either shows a failed line in its place: its
|
|
191
237
|
* run is over, or a later message started another run after its last model call came back
|
|
192
238
|
* unfinished. While Pi retries a failed call, the run goes on, so the turn shows as working.
|
|
193
239
|
*/
|
|
194
|
-
function threadViewOf({ steps, running, toolCalls = [], callingTools = false,
|
|
240
|
+
function threadViewOf({ steps, running, toolCalls = [], callingTools = false, drafts = {} }) {
|
|
195
241
|
const messages = [];
|
|
196
242
|
let waiting;
|
|
197
243
|
let unfinished = false;
|
|
@@ -213,7 +259,17 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ..
|
|
|
213
259
|
endTurn();
|
|
214
260
|
};
|
|
215
261
|
for (const step of steps) switch (step.type) {
|
|
216
|
-
case "editor":
|
|
262
|
+
case "editor": {
|
|
263
|
+
const card = step.handedOffTo ? messages.findLastIndex((message) => message.from === "draft" && message.draftId === step.handedOffTo) : -1;
|
|
264
|
+
if (card !== -1) {
|
|
265
|
+
messages.splice(card, 0, {
|
|
266
|
+
id: step.id,
|
|
267
|
+
from: "editor",
|
|
268
|
+
sender: step.sender,
|
|
269
|
+
text: step.text
|
|
270
|
+
});
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
217
273
|
if (unfinished) fail();
|
|
218
274
|
messages.push({
|
|
219
275
|
id: step.id,
|
|
@@ -223,6 +279,7 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ..
|
|
|
223
279
|
});
|
|
224
280
|
waiting = step.id;
|
|
225
281
|
break;
|
|
282
|
+
}
|
|
226
283
|
case "camox":
|
|
227
284
|
if (unfinished) fail();
|
|
228
285
|
messages.push({
|
|
@@ -257,6 +314,20 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ..
|
|
|
257
314
|
answered = before;
|
|
258
315
|
}
|
|
259
316
|
break;
|
|
317
|
+
case "draft":
|
|
318
|
+
if (waiting === void 0 && messages.at(-1)?.from === "stopped") break;
|
|
319
|
+
{
|
|
320
|
+
const draft = drafts[step.draftId];
|
|
321
|
+
messages.push({
|
|
322
|
+
id: step.id,
|
|
323
|
+
from: "draft",
|
|
324
|
+
draftId: step.draftId,
|
|
325
|
+
name: draft?.name ?? step.name,
|
|
326
|
+
...draft?.ended && { ended: draft.ended }
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
endTurn();
|
|
330
|
+
break;
|
|
260
331
|
case "continued":
|
|
261
332
|
if (!answered) break;
|
|
262
333
|
if (messages.at(-1)?.from === "agent") messages.pop();
|
|
@@ -275,7 +346,6 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ..
|
|
|
275
346
|
if (!running) fail();
|
|
276
347
|
const usingTools = running && (callingTools || toolCalls.length > 0);
|
|
277
348
|
return {
|
|
278
|
-
...drafts,
|
|
279
349
|
messages,
|
|
280
350
|
running,
|
|
281
351
|
...running && status && { status },
|
|
@@ -339,17 +409,13 @@ const reduceThreadEvents = (state, events) => events.reduce(reduceThreadState, s
|
|
|
339
409
|
function reduceThreadFrame(state, frame) {
|
|
340
410
|
switch (frame.type) {
|
|
341
411
|
case "events": return reduceThreadEvents(state, frame.events);
|
|
342
|
-
case "
|
|
343
|
-
...state,
|
|
344
|
-
startedDraft: frame.startedDraft
|
|
345
|
-
};
|
|
346
|
-
case "continued-in": return {
|
|
412
|
+
case "drafts": return {
|
|
347
413
|
...state,
|
|
348
|
-
|
|
414
|
+
drafts: frame.drafts
|
|
349
415
|
};
|
|
350
416
|
default: return state;
|
|
351
417
|
}
|
|
352
418
|
}
|
|
353
419
|
|
|
354
420
|
//#endregion
|
|
355
|
-
export { BUILD_STATUS_ENTRY, EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, RECONCILE_ENTRY, RUN_STOPPED_ENTRY, THREAD_ENDED_CLOSE_CODE, buildStatusDataSchema, buildStatusSchema, editorMessageDataSchema, isSender, reconcileDataSchema, reduceThreadEvents, reduceThreadFrame, reduceThreadState, startedDraftSchema, threadClientMessageSchema, threadSenderSchema, threadViewOf, touchKindSchema, touchSchema, touchedBatchSchema };
|
|
421
|
+
export { BRIEF_ENTRY, BUILD_STATUS_ENTRY, DRAFT_ENDED_ENTRY, EDITOR_MESSAGE_ENTRY, EMPTY_THREAD_STATE, EMPTY_THREAD_VIEW, RECONCILE_ENTRY, RUN_STOPPED_ENTRY, START_DRAFT_TOOL, THREAD_ENDED_CLOSE_CODE, buildStatusDataSchema, buildStatusSchema, editorMessageDataSchema, isSender, 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.57.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"files": [
|
|
6
6
|
"src",
|
|
@@ -44,6 +44,10 @@
|
|
|
44
44
|
"types": "./dist/locales.d.ts",
|
|
45
45
|
"default": "./dist/locales.js"
|
|
46
46
|
},
|
|
47
|
+
"./patrol": {
|
|
48
|
+
"types": "./dist/patrol.d.ts",
|
|
49
|
+
"default": "./dist/patrol.js"
|
|
50
|
+
},
|
|
47
51
|
"./router": {
|
|
48
52
|
"types": "./dist/router.d.ts",
|
|
49
53
|
"default": "./dist/router.js"
|
package/src/patrol.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Patrol (ADR 0024): every patroller of a project, the work each did as reports, as the
|
|
3
|
+
* studio shows it at the top of the Agent Sidebar. The API reads it for one scope, a draft or
|
|
4
|
+
* Live, and names each report's subject, so the studio needs no entity loaded to show it.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/** A patroller's stable id. The registry in the API lists them all. */
|
|
8
|
+
export type PatrollerId = "translator" | "seoManager";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* What a structured report says was done. Each has a failed form, by the report's outcome. A
|
|
12
|
+
* merge with Live's localization reads as a translation.
|
|
13
|
+
*/
|
|
14
|
+
export type ReportVerb =
|
|
15
|
+
| "translated"
|
|
16
|
+
| "mergedWithLive"
|
|
17
|
+
| "wroteSeo"
|
|
18
|
+
| "describedFile"
|
|
19
|
+
| "publishedCatchUp";
|
|
20
|
+
|
|
21
|
+
/** What a report can be about. */
|
|
22
|
+
export type ReportEntity =
|
|
23
|
+
| "page"
|
|
24
|
+
| "block"
|
|
25
|
+
| "sharedContent"
|
|
26
|
+
| "repeatableItem"
|
|
27
|
+
| "record"
|
|
28
|
+
| "file";
|
|
29
|
+
|
|
30
|
+
/** A report's subject, named as the Canvas names it, read when the Patrol is. */
|
|
31
|
+
export type ReportSubject = { entity: ReportEntity; id: string; name: string };
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* What a report says: structured facts the studio turns into a sentence, in the studio's
|
|
35
|
+
* language, or text an AI wrote, shown as is. `locale` is a locale's tag.
|
|
36
|
+
*/
|
|
37
|
+
export type ReportSummary =
|
|
38
|
+
| {
|
|
39
|
+
kind: "structured";
|
|
40
|
+
verb: ReportVerb;
|
|
41
|
+
subject: ReportSubject | null;
|
|
42
|
+
locale: string | null;
|
|
43
|
+
}
|
|
44
|
+
| { kind: "text"; text: string };
|
|
45
|
+
|
|
46
|
+
/** The locale a summary's work was in: a structured summary's, if any. A text summary has none. */
|
|
47
|
+
export const reportLocale = (
|
|
48
|
+
summary: { kind: "structured"; locale: string | null } | { kind: "text" },
|
|
49
|
+
) => (summary.kind === "structured" ? summary.locale : null);
|
|
50
|
+
|
|
51
|
+
/** Whether the work was done, or failed. A failure shows in its report only. */
|
|
52
|
+
export type ReportOutcome = "done" | "failed";
|
|
53
|
+
|
|
54
|
+
/** Whether a report needs a human. Nothing raises one yet. */
|
|
55
|
+
export type AttentionLevel = "none" | "needed";
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Where the studio shows a report's subject: a page on the Canvas, with the block and repeatable
|
|
59
|
+
* item to select on it, a collection record, or a file.
|
|
60
|
+
*/
|
|
61
|
+
export type ReportPlace =
|
|
62
|
+
| { kind: "page"; pageId: string; blockId: string | null; itemId: string | null }
|
|
63
|
+
| { kind: "record"; collectionId: string; recordId: string }
|
|
64
|
+
| { kind: "file"; fileId: string };
|
|
65
|
+
|
|
66
|
+
export type ReportView = {
|
|
67
|
+
/** Unique among the patroller's reports in the view. */
|
|
68
|
+
id: string;
|
|
69
|
+
summary: ReportSummary;
|
|
70
|
+
outcome: ReportOutcome;
|
|
71
|
+
attention: AttentionLevel;
|
|
72
|
+
/** When the work last settled, in milliseconds since the epoch. */
|
|
73
|
+
at: number;
|
|
74
|
+
/** Whether the report is Live's, seen from a draft or from Live. */
|
|
75
|
+
live: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Where its subject shows, in the report's scope and locale. Null without a subject, or for one
|
|
78
|
+
* with no single place, such as a synced block's shared content or a layout's block.
|
|
79
|
+
*/
|
|
80
|
+
place: ReportPlace | null;
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** How many subjects a patroller is on in one locale, a locale's tag. */
|
|
84
|
+
export type LocaleCount = { locale: string; count: number };
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* What a patroller is on: the one subject when there's one, and how many subjects in each
|
|
88
|
+
* locale, the most first. A subject in several locales counts in each.
|
|
89
|
+
*/
|
|
90
|
+
export type PatrollerWorking = {
|
|
91
|
+
subject: ReportSubject | null;
|
|
92
|
+
locales: LocaleCount[];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
export type PatrollerView = {
|
|
96
|
+
id: PatrollerId;
|
|
97
|
+
/** Its role, as editors know it: "Translator", "SEO manager". */
|
|
98
|
+
role: string;
|
|
99
|
+
/** Null when it isn't working in the scope. */
|
|
100
|
+
working: PatrollerWorking | null;
|
|
101
|
+
/** Its latest activity in the scope, or null when it never worked there. */
|
|
102
|
+
lastActivityAt: number | null;
|
|
103
|
+
/** Its newest reports, newest first. */
|
|
104
|
+
reports: ReportView[];
|
|
105
|
+
/** How many more reports it has than `reports` shows, per locale, the most first. */
|
|
106
|
+
more: { locale: string | null; count: number }[];
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
/** Every patroller of the registry, the latest activity first; never-worked ones last. */
|
|
110
|
+
export type PatrolView = { patrollers: PatrollerView[] };
|
|
111
|
+
|
|
112
|
+
/** How many reports the Patrol shows of each patroller; the rest are counted. */
|
|
113
|
+
export const REPORTS_SHOWN = 5;
|
package/src/query-keys.ts
CHANGED
|
@@ -59,6 +59,10 @@ export const queryKeys = {
|
|
|
59
59
|
// The draft a viewer is in, scoped to it like content.
|
|
60
60
|
get: ["camox", "drafts", "get"],
|
|
61
61
|
},
|
|
62
|
+
// The Patrol of a scope: its reports and its patrollers' pending work.
|
|
63
|
+
patrol: {
|
|
64
|
+
get: ["camox", "patrol"],
|
|
65
|
+
},
|
|
62
66
|
} satisfies Record<string, QueryKeyGroup>;
|
|
63
67
|
|
|
64
68
|
/** The last segment of a query key: what the query reads, Live or a draft (ADR 0011). */
|