@camox/api-contract 0.71.0 → 0.72.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 +5 -5
- package/dist/drafts.js +5 -5
- package/dist/index.d.ts +23 -0
- package/dist/index.js +46 -1
- package/dist/router.d.ts +8 -77
- package/dist/threads.d.ts +18 -4
- package/dist/threads.js +113 -103
- package/package.json +1 -1
- package/src/drafts.ts +5 -5
- package/src/empty-values.ts +52 -0
- package/src/index.ts +1 -0
- package/src/threads.ts +31 -11
package/dist/drafts.d.ts
CHANGED
|
@@ -42,17 +42,17 @@ export declare const LOOK_PATH_PARAM = "path";
|
|
|
42
42
|
export declare const LOOK_LOCALE_PARAM = "locale";
|
|
43
43
|
/**
|
|
44
44
|
* The look document's Block Definition mode: instead of a page, the block of the Block Definition
|
|
45
|
-
* `LOOK_BLOCK_DEFINITION_PARAM` names, alone in the site's shell, with
|
|
46
|
-
*
|
|
47
|
-
* the shape the content tools take.
|
|
45
|
+
* `LOOK_BLOCK_DEFINITION_PARAM` names, alone in the site's shell, with empty fields and the
|
|
46
|
+
* content and Block Settings `LOOK_CONTENT_PARAM` and `LOOK_SETTINGS_PARAM` give as JSON merged
|
|
47
|
+
* on, in the shape the content tools take.
|
|
48
48
|
*/
|
|
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
52
|
/**
|
|
53
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
|
|
55
|
-
* content and Block Settings
|
|
54
|
+
* `LOOK_LAYOUT_DEFINITION_PARAM` names, alone in the site's shell, its blocks with empty fields
|
|
55
|
+
* and the content and Block Settings `LOOK_BLOCKS_PARAM` gives as JSON merged on, by Block
|
|
56
56
|
* Definition name (`{ navbar: { content, settings } }`), around a labelled placeholder where a
|
|
57
57
|
* page's content goes, as tall as `LOOK_PLACEHOLDER_PARAM` says: one of `LOOK_PLACEHOLDER_HEIGHTS`,
|
|
58
58
|
* the first without.
|
package/dist/drafts.js
CHANGED
|
@@ -42,17 +42,17 @@ const LOOK_PATH_PARAM = "path";
|
|
|
42
42
|
const LOOK_LOCALE_PARAM = "locale";
|
|
43
43
|
/**
|
|
44
44
|
* The look document's Block Definition mode: instead of a page, the block of the Block Definition
|
|
45
|
-
* `LOOK_BLOCK_DEFINITION_PARAM` names, alone in the site's shell, with
|
|
46
|
-
*
|
|
47
|
-
* the shape the content tools take.
|
|
45
|
+
* `LOOK_BLOCK_DEFINITION_PARAM` names, alone in the site's shell, with empty fields and the
|
|
46
|
+
* content and Block Settings `LOOK_CONTENT_PARAM` and `LOOK_SETTINGS_PARAM` give as JSON merged
|
|
47
|
+
* on, in the shape the content tools take.
|
|
48
48
|
*/
|
|
49
49
|
const LOOK_BLOCK_DEFINITION_PARAM = "blockDefinition";
|
|
50
50
|
const LOOK_CONTENT_PARAM = "content";
|
|
51
51
|
const LOOK_SETTINGS_PARAM = "settings";
|
|
52
52
|
/**
|
|
53
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
|
|
55
|
-
* content and Block Settings
|
|
54
|
+
* `LOOK_LAYOUT_DEFINITION_PARAM` names, alone in the site's shell, its blocks with empty fields
|
|
55
|
+
* and the content and Block Settings `LOOK_BLOCKS_PARAM` gives as JSON merged on, by Block
|
|
56
56
|
* Definition name (`{ navbar: { content, settings } }`), around a labelled placeholder where a
|
|
57
57
|
* page's content goes, as tall as `LOOK_PLACEHOLDER_PARAM` says: one of `LOOK_PLACEHOLDER_HEIGHTS`,
|
|
58
58
|
* the first without.
|
package/dist/index.d.ts
CHANGED
|
@@ -17,6 +17,29 @@ export declare function artifactTitle(source: string): string;
|
|
|
17
17
|
*/
|
|
18
18
|
export declare function artifactSourceHash(source: string): Promise<string>;
|
|
19
19
|
//#endregion
|
|
20
|
+
//#region src/empty-values.d.ts
|
|
21
|
+
/**
|
|
22
|
+
* What a field holds before anyone writes it: the empty value of its kind. Fields declare no
|
|
23
|
+
* default, so content missing a field, or holding a value of the wrong kind, reads as this.
|
|
24
|
+
* Shared by the SDK (rendering, new repeatable items, authoring forms) and the API (creating
|
|
25
|
+
* content, conforming it on read), so both agree on what an unset field is.
|
|
26
|
+
*/
|
|
27
|
+
/** A link that goes nowhere yet. */
|
|
28
|
+
export declare const EMPTY_LINK: {
|
|
29
|
+
readonly type: "external";
|
|
30
|
+
readonly text: "";
|
|
31
|
+
readonly href: "";
|
|
32
|
+
readonly newTab: false;
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* The empty value of a field, from its stored JSON Schema (`fieldType` and, for an enum, its
|
|
36
|
+
* options): `""` for text, a link to nowhere, `false`, an enum's first option, `null` for one
|
|
37
|
+
* asset or record, and `[]` for lists and repeaters. A fresh copy each call.
|
|
38
|
+
*/
|
|
39
|
+
export declare function emptyFieldValue(fieldSchema: unknown): unknown;
|
|
40
|
+
/** The empty value of every field of a content or settings schema, by name. */
|
|
41
|
+
export declare function emptyFieldValues(schema: unknown): Record<string, unknown>;
|
|
42
|
+
//#endregion
|
|
20
43
|
//#region src/record-templates.d.ts
|
|
21
44
|
/**
|
|
22
45
|
* A collection's `toTitle` and `toDescription` are record templates: text with `{{field}}` tokens
|
package/dist/index.js
CHANGED
|
@@ -43,6 +43,51 @@ async function artifactSourceHash(source) {
|
|
|
43
43
|
return btoa(binary).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
//#endregion
|
|
47
|
+
//#region src/empty-values.ts
|
|
48
|
+
/**
|
|
49
|
+
* What a field holds before anyone writes it: the empty value of its kind. Fields declare no
|
|
50
|
+
* default, so content missing a field, or holding a value of the wrong kind, reads as this.
|
|
51
|
+
* Shared by the SDK (rendering, new repeatable items, authoring forms) and the API (creating
|
|
52
|
+
* content, conforming it on read), so both agree on what an unset field is.
|
|
53
|
+
*/
|
|
54
|
+
/** A link that goes nowhere yet. */
|
|
55
|
+
const EMPTY_LINK = {
|
|
56
|
+
type: "external",
|
|
57
|
+
text: "",
|
|
58
|
+
href: "",
|
|
59
|
+
newTab: false
|
|
60
|
+
};
|
|
61
|
+
function isObject(value) {
|
|
62
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* The empty value of a field, from its stored JSON Schema (`fieldType` and, for an enum, its
|
|
66
|
+
* options): `""` for text, a link to nowhere, `false`, an enum's first option, `null` for one
|
|
67
|
+
* asset or record, and `[]` for lists and repeaters. A fresh copy each call.
|
|
68
|
+
*/
|
|
69
|
+
function emptyFieldValue(fieldSchema) {
|
|
70
|
+
const schema = isObject(fieldSchema) ? fieldSchema : {};
|
|
71
|
+
switch (schema.fieldType) {
|
|
72
|
+
case "Link": return { ...EMPTY_LINK };
|
|
73
|
+
case "Boolean": return false;
|
|
74
|
+
case "Enum": return (Array.isArray(schema.enum) ? schema.enum : []).find((option) => typeof option === "string") ?? "";
|
|
75
|
+
case "Image":
|
|
76
|
+
case "File":
|
|
77
|
+
case "Reference": return null;
|
|
78
|
+
case "ImageList":
|
|
79
|
+
case "FileList":
|
|
80
|
+
case "ReferenceList":
|
|
81
|
+
case "Repeater": return [];
|
|
82
|
+
default: return "";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** The empty value of every field of a content or settings schema, by name. */
|
|
86
|
+
function emptyFieldValues(schema) {
|
|
87
|
+
const properties = isObject(schema) && isObject(schema.properties) ? schema.properties : {};
|
|
88
|
+
return Object.fromEntries(Object.entries(properties).map(([name, field]) => [name, emptyFieldValue(field)]));
|
|
89
|
+
}
|
|
90
|
+
|
|
46
91
|
//#endregion
|
|
47
92
|
//#region src/record-templates.ts
|
|
48
93
|
/**
|
|
@@ -144,4 +189,4 @@ function isSettingCondition(value) {
|
|
|
144
189
|
}
|
|
145
190
|
|
|
146
191
|
//#endregion
|
|
147
|
-
export { ARTICLE_TYPES, ICON_CATALOG_VERSION, PAGE_TEXT_LINK_PREFIX, QUERY_LIMIT_CAP, SYSTEM_ORDER_KEYS, UNTITLED_ARTIFACT, artifactSourceHash, artifactSourceTitle, artifactTitle, generateUuidv7, isPageUrlMarker, isQueryBacked, isSettingCondition, isSystemOrderKey, linkedRecordId, nonStringTemplateFields, projectIdSchema, recordLink, recordText, referenceQueryProblem, scopeIconSvgIds, templateFields, uuidv7JsonSchema, uuidv7Schema, validateIconValue };
|
|
192
|
+
export { ARTICLE_TYPES, EMPTY_LINK, ICON_CATALOG_VERSION, PAGE_TEXT_LINK_PREFIX, QUERY_LIMIT_CAP, SYSTEM_ORDER_KEYS, UNTITLED_ARTIFACT, artifactSourceHash, artifactSourceTitle, artifactTitle, emptyFieldValue, emptyFieldValues, generateUuidv7, isPageUrlMarker, isQueryBacked, isSettingCondition, isSystemOrderKey, linkedRecordId, nonStringTemplateFields, projectIdSchema, recordLink, recordText, referenceQueryProblem, scopeIconSvgIds, templateFields, uuidv7JsonSchema, uuidv7Schema, validateIconValue };
|
package/dist/router.d.ts
CHANGED
|
@@ -4728,40 +4728,6 @@ declare const schema: {
|
|
|
4728
4728
|
identity: undefined;
|
|
4729
4729
|
generated: undefined;
|
|
4730
4730
|
}, {}, {}>;
|
|
4731
|
-
defaultContent: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
4732
|
-
name: "default_content";
|
|
4733
|
-
tableName: "block_definitions";
|
|
4734
|
-
dataType: "json";
|
|
4735
|
-
columnType: "SQLiteTextJson";
|
|
4736
|
-
data: unknown;
|
|
4737
|
-
driverParam: string;
|
|
4738
|
-
notNull: false;
|
|
4739
|
-
hasDefault: false;
|
|
4740
|
-
isPrimaryKey: false;
|
|
4741
|
-
isAutoincrement: false;
|
|
4742
|
-
hasRuntimeDefault: false;
|
|
4743
|
-
enumValues: undefined;
|
|
4744
|
-
baseColumn: never;
|
|
4745
|
-
identity: undefined;
|
|
4746
|
-
generated: undefined;
|
|
4747
|
-
}, {}, {}>;
|
|
4748
|
-
defaultSettings: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
4749
|
-
name: "default_settings";
|
|
4750
|
-
tableName: "block_definitions";
|
|
4751
|
-
dataType: "json";
|
|
4752
|
-
columnType: "SQLiteTextJson";
|
|
4753
|
-
data: unknown;
|
|
4754
|
-
driverParam: string;
|
|
4755
|
-
notNull: false;
|
|
4756
|
-
hasDefault: false;
|
|
4757
|
-
isPrimaryKey: false;
|
|
4758
|
-
isAutoincrement: false;
|
|
4759
|
-
hasRuntimeDefault: false;
|
|
4760
|
-
enumValues: undefined;
|
|
4761
|
-
baseColumn: never;
|
|
4762
|
-
identity: undefined;
|
|
4763
|
-
generated: undefined;
|
|
4764
|
-
}, {}, {}>;
|
|
4765
4731
|
layoutOnly: import("drizzle-orm/sqlite-core").SQLiteColumn<{
|
|
4766
4732
|
name: "layout_only";
|
|
4767
4733
|
tableName: "block_definitions";
|
|
@@ -9934,11 +9900,9 @@ declare const TRANSLATED_ENTITIES: readonly ["page", "block", "sharedContent", "
|
|
|
9934
9900
|
type TranslatedEntity = (typeof TRANSLATED_ENTITIES)[number];
|
|
9935
9901
|
/**
|
|
9936
9902
|
* A job translating a default-locale entity into one locale of a draft (ADR 0015). Jobs are keyed
|
|
9937
|
-
* by entity, locale, draft and source, and debounced like generation jobs. A job
|
|
9938
|
-
* `
|
|
9939
|
-
*
|
|
9940
|
-
* merging with `live` merges the draft's localization with Live's, which Live changed since the
|
|
9941
|
-
* draft copied it, so it's no longer a conflict (ADR 0016).
|
|
9903
|
+
* by entity, locale, draft and source, and debounced like generation jobs. A job merging with
|
|
9904
|
+
* `live` merges the draft's localization with Live's, which Live changed since the draft copied
|
|
9905
|
+
* it, so it's no longer a conflict (ADR 0016).
|
|
9942
9906
|
*/
|
|
9943
9907
|
type TranslationJob = {
|
|
9944
9908
|
kind: "translation";
|
|
@@ -9946,7 +9910,7 @@ type TranslationJob = {
|
|
|
9946
9910
|
entityId: string;
|
|
9947
9911
|
locale: string;
|
|
9948
9912
|
draftId: string;
|
|
9949
|
-
source?: "
|
|
9913
|
+
source?: "live";
|
|
9950
9914
|
};
|
|
9951
9915
|
/**
|
|
9952
9916
|
* A job writing a draft's Draft Overview from its changes, keyed by draft: a newer request
|
|
@@ -10243,8 +10207,6 @@ type Field = FieldBase & ({
|
|
|
10243
10207
|
itemContentSchema: unknown;
|
|
10244
10208
|
/** The settings schema of the repeater's items, if they have settings. */
|
|
10245
10209
|
itemSettingsSchema: unknown;
|
|
10246
|
-
/** How many items a new block starts with. */
|
|
10247
|
-
defaultItems: number;
|
|
10248
10210
|
} | {
|
|
10249
10211
|
kind: "Reference";
|
|
10250
10212
|
collectionId: string;
|
|
@@ -17027,8 +16989,6 @@ declare const router: {
|
|
|
17027
16989
|
description: string;
|
|
17028
16990
|
contentSchema: unknown;
|
|
17029
16991
|
settingsSchema: unknown;
|
|
17030
|
-
defaultContent: unknown;
|
|
17031
|
-
defaultSettings: unknown;
|
|
17032
16992
|
layoutOnly: boolean | null;
|
|
17033
16993
|
synced: boolean;
|
|
17034
16994
|
schemaHash: string;
|
|
@@ -17043,8 +17003,6 @@ declare const router: {
|
|
|
17043
17003
|
description: string;
|
|
17044
17004
|
contentSchema: unknown;
|
|
17045
17005
|
settingsSchema: unknown;
|
|
17046
|
-
defaultContent: unknown;
|
|
17047
|
-
defaultSettings: unknown;
|
|
17048
17006
|
layoutOnly: boolean | null;
|
|
17049
17007
|
synced: boolean;
|
|
17050
17008
|
schemaHash: string;
|
|
@@ -17772,8 +17730,6 @@ declare const router: {
|
|
|
17772
17730
|
description: import("zod").ZodString;
|
|
17773
17731
|
contentSchema: import("zod").ZodUnknown;
|
|
17774
17732
|
settingsSchema: import("zod").ZodOptional<import("zod").ZodUnknown>;
|
|
17775
|
-
defaultContent: import("zod").ZodOptional<import("zod").ZodUnknown>;
|
|
17776
|
-
defaultSettings: import("zod").ZodOptional<import("zod").ZodUnknown>;
|
|
17777
17733
|
layoutOnly: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
17778
17734
|
synced: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
17779
17735
|
}, import("zod/v4/core").$strip>>;
|
|
@@ -17792,20 +17748,10 @@ declare const router: {
|
|
|
17792
17748
|
}, import("zod/v4/core").$strip>>;
|
|
17793
17749
|
blocks: import("zod").ZodArray<import("zod").ZodPreprocess<import("zod").ZodObject<{
|
|
17794
17750
|
type: import("zod").ZodString;
|
|
17795
|
-
content: import("zod").ZodUnknown;
|
|
17796
|
-
settings: import("zod").ZodOptional<import("zod").ZodUnknown>;
|
|
17797
17751
|
slot: import("zod").ZodOptional<import("zod").ZodEnum<{
|
|
17798
17752
|
after: "after";
|
|
17799
17753
|
before: "before";
|
|
17800
17754
|
}>>;
|
|
17801
|
-
repeatableItems: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
|
|
17802
|
-
tempId: import("zod").ZodString;
|
|
17803
|
-
parentTempId: import("zod").ZodNullable<import("zod").ZodString>;
|
|
17804
|
-
fieldName: import("zod").ZodString;
|
|
17805
|
-
content: import("zod").ZodUnknown;
|
|
17806
|
-
settings: import("zod").ZodOptional<import("zod").ZodUnknown>;
|
|
17807
|
-
position: import("zod").ZodString;
|
|
17808
|
-
}, import("zod/v4/core").$strip>>>;
|
|
17809
17755
|
}, import("zod/v4/core").$strip>, unknown>>;
|
|
17810
17756
|
}, import("zod/v4/core").$strip>>;
|
|
17811
17757
|
collections: import("zod").ZodArray<import("zod").ZodPipe<import("zod").ZodObject<{
|
|
@@ -17938,19 +17884,6 @@ declare const router: {
|
|
|
17938
17884
|
}>>>;
|
|
17939
17885
|
homepage: import("zod").ZodNullable<import("zod").ZodObject<{
|
|
17940
17886
|
layoutId: import("zod").ZodString;
|
|
17941
|
-
blocks: import("zod").ZodArray<import("zod").ZodObject<{
|
|
17942
|
-
type: import("zod").ZodString;
|
|
17943
|
-
content: import("zod").ZodUnknown;
|
|
17944
|
-
settings: import("zod").ZodOptional<import("zod").ZodUnknown>;
|
|
17945
|
-
repeatableItems: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodObject<{
|
|
17946
|
-
tempId: import("zod").ZodString;
|
|
17947
|
-
parentTempId: import("zod").ZodNullable<import("zod").ZodString>;
|
|
17948
|
-
fieldName: import("zod").ZodString;
|
|
17949
|
-
content: import("zod").ZodUnknown;
|
|
17950
|
-
settings: import("zod").ZodOptional<import("zod").ZodUnknown>;
|
|
17951
|
-
position: import("zod").ZodString;
|
|
17952
|
-
}, import("zod/v4/core").$strip>>>;
|
|
17953
|
-
}, import("zod/v4/core").$strip>>;
|
|
17954
17887
|
}, import("zod/v4/core").$strip>>;
|
|
17955
17888
|
}, import("zod/v4/core").$strip>;
|
|
17956
17889
|
}, import("zod/v4/core").$strip>, import("@orpc/server").Schema<{
|
|
@@ -17974,13 +17907,12 @@ declare const router: {
|
|
|
17974
17907
|
deletedDefinitionTypes: string[];
|
|
17975
17908
|
count: number;
|
|
17976
17909
|
} | null;
|
|
17977
|
-
homepage: {
|
|
17910
|
+
homepage: ({
|
|
17978
17911
|
created: false;
|
|
17979
17912
|
} | {
|
|
17980
17913
|
created: true;
|
|
17981
17914
|
pageId: string;
|
|
17982
|
-
|
|
17983
|
-
} | null;
|
|
17915
|
+
}) | null;
|
|
17984
17916
|
}, {
|
|
17985
17917
|
blocks: {
|
|
17986
17918
|
count: number;
|
|
@@ -18002,13 +17934,12 @@ declare const router: {
|
|
|
18002
17934
|
deletedDefinitionTypes: string[];
|
|
18003
17935
|
count: number;
|
|
18004
17936
|
} | null;
|
|
18005
|
-
homepage: {
|
|
17937
|
+
homepage: ({
|
|
18006
17938
|
created: false;
|
|
18007
17939
|
} | {
|
|
18008
17940
|
created: true;
|
|
18009
17941
|
pageId: string;
|
|
18010
|
-
|
|
18011
|
-
} | null;
|
|
17942
|
+
}) | null;
|
|
18012
17943
|
}>, Record<never, never>, Record<never, never>>;
|
|
18013
17944
|
};
|
|
18014
17945
|
drafts: {
|
package/dist/threads.d.ts
CHANGED
|
@@ -34,8 +34,8 @@ export declare const BUILD_STATUS_ENTRY = "camox.build-status";
|
|
|
34
34
|
export declare const RECONCILE_ENTRY = "camox.reconcile";
|
|
35
35
|
/**
|
|
36
36
|
* The kind of the transcript entry opening a draft's thread a Live thread handed a request off
|
|
37
|
-
* to: the Brief, which the model reads
|
|
38
|
-
*
|
|
37
|
+
* to: the Brief, which the model reads ahead of the editor's messages, and editors see collapsed
|
|
38
|
+
* after them, so the request reads on as they sent it. In a draft Camox opened, such as its Patrol Draft, Camox's own messages are Briefs
|
|
39
39
|
* too, with their `operationId` as data, and start the agent's run, which acts for Camox.
|
|
40
40
|
*/
|
|
41
41
|
export declare const BRIEF_ENTRY = "camox.brief";
|
|
@@ -48,8 +48,8 @@ export declare const DRAFT_ENDED_ENTRY = "camox.draft-ended";
|
|
|
48
48
|
/**
|
|
49
49
|
* The kind of the transcript entry a Live thread starts afresh from once its agent handed a
|
|
50
50
|
* request off to a draft (ADR 0036): a reset of Pi's context, with a `startedDraftCardSchema` as
|
|
51
|
-
* data. Editors see
|
|
52
|
-
*
|
|
51
|
+
* data. Editors see an empty thread, and the model reads a note naming the draft. Everything
|
|
52
|
+
* before it is left out of both.
|
|
53
53
|
*/
|
|
54
54
|
export declare const DRAFT_CARD_ENTRY = "camox.draft-card";
|
|
55
55
|
/**
|
|
@@ -330,6 +330,15 @@ export type ThreadMessage = {
|
|
|
330
330
|
from: "camox";
|
|
331
331
|
text: string;
|
|
332
332
|
} |
|
|
333
|
+
/**
|
|
334
|
+
* The Brief the agent on Live wrote as it handed the request off to the draft, shown after the
|
|
335
|
+
* editor's messages it opened the thread with, though the model reads it before them.
|
|
336
|
+
*/
|
|
337
|
+
{
|
|
338
|
+
id: string;
|
|
339
|
+
from: "brief";
|
|
340
|
+
text: string;
|
|
341
|
+
} |
|
|
333
342
|
/** The agent's final message of a turn, in Markdown. */
|
|
334
343
|
{
|
|
335
344
|
id: string;
|
|
@@ -470,6 +479,11 @@ type ThreadStep = {
|
|
|
470
479
|
{
|
|
471
480
|
type: "camox";
|
|
472
481
|
text: string;
|
|
482
|
+
} |
|
|
483
|
+
/** The Brief opening a draft's thread a Live thread handed a request off to. */
|
|
484
|
+
{
|
|
485
|
+
type: "brief";
|
|
486
|
+
text: string;
|
|
473
487
|
} | {
|
|
474
488
|
type: "answer";
|
|
475
489
|
text: string;
|
package/dist/threads.js
CHANGED
|
@@ -32,8 +32,8 @@ const BUILD_STATUS_ENTRY = "camox.build-status";
|
|
|
32
32
|
const RECONCILE_ENTRY = "camox.reconcile";
|
|
33
33
|
/**
|
|
34
34
|
* The kind of the transcript entry opening a draft's thread a Live thread handed a request off
|
|
35
|
-
* to: the Brief, which the model reads
|
|
36
|
-
*
|
|
35
|
+
* to: the Brief, which the model reads ahead of the editor's messages, and editors see collapsed
|
|
36
|
+
* after them, so the request reads on as they sent it. In a draft Camox opened, such as its Patrol Draft, Camox's own messages are Briefs
|
|
37
37
|
* too, with their `operationId` as data, and start the agent's run, which acts for Camox.
|
|
38
38
|
*/
|
|
39
39
|
const BRIEF_ENTRY = "camox.brief";
|
|
@@ -46,8 +46,8 @@ const DRAFT_ENDED_ENTRY = "camox.draft-ended";
|
|
|
46
46
|
/**
|
|
47
47
|
* The kind of the transcript entry a Live thread starts afresh from once its agent handed a
|
|
48
48
|
* request off to a draft (ADR 0036): a reset of Pi's context, with a `startedDraftCardSchema` as
|
|
49
|
-
* data. Editors see
|
|
50
|
-
*
|
|
49
|
+
* data. Editors see an empty thread, and the model reads a note naming the draft. Everything
|
|
50
|
+
* before it is left out of both.
|
|
51
51
|
*/
|
|
52
52
|
const DRAFT_CARD_ENTRY = "camox.draft-card";
|
|
53
53
|
/**
|
|
@@ -375,14 +375,7 @@ function threadStepOf(entry) {
|
|
|
375
375
|
...push
|
|
376
376
|
};
|
|
377
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
|
-
}
|
|
378
|
+
if (entry.kind === "camox.draft-card") return void 0;
|
|
386
379
|
const message = entry.model?.find((candidate) => candidate.role !== "system");
|
|
387
380
|
if (!message) return void 0;
|
|
388
381
|
if (message.role === "user") {
|
|
@@ -407,7 +400,7 @@ function threadStepOf(entry) {
|
|
|
407
400
|
}
|
|
408
401
|
if (entry.kind === "camox.brief") return {
|
|
409
402
|
id,
|
|
410
|
-
type: "camox",
|
|
403
|
+
type: z.object({ operationId: z.string() }).safeParse(entry.data).success ? "camox" : "brief",
|
|
411
404
|
text: textOf(message.content)
|
|
412
405
|
};
|
|
413
406
|
if (entry.kind === "camox.reconcile") {
|
|
@@ -474,101 +467,118 @@ function threadViewOf({ steps, running, toolCalls = [], callingTools = false, ch
|
|
|
474
467
|
});
|
|
475
468
|
endTurn();
|
|
476
469
|
};
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
id: step.id,
|
|
492
|
-
from: "connectedAgent",
|
|
493
|
-
member: step.member,
|
|
494
|
-
client: step.client,
|
|
495
|
-
text: step.text
|
|
496
|
-
});
|
|
497
|
-
waiting = step.id;
|
|
498
|
-
break;
|
|
499
|
-
case "camox":
|
|
500
|
-
if (unfinished) fail();
|
|
501
|
-
messages.push({
|
|
502
|
-
id: step.id,
|
|
503
|
-
from: "camox",
|
|
504
|
-
text: step.text
|
|
505
|
-
});
|
|
506
|
-
waiting = step.id;
|
|
507
|
-
break;
|
|
508
|
-
case "stopped":
|
|
509
|
-
if (waiting === void 0) break;
|
|
510
|
-
messages.push({
|
|
511
|
-
id: step.id,
|
|
512
|
-
from: "stopped",
|
|
513
|
-
sender: step.sender
|
|
514
|
-
});
|
|
515
|
-
endTurn();
|
|
516
|
-
break;
|
|
517
|
-
case "answer":
|
|
518
|
-
if (waiting === void 0 && messages.at(-1)?.from === "stopped") break;
|
|
519
|
-
messages.push({
|
|
520
|
-
id: step.id,
|
|
521
|
-
from: "agent",
|
|
522
|
-
text: step.text
|
|
523
|
-
});
|
|
524
|
-
{
|
|
525
|
-
const before = {
|
|
526
|
-
waiting,
|
|
527
|
-
status
|
|
470
|
+
/** A hand-off's Brief, shown once the editors' messages it opened the thread with are. */
|
|
471
|
+
let brief;
|
|
472
|
+
const showBrief = () => {
|
|
473
|
+
if (brief) messages.push(brief);
|
|
474
|
+
brief = void 0;
|
|
475
|
+
};
|
|
476
|
+
for (const step of steps) {
|
|
477
|
+
if (step.type !== "editor" && step.type !== "connectedAgent") showBrief();
|
|
478
|
+
switch (step.type) {
|
|
479
|
+
case "brief":
|
|
480
|
+
brief = {
|
|
481
|
+
id: step.id,
|
|
482
|
+
from: "brief",
|
|
483
|
+
text: step.text
|
|
528
484
|
};
|
|
485
|
+
break;
|
|
486
|
+
case "editor":
|
|
487
|
+
if (unfinished) fail();
|
|
488
|
+
messages.push({
|
|
489
|
+
id: step.id,
|
|
490
|
+
from: "editor",
|
|
491
|
+
sender: step.sender,
|
|
492
|
+
text: step.text
|
|
493
|
+
});
|
|
494
|
+
waiting = step.id;
|
|
495
|
+
break;
|
|
496
|
+
case "connectedAgent":
|
|
497
|
+
if (unfinished) fail();
|
|
498
|
+
messages.push({
|
|
499
|
+
id: step.id,
|
|
500
|
+
from: "connectedAgent",
|
|
501
|
+
member: step.member,
|
|
502
|
+
client: step.client,
|
|
503
|
+
text: step.text
|
|
504
|
+
});
|
|
505
|
+
waiting = step.id;
|
|
506
|
+
break;
|
|
507
|
+
case "camox":
|
|
508
|
+
if (unfinished) fail();
|
|
509
|
+
messages.push({
|
|
510
|
+
id: step.id,
|
|
511
|
+
from: "camox",
|
|
512
|
+
text: step.text
|
|
513
|
+
});
|
|
514
|
+
waiting = step.id;
|
|
515
|
+
break;
|
|
516
|
+
case "stopped":
|
|
517
|
+
if (waiting === void 0) break;
|
|
518
|
+
messages.push({
|
|
519
|
+
id: step.id,
|
|
520
|
+
from: "stopped",
|
|
521
|
+
sender: step.sender
|
|
522
|
+
});
|
|
523
|
+
endTurn();
|
|
524
|
+
break;
|
|
525
|
+
case "answer":
|
|
526
|
+
if (waiting === void 0 && messages.at(-1)?.from === "stopped") break;
|
|
527
|
+
messages.push({
|
|
528
|
+
id: step.id,
|
|
529
|
+
from: "agent",
|
|
530
|
+
text: step.text
|
|
531
|
+
});
|
|
532
|
+
{
|
|
533
|
+
const before = {
|
|
534
|
+
waiting,
|
|
535
|
+
status
|
|
536
|
+
};
|
|
537
|
+
endTurn();
|
|
538
|
+
answered = before;
|
|
539
|
+
}
|
|
540
|
+
break;
|
|
541
|
+
case "draft":
|
|
542
|
+
if (waiting === void 0 && messages.at(-1)?.from === "stopped") break;
|
|
543
|
+
{
|
|
544
|
+
const draft = drafts[step.draftId];
|
|
545
|
+
messages.push({
|
|
546
|
+
id: step.id,
|
|
547
|
+
from: "draft",
|
|
548
|
+
draftId: step.draftId,
|
|
549
|
+
name: draft?.name ?? step.name,
|
|
550
|
+
...draft?.ended && { ended: draft.ended }
|
|
551
|
+
});
|
|
552
|
+
}
|
|
529
553
|
endTurn();
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
554
|
+
break;
|
|
555
|
+
case "continued":
|
|
556
|
+
if (!answered) break;
|
|
557
|
+
if (messages.at(-1)?.from === "agent") messages.pop();
|
|
558
|
+
waiting = answered.waiting;
|
|
559
|
+
status = answered.status === "fixing" ? "fixing" : void 0;
|
|
560
|
+
answered = void 0;
|
|
561
|
+
break;
|
|
562
|
+
case "build":
|
|
563
|
+
status = step.status;
|
|
564
|
+
break;
|
|
565
|
+
case "push":
|
|
537
566
|
messages.push({
|
|
538
567
|
id: step.id,
|
|
539
|
-
from: "
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
568
|
+
from: "push",
|
|
569
|
+
sender: step.pusher,
|
|
570
|
+
subjects: step.subjects,
|
|
571
|
+
outcome: step.outcome
|
|
543
572
|
});
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
status = answered.status === "fixing" ? "fixing" : void 0;
|
|
552
|
-
answered = void 0;
|
|
553
|
-
break;
|
|
554
|
-
case "build":
|
|
555
|
-
status = step.status;
|
|
556
|
-
break;
|
|
557
|
-
case "push":
|
|
558
|
-
messages.push({
|
|
559
|
-
id: step.id,
|
|
560
|
-
from: "push",
|
|
561
|
-
sender: step.pusher,
|
|
562
|
-
subjects: step.subjects,
|
|
563
|
-
outcome: step.outcome
|
|
564
|
-
});
|
|
565
|
-
break;
|
|
566
|
-
case "tools":
|
|
567
|
-
unfinished = false;
|
|
568
|
-
status = void 0;
|
|
569
|
-
break;
|
|
570
|
-
case "unfinished": unfinished = true;
|
|
573
|
+
break;
|
|
574
|
+
case "tools":
|
|
575
|
+
unfinished = false;
|
|
576
|
+
status = void 0;
|
|
577
|
+
break;
|
|
578
|
+
case "unfinished": unfinished = true;
|
|
579
|
+
}
|
|
571
580
|
}
|
|
581
|
+
showBrief();
|
|
572
582
|
if (!running) fail();
|
|
573
583
|
if (!running) return {
|
|
574
584
|
messages,
|
|
@@ -607,7 +617,7 @@ const callsChangingTool = (block) => block.type === "toolCall" && changes(block.
|
|
|
607
617
|
const callsLook = (block) => block.type === "toolCall" && block.name === "look";
|
|
608
618
|
/**
|
|
609
619
|
* Adds an entry to the thread's state as Pi places it. An entry heading a new context, such as
|
|
610
|
-
* the
|
|
620
|
+
* the one a Live thread starts afresh from, leaves out every step before its head, as Pi's
|
|
611
621
|
* snapshots do.
|
|
612
622
|
*/
|
|
613
623
|
const append = (state, entry) => {
|
package/package.json
CHANGED
package/src/drafts.ts
CHANGED
|
@@ -50,9 +50,9 @@ export const LOOK_LOCALE_PARAM = "locale";
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* The look document's Block Definition mode: instead of a page, the block of the Block Definition
|
|
53
|
-
* `LOOK_BLOCK_DEFINITION_PARAM` names, alone in the site's shell, with
|
|
54
|
-
*
|
|
55
|
-
* the shape the content tools take.
|
|
53
|
+
* `LOOK_BLOCK_DEFINITION_PARAM` names, alone in the site's shell, with empty fields and the
|
|
54
|
+
* content and Block Settings `LOOK_CONTENT_PARAM` and `LOOK_SETTINGS_PARAM` give as JSON merged
|
|
55
|
+
* on, in the shape the content tools take.
|
|
56
56
|
*/
|
|
57
57
|
export const LOOK_BLOCK_DEFINITION_PARAM = "blockDefinition";
|
|
58
58
|
export const LOOK_CONTENT_PARAM = "content";
|
|
@@ -60,8 +60,8 @@ export const LOOK_SETTINGS_PARAM = "settings";
|
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
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
|
|
64
|
-
* content and Block Settings
|
|
63
|
+
* `LOOK_LAYOUT_DEFINITION_PARAM` names, alone in the site's shell, its blocks with empty fields
|
|
64
|
+
* and the content and Block Settings `LOOK_BLOCKS_PARAM` gives as JSON merged on, by Block
|
|
65
65
|
* Definition name (`{ navbar: { content, settings } }`), around a labelled placeholder where a
|
|
66
66
|
* page's content goes, as tall as `LOOK_PLACEHOLDER_PARAM` says: one of `LOOK_PLACEHOLDER_HEIGHTS`,
|
|
67
67
|
* the first without.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a field holds before anyone writes it: the empty value of its kind. Fields declare no
|
|
3
|
+
* default, so content missing a field, or holding a value of the wrong kind, reads as this.
|
|
4
|
+
* Shared by the SDK (rendering, new repeatable items, authoring forms) and the API (creating
|
|
5
|
+
* content, conforming it on read), so both agree on what an unset field is.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** A link that goes nowhere yet. */
|
|
9
|
+
export const EMPTY_LINK = { type: "external", text: "", href: "", newTab: false } as const;
|
|
10
|
+
|
|
11
|
+
function isObject(value: unknown): value is Record<string, unknown> {
|
|
12
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* The empty value of a field, from its stored JSON Schema (`fieldType` and, for an enum, its
|
|
17
|
+
* options): `""` for text, a link to nowhere, `false`, an enum's first option, `null` for one
|
|
18
|
+
* asset or record, and `[]` for lists and repeaters. A fresh copy each call.
|
|
19
|
+
*/
|
|
20
|
+
export function emptyFieldValue(fieldSchema: unknown): unknown {
|
|
21
|
+
const schema = isObject(fieldSchema) ? fieldSchema : {};
|
|
22
|
+
switch (schema.fieldType) {
|
|
23
|
+
case "Link":
|
|
24
|
+
return { ...EMPTY_LINK };
|
|
25
|
+
case "Boolean":
|
|
26
|
+
return false;
|
|
27
|
+
case "Enum": {
|
|
28
|
+
const options = Array.isArray(schema.enum) ? schema.enum : [];
|
|
29
|
+
const first = options.find((option) => typeof option === "string");
|
|
30
|
+
return first ?? "";
|
|
31
|
+
}
|
|
32
|
+
case "Image":
|
|
33
|
+
case "File":
|
|
34
|
+
case "Reference":
|
|
35
|
+
return null;
|
|
36
|
+
case "ImageList":
|
|
37
|
+
case "FileList":
|
|
38
|
+
case "ReferenceList":
|
|
39
|
+
case "Repeater":
|
|
40
|
+
return [];
|
|
41
|
+
default:
|
|
42
|
+
return "";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The empty value of every field of a content or settings schema, by name. */
|
|
47
|
+
export function emptyFieldValues(schema: unknown): Record<string, unknown> {
|
|
48
|
+
const properties = isObject(schema) && isObject(schema.properties) ? schema.properties : {};
|
|
49
|
+
return Object.fromEntries(
|
|
50
|
+
Object.entries(properties).map(([name, field]) => [name, emptyFieldValue(field)]),
|
|
51
|
+
);
|
|
52
|
+
}
|
package/src/index.ts
CHANGED
package/src/threads.ts
CHANGED
|
@@ -44,8 +44,8 @@ export const RECONCILE_ENTRY = "camox.reconcile";
|
|
|
44
44
|
|
|
45
45
|
/**
|
|
46
46
|
* The kind of the transcript entry opening a draft's thread a Live thread handed a request off
|
|
47
|
-
* to: the Brief, which the model reads
|
|
48
|
-
*
|
|
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. In a draft Camox opened, such as its Patrol Draft, Camox's own messages are Briefs
|
|
49
49
|
* too, with their `operationId` as data, and start the agent's run, which acts for Camox.
|
|
50
50
|
*/
|
|
51
51
|
export const BRIEF_ENTRY = "camox.brief";
|
|
@@ -60,8 +60,8 @@ export const DRAFT_ENDED_ENTRY = "camox.draft-ended";
|
|
|
60
60
|
/**
|
|
61
61
|
* The kind of the transcript entry a Live thread starts afresh from once its agent handed a
|
|
62
62
|
* request off to a draft (ADR 0036): a reset of Pi's context, with a `startedDraftCardSchema` as
|
|
63
|
-
* data. Editors see
|
|
64
|
-
*
|
|
63
|
+
* data. Editors see an empty thread, and the model reads a note naming the draft. Everything
|
|
64
|
+
* before it is left out of both.
|
|
65
65
|
*/
|
|
66
66
|
export const DRAFT_CARD_ENTRY = "camox.draft-card";
|
|
67
67
|
|
|
@@ -302,6 +302,11 @@ export type ThreadMessage =
|
|
|
302
302
|
}
|
|
303
303
|
/** Camox's own message, starting a turn it began itself: why it did. */
|
|
304
304
|
| { id: string; from: "camox"; text: string }
|
|
305
|
+
/**
|
|
306
|
+
* The Brief the agent on Live wrote as it handed the request off to the draft, shown after the
|
|
307
|
+
* editor's messages it opened the thread with, though the model reads it before them.
|
|
308
|
+
*/
|
|
309
|
+
| { id: string; from: "brief"; text: string }
|
|
305
310
|
/** The agent's final message of a turn, in Markdown. */
|
|
306
311
|
| { id: string; from: "agent"; text: string }
|
|
307
312
|
/**
|
|
@@ -406,6 +411,8 @@ type ThreadStep = { id: string } & (
|
|
|
406
411
|
| { type: "connectedAgent"; member: ThreadSender; client: ConnectedAgentClient; text: string }
|
|
407
412
|
/** Camox starting a turn of its own, saying why. */
|
|
408
413
|
| { type: "camox"; text: string }
|
|
414
|
+
/** The Brief opening a draft's thread a Live thread handed a request off to. */
|
|
415
|
+
| { type: "brief"; text: string }
|
|
409
416
|
| { type: "answer"; text: string }
|
|
410
417
|
/** A round of tool calls, or their results: the run goes on after it. */
|
|
411
418
|
| { type: "tools" }
|
|
@@ -560,11 +567,8 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
|
|
|
560
567
|
const push = pushDataSchema.safeParse(entry.data).data;
|
|
561
568
|
return push && { id, type: "push", ...push };
|
|
562
569
|
}
|
|
563
|
-
//
|
|
564
|
-
if (entry.kind === DRAFT_CARD_ENTRY)
|
|
565
|
-
const card = startedDraftCardSchema.safeParse(entry.data).data;
|
|
566
|
-
return card && { id, type: "draft", ...card };
|
|
567
|
-
}
|
|
570
|
+
// Only the model reads it, as a note naming the draft: editors see the Live thread start empty.
|
|
571
|
+
if (entry.kind === DRAFT_CARD_ENTRY) return undefined;
|
|
568
572
|
const message = entry.model?.find((candidate) => candidate.role !== "system");
|
|
569
573
|
if (!message) return undefined;
|
|
570
574
|
if (message.role === "user") {
|
|
@@ -583,7 +587,12 @@ function threadStepOf(entry: EntryRecord): ThreadStep | undefined {
|
|
|
583
587
|
}
|
|
584
588
|
);
|
|
585
589
|
}
|
|
586
|
-
if (entry.kind === BRIEF_ENTRY)
|
|
590
|
+
if (entry.kind === BRIEF_ENTRY) {
|
|
591
|
+
// Only Camox's own messages, in a draft it opened, carry an operation id: a hand-off's
|
|
592
|
+
// Brief is written ahead of the editor's messages, which start the run.
|
|
593
|
+
const own = z.object({ operationId: z.string() }).safeParse(entry.data).success;
|
|
594
|
+
return { id, type: own ? "camox" : "brief", text: textOf(message.content) };
|
|
595
|
+
}
|
|
587
596
|
if (entry.kind === RECONCILE_ENTRY) {
|
|
588
597
|
const reconcile = reconcileDataSchema.safeParse(entry.data).data;
|
|
589
598
|
return reconcile && { id, type: "camox", text: reconcile.message };
|
|
@@ -648,9 +657,19 @@ export function threadViewOf({
|
|
|
648
657
|
if (waiting !== undefined) messages.push({ id: `${waiting}:failed`, from: "failed" });
|
|
649
658
|
endTurn();
|
|
650
659
|
};
|
|
660
|
+
/** A hand-off's Brief, shown once the editors' messages it opened the thread with are. */
|
|
661
|
+
let brief: Extract<ThreadMessage, { from: "brief" }> | undefined;
|
|
662
|
+
const showBrief = () => {
|
|
663
|
+
if (brief) messages.push(brief);
|
|
664
|
+
brief = undefined;
|
|
665
|
+
};
|
|
651
666
|
|
|
652
667
|
for (const step of steps) {
|
|
668
|
+
if (step.type !== "editor" && step.type !== "connectedAgent") showBrief();
|
|
653
669
|
switch (step.type) {
|
|
670
|
+
case "brief":
|
|
671
|
+
brief = { id: step.id, from: "brief", text: step.text };
|
|
672
|
+
break;
|
|
654
673
|
case "editor":
|
|
655
674
|
// Pi places a steering message only once a model call is settled: after a round of tool
|
|
656
675
|
// calls, or with the answer ending the run. Never between a failed call and its retry, so
|
|
@@ -738,6 +757,7 @@ export function threadViewOf({
|
|
|
738
757
|
break;
|
|
739
758
|
}
|
|
740
759
|
}
|
|
760
|
+
showBrief();
|
|
741
761
|
if (!running) fail();
|
|
742
762
|
if (!running) return { messages, running };
|
|
743
763
|
return {
|
|
@@ -798,7 +818,7 @@ const callsLook = (block: AssistantMessage["content"][number]) =>
|
|
|
798
818
|
|
|
799
819
|
/**
|
|
800
820
|
* Adds an entry to the thread's state as Pi places it. An entry heading a new context, such as
|
|
801
|
-
* the
|
|
821
|
+
* the one a Live thread starts afresh from, leaves out every step before its head, as Pi's
|
|
802
822
|
* snapshots do.
|
|
803
823
|
*/
|
|
804
824
|
const append = (state: ThreadState, entry: EntryRecord | undefined): ThreadState => {
|