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.
- package/README.md +224 -59
- package/lib/api.js +178 -0
- package/lib/api.js.map +1 -1
- package/lib/block-types.js +25 -0
- package/lib/block-types.js.map +1 -0
- package/lib/client.js +9448 -1654
- package/lib/client.js.map +1 -1
- package/lib/diagnostics.js +170 -0
- package/lib/diagnostics.js.map +1 -0
- package/lib/index.js +75 -13
- package/lib/index.js.map +1 -1
- package/lib/insights.js +82 -0
- package/lib/insights.js.map +1 -0
- package/lib/interactions.js +70 -0
- package/lib/interactions.js.map +1 -0
- package/lib/journal.js +122 -0
- package/lib/journal.js.map +1 -0
- package/lib/lesson-markdown.js +72 -0
- package/lib/lesson-markdown.js.map +1 -0
- package/lib/lesson.js +152 -3
- package/lib/lesson.js.map +1 -1
- package/lib/math.js +126 -0
- package/lib/math.js.map +1 -0
- package/lib/path-api.js +140 -0
- package/lib/path-api.js.map +1 -0
- package/lib/path-runtime.js +361 -0
- package/lib/path-runtime.js.map +1 -0
- package/lib/path-tools.js +107 -0
- package/lib/path-tools.js.map +1 -0
- package/lib/path.js +179 -0
- package/lib/path.js.map +1 -0
- package/lib/plan-tools.js +89 -0
- package/lib/plan-tools.js.map +1 -0
- package/lib/plan.js +82 -0
- package/lib/plan.js.map +1 -0
- package/lib/project-session.js +48 -0
- package/lib/project-session.js.map +1 -0
- package/lib/prompt.js +4 -19
- package/lib/prompt.js.map +1 -1
- package/lib/review-sync.js +51 -0
- package/lib/review-sync.js.map +1 -0
- package/lib/review-tools.js +115 -0
- package/lib/review-tools.js.map +1 -0
- package/lib/review.js +103 -0
- package/lib/review.js.map +1 -0
- package/lib/source-schema.js +24 -0
- package/lib/source-schema.js.map +1 -0
- package/lib/source-tools.js +61 -0
- package/lib/source-tools.js.map +1 -0
- package/lib/sources.js +78 -0
- package/lib/sources.js.map +1 -0
- package/lib/state.js +148 -7
- package/lib/state.js.map +1 -1
- package/lib/tools.js +456 -40
- package/lib/tools.js.map +1 -1
- package/lib/types/api.d.ts +7 -0
- package/lib/types/api.d.ts.map +1 -1
- package/lib/types/block-types.d.ts +12 -0
- package/lib/types/block-types.d.ts.map +1 -0
- package/lib/types/contract.d.ts +33 -1
- package/lib/types/contract.d.ts.map +1 -1
- package/lib/types/diagnostics.d.ts +95 -0
- package/lib/types/diagnostics.d.ts.map +1 -0
- package/lib/types/handoff.d.ts +2 -2
- package/lib/types/index.d.ts +15 -9
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/insights.d.ts +57 -0
- package/lib/types/insights.d.ts.map +1 -0
- package/lib/types/interactions.d.ts +23 -0
- package/lib/types/interactions.d.ts.map +1 -0
- package/lib/types/journal.d.ts +57 -0
- package/lib/types/journal.d.ts.map +1 -0
- package/lib/types/lesson-markdown.d.ts +11 -0
- package/lib/types/lesson-markdown.d.ts.map +1 -0
- package/lib/types/lesson.d.ts +697 -5
- package/lib/types/lesson.d.ts.map +1 -1
- package/lib/types/math.d.ts +50 -0
- package/lib/types/math.d.ts.map +1 -0
- package/lib/types/path-api.d.ts +5 -0
- package/lib/types/path-api.d.ts.map +1 -0
- package/lib/types/path-runtime.d.ts +1558 -0
- package/lib/types/path-runtime.d.ts.map +1 -0
- package/lib/types/path-tools.d.ts +4 -0
- package/lib/types/path-tools.d.ts.map +1 -0
- package/lib/types/path.d.ts +744 -0
- package/lib/types/path.d.ts.map +1 -0
- package/lib/types/plan-tools.d.ts +4 -0
- package/lib/types/plan-tools.d.ts.map +1 -0
- package/lib/types/plan.d.ts +53 -0
- package/lib/types/plan.d.ts.map +1 -0
- package/lib/types/project-session.d.ts +33 -0
- package/lib/types/project-session.d.ts.map +1 -0
- package/lib/types/prompt.d.ts.map +1 -1
- package/lib/types/review-sync.d.ts +17 -0
- package/lib/types/review-sync.d.ts.map +1 -0
- package/lib/types/review-tools.d.ts +4 -0
- package/lib/types/review-tools.d.ts.map +1 -0
- package/lib/types/review.d.ts +65 -0
- package/lib/types/review.d.ts.map +1 -0
- package/lib/types/source-schema.d.ts +31 -0
- package/lib/types/source-schema.d.ts.map +1 -0
- package/lib/types/source-tools.d.ts +4 -0
- package/lib/types/source-tools.d.ts.map +1 -0
- package/lib/types/sources.d.ts +33 -0
- package/lib/types/sources.d.ts.map +1 -0
- package/lib/types/state.d.ts +516 -9
- package/lib/types/state.d.ts.map +1 -1
- package/lib/types/tools.d.ts +3 -1
- package/lib/types/tools.d.ts.map +1 -1
- package/lib/types/version.d.ts +11 -0
- package/lib/types/version.d.ts.map +1 -0
- package/lib/types/vocabulary.d.ts +2 -2
- package/lib/version.js +26 -0
- package/lib/version.js.map +1 -0
- 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":"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"}
|
package/lib/index.js
CHANGED
|
@@ -6,17 +6,23 @@
|
|
|
6
6
|
* on unload.
|
|
7
7
|
*
|
|
8
8
|
* ---------------------------------------------------------------------------
|
|
9
|
-
* v0.0
|
|
9
|
+
* Scope in v0.2.0 (content/input/review loop on top of the published learning runtime)
|
|
10
10
|
* ---------------------------------------------------------------------------
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
*
|
|
17
|
-
* -
|
|
18
|
-
* -
|
|
19
|
-
*
|
|
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) =>
|
|
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
|
-
|
|
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":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/lib/insights.js
ADDED
|
@@ -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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"}
|