@fenixforce/edition-voices 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/dist/addon.d.ts +45 -0
- package/dist/api/platform-middleware.d.ts +41 -0
- package/dist/api/platform-routes.d.ts +71 -0
- package/dist/assessment/hardened-grader.d.ts +76 -0
- package/dist/curriculum/zpd-selector.d.ts +57 -0
- package/dist/index.d.ts +68 -0
- package/dist/index.js +54 -0
- package/dist/manifest-merger.d.ts +13 -0
- package/dist/manifest.d.ts +13 -0
- package/dist/memory/learner-diary.d.ts +91 -0
- package/dist/system-prompt-patch.d.ts +17 -0
- package/dist/teacher/assessment/evaluator.d.ts +35 -0
- package/dist/teacher/assessment/feedback-generator.d.ts +11 -0
- package/dist/teacher/assessment/session-summary.d.ts +33 -0
- package/dist/teacher/corpus-importer.d.ts +44 -0
- package/dist/teacher/corpus-storage.d.ts +31 -0
- package/dist/teacher/curriculum/generator.d.ts +13 -0
- package/dist/teacher/curriculum/loader.d.ts +14 -0
- package/dist/teacher/curriculum/progress-tracker.d.ts +35 -0
- package/dist/teacher/curriculum/schema.d.ts +36 -0
- package/dist/teacher/exercises/builders/dialogue.d.ts +6 -0
- package/dist/teacher/exercises/builders/dictation.d.ts +6 -0
- package/dist/teacher/exercises/builders/error-correction.d.ts +6 -0
- package/dist/teacher/exercises/builders/fill-in-blank.d.ts +5 -0
- package/dist/teacher/exercises/builders/listening.d.ts +6 -0
- package/dist/teacher/exercises/builders/pronunciation.d.ts +6 -0
- package/dist/teacher/exercises/builders/reading.d.ts +6 -0
- package/dist/teacher/exercises/builders/sentence-construction.d.ts +6 -0
- package/dist/teacher/exercises/builders/translation.d.ts +7 -0
- package/dist/teacher/exercises/builders/vocabulary-drill.d.ts +5 -0
- package/dist/teacher/exercises/generator.d.ts +45 -0
- package/dist/teacher/exercises/index.d.ts +7 -0
- package/dist/teacher/importers/csv-importer.d.ts +22 -0
- package/dist/teacher/importers/json-importer.d.ts +11 -0
- package/dist/teacher/learner-model.d.ts +84 -0
- package/dist/teacher/learner-storage.d.ts +61 -0
- package/dist/teacher/level-classifier.d.ts +34 -0
- package/dist/teacher/loop/message-builder.d.ts +32 -0
- package/dist/teacher/loop/scheduler-integration.d.ts +45 -0
- package/dist/teacher/loop/teaching-loop.d.ts +72 -0
- package/dist/teacher/scrapthing/batch-job.d.ts +65 -0
- package/dist/teacher/scrapthing/content-cleaner.d.ts +27 -0
- package/dist/teacher/scrapthing/mcp-adapter.d.ts +50 -0
- package/dist/teacher/skills/index.d.ts +35 -0
- package/dist/teacher/skills/placement-test.d.ts +47 -0
- package/dist/teacher/skills/plan-lesson.d.ts +46 -0
- package/dist/teacher/spaced-repetition.d.ts +49 -0
- package/dist/teacher/teacher-memory.d.ts +79 -0
- package/dist/teacher/voice/custom-vocabulary.d.ts +28 -0
- package/dist/teacher/voice/exercise-voice.d.ts +39 -0
- package/dist/teacher/voice/language-voices.d.ts +26 -0
- package/dist/teacher/voice/pronunciation-scorer.d.ts +38 -0
- package/package.json +36 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Teaching Loop — Autonomous teaching loop called by the scheduler.
|
|
3
|
+
*
|
|
4
|
+
* Decision tree:
|
|
5
|
+
* 1. Skip if practiceFrequency is 'on-demand'
|
|
6
|
+
* 2. Skip if outside 30-min window of preferredPracticeTime
|
|
7
|
+
* 3. Check due review items → send practice exercise
|
|
8
|
+
* 4. Check inactivity → send nudge
|
|
9
|
+
* 5. Check milestones → send celebration
|
|
10
|
+
* 6. Nothing due → skip silently
|
|
11
|
+
*/
|
|
12
|
+
import type { LearnerProfile } from "../learner-model.js";
|
|
13
|
+
import type { Exercise } from "../exercises/generator.js";
|
|
14
|
+
import type { MessageEnvelope, Milestone } from "./message-builder.js";
|
|
15
|
+
export interface TeachingLoopConfig {
|
|
16
|
+
enabled: boolean;
|
|
17
|
+
defaultSchedule: string;
|
|
18
|
+
inactivityNudgeDays: number;
|
|
19
|
+
milestoneThresholds: {
|
|
20
|
+
vocabulary: number[];
|
|
21
|
+
streak: number[];
|
|
22
|
+
unitCompletion: boolean;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
export declare const DEFAULT_CONFIG: TeachingLoopConfig;
|
|
26
|
+
export interface DueReviewItems {
|
|
27
|
+
vocab: {
|
|
28
|
+
id: string;
|
|
29
|
+
word?: string;
|
|
30
|
+
}[];
|
|
31
|
+
grammar: {
|
|
32
|
+
id: string;
|
|
33
|
+
concept?: string;
|
|
34
|
+
}[];
|
|
35
|
+
}
|
|
36
|
+
export interface ProgressSummary {
|
|
37
|
+
totalVocab: number;
|
|
38
|
+
completedUnits: number;
|
|
39
|
+
}
|
|
40
|
+
export interface TeachingLoopDeps {
|
|
41
|
+
getLearner: (learnerId: string) => Promise<LearnerProfile | null>;
|
|
42
|
+
getDueReviewItems: (learnerId: string) => Promise<DueReviewItems>;
|
|
43
|
+
getProgress: (learnerId: string) => Promise<ProgressSummary>;
|
|
44
|
+
generateReviewExercise: (learnerId: string) => Promise<Exercise>;
|
|
45
|
+
sendMessage: (envelope: MessageEnvelope) => Promise<void>;
|
|
46
|
+
getMilestonesSent: (learnerId: string) => Promise<Set<string>>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Check if `now` is within ±30 minutes of the preferred practice time.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isWithinPracticeWindow(preferredTime: string | undefined, timezone: string, now?: Date): boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Days elapsed since the learner's last session.
|
|
54
|
+
*/
|
|
55
|
+
export declare function daysSinceLastSession(lastSessionAt: string | undefined, now?: Date): number;
|
|
56
|
+
export declare function detectMilestones(config: TeachingLoopConfig, learner: LearnerProfile, progress: ProgressSummary, alreadySent: Set<string>): Milestone[];
|
|
57
|
+
export type LoopOutcome = {
|
|
58
|
+
action: "skipped";
|
|
59
|
+
reason: string;
|
|
60
|
+
} | {
|
|
61
|
+
action: "review_sent";
|
|
62
|
+
exerciseId: string;
|
|
63
|
+
} | {
|
|
64
|
+
action: "nudge_sent";
|
|
65
|
+
daysSince: number;
|
|
66
|
+
} | {
|
|
67
|
+
action: "milestone_sent";
|
|
68
|
+
milestones: Milestone[];
|
|
69
|
+
} | {
|
|
70
|
+
action: "nothing_due";
|
|
71
|
+
};
|
|
72
|
+
export declare function runTeachingLoop(deps: TeachingLoopDeps, learnerId: string, config?: TeachingLoopConfig, now?: Date): Promise<LoopOutcome>;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrapthing Batch Job — Scheduled batch scraping with deduplication,
|
|
3
|
+
* rate limiting, and corpus import.
|
|
4
|
+
*
|
|
5
|
+
* Reads ScrapthingConfig from workspace profile, iterates configured
|
|
6
|
+
* sources, scrapes each, and imports results via the corpus importer.
|
|
7
|
+
*/
|
|
8
|
+
import type { Kysely } from "kysely";
|
|
9
|
+
import type { ScrapthingConfig, FetchPageFn } from "./mcp-adapter.js";
|
|
10
|
+
import type { CorpusItem } from "../corpus-importer.js";
|
|
11
|
+
export interface BatchJobResult {
|
|
12
|
+
jobId: string;
|
|
13
|
+
startedAt: string;
|
|
14
|
+
endedAt: string;
|
|
15
|
+
sourcesProcessed: number;
|
|
16
|
+
totalItems: number;
|
|
17
|
+
newItems: number;
|
|
18
|
+
duplicateItems: number;
|
|
19
|
+
errors: BatchJobError[];
|
|
20
|
+
}
|
|
21
|
+
export interface BatchJobError {
|
|
22
|
+
source: string;
|
|
23
|
+
url: string;
|
|
24
|
+
message: string;
|
|
25
|
+
}
|
|
26
|
+
export interface BatchJobDeps {
|
|
27
|
+
db: Kysely<any>;
|
|
28
|
+
fetchPage: FetchPageFn;
|
|
29
|
+
config: ScrapthingConfig;
|
|
30
|
+
/** Optional event emitter for progress tracking. */
|
|
31
|
+
onProgress?: (event: BatchProgressEvent) => void;
|
|
32
|
+
}
|
|
33
|
+
export interface BatchProgressEvent {
|
|
34
|
+
type: "source_start" | "source_done" | "source_error" | "item_imported" | "item_duplicate";
|
|
35
|
+
source?: string;
|
|
36
|
+
url?: string;
|
|
37
|
+
count?: number;
|
|
38
|
+
message?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Filter out duplicate items using content hashing.
|
|
42
|
+
*/
|
|
43
|
+
export declare function deduplicateItems(db: Kysely<any>, items: CorpusItem[]): Promise<{
|
|
44
|
+
newItems: CorpusItem[];
|
|
45
|
+
duplicateCount: number;
|
|
46
|
+
}>;
|
|
47
|
+
/**
|
|
48
|
+
* Simple domain-based rate limiter. Tracks last request time per domain
|
|
49
|
+
* and enforces a minimum delay between requests to the same domain.
|
|
50
|
+
*/
|
|
51
|
+
export declare class DomainRateLimiter {
|
|
52
|
+
private lastRequest;
|
|
53
|
+
private delayMs;
|
|
54
|
+
constructor(delayMs?: number);
|
|
55
|
+
waitForDomain(url: string): Promise<void>;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Run a batch scraping job across all configured sources.
|
|
59
|
+
*/
|
|
60
|
+
export declare function runBatchJob(deps: BatchJobDeps): Promise<BatchJobResult>;
|
|
61
|
+
/**
|
|
62
|
+
* Parse a simple cron-like schedule string and determine if the job
|
|
63
|
+
* should run now. Supports: "daily", "hourly", "every-N-hours".
|
|
64
|
+
*/
|
|
65
|
+
export declare function shouldRunNow(schedule: string, lastRunAt: string | undefined, now?: Date): boolean;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content Cleaner — Post-scrape content processing for Scrapthing.
|
|
3
|
+
*
|
|
4
|
+
* Strips HTML artifacts, extracts primary content blocks, splits into
|
|
5
|
+
* logical units, and validates language consistency.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Clean raw HTML content by removing navigation, ads, scripts, and
|
|
9
|
+
* extracting readable text.
|
|
10
|
+
*/
|
|
11
|
+
export declare function cleanContent(html: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Split cleaned text into individual content items.
|
|
14
|
+
* Uses double-newline paragraph boundaries, falling back to
|
|
15
|
+
* sentence boundaries for long paragraphs.
|
|
16
|
+
*/
|
|
17
|
+
export declare function splitIntoItems(text: string): string[];
|
|
18
|
+
/**
|
|
19
|
+
* Character-range heuristics for common script families.
|
|
20
|
+
* Returns a confidence score (0-1) that the text matches the expected language.
|
|
21
|
+
*/
|
|
22
|
+
export declare function detectLanguageConfidence(text: string, expectedLang: string): number;
|
|
23
|
+
/**
|
|
24
|
+
* Generate a simple content hash for deduplication.
|
|
25
|
+
* Normalizes whitespace and lowercases before hashing.
|
|
26
|
+
*/
|
|
27
|
+
export declare function contentHash(text: string): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scrapthing MCP Adapter — Registers scrapthing as an MCP tool
|
|
3
|
+
* for background corpus growth.
|
|
4
|
+
*
|
|
5
|
+
* All scraped content is tagged source: 'scraped', reviewStatus: 'unreviewed'.
|
|
6
|
+
*/
|
|
7
|
+
import type { CorpusItem } from "../corpus-importer.js";
|
|
8
|
+
export interface ScrapthingConfig {
|
|
9
|
+
enabled: boolean;
|
|
10
|
+
schedule: string;
|
|
11
|
+
maxPagesPerRun: number;
|
|
12
|
+
targetLanguages: string[];
|
|
13
|
+
sources: ScrapthingSource[];
|
|
14
|
+
}
|
|
15
|
+
export interface ScrapthingSource {
|
|
16
|
+
name: string;
|
|
17
|
+
url: string;
|
|
18
|
+
language: string;
|
|
19
|
+
contentSelector?: string;
|
|
20
|
+
fetcherType: "basic" | "dynamic" | "stealth";
|
|
21
|
+
contentType: string;
|
|
22
|
+
}
|
|
23
|
+
export interface ScrapeInput {
|
|
24
|
+
url: string;
|
|
25
|
+
language: string;
|
|
26
|
+
contentType?: string;
|
|
27
|
+
contentSelector?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface ScrapeResult {
|
|
30
|
+
items: CorpusItem[];
|
|
31
|
+
url: string;
|
|
32
|
+
itemCount: number;
|
|
33
|
+
}
|
|
34
|
+
export type FetchPageFn = (url: string, selector?: string) => Promise<string>;
|
|
35
|
+
export declare function scrapeUrl(fetchPage: FetchPageFn, input: ScrapeInput): Promise<ScrapeResult>;
|
|
36
|
+
export declare const SCRAPTHING_TOOL: {
|
|
37
|
+
name: string;
|
|
38
|
+
description: string;
|
|
39
|
+
handler: (input: ScrapeInput, ctx: any) => Promise<ScrapeResult>;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Register the scrapthing tool with a tool registry.
|
|
43
|
+
*/
|
|
44
|
+
export declare function registerScrapthingTool(registry: {
|
|
45
|
+
tool: (serverName: string, toolName: string, def: {
|
|
46
|
+
name: string;
|
|
47
|
+
description?: string;
|
|
48
|
+
handler: (input: any, ctx: any) => Promise<any>;
|
|
49
|
+
}) => void;
|
|
50
|
+
}, serverName?: string): void;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Teacher Skills Index — Exports and registers all teacher-layer
|
|
3
|
+
* agent skills with the tool registry.
|
|
4
|
+
*/
|
|
5
|
+
export { planLesson, buildExercisesFromUnit, PLAN_LESSON_TOOL } from "./plan-lesson.js";
|
|
6
|
+
export type { LessonPlan, PlannedExercise, ReviewItem, PlanLessonDeps } from "./plan-lesson.js";
|
|
7
|
+
export { runPlacementTest, determineLevel, shouldContinueTesting, getNextTestLevel, PLACEMENT_TEST_TOOL } from "./placement-test.js";
|
|
8
|
+
export type { PlacementResult, PlacementExercise, TestedLevel, PlacementTestDeps } from "./placement-test.js";
|
|
9
|
+
export declare const TEACHER_SKILLS: readonly [{
|
|
10
|
+
name: string;
|
|
11
|
+
description: string;
|
|
12
|
+
handler: (input: {
|
|
13
|
+
learnerId: string;
|
|
14
|
+
}, ctx: any) => Promise<import("./plan-lesson.js").LessonPlan>;
|
|
15
|
+
}, {
|
|
16
|
+
name: string;
|
|
17
|
+
description: string;
|
|
18
|
+
handler: (input: {
|
|
19
|
+
learnerId: string;
|
|
20
|
+
language: string;
|
|
21
|
+
}, ctx: any) => Promise<import("./placement-test.js").PlacementResult>;
|
|
22
|
+
}];
|
|
23
|
+
/**
|
|
24
|
+
* Register all teacher skills with a tool registry.
|
|
25
|
+
*
|
|
26
|
+
* @param registry — ToolRegistry-compatible object (from Sovereign RAG)
|
|
27
|
+
* @param serverName — Namespace for the tools (default: 'teacher_skills')
|
|
28
|
+
*/
|
|
29
|
+
export declare function registerTeacherSkills(registry: {
|
|
30
|
+
tool: (serverName: string, toolName: string, def: {
|
|
31
|
+
name: string;
|
|
32
|
+
description?: string;
|
|
33
|
+
handler: (input: any, ctx: any) => Promise<any>;
|
|
34
|
+
}) => void;
|
|
35
|
+
}, serverName?: string): void;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Placement Test Skill — Assesses a learner's current level in their
|
|
3
|
+
* target language by generating exercises at increasing difficulty.
|
|
4
|
+
*
|
|
5
|
+
* Stops when the learner scores below 0.5 on two consecutive levels.
|
|
6
|
+
* Sets the learner's currentLevel to the highest level passed.
|
|
7
|
+
*
|
|
8
|
+
* Registered as tool: placement_test
|
|
9
|
+
*/
|
|
10
|
+
import type { Kysely } from "kysely";
|
|
11
|
+
import type { CEFRLevel } from "../learner-model.js";
|
|
12
|
+
export interface PlacementExercise {
|
|
13
|
+
level: CEFRLevel;
|
|
14
|
+
prompt: string;
|
|
15
|
+
type: "translation" | "comprehension" | "grammar_choice";
|
|
16
|
+
}
|
|
17
|
+
export interface PlacementResult {
|
|
18
|
+
learnerId: string;
|
|
19
|
+
testedLevels: TestedLevel[];
|
|
20
|
+
assignedLevel: CEFRLevel;
|
|
21
|
+
recommendedUnitId?: string;
|
|
22
|
+
}
|
|
23
|
+
export interface TestedLevel {
|
|
24
|
+
level: CEFRLevel;
|
|
25
|
+
score: number;
|
|
26
|
+
passed: boolean;
|
|
27
|
+
}
|
|
28
|
+
export type ExerciseGeneratorFn = (level: CEFRLevel, language: string) => Promise<PlacementExercise[]>;
|
|
29
|
+
export type ExerciseScorerFn = (exercises: PlacementExercise[], responses: string[]) => Promise<number>;
|
|
30
|
+
export declare function determineLevel(testedLevels: TestedLevel[]): CEFRLevel;
|
|
31
|
+
export declare function shouldContinueTesting(testedLevels: TestedLevel[]): boolean;
|
|
32
|
+
export declare function getNextTestLevel(testedLevels: TestedLevel[]): CEFRLevel | null;
|
|
33
|
+
export interface PlacementTestDeps {
|
|
34
|
+
db: Kysely<any>;
|
|
35
|
+
generateExercises: ExerciseGeneratorFn;
|
|
36
|
+
scoreExercises: ExerciseScorerFn;
|
|
37
|
+
getResponses: (exercises: PlacementExercise[]) => Promise<string[]>;
|
|
38
|
+
}
|
|
39
|
+
export declare function runPlacementTest(deps: PlacementTestDeps, learnerId: string, language: string): Promise<PlacementResult>;
|
|
40
|
+
export declare const PLACEMENT_TEST_TOOL: {
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
handler: (input: {
|
|
44
|
+
learnerId: string;
|
|
45
|
+
language: string;
|
|
46
|
+
}, ctx: any) => Promise<PlacementResult>;
|
|
47
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plan Lesson Skill — Generates a structured lesson plan for the
|
|
3
|
+
* current learner, mixing new curriculum material with spaced-repetition
|
|
4
|
+
* review items.
|
|
5
|
+
*
|
|
6
|
+
* Registered as tool: plan_lesson
|
|
7
|
+
*/
|
|
8
|
+
import type { Kysely } from "kysely";
|
|
9
|
+
import type { CurriculumTemplate, CurriculumUnit } from "../curriculum/schema.js";
|
|
10
|
+
export interface LessonPlan {
|
|
11
|
+
learnerId: string;
|
|
12
|
+
unitId: string;
|
|
13
|
+
sessionNumber: number;
|
|
14
|
+
objectives: string[];
|
|
15
|
+
exercises: PlannedExercise[];
|
|
16
|
+
estimatedDuration: number;
|
|
17
|
+
reviewItems: ReviewItem[];
|
|
18
|
+
}
|
|
19
|
+
export interface PlannedExercise {
|
|
20
|
+
type: string;
|
|
21
|
+
objective: string;
|
|
22
|
+
targetVocabulary?: string[];
|
|
23
|
+
targetGrammar?: string[];
|
|
24
|
+
contentQuery?: string;
|
|
25
|
+
difficulty: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ReviewItem {
|
|
28
|
+
entryId: string;
|
|
29
|
+
entryType: "vocab" | "grammar";
|
|
30
|
+
word?: string;
|
|
31
|
+
concept?: string;
|
|
32
|
+
lastScore: number;
|
|
33
|
+
}
|
|
34
|
+
export declare function buildExercisesFromUnit(unit: CurriculumUnit, sessionNumber: number, maxExercises?: number): PlannedExercise[];
|
|
35
|
+
export interface PlanLessonDeps {
|
|
36
|
+
db: Kysely<any>;
|
|
37
|
+
generateTemplate?: (language: string) => Promise<CurriculumTemplate>;
|
|
38
|
+
}
|
|
39
|
+
export declare function planLesson(deps: PlanLessonDeps, learnerId: string): Promise<LessonPlan>;
|
|
40
|
+
export declare const PLAN_LESSON_TOOL: {
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
handler: (input: {
|
|
44
|
+
learnerId: string;
|
|
45
|
+
}, ctx: any) => Promise<LessonPlan>;
|
|
46
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SM-2 Spaced Repetition Algorithm
|
|
3
|
+
*
|
|
4
|
+
* Based on the SuperMemo SM-2 algorithm by Piotr Wozniak.
|
|
5
|
+
* Calculates optimal review intervals based on recall quality.
|
|
6
|
+
*
|
|
7
|
+
* Quality scale (0–5):
|
|
8
|
+
* 0 — Complete blackout
|
|
9
|
+
* 1 — Incorrect; correct answer remembered after seeing it
|
|
10
|
+
* 2 — Incorrect; correct answer seemed easy to recall
|
|
11
|
+
* 3 — Correct with serious difficulty
|
|
12
|
+
* 4 — Correct after hesitation
|
|
13
|
+
* 5 — Perfect recall
|
|
14
|
+
*/
|
|
15
|
+
export interface SM2State {
|
|
16
|
+
/** Number of consecutive correct recalls (quality >= 3) */
|
|
17
|
+
repetitions: number;
|
|
18
|
+
/** Current inter-repetition interval in days */
|
|
19
|
+
interval: number;
|
|
20
|
+
/** Ease factor (minimum 1.3) */
|
|
21
|
+
easeFactor: number;
|
|
22
|
+
}
|
|
23
|
+
export interface SM2Result extends SM2State {
|
|
24
|
+
/** Next review date as ISO string */
|
|
25
|
+
nextReviewAt: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Calculates the next review state using the SM-2 algorithm.
|
|
29
|
+
*
|
|
30
|
+
* @param quality — Recall quality (0–5)
|
|
31
|
+
* @param state — Current SM-2 state (repetitions, interval, easeFactor)
|
|
32
|
+
* @param now — Reference date for computing nextReviewAt (defaults to Date.now)
|
|
33
|
+
* @returns Updated SM-2 state with nextReviewAt
|
|
34
|
+
*/
|
|
35
|
+
export declare function calculateNextReview(quality: number, state: SM2State, now?: Date): SM2Result;
|
|
36
|
+
/**
|
|
37
|
+
* Maps an exercise score (0.0–1.0) to SM-2 quality (0–5).
|
|
38
|
+
*
|
|
39
|
+
* Mapping:
|
|
40
|
+
* 0.0–0.19 → 0 (complete blackout)
|
|
41
|
+
* 0.2–0.39 → 1 (incorrect, remembered after seeing answer)
|
|
42
|
+
* 0.4–0.59 → 2 (incorrect, seemed easy to recall)
|
|
43
|
+
* 0.6–0.69 → 3 (correct with difficulty)
|
|
44
|
+
* 0.7–0.89 → 4 (correct after hesitation)
|
|
45
|
+
* 0.9–1.0 → 5 (perfect recall)
|
|
46
|
+
*/
|
|
47
|
+
export declare function scoreToQuality(score: number): number;
|
|
48
|
+
/** Returns the default initial SM-2 state for a new item. */
|
|
49
|
+
export declare function initialSM2State(): SM2State;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Teacher Memory Fact Registration
|
|
3
|
+
*
|
|
4
|
+
* Registers teacher-layer fact categories with the Sovereign RAG ToolRegistry
|
|
5
|
+
* for progressive disclosure: the agent sees a compact learner stub by default
|
|
6
|
+
* and only expands to full detail when session planning requires it.
|
|
7
|
+
*
|
|
8
|
+
* Fact categories:
|
|
9
|
+
* teacher:profile — learner profile snapshot
|
|
10
|
+
* teacher:vocab — vocabulary mastery data
|
|
11
|
+
* teacher:grammar — grammar mastery data
|
|
12
|
+
* teacher:session — session history and focus areas
|
|
13
|
+
*/
|
|
14
|
+
import type { Kysely } from "kysely";
|
|
15
|
+
import type { LearnerStub } from "./learner-model.js";
|
|
16
|
+
export type TeacherFactCategory = "teacher:profile" | "teacher:vocab" | "teacher:grammar" | "teacher:session";
|
|
17
|
+
export declare const TEACHER_FACT_CATEGORIES: readonly TeacherFactCategory[];
|
|
18
|
+
export interface FactCategoryDefinition {
|
|
19
|
+
category: TeacherFactCategory;
|
|
20
|
+
description: string;
|
|
21
|
+
/** Returns compact data for progressive disclosure (always loaded). */
|
|
22
|
+
fetchStub: (db: Kysely<any>, learnerId: string) => Promise<unknown>;
|
|
23
|
+
/** Returns full detail (loaded on demand during session planning). */
|
|
24
|
+
fetchDetail: (db: Kysely<any>, learnerId: string) => Promise<unknown>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Register all teacher fact categories.
|
|
28
|
+
* Safe to call multiple times — subsequent calls are no-ops.
|
|
29
|
+
*/
|
|
30
|
+
export declare function registerTeacherFacts(): void;
|
|
31
|
+
/** Check whether a specific fact category is registered. */
|
|
32
|
+
export declare function isFactCategoryRegistered(category: TeacherFactCategory): boolean;
|
|
33
|
+
/** List all registered fact categories. */
|
|
34
|
+
export declare function listRegisteredFactCategories(): TeacherFactCategory[];
|
|
35
|
+
/** Get a category definition by name. */
|
|
36
|
+
export declare function getFactCategoryDefinition(category: TeacherFactCategory): FactCategoryDefinition | undefined;
|
|
37
|
+
/** Compact context provided to the agent by default. */
|
|
38
|
+
export interface TeacherContextStub {
|
|
39
|
+
learnerId: string;
|
|
40
|
+
stub: LearnerStub;
|
|
41
|
+
factSummaries: Record<TeacherFactCategory, unknown>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Builds the compact agent context for a learner.
|
|
45
|
+
* This is the progressive disclosure entry point — returns only the stub data
|
|
46
|
+
* so the agent context stays small until detail is explicitly requested.
|
|
47
|
+
*/
|
|
48
|
+
export declare function buildTeacherContextStub(db: Kysely<any>, learnerId: string): Promise<TeacherContextStub | null>;
|
|
49
|
+
/**
|
|
50
|
+
* Expands a single fact category to full detail.
|
|
51
|
+
* Called when the agent needs deeper data during session planning.
|
|
52
|
+
*/
|
|
53
|
+
export declare function expandFactCategory(db: Kysely<any>, learnerId: string, category: TeacherFactCategory): Promise<unknown>;
|
|
54
|
+
/**
|
|
55
|
+
* Expands all fact categories to full detail.
|
|
56
|
+
* Used when full session planning context is needed.
|
|
57
|
+
*/
|
|
58
|
+
export declare function expandAllFacts(db: Kysely<any>, learnerId: string): Promise<Record<TeacherFactCategory, unknown>>;
|
|
59
|
+
/**
|
|
60
|
+
* Registers teacher memory fact tools with a Sovereign RAG ToolRegistry.
|
|
61
|
+
*
|
|
62
|
+
* Tools registered under `serverName` (default: 'teacher'):
|
|
63
|
+
* - teacher.stub — Fetch compact learner context
|
|
64
|
+
* - teacher.expand — Expand a single fact category to full detail
|
|
65
|
+
* - teacher.expand_all — Expand all fact categories
|
|
66
|
+
* - teacher.due_reviews — Get items due for review
|
|
67
|
+
* - teacher.progress — Get progress summary
|
|
68
|
+
*
|
|
69
|
+
* @param registry — ToolRegistry instance from Sovereign RAG
|
|
70
|
+
* @param db — Kysely database instance
|
|
71
|
+
* @param serverName — Registration namespace (default: 'teacher')
|
|
72
|
+
*/
|
|
73
|
+
export declare function registerTeacherMemoryTools(registry: {
|
|
74
|
+
tool: (serverName: string, toolName: string, def: {
|
|
75
|
+
name: string;
|
|
76
|
+
description?: string;
|
|
77
|
+
handler: (input: any, ctx: any) => Promise<any>;
|
|
78
|
+
}) => void;
|
|
79
|
+
}, db: Kysely<any>, serverName?: string): void;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom Vocabulary — sounds-like mappings for African language terms.
|
|
3
|
+
*
|
|
4
|
+
* Critical for Ejiogbe Voices where general-purpose STT frequently
|
|
5
|
+
* misrecognizes Yoruba, Igbo, Hausa, and Swahili terms.
|
|
6
|
+
*
|
|
7
|
+
* These vocabulary entries are passed to STT providers that support
|
|
8
|
+
* the customVocabulary capability (e.g., Speechmatics, Deepgram, Google).
|
|
9
|
+
*/
|
|
10
|
+
export interface CustomVocabularyEntry {
|
|
11
|
+
content: string;
|
|
12
|
+
soundsLike?: string[];
|
|
13
|
+
}
|
|
14
|
+
export declare const YORUBA_VOCABULARY: CustomVocabularyEntry[];
|
|
15
|
+
export declare const IGBO_VOCABULARY: CustomVocabularyEntry[];
|
|
16
|
+
export declare const HAUSA_VOCABULARY: CustomVocabularyEntry[];
|
|
17
|
+
export declare const SWAHILI_VOCABULARY: CustomVocabularyEntry[];
|
|
18
|
+
export declare const BUSINESS_VOCABULARY: CustomVocabularyEntry[];
|
|
19
|
+
export declare const VOCABULARY_BY_LANGUAGE: Record<string, CustomVocabularyEntry[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Get custom vocabulary entries for a language.
|
|
22
|
+
* Returns empty array if no custom vocabulary exists.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getVocabularyForLanguage(language: string): CustomVocabularyEntry[];
|
|
25
|
+
/**
|
|
26
|
+
* Merge multiple vocabulary lists, deduplicating by content.
|
|
27
|
+
*/
|
|
28
|
+
export declare function mergeVocabulary(...lists: CustomVocabularyEntry[][]): CustomVocabularyEntry[];
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Exercise Voice — Renders audio for exercises that require TTS.
|
|
3
|
+
*
|
|
4
|
+
* Handles listening comprehension, dictation, pronunciation, and
|
|
5
|
+
* dialogue roleplay (multi-speaker via MOSS-TTSD).
|
|
6
|
+
*/
|
|
7
|
+
import type { Exercise } from "../exercises/generator.js";
|
|
8
|
+
import { type WorkspaceVoiceConfig } from "./language-voices.js";
|
|
9
|
+
export interface AudioOutput {
|
|
10
|
+
audio: Buffer;
|
|
11
|
+
format: "mp3" | "wav" | "ogg";
|
|
12
|
+
durationMs: number;
|
|
13
|
+
voiceId: string;
|
|
14
|
+
}
|
|
15
|
+
export interface DialogueTurn {
|
|
16
|
+
speaker: string;
|
|
17
|
+
text: string;
|
|
18
|
+
}
|
|
19
|
+
export interface MossTtsdRequest {
|
|
20
|
+
turns: DialogueTurn[];
|
|
21
|
+
voices: Record<string, string>;
|
|
22
|
+
format: "mp3" | "wav";
|
|
23
|
+
}
|
|
24
|
+
export type TtsFn = (text: string, voiceId: string) => Promise<Buffer>;
|
|
25
|
+
export type MossTtsdFn = (request: MossTtsdRequest) => Promise<Buffer>;
|
|
26
|
+
export interface ExerciseVoiceDeps {
|
|
27
|
+
tts?: TtsFn;
|
|
28
|
+
mossTtsd?: MossTtsdFn;
|
|
29
|
+
workspaceVoiceConfig?: WorkspaceVoiceConfig;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Build a MOSS-TTSD multi-speaker synthesis request from exercise metadata.
|
|
33
|
+
*/
|
|
34
|
+
export declare function buildDialogueRequest(exercise: Exercise, language: string, workspaceConfig?: WorkspaceVoiceConfig): MossTtsdRequest;
|
|
35
|
+
/**
|
|
36
|
+
* Render audio for an exercise that has `audioRequired: true`.
|
|
37
|
+
* Selects the appropriate synthesis method based on exercise type.
|
|
38
|
+
*/
|
|
39
|
+
export declare function renderExerciseAudio(deps: ExerciseVoiceDeps, exercise: Exercise, language: string): Promise<AudioOutput | null>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language Voices — Per-language voice identity configuration.
|
|
3
|
+
*
|
|
4
|
+
* Maps languages to TTS voice configs, supports workspace-level
|
|
5
|
+
* overrides, and falls back to a multilingual voice when no
|
|
6
|
+
* language-specific voice is configured.
|
|
7
|
+
*/
|
|
8
|
+
export interface VoiceIdentity {
|
|
9
|
+
voiceId: string;
|
|
10
|
+
languageCode: string;
|
|
11
|
+
name: string;
|
|
12
|
+
gender: "male" | "female" | "neutral";
|
|
13
|
+
provider: "google" | "cartesia" | "murf" | "moss-ttsd";
|
|
14
|
+
}
|
|
15
|
+
export interface WorkspaceVoiceConfig {
|
|
16
|
+
overrides?: Record<string, VoiceIdentity>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Get the TTS voice identity for a given language code.
|
|
20
|
+
* Checks workspace overrides first, then defaults, then multilingual fallback.
|
|
21
|
+
*/
|
|
22
|
+
export declare function getVoiceForLanguage(language: string, workspaceConfig?: WorkspaceVoiceConfig): VoiceIdentity;
|
|
23
|
+
/**
|
|
24
|
+
* List all languages with dedicated voice support.
|
|
25
|
+
*/
|
|
26
|
+
export declare function listSupportedLanguages(): string[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pronunciation Scorer — Evaluates learner pronunciation by comparing
|
|
3
|
+
* STT transcription against expected text at the word level.
|
|
4
|
+
*/
|
|
5
|
+
export interface PronunciationResult {
|
|
6
|
+
score: number;
|
|
7
|
+
transcript: string;
|
|
8
|
+
feedback: string;
|
|
9
|
+
wordDetails: WordDetail[];
|
|
10
|
+
}
|
|
11
|
+
export interface WordDetail {
|
|
12
|
+
expected: string;
|
|
13
|
+
spoken: string | null;
|
|
14
|
+
match: "exact" | "partial" | "missed";
|
|
15
|
+
}
|
|
16
|
+
export interface PronunciationDeps {
|
|
17
|
+
/** Transcribe audio buffer to text. */
|
|
18
|
+
stt?: (audio: Buffer, language: string) => Promise<string>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Compare expected words against transcribed words.
|
|
22
|
+
* Returns per-word match details.
|
|
23
|
+
*/
|
|
24
|
+
export declare function compareWords(expected: string, transcript: string): WordDetail[];
|
|
25
|
+
/**
|
|
26
|
+
* Calculate pronunciation score from word details (0.0 – 1.0).
|
|
27
|
+
* Exact matches score 1, partial matches score 0.5, misses score 0.
|
|
28
|
+
*/
|
|
29
|
+
export declare function scoreFromDetails(details: WordDetail[]): number;
|
|
30
|
+
/**
|
|
31
|
+
* Generate human-readable feedback from word details.
|
|
32
|
+
*/
|
|
33
|
+
export declare function buildFeedback(details: WordDetail[], score: number): string;
|
|
34
|
+
/**
|
|
35
|
+
* Score learner pronunciation by transcribing spoken audio and comparing
|
|
36
|
+
* word-by-word against the expected phrase.
|
|
37
|
+
*/
|
|
38
|
+
export declare function scorePronunciation(deps: PronunciationDeps, expected: string, spokenAudio: Buffer, language: string): Promise<PronunciationResult>;
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fenixforce/edition-voices",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"registry": "https://registry.npmjs.org",
|
|
9
|
+
"access": "restricted"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "https://github.com/ejiogbevoices/projectfenix.git",
|
|
14
|
+
"directory": "packages/edition-voices"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"import": "./dist/index.js",
|
|
19
|
+
"types": "./dist/index.d.ts"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "esbuild src/index.ts --bundle --format=esm --platform=node --target=es2022 --minify --outfile=dist/index.js --external:@fenixforce/kernel --external:kysely --external:nanoid && tsc --emitDeclarationOnly --project tsconfig.build.json",
|
|
27
|
+
"test": "bun test",
|
|
28
|
+
"typecheck": "tsc --noEmit"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@fenixforce/kernel": "workspace:*",
|
|
32
|
+
"kysely": "^0.27.0",
|
|
33
|
+
"nanoid": "^5.1.6"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {}
|
|
36
|
+
}
|