eklavya 1.52.5 → 1.53.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/dist/assets/tutor/references/panel.md +4 -0
- package/dist/plugin/.claude-plugin/plugin.json +1 -1
- package/dist/plugin/hooks/panel/model.ts +85 -1
- package/dist/plugin/hooks/panel/register.tsx +134 -45
- package/dist/plugin/skills/tutor/references/panel.md +4 -0
- package/dist/tools/panel_tools.js +1 -1
- package/dist/tools/panel_tools.js.map +1 -1
- package/package.json +1 -1
|
@@ -27,6 +27,10 @@ Make one call to `present_question` instead:
|
|
|
27
27
|
misconception (rows one and three). Grade 3 is not available to a pick: only
|
|
28
28
|
typed words can show a hesitant "right".
|
|
29
29
|
- `more: true` only in an explicit round (the plan has more items after this one, as for `/eklavya:quiz`): the panel then offers a Next button. Leave it out otherwise. Once a round is planned the server tracks it: the last question's `more` is set false for you, and Next arrives as a prompt telling you to call `get_session_quiz_plan` with `resume_round: true`.
|
|
30
|
+
- **Mark the code.** The panel draws the stem, each `description` and the `explanation` as Markdown, so
|
|
31
|
+
put every function, call, class, file and flag in backticks (`` `tester.pumpWidget(widget)` ``,
|
|
32
|
+
`` `shouldRepaint` ``) and nothing else: no headings, lists or emphasis. Option `label`s are drawn as
|
|
33
|
+
plain text, so write them in words and leave the backticks out of them.
|
|
30
34
|
- `explanation`: one line saying why the right option is right. The panel shows
|
|
31
35
|
it after the answer, so it is the whole of your feedback.
|
|
32
36
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eklavya",
|
|
3
3
|
"displayName": "Eklavya",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.53.0",
|
|
5
5
|
"description": "Learn while your agent works. Turns coding-agent generation time into adaptive, Socratic learning grounded in the code being written.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Ajay Kumar"
|
|
@@ -18,14 +18,96 @@ export const EMPTY: PanelState = {
|
|
|
18
18
|
/** Tier names as the dashboard shows them (`TIER` in `assets/dashboard.html`; a test keeps the two equal). */
|
|
19
19
|
export const TIER: Record<number, string> = { 1: 'recall', 2: 'mechanism', 3: 'judgement', 4: 'failure modes', 5: 'design' }
|
|
20
20
|
|
|
21
|
+
/** Brand verdigris (`--vd-300`): the selected card's border and the name. */
|
|
22
|
+
export const BRAND = '#79D5C4'
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* The logo: the Claude mascot (orange) holding an Eklavya bow and arrow (white), drawn on a
|
|
26
|
+
* grid where one cell is a quarter of a text character. Designed in the logo maker; a letter
|
|
27
|
+
* names its colour in LOGO_PALETTE and a dot is empty.
|
|
28
|
+
*/
|
|
29
|
+
export const LOGO_GRID = [
|
|
30
|
+
'.................dd.......',
|
|
31
|
+
'................d..d......',
|
|
32
|
+
'................d...d.....',
|
|
33
|
+
'...aaaaaaaaaaaa.d....d....',
|
|
34
|
+
'...aa.aaaaaa.aa.d.....d...',
|
|
35
|
+
'.aaaaaaaaaaaaaaadddddddddd',
|
|
36
|
+
'...aaaaaaaaaaaa.d.....d...',
|
|
37
|
+
'....a.a....a.a..d....d....',
|
|
38
|
+
'................d...d.....',
|
|
39
|
+
'................d..d......',
|
|
40
|
+
'.................dd.......',
|
|
41
|
+
'..........................',
|
|
42
|
+
] as const
|
|
43
|
+
export const LOGO_PALETTE: Record<string, string> = { a: '#D97757', d: '#FFFFFF' }
|
|
44
|
+
|
|
45
|
+
/** One stretch of text cells that share a colour pair. A text character holds two colours at most. */
|
|
46
|
+
export type LogoRun = { text: string; fg?: string; bg?: string }
|
|
47
|
+
|
|
48
|
+
/** Quadrant glyphs by which of the four cells (upper left 1, upper right 2, lower left 4, lower right 8) are filled. */
|
|
49
|
+
const QUADRANT = ' ▘▝▀▖▌▞▛▗▚▐▜▄▙▟█'
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The grid as rows of text runs: each 2 by 2 block becomes one quadrant character, the
|
|
53
|
+
* first colour as its foreground and a second, if any, as its background. A third colour
|
|
54
|
+
* in one block is dropped, so the logo degrades and never throws.
|
|
55
|
+
*/
|
|
56
|
+
export function logoRows(grid: readonly string[] = LOGO_GRID, palette: Record<string, string> = LOGO_PALETTE): LogoRun[][] {
|
|
57
|
+
const out: LogoRun[][] = []
|
|
58
|
+
for (let cy = 0; cy < Math.ceil(grid.length / 2); cy++) {
|
|
59
|
+
const runs: LogoRun[] = []
|
|
60
|
+
for (let cx = 0; cx < Math.ceil((grid[0]?.length ?? 0) / 2); cx++) {
|
|
61
|
+
const quad = [grid[cy * 2]?.[cx * 2], grid[cy * 2]?.[cx * 2 + 1], grid[cy * 2 + 1]?.[cx * 2], grid[cy * 2 + 1]?.[cx * 2 + 1]].map(c => (c && c !== '.' ? c : ''))
|
|
62
|
+
const [first, second] = [...new Set(quad.filter(Boolean))]
|
|
63
|
+
let run: LogoRun = { text: ' ' }
|
|
64
|
+
if (first) {
|
|
65
|
+
const mask = quad.reduce((n, c, i) => (c === first ? n | (1 << i) : n), 0)
|
|
66
|
+
run = { text: QUADRANT[mask], fg: palette[first], bg: second ? palette[second] : undefined }
|
|
67
|
+
}
|
|
68
|
+
const last = runs[runs.length - 1]
|
|
69
|
+
if (last && last.fg === run.fg && last.bg === run.bg) last.text += run.text
|
|
70
|
+
else runs.push(run)
|
|
71
|
+
}
|
|
72
|
+
out.push(runs)
|
|
73
|
+
}
|
|
74
|
+
return out
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** The name as the header sets it: spaced capitals, the nearest a terminal gets to a larger type size. */
|
|
78
|
+
/** A call such as `tester.tap(find.byType(X))`, nested up to three deep, a lowerCamelCase name or a snake_case name. */
|
|
79
|
+
const CODE_LIKE = /[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*\((?:[^()\n]|\((?:[^()\n]|\([^()\n]*\))*\))*\)|\b[a-z]{2,}(?:[A-Z][a-z0-9]+)+\b|\b[a-z0-9]+(?:_[a-z0-9]+)+\b/g
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Wraps code-looking words in backticks so the pane's Markdown draws them as code. A question
|
|
83
|
+
* is plain prose unless its author marked the code, and an old or careless one often is not.
|
|
84
|
+
* Text already in backticks is left as written.
|
|
85
|
+
*/
|
|
86
|
+
export function codify(text: string): string {
|
|
87
|
+
return text
|
|
88
|
+
.split(/(`[^`]*`)/)
|
|
89
|
+
.map((part, i) => (i % 2 ? part : part.replace(CODE_LIKE, '`$&`')))
|
|
90
|
+
.join('')
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** What a Button label can show of Markdown: it keeps the words and drops the code and bold marks. */
|
|
94
|
+
export function plainLabel(text: string): string {
|
|
95
|
+
return text.replace(/`|\*\*/g, '')
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export const WORDMARK = 'EKLAVYA'.split('').join(' ')
|
|
99
|
+
|
|
100
|
+
/** How long a finished result stays before it closes itself, when no Next is waiting. */
|
|
101
|
+
export const CLOSE_MS = 15_000
|
|
102
|
+
|
|
21
103
|
/** Every sentence the learner reads, in one place. The handoff's table, verbatim. */
|
|
22
104
|
export const STR = {
|
|
23
105
|
brand: 'Eklavya',
|
|
24
|
-
powered: 'Powered by Claude',
|
|
25
106
|
submit: 'Submit answer',
|
|
26
107
|
skip: 'Skip question',
|
|
27
108
|
next: 'Next question',
|
|
28
109
|
done: 'Done',
|
|
110
|
+
autoClose: 'Closes by itself in 15 seconds.',
|
|
29
111
|
retry: 'Retry',
|
|
30
112
|
close: 'Close',
|
|
31
113
|
other: 'Other — explain in your own words',
|
|
@@ -41,6 +123,8 @@ export const STR = {
|
|
|
41
123
|
nothingTyped: 'Write your answer first, or pick one of the options.',
|
|
42
124
|
graderFailed: "We couldn't check that answer. Your text is saved here — Retry.",
|
|
43
125
|
explainer: 'A page explaining this is being written.',
|
|
126
|
+
focus: 'Ctrl+X then Tab moves the keys here.',
|
|
127
|
+
keys: '1–4 pick · o other · s submit · k skip · Esc back to prompt',
|
|
44
128
|
off: 'The quiz panel is off. Turn it on with: eklavya config set quiz.panel true',
|
|
45
129
|
} as const
|
|
46
130
|
|
|
@@ -2,15 +2,21 @@ import { atom, read, update } from 'claude-code'
|
|
|
2
2
|
import type { EngineInterface, Register } from 'claude-code'
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
|
+
BRAND,
|
|
5
6
|
EMPTY,
|
|
6
7
|
EXPLAINER,
|
|
7
8
|
NEXT_PROMPT,
|
|
8
9
|
PANE,
|
|
9
10
|
REOPEN,
|
|
10
11
|
STR,
|
|
12
|
+
WORDMARK,
|
|
13
|
+
CLOSE_MS,
|
|
11
14
|
answerWas,
|
|
12
15
|
explainerBrief,
|
|
16
|
+
codify,
|
|
13
17
|
gradingRequest,
|
|
18
|
+
logoRows,
|
|
19
|
+
plainLabel,
|
|
14
20
|
levelLine,
|
|
15
21
|
parseVerdict,
|
|
16
22
|
payloadOf,
|
|
@@ -49,6 +55,8 @@ let busy = false
|
|
|
49
55
|
/** Bumped when the session ends: a submit that began before it is stale and must write nothing. */
|
|
50
56
|
let epoch = 0
|
|
51
57
|
let explainedAttempt = 0
|
|
58
|
+
/** The result closes itself after CLOSE_MS when nothing else is pending (no Next). */
|
|
59
|
+
let closer: { cancel: () => void } | null = null
|
|
52
60
|
|
|
53
61
|
/**
|
|
54
62
|
* One call to an Eklavya tool through the host's own connection, as the parsed
|
|
@@ -219,6 +227,9 @@ async function submit($: EngineInterface, wanted: PanelPending | null): Promise<
|
|
|
219
227
|
message: null,
|
|
220
228
|
}))
|
|
221
229
|
|
|
230
|
+
// The last question of a round has no Next: the result closes itself, as Done would.
|
|
231
|
+
if (!q.more) arm($, mine, q.question_id)
|
|
232
|
+
|
|
222
233
|
// A miss with explain_on_wrong on: the panel starts the explainer itself in
|
|
223
234
|
// the background, so no prompt shows in the transcript and nothing waits.
|
|
224
235
|
// Auto mode judges every spawn (it drops Agent allow rules), and its
|
|
@@ -270,6 +281,7 @@ export const register: Register = on => {
|
|
|
270
281
|
on('session.end', async ($, e, next) => {
|
|
271
282
|
try {
|
|
272
283
|
epoch += 1
|
|
284
|
+
disarm()
|
|
273
285
|
openedFor = null
|
|
274
286
|
busy = false
|
|
275
287
|
await update($, quiz, () => EMPTY)
|
|
@@ -332,22 +344,61 @@ export const register: Register = on => {
|
|
|
332
344
|
on('ui.render', { component: 'Pane', requestId: 'eklavya-quiz' }, async ($, e, next) => {
|
|
333
345
|
// Only our pane: every other pane, and everything else, is the host's.
|
|
334
346
|
if (e.requestId !== PANE) return next(e)
|
|
335
|
-
const { Box, Text, Button, Input } = $.ui.resolve(e) as any
|
|
347
|
+
const { Box, Text, Button, Input, Markdown } = $.ui.resolve(e) as any
|
|
336
348
|
const s: PanelState = await read($, quiz)
|
|
337
349
|
const q = s.question
|
|
338
350
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
351
|
+
// The mark is drawn in text, as Claude Code draws its own, so it shows in every terminal.
|
|
352
|
+
const logo = (
|
|
353
|
+
<Box flexDirection="column" flexShrink={0}>
|
|
354
|
+
{logoRows().map((runs, y) => (
|
|
355
|
+
<Box key={`logo-${y}`} flexDirection="row" flexShrink={0}>
|
|
356
|
+
{runs.map((run, x) => (
|
|
357
|
+
<Text key={`logo-${y}-${x}`} color={run.fg} backgroundColor={run.bg} wrap="truncate">
|
|
358
|
+
{run.text}
|
|
359
|
+
</Text>
|
|
360
|
+
))}
|
|
361
|
+
</Box>
|
|
362
|
+
))}
|
|
363
|
+
</Box>
|
|
364
|
+
)
|
|
365
|
+
// A list tile: the mark on the left; the name over the topic beside it.
|
|
366
|
+
const head = (subtitle?: string) => (
|
|
367
|
+
<Box flexDirection="row" alignItems="center" gap={2}>
|
|
368
|
+
{logo}
|
|
369
|
+
<Box flexDirection="column" flexShrink={1}>
|
|
370
|
+
<Text bold color={BRAND}>
|
|
371
|
+
{WORDMARK}
|
|
372
|
+
</Text>
|
|
373
|
+
{subtitle ? <Text>{subtitle}</Text> : null}
|
|
374
|
+
</Box>
|
|
375
|
+
</Box>
|
|
376
|
+
)
|
|
377
|
+
const hint = (
|
|
378
|
+
<Box flexDirection="column">
|
|
379
|
+
<Text dimColor>{STR.focus}</Text>
|
|
380
|
+
<Text dimColor>{STR.keys}</Text>
|
|
381
|
+
</Box>
|
|
382
|
+
)
|
|
383
|
+
// One answer: a bordered block. The Button holds the whole answer text, so a press anywhere on it
|
|
384
|
+
// selects; the border and a tick show the pick, and nothing implies it is right.
|
|
385
|
+
const card = (id: string, selected: boolean, button: unknown, body: unknown[], roomy = false) => (
|
|
386
|
+
<Box
|
|
387
|
+
key={`card-${id}`}
|
|
388
|
+
flexDirection="column"
|
|
389
|
+
paddingX={1}
|
|
390
|
+
paddingY={roomy ? 1 : 0}
|
|
391
|
+
gap={roomy ? 1 : 0}
|
|
392
|
+
borderStyle="single"
|
|
393
|
+
borderColor={selected ? BRAND : 'inactive'}
|
|
394
|
+
>
|
|
395
|
+
{button}
|
|
396
|
+
{body}
|
|
345
397
|
</Box>
|
|
346
398
|
)
|
|
347
|
-
const hint = <Text dimColor>Ctrl+X then Tab moves focus here. Enter presses a button. Esc returns to the prompt.</Text>
|
|
348
399
|
const frame = (...body: unknown[]) => (
|
|
349
400
|
<Box flexDirection="column" paddingX={1} gap={1}>
|
|
350
|
-
{head}
|
|
401
|
+
{head(q ? topicLabel(q) : undefined)}
|
|
351
402
|
{body}
|
|
352
403
|
{hint}
|
|
353
404
|
</Box>
|
|
@@ -364,25 +415,24 @@ export const register: Register = on => {
|
|
|
364
415
|
)
|
|
365
416
|
}
|
|
366
417
|
|
|
367
|
-
const
|
|
368
|
-
const stem = <Text bold>{q.stem}</Text>
|
|
418
|
+
const stem = <Markdown text={codify(q.stem)} />
|
|
369
419
|
|
|
370
420
|
if (s.step === 'feedback' || s.step === 'skipped') {
|
|
371
421
|
const r = s.result
|
|
372
422
|
if (s.step === 'skipped' || !r) {
|
|
373
|
-
return frame(
|
|
423
|
+
return frame(<Text>{STR.skipped}</Text>, q.more ? null : <Text dimColor>{STR.autoClose}</Text>, <Button key="done" label={STR.done} variant="primary" hotkey="d" onPress={() => done($)} />)
|
|
374
424
|
}
|
|
375
425
|
const right = r.correct === true
|
|
376
426
|
return frame(
|
|
377
|
-
topic,
|
|
378
427
|
stem,
|
|
379
428
|
<Text bold color={right ? 'suggestion' : 'error'}>
|
|
380
429
|
{right ? STR.correct : STR.wrong}
|
|
381
430
|
</Text>,
|
|
382
431
|
right || !r.correct_label ? null : <Text>{answerWas(r.correct_label)}</Text>,
|
|
383
|
-
r.explanation ? <
|
|
432
|
+
r.explanation ? <Markdown text={codify(r.explanation)} /> : null,
|
|
384
433
|
r.level_up ? <Text color="suggestion">{levelLine(r.level_up.from, projectName(q.repo))}</Text> : null,
|
|
385
434
|
r.explain ? <Text dimColor>{STR.explainer}</Text> : null,
|
|
435
|
+
q.more ? null : <Text dimColor>{STR.autoClose}</Text>,
|
|
386
436
|
<Box flexDirection="row" gap={2}>
|
|
387
437
|
{q.more ? <Button key="next" label={STR.next} hotkey="n" onPress={() => askNext($)} /> : null}
|
|
388
438
|
<Button key="done" label={STR.done} variant="primary" hotkey="d" onPress={() => done($)} />
|
|
@@ -393,45 +443,51 @@ export const register: Register = on => {
|
|
|
393
443
|
const locked = s.step === 'grading'
|
|
394
444
|
// Submit looks disabled until there is something to send; pressing it then does nothing.
|
|
395
445
|
const hasDraft = s.draft.other ? s.draft.text.trim().length > 0 : s.draft.picked !== null
|
|
446
|
+
// Markdown marks cannot show inside a Button's label, so the code and bold marks are dropped there; the note keeps them.
|
|
447
|
+
const answer = (id: string, key: string, hotkey: string, label: string, chosen: boolean) => (
|
|
448
|
+
<Button
|
|
449
|
+
key={key}
|
|
450
|
+
plain
|
|
451
|
+
hotkey={hotkey}
|
|
452
|
+
label={`${chosen ? '✓ ' : ''}${label}`}
|
|
453
|
+
onPress={() => (locked ? undefined : id === 'other' ? chooseOther($) : pick($, id))}
|
|
454
|
+
/>
|
|
455
|
+
)
|
|
396
456
|
const options = q.options.map((o, i) => {
|
|
397
457
|
const chosen = !s.draft.other && s.draft.picked === o.id
|
|
398
|
-
return (
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
label={`${chosen ? '●' : '○'} ${o.label}`}
|
|
404
|
-
hotkey={String(i + 1)}
|
|
405
|
-
onPress={() => (locked ? undefined : pick($, o.id))}
|
|
406
|
-
/>
|
|
407
|
-
{o.note ? <Text dimColor>{` ${o.note}`}</Text> : null}
|
|
408
|
-
</Box>
|
|
458
|
+
return card(
|
|
459
|
+
o.id,
|
|
460
|
+
chosen,
|
|
461
|
+
answer(o.id, `opt-${o.id}`, String(i + 1), plainLabel(o.label), chosen),
|
|
462
|
+
[o.note ? <Box key={`note-${o.id}`} paddingLeft={3}><Markdown text={codify(o.note)} dimColor /></Box> : null],
|
|
409
463
|
)
|
|
410
464
|
})
|
|
411
465
|
|
|
412
466
|
return frame(
|
|
413
|
-
topic,
|
|
414
467
|
stem,
|
|
415
|
-
<Box flexDirection="column"
|
|
468
|
+
<Box flexDirection="column">
|
|
416
469
|
{options}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
470
|
+
{card(
|
|
471
|
+
'other',
|
|
472
|
+
s.draft.other,
|
|
473
|
+
answer('other', 'opt-other', 'o', STR.other, s.draft.other),
|
|
474
|
+
[
|
|
475
|
+
s.draft.other ? (
|
|
476
|
+
<Box key="other-box" paddingLeft={3}>
|
|
477
|
+
<Input
|
|
478
|
+
key="other-text"
|
|
479
|
+
autoFocus
|
|
480
|
+
placeholder={STR.otherPlaceholder}
|
|
481
|
+
value={s.draft.text}
|
|
482
|
+
submitLabel="keep"
|
|
483
|
+
onInput={(value: string) => (locked ? undefined : typeOther($, value))}
|
|
484
|
+
onSubmit={(value: string) => (locked ? undefined : typeOther($, value))}
|
|
485
|
+
/>
|
|
486
|
+
</Box>
|
|
487
|
+
) : null,
|
|
488
|
+
],
|
|
489
|
+
s.draft.other,
|
|
490
|
+
)}
|
|
435
491
|
</Box>,
|
|
436
492
|
locked ? <Text dimColor>{STR.grading}</Text> : null,
|
|
437
493
|
s.step === 'error' && s.message ? <Text color="error">{s.message}</Text> : null,
|
|
@@ -452,6 +508,12 @@ export const register: Register = on => {
|
|
|
452
508
|
/** Selecting an option records a draft and nothing else: it never submits, grades or skips. */
|
|
453
509
|
async function pick($: EngineInterface, id: string): Promise<void> {
|
|
454
510
|
await update($, quiz, (s: PanelState) => (s.step === 'awaiting' || s.step === 'error' ? { ...s, draft: { ...s.draft, picked: id, other: false }, message: null } : s))
|
|
511
|
+
try {
|
|
512
|
+
// The host's focus mark follows the pick, so the highlighted answer is the chosen one even after a hotkey.
|
|
513
|
+
await $.ui.focus({ requestId: PANE, key: `opt-${id}` })
|
|
514
|
+
} catch {
|
|
515
|
+
/* Fail open: the pick stands without it. */
|
|
516
|
+
}
|
|
455
517
|
}
|
|
456
518
|
|
|
457
519
|
async function chooseOther($: EngineInterface): Promise<void> {
|
|
@@ -466,8 +528,34 @@ async function skip($: EngineInterface): Promise<void> {
|
|
|
466
528
|
await submit($, { kind: 'skip' })
|
|
467
529
|
}
|
|
468
530
|
|
|
531
|
+
/** Starts the auto-close for this result; any later Done, Next or new question cancels it. */
|
|
532
|
+
function arm($: EngineInterface, mine: number, id: string): void {
|
|
533
|
+
try {
|
|
534
|
+
closer?.cancel()
|
|
535
|
+
closer = $.clock.after(CLOSE_MS, async () => {
|
|
536
|
+
try {
|
|
537
|
+
closer = null
|
|
538
|
+
if (epoch !== mine) return
|
|
539
|
+
const s: PanelState = (await $.state.get(REF)).value ?? EMPTY
|
|
540
|
+
// Only the result it was started for: an answer-in-progress or a newer question stays.
|
|
541
|
+
if ((s.step === 'feedback' || s.step === 'skipped') && s.question?.question_id === id) await done($)
|
|
542
|
+
} catch {
|
|
543
|
+
/* Fail open. */
|
|
544
|
+
}
|
|
545
|
+
})
|
|
546
|
+
} catch {
|
|
547
|
+
/* No clock: the learner presses Done. */
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
function disarm(): void {
|
|
552
|
+
closer?.cancel()
|
|
553
|
+
closer = null
|
|
554
|
+
}
|
|
555
|
+
|
|
469
556
|
/** Done closes the pane; another question may already be waiting, and then the pane comes straight back. */
|
|
470
557
|
async function done($: EngineInterface): Promise<void> {
|
|
558
|
+
disarm()
|
|
471
559
|
try {
|
|
472
560
|
openedFor = null
|
|
473
561
|
await update($, quiz, () => EMPTY)
|
|
@@ -480,6 +568,7 @@ async function done($: EngineInterface): Promise<void> {
|
|
|
480
568
|
|
|
481
569
|
/** Next asks the model, by a queued prompt, for the next question of an explicit round. */
|
|
482
570
|
async function askNext($: EngineInterface): Promise<void> {
|
|
571
|
+
disarm()
|
|
483
572
|
try {
|
|
484
573
|
openedFor = null
|
|
485
574
|
await update($, quiz, () => ({ ...EMPTY, step: 'loading' }))
|
|
@@ -27,6 +27,10 @@ Make one call to `present_question` instead:
|
|
|
27
27
|
misconception (rows one and three). Grade 3 is not available to a pick: only
|
|
28
28
|
typed words can show a hesitant "right".
|
|
29
29
|
- `more: true` only in an explicit round (the plan has more items after this one, as for `/eklavya:quiz`): the panel then offers a Next button. Leave it out otherwise. Once a round is planned the server tracks it: the last question's `more` is set false for you, and Next arrives as a prompt telling you to call `get_session_quiz_plan` with `resume_round: true`.
|
|
30
|
+
- **Mark the code.** The panel draws the stem, each `description` and the `explanation` as Markdown, so
|
|
31
|
+
put every function, call, class, file and flag in backticks (`` `tester.pumpWidget(widget)` ``,
|
|
32
|
+
`` `shouldRepaint` ``) and nothing else: no headings, lists or emphasis. Option `label`s are drawn as
|
|
33
|
+
plain text, so write them in words and leave the backticks out of them.
|
|
30
34
|
- `explanation`: one line saying why the right option is right. The panel shows
|
|
31
35
|
it after the answer, so it is the whole of your feedback.
|
|
32
36
|
|
|
@@ -10,7 +10,7 @@ export const presentQuestionTool = {
|
|
|
10
10
|
session_id: z.string().max(LIMITS.sessionId).optional().describe(SESSION_HINT),
|
|
11
11
|
cwd: z.string().max(LIMITS.cwd).optional().describe(CWD_HINT),
|
|
12
12
|
slug: z.string().max(LIMITS.slug).describe('The concept asked about.'),
|
|
13
|
-
question: z.string().min(1).max(LIMITS.question).describe('The question stem only: no options, no attribution, no settings line.'),
|
|
13
|
+
question: z.string().min(1).max(LIMITS.question).describe('The question stem only: no options, no attribution, no settings line. Markdown: put code names and calls in backticks.'),
|
|
14
14
|
options: z
|
|
15
15
|
.array(z.object({
|
|
16
16
|
label: z.string().min(1).max(LIMITS.option).describe('The option text itself, a complete claim in the option\'s own words: never a letter or a number (the panel numbers them). Labels must differ.'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel_tools.js","sourceRoot":"","sources":["../../src/tools/panel_tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,eAAe,EAAuD,MAAM,aAAa,CAAC;AAC3H,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAgB,MAAM,YAAY,CAAC;AAE1E,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB,MAAM,CAAC,MAAM,mBAAmB,GAAY;IAC1C,IAAI,EAAE,kBAAkB;IACxB,KAAK,EAAE,2CAA2C;IAClD,WAAW,EACT,k0BAAk0B;IACp0B,WAAW,EAAE;QACX,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,YAAY,CAAC;QAC9E,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC7D,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,0BAA0B,CAAC;QACtE,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,QAAQ,CAAC,
|
|
1
|
+
{"version":3,"file":"panel_tools.js","sourceRoot":"","sources":["../../src/tools/panel_tools.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED