dsh-diagnostic-tutor 0.1.1 → 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 (115) hide show
  1. package/README.md +224 -59
  2. package/lib/api.js +178 -0
  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 +9448 -1654
  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/index.js +75 -13
  11. package/lib/index.js.map +1 -1
  12. package/lib/insights.js +82 -0
  13. package/lib/insights.js.map +1 -0
  14. package/lib/interactions.js +70 -0
  15. package/lib/interactions.js.map +1 -0
  16. package/lib/journal.js +122 -0
  17. package/lib/journal.js.map +1 -0
  18. package/lib/lesson-markdown.js +72 -0
  19. package/lib/lesson-markdown.js.map +1 -0
  20. package/lib/lesson.js +152 -3
  21. package/lib/lesson.js.map +1 -1
  22. package/lib/math.js +126 -0
  23. package/lib/math.js.map +1 -0
  24. package/lib/path-api.js +140 -0
  25. package/lib/path-api.js.map +1 -0
  26. package/lib/path-runtime.js +361 -0
  27. package/lib/path-runtime.js.map +1 -0
  28. package/lib/path-tools.js +107 -0
  29. package/lib/path-tools.js.map +1 -0
  30. package/lib/path.js +179 -0
  31. package/lib/path.js.map +1 -0
  32. package/lib/plan-tools.js +89 -0
  33. package/lib/plan-tools.js.map +1 -0
  34. package/lib/plan.js +82 -0
  35. package/lib/plan.js.map +1 -0
  36. package/lib/project-session.js +48 -0
  37. package/lib/project-session.js.map +1 -0
  38. package/lib/prompt.js +4 -19
  39. package/lib/prompt.js.map +1 -1
  40. package/lib/review-sync.js +51 -0
  41. package/lib/review-sync.js.map +1 -0
  42. package/lib/review-tools.js +115 -0
  43. package/lib/review-tools.js.map +1 -0
  44. package/lib/review.js +103 -0
  45. package/lib/review.js.map +1 -0
  46. package/lib/source-schema.js +24 -0
  47. package/lib/source-schema.js.map +1 -0
  48. package/lib/source-tools.js +61 -0
  49. package/lib/source-tools.js.map +1 -0
  50. package/lib/sources.js +78 -0
  51. package/lib/sources.js.map +1 -0
  52. package/lib/state.js +148 -7
  53. package/lib/state.js.map +1 -1
  54. package/lib/tools.js +456 -40
  55. package/lib/tools.js.map +1 -1
  56. package/lib/types/api.d.ts +7 -0
  57. package/lib/types/api.d.ts.map +1 -1
  58. package/lib/types/block-types.d.ts +12 -0
  59. package/lib/types/block-types.d.ts.map +1 -0
  60. package/lib/types/contract.d.ts +33 -1
  61. package/lib/types/contract.d.ts.map +1 -1
  62. package/lib/types/diagnostics.d.ts +95 -0
  63. package/lib/types/diagnostics.d.ts.map +1 -0
  64. package/lib/types/handoff.d.ts +2 -2
  65. package/lib/types/index.d.ts +15 -9
  66. package/lib/types/index.d.ts.map +1 -1
  67. package/lib/types/insights.d.ts +57 -0
  68. package/lib/types/insights.d.ts.map +1 -0
  69. package/lib/types/interactions.d.ts +23 -0
  70. package/lib/types/interactions.d.ts.map +1 -0
  71. package/lib/types/journal.d.ts +57 -0
  72. package/lib/types/journal.d.ts.map +1 -0
  73. package/lib/types/lesson-markdown.d.ts +11 -0
  74. package/lib/types/lesson-markdown.d.ts.map +1 -0
  75. package/lib/types/lesson.d.ts +697 -5
  76. package/lib/types/lesson.d.ts.map +1 -1
  77. package/lib/types/math.d.ts +50 -0
  78. package/lib/types/math.d.ts.map +1 -0
  79. package/lib/types/path-api.d.ts +5 -0
  80. package/lib/types/path-api.d.ts.map +1 -0
  81. package/lib/types/path-runtime.d.ts +1558 -0
  82. package/lib/types/path-runtime.d.ts.map +1 -0
  83. package/lib/types/path-tools.d.ts +4 -0
  84. package/lib/types/path-tools.d.ts.map +1 -0
  85. package/lib/types/path.d.ts +744 -0
  86. package/lib/types/path.d.ts.map +1 -0
  87. package/lib/types/plan-tools.d.ts +4 -0
  88. package/lib/types/plan-tools.d.ts.map +1 -0
  89. package/lib/types/plan.d.ts +53 -0
  90. package/lib/types/plan.d.ts.map +1 -0
  91. package/lib/types/project-session.d.ts +33 -0
  92. package/lib/types/project-session.d.ts.map +1 -0
  93. package/lib/types/prompt.d.ts.map +1 -1
  94. package/lib/types/review-sync.d.ts +17 -0
  95. package/lib/types/review-sync.d.ts.map +1 -0
  96. package/lib/types/review-tools.d.ts +4 -0
  97. package/lib/types/review-tools.d.ts.map +1 -0
  98. package/lib/types/review.d.ts +65 -0
  99. package/lib/types/review.d.ts.map +1 -0
  100. package/lib/types/source-schema.d.ts +31 -0
  101. package/lib/types/source-schema.d.ts.map +1 -0
  102. package/lib/types/source-tools.d.ts +4 -0
  103. package/lib/types/source-tools.d.ts.map +1 -0
  104. package/lib/types/sources.d.ts +33 -0
  105. package/lib/types/sources.d.ts.map +1 -0
  106. package/lib/types/state.d.ts +516 -9
  107. package/lib/types/state.d.ts.map +1 -1
  108. package/lib/types/tools.d.ts +3 -1
  109. package/lib/types/tools.d.ts.map +1 -1
  110. package/lib/types/version.d.ts +11 -0
  111. package/lib/types/version.d.ts.map +1 -0
  112. package/lib/types/vocabulary.d.ts +2 -2
  113. package/lib/version.js +26 -0
  114. package/lib/version.js.map +1 -0
  115. package/package.json +20 -18
@@ -0,0 +1,170 @@
1
+ /**
2
+ * Read-only health report for the tutoring runtime.
3
+ *
4
+ * The report is what "doctor" shows and what the local `/diagnostics` route
5
+ * returns. It is computed from durable state plus the recent event journal; it
6
+ * never writes, never calls a model and never includes lesson or chat content.
7
+ */
8
+ import { briefReady } from './path.js';
9
+ const STALE_REQUEST_MINUTES = 10;
10
+ const STALLED_ATTEMPT_MINUTES = 15;
11
+ function minutesBetween(now, iso) {
12
+ const at = Date.parse(iso);
13
+ return Number.isFinite(at) ? Math.max(0, Math.round((now - at) / 60_000)) : 0;
14
+ }
15
+ function assessmentReady(path) {
16
+ return path.assessment !== null
17
+ && path.assessment.briefRevision === path.briefRevision
18
+ && (path.assessment.status === 'ready' || path.assessment.status === 'skipped');
19
+ }
20
+ function unresolved(request, path) {
21
+ if (request.kind === 'diagnosis') {
22
+ const assessment = path.assessment;
23
+ return assessment === null || assessment.status === 'in-progress' || assessment.updatedAt < request.createdAt;
24
+ }
25
+ return !path.curricula.some((entry) => entry.createdAt >= request.createdAt);
26
+ }
27
+ /**
28
+ * Build one diagnostics report.
29
+ *
30
+ * @param state - open state handle.
31
+ * @param journal - journal whose recent events are included.
32
+ * @param options - clock and plugin identity for tests.
33
+ * @returns the report.
34
+ */
35
+ export async function buildDiagnostics(state, journal, options = {}) {
36
+ const now = options.now ?? new Date().toISOString();
37
+ const nowMs = Date.parse(now);
38
+ const exported = state.exportState(now);
39
+ const paths = new Map(exported.learningPaths.map((path) => [path.courseId, path]));
40
+ const runs = new Map(exported.courseRuns.map((run) => [run.courseId, run]));
41
+ const issues = [];
42
+ const projects = [];
43
+ for (const course of [...exported.courses].sort((left, right) => left.id.localeCompare(right.id))) {
44
+ const path = paths.get(course.id);
45
+ if (path === undefined) {
46
+ projects.push({
47
+ courseId: course.id, title: course.title, status: course.status, stage: 'legacy',
48
+ briefRevision: 0, assessment: null, curricula: 0, acceptedVersion: null,
49
+ requests: [], activeAttempt: null, completedItems: 0, totalItems: 0,
50
+ });
51
+ continue;
52
+ }
53
+ const ready = assessmentReady(path);
54
+ const accepted = path.acceptedVersion !== null
55
+ && path.curricula.some((entry) => entry.version === path.acceptedVersion);
56
+ const stage = !briefReady(path.brief)
57
+ ? 'goal'
58
+ : !ready
59
+ ? 'diagnosis'
60
+ : accepted
61
+ ? 'learning'
62
+ : 'route';
63
+ const run = runs.get(course.id);
64
+ const attempt = run?.attempts.find((entry) => entry.id === run.activeAttemptId);
65
+ const activeAttempt = attempt === undefined ? null : {
66
+ attemptId: attempt.id,
67
+ itemId: attempt.itemId,
68
+ status: attempt.status,
69
+ startedAt: attempt.startedAt,
70
+ ageMinutes: minutesBetween(nowMs, attempt.startedAt),
71
+ units: attempt.units.length,
72
+ nodeId: attempt.nodeId,
73
+ };
74
+ const pending = path.requests
75
+ .filter((request) => request.prompted
76
+ && ((request.failure !== null && request.failure !== undefined) || unresolved(request, path)))
77
+ .map((request) => ({
78
+ id: request.id,
79
+ kind: request.kind,
80
+ sessionId: request.sessionId,
81
+ createdAt: request.createdAt,
82
+ ageMinutes: minutesBetween(nowMs, request.createdAt),
83
+ prompted: request.prompted,
84
+ failure: request.failure ?? null,
85
+ }));
86
+ const acceptedPlan = path.curricula.find((entry) => entry.version === path.acceptedVersion) ?? path.curricula.at(-1);
87
+ const completedItems = new Set((run?.attempts ?? []).filter((entry) => entry.status === 'completed').map((entry) => entry.itemId)).size;
88
+ projects.push({
89
+ courseId: course.id,
90
+ title: course.title,
91
+ status: course.status,
92
+ stage,
93
+ briefRevision: path.briefRevision,
94
+ assessment: path.assessment?.status ?? null,
95
+ curricula: path.curricula.length,
96
+ acceptedVersion: path.acceptedVersion,
97
+ requests: pending,
98
+ activeAttempt,
99
+ completedItems,
100
+ totalItems: acceptedPlan?.items.length ?? 0,
101
+ });
102
+ for (const request of pending) {
103
+ if (request.failure !== null) {
104
+ issues.push({
105
+ code: 'failed-turn',
106
+ severity: 'error',
107
+ courseId: course.id,
108
+ message: `${request.kind} request ${request.id.slice(0, 8)} failed after waking the tutor: ${request.failure.slice(0, 240)}`,
109
+ });
110
+ }
111
+ else if (request.ageMinutes >= STALE_REQUEST_MINUTES) {
112
+ issues.push({
113
+ code: 'stale-request',
114
+ severity: 'warn',
115
+ courseId: course.id,
116
+ message: `${request.kind} request ${request.id.slice(0, 8)} has been waiting for ${request.ageMinutes} min without advancing`,
117
+ });
118
+ }
119
+ }
120
+ if (path.acceptedVersion !== null && !accepted) {
121
+ issues.push({
122
+ code: 'accepted-route-missing',
123
+ severity: 'error',
124
+ courseId: course.id,
125
+ message: `acceptedVersion ${path.acceptedVersion} has no matching curriculum revision`,
126
+ });
127
+ }
128
+ if (activeAttempt !== null && activeAttempt.status !== 'completed' && activeAttempt.units === 0
129
+ && activeAttempt.ageMinutes >= STALLED_ATTEMPT_MINUTES) {
130
+ issues.push({
131
+ code: 'stalled-attempt',
132
+ severity: 'warn',
133
+ courseId: course.id,
134
+ message: `lesson attempt ${activeAttempt.attemptId.slice(0, 8)} has no teaching units after ${activeAttempt.ageMinutes} min`,
135
+ });
136
+ }
137
+ }
138
+ const courseIds = new Set(exported.courses.map((course) => course.id));
139
+ for (const project of exported.projects) {
140
+ if (project.courseId !== undefined && !courseIds.has(project.courseId)) {
141
+ issues.push({
142
+ code: 'orphan-project-selection',
143
+ severity: 'warn',
144
+ message: `session ${project.sessionId} is bound to missing project ${project.courseId}`,
145
+ });
146
+ }
147
+ }
148
+ return {
149
+ ok: true,
150
+ generatedAt: now,
151
+ plugin: { name: 'dsh-diagnostic-tutor', version: options.pluginVersion ?? 'unknown' },
152
+ domain: exported.domain,
153
+ counts: {
154
+ courses: exported.courses.length,
155
+ nodes: exported.nodes.length,
156
+ lessons: exported.lessons.length,
157
+ focus: exported.focus.length,
158
+ projects: exported.projects.length,
159
+ learningPaths: exported.learningPaths.length,
160
+ courseRuns: exported.courseRuns.length,
161
+ },
162
+ issues,
163
+ projects,
164
+ journal: {
165
+ file: journal.file,
166
+ recent: await journal.tail(options.events ?? 40),
167
+ },
168
+ };
169
+ }
170
+ //# sourceMappingURL=diagnostics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diagnostics.js","sourceRoot":"","sources":["../src/diagnostics.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAA;AA4EtC,MAAM,qBAAqB,GAAG,EAAE,CAAA;AAChC,MAAM,uBAAuB,GAAG,EAAE,CAAA;AAElC,SAAS,cAAc,CAAC,GAAW,EAAE,GAAW;IAC9C,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC1B,OAAO,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;AAC/E,CAAC;AAED,SAAS,eAAe,CAAC,IAGxB;IACC,OAAO,IAAI,CAAC,UAAU,KAAK,IAAI;WAC1B,IAAI,CAAC,UAAU,CAAC,aAAa,KAAK,IAAI,CAAC,aAAa;WACpD,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,OAAO,IAAI,IAAI,CAAC,UAAU,CAAC,MAAM,KAAK,SAAS,CAAC,CAAA;AACnF,CAAC;AAED,SAAS,UAAU,CACjB,OAA0D,EAC1D,IAGC;IAED,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QACjC,MAAM,UAAU,GAAG,IAAI,CAAC,UAAU,CAAA;QAClC,OAAO,UAAU,KAAK,IAAI,IAAI,UAAU,CAAC,MAAM,KAAK,aAAa,IAAI,UAAU,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,CAAA;IAC/G,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,SAAS,IAAI,OAAO,CAAC,SAAS,CAAC,CAAA;AAC9E,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,KAAc,EACd,OAAgB,EAChB,UAAgG,EAAE;IAElG,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAA;IACnD,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;IAC7B,MAAM,QAAQ,GAAG,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;IACvC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,CAAA;IAClF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;IAC3E,MAAM,MAAM,GAAuB,EAAE,CAAA;IACrC,MAAM,QAAQ,GAAyB,EAAE,CAAA;IAEzC,KAAK,MAAM,MAAM,IAAI,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,aAAa,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QAClG,MAAM,IAAI,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QACjC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACvB,QAAQ,CAAC,IAAI,CAAC;gBACZ,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,QAAQ;gBAChF,aAAa,EAAE,CAAC,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,EAAE,eAAe,EAAE,IAAI;gBACvE,QAAQ,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,EAAE,UAAU,EAAE,CAAC;aACpE,CAAC,CAAA;YACF,SAAQ;QACV,CAAC;QAED,MAAM,KAAK,GAAG,eAAe,CAAC,IAAI,CAAC,CAAA;QACnC,MAAM,QAAQ,GAAG,IAAI,CAAC,eAAe,KAAK,IAAI;eACzC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,eAAe,CAAC,CAAA;QAC3E,MAAM,KAAK,GAAgC,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;YAChE,CAAC,CAAC,MAAM;YACR,CAAC,CAAC,CAAC,KAAK;gBACN,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,QAAQ;oBACR,CAAC,CAAC,UAAU;oBACZ,CAAC,CAAC,OAAO,CAAA;QAEf,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAC/B,MAAM,OAAO,GAAG,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,CAAC,eAAe,CAAC,CAAA;QAC/E,MAAM,aAAa,GAA8B,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YAC9E,SAAS,EAAE,OAAO,CAAC,EAAE;YACrB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,UAAU,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC;YACpD,KAAK,EAAE,OAAO,CAAC,KAAK,CAAC,MAAM;YAC3B,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAA;QAED,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ;aAC1B,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,QAAQ;eAChC,CAAC,CAAC,OAAO,CAAC,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,OAAO,KAAK,SAAS,CAAC,IAAI,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;aAC/F,GAAG,CAAqB,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACrC,EAAE,EAAE,OAAO,CAAC,EAAE;YACd,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,SAAS,EAAE,OAAO,CAAC,SAAS;YAC5B,UAAU,EAAE,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,SAAS,CAAC;YACpD,QAAQ,EAAE,OAAO,CAAC,QAAQ;YAC1B,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,IAAI;SACjC,CAAC,CAAC,CAAA;QAEL,MAAM,YAAY,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,KAAK,IAAI,CAAC,eAAe,CAAC,IAAI,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;QACpH,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,EAAE,QAAQ,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAA;QAEvI,QAAQ,CAAC,IAAI,CAAC;YACZ,QAAQ,EAAE,MAAM,CAAC,EAAE;YACnB,KAAK,EAAE,MAAM,CAAC,KAAK;YACnB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,KAAK;YACL,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,MAAM,IAAI,IAAI;YAC3C,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM;YAChC,eAAe,EAAE,IAAI,CAAC,eAAe;YACrC,QAAQ,EAAE,OAAO;YACjB,aAAa;YACb,cAAc;YACd,UAAU,EAAE,YAAY,EAAE,KAAK,CAAC,MAAM,IAAI,CAAC;SAC5C,CAAC,CAAA;QAEF,KAAK,MAAM,OAAO,IAAI,OAAO,EAAE,CAAC;YAC9B,IAAI,OAAO,CAAC,OAAO,KAAK,IAAI,EAAE,CAAC;gBAC7B,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,aAAa;oBACnB,QAAQ,EAAE,OAAO;oBACjB,QAAQ,EAAE,MAAM,CAAC,EAAE;oBACnB,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,YAAY,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,mCAAmC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE;iBAC7H,CAAC,CAAA;YACJ,CAAC;iBAAM,IAAI,OAAO,CAAC,UAAU,IAAI,qBAAqB,EAAE,CAAC;gBACvD,MAAM,CAAC,IAAI,CAAC;oBACV,IAAI,EAAE,eAAe;oBACrB,QAAQ,EAAE,MAAM;oBAChB,QAAQ,EAAE,MAAM,CAAC,EAAE;oBACnB,OAAO,EAAE,GAAG,OAAO,CAAC,IAAI,YAAY,OAAO,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,yBAAyB,OAAO,CAAC,UAAU,wBAAwB;iBAC9H,CAAC,CAAA;YACJ,CAAC;QACH,CAAC;QAED,IAAI,IAAI,CAAC,eAAe,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC/C,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,wBAAwB;gBAC9B,QAAQ,EAAE,OAAO;gBACjB,QAAQ,EAAE,MAAM,CAAC,EAAE;gBACnB,OAAO,EAAE,mBAAmB,IAAI,CAAC,eAAe,sCAAsC;aACvF,CAAC,CAAA;QACJ,CAAC;QAED,IAAI,aAAa,KAAK,IAAI,IAAI,aAAa,CAAC,MAAM,KAAK,WAAW,IAAI,aAAa,CAAC,KAAK,KAAK,CAAC;eAC1F,aAAa,CAAC,UAAU,IAAI,uBAAuB,EAAE,CAAC;YACzD,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,iBAAiB;gBACvB,QAAQ,EAAE,MAAM;gBAChB,QAAQ,EAAE,MAAM,CAAC,EAAE;gBACnB,OAAO,EAAE,kBAAkB,aAAa,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,gCAAgC,aAAa,CAAC,UAAU,MAAM;aAC7H,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAA;IACtE,KAAK,MAAM,OAAO,IAAI,QAAQ,CAAC,QAAQ,EAAE,CAAC;QACxC,IAAI,OAAO,CAAC,QAAQ,KAAK,SAAS,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAC;YACvE,MAAM,CAAC,IAAI,CAAC;gBACV,IAAI,EAAE,0BAA0B;gBAChC,QAAQ,EAAE,MAAM;gBAChB,OAAO,EAAE,WAAW,OAAO,CAAC,SAAS,gCAAgC,OAAO,CAAC,QAAQ,EAAE;aACxF,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,OAAO;QACL,EAAE,EAAE,IAAI;QACR,WAAW,EAAE,GAAG;QAChB,MAAM,EAAE,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,OAAO,CAAC,aAAa,IAAI,SAAS,EAAE;QACrF,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,MAAM,EAAE;YACN,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM;YAChC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM;YAC5B,OAAO,EAAE,QAAQ,CAAC,OAAO,CAAC,MAAM;YAChC,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,MAAM;YAC5B,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,MAAM;YAClC,aAAa,EAAE,QAAQ,CAAC,aAAa,CAAC,MAAM;YAC5C,UAAU,EAAE,QAAQ,CAAC,UAAU,CAAC,MAAM;SACvC;QACD,MAAM;QACN,QAAQ;QACR,OAAO,EAAE;YACP,IAAI,EAAE,OAAO,CAAC,IAAI;YAClB,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,IAAI,EAAE,CAAC;SACjD;KACF,CAAA;AACH,CAAC"}
package/lib/index.js CHANGED
@@ -6,17 +6,23 @@
6
6
  * on unload.
7
7
  *
8
8
  * ---------------------------------------------------------------------------
9
- * v0.0.3 scope (see docs/planning/PLAN.md §7)
9
+ * Scope in v0.2.0 (content/input/review loop on top of the published learning runtime)
10
10
  * ---------------------------------------------------------------------------
11
- * Goal → detection → diagnosis map. The runtime now carries real product
12
- * semantics: it records a learning goal in the learner's words, plants a map
13
- * root, and lets a map grow one diagnosis at a time under rules that make
14
- * unverified mastery impossible to store.
11
+ * The runtime owns learner state and the surfaces that show it; it owns no
12
+ * teaching decisions. What it registers:
15
13
  *
16
- * Still deliberately absent:
17
- * - no lesson generation, no quiz system -> v0.0.4+
18
- * - no client half / UI -> v0.0.4
19
- * - no resource ingestion, no RAG -> out of scope for v0.1
14
+ * - a goal → diagnosis map (courses, nodes, evidence) per project;
15
+ * - one learning focus, next step and handoff per conversation;
16
+ * - versioned course routes (goal brief, evidence-referenced assessment,
17
+ * immutable curriculum revisions, explicit learner acceptance);
18
+ * - session-scoped course runs: an attempt, teaching snapshots and the
19
+ * evidence that completion is allowed to cite;
20
+ * - the browser half: one "Learning map" main page, one "Current lesson"
21
+ * docked tab, and the HTTP routes both read through.
22
+ *
23
+ * Still deliberately absent: resource ingestion and RAG (out of scope), and
24
+ * any rule that decides what to teach. The Universal Diagnostic Tutor skill is
25
+ * the teaching brain; this plugin only records what it did.
20
26
  *
21
27
  * ---------------------------------------------------------------------------
22
28
  * Cross-version discipline (see PLAN.md 2.12 #9 / #16)
@@ -40,6 +46,9 @@
40
46
  */
41
47
  import { API_PREFIX, registerApi } from './api.js';
42
48
  import { withActivity } from './handoff.js';
49
+ import { openJournal } from './journal.js';
50
+ import { stampPathRequestFailure } from './path-runtime.js';
51
+ import { packageVersion } from './version.js';
43
52
  import { promptSession } from './prompt.js';
44
53
  import { UDT_DOMAIN_NAME, openUdState } from './state.js';
45
54
  import { registerTools } from './tools.js';
@@ -107,6 +116,9 @@ export async function apply(ctx) {
107
116
  // rejects new writes, drains queued ones, and releases the backend unit) and
108
117
  // Cordis awaits the returned promise before the plugin counts as unloaded.
109
118
  ctx.effect(() => () => state.close());
119
+ // Local, content-free monitoring. Disabled under the test runner and never
120
+ // allowed to fail the runtime (all writes are best-effort).
121
+ const journal = openJournal();
110
122
  // Mount the browser API if this profile has a web surface.
111
123
  //
112
124
  // Resolved eagerly first: a profile's web server is mounted by the base and
@@ -120,8 +132,19 @@ export async function apply(ctx) {
120
132
  return;
121
133
  ctx.effect(() => registerApi(server, {
122
134
  state,
123
- prompt: (sessionId, text) => promptSession(ctx, sessionId, text),
135
+ prompt: (sessionId, text) => {
136
+ const result = promptSession(ctx, sessionId, text);
137
+ journal.append('tutor.prompt', {
138
+ sessionId: sessionId ?? null,
139
+ prompted: result.prompted,
140
+ ...(result.reason === undefined ? {} : { reason: result.reason.slice(0, 240) }),
141
+ });
142
+ return result;
143
+ },
124
144
  teachingBrain: (sessionId) => teachingBrainForSession(ctx, sessionId),
145
+ journal,
146
+ pluginVersion: packageVersion(),
147
+ onEvent: (event, fields) => journal.append(event, fields),
125
148
  }));
126
149
  ctx.logger.debug(`[diagnostic-tutor] browser API mounted at ${API_PREFIX}`);
127
150
  };
@@ -137,12 +160,50 @@ export async function apply(ctx) {
137
160
  // happening". Only the first event counts, and only from the session the
138
161
  // handoff asked in, so a busy conversation elsewhere cannot make a quiet
139
162
  // handoff look alive.
163
+ // A prompted turn can fail after the request was accepted (a provider error,
164
+ // for example). The browser snapshot that would show it is memory-only, so
165
+ // stamp the durable request record here; the map page can then explain and
166
+ // retry even after a restart.
167
+ ctx.on('agent/error', ({ agent, error }) => {
168
+ const reason = error instanceof Error ? error.message : String(error);
169
+ journal.append('agent.error', { sessionId: String(agent.session.id), error: reason.slice(0, 240) });
170
+ void stampPathRequestFailure(state, String(agent.session.id), reason).catch(() => { });
171
+ });
172
+ const toolCalls = new Map();
140
173
  ctx.on('session/event', (session, event) => {
174
+ const sessionId = String(session.id);
175
+ // Tool metadata only: the name, the operation and the duration. Arguments
176
+ // stay out of the journal, so no lesson or goal text is recorded.
177
+ if (event.type === 'tool/call') {
178
+ const data = event.data;
179
+ if (typeof data.name === 'string' && data.name.startsWith('udt_')) {
180
+ let operation;
181
+ try {
182
+ const parsed = JSON.parse(data.arguments ?? '');
183
+ if (typeof parsed.op === 'string')
184
+ operation = parsed.op;
185
+ else if (typeof parsed.action === 'string')
186
+ operation = parsed.action;
187
+ }
188
+ catch { /* the tool gets to explain its own arguments */ }
189
+ journal.append('tool.call', { sessionId, tool: data.name, ...(operation === undefined ? {} : { op: operation }) });
190
+ if (data.callId !== undefined)
191
+ toolCalls.set(`${sessionId}:${data.callId}`, { name: data.name, start: Date.now() });
192
+ }
193
+ }
194
+ else if (event.type === 'tool/result') {
195
+ const message = event.data.message;
196
+ const key = message?.toolCallId === undefined ? undefined : `${sessionId}:${message.toolCallId}`;
197
+ const tracked = key === undefined ? undefined : toolCalls.get(key);
198
+ if (tracked !== undefined && key !== undefined) {
199
+ toolCalls.delete(key);
200
+ journal.append('tool.result', { sessionId, tool: tracked.name, durationMs: Math.max(0, Date.now() - tracked.start) });
201
+ }
202
+ }
141
203
  // The event says which conversation produced it, so the handoff is looked
142
204
  // up in that conversation rather than in the course at large. A busy
143
205
  // conversation elsewhere cannot make a quiet handoff look alive — and two
144
206
  // conversations working on the same target keep separate records.
145
- const sessionId = String(session.id);
146
207
  const handoff = state.activeHandoff(sessionId);
147
208
  if (handoff === undefined || handoff.status !== 'prompted')
148
209
  return;
@@ -154,7 +215,8 @@ export async function apply(ctx) {
154
215
  .updateHandoff(sessionId, handoff.targetNodeId, (current) => current.status === 'prompted' ? withActivity(current, new Date().toISOString()) : current)
155
216
  .catch(() => { });
156
217
  });
157
- registerTools({ tools }, state);
158
- ctx.logger.debug(`[diagnostic-tutor] ready — domain "${state.name}" v${state.version}, 4 tools registered`);
218
+ const toolCount = registerTools({ tools }, state);
219
+ journal.append('plugin.ready', { tools: toolCount, version: packageVersion() });
220
+ ctx.logger.debug(`[diagnostic-tutor] ready — domain "${state.name}" v${state.version}, ${toolCount} tools registered`);
159
221
  }
160
222
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Read-only facts for the Insights tab.
3
+ *
4
+ * Nothing here scores, ranks mastery or invents a weakness: every field is a
5
+ * count or a direct projection of stored evidence, review history and attempts.
6
+ */
7
+ import { dueCards } from './review.js';
8
+ const DAY_MS = 86_400_000;
9
+ function sortByAtDesc(items) {
10
+ return [...items].sort((left, right) => right.at.localeCompare(left.at));
11
+ }
12
+ /** Build one course's Insights view from stored facts. */
13
+ export function buildInsights(state, course, nowIso) {
14
+ const now = new Date(nowIso);
15
+ const cards = state.listReviewCards().filter((card) => card.courseId === course.id);
16
+ const cardsById = new Map(cards.map((card) => [card.id, card]));
17
+ const due = dueCards(cards, now);
18
+ const logs = state.listReviewLog().filter((log) => cardsById.has(log.cardId));
19
+ const recentReviews = sortByAtDesc(logs).slice(0, 8).map((log) => ({
20
+ at: log.at, rating: log.rating, front: cardsById.get(log.cardId)?.front ?? '',
21
+ }));
22
+ const lapsed = [...cards]
23
+ .filter((card) => card.lapses > 0)
24
+ .sort((left, right) => right.lapses - left.lapses)
25
+ .slice(0, 5)
26
+ .map((card) => ({ id: card.id, front: card.front, lapses: card.lapses, dueAt: card.dueAt }));
27
+ const nodes = state.listNodes(course.id);
28
+ const evidence = nodes.flatMap((node) => node.evidence);
29
+ const weekAgo = now.getTime() - 7 * DAY_MS;
30
+ const byKind = new Map();
31
+ for (const entry of evidence)
32
+ byKind.set(entry.kind, (byKind.get(entry.kind) ?? 0) + 1);
33
+ const weakNodes = nodes
34
+ .filter((node) => node.state === 'weak' || node.state === 'blocked')
35
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
36
+ .slice(0, 8)
37
+ .map((node) => ({
38
+ id: node.id, title: node.title, state: node.state,
39
+ evidenceCount: node.evidence.length, updatedAt: node.updatedAt,
40
+ }));
41
+ const attempts = state.listCourseRuns()
42
+ .filter((run) => run.courseId === course.id)
43
+ .flatMap((run) => run.attempts)
44
+ .sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
45
+ .slice(0, 6)
46
+ .map((attempt) => ({
47
+ attemptId: attempt.id, status: attempt.status, updatedAt: attempt.updatedAt,
48
+ units: attempt.units.length, responses: attempt.responses?.length ?? 0,
49
+ }));
50
+ const today = nowIso.slice(0, 10);
51
+ const courseTasks = state.listTasks().filter((task) => task.courseId === undefined || task.courseId === course.id);
52
+ const sources = state.listSources();
53
+ return {
54
+ ok: true,
55
+ courseId: course.id,
56
+ courseTitle: course.title,
57
+ generatedAt: nowIso,
58
+ review: {
59
+ due: due.length,
60
+ overdue: due.filter((card) => Date.parse(card.dueAt) < now.getTime()).length,
61
+ total: cards.length,
62
+ lapsed,
63
+ },
64
+ recentReviews,
65
+ evidence: {
66
+ total: evidence.length,
67
+ last7Days: evidence.filter((entry) => Date.parse(entry.at) >= weekAgo).length,
68
+ byKind: [...byKind.entries()].map(([kind, count]) => ({ kind, count })).sort((left, right) => right.count - left.count),
69
+ },
70
+ weakNodes,
71
+ recentAttempts: attempts,
72
+ tasks: {
73
+ open: courseTasks.filter((task) => task.status === 'open').length,
74
+ dueToday: courseTasks.filter((task) => task.status === 'open' && task.dueAt !== undefined && task.dueAt.slice(0, 10) === today).length,
75
+ },
76
+ sources: {
77
+ count: sources.length,
78
+ bytes: sources.reduce((sum, source) => sum + source.bytes, 0),
79
+ },
80
+ };
81
+ }
82
+ //# sourceMappingURL=insights.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"insights.js","sourceRoot":"","sources":["../src/insights.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,70 @@
1
+ /** Recording learner submissions. No grading, no mastery, no automatic next step. */
2
+ import { randomUUID } from 'node:crypto';
3
+ import { z } from 'zod';
4
+ import { isKnownBlock } from './block-types.js';
5
+ import { BlockAnswerSchema } from './path.js';
6
+ import { pathView, requireAttempt } from './path-runtime.js';
7
+ export const AnswerSubmissionSchema = z.object({
8
+ attemptId: z.string().min(1).max(200),
9
+ blockId: z.string().min(1).max(200),
10
+ answers: z.array(BlockAnswerSchema).min(1).max(16),
11
+ }).strict();
12
+ /**
13
+ * Persist a quiz/flashcard submission on the active attempt and append a neutral
14
+ * practice observation to its bound node. The node's state is never changed.
15
+ */
16
+ export async function recordBlockResponse(state, input) {
17
+ const path = state.readPath(input.courseId);
18
+ if (path === undefined)
19
+ throw new Error('no-learning-path: ask for a route first');
20
+ if (path.revision !== input.expectedRevision)
21
+ throw new Error('revision-conflict: refresh the learning path');
22
+ const attempt = requireAttempt(state, input.courseId, input.sessionId, input.attemptId);
23
+ const lesson = [...attempt.units].reverse()[0]?.lesson;
24
+ const block = lesson?.blocks.find((candidate) => candidate.id === input.blockId);
25
+ if (block === undefined || !isKnownBlock(block))
26
+ throw new Error('unknown-block: refresh the lesson');
27
+ if (block.type !== 'quiz' && block.type !== 'flashcard')
28
+ throw new Error('not-an-interactive-block: this block does not take an answer');
29
+ if (block.type === 'quiz') {
30
+ const questions = new Map(block.content.questions.map((question) => [question.id, question]));
31
+ for (const answer of input.answers) {
32
+ const question = questions.get(answer.questionId);
33
+ if (question === undefined)
34
+ throw new Error(`unknown-question: no question "${answer.questionId}"`);
35
+ if (question.options !== undefined && !question.options.includes(answer.value) && question.kind === 'mcq') {
36
+ throw new Error('invalid-answer-option: choose one of the offered options');
37
+ }
38
+ }
39
+ }
40
+ else if (input.answers.length !== 1 || input.answers[0]?.questionId !== block.id) {
41
+ throw new Error('invalid-flashcard-answer: answer the card itself');
42
+ }
43
+ const response = {
44
+ id: randomUUID(),
45
+ blockId: block.id,
46
+ kind: block.type,
47
+ answers: input.answers,
48
+ submittedAt: new Date().toISOString(),
49
+ };
50
+ await state.mutateCourseRun(input.courseId, input.sessionId, (run) => {
51
+ const current = requireAttempt(state, input.courseId, input.sessionId, input.attemptId);
52
+ return {
53
+ ...run,
54
+ attempts: run.attempts.map((entry) => entry.id === current.id
55
+ ? { ...entry, responses: [...(entry.responses ?? []).filter((entryResponse) => entryResponse.blockId !== block.id), response], updatedAt: response.submittedAt }
56
+ : entry),
57
+ updatedAt: response.submittedAt,
58
+ };
59
+ });
60
+ if (attempt.nodeId !== null) {
61
+ const at = response.submittedAt;
62
+ await state.mutateNode(input.courseId, attempt.nodeId, (node) => ({
63
+ ...node,
64
+ evidence: [...node.evidence, { kind: 'practice', at, note: `Submitted ${block.type} response for ${block.id}.`, sessionId: input.sessionId, attemptId: attempt.id }],
65
+ updatedAt: at,
66
+ }));
67
+ }
68
+ return pathView(state, input.courseId, input.sessionId);
69
+ }
70
+ //# sourceMappingURL=interactions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interactions.js","sourceRoot":"","sources":["../src/interactions.ts"],"names":[],"mappings":"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"}
package/lib/journal.js ADDED
@@ -0,0 +1,122 @@
1
+ /**
2
+ * Local, content-free event journal for the tutoring flow.
3
+ *
4
+ * Monitoring has to answer "where did it get stuck?" without harvesting the
5
+ * learner's chat. This journal therefore stores only lifecycle facts: the
6
+ * event name, ids, stage/op names, durations and error messages — never lesson
7
+ * text, goals, evidence notes or model replies.
8
+ *
9
+ * The file is local to the machine, size-capped and rotated once. Writing is
10
+ * best-effort and serialised: monitoring must never fail the thing it observes.
11
+ */
12
+ import { appendFile, mkdir, readFile, rename, stat } from 'node:fs/promises';
13
+ import { homedir } from 'node:os';
14
+ import { dirname, join } from 'node:path';
15
+ /** Environment override: a file path, or `off` to disable the journal. */
16
+ export const JOURNAL_ENV = 'DSH_DIAGNOSTIC_TUTOR_EVENTS';
17
+ /** Default cap per file, and the file kept beside it after rotation. */
18
+ export const JOURNAL_MAX_BYTES = 1_000_000;
19
+ /**
20
+ * Resolve the default journal file.
21
+ *
22
+ * `VITEST`/`NODE_ENV=test` disable the journal so the test suite never writes
23
+ * into the developer's real `~/.dsh`. Explicit configuration always wins.
24
+ *
25
+ * @param env - environment to read.
26
+ * @returns the file to append to, or `null` when disabled.
27
+ */
28
+ export function defaultJournalFile(env = process.env) {
29
+ const explicit = env[JOURNAL_ENV];
30
+ if (explicit === 'off' || explicit === '0')
31
+ return null;
32
+ if (explicit !== undefined && explicit.length > 0)
33
+ return explicit;
34
+ if (env.VITEST !== undefined || env.NODE_ENV === 'test')
35
+ return null;
36
+ const home = env.DSH_HOME ?? join(homedir(), '.dsh');
37
+ return join(home, 'logs', 'diagnostic-tutor-events.jsonl');
38
+ }
39
+ /** Best-effort rotation: keep one previous file, reset the current one. */
40
+ async function rotate(file, bytes, maxBytes) {
41
+ if (bytes < maxBytes)
42
+ return bytes;
43
+ try {
44
+ await rename(file, `${file}.1`);
45
+ }
46
+ catch {
47
+ // Missing current file (or a concurrent rotation) is fine.
48
+ }
49
+ return 0;
50
+ }
51
+ /**
52
+ * Open the journal.
53
+ *
54
+ * @param options - file/maxBytes/clock overrides for tests.
55
+ * @returns the journal handle; a disabled journal is a no-op.
56
+ */
57
+ export function openJournal(options = {}) {
58
+ const file = options.file === undefined ? defaultJournalFile() : options.file;
59
+ if (file === null) {
60
+ return { file: null, append: () => { }, tail: async () => [] };
61
+ }
62
+ const maxBytes = options.maxBytes ?? JOURNAL_MAX_BYTES;
63
+ const now = options.now ?? (() => new Date());
64
+ let bytes = null;
65
+ let chain = Promise.resolve();
66
+ const write = async (event, fields) => {
67
+ let line;
68
+ try {
69
+ line = JSON.stringify({ at: now().toISOString(), event, ...fields }) + '\n';
70
+ }
71
+ catch {
72
+ return; // A non-serialisable field must not break monitoring.
73
+ }
74
+ try {
75
+ await mkdir(dirname(file), { recursive: true });
76
+ if (bytes === null) {
77
+ try {
78
+ bytes = (await stat(file)).size;
79
+ }
80
+ catch {
81
+ bytes = 0;
82
+ }
83
+ }
84
+ bytes = await rotate(file, bytes, maxBytes);
85
+ await appendFile(file, line, 'utf8');
86
+ bytes += Buffer.byteLength(line);
87
+ }
88
+ catch {
89
+ // Local diagnostics are never allowed to fail the runtime.
90
+ }
91
+ };
92
+ return {
93
+ file,
94
+ append(event, fields = {}) {
95
+ chain = chain.then(() => write(event, fields));
96
+ },
97
+ async tail(limit = 50) {
98
+ await chain;
99
+ let text;
100
+ try {
101
+ text = await readFile(file, 'utf8');
102
+ }
103
+ catch {
104
+ return [];
105
+ }
106
+ const lines = text.split('\n').filter((line) => line.length > 0);
107
+ const events = [];
108
+ for (const line of lines.slice(-Math.max(1, limit))) {
109
+ try {
110
+ const parsed = JSON.parse(line);
111
+ if (typeof parsed.event === 'string')
112
+ events.push(parsed);
113
+ }
114
+ catch {
115
+ // A torn line is skipped; the rest of the journal still reads.
116
+ }
117
+ }
118
+ return events;
119
+ },
120
+ };
121
+ }
122
+ //# sourceMappingURL=journal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"journal.js","sourceRoot":"","sources":["../src/journal.ts"],"names":[],"mappings":"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"}