@khorsheed/dsh-library 0.0.0-stage → 0.1.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/CHANGELOG.md +19 -0
- package/README.en.md +49 -0
- package/README.i18n.yaml +6 -0
- package/README.md +48 -2
- package/cordis.patch.yml +17 -0
- package/lib/client.js +66334 -0
- package/lib/index.js +3318 -0
- package/lib/profiles-CFrPQdfO.js +1692 -0
- package/lib/tool.js +1597 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +3811 -0
- package/lib/typert.remote-client.d.ts +137 -0
- package/lib/typert.remote-client.js +3077 -0
- package/lib/types/cards.d.ts +41 -0
- package/lib/types/cards.js +111 -0
- package/lib/types/client/LibraryPane.d.ts +26 -0
- package/lib/types/client/LibraryPane.js +129 -0
- package/lib/types/client/bind/BindChip.d.ts +33 -0
- package/lib/types/client/bind/BindChip.js +102 -0
- package/lib/types/client/contract.d.ts +167 -0
- package/lib/types/client/contract.js +1 -0
- package/lib/types/client/covers.d.ts +36 -0
- package/lib/types/client/covers.js +66 -0
- package/lib/types/client/definition.d.ts +27 -0
- package/lib/types/client/definition.js +32 -0
- package/lib/types/client/icons-local.d.ts +41 -0
- package/lib/types/client/icons-local.js +25 -0
- package/lib/types/client/icons.d.ts +27 -0
- package/lib/types/client/icons.js +11 -0
- package/lib/types/client/index.d.ts +35 -0
- package/lib/types/client/index.js +231 -0
- package/lib/types/client/judge.d.ts +70 -0
- package/lib/types/client/judge.js +262 -0
- package/lib/types/client/locales.d.ts +553 -0
- package/lib/types/client/locales.js +1076 -0
- package/lib/types/client/look.d.ts +63 -0
- package/lib/types/client/look.js +64 -0
- package/lib/types/client/math.d.ts +24 -0
- package/lib/types/client/math.js +47 -0
- package/lib/types/client/mistakes.d.ts +21 -0
- package/lib/types/client/mistakes.js +28 -0
- package/lib/types/client/preset-visibility.d.ts +109 -0
- package/lib/types/client/preset-visibility.js +123 -0
- package/lib/types/client/quiz-drafts.d.ts +29 -0
- package/lib/types/client/quiz-drafts.js +50 -0
- package/lib/types/client/reveal.d.ts +21 -0
- package/lib/types/client/reveal.js +31 -0
- package/lib/types/client/store.d.ts +111 -0
- package/lib/types/client/store.js +48 -0
- package/lib/types/client/titles.d.ts +17 -0
- package/lib/types/client/titles.js +23 -0
- package/lib/types/client/ui.d.ts +96 -0
- package/lib/types/client/ui.js +124 -0
- package/lib/types/client/views/book.d.ts +57 -0
- package/lib/types/client/views/book.js +164 -0
- package/lib/types/client/views/cards.d.ts +17 -0
- package/lib/types/client/views/cards.js +53 -0
- package/lib/types/client/views/home.d.ts +26 -0
- package/lib/types/client/views/home.js +263 -0
- package/lib/types/client/views/lesson.d.ts +42 -0
- package/lib/types/client/views/lesson.js +146 -0
- package/lib/types/client/views/path.d.ts +32 -0
- package/lib/types/client/views/path.js +64 -0
- package/lib/types/client/views/quiz.d.ts +42 -0
- package/lib/types/client/views/quiz.js +366 -0
- package/lib/types/client/views/station-quiz.d.ts +43 -0
- package/lib/types/client/views/station-quiz.js +203 -0
- package/lib/types/client/views/station.d.ts +18 -0
- package/lib/types/client/views/station.js +142 -0
- package/lib/types/image-refs.d.ts +35 -0
- package/lib/types/image-refs.js +71 -0
- package/lib/types/index.d.ts +51 -0
- package/lib/types/index.js +31 -0
- package/lib/types/mastery.d.ts +81 -0
- package/lib/types/mastery.js +110 -0
- package/lib/types/profiles.d.ts +31 -0
- package/lib/types/profiles.js +25 -0
- package/lib/types/remote.d.ts +355 -0
- package/lib/types/remote.js +332 -0
- package/lib/types/service/assess.d.ts +144 -0
- package/lib/types/service/assess.js +773 -0
- package/lib/types/service/bindings.d.ts +92 -0
- package/lib/types/service/bindings.js +198 -0
- package/lib/types/service/cards.d.ts +85 -0
- package/lib/types/service/cards.js +320 -0
- package/lib/types/service/core.d.ts +124 -0
- package/lib/types/service/core.js +169 -0
- package/lib/types/service/pages.d.ts +60 -0
- package/lib/types/service/pages.js +125 -0
- package/lib/types/service/roadmap.d.ts +99 -0
- package/lib/types/service/roadmap.js +284 -0
- package/lib/types/service/rules.d.ts +63 -0
- package/lib/types/service/rules.js +245 -0
- package/lib/types/service/shelf.d.ts +136 -0
- package/lib/types/service/shelf.js +422 -0
- package/lib/types/service.d.ts +42 -0
- package/lib/types/service.js +41 -0
- package/lib/types/store.d.ts +257 -0
- package/lib/types/store.js +1376 -0
- package/lib/types/tool-definition.d.ts +49 -0
- package/lib/types/tool-definition.js +49 -0
- package/lib/types/tool.d.ts +71 -0
- package/lib/types/tool.js +174 -0
- package/lib/types/tools/assess.d.ts +11 -0
- package/lib/types/tools/assess.js +82 -0
- package/lib/types/tools/binding.d.ts +11 -0
- package/lib/types/tools/binding.js +46 -0
- package/lib/types/tools/cards.d.ts +11 -0
- package/lib/types/tools/cards.js +100 -0
- package/lib/types/tools/kit.d.ts +215 -0
- package/lib/types/tools/kit.js +281 -0
- package/lib/types/tools/notes.d.ts +11 -0
- package/lib/types/tools/notes.js +40 -0
- package/lib/types/tools/page.d.ts +11 -0
- package/lib/types/tools/page.js +59 -0
- package/lib/types/tools/project.d.ts +11 -0
- package/lib/types/tools/project.js +67 -0
- package/lib/types/tools/quiz.d.ts +11 -0
- package/lib/types/tools/quiz.js +74 -0
- package/lib/types/tools/read.d.ts +11 -0
- package/lib/types/tools/read.js +127 -0
- package/lib/types/tools/resource.d.ts +11 -0
- package/lib/types/tools/resource.js +147 -0
- package/lib/types/tools/roadmap.d.ts +11 -0
- package/lib/types/tools/roadmap.js +102 -0
- package/lib/types/types.d.ts +752 -0
- package/lib/types/types.js +159 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +135 -3
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The model-tool face of the library: one small tool per noun, each with a
|
|
3
|
+
* schema sized to its own job (schema-level required fields per operation —
|
|
4
|
+
* the mega-tool's per-`what` missing-field teaching class moved to the schema
|
|
5
|
+
* line). `library_read` answers everything read-only; the `library_<noun>`
|
|
6
|
+
* writes validate and persist what the coach authored (the card dedupe and
|
|
7
|
+
* the two-axis quiz checks live behind them); `library_assess` is the one
|
|
8
|
+
* cascading write. Content is always generated on the model side; the tools
|
|
9
|
+
* are mechanical. There is no skill to load: each tool's parameter
|
|
10
|
+
* descriptions carry its payload shape, and every refusal teaches the correct
|
|
11
|
+
* call back.
|
|
12
|
+
*
|
|
13
|
+
* A path belongs to one TEXTBOOK (a resource with a contents; proposal
|
|
14
|
+
* 2026-10-19-library-book-paths): `library_roadmap` takes the textbook as a
|
|
15
|
+
* required `resource`, and a station names that textbook's rows as
|
|
16
|
+
* `chapters: "3-4"` plus side references in `also` — both expand to the
|
|
17
|
+
* stored covers (`{ ref, locator? }`). The older spellings still parse
|
|
18
|
+
* (`covers`, `chapterRanges`, `book`, node `kind`), they are just no longer
|
|
19
|
+
* advertised. A node's `gate` says how it completes (quiz | acceptance |
|
|
20
|
+
* read).
|
|
21
|
+
*
|
|
22
|
+
* `project` / `resource` default to the session's binding (the header chip),
|
|
23
|
+
* so a bound session rarely names them; a resource id alone resolves its
|
|
24
|
+
* owner project.
|
|
25
|
+
*
|
|
26
|
+
* Every tool is a thin adapter over {@link LibraryService}, one per file in
|
|
27
|
+
* `tools/` (shared transport helpers, schemas and the per-call kit in
|
|
28
|
+
* `tools/kit.ts`). This module ASSEMBLES the definitions in workflow order
|
|
29
|
+
* and registers nothing; the package's `./tool` row (`src/tool.ts`) tags
|
|
30
|
+
* and registers them.
|
|
31
|
+
*
|
|
32
|
+
* @module @khorsheed/dsh-library/tool-definition
|
|
33
|
+
*/
|
|
34
|
+
import type { ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
35
|
+
import type { LibraryService } from './service.ts';
|
|
36
|
+
import { ImageRefCache } from './image-refs.ts';
|
|
37
|
+
export { chaptersLocator, splitTocText } from './tools/kit.ts';
|
|
38
|
+
/** The names this module registers, in registration (workflow) order. */
|
|
39
|
+
export declare const LIBRARY_TOOL_NAMES: readonly string[];
|
|
40
|
+
/**
|
|
41
|
+
* Build the tools. Creating them is the package's `./tool` row, which applies
|
|
42
|
+
* the origin tag; this module never registers.
|
|
43
|
+
* @param service - the library service the adapters translate to.
|
|
44
|
+
* @param images - the session image references the tool row remembers (a
|
|
45
|
+
* page's photo is resolved from them, never trusted to the model's copy).
|
|
46
|
+
* @returns the definitions, untagged and unregistered, in workflow order.
|
|
47
|
+
*/
|
|
48
|
+
export declare function libraryToolDefinitions(service: LibraryService, images?: ImageRefCache): ToolDefinition[];
|
|
49
|
+
//# sourceMappingURL=tool-definition.d.ts.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { ImageRefCache } from "./image-refs.js";
|
|
2
|
+
import { createToolKit } from "./tools/kit.js";
|
|
3
|
+
import { libraryReadTool } from "./tools/read.js";
|
|
4
|
+
import { libraryProjectTool } from "./tools/project.js";
|
|
5
|
+
import { libraryResourceTool } from "./tools/resource.js";
|
|
6
|
+
import { libraryRoadmapTool } from "./tools/roadmap.js";
|
|
7
|
+
import { libraryNotesTool } from "./tools/notes.js";
|
|
8
|
+
import { libraryPageTool } from "./tools/page.js";
|
|
9
|
+
import { libraryCardsTool } from "./tools/cards.js";
|
|
10
|
+
import { libraryQuizTool } from "./tools/quiz.js";
|
|
11
|
+
import { libraryBindingTool } from "./tools/binding.js";
|
|
12
|
+
import { libraryAssessTool } from "./tools/assess.js";
|
|
13
|
+
export { chaptersLocator, splitTocText } from "./tools/kit.js";
|
|
14
|
+
/** The names this module registers, in registration (workflow) order. */
|
|
15
|
+
export const LIBRARY_TOOL_NAMES = [
|
|
16
|
+
'library_read',
|
|
17
|
+
'library_project',
|
|
18
|
+
'library_textbook',
|
|
19
|
+
'library_roadmap',
|
|
20
|
+
'library_notes',
|
|
21
|
+
'library_page',
|
|
22
|
+
'library_cards',
|
|
23
|
+
'library_quiz',
|
|
24
|
+
'library_binding',
|
|
25
|
+
'library_assess',
|
|
26
|
+
];
|
|
27
|
+
/**
|
|
28
|
+
* Build the tools. Creating them is the package's `./tool` row, which applies
|
|
29
|
+
* the origin tag; this module never registers.
|
|
30
|
+
* @param service - the library service the adapters translate to.
|
|
31
|
+
* @param images - the session image references the tool row remembers (a
|
|
32
|
+
* page's photo is resolved from them, never trusted to the model's copy).
|
|
33
|
+
* @returns the definitions, untagged and unregistered, in workflow order.
|
|
34
|
+
*/
|
|
35
|
+
export function libraryToolDefinitions(service, images = new ImageRefCache()) {
|
|
36
|
+
const kit = createToolKit(service, images);
|
|
37
|
+
return [
|
|
38
|
+
libraryReadTool(kit),
|
|
39
|
+
libraryProjectTool(kit),
|
|
40
|
+
libraryResourceTool(kit),
|
|
41
|
+
libraryRoadmapTool(kit),
|
|
42
|
+
libraryNotesTool(kit),
|
|
43
|
+
libraryPageTool(kit),
|
|
44
|
+
libraryCardsTool(kit),
|
|
45
|
+
libraryQuizTool(kit),
|
|
46
|
+
libraryBindingTool(kit),
|
|
47
|
+
libraryAssessTool(kit),
|
|
48
|
+
];
|
|
49
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session-granted library tools AND the coach's prompt — this package's
|
|
3
|
+
* `./tool` composition entry for agent-preset compositions. The row provides
|
|
4
|
+
* NO service (the preset-mount isolate-realm rule forbids service rows); it
|
|
5
|
+
* registers the model tools (library_read, one library_<noun> per write,
|
|
6
|
+
* library_assess), their guidance section, and a per-turn context line naming
|
|
7
|
+
* the session's bound book and its weakest cards, all delegating to the
|
|
8
|
+
* global `ctx.library` service core the core row provides at the profile root
|
|
9
|
+
* — the official tool-row shape (the evaluation-family fold, T88). Granting
|
|
10
|
+
* is therefore per-session: a preset names the row, its sessions get all of
|
|
11
|
+
* it; every other preset's sessions get none.
|
|
12
|
+
*
|
|
13
|
+
* The row can also quiet its scope for a focused preset (the learning
|
|
14
|
+
* mode): `hideSections` blanks root prompt sections that do not apply there
|
|
15
|
+
* (a scoped section shadows a global one of the same name), and `denyTools`
|
|
16
|
+
* masks root-registered tools for the scope. Both are best-effort — a name
|
|
17
|
+
* the composition does not hold is skipped, never a boot failure.
|
|
18
|
+
*
|
|
19
|
+
* @module @khorsheed/dsh-library/tool
|
|
20
|
+
*/
|
|
21
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
22
|
+
import z from '@deepseek-ai/schemastery';
|
|
23
|
+
import type { LibraryService } from './service.ts';
|
|
24
|
+
/** Plugin configuration. */
|
|
25
|
+
export interface LibraryToolConfig {
|
|
26
|
+
/**
|
|
27
|
+
* Whether this row grants the library tools: `all` (the default) or `none`
|
|
28
|
+
* (no tools, no guidance, no context — a deployment where the model must not
|
|
29
|
+
* even read the library). The core row's faces stay mounted either way.
|
|
30
|
+
*/
|
|
31
|
+
tools?: 'all' | 'none';
|
|
32
|
+
/** Root prompt sections to blank in this scope (e.g. `harness:source`). */
|
|
33
|
+
hideSections?: string[];
|
|
34
|
+
/** Root-registered tools to mask in this scope (e.g. `list_capabilities`). */
|
|
35
|
+
denyTools?: string[];
|
|
36
|
+
}
|
|
37
|
+
export declare const Config: z;
|
|
38
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
39
|
+
export declare const name = "library-tool";
|
|
40
|
+
/**
|
|
41
|
+
* The `library` core is a declared inject (the owning-family companion
|
|
42
|
+
* exception to the community-service probe rule): a preset's standing scope
|
|
43
|
+
* mounts at registry-activation time, BEFORE the profile's later bundle rows
|
|
44
|
+
* provide the core, so a one-shot ctx.get probe at apply saw ABSENT there and
|
|
45
|
+
* nothing re-ran the row (rc.1 boot order; 3080 production 2026-09-27). The
|
|
46
|
+
* declared inject pends the row until the core provides, then the body
|
|
47
|
+
* applies. The tools registry and the prompt assembly still join through
|
|
48
|
+
* deferred injection so their mount order cannot strand the registrations.
|
|
49
|
+
*/
|
|
50
|
+
export declare const inject: string[];
|
|
51
|
+
/**
|
|
52
|
+
* The coach's guidance, registered beside the tools it describes (order 114,
|
|
53
|
+
* the eval line). Per-`what` payloads are in the tool descriptions.
|
|
54
|
+
*/
|
|
55
|
+
export declare const LIBRARY_PROMPT = "The library holds the learner's projects, textbooks, paths and quizzes \u2014 record them there, never only in the chat. A refusal says what to fix: fix it and resend. Quote with \u300C\u300D inside any text you pass.\n\nThe learner leads; follow this order and stop where it says:\n1. A goal \u2192 ask one or two questions (level, direction), then library_project.\n2. Textbooks \u2192 library_textbook: the learner's book (verify edition and contents online), one you pick, or one you author.\n3. A path \u2192 library_roadmap. Then describe it in two lines and STOP: the learner reviews and adopts it in the sidebar. Before that: no \u8BB2\u4E49, no teaching, no quiz.\n4. A station starts only when the learner says so (\u300C\u5F00\u59CB\u5B66\u8FD9\u4E00\u7AD9\u300D or naming it). Then write its \u8BB2\u4E49 for this learner (library_notes), say in one line that they are ready, and wait: the learner reads first and asks. Answer in the chat; if a question shows the \u8BB2\u4E49 missed or got something wrong, fix that section.\n5. Stuck on the material \u2192 library_cards on that point. A quiz when asked \u2192 library_quiz, then one line. Grade \u2192 library_assess.\n6. A station passed \u2192 name the next one and ask whether to start it.\n\nThe per-turn \"Library:\" context is the current state; trust it over the chat.";
|
|
56
|
+
/**
|
|
57
|
+
* Render the per-turn context for one session: its binding and the weakest
|
|
58
|
+
* cards there. Kept short — it rides every turn.
|
|
59
|
+
*/
|
|
60
|
+
export declare function renderBindingContext(service: LibraryService, sessionId: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Plugin body: register the three tools, their guidance, and the per-turn
|
|
63
|
+
* bound context; quiet the scope when configured. The declared `library`
|
|
64
|
+
* inject pends the row until the core provides; the in-body guard stays as
|
|
65
|
+
* the defensive direct-call path.
|
|
66
|
+
* @param ctx - Cordis context (the preset's agent-plane mount).
|
|
67
|
+
* @param config - validated plugin config.
|
|
68
|
+
*/
|
|
69
|
+
export declare function apply(ctx: Context, config?: LibraryToolConfig): void;
|
|
70
|
+
export { libraryToolDefinitions, LIBRARY_TOOL_NAMES } from './tool-definition.ts';
|
|
71
|
+
//# sourceMappingURL=tool.d.ts.map
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import z from '@deepseek-ai/schemastery';
|
|
2
|
+
import { libraryToolDefinitions } from "./tool-definition.js";
|
|
3
|
+
import { ImageRefCache } from "./image-refs.js";
|
|
4
|
+
import { masteryReason } from "./mastery.js";
|
|
5
|
+
import { profileOf } from "./profiles.js";
|
|
6
|
+
const PACKAGE_NAME = '@khorsheed/dsh-library';
|
|
7
|
+
/**
|
|
8
|
+
* Tag the model-visible tools with their origin (AGENTS.md § Tool origin
|
|
9
|
+
* tagging; seam S12): the capability catalog reads this `Symbol.for`-keyed
|
|
10
|
+
* tag back through `ctx.tools.get()`, so the library tools attribute to THIS
|
|
11
|
+
* package (the row the preset mounts). The tag is host-side only and never
|
|
12
|
+
* travels on the model wire. Inlined (the eval precedent) — importing the
|
|
13
|
+
* helper from capability-catalog would be a cross-package edge.
|
|
14
|
+
*/
|
|
15
|
+
const definePluginTool = (def) => Object.assign(def, {
|
|
16
|
+
[Symbol.for('dsh.tool.origin')]: { channel: 'plugin', owner: PACKAGE_NAME },
|
|
17
|
+
});
|
|
18
|
+
export const Config = z.object({
|
|
19
|
+
tools: z.union([z.const('all'), z.const('none')]).default('all'),
|
|
20
|
+
hideSections: z.array(z.string()).default([]),
|
|
21
|
+
denyTools: z.array(z.string()).default([]),
|
|
22
|
+
});
|
|
23
|
+
/** Cordis plugin name used by loader diagnostics. */
|
|
24
|
+
export const name = 'library-tool';
|
|
25
|
+
/**
|
|
26
|
+
* The `library` core is a declared inject (the owning-family companion
|
|
27
|
+
* exception to the community-service probe rule): a preset's standing scope
|
|
28
|
+
* mounts at registry-activation time, BEFORE the profile's later bundle rows
|
|
29
|
+
* provide the core, so a one-shot ctx.get probe at apply saw ABSENT there and
|
|
30
|
+
* nothing re-ran the row (rc.1 boot order; 3080 production 2026-09-27). The
|
|
31
|
+
* declared inject pends the row until the core provides, then the body
|
|
32
|
+
* applies. The tools registry and the prompt assembly still join through
|
|
33
|
+
* deferred injection so their mount order cannot strand the registrations.
|
|
34
|
+
*/
|
|
35
|
+
export const inject = ['library'];
|
|
36
|
+
/**
|
|
37
|
+
* The coach's guidance, registered beside the tools it describes (order 114,
|
|
38
|
+
* the eval line). Per-`what` payloads are in the tool descriptions.
|
|
39
|
+
*/
|
|
40
|
+
export const LIBRARY_PROMPT = `The library holds the learner's projects, textbooks, paths and quizzes — record them there, never only in the chat. A refusal says what to fix: fix it and resend. Quote with 「」 inside any text you pass.
|
|
41
|
+
|
|
42
|
+
The learner leads; follow this order and stop where it says:
|
|
43
|
+
1. A goal → ask one or two questions (level, direction), then library_project.
|
|
44
|
+
2. Textbooks → library_textbook: the learner's book (verify edition and contents online), one you pick, or one you author.
|
|
45
|
+
3. A path → library_roadmap. Then describe it in two lines and STOP: the learner reviews and adopts it in the sidebar. Before that: no 讲义, no teaching, no quiz.
|
|
46
|
+
4. A station starts only when the learner says so (「开始学这一站」 or naming it). Then write its 讲义 for this learner (library_notes), say in one line that they are ready, and wait: the learner reads first and asks. Answer in the chat; if a question shows the 讲义 missed or got something wrong, fix that section.
|
|
47
|
+
5. Stuck on the material → library_cards on that point. A quiz when asked → library_quiz, then one line. Grade → library_assess.
|
|
48
|
+
6. A station passed → name the next one and ask whether to start it.
|
|
49
|
+
|
|
50
|
+
The per-turn "Library:" context is the current state; trust it over the chat.`;
|
|
51
|
+
/** The bound-context line's order: right after the sandbox / approval snapshots. */
|
|
52
|
+
const CONTEXT_ORDER = 650;
|
|
53
|
+
/** How many weak cards the per-turn context names. */
|
|
54
|
+
const CONTEXT_CARDS = 12;
|
|
55
|
+
/**
|
|
56
|
+
* Render the per-turn context for one session: its binding and the weakest
|
|
57
|
+
* cards there. Kept short — it rides every turn.
|
|
58
|
+
*/
|
|
59
|
+
export function renderBindingContext(service, sessionId) {
|
|
60
|
+
const summary = service.bindingSummary(sessionId, CONTEXT_CARDS);
|
|
61
|
+
if (summary.binding === undefined)
|
|
62
|
+
return 'Library: this session is not bound to a project yet.';
|
|
63
|
+
if (summary.project === undefined)
|
|
64
|
+
return 'Library: this session\'s bound project no longer exists — ask the learner which one to use.';
|
|
65
|
+
const { project, resource, textbooks, weakCards } = summary;
|
|
66
|
+
const lines = [
|
|
67
|
+
`Library: bound to project 「${project.name}」 (${project.id}, template ${project.template})`
|
|
68
|
+
+ (resource === undefined ? '.' : `, textbook 《${resource.title}》 (${resource.id}).`),
|
|
69
|
+
];
|
|
70
|
+
if (summary.binding.previousProjectId !== undefined && summary.binding.previousProjectId !== project.id) {
|
|
71
|
+
const previous = service.projectName(summary.binding.previousProjectId);
|
|
72
|
+
lines.push(`This session worked on another project${previous === undefined ? '' : ` 「${previous}」`} before switching here: nothing said about it is 「${project.name}」's material — never quiz, teach or note from it here.`);
|
|
73
|
+
}
|
|
74
|
+
if (textbooks.length === 0) {
|
|
75
|
+
lines.push('Textbooks: none yet — shelve them first (library_textbook).');
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
lines.push('Textbooks (study order):');
|
|
79
|
+
for (const textbook of textbooks) {
|
|
80
|
+
const state = textbook.roadmap === undefined
|
|
81
|
+
? (textbook.hasDraft ? `draft ${textbook.draftTotal ?? 0} stations, waiting for the learner to adopt` : 'no path yet')
|
|
82
|
+
: `path adopted, ${textbook.roadmap.done}/${textbook.roadmap.total} done`
|
|
83
|
+
+ (textbook.current === undefined ? '' : `, current: ${textbook.current.title} (${textbook.current.id})`)
|
|
84
|
+
+ (textbook.hasDraft ? `; a new draft (${textbook.draftTotal ?? 0} stations) waits to replace it` : '');
|
|
85
|
+
lines.push(` ${textbook.seq ?? '-'}. 《${textbook.title}》 (${textbook.id}) ${state}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const profile = profileOf(project.template);
|
|
89
|
+
if (profile.pointsDriven && summary.station !== undefined) {
|
|
90
|
+
const { station } = summary;
|
|
91
|
+
lines.push(`Current station ${station.title} (${station.id}) — its points:`);
|
|
92
|
+
for (const point of station.mastery.points) {
|
|
93
|
+
const reason = masteryReason(point);
|
|
94
|
+
lines.push(` - ${point.title} (${point.id}): ${point.state}${reason === '' ? '' : ` — ${reason}`}`);
|
|
95
|
+
}
|
|
96
|
+
if (station.mastery.open.length > 0)
|
|
97
|
+
lines.push(`Open misses here: ${station.mastery.open.length} (the learner retakes them in the sidebar; they count for points, not for passing).`);
|
|
98
|
+
}
|
|
99
|
+
if (weakCards.length > 0)
|
|
100
|
+
lines.push(`Weak cards: ${weakCards.map(card => `${card.term} [${card.weakness}]`).join('; ')}.`);
|
|
101
|
+
lines.push(`Subject: ${profile.coachHint}`);
|
|
102
|
+
return lines.join('\n');
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Plugin body: register the three tools, their guidance, and the per-turn
|
|
106
|
+
* bound context; quiet the scope when configured. The declared `library`
|
|
107
|
+
* inject pends the row until the core provides; the in-body guard stays as
|
|
108
|
+
* the defensive direct-call path.
|
|
109
|
+
* @param ctx - Cordis context (the preset's agent-plane mount).
|
|
110
|
+
* @param config - validated plugin config.
|
|
111
|
+
*/
|
|
112
|
+
export function apply(ctx, config = {}) {
|
|
113
|
+
const service = ctx.get('library');
|
|
114
|
+
if (service === undefined) {
|
|
115
|
+
ctx.logger.info(`${PACKAGE_NAME}: the global library service is absent — the library tools are not registered`);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// `none` grants nothing at all (guidance about an absent tool is a wrong
|
|
119
|
+
// instruction, not a harmless one).
|
|
120
|
+
if ((config.tools ?? 'all') === 'none')
|
|
121
|
+
return;
|
|
122
|
+
// Deferred injection, NOT an apply-time probe: `ctx.get('tools')` races the
|
|
123
|
+
// tools registry's own mount order on the real composition tree.
|
|
124
|
+
// Photos: remember each session's image references as they are committed,
|
|
125
|
+
// so a recorded page carries the whole reference the model cannot see.
|
|
126
|
+
const images = new ImageRefCache();
|
|
127
|
+
ctx.on('session/event', ((session, event) => {
|
|
128
|
+
images.observe(String(session.id), event);
|
|
129
|
+
}));
|
|
130
|
+
ctx.inject(['tools'], (toolsCtx) => {
|
|
131
|
+
for (const definition of libraryToolDefinitions(service, images)) {
|
|
132
|
+
toolsCtx.effect(() => toolsCtx.tools.register(definePluginTool(definition)), `${PACKAGE_NAME}: ${definition.name} tool`);
|
|
133
|
+
}
|
|
134
|
+
for (const toolName of config.denyTools ?? []) {
|
|
135
|
+
try {
|
|
136
|
+
toolsCtx.effect(() => toolsCtx.tools.restrict({ deny: [toolName] }), `${PACKAGE_NAME}: mask ${toolName}`);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
ctx.logger.info(`${PACKAGE_NAME}: could not mask ${toolName} here — ${error instanceof Error ? error.message : String(error)}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
ctx.inject(['systemPrompt'], (promptCtx) => {
|
|
144
|
+
const prompt = promptCtx.get('systemPrompt');
|
|
145
|
+
if (prompt === undefined)
|
|
146
|
+
return;
|
|
147
|
+
prompt.section({ name: 'tool:library', order: 114, text: LIBRARY_PROMPT });
|
|
148
|
+
for (const sectionName of config.hideSections ?? []) {
|
|
149
|
+
try {
|
|
150
|
+
prompt.section({ name: sectionName, order: 0, text: '' });
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
ctx.logger.info(`${PACKAGE_NAME}: could not blank ${sectionName} — ${error instanceof Error ? error.message : String(error)}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
// The callback is synchronous; the store's reads are too, and small.
|
|
157
|
+
prompt.context({
|
|
158
|
+
name: 'library:binding',
|
|
159
|
+
order: CONTEXT_ORDER,
|
|
160
|
+
text: (context) => {
|
|
161
|
+
const id = context.agent?.session?.id;
|
|
162
|
+
if (id === undefined)
|
|
163
|
+
return '';
|
|
164
|
+
try {
|
|
165
|
+
return renderBindingContext(service, String(id));
|
|
166
|
+
}
|
|
167
|
+
catch {
|
|
168
|
+
return '';
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
export { libraryToolDefinitions, LIBRARY_TOOL_NAMES } from "./tool-definition.js";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_assess` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/assess
|
|
6
|
+
*/
|
|
7
|
+
import { type ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { type ToolKit } from './kit.ts';
|
|
9
|
+
/** Build the `library_assess` tool over the shared kit. */
|
|
10
|
+
export declare function libraryAssessTool(kit: ToolKit): ToolDefinition;
|
|
11
|
+
//# sourceMappingURL=assess.d.ts.map
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_assess` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/assess
|
|
6
|
+
*/
|
|
7
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { jsonOutput, pickString, unwrap } from "./kit.js";
|
|
9
|
+
/** A passed station's result carries the next step: name what follows and ask (the learner starts it). */
|
|
10
|
+
function withNext(result) {
|
|
11
|
+
if (result.completed === undefined)
|
|
12
|
+
return result;
|
|
13
|
+
return { ...result, next: `Congratulate the learner, name the next station${result.unlocked !== undefined && result.unlocked.length > 0 ? ` (${result.unlocked.join(', ')})` : ''} and ask whether to start it — start only when they say so.` };
|
|
14
|
+
}
|
|
15
|
+
/** Build the `library_assess` tool over the shared kit. */
|
|
16
|
+
export function libraryAssessTool(kit) {
|
|
17
|
+
const { service, projectOf } = kit;
|
|
18
|
+
return defineTool({
|
|
19
|
+
name: 'library_assess',
|
|
20
|
+
description: 'Record a graded sitting after judging it in the chat: one verdict per question. The stored answer can be wrong — judge by the question, and say so when it was. '
|
|
21
|
+
+ 'A hands-on station: pass node instead of quiz; each task is a line id (L1, L2…). Then tell the learner what moved and what unlocked.',
|
|
22
|
+
parameters: {
|
|
23
|
+
project: { type: 'string', description: 'Project id (defaults to the binding).' },
|
|
24
|
+
quiz: { type: 'string', description: 'The quiz id.' },
|
|
25
|
+
node: { type: 'string', description: 'A hands-on station, instead of quiz.' },
|
|
26
|
+
resource: { type: 'string', description: 'Its textbook (defaults to wherever the node is).' },
|
|
27
|
+
verdicts: {
|
|
28
|
+
type: 'array',
|
|
29
|
+
required: true,
|
|
30
|
+
items: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
additionalProperties: true,
|
|
33
|
+
properties: {
|
|
34
|
+
task: { type: 'string', description: 'The question id, or the line id (L1…).' },
|
|
35
|
+
pass: { type: 'boolean' },
|
|
36
|
+
ratio: { type: 'number', description: 'Partial credit 0..1.' },
|
|
37
|
+
why: { type: 'string', description: 'One line on the judgment.' },
|
|
38
|
+
void: { type: 'boolean', description: 'The learner flagged it broken.' },
|
|
39
|
+
missedCard: { type: 'object', additionalProperties: false, description: 'Language projects: the word they missed.', properties: { term: { type: 'string' }, gloss: { type: 'string' } } },
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
output: jsonOutput(),
|
|
45
|
+
isConcurrencySafe: () => false,
|
|
46
|
+
async execute(args, exec) {
|
|
47
|
+
if (!Array.isArray(args.verdicts) || args.verdicts.length === 0) {
|
|
48
|
+
throw new Error('verdicts is required — one per question: [{ task: <question id>, pass, why, ratio?, void? }]');
|
|
49
|
+
}
|
|
50
|
+
// why is the one line the coach writes; criterion / evidence / by keep
|
|
51
|
+
// their ledger slots (older calls still send them).
|
|
52
|
+
const verdicts = args.verdicts.map(verdict => {
|
|
53
|
+
const why = pickString(verdict.why, verdict.criterion, verdict.evidence) ?? '';
|
|
54
|
+
return {
|
|
55
|
+
...verdict,
|
|
56
|
+
criterion: pickString(verdict.criterion) ?? why,
|
|
57
|
+
evidence: pickString(verdict.evidence) ?? why,
|
|
58
|
+
by: verdict.by === 'self' || verdict.by === 'user' ? verdict.by : 'llm',
|
|
59
|
+
};
|
|
60
|
+
});
|
|
61
|
+
const node = pickString(args.node);
|
|
62
|
+
if (node !== undefined) {
|
|
63
|
+
const resource = pickString(args.resource, args.book);
|
|
64
|
+
return withNext(unwrap(await service.putAttempt({
|
|
65
|
+
projectId: projectOf(args.project, exec, resource),
|
|
66
|
+
nodeId: node,
|
|
67
|
+
...(resource === undefined ? {} : { resourceId: resource }),
|
|
68
|
+
verdicts,
|
|
69
|
+
})));
|
|
70
|
+
}
|
|
71
|
+
const quiz = pickString(args.quiz);
|
|
72
|
+
if (quiz === undefined)
|
|
73
|
+
throw new Error('quiz is the id library_quiz returned — or pass node (an acceptance station\'s id) for acceptance');
|
|
74
|
+
const result = unwrap(await service.putAttempt({
|
|
75
|
+
projectId: projectOf(args.project, exec),
|
|
76
|
+
assessmentId: quiz,
|
|
77
|
+
verdicts,
|
|
78
|
+
}));
|
|
79
|
+
return withNext(result);
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_binding` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/binding
|
|
6
|
+
*/
|
|
7
|
+
import { type ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { type ToolKit } from './kit.ts';
|
|
9
|
+
/** Build the `library_binding` tool over the shared kit. */
|
|
10
|
+
export declare function libraryBindingTool(kit: ToolKit): ToolDefinition;
|
|
11
|
+
//# sourceMappingURL=binding.d.ts.map
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_binding` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/binding
|
|
6
|
+
*/
|
|
7
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { jsonOutput, payload, pickString, sessionIdOf, unwrap, } from "./kit.js";
|
|
9
|
+
/** Build the `library_binding` tool over the shared kit. */
|
|
10
|
+
export function libraryBindingTool(kit) {
|
|
11
|
+
const { service, textbookList } = kit;
|
|
12
|
+
return defineTool({
|
|
13
|
+
name: 'library_binding',
|
|
14
|
+
description: 'Bind this session to a project (and optionally a textbook) — every tool\'s default. Writes bind an unbound session by themselves; use this to switch, or unbind: true.',
|
|
15
|
+
parameters: {
|
|
16
|
+
project: { type: 'string' },
|
|
17
|
+
resource: { type: 'string', description: 'A textbook in it.' },
|
|
18
|
+
unbind: { type: 'boolean' },
|
|
19
|
+
},
|
|
20
|
+
output: jsonOutput(),
|
|
21
|
+
isConcurrencySafe: () => false,
|
|
22
|
+
async execute(args, exec) {
|
|
23
|
+
const sessionId = sessionIdOf(exec);
|
|
24
|
+
if (sessionId === undefined)
|
|
25
|
+
throw new Error('binding needs a session — this call came from none');
|
|
26
|
+
// unbind: true (or the older binding: null) unbinds; the older nested
|
|
27
|
+
// binding { project, resource } still parses.
|
|
28
|
+
const argsAny = args;
|
|
29
|
+
if (args.unbind === true || argsAny.binding === null)
|
|
30
|
+
return unwrap(await service.setBinding(sessionId, null));
|
|
31
|
+
const body = payload(argsAny.binding) ?? {};
|
|
32
|
+
const projectId = pickString(body.project, body.projectId, args.project);
|
|
33
|
+
const resourceId = pickString(body.resource, body.resourceId, body.book, body.bookId, args.resource, args.book);
|
|
34
|
+
if (projectId === undefined) {
|
|
35
|
+
throw new Error('binding needs the project: { project, resource? } (library_read · projects lists them); unbind: true to unbind');
|
|
36
|
+
}
|
|
37
|
+
// An id the shelf does not hold (a guessed slug before the textbook is
|
|
38
|
+
// registered) binds the project and says so — not a failed call.
|
|
39
|
+
if (resourceId !== undefined && !(await service.getResource({ projectId, resourceId })).ok) {
|
|
40
|
+
const bound = unwrap(await service.setBinding(sessionId, { projectId }));
|
|
41
|
+
return { ...bound, note: `no resource ${JSON.stringify(resourceId)} in this project — bound the project only. Resource ids are minted (r_…); registering the textbook (library_textbook) binds this session to it by itself${textbookList(exec)}` };
|
|
42
|
+
}
|
|
43
|
+
return unwrap(await service.setBinding(sessionId, { projectId, ...(resourceId === undefined ? {} : { resourceId }) }));
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_cards` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/cards
|
|
6
|
+
*/
|
|
7
|
+
import { type ToolDefinition } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { type ToolKit } from './kit.ts';
|
|
9
|
+
/** Build the `library_cards` tool over the shared kit. */
|
|
10
|
+
export declare function libraryCardsTool(kit: ToolKit): ToolDefinition;
|
|
11
|
+
//# sourceMappingURL=cards.d.ts.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `library_cards` tool (moved verbatim from tool-definition.ts; the shared
|
|
3
|
+
* helpers and schemas are in `tools/kit.ts`).
|
|
4
|
+
*
|
|
5
|
+
* @module @khorsheed/dsh-library/tools/cards
|
|
6
|
+
*/
|
|
7
|
+
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
8
|
+
import { profileOf } from "../profiles.js";
|
|
9
|
+
import { arrival, jsonOutput, payload, payloadList, pickString, sessionIdOf, unwrap, } from "./kit.js";
|
|
10
|
+
/** Build the `library_cards` tool over the shared kit. */
|
|
11
|
+
export function libraryCardsTool(kit) {
|
|
12
|
+
const { service, bindingOf, projectOf, scopeParams } = kit;
|
|
13
|
+
return defineTool({
|
|
14
|
+
name: 'library_cards',
|
|
15
|
+
description: 'The learner is stuck on the material (mixes it up, asks the same thing again) → note it now, then say in one line what you noted. '
|
|
16
|
+
+ 'Subject projects: { point, gloss } — the note lands on the current station\'s knowledge point. Language projects: { term, gloss, sentence? } becomes a word card; a near-duplicate comes back pending — resend it with mergeInto or asNew. '
|
|
17
|
+
+ 'Not a weak point: curiosity, your own slip (fix the quiz), or feedback on a question or the tools.',
|
|
18
|
+
parameters: {
|
|
19
|
+
...scopeParams,
|
|
20
|
+
resource: { type: 'string', description: 'The textbook (defaults to the binding\'s).' },
|
|
21
|
+
cards: {
|
|
22
|
+
oneOf: [
|
|
23
|
+
{ type: 'array', items: { type: 'object', additionalProperties: true, properties: {
|
|
24
|
+
point: { type: 'string', description: 'Subject: the knowledge point id.' },
|
|
25
|
+
term: { type: 'string', description: 'What they stuck on, in a few words.' },
|
|
26
|
+
gloss: { type: 'string', description: 'What they got wrong, or the meaning.' },
|
|
27
|
+
sentence: { type: 'string', description: 'Language: the sentence it came from.' },
|
|
28
|
+
mergeInto: { type: 'string' },
|
|
29
|
+
asNew: { type: 'boolean' },
|
|
30
|
+
} } },
|
|
31
|
+
{ type: 'string', description: 'The same list as JSON text.' },
|
|
32
|
+
],
|
|
33
|
+
required: true,
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
output: jsonOutput(),
|
|
37
|
+
isConcurrencySafe: () => false,
|
|
38
|
+
async execute(args, exec) {
|
|
39
|
+
const list = payloadList(args.cards);
|
|
40
|
+
if (list === undefined) {
|
|
41
|
+
throw new Error(`library_cards takes cards: [{ point, gloss }] (subject) or [{ term, gloss, sentence? }] (language) — ${arrival('cards', args.cards)}`);
|
|
42
|
+
}
|
|
43
|
+
const sessionId = sessionIdOf(exec);
|
|
44
|
+
const resource = pickString(args.resource, args.book) ?? bindingOf(exec)?.resourceId;
|
|
45
|
+
const projectId = projectOf(args.project, exec, resource);
|
|
46
|
+
const project = profileOf(unwrap(await service.getProject({ id: projectId })).project.template);
|
|
47
|
+
const cards = list.map(card => {
|
|
48
|
+
const source = payload(card.source) ?? { type: 'chat' };
|
|
49
|
+
// A flat sentence is the context; a card from the chat weakens unless told otherwise.
|
|
50
|
+
const context = payload(card.context) ?? (pickString(card.sentence) === undefined ? undefined : { sentence: card.sentence });
|
|
51
|
+
return {
|
|
52
|
+
...card,
|
|
53
|
+
kind: card.kind === 'word' || card.kind === 'concept' ? card.kind : project.markKind,
|
|
54
|
+
...(card.weaken === undefined && source.type === 'chat' ? { weaken: true } : {}),
|
|
55
|
+
source: { ...source, ...(source.type === 'chat' && source.ref === undefined && sessionId !== undefined ? { ref: sessionId } : {}) },
|
|
56
|
+
...(context === undefined ? {} : { context: { sentence: String(context.sentence ?? ''), ...(typeof context.node === 'string' ? { nodeId: context.node } : {}) } }),
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
// A subject project's weak point with a knowledge point lands on the point.
|
|
60
|
+
const pointed = project.pointsDriven
|
|
61
|
+
? list.filter(card => pickString(card.point) !== undefined)
|
|
62
|
+
: [];
|
|
63
|
+
const noted = [];
|
|
64
|
+
for (const card of pointed) {
|
|
65
|
+
const station = pickString(card.node, payload(card.context)?.node) ?? (sessionId === undefined ? undefined : service.bindingSummary(sessionId, 0).station?.id);
|
|
66
|
+
if (station === undefined)
|
|
67
|
+
throw new Error('a point note needs its station: node (the per-turn context names the current station and its points)');
|
|
68
|
+
const term = pickString(card.term);
|
|
69
|
+
const text = [term, pickString(card.gloss, card.userGloss)].filter(Boolean).join(' — ');
|
|
70
|
+
const written = unwrap(await service.notePoint({
|
|
71
|
+
projectId,
|
|
72
|
+
...(resource === undefined ? {} : { resourceId: resource }),
|
|
73
|
+
nodeId: station,
|
|
74
|
+
pointId: pickString(card.point),
|
|
75
|
+
text: text === '' ? String(card.gloss ?? '') : text,
|
|
76
|
+
...(term === undefined ? {} : { term }),
|
|
77
|
+
...(sessionId === undefined ? {} : { session: sessionId }),
|
|
78
|
+
}));
|
|
79
|
+
noted.push({ point: written.point.title, state: written.point.state, ...(written.changes.length === 0 ? {} : { changed: written.changes.map(change => `${change.title}: ${change.from} → ${change.to}`) }) });
|
|
80
|
+
}
|
|
81
|
+
const rest = cards.filter((_card, index) => !pointed.includes(list[index]));
|
|
82
|
+
// In a points-driven project a weak point from the conversation lands on
|
|
83
|
+
// a knowledge point or nowhere: a free-floating chat card is how the
|
|
84
|
+
// coach's own slips and the learner's feedback leaked in as 「弱点」.
|
|
85
|
+
if (project.pointsDriven) {
|
|
86
|
+
const loose = rest.filter(card => card.source?.type === 'chat');
|
|
87
|
+
if (loose.length > 0) {
|
|
88
|
+
const points = sessionId === undefined ? [] : (service.bindingSummary(sessionId, 0).station?.mastery.points ?? []);
|
|
89
|
+
throw new Error(`in a subject project a weak point from the conversation lands on the station's knowledge point — add point (+ node) to: ${loose.map(card => card.term ?? '?').join(', ')}${points.length === 0 ? '' : `; this station's points: ${points.map(point => `${point.id} (${point.title})`).join(', ')}`}. Record only what the LEARNER got wrong about the material — never your own slip (a wrong answer key: fix the quiz) and never feedback on a question or the tools. If none of that applies, record nothing.`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
const recorded = rest.length === 0 ? undefined : unwrap(await service.writeCards({
|
|
93
|
+
projectId,
|
|
94
|
+
...(resource === undefined ? {} : { resourceId: resource }),
|
|
95
|
+
cards: rest,
|
|
96
|
+
}));
|
|
97
|
+
return { ...(recorded ?? { ok: true }), ...(noted.length === 0 ? {} : { pointNotes: noted }) };
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|