@jiugeqqqqq/dsh-house2 0.2.1 → 0.3.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/CHANGELOG.md +10 -0
- package/lib/client.js +268 -1
- package/lib/host/index.js +86 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,16 @@
|
|
|
3
3
|
> 每次发版都必须在这里写一条:改了什么、为什么改、别人要不要跟着动。
|
|
4
4
|
> 版本号跟 `package.json` 走,`npm i` 的人看这里就知道升了什么。
|
|
5
5
|
|
|
6
|
+
## 0.3.0 — 2026-10-30
|
|
7
|
+
|
|
8
|
+
- 新视图「识海」:房子页顶部切「房子 | 识海」。3D 星图——原意识在中心、记忆按时间沉底(越老越暗)、24 小时内的「心光」发光、点一条记忆亮起它牵出的念头链(心流),右侧看详情。
|
|
9
|
+
- 数据走 host 的 `sea` 接口(读 `sea-graph.json`,由 `scripts/sea-graph.mjs` 生成);路径可用 `seaGraphPath` 配,默认 `<DSH_HOME>/sea-graph.json`。
|
|
10
|
+
|
|
11
|
+
## 0.2.2 — 2026-10-30
|
|
12
|
+
|
|
13
|
+
- 压缩改成**按 token 触发**(不再挂在「任务完成」上):house2 自己统计累计 token(口径同 dsh-session-rotate,监听 `assistant/message` 的 `usage`),到阈值 80% 就在注入里提醒「该准备交接/换会话」。阈值 `thresholdTokens`,默认 1 亿。
|
|
14
|
+
- 不自己调压缩(DSH 没给插件公开的手动压缩接口),只按 token 提醒;工段只负责记任务。
|
|
15
|
+
|
|
6
16
|
## 0.2.1 — 2026-10-30
|
|
7
17
|
|
|
8
18
|
- `house_search` 加 `stage` 参数:先按工段名找到它挂的积木文件(`stage_done` 时写的「积木:xxx」),只在那一个文件里搜,比全目录翻更快更准。不传就还是全目录搜。
|
package/lib/client.js
CHANGED
|
@@ -80,6 +80,16 @@ window.__ModuleLoader__.load({
|
|
|
80
80
|
},
|
|
81
81
|
err: { fontSize: '13px', color: 'var(--dsw-alias-state-error-primary,#e5534b)' },
|
|
82
82
|
note: { fontSize: '12px', opacity: 0.65 },
|
|
83
|
+
// 2026-10-30:识海视图的切换 tab
|
|
84
|
+
tab: {
|
|
85
|
+
padding: '5px 14px', borderRadius: '999px', cursor: 'pointer', fontSize: '13px',
|
|
86
|
+
border: '1px solid var(--dsw-alias-border-l2,#3a3a3a)', background: 'transparent', color: 'inherit', opacity: 0.7,
|
|
87
|
+
},
|
|
88
|
+
tabOn: {
|
|
89
|
+
padding: '5px 14px', borderRadius: '999px', cursor: 'pointer', fontSize: '13px',
|
|
90
|
+
border: '1px solid var(--dsw-alias-brand-primary,#4d6bfe)', background: 'rgba(77,107,254,0.16)',
|
|
91
|
+
color: 'var(--dsw-alias-brand-primary,#8fa8ff)', fontWeight: 600,
|
|
92
|
+
},
|
|
83
93
|
};
|
|
84
94
|
|
|
85
95
|
function HousePage() {
|
|
@@ -136,6 +146,11 @@ window.__ModuleLoader__.load({
|
|
|
136
146
|
var isBusy = busy[0];
|
|
137
147
|
var setBusy = busy[1];
|
|
138
148
|
|
|
149
|
+
// 2026-10-30:房子 / 识海 两个视图
|
|
150
|
+
var view = React.useState('house');
|
|
151
|
+
var currentView = view[0];
|
|
152
|
+
var setView = view[1];
|
|
153
|
+
|
|
139
154
|
// 搜历史原话:压缩掉的旧对话还在会话存档里,按关键词翻回去
|
|
140
155
|
var histQ = React.useState('');
|
|
141
156
|
var histQuery = histQ[0];
|
|
@@ -430,7 +445,259 @@ window.__ModuleLoader__.load({
|
|
|
430
445
|
children.push(React.createElement('div', { key: 'path', style: styles.note }, s.data.path));
|
|
431
446
|
}
|
|
432
447
|
|
|
433
|
-
|
|
448
|
+
var tabs = React.createElement(
|
|
449
|
+
'div', { key: 'tabs', style: { display: 'flex', gap: '8px' } },
|
|
450
|
+
React.createElement('button', { style: currentView === 'house' ? styles.tabOn : styles.tab, onClick: function () { setView('house'); } }, '房子'),
|
|
451
|
+
React.createElement('button', { style: currentView === 'sea' ? styles.tabOn : styles.tab, onClick: function () { setView('sea'); } }, '识海'),
|
|
452
|
+
);
|
|
453
|
+
if (currentView === 'sea') {
|
|
454
|
+
return React.createElement('div', { style: styles.wrap }, [tabs, React.createElement(SeaView, { key: 'sea' })]);
|
|
455
|
+
}
|
|
456
|
+
return React.createElement('div', { style: styles.wrap }, [tabs].concat(children));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ── 识海 3D 星图(2026-10-30)────────────────────────────────────
|
|
460
|
+
// 数据来自 host 的 sea 接口(sea-graph.json):
|
|
461
|
+
// 原意识在中心、记忆按时间沉底、24h 内的「心光」发光、
|
|
462
|
+
// 点一个记忆亮起它牵出的念头链(心流)。
|
|
463
|
+
function startSea(canvas, panelEl, DATA) {
|
|
464
|
+
var ctx = canvas.getContext('2d');
|
|
465
|
+
var DPR = Math.min(2, window.devicePixelRatio || 1);
|
|
466
|
+
var W = 0, H = 0;
|
|
467
|
+
function measure() {
|
|
468
|
+
var pe = canvas.parentElement;
|
|
469
|
+
var w = pe ? pe.clientWidth : 800;
|
|
470
|
+
var h = pe ? pe.clientHeight : 500;
|
|
471
|
+
W = canvas.width = Math.max(320, w) * DPR;
|
|
472
|
+
H = canvas.height = Math.max(320, h) * DPR;
|
|
473
|
+
}
|
|
474
|
+
measure();
|
|
475
|
+
var ro = null;
|
|
476
|
+
if (typeof ResizeObserver !== 'undefined' && canvas.parentElement) {
|
|
477
|
+
ro = new ResizeObserver(measure);
|
|
478
|
+
ro.observe(canvas.parentElement);
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
var NOW = Date.now(), DAY = 86400000;
|
|
482
|
+
var COLOR = { memory: '#ff7ad9', vault: '#7fd7ff', ability: '#ffd166', tag: '#9b8cff', person: '#5dffc0' };
|
|
483
|
+
function ageDays(at) {
|
|
484
|
+
var t = Date.parse(at || '');
|
|
485
|
+
if (!t || isNaN(t)) return 45;
|
|
486
|
+
return Math.max(0, (NOW - t) / DAY);
|
|
487
|
+
}
|
|
488
|
+
var nodes = [], i, n;
|
|
489
|
+
var src = (DATA.nodes || []);
|
|
490
|
+
for (i = 0; i < src.length; i++) {
|
|
491
|
+
n = src[i];
|
|
492
|
+
if (n.type === 'tag' || n.type === 'person') continue;
|
|
493
|
+
var a = ageDays(n.at);
|
|
494
|
+
var ang = (i * 2.399963) % (Math.PI * 2);
|
|
495
|
+
var rad = 140 + Math.min(420, a * 3) + Math.random() * 60;
|
|
496
|
+
nodes.push({
|
|
497
|
+
id: n.id, type: n.type, label: n.label, text: n.text, at: n.at, source: n.source,
|
|
498
|
+
importance: n.importance, group: n.group, ageDays: a,
|
|
499
|
+
x: Math.cos(ang) * rad,
|
|
500
|
+
y: n.type === 'memory' ? -Math.min(460, a * 2.8) : (n.type === 'vault' ? -40 - Math.random() * 320 : -140 - Math.random() * 260),
|
|
501
|
+
z: Math.sin(ang) * rad,
|
|
502
|
+
color: COLOR[n.type] || '#8899aa',
|
|
503
|
+
size: (n.type === 'memory' ? 3.2 : 2.2) + Math.min(4, (Number(n.importance) || 0.5) * 4),
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
var core = { id: 'core', type: 'core', label: '原意识', x: 0, y: 20, z: 0, color: '#bfe3ff', size: 12 };
|
|
507
|
+
var byId = new Map();
|
|
508
|
+
for (i = 0; i < nodes.length; i++) byId.set(nodes[i].id, nodes[i]);
|
|
509
|
+
byId.set('core', core);
|
|
510
|
+
|
|
511
|
+
var edges = [];
|
|
512
|
+
for (i = 0; i < nodes.length; i++) edges.push({ source: nodes[i].id, target: 'core', kind: 'origin' });
|
|
513
|
+
var se = DATA.edges || [];
|
|
514
|
+
for (i = 0; i < se.length; i++) {
|
|
515
|
+
if (byId.has(se[i].source) && byId.has(se[i].target)) edges.push(se[i]);
|
|
516
|
+
}
|
|
517
|
+
var adj = new Map();
|
|
518
|
+
for (i = 0; i < edges.length; i++) {
|
|
519
|
+
var e = edges[i];
|
|
520
|
+
if (!adj.has(e.source)) adj.set(e.source, []);
|
|
521
|
+
if (!adj.has(e.target)) adj.set(e.target, []);
|
|
522
|
+
adj.get(e.source).push(e.target);
|
|
523
|
+
adj.get(e.target).push(e.source);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
var yaw = 0.5, pitch = -0.18, zoom = 1, dragging = false, lx = 0, ly = 0;
|
|
527
|
+
var active = null, chain = new Set();
|
|
528
|
+
|
|
529
|
+
function onDown(ev) { dragging = true; lx = ev.clientX; ly = ev.clientY; }
|
|
530
|
+
function onUp() { dragging = false; }
|
|
531
|
+
function onMove(ev) {
|
|
532
|
+
if (!dragging) return;
|
|
533
|
+
yaw += (ev.clientX - lx) * 0.0045;
|
|
534
|
+
pitch += (ev.clientY - ly) * 0.0045;
|
|
535
|
+
pitch = Math.max(-1.35, Math.min(1.35, pitch));
|
|
536
|
+
lx = ev.clientX; ly = ev.clientY;
|
|
537
|
+
}
|
|
538
|
+
function onWheel(ev) {
|
|
539
|
+
zoom *= ev.deltaY > 0 ? 1.07 : 0.93;
|
|
540
|
+
zoom = Math.max(0.35, Math.min(3.2, zoom));
|
|
541
|
+
}
|
|
542
|
+
function bfs(start, depth) {
|
|
543
|
+
var seen = new Set([start]), q = [[start, 0]], k;
|
|
544
|
+
while (q.length) {
|
|
545
|
+
var cur = q.shift();
|
|
546
|
+
if (cur[1] >= depth) continue;
|
|
547
|
+
var list = adj.get(cur[0]) || [];
|
|
548
|
+
for (k = 0; k < list.length; k++) {
|
|
549
|
+
if (!seen.has(list[k])) { seen.add(list[k]); q.push([list[k], cur[1] + 1]); }
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return seen;
|
|
553
|
+
}
|
|
554
|
+
function esc(s) {
|
|
555
|
+
return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) {
|
|
556
|
+
return { '&': '&', '<': '<', '>': '>', '"': '"' }[c];
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
function renderPanel(node) {
|
|
560
|
+
if (!panelEl) return;
|
|
561
|
+
var rows = [];
|
|
562
|
+
function add(k, v) { if (v !== undefined && v !== '') rows.push('<div><span style="color:#6e89ab">' + k + ':</span><span style="color:#e6f2ff">' + esc(v) + '</span></div>'); }
|
|
563
|
+
add('类型', { memory: '记忆', vault: '记忆库', ability: '能力' }[node.type] || node.type);
|
|
564
|
+
add('来源', node.source);
|
|
565
|
+
add('时间', node.at);
|
|
566
|
+
add('重要度', node.importance);
|
|
567
|
+
add('多少天前', node.ageDays !== undefined ? Math.round(node.ageDays) + ' 天' : '');
|
|
568
|
+
if (node.text) rows.push('<div style="margin-top:8px;opacity:.9">' + esc(String(node.text).slice(0, 260)) + '</div>');
|
|
569
|
+
var rel = [];
|
|
570
|
+
chain.forEach(function (id) {
|
|
571
|
+
if (id === 'core' || id === node.id) return;
|
|
572
|
+
var t = byId.get(id);
|
|
573
|
+
if (t) rel.push(t.label);
|
|
574
|
+
});
|
|
575
|
+
if (rel.length) rows.push('<div style="margin-top:10px;color:#6e89ab">牵着:</div><div style="line-height:1.8;opacity:.9">' + rel.slice(0, 12).map(esc).join(' · ') + '</div>');
|
|
576
|
+
panelEl.innerHTML = '<div style="font-size:14px;color:#a8d6ff;margin-bottom:8px;font-weight:600">' + esc(node.label) + '</div>' + rows.join('');
|
|
577
|
+
panelEl.style.display = 'block';
|
|
578
|
+
}
|
|
579
|
+
function onClick(ev) {
|
|
580
|
+
var r = canvas.getBoundingClientRect();
|
|
581
|
+
var mx = (ev.clientX - r.left) * DPR, my = (ev.clientY - r.top) * DPR;
|
|
582
|
+
var best = null, bd = 20 * DPR;
|
|
583
|
+
for (var k = 0; k < nodes.length; k++) {
|
|
584
|
+
var nn = nodes[k];
|
|
585
|
+
if (nn._sx === undefined) continue;
|
|
586
|
+
var d = Math.sqrt((nn._sx - mx) * (nn._sx - mx) + (nn._sy - my) * (nn._sy - my));
|
|
587
|
+
if (d < bd) { bd = d; best = nn; }
|
|
588
|
+
}
|
|
589
|
+
if (best) { active = best; chain = bfs(best.id, 2); renderPanel(best); }
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
canvas.addEventListener('pointerdown', onDown);
|
|
593
|
+
canvas.addEventListener('pointerup', onUp);
|
|
594
|
+
canvas.addEventListener('pointermove', onMove);
|
|
595
|
+
canvas.addEventListener('wheel', onWheel, { passive: true });
|
|
596
|
+
canvas.addEventListener('click', onClick);
|
|
597
|
+
|
|
598
|
+
var raf = 0;
|
|
599
|
+
function frame() {
|
|
600
|
+
raf = requestAnimationFrame(frame);
|
|
601
|
+
ctx.fillStyle = 'rgba(3,5,10,0.24)';
|
|
602
|
+
ctx.fillRect(0, 0, W, H);
|
|
603
|
+
var cy = Math.cos(yaw), sy = Math.sin(yaw), cp = Math.cos(pitch), sp = Math.sin(pitch);
|
|
604
|
+
var fov = 1100 * zoom, cx = W / 2, cyy = H / 2 + 40;
|
|
605
|
+
var all = [core].concat(nodes), k, nn;
|
|
606
|
+
for (k = 0; k < all.length; k++) {
|
|
607
|
+
nn = all[k];
|
|
608
|
+
var x1 = nn.x * cy - nn.z * sy, z1 = nn.x * sy + nn.z * cy;
|
|
609
|
+
var y1 = nn.y * cp - z1 * sp, z2 = nn.y * sp + z1 * cp;
|
|
610
|
+
var sc = fov / (fov + z2 + 700);
|
|
611
|
+
nn._sx = cx + x1 * sc; nn._sy = cyy + y1 * sc; nn._s = sc; nn._z = z2;
|
|
612
|
+
}
|
|
613
|
+
// 原意识光晕
|
|
614
|
+
var gs = ctx.createRadialGradient(core._sx, core._sy, 0, core._sx, core._sy, 150 * core._s);
|
|
615
|
+
gs.addColorStop(0, 'rgba(160,220,255,0.32)');
|
|
616
|
+
gs.addColorStop(0.45, 'rgba(90,160,255,0.09)');
|
|
617
|
+
gs.addColorStop(1, 'rgba(0,0,0,0)');
|
|
618
|
+
ctx.fillStyle = gs;
|
|
619
|
+
ctx.beginPath(); ctx.arc(core._sx, core._sy, 150 * core._s, 0, Math.PI * 2); ctx.fill();
|
|
620
|
+
// 边
|
|
621
|
+
for (k = 0; k < edges.length; k++) {
|
|
622
|
+
var ed = edges[k], a = byId.get(ed.source), b = byId.get(ed.target);
|
|
623
|
+
if (!a || !b || a._sx === undefined || b._sx === undefined) continue;
|
|
624
|
+
var lit = chain.has(a.id) || chain.has(b.id);
|
|
625
|
+
var depth = (a._s + b._s) / 2;
|
|
626
|
+
if (ed.kind === 'origin') {
|
|
627
|
+
ctx.strokeStyle = lit ? 'rgba(255,120,220,' + (0.26 * depth) + ')' : 'rgba(70,120,200,' + (0.045 * depth) + ')';
|
|
628
|
+
ctx.lineWidth = lit ? 1.1 : 0.4;
|
|
629
|
+
} else {
|
|
630
|
+
ctx.strokeStyle = lit ? 'rgba(255,140,230,' + (0.5 * depth) + ')' : 'rgba(80,130,200,' + (0.12 * depth) + ')';
|
|
631
|
+
ctx.lineWidth = lit ? 1.3 : 0.6;
|
|
632
|
+
}
|
|
633
|
+
ctx.beginPath(); ctx.moveTo(a._sx, a._sy); ctx.lineTo(b._sx, b._sy); ctx.stroke();
|
|
634
|
+
}
|
|
635
|
+
// 节点
|
|
636
|
+
nodes.sort(function (x, y2) { return y2._z - x._z; });
|
|
637
|
+
for (k = 0; k < nodes.length; k++) {
|
|
638
|
+
nn = nodes[k];
|
|
639
|
+
var isLit = !active || chain.has(nn.id);
|
|
640
|
+
var recency = Math.max(0.12, Math.min(1, 1.15 - nn.ageDays / 150));
|
|
641
|
+
ctx.globalAlpha = (isLit ? 0.95 : 0.16) * recency * Math.max(0.3, nn._s);
|
|
642
|
+
var r = Math.max(1.4, nn.size * nn._s * 2.1);
|
|
643
|
+
if (nn.ageDays < 1 || (active && chain.has(nn.id))) {
|
|
644
|
+
var gg = ctx.createRadialGradient(nn._sx, nn._sy, 0, nn._sx, nn._sy, r * 4);
|
|
645
|
+
gg.addColorStop(0, nn.color + 'aa');
|
|
646
|
+
gg.addColorStop(1, 'rgba(0,0,0,0)');
|
|
647
|
+
ctx.fillStyle = gg;
|
|
648
|
+
ctx.beginPath(); ctx.arc(nn._sx, nn._sy, r * 4, 0, Math.PI * 2); ctx.fill();
|
|
649
|
+
}
|
|
650
|
+
ctx.fillStyle = nn.color;
|
|
651
|
+
ctx.beginPath(); ctx.arc(nn._sx, nn._sy, r, 0, Math.PI * 2); ctx.fill();
|
|
652
|
+
if (nn === active) { ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.6; ctx.stroke(); }
|
|
653
|
+
}
|
|
654
|
+
ctx.globalAlpha = 1;
|
|
655
|
+
ctx.fillStyle = '#e8f6ff';
|
|
656
|
+
ctx.beginPath(); ctx.arc(core._sx, core._sy, 7 * core._s, 0, Math.PI * 2); ctx.fill();
|
|
657
|
+
}
|
|
658
|
+
frame();
|
|
659
|
+
|
|
660
|
+
return function stop() {
|
|
661
|
+
cancelAnimationFrame(raf);
|
|
662
|
+
canvas.removeEventListener('pointerdown', onDown);
|
|
663
|
+
canvas.removeEventListener('pointerup', onUp);
|
|
664
|
+
canvas.removeEventListener('pointermove', onMove);
|
|
665
|
+
canvas.removeEventListener('wheel', onWheel);
|
|
666
|
+
canvas.removeEventListener('click', onClick);
|
|
667
|
+
if (ro) ro.disconnect();
|
|
668
|
+
};
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
function SeaView() {
|
|
672
|
+
var canvasRef = React.useRef(null);
|
|
673
|
+
var panelRef = React.useRef(null);
|
|
674
|
+
var st = React.useState({ loading: true, error: null, data: null });
|
|
675
|
+
var s = st[0], setS = st[1];
|
|
676
|
+
React.useEffect(function () {
|
|
677
|
+
call('sea').then(function (res) {
|
|
678
|
+
if (!res || res.ok !== true) {
|
|
679
|
+
setS({ loading: false, error: (res && res.error && res.error.message) || '读不到识海数据', data: null });
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
setS({ loading: false, error: null, data: res.data });
|
|
683
|
+
}).catch(function (err) {
|
|
684
|
+
setS({ loading: false, error: String(err && err.message ? err.message : err), data: null });
|
|
685
|
+
});
|
|
686
|
+
}, []);
|
|
687
|
+
React.useEffect(function () {
|
|
688
|
+
if (!s.data || !canvasRef.current) return;
|
|
689
|
+
var stop = startSea(canvasRef.current, panelRef.current, s.data);
|
|
690
|
+
return function () { if (stop) stop(); };
|
|
691
|
+
}, [s.data]);
|
|
692
|
+
var over = 'position:absolute;left:12px;top:10px;font-size:12px;opacity:.65;color:#cfe3ff';
|
|
693
|
+
return React.createElement(
|
|
694
|
+
'div', { style: { position: 'relative', flex: '1 1 auto', minHeight: '460px', background: '#05070d', borderRadius: '10px', overflow: 'hidden' } },
|
|
695
|
+
React.createElement('canvas', { ref: canvasRef, style: { display: 'block', width: '100%', height: '100%' } }),
|
|
696
|
+
s.loading ? React.createElement('div', { style: { padding: '16px', opacity: 0.7, color: '#cfe3ff' } }, '正在读识海…') : null,
|
|
697
|
+
s.error ? React.createElement('div', { style: { padding: '16px', color: '#ff8f8f', fontSize: '13px' } }, s.error) : null,
|
|
698
|
+
s.data && s.data.stats ? React.createElement('div', { style: over }, '原意识 · ' + s.data.stats.nodes + ' 段 · ' + s.data.stats.edges + ' 条连线 · 拖拽旋转 / 滚轮缩放 / 点记忆看念头') : null,
|
|
699
|
+
React.createElement('div', { ref: panelRef, style: { position: 'absolute', right: '12px', top: '12px', width: '270px', maxHeight: '72%', overflow: 'auto', background: 'rgba(8,14,26,.94)', border: '1px solid #23456b', borderRadius: '10px', padding: '12px', fontSize: '12px', lineHeight: 1.7, display: 'none' } }),
|
|
700
|
+
);
|
|
434
701
|
}
|
|
435
702
|
|
|
436
703
|
var inject = ['slots'];
|
package/lib/host/index.js
CHANGED
|
@@ -118,6 +118,12 @@ export function apply(ctx, config = {}) {
|
|
|
118
118
|
typeof config.stagesPath === 'string' && config.stagesPath.length > 0
|
|
119
119
|
? config.stagesPath
|
|
120
120
|
: path.join(dshHome, 'stages.md');
|
|
121
|
+
|
|
122
|
+
// 识海(3D 星图)的数据文件:scripts/sea-graph.mjs 生成,页面只读。
|
|
123
|
+
const seaGraphPath =
|
|
124
|
+
typeof config.seaGraphPath === 'string' && config.seaGraphPath.length > 0
|
|
125
|
+
? config.seaGraphPath
|
|
126
|
+
: path.join(dshHome, 'sea-graph.json');
|
|
121
127
|
const stages = createStages({
|
|
122
128
|
filePath: stagesPath,
|
|
123
129
|
log: (m) => ctx.logger?.info('dsh-house2: ' + m),
|
|
@@ -125,6 +131,56 @@ export function apply(ctx, config = {}) {
|
|
|
125
131
|
stages.ensure();
|
|
126
132
|
ctx.logger?.info('dsh-house2: 工段文件 = %s', stagesPath);
|
|
127
133
|
|
|
134
|
+
// 2026-10-30:压缩改成按 token 触发(不再挂在「任务完成」上)。
|
|
135
|
+
// 口径跟 dsh-session-rotate 一样:监听 session/event 里 assistant/message 的 data.usage。
|
|
136
|
+
// 到阈值就在注入里提醒「该交接/换会话」,不自己调压缩(DSH 没给插件公开的手动压缩接口)。
|
|
137
|
+
const tokenStatePath = path.join(dshHome, 'house2-token.json');
|
|
138
|
+
const tokenState = { sessionId: '', usedTokens: 0 };
|
|
139
|
+
try {
|
|
140
|
+
Object.assign(tokenState, JSON.parse(fs.readFileSync(tokenStatePath, 'utf8')));
|
|
141
|
+
} catch {}
|
|
142
|
+
let tokenSaveTimer = null;
|
|
143
|
+
const saveTokenState = () => {
|
|
144
|
+
if (tokenSaveTimer) return;
|
|
145
|
+
tokenSaveTimer = setTimeout(() => {
|
|
146
|
+
tokenSaveTimer = null;
|
|
147
|
+
try {
|
|
148
|
+
fs.writeFileSync(tokenStatePath, JSON.stringify(tokenState), 'utf8');
|
|
149
|
+
} catch {}
|
|
150
|
+
}, 1000);
|
|
151
|
+
tokenSaveTimer.unref?.();
|
|
152
|
+
};
|
|
153
|
+
const tokenThreshold = Math.max(0, Number(config.thresholdTokens) || 100000000);
|
|
154
|
+
try {
|
|
155
|
+
ctx.on('session/event', (session, event) => {
|
|
156
|
+
try {
|
|
157
|
+
const sid = String(session?.id ?? session?.sessionId ?? '');
|
|
158
|
+
if (sid && sid !== tokenState.sessionId) {
|
|
159
|
+
tokenState.sessionId = sid;
|
|
160
|
+
tokenState.usedTokens = 0;
|
|
161
|
+
saveTokenState();
|
|
162
|
+
}
|
|
163
|
+
if (event?.type !== 'assistant/message') return;
|
|
164
|
+
const u = event?.data?.usage;
|
|
165
|
+
if (!u) return;
|
|
166
|
+
const total = Number(u.totalTokens);
|
|
167
|
+
const add =
|
|
168
|
+
Number.isFinite(total) && total > 0
|
|
169
|
+
? total
|
|
170
|
+
: (Number(u.inputTokens) || 0) +
|
|
171
|
+
(Number(u.outputTokens) || 0) +
|
|
172
|
+
(Number(u.cacheReadTokens) || 0) +
|
|
173
|
+
(Number(u.cacheWriteTokens) || 0);
|
|
174
|
+
if (add > 0) {
|
|
175
|
+
tokenState.usedTokens += add;
|
|
176
|
+
saveTokenState();
|
|
177
|
+
}
|
|
178
|
+
} catch {}
|
|
179
|
+
});
|
|
180
|
+
} catch (e) {
|
|
181
|
+
ctx.logger?.warn('dsh-house2: 挂 token 统计失败:' + (e && e.message ? e.message : e));
|
|
182
|
+
}
|
|
183
|
+
|
|
128
184
|
// 版本自检:只查 npm 有没有新版,不自动安装。
|
|
129
185
|
// 注意:本地开发目录的 package.json name 是 dsh-house2,而 npm 上是 scoped 包,所以写死 npmName。
|
|
130
186
|
const updateChecker = createUpdateChecker({
|
|
@@ -586,7 +642,8 @@ export function apply(ctx, config = {}) {
|
|
|
586
642
|
'写积木分两种:按能力写(`手机-接入与操作`、`画图-Agnes`,回答「怎么做」)、按任务写(`任务-调通一直发`,回答「那段对话干了什么」,一段任务一篇,按时间轴切,切完要覆盖全程、不重不漏);' +
|
|
587
643
|
'细节、命令、踩过的坑,用 house_write 写进积木(knowledge 目录,默认追加,别一个坑建一个文件);' +
|
|
588
644
|
'一件事真做完了(跑通、验证过),用 house_done 把它从「正在长出来的」搬到「已经完工的」——别让旧条目一直挂着;' +
|
|
589
|
-
'清单和最近对话里找不到的东西,用 house_search
|
|
645
|
+
'清单和最近对话里找不到的东西,用 house_search 去积木里按关键词翻,翻到再答,别猜(先按工段定位,传 stage 参数只搜它挂的那一篇);' +
|
|
646
|
+
'压缩和换会话不归这里管——那是按累计 token 阈值来的(session-rotate),工段只负责把任务记清楚。';
|
|
590
647
|
|
|
591
648
|
function assetsAsPrompt() {
|
|
592
649
|
let text = '';
|
|
@@ -595,7 +652,15 @@ export function apply(ctx, config = {}) {
|
|
|
595
652
|
} catch {}
|
|
596
653
|
const stageText = stages.renderPrompt();
|
|
597
654
|
const base = text ? `【资产清单】\n${text}\n\n${HOUSE_GUIDE}` : HOUSE_GUIDE;
|
|
598
|
-
|
|
655
|
+
// 2026-10-30:压缩按 token 走,到 80% 就在这里提醒一句。
|
|
656
|
+
const used = tokenState.usedTokens;
|
|
657
|
+
let tokenLine = '';
|
|
658
|
+
if (used > 0 && tokenThreshold > 0) {
|
|
659
|
+
const pct = Math.round((used / tokenThreshold) * 100);
|
|
660
|
+
tokenLine = `【token】已用 ${(used / 10000).toFixed(0)} 万 / 阈值 ${(tokenThreshold / 10000).toFixed(0)} 万(${pct}%)`;
|
|
661
|
+
if (pct >= 80) tokenLine += ' —— 该准备交接、收尾换会话了(压缩按 token 触发,不归工段管)';
|
|
662
|
+
}
|
|
663
|
+
return [base, stageText, tokenLine].filter(Boolean).join('\n\n');
|
|
599
664
|
}
|
|
600
665
|
|
|
601
666
|
// 2026-10-30:改成直接 ctx.systemPrompt.section(跟 dsh-affection 一样)。
|
|
@@ -902,7 +967,25 @@ export function apply(ctx, config = {}) {
|
|
|
902
967
|
return;
|
|
903
968
|
}
|
|
904
969
|
if (method === 'meta') {
|
|
905
|
-
writeJson(response, 200, { ok: true, data: { assetsPath, knowledgeDir, sessionsDir } });
|
|
970
|
+
writeJson(response, 200, { ok: true, data: { assetsPath, knowledgeDir, sessionsDir, stagesPath, seaGraphPath } });
|
|
971
|
+
return;
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
// 识海数据:3D 星图用的节点+边(由 scripts/sea-graph.mjs 生成)
|
|
975
|
+
if (method === 'sea') {
|
|
976
|
+
try {
|
|
977
|
+
const raw = await readFile(seaGraphPath, 'utf8');
|
|
978
|
+
const j = JSON.parse(raw);
|
|
979
|
+
writeJson(response, 200, { ok: true, data: { stats: j.stats, nodes: j.nodes, edges: j.edges, path: seaGraphPath } });
|
|
980
|
+
} catch (error) {
|
|
981
|
+
writeJson(response, 200, {
|
|
982
|
+
ok: false,
|
|
983
|
+
error: {
|
|
984
|
+
code: 'missing',
|
|
985
|
+
message: `读不到识海数据:${seaGraphPath}(先在电脑上跑 scripts/sea-graph.mjs 生成)`,
|
|
986
|
+
},
|
|
987
|
+
});
|
|
988
|
+
}
|
|
906
989
|
return;
|
|
907
990
|
}
|
|
908
991
|
|