@camox/api-contract 0.65.1 → 0.67.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/drafts.d.ts CHANGED
@@ -49,6 +49,20 @@ export declare const LOOK_LOCALE_PARAM = "locale";
49
49
  export declare const LOOK_BLOCK_DEFINITION_PARAM = "blockDefinition";
50
50
  export declare const LOOK_CONTENT_PARAM = "content";
51
51
  export declare const LOOK_SETTINGS_PARAM = "settings";
52
+ /**
53
+ * The look document's Layout Definition mode: instead of a page, the curated Layout
54
+ * `LOOK_LAYOUT_DEFINITION_PARAM` names, alone in the site's shell, its blocks with their default
55
+ * content and Block Settings and the overrides `LOOK_BLOCKS_PARAM` gives as JSON, by Block
56
+ * Definition name (`{ navbar: { content, settings } }`), around a labelled placeholder where a
57
+ * page's content goes, as tall as `LOOK_PLACEHOLDER_PARAM` says: one of `LOOK_PLACEHOLDER_HEIGHTS`,
58
+ * the first without.
59
+ */
60
+ export declare const LOOK_LAYOUT_DEFINITION_PARAM = "layoutDefinition";
61
+ export declare const LOOK_BLOCKS_PARAM = "blocks";
62
+ export declare const LOOK_PLACEHOLDER_PARAM = "placeholder";
63
+ /** A short placeholder shows whether the footer rises up the screen; a tall one, scrolling. */
64
+ export declare const LOOK_PLACEHOLDER_HEIGHTS: readonly ["short", "tall"];
65
+ export type LookPlaceholderHeight = (typeof LOOK_PLACEHOLDER_HEIGHTS)[number];
52
66
  /**
53
67
  * The id of a block shown without being placed, as the look document's Block Definition mode
54
68
  * shows it: never a stored block's, which are UUIDs. Its element is
package/dist/drafts.js CHANGED
@@ -50,6 +50,19 @@ const LOOK_BLOCK_DEFINITION_PARAM = "blockDefinition";
50
50
  const LOOK_CONTENT_PARAM = "content";
51
51
  const LOOK_SETTINGS_PARAM = "settings";
52
52
  /**
53
+ * The look document's Layout Definition mode: instead of a page, the curated Layout
54
+ * `LOOK_LAYOUT_DEFINITION_PARAM` names, alone in the site's shell, its blocks with their default
55
+ * content and Block Settings and the overrides `LOOK_BLOCKS_PARAM` gives as JSON, by Block
56
+ * Definition name (`{ navbar: { content, settings } }`), around a labelled placeholder where a
57
+ * page's content goes, as tall as `LOOK_PLACEHOLDER_PARAM` says: one of `LOOK_PLACEHOLDER_HEIGHTS`,
58
+ * the first without.
59
+ */
60
+ const LOOK_LAYOUT_DEFINITION_PARAM = "layoutDefinition";
61
+ const LOOK_BLOCKS_PARAM = "blocks";
62
+ const LOOK_PLACEHOLDER_PARAM = "placeholder";
63
+ /** A short placeholder shows whether the footer rises up the screen; a tall one, scrolling. */
64
+ const LOOK_PLACEHOLDER_HEIGHTS = ["short", "tall"];
65
+ /**
53
66
  * The id of a block shown without being placed, as the look document's Block Definition mode
54
67
  * shows it: never a stored block's, which are UUIDs. Its element is
55
68
  * `[data-camox-viewport-block="unplaced"]`.
@@ -223,4 +236,4 @@ function isContentWrite(path) {
223
236
  }
224
237
 
225
238
  //#endregion
226
- export { CAMOX_ACTOR, CAMOX_NAME, CHECKS_RUNNING_ERROR, CONFLICTS_CHANGED_ERROR, DRAFT_CREATE_HEADER, DRAFT_ENDED_ERROR_CODE, DRAFT_HEADER, DRAFT_SEARCH_PARAM, FINDINGS_OPEN_ERROR, LIVE_READ_ONLY_ERROR, LOCALE_HEADER, LOOK_BLOCK_DEFINITION_PARAM, LOOK_CONTENT_PARAM, LOOK_DOCUMENT_PATH, LOOK_LOCALE_PARAM, LOOK_PATH_PARAM, LOOK_SETTINGS_PARAM, LOOK_TOKEN_COOKIE, LOOK_TOKEN_HEADER, NOT_PUBLISHED_CODES, PUBLISHING_DRAFT_HEADER, PUBLISHING_DRAFT_VAR, PUBLISH_ABORT_REASONS, PUBLISH_BLOCKED_ERROR, REVISION_CHANGED_ERROR, TRANSLATIONS_PENDING_ERROR, UNPLACED_BLOCK_ID, isContentWrite };
239
+ export { CAMOX_ACTOR, CAMOX_NAME, CHECKS_RUNNING_ERROR, CONFLICTS_CHANGED_ERROR, DRAFT_CREATE_HEADER, DRAFT_ENDED_ERROR_CODE, DRAFT_HEADER, DRAFT_SEARCH_PARAM, FINDINGS_OPEN_ERROR, LIVE_READ_ONLY_ERROR, LOCALE_HEADER, LOOK_BLOCKS_PARAM, LOOK_BLOCK_DEFINITION_PARAM, LOOK_CONTENT_PARAM, LOOK_DOCUMENT_PATH, LOOK_LAYOUT_DEFINITION_PARAM, LOOK_LOCALE_PARAM, LOOK_PATH_PARAM, LOOK_PLACEHOLDER_HEIGHTS, LOOK_PLACEHOLDER_PARAM, LOOK_SETTINGS_PARAM, LOOK_TOKEN_COOKIE, LOOK_TOKEN_HEADER, NOT_PUBLISHED_CODES, PUBLISHING_DRAFT_HEADER, PUBLISHING_DRAFT_VAR, PUBLISH_ABORT_REASONS, PUBLISH_BLOCKED_ERROR, REVISION_CHANGED_ERROR, TRANSLATIONS_PENDING_ERROR, UNPLACED_BLOCK_ID, isContentWrite };
package/dist/patrol.d.ts CHANGED
@@ -5,14 +5,14 @@
5
5
  * Live, and names each report's subject, so the studio needs no entity loaded to show it.
6
6
  */
7
7
  /** Every patroller's stable id. The registry in the API lists them all. */
8
- export declare const PATROLLER_IDS: readonly ["translator", "seoManager", "linkChecker", "proofreader"];
8
+ export declare const PATROLLER_IDS: readonly ["translator", "seoManager", "linkChecker"];
9
9
  /** A patroller's stable id. */
10
10
  export type PatrollerId = (typeof PATROLLER_IDS)[number];
11
11
  /**
12
12
  * What a structured report says was done. Each has a failed form, by the report's outcome. A
13
13
  * merge with Live's localization reads as a translation.
14
14
  */
15
- export type ReportVerb = "translated" | "mergedWithLive" | "wroteSeo" | "describedFile" | "publishedCatchUp" | "removedLink" | "fixedLink" | "fixedMistake";
15
+ export type ReportVerb = "translated" | "mergedWithLive" | "wroteSeo" | "describedFile" | "publishedCatchUp" | "removedLink" | "fixedLink";
16
16
  /** What a report can be about. */
17
17
  export type ReportEntity = "page" | "block" | "sharedContent" | "repeatableItem" | "record" | "file";
18
18
  /** A report's subject, named as the Canvas names it, read when the Patrol is. */
@@ -84,7 +84,6 @@ export type ReportView = {
84
84
  * What a finding is about (ADR 0034), by its kind:
85
85
  * - `missingPage`: a link to a page that doesn't exist, the page's name as it was last known.
86
86
  * - `brokenLink`: an external link that failed, and how.
87
- * - `misspelling`, `grammar`: the flagged text, in the sentence it's in.
88
87
  * A link's `url` is as content stores it: a page link's is `camox:page:<id>`, and `text` is what
89
88
  * the link reads.
90
89
  */
@@ -98,10 +97,6 @@ export type FindingAbout = {
98
97
  url: string;
99
98
  text: string;
100
99
  failure: LinkFailure;
101
- } | {
102
- kind: "misspelling" | "grammar";
103
- text: string;
104
- sentence: string;
105
100
  };
106
101
  /** How a link failed: an HTTP error status, no connection (DNS included), or no answer in time. */
107
102
  export type LinkFailure = {
@@ -113,15 +108,9 @@ export type LinkFailure = {
113
108
  kind: "timeout";
114
109
  };
115
110
  export type FindingKind = FindingAbout["kind"];
116
- /**
117
- * The fix a finding suggests, which applying makes: removing the link, keeping its text, or
118
- * replacing the flagged text with `replacement` in its sentence, keeping its formatting.
119
- */
111
+ /** The fix a finding suggests, which applying makes: removing the link, keeping its text. */
120
112
  export type FindingFix = {
121
113
  kind: "removeLink";
122
- } | {
123
- kind: "replaceText";
124
- replacement: string;
125
114
  };
126
115
  /** A finding, open, in the scope viewed. */
127
116
  export type FindingView = {
@@ -136,6 +125,8 @@ export type FindingView = {
136
125
  locale: string;
137
126
  /** The content field it's in, by name. */
138
127
  field: string;
128
+ /** That field's kind, such as "String" or "Link", or null when unknown. */
129
+ fieldKind: string | null;
139
130
  /** When it was first raised, in milliseconds since the epoch. */
140
131
  raisedAt: number;
141
132
  /** Whether it's Live's. Live's are fixed in the Patrol Draft. */
@@ -148,6 +139,11 @@ export type FindingView = {
148
139
  * Footer links to About, a page that no longer exists".
149
140
  */
150
141
  export declare function findingSentence(finding: Pick<FindingView, "about" | "subject">): string;
142
+ /**
143
+ * A finding as one sentence where its content is already shown, as the studio's sidebar shows it
144
+ * by its field: "“About us” links to About, a page that no longer exists".
145
+ */
146
+ export declare function findingSentenceInPlace(finding: Pick<FindingView, "about">): string;
151
147
  /** A member's dismissal of what a finding was about: no finding like it is raised again. */
152
148
  export type DismissalView = {
153
149
  id: number;
package/dist/patrol.js CHANGED
@@ -8,8 +8,7 @@
8
8
  const PATROLLER_IDS = [
9
9
  "translator",
10
10
  "seoManager",
11
- "linkChecker",
12
- "proofreader"
11
+ "linkChecker"
13
12
  ];
14
13
  /** The locale a summary's work was in: a structured summary's, if any. A text summary has none. */
15
14
  const reportLocale = (summary) => summary.kind === "structured" ? summary.locale : null;
@@ -22,8 +21,6 @@ function aboutSentence(about) {
22
21
  const how = failure.kind === "status" ? `answers with an error (${failure.status})` : failure.kind === "timeout" ? "doesn't answer in time" : "can't be reached";
23
22
  return `links to ${about.url}, which ${how}`;
24
23
  }
25
- case "misspelling": return "may be misspelled";
26
- case "grammar": return "may be a grammar mistake";
27
24
  }
28
25
  }
29
26
  /**
@@ -33,8 +30,15 @@ function aboutSentence(about) {
33
30
  function findingSentence(finding) {
34
31
  return `“${finding.about.text}” in ${finding.subject.name} ${aboutSentence(finding.about)}`;
35
32
  }
33
+ /**
34
+ * A finding as one sentence where its content is already shown, as the studio's sidebar shows it
35
+ * by its field: "“About us” links to About, a page that no longer exists".
36
+ */
37
+ function findingSentenceInPlace(finding) {
38
+ return `“${finding.about.text}” ${aboutSentence(finding.about)}`;
39
+ }
36
40
  /** How many reports the Patrol shows of each patroller; the rest are counted. */
37
41
  const REPORTS_SHOWN = 5;
38
42
 
39
43
  //#endregion
40
- export { PATROLLER_IDS, REPORTS_SHOWN, findingSentence, reportLocale };
44
+ export { PATROLLER_IDS, REPORTS_SHOWN, findingSentence, findingSentenceInPlace, reportLocale };
package/dist/router.d.ts CHANGED
@@ -2298,6 +2298,25 @@ declare const schema: {
2298
2298
  }, {}, {
2299
2299
  length: number | undefined;
2300
2300
  }>;
2301
+ overviewName: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2302
+ name: "overview_name";
2303
+ tableName: "drafts";
2304
+ dataType: "string";
2305
+ columnType: "SQLiteText";
2306
+ data: string;
2307
+ driverParam: string;
2308
+ notNull: false;
2309
+ hasDefault: false;
2310
+ isPrimaryKey: false;
2311
+ isAutoincrement: false;
2312
+ hasRuntimeDefault: false;
2313
+ enumValues: [string, ...string[]];
2314
+ baseColumn: never;
2315
+ identity: undefined;
2316
+ generated: undefined;
2317
+ }, {}, {
2318
+ length: number | undefined;
2319
+ }>;
2301
2320
  overviewBullets: import("drizzle-orm/sqlite-core").SQLiteColumn<{
2302
2321
  name: "overview_bullets";
2303
2322
  tableName: "drafts";
@@ -9948,9 +9967,9 @@ type CheckJob = {
9948
9967
  };
9949
9968
  /**
9950
9969
  * A run of patrollers' patrols of a project's Live content (ADR 0034), keyed by project: the cron
9951
- * trigger requests one of every patroller patrolling Live for every project every 6 hours, and a
9952
- * release no draft published requests the Proofreader's. Requesting one while another is pending
9953
- * joins it: one run patrols for both, so its new findings are told of once.
9970
+ * trigger requests one of every patroller patrolling Live for every project every 6 hours.
9971
+ * Requesting one while another is pending joins it: one run patrols for both, so its new findings
9972
+ * are told of once.
9954
9973
  */
9955
9974
  type PatrolJob = {
9956
9975
  kind: "patrol";
@@ -10357,10 +10376,12 @@ type Overview =
10357
10376
  } |
10358
10377
  /**
10359
10378
  * Written. A `stale` one describes an older version of the draft's changes: it's still shown
10360
- * while a new one is written.
10379
+ * while a new one is written. `name` is the short name AI suggests keeping the draft under,
10380
+ * null for an overview written before overviews had one.
10361
10381
  */
10362
10382
  {
10363
10383
  status: "ready";
10384
+ name: string | null;
10364
10385
  headline: string;
10365
10386
  bullets: string[];
10366
10387
  stale: boolean;
@@ -14493,6 +14514,177 @@ declare const router: {
14493
14514
  structuredData: BlockStructuredData;
14494
14515
  recordStructuredData: import("@camox/api-contract").RecordStructuredData;
14495
14516
  }>, Record<never, never>, Record<never, never>>;
14517
+ getUnplaced: import("@orpc/server").DecoratedProcedure<import("@orpc/server").MergedInitialContext<import("@orpc/server").MergedInitialContext<BaseContext & Record<never, never>, BaseContext, BaseContext>, import("@orpc/server").MergedCurrentContext<BaseContext, Record<never, never>>, import("@orpc/server").MergedCurrentContext<BaseContext, Record<never, never>>>, import("@orpc/server").MergedCurrentContext<import("@orpc/server").MergedCurrentContext<BaseContext, Record<never, never>>, {
14518
+ draft: DraftRequest | null;
14519
+ locale: string | undefined;
14520
+ publishingDraft: string | undefined;
14521
+ look: LookGrant | undefined;
14522
+ }>, import("zod").ZodObject<{
14523
+ projectUuid: import("zod").ZodUUID;
14524
+ layoutId: import("zod").ZodString;
14525
+ blocks: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodObject<{
14526
+ content: import("zod").ZodOptional<import("zod").ZodUnknown>;
14527
+ settings: import("zod").ZodOptional<import("zod").ZodUnknown>;
14528
+ }, import("zod/v4/core").$strip>>>;
14529
+ }, import("zod/v4/core").$strip>, import("@orpc/server").Schema<{
14530
+ projectName: string;
14531
+ project: {
14532
+ id: number;
14533
+ updatedAt: number;
14534
+ analytics: AnalyticsProviders;
14535
+ socialProfiles: string[];
14536
+ };
14537
+ layout: {
14538
+ layoutId: string;
14539
+ contentUpdatedAt: number;
14540
+ updatedAt: number;
14541
+ beforeBlockIds: string[];
14542
+ afterBlockIds: string[];
14543
+ };
14544
+ blocks: (Omit<{
14545
+ baseVersion: number | null;
14546
+ content: unknown;
14547
+ createdAt: number;
14548
+ deleted: boolean;
14549
+ draftId: string | null;
14550
+ firstPublishedAt: number | null;
14551
+ id: string;
14552
+ layoutId: string | null;
14553
+ locale: string;
14554
+ pageId: string | null;
14555
+ position: string;
14556
+ projectId: number;
14557
+ publishedAt: number | null;
14558
+ publishedBy: string | null;
14559
+ rowId: number;
14560
+ settings: unknown;
14561
+ slot: "after" | "before" | null;
14562
+ type: string;
14563
+ updatedAt: number;
14564
+ version: number;
14565
+ }, "content"> & {
14566
+ content: Record<string, unknown>;
14567
+ } & {
14568
+ summary: string;
14569
+ title: string;
14570
+ artifactHashes: Record<string, string>;
14571
+ } & {
14572
+ references: ResolvedReferences;
14573
+ })[];
14574
+ repeatableItems: (Omit<{
14575
+ baseVersion: number | null;
14576
+ blockId: string | null;
14577
+ content: unknown;
14578
+ createdAt: number;
14579
+ deleted: boolean;
14580
+ draftId: string | null;
14581
+ fieldName: string;
14582
+ firstPublishedAt: number | null;
14583
+ id: string;
14584
+ locale: string;
14585
+ parentItemId: string | null;
14586
+ position: string;
14587
+ projectId: number | null;
14588
+ publishedAt: number | null;
14589
+ publishedBy: string | null;
14590
+ rowId: number;
14591
+ settings: unknown;
14592
+ sharedContentType: string | null;
14593
+ updatedAt: number;
14594
+ version: number;
14595
+ }, "content"> & {
14596
+ content: Record<string, unknown>;
14597
+ } & {
14598
+ blockId: string;
14599
+ summary: string;
14600
+ title: string;
14601
+ artifactHashes: Record<string, string>;
14602
+ } & {
14603
+ references: ResolvedReferences;
14604
+ })[];
14605
+ files: FileView[];
14606
+ structuredData: BlockStructuredData;
14607
+ recordStructuredData: import("@camox/api-contract").RecordStructuredData;
14608
+ }, {
14609
+ projectName: string;
14610
+ project: {
14611
+ id: number;
14612
+ updatedAt: number;
14613
+ analytics: AnalyticsProviders;
14614
+ socialProfiles: string[];
14615
+ };
14616
+ layout: {
14617
+ layoutId: string;
14618
+ contentUpdatedAt: number;
14619
+ updatedAt: number;
14620
+ beforeBlockIds: string[];
14621
+ afterBlockIds: string[];
14622
+ };
14623
+ blocks: (Omit<{
14624
+ baseVersion: number | null;
14625
+ content: unknown;
14626
+ createdAt: number;
14627
+ deleted: boolean;
14628
+ draftId: string | null;
14629
+ firstPublishedAt: number | null;
14630
+ id: string;
14631
+ layoutId: string | null;
14632
+ locale: string;
14633
+ pageId: string | null;
14634
+ position: string;
14635
+ projectId: number;
14636
+ publishedAt: number | null;
14637
+ publishedBy: string | null;
14638
+ rowId: number;
14639
+ settings: unknown;
14640
+ slot: "after" | "before" | null;
14641
+ type: string;
14642
+ updatedAt: number;
14643
+ version: number;
14644
+ }, "content"> & {
14645
+ content: Record<string, unknown>;
14646
+ } & {
14647
+ summary: string;
14648
+ title: string;
14649
+ artifactHashes: Record<string, string>;
14650
+ } & {
14651
+ references: ResolvedReferences;
14652
+ })[];
14653
+ repeatableItems: (Omit<{
14654
+ baseVersion: number | null;
14655
+ blockId: string | null;
14656
+ content: unknown;
14657
+ createdAt: number;
14658
+ deleted: boolean;
14659
+ draftId: string | null;
14660
+ fieldName: string;
14661
+ firstPublishedAt: number | null;
14662
+ id: string;
14663
+ locale: string;
14664
+ parentItemId: string | null;
14665
+ position: string;
14666
+ projectId: number | null;
14667
+ publishedAt: number | null;
14668
+ publishedBy: string | null;
14669
+ rowId: number;
14670
+ settings: unknown;
14671
+ sharedContentType: string | null;
14672
+ updatedAt: number;
14673
+ version: number;
14674
+ }, "content"> & {
14675
+ content: Record<string, unknown>;
14676
+ } & {
14677
+ blockId: string;
14678
+ summary: string;
14679
+ title: string;
14680
+ artifactHashes: Record<string, string>;
14681
+ } & {
14682
+ references: ResolvedReferences;
14683
+ })[];
14684
+ files: FileView[];
14685
+ structuredData: BlockStructuredData;
14686
+ recordStructuredData: import("@camox/api-contract").RecordStructuredData;
14687
+ }>, Record<never, never>, Record<never, never>>;
14496
14688
  list: import("@orpc/server").DecoratedProcedure<import("@orpc/server").MergedInitialContext<import("@orpc/server").MergedInitialContext<BaseContext & Record<never, never>, BaseContext, BaseContext>, import("@orpc/server").MergedCurrentContext<BaseContext, Record<never, never>>, import("@orpc/server").MergedCurrentContext<BaseContext, Record<never, never>>>, import("@orpc/server").MergedCurrentContext<import("@orpc/server").MergedCurrentContext<BaseContext, Record<never, never>>, {
14497
14689
  draft: DraftRequest | null;
14498
14690
  locale: string | undefined;
@@ -18103,16 +18295,17 @@ declare const router: {
18103
18295
  subject: import("@camox/api-contract/patrol").ReportSubject;
18104
18296
  locale: string;
18105
18297
  field: string;
18298
+ fieldKind: string | null;
18106
18299
  raisedAt: number;
18107
18300
  live: boolean;
18108
18301
  place: import("@camox/api-contract/patrol").ReportPlace | null;
18109
18302
  patroller: {
18110
- id: "linkChecker" | "proofreader" | "seoManager" | "translator";
18303
+ id: "linkChecker" | "seoManager" | "translator";
18111
18304
  role: string;
18112
18305
  };
18113
18306
  }[];
18114
18307
  checking: {
18115
- id: "linkChecker" | "proofreader" | "seoManager" | "translator";
18308
+ id: "linkChecker" | "seoManager" | "translator";
18116
18309
  role: string;
18117
18310
  }[];
18118
18311
  defaultLocale: string | null;
@@ -18121,6 +18314,7 @@ declare const router: {
18121
18314
  status: "pending";
18122
18315
  } | {
18123
18316
  status: "ready";
18317
+ name: string | null;
18124
18318
  headline: string;
18125
18319
  bullets: string[];
18126
18320
  stale: boolean;
@@ -18144,16 +18338,17 @@ declare const router: {
18144
18338
  subject: import("@camox/api-contract/patrol").ReportSubject;
18145
18339
  locale: string;
18146
18340
  field: string;
18341
+ fieldKind: string | null;
18147
18342
  raisedAt: number;
18148
18343
  live: boolean;
18149
18344
  place: import("@camox/api-contract/patrol").ReportPlace | null;
18150
18345
  patroller: {
18151
- id: "linkChecker" | "proofreader" | "seoManager" | "translator";
18346
+ id: "linkChecker" | "seoManager" | "translator";
18152
18347
  role: string;
18153
18348
  };
18154
18349
  }[];
18155
18350
  checking: {
18156
- id: "linkChecker" | "proofreader" | "seoManager" | "translator";
18351
+ id: "linkChecker" | "seoManager" | "translator";
18157
18352
  role: string;
18158
18353
  }[];
18159
18354
  defaultLocale: string | null;
@@ -18162,6 +18357,7 @@ declare const router: {
18162
18357
  status: "pending";
18163
18358
  } | {
18164
18359
  status: "ready";
18360
+ name: string | null;
18165
18361
  headline: string;
18166
18362
  bullets: string[];
18167
18363
  stale: boolean;
package/dist/threads.d.ts CHANGED
@@ -45,6 +45,13 @@ export declare const BRIEF_ENTRY = "camox.brief";
45
45
  * it at the end of the conversation; editors never see it, since the draft's card says so.
46
46
  */
47
47
  export declare const DRAFT_ENDED_ENTRY = "camox.draft-ended";
48
+ /**
49
+ * The kind of the transcript entry a Live thread starts afresh from once its agent handed a
50
+ * request off to a draft (ADR 0036): a reset of Pi's context, with a `startedDraftCardSchema` as
51
+ * data. Editors see it as the draft's card, alone, and the model reads a note naming the draft.
52
+ * Everything before it is left out of both.
53
+ */
54
+ export declare const DRAFT_CARD_ENTRY = "camox.draft-card";
48
55
  /**
49
56
  * The kind of the transcript entry recording a member's push to the draft's code from their own
50
57
  * checkout (ADR 0026), with a `pushDataSchema` as data. The model reads it as context, never
@@ -67,6 +74,43 @@ export declare const START_DRAFT_TOOL = "startDraft";
67
74
  export declare const BUILD_TOOL = "build";
68
75
  /** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
69
76
  export declare const LOOK_TOOL = "look";
77
+ /**
78
+ * The agent's tool handing an artifact's source to its Artifact Agent, which writes and changes
79
+ * it in a conversation of its own and answers the agent (ADR 0035).
80
+ */
81
+ export declare const ARTIFACT_TOOL = "artifact";
82
+ /**
83
+ * The kind of the entry recording a request the agent sent an Artifact Agent, in the Artifact
84
+ * Agent's own conversation, which editors never see: who the run acts for and the operation ids
85
+ * of its messages, as data (`artifactRequestDataSchema`).
86
+ */
87
+ export declare const ARTIFACT_REQUEST_ENTRY = "camox.artifact-request";
88
+ /** What an Artifact Agent's request records: the member the run acts for, and its messages. */
89
+ export declare const artifactRequestDataSchema: z.ZodObject<{
90
+ member: z.ZodString;
91
+ operationIds: z.ZodArray<z.ZodString>;
92
+ }, z.core.$strip>;
93
+ export type ArtifactRequestData = z.infer<typeof artifactRequestDataSchema>;
94
+ /**
95
+ * The agent's tool handing code work to the draft thread's Code Agent, which reads and writes the
96
+ * website's code in a conversation of its own and answers the agent.
97
+ */
98
+ export declare const CODE_TOOL = "code";
99
+ /**
100
+ * The kind of the entry recording a request the agent sent the Code Agent, in the Code Agent's
101
+ * own conversation, which editors never see: who the run acts for, the operation ids of its
102
+ * messages, and whether it's Camox's reconcile turn, as data (`codeRequestDataSchema`).
103
+ */
104
+ export declare const CODE_REQUEST_ENTRY = "camox.code-request";
105
+ /** What a Code Agent's request records: as an Artifact Agent's, and whether Camox reconciles. */
106
+ export declare const codeRequestDataSchema: z.ZodObject<{
107
+ member: z.ZodString;
108
+ operationIds: z.ZodArray<z.ZodString>;
109
+ reconciling: z.ZodOptional<z.ZodBoolean>;
110
+ }, z.core.$strip>;
111
+ export type CodeRequestData = z.infer<typeof codeRequestDataSchema>;
112
+ /** Whether `name` is a tool handing work to an agent of its own (`HANDING_OFF_TOOLS`). */
113
+ export declare const handsOff: (name: string) => boolean;
70
114
  /**
71
115
  * The tools whose calls change a draft: the content tools that write, and the code tools that
72
116
  * write files. The model calls the code tools, and code mode scripts call both. A test in the API
@@ -155,16 +199,12 @@ export declare const pushDataSchema: z.ZodObject<{
155
199
  }>;
156
200
  }, z.core.$strip>;
157
201
  export type PushData = z.infer<typeof pushDataSchema>;
158
- /**
159
- * What an editor's message entry records: who sent it, the operation id it was sent with, and for
160
- * a message sent on Live as the agent handed the request off, the draft it went to with it.
161
- */
202
+ /** What an editor's message entry records: who sent it, and the operation id it was sent with. */
162
203
  export declare const editorMessageDataSchema: z.ZodObject<{
163
204
  id: z.ZodString;
164
205
  name: z.ZodString;
165
206
  image: z.ZodNullable<z.ZodString>;
166
207
  operationId: z.ZodOptional<z.ZodString>;
167
- handedOffTo: z.ZodOptional<z.ZodString>;
168
208
  }, z.core.$strip>;
169
209
  /**
170
210
  * A draft the agent on Live started with `startDraft`, and the operation ids of the messages of
@@ -344,7 +384,35 @@ export type ThreadMessage = {
344
384
  * - `fixing`: the end of the turn's build failed, and the agent fixes it.
345
385
  * - `startingDraft`: on Live, its `startDraft` call runs.
346
386
  */
347
- export type AgentActivity = "thinking" | "exploring" | "changing" | "building" | "checking" | "fixing" | "startingDraft";
387
+ export declare const agentActivitySchema: z.ZodEnum<{
388
+ building: "building";
389
+ changing: "changing";
390
+ checking: "checking";
391
+ exploring: "exploring";
392
+ fixing: "fixing";
393
+ startingDraft: "startingDraft";
394
+ thinking: "thinking";
395
+ }>;
396
+ export type AgentActivity = z.infer<typeof agentActivitySchema>;
397
+ /**
398
+ * What the result of a call handing work off (`handsOff`), such as `artifact` or `code`, carries
399
+ * for the studio: the other agent's answer, whether it's the agent's answer to the turn too,
400
+ * which then ends on it, and while it works, what the other agent is doing.
401
+ */
402
+ export declare const handOffDetailsSchema: z.ZodObject<{
403
+ answer: z.ZodOptional<z.ZodString>;
404
+ final: z.ZodOptional<z.ZodBoolean>;
405
+ activity: z.ZodOptional<z.ZodEnum<{
406
+ building: "building";
407
+ changing: "changing";
408
+ checking: "checking";
409
+ exploring: "exploring";
410
+ fixing: "fixing";
411
+ startingDraft: "startingDraft";
412
+ thinking: "thinking";
413
+ }>>;
414
+ }, z.core.$strip>;
415
+ export type HandOffDetails = z.infer<typeof handOffDetailsSchema>;
348
416
  /** Everything the studio shows for a thread. */
349
417
  export type ThreadView = {
350
418
  messages: readonly ThreadMessage[];
@@ -376,6 +444,11 @@ export type ThreadState = {
376
444
  changing?: boolean;
377
445
  /** Whether the model's latest round calls `look`: from writing the call until it's called again. */
378
446
  checking?: boolean;
447
+ /**
448
+ * What the agent a running call handing work off (`handsOff`) waits for is doing, as it last
449
+ * said.
450
+ */
451
+ handedOffActivity?: AgentActivity;
379
452
  /** The drafts the thread's cards name, as the thread last looked them up. */
380
453
  drafts?: DraftCardStates;
381
454
  };
@@ -387,8 +460,6 @@ type ThreadStep = {
387
460
  type: "editor";
388
461
  sender: ThreadSender;
389
462
  text: string;
390
- /** The draft the message went to, as the agent on Live handed the request off. */
391
- handedOffTo?: string;
392
463
  } | {
393
464
  type: "connectedAgent";
394
465
  member: ThreadSender;
@@ -566,7 +637,7 @@ export declare const editorTextOf: (content: Message["content"]) => string;
566
637
  * run is over, or a later message started another run after its last model call came back
567
638
  * unfinished. While Pi retries a failed call, the run goes on, so the turn shows as working.
568
639
  */
569
- export declare function threadViewOf({ steps, running, toolCalls, callingTools, changing, checking, drafts }: ThreadState): ThreadView;
640
+ export declare function threadViewOf({ steps, running, toolCalls, callingTools, changing, checking, handedOffActivity, drafts }: ThreadState): ThreadView;
570
641
  /** Folds one of Pi's agent events into the thread's state. */
571
642
  export declare function reduceThreadState(state: ThreadState, event: AgentEvent): ThreadState;
572
643
  /** Folds a batch of Pi's agent events into the thread's state. */
package/dist/threads.js CHANGED
@@ -44,6 +44,13 @@ const BRIEF_ENTRY = "camox.brief";
44
44
  */
45
45
  const DRAFT_ENDED_ENTRY = "camox.draft-ended";
46
46
  /**
47
+ * The kind of the transcript entry a Live thread starts afresh from once its agent handed a
48
+ * request off to a draft (ADR 0036): a reset of Pi's context, with a `startedDraftCardSchema` as
49
+ * data. Editors see it as the draft's card, alone, and the model reads a note naming the draft.
50
+ * Everything before it is left out of both.
51
+ */
52
+ const DRAFT_CARD_ENTRY = "camox.draft-card";
53
+ /**
47
54
  * The kind of the transcript entry recording a member's push to the draft's code from their own
48
55
  * checkout (ADR 0026), with a `pushDataSchema` as data. The model reads it as context, never
49
56
  * answering it, and editors see who pushed, their commits, and whether they built.
@@ -65,6 +72,42 @@ const BUILD_TOOL = "build";
65
72
  /** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
66
73
  const LOOK_TOOL = "look";
67
74
  /**
75
+ * The agent's tool handing an artifact's source to its Artifact Agent, which writes and changes
76
+ * it in a conversation of its own and answers the agent (ADR 0035).
77
+ */
78
+ const ARTIFACT_TOOL = "artifact";
79
+ /**
80
+ * The kind of the entry recording a request the agent sent an Artifact Agent, in the Artifact
81
+ * Agent's own conversation, which editors never see: who the run acts for and the operation ids
82
+ * of its messages, as data (`artifactRequestDataSchema`).
83
+ */
84
+ const ARTIFACT_REQUEST_ENTRY = "camox.artifact-request";
85
+ /** What an Artifact Agent's request records: the member the run acts for, and its messages. */
86
+ const artifactRequestDataSchema = z.object({
87
+ member: z.string(),
88
+ operationIds: z.array(z.string())
89
+ });
90
+ /**
91
+ * The agent's tool handing code work to the draft thread's Code Agent, which reads and writes the
92
+ * website's code in a conversation of its own and answers the agent.
93
+ */
94
+ const CODE_TOOL = "code";
95
+ /**
96
+ * The kind of the entry recording a request the agent sent the Code Agent, in the Code Agent's
97
+ * own conversation, which editors never see: who the run acts for, the operation ids of its
98
+ * messages, and whether it's Camox's reconcile turn, as data (`codeRequestDataSchema`).
99
+ */
100
+ const CODE_REQUEST_ENTRY = "camox.code-request";
101
+ /** What a Code Agent's request records: as an Artifact Agent's, and whether Camox reconciles. */
102
+ const codeRequestDataSchema = artifactRequestDataSchema.extend({ reconciling: z.boolean().optional() });
103
+ /**
104
+ * The agent's tools handing work to an agent of its own, in a conversation beside the thread's,
105
+ * whose answer they return: editors see what that agent does as the agent's.
106
+ */
107
+ const HANDING_OFF_TOOLS = /* @__PURE__ */ new Set([ARTIFACT_TOOL, CODE_TOOL]);
108
+ /** Whether `name` is a tool handing work to an agent of its own (`HANDING_OFF_TOOLS`). */
109
+ const handsOff = (name) => HANDING_OFF_TOOLS.has(name);
110
+ /**
68
111
  * The tools whose calls change a draft: the content tools that write, and the code tools that
69
112
  * write files. The model calls the code tools, and code mode scripts call both. A test in the API
70
113
  * keeps this in step with the tools themselves.
@@ -132,14 +175,8 @@ const pushDataSchema = z.object({
132
175
  subjects: z.array(z.string()),
133
176
  outcome: pushOutcomeSchema
134
177
  });
135
- /**
136
- * What an editor's message entry records: who sent it, the operation id it was sent with, and for
137
- * a message sent on Live as the agent handed the request off, the draft it went to with it.
138
- */
139
- const editorMessageDataSchema = threadSenderSchema.extend({
140
- operationId: z.string().optional(),
141
- handedOffTo: z.string().optional()
142
- });
178
+ /** What an editor's message entry records: who sent it, and the operation id it was sent with. */
179
+ const editorMessageDataSchema = threadSenderSchema.extend({ operationId: z.string().optional() });
143
180
  /**
144
181
  * A draft the agent on Live started with `startDraft`, and the operation ids of the messages of
145
182
  * the run that started it: the studio that sent one of them enters the draft, if it still shows
@@ -193,6 +230,37 @@ const touchedBatchSchema = z.object({
193
230
  operationIds: z.array(z.string()),
194
231
  touches: z.array(touchSchema)
195
232
  });
233
+ /**
234
+ * What the agent is doing during a run, as editors see it, without the details of how it works:
235
+ * - `thinking`: the model is called, and hasn't started writing a tool call.
236
+ * - `exploring`: it uses tools that only read, from writing a call until the model is called again.
237
+ * - `changing`: a call of that round changes the draft (`CHANGING_TOOLS`), as soon as it's written
238
+ * or, in a code mode script, as soon as the script calls one.
239
+ * - `building`: its `build` call runs, or the end of the turn builds its code changes.
240
+ * - `checking`: it looks at what it designed, from writing a `look` call until the model is
241
+ * called again.
242
+ * - `fixing`: the end of the turn's build failed, and the agent fixes it.
243
+ * - `startingDraft`: on Live, its `startDraft` call runs.
244
+ */
245
+ const agentActivitySchema = z.enum([
246
+ "thinking",
247
+ "exploring",
248
+ "changing",
249
+ "building",
250
+ "checking",
251
+ "fixing",
252
+ "startingDraft"
253
+ ]);
254
+ /**
255
+ * What the result of a call handing work off (`handsOff`), such as `artifact` or `code`, carries
256
+ * for the studio: the other agent's answer, whether it's the agent's answer to the turn too,
257
+ * which then ends on it, and while it works, what the other agent is doing.
258
+ */
259
+ const handOffDetailsSchema = z.object({
260
+ answer: z.string().optional(),
261
+ final: z.boolean().optional(),
262
+ activity: agentActivitySchema.optional()
263
+ });
196
264
  const EMPTY_THREAD_VIEW = {
197
265
  messages: [],
198
266
  running: false
@@ -307,19 +375,23 @@ function threadStepOf(entry) {
307
375
  ...push
308
376
  };
309
377
  }
378
+ if (entry.kind === "camox.draft-card") {
379
+ const card = startedDraftCardSchema.safeParse(entry.data).data;
380
+ return card && {
381
+ id,
382
+ type: "draft",
383
+ ...card
384
+ };
385
+ }
310
386
  const message = entry.model?.find((candidate) => candidate.role !== "system");
311
387
  if (!message) return void 0;
312
388
  if (message.role === "user") {
313
- if (entry.kind === "camox.editor-message") {
314
- const handedOffTo = editorMessageDataSchema.safeParse(entry.data).data?.handedOffTo;
315
- return sender && {
316
- id,
317
- type: "editor",
318
- sender,
319
- text: editorTextOf(message.content),
320
- ...handedOffTo && { handedOffTo }
321
- };
322
- }
389
+ if (entry.kind === "camox.editor-message") return sender && {
390
+ id,
391
+ type: "editor",
392
+ sender,
393
+ text: editorTextOf(message.content)
394
+ };
323
395
  if (entry.kind === "camox.connected-agent-message") {
324
396
  const data = connectedAgentMessageDataSchema.safeParse(entry.data).data;
325
397
  return data && {
@@ -353,6 +425,14 @@ function threadStepOf(entry) {
353
425
  }
354
426
  if (message.role === "assistant") return assistantStep(id, message);
355
427
  if (message.role === "toolResult") {
428
+ if (handsOff(message.toolName) && !message.isError) {
429
+ const details = handOffDetailsSchema.safeParse(message.details).data;
430
+ if (details?.final && details.answer) return {
431
+ id,
432
+ type: "answer",
433
+ text: details.answer
434
+ };
435
+ }
356
436
  const card = message.toolName === "startDraft" && !message.isError ? startedDraftCardSchema.safeParse(message.details).data : void 0;
357
437
  return card ? {
358
438
  id,
@@ -373,7 +453,7 @@ function threadStepOf(entry) {
373
453
  * run is over, or a later message started another run after its last model call came back
374
454
  * unfinished. While Pi retries a failed call, the run goes on, so the turn shows as working.
375
455
  */
376
- function threadViewOf({ steps, running, toolCalls = [], callingTools = false, changing = false, checking = false, drafts = {} }) {
456
+ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, changing = false, checking = false, handedOffActivity, drafts = {} }) {
377
457
  const messages = [];
378
458
  let waiting;
379
459
  let unfinished = false;
@@ -395,17 +475,7 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
395
475
  endTurn();
396
476
  };
397
477
  for (const step of steps) switch (step.type) {
398
- case "editor": {
399
- const card = step.handedOffTo ? messages.findLastIndex((message) => message.from === "draft" && message.draftId === step.handedOffTo) : -1;
400
- if (card !== -1) {
401
- messages.splice(card, 0, {
402
- id: step.id,
403
- from: "editor",
404
- sender: step.sender,
405
- text: step.text
406
- });
407
- break;
408
- }
478
+ case "editor":
409
479
  if (unfinished) fail();
410
480
  messages.push({
411
481
  id: step.id,
@@ -415,7 +485,6 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
415
485
  });
416
486
  waiting = step.id;
417
487
  break;
418
- }
419
488
  case "connectedAgent":
420
489
  if (unfinished) fail();
421
490
  messages.push({
@@ -496,6 +565,7 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
496
565
  break;
497
566
  case "tools":
498
567
  unfinished = false;
568
+ status = void 0;
499
569
  break;
500
570
  case "unfinished": unfinished = true;
501
571
  }
@@ -512,16 +582,18 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
512
582
  toolCalls,
513
583
  callingTools,
514
584
  changing,
515
- checking
585
+ checking,
586
+ handedOffActivity
516
587
  })
517
588
  };
518
589
  }
519
590
  /** What the agent is doing during a run, from where the turn's build stands and its tool calls. */
520
- function activityOf({ status, toolCalls, callingTools, changing, checking }) {
591
+ function activityOf({ status, toolCalls, callingTools, changing, checking, handedOffActivity }) {
521
592
  const calling = (name) => toolCalls.some((call) => call.name === name);
522
593
  if (status === "building" || calling("build")) return "building";
523
- if (checking) return "checking";
524
594
  if (status === "fixing") return "fixing";
595
+ if (toolCalls.some((call) => handsOff(call.name))) return handedOffActivity ?? "changing";
596
+ if (checking) return "checking";
525
597
  if (calling("startDraft")) return "startingDraft";
526
598
  if (changing) return "changing";
527
599
  if (callingTools || toolCalls.length > 0) return "exploring";
@@ -533,8 +605,22 @@ const changes = (name) => CHANGING_TOOLS.has(name);
533
605
  const callsChangingTool = (block) => block.type === "toolCall" && changes(block.name);
534
606
  /** Whether a block of the model's message is a call of `look`. */
535
607
  const callsLook = (block) => block.type === "toolCall" && block.name === "look";
608
+ /**
609
+ * Adds an entry to the thread's state as Pi places it. An entry heading a new context, such as
610
+ * the card a Live thread starts afresh from, leaves out every step before its head, as Pi's
611
+ * snapshots do.
612
+ */
536
613
  const append = (state, entry) => {
537
- const step = entry && threadStepOf(entry);
614
+ if (!entry) return state;
615
+ const step = threadStepOf(entry);
616
+ if (entry.head !== void 0) {
617
+ const head = Number(entry.head);
618
+ const kept = state.steps.filter((known) => Number(known.id) >= head && known.id !== String(entry.id));
619
+ return {
620
+ ...state,
621
+ steps: step ? [step, ...kept] : kept
622
+ };
623
+ }
538
624
  if (!step || state.steps.some((known) => known.id === step.id)) return state;
539
625
  return {
540
626
  ...state,
@@ -550,9 +636,11 @@ function reduceThreadState(state, event) {
550
636
  name: tool.name
551
637
  }]);
552
638
  const generated = event.generation?.message?.content ?? [];
639
+ const handingOff = event.tools.find((tool) => handsOff(tool.name) && tool.status !== "done");
553
640
  return {
554
641
  ...state,
555
642
  steps: event.entries.flatMap((entry) => threadStepOf(entry) ?? []),
643
+ handedOffActivity: handOffDetailsSchema.safeParse(handingOff?.details).data?.activity,
556
644
  running: event.run !== void 0,
557
645
  toolCalls,
558
646
  callingTools: generated.some((part) => part.type === "toolCall"),
@@ -570,7 +658,8 @@ function reduceThreadState(state, event) {
570
658
  toolCalls: [],
571
659
  callingTools: false,
572
660
  changing: false,
573
- checking: false
661
+ checking: false,
662
+ handedOffActivity: void 0
574
663
  };
575
664
  case "message_start": return event.message.role === "assistant" ? {
576
665
  ...state,
@@ -604,9 +693,18 @@ function reduceThreadState(state, event) {
604
693
  }]
605
694
  };
606
695
  }
696
+ case "tool_execution_update": {
697
+ if (!handsOff(event.toolName) || event.parentToolCallId !== void 0) return state;
698
+ const activity = handOffDetailsSchema.safeParse(event.details).data?.activity;
699
+ return activity ? {
700
+ ...state,
701
+ handedOffActivity: activity
702
+ } : state;
703
+ }
607
704
  case "tool_execution_end": return append({
608
705
  ...state,
609
- toolCalls: state.toolCalls?.filter((call) => call.callId !== event.toolCallId)
706
+ toolCalls: state.toolCalls?.filter((call) => call.callId !== event.toolCallId),
707
+ ...handsOff(event.toolName) && event.parentToolCallId === void 0 && { handedOffActivity: void 0 }
610
708
  }, event.entry);
611
709
  case "message_end":
612
710
  case "entry_appended": return append(state, event.entry);
@@ -628,4 +726,4 @@ function reduceThreadFrame(state, frame) {
628
726
  }
629
727
 
630
728
  //#endregion
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 };
729
+ export { ARTIFACT_REQUEST_ENTRY, ARTIFACT_TOOL, BRIEF_ENTRY, BUILD_STATUS_ENTRY, BUILD_TOOL, CHANGING_TOOLS, CODE_REQUEST_ENTRY, CODE_TOOL, CONNECTED_AGENT_MESSAGE_ENTRY, DRAFT_CARD_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, agentActivitySchema, artifactRequestDataSchema, buildStatusDataSchema, buildStatusSchema, codeRequestDataSchema, connectedAgentClientSchema, connectedAgentMessageDataSchema, editorMessageDataSchema, editorSelectionSchema, editorTextOf, editorWhereSchema, handOffDetailsSchema, handsOff, 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.65.1",
3
+ "version": "0.67.0",
4
4
  "license": "MIT",
5
5
  "files": [
6
6
  "src",
package/src/drafts.ts CHANGED
@@ -58,6 +58,21 @@ export const LOOK_BLOCK_DEFINITION_PARAM = "blockDefinition";
58
58
  export const LOOK_CONTENT_PARAM = "content";
59
59
  export const LOOK_SETTINGS_PARAM = "settings";
60
60
 
61
+ /**
62
+ * The look document's Layout Definition mode: instead of a page, the curated Layout
63
+ * `LOOK_LAYOUT_DEFINITION_PARAM` names, alone in the site's shell, its blocks with their default
64
+ * content and Block Settings and the overrides `LOOK_BLOCKS_PARAM` gives as JSON, by Block
65
+ * Definition name (`{ navbar: { content, settings } }`), around a labelled placeholder where a
66
+ * page's content goes, as tall as `LOOK_PLACEHOLDER_PARAM` says: one of `LOOK_PLACEHOLDER_HEIGHTS`,
67
+ * the first without.
68
+ */
69
+ export const LOOK_LAYOUT_DEFINITION_PARAM = "layoutDefinition";
70
+ export const LOOK_BLOCKS_PARAM = "blocks";
71
+ export const LOOK_PLACEHOLDER_PARAM = "placeholder";
72
+ /** A short placeholder shows whether the footer rises up the screen; a tall one, scrolling. */
73
+ export const LOOK_PLACEHOLDER_HEIGHTS = ["short", "tall"] as const;
74
+ export type LookPlaceholderHeight = (typeof LOOK_PLACEHOLDER_HEIGHTS)[number];
75
+
61
76
  /**
62
77
  * The id of a block shown without being placed, as the look document's Block Definition mode
63
78
  * shows it: never a stored block's, which are UUIDs. Its element is
package/src/patrol.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  /** Every patroller's stable id. The registry in the API lists them all. */
8
- export const PATROLLER_IDS = ["translator", "seoManager", "linkChecker", "proofreader"] as const;
8
+ export const PATROLLER_IDS = ["translator", "seoManager", "linkChecker"] as const;
9
9
 
10
10
  /** A patroller's stable id. */
11
11
  export type PatrollerId = (typeof PATROLLER_IDS)[number];
@@ -21,8 +21,7 @@ export type ReportVerb =
21
21
  | "describedFile"
22
22
  | "publishedCatchUp"
23
23
  | "removedLink"
24
- | "fixedLink"
25
- | "fixedMistake";
24
+ | "fixedLink";
26
25
 
27
26
  /** What a report can be about. */
28
27
  export type ReportEntity =
@@ -92,14 +91,12 @@ export type ReportView = {
92
91
  * What a finding is about (ADR 0034), by its kind:
93
92
  * - `missingPage`: a link to a page that doesn't exist, the page's name as it was last known.
94
93
  * - `brokenLink`: an external link that failed, and how.
95
- * - `misspelling`, `grammar`: the flagged text, in the sentence it's in.
96
94
  * A link's `url` is as content stores it: a page link's is `camox:page:<id>`, and `text` is what
97
95
  * the link reads.
98
96
  */
99
97
  export type FindingAbout =
100
98
  | { kind: "missingPage"; url: string; text: string; page: string | null }
101
- | { kind: "brokenLink"; url: string; text: string; failure: LinkFailure }
102
- | { kind: "misspelling" | "grammar"; text: string; sentence: string };
99
+ | { kind: "brokenLink"; url: string; text: string; failure: LinkFailure };
103
100
 
104
101
  /** How a link failed: an HTTP error status, no connection (DNS included), or no answer in time. */
105
102
  export type LinkFailure =
@@ -109,11 +106,8 @@ export type LinkFailure =
109
106
 
110
107
  export type FindingKind = FindingAbout["kind"];
111
108
 
112
- /**
113
- * The fix a finding suggests, which applying makes: removing the link, keeping its text, or
114
- * replacing the flagged text with `replacement` in its sentence, keeping its formatting.
115
- */
116
- export type FindingFix = { kind: "removeLink" } | { kind: "replaceText"; replacement: string };
109
+ /** The fix a finding suggests, which applying makes: removing the link, keeping its text. */
110
+ export type FindingFix = { kind: "removeLink" };
117
111
 
118
112
  /** A finding, open, in the scope viewed. */
119
113
  export type FindingView = {
@@ -128,6 +122,8 @@ export type FindingView = {
128
122
  locale: string;
129
123
  /** The content field it's in, by name. */
130
124
  field: string;
125
+ /** That field's kind, such as "String" or "Link", or null when unknown. */
126
+ fieldKind: string | null;
131
127
  /** When it was first raised, in milliseconds since the epoch. */
132
128
  raisedAt: number;
133
129
  /** Whether it's Live's. Live's are fixed in the Patrol Draft. */
@@ -153,10 +149,6 @@ function aboutSentence(about: FindingAbout): string {
153
149
  : "can't be reached";
154
150
  return `links to ${about.url}, which ${how}`;
155
151
  }
156
- case "misspelling":
157
- return "may be misspelled";
158
- case "grammar":
159
- return "may be a grammar mistake";
160
152
  }
161
153
  }
162
154
 
@@ -168,6 +160,14 @@ export function findingSentence(finding: Pick<FindingView, "about" | "subject">)
168
160
  return `“${finding.about.text}” in ${finding.subject.name} ${aboutSentence(finding.about)}`;
169
161
  }
170
162
 
163
+ /**
164
+ * A finding as one sentence where its content is already shown, as the studio's sidebar shows it
165
+ * by its field: "“About us” links to About, a page that no longer exists".
166
+ */
167
+ export function findingSentenceInPlace(finding: Pick<FindingView, "about">): string {
168
+ return `“${finding.about.text}” ${aboutSentence(finding.about)}`;
169
+ }
170
+
171
171
  /** A member's dismissal of what a finding was about: no finding like it is raised again. */
172
172
  export type DismissalView = {
173
173
  id: number;
package/src/threads.ts CHANGED
@@ -57,6 +57,14 @@ export const BRIEF_ENTRY = "camox.brief";
57
57
  */
58
58
  export const DRAFT_ENDED_ENTRY = "camox.draft-ended";
59
59
 
60
+ /**
61
+ * The kind of the transcript entry a Live thread starts afresh from once its agent handed a
62
+ * request off to a draft (ADR 0036): a reset of Pi's context, with a `startedDraftCardSchema` as
63
+ * data. Editors see it as the draft's card, alone, and the model reads a note naming the draft.
64
+ * Everything before it is left out of both.
65
+ */
66
+ export const DRAFT_CARD_ENTRY = "camox.draft-card";
67
+
60
68
  /**
61
69
  * The kind of the transcript entry recording a member's push to the draft's code from their own
62
70
  * checkout (ADR 0026), with a `pushDataSchema` as data. The model reads it as context, never
@@ -80,6 +88,54 @@ export const BUILD_TOOL = "build";
80
88
  /** The agent's tool rendering the draft in a browser and returning screenshots to the model. */
81
89
  export const LOOK_TOOL = "look";
82
90
 
91
+ /**
92
+ * The agent's tool handing an artifact's source to its Artifact Agent, which writes and changes
93
+ * it in a conversation of its own and answers the agent (ADR 0035).
94
+ */
95
+ export const ARTIFACT_TOOL = "artifact";
96
+
97
+ /**
98
+ * The kind of the entry recording a request the agent sent an Artifact Agent, in the Artifact
99
+ * Agent's own conversation, which editors never see: who the run acts for and the operation ids
100
+ * of its messages, as data (`artifactRequestDataSchema`).
101
+ */
102
+ export const ARTIFACT_REQUEST_ENTRY = "camox.artifact-request";
103
+
104
+ /** What an Artifact Agent's request records: the member the run acts for, and its messages. */
105
+ export const artifactRequestDataSchema = z.object({
106
+ member: z.string(),
107
+ operationIds: z.array(z.string()),
108
+ });
109
+ export type ArtifactRequestData = z.infer<typeof artifactRequestDataSchema>;
110
+
111
+ /**
112
+ * The agent's tool handing code work to the draft thread's Code Agent, which reads and writes the
113
+ * website's code in a conversation of its own and answers the agent.
114
+ */
115
+ export const CODE_TOOL = "code";
116
+
117
+ /**
118
+ * The kind of the entry recording a request the agent sent the Code Agent, in the Code Agent's
119
+ * own conversation, which editors never see: who the run acts for, the operation ids of its
120
+ * messages, and whether it's Camox's reconcile turn, as data (`codeRequestDataSchema`).
121
+ */
122
+ export const CODE_REQUEST_ENTRY = "camox.code-request";
123
+
124
+ /** What a Code Agent's request records: as an Artifact Agent's, and whether Camox reconciles. */
125
+ export const codeRequestDataSchema = artifactRequestDataSchema.extend({
126
+ reconciling: z.boolean().optional(),
127
+ });
128
+ export type CodeRequestData = z.infer<typeof codeRequestDataSchema>;
129
+
130
+ /**
131
+ * The agent's tools handing work to an agent of its own, in a conversation beside the thread's,
132
+ * whose answer they return: editors see what that agent does as the agent's.
133
+ */
134
+ const HANDING_OFF_TOOLS: ReadonlySet<string> = new Set([ARTIFACT_TOOL, CODE_TOOL]);
135
+
136
+ /** Whether `name` is a tool handing work to an agent of its own (`HANDING_OFF_TOOLS`). */
137
+ export const handsOff = (name: string) => HANDING_OFF_TOOLS.has(name);
138
+
83
139
  /**
84
140
  * The tools whose calls change a draft: the content tools that write, and the code tools that
85
141
  * write files. The model calls the code tools, and code mode scripts call both. A test in the API
@@ -172,13 +228,9 @@ export const pushDataSchema = z.object({
172
228
  });
173
229
  export type PushData = z.infer<typeof pushDataSchema>;
174
230
 
175
- /**
176
- * What an editor's message entry records: who sent it, the operation id it was sent with, and for
177
- * a message sent on Live as the agent handed the request off, the draft it went to with it.
178
- */
231
+ /** What an editor's message entry records: who sent it, and the operation id it was sent with. */
179
232
  export const editorMessageDataSchema = threadSenderSchema.extend({
180
233
  operationId: z.string().optional(),
181
- handedOffTo: z.string().optional(),
182
234
  });
183
235
 
184
236
  /**
@@ -284,14 +336,28 @@ export type ThreadMessage =
284
336
  * - `fixing`: the end of the turn's build failed, and the agent fixes it.
285
337
  * - `startingDraft`: on Live, its `startDraft` call runs.
286
338
  */
287
- export type AgentActivity =
288
- | "thinking"
289
- | "exploring"
290
- | "changing"
291
- | "building"
292
- | "checking"
293
- | "fixing"
294
- | "startingDraft";
339
+ export const agentActivitySchema = z.enum([
340
+ "thinking",
341
+ "exploring",
342
+ "changing",
343
+ "building",
344
+ "checking",
345
+ "fixing",
346
+ "startingDraft",
347
+ ]);
348
+ export type AgentActivity = z.infer<typeof agentActivitySchema>;
349
+
350
+ /**
351
+ * What the result of a call handing work off (`handsOff`), such as `artifact` or `code`, carries
352
+ * for the studio: the other agent's answer, whether it's the agent's answer to the turn too,
353
+ * which then ends on it, and while it works, what the other agent is doing.
354
+ */
355
+ export const handOffDetailsSchema = z.object({
356
+ answer: z.string().optional(),
357
+ final: z.boolean().optional(),
358
+ activity: agentActivitySchema.optional(),
359
+ });
360
+ export type HandOffDetails = z.infer<typeof handOffDetailsSchema>;
295
361
 
296
362
  /** Everything the studio shows for a thread. */
297
363
  export type ThreadView = {
@@ -323,6 +389,11 @@ export type ThreadState = {
323
389
  changing?: boolean;
324
390
  /** Whether the model's latest round calls `look`: from writing the call until it's called again. */
325
391
  checking?: boolean;
392
+ /**
393
+ * What the agent a running call handing work off (`handsOff`) waits for is doing, as it last
394
+ * said.
395
+ */
396
+ handedOffActivity?: AgentActivity;
326
397
  /** The drafts the thread's cards name, as the thread last looked them up. */
327
398
  drafts?: DraftCardStates;
328
399
  };
@@ -331,13 +402,7 @@ export const EMPTY_THREAD_STATE: ThreadState = { steps: [], running: false };
331
402
 
332
403
  /** One transcript entry, as far as the studio's view is concerned. */
333
404
  type ThreadStep = { id: string } & (
334
- | {
335
- type: "editor";
336
- sender: ThreadSender;
337
- text: string;
338
- /** The draft the message went to, as the agent on Live handed the request off. */
339
- handedOffTo?: string;
340
- }
405
+ | { type: "editor"; sender: ThreadSender; text: string }
341
406
  | { type: "connectedAgent"; member: ThreadSender; client: ConnectedAgentClient; text: string }
342
407
  /** Camox starting a turn of its own, saying why. */
343
408
  | { type: "camox"; text: string }
@@ -495,20 +560,16 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
495
560
  const push = pushDataSchema.safeParse(entry.data).data;
496
561
  return push && { id, type: "push", ...push };
497
562
  }
563
+ // The model reads a note: editors see the card it starts the Live thread afresh with.
564
+ if (entry.kind === DRAFT_CARD_ENTRY) {
565
+ const card = startedDraftCardSchema.safeParse(entry.data).data;
566
+ return card && { id, type: "draft", ...card };
567
+ }
498
568
  const message = entry.model?.find((candidate) => candidate.role !== "system");
499
569
  if (!message) return undefined;
500
570
  if (message.role === "user") {
501
571
  if (entry.kind === EDITOR_MESSAGE_ENTRY) {
502
- const handedOffTo = editorMessageDataSchema.safeParse(entry.data).data?.handedOffTo;
503
- return (
504
- sender && {
505
- id,
506
- type: "editor",
507
- sender,
508
- text: editorTextOf(message.content),
509
- ...(handedOffTo && { handedOffTo }),
510
- }
511
- );
572
+ return sender && { id, type: "editor", sender, text: editorTextOf(message.content) };
512
573
  }
513
574
  if (entry.kind === CONNECTED_AGENT_MESSAGE_ENTRY) {
514
575
  const data = connectedAgentMessageDataSchema.safeParse(entry.data).data;
@@ -533,6 +594,12 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
533
594
  }
534
595
  if (message.role === "assistant") return assistantStep(id, message);
535
596
  if (message.role === "toolResult") {
597
+ // An Artifact Agent's or the Code Agent's answer that ends the turn is the agent's: editors
598
+ // never see it apart.
599
+ if (handsOff(message.toolName) && !message.isError) {
600
+ const details = handOffDetailsSchema.safeParse(message.details).data;
601
+ if (details?.final && details.answer) return { id, type: "answer", text: details.answer };
602
+ }
536
603
  const card =
537
604
  message.toolName === START_DRAFT_TOOL && !message.isError
538
605
  ? startedDraftCardSchema.safeParse(message.details).data
@@ -558,6 +625,7 @@ export function threadViewOf({
558
625
  callingTools = false,
559
626
  changing = false,
560
627
  checking = false,
628
+ handedOffActivity,
561
629
  drafts = {},
562
630
  }: ThreadState): ThreadView {
563
631
  const messages: ThreadMessage[] = [];
@@ -583,24 +651,7 @@ export function threadViewOf({
583
651
 
584
652
  for (const step of steps) {
585
653
  switch (step.type) {
586
- case "editor": {
587
- // A message sent as the agent on Live handed the request off went to the draft with it:
588
- // it belongs to the turn ending on that draft's card, which it shows just before. Pi
589
- // placed it after the card, which leaves the transcript the model read as it was.
590
- const card = step.handedOffTo
591
- ? messages.findLastIndex(
592
- (message) => message.from === "draft" && message.draftId === step.handedOffTo,
593
- )
594
- : -1;
595
- if (card !== -1) {
596
- messages.splice(card, 0, {
597
- id: step.id,
598
- from: "editor",
599
- sender: step.sender,
600
- text: step.text,
601
- });
602
- break;
603
- }
654
+ case "editor":
604
655
  // Pi places a steering message only once a model call is settled: after a round of tool
605
656
  // calls, or with the answer ending the run. Never between a failed call and its retry, so
606
657
  // an editor's message after an unfinished call means that call's turn is over.
@@ -608,7 +659,6 @@ export function threadViewOf({
608
659
  messages.push({ id: step.id, from: "editor", sender: step.sender, text: step.text });
609
660
  waiting = step.id;
610
661
  break;
611
- }
612
662
  case "connectedAgent":
613
663
  // Like an editor's message: it starts a turn, or steers the one going.
614
664
  if (unfinished) fail();
@@ -680,6 +730,8 @@ export function threadViewOf({
680
730
  break;
681
731
  case "tools":
682
732
  unfinished = false;
733
+ // The Code Agent's build status ends with its request, whose answer is a tool result.
734
+ status = undefined;
683
735
  break;
684
736
  case "unfinished":
685
737
  unfinished = true;
@@ -691,7 +743,14 @@ export function threadViewOf({
691
743
  return {
692
744
  messages,
693
745
  running,
694
- activity: activityOf({ status, toolCalls, callingTools, changing, checking }),
746
+ activity: activityOf({
747
+ status,
748
+ toolCalls,
749
+ callingTools,
750
+ changing,
751
+ checking,
752
+ handedOffActivity,
753
+ }),
695
754
  };
696
755
  }
697
756
 
@@ -702,19 +761,24 @@ function activityOf({
702
761
  callingTools,
703
762
  changing,
704
763
  checking,
764
+ handedOffActivity,
705
765
  }: {
706
766
  status: BuildStatus | undefined;
707
767
  toolCalls: NonNullable<ThreadState["toolCalls"]>;
708
768
  callingTools: boolean;
709
769
  changing: boolean;
710
770
  checking: boolean;
771
+ handedOffActivity: AgentActivity | undefined;
711
772
  }): AgentActivity {
712
773
  const calling = (name: string) => toolCalls.some((call) => call.name === name);
713
774
  // Fixing a build error takes thinking, reading and writing, which all read as fixing, but its
714
775
  // builds show as building.
715
776
  if (status === "building" || calling(BUILD_TOOL)) return "building";
716
- if (checking) return "checking";
717
777
  if (status === "fixing") return "fixing";
778
+ // An Artifact Agent or the Code Agent works on the agent's behalf: editors see what it does as
779
+ // the agent's.
780
+ if (toolCalls.some((call) => handsOff(call.name))) return handedOffActivity ?? "changing";
781
+ if (checking) return "checking";
718
782
  if (calling(START_DRAFT_TOOL)) return "startingDraft";
719
783
  if (changing) return "changing";
720
784
  if (callingTools || toolCalls.length > 0) return "exploring";
@@ -732,8 +796,21 @@ const callsChangingTool = (block: AssistantMessage["content"][number]) =>
732
796
  const callsLook = (block: AssistantMessage["content"][number]) =>
733
797
  block.type === "toolCall" && block.name === LOOK_TOOL;
734
798
 
799
+ /**
800
+ * Adds an entry to the thread's state as Pi places it. An entry heading a new context, such as
801
+ * the card a Live thread starts afresh from, leaves out every step before its head, as Pi's
802
+ * snapshots do.
803
+ */
735
804
  const append = (state: ThreadState, entry: EntryRecord | undefined): ThreadState => {
736
- const step = entry && threadStepOf(entry);
805
+ if (!entry) return state;
806
+ const step = threadStepOf(entry);
807
+ if (entry.head !== undefined) {
808
+ const head = Number(entry.head);
809
+ const kept = state.steps.filter(
810
+ (known) => Number(known.id) >= head && known.id !== String(entry.id),
811
+ );
812
+ return { ...state, steps: step ? [step, ...kept] : kept };
813
+ }
737
814
  if (!step || state.steps.some((known) => known.id === step.id)) return state;
738
815
  return { ...state, steps: [...state.steps, step] };
739
816
  };
@@ -746,9 +823,11 @@ export function reduceThreadState(state: ThreadState, event: AgentEvent): Thread
746
823
  tool.status === "done" ? [] : [{ callId: tool.callId, name: tool.name }],
747
824
  );
748
825
  const generated = event.generation?.message?.content ?? [];
826
+ const handingOff = event.tools.find((tool) => handsOff(tool.name) && tool.status !== "done");
749
827
  return {
750
828
  ...state,
751
829
  steps: event.entries.flatMap((entry) => threadStepOf(entry) ?? []),
830
+ handedOffActivity: handOffDetailsSchema.safeParse(handingOff?.details).data?.activity,
752
831
  running: event.run !== undefined,
753
832
  toolCalls,
754
833
  callingTools: generated.some((part) => part.type === "toolCall"),
@@ -778,6 +857,7 @@ export function reduceThreadState(state: ThreadState, event: AgentEvent): Thread
778
857
  callingTools: false,
779
858
  changing: false,
780
859
  checking: false,
860
+ handedOffActivity: undefined,
781
861
  };
782
862
  case "message_start":
783
863
  // The model is called again: it thinks until it writes a tool call.
@@ -811,11 +891,18 @@ export function reduceThreadState(state: ThreadState, event: AgentEvent): Thread
811
891
  toolCalls: [...(state.toolCalls ?? []), { callId: event.toolCallId, name: event.toolName }],
812
892
  };
813
893
  }
894
+ case "tool_execution_update": {
895
+ if (!handsOff(event.toolName) || event.parentToolCallId !== undefined) return state;
896
+ const activity = handOffDetailsSchema.safeParse(event.details).data?.activity;
897
+ return activity ? { ...state, handedOffActivity: activity } : state;
898
+ }
814
899
  case "tool_execution_end":
815
900
  return append(
816
901
  {
817
902
  ...state,
818
903
  toolCalls: state.toolCalls?.filter((call) => call.callId !== event.toolCallId),
904
+ ...(handsOff(event.toolName) &&
905
+ event.parentToolCallId === undefined && { handedOffActivity: undefined }),
819
906
  },
820
907
  event.entry,
821
908
  );