@getfamiliar/shared 0.1.16 → 0.1.18
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/build/AssistantAvatar.d.ts +72 -0
- package/build/AssistantAvatar.js +337 -0
- package/build/BannerBus.d.ts +100 -0
- package/build/BannerBus.js +133 -0
- package/build/Config.d.ts +19 -0
- package/build/Config.js +18 -0
- package/build/Plugin.d.ts +44 -6
- package/build/PromptPlaceholders.js +10 -0
- package/build/RuntimeTime.d.ts +14 -0
- package/build/RuntimeTime.js +38 -0
- package/build/Schema.d.ts +1 -1
- package/build/Schema.js +20 -0
- package/build/WebPlugin.d.ts +10 -0
- package/build/index.d.ts +5 -2
- package/build/index.js +4 -1
- package/package.json +5 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The assistant's avatar (`core.assistantAvatar` in `config.yml`): an
|
|
3
|
+
* Avataaars face described by one value per option. Plain data and
|
|
4
|
+
* validation only — rendering lives in `@getfamiliar/avatar`.
|
|
5
|
+
*
|
|
6
|
+
* Kept free of node imports: browser bundles import it through the
|
|
7
|
+
* `@getfamiliar/shared/avatar` subpath.
|
|
8
|
+
*/
|
|
9
|
+
/** Config key of the assistant's avatar. */
|
|
10
|
+
export declare const ASSISTANT_AVATAR_CONFIG_KEY = "core.assistantAvatar";
|
|
11
|
+
/** Allowed values per avatar option (the Avataaars option values). */
|
|
12
|
+
export declare const AVATAR_OPTION_VALUES: {
|
|
13
|
+
readonly topType: readonly ["NoHair", "Eyepatch", "Hat", "Hijab", "Turban", "WinterHat1", "WinterHat2", "WinterHat3", "WinterHat4", "LongHairBigHair", "LongHairBob", "LongHairBun", "LongHairCurly", "LongHairCurvy", "LongHairDreads", "LongHairFrida", "LongHairFro", "LongHairFroBand", "LongHairNotTooLong", "LongHairShavedSides", "LongHairMiaWallace", "LongHairStraight", "LongHairStraight2", "LongHairStraightStrand", "ShortHairDreads01", "ShortHairDreads02", "ShortHairFrizzle", "ShortHairShaggyMullet", "ShortHairShortCurly", "ShortHairShortFlat", "ShortHairShortRound", "ShortHairShortWaved", "ShortHairSides", "ShortHairTheCaesar", "ShortHairTheCaesarSidePart"];
|
|
14
|
+
readonly accessoriesType: readonly ["Blank", "Kurt", "Prescription01", "Prescription02", "Round", "Sunglasses", "Wayfarers"];
|
|
15
|
+
readonly hairColor: readonly ["Auburn", "Black", "Blonde", "BlondeGolden", "Brown", "BrownDark", "PastelPink", "Blue", "Platinum", "Red", "SilverGray"];
|
|
16
|
+
readonly hatColor: readonly ["Black", "Blue01", "Blue02", "Blue03", "Gray01", "Gray02", "Heather", "PastelBlue", "PastelGreen", "PastelOrange", "PastelRed", "PastelYellow", "Pink", "Red", "White"];
|
|
17
|
+
readonly facialHairType: readonly ["Blank", "BeardLight", "BeardMajestic", "BeardMedium", "MoustacheFancy", "MoustacheMagnum"];
|
|
18
|
+
readonly facialHairColor: readonly ["Auburn", "Black", "Blonde", "BlondeGolden", "Brown", "BrownDark", "Platinum", "Red", "SilverGray"];
|
|
19
|
+
readonly clotheType: readonly ["BlazerShirt", "BlazerSweater", "CollarSweater", "GraphicShirt", "Hoodie", "Overall", "ShirtCrewNeck", "ShirtScoopNeck", "ShirtVNeck"];
|
|
20
|
+
readonly clotheColor: readonly ["Black", "Blue01", "Blue02", "Blue03", "Gray01", "Gray02", "Heather", "PastelBlue", "PastelGreen", "PastelOrange", "PastelRed", "PastelYellow", "Pink", "Red", "White"];
|
|
21
|
+
readonly graphicType: readonly ["Bat", "Bear", "Cumbia", "Deer", "Diamond", "Hola", "Pizza", "Resist", "Selena", "Skull", "SkullOutline"];
|
|
22
|
+
readonly eyeType: readonly ["Close", "Cry", "Default", "Dizzy", "EyeRoll", "Happy", "Hearts", "Side", "Squint", "Surprised", "Wink", "WinkWacky"];
|
|
23
|
+
readonly eyebrowType: readonly ["Angry", "AngryNatural", "Default", "DefaultNatural", "FlatNatural", "FrownNatural", "RaisedExcited", "RaisedExcitedNatural", "SadConcerned", "SadConcernedNatural", "UnibrowNatural", "UpDown", "UpDownNatural"];
|
|
24
|
+
readonly mouthType: readonly ["Concerned", "Default", "Disbelief", "Eating", "Grimace", "Sad", "ScreamOpen", "Serious", "Smile", "Tongue", "Twinkle", "Vomit"];
|
|
25
|
+
readonly skinColor: readonly ["Tanned", "Yellow", "Pale", "Light", "Brown", "DarkBrown", "Black"];
|
|
26
|
+
};
|
|
27
|
+
/** An avatar option with a fixed set of values. */
|
|
28
|
+
export type AvatarOptionField = keyof typeof AVATAR_OPTION_VALUES;
|
|
29
|
+
/** A complete avatar: one value per option plus the background color. */
|
|
30
|
+
export type AvatarConfig = {
|
|
31
|
+
readonly [Field in AvatarOptionField]: (typeof AVATAR_OPTION_VALUES)[Field][number];
|
|
32
|
+
} & {
|
|
33
|
+
/** Background color as `#rrggbb`. */
|
|
34
|
+
readonly backgroundColor: string;
|
|
35
|
+
};
|
|
36
|
+
/** Value of the `topType` option (hair, hats). */
|
|
37
|
+
export type AvatarTopType = AvatarConfig["topType"];
|
|
38
|
+
/** Value of the `eyeType` option. */
|
|
39
|
+
export type AvatarEyeType = AvatarConfig["eyeType"];
|
|
40
|
+
/** Value of the `eyebrowType` option. */
|
|
41
|
+
export type AvatarEyebrowType = AvatarConfig["eyebrowType"];
|
|
42
|
+
/** Value of the `mouthType` option. */
|
|
43
|
+
export type AvatarMouthType = AvatarConfig["mouthType"];
|
|
44
|
+
/** The avatar used while `core.assistantAvatar` is unset. */
|
|
45
|
+
export declare const DEFAULT_AVATAR: AvatarConfig;
|
|
46
|
+
/**
|
|
47
|
+
* Whether an option has a visible effect for the rest of the avatar,
|
|
48
|
+
* e.g. the hat color only matters for a hat. Editors hide the others.
|
|
49
|
+
*
|
|
50
|
+
* @param field The option.
|
|
51
|
+
* @param config The avatar.
|
|
52
|
+
* @returns `true` when changing the option changes the picture.
|
|
53
|
+
*/
|
|
54
|
+
export declare function isAvatarOptionRelevant(field: AvatarOptionField, config: AvatarConfig): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* List what is wrong with a stored avatar: not a mapping, unknown
|
|
57
|
+
* options, values outside the allowed set, a malformed background color.
|
|
58
|
+
* Missing options are fine (they fall back to {@link DEFAULT_AVATAR}).
|
|
59
|
+
*
|
|
60
|
+
* @param value Raw value of `core.assistantAvatar`.
|
|
61
|
+
* @returns Human-readable problems, empty when the value is usable as is.
|
|
62
|
+
*/
|
|
63
|
+
export declare function findAvatarConfigProblems(value: unknown): string[];
|
|
64
|
+
/**
|
|
65
|
+
* Turn a stored avatar into a complete one: every missing or invalid
|
|
66
|
+
* option takes its {@link DEFAULT_AVATAR} value. Never throws, so a
|
|
67
|
+
* hand-edited config cannot break rendering.
|
|
68
|
+
*
|
|
69
|
+
* @param value Raw value of `core.assistantAvatar`.
|
|
70
|
+
* @returns A complete avatar.
|
|
71
|
+
*/
|
|
72
|
+
export declare function normalizeAvatarConfig(value: unknown): AvatarConfig;
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The assistant's avatar (`core.assistantAvatar` in `config.yml`): an
|
|
3
|
+
* Avataaars face described by one value per option. Plain data and
|
|
4
|
+
* validation only — rendering lives in `@getfamiliar/avatar`.
|
|
5
|
+
*
|
|
6
|
+
* Kept free of node imports: browser bundles import it through the
|
|
7
|
+
* `@getfamiliar/shared/avatar` subpath.
|
|
8
|
+
*/
|
|
9
|
+
/** Config key of the assistant's avatar. */
|
|
10
|
+
export const ASSISTANT_AVATAR_CONFIG_KEY = "core.assistantAvatar";
|
|
11
|
+
/** Colors of fabric (clothes and hats). */
|
|
12
|
+
const FABRIC_COLORS = [
|
|
13
|
+
"Black",
|
|
14
|
+
"Blue01",
|
|
15
|
+
"Blue02",
|
|
16
|
+
"Blue03",
|
|
17
|
+
"Gray01",
|
|
18
|
+
"Gray02",
|
|
19
|
+
"Heather",
|
|
20
|
+
"PastelBlue",
|
|
21
|
+
"PastelGreen",
|
|
22
|
+
"PastelOrange",
|
|
23
|
+
"PastelRed",
|
|
24
|
+
"PastelYellow",
|
|
25
|
+
"Pink",
|
|
26
|
+
"Red",
|
|
27
|
+
"White",
|
|
28
|
+
];
|
|
29
|
+
/** Allowed values per avatar option (the Avataaars option values). */
|
|
30
|
+
export const AVATAR_OPTION_VALUES = {
|
|
31
|
+
topType: [
|
|
32
|
+
"NoHair",
|
|
33
|
+
"Eyepatch",
|
|
34
|
+
"Hat",
|
|
35
|
+
"Hijab",
|
|
36
|
+
"Turban",
|
|
37
|
+
"WinterHat1",
|
|
38
|
+
"WinterHat2",
|
|
39
|
+
"WinterHat3",
|
|
40
|
+
"WinterHat4",
|
|
41
|
+
"LongHairBigHair",
|
|
42
|
+
"LongHairBob",
|
|
43
|
+
"LongHairBun",
|
|
44
|
+
"LongHairCurly",
|
|
45
|
+
"LongHairCurvy",
|
|
46
|
+
"LongHairDreads",
|
|
47
|
+
"LongHairFrida",
|
|
48
|
+
"LongHairFro",
|
|
49
|
+
"LongHairFroBand",
|
|
50
|
+
"LongHairNotTooLong",
|
|
51
|
+
"LongHairShavedSides",
|
|
52
|
+
"LongHairMiaWallace",
|
|
53
|
+
"LongHairStraight",
|
|
54
|
+
"LongHairStraight2",
|
|
55
|
+
"LongHairStraightStrand",
|
|
56
|
+
"ShortHairDreads01",
|
|
57
|
+
"ShortHairDreads02",
|
|
58
|
+
"ShortHairFrizzle",
|
|
59
|
+
"ShortHairShaggyMullet",
|
|
60
|
+
"ShortHairShortCurly",
|
|
61
|
+
"ShortHairShortFlat",
|
|
62
|
+
"ShortHairShortRound",
|
|
63
|
+
"ShortHairShortWaved",
|
|
64
|
+
"ShortHairSides",
|
|
65
|
+
"ShortHairTheCaesar",
|
|
66
|
+
"ShortHairTheCaesarSidePart",
|
|
67
|
+
],
|
|
68
|
+
accessoriesType: [
|
|
69
|
+
"Blank",
|
|
70
|
+
"Kurt",
|
|
71
|
+
"Prescription01",
|
|
72
|
+
"Prescription02",
|
|
73
|
+
"Round",
|
|
74
|
+
"Sunglasses",
|
|
75
|
+
"Wayfarers",
|
|
76
|
+
],
|
|
77
|
+
hairColor: [
|
|
78
|
+
"Auburn",
|
|
79
|
+
"Black",
|
|
80
|
+
"Blonde",
|
|
81
|
+
"BlondeGolden",
|
|
82
|
+
"Brown",
|
|
83
|
+
"BrownDark",
|
|
84
|
+
"PastelPink",
|
|
85
|
+
"Blue",
|
|
86
|
+
"Platinum",
|
|
87
|
+
"Red",
|
|
88
|
+
"SilverGray",
|
|
89
|
+
],
|
|
90
|
+
hatColor: FABRIC_COLORS,
|
|
91
|
+
facialHairType: [
|
|
92
|
+
"Blank",
|
|
93
|
+
"BeardLight",
|
|
94
|
+
"BeardMajestic",
|
|
95
|
+
"BeardMedium",
|
|
96
|
+
"MoustacheFancy",
|
|
97
|
+
"MoustacheMagnum",
|
|
98
|
+
],
|
|
99
|
+
facialHairColor: [
|
|
100
|
+
"Auburn",
|
|
101
|
+
"Black",
|
|
102
|
+
"Blonde",
|
|
103
|
+
"BlondeGolden",
|
|
104
|
+
"Brown",
|
|
105
|
+
"BrownDark",
|
|
106
|
+
"Platinum",
|
|
107
|
+
"Red",
|
|
108
|
+
"SilverGray",
|
|
109
|
+
],
|
|
110
|
+
clotheType: [
|
|
111
|
+
"BlazerShirt",
|
|
112
|
+
"BlazerSweater",
|
|
113
|
+
"CollarSweater",
|
|
114
|
+
"GraphicShirt",
|
|
115
|
+
"Hoodie",
|
|
116
|
+
"Overall",
|
|
117
|
+
"ShirtCrewNeck",
|
|
118
|
+
"ShirtScoopNeck",
|
|
119
|
+
"ShirtVNeck",
|
|
120
|
+
],
|
|
121
|
+
clotheColor: FABRIC_COLORS,
|
|
122
|
+
graphicType: [
|
|
123
|
+
"Bat",
|
|
124
|
+
"Bear",
|
|
125
|
+
"Cumbia",
|
|
126
|
+
"Deer",
|
|
127
|
+
"Diamond",
|
|
128
|
+
"Hola",
|
|
129
|
+
"Pizza",
|
|
130
|
+
"Resist",
|
|
131
|
+
"Selena",
|
|
132
|
+
"Skull",
|
|
133
|
+
"SkullOutline",
|
|
134
|
+
],
|
|
135
|
+
eyeType: [
|
|
136
|
+
"Close",
|
|
137
|
+
"Cry",
|
|
138
|
+
"Default",
|
|
139
|
+
"Dizzy",
|
|
140
|
+
"EyeRoll",
|
|
141
|
+
"Happy",
|
|
142
|
+
"Hearts",
|
|
143
|
+
"Side",
|
|
144
|
+
"Squint",
|
|
145
|
+
"Surprised",
|
|
146
|
+
"Wink",
|
|
147
|
+
"WinkWacky",
|
|
148
|
+
],
|
|
149
|
+
eyebrowType: [
|
|
150
|
+
"Angry",
|
|
151
|
+
"AngryNatural",
|
|
152
|
+
"Default",
|
|
153
|
+
"DefaultNatural",
|
|
154
|
+
"FlatNatural",
|
|
155
|
+
"FrownNatural",
|
|
156
|
+
"RaisedExcited",
|
|
157
|
+
"RaisedExcitedNatural",
|
|
158
|
+
"SadConcerned",
|
|
159
|
+
"SadConcernedNatural",
|
|
160
|
+
"UnibrowNatural",
|
|
161
|
+
"UpDown",
|
|
162
|
+
"UpDownNatural",
|
|
163
|
+
],
|
|
164
|
+
mouthType: [
|
|
165
|
+
"Concerned",
|
|
166
|
+
"Default",
|
|
167
|
+
"Disbelief",
|
|
168
|
+
"Eating",
|
|
169
|
+
"Grimace",
|
|
170
|
+
"Sad",
|
|
171
|
+
"ScreamOpen",
|
|
172
|
+
"Serious",
|
|
173
|
+
"Smile",
|
|
174
|
+
"Tongue",
|
|
175
|
+
"Twinkle",
|
|
176
|
+
"Vomit",
|
|
177
|
+
],
|
|
178
|
+
skinColor: ["Tanned", "Yellow", "Pale", "Light", "Brown", "DarkBrown", "Black"],
|
|
179
|
+
};
|
|
180
|
+
/** The avatar used while `core.assistantAvatar` is unset. */
|
|
181
|
+
export const DEFAULT_AVATAR = {
|
|
182
|
+
topType: "LongHairStraight2",
|
|
183
|
+
accessoriesType: "Blank",
|
|
184
|
+
hairColor: "Auburn",
|
|
185
|
+
hatColor: "Blue02",
|
|
186
|
+
facialHairType: "Blank",
|
|
187
|
+
facialHairColor: "Black",
|
|
188
|
+
clotheType: "BlazerSweater",
|
|
189
|
+
clotheColor: "Pink",
|
|
190
|
+
graphicType: "Cumbia",
|
|
191
|
+
eyeType: "Squint",
|
|
192
|
+
eyebrowType: "Default",
|
|
193
|
+
mouthType: "Default",
|
|
194
|
+
skinColor: "Light",
|
|
195
|
+
backgroundColor: "#65c9ff",
|
|
196
|
+
};
|
|
197
|
+
/** Tops drawn with the hair color. */
|
|
198
|
+
const TOPS_WITH_HAIR_COLOR = new Set([
|
|
199
|
+
"LongHairBigHair",
|
|
200
|
+
"LongHairBob",
|
|
201
|
+
"LongHairBun",
|
|
202
|
+
"LongHairCurly",
|
|
203
|
+
"LongHairCurvy",
|
|
204
|
+
"LongHairDreads",
|
|
205
|
+
"LongHairFro",
|
|
206
|
+
"LongHairFroBand",
|
|
207
|
+
"LongHairNotTooLong",
|
|
208
|
+
"LongHairMiaWallace",
|
|
209
|
+
"LongHairStraight",
|
|
210
|
+
"LongHairStraight2",
|
|
211
|
+
"LongHairStraightStrand",
|
|
212
|
+
"ShortHairDreads01",
|
|
213
|
+
"ShortHairDreads02",
|
|
214
|
+
"ShortHairFrizzle",
|
|
215
|
+
"ShortHairShaggyMullet",
|
|
216
|
+
"ShortHairShortCurly",
|
|
217
|
+
"ShortHairShortFlat",
|
|
218
|
+
"ShortHairShortRound",
|
|
219
|
+
"ShortHairShortWaved",
|
|
220
|
+
"ShortHairSides",
|
|
221
|
+
"ShortHairTheCaesar",
|
|
222
|
+
"ShortHairTheCaesarSidePart",
|
|
223
|
+
]);
|
|
224
|
+
/** Tops drawn with the hat color. */
|
|
225
|
+
const TOPS_WITH_HAT_COLOR = new Set([
|
|
226
|
+
"Hijab",
|
|
227
|
+
"Turban",
|
|
228
|
+
"WinterHat1",
|
|
229
|
+
"WinterHat2",
|
|
230
|
+
"WinterHat3",
|
|
231
|
+
"WinterHat4",
|
|
232
|
+
]);
|
|
233
|
+
/** Clothes drawn with the fabric color. */
|
|
234
|
+
const CLOTHES_WITH_COLOR = new Set([
|
|
235
|
+
"CollarSweater",
|
|
236
|
+
"GraphicShirt",
|
|
237
|
+
"Hoodie",
|
|
238
|
+
"Overall",
|
|
239
|
+
"ShirtCrewNeck",
|
|
240
|
+
"ShirtScoopNeck",
|
|
241
|
+
"ShirtVNeck",
|
|
242
|
+
]);
|
|
243
|
+
/** `#rrggbb`. */
|
|
244
|
+
const HEX_COLOR_PATTERN = /^#[0-9a-fA-F]{6}$/;
|
|
245
|
+
/**
|
|
246
|
+
* Whether an option has a visible effect for the rest of the avatar,
|
|
247
|
+
* e.g. the hat color only matters for a hat. Editors hide the others.
|
|
248
|
+
*
|
|
249
|
+
* @param field The option.
|
|
250
|
+
* @param config The avatar.
|
|
251
|
+
* @returns `true` when changing the option changes the picture.
|
|
252
|
+
*/
|
|
253
|
+
export function isAvatarOptionRelevant(field, config) {
|
|
254
|
+
switch (field) {
|
|
255
|
+
case "hairColor":
|
|
256
|
+
return TOPS_WITH_HAIR_COLOR.has(config.topType);
|
|
257
|
+
case "hatColor":
|
|
258
|
+
return TOPS_WITH_HAT_COLOR.has(config.topType);
|
|
259
|
+
case "facialHairColor":
|
|
260
|
+
return config.facialHairType !== "Blank";
|
|
261
|
+
case "clotheColor":
|
|
262
|
+
return CLOTHES_WITH_COLOR.has(config.clotheType);
|
|
263
|
+
case "graphicType":
|
|
264
|
+
return config.clotheType === "GraphicShirt";
|
|
265
|
+
default:
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Whether `value` is an allowed value of `field`.
|
|
271
|
+
*
|
|
272
|
+
* @param field The option.
|
|
273
|
+
* @param value Candidate value.
|
|
274
|
+
* @returns `true` for an allowed value.
|
|
275
|
+
*/
|
|
276
|
+
function isAllowedValue(field, value) {
|
|
277
|
+
return AVATAR_OPTION_VALUES[field].includes(value);
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* List what is wrong with a stored avatar: not a mapping, unknown
|
|
281
|
+
* options, values outside the allowed set, a malformed background color.
|
|
282
|
+
* Missing options are fine (they fall back to {@link DEFAULT_AVATAR}).
|
|
283
|
+
*
|
|
284
|
+
* @param value Raw value of `core.assistantAvatar`.
|
|
285
|
+
* @returns Human-readable problems, empty when the value is usable as is.
|
|
286
|
+
*/
|
|
287
|
+
export function findAvatarConfigProblems(value) {
|
|
288
|
+
if (value === undefined || value === null) {
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
291
|
+
if (typeof value !== "object" || Array.isArray(value)) {
|
|
292
|
+
return ["must be a mapping of avatar options"];
|
|
293
|
+
}
|
|
294
|
+
const problems = [];
|
|
295
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
296
|
+
if (key === "backgroundColor") {
|
|
297
|
+
if (typeof entry !== "string" || !HEX_COLOR_PATTERN.test(entry)) {
|
|
298
|
+
problems.push(`backgroundColor must be a #rrggbb color (got ${String(entry)})`);
|
|
299
|
+
}
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
if (!(key in AVATAR_OPTION_VALUES)) {
|
|
303
|
+
problems.push(`unknown option ${key}`);
|
|
304
|
+
continue;
|
|
305
|
+
}
|
|
306
|
+
const field = key;
|
|
307
|
+
if (!isAllowedValue(field, entry)) {
|
|
308
|
+
problems.push(`${key} must be one of ${AVATAR_OPTION_VALUES[field].join(", ")} (got ${String(entry)})`);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return problems;
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Turn a stored avatar into a complete one: every missing or invalid
|
|
315
|
+
* option takes its {@link DEFAULT_AVATAR} value. Never throws, so a
|
|
316
|
+
* hand-edited config cannot break rendering.
|
|
317
|
+
*
|
|
318
|
+
* @param value Raw value of `core.assistantAvatar`.
|
|
319
|
+
* @returns A complete avatar.
|
|
320
|
+
*/
|
|
321
|
+
export function normalizeAvatarConfig(value) {
|
|
322
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
323
|
+
return DEFAULT_AVATAR;
|
|
324
|
+
}
|
|
325
|
+
const raw = value;
|
|
326
|
+
const result = { ...DEFAULT_AVATAR };
|
|
327
|
+
for (const field of Object.keys(AVATAR_OPTION_VALUES)) {
|
|
328
|
+
if (isAllowedValue(field, raw[field])) {
|
|
329
|
+
result[field] = raw[field];
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const background = raw.backgroundColor;
|
|
333
|
+
if (typeof background === "string" && HEX_COLOR_PATTERN.test(background)) {
|
|
334
|
+
result.backgroundColor = background.toLowerCase();
|
|
335
|
+
}
|
|
336
|
+
return result;
|
|
337
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { PostgresConnection } from "./PostgresConnection.js";
|
|
2
|
+
/** Longest banner text accepted, in characters. Keeps every prompt bounded. */
|
|
3
|
+
export declare const BANNER_MAX_TEXT_CHARS = 500;
|
|
4
|
+
/** Most banners that may be active at the same time. */
|
|
5
|
+
export declare const BANNER_MAX_ACTIVE = 20;
|
|
6
|
+
/**
|
|
7
|
+
* Longest lifetime of a banner, in days. Anything that stays true longer
|
|
8
|
+
* belongs into the long-term memory (wiki), not into every prompt.
|
|
9
|
+
*/
|
|
10
|
+
export declare const BANNER_MAX_LIFETIME_DAYS = 30;
|
|
11
|
+
/** One persisted banner. */
|
|
12
|
+
export interface BannerRow {
|
|
13
|
+
/** Numeric id as string (`bigserial`, as `pg` returns it). */
|
|
14
|
+
readonly id: string;
|
|
15
|
+
/** The note itself. */
|
|
16
|
+
readonly text: string;
|
|
17
|
+
/** UTC ISO-8601 instant at which the banner disappears. */
|
|
18
|
+
readonly expiresAt: string;
|
|
19
|
+
/** UTC ISO-8601 creation instant. */
|
|
20
|
+
readonly createdAt: string;
|
|
21
|
+
/** Agentrun that created the banner, or `null` (CLI, deleted run). */
|
|
22
|
+
readonly createdByAgentrunId: string | null;
|
|
23
|
+
/** Topic of the creating agentrun, or `null`. */
|
|
24
|
+
readonly createdByTopic: string | null;
|
|
25
|
+
}
|
|
26
|
+
/** Input to {@link BannerBus.create}. */
|
|
27
|
+
export interface NewBanner {
|
|
28
|
+
readonly text: string;
|
|
29
|
+
/** UTC ISO-8601 expiry instant. */
|
|
30
|
+
readonly expiresAt: string;
|
|
31
|
+
readonly createdByAgentrunId?: string | null;
|
|
32
|
+
readonly createdByTopic?: string | null;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Domain client for the `banners` table — short-lived notes the agent
|
|
36
|
+
* leaves for every later agentrun. Used by the container (the
|
|
37
|
+
* `banner_*` tools and the `{BANNER_LIST}` prompt placeholder) and by the
|
|
38
|
+
* host CLI (`familiar banners`).
|
|
39
|
+
*
|
|
40
|
+
* Expiry is enforced on read: every listing deletes expired rows first,
|
|
41
|
+
* so a banner "disappears" without a dedicated sweep job. All times are
|
|
42
|
+
* stored and returned as UTC; agent-facing surfaces project them into
|
|
43
|
+
* `core.timezone` (see {@link formatBannerList}).
|
|
44
|
+
*/
|
|
45
|
+
export declare class BannerBus {
|
|
46
|
+
private readonly connection;
|
|
47
|
+
constructor(connection: PostgresConnection);
|
|
48
|
+
/**
|
|
49
|
+
* Apply the bus-state schema (idempotent). Same SQL bundle every
|
|
50
|
+
* other bus client installs.
|
|
51
|
+
*/
|
|
52
|
+
installSchema(): Promise<void>;
|
|
53
|
+
/**
|
|
54
|
+
* Insert a banner. Validation (length, lifetime, active count) is the
|
|
55
|
+
* caller's job — see the `banner_create` tool.
|
|
56
|
+
*
|
|
57
|
+
* @param banner The banner to persist.
|
|
58
|
+
* @returns The persisted row.
|
|
59
|
+
*/
|
|
60
|
+
create(banner: NewBanner): Promise<BannerRow>;
|
|
61
|
+
/**
|
|
62
|
+
* Delete banners by id.
|
|
63
|
+
*
|
|
64
|
+
* @param ids Banner ids (numeric strings).
|
|
65
|
+
* @returns The ids that existed and were deleted.
|
|
66
|
+
*/
|
|
67
|
+
remove(ids: readonly string[]): Promise<string[]>;
|
|
68
|
+
/**
|
|
69
|
+
* List every banner that has not expired yet, oldest first. Deletes
|
|
70
|
+
* expired banners before reading.
|
|
71
|
+
*
|
|
72
|
+
* @returns The active banners.
|
|
73
|
+
*/
|
|
74
|
+
listActive(): Promise<BannerRow[]>;
|
|
75
|
+
/**
|
|
76
|
+
* Delete every banner whose expiry has passed.
|
|
77
|
+
*
|
|
78
|
+
* @returns Number of deleted rows.
|
|
79
|
+
*/
|
|
80
|
+
purgeExpired(): Promise<number>;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Render a UTC instant as compact local wall-clock `YYYY-MM-DD HH:mm`.
|
|
84
|
+
* Falls back to the input on an unparsable instant or unknown zone.
|
|
85
|
+
*
|
|
86
|
+
* @param utcIso UTC ISO-8601 instant.
|
|
87
|
+
* @param zone IANA zone (typically `core.timezone`).
|
|
88
|
+
* @returns The local wall-clock string.
|
|
89
|
+
*/
|
|
90
|
+
export declare function formatBannerTime(utcIso: string, zone: string): string;
|
|
91
|
+
/**
|
|
92
|
+
* Render banners as the markdown bullet list the prompt shows, one
|
|
93
|
+
* `* [ID:<id>] <created> (until <expires>): <text>` line each. Newlines
|
|
94
|
+
* inside a banner text are collapsed so one banner stays one bullet.
|
|
95
|
+
*
|
|
96
|
+
* @param banners Active banners, in display order.
|
|
97
|
+
* @param zone IANA zone the times are shown in.
|
|
98
|
+
* @returns The list, or `(none)` when there are no banners.
|
|
99
|
+
*/
|
|
100
|
+
export declare function formatBannerList(banners: readonly BannerRow[], zone: string): string;
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { DateTime } from "luxon";
|
|
2
|
+
import { SCHEMA_SQL } from "./Schema.js";
|
|
3
|
+
/** Longest banner text accepted, in characters. Keeps every prompt bounded. */
|
|
4
|
+
export const BANNER_MAX_TEXT_CHARS = 500;
|
|
5
|
+
/** Most banners that may be active at the same time. */
|
|
6
|
+
export const BANNER_MAX_ACTIVE = 20;
|
|
7
|
+
/**
|
|
8
|
+
* Longest lifetime of a banner, in days. Anything that stays true longer
|
|
9
|
+
* belongs into the long-term memory (wiki), not into every prompt.
|
|
10
|
+
*/
|
|
11
|
+
export const BANNER_MAX_LIFETIME_DAYS = 30;
|
|
12
|
+
/**
|
|
13
|
+
* Domain client for the `banners` table — short-lived notes the agent
|
|
14
|
+
* leaves for every later agentrun. Used by the container (the
|
|
15
|
+
* `banner_*` tools and the `{BANNER_LIST}` prompt placeholder) and by the
|
|
16
|
+
* host CLI (`familiar banners`).
|
|
17
|
+
*
|
|
18
|
+
* Expiry is enforced on read: every listing deletes expired rows first,
|
|
19
|
+
* so a banner "disappears" without a dedicated sweep job. All times are
|
|
20
|
+
* stored and returned as UTC; agent-facing surfaces project them into
|
|
21
|
+
* `core.timezone` (see {@link formatBannerList}).
|
|
22
|
+
*/
|
|
23
|
+
export class BannerBus {
|
|
24
|
+
connection;
|
|
25
|
+
constructor(connection) {
|
|
26
|
+
this.connection = connection;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Apply the bus-state schema (idempotent). Same SQL bundle every
|
|
30
|
+
* other bus client installs.
|
|
31
|
+
*/
|
|
32
|
+
async installSchema() {
|
|
33
|
+
await this.connection.getPool().query(SCHEMA_SQL);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Insert a banner. Validation (length, lifetime, active count) is the
|
|
37
|
+
* caller's job — see the `banner_create` tool.
|
|
38
|
+
*
|
|
39
|
+
* @param banner The banner to persist.
|
|
40
|
+
* @returns The persisted row.
|
|
41
|
+
*/
|
|
42
|
+
async create(banner) {
|
|
43
|
+
await this.purgeExpired();
|
|
44
|
+
const result = await this.connection.getPool().query(`INSERT INTO banners (text, expires_at, created_by_agentrun_id, created_by_topic)
|
|
45
|
+
VALUES ($1, $2, $3, $4)
|
|
46
|
+
RETURNING *`, [
|
|
47
|
+
banner.text,
|
|
48
|
+
banner.expiresAt,
|
|
49
|
+
banner.createdByAgentrunId ?? null,
|
|
50
|
+
banner.createdByTopic ?? null,
|
|
51
|
+
]);
|
|
52
|
+
return mapRow(result.rows[0]);
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Delete banners by id.
|
|
56
|
+
*
|
|
57
|
+
* @param ids Banner ids (numeric strings).
|
|
58
|
+
* @returns The ids that existed and were deleted.
|
|
59
|
+
*/
|
|
60
|
+
async remove(ids) {
|
|
61
|
+
if (ids.length === 0) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
const result = await this.connection
|
|
65
|
+
.getPool()
|
|
66
|
+
.query(`DELETE FROM banners WHERE id = ANY($1::bigint[]) RETURNING id`, [ids]);
|
|
67
|
+
return result.rows.map((row) => row.id);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* List every banner that has not expired yet, oldest first. Deletes
|
|
71
|
+
* expired banners before reading.
|
|
72
|
+
*
|
|
73
|
+
* @returns The active banners.
|
|
74
|
+
*/
|
|
75
|
+
async listActive() {
|
|
76
|
+
await this.purgeExpired();
|
|
77
|
+
const result = await this.connection
|
|
78
|
+
.getPool()
|
|
79
|
+
.query(`SELECT * FROM banners WHERE expires_at > now() ORDER BY created_at ASC, id ASC`);
|
|
80
|
+
return result.rows.map(mapRow);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Delete every banner whose expiry has passed.
|
|
84
|
+
*
|
|
85
|
+
* @returns Number of deleted rows.
|
|
86
|
+
*/
|
|
87
|
+
async purgeExpired() {
|
|
88
|
+
const result = await this.connection
|
|
89
|
+
.getPool()
|
|
90
|
+
.query(`DELETE FROM banners WHERE expires_at <= now()`);
|
|
91
|
+
return result.rowCount ?? 0;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/** Convert a snake_case raw row into the camelCase domain shape. */
|
|
95
|
+
function mapRow(raw) {
|
|
96
|
+
return {
|
|
97
|
+
id: String(raw.id),
|
|
98
|
+
text: raw.text,
|
|
99
|
+
expiresAt: raw.expires_at.toISOString(),
|
|
100
|
+
createdAt: raw.created_at.toISOString(),
|
|
101
|
+
createdByAgentrunId: raw.created_by_agentrun_id === null ? null : String(raw.created_by_agentrun_id),
|
|
102
|
+
createdByTopic: raw.created_by_topic,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Render a UTC instant as compact local wall-clock `YYYY-MM-DD HH:mm`.
|
|
107
|
+
* Falls back to the input on an unparsable instant or unknown zone.
|
|
108
|
+
*
|
|
109
|
+
* @param utcIso UTC ISO-8601 instant.
|
|
110
|
+
* @param zone IANA zone (typically `core.timezone`).
|
|
111
|
+
* @returns The local wall-clock string.
|
|
112
|
+
*/
|
|
113
|
+
export function formatBannerTime(utcIso, zone) {
|
|
114
|
+
const local = DateTime.fromISO(utcIso, { zone: "utc" }).setZone(zone);
|
|
115
|
+
return local.isValid ? local.toFormat("yyyy-MM-dd HH:mm") : utcIso;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Render banners as the markdown bullet list the prompt shows, one
|
|
119
|
+
* `* [ID:<id>] <created> (until <expires>): <text>` line each. Newlines
|
|
120
|
+
* inside a banner text are collapsed so one banner stays one bullet.
|
|
121
|
+
*
|
|
122
|
+
* @param banners Active banners, in display order.
|
|
123
|
+
* @param zone IANA zone the times are shown in.
|
|
124
|
+
* @returns The list, or `(none)` when there are no banners.
|
|
125
|
+
*/
|
|
126
|
+
export function formatBannerList(banners, zone) {
|
|
127
|
+
if (banners.length === 0) {
|
|
128
|
+
return "(none)";
|
|
129
|
+
}
|
|
130
|
+
return banners
|
|
131
|
+
.map((banner) => `* [ID:${banner.id}] ${formatBannerTime(banner.createdAt, zone)} (until ${formatBannerTime(banner.expiresAt, zone)}): ${banner.text.replace(/\s*\n\s*/g, " ").trim()}`)
|
|
132
|
+
.join("\n");
|
|
133
|
+
}
|
package/build/Config.d.ts
CHANGED
|
@@ -24,6 +24,18 @@
|
|
|
24
24
|
* the workspace globs the agent may write to, which are never handlers.
|
|
25
25
|
*/
|
|
26
26
|
export declare const DEFAULT_WRITABLE_PATHS: readonly string[];
|
|
27
|
+
/** Config key of the assistant's name (web UI branding, `{ASSISTANT_NAME}`). */
|
|
28
|
+
export declare const ASSISTANT_NAME_CONFIG_KEY = "core.assistantName";
|
|
29
|
+
/** Assistant name when `core.assistantName` is unset or empty. */
|
|
30
|
+
export declare const DEFAULT_ASSISTANT_NAME = "Familiar";
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the assistant's name from a raw config value: a non-blank
|
|
33
|
+
* string (trimmed), otherwise {@link DEFAULT_ASSISTANT_NAME}.
|
|
34
|
+
*
|
|
35
|
+
* @param value Raw value of `core.assistantName`, or undefined.
|
|
36
|
+
* @returns The name to show and to render into prompts.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolveAssistantName(value: unknown): string;
|
|
27
39
|
export interface ConfigService {
|
|
28
40
|
/**
|
|
29
41
|
* Read a string under a dotted path. Throws when the key is
|
|
@@ -85,4 +97,11 @@ export interface ConfigService {
|
|
|
85
97
|
* atomic write fails.
|
|
86
98
|
*/
|
|
87
99
|
set(key: string, value: unknown): Promise<void>;
|
|
100
|
+
/**
|
|
101
|
+
* Read the raw value under a dotted path, without shape checks — for
|
|
102
|
+
* structured values (mappings) whose reader validates them itself.
|
|
103
|
+
*
|
|
104
|
+
* @returns The raw value, or `undefined` when absent.
|
|
105
|
+
*/
|
|
106
|
+
getValue(key: string): unknown;
|
|
88
107
|
}
|
package/build/Config.js
CHANGED
|
@@ -24,3 +24,21 @@
|
|
|
24
24
|
* the workspace globs the agent may write to, which are never handlers.
|
|
25
25
|
*/
|
|
26
26
|
export const DEFAULT_WRITABLE_PATHS = ["wiki/**", "files/**"];
|
|
27
|
+
/** Config key of the assistant's name (web UI branding, `{ASSISTANT_NAME}`). */
|
|
28
|
+
export const ASSISTANT_NAME_CONFIG_KEY = "core.assistantName";
|
|
29
|
+
/** Assistant name when `core.assistantName` is unset or empty. */
|
|
30
|
+
export const DEFAULT_ASSISTANT_NAME = "Familiar";
|
|
31
|
+
/**
|
|
32
|
+
* Resolve the assistant's name from a raw config value: a non-blank
|
|
33
|
+
* string (trimmed), otherwise {@link DEFAULT_ASSISTANT_NAME}.
|
|
34
|
+
*
|
|
35
|
+
* @param value Raw value of `core.assistantName`, or undefined.
|
|
36
|
+
* @returns The name to show and to render into prompts.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveAssistantName(value) {
|
|
39
|
+
if (typeof value !== "string") {
|
|
40
|
+
return DEFAULT_ASSISTANT_NAME;
|
|
41
|
+
}
|
|
42
|
+
const trimmed = value.trim();
|
|
43
|
+
return trimmed === "" ? DEFAULT_ASSISTANT_NAME : trimmed;
|
|
44
|
+
}
|
package/build/Plugin.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
2
2
|
import type { CommandDef } from "citty";
|
|
3
3
|
import type { AgentRunRow } from "./AgentRun.js";
|
|
4
|
+
import type { AvatarConfig } from "./AssistantAvatar.js";
|
|
5
|
+
import type { BannerRow } from "./BannerBus.js";
|
|
4
6
|
import type { CalendarApi } from "./Calendar.js";
|
|
5
7
|
import type { ChatFilter, ChatHistoryQuery, ChatMessage } from "./ChatMessage.js";
|
|
6
8
|
import type { ChatHandler, ChatUnsubscribe } from "./ChatMessageBus.js";
|
|
@@ -258,6 +260,21 @@ export interface HostContext {
|
|
|
258
260
|
*/
|
|
259
261
|
listMessages(query: ChatHistoryQuery): Promise<readonly ChatMessage[]>;
|
|
260
262
|
};
|
|
263
|
+
/**
|
|
264
|
+
* Read access to the banners — the short-lived notes every agentrun
|
|
265
|
+
* sees through `{BANNER_LIST}`. Read-only on purpose: banners are the
|
|
266
|
+
* agent's notes, created through its `banner_create` tool. For
|
|
267
|
+
* plugins that build prompts of their own (e.g. the voice model's
|
|
268
|
+
* instructions) and should show the same notes.
|
|
269
|
+
*/
|
|
270
|
+
readonly banners: {
|
|
271
|
+
/**
|
|
272
|
+
* The banners that have not expired, oldest first (expired ones
|
|
273
|
+
* are deleted on the way). Times are UTC; render them with
|
|
274
|
+
* `formatBannerList(banners, timezone)`.
|
|
275
|
+
*/
|
|
276
|
+
listActive(): Promise<readonly BannerRow[]>;
|
|
277
|
+
};
|
|
261
278
|
/**
|
|
262
279
|
* Per-event scratch directory operations. Each event the bus
|
|
263
280
|
* persists has a private scratch dir at `/scratch/<event-id>/`
|
|
@@ -319,6 +336,17 @@ export interface HostContext {
|
|
|
319
336
|
readonly isDevMode: boolean;
|
|
320
337
|
/** Version of the running familiar host (the `@getfamiliar/host` package version, e.g. `0.4.2`). */
|
|
321
338
|
readonly familiarVersion: string;
|
|
339
|
+
/**
|
|
340
|
+
* The assistant's name (`core.assistantName`, default `Familiar`),
|
|
341
|
+
* read on every access so it follows `familiar config reload`. Use it
|
|
342
|
+
* wherever the assistant names itself to the user (UI branding, voice).
|
|
343
|
+
*/
|
|
344
|
+
readonly assistantName: string;
|
|
345
|
+
/**
|
|
346
|
+
* The assistant's avatar (`core.assistantAvatar`, completed with
|
|
347
|
+
* defaults), read on every access. Render it with `@getfamiliar/avatar`.
|
|
348
|
+
*/
|
|
349
|
+
readonly assistantAvatar: AvatarConfig;
|
|
322
350
|
/**
|
|
323
351
|
* Absolute host path of the project's `data/` directory.
|
|
324
352
|
*
|
|
@@ -882,14 +910,24 @@ export interface PluginManifest {
|
|
|
882
910
|
/** Host-side surface (CLI commands, daemons, cronjobs, tools). */
|
|
883
911
|
readonly host?: PluginHostManifest;
|
|
884
912
|
/**
|
|
885
|
-
* Absolute path to a directory of files
|
|
886
|
-
*
|
|
887
|
-
*
|
|
888
|
-
*
|
|
889
|
-
* `
|
|
890
|
-
* `import.meta.
|
|
913
|
+
* Absolute path to a directory of default workspace files (handlers,
|
|
914
|
+
* skills). Missing files are seeded into `<home>/workspace/`; later
|
|
915
|
+
* template changes reach the workspace through the workspace updater
|
|
916
|
+
* (fast-forward when the user never edited the file, otherwise the
|
|
917
|
+
* `update:merge-file` agent run). Plugins typically derive this with
|
|
918
|
+
* `path.join(import.meta.dirname, "..", "workspace-template")`.
|
|
891
919
|
*/
|
|
892
920
|
readonly workspaceTemplate?: string;
|
|
921
|
+
/**
|
|
922
|
+
* Absolute path to a directory of update notes (`YYYY-MM-DD-<slug>.md`):
|
|
923
|
+
* one-off instructions for changes a template diff cannot express —
|
|
924
|
+
* renamed placeholders, moved files, conventions that affect files the
|
|
925
|
+
* user created. Each note is applied once by an `update:apply-note`
|
|
926
|
+
* agent run and then recorded as done; a fresh install marks every
|
|
927
|
+
* existing note done. Conventionally
|
|
928
|
+
* `path.join(import.meta.dirname, "..", "workspace-updates")`.
|
|
929
|
+
*/
|
|
930
|
+
readonly workspaceUpdates?: string;
|
|
893
931
|
}
|
|
894
932
|
/**
|
|
895
933
|
* Identity function used by plugins to declare their manifest:
|
|
@@ -43,6 +43,11 @@ export const CORE_PROMPT_PLACEHOLDERS = [
|
|
|
43
43
|
description: "Workspace-relative path of the handler file, e.g. `chat/telegram.md`.",
|
|
44
44
|
kind: "static",
|
|
45
45
|
},
|
|
46
|
+
{
|
|
47
|
+
name: "ASSISTANT_NAME",
|
|
48
|
+
description: "The assistant's name from `core.assistantName` (default `Familiar`).",
|
|
49
|
+
kind: "static",
|
|
50
|
+
},
|
|
46
51
|
{
|
|
47
52
|
name: "SKILL_LIST",
|
|
48
53
|
description: "Bullet list of the skills in `skills/` (id and description).",
|
|
@@ -83,6 +88,11 @@ export const CORE_PROMPT_PLACEHOLDERS = [
|
|
|
83
88
|
description: "`yes` when the run stems from the system owner, otherwise `no`.",
|
|
84
89
|
kind: "dynamic",
|
|
85
90
|
},
|
|
91
|
+
{
|
|
92
|
+
name: "BANNER_LIST",
|
|
93
|
+
description: "Bullet list of active banners (short-lived notes runs left via `banner_create`), one `* [ID:<id>] <created> (until <expires>): <text>` line each.",
|
|
94
|
+
kind: "dynamic",
|
|
95
|
+
},
|
|
86
96
|
{
|
|
87
97
|
name: "STAGED_FILE_LIST",
|
|
88
98
|
description: "Bullet list of files staged in this event's `/scratch/<event-id>/` folder.",
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a `Date` for the runtime facts:
|
|
3
|
+
* `Friday, 2026-05-19T18:43:12 in timezone Europe/Berlin`.
|
|
4
|
+
*
|
|
5
|
+
* Weekday name + ISO-like local time + the IANA tz label, all relative
|
|
6
|
+
* to `timezone`. Built from `Intl.DateTimeFormat.formatToParts` so we
|
|
7
|
+
* control the separators directly — the locale-default formatter
|
|
8
|
+
* inserts AM/PM and locale punctuation we don't want.
|
|
9
|
+
*
|
|
10
|
+
* @param date The instant to format.
|
|
11
|
+
* @param timezone IANA timezone.
|
|
12
|
+
* @returns The formatted string.
|
|
13
|
+
*/
|
|
14
|
+
export declare function formatRuntimeTime(date: Date, timezone: string): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Format a `Date` for the runtime facts:
|
|
3
|
+
* `Friday, 2026-05-19T18:43:12 in timezone Europe/Berlin`.
|
|
4
|
+
*
|
|
5
|
+
* Weekday name + ISO-like local time + the IANA tz label, all relative
|
|
6
|
+
* to `timezone`. Built from `Intl.DateTimeFormat.formatToParts` so we
|
|
7
|
+
* control the separators directly — the locale-default formatter
|
|
8
|
+
* inserts AM/PM and locale punctuation we don't want.
|
|
9
|
+
*
|
|
10
|
+
* @param date The instant to format.
|
|
11
|
+
* @param timezone IANA timezone.
|
|
12
|
+
* @returns The formatted string.
|
|
13
|
+
*/
|
|
14
|
+
export function formatRuntimeTime(date, timezone) {
|
|
15
|
+
const parts = new Intl.DateTimeFormat("en-US", {
|
|
16
|
+
timeZone: timezone,
|
|
17
|
+
weekday: "long",
|
|
18
|
+
year: "numeric",
|
|
19
|
+
month: "2-digit",
|
|
20
|
+
day: "2-digit",
|
|
21
|
+
hour: "2-digit",
|
|
22
|
+
minute: "2-digit",
|
|
23
|
+
second: "2-digit",
|
|
24
|
+
hour12: false,
|
|
25
|
+
}).formatToParts(date);
|
|
26
|
+
const lookup = new Map(parts.map((p) => [p.type, p.value]));
|
|
27
|
+
const weekday = lookup.get("weekday") ?? "";
|
|
28
|
+
const year = lookup.get("year") ?? "";
|
|
29
|
+
const month = lookup.get("month") ?? "";
|
|
30
|
+
const day = lookup.get("day") ?? "";
|
|
31
|
+
// `hour: '2-digit'` with `hour12: false` can yield "24" at midnight
|
|
32
|
+
// on some implementations; normalise to "00".
|
|
33
|
+
const rawHour = lookup.get("hour") ?? "";
|
|
34
|
+
const hour = rawHour === "24" ? "00" : rawHour;
|
|
35
|
+
const minute = lookup.get("minute") ?? "";
|
|
36
|
+
const second = lookup.get("second") ?? "";
|
|
37
|
+
return `${weekday}, ${year}-${month}-${day}T${hour}:${minute}:${second} in timezone ${timezone}`;
|
|
38
|
+
}
|
package/build/Schema.d.ts
CHANGED
|
@@ -62,7 +62,7 @@ export declare const SCHEDULED_SUBAGENTS_CHANNEL = "scheduled_subagents_changed"
|
|
|
62
62
|
* match exactly (see `HandlerResolution.ts`).
|
|
63
63
|
*/
|
|
64
64
|
export declare const TOPIC_PATTERN = "^[\\w-]+(:[\\w-]+)*$";
|
|
65
|
-
export declare const SCHEMA_SQL = "\n-- Drop legacy single-table artifacts from before the events/agentruns\n-- split. Safe no-ops on fresh databases. The current schema reuses the\n-- name events_notify_new() for its own NOTIFY function, so we do NOT\n-- drop it here (CREATE OR REPLACE FUNCTION below handles the update);\n-- dropping would fail anyway once the current events_new_trg exists.\nDROP TRIGGER IF EXISTS events_notify_changed_trg ON events;\nDROP TRIGGER IF EXISTS events_notify_new_trg ON events;\nDROP FUNCTION IF EXISTS events_notify_changed();\nDROP INDEX IF EXISTS events_pending_priority_idx;\nDROP INDEX IF EXISTS events_state_priority_idx;\n\nCREATE TABLE IF NOT EXISTS events (\n id bigserial PRIMARY KEY,\n topic text NOT NULL,\n priority smallint NOT NULL DEFAULT 50,\n state text NOT NULL DEFAULT 'pending',\n payload jsonb NOT NULL DEFAULT '{}'::jsonb,\n idempotency_key text UNIQUE,\n privileged boolean NOT NULL DEFAULT false,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT events_topic_format CHECK (topic ~ '^[\\w-]+(:[\\w-]+)*$')\n);\n\n-- Drop legacy columns from earlier schema generations. Single-instance\n-- project; column drops are fine.\nALTER TABLE events DROP COLUMN IF EXISTS supervisor_prompt;\nALTER TABLE events DROP COLUMN IF EXISTS causation_chain;\n\n-- Chat-aware event metadata. `is_chat` opts into automatic user-message\n-- persistence in EventBus.add; `preferred_chat_channel_id` names the\n-- channel an assistant reply should reach the user on. The container is\n-- channel-blind \u2014 both fields are read host-side (for routing) or via\n-- JOIN inside the chatmessages NOTIFY trigger.\nALTER TABLE events ADD COLUMN IF NOT EXISTS is_chat boolean NOT NULL DEFAULT false;\nALTER TABLE events ADD COLUMN IF NOT EXISTS preferred_chat_channel_id text;\n-- Required for non-chat events (validated at `EventBus.add` time and at\n-- the type level via `NewEvent`'s discriminated union); always null for\n-- chat events because the user's text in `chatmessages` already serves\n-- as the agent-visible \"what happened\" message. The EventWatcher copies\n-- this verbatim into the root `agentruns.prompt` so the agent sees the\n-- emitter's framing without the EventWatcher having to reach into the\n-- payload schema.\nALTER TABLE events ADD COLUMN IF NOT EXISTS prompt text;\n-- The topic is the handler's whole address now; the separate start\n-- handler basename is gone.\nALTER TABLE events DROP COLUMN IF EXISTS start_handler;\n-- When true the topic must name an existing handler file exactly; when\n-- false (world events) handler resolution falls back up the topic chain.\n-- Copied onto the root agentrun by the input-event watcher.\nALTER TABLE events ADD COLUMN IF NOT EXISTS is_exact_topic boolean NOT NULL DEFAULT false;\n-- Privileged events originate from a trusted user-input source (operator\n-- at the local terminal via cli-chat, operator on Telegram). The flag\n-- propagates verbatim to the root agentrun and to every child agentrun\n-- spawned via schedule_subagent / start_subagent, so future system tools can\n-- gate risky reads / writes (editing SOUL.md etc.) on whether the run\n-- descends from a trusted input. Default false: any plugin that doesn't\n-- explicitly opt in produces non-privileged events.\nALTER TABLE events ADD COLUMN IF NOT EXISTS privileged boolean NOT NULL DEFAULT false;\n-- When true and the event ends in 'failed', `HostContextImpl.emitAndAwait`\n-- writes a role='assistant' chatmessage with the failure text before\n-- rejecting handle.settled. Default false: only chat plugins (cli-chat,\n-- telegram) opt in. See [[outputChatOnFailure]] on NewEvent for the\n-- rationale.\nALTER TABLE events ADD COLUMN IF NOT EXISTS output_chat_on_failure boolean NOT NULL DEFAULT false;\n\nCREATE INDEX IF NOT EXISTS events_state_priority_idx\n ON events (state, priority DESC, id ASC);\nCREATE INDEX IF NOT EXISTS events_topic_idx ON events (topic);\n\nCREATE TABLE IF NOT EXISTS agentruns (\n id bigserial PRIMARY KEY,\n event_id bigint NOT NULL REFERENCES events(id) ON DELETE CASCADE,\n parent_agentrun_id bigint REFERENCES agentruns(id) ON DELETE CASCADE,\n topic text NOT NULL,\n priority smallint NOT NULL DEFAULT 50,\n state text NOT NULL DEFAULT 'pending',\n prompt text,\n payload jsonb NOT NULL DEFAULT '{}'::jsonb,\n result jsonb,\n result_text text,\n error text,\n privileged boolean NOT NULL DEFAULT false,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT agentruns_topic_format CHECK (topic ~ '^[\\w-]+(:[\\w-]+)*$')\n);\n\n-- Forward-compat ALTER for databases created before result_text landed.\n-- Safe no-op on fresh databases where the column already exists.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS result_text text;\n-- The topic is the handler's whole address; the basename column is gone.\nALTER TABLE agentruns DROP COLUMN IF EXISTS handler;\n-- Whether handler resolution must match the topic exactly. Root\n-- agentruns copy the event's flag; spawned children are always exact.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS is_exact_topic boolean NOT NULL DEFAULT true;\n-- Inherited from the originating event (root agentrun) or parent\n-- agentrun (children spawned via schedule_subagent / start_subagent). See\n-- the matching ALTER on the events table for the trust-model rationale.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS privileged boolean NOT NULL DEFAULT false;\n-- Number of retry attempts already made on this agentrun. Bumped each\n-- time AgentRunner postpones the row due to a retryable inference\n-- error (APICallError.isRetryable). Capped per-handler via maxRetries\n-- (frontmatter override) or globally via inference.maxRetries.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS retry_count int NOT NULL DEFAULT 0;\n-- Earliest moment the agentrun watcher is allowed to re-claim this\n-- row. NULL = claim immediately (default for fresh rows). A future\n-- timestamp = leave the row alone until then; the watcher's claim\n-- query gates on this so a parked row doesn't waste the slot while\n-- other agentruns (potentially using different models) can still run.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS not_before timestamptz;\n-- Resolved model id for this agentrun, of the form\n-- `<provider>/<modelId>` (e.g. `featherless/zai-org/GLM-5.1`).\n-- Populated by AgentRunner once it builds the model client; stays\n-- NULL for any agentrun that never reached generate() (failed at\n-- handler-load time, etc.).\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS model text;\n-- Resolved prompt (rendered prompt template: system prompt, then\n-- user-message head) used for this agentrun. Populated by AgentRunner\n-- only when core.logSystemPrompt is true; otherwise NULL. Surfaced\n-- by the report layer's Agentrun Start section when the consumer\n-- opts into withDetails.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS system_prompt text;\n-- Verbatim model-message array assembled for the first agent.generate()\n-- call (prior history + the run's leading user turn). Populated by\n-- AgentRunner only when inference.captureInitialMessageHistory is true;\n-- otherwise NULL. Surfaced by the report layer's -vv view so an operator\n-- can see exactly what was sent into the model loop.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS initial_messages jsonb;\n\n-- Distinguishes how a child agentrun was spawned.\n-- NULL \u2014 root agentrun (created by the input-event watcher, no parent).\n-- 'scheduled' \u2014 fire-and-forget child spawned by `schedule_subagent` without\n-- a future `when` (immediate dispatch under the same event).\n-- 'started' \u2014 synchronous child spawned via the `start_subagent` tool; the\n-- parent is parked in state='waiting' until this row settles.\n-- The Scheduler reads this column to decide whether settling a row should\n-- wake a waiting parent.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS calltype text;\nALTER TABLE agentruns DROP CONSTRAINT IF EXISTS agentruns_calltype_format;\nALTER TABLE agentruns ADD CONSTRAINT agentruns_calltype_format\n CHECK (calltype IS NULL OR calltype IN ('scheduled','started'));\n\n-- Partial index for the \"are all started children of $parent settled?\"\n-- check the Scheduler runs after every 'started' child settles. Indexes\n-- only the rows that actually participate in the question.\nCREATE INDEX IF NOT EXISTS agentruns_parent_calltype_state_idx\n ON agentruns (parent_agentrun_id, state)\n WHERE calltype = 'started';\n\n-- Forward-compat: relax the topic CHECK constraint to allow arbitrarily\n-- deep `a:b:c:\u2026` topics. CREATE TABLE IF NOT EXISTS above doesn't\n-- update existing constraints, so we drop+re-add unconditionally. The\n-- DROP IF EXISTS / ADD pair is idempotent.\nALTER TABLE events DROP CONSTRAINT IF EXISTS events_topic_format;\nALTER TABLE events ADD CONSTRAINT events_topic_format CHECK (topic ~ '^[\\w-]+(:[\\w-]+)*$');\nALTER TABLE agentruns DROP CONSTRAINT IF EXISTS agentruns_topic_format;\nALTER TABLE agentruns ADD CONSTRAINT agentruns_topic_format CHECK (topic ~ '^[\\w-]+(:[\\w-]+)*$');\n\nCREATE INDEX IF NOT EXISTS agentruns_state_priority_idx\n ON agentruns (state, priority DESC, id ASC);\nCREATE INDEX IF NOT EXISTS agentruns_event_id_idx ON agentruns (event_id);\nCREATE INDEX IF NOT EXISTS agentruns_parent_idx ON agentruns (parent_agentrun_id);\n\nCREATE TABLE IF NOT EXISTS stepresults (\n id bigserial PRIMARY KEY,\n agent_run_id bigint NOT NULL REFERENCES agentruns(id) ON DELETE CASCADE,\n event_id bigint NOT NULL REFERENCES events(id) ON DELETE CASCADE,\n step_number int NOT NULL,\n finish_reason text NOT NULL,\n result_text text,\n reasoning_text text,\n input_tokens int,\n output_tokens int,\n total_tokens int,\n tool_call_count int NOT NULL DEFAULT 0,\n tool_calls jsonb NOT NULL DEFAULT '[]'::jsonb,\n tool_results jsonb NOT NULL DEFAULT '[]'::jsonb,\n created_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT stepresults_step_number_unique UNIQUE (agent_run_id, step_number)\n);\n\nCREATE INDEX IF NOT EXISTS stepresults_agent_run_id_idx ON stepresults (agent_run_id);\nCREATE INDEX IF NOT EXISTS stepresults_event_id_idx ON stepresults (event_id);\n\n-- AI SDK per-step usage breakdown\n-- (LanguageModelUsage.inputTokenDetails / outputTokenDetails). Each\n-- column is NULL when the provider doesn't report that detail.\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS input_tokens_no_cache int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS input_tokens_cache_read int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS input_tokens_cache_write int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS output_tokens_text int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS output_tokens_reasoning int;\n-- Full step object snapshot, populated only when the operator opts\n-- in via inference.captureRawStepResultToDatabase: true. Used to\n-- inspect provider-specific fields (Anthropic cache control,\n-- OpenAI logprobs, ...) that don't fit dedicated columns.\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS raw_result jsonb;\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 chatmessages \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Persistent chat history across all channels. Each row links to the\n-- event whose lifecycle the message belongs to:\n-- * For role='user': the event the host plugin emitted with isChat=true.\n-- * For role='assistant': the event whose agentrun called send_chat.\n-- The channel is reachable via JOIN to events.preferred_chat_channel_id;\n-- there is intentionally no channel_id column here \u2014 channel routing is\n-- a host-side concern, the container never names a channel.\n--\n-- delivered_at is set when ANY listener returns true from its async\n-- handler. New subscribers replay all undelivered matching rows on\n-- registration so a message produced when nobody was listening is still\n-- delivered later.\nCREATE TABLE IF NOT EXISTS chatmessages (\n id bigserial PRIMARY KEY,\n event_id bigint NOT NULL REFERENCES events(id) ON DELETE CASCADE,\n role text NOT NULL CHECK (role IN ('user','assistant')),\n text_content text NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n delivered_at timestamptz\n);\n\nCREATE INDEX IF NOT EXISTS chatmessages_event_idx\n ON chatmessages (event_id);\nCREATE INDEX IF NOT EXISTS chatmessages_undelivered_idx\n ON chatmessages (created_at) WHERE delivered_at IS NULL;\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 NOTIFY triggers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nCREATE OR REPLACE FUNCTION events_notify_new() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify('events_new', NEW.id::text);\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS events_new_trg ON events;\nCREATE TRIGGER events_new_trg\n AFTER INSERT ON events\n FOR EACH ROW EXECUTE FUNCTION events_notify_new();\n\nCREATE OR REPLACE FUNCTION events_notify_state() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify('events_state', NEW.id::text || ':' || NEW.state);\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS events_state_trg ON events;\nCREATE TRIGGER events_state_trg\n AFTER UPDATE OF state ON events\n FOR EACH ROW EXECUTE FUNCTION events_notify_state();\n\nCREATE OR REPLACE FUNCTION agentruns_notify_changed() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify(\n 'agentruns_changed',\n NEW.event_id::text || ':' || NEW.id::text\n );\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS agentruns_notify_changed_trg ON agentruns;\nCREATE TRIGGER agentruns_notify_changed_trg\n AFTER INSERT OR UPDATE OF state ON agentruns\n FOR EACH ROW EXECUTE FUNCTION agentruns_notify_changed();\n\nCREATE OR REPLACE FUNCTION stepresults_notify_new() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify(\n 'stepresults_new',\n NEW.event_id::text || ':' || NEW.agent_run_id::text || ':' || NEW.id::text\n );\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS stepresults_new_trg ON stepresults;\nCREATE TRIGGER stepresults_new_trg\n AFTER INSERT ON stepresults\n FOR EACH ROW EXECUTE FUNCTION stepresults_notify_new();\n\n-- chatmessages NOTIFY: payload is \"<channel_id>:<role>:<id>\". Channel\n-- comes from the parent event's preferred_chat_channel_id, JOINed at\n-- trigger time. Empty channel becomes empty prefix \u2014 listeners will\n-- filter it out unless they explicitly subscribe to channelId=\"\".\nCREATE OR REPLACE FUNCTION chatmessages_notify_new() RETURNS trigger AS $$\nDECLARE\n channel text;\nBEGIN\n SELECT preferred_chat_channel_id INTO channel FROM events WHERE id = NEW.event_id;\n PERFORM pg_notify(\n 'chatmessages_new',\n COALESCE(channel, '') || ':' || NEW.role || ':' || NEW.id::text\n );\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS chatmessages_new_trg ON chatmessages;\nCREATE TRIGGER chatmessages_new_trg\n AFTER INSERT ON chatmessages\n FOR EACH ROW EXECUTE FUNCTION chatmessages_notify_new();\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 calendars + calendar_events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Plugin-agnostic calendar data layer. `calendars` lists every\n-- subscription (own or shared) any provider plugin (ms365, future\n-- google/caldav) has surfaced; `calendar_events` stores every\n-- instance. Providers persist every occurrence of a recurring\n-- series independently (no master-expansion in core).\n--\n-- Identity:\n-- * `calendars.unique_key` is the provider's stable id. Uniqueness\n-- within `(plugin_id, unique_key)` lets multiple providers\n-- coexist.\n-- * `calendar_events.id` is provider-prefixed (e.g. `ms365:<graph-id>`)\n-- so collisions across providers are impossible by construction.\n--\n-- Refresh reconciliation uses a scan-generation tag: a refresh walk\n-- bumps `calendars.scan_generation`, upserts every event in window\n-- with the new value, then DELETEs events whose generation is older\n-- than the calendar's current value.\n\nCREATE TABLE IF NOT EXISTS calendars (\n id bigserial PRIMARY KEY,\n plugin_id text NOT NULL,\n unique_key text NOT NULL,\n name text NOT NULL,\n type text NOT NULL CHECK (type IN ('own','shared')),\n owner_name text,\n scan_generation int NOT NULL DEFAULT 0,\n created_at timestamptz NOT NULL DEFAULT now(),\n UNIQUE (plugin_id, unique_key)\n);\n\n-- The provider's own \"default calendar\" flag (Graph's\n-- `isDefaultCalendar`). Used by `CalendarService.resolveDefaultCalendar`\n-- as the fallback when `core.defaultCalendar` is unset \u2014 we pick the\n-- first registered calendar (lowest id) flagged here. Added via\n-- ALTER so existing databases don't need a schema reset.\nALTER TABLE calendars ADD COLUMN IF NOT EXISTS is_default smallint NOT NULL DEFAULT 0;\n\nCREATE INDEX IF NOT EXISTS calendars_plugin_id_idx ON calendars (plugin_id);\n\nCREATE TABLE IF NOT EXISTS calendar_events (\n id text PRIMARY KEY,\n calendar_id bigint NOT NULL REFERENCES calendars(id) ON DELETE CASCADE,\n series_master_id text,\n type text NOT NULL CHECK (type IN ('singleInstance','occurrence','exception','seriesMaster')),\n subject text,\n start_dt text NOT NULL,\n end_dt text NOT NULL,\n event_tz text,\n is_all_day smallint NOT NULL DEFAULT 0,\n is_cancelled smallint NOT NULL DEFAULT 0,\n show_as text,\n sensitivity text,\n importance text,\n location text,\n is_online_meeting smallint NOT NULL DEFAULT 0,\n online_meeting_url text,\n organizer_name text,\n organizer_email text,\n response_status text,\n attendees_json jsonb,\n body text,\n attachments jsonb,\n scan_generation int NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS calendar_events_window_idx\n ON calendar_events (calendar_id, start_dt);\nCREATE INDEX IF NOT EXISTS calendar_events_series_idx\n ON calendar_events (series_master_id);\nCREATE INDEX IF NOT EXISTS calendar_events_generation_idx\n ON calendar_events (calendar_id, scan_generation);\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 scheduled_subagents \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- One-off agent-scheduled wake-ups. Each row is upserted by the\n-- container's `schedule_subagent` tool and read by the host-side\n-- `ScheduledSubagentScheduler`, which installs a Croner job per row\n-- and atomically deletes the row when it fires. `key` is the\n-- agent-supplied unique id used for replace-on-conflict semantics\n-- (\"rescheduling a key overwrites the previous timing\"). `fire_at`\n-- is the UTC firing time; the agent passes wall-clock-in-`core.timezone`\n-- and the container projects to UTC before insert. On daemon startup,\n-- past-due rows are deleted with a warn log; future rows are\n-- reinstalled. See [[feedback_agent_facing_format]] \u2014 UTC storage,\n-- local-tz projection at every agent-facing edge.\nCREATE TABLE IF NOT EXISTS scheduled_subagents (\n key text PRIMARY KEY,\n fire_at timestamptz NOT NULL,\n topic text NOT NULL,\n prompt text,\n payload jsonb NOT NULL DEFAULT '{}'::jsonb,\n priority smallint NOT NULL DEFAULT 50,\n privileged boolean NOT NULL DEFAULT false,\n created_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT scheduled_subagents_topic_format CHECK (topic ~ '^[\\w-]+(:[\\w-]+)*$')\n);\n\n-- The topic is the handler's whole address; the basename column is gone.\nALTER TABLE scheduled_subagents DROP COLUMN IF EXISTS handler;\n-- Re-add the topic CHECK so a widened TOPIC_PATTERN reaches existing tables.\nALTER TABLE scheduled_subagents DROP CONSTRAINT IF EXISTS scheduled_subagents_topic_format;\nALTER TABLE scheduled_subagents ADD CONSTRAINT scheduled_subagents_topic_format CHECK (topic ~ '^[\\w-]+(:[\\w-]+)*$');\n\nCREATE INDEX IF NOT EXISTS scheduled_subagents_fire_at_idx\n ON scheduled_subagents (fire_at);\n\nCREATE OR REPLACE FUNCTION scheduled_subagents_notify_changed() RETURNS trigger AS $$\nDECLARE\n payload_key text;\n op text;\nBEGIN\n IF TG_OP = 'DELETE' THEN\n payload_key := OLD.key;\n op := 'd';\n ELSE\n payload_key := NEW.key;\n op := 'u';\n END IF;\n PERFORM pg_notify('scheduled_subagents_changed', payload_key || ':' || op);\n RETURN COALESCE(NEW, OLD);\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS scheduled_subagents_notify_changed_trg ON scheduled_subagents;\nCREATE TRIGGER scheduled_subagents_notify_changed_trg\n AFTER INSERT OR UPDATE OR DELETE ON scheduled_subagents\n FOR EACH ROW EXECUTE FUNCTION scheduled_subagents_notify_changed();\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 inference_events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Append-only audit trail of every upstream model HTTP call attempt.\n-- Written by AgentRunner on each generate() outcome (success / retryable /\n-- fatal). Read by the host-side `inference_status` reflection tool to\n-- answer \"is this model misbehaving right now?\". One row per attempt;\n-- retries write multiple rows. NULL agent_run_id is kept for runs whose\n-- row was later deleted (parent event cascade) so the historical signal\n-- survives.\nCREATE TABLE IF NOT EXISTS inference_events (\n id bigserial PRIMARY KEY,\n provider text NOT NULL,\n model text NOT NULL,\n agent_run_id bigint REFERENCES agentruns(id) ON DELETE SET NULL,\n outcome text NOT NULL CHECK (outcome IN ('success','retryable','fatal')),\n status_code int,\n error_excerpt text,\n occurred_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS inference_events_model_time_idx\n ON inference_events (model, occurred_at DESC);\nCREATE INDEX IF NOT EXISTS inference_events_occurred_at_idx\n ON inference_events (occurred_at DESC);\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 tool_calls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Append-only record of every tool invocation an agentrun attempted,\n-- tagged with the resolved handler markdown path and whether the call\n-- threw. Written by the tool wrapper in the container's ToolsFactory on\n-- each execute() outcome. Read by the heuristic tool-preloader\n-- (ToolCallBus.topToolsForHandler): the tools a handler successfully\n-- used across its recent non-error runs get loaded up front on the next\n-- run, so warm handlers rarely need a tool_list / tool_call round-trip.\n-- NULL agent_run_id survives a parent-event cascade so the historical\n-- usage signal outlives the run row.\nCREATE TABLE IF NOT EXISTS tool_calls (\n id bigserial PRIMARY KEY,\n agent_run_id bigint REFERENCES agentruns(id) ON DELETE SET NULL,\n handler_path text NOT NULL,\n tool_name text NOT NULL,\n successful boolean NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS tool_calls_handler_run_idx\n ON tool_calls (handler_path, agent_run_id DESC);\nCREATE INDEX IF NOT EXISTS tool_calls_created_at_idx\n ON tool_calls (created_at DESC);\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 web_login_tokens / web_sessions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Authentication state of the host webserver (host-only; the container\n-- never reads these). A login token is a one-time link minted by\n-- `familiar web login`; redeeming it deletes the row and creates a\n-- session. Only sha256 hashes of tokens are stored, never the tokens.\nCREATE TABLE IF NOT EXISTS web_login_tokens (\n token_hash text PRIMARY KEY,\n login text NOT NULL,\n expires_at timestamptz NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE TABLE IF NOT EXISTS web_sessions (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n token_hash text NOT NULL UNIQUE,\n login text NOT NULL,\n user_agent text,\n created_at timestamptz NOT NULL DEFAULT now(),\n last_seen_at timestamptz NOT NULL DEFAULT now(),\n expires_at timestamptz NOT NULL\n);\n";
|
|
65
|
+
export declare const SCHEMA_SQL = "\n-- Drop legacy single-table artifacts from before the events/agentruns\n-- split. Safe no-ops on fresh databases. The current schema reuses the\n-- name events_notify_new() for its own NOTIFY function, so we do NOT\n-- drop it here (CREATE OR REPLACE FUNCTION below handles the update);\n-- dropping would fail anyway once the current events_new_trg exists.\nDROP TRIGGER IF EXISTS events_notify_changed_trg ON events;\nDROP TRIGGER IF EXISTS events_notify_new_trg ON events;\nDROP FUNCTION IF EXISTS events_notify_changed();\nDROP INDEX IF EXISTS events_pending_priority_idx;\nDROP INDEX IF EXISTS events_state_priority_idx;\n\nCREATE TABLE IF NOT EXISTS events (\n id bigserial PRIMARY KEY,\n topic text NOT NULL,\n priority smallint NOT NULL DEFAULT 50,\n state text NOT NULL DEFAULT 'pending',\n payload jsonb NOT NULL DEFAULT '{}'::jsonb,\n idempotency_key text UNIQUE,\n privileged boolean NOT NULL DEFAULT false,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT events_topic_format CHECK (topic ~ '^[\\w-]+(:[\\w-]+)*$')\n);\n\n-- Drop legacy columns from earlier schema generations. Single-instance\n-- project; column drops are fine.\nALTER TABLE events DROP COLUMN IF EXISTS supervisor_prompt;\nALTER TABLE events DROP COLUMN IF EXISTS causation_chain;\n\n-- Chat-aware event metadata. `is_chat` opts into automatic user-message\n-- persistence in EventBus.add; `preferred_chat_channel_id` names the\n-- channel an assistant reply should reach the user on. The container is\n-- channel-blind \u2014 both fields are read host-side (for routing) or via\n-- JOIN inside the chatmessages NOTIFY trigger.\nALTER TABLE events ADD COLUMN IF NOT EXISTS is_chat boolean NOT NULL DEFAULT false;\nALTER TABLE events ADD COLUMN IF NOT EXISTS preferred_chat_channel_id text;\n-- Required for non-chat events (validated at `EventBus.add` time and at\n-- the type level via `NewEvent`'s discriminated union); always null for\n-- chat events because the user's text in `chatmessages` already serves\n-- as the agent-visible \"what happened\" message. The EventWatcher copies\n-- this verbatim into the root `agentruns.prompt` so the agent sees the\n-- emitter's framing without the EventWatcher having to reach into the\n-- payload schema.\nALTER TABLE events ADD COLUMN IF NOT EXISTS prompt text;\n-- The topic is the handler's whole address now; the separate start\n-- handler basename is gone.\nALTER TABLE events DROP COLUMN IF EXISTS start_handler;\n-- When true the topic must name an existing handler file exactly; when\n-- false (world events) handler resolution falls back up the topic chain.\n-- Copied onto the root agentrun by the input-event watcher.\nALTER TABLE events ADD COLUMN IF NOT EXISTS is_exact_topic boolean NOT NULL DEFAULT false;\n-- Privileged events originate from a trusted user-input source (operator\n-- at the local terminal via cli-chat, operator on Telegram). The flag\n-- propagates verbatim to the root agentrun and to every child agentrun\n-- spawned via schedule_subagent / start_subagent, so future system tools can\n-- gate risky reads / writes (editing SOUL.md etc.) on whether the run\n-- descends from a trusted input. Default false: any plugin that doesn't\n-- explicitly opt in produces non-privileged events.\nALTER TABLE events ADD COLUMN IF NOT EXISTS privileged boolean NOT NULL DEFAULT false;\n-- When true and the event ends in 'failed', `HostContextImpl.emitAndAwait`\n-- writes a role='assistant' chatmessage with the failure text before\n-- rejecting handle.settled. Default false: only chat plugins (cli-chat,\n-- telegram) opt in. See [[outputChatOnFailure]] on NewEvent for the\n-- rationale.\nALTER TABLE events ADD COLUMN IF NOT EXISTS output_chat_on_failure boolean NOT NULL DEFAULT false;\n\nCREATE INDEX IF NOT EXISTS events_state_priority_idx\n ON events (state, priority DESC, id ASC);\nCREATE INDEX IF NOT EXISTS events_topic_idx ON events (topic);\n\nCREATE TABLE IF NOT EXISTS agentruns (\n id bigserial PRIMARY KEY,\n event_id bigint NOT NULL REFERENCES events(id) ON DELETE CASCADE,\n parent_agentrun_id bigint REFERENCES agentruns(id) ON DELETE CASCADE,\n topic text NOT NULL,\n priority smallint NOT NULL DEFAULT 50,\n state text NOT NULL DEFAULT 'pending',\n prompt text,\n payload jsonb NOT NULL DEFAULT '{}'::jsonb,\n result jsonb,\n result_text text,\n error text,\n privileged boolean NOT NULL DEFAULT false,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT agentruns_topic_format CHECK (topic ~ '^[\\w-]+(:[\\w-]+)*$')\n);\n\n-- Forward-compat ALTER for databases created before result_text landed.\n-- Safe no-op on fresh databases where the column already exists.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS result_text text;\n-- The topic is the handler's whole address; the basename column is gone.\nALTER TABLE agentruns DROP COLUMN IF EXISTS handler;\n-- Whether handler resolution must match the topic exactly. Root\n-- agentruns copy the event's flag; spawned children are always exact.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS is_exact_topic boolean NOT NULL DEFAULT true;\n-- Inherited from the originating event (root agentrun) or parent\n-- agentrun (children spawned via schedule_subagent / start_subagent). See\n-- the matching ALTER on the events table for the trust-model rationale.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS privileged boolean NOT NULL DEFAULT false;\n-- Number of retry attempts already made on this agentrun. Bumped each\n-- time AgentRunner postpones the row due to a retryable inference\n-- error (APICallError.isRetryable). Capped per-handler via maxRetries\n-- (frontmatter override) or globally via inference.maxRetries.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS retry_count int NOT NULL DEFAULT 0;\n-- Earliest moment the agentrun watcher is allowed to re-claim this\n-- row. NULL = claim immediately (default for fresh rows). A future\n-- timestamp = leave the row alone until then; the watcher's claim\n-- query gates on this so a parked row doesn't waste the slot while\n-- other agentruns (potentially using different models) can still run.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS not_before timestamptz;\n-- Resolved model id for this agentrun, of the form\n-- `<provider>/<modelId>` (e.g. `featherless/zai-org/GLM-5.1`).\n-- Populated by AgentRunner once it builds the model client; stays\n-- NULL for any agentrun that never reached generate() (failed at\n-- handler-load time, etc.).\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS model text;\n-- Resolved prompt (rendered prompt template: system prompt, then\n-- user-message head) used for this agentrun. Populated by AgentRunner\n-- only when core.logSystemPrompt is true; otherwise NULL. Surfaced\n-- by the report layer's Agentrun Start section when the consumer\n-- opts into withDetails.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS system_prompt text;\n-- Verbatim model-message array assembled for the first agent.generate()\n-- call (prior history + the run's leading user turn). Populated by\n-- AgentRunner only when inference.captureInitialMessageHistory is true;\n-- otherwise NULL. Surfaced by the report layer's -vv view so an operator\n-- can see exactly what was sent into the model loop.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS initial_messages jsonb;\n\n-- Distinguishes how a child agentrun was spawned.\n-- NULL \u2014 root agentrun (created by the input-event watcher, no parent).\n-- 'scheduled' \u2014 fire-and-forget child spawned by `schedule_subagent` without\n-- a future `when` (immediate dispatch under the same event).\n-- 'started' \u2014 synchronous child spawned via the `start_subagent` tool; the\n-- parent is parked in state='waiting' until this row settles.\n-- The Scheduler reads this column to decide whether settling a row should\n-- wake a waiting parent.\nALTER TABLE agentruns ADD COLUMN IF NOT EXISTS calltype text;\nALTER TABLE agentruns DROP CONSTRAINT IF EXISTS agentruns_calltype_format;\nALTER TABLE agentruns ADD CONSTRAINT agentruns_calltype_format\n CHECK (calltype IS NULL OR calltype IN ('scheduled','started'));\n\n-- Partial index for the \"are all started children of $parent settled?\"\n-- check the Scheduler runs after every 'started' child settles. Indexes\n-- only the rows that actually participate in the question.\nCREATE INDEX IF NOT EXISTS agentruns_parent_calltype_state_idx\n ON agentruns (parent_agentrun_id, state)\n WHERE calltype = 'started';\n\n-- Forward-compat: relax the topic CHECK constraint to allow arbitrarily\n-- deep `a:b:c:\u2026` topics. CREATE TABLE IF NOT EXISTS above doesn't\n-- update existing constraints, so we drop+re-add unconditionally. The\n-- DROP IF EXISTS / ADD pair is idempotent.\nALTER TABLE events DROP CONSTRAINT IF EXISTS events_topic_format;\nALTER TABLE events ADD CONSTRAINT events_topic_format CHECK (topic ~ '^[\\w-]+(:[\\w-]+)*$');\nALTER TABLE agentruns DROP CONSTRAINT IF EXISTS agentruns_topic_format;\nALTER TABLE agentruns ADD CONSTRAINT agentruns_topic_format CHECK (topic ~ '^[\\w-]+(:[\\w-]+)*$');\n\nCREATE INDEX IF NOT EXISTS agentruns_state_priority_idx\n ON agentruns (state, priority DESC, id ASC);\nCREATE INDEX IF NOT EXISTS agentruns_event_id_idx ON agentruns (event_id);\nCREATE INDEX IF NOT EXISTS agentruns_parent_idx ON agentruns (parent_agentrun_id);\n\nCREATE TABLE IF NOT EXISTS stepresults (\n id bigserial PRIMARY KEY,\n agent_run_id bigint NOT NULL REFERENCES agentruns(id) ON DELETE CASCADE,\n event_id bigint NOT NULL REFERENCES events(id) ON DELETE CASCADE,\n step_number int NOT NULL,\n finish_reason text NOT NULL,\n result_text text,\n reasoning_text text,\n input_tokens int,\n output_tokens int,\n total_tokens int,\n tool_call_count int NOT NULL DEFAULT 0,\n tool_calls jsonb NOT NULL DEFAULT '[]'::jsonb,\n tool_results jsonb NOT NULL DEFAULT '[]'::jsonb,\n created_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT stepresults_step_number_unique UNIQUE (agent_run_id, step_number)\n);\n\nCREATE INDEX IF NOT EXISTS stepresults_agent_run_id_idx ON stepresults (agent_run_id);\nCREATE INDEX IF NOT EXISTS stepresults_event_id_idx ON stepresults (event_id);\n\n-- AI SDK per-step usage breakdown\n-- (LanguageModelUsage.inputTokenDetails / outputTokenDetails). Each\n-- column is NULL when the provider doesn't report that detail.\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS input_tokens_no_cache int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS input_tokens_cache_read int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS input_tokens_cache_write int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS output_tokens_text int;\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS output_tokens_reasoning int;\n-- Full step object snapshot, populated only when the operator opts\n-- in via inference.captureRawStepResultToDatabase: true. Used to\n-- inspect provider-specific fields (Anthropic cache control,\n-- OpenAI logprobs, ...) that don't fit dedicated columns.\nALTER TABLE stepresults ADD COLUMN IF NOT EXISTS raw_result jsonb;\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 chatmessages \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Persistent chat history across all channels. Each row links to the\n-- event whose lifecycle the message belongs to:\n-- * For role='user': the event the host plugin emitted with isChat=true.\n-- * For role='assistant': the event whose agentrun called send_chat.\n-- The channel is reachable via JOIN to events.preferred_chat_channel_id;\n-- there is intentionally no channel_id column here \u2014 channel routing is\n-- a host-side concern, the container never names a channel.\n--\n-- delivered_at is set when ANY listener returns true from its async\n-- handler. New subscribers replay all undelivered matching rows on\n-- registration so a message produced when nobody was listening is still\n-- delivered later.\nCREATE TABLE IF NOT EXISTS chatmessages (\n id bigserial PRIMARY KEY,\n event_id bigint NOT NULL REFERENCES events(id) ON DELETE CASCADE,\n role text NOT NULL CHECK (role IN ('user','assistant')),\n text_content text NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n delivered_at timestamptz\n);\n\nCREATE INDEX IF NOT EXISTS chatmessages_event_idx\n ON chatmessages (event_id);\nCREATE INDEX IF NOT EXISTS chatmessages_undelivered_idx\n ON chatmessages (created_at) WHERE delivered_at IS NULL;\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 NOTIFY triggers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nCREATE OR REPLACE FUNCTION events_notify_new() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify('events_new', NEW.id::text);\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS events_new_trg ON events;\nCREATE TRIGGER events_new_trg\n AFTER INSERT ON events\n FOR EACH ROW EXECUTE FUNCTION events_notify_new();\n\nCREATE OR REPLACE FUNCTION events_notify_state() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify('events_state', NEW.id::text || ':' || NEW.state);\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS events_state_trg ON events;\nCREATE TRIGGER events_state_trg\n AFTER UPDATE OF state ON events\n FOR EACH ROW EXECUTE FUNCTION events_notify_state();\n\nCREATE OR REPLACE FUNCTION agentruns_notify_changed() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify(\n 'agentruns_changed',\n NEW.event_id::text || ':' || NEW.id::text\n );\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS agentruns_notify_changed_trg ON agentruns;\nCREATE TRIGGER agentruns_notify_changed_trg\n AFTER INSERT OR UPDATE OF state ON agentruns\n FOR EACH ROW EXECUTE FUNCTION agentruns_notify_changed();\n\nCREATE OR REPLACE FUNCTION stepresults_notify_new() RETURNS trigger AS $$\nBEGIN\n PERFORM pg_notify(\n 'stepresults_new',\n NEW.event_id::text || ':' || NEW.agent_run_id::text || ':' || NEW.id::text\n );\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS stepresults_new_trg ON stepresults;\nCREATE TRIGGER stepresults_new_trg\n AFTER INSERT ON stepresults\n FOR EACH ROW EXECUTE FUNCTION stepresults_notify_new();\n\n-- chatmessages NOTIFY: payload is \"<channel_id>:<role>:<id>\". Channel\n-- comes from the parent event's preferred_chat_channel_id, JOINed at\n-- trigger time. Empty channel becomes empty prefix \u2014 listeners will\n-- filter it out unless they explicitly subscribe to channelId=\"\".\nCREATE OR REPLACE FUNCTION chatmessages_notify_new() RETURNS trigger AS $$\nDECLARE\n channel text;\nBEGIN\n SELECT preferred_chat_channel_id INTO channel FROM events WHERE id = NEW.event_id;\n PERFORM pg_notify(\n 'chatmessages_new',\n COALESCE(channel, '') || ':' || NEW.role || ':' || NEW.id::text\n );\n RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS chatmessages_new_trg ON chatmessages;\nCREATE TRIGGER chatmessages_new_trg\n AFTER INSERT ON chatmessages\n FOR EACH ROW EXECUTE FUNCTION chatmessages_notify_new();\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 calendars + calendar_events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Plugin-agnostic calendar data layer. `calendars` lists every\n-- subscription (own or shared) any provider plugin (ms365, future\n-- google/caldav) has surfaced; `calendar_events` stores every\n-- instance. Providers persist every occurrence of a recurring\n-- series independently (no master-expansion in core).\n--\n-- Identity:\n-- * `calendars.unique_key` is the provider's stable id. Uniqueness\n-- within `(plugin_id, unique_key)` lets multiple providers\n-- coexist.\n-- * `calendar_events.id` is provider-prefixed (e.g. `ms365:<graph-id>`)\n-- so collisions across providers are impossible by construction.\n--\n-- Refresh reconciliation uses a scan-generation tag: a refresh walk\n-- bumps `calendars.scan_generation`, upserts every event in window\n-- with the new value, then DELETEs events whose generation is older\n-- than the calendar's current value.\n\nCREATE TABLE IF NOT EXISTS calendars (\n id bigserial PRIMARY KEY,\n plugin_id text NOT NULL,\n unique_key text NOT NULL,\n name text NOT NULL,\n type text NOT NULL CHECK (type IN ('own','shared')),\n owner_name text,\n scan_generation int NOT NULL DEFAULT 0,\n created_at timestamptz NOT NULL DEFAULT now(),\n UNIQUE (plugin_id, unique_key)\n);\n\n-- The provider's own \"default calendar\" flag (Graph's\n-- `isDefaultCalendar`). Used by `CalendarService.resolveDefaultCalendar`\n-- as the fallback when `core.defaultCalendar` is unset \u2014 we pick the\n-- first registered calendar (lowest id) flagged here. Added via\n-- ALTER so existing databases don't need a schema reset.\nALTER TABLE calendars ADD COLUMN IF NOT EXISTS is_default smallint NOT NULL DEFAULT 0;\n\nCREATE INDEX IF NOT EXISTS calendars_plugin_id_idx ON calendars (plugin_id);\n\nCREATE TABLE IF NOT EXISTS calendar_events (\n id text PRIMARY KEY,\n calendar_id bigint NOT NULL REFERENCES calendars(id) ON DELETE CASCADE,\n series_master_id text,\n type text NOT NULL CHECK (type IN ('singleInstance','occurrence','exception','seriesMaster')),\n subject text,\n start_dt text NOT NULL,\n end_dt text NOT NULL,\n event_tz text,\n is_all_day smallint NOT NULL DEFAULT 0,\n is_cancelled smallint NOT NULL DEFAULT 0,\n show_as text,\n sensitivity text,\n importance text,\n location text,\n is_online_meeting smallint NOT NULL DEFAULT 0,\n online_meeting_url text,\n organizer_name text,\n organizer_email text,\n response_status text,\n attendees_json jsonb,\n body text,\n attachments jsonb,\n scan_generation int NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now(),\n updated_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS calendar_events_window_idx\n ON calendar_events (calendar_id, start_dt);\nCREATE INDEX IF NOT EXISTS calendar_events_series_idx\n ON calendar_events (series_master_id);\nCREATE INDEX IF NOT EXISTS calendar_events_generation_idx\n ON calendar_events (calendar_id, scan_generation);\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 scheduled_subagents \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- One-off agent-scheduled wake-ups. Each row is upserted by the\n-- container's `schedule_subagent` tool and read by the host-side\n-- `ScheduledSubagentScheduler`, which installs a Croner job per row\n-- and atomically deletes the row when it fires. `key` is the\n-- agent-supplied unique id used for replace-on-conflict semantics\n-- (\"rescheduling a key overwrites the previous timing\"). `fire_at`\n-- is the UTC firing time; the agent passes wall-clock-in-`core.timezone`\n-- and the container projects to UTC before insert. On daemon startup,\n-- past-due rows are deleted with a warn log; future rows are\n-- reinstalled. See [[feedback_agent_facing_format]] \u2014 UTC storage,\n-- local-tz projection at every agent-facing edge.\nCREATE TABLE IF NOT EXISTS scheduled_subagents (\n key text PRIMARY KEY,\n fire_at timestamptz NOT NULL,\n topic text NOT NULL,\n prompt text,\n payload jsonb NOT NULL DEFAULT '{}'::jsonb,\n priority smallint NOT NULL DEFAULT 50,\n privileged boolean NOT NULL DEFAULT false,\n created_at timestamptz NOT NULL DEFAULT now(),\n CONSTRAINT scheduled_subagents_topic_format CHECK (topic ~ '^[\\w-]+(:[\\w-]+)*$')\n);\n\n-- The topic is the handler's whole address; the basename column is gone.\nALTER TABLE scheduled_subagents DROP COLUMN IF EXISTS handler;\n-- Re-add the topic CHECK so a widened TOPIC_PATTERN reaches existing tables.\nALTER TABLE scheduled_subagents DROP CONSTRAINT IF EXISTS scheduled_subagents_topic_format;\nALTER TABLE scheduled_subagents ADD CONSTRAINT scheduled_subagents_topic_format CHECK (topic ~ '^[\\w-]+(:[\\w-]+)*$');\n\nCREATE INDEX IF NOT EXISTS scheduled_subagents_fire_at_idx\n ON scheduled_subagents (fire_at);\n\nCREATE OR REPLACE FUNCTION scheduled_subagents_notify_changed() RETURNS trigger AS $$\nDECLARE\n payload_key text;\n op text;\nBEGIN\n IF TG_OP = 'DELETE' THEN\n payload_key := OLD.key;\n op := 'd';\n ELSE\n payload_key := NEW.key;\n op := 'u';\n END IF;\n PERFORM pg_notify('scheduled_subagents_changed', payload_key || ':' || op);\n RETURN COALESCE(NEW, OLD);\nEND;\n$$ LANGUAGE plpgsql;\n\nDROP TRIGGER IF EXISTS scheduled_subagents_notify_changed_trg ON scheduled_subagents;\nCREATE TRIGGER scheduled_subagents_notify_changed_trg\n AFTER INSERT OR UPDATE OR DELETE ON scheduled_subagents\n FOR EACH ROW EXECUTE FUNCTION scheduled_subagents_notify_changed();\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 inference_events \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Append-only audit trail of every upstream model HTTP call attempt.\n-- Written by AgentRunner on each generate() outcome (success / retryable /\n-- fatal). Read by the host-side `inference_status` reflection tool to\n-- answer \"is this model misbehaving right now?\". One row per attempt;\n-- retries write multiple rows. NULL agent_run_id is kept for runs whose\n-- row was later deleted (parent event cascade) so the historical signal\n-- survives.\nCREATE TABLE IF NOT EXISTS inference_events (\n id bigserial PRIMARY KEY,\n provider text NOT NULL,\n model text NOT NULL,\n agent_run_id bigint REFERENCES agentruns(id) ON DELETE SET NULL,\n outcome text NOT NULL CHECK (outcome IN ('success','retryable','fatal')),\n status_code int,\n error_excerpt text,\n occurred_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS inference_events_model_time_idx\n ON inference_events (model, occurred_at DESC);\nCREATE INDEX IF NOT EXISTS inference_events_occurred_at_idx\n ON inference_events (occurred_at DESC);\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 tool_calls \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Append-only record of every tool invocation an agentrun attempted,\n-- tagged with the resolved handler markdown path and whether the call\n-- threw. Written by the tool wrapper in the container's ToolsFactory on\n-- each execute() outcome. Read by the heuristic tool-preloader\n-- (ToolCallBus.topToolsForHandler): the tools a handler successfully\n-- used across its recent non-error runs get loaded up front on the next\n-- run, so warm handlers rarely need a tool_list / tool_call round-trip.\n-- NULL agent_run_id survives a parent-event cascade so the historical\n-- usage signal outlives the run row.\nCREATE TABLE IF NOT EXISTS tool_calls (\n id bigserial PRIMARY KEY,\n agent_run_id bigint REFERENCES agentruns(id) ON DELETE SET NULL,\n handler_path text NOT NULL,\n tool_name text NOT NULL,\n successful boolean NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS tool_calls_handler_run_idx\n ON tool_calls (handler_path, agent_run_id DESC);\nCREATE INDEX IF NOT EXISTS tool_calls_created_at_idx\n ON tool_calls (created_at DESC);\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 web_login_tokens / web_sessions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Authentication state of the host webserver (host-only; the container\n-- never reads these). A login token is a one-time link minted by\n-- `familiar web login`; redeeming it deletes the row and creates a\n-- session. Only sha256 hashes of tokens are stored, never the tokens.\nCREATE TABLE IF NOT EXISTS web_login_tokens (\n token_hash text PRIMARY KEY,\n login text NOT NULL,\n expires_at timestamptz NOT NULL,\n created_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE TABLE IF NOT EXISTS web_sessions (\n id uuid PRIMARY KEY DEFAULT gen_random_uuid(),\n token_hash text NOT NULL UNIQUE,\n login text NOT NULL,\n user_agent text,\n created_at timestamptz NOT NULL DEFAULT now(),\n last_seen_at timestamptz NOT NULL DEFAULT now(),\n expires_at timestamptz NOT NULL\n);\n\n-- \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 banners \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n--\n-- Short-lived notes the agent leaves for every later agentrun (e.g. \"the\n-- user is setting up a new phone today\"). Active banners are rendered\n-- into each prompt via the core {BANNER_LIST} placeholder; a banner\n-- disappears once `expires_at` passes (expired rows are deleted lazily\n-- by `BannerBus` on read / create). The created_by_* columns are the\n-- audit trail: which run (and topic) left the note.\nCREATE TABLE IF NOT EXISTS banners (\n id bigserial PRIMARY KEY,\n text text NOT NULL,\n expires_at timestamptz NOT NULL,\n created_by_agentrun_id bigint REFERENCES agentruns(id) ON DELETE SET NULL,\n created_by_topic text,\n created_at timestamptz NOT NULL DEFAULT now()\n);\n\nCREATE INDEX IF NOT EXISTS banners_expires_at_idx\n ON banners (expires_at);\n";
|
|
66
66
|
/**
|
|
67
67
|
* SQL fragment that flips the parent event's terminal state when the
|
|
68
68
|
* **root** agentrun settles. Run inside the same transaction as the
|
package/build/Schema.js
CHANGED
|
@@ -572,6 +572,26 @@ CREATE TABLE IF NOT EXISTS web_sessions (
|
|
|
572
572
|
last_seen_at timestamptz NOT NULL DEFAULT now(),
|
|
573
573
|
expires_at timestamptz NOT NULL
|
|
574
574
|
);
|
|
575
|
+
|
|
576
|
+
-- ───────── banners ─────────
|
|
577
|
+
--
|
|
578
|
+
-- Short-lived notes the agent leaves for every later agentrun (e.g. "the
|
|
579
|
+
-- user is setting up a new phone today"). Active banners are rendered
|
|
580
|
+
-- into each prompt via the core {BANNER_LIST} placeholder; a banner
|
|
581
|
+
-- disappears once \`expires_at\` passes (expired rows are deleted lazily
|
|
582
|
+
-- by \`BannerBus\` on read / create). The created_by_* columns are the
|
|
583
|
+
-- audit trail: which run (and topic) left the note.
|
|
584
|
+
CREATE TABLE IF NOT EXISTS banners (
|
|
585
|
+
id bigserial PRIMARY KEY,
|
|
586
|
+
text text NOT NULL,
|
|
587
|
+
expires_at timestamptz NOT NULL,
|
|
588
|
+
created_by_agentrun_id bigint REFERENCES agentruns(id) ON DELETE SET NULL,
|
|
589
|
+
created_by_topic text,
|
|
590
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
591
|
+
);
|
|
592
|
+
|
|
593
|
+
CREATE INDEX IF NOT EXISTS banners_expires_at_idx
|
|
594
|
+
ON banners (expires_at);
|
|
575
595
|
`;
|
|
576
596
|
/**
|
|
577
597
|
* SQL fragment that flips the parent event's terminal state when the
|
package/build/WebPlugin.d.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* `/plugins/<plugin-id>`.
|
|
9
9
|
*/
|
|
10
10
|
import type { Hono } from "hono";
|
|
11
|
+
import type { AvatarConfig } from "./AssistantAvatar.js";
|
|
11
12
|
import type { WebPrincipal, WebUiEnv } from "./WebAuth.js";
|
|
12
13
|
/** Where a plugin page's ES module comes from. */
|
|
13
14
|
export interface WebPageModule {
|
|
@@ -124,9 +125,18 @@ export interface WebPageContext {
|
|
|
124
125
|
readonly pathname: string;
|
|
125
126
|
/** The signed-in user. */
|
|
126
127
|
readonly user: WebPrincipal;
|
|
128
|
+
/** The assistant's name and avatar, as configured (render with `@getfamiliar/avatar`). */
|
|
129
|
+
readonly assistant: WebAssistantIdentity;
|
|
127
130
|
/** Navigate the web UI to another route. */
|
|
128
131
|
navigate(to: string): void;
|
|
129
132
|
}
|
|
133
|
+
/** How the assistant presents itself in the web UI. */
|
|
134
|
+
export interface WebAssistantIdentity {
|
|
135
|
+
/** `core.assistantName`, default `Familiar`. */
|
|
136
|
+
readonly name: string;
|
|
137
|
+
/** `core.assistantAvatar`, completed with defaults. */
|
|
138
|
+
readonly avatar: AvatarConfig;
|
|
139
|
+
}
|
|
130
140
|
/**
|
|
131
141
|
* Default export of a page module: render the page into `container`
|
|
132
142
|
* (the web UI's content area) and return a function that unmounts it.
|
package/build/index.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
export type { AgentRunCallType, AgentRunPatch, AgentRunRow, AgentRunState, NewAgentRun, } from "./AgentRun.js";
|
|
2
2
|
export { AgentRunBus, type AgentRunUnsubscribe } from "./AgentRunBus.js";
|
|
3
|
+
export { ASSISTANT_AVATAR_CONFIG_KEY, AVATAR_OPTION_VALUES, type AvatarConfig, type AvatarEyebrowType, type AvatarEyeType, type AvatarMouthType, type AvatarOptionField, type AvatarTopType, DEFAULT_AVATAR, findAvatarConfigProblems, isAvatarOptionRelevant, normalizeAvatarConfig, } from "./AssistantAvatar.js";
|
|
4
|
+
export { BANNER_MAX_ACTIVE, BANNER_MAX_LIFETIME_DAYS, BANNER_MAX_TEXT_CHARS, BannerBus, type BannerRow, formatBannerList, formatBannerTime, type NewBanner, } from "./BannerBus.js";
|
|
3
5
|
export { BASTION_TOKEN_HEADER } from "./BastionAuth.js";
|
|
4
6
|
export { buildCalendarEventId, type CalendarApi, type CalendarAttachmentMeta, type CalendarAttendee, type CalendarChangePayload, type CalendarEventRow, type CalendarEventType, type CalendarImportance, type CalendarProvider, type CalendarResponseStatus, type CalendarRow, type CalendarSensitivity, type CalendarShowAs, type CalendarType, type CreateEventInput, type FindEventsQuery, type NewCalendarEvent, parseCalendarEventId, type UpdateEventInput, type UpsertCalendarInput, } from "./Calendar.js";
|
|
5
7
|
export { type ParsedChatInput, parseChatInput } from "./ChatInput.js";
|
|
6
8
|
export { type ChatFilter, type ChatHistoryQuery, type ChatMessage, type ChatRole, MAX_CHAT_HISTORY_PAGE, type NewChatMessage, } from "./ChatMessage.js";
|
|
7
9
|
export { type ChatHandler, ChatMessageBus, type ChatUnsubscribe } from "./ChatMessageBus.js";
|
|
8
|
-
export { type ConfigService, DEFAULT_WRITABLE_PATHS } from "./Config.js";
|
|
10
|
+
export { ASSISTANT_NAME_CONFIG_KEY, type ConfigService, DEFAULT_ASSISTANT_NAME, DEFAULT_WRITABLE_PATHS, resolveAssistantName, } from "./Config.js";
|
|
9
11
|
export type { ContainerToolInfo } from "./ContainerTool.js";
|
|
10
12
|
export { type ParsedCron, parseCron } from "./CronExpression.js";
|
|
11
13
|
export { buildNetworkCreateArgs, connectNetwork, type DockerCaptureResult, type DockerExecOptions, dockerCapture, dockerExec, dockerInteractive, ensureNetwork, hostGatewayArgs, ISOLATED_NETWORK_NAME, isContainerRunning, isImagePresent, isNetworkPresent, type NetworkCreateOptions, removeContainer, SHARED_NETWORK_NAME, stopContainer, } from "./DockerTools.js";
|
|
@@ -30,6 +32,7 @@ export { DaemonStoppedError, definePlugin } from "./Plugin.js";
|
|
|
30
32
|
export { type NotificationHandler, POSTGRES_DB, POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USER, PostgresConnection, type PostgresConnectionConfig, } from "./PostgresConnection.js";
|
|
31
33
|
export { CACHE_MARKER_PLACEHOLDER, CORE_PROMPT_PLACEHOLDERS, isCorePromptPlaceholder, PROMPT_PLACEHOLDER_NAME_PATTERN, PROMPT_TEMPLATE_FILE, PromptPlaceholderConflictError, type PromptPlaceholderDefinition, type PromptPlaceholderKind, } from "./PromptPlaceholders.js";
|
|
32
34
|
export { isSafeIncludePath, MAX_TEMPLATE_DEPTH, type ParsedTemplate, type ParseTemplateOptions, type PlaceholderUse, parseTemplate, type RenderedTemplate, renderTemplate, type TemplateNode, } from "./PromptTemplate.js";
|
|
35
|
+
export { formatRuntimeTime } from "./RuntimeTime.js";
|
|
33
36
|
export type { NewScheduledSubagent, ScheduledSubagentRow } from "./ScheduledSubagent.js";
|
|
34
37
|
export { ScheduledSubagentBus, type ScheduledSubagentNotification, type ScheduledSubagentOp, type ScheduledSubagentUnsubscribe, } from "./ScheduledSubagentBus.js";
|
|
35
38
|
export { AGENTRUNS_CHANNEL, CHATMESSAGES_NEW_CHANNEL, EVENT_TERMINAL_UPDATE_SQL, EVENTS_NEW_CHANNEL, EVENTS_STATE_CHANNEL, SCHEDULED_SUBAGENTS_CHANNEL, SCHEMA_SQL, STEPRESULTS_NEW_CHANNEL, TOPIC_PATTERN, } from "./Schema.js";
|
|
@@ -45,7 +48,7 @@ export { DEFAULT_TOOL_CALL_OFFLOADING_LIMIT, type OffloadedJson, runJsonLinesToo
|
|
|
45
48
|
export { ALL_GROUP_NAME, CORE_PLUGIN_ID, IDENT_PATTERN, MCP_GROUP_NAME, matchesAnyToolPattern, NONE_GROUP_NAME, RESERVED_GROUP_NAMES, toolPatternMatches, validateGroupName, } from "./ToolsExpressionUtils.js";
|
|
46
49
|
export { MIN_PROXY_SECRET_LENGTH, PROXY_SECRET_HEADER, type TrustedProxyRegistration, type WebApi, type WebAuthPageView, type WebPrincipal, type WebUi, type WebUiBindings, type WebUiEnv, } from "./WebAuth.js";
|
|
47
50
|
export { firstMenuRoute, WEB_MENU_ICON_PATTERN, type WebMenuContribution, type WebMenuEntry, type WebMenuItem, type WebMenuSubItem, } from "./WebMenu.js";
|
|
48
|
-
export { type MountWebPage, type WebPageContext, type WebPageEntry, type WebPageModule, type WebPluginSocket, type WebPluginUi, type WebSocketConnectionInfo, type WebSocketPeer, webPluginBasePath, } from "./WebPlugin.js";
|
|
51
|
+
export { type MountWebPage, type WebAssistantIdentity, type WebPageContext, type WebPageEntry, type WebPageModule, type WebPluginSocket, type WebPluginUi, type WebSocketConnectionInfo, type WebSocketPeer, webPluginBasePath, } from "./WebPlugin.js";
|
|
49
52
|
export { DEFAULT_WEBSERVER_HOST, DEFAULT_WEBSERVER_PORT } from "./WebServerDefaults.js";
|
|
50
53
|
export { contentTypeForFile, WebStaticAssets, type WebStaticAssetsOptions, } from "./WebStaticAssets.js";
|
|
51
54
|
export type { WorkspaceFile, WorkspaceFileFilter, WorkspaceWatcherApi, } from "./WorkspaceFile.js";
|
package/build/index.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
export { AgentRunBus } from "./AgentRunBus.js";
|
|
2
|
+
export { ASSISTANT_AVATAR_CONFIG_KEY, AVATAR_OPTION_VALUES, DEFAULT_AVATAR, findAvatarConfigProblems, isAvatarOptionRelevant, normalizeAvatarConfig, } from "./AssistantAvatar.js";
|
|
3
|
+
export { BANNER_MAX_ACTIVE, BANNER_MAX_LIFETIME_DAYS, BANNER_MAX_TEXT_CHARS, BannerBus, formatBannerList, formatBannerTime, } from "./BannerBus.js";
|
|
2
4
|
export { BASTION_TOKEN_HEADER } from "./BastionAuth.js";
|
|
3
5
|
export { buildCalendarEventId, parseCalendarEventId, } from "./Calendar.js";
|
|
4
6
|
export { parseChatInput } from "./ChatInput.js";
|
|
5
7
|
export { MAX_CHAT_HISTORY_PAGE, } from "./ChatMessage.js";
|
|
6
8
|
export { ChatMessageBus } from "./ChatMessageBus.js";
|
|
7
|
-
export { DEFAULT_WRITABLE_PATHS } from "./Config.js";
|
|
9
|
+
export { ASSISTANT_NAME_CONFIG_KEY, DEFAULT_ASSISTANT_NAME, DEFAULT_WRITABLE_PATHS, resolveAssistantName, } from "./Config.js";
|
|
8
10
|
export { parseCron } from "./CronExpression.js";
|
|
9
11
|
export { buildNetworkCreateArgs, connectNetwork, dockerCapture, dockerExec, dockerInteractive, ensureNetwork, hostGatewayArgs, ISOLATED_NETWORK_NAME, isContainerRunning, isImagePresent, isNetworkPresent, removeContainer, SHARED_NETWORK_NAME, stopContainer, } from "./DockerTools.js";
|
|
10
12
|
export { EVENT_PRIORITY } from "./Event.js";
|
|
@@ -25,6 +27,7 @@ export { DaemonStoppedError, definePlugin } from "./Plugin.js";
|
|
|
25
27
|
export { POSTGRES_DB, POSTGRES_HOST, POSTGRES_PORT, POSTGRES_USER, PostgresConnection, } from "./PostgresConnection.js";
|
|
26
28
|
export { CACHE_MARKER_PLACEHOLDER, CORE_PROMPT_PLACEHOLDERS, isCorePromptPlaceholder, PROMPT_PLACEHOLDER_NAME_PATTERN, PROMPT_TEMPLATE_FILE, PromptPlaceholderConflictError, } from "./PromptPlaceholders.js";
|
|
27
29
|
export { isSafeIncludePath, MAX_TEMPLATE_DEPTH, parseTemplate, renderTemplate, } from "./PromptTemplate.js";
|
|
30
|
+
export { formatRuntimeTime } from "./RuntimeTime.js";
|
|
28
31
|
export { ScheduledSubagentBus, } from "./ScheduledSubagentBus.js";
|
|
29
32
|
export { AGENTRUNS_CHANNEL, CHATMESSAGES_NEW_CHANNEL, EVENT_TERMINAL_UPDATE_SQL, EVENTS_NEW_CHANNEL, EVENTS_STATE_CHANNEL, SCHEDULED_SUBAGENTS_CHANNEL, SCHEMA_SQL, STEPRESULTS_NEW_CHANNEL, TOPIC_PATTERN, } from "./Schema.js";
|
|
30
33
|
export { isHiddenScratchFile, scratchSpillFileName } from "./Scratch.js";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getfamiliar/shared",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.18",
|
|
5
5
|
"description": "Familiar — shared types and bus-state client between host and container.",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -13,6 +13,10 @@
|
|
|
13
13
|
"types": "./build/index.d.ts",
|
|
14
14
|
"default": "./build/index.js"
|
|
15
15
|
},
|
|
16
|
+
"./avatar": {
|
|
17
|
+
"types": "./build/AssistantAvatar.d.ts",
|
|
18
|
+
"default": "./build/AssistantAvatar.js"
|
|
19
|
+
},
|
|
16
20
|
"./testing": {
|
|
17
21
|
"types": "./build/testing/index.d.ts",
|
|
18
22
|
"default": "./build/testing/index.js"
|