dsh-diagnostic-tutor 0.1.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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +658 -0
  3. package/cordis.patch.yml +23 -0
  4. package/lib/api.js +413 -0
  5. package/lib/api.js.map +1 -0
  6. package/lib/client.js +2029 -0
  7. package/lib/client.js.map +1 -0
  8. package/lib/contract.js +14 -0
  9. package/lib/contract.js.map +1 -0
  10. package/lib/diagnosis.js +224 -0
  11. package/lib/diagnosis.js.map +1 -0
  12. package/lib/handoff.js +194 -0
  13. package/lib/handoff.js.map +1 -0
  14. package/lib/index.js +186 -0
  15. package/lib/index.js.map +1 -0
  16. package/lib/lesson.js +285 -0
  17. package/lib/lesson.js.map +1 -0
  18. package/lib/prompt.js +96 -0
  19. package/lib/prompt.js.map +1 -0
  20. package/lib/state.js +500 -0
  21. package/lib/state.js.map +1 -0
  22. package/lib/tools.js +994 -0
  23. package/lib/tools.js.map +1 -0
  24. package/lib/trust-fence.js +101 -0
  25. package/lib/trust-fence.js.map +1 -0
  26. package/lib/types/api.d.ts +62 -0
  27. package/lib/types/api.d.ts.map +1 -0
  28. package/lib/types/contract.d.ts +147 -0
  29. package/lib/types/contract.d.ts.map +1 -0
  30. package/lib/types/diagnosis.d.ts +116 -0
  31. package/lib/types/diagnosis.d.ts.map +1 -0
  32. package/lib/types/handoff.d.ts +141 -0
  33. package/lib/types/handoff.d.ts.map +1 -0
  34. package/lib/types/index.d.ts +71 -0
  35. package/lib/types/index.d.ts.map +1 -0
  36. package/lib/types/lesson.d.ts +295 -0
  37. package/lib/types/lesson.d.ts.map +1 -0
  38. package/lib/types/prompt.d.ts +85 -0
  39. package/lib/types/prompt.d.ts.map +1 -0
  40. package/lib/types/state.d.ts +627 -0
  41. package/lib/types/state.d.ts.map +1 -0
  42. package/lib/types/tools.d.ts +38 -0
  43. package/lib/types/tools.d.ts.map +1 -0
  44. package/lib/types/trust-fence.d.ts +53 -0
  45. package/lib/types/trust-fence.d.ts.map +1 -0
  46. package/lib/types/udt.d.ts +95 -0
  47. package/lib/types/udt.d.ts.map +1 -0
  48. package/lib/types/vocabulary.d.ts +162 -0
  49. package/lib/types/vocabulary.d.ts.map +1 -0
  50. package/lib/udt.js +141 -0
  51. package/lib/udt.js.map +1 -0
  52. package/lib/vocabulary.js +182 -0
  53. package/lib/vocabulary.js.map +1 -0
  54. package/package.json +104 -0
package/lib/handoff.js ADDED
@@ -0,0 +1,194 @@
1
+ /**
2
+ * The handoff: what happens between pressing Continue and seeing a lesson.
3
+ *
4
+ * Moving from one node to another is not instant — the runtime records a focus,
5
+ * an agent is woken, a model turn runs, and only then does a lesson exist. That
6
+ * gap used to be silent, which is the worst thing a gap can be: the learner
7
+ * cannot tell "working" from "broken", and a seven-minute turn is
8
+ * indistinguishable from a dead button.
9
+ *
10
+ * So the handoff is a **persisted record with timestamps**, and everything the
11
+ * UI shows about progress is derived from it. Persisting it is what makes the
12
+ * behaviours that matter possible at all:
13
+ *
14
+ * - **idempotent** — keyed by target node, so pressing Continue twice is one
15
+ * handoff, not two;
16
+ * - **refresh-proof** — a reload re-reads the same record;
17
+ * - **restart-proof** — the record outlives the process;
18
+ * - **retryable** — the attempt count and timestamps say whether the tutor
19
+ * ever answered, so a retry is informed rather than blind;
20
+ * - **non-destructive on timeout** — a timeout is a statement about the
21
+ * *wait*, never about the focus. The record goes stale; the focus stands.
22
+ *
23
+ * The timestamps are also the measurement. `requestedAt → focusRecordedAt →
24
+ * promptedAt → firstActivityAt → lessonAt → observedAt` is the whole chain, so
25
+ * "where did the time go" is a question with an answer rather than an
26
+ * argument.
27
+ */
28
+ import { z } from 'zod';
29
+ /**
30
+ * Where a handoff has got to.
31
+ *
32
+ * `requested` the button was pressed; nothing has been persisted yet
33
+ * `prompted` the focus is recorded and the tutor was woken
34
+ * `working` the agent has produced its first event since being woken
35
+ * `ready` a lesson exists for the target node
36
+ * `failed` the tutor could not be reached
37
+ *
38
+ * There is deliberately no `timeout` status: a timeout is the *reader's*
39
+ * judgement about a record that has stopped moving, and storing it would mean
40
+ * writing to a record because time passed. The UI derives it from `updatedAt`.
41
+ */
42
+ export const HANDOFF_STATUSES = ['requested', 'prompted', 'working', 'ready', 'failed'];
43
+ export const HandoffStatusSchema = z.enum(HANDOFF_STATUSES);
44
+ export const HandoffSchema = z.object({
45
+ /** The node being moved to. The key, which is what makes this idempotent. */
46
+ targetNodeId: z.string().min(1),
47
+ courseId: z.string().min(1),
48
+ fromNodeId: z.string().min(1),
49
+ status: HandoffStatusSchema,
50
+ /** How many times the tutor has been asked. A retry increments this. */
51
+ attempts: z.number().int().min(1),
52
+ /**
53
+ * The session the tutor was asked in.
54
+ *
55
+ * Kept so the first-activity listener can ignore events from every other
56
+ * conversation: a busy session elsewhere must not make this handoff look like
57
+ * it has started.
58
+ */
59
+ sessionId: z.string().optional(),
60
+ /** When the request reached the host. */
61
+ requestedAt: z.string(),
62
+ /** When the focus was durably recorded. */
63
+ focusRecordedAt: z.string().optional(),
64
+ /** When `followup` was accepted. */
65
+ promptedAt: z.string().optional(),
66
+ /** The first session event after the prompt — the tutor actually started. */
67
+ firstActivityAt: z.string().optional(),
68
+ /** When a lesson for the target node was written. */
69
+ lessonAt: z.string().optional(),
70
+ /** When a surface first rendered that lesson. */
71
+ observedAt: z.string().optional(),
72
+ failedAt: z.string().optional(),
73
+ failureReason: z.string().optional(),
74
+ updatedAt: z.string(),
75
+ });
76
+ /* -------------------------------------------------------------------------- */
77
+ /* Pure transitions */
78
+ /* -------------------------------------------------------------------------- */
79
+ /** Start (or restart) a handoff for a target node. */
80
+ export function beginHandoff(input) {
81
+ return {
82
+ courseId: input.courseId,
83
+ fromNodeId: input.fromNodeId,
84
+ targetNodeId: input.targetNodeId,
85
+ status: 'requested',
86
+ attempts: (input.previous?.attempts ?? 0) + 1,
87
+ requestedAt: input.now,
88
+ updatedAt: input.now,
89
+ ...(input.sessionId === undefined ? {} : { sessionId: input.sessionId }),
90
+ };
91
+ }
92
+ export function withFocusRecorded(record, now) {
93
+ return { ...record, focusRecordedAt: now, updatedAt: now };
94
+ }
95
+ export function withPrompted(record, now) {
96
+ return { ...record, status: 'prompted', promptedAt: now, updatedAt: now };
97
+ }
98
+ export function withPromptFailure(record, now, reason) {
99
+ return { ...record, status: 'failed', failedAt: now, failureReason: reason, updatedAt: now };
100
+ }
101
+ /**
102
+ * Record the tutor's first sign of activity.
103
+ *
104
+ * Only the first one is kept: this answers "how long until it started", and a
105
+ * later event would answer a different question.
106
+ */
107
+ export function withActivity(record, now) {
108
+ if (record.firstActivityAt !== undefined)
109
+ return record;
110
+ return { ...record, status: 'working', firstActivityAt: now, updatedAt: now };
111
+ }
112
+ export function withLesson(record, now) {
113
+ return {
114
+ ...record,
115
+ status: 'ready',
116
+ lessonAt: record.lessonAt ?? now,
117
+ firstActivityAt: record.firstActivityAt ?? now,
118
+ updatedAt: now,
119
+ };
120
+ }
121
+ export function withObserved(record, now) {
122
+ if (record.observedAt !== undefined)
123
+ return record;
124
+ return { ...record, observedAt: now, updatedAt: now };
125
+ }
126
+ /**
127
+ * How long a surface waits before calling a quiet handoff stalled.
128
+ *
129
+ * Long, on purpose: a real model turn on a large node has taken minutes here,
130
+ * and telling a learner the tutor is broken while it is still thinking is worse
131
+ * than a longer wait. A stall is a prompt to retry, never a verdict.
132
+ */
133
+ export const STALL_AFTER_MS = 150_000;
134
+ function offset(from, requestedMs) {
135
+ if (from === undefined)
136
+ return undefined;
137
+ const at = Date.parse(from);
138
+ return Number.isNaN(at) ? undefined : Math.max(0, at - requestedMs);
139
+ }
140
+ /**
141
+ * Derive what a surface should show.
142
+ *
143
+ * `stalled` is computed, not stored: a record that has stopped moving is not a
144
+ * different record, and writing to it because time passed would make the store
145
+ * a clock.
146
+ *
147
+ * @param record - the handoff.
148
+ * @param nowMs - current time, injected so this is testable.
149
+ * @param hasLesson - whether a lesson for the target node already exists.
150
+ * @returns the phase, a label, the elapsed time and the stage breakdown.
151
+ */
152
+ export function handoffView(record, nowMs, hasLesson) {
153
+ const stages = {};
154
+ const requested = Date.parse(record.requestedAt);
155
+ const add = (name, from) => {
156
+ if (Number.isNaN(requested))
157
+ return;
158
+ const offsetMs = offset(from, requested);
159
+ if (offsetMs !== undefined)
160
+ stages[name] = offsetMs;
161
+ };
162
+ add('toFocusRecorded', record.focusRecordedAt);
163
+ add('toPrompted', record.promptedAt);
164
+ add('toFirstActivity', record.firstActivityAt);
165
+ add('toLesson', record.lessonAt);
166
+ add('toObserved', record.observedAt);
167
+ const elapsedMs = Math.max(0, nowMs - (Number.isNaN(requested) ? nowMs : requested));
168
+ const base = { targetNodeId: record.targetNodeId, elapsedMs, attempts: record.attempts, stages };
169
+ if (record.status === 'failed') {
170
+ return {
171
+ ...base,
172
+ phase: 'failed',
173
+ label: 'Could not reach the tutor',
174
+ ...(record.failureReason === undefined ? {} : { detail: record.failureReason }),
175
+ };
176
+ }
177
+ if (hasLesson || record.status === 'ready') {
178
+ return { ...base, phase: 'lesson-ready', label: 'Lesson ready' };
179
+ }
180
+ if (record.status === 'working' || record.firstActivityAt !== undefined) {
181
+ if (nowMs - Date.parse(record.updatedAt) > STALL_AFTER_MS) {
182
+ return { ...base, phase: 'stalled', label: 'The tutor has gone quiet', detail: 'Still working, or stopped — you can ask again.' };
183
+ }
184
+ return { ...base, phase: 'tutor-working', label: 'Tutor working…' };
185
+ }
186
+ if (record.status === 'prompted') {
187
+ if (nowMs - Date.parse(record.updatedAt) > STALL_AFTER_MS) {
188
+ return { ...base, phase: 'stalled', label: 'No answer from the tutor yet', detail: 'You can ask again without losing your place.' };
189
+ }
190
+ return { ...base, phase: 'tutor-requested', label: 'Tutor requested…' };
191
+ }
192
+ return { ...base, phase: 'focus-recorded', label: 'Focus recorded' };
193
+ }
194
+ //# sourceMappingURL=handoff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoff.js","sourceRoot":"","sources":["../src/handoff.ts"],"names":[],"mappings":"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"}
package/lib/index.js ADDED
@@ -0,0 +1,186 @@
1
+ /**
2
+ * dsh-diagnostic-tutor — DeepSeek Harness plugin entry point.
3
+ *
4
+ * A DSH plugin is a module exporting `apply(ctx, config)`. Cordis calls it at
5
+ * load time and everything registered through `ctx` is disposed automatically
6
+ * on unload.
7
+ *
8
+ * ---------------------------------------------------------------------------
9
+ * v0.0.3 scope (see docs/planning/PLAN.md §7)
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.
15
+ *
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
20
+ *
21
+ * ---------------------------------------------------------------------------
22
+ * Cross-version discipline (see PLAN.md 2.12 #9 / #16)
23
+ * ---------------------------------------------------------------------------
24
+ * Three harness cohorts can be resolvable at once (the running host, the
25
+ * shared profile fallback, and this package's own store). Therefore:
26
+ *
27
+ * 1. never `export default` — Cordis' `unwrapExports` prefers `.default` and
28
+ * would silently DROP `inject`, so only named exports appear here.
29
+ * 2. every *type* coming from `@deepseek-ai/*` is imported with `import type`
30
+ * (enforced by `verbatimModuleSyntax`), so no second runtime copy is
31
+ * resolved for anything that merely describes a shape.
32
+ * 3. services and instances — `tools`, `storageDomain`, `skills`,
33
+ * `systemPrompt`, the opened `Domain` — are always taken from `ctx` via
34
+ * `ctx.get(...)`, never constructed and never `instanceof`-checked. A
35
+ * cross-cohort copy would make such a check silently false.
36
+ *
37
+ * Pure *builder* helpers (`defineTool`, `defineDomain`, `domainTable`) are the
38
+ * one documented exception: they take plain data and return plain data, so
39
+ * they are imported as values. See the header of `state.ts`.
40
+ */
41
+ import { API_PREFIX, registerApi } from './api.js';
42
+ import { withActivity } from './handoff.js';
43
+ import { promptSession } from './prompt.js';
44
+ import { UDT_DOMAIN_NAME, openUdState } from './state.js';
45
+ import { registerTools } from './tools.js';
46
+ import { describeUdtStatus, detectUdtSkill } from './udt.js';
47
+ /**
48
+ * Plugin module name. Stable kebab-case, equal to the loader row `id` in
49
+ * cordis.patch.yml and distinct from the npm package name.
50
+ */
51
+ export const name = 'diagnostic-tutor';
52
+ /**
53
+ * Services this plugin requires before it may load.
54
+ *
55
+ * Only services the standard profiles guarantee are declared here — both come
56
+ * from `@deepseek-ai/dsh-base`. Everything optional (the skill catalog, the
57
+ * system prompt) is resolved lazily with `ctx.get(...)`, so a profile without
58
+ * them loses the corresponding enhancement instead of leaving the whole plugin
59
+ * tree pending. A pending plugin prints nothing at all, which is hard to
60
+ * diagnose.
61
+ */
62
+ export const inject = ['tools', 'storageDomain'];
63
+ /**
64
+ * Load the plugin.
65
+ *
66
+ * `apply` is async, and Cordis keeps the fiber in `LOADING` until the returned
67
+ * promise settles — so `await ctx.plugin(...)` genuinely waits for the storage
68
+ * domain to be open, for first-run initialization to be durable, and for the
69
+ * tools to be registered. A nested `ctx.inject(...)` would return its own
70
+ * fiber, letting the outer fiber report ACTIVE while the domain was still
71
+ * opening.
72
+ *
73
+ * @param ctx - the Cordis context the loader hands to this plugin.
74
+ */
75
+ export async function apply(ctx) {
76
+ ctx.logger.debug('[diagnostic-tutor] plugin loading');
77
+ const tools = ctx.get('tools');
78
+ const facility = ctx.get('storageDomain');
79
+ if (!tools || !facility) {
80
+ // Never degrade silently: a missing seam is a configuration error.
81
+ ctx.logger.error(`[diagnostic-tutor] missing required service(s):${tools ? '' : ' tools'}${facility ? '' : ' storageDomain'}`);
82
+ return;
83
+ }
84
+ // A storage failure must not take the whole plugin tree down with it. The
85
+ // loader treats a rejection from `apply` as a fatal composition error, so an
86
+ // unreadable, partial or version-mismatched store file would otherwise stop
87
+ // every unrelated plugin in the profile from loading — and would do it
88
+ // silently, because nothing is registered to report it. Degrade instead:
89
+ // report the cause loudly, register nothing, and stay inert.
90
+ //
91
+ // First-run initialization is inside this guard on purpose. It is the first
92
+ // *read* of the stored document, so it is where a partial file actually
93
+ // throws — a domain can open successfully and still fail on the first record
94
+ // that does not match its schema.
95
+ let state;
96
+ try {
97
+ state = await openUdState(facility);
98
+ // First run writes the learner record; later runs leave it untouched.
99
+ await state.ensureLearner(new Date().toISOString());
100
+ }
101
+ catch (error) {
102
+ ctx.logger.error(`[diagnostic-tutor] could not open storage domain "${UDT_DOMAIN_NAME}": ${error.message}. ` +
103
+ 'The plugin is loaded but inert — no tools were registered. ' +
104
+ 'This usually means a stored document written by an incompatible version, or one edited by hand.');
105
+ return;
106
+ }
107
+ // Unloading must leave no residue: this disposer closes the domain (which
108
+ // rejects new writes, drains queued ones, and releases the backend unit) and
109
+ // Cordis awaits the returned promise before the plugin counts as unloaded.
110
+ ctx.effect(() => () => state.close());
111
+ // Is the teaching brain present? Resolved lazily and never fatal: the
112
+ // runtime is useful without it, and a missing skill is a degraded mode, not
113
+ // an error. The result stays internal — it is logged, never surfaced to a
114
+ // learner through a tool, because the skill's own protocol forbids naming
115
+ // its files and versions in learner-facing text.
116
+ //
117
+ // Nothing is installed on the strength of it. Until v0.0.8 this gated a
118
+ // runtime-semantics system-prompt section that explained the storage model to
119
+ // a skill whose guardrails read as forbidding it; UDT v2.1's
120
+ // `learning_runtime_contract.md` now says all of that in the skill's own
121
+ // words, so the bridge was deleted rather than kept as a second voice.
122
+ const udt = await detectUdtSkill(ctx.get('skills'));
123
+ if (udt.available) {
124
+ ctx.logger.debug(`[diagnostic-tutor] teaching brain: ${describeUdtStatus(udt)}`);
125
+ }
126
+ else {
127
+ // Not debug: without the skill nothing will ever be taught, and a learner
128
+ // staring at an empty panel deserves a reason in the log. The panel says
129
+ // the same thing in its own words; neither names the skill's files or
130
+ // version, because its protocol forbids that in learner-facing text.
131
+ ctx.logger.warn(`[diagnostic-tutor] no teaching brain in this profile: ${describeUdtStatus(udt)}. ` +
132
+ 'The runtime will record and display state, but no lesson will be written until the ' +
133
+ 'Universal Diagnostic Tutor skill is installed for this workspace.');
134
+ }
135
+ // Mount the browser API if this profile has a web surface.
136
+ //
137
+ // Resolved eagerly first: a profile's web server is mounted by the base and
138
+ // web-app bundles, which always precede a user bundle, so it is present by
139
+ // the time this runs. The deferred `ctx.inject` is the fallback for the rare
140
+ // case where the service arrives later — awaiting the outer fiber would not
141
+ // wait for that nested fiber, so preferring the eager path also keeps the
142
+ // load deterministic for callers and tests.
143
+ const mountApi = (server) => {
144
+ if (!server)
145
+ return;
146
+ ctx.effect(() => registerApi(server, {
147
+ state,
148
+ prompt: (sessionId, text) => promptSession(ctx, sessionId, text),
149
+ // Three states, because two would be a lie. `null` means this scope
150
+ // cannot see the catalog well enough to say — the normal case in a web
151
+ // profile, where skills are mounted per agent — and a surface must not
152
+ // turn that into "no tutor installed".
153
+ teachingBrain: udt.available ? true : udt.catalogVisible ? false : null,
154
+ }));
155
+ ctx.logger.debug(`[diagnostic-tutor] browser API mounted at ${API_PREFIX}`);
156
+ };
157
+ const webServer = ctx.get('webServer');
158
+ if (webServer)
159
+ mountApi(webServer);
160
+ else
161
+ ctx.inject(['webServer'], (webCtx) => mountApi(webCtx.get('webServer')));
162
+ // First sign of life from the tutor.
163
+ //
164
+ // The handoff records when the tutor was asked; this records when it actually
165
+ // started, which is the difference between "waiting" and "nothing is
166
+ // happening". Only the first event counts, and only from the session the
167
+ // handoff asked in, so a busy conversation elsewhere cannot make a quiet
168
+ // handoff look alive.
169
+ ctx.on('session/event', (session, event) => {
170
+ const handoff = state.activeHandoff();
171
+ if (handoff === undefined || handoff.status !== 'prompted')
172
+ return;
173
+ if (handoff.sessionId !== undefined && String(session.id) !== handoff.sessionId)
174
+ return;
175
+ void event;
176
+ // Atomic, and re-checked inside the transform: the lesson write races this
177
+ // listener, and a plain read-modify-write here would clobber a handoff that
178
+ // had already become ready.
179
+ void state
180
+ .updateHandoff(handoff.targetNodeId, (current) => current.status === 'prompted' ? withActivity(current, new Date().toISOString()) : current)
181
+ .catch(() => { });
182
+ });
183
+ registerTools({ tools }, state);
184
+ ctx.logger.debug(`[diagnostic-tutor] ready — domain "${state.name}" v${state.version}, 4 tools registered`);
185
+ }
186
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}