chapterhouse 0.8.2 → 0.9.1

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.
@@ -0,0 +1,615 @@
1
+ /**
2
+ * Shared API contract schemas — single source of truth for every server↔client
3
+ * response type in Chapterhouse.
4
+ *
5
+ * Both the backend (`src/api/server.ts`) and the frontend (`web/src/api-schemas.ts`)
6
+ * import from here. When a server response shape changes, the TypeScript compiler
7
+ * immediately surfaces any mismatch on the import side — schema drift becomes a
8
+ * compile error rather than a runtime bug.
9
+ *
10
+ * Excluded from this file:
11
+ * - Parts model (TextPart, ToolCallPart, etc.) — client-only; synthesised from SSE
12
+ * frames in the frontend store. Lives in `web/src/api-schemas.ts`.
13
+ * - TurnEventSchema — depends on the client-side PartSchema (which includes the
14
+ * client-only ActivityHeartbeatPart). Lives in `web/src/api-schemas.ts`.
15
+ * See `src/copilot/turn-events.ts` for the daemon-side TypeScript mirror.
16
+ *
17
+ * Migration status: initial extraction from web/src/api-schemas.ts (issue #369).
18
+ */
19
+ import { z } from "zod";
20
+ // ---------------------------------------------------------------------------
21
+ // /api/config/public
22
+ // ---------------------------------------------------------------------------
23
+ const StandaloneConfigSchema = z.object({
24
+ appName: z.literal("Chapterhouse"),
25
+ entraAuthEnabled: z.literal(false),
26
+ standalone: z.literal(true),
27
+ chatSseEnabled: z.boolean().optional(),
28
+ });
29
+ const NonStandaloneConfigSchema = z.object({
30
+ appName: z.literal("Chapterhouse"),
31
+ entraAuthEnabled: z.literal(false),
32
+ standalone: z.literal(false),
33
+ chatSseEnabled: z.boolean().optional(),
34
+ });
35
+ const EntraRuntimeConfigSchema = z.object({
36
+ appName: z.literal("Chapterhouse"),
37
+ entraAuthEnabled: z.literal(true),
38
+ entraClientId: z.string(),
39
+ entraTenantId: z.string(),
40
+ chatSseEnabled: z.boolean().optional(),
41
+ });
42
+ export const PublicConfigResponseSchema = z.union([
43
+ StandaloneConfigSchema,
44
+ NonStandaloneConfigSchema,
45
+ EntraRuntimeConfigSchema,
46
+ ]);
47
+ // ---------------------------------------------------------------------------
48
+ // /api/bootstrap
49
+ // ---------------------------------------------------------------------------
50
+ export const BootstrapResponseSchema = z.union([
51
+ z.object({ authMode: z.literal("legacy"), token: z.string().nullable() }),
52
+ z.object({ authMode: z.literal("entra") }),
53
+ z.object({ authMode: z.literal("standalone") }),
54
+ ]);
55
+ // ---------------------------------------------------------------------------
56
+ // /api/message /api/restart
57
+ // ---------------------------------------------------------------------------
58
+ export const StatusResponseSchema = z.object({ status: z.string() });
59
+ // ---------------------------------------------------------------------------
60
+ // /api/cancel
61
+ // ---------------------------------------------------------------------------
62
+ export const CancelResponseSchema = z.object({
63
+ status: z.string(),
64
+ cancelled: z.boolean(),
65
+ });
66
+ // ---------------------------------------------------------------------------
67
+ // /api/agents
68
+ // ---------------------------------------------------------------------------
69
+ export const AgentSummarySchema = z.object({
70
+ name: z.string(),
71
+ slug: z.string(),
72
+ description: z.string(),
73
+ model: z.string(),
74
+ scope: z.string().nullable(),
75
+ type: z.enum(["builtin", "custom"]),
76
+ lastEdited: z.string().nullable(),
77
+ });
78
+ export const AgentListSchema = z.array(AgentSummarySchema);
79
+ export const AgentDetailSchema = z.object({
80
+ name: z.string(),
81
+ slug: z.string(),
82
+ description: z.string(),
83
+ model: z.string(),
84
+ scope: z.string().nullable(),
85
+ persistent: z.boolean(),
86
+ skills: z.array(z.string()),
87
+ type: z.enum(["builtin", "custom"]),
88
+ editable: z.boolean(),
89
+ systemPrompt: z.string(),
90
+ });
91
+ export const AgentPatchRequestSchema = z.object({
92
+ name: z.string().optional(),
93
+ description: z.string().optional(),
94
+ model: z.string().optional(),
95
+ systemPrompt: z.string().optional(),
96
+ }).strict();
97
+ // ---------------------------------------------------------------------------
98
+ // /api/projects
99
+ // ---------------------------------------------------------------------------
100
+ export const ProjectSummarySchema = z.object({
101
+ slug: z.string(),
102
+ cwd: z.string(),
103
+ hardRuleCount: z.number().int().nullable(),
104
+ softRuleCount: z.number().int().nullable(),
105
+ });
106
+ export const ProjectListSchema = z.array(ProjectSummarySchema);
107
+ export const ProjectHardRulesSchema = z.object({
108
+ auto_pr: z.boolean(),
109
+ require_worktree: z.boolean(),
110
+ pr_draft_default: z.boolean(),
111
+ default_branch: z.string(),
112
+ commit_co_author: z.string(),
113
+ test_command: z.string(),
114
+ build_command: z.string(),
115
+ lint_command: z.string(),
116
+ require_clean_worktree: z.boolean(),
117
+ });
118
+ export const ProjectHardRulesUpdateRequestSchema = z.object({
119
+ hardRules: ProjectHardRulesSchema,
120
+ });
121
+ export const ProjectSoftRulesUpdateRequestSchema = z.object({
122
+ softRules: z.array(z.string()),
123
+ });
124
+ export const ProjectDetailSchema = z.discriminatedUnion("rulesFound", [
125
+ z.object({
126
+ slug: z.string(),
127
+ cwd: z.string(),
128
+ rulesFound: z.literal(true),
129
+ hardRules: ProjectHardRulesSchema,
130
+ softRules: z.array(z.string()),
131
+ }),
132
+ z.object({
133
+ slug: z.string(),
134
+ cwd: z.string(),
135
+ rulesFound: z.literal(false),
136
+ hardRules: z.null(),
137
+ softRules: z.array(z.string()),
138
+ }),
139
+ ]);
140
+ export const ProjectDeleteResponseSchema = z.object({
141
+ ok: z.literal(true),
142
+ slug: z.string(),
143
+ });
144
+ export const AgentSaveResponseSchema = z.object({
145
+ ok: z.literal(true),
146
+ slug: z.string(),
147
+ });
148
+ // ---------------------------------------------------------------------------
149
+ // /api/workers (list)
150
+ // ---------------------------------------------------------------------------
151
+ export const WorkerRowSchema = z.object({
152
+ slug: z.string(),
153
+ name: z.string(),
154
+ model: z.string(),
155
+ taskId: z.string(),
156
+ description: z.string(),
157
+ status: z.string(),
158
+ startedAt: z.string(),
159
+ completedAt: z.string().nullable(),
160
+ });
161
+ export const WorkerListSchema = z.array(WorkerRowSchema);
162
+ // ---------------------------------------------------------------------------
163
+ // /api/workers/:taskId (detail)
164
+ // ---------------------------------------------------------------------------
165
+ export const WorkerDetailSchema = z.object({
166
+ taskId: z.string(),
167
+ agentSlug: z.string(),
168
+ name: z.string(),
169
+ description: z.string(),
170
+ prompt: z.string().nullable(),
171
+ status: z.string(),
172
+ result: z.string().nullable(),
173
+ startedAt: z.string(),
174
+ completedAt: z.string().nullable(),
175
+ });
176
+ // ---------------------------------------------------------------------------
177
+ // /api/workers/:taskId/events (historical event log)
178
+ // ---------------------------------------------------------------------------
179
+ export const TaskEventSchema = z.object({
180
+ id: z.number(),
181
+ taskId: z.string(),
182
+ seq: z.number(),
183
+ ts: z.number(),
184
+ kind: z.enum(["tool_start", "tool_complete"]),
185
+ toolName: z.string().nullable(),
186
+ summary: z.string().nullable(),
187
+ });
188
+ export const TaskEventsResponseSchema = z.object({
189
+ taskId: z.string(),
190
+ events: z.array(TaskEventSchema),
191
+ });
192
+ // ---------------------------------------------------------------------------
193
+ // /api/model (get / set)
194
+ // ---------------------------------------------------------------------------
195
+ export const ModelResponseSchema = z.object({ model: z.string() });
196
+ export const SetModelResponseSchema = z.object({ previous: z.string(), current: z.string() });
197
+ export const ListModelsResponseSchema = z.object({
198
+ models: z.array(z.string()),
199
+ current: z.string(),
200
+ });
201
+ // ---------------------------------------------------------------------------
202
+ // /api/auto
203
+ // ---------------------------------------------------------------------------
204
+ export const AutoConfigSchema = z.object({
205
+ enabled: z.boolean(),
206
+ tierModels: z.record(z.string(), z.string()),
207
+ cooldownMessages: z.number(),
208
+ currentModel: z.string(),
209
+ lastRoute: z.unknown(),
210
+ });
211
+ // ---------------------------------------------------------------------------
212
+ // /api/wiki/pages
213
+ // ---------------------------------------------------------------------------
214
+ export const WikiPageMetaSchema = z.object({
215
+ path: z.string(),
216
+ title: z.string(),
217
+ summary: z.string(),
218
+ section: z.string(),
219
+ tags: z.array(z.string()),
220
+ updated: z.string(),
221
+ scope: z.enum(["personal", "team"]),
222
+ });
223
+ export const WikiPageListSchema = z.array(WikiPageMetaSchema);
224
+ // ---------------------------------------------------------------------------
225
+ // /api/wiki/page (read / write / delete)
226
+ // ---------------------------------------------------------------------------
227
+ const WikiFrontmatterValueSchema = z.union([z.string(), z.array(z.string()), z.boolean()]);
228
+ export const WikiPageFrontmatterSchema = z.object({
229
+ title: z.string().optional(),
230
+ summary: z.string().optional(),
231
+ updated: z.string().optional(),
232
+ tags: z.array(z.string()).optional(),
233
+ autostub: z.boolean().optional(),
234
+ confidence: z.enum(["high", "medium", "low"]).optional(),
235
+ contested: z.boolean().optional(),
236
+ contradictions: z.array(z.string()).optional(),
237
+ related: z.array(z.string()).optional(),
238
+ auto_pr: z.boolean().optional(),
239
+ require_worktree: z.boolean().optional(),
240
+ pr_draft_default: z.boolean().optional(),
241
+ default_branch: z.string().optional(),
242
+ commit_co_author: z.string().optional(),
243
+ test_command: z.string().optional(),
244
+ build_command: z.string().optional(),
245
+ lint_command: z.string().optional(),
246
+ require_clean_worktree: z.boolean().optional(),
247
+ metadata: z.record(z.string(), WikiFrontmatterValueSchema),
248
+ });
249
+ export const WikiPageContentSchema = z.object({
250
+ path: z.string(),
251
+ content: z.string(),
252
+ renderedContent: z.string(),
253
+ frontmatter: WikiPageFrontmatterSchema,
254
+ });
255
+ export const WikiWriteResponseSchema = z.object({
256
+ ok: z.boolean(),
257
+ created: z.boolean(),
258
+ path: z.string(),
259
+ });
260
+ export const WikiDeleteResponseSchema = z.object({
261
+ ok: z.boolean(),
262
+ path: z.string(),
263
+ });
264
+ export const WikiBrowserPageSchema = z.object({
265
+ slug: z.string(),
266
+ title: z.string().default(""),
267
+ summary: z.string().default(""),
268
+ type: z.string().default("topics"),
269
+ last_updated: z.string().default(""),
270
+ pinned: z.boolean().optional(),
271
+ }).passthrough();
272
+ export const WikiBrowserPageListSchema = z.object({
273
+ pages: z.array(WikiBrowserPageSchema),
274
+ });
275
+ export const WikiLinkSchema = z.object({
276
+ source_slug: z.string(),
277
+ target_slug: z.string(),
278
+ link_type: z.string(),
279
+ }).passthrough();
280
+ export const WikiLinksResponseSchema = z.object({
281
+ links: z.array(WikiLinkSchema),
282
+ });
283
+ export const WikiSourceSchema = z.object({
284
+ source_url: z.string().optional(),
285
+ path: z.string().optional(),
286
+ kind: z.string(),
287
+ status: z.string(),
288
+ session_id: z.string().nullable().optional(),
289
+ ingested_at: z.string().nullable().optional(),
290
+ }).passthrough();
291
+ export const WikiSourcesResponseSchema = z.object({
292
+ sources: z.array(WikiSourceSchema),
293
+ });
294
+ export const WikiResearchSessionSchema = z.object({
295
+ id: z.string().optional(),
296
+ name: z.string(),
297
+ source_count: z.number().int().default(0),
298
+ open_questions_count: z.number().int().default(0),
299
+ last_activity_at: z.string().default(""),
300
+ }).passthrough();
301
+ export const WikiResearchSessionsResponseSchema = z.object({
302
+ sessions: z.array(WikiResearchSessionSchema),
303
+ });
304
+ export const WikiPageDetailSchema = z.object({
305
+ page: WikiBrowserPageSchema.extend({
306
+ compiled_truth: z.string().default(""),
307
+ pinned: z.boolean().default(false),
308
+ frontmatter: z.record(z.string(), z.unknown()).optional(),
309
+ links: z.array(WikiLinkSchema).optional(),
310
+ }).passthrough(),
311
+ });
312
+ export const WikiPageUpdateResponseSchema = z.object({
313
+ ok: z.boolean(),
314
+ page: WikiPageDetailSchema.shape.page.optional(),
315
+ pinned: z.boolean().optional(),
316
+ }).passthrough();
317
+ export const WikiReingestResponseSchema = z.object({
318
+ ok: z.boolean(),
319
+ }).passthrough();
320
+ // ---------------------------------------------------------------------------
321
+ // /api/skills
322
+ // ---------------------------------------------------------------------------
323
+ export const SkillSchema = z.object({
324
+ slug: z.string(),
325
+ name: z.string(),
326
+ description: z.string(),
327
+ directory: z.string(),
328
+ source: z.enum(["bundled", "local", "global"]),
329
+ });
330
+ export const SkillListSchema = z.array(SkillSchema);
331
+ export const RemoveSkillResponseSchema = z.object({
332
+ ok: z.boolean(),
333
+ message: z.string(),
334
+ });
335
+ // ---------------------------------------------------------------------------
336
+ // /api/channels
337
+ // ---------------------------------------------------------------------------
338
+ export const ChannelSchema = z.object({
339
+ key: z.string().min(1),
340
+ label: z.string().min(1),
341
+ slug: z.string().optional(),
342
+ name: z.string().optional(),
343
+ description: z.string().optional(),
344
+ scope: z.string().optional(),
345
+ });
346
+ export const ChannelListSchema = z.array(ChannelSchema);
347
+ // ---------------------------------------------------------------------------
348
+ // /api/session/:key/messages
349
+ // ---------------------------------------------------------------------------
350
+ export const SessionMessageSchema = z.object({
351
+ id: z.number().int(),
352
+ role: z.enum(["user", "assistant"]),
353
+ content: z.string(),
354
+ ts: z.string(),
355
+ turn_id: z.string().nullable(),
356
+ turnId: z.string().optional(),
357
+ agentSlug: z.string().optional(),
358
+ agentDisplayName: z.string().optional(),
359
+ });
360
+ export const SessionMessagesResponseSchema = z.object({
361
+ sessionKey: z.string(),
362
+ messages: z.array(SessionMessageSchema),
363
+ });
364
+ // ---------------------------------------------------------------------------
365
+ // SSE stream events (/stream)
366
+ // ---------------------------------------------------------------------------
367
+ const RouteInfoSchema = z.object({
368
+ model: z.string(),
369
+ routerMode: z.string().optional(),
370
+ tier: z.string().nullable().optional(),
371
+ overrideName: z.string().optional(),
372
+ });
373
+ const ThinkingDeltaFrameSchema = z.object({
374
+ kind: z.literal("thinking_delta"),
375
+ reasoningId: z.string(),
376
+ deltaContent: z.string(),
377
+ agentSlug: z.string().optional(),
378
+ });
379
+ const ToolStartFrameSchema = z.object({
380
+ kind: z.literal("tool_start"),
381
+ toolCallId: z.string(),
382
+ toolName: z.string(),
383
+ mcpServerName: z.string().optional(),
384
+ arguments: z.record(z.string(), z.unknown()).optional(),
385
+ agentSlug: z.string().optional(),
386
+ });
387
+ const ToolCompleteFrameSchema = z.object({
388
+ kind: z.literal("tool_complete"),
389
+ toolCallId: z.string(),
390
+ success: z.boolean(),
391
+ resultPreview: z.string().optional(),
392
+ detailedContent: z.string().optional(),
393
+ agentSlug: z.string().optional(),
394
+ });
395
+ const SubagentStartedFrameSchema = z.object({
396
+ kind: z.literal("subagent_started"),
397
+ toolCallId: z.string(),
398
+ agentName: z.string(),
399
+ agentDisplayName: z.string(),
400
+ agentDescription: z.string(),
401
+ agentSlug: z.string().optional(),
402
+ });
403
+ const SubagentCompletedFrameSchema = z.object({
404
+ kind: z.literal("subagent_completed"),
405
+ toolCallId: z.string(),
406
+ agentName: z.string(),
407
+ agentDisplayName: z.string(),
408
+ durationMs: z.number().optional(),
409
+ agentSlug: z.string().optional(),
410
+ });
411
+ const SubagentFailedFrameSchema = z.object({
412
+ kind: z.literal("subagent_failed"),
413
+ toolCallId: z.string(),
414
+ agentName: z.string(),
415
+ agentDisplayName: z.string(),
416
+ error: z.string().optional(),
417
+ agentSlug: z.string().optional(),
418
+ });
419
+ export const ActivityFrameSchema = z.discriminatedUnion("kind", [
420
+ ThinkingDeltaFrameSchema,
421
+ ToolStartFrameSchema,
422
+ ToolCompleteFrameSchema,
423
+ SubagentStartedFrameSchema,
424
+ SubagentCompletedFrameSchema,
425
+ SubagentFailedFrameSchema,
426
+ ]);
427
+ export const StreamEventSchema = z.union([
428
+ z.object({ type: z.literal("connected"), connectionId: z.string() }),
429
+ z.object({ type: z.literal("delta"), content: z.string(), sessionKey: z.string().optional(), turnId: z.string().optional() }),
430
+ z.object({
431
+ type: z.literal("message"),
432
+ content: z.string(),
433
+ sessionKey: z.string().optional(),
434
+ turnId: z.string().optional(),
435
+ route: RouteInfoSchema.optional(),
436
+ }),
437
+ z.object({ type: z.literal("cancelled"), sessionKey: z.string().optional() }),
438
+ z.object({
439
+ type: z.literal("status"),
440
+ status: z.enum(["idle", "dreaming"]),
441
+ message: z.string(),
442
+ }),
443
+ z.object({
444
+ type: z.literal("agent_reload_pending"),
445
+ slug: z.string(),
446
+ reason: z.literal("in_flight"),
447
+ }),
448
+ z.object({
449
+ type: z.literal("agent_reloaded"),
450
+ slug: z.string(),
451
+ reason: z.enum(["session_restart", "confirmed_restart"]),
452
+ }),
453
+ z.object({
454
+ type: z.literal("queued"),
455
+ position: z.number(),
456
+ sessionKey: z.string().optional(),
457
+ turnId: z.string().optional(),
458
+ msgId: z.string().optional(),
459
+ }),
460
+ // Activity events — each ActivityFrame kind merged inline with the activity wrapper.
461
+ // turnId is included for future use (e.g., the activity sidecar in #85) but is NOT
462
+ // used by App.tsx for bubble-boundary detection — activity events don't drive bubble
463
+ // boundaries; only delta/message events (which carry the authoritative turnId) do.
464
+ z.object({
465
+ type: z.literal("activity"),
466
+ sessionKey: z.string().optional(),
467
+ turnId: z.string().optional(),
468
+ kind: z.literal("thinking_delta"),
469
+ reasoningId: z.string(),
470
+ deltaContent: z.string(),
471
+ agentSlug: z.string().optional(),
472
+ }),
473
+ z.object({
474
+ type: z.literal("activity"),
475
+ sessionKey: z.string().optional(),
476
+ turnId: z.string().optional(),
477
+ kind: z.literal("tool_start"),
478
+ toolCallId: z.string(),
479
+ toolName: z.string(),
480
+ mcpServerName: z.string().optional(),
481
+ arguments: z.record(z.string(), z.unknown()).optional(),
482
+ agentSlug: z.string().optional(),
483
+ }),
484
+ z.object({
485
+ type: z.literal("activity"),
486
+ sessionKey: z.string().optional(),
487
+ turnId: z.string().optional(),
488
+ kind: z.literal("tool_complete"),
489
+ toolCallId: z.string(),
490
+ success: z.boolean(),
491
+ resultPreview: z.string().optional(),
492
+ detailedContent: z.string().optional(),
493
+ agentSlug: z.string().optional(),
494
+ }),
495
+ z.object({
496
+ type: z.literal("activity"),
497
+ sessionKey: z.string().optional(),
498
+ turnId: z.string().optional(),
499
+ kind: z.literal("subagent_started"),
500
+ toolCallId: z.string(),
501
+ agentName: z.string(),
502
+ agentDisplayName: z.string(),
503
+ agentDescription: z.string(),
504
+ agentSlug: z.string().optional(),
505
+ }),
506
+ z.object({
507
+ type: z.literal("activity"),
508
+ sessionKey: z.string().optional(),
509
+ turnId: z.string().optional(),
510
+ kind: z.literal("subagent_completed"),
511
+ toolCallId: z.string(),
512
+ agentName: z.string(),
513
+ agentDisplayName: z.string(),
514
+ durationMs: z.number().optional(),
515
+ agentSlug: z.string().optional(),
516
+ }),
517
+ z.object({
518
+ type: z.literal("activity"),
519
+ sessionKey: z.string().optional(),
520
+ turnId: z.string().optional(),
521
+ kind: z.literal("subagent_failed"),
522
+ toolCallId: z.string(),
523
+ agentName: z.string(),
524
+ agentDisplayName: z.string(),
525
+ error: z.string().optional(),
526
+ agentSlug: z.string().optional(),
527
+ }),
528
+ // Emitted by the backend when a queued turn starts processing, with the number
529
+ // of messages still waiting behind it. Frontend renumbers "N ahead" indicators.
530
+ z.object({
531
+ type: z.literal("queue-advance"),
532
+ length: z.number(),
533
+ sessionKey: z.string().optional(),
534
+ }),
535
+ // Emitted by the backend when an active turn is aborted by the user via the
536
+ // interrupt endpoint. The frontend should drop the partial in-flight bubble
537
+ // (identified by abortedTurnId) so the replacement turn renders fresh.
538
+ z.object({
539
+ type: z.literal("turn-interrupted"),
540
+ abortedTurnId: z.string(),
541
+ sessionKey: z.string().optional(),
542
+ }),
543
+ ]);
544
+ // ---------------------------------------------------------------------------
545
+ // /api/sessions/:key/turn (POST — fire-and-forget submit, #131)
546
+ // ---------------------------------------------------------------------------
547
+ export const SubmitTurnResponseSchema = z.object({
548
+ turnId: z.string(),
549
+ });
550
+ // ---------------------------------------------------------------------------
551
+ // /api/memory/active-scope (GET + POST)
552
+ // ---------------------------------------------------------------------------
553
+ export const ActiveMemoryScopeSchema = z.object({
554
+ slug: z.string(),
555
+ title: z.string(),
556
+ }).nullable();
557
+ export const SetActiveScopeResponseSchema = z.object({
558
+ ok: z.literal(true),
559
+ scope: z.string().nullable(),
560
+ });
561
+ // ---------------------------------------------------------------------------
562
+ // /api/memory/scopes
563
+ // ---------------------------------------------------------------------------
564
+ const MemoryScopeCountsSchema = z.object({
565
+ observations: z.number(),
566
+ decisions: z.number(),
567
+ entities: z.number(),
568
+ action_items: z.number(),
569
+ });
570
+ const MemoryScopeItemSchema = z.object({
571
+ slug: z.string(),
572
+ title: z.string(),
573
+ description: z.string(),
574
+ active: z.boolean(),
575
+ counts: MemoryScopeCountsSchema,
576
+ });
577
+ export const MemoryScopeListSchema = z.object({
578
+ scopes: z.array(MemoryScopeItemSchema),
579
+ });
580
+ // ---------------------------------------------------------------------------
581
+ // /api/memory/:scope (entries)
582
+ // ---------------------------------------------------------------------------
583
+ export const MemoryEntriesSchema = z.object({
584
+ entries: z.array(z.record(z.string(), z.unknown())),
585
+ total: z.number(),
586
+ });
587
+ // ---------------------------------------------------------------------------
588
+ // /api/memory/:scope/remember
589
+ // ---------------------------------------------------------------------------
590
+ export const MemoryRememberResponseSchema = z.object({
591
+ ok: z.literal(true),
592
+ id: z.string(),
593
+ });
594
+ // ---------------------------------------------------------------------------
595
+ // /api/memory/inbox
596
+ // ---------------------------------------------------------------------------
597
+ const InboxItemSchema = z.object({
598
+ id: z.number(),
599
+ scope_slug: z.string().nullable(),
600
+ kind: z.string(),
601
+ payload: z.string(),
602
+ source_agent: z.string(),
603
+ created_at: z.string(),
604
+ });
605
+ export const MemoryInboxSchema = z.object({
606
+ items: z.array(InboxItemSchema),
607
+ total: z.number(),
608
+ });
609
+ // ---------------------------------------------------------------------------
610
+ // /api/memory/inbox/:id/route
611
+ // ---------------------------------------------------------------------------
612
+ export const InboxRouteResponseSchema = z.object({
613
+ ok: z.literal(true),
614
+ });
615
+ //# sourceMappingURL=api-schemas.js.map
package/dist/store/db.js CHANGED
@@ -551,6 +551,7 @@ export function getDb() {
551
551
  ensureChapterhouseHome();
552
552
  db = new Database(getDbPath());
553
553
  db.pragma("journal_mode = WAL");
554
+ db.pragma("busy_timeout = 5000");
554
555
  db.pragma("foreign_keys = ON");
555
556
  db.exec(`
556
557
  CREATE TABLE IF NOT EXISTS worker_sessions (
@@ -77,7 +77,7 @@ export function upsertWikiPage(path, frontmatter, summary) {
77
77
  const db = getDb();
78
78
  const normalizedPath = normalizeWikiPath(path);
79
79
  const title = frontmatter.title ?? basenameTitle(normalizedPath);
80
- const entityType = frontmatter.metadata?.["entity_type"] ?? null;
80
+ const entityType = frontmatter.metadata?.["entity_type"] ?? categoryOfPath(normalizedPath);
81
81
  const tags = JSON.stringify(frontmatter.tags ?? []);
82
82
  const lastUpdated = frontmatter.updated ?? new Date().toISOString();
83
83
  db.prepare(`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chapterhouse",
3
- "version": "0.8.2",
3
+ "version": "0.9.1",
4
4
  "description": "Chapterhouse — a team-level AI assistant for engineering teams, built on the GitHub Copilot SDK. Web UI only.",
5
5
  "bin": {
6
6
  "chapterhouse": "dist/cli.js"