@istarry/dsh-assistant-profile 0.4.5 → 0.5.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/client.js +139 -4273
- package/lib/client.js.map +1 -1
- package/lib/index.js +141 -356
- package/package.json +1 -5
- package/lib/typert.host.d.ts +0 -3
- package/lib/typert.host.js +0 -285
- package/lib/typert.remote-client.d.ts +0 -32
- package/lib/typert.remote-client.js +0 -202
package/lib/index.js
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
import z from "@deepseek-ai/schemastery";
|
|
2
|
-
import {
|
|
2
|
+
import { TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
3
3
|
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { dirname, join } from "node:path";
|
|
8
|
-
import { dshHomePath } from "@deepseek-ai/dsh-home-paths";
|
|
4
|
+
import "@deepseek-ai/dsh-llm";
|
|
5
|
+
import "@deepseek-ai/dsh-session";
|
|
9
6
|
//#region ../tooling/node_modules/.pnpm/js-yaml@4.2.0/node_modules/js-yaml/dist/js-yaml.mjs
|
|
10
7
|
/*! js-yaml 4.2.0 https://github.com/nodeca/js-yaml @license MIT */
|
|
11
8
|
var __create = Object.create;
|
|
@@ -2416,45 +2413,35 @@ const DEFAULT_PERSONA = {
|
|
|
2416
2413
|
//#endregion
|
|
2417
2414
|
//#region ../.build/tsc/memory.js
|
|
2418
2415
|
/**
|
|
2419
|
-
*
|
|
2416
|
+
* 助理记忆的存储形态:**一段 markdown 文本**,存在本插件的配置字段里
|
|
2417
|
+
* (`memoryText`),而不是独立的文件。
|
|
2420
2418
|
*
|
|
2421
|
-
*
|
|
2422
|
-
*
|
|
2419
|
+
* 为什么不用文件:记忆要能被设置页读写、能随 profile 持久化、能在注入时同步取到。
|
|
2420
|
+
* 官方设置通道(`configForms` → profile 的 Cordis patch)对**标量字段**支持最好,
|
|
2421
|
+
* 而自定义 RPC 需要 typert 生成物,那条路在本插件里没走通(client 半端加载器会跳过
|
|
2422
|
+
* 带生成 descriptors 的 bundle)。所以把整段文本当作一个配置项:编辑器保存 = 写配置,
|
|
2423
|
+
* 注入 = 读配置,重启后仍在,全程不经过远程调用。
|
|
2423
2424
|
*
|
|
2424
|
-
*
|
|
2425
|
-
* `- [YYYY-MM-DD HH:mm]
|
|
2426
|
-
* 时间戳而不是复制一行。
|
|
2427
|
-
*
|
|
2428
|
-
* 读写都是同步的:提示词组装调用 section thunk 时没有 await 点,而文件很小。
|
|
2425
|
+
* 文本形状与 agent-suite 的 memory.md 保持一致,便于以后迁移:
|
|
2426
|
+
* `# 标题` 之类的自由文本由用户写,每条记忆是一行 `- [YYYY-MM-DD HH:mm] 内容`。
|
|
2429
2427
|
*
|
|
2430
2428
|
* @module @istarry/dsh-assistant-profile/memory
|
|
2431
2429
|
*/
|
|
2432
|
-
/** 本包在 harness home 下占用的目录名。 */
|
|
2433
|
-
const MEMORY_DIR = "assistant-profile";
|
|
2434
|
-
/** 记忆文档的文件名。 */
|
|
2435
|
-
const MEMORY_FILE = "memory.md";
|
|
2436
2430
|
const ENTRY_RE = /^-?\s*\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2})\]\s*(.*)$/;
|
|
2437
|
-
/**
|
|
2431
|
+
/**
|
|
2432
|
+
* 把本地时间格式化成条目的时间戳。
|
|
2433
|
+
* @param date - 时间来源,测试可注入。
|
|
2434
|
+
* @returns `YYYY-MM-DD HH:mm`。
|
|
2435
|
+
*/
|
|
2438
2436
|
function stampNow(date = /* @__PURE__ */ new Date()) {
|
|
2439
2437
|
const pad = (n) => String(n).padStart(2, "0");
|
|
2440
2438
|
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
|
|
2441
2439
|
}
|
|
2442
|
-
/**
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
function readMemoryText() {
|
|
2448
|
-
const path = memoryPath();
|
|
2449
|
-
return existsSync(path) ? readFileSync(path, "utf8") : "";
|
|
2450
|
-
}
|
|
2451
|
-
/** 整体替换记忆文档(用户编辑框里的保存)。 */
|
|
2452
|
-
function writeMemory(text) {
|
|
2453
|
-
const path = memoryPath();
|
|
2454
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
2455
|
-
writeFileSync(path, text, "utf8");
|
|
2456
|
-
}
|
|
2457
|
-
/** 从文档正文里解析出所有记忆条目。 */
|
|
2440
|
+
/**
|
|
2441
|
+
* 从文本里解析出所有记忆条目。
|
|
2442
|
+
* @param text - 整段记忆文本。
|
|
2443
|
+
* @returns 按出现顺序的条目。
|
|
2444
|
+
*/
|
|
2458
2445
|
function parseEntries(text) {
|
|
2459
2446
|
const entries = [];
|
|
2460
2447
|
for (const line of text.split(/\r?\n/)) {
|
|
@@ -2468,30 +2455,29 @@ function parseEntries(text) {
|
|
|
2468
2455
|
return entries;
|
|
2469
2456
|
}
|
|
2470
2457
|
/**
|
|
2471
|
-
*
|
|
2472
|
-
*
|
|
2473
|
-
* @
|
|
2474
|
-
* @returns 用于比较的规范键。
|
|
2458
|
+
* 去重比较键:大小写折叠、连续空白压成一个。
|
|
2459
|
+
* @param content - 条目内容。
|
|
2460
|
+
* @returns 规范键。
|
|
2475
2461
|
*/
|
|
2476
2462
|
function dedupeKey(content) {
|
|
2477
2463
|
return content.trim().toLowerCase().replace(/\s+/g, " ");
|
|
2478
2464
|
}
|
|
2479
2465
|
/**
|
|
2480
|
-
*
|
|
2481
|
-
*
|
|
2482
|
-
* @param
|
|
2483
|
-
* @param
|
|
2484
|
-
* @
|
|
2466
|
+
* 往整段记忆文本里追加一条事实,并对已有条目去重:
|
|
2467
|
+
* 同义条目刷新时间戳,否则追加新行;非条目行(标题、空行)原样保留。
|
|
2468
|
+
* @param text - 现有整段文本。
|
|
2469
|
+
* @param content - 要记住的事实。
|
|
2470
|
+
* @param at - 时间来源。
|
|
2471
|
+
* @returns 追加结果,含新文本。
|
|
2485
2472
|
*/
|
|
2486
|
-
function
|
|
2487
|
-
const text = readMemoryText();
|
|
2488
|
-
const stamp = stampNow(at);
|
|
2473
|
+
function appendToText(text, content, at = /* @__PURE__ */ new Date()) {
|
|
2489
2474
|
const trimmed = content.trim();
|
|
2490
2475
|
if (trimmed === "") return {
|
|
2491
2476
|
appended: false,
|
|
2492
2477
|
refreshed: false,
|
|
2493
|
-
|
|
2478
|
+
text
|
|
2494
2479
|
};
|
|
2480
|
+
const stamp = stampNow(at);
|
|
2495
2481
|
const key = dedupeKey(trimmed);
|
|
2496
2482
|
const headerLines = [];
|
|
2497
2483
|
const entryLines = [];
|
|
@@ -2502,29 +2488,27 @@ function appendMemory(content, at = /* @__PURE__ */ new Date()) {
|
|
|
2502
2488
|
if (match === null) continue;
|
|
2503
2489
|
if (dedupeKey((match[2] ?? "").trim()) === key) {
|
|
2504
2490
|
entryLines[index] = `- [${stamp}] ${trimmed}`;
|
|
2505
|
-
writeChunked(headerLines, entryLines);
|
|
2506
2491
|
return {
|
|
2507
2492
|
appended: false,
|
|
2508
2493
|
refreshed: true,
|
|
2509
|
-
|
|
2494
|
+
text: compose(headerLines, entryLines)
|
|
2510
2495
|
};
|
|
2511
2496
|
}
|
|
2512
2497
|
}
|
|
2513
2498
|
entryLines.push(`- [${stamp}] ${trimmed}`);
|
|
2514
|
-
writeChunked(headerLines, entryLines);
|
|
2515
2499
|
return {
|
|
2516
2500
|
appended: true,
|
|
2517
2501
|
refreshed: false,
|
|
2518
|
-
|
|
2502
|
+
text: compose(headerLines, entryLines)
|
|
2519
2503
|
};
|
|
2520
2504
|
}
|
|
2521
|
-
/**
|
|
2522
|
-
function
|
|
2505
|
+
/** 拼回整段文本,顺手收掉标题块首尾的空行(避免反复追加时越积越多)。 */
|
|
2506
|
+
function compose(headerLines, entryLines) {
|
|
2523
2507
|
const meaningful = [...headerLines];
|
|
2524
2508
|
while (meaningful.length > 0 && /^\s*$/.test(meaningful[0])) meaningful.shift();
|
|
2525
2509
|
while (meaningful.length > 0 && /^\s*$/.test(meaningful[meaningful.length - 1])) meaningful.pop();
|
|
2526
2510
|
const body = [...meaningful, ...entryLines].join("\n");
|
|
2527
|
-
|
|
2511
|
+
return body.endsWith("\n") ? body : `${body}\n`;
|
|
2528
2512
|
}
|
|
2529
2513
|
//#endregion
|
|
2530
2514
|
//#region ../.build/tsc/index.js
|
|
@@ -2556,44 +2540,6 @@ function writeChunked(headerLines, entryLines) {
|
|
|
2556
2540
|
*
|
|
2557
2541
|
* @module @istarry/dsh-assistant-profile
|
|
2558
2542
|
*/
|
|
2559
|
-
var __runInitializers = function(thisArg, initializers, value) {
|
|
2560
|
-
var useValue = arguments.length > 2;
|
|
2561
|
-
for (var i = 0; i < initializers.length; i++) value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
|
2562
|
-
return useValue ? value : void 0;
|
|
2563
|
-
};
|
|
2564
|
-
var __esDecorate = function(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
|
2565
|
-
function accept(f) {
|
|
2566
|
-
if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected");
|
|
2567
|
-
return f;
|
|
2568
|
-
}
|
|
2569
|
-
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
|
2570
|
-
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
|
2571
|
-
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
|
2572
|
-
var _, done = false;
|
|
2573
|
-
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
2574
|
-
var context = {};
|
|
2575
|
-
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
|
2576
|
-
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
|
2577
|
-
context.addInitializer = function(f) {
|
|
2578
|
-
if (done) throw new TypeError("Cannot add initializers after decoration has completed");
|
|
2579
|
-
extraInitializers.push(accept(f || null));
|
|
2580
|
-
};
|
|
2581
|
-
var result = (0, decorators[i])(kind === "accessor" ? {
|
|
2582
|
-
get: descriptor.get,
|
|
2583
|
-
set: descriptor.set
|
|
2584
|
-
} : descriptor[key], context);
|
|
2585
|
-
if (kind === "accessor") {
|
|
2586
|
-
if (result === void 0) continue;
|
|
2587
|
-
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
|
2588
|
-
if (_ = accept(result.get)) descriptor.get = _;
|
|
2589
|
-
if (_ = accept(result.set)) descriptor.set = _;
|
|
2590
|
-
if (_ = accept(result.init)) initializers.unshift(_);
|
|
2591
|
-
} else if (_ = accept(result)) if (kind === "field") initializers.unshift(_);
|
|
2592
|
-
else descriptor[key] = _;
|
|
2593
|
-
}
|
|
2594
|
-
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
|
2595
|
-
done = true;
|
|
2596
|
-
};
|
|
2597
2543
|
/** Cordis 插件名。 */
|
|
2598
2544
|
const name = "assistant-profile";
|
|
2599
2545
|
/** 人格所在的那一段的规范名——预设与档案共用同一个名字,遮蔽才成立。 */
|
|
@@ -2616,7 +2562,8 @@ const Config = z.object({
|
|
|
2616
2562
|
persona: z.string().default(DEFAULT_PERSONA.persona).volatile(),
|
|
2617
2563
|
customsJson: z.string().default("[]").volatile(),
|
|
2618
2564
|
memoryEnabled: z.boolean().default(true).description("Inject the assistant memory into the system prompt").volatile(),
|
|
2619
|
-
autoMemory: z.boolean().default(false).description("Let the model write memories on its own judgment (requires memoryEnabled)").volatile()
|
|
2565
|
+
autoMemory: z.boolean().default(false).description("Let the model write memories on its own judgment (requires memoryEnabled)").volatile(),
|
|
2566
|
+
memoryText: z.string().default("").description("The assistant memory document").volatile()
|
|
2620
2567
|
});
|
|
2621
2568
|
/** 跨插件重载共享的当前配置引用:旧闭包也能读到新值。 */
|
|
2622
2569
|
const LIVE = Symbol.for("dsh.assistant-profile.config");
|
|
@@ -2737,273 +2684,111 @@ const MEMORY_ADD_VALUE_SCHEMA = {
|
|
|
2737
2684
|
content: { type: "string" }
|
|
2738
2685
|
}
|
|
2739
2686
|
};
|
|
2740
|
-
/**
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
2747
|
-
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
private: false,
|
|
2770
|
-
access: {
|
|
2771
|
-
has: (obj) => "writeGlobal" in obj,
|
|
2772
|
-
get: (obj) => obj.writeGlobal
|
|
2773
|
-
},
|
|
2774
|
-
metadata: _metadata
|
|
2775
|
-
}, null, _instanceExtraInitializers);
|
|
2776
|
-
__esDecorate(this, null, _remember_decorators, {
|
|
2777
|
-
kind: "method",
|
|
2778
|
-
name: "remember",
|
|
2779
|
-
static: false,
|
|
2780
|
-
private: false,
|
|
2781
|
-
access: {
|
|
2782
|
-
has: (obj) => "remember" in obj,
|
|
2783
|
-
get: (obj) => obj.remember
|
|
2784
|
-
},
|
|
2785
|
-
metadata: _metadata
|
|
2786
|
-
}, null, _instanceExtraInitializers);
|
|
2787
|
-
if (_metadata) Object.defineProperty(this, Symbol.metadata, {
|
|
2788
|
-
enumerable: true,
|
|
2789
|
-
configurable: true,
|
|
2790
|
-
writable: true,
|
|
2791
|
-
value: _metadata
|
|
2792
|
-
});
|
|
2793
|
-
}
|
|
2794
|
-
config = __runInitializers(this, _instanceExtraInitializers);
|
|
2795
|
-
static inject = [
|
|
2796
|
-
"systemPrompt",
|
|
2797
|
-
"tools",
|
|
2798
|
-
"llm"
|
|
2799
|
-
];
|
|
2800
|
-
static Config = Config;
|
|
2801
|
-
/** `memory_add` 工具的注册 disposer,只在开关允许时存在。 */
|
|
2802
|
-
toolDisposer;
|
|
2803
|
-
constructor(ctx, config) {
|
|
2804
|
-
super(ctx, "assistantProfile");
|
|
2805
|
-
this.config = config;
|
|
2806
|
-
live.config = config;
|
|
2807
|
-
ctx.inject(["settings"], (child) => {
|
|
2808
|
-
child.effect(() => child.settings.configure({ auto: false }, ctx.fiber));
|
|
2809
|
-
});
|
|
2810
|
-
ctx.effect(() => ctx.systemPrompt.section({
|
|
2811
|
-
name: MEMORY_SECTION,
|
|
2812
|
-
order: MEMORY_ORDER,
|
|
2813
|
-
text: () => {
|
|
2814
|
-
if (config.memoryEnabled.get() !== true) return "";
|
|
2815
|
-
return readMemoryText().trim();
|
|
2816
|
-
}
|
|
2817
|
-
}), "assistant-profile: memory section");
|
|
2818
|
-
const reinstall = (agent) => installPersona(ctx, agent).catch((error) => {
|
|
2819
|
-
ctx.logger.warn(`assistant-profile: persona install failed: ${String(error)}`);
|
|
2820
|
-
});
|
|
2821
|
-
ctx.effect(() => ctx.on("agent/created", ({ agent }) => {
|
|
2822
|
-
reinstall(agent);
|
|
2823
|
-
}), "assistant-profile: persona");
|
|
2824
|
-
ctx.effect(() => ctx.on("agent-preset/selected", (sessionId) => {
|
|
2825
|
-
const agent = ctx.get("agents")?.get(String(sessionId));
|
|
2826
|
-
if (agent !== void 0) reinstall(agent);
|
|
2827
|
-
}), "assistant-profile: preset switch");
|
|
2828
|
-
this.syncTool();
|
|
2829
|
-
ctx.on("loader/volatile-update", () => {
|
|
2830
|
-
this.syncTool();
|
|
2831
|
-
});
|
|
2832
|
-
}
|
|
2833
|
-
/** 按当前开关注册或撤下 `memory_add` 工具。 */
|
|
2834
|
-
syncTool() {
|
|
2835
|
-
const want = this.config.memoryEnabled.get() === true && this.config.autoMemory.get() === true;
|
|
2836
|
-
if (want && this.toolDisposer === void 0) this.toolDisposer = this.registerTool();
|
|
2837
|
-
else if (!want && this.toolDisposer !== void 0) {
|
|
2838
|
-
this.toolDisposer();
|
|
2839
|
-
this.toolDisposer = void 0;
|
|
2687
|
+
/**
|
|
2688
|
+
* 助理档案服务:人格注入 + 单份长期记忆。
|
|
2689
|
+
*
|
|
2690
|
+
* 记忆的存储、注入、两个开关、`memory_add` 工具和按钮提炼都在这里;
|
|
2691
|
+
* 客户端只负责设置页与弹窗。
|
|
2692
|
+
*/
|
|
2693
|
+
var AssistantProfile = class extends TypertRemoteService {
|
|
2694
|
+
config;
|
|
2695
|
+
static inject = [
|
|
2696
|
+
"systemPrompt",
|
|
2697
|
+
"tools",
|
|
2698
|
+
"llm"
|
|
2699
|
+
];
|
|
2700
|
+
static Config = Config;
|
|
2701
|
+
/** `memory_add` 工具的注册 disposer,只在开关允许时存在。 */
|
|
2702
|
+
toolDisposer;
|
|
2703
|
+
constructor(ctx, config) {
|
|
2704
|
+
super(ctx, "assistantProfile");
|
|
2705
|
+
this.config = config;
|
|
2706
|
+
live.config = config;
|
|
2707
|
+
ctx.inject(["settings"], (child) => {
|
|
2708
|
+
child.effect(() => child.settings.configure({ auto: false }, ctx.fiber));
|
|
2709
|
+
});
|
|
2710
|
+
ctx.effect(() => ctx.systemPrompt.section({
|
|
2711
|
+
name: MEMORY_SECTION,
|
|
2712
|
+
order: MEMORY_ORDER,
|
|
2713
|
+
text: () => {
|
|
2714
|
+
if (config.memoryEnabled.get() !== true) return "";
|
|
2715
|
+
return config.memoryText.get().trim();
|
|
2840
2716
|
}
|
|
2841
|
-
}
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2717
|
+
}), "assistant-profile: memory section");
|
|
2718
|
+
const reinstall = (agent) => installPersona(ctx, agent).catch((error) => {
|
|
2719
|
+
ctx.logger.warn(`assistant-profile: persona install failed: ${String(error)}`);
|
|
2720
|
+
});
|
|
2721
|
+
ctx.effect(() => ctx.on("agent/created", ({ agent }) => {
|
|
2722
|
+
reinstall(agent);
|
|
2723
|
+
}), "assistant-profile: persona");
|
|
2724
|
+
ctx.effect(() => ctx.on("agent-preset/selected", (sessionId) => {
|
|
2725
|
+
const agent = ctx.get("agents")?.get(String(sessionId));
|
|
2726
|
+
if (agent !== void 0) reinstall(agent);
|
|
2727
|
+
}), "assistant-profile: preset switch");
|
|
2728
|
+
this.syncTool();
|
|
2729
|
+
ctx.on("loader/volatile-update", () => {
|
|
2730
|
+
this.syncTool();
|
|
2731
|
+
});
|
|
2732
|
+
}
|
|
2733
|
+
/** 按当前开关注册或撤下 `memory_add` 工具。 */
|
|
2734
|
+
syncTool() {
|
|
2735
|
+
const want = this.config.memoryEnabled.get() === true && this.config.autoMemory.get() === true;
|
|
2736
|
+
if (want && this.toolDisposer === void 0) this.toolDisposer = this.registerTool();
|
|
2737
|
+
else if (!want && this.toolDisposer !== void 0) {
|
|
2738
|
+
this.toolDisposer();
|
|
2739
|
+
this.toolDisposer = void 0;
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
/** 注册模型侧的记忆工具,返回撤下函数。 */
|
|
2743
|
+
registerTool() {
|
|
2744
|
+
const ctx = this.ctx;
|
|
2745
|
+
const disposer = ctx.tools.register(defineTool({
|
|
2746
|
+
name: "memory_add",
|
|
2747
|
+
description: MEMORY_ADD_DESCRIPTION,
|
|
2748
|
+
parameters: { text: {
|
|
2749
|
+
type: "string",
|
|
2750
|
+
required: true,
|
|
2751
|
+
description: "The stable fact to remember, in its final prose form."
|
|
2752
|
+
} },
|
|
2753
|
+
output: {
|
|
2754
|
+
schema: MEMORY_ADD_VALUE_SCHEMA,
|
|
2755
|
+
render: (_args, value) => {
|
|
2756
|
+
const v = value;
|
|
2757
|
+
if (v.saved) return [{
|
|
2758
|
+
type: "text",
|
|
2759
|
+
text: `记住:${v.content ?? ""}`
|
|
2760
|
+
}];
|
|
2761
|
+
return [{
|
|
2762
|
+
type: "text",
|
|
2763
|
+
text: `未记住:${v.outcome ?? "未启用"}`
|
|
2764
|
+
}];
|
|
2879
2765
|
}
|
|
2880
|
-
}
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
};
|
|
2885
|
-
ctx.effect(() => teardown, "assistant-profile: memory tool");
|
|
2886
|
-
return () => {
|
|
2887
|
-
disposer();
|
|
2888
|
-
};
|
|
2889
|
-
}
|
|
2890
|
-
/** 读取记忆全文(设置页编辑框)。 */
|
|
2891
|
-
readGlobal() {
|
|
2892
|
-
return readMemoryText();
|
|
2893
|
-
}
|
|
2894
|
-
/** 整体保存记忆(设置页编辑框的保存按钮)。 */
|
|
2895
|
-
writeGlobal(text) {
|
|
2896
|
-
writeMemory(text);
|
|
2897
|
-
}
|
|
2898
|
-
/** 追加一条记忆(内部使用/将来的其它入口)。 */
|
|
2899
|
-
appendMemory(content) {
|
|
2900
|
-
return appendMemory(content);
|
|
2901
|
-
}
|
|
2902
|
-
/**
|
|
2903
|
-
* 用某个会话自己的模型,把选中的对话文本提炼成记忆条目后追加。
|
|
2904
|
-
*
|
|
2905
|
-
* 这是 UI 按钮的路径:用户在弹窗里选一段聊天记录,模型把其中稳定的事实抽出来。
|
|
2906
|
-
* 会话必须是活的(它的 agent 提供模型配置),否则没有可用的 provider/model。
|
|
2907
|
-
* @param sessionId - 执行提炼的会话。
|
|
2908
|
-
* @param text - 选中的对话文本。
|
|
2909
|
-
* @returns 是否写入、写入了哪些条目,以及失败原因。
|
|
2910
|
-
*/
|
|
2911
|
-
async remember(sessionId, text) {
|
|
2912
|
-
const agent = this.ctx.get("agents")?.get(SessionId(sessionId));
|
|
2913
|
-
if (agent === void 0) return {
|
|
2914
|
-
saved: false,
|
|
2915
|
-
outcome: "session not open"
|
|
2916
|
-
};
|
|
2917
|
-
const callConfig = (agent.session.requestHeader?.())?.config;
|
|
2918
|
-
if (callConfig === void 0) return {
|
|
2919
|
-
saved: false,
|
|
2920
|
-
outcome: "no provider/model available"
|
|
2921
|
-
};
|
|
2922
|
-
const trimmed = text.trim();
|
|
2923
|
-
if (trimmed === "") return {
|
|
2924
|
-
saved: false,
|
|
2925
|
-
outcome: "empty selection"
|
|
2926
|
-
};
|
|
2927
|
-
const assembler = new BlockAssembler();
|
|
2928
|
-
const messages = [createUserMessage({
|
|
2929
|
-
content: [{
|
|
2930
|
-
type: "text",
|
|
2931
|
-
text: trimmed
|
|
2932
|
-
}],
|
|
2933
|
-
source: { kind: "user" }
|
|
2934
|
-
}), createUserMessage({
|
|
2935
|
-
content: [{
|
|
2936
|
-
type: "text",
|
|
2937
|
-
text: REMEMBER_INSTRUCTION
|
|
2938
|
-
}],
|
|
2939
|
-
source: { kind: "user" }
|
|
2940
|
-
})];
|
|
2941
|
-
try {
|
|
2942
|
-
for await (const chunk of this.ctx.llm.stream({
|
|
2943
|
-
...callConfig,
|
|
2944
|
-
messages,
|
|
2945
|
-
sessionId: SessionId(sessionId)
|
|
2946
|
-
})) assembler.push(chunk);
|
|
2947
|
-
} catch (error) {
|
|
2948
|
-
return {
|
|
2766
|
+
},
|
|
2767
|
+
execute: async (args, _exec) => {
|
|
2768
|
+
const trimmed = args.text.trim();
|
|
2769
|
+
if (trimmed === "") return {
|
|
2949
2770
|
saved: false,
|
|
2950
|
-
outcome:
|
|
2771
|
+
outcome: "empty fact"
|
|
2772
|
+
};
|
|
2773
|
+
const result = appendToText(this.config.memoryText.get(), trimmed);
|
|
2774
|
+
this.config.memoryText.set(result.text);
|
|
2775
|
+
return {
|
|
2776
|
+
saved: true,
|
|
2777
|
+
content: result.content,
|
|
2778
|
+
outcome: result.appended ? "appended" : result.refreshed ? "refreshed" : "unchanged"
|
|
2951
2779
|
};
|
|
2952
2780
|
}
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
};
|
|
2963
|
-
for (const entry of entries) appendMemory(entry);
|
|
2964
|
-
return {
|
|
2965
|
-
saved: true,
|
|
2966
|
-
entries,
|
|
2967
|
-
outcome: `remembered ${entries.length} entries`
|
|
2968
|
-
};
|
|
2969
|
-
}
|
|
2970
|
-
};
|
|
2971
|
-
})();
|
|
2972
|
-
/** 把错误及其 cause 链压成一行。 */
|
|
2973
|
-
function errorChain(error) {
|
|
2974
|
-
const parts = [];
|
|
2975
|
-
let current = error;
|
|
2976
|
-
while (typeof current === "object" && current !== null && parts.length < 4) {
|
|
2977
|
-
const message = current.message;
|
|
2978
|
-
if (typeof message !== "string" || message === "") break;
|
|
2979
|
-
parts.push(message);
|
|
2980
|
-
current = current.cause;
|
|
2981
|
-
}
|
|
2982
|
-
return parts.join(" | ");
|
|
2983
|
-
}
|
|
2984
|
-
/** 把终止原因映射成失败描述(fail-closed)。 */
|
|
2985
|
-
function rememberFinishError(finish) {
|
|
2986
|
-
switch (finish.kind) {
|
|
2987
|
-
case "error":
|
|
2988
|
-
case "aborted": return `analysis ${finish.kind}: ${errorChain(finish.failure)}`;
|
|
2989
|
-
case "max-tokens": return "analysis truncated at the token cap";
|
|
2990
|
-
default: return;
|
|
2991
|
-
}
|
|
2992
|
-
}
|
|
2993
|
-
/** 把模型输出的文本拆成一行一条记忆(去前缀、丢空行、丢占位词)。 */
|
|
2994
|
-
function splitEntries(blocks) {
|
|
2995
|
-
const entries = [];
|
|
2996
|
-
for (const block of blocks) {
|
|
2997
|
-
if (block.type !== "text" || typeof block.text !== "string") continue;
|
|
2998
|
-
for (const raw of block.text.split(/\r?\n/)) {
|
|
2999
|
-
const line = stripInvisible(raw.trim().replace(/^[-*]\s+/, "").trim());
|
|
3000
|
-
if (line === "") continue;
|
|
3001
|
-
if (isVacuousEntry(line)) continue;
|
|
3002
|
-
entries.push(line);
|
|
3003
|
-
}
|
|
2781
|
+
}));
|
|
2782
|
+
const teardown = () => {
|
|
2783
|
+
disposer();
|
|
2784
|
+
this.toolDisposer = void 0;
|
|
2785
|
+
};
|
|
2786
|
+
ctx.effect(() => teardown, "assistant-profile: memory tool");
|
|
2787
|
+
return () => {
|
|
2788
|
+
disposer();
|
|
2789
|
+
};
|
|
3004
2790
|
}
|
|
3005
|
-
|
|
3006
|
-
}
|
|
2791
|
+
};
|
|
3007
2792
|
/** 去掉模型可能在正文周围吐出的不可见字符。 */
|
|
3008
2793
|
function stripInvisible(value) {
|
|
3009
2794
|
return value.replace(/[\u0000-\u001F\u007F-\u009F\u00A0\u00AD\u1680\u2000-\u200B\u2028-\u202F\u205F-\u206F\u3000\uFEFF\uFFFD]/g, "").trim();
|
|
@@ -3016,4 +2801,4 @@ function isVacuousEntry(line) {
|
|
|
3016
2801
|
return normalized === "" || normalized === "空行" || normalized === "无" || normalized === "没有" || normalized === "无内容" || normalized === "无值得记住的内容" || normalized === "暂无" || normalized === "none" || normalized === "no" || normalized === "nothing" || normalized === "na" || normalized === "n/a";
|
|
3017
2802
|
}
|
|
3018
2803
|
//#endregion
|
|
3019
|
-
export { Config,
|
|
2804
|
+
export { Config, MEMORY_SECTION, appendToText, dedupeKey, AssistantProfile as default, isVacuousEntry, name, parseEntries, renderPersona, stampNow, stripInvisible };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@istarry/dsh-assistant-profile",
|
|
3
3
|
"description": "助理档案:给 DSH 一份全局人格(名称 / 风格 / 语言 / 人格提示词),每个新会话的系统提示最前面都带着它;侧栏「新会话」上方显示助理名",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.5.0",
|
|
5
5
|
"icon": "./icon.svg",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
@@ -19,10 +19,6 @@
|
|
|
19
19
|
"lib/index.js",
|
|
20
20
|
"lib/client.js",
|
|
21
21
|
"lib/client.js.map",
|
|
22
|
-
"lib/typert.host.js",
|
|
23
|
-
"lib/typert.host.d.ts",
|
|
24
|
-
"lib/typert.remote-client.js",
|
|
25
|
-
"lib/typert.remote-client.d.ts",
|
|
26
22
|
"cordis.patch.yml",
|
|
27
23
|
"LICENSE",
|
|
28
24
|
"README*.md"
|
package/lib/typert.host.d.ts
DELETED