@camox/api-contract 0.72.0 → 0.75.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/claim-links.d.ts +17 -0
- package/dist/claim-links.js +17 -0
- package/dist/drafts.d.ts +7 -32
- package/dist/drafts.js +94 -110
- package/dist/query-keys.d.ts +3 -0
- package/dist/query-keys.js +1 -0
- package/dist/router.d.ts +1517 -347
- package/dist/{patrol.d.ts → specialists.d.ts} +34 -30
- package/dist/{patrol.js → specialists.js} +20 -11
- package/dist/threads.d.ts +51 -6
- package/dist/threads.js +44 -8
- package/package.json +8 -4
- package/src/claim-links.ts +23 -0
- package/src/drafts.ts +11 -37
- package/src/query-keys.ts +5 -1
- package/src/{patrol.ts → specialists.ts} +40 -31
- package/src/threads.ts +57 -6
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The
|
|
2
|
+
* The Specialists (ADR 0024): every specialist of a project, the work each did as reports, as the
|
|
3
3
|
* studio shows it at the top of the Agent Sidebar. The API reads it for one scope, a draft or
|
|
4
4
|
* Live, and names each report's subject, so the studio needs no entity loaded to show it.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
/** Every
|
|
8
|
-
export const
|
|
7
|
+
/** Every specialist's stable id. The registry in the API lists them all. */
|
|
8
|
+
export const SPECIALIST_IDS = ["translator", "seoManager", "linkChecker"] as const;
|
|
9
9
|
|
|
10
|
-
/** A
|
|
11
|
-
export type
|
|
10
|
+
/** A specialist's stable id. */
|
|
11
|
+
export type SpecialistId = (typeof SPECIALIST_IDS)[number];
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* What a structured report says was done. Each has a failed form, by the report's outcome. A
|
|
@@ -32,7 +32,7 @@ export type ReportEntity =
|
|
|
32
32
|
| "record"
|
|
33
33
|
| "file";
|
|
34
34
|
|
|
35
|
-
/** A report's subject, named as the Canvas names it, read when the
|
|
35
|
+
/** A report's subject, named as the Canvas names it, read when the Specialists is. */
|
|
36
36
|
export type ReportSubject = { entity: ReportEntity; id: string; name: string };
|
|
37
37
|
|
|
38
38
|
/**
|
|
@@ -57,10 +57,10 @@ export const reportLocale = (
|
|
|
57
57
|
export type ReportOutcome = "done" | "failed";
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* Where a
|
|
61
|
-
* publish
|
|
60
|
+
* Where a specialist is on duty, in drafts or on Live: it works as you go, or is off there (ADR
|
|
61
|
+
* 0034). None checks when you publish: publishing never waits on Camox (ADR 0043).
|
|
62
62
|
*/
|
|
63
|
-
export type
|
|
63
|
+
export type SpecialistDuty = "asYouGo" | "off";
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* Where the studio shows a report's subject: a page on the Canvas, with the block and repeatable
|
|
@@ -72,7 +72,7 @@ export type ReportPlace =
|
|
|
72
72
|
| { kind: "file"; fileId: string };
|
|
73
73
|
|
|
74
74
|
export type ReportView = {
|
|
75
|
-
/** Unique among the
|
|
75
|
+
/** Unique among the specialist's reports in the view. */
|
|
76
76
|
id: string;
|
|
77
77
|
summary: ReportSummary;
|
|
78
78
|
outcome: ReportOutcome;
|
|
@@ -91,12 +91,14 @@ export type ReportView = {
|
|
|
91
91
|
* What a finding is about (ADR 0034), by its kind:
|
|
92
92
|
* - `missingPage`: a link to a page that doesn't exist, the page's name as it was last known.
|
|
93
93
|
* - `brokenLink`: an external link that failed, and how.
|
|
94
|
+
* - `missingMetaTitle`: a page whose meta title is written by hand, and empty.
|
|
94
95
|
* A link's `url` is as content stores it: a page link's is `camox:page:<id>`, and `text` is what
|
|
95
96
|
* the link reads.
|
|
96
97
|
*/
|
|
97
98
|
export type FindingAbout =
|
|
98
99
|
| { kind: "missingPage"; url: string; text: string; page: string | null }
|
|
99
|
-
| { kind: "brokenLink"; url: string; text: string; failure: LinkFailure }
|
|
100
|
+
| { kind: "brokenLink"; url: string; text: string; failure: LinkFailure }
|
|
101
|
+
| { kind: "missingMetaTitle" };
|
|
100
102
|
|
|
101
103
|
/** How a link failed: an HTTP error status, no connection (DNS included), or no answer in time. */
|
|
102
104
|
export type LinkFailure =
|
|
@@ -126,14 +128,17 @@ export type FindingView = {
|
|
|
126
128
|
fieldKind: string | null;
|
|
127
129
|
/** When it was first raised, in milliseconds since the epoch. */
|
|
128
130
|
raisedAt: number;
|
|
129
|
-
/**
|
|
131
|
+
/**
|
|
132
|
+
* Whether it's Live's. A draft shows Live's findings on the content it still holds, and fixes
|
|
133
|
+
* them there.
|
|
134
|
+
*/
|
|
130
135
|
live: boolean;
|
|
131
136
|
/** Where its content shows, as for reports. */
|
|
132
137
|
place: ReportPlace | null;
|
|
133
138
|
};
|
|
134
139
|
|
|
135
|
-
/** What a finding is about, after where it is: "links to About, a page that no longer exists". */
|
|
136
|
-
function
|
|
140
|
+
/** What a link finding is about, after where it is: "links to About, a page that no longer exists". */
|
|
141
|
+
function linkSentence(about: Exclude<FindingAbout, { kind: "missingMetaTitle" }>): string {
|
|
137
142
|
switch (about.kind) {
|
|
138
143
|
case "missingPage":
|
|
139
144
|
return about.page === null
|
|
@@ -154,10 +159,12 @@ function aboutSentence(about: FindingAbout): string {
|
|
|
154
159
|
|
|
155
160
|
/**
|
|
156
161
|
* A finding as one sentence, as the studio, emails, Briefs and refusals word it: "“About us” in
|
|
157
|
-
* Footer links to About, a page that no longer exists".
|
|
162
|
+
* Footer links to About, a page that no longer exists", or "Pricing has no meta title".
|
|
158
163
|
*/
|
|
159
164
|
export function findingSentence(finding: Pick<FindingView, "about" | "subject">): string {
|
|
160
|
-
|
|
165
|
+
const { about, subject } = finding;
|
|
166
|
+
if (about.kind === "missingMetaTitle") return `${subject.name} has no meta title`;
|
|
167
|
+
return `“${about.text}” in ${subject.name} ${linkSentence(about)}`;
|
|
161
168
|
}
|
|
162
169
|
|
|
163
170
|
/**
|
|
@@ -165,7 +172,9 @@ export function findingSentence(finding: Pick<FindingView, "about" | "subject">)
|
|
|
165
172
|
* by its field: "“About us” links to About, a page that no longer exists".
|
|
166
173
|
*/
|
|
167
174
|
export function findingSentenceInPlace(finding: Pick<FindingView, "about">): string {
|
|
168
|
-
|
|
175
|
+
const { about } = finding;
|
|
176
|
+
if (about.kind === "missingMetaTitle") return "The page has no meta title";
|
|
177
|
+
return `“${about.text}” ${linkSentence(about)}`;
|
|
169
178
|
}
|
|
170
179
|
|
|
171
180
|
/** A member's dismissal of what a finding was about: no finding like it is raised again. */
|
|
@@ -180,26 +189,26 @@ export type DismissalView = {
|
|
|
180
189
|
/** What applying a finding's fix did: applied it, or found its content changed and rechecked. */
|
|
181
190
|
export type ApplyResult = { applied: boolean };
|
|
182
191
|
|
|
183
|
-
/** How many subjects a
|
|
192
|
+
/** How many subjects a specialist is on in one locale, a locale's tag. */
|
|
184
193
|
export type LocaleCount = { locale: string; count: number };
|
|
185
194
|
|
|
186
195
|
/**
|
|
187
|
-
* What a
|
|
196
|
+
* What a specialist is on: the one subject when there's one, and how many subjects in each
|
|
188
197
|
* locale, the most first. A subject in several locales counts in each.
|
|
189
198
|
*/
|
|
190
|
-
export type
|
|
199
|
+
export type SpecialistWorking = {
|
|
191
200
|
subject: ReportSubject | null;
|
|
192
201
|
locales: LocaleCount[];
|
|
193
202
|
};
|
|
194
203
|
|
|
195
|
-
export type
|
|
196
|
-
id:
|
|
204
|
+
export type SpecialistView = {
|
|
205
|
+
id: SpecialistId;
|
|
197
206
|
/** Its role, as editors know it: "Translator", "SEO manager". */
|
|
198
207
|
role: string;
|
|
199
208
|
/** Its duty in the scope viewed. Off there, it still has its reports. */
|
|
200
|
-
duty:
|
|
209
|
+
duty: SpecialistDuty;
|
|
201
210
|
/** Null when it isn't working in the scope. */
|
|
202
|
-
working:
|
|
211
|
+
working: SpecialistWorking | null;
|
|
203
212
|
/**
|
|
204
213
|
* When it last finished a check in the scope, whether it found anything or not, or null when it
|
|
205
214
|
* never checked there.
|
|
@@ -220,14 +229,14 @@ export type PatrollerView = {
|
|
|
220
229
|
dismissals: DismissalView[];
|
|
221
230
|
};
|
|
222
231
|
|
|
232
|
+
/** Every specialist of the registry, the latest activity first; never-worked ones last. */
|
|
233
|
+
export type SpecialistsView = { specialists: SpecialistView[] };
|
|
234
|
+
|
|
223
235
|
/**
|
|
224
|
-
*
|
|
225
|
-
*
|
|
236
|
+
* URL search param the studio opens a specialist's findings from, by its id, as the email about
|
|
237
|
+
* Live's new findings links to them. The studio drops it once opened.
|
|
226
238
|
*/
|
|
227
|
-
export
|
|
228
|
-
patrollers: PatrollerView[];
|
|
229
|
-
patrolDraft: { id: string; name: string | null } | null;
|
|
230
|
-
};
|
|
239
|
+
export const SPECIALIST_SEARCH_PARAM = "camox-specialist";
|
|
231
240
|
|
|
232
|
-
/** How many reports the
|
|
241
|
+
/** How many reports the Specialists shows of each specialist; the rest are counted. */
|
|
233
242
|
export const REPORTS_SHOWN = 5;
|
package/src/threads.ts
CHANGED
|
@@ -45,8 +45,7 @@ export const RECONCILE_ENTRY = "camox.reconcile";
|
|
|
45
45
|
/**
|
|
46
46
|
* The kind of the transcript entry opening a draft's thread a Live thread handed a request off
|
|
47
47
|
* to: the Brief, which the model reads ahead of the editor's messages, and editors see collapsed
|
|
48
|
-
* after them, so the request reads on as they sent it.
|
|
49
|
-
* too, with their `operationId` as data, and start the agent's run, which acts for Camox.
|
|
48
|
+
* after them, so the request reads on as they sent it.
|
|
50
49
|
*/
|
|
51
50
|
export const BRIEF_ENTRY = "camox.brief";
|
|
52
51
|
|
|
@@ -127,6 +126,30 @@ export const codeRequestDataSchema = artifactRequestDataSchema.extend({
|
|
|
127
126
|
});
|
|
128
127
|
export type CodeRequestData = z.infer<typeof codeRequestDataSchema>;
|
|
129
128
|
|
|
129
|
+
/**
|
|
130
|
+
* The draft thread's agent's tool asking the Connected Agents following its turn questions, which
|
|
131
|
+
* their waiting calls come back with as the agent works on (ADR 0042). The model calls it itself,
|
|
132
|
+
* never from a script.
|
|
133
|
+
*/
|
|
134
|
+
export const ASK_TOOL = "askConnectedAgent";
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* The kind of the transcript entry recording questions the agent asked the Connected Agents
|
|
138
|
+
* following its turn, with a `questionsDataSchema` as data. Neither the model nor editors read
|
|
139
|
+
* it: questions are never a thread's messages, and never end its turn.
|
|
140
|
+
*/
|
|
141
|
+
export const QUESTIONS_ENTRY = "camox.questions";
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* What a questions entry records: the questions, in the order the agent asked them, and the
|
|
145
|
+
* Connected Agents they were asked of, each by its member's id and client id.
|
|
146
|
+
*/
|
|
147
|
+
export const questionsDataSchema = z.object({
|
|
148
|
+
questions: z.array(z.string()).min(1),
|
|
149
|
+
to: z.array(z.object({ memberId: z.string(), clientId: z.string() })),
|
|
150
|
+
});
|
|
151
|
+
export type QuestionsData = z.infer<typeof questionsDataSchema>;
|
|
152
|
+
|
|
130
153
|
/**
|
|
131
154
|
* The agent's tools handing work to an agent of its own, in a conversation beside the thread's,
|
|
132
155
|
* whose answer they return: editors see what that agent does as the agent's.
|
|
@@ -209,14 +232,26 @@ export const isSender = (data: unknown): data is ThreadSender =>
|
|
|
209
232
|
export const connectedAgentClientSchema = z.object({ name: z.string(), verified: z.boolean() });
|
|
210
233
|
export type ConnectedAgentClient = z.infer<typeof connectedAgentClientSchema>;
|
|
211
234
|
|
|
235
|
+
/** An Attachment a Connected Agent's message carries: an image the agent sees with it. */
|
|
236
|
+
export const messageAttachmentSchema = z.object({
|
|
237
|
+
id: z.string(),
|
|
238
|
+
filename: z.string(),
|
|
239
|
+
mimeType: z.string(),
|
|
240
|
+
url: z.string(),
|
|
241
|
+
});
|
|
242
|
+
export type MessageAttachmentData = z.infer<typeof messageAttachmentSchema>;
|
|
243
|
+
|
|
212
244
|
/**
|
|
213
|
-
* What a Connected Agent's message entry records: the member it acts as, its client,
|
|
214
|
-
* operation id it was sent with.
|
|
245
|
+
* What a Connected Agent's message entry records: the member it acts as, its client, the
|
|
246
|
+
* operation id it was sent with, and whether it answers the agent's questions (ADR 0042).
|
|
215
247
|
*/
|
|
216
248
|
export const connectedAgentMessageDataSchema = z.object({
|
|
217
249
|
member: threadSenderSchema,
|
|
218
250
|
client: connectedAgentClientSchema,
|
|
219
251
|
operationId: z.string().optional(),
|
|
252
|
+
answer: z.boolean().optional(),
|
|
253
|
+
/** The images the message carries for the agent to see, as studios list them (ADR 0044). */
|
|
254
|
+
attachments: z.array(messageAttachmentSchema).optional(),
|
|
220
255
|
});
|
|
221
256
|
export type ConnectedAgentMessageData = z.infer<typeof connectedAgentMessageDataSchema>;
|
|
222
257
|
|
|
@@ -291,7 +326,9 @@ export type ThreadMessage =
|
|
|
291
326
|
| { id: string; from: "editor"; sender: ThreadSender; text: string }
|
|
292
327
|
/**
|
|
293
328
|
* A member's Connected Agent's message: the member's own AI, in its client, writing as itself.
|
|
294
|
-
* Never shown as the member's own words.
|
|
329
|
+
* Never shown as the member's own words. An answer to the agent's questions shows as the
|
|
330
|
+
* context the draft was built on, collapsed, as a Brief does, rather than as a request
|
|
331
|
+
* (ADR 0042).
|
|
295
332
|
*/
|
|
296
333
|
| {
|
|
297
334
|
id: string;
|
|
@@ -299,6 +336,9 @@ export type ThreadMessage =
|
|
|
299
336
|
member: ThreadSender;
|
|
300
337
|
client: ConnectedAgentClient;
|
|
301
338
|
text: string;
|
|
339
|
+
answer: boolean;
|
|
340
|
+
/** The images it carries for the agent to see, if any (ADR 0044). */
|
|
341
|
+
attachments?: MessageAttachmentData[];
|
|
302
342
|
}
|
|
303
343
|
/** Camox's own message, starting a turn it began itself: why it did. */
|
|
304
344
|
| { id: string; from: "camox"; text: string }
|
|
@@ -408,7 +448,14 @@ export const EMPTY_THREAD_STATE: ThreadState = { steps: [], running: false };
|
|
|
408
448
|
/** One transcript entry, as far as the studio's view is concerned. */
|
|
409
449
|
type ThreadStep = { id: string } & (
|
|
410
450
|
| { type: "editor"; sender: ThreadSender; text: string }
|
|
411
|
-
| {
|
|
451
|
+
| {
|
|
452
|
+
type: "connectedAgent";
|
|
453
|
+
member: ThreadSender;
|
|
454
|
+
client: ConnectedAgentClient;
|
|
455
|
+
text: string;
|
|
456
|
+
answer: boolean;
|
|
457
|
+
attachments?: MessageAttachmentData[];
|
|
458
|
+
}
|
|
412
459
|
/** Camox starting a turn of its own, saying why. */
|
|
413
460
|
| { type: "camox"; text: string }
|
|
414
461
|
/** The Brief opening a draft's thread a Live thread handed a request off to. */
|
|
@@ -584,6 +631,8 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
|
|
|
584
631
|
member: senderOf(data.member)!,
|
|
585
632
|
client: { name: data.client.name, verified: data.client.verified },
|
|
586
633
|
text: editorTextOf(message.content),
|
|
634
|
+
answer: data.answer ?? false,
|
|
635
|
+
...(data.attachments?.length && { attachments: data.attachments }),
|
|
587
636
|
}
|
|
588
637
|
);
|
|
589
638
|
}
|
|
@@ -687,6 +736,8 @@ export function threadViewOf({
|
|
|
687
736
|
member: step.member,
|
|
688
737
|
client: step.client,
|
|
689
738
|
text: step.text,
|
|
739
|
+
answer: step.answer,
|
|
740
|
+
...(step.attachments && { attachments: step.attachments }),
|
|
690
741
|
});
|
|
691
742
|
waiting = step.id;
|
|
692
743
|
break;
|