dsh-diagnostic-tutor 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +316 -79
  2. package/lib/api.js +364 -78
  3. package/lib/api.js.map +1 -1
  4. package/lib/block-types.js +25 -0
  5. package/lib/block-types.js.map +1 -0
  6. package/lib/client.js +9952 -821
  7. package/lib/client.js.map +1 -1
  8. package/lib/diagnostics.js +170 -0
  9. package/lib/diagnostics.js.map +1 -0
  10. package/lib/execution-key.js +91 -0
  11. package/lib/execution-key.js.map +1 -0
  12. package/lib/handoff.js +44 -1
  13. package/lib/handoff.js.map +1 -1
  14. package/lib/index.js +87 -51
  15. package/lib/index.js.map +1 -1
  16. package/lib/insights.js +82 -0
  17. package/lib/insights.js.map +1 -0
  18. package/lib/interactions.js +70 -0
  19. package/lib/interactions.js.map +1 -0
  20. package/lib/journal.js +122 -0
  21. package/lib/journal.js.map +1 -0
  22. package/lib/lesson-markdown.js +72 -0
  23. package/lib/lesson-markdown.js.map +1 -0
  24. package/lib/lesson.js +152 -3
  25. package/lib/lesson.js.map +1 -1
  26. package/lib/math.js +126 -0
  27. package/lib/math.js.map +1 -0
  28. package/lib/path-api.js +140 -0
  29. package/lib/path-api.js.map +1 -0
  30. package/lib/path-runtime.js +361 -0
  31. package/lib/path-runtime.js.map +1 -0
  32. package/lib/path-tools.js +107 -0
  33. package/lib/path-tools.js.map +1 -0
  34. package/lib/path.js +179 -0
  35. package/lib/path.js.map +1 -0
  36. package/lib/plan-tools.js +89 -0
  37. package/lib/plan-tools.js.map +1 -0
  38. package/lib/plan.js +82 -0
  39. package/lib/plan.js.map +1 -0
  40. package/lib/project-session.js +48 -0
  41. package/lib/project-session.js.map +1 -0
  42. package/lib/prompt.js +4 -19
  43. package/lib/prompt.js.map +1 -1
  44. package/lib/review-sync.js +51 -0
  45. package/lib/review-sync.js.map +1 -0
  46. package/lib/review-tools.js +115 -0
  47. package/lib/review-tools.js.map +1 -0
  48. package/lib/review.js +103 -0
  49. package/lib/review.js.map +1 -0
  50. package/lib/source-schema.js +24 -0
  51. package/lib/source-schema.js.map +1 -0
  52. package/lib/source-tools.js +61 -0
  53. package/lib/source-tools.js.map +1 -0
  54. package/lib/sources.js +78 -0
  55. package/lib/sources.js.map +1 -0
  56. package/lib/state.js +353 -47
  57. package/lib/state.js.map +1 -1
  58. package/lib/tools.js +620 -123
  59. package/lib/tools.js.map +1 -1
  60. package/lib/types/api.d.ts +10 -3
  61. package/lib/types/api.d.ts.map +1 -1
  62. package/lib/types/block-types.d.ts +12 -0
  63. package/lib/types/block-types.d.ts.map +1 -0
  64. package/lib/types/contract.d.ts +81 -7
  65. package/lib/types/contract.d.ts.map +1 -1
  66. package/lib/types/diagnostics.d.ts +95 -0
  67. package/lib/types/diagnostics.d.ts.map +1 -0
  68. package/lib/types/execution-key.d.ts +59 -0
  69. package/lib/types/execution-key.d.ts.map +1 -0
  70. package/lib/types/handoff.d.ts +2 -2
  71. package/lib/types/handoff.d.ts.map +1 -1
  72. package/lib/types/index.d.ts +19 -14
  73. package/lib/types/index.d.ts.map +1 -1
  74. package/lib/types/insights.d.ts +57 -0
  75. package/lib/types/insights.d.ts.map +1 -0
  76. package/lib/types/interactions.d.ts +23 -0
  77. package/lib/types/interactions.d.ts.map +1 -0
  78. package/lib/types/journal.d.ts +57 -0
  79. package/lib/types/journal.d.ts.map +1 -0
  80. package/lib/types/lesson-markdown.d.ts +11 -0
  81. package/lib/types/lesson-markdown.d.ts.map +1 -0
  82. package/lib/types/lesson.d.ts +697 -5
  83. package/lib/types/lesson.d.ts.map +1 -1
  84. package/lib/types/math.d.ts +50 -0
  85. package/lib/types/math.d.ts.map +1 -0
  86. package/lib/types/path-api.d.ts +5 -0
  87. package/lib/types/path-api.d.ts.map +1 -0
  88. package/lib/types/path-runtime.d.ts +1558 -0
  89. package/lib/types/path-runtime.d.ts.map +1 -0
  90. package/lib/types/path-tools.d.ts +4 -0
  91. package/lib/types/path-tools.d.ts.map +1 -0
  92. package/lib/types/path.d.ts +744 -0
  93. package/lib/types/path.d.ts.map +1 -0
  94. package/lib/types/plan-tools.d.ts +4 -0
  95. package/lib/types/plan-tools.d.ts.map +1 -0
  96. package/lib/types/plan.d.ts +53 -0
  97. package/lib/types/plan.d.ts.map +1 -0
  98. package/lib/types/project-session.d.ts +33 -0
  99. package/lib/types/project-session.d.ts.map +1 -0
  100. package/lib/types/prompt.d.ts.map +1 -1
  101. package/lib/types/review-sync.d.ts +17 -0
  102. package/lib/types/review-sync.d.ts.map +1 -0
  103. package/lib/types/review-tools.d.ts +4 -0
  104. package/lib/types/review-tools.d.ts.map +1 -0
  105. package/lib/types/review.d.ts +65 -0
  106. package/lib/types/review.d.ts.map +1 -0
  107. package/lib/types/source-schema.d.ts +31 -0
  108. package/lib/types/source-schema.d.ts.map +1 -0
  109. package/lib/types/source-tools.d.ts +4 -0
  110. package/lib/types/source-tools.d.ts.map +1 -0
  111. package/lib/types/sources.d.ts +33 -0
  112. package/lib/types/sources.d.ts.map +1 -0
  113. package/lib/types/state.d.ts +607 -23
  114. package/lib/types/state.d.ts.map +1 -1
  115. package/lib/types/tools.d.ts +3 -1
  116. package/lib/types/tools.d.ts.map +1 -1
  117. package/lib/types/udt.d.ts +20 -24
  118. package/lib/types/udt.d.ts.map +1 -1
  119. package/lib/types/version.d.ts +11 -0
  120. package/lib/types/version.d.ts.map +1 -0
  121. package/lib/types/vocabulary.d.ts +2 -2
  122. package/lib/udt.js +72 -19
  123. package/lib/udt.js.map +1 -1
  124. package/lib/version.js +26 -0
  125. package/lib/version.js.map +1 -0
  126. package/package.json +27 -22
@@ -37,7 +37,11 @@ import type { DomainFacility } from '@deepseek-ai/dsh-storage-domain';
37
37
  import { z } from 'zod';
38
38
  import type { NodeRelation, NodeState, Readiness } from './vocabulary.js';
39
39
  import type { LessonKey, LessonRecord } from './lesson.js';
40
+ import type { SourceRecord } from './source-schema.js';
41
+ import type { TaskRecord } from './plan.js';
42
+ import type { ReviewCard, ReviewLog } from './review.js';
40
43
  import type { HandoffKey, HandoffRecord } from './handoff.js';
44
+ import type { CourseRun, LearningPath } from './path.js';
41
45
  /**
42
46
  * The learner singleton. Stored in the domain's **global** slot rather than a
43
47
  * one-row table, because the global slot exists precisely for a single
@@ -65,9 +69,9 @@ export type LearnerProfile = z.infer<typeof LearnerProfileSchema>;
65
69
  /** Lifecycle of a learning goal. Not a mastery status. */
66
70
  export declare const COURSE_STATUSES: readonly ["active", "paused", "archived"];
67
71
  export declare const CourseStatusSchema: z.ZodEnum<{
72
+ archived: "archived";
68
73
  active: "active";
69
74
  paused: "paused";
70
- archived: "archived";
71
75
  }>;
72
76
  export type CourseStatus = z.infer<typeof CourseStatusSchema>;
73
77
  /**
@@ -81,9 +85,9 @@ export declare const CourseSchema: z.ZodObject<{
81
85
  title: z.ZodString;
82
86
  goal: z.ZodString;
83
87
  status: z.ZodEnum<{
88
+ archived: "archived";
84
89
  active: "active";
85
90
  paused: "paused";
86
- archived: "archived";
87
91
  }>;
88
92
  createdAt: z.ZodString;
89
93
  updatedAt: z.ZodString;
@@ -99,11 +103,11 @@ export type CourseRecord = z.infer<typeof CourseSchema>;
99
103
  */
100
104
  export declare const EvidenceSchema: z.ZodObject<{
101
105
  kind: z.ZodEnum<{
106
+ check: "check";
107
+ explanation: "explanation";
102
108
  "goal-stated": "goal-stated";
103
109
  diagnosis: "diagnosis";
104
- explanation: "explanation";
105
110
  practice: "practice";
106
- check: "check";
107
111
  transfer: "transfer";
108
112
  }>;
109
113
  at: z.ZodString;
@@ -116,6 +120,8 @@ export declare const EvidenceSchema: z.ZodObject<{
116
120
  "diagnose-again": "diagnose-again";
117
121
  "more-practice": "more-practice";
118
122
  }>>;
123
+ sessionId: z.ZodOptional<z.ZodString>;
124
+ attemptId: z.ZodOptional<z.ZodString>;
119
125
  }, z.core.$strip>;
120
126
  export type Evidence = z.infer<typeof EvidenceSchema>;
121
127
  /**
@@ -148,11 +154,11 @@ export declare const NodeSchema: z.ZodObject<{
148
154
  }>;
149
155
  evidence: z.ZodArray<z.ZodObject<{
150
156
  kind: z.ZodEnum<{
157
+ check: "check";
158
+ explanation: "explanation";
151
159
  "goal-stated": "goal-stated";
152
160
  diagnosis: "diagnosis";
153
- explanation: "explanation";
154
161
  practice: "practice";
155
- check: "check";
156
162
  transfer: "transfer";
157
163
  }>;
158
164
  at: z.ZodString;
@@ -165,6 +171,8 @@ export declare const NodeSchema: z.ZodObject<{
165
171
  "diagnose-again": "diagnose-again";
166
172
  "more-practice": "more-practice";
167
173
  }>>;
174
+ sessionId: z.ZodOptional<z.ZodString>;
175
+ attemptId: z.ZodOptional<z.ZodString>;
168
176
  }, z.core.$strip>>;
169
177
  createdAt: z.ZodString;
170
178
  updatedAt: z.ZodString;
@@ -196,10 +204,43 @@ export declare const FocusSchema: z.ZodObject<{
196
204
  }>;
197
205
  updatedAt: z.ZodString;
198
206
  endedAt: z.ZodOptional<z.ZodString>;
207
+ sessionId: z.ZodOptional<z.ZodString>;
199
208
  nextStepId: z.ZodOptional<z.ZodString>;
200
209
  }, z.core.$strip>;
201
210
  export type FocusRecord = z.infer<typeof FocusSchema>;
202
211
  export type FocusKey = string;
212
+ /**
213
+ * The Learning Project one conversation has selected.
214
+ *
215
+ * `courseId` is the project: a course record IS a learning project, and the UI
216
+ * calls it that. There is deliberately no separate project entity — a second
217
+ * one would have to be kept in step with the first, and the first already
218
+ * carries the map, the evidence and the lessons.
219
+ */
220
+ export declare const ProjectSchema: z.ZodObject<{
221
+ sessionId: z.ZodString;
222
+ courseId: z.ZodOptional<z.ZodString>;
223
+ selectedAt: z.ZodString;
224
+ }, z.core.$strip>;
225
+ export type ProjectRecord = z.infer<typeof ProjectSchema>;
226
+ export type ProjectKey = string;
227
+ /** The conversation-local project state shared by the API and model tools. */
228
+ export declare const PROJECT_SELECTIONS: readonly ["none", "new", "project"];
229
+ export type ProjectSelection = (typeof PROJECT_SELECTIONS)[number];
230
+ export interface SessionProjectResolution {
231
+ selection: ProjectSelection;
232
+ sessionAvailable: boolean;
233
+ course?: CourseRecord;
234
+ reason?: 'missing-session-id' | 'ambiguous-session-project' | 'stale-project';
235
+ /** In-process choice revision, used to reject a late adoption after a UI choice. */
236
+ revision: number;
237
+ }
238
+ export declare const PROJECT_BINDINGS: readonly ["adopted", "already-selected", "not-selected", "missing-session-id"];
239
+ export type ProjectBinding = (typeof PROJECT_BINDINGS)[number];
240
+ export interface ProjectBindingResult {
241
+ projectAdopted: boolean;
242
+ projectBinding: ProjectBinding;
243
+ }
203
244
  /**
204
245
  * The tutor's decision about where the learner should go after this node.
205
246
  *
@@ -261,9 +302,23 @@ export interface UdExport {
261
302
  readonly lessons: readonly LessonRecord[];
262
303
  readonly nextSteps: readonly NextStepRecord[];
263
304
  readonly focus: readonly FocusRecord[];
305
+ /** Which Learning Project each conversation had open. */
306
+ readonly projects: readonly ProjectRecord[];
307
+ /** Planning and actual course attempts are learner-owned history too. */
308
+ readonly learningPaths: readonly LearningPath[];
309
+ readonly courseRuns: readonly CourseRun[];
310
+ /** Learner-supplied source material, with citations pointing into it. */
311
+ readonly sources: readonly SourceRecord[];
312
+ readonly tasks: readonly TaskRecord[];
313
+ readonly reviewCards: readonly ReviewCard[];
314
+ readonly reviewLog: readonly ReviewLog[];
264
315
  }
265
316
  export type CourseKey = string;
266
317
  export type NodeKey = string;
318
+ export type SourceKey = string;
319
+ export type TaskKey = string;
320
+ export type ReviewCardKey = string;
321
+ export type ReviewLogKey = string;
267
322
  /** Domain name. Must match the storage hub's `UNIT_NAME_RE` (`^[a-z][a-z0-9_]*$`). */
268
323
  export declare const UDT_DOMAIN_NAME = "udt";
269
324
  /**
@@ -291,6 +346,21 @@ export declare const LESSONS_TABLE = "lessons";
291
346
  export declare const FOCUS_TABLE = "focus";
292
347
  export declare const NEXT_STEPS_TABLE = "next_steps";
293
348
  export declare const HANDOFFS_TABLE = "handoffs";
349
+ /**
350
+ * Which Learning Project each conversation is looking at.
351
+ *
352
+ * The projects themselves are learner-level: the course, its map, its evidence
353
+ * and its lessons are shared by every conversation, and this table adds nothing
354
+ * to them. What it records is a *choice* — which of those projects this
355
+ * conversation has open — and a choice belongs to the conversation that made
356
+ * it. Without this the runtime had to guess, and the guess was "the most
357
+ * recently updated course", which is how a brand-new chat opened onto the
358
+ * learner's old machine-learning project.
359
+ *
360
+ * Keyed by session id. A conversation with no row here has selected nothing,
361
+ * and that is a real state rather than a missing one.
362
+ */
363
+ export declare const PROJECTS_TABLE = "session_projects";
294
364
  /** Sentinel meaning "the global slot has never been written". */
295
365
  export declare const UNINITIALIZED = "";
296
366
  export declare const udtDomain: {
@@ -319,7 +389,7 @@ export declare const udtDomain: {
319
389
  id: string;
320
390
  title: string;
321
391
  goal: string;
322
- status: "active" | "paused" | "archived";
392
+ status: "archived" | "active" | "paused";
323
393
  createdAt: string;
324
394
  updatedAt: string;
325
395
  }>;
@@ -330,10 +400,12 @@ export declare const udtDomain: {
330
400
  relation: "goal" | "part-of" | "prerequisite" | "related";
331
401
  state: "unconfirmed" | "explained" | "practiced" | "checked" | "weak" | "blocked" | "confirmed";
332
402
  evidence: {
333
- kind: "goal-stated" | "diagnosis" | "explanation" | "practice" | "check" | "transfer";
403
+ kind: "check" | "explanation" | "goal-stated" | "diagnosis" | "practice" | "transfer";
334
404
  at: string;
335
405
  note?: string | undefined;
336
406
  readiness?: "advance" | "advance-with-caution" | "review-first" | "step-down" | "diagnose-again" | "more-practice" | undefined;
407
+ sessionId?: string | undefined;
408
+ attemptId?: string | undefined;
337
409
  }[];
338
410
  createdAt: string;
339
411
  updatedAt: string;
@@ -348,9 +420,18 @@ export declare const udtDomain: {
348
420
  type: "text";
349
421
  content: {
350
422
  md: string;
423
+ math?: {
424
+ tex: string;
425
+ display: boolean;
426
+ mathml: string;
427
+ }[] | undefined;
351
428
  };
352
429
  id: string;
353
430
  metadata?: Record<string, unknown> | undefined;
431
+ sourceRefs?: {
432
+ sourceId: string;
433
+ locator: string;
434
+ }[] | undefined;
354
435
  } | {
355
436
  type: "example";
356
437
  content: {
@@ -360,6 +441,10 @@ export declare const udtDomain: {
360
441
  };
361
442
  id: string;
362
443
  metadata?: Record<string, unknown> | undefined;
444
+ sourceRefs?: {
445
+ sourceId: string;
446
+ locator: string;
447
+ }[] | undefined;
363
448
  } | {
364
449
  type: "diagram";
365
450
  content: {
@@ -369,6 +454,10 @@ export declare const udtDomain: {
369
454
  };
370
455
  id: string;
371
456
  metadata?: Record<string, unknown> | undefined;
457
+ sourceRefs?: {
458
+ sourceId: string;
459
+ locator: string;
460
+ }[] | undefined;
372
461
  } | {
373
462
  type: "check";
374
463
  content: {
@@ -378,6 +467,107 @@ export declare const udtDomain: {
378
467
  };
379
468
  id: string;
380
469
  metadata?: Record<string, unknown> | undefined;
470
+ sourceRefs?: {
471
+ sourceId: string;
472
+ locator: string;
473
+ }[] | undefined;
474
+ } | {
475
+ type: "formula";
476
+ content: {
477
+ tex: string;
478
+ display?: boolean | undefined;
479
+ mathml?: string | undefined;
480
+ caption?: string | undefined;
481
+ };
482
+ id: string;
483
+ metadata?: Record<string, unknown> | undefined;
484
+ sourceRefs?: {
485
+ sourceId: string;
486
+ locator: string;
487
+ }[] | undefined;
488
+ } | {
489
+ type: "code";
490
+ content: {
491
+ source: string;
492
+ language?: string | undefined;
493
+ caption?: string | undefined;
494
+ };
495
+ id: string;
496
+ metadata?: Record<string, unknown> | undefined;
497
+ sourceRefs?: {
498
+ sourceId: string;
499
+ locator: string;
500
+ }[] | undefined;
501
+ } | {
502
+ type: "quiz";
503
+ content: {
504
+ questions: {
505
+ id: string;
506
+ kind: "mcq" | "short";
507
+ prompt: string;
508
+ options?: string[] | undefined;
509
+ answer?: string | undefined;
510
+ rubric?: string | undefined;
511
+ explanation?: string | undefined;
512
+ }[];
513
+ title?: string | undefined;
514
+ };
515
+ id: string;
516
+ metadata?: Record<string, unknown> | undefined;
517
+ sourceRefs?: {
518
+ sourceId: string;
519
+ locator: string;
520
+ }[] | undefined;
521
+ } | {
522
+ type: "flashcard";
523
+ content: {
524
+ front: string;
525
+ back: string;
526
+ tags?: string[] | undefined;
527
+ };
528
+ id: string;
529
+ metadata?: Record<string, unknown> | undefined;
530
+ sourceRefs?: {
531
+ sourceId: string;
532
+ locator: string;
533
+ }[] | undefined;
534
+ } | {
535
+ type: "resource";
536
+ content: {
537
+ title: string;
538
+ sourceId?: string | undefined;
539
+ locator?: string | undefined;
540
+ url?: string | undefined;
541
+ note?: string | undefined;
542
+ };
543
+ id: string;
544
+ metadata?: Record<string, unknown> | undefined;
545
+ sourceRefs?: {
546
+ sourceId: string;
547
+ locator: string;
548
+ }[] | undefined;
549
+ } | {
550
+ type: "comparison";
551
+ content: {
552
+ columns: string[];
553
+ rows: string[][];
554
+ caption?: string | undefined;
555
+ };
556
+ id: string;
557
+ metadata?: Record<string, unknown> | undefined;
558
+ sourceRefs?: {
559
+ sourceId: string;
560
+ locator: string;
561
+ }[] | undefined;
562
+ } | {
563
+ id: string;
564
+ type: string;
565
+ content: unknown;
566
+ metadata?: Record<string, unknown> | undefined;
567
+ sourceRefs?: {
568
+ sourceId: string;
569
+ locator: string;
570
+ }[] | undefined;
381
571
  })[];
382
572
  origin: "prototype" | "tutor";
383
573
  createdAt: string;
@@ -390,6 +580,7 @@ export declare const udtDomain: {
390
580
  status: "active" | "ended";
391
581
  updatedAt: string;
392
582
  endedAt?: string | undefined;
583
+ sessionId?: string | undefined;
393
584
  nextStepId?: string | undefined;
394
585
  }>;
395
586
  next_steps: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
@@ -404,7 +595,7 @@ export declare const udtDomain: {
404
595
  targetNodeId: string;
405
596
  courseId: string;
406
597
  fromNodeId: string;
407
- status: "requested" | "prompted" | "working" | "ready" | "failed";
598
+ status: "ready" | "requested" | "prompted" | "working" | "failed";
408
599
  attempts: number;
409
600
  requestedAt: string;
410
601
  updatedAt: string;
@@ -417,6 +608,353 @@ export declare const udtDomain: {
417
608
  failedAt?: string | undefined;
418
609
  failureReason?: string | undefined;
419
610
  }>;
611
+ session_projects: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
612
+ sessionId: string;
613
+ selectedAt: string;
614
+ courseId?: string | undefined;
615
+ }>;
616
+ learning_paths: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
617
+ courseId: string;
618
+ revision: number;
619
+ briefRevision: number;
620
+ assessmentRevision: number;
621
+ brief: {
622
+ purpose: string;
623
+ outcomes: string[];
624
+ background: string;
625
+ timeZone: string;
626
+ targetDate: string | null;
627
+ durationWeeks: number | null;
628
+ deadline: "none" | "flexible" | "fixed";
629
+ weeklyMinutes: number | null;
630
+ sessionMinutes: number | null;
631
+ preferences: string;
632
+ } | null;
633
+ assessment: {
634
+ status: "ready" | "in-progress" | "skipped";
635
+ summary: string;
636
+ strengths: string[];
637
+ gaps: string[];
638
+ uncertainties: string[];
639
+ evidence: {
640
+ nodeId: string;
641
+ evidenceIndex: number;
642
+ }[];
643
+ briefRevision: number;
644
+ sessionId: string;
645
+ updatedAt: string;
646
+ } | null;
647
+ curricula: {
648
+ version: number;
649
+ briefRevision: number;
650
+ assessmentRevision: number;
651
+ createdAt: string;
652
+ acceptedAt: string | null;
653
+ title: string;
654
+ summary: string;
655
+ modules: {
656
+ id: string;
657
+ title: string;
658
+ overview: string;
659
+ outcomes: string[];
660
+ milestone: string;
661
+ }[];
662
+ items: {
663
+ id: string;
664
+ moduleId: string;
665
+ title: string;
666
+ overview: string;
667
+ outcomes: string[];
668
+ outline: string[];
669
+ topics: string[];
670
+ nodeIds: string[];
671
+ practice: string;
672
+ assessment: string;
673
+ minutes: {
674
+ min: number;
675
+ max: number;
676
+ };
677
+ }[];
678
+ edges: {
679
+ from: string;
680
+ to: string;
681
+ kind: "optional" | "required";
682
+ }[];
683
+ assumptions: string[];
684
+ changeReason: string;
685
+ }[];
686
+ acceptedVersion: number | null;
687
+ requests: {
688
+ id: string;
689
+ sessionId: string;
690
+ kind: "diagnosis" | "plan";
691
+ briefRevision: number;
692
+ createdAt: string;
693
+ prompted: boolean;
694
+ reason: string | null;
695
+ failure?: string | null | undefined;
696
+ }[];
697
+ updatedAt: string;
698
+ }>;
699
+ course_runs: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
700
+ courseId: string;
701
+ sessionId: string;
702
+ revision: number;
703
+ activeAttemptId: string | null;
704
+ attempts: {
705
+ id: string;
706
+ operationId: string;
707
+ operations: string[];
708
+ dispatchId: string | null;
709
+ version: number;
710
+ itemId: string;
711
+ status: "requested" | "working" | "completed" | "interrupted";
712
+ nodeId: string | null;
713
+ startedAt: string;
714
+ updatedAt: string;
715
+ prompted: boolean;
716
+ promptReason: string | null;
717
+ outcome: string | null;
718
+ evidence: {
719
+ nodeId: string;
720
+ evidenceIndex: number;
721
+ }[];
722
+ units: {
723
+ id: string;
724
+ writtenAt: string;
725
+ lesson: {
726
+ id: string;
727
+ courseId: string;
728
+ nodeId: string;
729
+ title: string;
730
+ blocks: ({
731
+ type: "text";
732
+ content: {
733
+ md: string;
734
+ math?: {
735
+ tex: string;
736
+ display: boolean;
737
+ mathml: string;
738
+ }[] | undefined;
739
+ };
740
+ id: string;
741
+ metadata?: Record<string, unknown> | undefined;
742
+ sourceRefs?: {
743
+ sourceId: string;
744
+ locator: string;
745
+ }[] | undefined;
746
+ } | {
747
+ type: "example";
748
+ content: {
749
+ title: string;
750
+ steps: string[];
751
+ takeaway?: string | undefined;
752
+ };
753
+ id: string;
754
+ metadata?: Record<string, unknown> | undefined;
755
+ sourceRefs?: {
756
+ sourceId: string;
757
+ locator: string;
758
+ }[] | undefined;
759
+ } | {
760
+ type: "diagram";
761
+ content: {
762
+ format: "ascii" | "mermaid";
763
+ spec: string;
764
+ caption?: string | undefined;
765
+ };
766
+ id: string;
767
+ metadata?: Record<string, unknown> | undefined;
768
+ sourceRefs?: {
769
+ sourceId: string;
770
+ locator: string;
771
+ }[] | undefined;
772
+ } | {
773
+ type: "check";
774
+ content: {
775
+ prompt: string;
776
+ expect?: "reasoning" | "answer" | undefined;
777
+ hint?: string | undefined;
778
+ };
779
+ id: string;
780
+ metadata?: Record<string, unknown> | undefined;
781
+ sourceRefs?: {
782
+ sourceId: string;
783
+ locator: string;
784
+ }[] | undefined;
785
+ } | {
786
+ type: "formula";
787
+ content: {
788
+ tex: string;
789
+ display?: boolean | undefined;
790
+ mathml?: string | undefined;
791
+ caption?: string | undefined;
792
+ };
793
+ id: string;
794
+ metadata?: Record<string, unknown> | undefined;
795
+ sourceRefs?: {
796
+ sourceId: string;
797
+ locator: string;
798
+ }[] | undefined;
799
+ } | {
800
+ type: "code";
801
+ content: {
802
+ source: string;
803
+ language?: string | undefined;
804
+ caption?: string | undefined;
805
+ };
806
+ id: string;
807
+ metadata?: Record<string, unknown> | undefined;
808
+ sourceRefs?: {
809
+ sourceId: string;
810
+ locator: string;
811
+ }[] | undefined;
812
+ } | {
813
+ type: "quiz";
814
+ content: {
815
+ questions: {
816
+ id: string;
817
+ kind: "mcq" | "short";
818
+ prompt: string;
819
+ options?: string[] | undefined;
820
+ answer?: string | undefined;
821
+ rubric?: string | undefined;
822
+ explanation?: string | undefined;
823
+ }[];
824
+ title?: string | undefined;
825
+ };
826
+ id: string;
827
+ metadata?: Record<string, unknown> | undefined;
828
+ sourceRefs?: {
829
+ sourceId: string;
830
+ locator: string;
831
+ }[] | undefined;
832
+ } | {
833
+ type: "flashcard";
834
+ content: {
835
+ front: string;
836
+ back: string;
837
+ tags?: string[] | undefined;
838
+ };
839
+ id: string;
840
+ metadata?: Record<string, unknown> | undefined;
841
+ sourceRefs?: {
842
+ sourceId: string;
843
+ locator: string;
844
+ }[] | undefined;
845
+ } | {
846
+ type: "resource";
847
+ content: {
848
+ title: string;
849
+ sourceId?: string | undefined;
850
+ locator?: string | undefined;
851
+ url?: string | undefined;
852
+ note?: string | undefined;
853
+ };
854
+ id: string;
855
+ metadata?: Record<string, unknown> | undefined;
856
+ sourceRefs?: {
857
+ sourceId: string;
858
+ locator: string;
859
+ }[] | undefined;
860
+ } | {
861
+ type: "comparison";
862
+ content: {
863
+ columns: string[];
864
+ rows: string[][];
865
+ caption?: string | undefined;
866
+ };
867
+ id: string;
868
+ metadata?: Record<string, unknown> | undefined;
869
+ sourceRefs?: {
870
+ sourceId: string;
871
+ locator: string;
872
+ }[] | undefined;
873
+ } | {
874
+ id: string;
875
+ type: string;
876
+ content: unknown;
877
+ metadata?: Record<string, unknown> | undefined;
878
+ sourceRefs?: {
879
+ sourceId: string;
880
+ locator: string;
881
+ }[] | undefined;
882
+ })[];
883
+ origin: "prototype" | "tutor";
884
+ createdAt: string;
885
+ updatedAt: string;
886
+ };
887
+ }[];
888
+ responses?: {
889
+ id: string;
890
+ blockId: string;
891
+ kind: "quiz" | "flashcard";
892
+ answers: {
893
+ questionId: string;
894
+ value: string;
895
+ }[];
896
+ submittedAt: string;
897
+ }[] | undefined;
898
+ }[];
899
+ updatedAt: string;
900
+ }>;
901
+ sources: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
902
+ id: string;
903
+ kind: "text" | "file" | "markdown";
904
+ title: string;
905
+ locator: string;
906
+ sha256: string;
907
+ bytes: number;
908
+ createdAt: string;
909
+ status: "error" | "ready";
910
+ content?: string | undefined;
911
+ }>;
912
+ tasks: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
913
+ id: string;
914
+ title: string;
915
+ status: "open" | "done" | "archived";
916
+ createdAt: string;
917
+ updatedAt: string;
918
+ courseId?: string | undefined;
919
+ nodeId?: string | undefined;
920
+ dueAt?: string | undefined;
921
+ estimateMin?: number | undefined;
922
+ sourceId?: string | undefined;
923
+ }>;
924
+ review_cards: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
925
+ id: string;
926
+ front: string;
927
+ back: string;
928
+ dueAt: string;
929
+ intervalDays: number;
930
+ ease: number;
931
+ reps: number;
932
+ lapses: number;
933
+ status: "open" | "suspended";
934
+ createdAt: string;
935
+ updatedAt: string;
936
+ courseId?: string | undefined;
937
+ nodeId?: string | undefined;
938
+ blockId?: string | undefined;
939
+ sourceRefs?: {
940
+ sourceId: string;
941
+ locator: string;
942
+ }[] | undefined;
943
+ }>;
944
+ review_log: import("@deepseek-ai/dsh-storage-domain").DomainTableSpec<string, {
945
+ id: string;
946
+ cardId: string;
947
+ rating: "again" | "hard" | "good" | "easy";
948
+ at: string;
949
+ previousDue: string;
950
+ nextDue: string;
951
+ intervalDays: number;
952
+ ease: number;
953
+ previousIntervalDays?: number | undefined;
954
+ previousEase?: number | undefined;
955
+ previousReps?: number | undefined;
956
+ previousLapses?: number | undefined;
957
+ }>;
420
958
  };
421
959
  };
422
960
  export type UdtDomain = typeof udtDomain;
@@ -487,12 +1025,36 @@ export interface UdState {
487
1025
  readNode(id: NodeKey): NodeRecord | undefined;
488
1026
  /** Insert or fully replace one node. */
489
1027
  writeNode(record: NodeRecord): Promise<NodeRecord>;
1028
+ /** Read, validate, and commit a node change on the project/planning queue. The transform must not enqueue writes. */
1029
+ mutateNode(courseId: CourseKey, nodeId: NodeKey, change: (current: NodeRecord) => NodeRecord): Promise<NodeRecord>;
490
1030
  /** Number of nodes across every course. */
491
1031
  nodeCount(): number;
492
1032
  /** The course plus its map. */
493
1033
  readMap(courseId: CourseKey): CourseMap | undefined;
494
1034
  /** The recorded focus for one course, if any. */
495
- readFocus(courseId: CourseKey): FocusRecord | undefined;
1035
+ readFocus(sessionId: string, courseId: CourseKey): FocusRecord | undefined;
1036
+ /**
1037
+ * The Learning Project this conversation has selected.
1038
+ *
1039
+ * @returns the record, or undefined when it has chosen nothing — which is
1040
+ * the state every new conversation starts in.
1041
+ */
1042
+ readProject(sessionId: string): ProjectRecord | undefined;
1043
+ /** Resolve only this conversation's project; never use learner.activeCourseId. */
1044
+ resolveProject(sessionId?: string): SessionProjectResolution;
1045
+ /**
1046
+ * Record this conversation's choice.
1047
+ *
1048
+ * @param sessionId - the conversation.
1049
+ * @param courseId - the project it picked, or undefined for "start
1050
+ * something new" — a deliberate state, not an absence of one.
1051
+ * @param now - ISO timestamp.
1052
+ */
1053
+ selectProject(sessionId: string, courseId: string | undefined, now: string): Promise<ProjectRecord>;
1054
+ /** Adopt after a successful write only if the conversation's choice has not changed. */
1055
+ adoptProject(sessionId: string | undefined, courseId: string, now: string, expected: SessionProjectResolution, allowNew?: boolean): Promise<ProjectBindingResult>;
1056
+ /** Every conversation's choice, for export. */
1057
+ listProjects(): ProjectRecord[];
496
1058
  /**
497
1059
  * Record that learning is starting on one node of one course.
498
1060
  *
@@ -505,9 +1067,9 @@ export interface UdState {
505
1067
  * @param now - ISO timestamp.
506
1068
  * @returns the stored focus.
507
1069
  */
508
- startFocus(courseId: CourseKey, nodeId: NodeKey, now: string): Promise<FocusRecord>;
509
- /** Mark the course's focus as ended. No-op when there is none. */
510
- endFocus(courseId: CourseKey, now: string): Promise<void>;
1070
+ startFocus(courseId: CourseKey, nodeId: NodeKey, now: string, sessionId?: string): Promise<FocusRecord>;
1071
+ /** Mark this conversation's focus as ended. No-op when there is none. */
1072
+ endFocus(sessionId: string, courseId: CourseKey, now: string): Promise<void>;
511
1073
  /**
512
1074
  * Record the tutor's decision about where to go next.
513
1075
  *
@@ -529,14 +1091,13 @@ export interface UdState {
529
1091
  targetNodeId?: NodeKey;
530
1092
  reason: string;
531
1093
  now: string;
1094
+ sessionId: string;
532
1095
  }): Promise<{
533
1096
  nextStep: NextStepRecord;
534
1097
  focus: FocusRecord;
535
1098
  }>;
536
- /** The most recent recommendation recorded for a course, if any. */
537
- latestNextStep(courseId: CourseKey): NextStepRecord | undefined;
538
- /** A recommendation by key. */
539
- readNextStep(id: NextStepKey): NextStepRecord | undefined;
1099
+ /** A recommendation by the conversation that reached it. */
1100
+ readNextStep(sessionId: string, fromNodeId: NodeKey): NextStepRecord | undefined;
540
1101
  /** Every recommendation, for export. */
541
1102
  listNextSteps(): NextStepRecord[];
542
1103
  /** Every lesson, for export. */
@@ -558,7 +1119,7 @@ export interface UdState {
558
1119
  * learner asked to delete, which is the opposite of the point.
559
1120
  */
560
1121
  resetState(now: string): Promise<void>;
561
- readHandoff(targetNodeId: HandoffKey): HandoffRecord | undefined;
1122
+ readHandoff(sessionId: string, targetNodeId: HandoffKey): HandoffRecord | undefined;
562
1123
  /** Insert or replace one handoff. */
563
1124
  writeHandoff(record: HandoffRecord): Promise<HandoffRecord>;
564
1125
  /**
@@ -573,13 +1134,13 @@ export interface UdState {
573
1134
  * @param transform - pure transform from current to next.
574
1135
  * @returns the stored record.
575
1136
  */
576
- updateHandoff(targetNodeId: HandoffKey, transform: (record: HandoffRecord) => HandoffRecord): Promise<HandoffRecord>;
577
- /** The handoff for whichever node is currently focused, if any. */
578
- activeHandoff(): HandoffRecord | undefined;
1137
+ updateHandoff(sessionId: string, targetNodeId: HandoffKey, transform: (record: HandoffRecord) => HandoffRecord): Promise<HandoffRecord>;
1138
+ /** The handoff for whichever node this conversation is currently focused on. */
1139
+ activeHandoff(sessionId: string): HandoffRecord | undefined;
579
1140
  /** Every handoff, for diagnostics and tests. */
580
1141
  listHandoffs(): HandoffRecord[];
581
- /** The focus of the learner's active course, joined with its course and node. */
582
- activeFocus(): {
1142
+ /** This conversation's focus, joined with its course and node. */
1143
+ activeFocus(sessionId: string): {
583
1144
  focus: FocusRecord;
584
1145
  course: CourseRecord;
585
1146
  node: NodeRecord;
@@ -589,8 +1150,31 @@ export interface UdState {
589
1150
  lessonForNode(nodeId: NodeKey): LessonRecord | undefined;
590
1151
  /** Insert or fully replace one lesson. */
591
1152
  writeLesson(record: LessonRecord): Promise<LessonRecord>;
1153
+ /** Read, merge, and commit a node's lesson on the project/planning queue. The transform must not enqueue writes. */
1154
+ mutateLesson(courseId: CourseKey, nodeId: NodeKey, change: (current: LessonRecord | undefined) => LessonRecord): Promise<LessonRecord>;
592
1155
  /** Number of lessons across every course. */
593
1156
  lessonCount(): number;
1157
+ /** Learner-supplied material, newest first is not guaranteed; callers sort if needed. */
1158
+ listSources(): SourceRecord[];
1159
+ readSource(id: SourceKey): SourceRecord | undefined;
1160
+ writeSource(record: SourceRecord): Promise<SourceRecord>;
1161
+ listTasks(): TaskRecord[];
1162
+ readTask(id: TaskKey): TaskRecord | undefined;
1163
+ writeTask(record: TaskRecord): Promise<TaskRecord>;
1164
+ deleteTask(id: TaskKey): Promise<void>;
1165
+ listReviewCards(): ReviewCard[];
1166
+ readReviewCard(id: ReviewCardKey): ReviewCard | undefined;
1167
+ writeReviewCard(record: ReviewCard): Promise<ReviewCard>;
1168
+ listReviewLog(): ReviewLog[];
1169
+ writeReviewLog(record: ReviewLog): Promise<ReviewLog>;
1170
+ deleteReviewLog(id: ReviewLogKey): Promise<void>;
1171
+ /** Immutable planning snapshots; writes are validated and revision checked inside one queue. */
1172
+ readPath(courseId: string): LearningPath | undefined;
1173
+ mutatePath(courseId: string, expectedRevision: number, change: (current: LearningPath) => LearningPath): Promise<LearningPath>;
1174
+ readCourseRun(courseId: string, sessionId: string): CourseRun | undefined;
1175
+ /** Every planned run across conversations, for resolving "continue learning". */
1176
+ listCourseRuns(): CourseRun[];
1177
+ mutateCourseRun(courseId: string, sessionId: string, change: (current: CourseRun) => CourseRun): Promise<CourseRun>;
594
1178
  snapshot(): UdStateSnapshot;
595
1179
  /** Release the backend unit. Idempotent. */
596
1180
  close(): Promise<void>;