@crossworks/client-types 0.232.133 → 0.232.142
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/package.json +1 -1
- package/src/dto/agent-graph.ts +229 -0
- package/src/dto/agents.ts +190 -0
- package/src/dto/comms.ts +114 -0
- package/src/dto/heartbeats.ts +60 -0
- package/src/dto/recall.ts +21 -0
- package/src/dto/rows.ts +882 -0
- package/src/dto/turns.ts +192 -0
- package/src/dto/views.ts +936 -0
- package/src/index.ts +15 -2534
- package/src/model-pools-data.json +2194 -0
- package/src/model-pools-data.ts +10 -2194
- package/src/model-pools-template.test.ts +77 -0
package/src/dto/rows.ts
ADDED
|
@@ -0,0 +1,882 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @mantle/client-types · rows
|
|
3
|
+
*
|
|
4
|
+
* Row/DTO shapes lifted out of the server packages at the jackdaw split,
|
|
5
|
+
* the hand-written mirrors of @mantle/db jsonb/enum shapes, and the redacted
|
|
6
|
+
* account DTOs.
|
|
7
|
+
*
|
|
8
|
+
* Split out of the 2548-line index.ts on 2026-09-02 (audit, tier 3) with the
|
|
9
|
+
* contents unchanged. index.ts re-exports every one of these, so the package's
|
|
10
|
+
* public surface is byte-identical — only the file a symbol lives in moved.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// ── Row/DTO shapes moved from the server packages (jackdaw split P0) ─────────
|
|
14
|
+
// Sources: @mantle/content, @mantle/email, @mantle/microsoft, @mantle/runtime/agent
|
|
15
|
+
// re-export these names, so server code keeps its original import paths.
|
|
16
|
+
|
|
17
|
+
export type TaskRow = {
|
|
18
|
+
id: string;
|
|
19
|
+
title: string;
|
|
20
|
+
body: string;
|
|
21
|
+
status: TaskStatus;
|
|
22
|
+
priority: TaskPriority;
|
|
23
|
+
dueAt: string | null;
|
|
24
|
+
tags: string[];
|
|
25
|
+
/** Checklist inside the task ("task breakup"). Stored in `data.todos`. */
|
|
26
|
+
todos: TaskTodo[];
|
|
27
|
+
/** Fractional ordering key for the board (within-column order). Stored in
|
|
28
|
+
* `data.rank`; null on tasks never dragged — they sort after ranked ones. */
|
|
29
|
+
rank: string | null;
|
|
30
|
+
/** Comments on this task (node_comments rows). List/detail badge material. */
|
|
31
|
+
commentCount: number;
|
|
32
|
+
summary: string | null;
|
|
33
|
+
/** When the task was filed away, or null while it is live. Archived tasks are
|
|
34
|
+
* excluded from every list, count and board unless explicitly requested —
|
|
35
|
+
* it is what keeps a Done column from growing without bound. Orthogonal to
|
|
36
|
+
* `status`: an archived task keeps the status it had. */
|
|
37
|
+
archivedAt: string | null;
|
|
38
|
+
createdAt: string;
|
|
39
|
+
updatedAt: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/** One checklist item inside a task. Server assigns `id` on write. */
|
|
43
|
+
export type TaskTodo = {
|
|
44
|
+
id: string;
|
|
45
|
+
text: string;
|
|
46
|
+
done: boolean;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
/** Mirrors @mantle/db `NodeCommentAuthorKind`. */
|
|
50
|
+
export type NodeCommentAuthorKind = 'owner' | 'member' | 'agent';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A comment on a node (tasks first; the table is node-generic). `authorName`
|
|
54
|
+
* is a display snapshot at post time; `mine` is computed server-side per
|
|
55
|
+
* viewer (an owner login sees its own comments as mine, a team member sees
|
|
56
|
+
* theirs), so clients never reconcile the two auth worlds themselves.
|
|
57
|
+
*/
|
|
58
|
+
export type NodeComment = {
|
|
59
|
+
id: string;
|
|
60
|
+
nodeId: string;
|
|
61
|
+
authorKind: NodeCommentAuthorKind;
|
|
62
|
+
authorName: string;
|
|
63
|
+
mine: boolean;
|
|
64
|
+
body: string;
|
|
65
|
+
createdAt: string;
|
|
66
|
+
editedAt: string | null;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export type JournalRow = {
|
|
70
|
+
id: string;
|
|
71
|
+
title: string;
|
|
72
|
+
body: string;
|
|
73
|
+
/** Who wrote the entry. Stamped server-side; agent tool calls can't spoof it. */
|
|
74
|
+
author: 'user' | 'agent';
|
|
75
|
+
/** Authoring agent's slug when author='agent'; null for user-authored rows. */
|
|
76
|
+
agentSlug: string | null;
|
|
77
|
+
/** Kind key (see KINDS in journal-options). Legacy pre-v2 rows map their old
|
|
78
|
+
* `category` to a kind at read time; free text is tolerated. */
|
|
79
|
+
kind: string | null;
|
|
80
|
+
/** Gap lifecycle — only entries with kind='gap' carry one ('open'|'resolved'). */
|
|
81
|
+
status: string | null;
|
|
82
|
+
entryDate: string | null;
|
|
83
|
+
tags: string[];
|
|
84
|
+
summary: string | null;
|
|
85
|
+
createdAt: string;
|
|
86
|
+
updatedAt: string;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export type EventRow = {
|
|
90
|
+
id: string;
|
|
91
|
+
title: string;
|
|
92
|
+
body: string;
|
|
93
|
+
startsAt: string;
|
|
94
|
+
endsAt: string | null;
|
|
95
|
+
location: string | null;
|
|
96
|
+
remindMinutesBefore: number;
|
|
97
|
+
remindAt: string;
|
|
98
|
+
reminderSentAt: string | null;
|
|
99
|
+
/** IANA timezone (e.g. "Africa/Johannesburg") captured from the
|
|
100
|
+
* client at create time. Used for display only — `starts_at` is
|
|
101
|
+
* always a UTC instant so the reminder fires at the right moment
|
|
102
|
+
* regardless of where the agent process or DB run. Defaults to
|
|
103
|
+
* 'UTC' if the client didn't supply one. */
|
|
104
|
+
timezone: string;
|
|
105
|
+
/** Recurrence frequency; 'none' for a one-shot event. */
|
|
106
|
+
recur: RecurFreq;
|
|
107
|
+
/** Optional end-of-series cutoff (ISO). null = repeats until deleted. */
|
|
108
|
+
recurUntil: string | null;
|
|
109
|
+
tags: string[];
|
|
110
|
+
summary: string | null;
|
|
111
|
+
createdAt: string;
|
|
112
|
+
updatedAt: string;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** Notion-style content width: centered/narrow vs full available space. */
|
|
116
|
+
export type PageWidth = 'narrow' | 'wide';
|
|
117
|
+
|
|
118
|
+
export type PageRow = {
|
|
119
|
+
id: string;
|
|
120
|
+
/** Parent page id, or null for a top-level page. Drives the /pages tree
|
|
121
|
+
* and the `childPage` card (Phase 4a sub-pages). */
|
|
122
|
+
parentId: string | null;
|
|
123
|
+
title: string;
|
|
124
|
+
icon: string | null;
|
|
125
|
+
tags: string[];
|
|
126
|
+
summary: string | null;
|
|
127
|
+
visibility: PageVisibility;
|
|
128
|
+
width: PageWidth;
|
|
129
|
+
createdAt: string;
|
|
130
|
+
updatedAt: string;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
export type AppRow = {
|
|
134
|
+
id: string;
|
|
135
|
+
title: string;
|
|
136
|
+
icon: string | null;
|
|
137
|
+
tags: string[];
|
|
138
|
+
summary: string | null;
|
|
139
|
+
description: string | null;
|
|
140
|
+
/** Number of declared api_tool slugs. */
|
|
141
|
+
toolCount: number;
|
|
142
|
+
/** Whether the published source has a green build (renders today). */
|
|
143
|
+
hasBuild: boolean;
|
|
144
|
+
/** Whether an uncommitted draft exists. */
|
|
145
|
+
hasDraft: boolean;
|
|
146
|
+
/**
|
|
147
|
+
* The app's exposure: mode of its active share ('public' | 'team'), or null
|
|
148
|
+
* when it has never been shared / the share is revoked (owner-only).
|
|
149
|
+
*/
|
|
150
|
+
shareMode: ShareMode | null;
|
|
151
|
+
/** Whether this app is the designated Team Hub (prefs.teamHubAppId). */
|
|
152
|
+
isHub: boolean;
|
|
153
|
+
createdAt: string;
|
|
154
|
+
updatedAt: string;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
export type AppDetail = AppRow & {
|
|
158
|
+
source: AppSource;
|
|
159
|
+
draft: AppSource | null;
|
|
160
|
+
manifest: AppManifest;
|
|
161
|
+
draftBuild: BuildRef | null;
|
|
162
|
+
publishedBuild: BuildRef | null;
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export type ProfilePreferences = {
|
|
166
|
+
/** IANA timezone, e.g. 'Africa/Johannesburg'. UTC when not set. */
|
|
167
|
+
timezone: string;
|
|
168
|
+
/** The last zone the auto-from-location hook DERIVED (not necessarily the one
|
|
169
|
+
* in `timezone`, if the user manually overrode since). Used purely for
|
|
170
|
+
* hysteresis: the hook only acts when the freshly-derived zone differs from
|
|
171
|
+
* this, so it won't fight a manual change or re-switch every turn at the same
|
|
172
|
+
* place. See auto-timezone.ts. */
|
|
173
|
+
lastAutoTimezone?: string;
|
|
174
|
+
/** BCP-47 locale, e.g. 'en-GB'. Drives date/number/currency
|
|
175
|
+
* formatting. Falls back to en-GB to match the legacy pinned
|
|
176
|
+
* format-datetime behaviour, so existing UI doesn't shift for
|
|
177
|
+
* users who haven't visited /settings/profile yet. */
|
|
178
|
+
locale: string;
|
|
179
|
+
/** Avatar style id — the BRAIN's avatar visual language, applied to every
|
|
180
|
+
* generated avatar (the owner's and every agent's). Brain-level alongside
|
|
181
|
+
* colorTheme and the display fonts, because it is a branding choice, not a
|
|
182
|
+
* personal one: one style with a different seed per entity reads as one
|
|
183
|
+
* product, six unrelated styles at once read as noise. Individuality lives
|
|
184
|
+
* in `avatarSeed`, which stays personal. See @mantle/web-ui/avatar for the
|
|
185
|
+
* registry; unknown ids resolve to the default rather than stranding. */
|
|
186
|
+
avatarStyle?: string;
|
|
187
|
+
/** How much of the theme generated avatars take on: 'native' (the style's own
|
|
188
|
+
* palette), 'mixed' (themed background, original artwork — the default) or
|
|
189
|
+
* 'theme' (theme colours throughout). Brain-level for the same reason as
|
|
190
|
+
* avatarStyle: it describes how this brain's avatars look, not one login's
|
|
191
|
+
* taste. Read via projectAvatarTint, never raw. */
|
|
192
|
+
avatarTint?: string;
|
|
193
|
+
/** Which generated background each area of the shell shows, as
|
|
194
|
+
* `area=style` pairs (`menu=waves,header=off`). Brain-level for the same
|
|
195
|
+
* reason as avatarStyle and colorTheme: it is the look of the product.
|
|
196
|
+
* `off` is a real, storable choice, see @mantle/web-ui/backgrounds. Areas
|
|
197
|
+
* on their default are omitted, so a default change still reaches brains
|
|
198
|
+
* that never chose. Read via projectBackgrounds, never raw. */
|
|
199
|
+
backgrounds?: string;
|
|
200
|
+
/** The generated whole-surface Neat gradient (login screen, content area),
|
|
201
|
+
* as a compact JSON spec `{v,seed,tone,speed}` — colours are DERIVED from
|
|
202
|
+
* the live theme tokens client-side, never stored, so the background
|
|
203
|
+
* follows every colour theme and mode. Brain-level for the same reason as
|
|
204
|
+
* backgrounds: it is the look of the product. Unset ⇒ the plain themed
|
|
205
|
+
* fill. Read via projectNeatBackground, never raw. */
|
|
206
|
+
neatBackground?: string;
|
|
207
|
+
/** The brain's default light/dark mode for surfaces where the visitor has
|
|
208
|
+
* not chosen one themselves — today the public /s share reader, which stamps
|
|
209
|
+
* it server-side and lets the visitor's own toggle override it locally.
|
|
210
|
+
* 'light' | 'dark' | 'system'; unset ⇒ 'light' (the share surface's
|
|
211
|
+
* historical rendering, so an unset brain looks exactly as before). Brain-
|
|
212
|
+
* level like colorTheme: it is the look of the product's public face. Read
|
|
213
|
+
* via projectDefaultMode, never raw. */
|
|
214
|
+
defaultMode?: string;
|
|
215
|
+
/** Whether the public /s share reader paints the saved Neat gradient at all.
|
|
216
|
+
* Default ON (only an explicit `false` disables, the streamThoughts
|
|
217
|
+
* contract): the switch exists for owners who want share links to stay on
|
|
218
|
+
* the plain themed surface — the printable rendering — while the app keeps
|
|
219
|
+
* its background. Brain-level like neatBackground itself. */
|
|
220
|
+
shareNeat?: boolean;
|
|
221
|
+
/** Seed for THIS user's avatar; the UI defaults it to the user id when unset
|
|
222
|
+
* so an avatar still renders. Personal — two admins share the brain's style
|
|
223
|
+
* but never the same avatar. */
|
|
224
|
+
avatarSeed?: string;
|
|
225
|
+
/** Avatar-builder component choices for THIS login's avatar, layered over
|
|
226
|
+
* the seed: component name → pinned variant, or null to hide an optional
|
|
227
|
+
* component. Per-login (the profile routes address the ACTOR's row). Stale
|
|
228
|
+
* entries (saved under another brain style) are ignored at render time.
|
|
229
|
+
* READ: absent/empty = seed only. WRITE (profile PUT): applied only when
|
|
230
|
+
* SENT; `{}` clears. */
|
|
231
|
+
avatarParts?: Record<string, string | null>;
|
|
232
|
+
/** Content-addressed storage key of THIS login's uploaded profile PHOTO —
|
|
233
|
+
* when set, clients show the photo instead of the generated avatar
|
|
234
|
+
* (photo → generated seed → initials). Per-login (the photo routes address
|
|
235
|
+
* the ACTOR's row); set only from Settings → Profile, never for agents.
|
|
236
|
+
* Served privately by GET /api/profile/photo (cookie or asset token). */
|
|
237
|
+
avatarPhotoKey?: string;
|
|
238
|
+
/** Content-Type of the photo bytes (png/jpeg/webp — never SVG). */
|
|
239
|
+
avatarPhotoType?: string;
|
|
240
|
+
/** Slug of the responder agent whose Telegram bot delivers event reminders.
|
|
241
|
+
* Unset → the reminder worker falls back to the most-recently-active allowed
|
|
242
|
+
* DM (whichever bot you last messaged). Set it to pin reminders to one
|
|
243
|
+
* persona, e.g. 'telegram-default' (Saskia), so they don't come from
|
|
244
|
+
* whichever bot happened to be most recent. */
|
|
245
|
+
reminderAgentSlug?: string;
|
|
246
|
+
/** Where event reminders are delivered: 'telegram' (a bot DM) or 'mobile' (a
|
|
247
|
+
* push to the companion app). Auto-tracked — it follows the last channel the
|
|
248
|
+
* user actually messaged on (see noteInboundChannel), and can be set manually
|
|
249
|
+
* from the profile; a manual choice holds until the next message on the other
|
|
250
|
+
* channel supersedes it. Unset ⇒ the reminder worker defaults to 'telegram'
|
|
251
|
+
* (backward-compatible). See docs/reminder-delivery-routing.md. */
|
|
252
|
+
reminderChannel?: ReminderChannel;
|
|
253
|
+
/** What the user likes to be called (captured during onboarding). Cosmetic —
|
|
254
|
+
* the assistant's real knowledge of the user comes from the Journal identity
|
|
255
|
+
* block; this is for greetings/UI. */
|
|
256
|
+
displayName?: string;
|
|
257
|
+
/** Custom site name rendered as the header wordmark in place of "mantle" —
|
|
258
|
+
* a per-box label (e.g. 'Refinery') so anyone with several brains can see at
|
|
259
|
+
* a glance which one they're on. Cosmetic only; unset ⇒ the Mantle wordmark.
|
|
260
|
+
* Read via projectSiteName, never raw. */
|
|
261
|
+
siteName?: string;
|
|
262
|
+
/** This brain's peer name — shown in the header CENTRE (replacing the old page
|
|
263
|
+
* title) as this node's federation-facing identity label. Cosmetic; unset ⇒
|
|
264
|
+
* the header centre is empty. Read via projectPeerName, never raw. */
|
|
265
|
+
peerName?: string;
|
|
266
|
+
/** The owner's writing conventions, in their own words — appended to EVERY
|
|
267
|
+
* agent's composed system prompt as a `## House style` block (see
|
|
268
|
+
* composeSystemPromptWithSkills). Brain-level, because it describes how this
|
|
269
|
+
* brain writes, not how one login works.
|
|
270
|
+
*
|
|
271
|
+
* Free text rather than a checkbox on purpose: the first rule anyone wants
|
|
272
|
+
* is "no em dashes", the second is "don't say 'delve'", and a boolean per
|
|
273
|
+
* rule is a migration per taste. Unset ⇒ no block is emitted at all, so the
|
|
274
|
+
* cached prompt prefix is byte-identical to before the feature existed.
|
|
275
|
+
* Read via projectHouseStyle, never raw. */
|
|
276
|
+
houseStyle?: string;
|
|
277
|
+
/** The UI colour-theme id (the header theme toggler / random shuffle). The
|
|
278
|
+
* DB copy is the source of truth so the choice follows the owner across
|
|
279
|
+
* browsers and brands member-facing surfaces (/s, /team) — localStorage
|
|
280
|
+
* stays only as the before-paint fast path. Unset ⇒ the default theme.
|
|
281
|
+
* Read via projectColorTheme, never raw. */
|
|
282
|
+
colorTheme?: string;
|
|
283
|
+
/** Selectable header WORDMARK font key (Settings → Appearance → Fonts). The
|
|
284
|
+
* font LIST lives in the web app (server/web/lib/display-fonts.ts); the server
|
|
285
|
+
* stores any well-formed slug and the client falls back to the default for
|
|
286
|
+
* keys it doesn't know, so trimming the library never strands the preference.
|
|
287
|
+
* Unset ⇒ the default wordmark face (Bricolage Grotesque). Read via
|
|
288
|
+
* projectFontKey, never raw. */
|
|
289
|
+
fontLogo?: string;
|
|
290
|
+
/** Selectable header page-TITLE font key — same contract as `fontLogo`.
|
|
291
|
+
* Unset ⇒ the default UI sans. Read via projectFontKey, never raw. */
|
|
292
|
+
fontTitle?: string;
|
|
293
|
+
/** The INTERFACE font key — what the whole UI is set in, not just a header
|
|
294
|
+
* ornament. Same contract as `fontLogo`; unset ⇒ Inter (the always-loaded
|
|
295
|
+
* next/font face). Read via projectFontKey, never raw. */
|
|
296
|
+
fontUi?: string;
|
|
297
|
+
/** The PAGES/NOTES font key — what long-form prose is set in, in the editor,
|
|
298
|
+
* on shared pages, and in the PDF export. Same contract as `fontLogo`; unset
|
|
299
|
+
* ⇒ 'inherit' (follow the interface font). This is the one slot where the
|
|
300
|
+
* choice leaves the browser: a page exported to PDF is typeset in it. */
|
|
301
|
+
fontProse?: string;
|
|
302
|
+
/** UI scale: 'xsmall' | 'small' | 'medium' | 'large'. Drives the ROOT
|
|
303
|
+
* font-size, so the rem-based shell scales with it rather than only the
|
|
304
|
+
* letters. Unset ⇒ 'medium'. Read via projectFontSize, never raw. */
|
|
305
|
+
fontSize?: string;
|
|
306
|
+
/** Wordmark scale — same vocabulary as `fontSize`, but a LOCAL multiplier on
|
|
307
|
+
* one element rather than the root font-size (a wordmark that rescaled the
|
|
308
|
+
* whole shell would be a bug). Unset ⇒ 'medium'. */
|
|
309
|
+
fontLogoSize?: string;
|
|
310
|
+
/** Peer-name scale. Same contract as `fontLogoSize`. */
|
|
311
|
+
fontTitleSize?: string;
|
|
312
|
+
/** Pages/Notes prose scale. Same contract as `fontLogoSize`. */
|
|
313
|
+
fontProseSize?: string;
|
|
314
|
+
/** Brand logo: the content-addressed storage key of the uploaded image
|
|
315
|
+
* (attachments/aa/bb/<sha256> — @mantle/storage contentKey). Set/cleared
|
|
316
|
+
* ONLY via PUT/DELETE /api/profile/logo, which validates the bytes; when
|
|
317
|
+
* set, both headers render the image in place of the siteName wordmark.
|
|
318
|
+
* The sha in the key doubles as the cache-busting version. Read via
|
|
319
|
+
* projectLogoKey, never raw. */
|
|
320
|
+
logoKey?: string;
|
|
321
|
+
/** The logo's mime type, from the validated upload (svg/png/jpeg/webp
|
|
322
|
+
* allowlist — projectLogoType). The public serve route replays it. */
|
|
323
|
+
logoType?: string;
|
|
324
|
+
/** Optional DARK-MODE logo variant — same storage/validation contract as
|
|
325
|
+
* logoKey, uploaded via PUT /api/profile/logo?variant=dark. Renderers show
|
|
326
|
+
* it when the UI is in dark mode and fall back to the base logo (then the
|
|
327
|
+
* wordmark) when unset — so a light-on-transparent mark stays readable on
|
|
328
|
+
* both themes without forcing every brain to upload two files. */
|
|
329
|
+
logoDarkKey?: string;
|
|
330
|
+
/** The dark variant's mime type (same allowlist as logoType). */
|
|
331
|
+
logoDarkType?: string;
|
|
332
|
+
/** Free-text "what this brain is for" — captured at onboarding, editable in
|
|
333
|
+
* Settings → Profile. Injected as the "# Purpose of this brain" section of the
|
|
334
|
+
* always-on identity block (identity-context.ts), so every agent knows the
|
|
335
|
+
* brain's mission. */
|
|
336
|
+
purpose?: string;
|
|
337
|
+
/** The brain's speciality archetype key (see onboarding-questions.ts
|
|
338
|
+
* PURPOSE_ARCHETYPES — 'personal' | 'analytics' | 'research' | 'robotics' |
|
|
339
|
+
* 'team' | 'custom'). Descriptive for now; the seam a later phase can branch
|
|
340
|
+
* default provisioning on. */
|
|
341
|
+
purposeArchetype?: string;
|
|
342
|
+
/** ISO instant onboarding was completed. Unset ⇒ the onboarding wizard runs
|
|
343
|
+
* on next login; the (app) shell redirects there. Set ⇒ shell renders normally. */
|
|
344
|
+
onboardedAt?: string;
|
|
345
|
+
/** Resume marker for the onboarding wizard — the key of the furthest step the
|
|
346
|
+
* user has reached. Lets a refreshed/re-entered wizard pick up where it left off. */
|
|
347
|
+
onboardingStep?: string;
|
|
348
|
+
/** Model choices captured by the onboarding "Models" step — the operator
|
|
349
|
+
* overlay `provisionDefaults()` applies on top of the manifest seed (the
|
|
350
|
+
* assistant's chat model + the indexing workers' fast model). When
|
|
351
|
+
* `route: 'azure'`, those rows are pinned to an Azure OpenAI endpoint via
|
|
352
|
+
* the `custom` provider (key stored under service `custom`). */
|
|
353
|
+
onboardingModels?: OnboardingModelChoices;
|
|
354
|
+
/** When true, tools an AGENT authors (via Toolsmith / api_tool_create) start
|
|
355
|
+
* confirm-gated: every call parks for operator approval until the operator
|
|
356
|
+
* clears "requires confirm" for that tool in Settings → Tools. Defaults
|
|
357
|
+
* OFF — a simple single-owner brain trusts itself; turn it ON if you grant
|
|
358
|
+
* tool-authoring to an agent that reads untrusted content (email/web), so an
|
|
359
|
+
* injected agent can't stand up a silent exfiltration endpoint. Independent
|
|
360
|
+
* of the always-on guards (self-grant block, no-lower-via-update, SSRF). */
|
|
361
|
+
toolsmithRequireApproval?: boolean;
|
|
362
|
+
/** APP_VERSION the boot-time manifest reconcile last synced this brain to.
|
|
363
|
+
* The reconcile (server/web instrumentation → reconcileManifestOnBoot) runs once
|
|
364
|
+
* per version on a deployed/updated instance, so a self-hoster who only pulls a
|
|
365
|
+
* new image still gets new tools/skills/group-membership without running seed
|
|
366
|
+
* scripts. Equal to APP_VERSION ⇒ already reconciled, skip. */
|
|
367
|
+
lastReconciledVersion?: string;
|
|
368
|
+
/** When true, outbound/egress tools (email_send, web_fetch, web_search)
|
|
369
|
+
* fired during an UNATTENDED heartbeat run park for operator approval
|
|
370
|
+
* instead of executing inline. Only tools that reach OUT are gated — the
|
|
371
|
+
* heartbeat's own surface reply (the final Telegram message) is not a tool
|
|
372
|
+
* and still goes through. Defaults OFF: most heartbeats are trusted
|
|
373
|
+
* routines. Turn it ON for an agent that reads untrusted content on a
|
|
374
|
+
* timer, so an injected instruction can't silently email or fetch on your
|
|
375
|
+
* behalf while you're away. Pairs with the interactive Telegram approval
|
|
376
|
+
* card so a parked egress call can be cleared from a phone. */
|
|
377
|
+
heartbeatEgressGate?: boolean;
|
|
378
|
+
/** Show the live "thinking" trail + stream the reply token-by-token in the
|
|
379
|
+
* /assistant chat (and the companion). **Defaults ON** (undefined → on); set
|
|
380
|
+
* false to fall back to a static thinking bubble + the reply appearing whole
|
|
381
|
+
* on completion. This is the per-brain runtime control for live turn
|
|
382
|
+
* streaming; the `MANTLE_TURN_STREAMING` env var is a deploy-level override
|
|
383
|
+
* (env off wins). Read by the web turn route (202 vs blocking + the SSE gate)
|
|
384
|
+
* via `isStreamThoughtsEnabled`. */
|
|
385
|
+
streamThoughts?: boolean;
|
|
386
|
+
/** How the LIVE thinking trail renders during a turn: 'list' stacks completed
|
|
387
|
+
* actions above the active line (default); 'replace' shows only the current
|
|
388
|
+
* action, each one replacing the last (compact, single line). The frozen
|
|
389
|
+
* record view (after the turn) is unaffected. */
|
|
390
|
+
thoughtTrailMode?: ThoughtTrailMode;
|
|
391
|
+
/** Persist the thought trail onto the finished message so it survives a page
|
|
392
|
+
* refresh — reconstructed from the turn's tool actions and stored on the
|
|
393
|
+
* durable row, so it reloads on web AND the companion. **Defaults ON**; set
|
|
394
|
+
* false to keep it ephemeral (in-memory only; clears on reload). See
|
|
395
|
+
* `isPersistThoughtsEnabled`. */
|
|
396
|
+
persistThoughts?: boolean;
|
|
397
|
+
/** Per-user thinking budget in tokens. Real model reasoning is requested only
|
|
398
|
+
* when the live-thinking switch is ON (`streamThoughts`) AND this is > 0;
|
|
399
|
+
* 0 / unset = no thinking. Maps to the provider's knob in the adapters
|
|
400
|
+
* (Anthropic adaptive, OpenRouter `reasoning.max_tokens`, Gemini
|
|
401
|
+
* `thinkingConfig`, Copilot `reasoning_effort`). This is the per-user
|
|
402
|
+
* replacement for the old per-box `MANTLE_THINKING_BUDGET` env gate. Resolve
|
|
403
|
+
* via `resolveThinkingBudget` — never read raw, so the switch gate always
|
|
404
|
+
* applies. **Defaults unset (off).** */
|
|
405
|
+
thinkingBudget?: number;
|
|
406
|
+
/** Whether this box exposes its remote MCP connector (the OAuth-gated
|
|
407
|
+
* `/api/mcp` endpoint addable as a claude.ai custom connector). **Defaults
|
|
408
|
+
* OFF** — it's an explicit opt-in because it puts the tool surface on the
|
|
409
|
+
* public internet (behind OAuth). When off, `/api/mcp` + the OAuth
|
|
410
|
+
* authorize/register endpoints 404, so no new client can connect and existing
|
|
411
|
+
* tokens stop working. Flip it in Settings → MCP. */
|
|
412
|
+
remoteMcpEnabled?: boolean;
|
|
413
|
+
/** Whether the external Team Chat responder may read the owner's PRIVATE
|
|
414
|
+
* corpus — email + journal — on a team member's behalf. **Defaults OFF**:
|
|
415
|
+
* team members always get brain-wide knowledge reads (search, files, notes,
|
|
416
|
+
* pages, tables, tasks, contacts, app data), but the owner's personal email
|
|
417
|
+
* history and journal stay off-limits unless this is explicitly turned on.
|
|
418
|
+
* Enforced at the team turn's tool resolution (`isTeamPrivateReadsEnabled`
|
|
419
|
+
* strips `email_*`/`journal_*` when off), independent of the `team-read`
|
|
420
|
+
* group grant, so the switch can't be bypassed by a manifest change. Flip it
|
|
421
|
+
* from the Team admin surface. */
|
|
422
|
+
teamPrivateReads?: boolean;
|
|
423
|
+
/** Node id of the mini-app designated as this brain's TEAM HUB. When set (and
|
|
424
|
+
* the app has a green published build + an active team-mode share), the /team
|
|
425
|
+
* shell renders that app full-bleed in place of the built-in hub body; the
|
|
426
|
+
* built-in hub remains the fallback for every other state. Resolve via
|
|
427
|
+
* `resolveTeamHubApp` (team-hub.ts), never raw — designation is only honoured
|
|
428
|
+
* when the whole chain (pref → app → build → share) is intact. Read via
|
|
429
|
+
* projectTeamHubAppId, never raw. */
|
|
430
|
+
teamHubAppId?: string;
|
|
431
|
+
/** Tags the owner curates as Dashboard sections on the /team overview: each
|
|
432
|
+
* tag renders a section of up to 5 team-visible shared pages carrying it
|
|
433
|
+
* (newest-updated first, title + summary + /s link). Order here = section
|
|
434
|
+
* order. The share stays the single source of truth for WHAT is visible —
|
|
435
|
+
* this pref only chooses which tag groupings get pinned. Unset/empty ⇒ no
|
|
436
|
+
* curated sections. Read via projectTeamHubTags, never raw. */
|
|
437
|
+
teamHubTags?: string[];
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
export type BackupConfig = {
|
|
441
|
+
enabled: boolean;
|
|
442
|
+
frequency: BackupFrequency;
|
|
443
|
+
/** Hour of day (0-23) in the USER's timezone (profiles.preferences.timezone). */
|
|
444
|
+
hour: number;
|
|
445
|
+
/** Newest N dumps retained in the directory. */
|
|
446
|
+
keep: number;
|
|
447
|
+
/** Absolute destination directory. Empty/unset → resolveBackupDir default. */
|
|
448
|
+
location?: string;
|
|
449
|
+
};
|
|
450
|
+
|
|
451
|
+
export type BackupFile = { name: string; bytes: number; mtime: string };
|
|
452
|
+
|
|
453
|
+
export type BackupStatus = {
|
|
454
|
+
lastRunAt: string;
|
|
455
|
+
ok: boolean;
|
|
456
|
+
/** Set when ok=false. */
|
|
457
|
+
error?: string;
|
|
458
|
+
file?: string;
|
|
459
|
+
bytes?: number;
|
|
460
|
+
durationMs?: number;
|
|
461
|
+
/** 'schedule' | 'manual' — what triggered the run. */
|
|
462
|
+
trigger: string;
|
|
463
|
+
/** When the last SUCCESSFUL run finished — preserved across failed runs,
|
|
464
|
+
* so the /debug/integrity staleness check can tell "failing for a week"
|
|
465
|
+
* from "failed once after last night's good dump". */
|
|
466
|
+
lastSuccessAt?: string;
|
|
467
|
+
/** Sqlite-native table workbooks snapshotted beside the dump (durability
|
|
468
|
+
* gate 2). failed>0 is surfaced in the settings card — a backup that
|
|
469
|
+
* silently skips a workbook is the gap this closes. */
|
|
470
|
+
tableDbs?: { snapshotted: number; missing: number; failed: number };
|
|
471
|
+
/** Per-app mini-app SQLite databases snapshotted beside the dump. Same
|
|
472
|
+
* durability gate as tableDbs: these live on their own volume, so pg_dump
|
|
473
|
+
* alone misses them and a scheduled backup would silently omit all app
|
|
474
|
+
* data (e.g. a Team Hub app's DB) without this pass. */
|
|
475
|
+
appDbs?: { snapshotted: number; missing: number; failed: number };
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
export type CuratedTeamSection = {
|
|
479
|
+
/** The curated tag — the section heading (display-cased by the UI). */
|
|
480
|
+
tag: string;
|
|
481
|
+
/** Up to {@link TEAM_CURATED_SECTION_LIMIT} team-visible page shares carrying
|
|
482
|
+
* the tag, newest node update first. */
|
|
483
|
+
items: TeamVisibleShare[];
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
export type TeamMemberActivity = {
|
|
487
|
+
contactId: string;
|
|
488
|
+
/** Contact node title; '(deleted contact)' can't occur here — membership
|
|
489
|
+
* rows cascade with the contact. */
|
|
490
|
+
contactName: string;
|
|
491
|
+
memberSince: string;
|
|
492
|
+
tokenLastUsedAt: string | null;
|
|
493
|
+
lastMessageAt: string | null;
|
|
494
|
+
lastMessageText: string | null;
|
|
495
|
+
lastMessageDirection: 'inbound' | 'outbound' | null;
|
|
496
|
+
messageCount: number;
|
|
497
|
+
/** Member inbound messages since the owner last read this thread in
|
|
498
|
+
* /team-admin (all inbound when never read). Drives the unread badge. */
|
|
499
|
+
unread: number;
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
export type TeamRequest = {
|
|
503
|
+
taskId: string;
|
|
504
|
+
title: string;
|
|
505
|
+
body: string;
|
|
506
|
+
status: 'open' | 'done';
|
|
507
|
+
priority: string;
|
|
508
|
+
createdAt: string;
|
|
509
|
+
/** Provenance from data.teamRequest — null contactId means a malformed row
|
|
510
|
+
* (shouldn't happen; team_request_create always stamps it). */
|
|
511
|
+
contactId: string | null;
|
|
512
|
+
contactName: string | null;
|
|
513
|
+
/** When the owner last posted a resolution to the member for this request. */
|
|
514
|
+
notifiedAt: string | null;
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
export type ForumTopicListItem = {
|
|
518
|
+
id: string;
|
|
519
|
+
title: string;
|
|
520
|
+
kind: ForumTopicKind;
|
|
521
|
+
visibility: ForumTopicVisibility;
|
|
522
|
+
pinned: boolean;
|
|
523
|
+
status: ForumTopicStatus;
|
|
524
|
+
authorName: string;
|
|
525
|
+
createdByContactId: string | null;
|
|
526
|
+
postCount: number;
|
|
527
|
+
lastPostAt: string;
|
|
528
|
+
createdAt: string;
|
|
529
|
+
lastPostAuthor: string | null;
|
|
530
|
+
lastPostPreview: string | null;
|
|
531
|
+
/** Posts by OTHERS since this viewer last read the topic (all of them when
|
|
532
|
+
* never read). Drives the unread dot. */
|
|
533
|
+
unread: number;
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
export type ForumMemberActivity = {
|
|
537
|
+
contactId: string;
|
|
538
|
+
postCount: number;
|
|
539
|
+
topicsStarted: number;
|
|
540
|
+
lastPostAt: string | null;
|
|
541
|
+
lastPostBody: string | null;
|
|
542
|
+
lastPostTopicTitle: string | null;
|
|
543
|
+
/** This member's posts newer than the OWNER's read cursor on the containing
|
|
544
|
+
* topic. Deliberately only cleared by opening the TOPIC — reading someone's
|
|
545
|
+
* activity feed is not reading the thread the whole room saw. */
|
|
546
|
+
unread: number;
|
|
547
|
+
};
|
|
548
|
+
|
|
549
|
+
export type ForumMemberPost = {
|
|
550
|
+
id: string;
|
|
551
|
+
body: string;
|
|
552
|
+
createdAt: string;
|
|
553
|
+
/** Set when this post filed a review/feature/bug request. */
|
|
554
|
+
kind: ForumPostRequestKind | null;
|
|
555
|
+
attachments: ConversationAttachment[];
|
|
556
|
+
topicId: string;
|
|
557
|
+
topicTitle: string;
|
|
558
|
+
topicVisibility: ForumTopicVisibility;
|
|
559
|
+
topicStatus: ForumTopicStatus;
|
|
560
|
+
/** The agent's answer to THIS post, or null when the turn was waved off
|
|
561
|
+
* ("no answer needed") or is still owed. */
|
|
562
|
+
reply: {
|
|
563
|
+
id: string;
|
|
564
|
+
body: string;
|
|
565
|
+
authorName: string;
|
|
566
|
+
traceId: string | null;
|
|
567
|
+
status: 'pending' | 'complete' | 'failed';
|
|
568
|
+
error: string | null;
|
|
569
|
+
createdAt: string;
|
|
570
|
+
} | null;
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
export type ForumAuthoredTopic = {
|
|
574
|
+
id: string;
|
|
575
|
+
title: string;
|
|
576
|
+
kind: ForumTopicKind;
|
|
577
|
+
visibility: ForumTopicVisibility;
|
|
578
|
+
status: ForumTopicStatus;
|
|
579
|
+
pinned: boolean;
|
|
580
|
+
postCount: number;
|
|
581
|
+
lastPostAt: string | null;
|
|
582
|
+
createdAt: string;
|
|
583
|
+
};
|
|
584
|
+
|
|
585
|
+
export type PendingForumUpload = {
|
|
586
|
+
id: string;
|
|
587
|
+
topicId: string | null;
|
|
588
|
+
postId: string | null;
|
|
589
|
+
topicTitle: string | null;
|
|
590
|
+
contactId: string | null;
|
|
591
|
+
contactName: string | null;
|
|
592
|
+
filename: string;
|
|
593
|
+
mime: string;
|
|
594
|
+
sizeBytes: number;
|
|
595
|
+
createdAt: string;
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
export type AccountFoldersResult =
|
|
599
|
+
| {
|
|
600
|
+
ok: true;
|
|
601
|
+
address: string;
|
|
602
|
+
/** Every folder the server reports right now (the pick list). */
|
|
603
|
+
allFolders: string[];
|
|
604
|
+
/** The current explicit allow-list, or null = "scan all non-excluded". */
|
|
605
|
+
included: string[] | null;
|
|
606
|
+
/** Folders the operator opted OUT of (rendered disabled). */
|
|
607
|
+
excluded: string[];
|
|
608
|
+
/** Folders the sync has actually touched (per the cursor). */
|
|
609
|
+
scanned: string[];
|
|
610
|
+
}
|
|
611
|
+
| { ok: false; error: string };
|
|
612
|
+
|
|
613
|
+
export interface FolderFacet {
|
|
614
|
+
folder: string;
|
|
615
|
+
count: number;
|
|
616
|
+
unread: number;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
export interface MessageListItem {
|
|
620
|
+
id: string;
|
|
621
|
+
fromAddr: string;
|
|
622
|
+
fromName: string | null;
|
|
623
|
+
subject: string | null;
|
|
624
|
+
snippet: string | null;
|
|
625
|
+
internalDate: Date;
|
|
626
|
+
isRead: boolean;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
export interface MsConfigStatus {
|
|
630
|
+
configured: boolean;
|
|
631
|
+
/** Where the active config comes from — drives the UI ("set here" vs "from
|
|
632
|
+
* environment, read-only"). */
|
|
633
|
+
source: 'db' | 'env' | null;
|
|
634
|
+
clientId: string | null;
|
|
635
|
+
tenant: string;
|
|
636
|
+
redirectUri: string | null;
|
|
637
|
+
/** Masked secret for display; never the plaintext. */
|
|
638
|
+
secretMasked: string | null;
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
/** One retrieved (or near-miss) item: capped text + its ranking distance. */
|
|
642
|
+
export type SnapshotItem = {
|
|
643
|
+
text: string;
|
|
644
|
+
/** Ranking distance (cosine, salience/recency-adjusted where the section
|
|
645
|
+
* ranks that way). Null for always-injected items (preferences) that
|
|
646
|
+
* bypass the vector race. */
|
|
647
|
+
dist: number | null;
|
|
648
|
+
kind?: string | null;
|
|
649
|
+
entity?: string | null;
|
|
650
|
+
nodeId?: string | null;
|
|
651
|
+
title?: string | null;
|
|
652
|
+
heading?: string | null;
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
export type ContextSnapshot = {
|
|
656
|
+
query: {
|
|
657
|
+
/** The inbound text as given to retrieval (snipped). */
|
|
658
|
+
inbound: string;
|
|
659
|
+
/** The anaphora-enriched text actually embedded, when it differs. */
|
|
660
|
+
enriched: string | null;
|
|
661
|
+
/** False when embedding was skipped or failed — retrieval ran blind. */
|
|
662
|
+
embedded: boolean;
|
|
663
|
+
};
|
|
664
|
+
facts: { sent: SnapshotItem[]; dropped: SnapshotItem[]; guard: number };
|
|
665
|
+
contentHits: { sent: SnapshotItem[]; dropped: SnapshotItem[]; cutoff: number };
|
|
666
|
+
chunkHits: { sent: SnapshotItem[]; dropped: SnapshotItem[]; cutoff: number };
|
|
667
|
+
relations: string[];
|
|
668
|
+
digests: { count: number; topics: string[] };
|
|
669
|
+
history: {
|
|
670
|
+
count: number;
|
|
671
|
+
/** How many outbound turns carried a [tool record: …] read-back suffix. */
|
|
672
|
+
toolRecords: number;
|
|
673
|
+
/** How many turns carried a [media record: …] read-back suffix. */
|
|
674
|
+
mediaRecords: number;
|
|
675
|
+
};
|
|
676
|
+
personaNotes: { count: number };
|
|
677
|
+
corpusMap: { count: number; truncated: boolean };
|
|
678
|
+
};
|
|
679
|
+
|
|
680
|
+
export type BackupFrequency = 'daily' | 'weekly';
|
|
681
|
+
|
|
682
|
+
export type PageVisibility = 'private' | 'public';
|
|
683
|
+
|
|
684
|
+
/**
|
|
685
|
+
* Recurrence frequencies an event can repeat on. `none` is the default —
|
|
686
|
+
* a one-shot event. The reminder worker rolls a recurring event's single
|
|
687
|
+
* row forward to its next occurrence after each ping (no instance
|
|
688
|
+
* materialisation), so one node always represents the next upcoming hit.
|
|
689
|
+
*/
|
|
690
|
+
export type RecurFreq = 'none' | 'daily' | 'weekly' | 'monthly' | 'yearly';
|
|
691
|
+
|
|
692
|
+
/** Transports that can deliver a reminder out-of-band. A browser ('web') can't
|
|
693
|
+
* receive a push, so it never becomes a reminder target. */
|
|
694
|
+
export type ReminderChannel = 'telegram' | 'mobile';
|
|
695
|
+
|
|
696
|
+
/** Live thinking-trail display modes. */
|
|
697
|
+
export type ThoughtTrailMode = 'list' | 'replace';
|
|
698
|
+
|
|
699
|
+
/** The onboarding "Models" step's stored choices. Kept as one object so the
|
|
700
|
+
* projection can't half-apply; every field optional so partial saves survive. */
|
|
701
|
+
export interface OnboardingModelChoices {
|
|
702
|
+
/** OpenRouter slug for the assistant/persona agent (e.g. `anthropic/claude-sonnet-4.6`). */
|
|
703
|
+
assistantModel?: string;
|
|
704
|
+
/** OpenRouter slug for the indexing workers (e.g. `google/gemini-3.1-flash-lite`). */
|
|
705
|
+
workerModel?: string;
|
|
706
|
+
/** Where the models run: OpenRouter (default) or an Azure OpenAI endpoint. */
|
|
707
|
+
route?: 'openrouter' | 'azure';
|
|
708
|
+
/** Azure OpenAI base URL (the OpenAI-compatible v1 endpoint), when route=azure. */
|
|
709
|
+
azureBaseUrl?: string;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
/**
|
|
713
|
+
* Who a share admits. Lives in `shares.settings.mode` (absent = 'public', so
|
|
714
|
+
* every pre-existing share keeps its behavior).
|
|
715
|
+
*
|
|
716
|
+
* public — anyone with the link (the original model).
|
|
717
|
+
* team — the visitor must additionally present a live team credential
|
|
718
|
+
* (see @mantle/content/team-tokens). Enforced for every kind on
|
|
719
|
+
* the /s/ surface (page render, asset bytes, app brokers).
|
|
720
|
+
* Team-mode PAGE shares double as the /team hub's briefing
|
|
721
|
+
* sections (see ./team-hub).
|
|
722
|
+
*/
|
|
723
|
+
export type ShareMode = 'public' | 'team';
|
|
724
|
+
|
|
725
|
+
export type TeamVisibleShare = {
|
|
726
|
+
/** Share token — the workspace opens /s/<token>. */
|
|
727
|
+
token: string;
|
|
728
|
+
nodeId: string;
|
|
729
|
+
title: string;
|
|
730
|
+
icon: string | null;
|
|
731
|
+
summary: string | null;
|
|
732
|
+
updatedAt: string;
|
|
733
|
+
/** 'team' or 'public' — a member may open both, the badge tells them apart. */
|
|
734
|
+
mode: 'team' | 'public';
|
|
735
|
+
/** Parent node id — lets the pages section rebuild the sub-page tree over
|
|
736
|
+
* the SHARED subset (an unshared parent leaves its children as roots). */
|
|
737
|
+
parentId: string | null;
|
|
738
|
+
tags: string[];
|
|
739
|
+
/**
|
|
740
|
+
* EVENTS ONLY — the event's own start, from `nodes.data.starts_at`.
|
|
741
|
+
*
|
|
742
|
+
* Every other field here describes the SHARE; this one describes the thing
|
|
743
|
+
* shared, and it is carried because for an event the two are not
|
|
744
|
+
* interchangeable. `updatedAt` says when the row was last written, which is
|
|
745
|
+
* the right meta line for a note or a table and useless for an event: a
|
|
746
|
+
* member scanning what is coming up needs WHEN IT HAPPENS, and an event
|
|
747
|
+
* edited this morning sorts above one starting tomorrow.
|
|
748
|
+
*
|
|
749
|
+
* Optional so a client pinned to an older server still parses the payload,
|
|
750
|
+
* and null for every non-event type.
|
|
751
|
+
*/
|
|
752
|
+
startsAt?: string | null;
|
|
753
|
+
};
|
|
754
|
+
|
|
755
|
+
// ── Mirrors of @mantle/db jsonb/enum shapes (jackdaw split P0) ────────────────
|
|
756
|
+
// Kept standalone so this package stays zero-dep (same convention as ToolHandler
|
|
757
|
+
// above). Drift is caught where the server builds these DTOs from db rows —
|
|
758
|
+
// an incompatible change there is a compile error at the row-builder.
|
|
759
|
+
|
|
760
|
+
/** Task lifecycle vocabulary — mirrors content's TASK_STATUSES/TASK_PRIORITIES
|
|
761
|
+
* consts, which are `satisfies`-checked against these unions. */
|
|
762
|
+
export type TaskStatus = 'open' | 'in_progress' | 'blocked' | 'done';
|
|
763
|
+
export type TaskPriority = 'low' | 'normal' | 'high';
|
|
764
|
+
|
|
765
|
+
/** Mirrors @mantle/db `ForumTopicKind`. */
|
|
766
|
+
export type ForumTopicKind = 'question' | 'review' | 'feature' | 'bug' | 'discussion';
|
|
767
|
+
/** Mirrors @mantle/db `ForumTopicVisibility`. */
|
|
768
|
+
export type ForumTopicVisibility = 'team' | 'private';
|
|
769
|
+
/** Mirrors @mantle/db `ForumTopicStatus`. */
|
|
770
|
+
export type ForumTopicStatus = 'open' | 'answered' | 'closed';
|
|
771
|
+
/** Mirrors @mantle/db `ForumPostRequestKind` — the topic kinds that file an
|
|
772
|
+
* owner review task. */
|
|
773
|
+
export type ForumPostRequestKind = 'review' | 'feature' | 'bug';
|
|
774
|
+
|
|
775
|
+
/** Mirrors @mantle/db `ConversationAttachment` (jsonb on conversation rows). */
|
|
776
|
+
export type ConversationAttachment = {
|
|
777
|
+
kind: 'image' | 'audio' | 'voice' | 'document' | 'video';
|
|
778
|
+
mime?: string;
|
|
779
|
+
caption?: string;
|
|
780
|
+
nodeId?: string;
|
|
781
|
+
fileId?: string;
|
|
782
|
+
url?: string;
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
/** Mirrors @mantle/db `AppSource` — a mini app's virtual file tree. */
|
|
786
|
+
export type AppSource = {
|
|
787
|
+
/** Path of the entry module within `files`; must `export default App`. */
|
|
788
|
+
entry: string;
|
|
789
|
+
/** path → TSX/TS source. Bounded (~30 files / ~256 KB) to stay a mini app. */
|
|
790
|
+
files: Record<string, string>;
|
|
791
|
+
};
|
|
792
|
+
|
|
793
|
+
/** Mirrors @mantle/db `AppManifest` — the runtime contract for a running app. */
|
|
794
|
+
export type AppManifest = {
|
|
795
|
+
toolSlugs?: string[];
|
|
796
|
+
sqlite?: { schemaSql: string; schemaVersion: number };
|
|
797
|
+
description?: string;
|
|
798
|
+
};
|
|
799
|
+
|
|
800
|
+
/** Mirrors @mantle/db `BuildRef` — pointer to a bundled artifact in storage. */
|
|
801
|
+
export type BuildRef = {
|
|
802
|
+
storageKey: string;
|
|
803
|
+
sha256: string;
|
|
804
|
+
builtAt: string;
|
|
805
|
+
esbuildVersion: string;
|
|
806
|
+
bytes: number;
|
|
807
|
+
ok: boolean;
|
|
808
|
+
warnings?: string[];
|
|
809
|
+
css?: { storageKey: string; sha256: string; bytes: number };
|
|
810
|
+
};
|
|
811
|
+
|
|
812
|
+
// ── Redacted account DTOs (hand-mirrored; jackdaw split P0) ───────────────────
|
|
813
|
+
// These mirror db-derived server types (`Omit<EmailAccount,…>` etc.) that can't
|
|
814
|
+
// be re-exported without dragging the postgres type graph in. Timestamps are
|
|
815
|
+
// ISO strings here — the wire truth — where the server-side originals carry
|
|
816
|
+
// `Date`. Key-set drift checks live next to the server definitions
|
|
817
|
+
// (email/accounts.ts, microsoft/accounts.ts, email's sync-runs consumer).
|
|
818
|
+
|
|
819
|
+
/** Mirrors @mantle/email `PublicEmailAccount` (an `email_accounts` row minus
|
|
820
|
+
* the sealed IMAP secret). */
|
|
821
|
+
export interface PublicEmailAccount {
|
|
822
|
+
id: string;
|
|
823
|
+
userId: string;
|
|
824
|
+
provider: 'gmail' | 'microsoft' | 'imap';
|
|
825
|
+
address: string;
|
|
826
|
+
displayName: string | null;
|
|
827
|
+
imapHost: string | null;
|
|
828
|
+
imapPort: number | null;
|
|
829
|
+
imapSecure: boolean;
|
|
830
|
+
smtpHost: string | null;
|
|
831
|
+
smtpPort: number | null;
|
|
832
|
+
smtpSecure: boolean;
|
|
833
|
+
/** @deprecated historical reads only (migration 0002). */
|
|
834
|
+
imapFolders: string[];
|
|
835
|
+
imapExcludedFolders: string[];
|
|
836
|
+
imapIncludedFolders: string[] | null;
|
|
837
|
+
firstScanDays: number;
|
|
838
|
+
ingestPolicy: 'approve_list' | 'block_list';
|
|
839
|
+
branchPath: string;
|
|
840
|
+
msAccountId: string | null;
|
|
841
|
+
syncState: Record<string, unknown>;
|
|
842
|
+
lastSyncAt: string | null;
|
|
843
|
+
lastSyncError: string | null;
|
|
844
|
+
enabled: boolean;
|
|
845
|
+
createdAt: string;
|
|
846
|
+
updatedAt: string;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/** Mirrors @mantle/db `SyncRun` (a `sync_runs` row) as it crosses the wire. */
|
|
850
|
+
export interface SyncRun {
|
|
851
|
+
id: string;
|
|
852
|
+
accountId: string;
|
|
853
|
+
startedAt: string;
|
|
854
|
+
finishedAt: string | null;
|
|
855
|
+
durationMs: number | null;
|
|
856
|
+
status: 'running' | 'ok' | 'error';
|
|
857
|
+
scanned: number;
|
|
858
|
+
ingested: number;
|
|
859
|
+
error: string | null;
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
/** Mirrors @mantle/microsoft `PublicMsAccount` (an `ms_accounts` row with the
|
|
863
|
+
* sealed OAuth tokens replaced by presence flags). */
|
|
864
|
+
export interface PublicMsAccount {
|
|
865
|
+
id: string;
|
|
866
|
+
userId: string;
|
|
867
|
+
upn: string;
|
|
868
|
+
displayName: string | null;
|
|
869
|
+
tenantId: string | null;
|
|
870
|
+
tokenExpiresAt: string | null;
|
|
871
|
+
scopes: string[];
|
|
872
|
+
branchPath: string;
|
|
873
|
+
surfaces: Record<string, boolean>;
|
|
874
|
+
syncState: Record<string, unknown>;
|
|
875
|
+
lastSyncAt: string | null;
|
|
876
|
+
lastSyncError: string | null;
|
|
877
|
+
enabled: boolean;
|
|
878
|
+
createdAt: string;
|
|
879
|
+
updatedAt: string;
|
|
880
|
+
hasAccessToken: boolean;
|
|
881
|
+
hasRefreshToken: boolean;
|
|
882
|
+
}
|