eklavya 1.53.0 → 1.54.1
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/panel-state.js
CHANGED
|
@@ -13,11 +13,13 @@ import { isCowork } from './surface.js';
|
|
|
13
13
|
/** How long after its last `panel_sync` a mod still counts as present. */
|
|
14
14
|
export const PANEL_HEARTBEAT_TTL_MS = 90_000;
|
|
15
15
|
/**
|
|
16
|
-
* Host surfaces
|
|
17
|
-
*
|
|
18
|
-
*
|
|
16
|
+
* Host surfaces the planner may present the panel on. The terminal was verified
|
|
17
|
+
* (docs/verified-schemas.md); the Desktop Code tab is enabled but not yet
|
|
18
|
+
* observed. It is safe to try: a host that cannot seat the pane reports
|
|
19
|
+
* `placed: false` and the question stays on the card, and a host that never
|
|
20
|
+
* loads the mod never stamps a heartbeat.
|
|
19
21
|
*/
|
|
20
|
-
export const PANEL_SURFACES = ['terminal'];
|
|
22
|
+
export const PANEL_SURFACES = ['terminal', 'desktop'];
|
|
21
23
|
/** The phases in which a question is still waiting for its answer. */
|
|
22
24
|
export const OPEN_PHASES = "('pending','unplaced','grading')";
|
|
23
25
|
/** An unanswered question stops being open after this long (`panel.ts` expires it). */
|
package/dist/panel-state.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panel-state.js","sourceRoot":"","sources":["../src/panel-state.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC;;;;;;;;;;GAUG;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE7C
|
|
1
|
+
{"version":3,"file":"panel-state.js","sourceRoot":"","sources":["../src/panel-state.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAExC;;;;;;;;;;GAUG;AAEH,0EAA0E;AAC1E,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE7C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAsB,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;AAEzE,sEAAsE;AACtE,MAAM,CAAC,MAAM,WAAW,GAAG,kCAAkC,CAAC;AAE9D,uFAAuF;AACvF,MAAM,CAAC,MAAM,kBAAkB,GAAG,EAAE,CAAC;AAErC,MAAM,YAAY,GAAG,CAAC,SAAiB,EAAU,EAAE,CAAC,YAAY,SAAS,EAAE,CAAC;AAe5E;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,EAAM,EAAE,SAAiB,EAAE,IAAgB,EAAE,MAA2B,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE;IACxH,MAAM,MAAM,GAAG,aAAa,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IAC5C,MAAM,IAAI,GAAc,EAAE,EAAE,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,IAAI,MAAM,EAAE,MAAM,IAAI,IAAI,EAAE,CAAC;IACnH,EAAE,CAAC,OAAO,CACR;2DACuD,CACxD,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IACrD,+EAA+E;IAC/E,2DAA2D;IAC3D,EAAE,CAAC,OAAO,CAAC,kFAAkF,CAAC,CAAC,GAAG,CAChG,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,GAAG,kBAAkB,GAAG,QAAQ,CAAC,CAAC,WAAW,EAAE,CACtE,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,EAAM,EAAE,SAAiB;IAC9C,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,SAAS,CAAC,CAAkC,CAAC;IAC7H,OAAO,GAAG,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAe,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,EAAM,EAAE,MAAoC,EAAE,SAAiB,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE;IACjH,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,IAAI,QAAQ,EAAE;QAAE,OAAO,MAAM,CAAC;IACpD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,aAAa,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC;YAAE,OAAO,MAAM,CAAC;QACnF,OAAO,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,IAAI,sBAAsB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;IAC1F,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAAC,EAAM,EAAE,SAAiB;IAC5D,IAAI,CAAC;QACH,OAAO,CACL,EAAE;aACC,OAAO,CACN;gDACsC,WAAW,+CAA+C,CACjG;aACA,GAAG,CAAC,SAAS,EAAE,IAAI,kBAAkB,QAAQ,CAAC,KAAK,SAAS,CAChE,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,QAAQ,GAAG,CAAC,SAAiB,EAAU,EAAE,CAAC,eAAe,SAAS,EAAE,CAAC;AAE3E,MAAM,UAAU,SAAS,CAAC,EAAM,EAAE,SAAiB,EAAE,KAAe,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE;IACpF,EAAE,CAAC,OAAO,CACR;2DACuD,CACxD,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,EAAE,GAAG,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;AAC/E,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,EAAM,EAAE,SAAiB;IAClD,EAAE,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,SAAS,CAAC,EAAM,EAAE,SAAiB,EAAE,GAAG,GAAG,IAAI,IAAI,EAAE;IACnE,MAAM,GAAG,GAAG,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAkC,CAAC;IACzH,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAoC,CAAC;IACvE,OAAO,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,kBAAkB,GAAG,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AACpG,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,EAAM,EAAE,SAAiB,EAAE,IAAY;IAClE,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACjD,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;IAC7C,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,SAAS,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,CAAC;;QAC/C,UAAU,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC;IAC/B,OAAO,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;AACzB,CAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eklavya",
|
|
3
3
|
"displayName": "Eklavya",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.54.1",
|
|
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"
|
|
@@ -74,6 +74,20 @@ export function logoRows(grid: readonly string[] = LOGO_GRID, palette: Record<st
|
|
|
74
74
|
return out
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
/** The grid as rows of boxes for a host whose font cannot tile glyphs: runs of one colour (or none) and their width in cells. */
|
|
78
|
+
export function boxRows(grid: readonly string[] = LOGO_GRID, palette: Record<string, string> = LOGO_PALETTE): { width: number; color?: string }[][] {
|
|
79
|
+
return grid.map(row => {
|
|
80
|
+
const runs: { width: number; color?: string }[] = []
|
|
81
|
+
for (const c of row) {
|
|
82
|
+
const color = c === '.' ? undefined : palette[c]
|
|
83
|
+
const last = runs[runs.length - 1]
|
|
84
|
+
if (last && last.color === color) last.width++
|
|
85
|
+
else runs.push({ width: 1, color })
|
|
86
|
+
}
|
|
87
|
+
return runs
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
77
91
|
/** The name as the header sets it: spaced capitals, the nearest a terminal gets to a larger type size. */
|
|
78
92
|
/** A call such as `tester.tap(find.byType(X))`, nested up to three deep, a lowerCamelCase name or a snake_case name. */
|
|
79
93
|
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
|
|
@@ -90,6 +104,17 @@ export function codify(text: string): string {
|
|
|
90
104
|
.join('')
|
|
91
105
|
}
|
|
92
106
|
|
|
107
|
+
/** Block Markdown that bold marks around it would break: a fence, heading, list, quote, or bold already in it. */
|
|
108
|
+
const NOT_BOLDABLE = /```|\*\*|^\s*(?:#|[-*+>]|\d+[.)])\s/m
|
|
109
|
+
|
|
110
|
+
/** Bolds each plain paragraph of a question, so a blank line never sits inside a bold span. */
|
|
111
|
+
export function boldParagraphs(text: string): string {
|
|
112
|
+
return text
|
|
113
|
+
.split(/\n{2,}/)
|
|
114
|
+
.map(p => (p.trim() && !NOT_BOLDABLE.test(p) ? `**${p.trim()}**` : p))
|
|
115
|
+
.join('\n\n')
|
|
116
|
+
}
|
|
117
|
+
|
|
93
118
|
/** What a Button label can show of Markdown: it keeps the words and drops the code and bold marks. */
|
|
94
119
|
export function plainLabel(text: string): string {
|
|
95
120
|
return text.replace(/`|\*\*/g, '')
|
|
@@ -125,6 +150,7 @@ export const STR = {
|
|
|
125
150
|
explainer: 'A page explaining this is being written.',
|
|
126
151
|
focus: 'Ctrl+X then Tab moves the keys here.',
|
|
127
152
|
keys: '1–4 pick · o other · s submit · k skip · Esc back to prompt',
|
|
153
|
+
keysApp: '1–4 pick · o other · s submit · k skip',
|
|
128
154
|
off: 'The quiz panel is off. Turn it on with: eklavya config set quiz.panel true',
|
|
129
155
|
} as const
|
|
130
156
|
|
|
@@ -163,6 +189,14 @@ export function explainerBrief(x: ExplainBlock): string {
|
|
|
163
189
|
}
|
|
164
190
|
|
|
165
191
|
export const answerWas = (label: string): string => `The answer was: ${label}`
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* The hint lines under the pane. Moving the keys with Ctrl+X then Tab and
|
|
195
|
+
* leaving with Esc are the terminal's; an app draws native buttons, so it gets
|
|
196
|
+
* the answer keys only.
|
|
197
|
+
*/
|
|
198
|
+
export const hintLines = (surface: string): string[] => (surface === 'terminal' ? [STR.focus, STR.keys] : [STR.keysApp])
|
|
199
|
+
|
|
166
200
|
export const unplacedNotice = (): string => `A question is waiting. ${REOPEN} to open it.`
|
|
167
201
|
export const levelLine = (level: string, project: string): string => `You've cleared ${level} on ${project}.`
|
|
168
202
|
|
|
@@ -16,6 +16,8 @@ import {
|
|
|
16
16
|
codify,
|
|
17
17
|
gradingRequest,
|
|
18
18
|
logoRows,
|
|
19
|
+
boxRows,
|
|
20
|
+
boldParagraphs,
|
|
19
21
|
plainLabel,
|
|
20
22
|
levelLine,
|
|
21
23
|
parseVerdict,
|
|
@@ -23,6 +25,7 @@ import {
|
|
|
23
25
|
projectName,
|
|
24
26
|
topicLabel,
|
|
25
27
|
unplacedNotice,
|
|
28
|
+
hintLines,
|
|
26
29
|
} from './model'
|
|
27
30
|
import type { PanelPending, PanelQuestion, PanelState } from './types'
|
|
28
31
|
|
|
@@ -348,20 +351,38 @@ export const register: Register = on => {
|
|
|
348
351
|
const s: PanelState = await read($, quiz)
|
|
349
352
|
const q = s.question
|
|
350
353
|
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
354
|
+
// Any surface but the terminal is the desktop app's: it sets the question bold, spaces the answers
|
|
355
|
+
// apart and draws the mark in boxes. The terminal keeps its text layout.
|
|
356
|
+
const app = e.surface !== 'terminal'
|
|
357
|
+
// The mark is drawn in text, as Claude Code draws its own, so it shows in every terminal. The
|
|
358
|
+
// desktop app sets text in a proportional font with its own line height, so glyph cells do not
|
|
359
|
+
// tile there: it gets the same grid as filled boxes, one per run of a colour. A cell is one unit
|
|
360
|
+
// wide and half a unit tall, which is square in the app (checked live there; the host's
|
|
361
|
+
// fractional-height support is not documented, so a failure shows as a stretched mark).
|
|
362
|
+
const logo =
|
|
363
|
+
!app ? (
|
|
364
|
+
<Box flexDirection="column" flexShrink={0}>
|
|
365
|
+
{logoRows().map((runs, y) => (
|
|
366
|
+
<Box key={`logo-${y}`} flexDirection="row" flexShrink={0}>
|
|
367
|
+
{runs.map((run, x) => (
|
|
368
|
+
<Text key={`logo-${y}-${x}`} color={run.fg} backgroundColor={run.bg} wrap="truncate">
|
|
369
|
+
{run.text}
|
|
370
|
+
</Text>
|
|
371
|
+
))}
|
|
372
|
+
</Box>
|
|
373
|
+
))}
|
|
374
|
+
</Box>
|
|
375
|
+
) : (
|
|
376
|
+
<Box flexDirection="column" flexShrink={0}>
|
|
377
|
+
{boxRows().map((runs, y) => (
|
|
378
|
+
<Box key={`logo-${y}`} flexDirection="row" flexShrink={0} height={0.5}>
|
|
379
|
+
{runs.map((run, x) => (
|
|
380
|
+
<Box key={`logo-${y}-${x}`} width={run.width} height={0.5} flexShrink={0} backgroundColor={run.color} />
|
|
381
|
+
))}
|
|
382
|
+
</Box>
|
|
383
|
+
))}
|
|
384
|
+
</Box>
|
|
385
|
+
)
|
|
365
386
|
// A list tile: the mark on the left; the name over the topic beside it.
|
|
366
387
|
const head = (subtitle?: string) => (
|
|
367
388
|
<Box flexDirection="row" alignItems="center" gap={2}>
|
|
@@ -376,8 +397,11 @@ export const register: Register = on => {
|
|
|
376
397
|
)
|
|
377
398
|
const hint = (
|
|
378
399
|
<Box flexDirection="column">
|
|
379
|
-
|
|
380
|
-
|
|
400
|
+
{hintLines(e.surface).map(line => (
|
|
401
|
+
<Text key={line} dimColor>
|
|
402
|
+
{line}
|
|
403
|
+
</Text>
|
|
404
|
+
))}
|
|
381
405
|
</Box>
|
|
382
406
|
)
|
|
383
407
|
// One answer: a bordered block. The Button holds the whole answer text, so a press anywhere on it
|
|
@@ -415,7 +439,7 @@ export const register: Register = on => {
|
|
|
415
439
|
)
|
|
416
440
|
}
|
|
417
441
|
|
|
418
|
-
const stem = <Markdown text={codify(q.stem)} />
|
|
442
|
+
const stem = <Markdown text={app ? boldParagraphs(codify(q.stem)) : codify(q.stem)} />
|
|
419
443
|
|
|
420
444
|
if (s.step === 'feedback' || s.step === 'skipped') {
|
|
421
445
|
const r = s.result
|
|
@@ -465,7 +489,7 @@ export const register: Register = on => {
|
|
|
465
489
|
|
|
466
490
|
return frame(
|
|
467
491
|
stem,
|
|
468
|
-
<Box flexDirection="column">
|
|
492
|
+
<Box flexDirection="column" gap={app ? 1 : 0}>
|
|
469
493
|
{options}
|
|
470
494
|
{card(
|
|
471
495
|
'other',
|
package/package.json
CHANGED