@crossworks/client-types 0.232.127 → 0.232.140
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/index.ts
CHANGED
|
@@ -16,2538 +16,19 @@
|
|
|
16
16
|
* must remain zero-dependency and browser-safe.
|
|
17
17
|
*
|
|
18
18
|
* Dates are ISO strings here — that's how they cross the wire (JSON has no Date).
|
|
19
|
-
*/
|
|
20
|
-
import type { AuditSeverity, SystemReport } from './types/integrity';
|
|
21
|
-
import type { TraceDetail } from './traces-format';
|
|
22
|
-
|
|
23
|
-
// ── Skills ────────────────────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
/** A skill as returned by `GET /api/skills`. */
|
|
26
|
-
export interface SkillDTO {
|
|
27
|
-
id: string;
|
|
28
|
-
slug: string;
|
|
29
|
-
name: string;
|
|
30
|
-
description: string;
|
|
31
|
-
instructions: string;
|
|
32
|
-
/** Template state heartbeats inherit on create. */
|
|
33
|
-
defaultState: Record<string, unknown>;
|
|
34
|
-
enabled: boolean;
|
|
35
|
-
createdAt: string;
|
|
36
|
-
updatedAt: string;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** A heartbeat that references a skill — drives the "used by N heartbeats" badge. */
|
|
40
|
-
export interface HeartbeatRef {
|
|
41
|
-
slug: string;
|
|
42
|
-
name: string;
|
|
43
|
-
status: string;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** `GET /api/skills/backrefs` — heartbeat refs keyed by skill slug. */
|
|
47
|
-
export type SkillBackrefs = Record<string, HeartbeatRef[]>;
|
|
48
|
-
|
|
49
|
-
// ── Tools ─────────────────────────────────────────────────────────────────────
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Tool handler descriptor — the canonical wire shape. Mirrors @mantle/db's
|
|
53
|
-
* `ToolHandler` union; kept standalone here so this package stays zero-dep (no
|
|
54
|
-
* postgres type graph). Drift is caught where it matters: `@mantle/tools` aliases
|
|
55
|
-
* `ToolSummary = ToolDTO`, so if db's union ever diverges from this one, that
|
|
56
|
-
* package fails to compile.
|
|
57
|
-
*/
|
|
58
|
-
export interface RecipeStep {
|
|
59
|
-
tool: string;
|
|
60
|
-
input?: Record<string, unknown>;
|
|
61
|
-
as?: string;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export type ToolHandler =
|
|
65
|
-
| { kind: 'builtin'; ref: string }
|
|
66
|
-
| {
|
|
67
|
-
kind: 'http';
|
|
68
|
-
url: string;
|
|
69
|
-
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
70
|
-
headers?: Record<string, string>;
|
|
71
|
-
query?: Record<string, string>;
|
|
72
|
-
body?: string | null;
|
|
73
|
-
headersRef?: string | null;
|
|
74
|
-
authRef?: string | null;
|
|
75
|
-
timeoutMs?: number;
|
|
76
|
-
/** Provenance on rows materialised by an OpenAPI connector's sync. */
|
|
77
|
-
openapi?: { group: string; op: string; vanishedAt?: string; editedAt?: string };
|
|
78
|
-
}
|
|
79
|
-
| { kind: 'shell'; cmd: string }
|
|
80
|
-
| { kind: 'mcp'; group: string; toolName: string; vanishedAt?: string }
|
|
81
|
-
| { kind: 'recipe'; steps: RecipeStep[]; output?: unknown };
|
|
82
|
-
|
|
83
|
-
/** A tool as returned by `GET /api/tools`. */
|
|
84
|
-
export interface ToolDTO {
|
|
85
|
-
id: string;
|
|
86
|
-
slug: string;
|
|
87
|
-
name: string;
|
|
88
|
-
description: string;
|
|
89
|
-
inputSchema: Record<string, unknown>;
|
|
90
|
-
handler: ToolHandler;
|
|
91
|
-
requiresConfirm: boolean;
|
|
92
|
-
enabled: boolean;
|
|
93
|
-
createdAt: string;
|
|
94
|
-
updatedAt: string;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/** `GET/PUT /api/tools/settings` — the two owner-level tool policy toggles. */
|
|
98
|
-
export interface ToolSettings {
|
|
99
|
-
/** Tools an agent authors (Toolsmith) start confirm-gated until cleared. */
|
|
100
|
-
requireApproval: boolean;
|
|
101
|
-
/** Unattended heartbeats park email/web calls for approval. */
|
|
102
|
-
egressGate: boolean;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// ── Tool groups ───────────────────────────────────────────────────────────────
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* The service binding on a group that IS an API integration: where its calls go,
|
|
109
|
-
* which vault entry authenticates them, where that credential is placed, and
|
|
110
|
-
* pointers to the stored API docs + usage skill. Mirrors the `ToolGroupIntegration`
|
|
111
|
-
* type in @mantle/db. `secretRef` is a `service/label` pointer and auth-template
|
|
112
|
-
* values are `{{secret:…}}` refs — a plaintext key never crosses this wire.
|
|
113
|
-
*/
|
|
114
|
-
export interface ToolGroupIntegrationDTO {
|
|
115
|
-
service: string;
|
|
116
|
-
baseUrl?: string;
|
|
117
|
-
secretRef?: string;
|
|
118
|
-
authTemplate?: {
|
|
119
|
-
headers?: Record<string, string>;
|
|
120
|
-
query?: Record<string, string>;
|
|
121
|
-
};
|
|
122
|
-
docsNodeId?: string;
|
|
123
|
-
docsSourceUrl?: string;
|
|
124
|
-
docsUpdatedAt?: string;
|
|
125
|
-
/** Slug of the usage skill that travels with this group's grant. */
|
|
126
|
-
skillSlug?: string;
|
|
127
|
-
/** Set when the group is an MCP CONNECTOR — a mirror of an external MCP
|
|
128
|
-
* server's tools. `secretRef` is a `service/label` vault pointer; the
|
|
129
|
-
* sync-bookkeeping fields are written by the connector sync. */
|
|
130
|
-
mcp?: {
|
|
131
|
-
url: string;
|
|
132
|
-
secretRef?: string;
|
|
133
|
-
authHeader?: string;
|
|
134
|
-
authScheme?: string;
|
|
135
|
-
/** OAuth bookkeeping when the server uses the MCP auth flow. Tokens and
|
|
136
|
-
* the client registration are vault-sealed and never cross this wire. */
|
|
137
|
-
oauth?: {
|
|
138
|
-
enabled: true;
|
|
139
|
-
status: 'pending' | 'connected' | 'needs_reconnect';
|
|
140
|
-
clientId?: string;
|
|
141
|
-
pending?: { state: string; redirectUri: string; startedAt: string };
|
|
142
|
-
redirectUri?: string;
|
|
143
|
-
tokenExpiresAt?: string;
|
|
144
|
-
connectedAt?: string;
|
|
145
|
-
lastError?: string;
|
|
146
|
-
};
|
|
147
|
-
lastSyncAt?: string;
|
|
148
|
-
toolCount?: number;
|
|
149
|
-
serverInfo?: { name?: string; version?: string };
|
|
150
|
-
};
|
|
151
|
-
/** Set when the group is an OPENAPI CONNECTOR — its operations are compiled
|
|
152
|
-
* into ordinary http tools by the connector sync. Auth stays on the
|
|
153
|
-
* surrounding integration fields (`baseUrl`/`secretRef`/`authTemplate`),
|
|
154
|
-
* never in this block; the sync-bookkeeping fields are written by the sync. */
|
|
155
|
-
openapi?: {
|
|
156
|
-
specUrl: string;
|
|
157
|
-
specHash?: string;
|
|
158
|
-
selection?: { tags?: string[]; operations?: string[] };
|
|
159
|
-
apiTitle?: string;
|
|
160
|
-
apiVersion?: string;
|
|
161
|
-
lastSyncAt?: string;
|
|
162
|
-
toolCount?: number;
|
|
163
|
-
};
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/** A tool group — a named bundle of tool slugs granted to agents wholesale. */
|
|
167
|
-
export interface ToolGroupDTO {
|
|
168
|
-
id: string;
|
|
169
|
-
slug: string;
|
|
170
|
-
name: string;
|
|
171
|
-
description: string;
|
|
172
|
-
toolSlugs: string[];
|
|
173
|
-
/** Set when the group is an API integration; null for capability-only bundles. */
|
|
174
|
-
integration: ToolGroupIntegrationDTO | null;
|
|
175
|
-
enabled: boolean;
|
|
176
|
-
createdAt: string;
|
|
177
|
-
updatedAt: string;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/** `GET /api/tool-groups` — each group plus which agent slugs grant it. */
|
|
181
|
-
export interface ToolGroupWithRefs extends ToolGroupDTO {
|
|
182
|
-
grantedTo: string[];
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ── AI workers ────────────────────────────────────────────────────────────────
|
|
186
|
-
|
|
187
|
-
/** Worker kinds (mirrors the @mantle/db `ai_worker_kind` enum). Drift is caught
|
|
188
|
-
* by `toAiWorkerDTO` in lib/ai-workers, whose mapping won't compile if the db
|
|
189
|
-
* enum gains/renames a value. */
|
|
190
|
-
export type AiWorkerKind =
|
|
191
|
-
| 'reflector'
|
|
192
|
-
| 'extractor'
|
|
193
|
-
| 'summarizer'
|
|
194
|
-
| 'tts'
|
|
195
|
-
| 'stt'
|
|
196
|
-
| 'vision'
|
|
197
|
-
| 'document'
|
|
198
|
-
| 'image_gen'
|
|
199
|
-
| 'embedding'
|
|
200
|
-
| 'search'
|
|
201
|
-
| 'search_advanced'
|
|
202
|
-
| 'narrator'
|
|
203
|
-
| 'suggester';
|
|
204
|
-
|
|
205
|
-
/** An AI worker as returned by `GET /api/ai-workers`. `params` is jsonb (shape
|
|
206
|
-
* varies by kind) — kept loose here; the form narrows per kind. */
|
|
207
|
-
export interface AiWorkerDTO {
|
|
208
|
-
id: string;
|
|
209
|
-
slug: string;
|
|
210
|
-
name: string;
|
|
211
|
-
kind: AiWorkerKind;
|
|
212
|
-
provider: string;
|
|
213
|
-
model: string;
|
|
214
|
-
apiKeyId: string | null;
|
|
215
|
-
systemPrompt: string | null;
|
|
216
|
-
params: Record<string, unknown>;
|
|
217
|
-
enabled: boolean;
|
|
218
|
-
priority: number;
|
|
219
|
-
isDefault: boolean;
|
|
220
|
-
backupProvider: string | null;
|
|
221
|
-
backupModel: string | null;
|
|
222
|
-
backupApiKeyId: string | null;
|
|
223
|
-
backupEnabled: boolean;
|
|
224
|
-
baseUrl: string | null;
|
|
225
|
-
viaTailnet: boolean;
|
|
226
|
-
backupBaseUrl: string | null;
|
|
227
|
-
backupViaTailnet: boolean;
|
|
228
|
-
usageCount: number;
|
|
229
|
-
lastUsedAt: string | null;
|
|
230
|
-
createdAt: string;
|
|
231
|
-
updatedAt: string;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/** `GET /api/ai-workers/config` — static-ish bits the worker form needs. */
|
|
235
|
-
export interface AiWorkerConfig {
|
|
236
|
-
/** Providers with a native-PDF document adapter (vs. rasterize-at-ingest). */
|
|
237
|
-
nativeDocProviders: string[];
|
|
238
|
-
/** Online tailnet peer MagicDNS names (route base-URL datalist). */
|
|
239
|
-
tailnetPeers: string[];
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
// ── Agents ────────────────────────────────────────────────────────────────────
|
|
243
|
-
|
|
244
|
-
/** Conversational + worker roles an agent row can carry. Mirrors the
|
|
245
|
-
* `agent_role` enum (`packages/db/src/schema/agents.ts`); the `/settings/agents`
|
|
246
|
-
* page only lists the conversational ones. */
|
|
247
|
-
export type AgentRole =
|
|
248
|
-
| 'assistant'
|
|
249
|
-
| 'responder'
|
|
250
|
-
| 'extractor'
|
|
251
|
-
| 'summarizer'
|
|
252
|
-
| 'reflector'
|
|
253
|
-
| 'custom'
|
|
254
|
-
// Runner-queue worker template (docs/runs.md) — never conversational.
|
|
255
|
-
| 'worker';
|
|
256
|
-
|
|
257
|
-
/** Per-agent generated avatar (style + seed → DiceBear). null = initials. */
|
|
258
|
-
export interface AgentAvatarDTO {
|
|
259
|
-
style: string;
|
|
260
|
-
seed: string;
|
|
261
|
-
/** Avatar-builder component choices layered over the seed: component name →
|
|
262
|
-
* pinned variant, or null to hide an optional component. Stale entries
|
|
263
|
-
* (from another style) are ignored at render time.
|
|
264
|
-
*
|
|
265
|
-
* READ: absent = seed only. WRITE protocol (agents create/patch): an
|
|
266
|
-
* ABSENT parts key means "keep what's stored" — so a parts-unaware client
|
|
267
|
-
* can never wipe pins — `{}` is the explicit clear, and a non-empty map
|
|
268
|
-
* replaces. Every client that writes avatars must send `{}` to clear. */
|
|
269
|
-
parts?: Record<string, string | null>;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/** Memory/budget tuning (jsonb). All fields optional — empty = runtime defaults.
|
|
273
|
-
* Replicated standalone (NOT re-exported from @mantle/db) to keep this package
|
|
274
|
-
* zero-dep; the server aliases its `AgentMemoryConfig` against this so drift is
|
|
275
|
-
* a compile error. */
|
|
276
|
-
export interface AgentMemoryConfigDTO {
|
|
277
|
-
history_limit?: number;
|
|
278
|
-
history_window_hours?: number | null;
|
|
279
|
-
digest_limit?: number;
|
|
280
|
-
fact_limit?: number;
|
|
281
|
-
content_hit_limit?: number;
|
|
282
|
-
chunk_limit?: number;
|
|
283
|
-
inject_journal?: boolean;
|
|
284
|
-
inject_working_notes?: boolean;
|
|
285
|
-
summarize_threshold?: number;
|
|
286
|
-
summarize_batch?: number;
|
|
287
|
-
extract_types?: string[];
|
|
288
|
-
extract_facts?: boolean;
|
|
289
|
-
extract_cost_cap_micro_usd?: number | null;
|
|
290
|
-
delegate_to?: string[];
|
|
291
|
-
max_iterations?: number;
|
|
292
|
-
result_handling?: {
|
|
293
|
-
inline_max_kb?: number;
|
|
294
|
-
embed_min_kb?: number;
|
|
295
|
-
spill_max_kb?: number;
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/** Sampling + voice-reply params (jsonb). */
|
|
300
|
-
export interface AgentParamsDTO {
|
|
301
|
-
temperature?: number;
|
|
302
|
-
max_tokens?: number;
|
|
303
|
-
top_p?: number;
|
|
304
|
-
max_retries?: number;
|
|
305
|
-
voice?: {
|
|
306
|
-
enabled?: boolean;
|
|
307
|
-
name?: 'alloy' | 'echo' | 'fable' | 'nova' | 'onyx' | 'shimmer';
|
|
308
|
-
model?: 'tts-1' | 'tts-1-hd';
|
|
309
|
-
speed?: number;
|
|
310
|
-
};
|
|
311
|
-
/** Propose a follow-up question after each completed turn (the suggester
|
|
312
|
-
* worker's chip above the chat composer). Absent/false = off. */
|
|
313
|
-
suggest_follow_up?: boolean;
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/** One persona note (jsonb element). Soft-retired, never deleted — the read
|
|
317
|
-
* path filters `retiredAt`. `at`/`retiredAt` are ISO strings. */
|
|
318
|
-
export interface PersonaNoteDTO {
|
|
319
|
-
id?: string;
|
|
320
|
-
kind: 'style' | 'relationship' | 'correction';
|
|
321
|
-
content: string;
|
|
322
|
-
at: string;
|
|
323
|
-
source?: { type: 'turn' | 'digest'; id: string };
|
|
324
|
-
retiredAt?: string;
|
|
325
|
-
retiredReason?: 'superseded' | 'removed';
|
|
326
|
-
supersededBy?: string;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/** Raw counters behind an agent's experience level — always shipped next to
|
|
330
|
-
* the level so the UI can show WHY it is level N. All are lifetime counts for
|
|
331
|
-
* THIS brain (experience is per-brain, display-only — never a trust gate). */
|
|
332
|
-
export interface AgentExperienceComponentsDTO {
|
|
333
|
-
/** Completed conversation turns this agent answered on the assistant
|
|
334
|
-
* stream (web, Telegram, mobile). Team/forum turns live in a different
|
|
335
|
-
* store and are not counted (yet). */
|
|
336
|
-
turns: number;
|
|
337
|
-
/** Tool calls that succeeded across those turns. */
|
|
338
|
-
toolSuccesses: number;
|
|
339
|
-
/** Delegated runs this agent completed for other agents. */
|
|
340
|
-
delegations: number;
|
|
341
|
-
/** Heartbeat fires this agent completed. */
|
|
342
|
-
heartbeats: number;
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/** An agent's experience readout — a soft-capped level derived from real
|
|
346
|
-
* accumulated usage (see `agent-experience.ts` server-side for the weights
|
|
347
|
-
* and curve). Computed at read time; nothing is stored. */
|
|
348
|
-
export interface AgentExperienceDTO {
|
|
349
|
-
level: number;
|
|
350
|
-
/** Total XP earned. */
|
|
351
|
-
xp: number;
|
|
352
|
-
/** Cumulative XP at which the current level began. */
|
|
353
|
-
levelXp: number;
|
|
354
|
-
/** Cumulative XP needed to reach the next level. */
|
|
355
|
-
nextLevelXp: number;
|
|
356
|
-
components: AgentExperienceComponentsDTO;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
/** An agent as returned by `GET /api/agents` (and `…/[id]`). Dates are ISO
|
|
360
|
-
* strings. The server aliases its `AgentSummary` to this so the wire shape and
|
|
361
|
-
* the consuming client can't drift. */
|
|
362
|
-
export interface AgentDTO {
|
|
363
|
-
id: string;
|
|
364
|
-
slug: string;
|
|
365
|
-
name: string;
|
|
366
|
-
description: string | null;
|
|
367
|
-
role: AgentRole;
|
|
368
|
-
provider: string;
|
|
369
|
-
model: string;
|
|
370
|
-
apiKeyId: string | null;
|
|
371
|
-
backupProvider: string | null;
|
|
372
|
-
backupModel: string | null;
|
|
373
|
-
backupApiKeyId: string | null;
|
|
374
|
-
backupEnabled: boolean;
|
|
375
|
-
baseUrl: string | null;
|
|
376
|
-
viaTailnet: boolean;
|
|
377
|
-
backupBaseUrl: string | null;
|
|
378
|
-
backupViaTailnet: boolean;
|
|
379
|
-
ttsWorkerId: string | null;
|
|
380
|
-
systemPrompt: string;
|
|
381
|
-
skillSlugs: string[];
|
|
382
|
-
toolGroupSlugs: string[];
|
|
383
|
-
memoryConfig: AgentMemoryConfigDTO;
|
|
384
|
-
params: AgentParamsDTO;
|
|
385
|
-
avatar: AgentAvatarDTO | null;
|
|
386
|
-
personaNotes: PersonaNoteDTO[];
|
|
387
|
-
/** The co-admin login this agent is the personal assistant for (migration
|
|
388
|
-
* 0143), or null for a shared agent. Set, it becomes that login's default
|
|
389
|
-
* chat target — the mechanism that keeps two people typing at once out of
|
|
390
|
-
* one interleaved thread. Not a privacy boundary: every login still sees
|
|
391
|
-
* and can open every agent. */
|
|
392
|
-
assignedUserId: string | null;
|
|
393
|
-
/** ISO timestamp of the current assignment; null when unassigned. */
|
|
394
|
-
assignedAt: string | null;
|
|
395
|
-
priority: number;
|
|
396
|
-
enabled: boolean;
|
|
397
|
-
/** True when this agent ships from the system manifest (a def-synced
|
|
398
|
-
* specialist). Since 2026-07-29 only its params/memoryConfig tuning
|
|
399
|
-
* re-syncs on upgrade — prompt, model, provider and key are operator-owned
|
|
400
|
-
* and survive. Drives the "system" badge on the agents screens. */
|
|
401
|
-
manifestManaged: boolean;
|
|
402
|
-
lastUsedAt: string | null;
|
|
403
|
-
usageCount: number;
|
|
404
|
-
/** Experience readout (level + raw counters). Optional: filled on the list
|
|
405
|
-
* reads the agent screens use (`GET /api/agents`, the assistant thread
|
|
406
|
-
* bundle); absent on single-row CRUD echoes where computing it would cost
|
|
407
|
-
* extra queries for nothing. */
|
|
408
|
-
experience?: AgentExperienceDTO;
|
|
409
|
-
createdAt: string;
|
|
410
|
-
updatedAt: string;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/** A lightweight agent option (slug + name + role) for picker dropdowns —
|
|
414
|
-
* `GET /api/agents/options`. Unlike `GET /api/agents` (conversational roles
|
|
415
|
-
* only), this lists EVERY agent, so heartbeats can bind worker-role agents. */
|
|
416
|
-
export interface AgentOptionDTO {
|
|
417
|
-
slug: string;
|
|
418
|
-
name: string;
|
|
419
|
-
role: AgentRole;
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
// ── Calendar ────────────────────────────────────────────────────────────────────
|
|
423
|
-
|
|
424
|
-
/** A subscribed calendar feed as returned by `GET /api/calendar` — the wire
|
|
425
|
-
* projection of @mantle/db's `CalendarAccount` row. The sealed `feedUrlEnc`
|
|
426
|
-
* credential, `ownerId`, and `syncState` are server-only and intentionally
|
|
427
|
-
* omitted; dates are ISO strings. The route maps its rows to this so the wire
|
|
428
|
-
* shape and the consuming client can't drift. */
|
|
429
|
-
export interface CalendarAccountDTO {
|
|
430
|
-
id: string;
|
|
431
|
-
/** 'ics' (future: 'google' | 'microsoft'). */
|
|
432
|
-
provider: string;
|
|
433
|
-
displayName: string;
|
|
434
|
-
/** Optional UI accent (hex) so multiple calendars are distinguishable. */
|
|
435
|
-
color: string | null;
|
|
436
|
-
enabled: boolean;
|
|
437
|
-
lastEventCount: number | null;
|
|
438
|
-
lastSyncAt: string | null;
|
|
439
|
-
lastSyncError: string | null;
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
// ── Microsoft (SharePoint / OneDrive) ───────────────────────────────────────────
|
|
443
|
-
|
|
444
|
-
/** A discovered drive as returned by `GET/POST /api/microsoft/accounts/[id]/drives`
|
|
445
|
-
* — the wire projection of @mantle/db's `MsDrive` row. The Graph `deltaLink`
|
|
446
|
-
* cursor and `accountId` are server-only and omitted; `lastSyncAt` is an ISO
|
|
447
|
-
* string. The route maps its rows to this so the shapes can't drift. */
|
|
448
|
-
export interface MsDriveDTO {
|
|
449
|
-
id: string;
|
|
450
|
-
/** Graph drive id. */
|
|
451
|
-
driveId: string;
|
|
452
|
-
/** `personal` (OneDrive) | `documentLibrary` (SharePoint) | other. */
|
|
453
|
-
driveType: string;
|
|
454
|
-
name: string;
|
|
455
|
-
/** SharePoint site display name; null for OneDrive. */
|
|
456
|
-
siteName: string | null;
|
|
457
|
-
webUrl: string | null;
|
|
458
|
-
enabled: boolean;
|
|
459
|
-
lastSyncAt: string | null;
|
|
460
|
-
lastError: string | null;
|
|
461
|
-
/** How many scope selections the drive has; 0 = syncing everything. */
|
|
462
|
-
scopeCount: number;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/** One scope selection on a drive, as stored/returned by
|
|
466
|
-
* `GET/PUT /api/microsoft/drives/[id]/scopes`. Folder scopes include the
|
|
467
|
-
* whole subtree (path prefix); file scopes match that one item. */
|
|
468
|
-
export interface MsDriveScopeDTO {
|
|
469
|
-
itemId: string;
|
|
470
|
-
/** After-`root:` path, always starting with `/` (e.g. `/Reports/2026`). */
|
|
471
|
-
path: string;
|
|
472
|
-
isFolder: boolean;
|
|
473
|
-
name: string | null;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
/** One row of a drive-folder listing from
|
|
477
|
-
* `GET /api/microsoft/drives/[id]/browse` — the scope picker's navigation
|
|
478
|
-
* unit. Selection state is client-derived by matching against the scope set. */
|
|
479
|
-
export interface MsDriveChildDTO {
|
|
480
|
-
itemId: string;
|
|
481
|
-
name: string;
|
|
482
|
-
isFolder: boolean;
|
|
483
|
-
childCount: number | null;
|
|
484
|
-
size: number | null;
|
|
485
|
-
path: string | null;
|
|
486
|
-
webUrl: string | null;
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
// ── Email (inbox reading pane) ──────────────────────────────────────────────────
|
|
490
|
-
|
|
491
|
-
/** One message as returned by `GET /api/email/messages/[id]` — the wire
|
|
492
|
-
* projection of @mantle/db's `Email` row, trimmed to what the reading pane
|
|
493
|
-
* renders. Server-only/sensitive columns are dropped: the raw `bodyHtml` (it's
|
|
494
|
-
* sanitized server-side into `MessageDetailDTO.bodyHtmlSafe` and must never
|
|
495
|
-
* cross the wire untrusted), plus account/node/provider ids, labels, snippet,
|
|
496
|
-
* etc. `internalDate` is an ISO string. */
|
|
497
|
-
export interface EmailDTO {
|
|
498
|
-
id: string;
|
|
499
|
-
subject: string | null;
|
|
500
|
-
fromAddr: string;
|
|
501
|
-
fromName: string | null;
|
|
502
|
-
toAddrs: string[];
|
|
503
|
-
ccAddrs: string[];
|
|
504
|
-
internalDate: string;
|
|
505
|
-
folder: string | null;
|
|
506
|
-
isRead: boolean;
|
|
507
|
-
isStarred: boolean;
|
|
508
|
-
bodyText: string | null;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
/** One attachment row returned with a message. */
|
|
512
|
-
export interface EmailAttachmentDTO {
|
|
513
|
-
id: string;
|
|
514
|
-
filename: string;
|
|
515
|
-
mimeType: string | null;
|
|
516
|
-
sizeBytes: number | null;
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
/** `GET /api/email/messages/[id]` — a message, its attachments, and the
|
|
520
|
-
* server-sanitized HTML body (the raw `bodyHtml` never crosses the wire). */
|
|
521
|
-
export interface MessageDetailDTO {
|
|
522
|
-
email: EmailDTO;
|
|
523
|
-
attachments: EmailAttachmentDTO[];
|
|
524
|
-
bodyHtmlSafe: string | null;
|
|
525
|
-
}
|
|
526
|
-
|
|
527
|
-
// ── Heartbeats ─────────────────────────────────────────────────────────────────
|
|
528
|
-
|
|
529
|
-
/** A heartbeat's schedule (jsonb). `cron` is read-only in v1 (the form locks it);
|
|
530
|
-
* create/update only accept once/interval/manual. `at` is an ISO string. */
|
|
531
|
-
export type HeartbeatScheduleSpecDTO =
|
|
532
|
-
| { kind: 'once'; at: string }
|
|
533
|
-
| { kind: 'interval'; every_minutes: number; jitter_minutes?: number }
|
|
534
|
-
| { kind: 'cron'; expr: string }
|
|
535
|
-
| { kind: 'manual' };
|
|
536
|
-
|
|
537
|
-
/** Where a heartbeat's reply is delivered (jsonb). */
|
|
538
|
-
export type HeartbeatSurfaceDTO = { kind: 'telegram'; chat_id: string } | { kind: 'web' };
|
|
539
|
-
|
|
540
|
-
/** Optional quiet-hours window (jsonb). null tz = use the profile timezone. */
|
|
541
|
-
export interface HeartbeatQuietHoursDTO {
|
|
542
|
-
from: string;
|
|
543
|
-
to: string;
|
|
544
|
-
tz?: string | null;
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
/** A heartbeat as returned by `GET /api/heartbeats(/[id])`. Dates are ISO
|
|
548
|
-
* strings. The server aliases its `HeartbeatSummary` to this so the wire shape
|
|
549
|
-
* and the consuming client can't drift. */
|
|
550
|
-
/** Alias kept for the heartbeats settings screens (formerly @server/lib/heartbeats). */
|
|
551
|
-
export type HeartbeatSummary = HeartbeatDTO;
|
|
552
|
-
|
|
553
|
-
export interface HeartbeatDTO {
|
|
554
|
-
id: string;
|
|
555
|
-
slug: string;
|
|
556
|
-
name: string;
|
|
557
|
-
description: string | null;
|
|
558
|
-
agentSlug: string;
|
|
559
|
-
skillSlug: string;
|
|
560
|
-
scheduleKind: 'once' | 'interval' | 'cron' | 'manual';
|
|
561
|
-
schedule: HeartbeatScheduleSpecDTO;
|
|
562
|
-
surface: HeartbeatSurfaceDTO;
|
|
563
|
-
nextFireAt: string | null;
|
|
564
|
-
lastFiredAt: string | null;
|
|
565
|
-
fireCount: number;
|
|
566
|
-
maxFires: number | null;
|
|
567
|
-
minIdleMinutes: number | null;
|
|
568
|
-
quietHours: HeartbeatQuietHoursDTO | null;
|
|
569
|
-
earliestAt: string | null;
|
|
570
|
-
cooldownMinutes: number | null;
|
|
571
|
-
state: Record<string, unknown>;
|
|
572
|
-
status: 'active' | 'paused' | 'completed' | 'cancelled';
|
|
573
|
-
completionReason: string | null;
|
|
574
|
-
createdAt: string;
|
|
575
|
-
updatedAt: string;
|
|
576
|
-
}
|
|
577
|
-
|
|
578
|
-
// ── Live turn streaming ─────────────────────────────────────────────────────────
|
|
579
|
-
|
|
580
|
-
/**
|
|
581
|
-
* The cross-client contract for live "what the agent is doing" updates during a
|
|
582
|
-
* turn — consumed identically by the web client and the Flutter companion (see
|
|
583
|
-
* `docs/live-turn-streaming.md`). One event stream unifies coarse status, tool
|
|
584
|
-
* activity, reasoning, and token deltas.
|
|
585
|
-
*
|
|
586
|
-
* This is the wire shape ONLY (zero-runtime, per this package's invariant): the
|
|
587
|
-
* server-side channel + publisher + schema-version constant live in
|
|
588
|
-
* `@mantle/turn-stream`; the producer stamps `v`/`seq`/`round`.
|
|
589
19
|
*
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
export interface TurnStartData {
|
|
606
|
-
agentSlug: string;
|
|
607
|
-
/** Resolved model id, when known at turn start (else null). */
|
|
608
|
-
model: string | null;
|
|
609
|
-
/** Durable `assistant_messages` id of the inbound (user) row, persisted before
|
|
610
|
-
* the model runs. Lets a client swap its optimistic user bubble for the
|
|
611
|
-
* canonical row without waiting on the POST. Optional (additive): a client
|
|
612
|
-
* that predates this field ignores it. */
|
|
613
|
-
inboundId?: string;
|
|
614
|
-
/** Durable `assistant_messages` id of the outbound (reply) row, inserted
|
|
615
|
-
* `pending` at turn start. This is the turn's authoritative reconciliation
|
|
616
|
-
* handle — the client binds the reply bubble to it and, on `done`, reads the
|
|
617
|
-
* final text from this row (vs. the advisory streamed buffer). Optional. */
|
|
618
|
-
outboundId?: string;
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/** A short "what it's doing now" line ("Searching your brain…"). `kind` is an
|
|
622
|
-
* optional coarse bucket the UI can theme/iconify. `stepId` ties together the
|
|
623
|
-
* grounded line and its later narrated upgrade for the SAME step, so the client
|
|
624
|
-
* replaces the line in place rather than appending a duplicate. */
|
|
625
|
-
export interface TurnStatusData {
|
|
626
|
-
label: string;
|
|
627
|
-
kind?: string;
|
|
628
|
-
/** Stable id for the step this status describes. Two events sharing a stepId
|
|
629
|
-
* are the same step (grounded → narrated); the client upserts by it. */
|
|
630
|
-
stepId?: string;
|
|
631
|
-
/** Present (true) only on the narrator's rephrased line for a step — the warm
|
|
632
|
-
* first-person paragraph. Grounded lines omit it. Lets clients keep narrated
|
|
633
|
-
* text visible while later grounded lines tick past. */
|
|
634
|
-
narrated?: true;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
/** A tool round began. `summary` is an optional one-line, secret-free preview. */
|
|
638
|
-
export interface TurnToolStartData {
|
|
639
|
-
name: string;
|
|
640
|
-
summary?: string;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
/** A tool round finished (`ok=false` = it errored — the turn may still recover). */
|
|
644
|
-
export interface TurnToolEndData {
|
|
645
|
-
name: string;
|
|
646
|
-
ok: boolean;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
/** A chunk of the model's reasoning stream (raw; may be curated before display). */
|
|
650
|
-
export interface TurnReasoningDeltaData {
|
|
651
|
-
text: string;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
/** A chunk of the visible reply text. */
|
|
655
|
-
export interface TurnTextDeltaData {
|
|
656
|
-
text: string;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
/** Terminal success. The client now reconciles against the durable message row;
|
|
660
|
-
* the streamed text is advisory, the DB row is authoritative. */
|
|
661
|
-
export interface TurnDoneData {
|
|
662
|
-
status: 'complete';
|
|
663
|
-
/** Real output-token total for the whole turn (summed across rounds). The
|
|
664
|
-
* client shows a streamed char-based estimate while the reply types out, then
|
|
665
|
-
* swaps it for this exact figure on `done`. Optional + additive: absent when
|
|
666
|
-
* no provider reported usage, or from a producer that predates the field. */
|
|
667
|
-
tokensOut?: number;
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/** Terminal failure. */
|
|
671
|
-
export interface TurnErrorData {
|
|
672
|
-
status: 'failed';
|
|
673
|
-
message: string;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
/** Fields every turn event carries. */
|
|
677
|
-
export interface TurnEventBase {
|
|
678
|
-
/** Schema version (`TURN_EVENT_SCHEMA_VERSION` at emit time). */
|
|
679
|
-
v: number;
|
|
680
|
-
/** Durable turn id = the outbound `assistant_messages` id. Stable for the turn. */
|
|
681
|
-
turnId: string;
|
|
682
|
-
/** Monotonic per-turn sequence — the SSE `id:` field and the resume cursor. */
|
|
683
|
-
seq: number;
|
|
684
|
-
/** Tool-loop round this event belongs to (0 = before the first round). */
|
|
685
|
-
round: number;
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
/** One live turn event. Discriminated on `type`; `data` is the matching payload. */
|
|
689
|
-
export type TurnEvent =
|
|
690
|
-
| (TurnEventBase & { type: 'turn-start'; data: TurnStartData })
|
|
691
|
-
| (TurnEventBase & { type: 'status'; data: TurnStatusData })
|
|
692
|
-
| (TurnEventBase & { type: 'tool-start'; data: TurnToolStartData })
|
|
693
|
-
| (TurnEventBase & { type: 'tool-end'; data: TurnToolEndData })
|
|
694
|
-
| (TurnEventBase & { type: 'reasoning-delta'; data: TurnReasoningDeltaData })
|
|
695
|
-
| (TurnEventBase & { type: 'text-delta'; data: TurnTextDeltaData })
|
|
696
|
-
| (TurnEventBase & { type: 'done'; data: TurnDoneData })
|
|
697
|
-
| (TurnEventBase & { type: 'error'; data: TurnErrorData });
|
|
698
|
-
|
|
699
|
-
// ── ask_human questionnaire (runner queues) ───────────────────────────────────
|
|
700
|
-
// THE single source of truth for the questionnaire contract. The plan parser
|
|
701
|
-
// (@mantle/tools) validates against these caps, the answer path (@mantle/runs)
|
|
702
|
-
// re-checks submissions against them, and the client renders whatever they
|
|
703
|
-
// admit. They lived in three places once and immediately disagreed — the
|
|
704
|
-
// client's id fallback diverged from the server's, and the client had no
|
|
705
|
-
// question cap while the API capped answers at 4, so a 5-question form
|
|
706
|
-
// rendered fine and then 400'd on submit.
|
|
707
|
-
|
|
708
|
-
/** One selectable answer. `description` is the muted subtext on the chip. */
|
|
709
|
-
export interface AskHumanFormOption {
|
|
710
|
-
label: string;
|
|
711
|
-
description?: string;
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
/** One sub-question of a questionnaire. `id` is the routing key answers are
|
|
715
|
-
* submitted under; `header` is the short chip shown beside the question. */
|
|
716
|
-
export interface AskHumanFormQuestion {
|
|
717
|
-
id: string;
|
|
718
|
-
header?: string;
|
|
719
|
-
question: string;
|
|
720
|
-
options: AskHumanFormOption[];
|
|
721
|
-
multi_select?: boolean;
|
|
722
|
-
/** Free-text escape. Defaults ON — a question whose options don't fit and
|
|
723
|
-
* offers no way to say so forces a wrong answer. */
|
|
724
|
-
allow_other?: boolean;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
export interface AskHumanForm {
|
|
728
|
-
questions: AskHumanFormQuestion[];
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
/** One answered sub-question, as submitted to `PATCH /api/pending/:id` and
|
|
732
|
-
* `pending_approve`. `question` is the form question's `id`. */
|
|
733
|
-
export interface AskHumanFormAnswer {
|
|
734
|
-
question: string;
|
|
735
|
-
selected: string[];
|
|
736
|
-
other?: string;
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
/**
|
|
740
|
-
* Caps on a questionnaire. These are a CONTRACT, not advice: every answer
|
|
741
|
-
* surface renders whatever the parser admits, so an unbounded form is an
|
|
742
|
-
* unanswerable screen — and a cap enforced on only one side is a 400 the
|
|
743
|
-
* operator can't act on.
|
|
744
|
-
*/
|
|
745
|
-
export const ASK_HUMAN_FORM_LIMITS = {
|
|
746
|
-
/** Ask more than this and the answers to the first few probably change what
|
|
747
|
-
* you still need to ask — use a later `ask_human` step. */
|
|
748
|
-
maxQuestions: 4,
|
|
749
|
-
maxOptions: 8,
|
|
750
|
-
/** A header renders as a chip, not a sentence. */
|
|
751
|
-
maxHeaderChars: 24,
|
|
752
|
-
maxQuestionChars: 300,
|
|
753
|
-
maxLabelChars: 80,
|
|
754
|
-
maxDescriptionChars: 200,
|
|
755
|
-
maxOtherChars: 2_000,
|
|
756
|
-
/** The form rides in `run_items.payload` AND the pending row's args, and
|
|
757
|
-
* both are read into prompts. */
|
|
758
|
-
maxFormJsonBytes: 8_000,
|
|
759
|
-
} as const;
|
|
760
|
-
|
|
761
|
-
// ── Row/DTO shapes moved from the server packages (jackdaw split P0) ─────────
|
|
762
|
-
// Sources: @mantle/content, @mantle/email, @mantle/microsoft, @mantle/agent-runtime
|
|
763
|
-
// re-export these names, so server code keeps its original import paths.
|
|
764
|
-
|
|
765
|
-
export type TaskRow = {
|
|
766
|
-
id: string;
|
|
767
|
-
title: string;
|
|
768
|
-
body: string;
|
|
769
|
-
status: TaskStatus;
|
|
770
|
-
priority: TaskPriority;
|
|
771
|
-
dueAt: string | null;
|
|
772
|
-
tags: string[];
|
|
773
|
-
/** Checklist inside the task ("task breakup"). Stored in `data.todos`. */
|
|
774
|
-
todos: TaskTodo[];
|
|
775
|
-
/** Fractional ordering key for the board (within-column order). Stored in
|
|
776
|
-
* `data.rank`; null on tasks never dragged — they sort after ranked ones. */
|
|
777
|
-
rank: string | null;
|
|
778
|
-
/** Comments on this task (node_comments rows). List/detail badge material. */
|
|
779
|
-
commentCount: number;
|
|
780
|
-
summary: string | null;
|
|
781
|
-
/** When the task was filed away, or null while it is live. Archived tasks are
|
|
782
|
-
* excluded from every list, count and board unless explicitly requested —
|
|
783
|
-
* it is what keeps a Done column from growing without bound. Orthogonal to
|
|
784
|
-
* `status`: an archived task keeps the status it had. */
|
|
785
|
-
archivedAt: string | null;
|
|
786
|
-
createdAt: string;
|
|
787
|
-
updatedAt: string;
|
|
788
|
-
};
|
|
789
|
-
|
|
790
|
-
/** One checklist item inside a task. Server assigns `id` on write. */
|
|
791
|
-
export type TaskTodo = {
|
|
792
|
-
id: string;
|
|
793
|
-
text: string;
|
|
794
|
-
done: boolean;
|
|
795
|
-
};
|
|
796
|
-
|
|
797
|
-
/** Mirrors @mantle/db `NodeCommentAuthorKind`. */
|
|
798
|
-
export type NodeCommentAuthorKind = 'owner' | 'member' | 'agent';
|
|
799
|
-
|
|
800
|
-
/**
|
|
801
|
-
* A comment on a node (tasks first; the table is node-generic). `authorName`
|
|
802
|
-
* is a display snapshot at post time; `mine` is computed server-side per
|
|
803
|
-
* viewer (an owner login sees its own comments as mine, a team member sees
|
|
804
|
-
* theirs), so clients never reconcile the two auth worlds themselves.
|
|
805
|
-
*/
|
|
806
|
-
export type NodeComment = {
|
|
807
|
-
id: string;
|
|
808
|
-
nodeId: string;
|
|
809
|
-
authorKind: NodeCommentAuthorKind;
|
|
810
|
-
authorName: string;
|
|
811
|
-
mine: boolean;
|
|
812
|
-
body: string;
|
|
813
|
-
createdAt: string;
|
|
814
|
-
editedAt: string | null;
|
|
815
|
-
};
|
|
816
|
-
|
|
817
|
-
export type JournalRow = {
|
|
818
|
-
id: string;
|
|
819
|
-
title: string;
|
|
820
|
-
body: string;
|
|
821
|
-
/** Who wrote the entry. Stamped server-side; agent tool calls can't spoof it. */
|
|
822
|
-
author: 'user' | 'agent';
|
|
823
|
-
/** Authoring agent's slug when author='agent'; null for user-authored rows. */
|
|
824
|
-
agentSlug: string | null;
|
|
825
|
-
/** Kind key (see KINDS in journal-options). Legacy pre-v2 rows map their old
|
|
826
|
-
* `category` to a kind at read time; free text is tolerated. */
|
|
827
|
-
kind: string | null;
|
|
828
|
-
/** Gap lifecycle — only entries with kind='gap' carry one ('open'|'resolved'). */
|
|
829
|
-
status: string | null;
|
|
830
|
-
entryDate: string | null;
|
|
831
|
-
tags: string[];
|
|
832
|
-
summary: string | null;
|
|
833
|
-
createdAt: string;
|
|
834
|
-
updatedAt: string;
|
|
835
|
-
};
|
|
836
|
-
|
|
837
|
-
export type EventRow = {
|
|
838
|
-
id: string;
|
|
839
|
-
title: string;
|
|
840
|
-
body: string;
|
|
841
|
-
startsAt: string;
|
|
842
|
-
endsAt: string | null;
|
|
843
|
-
location: string | null;
|
|
844
|
-
remindMinutesBefore: number;
|
|
845
|
-
remindAt: string;
|
|
846
|
-
reminderSentAt: string | null;
|
|
847
|
-
/** IANA timezone (e.g. "Africa/Johannesburg") captured from the
|
|
848
|
-
* client at create time. Used for display only — `starts_at` is
|
|
849
|
-
* always a UTC instant so the reminder fires at the right moment
|
|
850
|
-
* regardless of where the agent process or DB run. Defaults to
|
|
851
|
-
* 'UTC' if the client didn't supply one. */
|
|
852
|
-
timezone: string;
|
|
853
|
-
/** Recurrence frequency; 'none' for a one-shot event. */
|
|
854
|
-
recur: RecurFreq;
|
|
855
|
-
/** Optional end-of-series cutoff (ISO). null = repeats until deleted. */
|
|
856
|
-
recurUntil: string | null;
|
|
857
|
-
tags: string[];
|
|
858
|
-
summary: string | null;
|
|
859
|
-
createdAt: string;
|
|
860
|
-
updatedAt: string;
|
|
861
|
-
};
|
|
862
|
-
|
|
863
|
-
/** Notion-style content width: centered/narrow vs full available space. */
|
|
864
|
-
export type PageWidth = 'narrow' | 'wide';
|
|
865
|
-
|
|
866
|
-
export type PageRow = {
|
|
867
|
-
id: string;
|
|
868
|
-
/** Parent page id, or null for a top-level page. Drives the /pages tree
|
|
869
|
-
* and the `childPage` card (Phase 4a sub-pages). */
|
|
870
|
-
parentId: string | null;
|
|
871
|
-
title: string;
|
|
872
|
-
icon: string | null;
|
|
873
|
-
tags: string[];
|
|
874
|
-
summary: string | null;
|
|
875
|
-
visibility: PageVisibility;
|
|
876
|
-
width: PageWidth;
|
|
877
|
-
createdAt: string;
|
|
878
|
-
updatedAt: string;
|
|
879
|
-
};
|
|
880
|
-
|
|
881
|
-
export type AppRow = {
|
|
882
|
-
id: string;
|
|
883
|
-
title: string;
|
|
884
|
-
icon: string | null;
|
|
885
|
-
tags: string[];
|
|
886
|
-
summary: string | null;
|
|
887
|
-
description: string | null;
|
|
888
|
-
/** Number of declared api_tool slugs. */
|
|
889
|
-
toolCount: number;
|
|
890
|
-
/** Whether the published source has a green build (renders today). */
|
|
891
|
-
hasBuild: boolean;
|
|
892
|
-
/** Whether an uncommitted draft exists. */
|
|
893
|
-
hasDraft: boolean;
|
|
894
|
-
/**
|
|
895
|
-
* The app's exposure: mode of its active share ('public' | 'team'), or null
|
|
896
|
-
* when it has never been shared / the share is revoked (owner-only).
|
|
897
|
-
*/
|
|
898
|
-
shareMode: ShareMode | null;
|
|
899
|
-
/** Whether this app is the designated Team Hub (prefs.teamHubAppId). */
|
|
900
|
-
isHub: boolean;
|
|
901
|
-
createdAt: string;
|
|
902
|
-
updatedAt: string;
|
|
903
|
-
};
|
|
904
|
-
|
|
905
|
-
export type AppDetail = AppRow & {
|
|
906
|
-
source: AppSource;
|
|
907
|
-
draft: AppSource | null;
|
|
908
|
-
manifest: AppManifest;
|
|
909
|
-
draftBuild: BuildRef | null;
|
|
910
|
-
publishedBuild: BuildRef | null;
|
|
911
|
-
};
|
|
912
|
-
|
|
913
|
-
export type ProfilePreferences = {
|
|
914
|
-
/** IANA timezone, e.g. 'Africa/Johannesburg'. UTC when not set. */
|
|
915
|
-
timezone: string;
|
|
916
|
-
/** The last zone the auto-from-location hook DERIVED (not necessarily the one
|
|
917
|
-
* in `timezone`, if the user manually overrode since). Used purely for
|
|
918
|
-
* hysteresis: the hook only acts when the freshly-derived zone differs from
|
|
919
|
-
* this, so it won't fight a manual change or re-switch every turn at the same
|
|
920
|
-
* place. See auto-timezone.ts. */
|
|
921
|
-
lastAutoTimezone?: string;
|
|
922
|
-
/** BCP-47 locale, e.g. 'en-GB'. Drives date/number/currency
|
|
923
|
-
* formatting. Falls back to en-GB to match the legacy pinned
|
|
924
|
-
* format-datetime behaviour, so existing UI doesn't shift for
|
|
925
|
-
* users who haven't visited /settings/profile yet. */
|
|
926
|
-
locale: string;
|
|
927
|
-
/** Avatar style id — the BRAIN's avatar visual language, applied to every
|
|
928
|
-
* generated avatar (the owner's and every agent's). Brain-level alongside
|
|
929
|
-
* colorTheme and the display fonts, because it is a branding choice, not a
|
|
930
|
-
* personal one: one style with a different seed per entity reads as one
|
|
931
|
-
* product, six unrelated styles at once read as noise. Individuality lives
|
|
932
|
-
* in `avatarSeed`, which stays personal. See @mantle/web-ui/avatar for the
|
|
933
|
-
* registry; unknown ids resolve to the default rather than stranding. */
|
|
934
|
-
avatarStyle?: string;
|
|
935
|
-
/** How much of the theme generated avatars take on: 'native' (the style's own
|
|
936
|
-
* palette), 'mixed' (themed background, original artwork — the default) or
|
|
937
|
-
* 'theme' (theme colours throughout). Brain-level for the same reason as
|
|
938
|
-
* avatarStyle: it describes how this brain's avatars look, not one login's
|
|
939
|
-
* taste. Read via projectAvatarTint, never raw. */
|
|
940
|
-
avatarTint?: string;
|
|
941
|
-
/** Which generated background each area of the shell shows, as
|
|
942
|
-
* `area=style` pairs (`menu=waves,header=off`). Brain-level for the same
|
|
943
|
-
* reason as avatarStyle and colorTheme: it is the look of the product.
|
|
944
|
-
* `off` is a real, storable choice, see @mantle/web-ui/backgrounds. Areas
|
|
945
|
-
* on their default are omitted, so a default change still reaches brains
|
|
946
|
-
* that never chose. Read via projectBackgrounds, never raw. */
|
|
947
|
-
backgrounds?: string;
|
|
948
|
-
/** The generated whole-surface Neat gradient (login screen, content area),
|
|
949
|
-
* as a compact JSON spec `{v,seed,tone,speed}` — colours are DERIVED from
|
|
950
|
-
* the live theme tokens client-side, never stored, so the background
|
|
951
|
-
* follows every colour theme and mode. Brain-level for the same reason as
|
|
952
|
-
* backgrounds: it is the look of the product. Unset ⇒ the plain themed
|
|
953
|
-
* fill. Read via projectNeatBackground, never raw. */
|
|
954
|
-
neatBackground?: string;
|
|
955
|
-
/** The brain's default light/dark mode for surfaces where the visitor has
|
|
956
|
-
* not chosen one themselves — today the public /s share reader, which stamps
|
|
957
|
-
* it server-side and lets the visitor's own toggle override it locally.
|
|
958
|
-
* 'light' | 'dark' | 'system'; unset ⇒ 'light' (the share surface's
|
|
959
|
-
* historical rendering, so an unset brain looks exactly as before). Brain-
|
|
960
|
-
* level like colorTheme: it is the look of the product's public face. Read
|
|
961
|
-
* via projectDefaultMode, never raw. */
|
|
962
|
-
defaultMode?: string;
|
|
963
|
-
/** Whether the public /s share reader paints the saved Neat gradient at all.
|
|
964
|
-
* Default ON (only an explicit `false` disables, the streamThoughts
|
|
965
|
-
* contract): the switch exists for owners who want share links to stay on
|
|
966
|
-
* the plain themed surface — the printable rendering — while the app keeps
|
|
967
|
-
* its background. Brain-level like neatBackground itself. */
|
|
968
|
-
shareNeat?: boolean;
|
|
969
|
-
/** Seed for THIS user's avatar; the UI defaults it to the user id when unset
|
|
970
|
-
* so an avatar still renders. Personal — two admins share the brain's style
|
|
971
|
-
* but never the same avatar. */
|
|
972
|
-
avatarSeed?: string;
|
|
973
|
-
/** Avatar-builder component choices for THIS login's avatar, layered over
|
|
974
|
-
* the seed: component name → pinned variant, or null to hide an optional
|
|
975
|
-
* component. Per-login (the profile routes address the ACTOR's row). Stale
|
|
976
|
-
* entries (saved under another brain style) are ignored at render time.
|
|
977
|
-
* READ: absent/empty = seed only. WRITE (profile PUT): applied only when
|
|
978
|
-
* SENT; `{}` clears. */
|
|
979
|
-
avatarParts?: Record<string, string | null>;
|
|
980
|
-
/** Content-addressed storage key of THIS login's uploaded profile PHOTO —
|
|
981
|
-
* when set, clients show the photo instead of the generated avatar
|
|
982
|
-
* (photo → generated seed → initials). Per-login (the photo routes address
|
|
983
|
-
* the ACTOR's row); set only from Settings → Profile, never for agents.
|
|
984
|
-
* Served privately by GET /api/profile/photo (cookie or asset token). */
|
|
985
|
-
avatarPhotoKey?: string;
|
|
986
|
-
/** Content-Type of the photo bytes (png/jpeg/webp — never SVG). */
|
|
987
|
-
avatarPhotoType?: string;
|
|
988
|
-
/** Slug of the responder agent whose Telegram bot delivers event reminders.
|
|
989
|
-
* Unset → the reminder worker falls back to the most-recently-active allowed
|
|
990
|
-
* DM (whichever bot you last messaged). Set it to pin reminders to one
|
|
991
|
-
* persona, e.g. 'telegram-default' (Saskia), so they don't come from
|
|
992
|
-
* whichever bot happened to be most recent. */
|
|
993
|
-
reminderAgentSlug?: string;
|
|
994
|
-
/** Where event reminders are delivered: 'telegram' (a bot DM) or 'mobile' (a
|
|
995
|
-
* push to the companion app). Auto-tracked — it follows the last channel the
|
|
996
|
-
* user actually messaged on (see noteInboundChannel), and can be set manually
|
|
997
|
-
* from the profile; a manual choice holds until the next message on the other
|
|
998
|
-
* channel supersedes it. Unset ⇒ the reminder worker defaults to 'telegram'
|
|
999
|
-
* (backward-compatible). See docs/reminder-delivery-routing.md. */
|
|
1000
|
-
reminderChannel?: ReminderChannel;
|
|
1001
|
-
/** What the user likes to be called (captured during onboarding). Cosmetic —
|
|
1002
|
-
* the assistant's real knowledge of the user comes from the Journal identity
|
|
1003
|
-
* block; this is for greetings/UI. */
|
|
1004
|
-
displayName?: string;
|
|
1005
|
-
/** Custom site name rendered as the header wordmark in place of "mantle" —
|
|
1006
|
-
* a per-box label (e.g. 'Refinery') so anyone with several brains can see at
|
|
1007
|
-
* a glance which one they're on. Cosmetic only; unset ⇒ the Mantle wordmark.
|
|
1008
|
-
* Read via projectSiteName, never raw. */
|
|
1009
|
-
siteName?: string;
|
|
1010
|
-
/** This brain's peer name — shown in the header CENTRE (replacing the old page
|
|
1011
|
-
* title) as this node's federation-facing identity label. Cosmetic; unset ⇒
|
|
1012
|
-
* the header centre is empty. Read via projectPeerName, never raw. */
|
|
1013
|
-
peerName?: string;
|
|
1014
|
-
/** The owner's writing conventions, in their own words — appended to EVERY
|
|
1015
|
-
* agent's composed system prompt as a `## House style` block (see
|
|
1016
|
-
* composeSystemPromptWithSkills). Brain-level, because it describes how this
|
|
1017
|
-
* brain writes, not how one login works.
|
|
1018
|
-
*
|
|
1019
|
-
* Free text rather than a checkbox on purpose: the first rule anyone wants
|
|
1020
|
-
* is "no em dashes", the second is "don't say 'delve'", and a boolean per
|
|
1021
|
-
* rule is a migration per taste. Unset ⇒ no block is emitted at all, so the
|
|
1022
|
-
* cached prompt prefix is byte-identical to before the feature existed.
|
|
1023
|
-
* Read via projectHouseStyle, never raw. */
|
|
1024
|
-
houseStyle?: string;
|
|
1025
|
-
/** The UI colour-theme id (the header theme toggler / random shuffle). The
|
|
1026
|
-
* DB copy is the source of truth so the choice follows the owner across
|
|
1027
|
-
* browsers and brands member-facing surfaces (/s, /team) — localStorage
|
|
1028
|
-
* stays only as the before-paint fast path. Unset ⇒ the default theme.
|
|
1029
|
-
* Read via projectColorTheme, never raw. */
|
|
1030
|
-
colorTheme?: string;
|
|
1031
|
-
/** Selectable header WORDMARK font key (Settings → Appearance → Fonts). The
|
|
1032
|
-
* font LIST lives in the web app (server/web/lib/display-fonts.ts); the server
|
|
1033
|
-
* stores any well-formed slug and the client falls back to the default for
|
|
1034
|
-
* keys it doesn't know, so trimming the library never strands the preference.
|
|
1035
|
-
* Unset ⇒ the default wordmark face (Bricolage Grotesque). Read via
|
|
1036
|
-
* projectFontKey, never raw. */
|
|
1037
|
-
fontLogo?: string;
|
|
1038
|
-
/** Selectable header page-TITLE font key — same contract as `fontLogo`.
|
|
1039
|
-
* Unset ⇒ the default UI sans. Read via projectFontKey, never raw. */
|
|
1040
|
-
fontTitle?: string;
|
|
1041
|
-
/** The INTERFACE font key — what the whole UI is set in, not just a header
|
|
1042
|
-
* ornament. Same contract as `fontLogo`; unset ⇒ Inter (the always-loaded
|
|
1043
|
-
* next/font face). Read via projectFontKey, never raw. */
|
|
1044
|
-
fontUi?: string;
|
|
1045
|
-
/** The PAGES/NOTES font key — what long-form prose is set in, in the editor,
|
|
1046
|
-
* on shared pages, and in the PDF export. Same contract as `fontLogo`; unset
|
|
1047
|
-
* ⇒ 'inherit' (follow the interface font). This is the one slot where the
|
|
1048
|
-
* choice leaves the browser: a page exported to PDF is typeset in it. */
|
|
1049
|
-
fontProse?: string;
|
|
1050
|
-
/** UI scale: 'xsmall' | 'small' | 'medium' | 'large'. Drives the ROOT
|
|
1051
|
-
* font-size, so the rem-based shell scales with it rather than only the
|
|
1052
|
-
* letters. Unset ⇒ 'medium'. Read via projectFontSize, never raw. */
|
|
1053
|
-
fontSize?: string;
|
|
1054
|
-
/** Wordmark scale — same vocabulary as `fontSize`, but a LOCAL multiplier on
|
|
1055
|
-
* one element rather than the root font-size (a wordmark that rescaled the
|
|
1056
|
-
* whole shell would be a bug). Unset ⇒ 'medium'. */
|
|
1057
|
-
fontLogoSize?: string;
|
|
1058
|
-
/** Peer-name scale. Same contract as `fontLogoSize`. */
|
|
1059
|
-
fontTitleSize?: string;
|
|
1060
|
-
/** Pages/Notes prose scale. Same contract as `fontLogoSize`. */
|
|
1061
|
-
fontProseSize?: string;
|
|
1062
|
-
/** Brand logo: the content-addressed storage key of the uploaded image
|
|
1063
|
-
* (attachments/aa/bb/<sha256> — @mantle/storage contentKey). Set/cleared
|
|
1064
|
-
* ONLY via PUT/DELETE /api/profile/logo, which validates the bytes; when
|
|
1065
|
-
* set, both headers render the image in place of the siteName wordmark.
|
|
1066
|
-
* The sha in the key doubles as the cache-busting version. Read via
|
|
1067
|
-
* projectLogoKey, never raw. */
|
|
1068
|
-
logoKey?: string;
|
|
1069
|
-
/** The logo's mime type, from the validated upload (svg/png/jpeg/webp
|
|
1070
|
-
* allowlist — projectLogoType). The public serve route replays it. */
|
|
1071
|
-
logoType?: string;
|
|
1072
|
-
/** Optional DARK-MODE logo variant — same storage/validation contract as
|
|
1073
|
-
* logoKey, uploaded via PUT /api/profile/logo?variant=dark. Renderers show
|
|
1074
|
-
* it when the UI is in dark mode and fall back to the base logo (then the
|
|
1075
|
-
* wordmark) when unset — so a light-on-transparent mark stays readable on
|
|
1076
|
-
* both themes without forcing every brain to upload two files. */
|
|
1077
|
-
logoDarkKey?: string;
|
|
1078
|
-
/** The dark variant's mime type (same allowlist as logoType). */
|
|
1079
|
-
logoDarkType?: string;
|
|
1080
|
-
/** Free-text "what this brain is for" — captured at onboarding, editable in
|
|
1081
|
-
* Settings → Profile. Injected as the "# Purpose of this brain" section of the
|
|
1082
|
-
* always-on identity block (identity-context.ts), so every agent knows the
|
|
1083
|
-
* brain's mission. */
|
|
1084
|
-
purpose?: string;
|
|
1085
|
-
/** The brain's speciality archetype key (see onboarding-questions.ts
|
|
1086
|
-
* PURPOSE_ARCHETYPES — 'personal' | 'analytics' | 'research' | 'robotics' |
|
|
1087
|
-
* 'team' | 'custom'). Descriptive for now; the seam a later phase can branch
|
|
1088
|
-
* default provisioning on. */
|
|
1089
|
-
purposeArchetype?: string;
|
|
1090
|
-
/** ISO instant onboarding was completed. Unset ⇒ the onboarding wizard runs
|
|
1091
|
-
* on next login; the (app) shell redirects there. Set ⇒ shell renders normally. */
|
|
1092
|
-
onboardedAt?: string;
|
|
1093
|
-
/** Resume marker for the onboarding wizard — the key of the furthest step the
|
|
1094
|
-
* user has reached. Lets a refreshed/re-entered wizard pick up where it left off. */
|
|
1095
|
-
onboardingStep?: string;
|
|
1096
|
-
/** Model choices captured by the onboarding "Models" step — the operator
|
|
1097
|
-
* overlay `provisionDefaults()` applies on top of the manifest seed (the
|
|
1098
|
-
* assistant's chat model + the indexing workers' fast model). When
|
|
1099
|
-
* `route: 'azure'`, those rows are pinned to an Azure OpenAI endpoint via
|
|
1100
|
-
* the `custom` provider (key stored under service `custom`). */
|
|
1101
|
-
onboardingModels?: OnboardingModelChoices;
|
|
1102
|
-
/** When true, tools an AGENT authors (via Toolsmith / api_tool_create) start
|
|
1103
|
-
* confirm-gated: every call parks for operator approval until the operator
|
|
1104
|
-
* clears "requires confirm" for that tool in Settings → Tools. Defaults
|
|
1105
|
-
* OFF — a simple single-owner brain trusts itself; turn it ON if you grant
|
|
1106
|
-
* tool-authoring to an agent that reads untrusted content (email/web), so an
|
|
1107
|
-
* injected agent can't stand up a silent exfiltration endpoint. Independent
|
|
1108
|
-
* of the always-on guards (self-grant block, no-lower-via-update, SSRF). */
|
|
1109
|
-
toolsmithRequireApproval?: boolean;
|
|
1110
|
-
/** APP_VERSION the boot-time manifest reconcile last synced this brain to.
|
|
1111
|
-
* The reconcile (server/web instrumentation → reconcileManifestOnBoot) runs once
|
|
1112
|
-
* per version on a deployed/updated instance, so a self-hoster who only pulls a
|
|
1113
|
-
* new image still gets new tools/skills/group-membership without running seed
|
|
1114
|
-
* scripts. Equal to APP_VERSION ⇒ already reconciled, skip. */
|
|
1115
|
-
lastReconciledVersion?: string;
|
|
1116
|
-
/** When true, outbound/egress tools (email_send, web_fetch, web_search)
|
|
1117
|
-
* fired during an UNATTENDED heartbeat run park for operator approval
|
|
1118
|
-
* instead of executing inline. Only tools that reach OUT are gated — the
|
|
1119
|
-
* heartbeat's own surface reply (the final Telegram message) is not a tool
|
|
1120
|
-
* and still goes through. Defaults OFF: most heartbeats are trusted
|
|
1121
|
-
* routines. Turn it ON for an agent that reads untrusted content on a
|
|
1122
|
-
* timer, so an injected instruction can't silently email or fetch on your
|
|
1123
|
-
* behalf while you're away. Pairs with the interactive Telegram approval
|
|
1124
|
-
* card so a parked egress call can be cleared from a phone. */
|
|
1125
|
-
heartbeatEgressGate?: boolean;
|
|
1126
|
-
/** Show the live "thinking" trail + stream the reply token-by-token in the
|
|
1127
|
-
* /assistant chat (and the companion). **Defaults ON** (undefined → on); set
|
|
1128
|
-
* false to fall back to a static thinking bubble + the reply appearing whole
|
|
1129
|
-
* on completion. This is the per-brain runtime control for live turn
|
|
1130
|
-
* streaming; the `MANTLE_TURN_STREAMING` env var is a deploy-level override
|
|
1131
|
-
* (env off wins). Read by the web turn route (202 vs blocking + the SSE gate)
|
|
1132
|
-
* via `isStreamThoughtsEnabled`. */
|
|
1133
|
-
streamThoughts?: boolean;
|
|
1134
|
-
/** How the LIVE thinking trail renders during a turn: 'list' stacks completed
|
|
1135
|
-
* actions above the active line (default); 'replace' shows only the current
|
|
1136
|
-
* action, each one replacing the last (compact, single line). The frozen
|
|
1137
|
-
* record view (after the turn) is unaffected. */
|
|
1138
|
-
thoughtTrailMode?: ThoughtTrailMode;
|
|
1139
|
-
/** Persist the thought trail onto the finished message so it survives a page
|
|
1140
|
-
* refresh — reconstructed from the turn's tool actions and stored on the
|
|
1141
|
-
* durable row, so it reloads on web AND the companion. **Defaults ON**; set
|
|
1142
|
-
* false to keep it ephemeral (in-memory only; clears on reload). See
|
|
1143
|
-
* `isPersistThoughtsEnabled`. */
|
|
1144
|
-
persistThoughts?: boolean;
|
|
1145
|
-
/** Per-user thinking budget in tokens. Real model reasoning is requested only
|
|
1146
|
-
* when the live-thinking switch is ON (`streamThoughts`) AND this is > 0;
|
|
1147
|
-
* 0 / unset = no thinking. Maps to the provider's knob in the adapters
|
|
1148
|
-
* (Anthropic adaptive, OpenRouter `reasoning.max_tokens`, Gemini
|
|
1149
|
-
* `thinkingConfig`, Copilot `reasoning_effort`). This is the per-user
|
|
1150
|
-
* replacement for the old per-box `MANTLE_THINKING_BUDGET` env gate. Resolve
|
|
1151
|
-
* via `resolveThinkingBudget` — never read raw, so the switch gate always
|
|
1152
|
-
* applies. **Defaults unset (off).** */
|
|
1153
|
-
thinkingBudget?: number;
|
|
1154
|
-
/** Whether this box exposes its remote MCP connector (the OAuth-gated
|
|
1155
|
-
* `/api/mcp` endpoint addable as a claude.ai custom connector). **Defaults
|
|
1156
|
-
* OFF** — it's an explicit opt-in because it puts the tool surface on the
|
|
1157
|
-
* public internet (behind OAuth). When off, `/api/mcp` + the OAuth
|
|
1158
|
-
* authorize/register endpoints 404, so no new client can connect and existing
|
|
1159
|
-
* tokens stop working. Flip it in Settings → MCP. */
|
|
1160
|
-
remoteMcpEnabled?: boolean;
|
|
1161
|
-
/** Whether the external Team Chat responder may read the owner's PRIVATE
|
|
1162
|
-
* corpus — email + journal — on a team member's behalf. **Defaults OFF**:
|
|
1163
|
-
* team members always get brain-wide knowledge reads (search, files, notes,
|
|
1164
|
-
* pages, tables, tasks, contacts, app data), but the owner's personal email
|
|
1165
|
-
* history and journal stay off-limits unless this is explicitly turned on.
|
|
1166
|
-
* Enforced at the team turn's tool resolution (`isTeamPrivateReadsEnabled`
|
|
1167
|
-
* strips `email_*`/`journal_*` when off), independent of the `team-read`
|
|
1168
|
-
* group grant, so the switch can't be bypassed by a manifest change. Flip it
|
|
1169
|
-
* from the Team admin surface. */
|
|
1170
|
-
teamPrivateReads?: boolean;
|
|
1171
|
-
/** Node id of the mini-app designated as this brain's TEAM HUB. When set (and
|
|
1172
|
-
* the app has a green published build + an active team-mode share), the /team
|
|
1173
|
-
* shell renders that app full-bleed in place of the built-in hub body; the
|
|
1174
|
-
* built-in hub remains the fallback for every other state. Resolve via
|
|
1175
|
-
* `resolveTeamHubApp` (team-hub.ts), never raw — designation is only honoured
|
|
1176
|
-
* when the whole chain (pref → app → build → share) is intact. Read via
|
|
1177
|
-
* projectTeamHubAppId, never raw. */
|
|
1178
|
-
teamHubAppId?: string;
|
|
1179
|
-
/** Tags the owner curates as Dashboard sections on the /team overview: each
|
|
1180
|
-
* tag renders a section of up to 5 team-visible shared pages carrying it
|
|
1181
|
-
* (newest-updated first, title + summary + /s link). Order here = section
|
|
1182
|
-
* order. The share stays the single source of truth for WHAT is visible —
|
|
1183
|
-
* this pref only chooses which tag groupings get pinned. Unset/empty ⇒ no
|
|
1184
|
-
* curated sections. Read via projectTeamHubTags, never raw. */
|
|
1185
|
-
teamHubTags?: string[];
|
|
1186
|
-
};
|
|
1187
|
-
|
|
1188
|
-
export type BackupConfig = {
|
|
1189
|
-
enabled: boolean;
|
|
1190
|
-
frequency: BackupFrequency;
|
|
1191
|
-
/** Hour of day (0-23) in the USER's timezone (profiles.preferences.timezone). */
|
|
1192
|
-
hour: number;
|
|
1193
|
-
/** Newest N dumps retained in the directory. */
|
|
1194
|
-
keep: number;
|
|
1195
|
-
/** Absolute destination directory. Empty/unset → resolveBackupDir default. */
|
|
1196
|
-
location?: string;
|
|
1197
|
-
};
|
|
1198
|
-
|
|
1199
|
-
export type BackupFile = { name: string; bytes: number; mtime: string };
|
|
1200
|
-
|
|
1201
|
-
export type BackupStatus = {
|
|
1202
|
-
lastRunAt: string;
|
|
1203
|
-
ok: boolean;
|
|
1204
|
-
/** Set when ok=false. */
|
|
1205
|
-
error?: string;
|
|
1206
|
-
file?: string;
|
|
1207
|
-
bytes?: number;
|
|
1208
|
-
durationMs?: number;
|
|
1209
|
-
/** 'schedule' | 'manual' — what triggered the run. */
|
|
1210
|
-
trigger: string;
|
|
1211
|
-
/** When the last SUCCESSFUL run finished — preserved across failed runs,
|
|
1212
|
-
* so the /debug/integrity staleness check can tell "failing for a week"
|
|
1213
|
-
* from "failed once after last night's good dump". */
|
|
1214
|
-
lastSuccessAt?: string;
|
|
1215
|
-
/** Sqlite-native table workbooks snapshotted beside the dump (durability
|
|
1216
|
-
* gate 2). failed>0 is surfaced in the settings card — a backup that
|
|
1217
|
-
* silently skips a workbook is the gap this closes. */
|
|
1218
|
-
tableDbs?: { snapshotted: number; missing: number; failed: number };
|
|
1219
|
-
/** Per-app mini-app SQLite databases snapshotted beside the dump. Same
|
|
1220
|
-
* durability gate as tableDbs: these live on their own volume, so pg_dump
|
|
1221
|
-
* alone misses them and a scheduled backup would silently omit all app
|
|
1222
|
-
* data (e.g. a Team Hub app's DB) without this pass. */
|
|
1223
|
-
appDbs?: { snapshotted: number; missing: number; failed: number };
|
|
1224
|
-
};
|
|
1225
|
-
|
|
1226
|
-
export type CuratedTeamSection = {
|
|
1227
|
-
/** The curated tag — the section heading (display-cased by the UI). */
|
|
1228
|
-
tag: string;
|
|
1229
|
-
/** Up to {@link TEAM_CURATED_SECTION_LIMIT} team-visible page shares carrying
|
|
1230
|
-
* the tag, newest node update first. */
|
|
1231
|
-
items: TeamVisibleShare[];
|
|
1232
|
-
};
|
|
1233
|
-
|
|
1234
|
-
export type TeamMemberActivity = {
|
|
1235
|
-
contactId: string;
|
|
1236
|
-
/** Contact node title; '(deleted contact)' can't occur here — membership
|
|
1237
|
-
* rows cascade with the contact. */
|
|
1238
|
-
contactName: string;
|
|
1239
|
-
memberSince: string;
|
|
1240
|
-
tokenLastUsedAt: string | null;
|
|
1241
|
-
lastMessageAt: string | null;
|
|
1242
|
-
lastMessageText: string | null;
|
|
1243
|
-
lastMessageDirection: 'inbound' | 'outbound' | null;
|
|
1244
|
-
messageCount: number;
|
|
1245
|
-
/** Member inbound messages since the owner last read this thread in
|
|
1246
|
-
* /team-admin (all inbound when never read). Drives the unread badge. */
|
|
1247
|
-
unread: number;
|
|
1248
|
-
};
|
|
1249
|
-
|
|
1250
|
-
export type TeamRequest = {
|
|
1251
|
-
taskId: string;
|
|
1252
|
-
title: string;
|
|
1253
|
-
body: string;
|
|
1254
|
-
status: 'open' | 'done';
|
|
1255
|
-
priority: string;
|
|
1256
|
-
createdAt: string;
|
|
1257
|
-
/** Provenance from data.teamRequest — null contactId means a malformed row
|
|
1258
|
-
* (shouldn't happen; team_request_create always stamps it). */
|
|
1259
|
-
contactId: string | null;
|
|
1260
|
-
contactName: string | null;
|
|
1261
|
-
/** When the owner last posted a resolution to the member for this request. */
|
|
1262
|
-
notifiedAt: string | null;
|
|
1263
|
-
};
|
|
1264
|
-
|
|
1265
|
-
export type ForumTopicListItem = {
|
|
1266
|
-
id: string;
|
|
1267
|
-
title: string;
|
|
1268
|
-
kind: ForumTopicKind;
|
|
1269
|
-
visibility: ForumTopicVisibility;
|
|
1270
|
-
pinned: boolean;
|
|
1271
|
-
status: ForumTopicStatus;
|
|
1272
|
-
authorName: string;
|
|
1273
|
-
createdByContactId: string | null;
|
|
1274
|
-
postCount: number;
|
|
1275
|
-
lastPostAt: string;
|
|
1276
|
-
createdAt: string;
|
|
1277
|
-
lastPostAuthor: string | null;
|
|
1278
|
-
lastPostPreview: string | null;
|
|
1279
|
-
/** Posts by OTHERS since this viewer last read the topic (all of them when
|
|
1280
|
-
* never read). Drives the unread dot. */
|
|
1281
|
-
unread: number;
|
|
1282
|
-
};
|
|
1283
|
-
|
|
1284
|
-
export type ForumMemberActivity = {
|
|
1285
|
-
contactId: string;
|
|
1286
|
-
postCount: number;
|
|
1287
|
-
topicsStarted: number;
|
|
1288
|
-
lastPostAt: string | null;
|
|
1289
|
-
lastPostBody: string | null;
|
|
1290
|
-
lastPostTopicTitle: string | null;
|
|
1291
|
-
/** This member's posts newer than the OWNER's read cursor on the containing
|
|
1292
|
-
* topic. Deliberately only cleared by opening the TOPIC — reading someone's
|
|
1293
|
-
* activity feed is not reading the thread the whole room saw. */
|
|
1294
|
-
unread: number;
|
|
1295
|
-
};
|
|
1296
|
-
|
|
1297
|
-
export type ForumMemberPost = {
|
|
1298
|
-
id: string;
|
|
1299
|
-
body: string;
|
|
1300
|
-
createdAt: string;
|
|
1301
|
-
/** Set when this post filed a review/feature/bug request. */
|
|
1302
|
-
kind: ForumPostRequestKind | null;
|
|
1303
|
-
attachments: ConversationAttachment[];
|
|
1304
|
-
topicId: string;
|
|
1305
|
-
topicTitle: string;
|
|
1306
|
-
topicVisibility: ForumTopicVisibility;
|
|
1307
|
-
topicStatus: ForumTopicStatus;
|
|
1308
|
-
/** The agent's answer to THIS post, or null when the turn was waved off
|
|
1309
|
-
* ("no answer needed") or is still owed. */
|
|
1310
|
-
reply: {
|
|
1311
|
-
id: string;
|
|
1312
|
-
body: string;
|
|
1313
|
-
authorName: string;
|
|
1314
|
-
traceId: string | null;
|
|
1315
|
-
status: 'pending' | 'complete' | 'failed';
|
|
1316
|
-
error: string | null;
|
|
1317
|
-
createdAt: string;
|
|
1318
|
-
} | null;
|
|
1319
|
-
};
|
|
1320
|
-
|
|
1321
|
-
export type ForumAuthoredTopic = {
|
|
1322
|
-
id: string;
|
|
1323
|
-
title: string;
|
|
1324
|
-
kind: ForumTopicKind;
|
|
1325
|
-
visibility: ForumTopicVisibility;
|
|
1326
|
-
status: ForumTopicStatus;
|
|
1327
|
-
pinned: boolean;
|
|
1328
|
-
postCount: number;
|
|
1329
|
-
lastPostAt: string | null;
|
|
1330
|
-
createdAt: string;
|
|
1331
|
-
};
|
|
1332
|
-
|
|
1333
|
-
export type PendingForumUpload = {
|
|
1334
|
-
id: string;
|
|
1335
|
-
topicId: string | null;
|
|
1336
|
-
postId: string | null;
|
|
1337
|
-
topicTitle: string | null;
|
|
1338
|
-
contactId: string | null;
|
|
1339
|
-
contactName: string | null;
|
|
1340
|
-
filename: string;
|
|
1341
|
-
mime: string;
|
|
1342
|
-
sizeBytes: number;
|
|
1343
|
-
createdAt: string;
|
|
1344
|
-
};
|
|
1345
|
-
|
|
1346
|
-
export type AccountFoldersResult =
|
|
1347
|
-
| {
|
|
1348
|
-
ok: true;
|
|
1349
|
-
address: string;
|
|
1350
|
-
/** Every folder the server reports right now (the pick list). */
|
|
1351
|
-
allFolders: string[];
|
|
1352
|
-
/** The current explicit allow-list, or null = "scan all non-excluded". */
|
|
1353
|
-
included: string[] | null;
|
|
1354
|
-
/** Folders the operator opted OUT of (rendered disabled). */
|
|
1355
|
-
excluded: string[];
|
|
1356
|
-
/** Folders the sync has actually touched (per the cursor). */
|
|
1357
|
-
scanned: string[];
|
|
1358
|
-
}
|
|
1359
|
-
| { ok: false; error: string };
|
|
1360
|
-
|
|
1361
|
-
export interface FolderFacet {
|
|
1362
|
-
folder: string;
|
|
1363
|
-
count: number;
|
|
1364
|
-
unread: number;
|
|
1365
|
-
}
|
|
1366
|
-
|
|
1367
|
-
export interface MessageListItem {
|
|
1368
|
-
id: string;
|
|
1369
|
-
fromAddr: string;
|
|
1370
|
-
fromName: string | null;
|
|
1371
|
-
subject: string | null;
|
|
1372
|
-
snippet: string | null;
|
|
1373
|
-
internalDate: Date;
|
|
1374
|
-
isRead: boolean;
|
|
1375
|
-
}
|
|
1376
|
-
|
|
1377
|
-
export interface MsConfigStatus {
|
|
1378
|
-
configured: boolean;
|
|
1379
|
-
/** Where the active config comes from — drives the UI ("set here" vs "from
|
|
1380
|
-
* environment, read-only"). */
|
|
1381
|
-
source: 'db' | 'env' | null;
|
|
1382
|
-
clientId: string | null;
|
|
1383
|
-
tenant: string;
|
|
1384
|
-
redirectUri: string | null;
|
|
1385
|
-
/** Masked secret for display; never the plaintext. */
|
|
1386
|
-
secretMasked: string | null;
|
|
1387
|
-
}
|
|
1388
|
-
|
|
1389
|
-
/** One retrieved (or near-miss) item: capped text + its ranking distance. */
|
|
1390
|
-
export type SnapshotItem = {
|
|
1391
|
-
text: string;
|
|
1392
|
-
/** Ranking distance (cosine, salience/recency-adjusted where the section
|
|
1393
|
-
* ranks that way). Null for always-injected items (preferences) that
|
|
1394
|
-
* bypass the vector race. */
|
|
1395
|
-
dist: number | null;
|
|
1396
|
-
kind?: string | null;
|
|
1397
|
-
entity?: string | null;
|
|
1398
|
-
nodeId?: string | null;
|
|
1399
|
-
title?: string | null;
|
|
1400
|
-
heading?: string | null;
|
|
1401
|
-
};
|
|
1402
|
-
|
|
1403
|
-
export type ContextSnapshot = {
|
|
1404
|
-
query: {
|
|
1405
|
-
/** The inbound text as given to retrieval (snipped). */
|
|
1406
|
-
inbound: string;
|
|
1407
|
-
/** The anaphora-enriched text actually embedded, when it differs. */
|
|
1408
|
-
enriched: string | null;
|
|
1409
|
-
/** False when embedding was skipped or failed — retrieval ran blind. */
|
|
1410
|
-
embedded: boolean;
|
|
1411
|
-
};
|
|
1412
|
-
facts: { sent: SnapshotItem[]; dropped: SnapshotItem[]; guard: number };
|
|
1413
|
-
contentHits: { sent: SnapshotItem[]; dropped: SnapshotItem[]; cutoff: number };
|
|
1414
|
-
chunkHits: { sent: SnapshotItem[]; dropped: SnapshotItem[]; cutoff: number };
|
|
1415
|
-
relations: string[];
|
|
1416
|
-
digests: { count: number; topics: string[] };
|
|
1417
|
-
history: {
|
|
1418
|
-
count: number;
|
|
1419
|
-
/** How many outbound turns carried a [tool record: …] read-back suffix. */
|
|
1420
|
-
toolRecords: number;
|
|
1421
|
-
/** How many turns carried a [media record: …] read-back suffix. */
|
|
1422
|
-
mediaRecords: number;
|
|
1423
|
-
};
|
|
1424
|
-
personaNotes: { count: number };
|
|
1425
|
-
corpusMap: { count: number; truncated: boolean };
|
|
1426
|
-
};
|
|
1427
|
-
|
|
1428
|
-
export type BackupFrequency = 'daily' | 'weekly';
|
|
1429
|
-
|
|
1430
|
-
export type PageVisibility = 'private' | 'public';
|
|
1431
|
-
|
|
1432
|
-
/**
|
|
1433
|
-
* Recurrence frequencies an event can repeat on. `none` is the default —
|
|
1434
|
-
* a one-shot event. The reminder worker rolls a recurring event's single
|
|
1435
|
-
* row forward to its next occurrence after each ping (no instance
|
|
1436
|
-
* materialisation), so one node always represents the next upcoming hit.
|
|
1437
|
-
*/
|
|
1438
|
-
export type RecurFreq = 'none' | 'daily' | 'weekly' | 'monthly' | 'yearly';
|
|
1439
|
-
|
|
1440
|
-
/** Transports that can deliver a reminder out-of-band. A browser ('web') can't
|
|
1441
|
-
* receive a push, so it never becomes a reminder target. */
|
|
1442
|
-
export type ReminderChannel = 'telegram' | 'mobile';
|
|
1443
|
-
|
|
1444
|
-
/** Live thinking-trail display modes. */
|
|
1445
|
-
export type ThoughtTrailMode = 'list' | 'replace';
|
|
1446
|
-
|
|
1447
|
-
/** The onboarding "Models" step's stored choices. Kept as one object so the
|
|
1448
|
-
* projection can't half-apply; every field optional so partial saves survive. */
|
|
1449
|
-
export interface OnboardingModelChoices {
|
|
1450
|
-
/** OpenRouter slug for the assistant/persona agent (e.g. `anthropic/claude-sonnet-4.6`). */
|
|
1451
|
-
assistantModel?: string;
|
|
1452
|
-
/** OpenRouter slug for the indexing workers (e.g. `google/gemini-3.1-flash-lite`). */
|
|
1453
|
-
workerModel?: string;
|
|
1454
|
-
/** Where the models run: OpenRouter (default) or an Azure OpenAI endpoint. */
|
|
1455
|
-
route?: 'openrouter' | 'azure';
|
|
1456
|
-
/** Azure OpenAI base URL (the OpenAI-compatible v1 endpoint), when route=azure. */
|
|
1457
|
-
azureBaseUrl?: string;
|
|
1458
|
-
}
|
|
1459
|
-
|
|
1460
|
-
/**
|
|
1461
|
-
* Who a share admits. Lives in `shares.settings.mode` (absent = 'public', so
|
|
1462
|
-
* every pre-existing share keeps its behavior).
|
|
1463
|
-
*
|
|
1464
|
-
* public — anyone with the link (the original model).
|
|
1465
|
-
* team — the visitor must additionally present a live team credential
|
|
1466
|
-
* (see @mantle/content/team-tokens). Enforced for every kind on
|
|
1467
|
-
* the /s/ surface (page render, asset bytes, app brokers).
|
|
1468
|
-
* Team-mode PAGE shares double as the /team hub's briefing
|
|
1469
|
-
* sections (see ./team-hub).
|
|
1470
|
-
*/
|
|
1471
|
-
export type ShareMode = 'public' | 'team';
|
|
1472
|
-
|
|
1473
|
-
export type TeamVisibleShare = {
|
|
1474
|
-
/** Share token — the workspace opens /s/<token>. */
|
|
1475
|
-
token: string;
|
|
1476
|
-
nodeId: string;
|
|
1477
|
-
title: string;
|
|
1478
|
-
icon: string | null;
|
|
1479
|
-
summary: string | null;
|
|
1480
|
-
updatedAt: string;
|
|
1481
|
-
/** 'team' or 'public' — a member may open both, the badge tells them apart. */
|
|
1482
|
-
mode: 'team' | 'public';
|
|
1483
|
-
/** Parent node id — lets the pages section rebuild the sub-page tree over
|
|
1484
|
-
* the SHARED subset (an unshared parent leaves its children as roots). */
|
|
1485
|
-
parentId: string | null;
|
|
1486
|
-
tags: string[];
|
|
1487
|
-
/**
|
|
1488
|
-
* EVENTS ONLY — the event's own start, from `nodes.data.starts_at`.
|
|
1489
|
-
*
|
|
1490
|
-
* Every other field here describes the SHARE; this one describes the thing
|
|
1491
|
-
* shared, and it is carried because for an event the two are not
|
|
1492
|
-
* interchangeable. `updatedAt` says when the row was last written, which is
|
|
1493
|
-
* the right meta line for a note or a table and useless for an event: a
|
|
1494
|
-
* member scanning what is coming up needs WHEN IT HAPPENS, and an event
|
|
1495
|
-
* edited this morning sorts above one starting tomorrow.
|
|
1496
|
-
*
|
|
1497
|
-
* Optional so a client pinned to an older server still parses the payload,
|
|
1498
|
-
* and null for every non-event type.
|
|
1499
|
-
*/
|
|
1500
|
-
startsAt?: string | null;
|
|
1501
|
-
};
|
|
1502
|
-
|
|
1503
|
-
// ── Mirrors of @mantle/db jsonb/enum shapes (jackdaw split P0) ────────────────
|
|
1504
|
-
// Kept standalone so this package stays zero-dep (same convention as ToolHandler
|
|
1505
|
-
// above). Drift is caught where the server builds these DTOs from db rows —
|
|
1506
|
-
// an incompatible change there is a compile error at the row-builder.
|
|
1507
|
-
|
|
1508
|
-
/** Task lifecycle vocabulary — mirrors content's TASK_STATUSES/TASK_PRIORITIES
|
|
1509
|
-
* consts, which are `satisfies`-checked against these unions. */
|
|
1510
|
-
export type TaskStatus = 'open' | 'in_progress' | 'blocked' | 'done';
|
|
1511
|
-
export type TaskPriority = 'low' | 'normal' | 'high';
|
|
1512
|
-
|
|
1513
|
-
/** Mirrors @mantle/db `ForumTopicKind`. */
|
|
1514
|
-
export type ForumTopicKind = 'question' | 'review' | 'feature' | 'bug' | 'discussion';
|
|
1515
|
-
/** Mirrors @mantle/db `ForumTopicVisibility`. */
|
|
1516
|
-
export type ForumTopicVisibility = 'team' | 'private';
|
|
1517
|
-
/** Mirrors @mantle/db `ForumTopicStatus`. */
|
|
1518
|
-
export type ForumTopicStatus = 'open' | 'answered' | 'closed';
|
|
1519
|
-
/** Mirrors @mantle/db `ForumPostRequestKind` — the topic kinds that file an
|
|
1520
|
-
* owner review task. */
|
|
1521
|
-
export type ForumPostRequestKind = 'review' | 'feature' | 'bug';
|
|
1522
|
-
|
|
1523
|
-
/** Mirrors @mantle/db `ConversationAttachment` (jsonb on conversation rows). */
|
|
1524
|
-
export type ConversationAttachment = {
|
|
1525
|
-
kind: 'image' | 'audio' | 'voice' | 'document' | 'video';
|
|
1526
|
-
mime?: string;
|
|
1527
|
-
caption?: string;
|
|
1528
|
-
nodeId?: string;
|
|
1529
|
-
fileId?: string;
|
|
1530
|
-
url?: string;
|
|
1531
|
-
};
|
|
1532
|
-
|
|
1533
|
-
/** Mirrors @mantle/db `AppSource` — a mini app's virtual file tree. */
|
|
1534
|
-
export type AppSource = {
|
|
1535
|
-
/** Path of the entry module within `files`; must `export default App`. */
|
|
1536
|
-
entry: string;
|
|
1537
|
-
/** path → TSX/TS source. Bounded (~30 files / ~256 KB) to stay a mini app. */
|
|
1538
|
-
files: Record<string, string>;
|
|
1539
|
-
};
|
|
1540
|
-
|
|
1541
|
-
/** Mirrors @mantle/db `AppManifest` — the runtime contract for a running app. */
|
|
1542
|
-
export type AppManifest = {
|
|
1543
|
-
toolSlugs?: string[];
|
|
1544
|
-
sqlite?: { schemaSql: string; schemaVersion: number };
|
|
1545
|
-
description?: string;
|
|
1546
|
-
};
|
|
1547
|
-
|
|
1548
|
-
/** Mirrors @mantle/db `BuildRef` — pointer to a bundled artifact in storage. */
|
|
1549
|
-
export type BuildRef = {
|
|
1550
|
-
storageKey: string;
|
|
1551
|
-
sha256: string;
|
|
1552
|
-
builtAt: string;
|
|
1553
|
-
esbuildVersion: string;
|
|
1554
|
-
bytes: number;
|
|
1555
|
-
ok: boolean;
|
|
1556
|
-
warnings?: string[];
|
|
1557
|
-
css?: { storageKey: string; sha256: string; bytes: number };
|
|
1558
|
-
};
|
|
1559
|
-
|
|
1560
|
-
// ── Redacted account DTOs (hand-mirrored; jackdaw split P0) ───────────────────
|
|
1561
|
-
// These mirror db-derived server types (`Omit<EmailAccount,…>` etc.) that can't
|
|
1562
|
-
// be re-exported without dragging the postgres type graph in. Timestamps are
|
|
1563
|
-
// ISO strings here — the wire truth — where the server-side originals carry
|
|
1564
|
-
// `Date`. Key-set drift checks live next to the server definitions
|
|
1565
|
-
// (email/accounts.ts, microsoft/accounts.ts, email's sync-runs consumer).
|
|
1566
|
-
|
|
1567
|
-
/** Mirrors @mantle/email `PublicEmailAccount` (an `email_accounts` row minus
|
|
1568
|
-
* the sealed IMAP secret). */
|
|
1569
|
-
export interface PublicEmailAccount {
|
|
1570
|
-
id: string;
|
|
1571
|
-
userId: string;
|
|
1572
|
-
provider: 'gmail' | 'microsoft' | 'imap';
|
|
1573
|
-
address: string;
|
|
1574
|
-
displayName: string | null;
|
|
1575
|
-
imapHost: string | null;
|
|
1576
|
-
imapPort: number | null;
|
|
1577
|
-
imapSecure: boolean;
|
|
1578
|
-
smtpHost: string | null;
|
|
1579
|
-
smtpPort: number | null;
|
|
1580
|
-
smtpSecure: boolean;
|
|
1581
|
-
/** @deprecated historical reads only (migration 0002). */
|
|
1582
|
-
imapFolders: string[];
|
|
1583
|
-
imapExcludedFolders: string[];
|
|
1584
|
-
imapIncludedFolders: string[] | null;
|
|
1585
|
-
firstScanDays: number;
|
|
1586
|
-
ingestPolicy: 'approve_list' | 'block_list';
|
|
1587
|
-
branchPath: string;
|
|
1588
|
-
msAccountId: string | null;
|
|
1589
|
-
syncState: Record<string, unknown>;
|
|
1590
|
-
lastSyncAt: string | null;
|
|
1591
|
-
lastSyncError: string | null;
|
|
1592
|
-
enabled: boolean;
|
|
1593
|
-
createdAt: string;
|
|
1594
|
-
updatedAt: string;
|
|
1595
|
-
}
|
|
1596
|
-
|
|
1597
|
-
/** Mirrors @mantle/db `SyncRun` (a `sync_runs` row) as it crosses the wire. */
|
|
1598
|
-
export interface SyncRun {
|
|
1599
|
-
id: string;
|
|
1600
|
-
accountId: string;
|
|
1601
|
-
startedAt: string;
|
|
1602
|
-
finishedAt: string | null;
|
|
1603
|
-
durationMs: number | null;
|
|
1604
|
-
status: 'running' | 'ok' | 'error';
|
|
1605
|
-
scanned: number;
|
|
1606
|
-
ingested: number;
|
|
1607
|
-
error: string | null;
|
|
1608
|
-
}
|
|
1609
|
-
|
|
1610
|
-
/** Mirrors @mantle/microsoft `PublicMsAccount` (an `ms_accounts` row with the
|
|
1611
|
-
* sealed OAuth tokens replaced by presence flags). */
|
|
1612
|
-
export interface PublicMsAccount {
|
|
1613
|
-
id: string;
|
|
1614
|
-
userId: string;
|
|
1615
|
-
upn: string;
|
|
1616
|
-
displayName: string | null;
|
|
1617
|
-
tenantId: string | null;
|
|
1618
|
-
tokenExpiresAt: string | null;
|
|
1619
|
-
scopes: string[];
|
|
1620
|
-
branchPath: string;
|
|
1621
|
-
surfaces: Record<string, boolean>;
|
|
1622
|
-
syncState: Record<string, unknown>;
|
|
1623
|
-
lastSyncAt: string | null;
|
|
1624
|
-
lastSyncError: string | null;
|
|
1625
|
-
enabled: boolean;
|
|
1626
|
-
createdAt: string;
|
|
1627
|
-
updatedAt: string;
|
|
1628
|
-
hasAccessToken: boolean;
|
|
1629
|
-
hasRefreshToken: boolean;
|
|
1630
|
-
}
|
|
1631
|
-
|
|
1632
|
-
// ── Server-lib view/query DTOs (jackdaw split P0 follow-up: @server/* purge) ──
|
|
1633
|
-
// Moved from server/web/lib/* and @mantle/content; the originals re-export
|
|
1634
|
-
// these names so server import paths are unchanged.
|
|
1635
|
-
|
|
1636
|
-
/** Sort order for the pages list. 'edited' (last updated) is the default. */
|
|
1637
|
-
export type PageSort = 'edited' | 'newest' | 'oldest' | 'title';
|
|
1638
|
-
|
|
1639
|
-
/** A node that links TO a given page — one inbound `references` edge, resolved
|
|
1640
|
-
* to its source node. Powers the "Referenced by" panel. */
|
|
1641
|
-
export type Backlink = {
|
|
1642
|
-
id: string;
|
|
1643
|
-
title: string;
|
|
1644
|
-
/** The source node's type (wire truth: the db enum widens to string here). */
|
|
1645
|
-
type: string;
|
|
1646
|
-
icon: string | null;
|
|
1647
|
-
};
|
|
1648
|
-
|
|
1649
|
-
export type CapacityZone = 'green' | 'watch' | 'split';
|
|
1650
|
-
|
|
1651
|
-
export type CapacityMetric = {
|
|
1652
|
-
count: number;
|
|
1653
|
-
watch: number;
|
|
1654
|
-
split: number;
|
|
1655
|
-
/** count / split — may exceed 1 when the split point is passed. */
|
|
1656
|
-
ratio: number;
|
|
1657
|
-
zone: CapacityZone;
|
|
1658
|
-
};
|
|
1659
|
-
|
|
1660
|
-
export type BrainCapacity = {
|
|
1661
|
-
docs: CapacityMetric;
|
|
1662
|
-
chunkVectors: CapacityMetric;
|
|
1663
|
-
/** Worst zone across both axes — the brain's headline state. */
|
|
1664
|
-
zone: CapacityZone;
|
|
1665
|
-
/** Worst-axis fill as an integer percentage of the split budget (may exceed 100). */
|
|
1666
|
-
pctOfSplit: number;
|
|
1667
|
-
};
|
|
1668
|
-
|
|
1669
|
-
export type AgentContext = {
|
|
1670
|
-
agentId: string;
|
|
1671
|
-
agentName: string | null;
|
|
1672
|
-
agentSlug: string | null;
|
|
1673
|
-
modelSlug: string;
|
|
1674
|
-
lastTokensIn: number;
|
|
1675
|
-
contextLimit: number | null;
|
|
1676
|
-
/** Where contextLimit came from: live OpenRouter data, the static
|
|
1677
|
-
* fallback, or unknown (slug not in either). Surfaced in the UI. */
|
|
1678
|
-
contextSource: ContextSource;
|
|
1679
|
-
pct: number | null;
|
|
1680
|
-
lastRunAt: string;
|
|
1681
|
-
};
|
|
1682
|
-
|
|
1683
|
-
export type SpendRange = 'day' | 'week' | 'month';
|
|
1684
|
-
|
|
1685
|
-
export type AgentSpend = {
|
|
1686
|
-
agentId: string | null;
|
|
1687
|
-
agentName: string | null;
|
|
1688
|
-
agentSlug: string | null;
|
|
1689
|
-
costMicroUsd: number;
|
|
1690
|
-
tokensIn: number;
|
|
1691
|
-
tokensOut: number;
|
|
1692
|
-
cacheReadTokens: number;
|
|
1693
|
-
runs: number;
|
|
1694
|
-
};
|
|
1695
|
-
|
|
1696
|
-
export type ModelSpend = {
|
|
1697
|
-
/** The OpenRouter model slug captured in trace_steps.meta.model. */
|
|
1698
|
-
model: string;
|
|
1699
|
-
costMicroUsd: number;
|
|
1700
|
-
tokensIn: number;
|
|
1701
|
-
tokensOut: number;
|
|
1702
|
-
cacheReadTokens: number;
|
|
1703
|
-
calls: number;
|
|
1704
|
-
};
|
|
1705
|
-
|
|
1706
|
-
export type DailySpend = {
|
|
1707
|
-
/** ISO date (YYYY-MM-DD) in the server's local timezone. */
|
|
1708
|
-
day: string;
|
|
1709
|
-
costMicroUsd: number;
|
|
1710
|
-
tokensIn: number;
|
|
1711
|
-
tokensOut: number;
|
|
1712
|
-
cacheReadTokens: number;
|
|
1713
|
-
runs: number;
|
|
1714
|
-
};
|
|
1715
|
-
|
|
1716
|
-
export type RecentFailure = {
|
|
1717
|
-
id: string;
|
|
1718
|
-
kind: string;
|
|
1719
|
-
startedAt: string;
|
|
1720
|
-
error: string;
|
|
1721
|
-
};
|
|
1722
|
-
|
|
1723
|
-
export type TopError = {
|
|
1724
|
-
message: string;
|
|
1725
|
-
count: number;
|
|
1726
|
-
lastAt: string;
|
|
1727
|
-
lastTraceId: string;
|
|
1728
|
-
};
|
|
1729
|
-
|
|
1730
|
-
/** Per-tool tallies of calls the central validator flagged. Clean calls
|
|
1731
|
-
* write no `arg_validation` meta at all, so these are problem counts,
|
|
1732
|
-
* not rates — an empty result means nothing was flagged, not no calls. */
|
|
1733
|
-
export type ToolValidationAgg = {
|
|
1734
|
-
tool: string;
|
|
1735
|
-
flaggedCalls: number;
|
|
1736
|
-
withRepairs: number;
|
|
1737
|
-
withUnknownKeys: number;
|
|
1738
|
-
withViolations: number;
|
|
1739
|
-
lastAt: string;
|
|
1740
|
-
};
|
|
1741
|
-
|
|
1742
|
-
export type ToolValidationEvent = {
|
|
1743
|
-
stepId: string;
|
|
1744
|
-
traceId: string;
|
|
1745
|
-
tool: string;
|
|
1746
|
-
mode: string;
|
|
1747
|
-
repairs: Array<{ key: string; kind: string; note: string }>;
|
|
1748
|
-
unknownKeys: Array<{ key: string; suggestion: string | null }>;
|
|
1749
|
-
violations: string[];
|
|
1750
|
-
startedAt: string;
|
|
1751
|
-
};
|
|
1752
|
-
|
|
1753
|
-
export type AgentActivityRow = {
|
|
1754
|
-
id: string;
|
|
1755
|
-
slug: string;
|
|
1756
|
-
name: string;
|
|
1757
|
-
role: string;
|
|
1758
|
-
model: string;
|
|
1759
|
-
priority: number;
|
|
1760
|
-
enabled: boolean;
|
|
1761
|
-
lastUsedAt: string | null;
|
|
1762
|
-
usageCount: number;
|
|
1763
|
-
};
|
|
1764
|
-
|
|
1765
|
-
export type ChatRow = {
|
|
1766
|
-
id: string;
|
|
1767
|
-
title: string | null;
|
|
1768
|
-
username: string | null;
|
|
1769
|
-
telegramChatId: string;
|
|
1770
|
-
allowlistStatus: string;
|
|
1771
|
-
totalTurns: number;
|
|
1772
|
-
digested: number;
|
|
1773
|
-
undigested: number;
|
|
1774
|
-
lastActivity: string | null;
|
|
1775
|
-
responderAgentId: string | null;
|
|
1776
|
-
};
|
|
1777
|
-
|
|
1778
|
-
export type PersonaNotesRow = {
|
|
1779
|
-
agentId: string;
|
|
1780
|
-
agentName: string;
|
|
1781
|
-
agentSlug: string;
|
|
1782
|
-
notes: PersonaNoteDTO[];
|
|
1783
|
-
};
|
|
1784
|
-
|
|
1785
|
-
export type ContentIndexCoverage = {
|
|
1786
|
-
total: number;
|
|
1787
|
-
indexed: number;
|
|
1788
|
-
byType: Array<{ type: string; total: number; indexed: number }>;
|
|
1789
|
-
};
|
|
1790
|
-
|
|
1791
|
-
/**
|
|
1792
|
-
* Awareness of duplicate graph edges. Going forward the extractor rebuilds
|
|
1793
|
-
* edges per node (idempotent), but content re-edited *before* that fix may
|
|
1794
|
-
* carry historical duplicate `mentioned_in` / `references` rows. This surfaces
|
|
1795
|
-
* the count + a few labelled samples so the operator knows to run
|
|
1796
|
-
* `pnpm dedupe:edges`. Read-only — cleaning stays the deliberate CLI tool.
|
|
1797
|
-
*/
|
|
1798
|
-
export type DuplicateEdgeStats = {
|
|
1799
|
-
groups: number; // logical edges with >1 row
|
|
1800
|
-
redundant: number; // rows that could be removed (sum of count-1)
|
|
1801
|
-
samples: { relation: string; label: string; count: number }[];
|
|
1802
|
-
};
|
|
1803
|
-
|
|
1804
|
-
/** One responder turn: the question, the retrieval snapshot the turn's
|
|
1805
|
-
* 'load_context' trace step persisted (null for pre-instrumentation turns),
|
|
1806
|
-
* and the outbound reply. See ContextSnapshot in @mantle/agent-runtime. */
|
|
1807
|
-
/** Mirrors @mantle/tracing `ContextSource`. */
|
|
1808
|
-
export type ContextSource = 'live' | 'fallback' | 'unknown';
|
|
1809
|
-
|
|
1810
|
-
export type ContextTurnRow = {
|
|
1811
|
-
traceId: string;
|
|
1812
|
-
startedAt: string;
|
|
1813
|
-
status: string;
|
|
1814
|
-
surface: string | null;
|
|
1815
|
-
agentSlug: string | null;
|
|
1816
|
-
model: string | null;
|
|
1817
|
-
durationMs: number | null;
|
|
1818
|
-
question: string | null;
|
|
1819
|
-
snapshot: ContextSnapshot | null;
|
|
1820
|
-
response: string | null;
|
|
1821
|
-
};
|
|
1822
|
-
|
|
1823
|
-
export type DigestRow = {
|
|
1824
|
-
id: string;
|
|
1825
|
-
title: string;
|
|
1826
|
-
createdAt: string;
|
|
1827
|
-
/** All fields below are pulled out of nodes.data (jsonb). */
|
|
1828
|
-
chatId: string;
|
|
1829
|
-
telegramChatId: string | null;
|
|
1830
|
-
periodStart: string;
|
|
1831
|
-
periodEnd: string;
|
|
1832
|
-
sourceTurnCount: number;
|
|
1833
|
-
model: string;
|
|
1834
|
-
agent: string;
|
|
1835
|
-
summary: string;
|
|
1836
|
-
topic: string | null;
|
|
1837
|
-
topicSlug: string | null;
|
|
1838
|
-
};
|
|
1839
|
-
|
|
1840
|
-
export type FactRow = {
|
|
1841
|
-
id: string;
|
|
1842
|
-
content: string;
|
|
1843
|
-
kind: string;
|
|
1844
|
-
confidence: number;
|
|
1845
|
-
entityName: string | null;
|
|
1846
|
-
entityKind: string | null;
|
|
1847
|
-
sourceNodeId: string | null;
|
|
1848
|
-
sourceTitle: string | null;
|
|
1849
|
-
createdAt: string;
|
|
1850
|
-
};
|
|
1851
|
-
|
|
1852
|
-
export type TopicRow = {
|
|
1853
|
-
topic: string;
|
|
1854
|
-
topicSlug: string;
|
|
1855
|
-
digestCount: number;
|
|
1856
|
-
turnCount: number;
|
|
1857
|
-
firstSeen: string;
|
|
1858
|
-
lastSeen: string;
|
|
1859
|
-
};
|
|
1860
|
-
|
|
1861
|
-
/** One key/count bucket in the corpus histograms below. */
|
|
1862
|
-
export type Bucket = { key: string; count: number };
|
|
1863
|
-
|
|
1864
|
-
export type BrainCounts = {
|
|
1865
|
-
nodesTotal: number;
|
|
1866
|
-
nodesByType: Bucket[];
|
|
1867
|
-
factsTotal: number;
|
|
1868
|
-
factsByKind: Bucket[];
|
|
1869
|
-
entitiesTotal: number;
|
|
1870
|
-
entitiesByKind: Bucket[];
|
|
1871
|
-
edgesTotal: number;
|
|
1872
|
-
edgesByRelation: Bucket[];
|
|
1873
|
-
};
|
|
1874
|
-
|
|
1875
|
-
/** A health check, not a fixer. Counts active edges that share the same
|
|
1876
|
-
* (source, target, relation) — i.e. duplicates. The extractor's
|
|
1877
|
-
* delete-then-rebuild discipline (see architecture §9k) means this should
|
|
1878
|
-
* stay 0; a non-zero value flags a regression in edge writing. The remedy is
|
|
1879
|
-
* the one-shot `pnpm dedupe:edges --apply`, NOT a recurring auto-clean (which
|
|
1880
|
-
* would mask the regression). */
|
|
1881
|
-
export type GraphIntegrity = {
|
|
1882
|
-
/** Distinct (source, target, relation) groups with more than one row. */
|
|
1883
|
-
duplicateEdgeGroups: number;
|
|
1884
|
-
/** Total redundant rows across those groups (Σ count-1) — how many
|
|
1885
|
-
* `dedupe:edges --apply` would remove. */
|
|
1886
|
-
redundantEdgeRows: number;
|
|
1887
|
-
};
|
|
1888
|
-
|
|
1889
|
-
export type VectorCounts = {
|
|
1890
|
-
nodesIndexed: number;
|
|
1891
|
-
nodesTotal: number;
|
|
1892
|
-
factsIndexed: number;
|
|
1893
|
-
factsTotal: number;
|
|
1894
|
-
entitiesIndexed: number;
|
|
1895
|
-
entitiesTotal: number;
|
|
1896
|
-
/** The headline: total embedded vectors across nodes + facts + entities. */
|
|
1897
|
-
vectorsTotal: number;
|
|
1898
|
-
/** Global content-addressed embedding cache (not owner-scoped). */
|
|
1899
|
-
embeddingCacheRows: number;
|
|
1900
|
-
};
|
|
1901
|
-
|
|
1902
|
-
export type EmailStats = {
|
|
1903
|
-
total: number;
|
|
1904
|
-
unread: number;
|
|
1905
|
-
withAttachments: number;
|
|
1906
|
-
byAccount: { accountId: string; address: string; total: number; unread: number }[];
|
|
1907
|
-
latestSync: {
|
|
1908
|
-
accountId: string;
|
|
1909
|
-
address: string;
|
|
1910
|
-
status: string;
|
|
1911
|
-
finishedAt: string | null;
|
|
1912
|
-
ingested: number;
|
|
1913
|
-
scanned: number;
|
|
1914
|
-
error: string | null;
|
|
1915
|
-
}[];
|
|
1916
|
-
};
|
|
1917
|
-
|
|
1918
|
-
export type HeartbeatStats = {
|
|
1919
|
-
byStatus: Bucket[];
|
|
1920
|
-
recentFiresByDisposition: Bucket[];
|
|
1921
|
-
};
|
|
1922
|
-
|
|
1923
|
-
export type TelegramStats = {
|
|
1924
|
-
messagesTotal: number;
|
|
1925
|
-
unprocessed: number;
|
|
1926
|
-
chatsByStatus: Bucket[];
|
|
1927
|
-
};
|
|
1928
|
-
|
|
1929
|
-
export type IngestDay = {
|
|
1930
|
-
day: string; // YYYY-MM-DD
|
|
1931
|
-
total: number;
|
|
1932
|
-
byType: Record<string, number>;
|
|
1933
|
-
};
|
|
1934
|
-
|
|
1935
|
-
/** One model as shown in the explorer. Normalised fields are best-effort
|
|
1936
|
-
* (absent when the provider's API doesn't return them); `raw` is always the
|
|
1937
|
-
* untouched object the API gave us. */
|
|
1938
|
-
export type ExplorerModel = {
|
|
1939
|
-
/** Provider model id / slug (e.g. 'anthropic/claude-sonnet-4.6'). */
|
|
1940
|
-
id: string;
|
|
1941
|
-
/** Friendly display name if the API provides one. */
|
|
1942
|
-
name?: string;
|
|
1943
|
-
description?: string;
|
|
1944
|
-
/** Total context window in tokens. */
|
|
1945
|
-
contextTokens?: number;
|
|
1946
|
-
/** Max output/completion tokens, when stated separately. */
|
|
1947
|
-
maxOutputTokens?: number;
|
|
1948
|
-
/** USD per 1M input (prompt) tokens. 0 means free; undefined means unknown. */
|
|
1949
|
-
inputPricePerM?: number;
|
|
1950
|
-
/** USD per 1M output (completion) tokens. */
|
|
1951
|
-
outputPricePerM?: number;
|
|
1952
|
-
/** Other priced dimensions the API exposes, surfaced verbatim. */
|
|
1953
|
-
extraPricing?: { label: string; value: string }[];
|
|
1954
|
-
/** e.g. 'text+image→text'. */
|
|
1955
|
-
modality?: string;
|
|
1956
|
-
/** Coarse type: chat | embedding | image | tts | stt | rerank | other. */
|
|
1957
|
-
kind?: string;
|
|
1958
|
-
/** Release/creation time as ISO, when provided. */
|
|
1959
|
-
created?: string;
|
|
1960
|
-
/** The provider's untouched model object. */
|
|
1961
|
-
raw: unknown;
|
|
1962
|
-
};
|
|
1963
|
-
|
|
1964
|
-
export type ModelSort = 'name' | 'context' | 'input' | 'output' | 'created';
|
|
1965
|
-
|
|
1966
|
-
export type StudioNode = {
|
|
1967
|
-
/** Stable canvas id, namespaced by kind: `agent:<slug>` / `skill:<slug>`. */
|
|
1968
|
-
id: string;
|
|
1969
|
-
kind: StudioNodeKind;
|
|
1970
|
-
slug: string;
|
|
1971
|
-
label: string;
|
|
1972
|
-
/** Secondary line — model for agents, tool-count for skills. */
|
|
1973
|
-
sublabel: string;
|
|
1974
|
-
enabled: boolean;
|
|
1975
|
-
isPersona: boolean;
|
|
1976
|
-
/** Node-local referential problems (dangling tool/skill/delegate, disabled). */
|
|
1977
|
-
issues: string[];
|
|
1978
|
-
};
|
|
1979
|
-
|
|
1980
|
-
export type StudioEdge = {
|
|
1981
|
-
id: string;
|
|
1982
|
-
source: string;
|
|
1983
|
-
target: string;
|
|
1984
|
-
kind: 'skill' | 'delegate' | 'group';
|
|
1985
|
-
};
|
|
1986
|
-
|
|
1987
|
-
export type NodeBiographyView = {
|
|
1988
|
-
node: {
|
|
1989
|
-
id: string;
|
|
1990
|
-
type: string;
|
|
1991
|
-
title: string;
|
|
1992
|
-
path: string;
|
|
1993
|
-
tags: string[];
|
|
1994
|
-
createdAt: string;
|
|
1995
|
-
updatedAt: string;
|
|
1996
|
-
/** First N chars of the summary the extractor wrote — null if
|
|
1997
|
-
* the extractor hasn't run (or refused to). */
|
|
1998
|
-
summary: string | null;
|
|
1999
|
-
/** True when the node has an embedding vector — second half of
|
|
2000
|
-
* the "is this node ready for retrieval?" check. */
|
|
2001
|
-
hasEmbedding: boolean;
|
|
2002
|
-
/** Bytes of the content field (text-shaped nodes) or 0
|
|
2003
|
-
* otherwise. Useful for "did extractor skip because body too
|
|
2004
|
-
* short?" debugging. */
|
|
2005
|
-
contentChars: number;
|
|
2006
|
-
/** First 4KB of content. Lets the biography page show a quick
|
|
2007
|
-
* preview of what was actually saved. */
|
|
2008
|
-
contentPreview: string | null;
|
|
2009
|
-
/** The data jsonb truncated and key-summarised so we don't blow
|
|
2010
|
-
* up the page rendering a 1MB blob inline. */
|
|
2011
|
-
dataKeys: string[];
|
|
2012
|
-
};
|
|
2013
|
-
/** Traces in chronological order (oldest first). Operators read
|
|
2014
|
-
* these top-to-bottom as a story: ingest → extractor → ... */
|
|
2015
|
-
traces: TraceDetail[];
|
|
2016
|
-
stats: {
|
|
2017
|
-
totalTraces: number;
|
|
2018
|
-
totalCostMicroUsd: number;
|
|
2019
|
-
totalTokensIn: number;
|
|
2020
|
-
totalTokensOut: number;
|
|
2021
|
-
/** ISO timestamp of the earliest trace touching this node, or
|
|
2022
|
-
* the node's own createdAt if there are no traces. */
|
|
2023
|
-
firstSeen: string;
|
|
2024
|
-
/** ISO timestamp of the most recent trace. Equal to firstSeen
|
|
2025
|
-
* when there's only one. */
|
|
2026
|
-
lastTouched: string;
|
|
2027
|
-
/** Counts by kind + status for the header chips. */
|
|
2028
|
-
byKind: Record<string, number>;
|
|
2029
|
-
byStatus: Record<string, number>;
|
|
2030
|
-
};
|
|
2031
|
-
};
|
|
2032
|
-
|
|
2033
|
-
export type AssistantAgentOption = {
|
|
2034
|
-
id: string;
|
|
2035
|
-
slug: string;
|
|
2036
|
-
name: string;
|
|
2037
|
-
role: string;
|
|
2038
|
-
model: string;
|
|
2039
|
-
};
|
|
2040
|
-
|
|
2041
|
-
export type AssistantTimelineRow = {
|
|
2042
|
-
id: string;
|
|
2043
|
-
direction: 'inbound' | 'outbound';
|
|
2044
|
-
text: string;
|
|
2045
|
-
model: string | null;
|
|
2046
|
-
/** Transport the turn arrived/left on — drives the channel badge in the UI.
|
|
2047
|
-
* 'web' for native /assistant turns; 'telegram' (etc.) for turns that came
|
|
2048
|
-
* in on another surface and now show in the unified stream. */
|
|
2049
|
-
channel: string;
|
|
2050
|
-
/** Execution state (migration 0105). 'complete' for every historical/inbound
|
|
2051
|
-
* row; an outbound row is 'pending' while the durable runner works and
|
|
2052
|
-
* 'failed' if it errored — so a reload mid-turn renders a live "thinking…"
|
|
2053
|
-
* bubble (or the error) instead of nothing. See docs/live-turn-streaming.md. */
|
|
2054
|
-
status: 'pending' | 'complete' | 'failed';
|
|
2055
|
-
/** Human-readable failure reason for a 'failed' turn; null otherwise. */
|
|
2056
|
-
error: string | null;
|
|
2057
|
-
/** Persisted media (images, voice notes, docs) so the turn renders its
|
|
2058
|
-
* attachments on load — no bytes, just node/file references. */
|
|
2059
|
-
attachments: ConversationAttachment[];
|
|
2060
|
-
/** Persisted thought trail (grounded action labels), present on an outbound
|
|
2061
|
-
* row when the brain has trail-persistence on — lets the "Thought process"
|
|
2062
|
-
* record survive a reload. Undefined when not persisted. */
|
|
2063
|
-
thoughts?: Array<{ kind: string; label: string; elapsedMs?: number }>;
|
|
2064
|
-
/** Deterministic tool-outcome tally for the turn — the runtime's own
|
|
2065
|
-
* ledger, persisted at finalize. Drives the "N tool calls · M failed"
|
|
2066
|
-
* footer so the record is independent of the reply's claims. */
|
|
2067
|
-
toolStats?: ToolOutcomeStatsRow;
|
|
2068
|
-
/** True when this row belongs to a superseded (replaced) turn pair — the
|
|
2069
|
-
* user cancelled the turn mid-stream and re-sent original + correction as
|
|
2070
|
-
* one combined turn (data.superseded_by). The pair stays in the transcript,
|
|
2071
|
-
* rendered dimmed with a "replaced" tag; prompt history and digests skip it. */
|
|
2072
|
-
superseded?: boolean;
|
|
2073
|
-
createdAt: string;
|
|
2074
|
-
};
|
|
2075
|
-
|
|
2076
|
-
export type TestApiKeyResult = {
|
|
2077
|
-
ok: boolean;
|
|
2078
|
-
/** One-line summary for the UI — e.g. '13 models accessible' or
|
|
2079
|
-
* 'OpenAI rejected the key (401)'. */
|
|
2080
|
-
message: string;
|
|
2081
|
-
/** Provider label for the result line. Empty when we can't resolve the
|
|
2082
|
-
* provider from the key's service. */
|
|
2083
|
-
provider: string;
|
|
2084
|
-
/** Which adapter ran the probe ('openai-tts', 'anthropic-chat', …). */
|
|
2085
|
-
adapter: string;
|
|
2086
|
-
/** Number of models accessible to this key, if discovery succeeded. */
|
|
2087
|
-
modelsFound?: number;
|
|
2088
|
-
};
|
|
2089
|
-
|
|
2090
|
-
export type ComposeStatus = {
|
|
2091
|
-
state: ComposeState;
|
|
2092
|
-
/** The updater's last refresh outcome verbatim (e.g. 'refreshed',
|
|
2093
|
-
* 'modified', 'no-baseline', 'unavailable'), for the details view. */
|
|
2094
|
-
refresh: string | null;
|
|
2095
|
-
/** The CLIENT stack's compose (v0.200 split). 'absent' state = a
|
|
2096
|
-
* server-only box (no docker-compose.client.yml — nothing to drift). */
|
|
2097
|
-
client: { state: ComposeState | 'absent'; refresh: string | null };
|
|
2098
|
-
/** The updater sidecar's own script (v0.206+). Before the self-refresh
|
|
2099
|
-
* landed this was the silent failure: a stale script rolled the server
|
|
2100
|
-
* stack, reported ok, and skipped the client stack with no error anywhere.
|
|
2101
|
-
* 'unknown' on any box still running that script — it reports no sha. */
|
|
2102
|
-
updater: { state: UpdaterScriptState; refresh: string | null };
|
|
2103
|
-
/** The front door (v0.232.126+): infra/caddy/Caddyfile is release-owned and
|
|
2104
|
-
* refreshed by the updater like compose. 'unknown' on a box whose updater
|
|
2105
|
-
* predates the field. A 'modified' Caddyfile means release-level front-door
|
|
2106
|
-
* changes are not arriving there; box routes belong in conf.d/. */
|
|
2107
|
-
caddy: { state: ComposeState; refresh: string | null };
|
|
2108
|
-
checkedAt: string | null;
|
|
2109
|
-
};
|
|
2110
|
-
|
|
2111
|
-
export type UpdateCheck = {
|
|
2112
|
-
currentVersion: string;
|
|
2113
|
-
latest: ReleaseInfo | null;
|
|
2114
|
-
updateAvailable: boolean;
|
|
2115
|
-
checkedAt: string;
|
|
2116
|
-
/** Set when the check itself failed (network, rate limit, no releases yet). */
|
|
2117
|
-
error: string | null;
|
|
2118
|
-
/** The owner-UI (jackdaw) release stream — versioned separately since the
|
|
2119
|
-
* repo split. `latest` is jackdaw's newest release; `pairedTag` is the
|
|
2120
|
-
* client tag THIS server release was tested with (from the release-pair
|
|
2121
|
-
* file baked into the image). The server cannot know which client build a
|
|
2122
|
-
* browser is running, so "is an interface update available" is computed by
|
|
2123
|
-
* the client itself against its own APP_VERSION. Absent on servers that
|
|
2124
|
-
* predate the field. */
|
|
2125
|
-
client?: {
|
|
2126
|
-
latest: ReleaseInfo | null;
|
|
2127
|
-
pairedTag: string | null;
|
|
2128
|
-
error: string | null;
|
|
2129
|
-
} | null;
|
|
2130
|
-
};
|
|
2131
|
-
|
|
2132
|
-
export type UpdaterStatus = {
|
|
2133
|
-
phase: UpdaterPhase;
|
|
2134
|
-
target: string;
|
|
2135
|
-
startedAt: string | null;
|
|
2136
|
-
finishedAt: string | null;
|
|
2137
|
-
ok: boolean | null;
|
|
2138
|
-
error: string | null;
|
|
2139
|
-
};
|
|
2140
|
-
|
|
2141
|
-
export interface TailnetStatus {
|
|
2142
|
-
available: true;
|
|
2143
|
-
/** tailscaled backend state: "Running" when connected; "NeedsLogin",
|
|
2144
|
-
* "Stopped", "Starting" otherwise. */
|
|
2145
|
-
backendState: string;
|
|
2146
|
-
/** This node's MagicDNS name + hostname (how peers reach US). */
|
|
2147
|
-
self: { dnsName: string; hostName: string; online: boolean } | null;
|
|
2148
|
-
/** The tailnet domain, e.g. "tail1234.ts.net". */
|
|
2149
|
-
magicDNSSuffix: string | null;
|
|
2150
|
-
peers: TailnetPeer[];
|
|
2151
|
-
}
|
|
2152
|
-
|
|
2153
|
-
export interface TailnetUnavailable {
|
|
2154
|
-
available: false;
|
|
2155
|
-
/** Human-readable why — shown in the status tile. */
|
|
2156
|
-
reason: string;
|
|
2157
|
-
}
|
|
2158
|
-
|
|
2159
|
-
export type TailnetResult = TailnetStatus | TailnetUnavailable;
|
|
2160
|
-
|
|
2161
|
-
export type TailscaleConfigSummary = {
|
|
2162
|
-
hostname: string;
|
|
2163
|
-
masked: string;
|
|
2164
|
-
lastActivatedAt: Date | null;
|
|
2165
|
-
};
|
|
2166
|
-
|
|
2167
|
-
export type SystemHealth = {
|
|
2168
|
-
ts: string;
|
|
2169
|
-
scope: 'container' | 'host';
|
|
2170
|
-
host: {
|
|
2171
|
-
cpuLoadPct: number | null;
|
|
2172
|
-
mem: { usedBytes: number; totalBytes: number; usedPct: number } | null;
|
|
2173
|
-
disk: DiskInfo | null;
|
|
2174
|
-
uptimeSec: number;
|
|
2175
|
-
heapUsedBytes: number;
|
|
2176
|
-
rssBytes: number;
|
|
2177
|
-
loadAvg: number[];
|
|
2178
|
-
cpuCores: number;
|
|
2179
|
-
};
|
|
2180
|
-
postgres: {
|
|
2181
|
-
up: boolean;
|
|
2182
|
-
dbSizeBytes: number | null;
|
|
2183
|
-
connections: number | null;
|
|
2184
|
-
cacheHitPct: number | null;
|
|
2185
|
-
topTables: { name: string; bytes: number }[];
|
|
2186
|
-
};
|
|
2187
|
-
storage: {
|
|
2188
|
-
minioUp: boolean | null;
|
|
2189
|
-
attachmentBytes: number | null;
|
|
2190
|
-
filesDisk: DiskInfo | null;
|
|
2191
|
-
};
|
|
2192
|
-
/** Tier-2 document parser fallback (.odt / .pptx / .doc / .rtf / .epub /
|
|
2193
|
-
* …) — sibling docker service. `up: false` means the fallback path
|
|
2194
|
-
* degrades cleanly to `no_text_layer` on every new ingest of those
|
|
2195
|
-
* formats; in-process parsers (pdf/docx/xlsx/text) keep working. */
|
|
2196
|
-
tika: {
|
|
2197
|
-
up: boolean;
|
|
2198
|
-
version: string | null;
|
|
2199
|
-
};
|
|
2200
|
-
/** The browser sidecar (browserless/chromium) — the Pages → PDF export
|
|
2201
|
-
* engine, a sibling docker service like Tika. `up: false` means PDF
|
|
2202
|
-
* downloads 503 until it's back (Markdown/Word unaffected); `up: null`
|
|
2203
|
-
* means BROWSER_WS_ENDPOINT isn't configured (e.g. detached dev). */
|
|
2204
|
-
browser: {
|
|
2205
|
-
up: boolean | null;
|
|
2206
|
-
version: string | null;
|
|
2207
|
-
};
|
|
2208
|
-
/** The configured embedding server. For the `local` provider this is the
|
|
2209
|
-
* self-hosted Ollama/LM Studio/TEI on MANTLE_LOCAL_EMBEDDING_URL (the
|
|
2210
|
-
* bundled `ollama` compose service in prod). `up: true` means it's
|
|
2211
|
-
* reachable AND the configured model is loaded — the only state in which
|
|
2212
|
-
* ingest can actually embed. `up: null` = a remote/cloud embedder
|
|
2213
|
-
* (openrouter/openai/google), which isn't pingable from here without a key,
|
|
2214
|
-
* so it's surfaced as "remote" rather than a misleading red dot. */
|
|
2215
|
-
embedder: {
|
|
2216
|
-
up: boolean | null;
|
|
2217
|
-
provider: string | null;
|
|
2218
|
-
model: string | null;
|
|
2219
|
-
detail: string | null;
|
|
2220
|
-
/** Where the embedder runs: a self-hosted server ('local') or a cloud
|
|
2221
|
-
* provider ('remote'). Shown on the dashboard pill label. */
|
|
2222
|
-
scope: 'remote' | 'local' | null;
|
|
2223
|
-
};
|
|
2224
|
-
/** CLI sandboxes supervisor (sandboxd) — profile-gated like the tailnet,
|
|
2225
|
-
* so `up: null` (muted pill) is the resting state on a box without the
|
|
2226
|
-
* `sandboxes` compose profile. `up: true` requires sandboxd answering
|
|
2227
|
-
* (its own /healthz additionally verifies docker); counts and the disk
|
|
2228
|
-
* budget come from its live listing. */
|
|
2229
|
-
sandboxes: {
|
|
2230
|
-
up: boolean | null;
|
|
2231
|
-
total: number | null;
|
|
2232
|
-
running: number | null;
|
|
2233
|
-
disk: { usedBytes: number | null; budgetBytes: number } | null;
|
|
2234
|
-
};
|
|
2235
|
-
/** Media sidecar (yt-dlp + ffmpeg) — the video_ingest fetch/transcode
|
|
2236
|
-
* engine, profile-gated like sandboxes, so `up: null` (muted pill) is the
|
|
2237
|
-
* resting state on a box without the `media` compose profile. Versions
|
|
2238
|
-
* come from its /healthz — that is what makes a stale or failed yt-dlp
|
|
2239
|
-
* self-update VISIBLE instead of silently breaking downloads. */
|
|
2240
|
-
media: {
|
|
2241
|
-
up: boolean | null;
|
|
2242
|
-
ytDlpVersion: string | null;
|
|
2243
|
-
ffmpegVersion: string | null;
|
|
2244
|
-
/** null on images built before the CAD tier (v0.232.92) — DWF renders
|
|
2245
|
-
* fall back to embedded thumbnails without it. */
|
|
2246
|
-
ezdwfVersion: string | null;
|
|
2247
|
-
/** null on images built before the DWG tier (v0.232.99) — with either of
|
|
2248
|
-
* these missing the UI should show "DWG tier missing" (DWG parsing and
|
|
2249
|
-
* rendering both need the sidecar; absence means the whole format). */
|
|
2250
|
-
dwg2dxfVersion: string | null;
|
|
2251
|
-
ezdxfVersion: string | null;
|
|
2252
|
-
};
|
|
2253
|
-
/** Tailscale / local network — the optional tailnet that lets a cloud VPS
|
|
2254
|
-
* reach a LAN model box by MagicDNS name. Profile-gated and off by default
|
|
2255
|
-
* in dev, so `up: null` (a muted/disabled pill) is the normal resting state;
|
|
2256
|
-
* `up: true` only when tailscaled reports backendState 'Running'. */
|
|
2257
|
-
network: {
|
|
2258
|
-
up: boolean | null;
|
|
2259
|
-
detail: string | null;
|
|
2260
|
-
};
|
|
2261
|
-
degraded: string[];
|
|
2262
|
-
};
|
|
2263
|
-
|
|
2264
|
-
export type ProvisionResult = {
|
|
2265
|
-
createdWorkers: { kind: string; name: string; provider: string; model: string }[];
|
|
2266
|
-
createdAgent: { slug: string; name: string } | null;
|
|
2267
|
-
/** Capabilities skipped because the optional key wasn't provided. */
|
|
2268
|
-
skipped: string[];
|
|
2269
|
-
/** Specialist agents seeded alongside the persona (Pages, Ledger, Remy,
|
|
2270
|
-
* Researcher, Coder) and wired into the assistant's delegate_to. Names of the
|
|
2271
|
-
* ones that seeded successfully; a seed that throws is logged + omitted (it
|
|
2272
|
-
* never aborts onboarding — the persona is what matters). */
|
|
2273
|
-
seededSpecialists: string[];
|
|
2274
|
-
};
|
|
2275
|
-
|
|
2276
|
-
export type HeartbeatFireSummary = {
|
|
2277
|
-
id: string;
|
|
2278
|
-
firedAt: string;
|
|
2279
|
-
traceId: string | null;
|
|
2280
|
-
disposition: string;
|
|
2281
|
-
stateBefore: Record<string, unknown> | null;
|
|
2282
|
-
stateAfter: Record<string, unknown> | null;
|
|
2283
|
-
replyText: string | null;
|
|
2284
|
-
replySurfaceRef: Record<string, unknown> | null;
|
|
2285
|
-
errorMessage: string | null;
|
|
2286
|
-
};
|
|
2287
|
-
|
|
2288
|
-
export type AgentTelegramBinding = {
|
|
2289
|
-
accountId: string;
|
|
2290
|
-
botUsername: string;
|
|
2291
|
-
enabled: boolean;
|
|
2292
|
-
lastPollAt: string | null;
|
|
2293
|
-
lastPollError: string | null;
|
|
2294
|
-
};
|
|
2295
|
-
|
|
2296
|
-
export type AgentTelegramChat = {
|
|
2297
|
-
id: string;
|
|
2298
|
-
telegramChatId: string;
|
|
2299
|
-
label: string;
|
|
2300
|
-
status: 'pending' | 'allowed' | 'denied';
|
|
2301
|
-
lastMessageAt: string | null;
|
|
2302
|
-
};
|
|
2303
|
-
|
|
2304
|
-
export type DiffStatus =
|
|
2305
|
-
/** Live matches the template (for tracked fields). */
|
|
2306
|
-
| 'ok'
|
|
2307
|
-
/** In the template, absent (or disabled) in the brain — a capability not landed. */
|
|
2308
|
-
| 'missing'
|
|
2309
|
-
/** In the brain, not in the template — operator-added, informational. */
|
|
2310
|
-
| 'extra'
|
|
2311
|
-
/** Present in both, but a tracked field diverges. */
|
|
2312
|
-
| 'modified';
|
|
2313
|
-
|
|
2314
|
-
export type FieldDiff = {
|
|
2315
|
-
/** 'toolGroupSlugs' | 'skillSlugs' | 'delegate_to' | 'instructions' |
|
|
2316
|
-
* 'toolSlugs' | 'model' | 'systemPrompt' | 'enabled' */
|
|
2317
|
-
field: string;
|
|
2318
|
-
/** The template value — what an "adopt" would write. */
|
|
2319
|
-
manifest: string | string[] | null;
|
|
2320
|
-
/** The live value in the brain. */
|
|
2321
|
-
live: string | string[] | null;
|
|
2322
|
-
/** Set fields only: members in `live` but not `manifest` (operator-added). */
|
|
2323
|
-
added?: string[];
|
|
2324
|
-
/** Set fields only: members in `manifest` but not `live` (not landed). */
|
|
2325
|
-
removed?: string[];
|
|
2326
|
-
/** Informational-only diff (e.g. a specialist prompt) — shown, not weighted. */
|
|
2327
|
-
info?: boolean;
|
|
2328
|
-
};
|
|
2329
|
-
|
|
2330
|
-
export type EntityDiff = {
|
|
2331
|
-
kind: EntityKind;
|
|
2332
|
-
/** Agent/skill/group slug, or the worker kind. */
|
|
2333
|
-
slug: string;
|
|
2334
|
-
name: string;
|
|
2335
|
-
status: DiffStatus;
|
|
2336
|
-
severity: AuditSeverity;
|
|
2337
|
-
/** One-line human summary of the difference. */
|
|
2338
|
-
summary: string;
|
|
2339
|
-
/** Tracked fields that differ (empty when status is 'ok'). */
|
|
2340
|
-
fields: FieldDiff[];
|
|
2341
|
-
/** Can the operator "Adopt from template" this item? True for missing/modified
|
|
2342
|
-
* (apply the manifest version); false for ok (nothing to do) and extra
|
|
2343
|
-
* (operator-added — adopting would mean deleting, which we never do). */
|
|
2344
|
-
adoptable: boolean;
|
|
2345
|
-
};
|
|
2346
|
-
|
|
2347
|
-
export type ConfigDiffReport = {
|
|
2348
|
-
generatedAt: string;
|
|
2349
|
-
/** The shipped template version (APP_VERSION). */
|
|
2350
|
-
appVersion: string;
|
|
2351
|
-
/** The version the brain was last auto-reconciled to (null if never). */
|
|
2352
|
-
lastReconciledVersion: string | null;
|
|
2353
|
-
entities: EntityDiff[];
|
|
2354
|
-
counts: { ok: number; missing: number; extra: number; modified: number };
|
|
2355
|
-
};
|
|
2356
|
-
|
|
2357
|
-
export type AdoptKind = 'persona' | 'agent' | 'skill' | 'tool-group' | 'worker';
|
|
2358
|
-
|
|
2359
|
-
// ── Server-lib view/query DTOs (jackdaw split P0 follow-up: @server/* purge) ──
|
|
2360
|
-
// Moved from server/web/lib/* and @mantle/content; the originals re-export
|
|
2361
|
-
// these names so server import paths are unchanged.
|
|
2362
|
-
|
|
2363
|
-
export type UpdaterPhase =
|
|
2364
|
-
'idle' | 'pulling' | 'rolling' | 'done' | 'error' | 'unconfigured' | 'requested';
|
|
2365
|
-
|
|
2366
|
-
/** The updater SCRIPT's own currency. Deliberately not `ComposeState`: the
|
|
2367
|
-
* script has no `no-baseline` standoff (it self-adopts, having no supported
|
|
2368
|
-
* box-local variation), so a missing baseline is not a state an operator can
|
|
2369
|
-
* act on — the only actionable state is `modified`. */
|
|
2370
|
-
export type UpdaterScriptState =
|
|
2371
|
-
| 'in-sync' // box script == this release's canonical
|
|
2372
|
-
| 'stale' // differs — self-refreshes on the next successful update
|
|
2373
|
-
| 'modified' // differs from its baseline: hand-edited, refresh refused
|
|
2374
|
-
| 'unknown'; // no stack.json, or a pre-v0.206 updater that reports no sha
|
|
2375
|
-
|
|
2376
|
-
export type ComposeState =
|
|
2377
|
-
| 'in-sync' // box compose == this release's canonical
|
|
2378
|
-
| 'stale' // pristine (== baseline) but not this release's — refresh hasn't run
|
|
2379
|
-
| 'modified' // hand-edited canonical file — auto-refresh disabled, needs adoption
|
|
2380
|
-
| 'no-baseline' // pre-adoption box — run scripts/compose-adopt.sh once
|
|
2381
|
-
| 'unknown'; // no stack.json (old updater.sh / no sidecar / dev)
|
|
2382
|
-
|
|
2383
|
-
export type ReleaseInfo = {
|
|
2384
|
-
/** Tag as published, e.g. "v0.20.67". */
|
|
2385
|
-
tag: string;
|
|
2386
|
-
/** Bare version, e.g. "0.20.67". */
|
|
2387
|
-
version: string;
|
|
2388
|
-
name: string;
|
|
2389
|
-
url: string;
|
|
2390
|
-
publishedAt: string | null;
|
|
2391
|
-
};
|
|
2392
|
-
|
|
2393
|
-
export type DiskInfo = { usedBytes: number; totalBytes: number; usedPct: number; mount: string };
|
|
2394
|
-
|
|
2395
|
-
export type EntityKind = 'persona' | 'agent' | 'skill' | 'tool-group' | 'worker';
|
|
2396
|
-
|
|
2397
|
-
export type StudioNodeKind = 'agent' | 'skill' | 'group';
|
|
2398
|
-
|
|
2399
|
-
/** One peer on the tailnet (another device sharing your tailnet). */
|
|
2400
|
-
export interface TailnetPeer {
|
|
2401
|
-
/** MagicDNS name, trailing dot stripped — e.g. "gemma-box.tail1234.ts.net".
|
|
2402
|
-
* This is what you'd put in a route base URL: http://<dnsName>:<port>/v1 */
|
|
2403
|
-
dnsName: string;
|
|
2404
|
-
/** Short hostname — e.g. "gemma-box". */
|
|
2405
|
-
hostName: string;
|
|
2406
|
-
/** Tailscale IPs (100.x.y.z / fd7a:…). Surfaced for reference; prefer names. */
|
|
2407
|
-
ips: string[];
|
|
2408
|
-
online: boolean;
|
|
2409
|
-
/** OS string tailscaled reports (linux / windows / macOS …), best-effort. */
|
|
2410
|
-
os: string | null;
|
|
2411
|
-
}
|
|
2412
|
-
|
|
2413
|
-
export type ToolOutcomeStatsRow = {
|
|
2414
|
-
calls: number;
|
|
2415
|
-
succeeded: number;
|
|
2416
|
-
failed: number;
|
|
2417
|
-
skipped: number;
|
|
2418
|
-
/** Confirm-gated calls parked behind operator approval — not yet run. */
|
|
2419
|
-
queued: number;
|
|
2420
|
-
failures: Array<{ slug: string; error: string }>;
|
|
2421
|
-
};
|
|
2422
|
-
|
|
2423
|
-
// ── Server-lib view/query DTOs (jackdaw split P0 follow-up: @server/* purge) ──
|
|
2424
|
-
// Moved from server/web/lib/* and @mantle/content; the originals re-export
|
|
2425
|
-
// these names so server import paths are unchanged.
|
|
2426
|
-
|
|
2427
|
-
export type CacheHitStats = {
|
|
2428
|
-
hits: number;
|
|
2429
|
-
misses: number;
|
|
2430
|
-
apiCalls: number;
|
|
2431
|
-
};
|
|
2432
|
-
|
|
2433
|
-
export type DuplicateSuppression = {
|
|
2434
|
-
/** Model slug captured in trace_steps.meta.model at suppression time. */
|
|
2435
|
-
model: string;
|
|
2436
|
-
/** How many duplicate tool_use blocks were suppressed in the window. */
|
|
2437
|
-
count: number;
|
|
2438
|
-
/** Distinct tool slugs the duplicates targeted (top 5, comma-separated). */
|
|
2439
|
-
topSlugs: string;
|
|
2440
|
-
/** Most recent suppression, ISO string. */
|
|
2441
|
-
lastAt: string;
|
|
2442
|
-
};
|
|
2443
|
-
|
|
2444
|
-
export type FactCostCapStats = {
|
|
2445
|
-
/** Extractor model slug captured in trace_steps.meta.model. */
|
|
2446
|
-
model: string;
|
|
2447
|
-
/** How many process_facts steps dropped facts to the cap in the window. */
|
|
2448
|
-
runs: number;
|
|
2449
|
-
/** Total facts discarded across those runs (sum of meta.dropped). */
|
|
2450
|
-
factsDropped: number;
|
|
2451
|
-
/** Most recent occurrence, ISO string. */
|
|
2452
|
-
lastAt: string;
|
|
2453
|
-
};
|
|
2454
|
-
|
|
2455
|
-
export type Traffic = {
|
|
2456
|
-
count: number;
|
|
2457
|
-
errorCount: number;
|
|
2458
|
-
avgMs: number | null;
|
|
2459
|
-
costMicroUsd: number;
|
|
2460
|
-
tokensIn: number;
|
|
2461
|
-
tokensOut: number;
|
|
2462
|
-
tokensCacheRead: number;
|
|
2463
|
-
};
|
|
2464
|
-
|
|
2465
|
-
export type StudioGraph = {
|
|
2466
|
-
generatedAt: string;
|
|
2467
|
-
nodes: StudioNode[];
|
|
2468
|
-
edges: StudioEdge[];
|
|
2469
|
-
agents: StudioAgentDetail[];
|
|
2470
|
-
skills: StudioSkillDetail[];
|
|
2471
|
-
toolGroups: StudioToolGroupDetail[];
|
|
2472
|
-
workers: StudioWorkerDetail[];
|
|
2473
|
-
/** Live config-integrity report (the same checker behind /debug/integrity). */
|
|
2474
|
-
report: SystemReport;
|
|
2475
|
-
};
|
|
2476
|
-
|
|
2477
|
-
export type StudioAgentDetail = {
|
|
2478
|
-
id: string;
|
|
2479
|
-
slug: string;
|
|
2480
|
-
name: string;
|
|
2481
|
-
model: string;
|
|
2482
|
-
role: string;
|
|
2483
|
-
enabled: boolean;
|
|
2484
|
-
isPersona: boolean;
|
|
2485
|
-
skillSlugs: string[];
|
|
2486
|
-
/** Skills attached but NOT resolved (missing or disabled) — surfaced honestly. */
|
|
2487
|
-
missingSkillSlugs: string[];
|
|
2488
|
-
delegateSlugs: string[];
|
|
2489
|
-
/** Tool groups granted to this agent. */
|
|
2490
|
-
toolGroupSlugs: string[];
|
|
2491
|
-
/** Granted groups that are missing or disabled — surfaced honestly. */
|
|
2492
|
-
missingToolGroupSlugs: string[];
|
|
2493
|
-
toolCount: number;
|
|
2494
|
-
params: { temperature?: number; max_tokens?: number };
|
|
2495
|
-
maxIterations?: number;
|
|
2496
|
-
/** Whether this is a manifest agent that can be reset to its canonical default. */
|
|
2497
|
-
resettable: boolean;
|
|
2498
|
-
/** The base system prompt (editable prose in Phase 2). */
|
|
2499
|
-
systemPrompt: string;
|
|
2500
|
-
/** The enabled, attached skills in composition order. */
|
|
2501
|
-
skillBlocks: ComposedSkillBlock[];
|
|
2502
|
-
/** The full assembled system prompt the model receives (base + skill blocks),
|
|
2503
|
-
* exactly as `composeSystemPromptWithSkills` builds it on a real turn. */
|
|
2504
|
-
composedPrompt: string;
|
|
2505
|
-
};
|
|
2506
|
-
|
|
2507
|
-
export type ComposedSkillBlock = { slug: string; name: string; instructions: string };
|
|
2508
|
-
|
|
2509
|
-
export type StudioSkillDetail = {
|
|
2510
|
-
id: string;
|
|
2511
|
-
slug: string;
|
|
2512
|
-
name: string;
|
|
2513
|
-
enabled: boolean;
|
|
2514
|
-
instructions: string;
|
|
2515
|
-
/** Fan-out: every agent that attaches this skill (the many-to-many). */
|
|
2516
|
-
usedByAgentSlugs: string[];
|
|
2517
|
-
};
|
|
2518
|
-
|
|
2519
|
-
export type StudioToolGroupDetail = {
|
|
2520
|
-
id: string;
|
|
2521
|
-
slug: string;
|
|
2522
|
-
name: string;
|
|
2523
|
-
enabled: boolean;
|
|
2524
|
-
toolSlugs: string[];
|
|
2525
|
-
/** Fan-out: every agent that grants this group. */
|
|
2526
|
-
usedByAgentSlugs: string[];
|
|
2527
|
-
};
|
|
2528
|
-
|
|
2529
|
-
export type StudioWorkerDetail = {
|
|
2530
|
-
id: string;
|
|
2531
|
-
kind: string;
|
|
2532
|
-
name: string;
|
|
2533
|
-
model: string;
|
|
2534
|
-
enabled: boolean;
|
|
2535
|
-
isDefault: boolean;
|
|
2536
|
-
/** Worker prose (registry): the chat-worker system prompt + the vision/document
|
|
2537
|
-
* extraction prompt, when present. */
|
|
2538
|
-
systemPrompt: string | null;
|
|
2539
|
-
extractionPrompt: string | null;
|
|
2540
|
-
issues: string[];
|
|
2541
|
-
};
|
|
2542
|
-
|
|
2543
|
-
// ── Recall (memory maps) ─────────────────────────────────────────────────────
|
|
2544
|
-
|
|
2545
|
-
export type {
|
|
2546
|
-
RecallLintIssueDTO,
|
|
2547
|
-
RecallLintSeverity,
|
|
2548
|
-
RecallMapDetailDTO,
|
|
2549
|
-
RecallMapSummaryDTO,
|
|
2550
|
-
RecallNodeDTO,
|
|
2551
|
-
RecallOptionDTO,
|
|
2552
|
-
RecallPageStateDTO,
|
|
2553
|
-
} from './types/recall';
|
|
20
|
+
* The 2548 lines this file used to hold are now ./dto/*, split by domain
|
|
21
|
+
* (2026-09-02 audit, tier 3). It changed 102 times in 90 days as one file, so
|
|
22
|
+
* every screen that added a DTO collided with every other. The re-exports below
|
|
23
|
+
* are the whole file: the package's public surface is unchanged, and a new DTO
|
|
24
|
+
* now touches one domain module instead of this one.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
export * from './dto/agent-graph';
|
|
28
|
+
export * from './dto/agents';
|
|
29
|
+
export * from './dto/comms';
|
|
30
|
+
export * from './dto/heartbeats';
|
|
31
|
+
export * from './dto/turns';
|
|
32
|
+
export * from './dto/rows';
|
|
33
|
+
export * from './dto/views';
|
|
34
|
+
export * from './dto/recall';
|