@jiugeqqqqq/dsh-house 0.2.3 → 0.3.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 +45 -0
- package/README.md +33 -0
- package/cordis.patch.yml +2 -2
- package/lib/host/index.js +254 -9
- package/package.json +45 -44
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# 更新日志
|
|
2
|
+
|
|
3
|
+
> 每次发版都必须在这里写一条:改了什么、为什么改、别人要不要跟着动。
|
|
4
|
+
> 版本号跟 `package.json` 走,`npm i` 的人看这里就知道升了什么。
|
|
5
|
+
|
|
6
|
+
## 0.3.1 — 2026-10-29
|
|
7
|
+
|
|
8
|
+
**新增写积木的能力,积木终于能自己长出来。**
|
|
9
|
+
|
|
10
|
+
- 新增 `house_write`:往积木里写一篇(`file` + `content`,默认追加;`mode: overwrite` 才整篇重写)。
|
|
11
|
+
之前只有 `house_add`(记清单一行)和 `house_search`(翻积木),**没有写积木的工具**——别人装完 `knowledge` 一直是空的。
|
|
12
|
+
- 系统提示里教「两条轴」:能力型(`手机-接入与操作`,回答「怎么做」)和任务型(`任务-调通一直发`,回答「那段轮对话干了什么」)。
|
|
13
|
+
任务轴按时间轴切,一段任务一篇,要求覆盖全程、不重不漏。
|
|
14
|
+
- `house_write` 的描述点名两种命名都行。
|
|
15
|
+
- 插件 README 增加「积木怎么组织:两条轴」一节。
|
|
16
|
+
|
|
17
|
+
> 升级提醒:如果对方有工具白名单(比如 QQ 桥接的 `qq-tool-restrict` / `dsh-qq-guard`),
|
|
18
|
+
> 要把 `house_add`、`house_write`、`house_search` 三个都加进去,否则调了会被拒。
|
|
19
|
+
|
|
20
|
+
## 0.3.0 — 2026-10-28
|
|
21
|
+
|
|
22
|
+
- 新增 `house_add`:模型自己往清单加一行(`text` + 可选 `group`)。
|
|
23
|
+
- 新增 `house_search`:在 `knowledge` 目录里按关键词翻原文(递归、跳过 README)。
|
|
24
|
+
- 启动时自动建 `knowledge` 目录 + 带说明的 `README.md`,别人装完知道往里写什么。
|
|
25
|
+
- 系统提示里带用法引导(之前只注入清单、不告诉模型去哪翻)。
|
|
26
|
+
|
|
27
|
+
## 0.2.3 — 2026-10-27
|
|
28
|
+
|
|
29
|
+
- 修别人装完侧栏不出现:包名和 patch 名统一成 `@jiugeqqqqq/dsh-house`,`inject` 写 `slots`,`ctx.get('slots')` 兼容 rc.3。
|
|
30
|
+
|
|
31
|
+
## 0.2.x — 2026-10-25 ~ 10-27
|
|
32
|
+
|
|
33
|
+
- 侧边栏「房子」页:搜索、增、改、删(删除二次确认)。
|
|
34
|
+
- 清单每轮注入系统提示,改完立刻生效,不用重启。
|
|
35
|
+
- 「搜历史」:翻会话存档里被压缩掉的原文。
|
|
36
|
+
- 写回前自动备份 `house.md.bak-<时间戳>`。
|
|
37
|
+
- `autoRotate`(可选,默认关):清单改过且 `pendingGroup` 清空时,调桥接换新会话。
|
|
38
|
+
- 每日自查 npm 新版本(只提示,不自动装)。
|
|
39
|
+
|
|
40
|
+
## 0.1.x — 2026-10-24
|
|
41
|
+
|
|
42
|
+
- 初始版本:清单 + 页面。
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
格式约定:`## 版本 — 日期`,下面用短句列改动,先说「用户能感知到什么」,再说实现。
|
package/README.md
CHANGED
|
@@ -37,6 +37,35 @@ dsh plugin --profile web add @jiugeqqqqq/dsh-house
|
|
|
37
37
|
<DSH_HOME>\logs\... # 启动日志里会有 dsh-house: 房子清单 = ...
|
|
38
38
|
```
|
|
39
39
|
|
|
40
|
+
## 给模型的三个工具(0.3.1 起)
|
|
41
|
+
|
|
42
|
+
装完模型自己能维护清单和积木,不用你手动改:
|
|
43
|
+
|
|
44
|
+
- `house_add`:做完一件大事往清单加一行(`text` + 可选 `group`,默认「已经有的」)
|
|
45
|
+
- `house_write`:往积木里写一篇(`file` + `content`,默认追加;`mode: overwrite` 才整篇重写)
|
|
46
|
+
- `house_search`:在积木(`knowledge` 目录里的 .md)里按关键词翻原文
|
|
47
|
+
|
|
48
|
+
系统提示里会带一句引导:「做完大事用 house_add 记一行;写积木分能力型和任务型;清单和最近对话里找不到的,用 house_search 去积木里翻,翻到再答,别猜。」
|
|
49
|
+
|
|
50
|
+
`knowledge` 目录不存在时会自动建一份带说明的 `README.md`,别人装完知道该往里写什么。
|
|
51
|
+
|
|
52
|
+
## 积木怎么组织:两条轴(0.3.1 起)
|
|
53
|
+
|
|
54
|
+
`house_write` 的文件名就是组织方式,两种都支持、都要有:
|
|
55
|
+
|
|
56
|
+
| 轴 | 一个文件对应 | 例子 | 回答的问题 |
|
|
57
|
+
|---|---|---|---|
|
|
58
|
+
| 能力轴 | 一个能力/设备/工具 | `手机-接入与操作`、`画图-Agnes` | 「这个怎么做」 |
|
|
59
|
+
| 任务轴 | 一段任务 | `任务-调通一直发` | 「那三十轮干了什么」 |
|
|
60
|
+
|
|
61
|
+
任务轴是**按时间轴切,不是按标签分类**:
|
|
62
|
+
|
|
63
|
+
- 一段对话里有几个任务就写几篇,切完正好覆盖全程,不重不漏
|
|
64
|
+
- 任务边界:① 目标变了;② 他明说换事;③ 交付物变了
|
|
65
|
+
- 一篇只写「做了什么 / 卡在哪 / 最后怎么定的」,别复述过程
|
|
66
|
+
|
|
67
|
+
> 为什么要两条轴:能力轴回答「怎么用」,任务轴回答「发生过什么」。上下文压缩之后,任务轴是唯一能证明「那段时间真的干过这件事」的东西。
|
|
68
|
+
|
|
40
69
|
## 清单格式
|
|
41
70
|
|
|
42
71
|
只有两种语法,够用就好:
|
|
@@ -102,6 +131,10 @@ dsh plugin --profile web add @jiugeqqqqq/dsh-house
|
|
|
102
131
|
|
|
103
132
|
默认就是空的,正常不用动。
|
|
104
133
|
|
|
134
|
+
## 更新日志
|
|
135
|
+
|
|
136
|
+
每次发版改了什么,看 [CHANGELOG.md](./CHANGELOG.md)——`npm i` 之前先扫一眼,能知道要不要跟着动配置。
|
|
137
|
+
|
|
105
138
|
## 更新
|
|
106
139
|
|
|
107
140
|
```powershell
|
package/cordis.patch.yml
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# Bundle patch layer for dsh-house(房子)。
|
|
2
2
|
#
|
|
3
3
|
# 只插入一行 Host。浏览器那一半由 package.json 的 dsh.client 声明。
|
|
4
4
|
# 这里的默认值**不写死任何人的路径**:装完直接能用,清单文件不存在会自动建模板。
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
- insert:
|
|
8
8
|
- id: house
|
|
9
|
-
name: "
|
|
9
|
+
name: "dsh-house"
|
|
10
10
|
config:
|
|
11
11
|
# 房子清单:Markdown,按 "## 分组" 和 "- 条目" 解析。留空 = <DSH_HOME>/house.md
|
|
12
12
|
assetsPath: ''
|
package/lib/host/index.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* 2026-10-26:加写能力(之前只有 list/read,装了等于 Markdown 预览器)+ 搜存档。
|
|
15
15
|
*/
|
|
16
|
-
import { readFile, writeFile, mkdir, copyFile } from 'node:fs/promises';
|
|
16
|
+
import { readFile, writeFile, appendFile, mkdir, copyFile } from 'node:fs/promises';
|
|
17
17
|
import fs from 'node:fs';
|
|
18
18
|
import zlib from 'node:zlib';
|
|
19
19
|
import path from 'node:path';
|
|
@@ -144,6 +144,42 @@ export function apply(ctx, config = {}) {
|
|
|
144
144
|
'',
|
|
145
145
|
].join('\n');
|
|
146
146
|
|
|
147
|
+
// 积木目录:房子只记结论,细节(怎么调通的、踩了什么坑、为什么这么改)写这里。
|
|
148
|
+
// 别人装完如果目录不存在,自动建一份带说明的 README —— 否则页面只显示一个空路径,
|
|
149
|
+
// 谁也不知道该往里写什么。
|
|
150
|
+
const KNOWLEDGE_TEMPLATE = [
|
|
151
|
+
'# 积木库',
|
|
152
|
+
'',
|
|
153
|
+
'> 房子清单(house.md)只记「有/没有、到哪一步、怎么用一句话」。',
|
|
154
|
+
'> 怎么调通的、踩了什么坑、为什么这么改 —— 写在这里,一个主题一个 .md。',
|
|
155
|
+
'> 新会话不会自动读这里;需要时用 house_search 按关键词翻,或者直接打开文件。',
|
|
156
|
+
'',
|
|
157
|
+
'## 怎么写',
|
|
158
|
+
'- 文件名用主题,比如 `手机-接入与操作.md`、`画图-Agnes.md`',
|
|
159
|
+
'- 只记「最后怎么定的 + 为什么」,不记纠结过程',
|
|
160
|
+
'- 原始输出留在日志里,不要整段粘进来',
|
|
161
|
+
'',
|
|
162
|
+
'## 索引',
|
|
163
|
+
'(把自己的文件一行一个写在这里,方便翻)',
|
|
164
|
+
'',
|
|
165
|
+
].join('\n');
|
|
166
|
+
|
|
167
|
+
async function ensureKnowledge() {
|
|
168
|
+
try {
|
|
169
|
+
await mkdir(knowledgeDir, { recursive: true });
|
|
170
|
+
const readme = path.join(knowledgeDir, 'README.md');
|
|
171
|
+
if (!fs.existsSync(readme)) await writeFile(readme, KNOWLEDGE_TEMPLATE, 'utf8');
|
|
172
|
+
return { created: true };
|
|
173
|
+
} catch (error) {
|
|
174
|
+
return { created: false, error: error instanceof Error ? error.message : String(error) };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// 启动就建,不等页面打开(fire and forget,失败也不影响清单)。
|
|
178
|
+
ensureKnowledge().then((r) => {
|
|
179
|
+
if (r.created) ctx.logger?.info('dsh-house: 积木目录 = %s', knowledgeDir);
|
|
180
|
+
else ctx.logger?.warn('dsh-house: 建积木目录失败:%s', r.error);
|
|
181
|
+
});
|
|
182
|
+
|
|
147
183
|
async function ensureAssets() {
|
|
148
184
|
try {
|
|
149
185
|
await readFile(assetsPath, 'utf8');
|
|
@@ -375,20 +411,103 @@ export function apply(ctx, config = {}) {
|
|
|
375
411
|
}
|
|
376
412
|
}
|
|
377
413
|
|
|
378
|
-
|
|
414
|
+
/** 往清单里加一行(工具 house_add 用,走和页面同一条写回链路)。 */
|
|
415
|
+
async function addHouseItem(group, text) {
|
|
416
|
+
const item = String(text || '').replace(/\s+/g, ' ').trim();
|
|
417
|
+
if (!item) return { ok: false, error: { code: 'invalid', message: '要写点什么。' } };
|
|
418
|
+
// 别人可能还没打开过页面,清单文件不存在 —— 先建模板再写,别让第一次 house_add 就失败。
|
|
419
|
+
if (!fs.existsSync(assetsPath)) await ensureAssets();
|
|
420
|
+
const g = String(group || '已经有的').trim() || '已经有的';
|
|
421
|
+
const r = await mutate((lines) => {
|
|
422
|
+
let gi = lines.findIndex((l) => l.trim() === '## ' + g);
|
|
423
|
+
if (gi < 0) {
|
|
424
|
+
lines.push('', '## ' + g);
|
|
425
|
+
gi = lines.length - 1;
|
|
426
|
+
}
|
|
427
|
+
let end = lines.length;
|
|
428
|
+
for (let i = gi + 1; i < lines.length; i++) {
|
|
429
|
+
if (/^##\s+/.test(lines[i])) {
|
|
430
|
+
end = i;
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
let insertAt = end;
|
|
435
|
+
for (let i = end - 1; i > gi; i--) {
|
|
436
|
+
if (lines[i].trim()) {
|
|
437
|
+
insertAt = i + 1;
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
lines.splice(insertAt, 0, '- ' + item);
|
|
442
|
+
return true;
|
|
443
|
+
});
|
|
444
|
+
return r.ok ? { ok: true, data: { group: g, text: item } } : r;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/** 在积木目录里按关键词翻(工具 house_search 用)。 */
|
|
448
|
+
function walkKnowledge(dir, out = [], depth = 0) {
|
|
449
|
+
if (depth > 4) return out;
|
|
450
|
+
let ents;
|
|
451
|
+
try {
|
|
452
|
+
ents = fs.readdirSync(dir, { withFileTypes: true });
|
|
453
|
+
} catch {
|
|
454
|
+
return out;
|
|
455
|
+
}
|
|
456
|
+
for (const e of ents) {
|
|
457
|
+
const p = path.join(dir, e.name);
|
|
458
|
+
if (e.isDirectory()) walkKnowledge(p, out, depth + 1);
|
|
459
|
+
else if (/\.md$/i.test(e.name) && e.name !== 'README.md') out.push(p);
|
|
460
|
+
}
|
|
461
|
+
return out;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function searchKnowledge(query, limit) {
|
|
465
|
+
const q = String(query ?? '').trim().toLowerCase();
|
|
466
|
+
if (!q) return { ok: false, error: { code: 'invalid', message: '要给个关键词。' } };
|
|
467
|
+
const max = Math.max(1, Math.min(30, Number(limit) || 8));
|
|
468
|
+
const files = walkKnowledge(knowledgeDir);
|
|
469
|
+
const hits = [];
|
|
470
|
+
for (const f of files) {
|
|
471
|
+
if (hits.length >= max) break;
|
|
472
|
+
let text;
|
|
473
|
+
try {
|
|
474
|
+
text = fs.readFileSync(f, 'utf8');
|
|
475
|
+
} catch {
|
|
476
|
+
continue;
|
|
477
|
+
}
|
|
478
|
+
const lines = text.split('\n');
|
|
479
|
+
for (let i = 0; i < lines.length; i++) {
|
|
480
|
+
if (hits.length >= max) break;
|
|
481
|
+
if (lines[i].toLowerCase().includes(q)) {
|
|
482
|
+
hits.push({
|
|
483
|
+
file: path.relative(knowledgeDir, f),
|
|
484
|
+
line: i + 1,
|
|
485
|
+
text: lines[i].trim().slice(0, 240),
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return { ok: true, data: { query: q, hits, scanned: files.length } };
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
|
|
379
494
|
// 2026-10-27:以前「房子清单能不能进模型上下文」要靠**另一个插件**
|
|
380
495
|
// (dsh-session-rotate 的 assetsPath) 配一下才行 —— 等于装了这个插件的人也
|
|
381
496
|
// 只得到页面、模型根本看不见清单。粑粑要求「装上就和我们一样」,所以这里自己注入。
|
|
382
497
|
// 文件小(几 KB)且每轮重读,保证清单改了立刻生效;读不到就注入空串,不报错。
|
|
498
|
+
// 每轮都带一段用法提示:只给清单、不告诉它「找不到去哪翻」,模型就会自己猜。
|
|
499
|
+
const HOUSE_GUIDE =
|
|
500
|
+
'【房子怎么用】做完一件大事,用 house_add 往清单加一行(分组写「已经有的」或「正在长出来的」);' +
|
|
501
|
+
'写积木分两种:按能力写(`手机-接入与操作`、`画图-Agnes`,回答「怎么做」)、按任务写(`任务-调通一直发`,回答「那段对话干了什么」,一段任务一篇,按时间轴切,切完要覆盖全程、不重不漏);' +
|
|
502
|
+
'细节、命令、踩过的坑,用 house_write 写进积木(knowledge 目录,默认追加,别一个坑建一个文件);' +
|
|
503
|
+
'清单和最近对话里找不到的东西,用 house_search 去积木里按关键词翻,翻到再答,别猜。';
|
|
504
|
+
|
|
383
505
|
function assetsAsPrompt() {
|
|
506
|
+
let text = '';
|
|
384
507
|
try {
|
|
385
|
-
if (
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
return `【资产清单】\n${text}`;
|
|
389
|
-
} catch {
|
|
390
|
-
return '';
|
|
391
|
-
}
|
|
508
|
+
if (fs.existsSync(assetsPath)) text = fs.readFileSync(assetsPath, 'utf8').trim();
|
|
509
|
+
} catch {}
|
|
510
|
+
return text ? `【资产清单】\n${text}\n\n${HOUSE_GUIDE}` : HOUSE_GUIDE;
|
|
392
511
|
}
|
|
393
512
|
|
|
394
513
|
ctx.inject(['systemPrompt'], (scoped) => {
|
|
@@ -409,6 +528,132 @@ export function apply(ctx, config = {}) {
|
|
|
409
528
|
);
|
|
410
529
|
});
|
|
411
530
|
|
|
531
|
+
// ----------------------------------------------------------- 两个工具
|
|
532
|
+
// 2026-10-28 加的:以前房子只能靠页面手改,模型自己没法维护;
|
|
533
|
+
// 积木目录也没人搜得到。给模型两个口子,它才能「做完事记一行、找不到就翻」。
|
|
534
|
+
ctx.inject(['tools'], (scoped) => {
|
|
535
|
+
const tools = scoped.get('tools');
|
|
536
|
+
if (tools === undefined || typeof tools.register !== 'function') {
|
|
537
|
+
ctx.logger?.warn('dsh-house: 宿主没有 tools,house_add / house_search 不可用(页面不受影响)。');
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const render = (args, value) => [{ type: 'text', text: String(value?.message ?? '') }];
|
|
541
|
+
const output = {
|
|
542
|
+
schema: {
|
|
543
|
+
type: 'object',
|
|
544
|
+
properties: {
|
|
545
|
+
ok: { type: 'boolean', description: '是否成功' },
|
|
546
|
+
message: { type: 'string', description: '给模型看的一句话结果' },
|
|
547
|
+
},
|
|
548
|
+
required: ['ok', 'message'],
|
|
549
|
+
additionalProperties: false,
|
|
550
|
+
},
|
|
551
|
+
render,
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
scoped.effect(
|
|
555
|
+
() =>
|
|
556
|
+
tools.register({
|
|
557
|
+
name: 'house_add',
|
|
558
|
+
description:
|
|
559
|
+
'往「房子」清单里加一行。做完一件大事(装好一个工具、接好一个设备、跑通一个流程)就调一次,' +
|
|
560
|
+
'只写「有/没有、到哪一步、怎么用一句话」,细节留在积木里。分组不写默认「已经有的」,' +
|
|
561
|
+
'没做完的写「正在长出来的」。',
|
|
562
|
+
parameters: {
|
|
563
|
+
type: 'object',
|
|
564
|
+
properties: {
|
|
565
|
+
text: { type: 'string', description: '一行话,比如「手机截图 ✅ phone.cmd shot」' },
|
|
566
|
+
group: { type: 'string', description: '分组名:已经有的 / 正在长出来的 / 或者你自己开的分组' },
|
|
567
|
+
},
|
|
568
|
+
required: ['text'],
|
|
569
|
+
additionalProperties: false,
|
|
570
|
+
},
|
|
571
|
+
output,
|
|
572
|
+
async execute(args) {
|
|
573
|
+
const r = await addHouseItem(args?.group, args?.text);
|
|
574
|
+
return r.ok
|
|
575
|
+
? { ok: true, message: `已加到「${r.data.group}」:${r.data.text}` }
|
|
576
|
+
: { ok: false, message: r.error?.message || '没写成' };
|
|
577
|
+
},
|
|
578
|
+
}),
|
|
579
|
+
'dsh-house: house_add',
|
|
580
|
+
);
|
|
581
|
+
|
|
582
|
+
scoped.effect(
|
|
583
|
+
() =>
|
|
584
|
+
tools.register({
|
|
585
|
+
name: 'house_search',
|
|
586
|
+
description:
|
|
587
|
+
'在「积木」(knowledge 目录里的 .md)里按关键词翻原文。房子清单和最近对话里找不到的细节,' +
|
|
588
|
+
'先在这里搜;搜到再回答,搜不到就说没找到,别猜。',
|
|
589
|
+
parameters: {
|
|
590
|
+
type: 'object',
|
|
591
|
+
properties: {
|
|
592
|
+
query: { type: 'string', description: '关键词,比如 抖音、画图、报错信息里的词' },
|
|
593
|
+
limit: { type: 'number', description: '最多返回几处,默认 8' },
|
|
594
|
+
},
|
|
595
|
+
required: ['query'],
|
|
596
|
+
additionalProperties: false,
|
|
597
|
+
},
|
|
598
|
+
output,
|
|
599
|
+
async execute(args) {
|
|
600
|
+
const r = searchKnowledge(args?.query, args?.limit);
|
|
601
|
+
if (!r.ok) return { ok: false, message: r.error?.message || '没搜成' };
|
|
602
|
+
if (!r.data.hits.length) return { ok: true, message: `积木里没搜到「${r.data.query}」(翻过 ${r.data.scanned} 个文件)` };
|
|
603
|
+
const body = r.data.hits.map((h) => `${h.file}:${h.line} ${h.text}`).join('\n');
|
|
604
|
+
return { ok: true, message: `在积木里找到 ${r.data.hits.length} 处:\n${body}` };
|
|
605
|
+
},
|
|
606
|
+
}),
|
|
607
|
+
'dsh-house: house_search',
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
// 0.3.1:写积木。之前只有「加一行清单」和「翻积木」,积木永远长不出来。
|
|
611
|
+
scoped.effect(
|
|
612
|
+
() =>
|
|
613
|
+
tools.register({
|
|
614
|
+
name: 'house_write',
|
|
615
|
+
description:
|
|
616
|
+
'往「积木」里写一篇:按名字写进 knowledge/<名字>.md。' +
|
|
617
|
+
'做完一件大事、踩了坑、调通一个流程,就把命令/坑/为什么这么改记进来。' +
|
|
618
|
+
'名字两种:能力型(手机-接入与操作)或任务型(任务-调通一直发,一段任务一篇、覆盖全程)。' +
|
|
619
|
+
'默认追加;同一个名字只开一个文件,别一个坑建一个文件。',
|
|
620
|
+
parameters: {
|
|
621
|
+
type: 'object',
|
|
622
|
+
properties: {
|
|
623
|
+
file: { type: 'string', description: '积木文件名(不带 .md),比如「手机-接入与操作」' },
|
|
624
|
+
content: { type: 'string', description: '要写的内容,markdown;只写结果/命令/坑,别写纠结过程' },
|
|
625
|
+
mode: { type: 'string', enum: ['append', 'overwrite'], description: '默认 append(追加);只有整篇重写才用 overwrite' },
|
|
626
|
+
},
|
|
627
|
+
required: ['file', 'content'],
|
|
628
|
+
additionalProperties: false,
|
|
629
|
+
},
|
|
630
|
+
output,
|
|
631
|
+
async execute(args) {
|
|
632
|
+
const rawName = String(args?.file ?? '').trim().replace(/\.md$/i, '');
|
|
633
|
+
if (!rawName || /[\\/:*?"<>|]/.test(rawName) || rawName.includes('..')) {
|
|
634
|
+
return { ok: false, message: '文件名不合法(不能带路径分隔符或 ..)' };
|
|
635
|
+
}
|
|
636
|
+
const content = String(args?.content ?? '').trim();
|
|
637
|
+
if (!content) return { ok: false, message: '内容不能为空' };
|
|
638
|
+
const file = path.join(knowledgeDir, rawName + '.md');
|
|
639
|
+
const mode = args?.mode === 'overwrite' ? 'overwrite' : 'append';
|
|
640
|
+
try {
|
|
641
|
+
await mkdir(knowledgeDir, { recursive: true });
|
|
642
|
+
if (mode === 'append' && fs.existsSync(file)) {
|
|
643
|
+
await appendFile(file, `\n${content}\n`, 'utf8');
|
|
644
|
+
} else {
|
|
645
|
+
await writeFile(file, `${content}\n`, 'utf8');
|
|
646
|
+
}
|
|
647
|
+
return { ok: true, message: `已${mode === 'append' ? '追加到' : '写入'} 积木/${rawName}.md(${content.length} 字)` };
|
|
648
|
+
} catch (error) {
|
|
649
|
+
return { ok: false, message: error?.message || '写失败' };
|
|
650
|
+
}
|
|
651
|
+
},
|
|
652
|
+
}),
|
|
653
|
+
'dsh-house: house_write',
|
|
654
|
+
);
|
|
655
|
+
});
|
|
656
|
+
|
|
412
657
|
// ---------------------------------------------------------------- web API
|
|
413
658
|
ctx.inject(['webServer'], (scoped) => {
|
|
414
659
|
const webServer = scoped.get('webServer');
|
package/package.json
CHANGED
|
@@ -1,44 +1,45 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@jiugeqqqqq/dsh-house",
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "房子 — 常驻的资产/能力清单,配一个侧边栏页面。清单会自动进系统提示(装了就生效);房子只记有没有、到哪一步,细节放 knowledge 和日志里。",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./lib/host/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./lib/host/index.js",
|
|
10
|
+
"./client": "./lib/client.js",
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"meta": {
|
|
14
|
+
"title": "房子",
|
|
15
|
+
"description": "常驻的资产与能力清单:只记有没有、到哪一步、怎么用一句话。细节留给 knowledge 和日志,需要时再查。"
|
|
16
|
+
},
|
|
17
|
+
"dsh": {
|
|
18
|
+
"bundle": {
|
|
19
|
+
"patch": "./cordis.patch.yml"
|
|
20
|
+
},
|
|
21
|
+
"client": {
|
|
22
|
+
"platform": "web",
|
|
23
|
+
"immediately": true,
|
|
24
|
+
"inject": [
|
|
25
|
+
"slots"
|
|
26
|
+
]
|
|
27
|
+
},
|
|
28
|
+
"manifestVersion": 1
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"dsh",
|
|
32
|
+
"deepseek-harness",
|
|
33
|
+
"assets",
|
|
34
|
+
"capabilities",
|
|
35
|
+
"sidebar",
|
|
36
|
+
"memory"
|
|
37
|
+
],
|
|
38
|
+
"author": "jiuge",
|
|
39
|
+
"files": [
|
|
40
|
+
"lib/**/*.js",
|
|
41
|
+
"cordis.patch.yml",
|
|
42
|
+
"README.md",
|
|
43
|
+
"CHANGELOG.md"
|
|
44
|
+
]
|
|
45
|
+
}
|