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.
- package/LICENSE +21 -0
- package/README.md +658 -0
- package/cordis.patch.yml +23 -0
- package/lib/api.js +413 -0
- package/lib/api.js.map +1 -0
- package/lib/client.js +2029 -0
- package/lib/client.js.map +1 -0
- package/lib/contract.js +14 -0
- package/lib/contract.js.map +1 -0
- package/lib/diagnosis.js +224 -0
- package/lib/diagnosis.js.map +1 -0
- package/lib/handoff.js +194 -0
- package/lib/handoff.js.map +1 -0
- package/lib/index.js +186 -0
- package/lib/index.js.map +1 -0
- package/lib/lesson.js +285 -0
- package/lib/lesson.js.map +1 -0
- package/lib/prompt.js +96 -0
- package/lib/prompt.js.map +1 -0
- package/lib/state.js +500 -0
- package/lib/state.js.map +1 -0
- package/lib/tools.js +994 -0
- package/lib/tools.js.map +1 -0
- package/lib/trust-fence.js +101 -0
- package/lib/trust-fence.js.map +1 -0
- package/lib/types/api.d.ts +62 -0
- package/lib/types/api.d.ts.map +1 -0
- package/lib/types/contract.d.ts +147 -0
- package/lib/types/contract.d.ts.map +1 -0
- package/lib/types/diagnosis.d.ts +116 -0
- package/lib/types/diagnosis.d.ts.map +1 -0
- package/lib/types/handoff.d.ts +141 -0
- package/lib/types/handoff.d.ts.map +1 -0
- package/lib/types/index.d.ts +71 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/lesson.d.ts +295 -0
- package/lib/types/lesson.d.ts.map +1 -0
- package/lib/types/prompt.d.ts +85 -0
- package/lib/types/prompt.d.ts.map +1 -0
- package/lib/types/state.d.ts +627 -0
- package/lib/types/state.d.ts.map +1 -0
- package/lib/types/tools.d.ts +38 -0
- package/lib/types/tools.d.ts.map +1 -0
- package/lib/types/trust-fence.d.ts +53 -0
- package/lib/types/trust-fence.d.ts.map +1 -0
- package/lib/types/udt.d.ts +95 -0
- package/lib/types/udt.d.ts.map +1 -0
- package/lib/types/vocabulary.d.ts +162 -0
- package/lib/types/vocabulary.d.ts.map +1 -0
- package/lib/udt.js +141 -0
- package/lib/udt.js.map +1 -0
- package/lib/vocabulary.js +182 -0
- package/lib/vocabulary.js.map +1 -0
- package/package.json +104 -0
package/lib/lesson.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learning Block and Lesson schemas.
|
|
3
|
+
*
|
|
4
|
+
* The data schema lives here (host side, zod-validated at the durable
|
|
5
|
+
* boundary). The **renderers live in the client half** and are keyed by
|
|
6
|
+
* `block.type`, so adding Formula, Code, Comparison, Practice or Resource later
|
|
7
|
+
* means adding one renderer entry — never rewriting the lesson renderer.
|
|
8
|
+
*
|
|
9
|
+
* A block is `{ id, type, content, metadata? }`: the envelope is uniform and
|
|
10
|
+
* `content` is shaped per type. The envelope is what the renderer registry
|
|
11
|
+
* dispatches on; `content` is what a renderer understands. An unrecognised
|
|
12
|
+
* `type` is not an error — the client falls back to a readable placeholder, so
|
|
13
|
+
* a lesson authored by a newer host still renders in an older browser half.
|
|
14
|
+
*
|
|
15
|
+
* ---------------------------------------------------------------------------
|
|
16
|
+
* What v0.0.4 deliberately does NOT do
|
|
17
|
+
* ---------------------------------------------------------------------------
|
|
18
|
+
* There is no model-generated lesson here. `buildPrototypeLesson` is a
|
|
19
|
+
* **deterministic projection of state the runtime already holds** — the node's
|
|
20
|
+
* own title, relation, state and evidence — assembled into the four block
|
|
21
|
+
* types. It exists to prove the schema and the renderer end to end.
|
|
22
|
+
*
|
|
23
|
+
* That is why every lesson carries `origin`. A prototype lesson can never be
|
|
24
|
+
* mistaken for teaching content, and when real generation arrives it will be
|
|
25
|
+
* distinguishable in the stored record rather than by convention.
|
|
26
|
+
*/
|
|
27
|
+
import { z } from 'zod';
|
|
28
|
+
/* -------------------------------------------------------------------------- */
|
|
29
|
+
/* Size limits */
|
|
30
|
+
/* -------------------------------------------------------------------------- */
|
|
31
|
+
/**
|
|
32
|
+
* A teaching unit is a few blocks, not a chapter.
|
|
33
|
+
*
|
|
34
|
+
* The cap is what makes "never generate a whole course in one call" structural
|
|
35
|
+
* rather than advisory — the same reasoning as the diagnosis map's node caps.
|
|
36
|
+
* Six leaves room for a real unit (orient, example, diagram, check) plus a
|
|
37
|
+
* correction.
|
|
38
|
+
*/
|
|
39
|
+
export const MAX_BLOCKS_PER_UPDATE = 6;
|
|
40
|
+
/**
|
|
41
|
+
* A lesson accumulates across a session, so its ceiling is higher than one
|
|
42
|
+
* call's — but it is still a ceiling. Past this the lesson has stopped being a
|
|
43
|
+
* learning surface and become a document.
|
|
44
|
+
*/
|
|
45
|
+
export const MAX_BLOCKS_PER_LESSON = 24;
|
|
46
|
+
/**
|
|
47
|
+
* Per-field length caps, enforced by zod at the durable boundary.
|
|
48
|
+
*
|
|
49
|
+
* Structural validation alone would accept a single 5 MB text block; these make
|
|
50
|
+
* "oversized payload" a rejection rather than a rendering problem.
|
|
51
|
+
*/
|
|
52
|
+
export const MAX_TEXT_CHARS = 6000;
|
|
53
|
+
export const MAX_DIAGRAM_CHARS = 4000;
|
|
54
|
+
export const MAX_PROMPT_CHARS = 1000;
|
|
55
|
+
export const MAX_TITLE_CHARS = 200;
|
|
56
|
+
export const MAX_STEP_CHARS = 400;
|
|
57
|
+
export const MAX_STEPS = 12;
|
|
58
|
+
/* -------------------------------------------------------------------------- */
|
|
59
|
+
/* Blocks */
|
|
60
|
+
/* -------------------------------------------------------------------------- */
|
|
61
|
+
/** Fields every block carries, whatever its type. */
|
|
62
|
+
const blockBase = {
|
|
63
|
+
id: z.string().min(1),
|
|
64
|
+
/** Renderer-free annotations; never interpreted by the schema. */
|
|
65
|
+
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
66
|
+
};
|
|
67
|
+
export const TextBlockSchema = z.object({
|
|
68
|
+
...blockBase,
|
|
69
|
+
type: z.literal('text'),
|
|
70
|
+
content: z.object({
|
|
71
|
+
/** Markdown. Math uses the skill's convention: `\(...\)` / `\[...\]`. */
|
|
72
|
+
md: z.string().min(1).max(MAX_TEXT_CHARS),
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
export const ExampleBlockSchema = z.object({
|
|
76
|
+
...blockBase,
|
|
77
|
+
type: z.literal('example'),
|
|
78
|
+
content: z.object({
|
|
79
|
+
title: z.string().min(1).max(MAX_TITLE_CHARS),
|
|
80
|
+
steps: z.array(z.string().min(1).max(MAX_STEP_CHARS)).min(1).max(MAX_STEPS),
|
|
81
|
+
takeaway: z.string().min(1).max(MAX_STEP_CHARS).optional(),
|
|
82
|
+
}),
|
|
83
|
+
});
|
|
84
|
+
export const DiagramBlockSchema = z.object({
|
|
85
|
+
...blockBase,
|
|
86
|
+
type: z.literal('diagram'),
|
|
87
|
+
content: z.object({
|
|
88
|
+
/**
|
|
89
|
+
* `ascii` renders as preformatted text and needs no dependency.
|
|
90
|
+
* `mermaid` is carried by the schema from the start so a lesson authored
|
|
91
|
+
* for a mermaid-capable client stays valid here; this client renders it as
|
|
92
|
+
* a labelled source block rather than pulling in a renderer.
|
|
93
|
+
*/
|
|
94
|
+
format: z.enum(['ascii', 'mermaid']),
|
|
95
|
+
spec: z.string().min(1).max(MAX_DIAGRAM_CHARS),
|
|
96
|
+
caption: z.string().min(1).max(MAX_TITLE_CHARS).optional(),
|
|
97
|
+
}),
|
|
98
|
+
});
|
|
99
|
+
export const CheckBlockSchema = z.object({
|
|
100
|
+
...blockBase,
|
|
101
|
+
type: z.literal('check'),
|
|
102
|
+
content: z.object({
|
|
103
|
+
prompt: z.string().min(1).max(MAX_PROMPT_CHARS),
|
|
104
|
+
/** What the check is really probing; a hint for the teaching brain. */
|
|
105
|
+
expect: z.enum(['reasoning', 'answer']).optional(),
|
|
106
|
+
hint: z.string().min(1).max(MAX_PROMPT_CHARS).optional(),
|
|
107
|
+
}),
|
|
108
|
+
});
|
|
109
|
+
/** Every block type this version understands. */
|
|
110
|
+
export const BLOCK_SCHEMAS = [
|
|
111
|
+
TextBlockSchema,
|
|
112
|
+
ExampleBlockSchema,
|
|
113
|
+
DiagramBlockSchema,
|
|
114
|
+
CheckBlockSchema,
|
|
115
|
+
];
|
|
116
|
+
export const BlockSchema = z.discriminatedUnion('type', [
|
|
117
|
+
TextBlockSchema,
|
|
118
|
+
ExampleBlockSchema,
|
|
119
|
+
DiagramBlockSchema,
|
|
120
|
+
CheckBlockSchema,
|
|
121
|
+
]);
|
|
122
|
+
/** The block types this build can render, for diagnostics and tests. */
|
|
123
|
+
export const SUPPORTED_BLOCK_TYPES = BLOCK_SCHEMAS.map((schema) => schema.shape.type.value);
|
|
124
|
+
/* -------------------------------------------------------------------------- */
|
|
125
|
+
/* Lesson */
|
|
126
|
+
/* -------------------------------------------------------------------------- */
|
|
127
|
+
/**
|
|
128
|
+
* `tutor` is what the runtime writes now: blocks the teaching brain submitted
|
|
129
|
+
* through `udt_lesson_update`.
|
|
130
|
+
*
|
|
131
|
+
* `prototype` is retained rather than removed because v0.0.4 wrote records with
|
|
132
|
+
* it, and the domain version cannot be bumped to invalidate them (the `single`
|
|
133
|
+
* layout rejects a version mismatch outright — see `state.ts`). It is no longer
|
|
134
|
+
* produced by the runtime.
|
|
135
|
+
*/
|
|
136
|
+
export const LESSON_ORIGINS = ['prototype', 'tutor'];
|
|
137
|
+
export const LessonOriginSchema = z.enum(LESSON_ORIGINS);
|
|
138
|
+
export const LessonSchema = z.object({
|
|
139
|
+
id: z.string().min(1),
|
|
140
|
+
courseId: z.string().min(1),
|
|
141
|
+
nodeId: z.string().min(1),
|
|
142
|
+
title: z.string().min(1),
|
|
143
|
+
blocks: z.array(BlockSchema).min(1).max(MAX_BLOCKS_PER_LESSON),
|
|
144
|
+
/** Who produced the blocks: the teaching brain, or the v0.0.4 scaffold. */
|
|
145
|
+
origin: LessonOriginSchema,
|
|
146
|
+
createdAt: z.string(),
|
|
147
|
+
updatedAt: z.string(),
|
|
148
|
+
});
|
|
149
|
+
/* -------------------------------------------------------------------------- */
|
|
150
|
+
/* Deterministic prototype lesson */
|
|
151
|
+
/* -------------------------------------------------------------------------- */
|
|
152
|
+
/** Render the node's position in the map as an indented tree. */
|
|
153
|
+
function asciiTree(nodes, focusId) {
|
|
154
|
+
const childrenOf = new Map();
|
|
155
|
+
for (const node of nodes) {
|
|
156
|
+
const bucket = childrenOf.get(node.parentId) ?? [];
|
|
157
|
+
bucket.push(node);
|
|
158
|
+
childrenOf.set(node.parentId, bucket);
|
|
159
|
+
}
|
|
160
|
+
const lines = [];
|
|
161
|
+
const walk = (node, depth, isLast) => {
|
|
162
|
+
const marker = depth === 0 ? '' : isLast ? '└─ ' : '├─ ';
|
|
163
|
+
const focus = node.id === focusId ? ' ◀ this node' : '';
|
|
164
|
+
lines.push(`${' '.repeat(Math.max(0, depth - 1))}${marker}${node.title} [${node.state}]${focus}`);
|
|
165
|
+
const children = childrenOf.get(node.id) ?? [];
|
|
166
|
+
children.forEach((child, index) => walk(child, depth + 1, index === children.length - 1));
|
|
167
|
+
};
|
|
168
|
+
for (const root of childrenOf.get(undefined) ?? [])
|
|
169
|
+
walk(root, 0, true);
|
|
170
|
+
return lines.join('\n');
|
|
171
|
+
}
|
|
172
|
+
/** One readable line per evidence entry, newest last. */
|
|
173
|
+
function evidenceSteps(node) {
|
|
174
|
+
if (node.evidence.length === 0) {
|
|
175
|
+
return ['No observation has been recorded against this node yet.'];
|
|
176
|
+
}
|
|
177
|
+
return node.evidence.map((entry) => {
|
|
178
|
+
const readiness = entry.readiness === undefined ? '' : ` — readiness: ${entry.readiness}`;
|
|
179
|
+
const note = entry.note === undefined ? '' : `: ${entry.note}`;
|
|
180
|
+
return `${entry.kind}${readiness}${note}`;
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
/** How a node came to be on the map, in the map's own terms. */
|
|
184
|
+
function relationExplanation(node) {
|
|
185
|
+
switch (node.relation) {
|
|
186
|
+
case 'goal':
|
|
187
|
+
return 'This is the goal itself — the frame the rest of the map hangs from. A goal is never marked confirmed, because it is not a claim about what you can do.';
|
|
188
|
+
case 'prerequisite':
|
|
189
|
+
return 'This was recorded as a **prerequisite**: something diagnosed as blocking the node above it.';
|
|
190
|
+
case 'part-of':
|
|
191
|
+
return 'This is a **part of** the node above it — a component the diagnosis separated out.';
|
|
192
|
+
case 'related':
|
|
193
|
+
return 'This is **related** to the node above it: useful context, but not a component and not a blocker.';
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Build a prototype lesson for one node.
|
|
198
|
+
*
|
|
199
|
+
* **Not on the runtime path any more.** v0.0.5 teaches through the tutor, and
|
|
200
|
+
* `udt_lesson_update` is how blocks arrive. This builder is kept for two
|
|
201
|
+
* honest reasons: v0.0.4 wrote `origin: 'prototype'` records that must still
|
|
202
|
+
* parse, and it is a convenient deterministic fixture for the preview and the
|
|
203
|
+
* block-renderer tests.
|
|
204
|
+
*
|
|
205
|
+
* It projects stored state into the four block types — it does not invent
|
|
206
|
+
* teaching content.
|
|
207
|
+
*
|
|
208
|
+
* @param input.course - the course the node belongs to.
|
|
209
|
+
* @param input.node - the node to build for.
|
|
210
|
+
* @param input.nodes - every node of the course, for the map diagram.
|
|
211
|
+
* @param input.now - ISO timestamp.
|
|
212
|
+
* @returns a validated lesson record.
|
|
213
|
+
*/
|
|
214
|
+
export function buildPrototypeLesson(input) {
|
|
215
|
+
const { course, node, nodes, now } = input;
|
|
216
|
+
const evidenceCount = node.evidence.length;
|
|
217
|
+
return LessonSchema.parse({
|
|
218
|
+
id: `${node.id}:prototype`,
|
|
219
|
+
courseId: course.id,
|
|
220
|
+
nodeId: node.id,
|
|
221
|
+
title: node.title,
|
|
222
|
+
origin: 'prototype',
|
|
223
|
+
createdAt: now,
|
|
224
|
+
updatedAt: now,
|
|
225
|
+
blocks: [
|
|
226
|
+
{
|
|
227
|
+
id: 'where',
|
|
228
|
+
type: 'text',
|
|
229
|
+
content: {
|
|
230
|
+
md: `**${node.title}** is on your map for *${course.title}* in the state \`${node.state}\`, ` +
|
|
231
|
+
`with ${evidenceCount} recorded observation${evidenceCount === 1 ? '' : 's'}.\n\n` +
|
|
232
|
+
relationExplanation(node),
|
|
233
|
+
},
|
|
234
|
+
metadata: { role: 'orientation' },
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: 'evidence',
|
|
238
|
+
type: 'example',
|
|
239
|
+
content: {
|
|
240
|
+
title: 'What the runtime has actually recorded',
|
|
241
|
+
steps: evidenceSteps(node),
|
|
242
|
+
takeaway: 'A node only moves off `unconfirmed` because something was observed — never because it was asserted.',
|
|
243
|
+
},
|
|
244
|
+
metadata: { role: 'evidence' },
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: 'map',
|
|
248
|
+
type: 'diagram',
|
|
249
|
+
content: {
|
|
250
|
+
format: 'ascii',
|
|
251
|
+
spec: asciiTree(nodes, node.id),
|
|
252
|
+
caption: `Where ${node.title} sits in the diagnosis map.`,
|
|
253
|
+
},
|
|
254
|
+
metadata: { role: 'context' },
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: 'check',
|
|
258
|
+
type: 'check',
|
|
259
|
+
content: {
|
|
260
|
+
prompt: `Before any teaching starts: in your own words, what do you already know about **${node.title}**, ` +
|
|
261
|
+
'and where does it stop being clear?',
|
|
262
|
+
expect: 'reasoning',
|
|
263
|
+
hint: 'A rough answer is more useful than a polished one — the gaps are the point.',
|
|
264
|
+
},
|
|
265
|
+
metadata: { role: 'check', stopAndWait: true },
|
|
266
|
+
},
|
|
267
|
+
],
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Read a block's type without trusting the value.
|
|
272
|
+
*
|
|
273
|
+
* Used by the client's renderer registry so an unknown type degrades instead of
|
|
274
|
+
* throwing.
|
|
275
|
+
*
|
|
276
|
+
* @param value - any parsed block-shaped value.
|
|
277
|
+
* @returns the declared type, or `undefined`.
|
|
278
|
+
*/
|
|
279
|
+
export function blockTypeOf(value) {
|
|
280
|
+
if (value === null || typeof value !== 'object')
|
|
281
|
+
return undefined;
|
|
282
|
+
const type = value.type;
|
|
283
|
+
return typeof type === 'string' ? type : undefined;
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=lesson.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lesson.js","sourceRoot":"","sources":["../src/lesson.ts"],"names":[],"mappings":"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"}
|
package/lib/prompt.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Waking the teaching brain.
|
|
3
|
+
*
|
|
4
|
+
* "Start learning" is a UI act, but teaching happens in the chat — so the panel
|
|
5
|
+
* needs a way to tell the tutor that a node is now in focus. This module is
|
|
6
|
+
* that hand-off: it composes one ordinary user-role turn from the plugin and
|
|
7
|
+
* delivers it to the agent that owns the learner's session.
|
|
8
|
+
*
|
|
9
|
+
* Why a user-role message rather than injected context: `agent.inject()` adds
|
|
10
|
+
* model-visible context but **does not wake an idle agent**, so nothing would
|
|
11
|
+
* happen until the learner typed something. `followup()` opens a turn, which is
|
|
12
|
+
* exactly the intent of pressing the button. `@deepseek-ai/dsh-command-goal`
|
|
13
|
+
* and `dsh-headless` do the same thing for the same reason.
|
|
14
|
+
*
|
|
15
|
+
* The source is `{ kind: 'diagnostic-tutor' }` — the plugin declares its own
|
|
16
|
+
* kind. Harness 0.1.7 removed the shared catch-all `plugin` kind: a producer now
|
|
17
|
+
* merges its own entry into `MessageSourceMap`, and the augmentation below is
|
|
18
|
+
* that declaration. It is also what makes the value type-check at all, so a
|
|
19
|
+
* future rename cannot drift out of sync silently.
|
|
20
|
+
*
|
|
21
|
+
* Note what this module does **not** do: it does not decide what to teach. It
|
|
22
|
+
* reports which node the learner selected and asks the tutor to begin. Every
|
|
23
|
+
* teaching decision stays with the teaching brain.
|
|
24
|
+
*/
|
|
25
|
+
import { createUserMessage } from '@deepseek-ai/dsh-llm';
|
|
26
|
+
/**
|
|
27
|
+
* Attribution for every turn this plugin opens.
|
|
28
|
+
*
|
|
29
|
+
* Must equal the declared kind below; TypeScript enforces it, because the value
|
|
30
|
+
* is passed where the augmented union is expected.
|
|
31
|
+
*/
|
|
32
|
+
export const PLUGIN_SOURCE = 'diagnostic-tutor';
|
|
33
|
+
/**
|
|
34
|
+
* Compose the turn text.
|
|
35
|
+
*
|
|
36
|
+
* Written in the learner's voice, because it lands in the transcript they are
|
|
37
|
+
* reading: it should look like the button they just pressed, not like a system
|
|
38
|
+
* directive. It states the node and the expected shape of the reply, and stops
|
|
39
|
+
* — the tool descriptions carry the detail about blocks and caps.
|
|
40
|
+
*
|
|
41
|
+
* @param course - the course being learned.
|
|
42
|
+
* @param node - the node put in focus.
|
|
43
|
+
* @returns the message text.
|
|
44
|
+
*/
|
|
45
|
+
export function focusPromptText(course, node) {
|
|
46
|
+
return (`Let's start on **${node.title}** (${node.relation} of ${course.title}).\n\n` +
|
|
47
|
+
'Please teach me this node. Put the teaching itself in the Learning Surface beside the map, ' +
|
|
48
|
+
'end with a check I can answer here in the chat, and stop there.');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Deliver one turn to the agent that owns a session.
|
|
52
|
+
*
|
|
53
|
+
* Never throws for a missing agent: the focus has already been recorded by the
|
|
54
|
+
* time this runs, and the tutor can pick it up on the next turn regardless. A
|
|
55
|
+
* graceful "not prompted" keeps the button useful in every configuration.
|
|
56
|
+
*
|
|
57
|
+
* @param ctx - the plugin's context.
|
|
58
|
+
* @param sessionId - the session the panel is showing, when it knows one.
|
|
59
|
+
* @param text - the turn text.
|
|
60
|
+
* @returns whether a turn was opened, and why not when it was not.
|
|
61
|
+
*/
|
|
62
|
+
export function promptSession(ctx, sessionId, text) {
|
|
63
|
+
if (sessionId === undefined || sessionId.length === 0) {
|
|
64
|
+
return { prompted: false, reason: 'the panel did not report a session' };
|
|
65
|
+
}
|
|
66
|
+
const agents = ctx.get('agents');
|
|
67
|
+
if (!agents) {
|
|
68
|
+
return { prompted: false, reason: 'this profile has no agent registry' };
|
|
69
|
+
}
|
|
70
|
+
// The requested session is the right target. DSH creates an agent lazily —
|
|
71
|
+
// a brand-new session has none until something is said in it — so fall back
|
|
72
|
+
// to the registry only when doing so is unambiguous, and never guess between
|
|
73
|
+
// several conversations.
|
|
74
|
+
let agent = agents.get(sessionId);
|
|
75
|
+
if (!agent) {
|
|
76
|
+
const live = agents.list();
|
|
77
|
+
if (live.length === 1 && live[0] !== undefined) {
|
|
78
|
+
agent = live[0];
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
return {
|
|
82
|
+
prompted: false,
|
|
83
|
+
reason: live.length === 0
|
|
84
|
+
? 'that session has no live agent yet — say anything in the chat and the tutor will pick the focus up'
|
|
85
|
+
: `that session has no live agent, and ${live.length} other conversations are open`,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const target = agent;
|
|
90
|
+
target.followup(createUserMessage({
|
|
91
|
+
content: [{ type: 'text', text }],
|
|
92
|
+
source: { kind: PLUGIN_SOURCE },
|
|
93
|
+
}));
|
|
94
|
+
return { prompted: true };
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=prompt.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prompt.js","sourceRoot":"","sources":["../src/prompt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAA;AAIxD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,kBAAkB,CAAA;AA2B/C;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,eAAe,CAAC,MAAoB,EAAE,IAAgB;IACpE,OAAO,CACL,oBAAoB,IAAI,CAAC,KAAK,OAAO,IAAI,CAAC,QAAQ,OAAO,MAAM,CAAC,KAAK,QAAQ;QAC7E,6FAA6F;QAC7F,iEAAiE,CAClE,CAAA;AACH,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,GAAY,EAAE,SAA6B,EAAE,IAAY;IACrF,IAAI,SAAS,KAAK,SAAS,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,oCAAoC,EAAE,CAAA;IAC1E,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAChC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,oCAAoC,EAAE,CAAA;IAC1E,CAAC;IAED,2EAA2E;IAC3E,4EAA4E;IAC5E,6EAA6E;IAC7E,yBAAyB;IACzB,IAAI,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,SAAkB,CAAC,CAAA;IAC1C,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,EAAE,CAAA;QAC1B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;YAC/C,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;QACjB,CAAC;aAAM,CAAC;YACN,OAAO;gBACL,QAAQ,EAAE,KAAK;gBACf,MAAM,EACJ,IAAI,CAAC,MAAM,KAAK,CAAC;oBACf,CAAC,CAAC,oGAAoG;oBACtG,CAAC,CAAC,uCAAuC,IAAI,CAAC,MAAM,+BAA+B;aACxF,CAAA;QACH,CAAC;IACH,CAAC;IAED,MAAM,MAAM,GAAG,KAAK,CAAA;IACpB,MAAM,CAAC,QAAQ,CACb,iBAAiB,CAAC;QAChB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QACjC,MAAM,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE;KAChC,CAAC,CACH,CAAA;IACD,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAA;AAC3B,CAAC"}
|