dsh-deeptutor 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/lib/index.d.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * DeepTutor bridge plugin for DeepSeek Harness (dsh).
3
+ *
4
+ * Migrated from the pi coding-agent extension (TecFancy/pi-extensions,
5
+ * extensions/deeptutor + skills/deeptutor). Registers three model-facing tools:
6
+ * - deeptutor_run — execute a DeepTutor learning capability (HTTP/WS first, CLI fallback)
7
+ * - deeptutor_kb — list / search / info knowledge bases
8
+ * - deeptutor_note — archive a Markdown learning note to a server notebook
9
+ *
10
+ * Deployment auto-detection:
11
+ * - Local: deeptutor serve runs on this machine → API probe succeeds directly; if serve is
12
+ * not running, falls back to the local CLI (DEEPTUTOR_LOCAL_BIN).
13
+ * - Remote: serve runs on a server (listening on 127.0.0.1 only) → an SSH tunnel is started
14
+ * automatically to reach the API; if the tunnel is unavailable, falls back to the SSH CLI.
15
+ *
16
+ * Env config (agent-agnostic): DEEPTUTOR_SSH_HOST / DEEPTUTOR_API_BASE / DEEPTUTOR_LOCAL_BIN /
17
+ * DEEPTUTOR_REMOTE_BIN / DEEPTUTOR_REMOTE_HOME — see src/config.ts and skills/deeptutor/SKILL.md.
18
+ */
19
+ import type { Context } from "@deepseek-ai/cordis";
20
+ export declare const name = "dsh-deeptutor";
21
+ export declare const inject: readonly ["tools"];
22
+ export declare function apply(ctx: Context): void;
package/lib/index.js ADDED
@@ -0,0 +1,384 @@
1
+ import { defineTool } from "@deepseek-ai/dsh-tools";
2
+ import { CAPABILITIES, configGuide, configHint, SSH_HOST, truncate } from "./config.js";
3
+ import { cliNoteLocal, cliNoteRemote, runCli } from "./cli-exec.js";
4
+ import { apiJson, ensureApi, killTunnel } from "./http-api.js";
5
+ import { renderHtml } from "./html-render.js";
6
+ import { cliRunTurn, fmtTurnResult, wsRunTurn } from "./turn.js";
7
+ import { HTML_DOC_SCRIPT } from "./html-render.js";
8
+ export const name = "dsh-deeptutor";
9
+ export const inject = ["tools"];
10
+ function json(o) {
11
+ const out = {};
12
+ for (const [k, v] of Object.entries(o))
13
+ if (v !== undefined)
14
+ out[k] = v;
15
+ return out;
16
+ }
17
+ export function apply(ctx) {
18
+ console.log(`[dsh-deeptutor] plugin loaded (html script: ${HTML_DOC_SCRIPT})`);
19
+ // Kill the SSH tunnel process when the plugin unloads.
20
+ ctx.effect(() => {
21
+ return () => killTunnel();
22
+ });
23
+ // ---------- Tool 1: run a DeepTutor learning capability ----------
24
+ ctx.tools.register(defineTool({
25
+ name: "deeptutor_run",
26
+ description: "Execute one learning call on the DeepTutor service: deep_solve (in-depth explanation), deep_question (generate self-test questions), deep_research (deep research), chat (conversation), mastery_path (learning path planning), visualize / math_animator (visualization). Can mount knowledge bases (kbs) and tools (tools like rag / web_search). Returns the final answer plus session_id; pass session_id in later turns to continue the context (inherits chat history, knowledge bases and tools).",
27
+ parameters: {
28
+ capability: {
29
+ type: "string",
30
+ enum: [...CAPABILITIES],
31
+ required: true,
32
+ description: "The learning capability to run",
33
+ },
34
+ prompt: {
35
+ type: "string",
36
+ required: true,
37
+ description: "The learning question or instruction for this turn (e.g. explain the async/await state machine)",
38
+ },
39
+ kbs: {
40
+ type: "array",
41
+ items: { type: "string" },
42
+ description: "Knowledge base names (check deeptutor_kb list for available ones, e.g. the user's dotnet/sqlserver bases)",
43
+ },
44
+ tools: {
45
+ type: "array",
46
+ items: { type: "string" },
47
+ description: "Tools to enable: rag (KB retrieval), web_search, reason, brainstorm, paper_search, code_execution, read_source, etc.",
48
+ },
49
+ session_id: {
50
+ type: "string",
51
+ description: "Session ID from a previous deeptutor_run call to continue that context",
52
+ },
53
+ config: {
54
+ type: "object",
55
+ additionalProperties: true,
56
+ description: "Capability config key-values, e.g. num_questions=5 for deep_question, mode=report / depth=standard for deep_research",
57
+ },
58
+ language: {
59
+ type: "string",
60
+ description: "Response language code, default en; use zh for Chinese",
61
+ },
62
+ timeout: {
63
+ type: "number",
64
+ description: "Timeout in seconds, default 600; use 900+ for deep_research",
65
+ },
66
+ max_chars: {
67
+ type: "number",
68
+ description: "Max characters of the answer returned to the model, default 30000",
69
+ },
70
+ html: {
71
+ type: "string",
72
+ description: "Optional output path for a self-contained local HTML render of the answer (bundled md-to-html converter; also writes the companion .md source next to it). e.g. data/study/abstract-class/knowledge.html — open the HTML in a browser to read comfortably. Set it when the user wants browsable/printable study material.",
73
+ },
74
+ html_title: {
75
+ type: "string",
76
+ description: "HTML page title (default: derived from the html output file name)",
77
+ },
78
+ },
79
+ output: {
80
+ schema: {
81
+ type: "object",
82
+ properties: {
83
+ mode: { type: "string" },
84
+ sessionId: { type: "string" },
85
+ answer: { type: "string" },
86
+ toolCalls: { type: "array", items: { type: "string" } },
87
+ errors: { type: "array", items: { type: "string" } },
88
+ text: { type: "string" },
89
+ },
90
+ additionalProperties: true,
91
+ },
92
+ render: (_args, value) => [{ type: "text", text: value.text ?? "" }],
93
+ },
94
+ async execute(args, exec) {
95
+ const timeoutMs = (args.timeout ?? 600) * 1000;
96
+ const maxChars = args.max_chars ?? 30000;
97
+ try {
98
+ if (await ensureApi()) {
99
+ const r = await wsRunTurn(args, timeoutMs, exec.signal);
100
+ const htmlNote = await renderHtml(args, r.answer);
101
+ return json({
102
+ mode: "http",
103
+ sessionId: r.sessionId,
104
+ toolCalls: r.toolCalls,
105
+ errors: r.errors,
106
+ text: configHint() + fmtTurnResult(args.capability, r, maxChars) + htmlNote,
107
+ });
108
+ }
109
+ }
110
+ catch (err) {
111
+ if (exec.signal.aborted) {
112
+ // user cancelled mid-flight: don't start a fresh CLI run behind their back
113
+ throw new Error("[deeptutor_run cancelled]");
114
+ }
115
+ console.log(`[deeptutor] HTTP/WS failed, falling back to CLI: ${err?.message}`);
116
+ }
117
+ // fallback: local/SSH CLI
118
+ try {
119
+ const r = await cliRunTurn(args, timeoutMs, exec.signal);
120
+ const htmlNote = await renderHtml(args, r.answer);
121
+ return json({
122
+ mode: "cli",
123
+ sessionId: r.sessionId,
124
+ toolCalls: r.toolCalls,
125
+ errors: r.errors,
126
+ text: configHint() + fmtTurnResult(args.capability, r, maxChars) + htmlNote,
127
+ });
128
+ }
129
+ catch (err) {
130
+ throw new Error(configGuide() +
131
+ `[deeptutor_run failed (HTTP and CLI both unavailable)] ${err?.message ?? String(err)}`);
132
+ }
133
+ },
134
+ }));
135
+ // ---------- Tool 2: knowledge base operations ----------
136
+ ctx.tools.register(defineTool({
137
+ name: "deeptutor_kb",
138
+ description: "Manage/search DeepTutor knowledge bases: list all bases, search inside a base (RAG hit snippets), info for details. Works in both local and remote deployments. Search the user's personal knowledge bases before generating learning content and combine the hits with authoritative sources.",
139
+ parameters: {
140
+ action: {
141
+ type: "string",
142
+ enum: ["list", "search", "info"],
143
+ required: true,
144
+ description: "What to do with the knowledge bases",
145
+ },
146
+ kb: {
147
+ type: "string",
148
+ description: "Knowledge base name (required for search/info)",
149
+ },
150
+ query: {
151
+ type: "string",
152
+ description: "Search question (required for search, e.g. C# generics covariance/contravariance)",
153
+ },
154
+ mode: {
155
+ type: "string",
156
+ enum: ["hybrid", "dense", "sparse"],
157
+ description: "Retrieval mode, default hybrid",
158
+ },
159
+ top_k: {
160
+ type: "number",
161
+ description: "Number of hits to return, default 5",
162
+ },
163
+ max_chars: {
164
+ type: "number",
165
+ description: "Max characters of the returned content, default 30000",
166
+ },
167
+ },
168
+ output: {
169
+ schema: {
170
+ type: "object",
171
+ properties: {
172
+ action: { type: "string" },
173
+ mode: { type: "string" },
174
+ kb: { type: "string" },
175
+ query: { type: "string" },
176
+ text: { type: "string" },
177
+ },
178
+ additionalProperties: true,
179
+ },
180
+ render: (_args, value) => [{ type: "text", text: value.text ?? "" }],
181
+ },
182
+ async execute(args, exec) {
183
+ const maxChars = args.max_chars ?? 30000;
184
+ // search goes through the CLI (REST has no dedicated search route);
185
+ // list/info prefer HTTP, fall back to CLI
186
+ if (args.action !== "search") {
187
+ try {
188
+ if (await ensureApi()) {
189
+ if (args.action === "list") {
190
+ const arr = await apiJson("/api/v1/knowledge/list", undefined, {
191
+ signal: exec.signal,
192
+ });
193
+ const body = Array.isArray(arr)
194
+ ? arr
195
+ .map((kb) => `- ${kb.name} (status=${kb.status ?? "?"}, ${kb.statistics ? JSON.stringify(kb.statistics) : "no stats"}${kb.is_default ? ", default" : ""})`)
196
+ .join("\n")
197
+ : JSON.stringify(arr);
198
+ return json({
199
+ action: "list",
200
+ mode: "http",
201
+ text: configHint() +
202
+ `Knowledge bases (${Array.isArray(arr) ? arr.length : "?"}):\n${body}`,
203
+ });
204
+ }
205
+ const kb = await apiJson(`/api/v1/knowledge/${encodeURIComponent(args.kb ?? "")}`, undefined, { signal: exec.signal });
206
+ return json({
207
+ action: "info",
208
+ mode: "http",
209
+ kb: args.kb,
210
+ text: truncate(JSON.stringify(kb, null, 2), maxChars),
211
+ });
212
+ }
213
+ }
214
+ catch (err) {
215
+ console.log(`[deeptutor] kb ${args.action} HTTP failed, falling back to CLI: ${err?.message}`);
216
+ }
217
+ }
218
+ // CLI fallback (local or SSH)
219
+ let argv;
220
+ if (args.action === "list") {
221
+ argv = ["kb", "list", "--format", "json"];
222
+ }
223
+ else if (args.action === "info") {
224
+ argv = ["kb", "info", args.kb ?? ""];
225
+ }
226
+ else {
227
+ argv = [
228
+ "kb",
229
+ "search",
230
+ args.kb ?? "",
231
+ args.query ?? "",
232
+ "--format",
233
+ "json",
234
+ "--mode",
235
+ args.mode ?? "hybrid",
236
+ ];
237
+ if (args.top_k)
238
+ argv.push("--top-k", String(args.top_k));
239
+ }
240
+ const { stdout, stderr, code, killed } = await runCli(argv, {
241
+ signal: exec.signal,
242
+ timeoutMs: 90_000,
243
+ });
244
+ if (killed)
245
+ throw new Error(configGuide() + "[deeptutor_kb timed out]");
246
+ if (code !== 0) {
247
+ throw new Error(`[deeptutor_kb failed] exit=${code}\nstderr: ${truncate(stderr || "(none)", 2000)}` +
248
+ configGuide());
249
+ }
250
+ let body = stdout.trim() || "(empty result)";
251
+ if (args.action === "search") {
252
+ try {
253
+ const arr = JSON.parse(stdout);
254
+ if (Array.isArray(arr)) {
255
+ body = arr
256
+ .map((s, i) => {
257
+ const text = s.text ?? s.content ?? s.snippet ?? JSON.stringify(s);
258
+ const src = s.source ?? s.doc_name ?? s.file ?? "";
259
+ return `[${i + 1}]${src ? ` source: ${src}` : ""}\n${typeof text === "string" ? text : JSON.stringify(text)}`;
260
+ })
261
+ .join("\n\n");
262
+ body = `${arr.length} hits:\n\n` + body;
263
+ }
264
+ }
265
+ catch {
266
+ /* non-JSON output returned as-is */
267
+ }
268
+ }
269
+ return json({
270
+ action: args.action,
271
+ mode: "cli",
272
+ kb: args.kb,
273
+ query: args.query,
274
+ text: truncate(body, maxChars),
275
+ });
276
+ },
277
+ }));
278
+ // ---------- Tool 3: archive learning notes to a server notebook ----------
279
+ ctx.tools.register(defineTool({
280
+ name: "deeptutor_note",
281
+ description: "Archive a Markdown learning note (study plan, deep-solve summary, wrong answers, etc.) to a DeepTutor notebook (auto-creates the notebook if missing). Record type: chat / question / research / solve.",
282
+ parameters: {
283
+ notebook: {
284
+ type: "string",
285
+ required: true,
286
+ description: "Notebook name, e.g. dotnet-learning (auto-created if missing)",
287
+ },
288
+ title: {
289
+ type: "string",
290
+ required: true,
291
+ description: "Record title, e.g. async/await state machine deep-dive notes",
292
+ },
293
+ type: {
294
+ type: "string",
295
+ enum: ["chat", "question", "research", "solve"],
296
+ description: "Record type, default chat",
297
+ },
298
+ content: {
299
+ type: "string",
300
+ required: true,
301
+ description: "Learning note content in Markdown",
302
+ },
303
+ },
304
+ output: {
305
+ schema: {
306
+ type: "object",
307
+ properties: {
308
+ notebook: { type: "string" },
309
+ nbId: { type: "string" },
310
+ title: { type: "string" },
311
+ mode: { type: "string" },
312
+ text: { type: "string" },
313
+ },
314
+ additionalProperties: true,
315
+ },
316
+ render: (_args, value) => [{ type: "text", text: value.text ?? "" }],
317
+ },
318
+ async execute(args, exec) {
319
+ const recordType = (args.type ?? "chat");
320
+ const nbName = args.notebook.trim();
321
+ // HTTP first
322
+ try {
323
+ if (await ensureApi()) {
324
+ const list = await apiJson("/api/v1/notebook/list", undefined, {
325
+ signal: exec.signal,
326
+ });
327
+ const notebooks = list?.notebooks ?? [];
328
+ let nb = notebooks.find((n) => n.name?.trim() === nbName);
329
+ if (!nb) {
330
+ const created = await apiJson("/api/v1/notebook/create", {
331
+ method: "POST",
332
+ headers: { "Content-Type": "application/json" },
333
+ body: JSON.stringify({
334
+ name: nbName,
335
+ description: `Archived by dsh agent (${new Date().toISOString().slice(0, 10)})`,
336
+ }),
337
+ }, { signal: exec.signal });
338
+ nb = created?.notebook ?? created;
339
+ }
340
+ const nbId = nb?.id ?? (typeof nb === "string" ? nb : undefined);
341
+ if (!nbId)
342
+ throw new Error("cannot resolve notebook id");
343
+ const added = await apiJson("/api/v1/notebook/add_record", {
344
+ method: "POST",
345
+ headers: { "Content-Type": "application/json" },
346
+ body: JSON.stringify({
347
+ notebook_ids: [nbId],
348
+ record_type: recordType,
349
+ title: args.title,
350
+ user_query: args.title,
351
+ output: args.content,
352
+ }),
353
+ }, { signal: exec.signal });
354
+ return json({
355
+ notebook: nbName,
356
+ nbId: String(nbId),
357
+ title: args.title,
358
+ mode: "http",
359
+ text: configHint() +
360
+ `[note archived (HTTP)] notebook=${nbName} (${nbId}), title=${args.title}, type=${recordType}\n${truncate(JSON.stringify(added ?? {}), 800)}`,
361
+ });
362
+ }
363
+ }
364
+ catch (err) {
365
+ console.log(`[deeptutor] note HTTP failed, falling back to CLI: ${err?.message}`);
366
+ }
367
+ // CLI fallback: remote deployment uses the remote PY_NOTE script; local deployment runs locally
368
+ const { stdout, stderr, code } = SSH_HOST
369
+ ? await cliNoteRemote(nbName, args.title, recordType, args.content, exec.signal)
370
+ : await cliNoteLocal(nbName, args.title, recordType, args.content, exec.signal);
371
+ if (code !== 0) {
372
+ throw new Error(`[deeptutor_note failed] exit=${code}\n${truncate(stderr || stdout, 2000)}` +
373
+ configGuide());
374
+ }
375
+ return json({
376
+ notebook: nbName,
377
+ title: args.title,
378
+ mode: "cli",
379
+ text: configHint() +
380
+ `[note archived (CLI)] notebook=${nbName}, title=${args.title}, type=${recordType}\n${truncate((stdout ?? "").trim(), 2000)}`,
381
+ });
382
+ },
383
+ }));
384
+ }
package/lib/turn.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ export interface TurnResult {
2
+ answer: string;
3
+ errors: string[];
4
+ toolCalls: string[];
5
+ sessionId?: string;
6
+ title?: string;
7
+ }
8
+ /** Fields both wsRunTurn and cliRunTurn read off the deeptutor_run tool params. */
9
+ export interface RunParams {
10
+ prompt: string;
11
+ capability: string;
12
+ kbs?: string[];
13
+ tools?: string[];
14
+ session_id?: string;
15
+ language?: string;
16
+ config?: Record<string, unknown>;
17
+ }
18
+ /** Fold one event (WS or CLI JSONL) into the running turn state. Exported for unit tests. */
19
+ export declare function collectEvent(state: TurnResult, e: any): void;
20
+ /** Execute one turn over WebSocket (streaming). */
21
+ export declare function wsRunTurn(params: RunParams, timeoutMs: number, signal?: AbortSignal): Promise<TurnResult>;
22
+ /** Execute one turn via the CLI (local or SSH). */
23
+ export declare function cliRunTurn(params: RunParams, timeoutMs: number, signal?: AbortSignal): Promise<TurnResult>;
24
+ export declare const fmtTurnResult: (capability: string, r: TurnResult, maxChars: number) => string;
package/lib/turn.js ADDED
@@ -0,0 +1,159 @@
1
+ /**
2
+ * DeepTutor bridge: one learning "turn" — running a capability over WebSocket (HTTP mode)
3
+ * or the CLI (fallback), and shared JSONL/WS event parsing + result formatting.
4
+ */
5
+ import { API_BASE, truncate } from "./config.js";
6
+ import { runCli } from "./cli-exec.js";
7
+ /** Fold one event (WS or CLI JSONL) into the running turn state. Exported for unit tests. */
8
+ export function collectEvent(state, e) {
9
+ if (e.type === "content" && typeof e.content === "string")
10
+ state.answer += e.content;
11
+ else if (e.type === "result" && typeof e.metadata?.response === "string")
12
+ state.answer = e.metadata.response;
13
+ else if (e.type === "error")
14
+ state.errors.push(typeof e.content === "string" ? e.content : JSON.stringify(e.metadata ?? ""));
15
+ else if (e.type === "tool_call")
16
+ state.toolCalls.push(typeof e.content === "string" ? e.content : String(e.metadata?.name ?? ""));
17
+ else if (e.type === "session_meta" && e.metadata?.title)
18
+ state.title = e.metadata.title;
19
+ if (!state.sessionId && typeof e.session_id === "string")
20
+ state.sessionId = e.session_id;
21
+ }
22
+ /** Execute one turn over WebSocket (streaming). */
23
+ export function wsRunTurn(params, timeoutMs, signal) {
24
+ return new Promise((resolve, reject) => {
25
+ if (signal?.aborted)
26
+ return reject(new Error("Aborted"));
27
+ const WSImpl = globalThis.WebSocket;
28
+ if (!WSImpl)
29
+ return reject(new Error("WebSocket is not available in this environment"));
30
+ const ws = new WSImpl(`${API_BASE.replace(/^http/, "ws")}/api/v1/ws`);
31
+ let settled = false;
32
+ const state = { answer: "", errors: [], toolCalls: [] };
33
+ let timer;
34
+ const finish = (fn) => {
35
+ if (settled)
36
+ return;
37
+ settled = true;
38
+ if (timer)
39
+ clearTimeout(timer);
40
+ signal?.removeEventListener("abort", onAbort);
41
+ fn();
42
+ };
43
+ timer = setTimeout(() => {
44
+ finish(() => {
45
+ try {
46
+ ws.close();
47
+ }
48
+ catch { }
49
+ reject(new Error(`WebSocket timeout (${Math.round(timeoutMs / 1000)}s)`));
50
+ });
51
+ }, timeoutMs);
52
+ const onAbort = () => {
53
+ finish(() => {
54
+ try {
55
+ ws.close();
56
+ }
57
+ catch { }
58
+ reject(new Error("Aborted"));
59
+ });
60
+ };
61
+ signal?.addEventListener("abort", onAbort, { once: true });
62
+ ws.onopen = () => {
63
+ ws.send(JSON.stringify({
64
+ type: "start_turn",
65
+ content: params.prompt,
66
+ capability: params.capability,
67
+ session_id: params.session_id ?? null,
68
+ tools: params.tools ?? [],
69
+ knowledge_bases: params.kbs ?? [],
70
+ language: params.language ?? "en",
71
+ config: params.config ?? {},
72
+ notebook_references: [],
73
+ history_references: [],
74
+ attachments: [],
75
+ skills: [],
76
+ }));
77
+ };
78
+ ws.onmessage = (ev) => {
79
+ let e;
80
+ try {
81
+ e = JSON.parse(ev.data);
82
+ }
83
+ catch {
84
+ return;
85
+ }
86
+ collectEvent(state, e);
87
+ if (e.type === "done") {
88
+ finish(() => {
89
+ try {
90
+ ws.close();
91
+ }
92
+ catch { }
93
+ resolve(state);
94
+ });
95
+ }
96
+ };
97
+ ws.onerror = () => {
98
+ finish(() => {
99
+ try {
100
+ ws.close();
101
+ }
102
+ catch { }
103
+ reject(new Error("WebSocket connection error (server not running or tunnel down)"));
104
+ });
105
+ };
106
+ ws.onclose = () => {
107
+ finish(() => {
108
+ reject(new Error("WebSocket closed unexpectedly"));
109
+ });
110
+ };
111
+ });
112
+ }
113
+ /** Execute one turn via the CLI (local or SSH). */
114
+ export async function cliRunTurn(params, timeoutMs, signal) {
115
+ const argv = ["run", params.capability, params.prompt, "--format", "json"];
116
+ for (const kb of params.kbs ?? [])
117
+ argv.push("--kb", kb);
118
+ for (const t of params.tools ?? [])
119
+ argv.push("--tool", t);
120
+ if (params.session_id)
121
+ argv.push("--session", params.session_id);
122
+ if (params.language)
123
+ argv.push("--language", params.language);
124
+ for (const [k, v] of Object.entries(params.config ?? {}))
125
+ argv.push("--config", `${k}=${typeof v === "object" ? JSON.stringify(v) : v}`);
126
+ const { stdout, stderr, code, killed } = await runCli(argv, { signal, timeoutMs });
127
+ if (killed)
128
+ throw new Error(`CLI execution timed out (${Math.round(timeoutMs / 1000)}s)`);
129
+ if (code !== 0)
130
+ throw new Error(`CLI exit=${code}: ${stderr || "(no stderr)"}`);
131
+ const state = { answer: "", errors: [], toolCalls: [] };
132
+ for (const line of stdout.split("\n")) {
133
+ if (!line.trim())
134
+ continue;
135
+ let e;
136
+ try {
137
+ e = JSON.parse(line);
138
+ }
139
+ catch {
140
+ continue;
141
+ }
142
+ collectEvent(state, e);
143
+ }
144
+ return state;
145
+ }
146
+ export const fmtTurnResult = (capability, r, maxChars) => {
147
+ const parts = [];
148
+ parts.push(`[deeptutor ${capability} done]`);
149
+ if (r.sessionId)
150
+ parts.push(`- session_id: ${r.sessionId} (pass session_id in later turns to continue this context)`);
151
+ if (r.title)
152
+ parts.push(`- session title: ${r.title}`);
153
+ if (r.toolCalls.length)
154
+ parts.push(`- tools used: ${[...new Set(r.toolCalls)].join(", ")}`);
155
+ if (r.errors.length)
156
+ parts.push(`- note (some steps failed): ${r.errors.slice(0, 3).join(" | ")}`);
157
+ parts.push(`\n===== Answer =====\n${truncate(r.answer || "(no answer)", maxChars)}`);
158
+ return parts.join("\n");
159
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "dsh-deeptutor",
3
+ "version": "0.1.0",
4
+ "description": "DeepTutor bridge bundle for DeepSeek Harness: learning capabilities, knowledge bases, and note archiving (HTTP/WS first, CLI fallback; auto-adapts local/remote deployment).",
5
+ "keywords": [
6
+ "dsh",
7
+ "dsh-plugin",
8
+ "dsh-bundle",
9
+ "deeptutor",
10
+ "tutoring",
11
+ "rag",
12
+ "agent"
13
+ ],
14
+ "license": "MIT",
15
+ "type": "module",
16
+ "main": "./lib/index.js",
17
+ "types": "./lib/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./lib/index.d.ts",
21
+ "default": "./lib/index.js"
22
+ },
23
+ "./cordis.patch.yml": "./cordis.patch.yml",
24
+ "./src/*": "./src/*",
25
+ "./package.json": "./package.json"
26
+ },
27
+ "files": [
28
+ "lib",
29
+ "src",
30
+ "scripts",
31
+ "cordis.patch.yml",
32
+ "README.md"
33
+ ],
34
+ "dsh": {
35
+ "bundle": {
36
+ "patch": "./cordis.patch.yml"
37
+ }
38
+ },
39
+ "scripts": {
40
+ "build": "tsc -p tsconfig.build.json",
41
+ "typecheck": "tsc --noEmit",
42
+ "prepack": "npm run build && npm run typecheck",
43
+ "pack:check": "npm pack --dry-run"
44
+ },
45
+ "dependencies": {
46
+ "@deepseek-ai/cordis": "^4.0.1",
47
+ "@deepseek-ai/dsh-tools": "^0.1.0-rc.6"
48
+ },
49
+ "devDependencies": {
50
+ "@types/node": "^24.0.0",
51
+ "typescript": "^5.9.3"
52
+ }
53
+ }