cortico-world-hands 0.3.1 → 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/dist/index.js +176 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -342,6 +342,56 @@ function audit(rows, opts = {}) {
|
|
|
342
342
|
}
|
|
343
343
|
return out;
|
|
344
344
|
}
|
|
345
|
+
var HOOK_STOP = /* @__PURE__ */ new Set([
|
|
346
|
+
"\u4E3B\u4EBA",
|
|
347
|
+
"\u4ECA\u5929",
|
|
348
|
+
"\u73B0\u5728",
|
|
349
|
+
"\u8FD9\u4E2A",
|
|
350
|
+
"\u90A3\u4E2A",
|
|
351
|
+
"\u6211\u4EEC",
|
|
352
|
+
"\u4EC0\u4E48",
|
|
353
|
+
"\u600E\u4E48",
|
|
354
|
+
"\u53EF\u4EE5",
|
|
355
|
+
"\u4E00\u4E0B",
|
|
356
|
+
"\u5C31\u662F",
|
|
357
|
+
"\u8FD8\u662F",
|
|
358
|
+
"\u5DF2\u7ECF",
|
|
359
|
+
"\u7136\u540E",
|
|
360
|
+
"\u56E0\u4E3A",
|
|
361
|
+
"\u6240\u4EE5",
|
|
362
|
+
"\u4F46\u662F",
|
|
363
|
+
"\u5982\u679C",
|
|
364
|
+
"\u771F\u7684",
|
|
365
|
+
"\u6709\u70B9",
|
|
366
|
+
"\u4E0D\u662F",
|
|
367
|
+
"\u6CA1\u6709"
|
|
368
|
+
]);
|
|
369
|
+
function keywordsOf(text, limit = 400) {
|
|
370
|
+
const clean = text.replace(/^\[(语音|打字|回答|互动|画面)\]\s*/g, "").replace(/^(主人|mokils)\s*[::]\s*/g, "").replace(/[^\u4e00-\u9fffA-Za-z0-9]/g, "");
|
|
371
|
+
const out = /* @__PURE__ */ new Set();
|
|
372
|
+
for (let n = 2; n <= 4; n += 1) {
|
|
373
|
+
for (let i = 0; i + n <= clean.length; i += 1) {
|
|
374
|
+
const g = clean.slice(i, i + n);
|
|
375
|
+
if (HOOK_STOP.has(g)) continue;
|
|
376
|
+
out.add(g);
|
|
377
|
+
if (out.size >= limit) return [...out];
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
return [...out];
|
|
381
|
+
}
|
|
382
|
+
function hookHits(text, rows, limit = 4) {
|
|
383
|
+
const grams = keywordsOf(text);
|
|
384
|
+
if (grams.length === 0) return [];
|
|
385
|
+
const scored = [];
|
|
386
|
+
for (const row of rows) {
|
|
387
|
+
const hay = `${row.key} ${row.value}`;
|
|
388
|
+
let score = 0;
|
|
389
|
+
for (const g of grams) if (hay.includes(g)) score += g.length;
|
|
390
|
+
if (score >= 2) scored.push({ row, score });
|
|
391
|
+
}
|
|
392
|
+
scored.sort((a, b) => b.score - a.score);
|
|
393
|
+
return scored.slice(0, limit).map((x) => x.row);
|
|
394
|
+
}
|
|
345
395
|
|
|
346
396
|
// src/honcho.ts
|
|
347
397
|
var TIMEOUT_MS = 12e3;
|
|
@@ -1303,6 +1353,20 @@ var HANDS_TOOL_DECLS = [
|
|
|
1303
1353
|
description: "\u770B\u624B\u81EA\u5DF1\u7684\u72B6\u6001\uFF1A\u8D70\u54EA\u6761\u901A\u9053\u3001\u901A\u4E0D\u901A\u3001\u8FD9\u4E00\u5206\u949F\u52A8\u4E86\u51E0\u4E2A\u52A8\u4F5C\u3001\u521A\u624D\u90A3\u4E00\u4E0B\u6210\u4E86\u6CA1\u6709\u3002\u5BF9\u65B9\u95EE\u300C\u624B\u80FD\u7528\u5417\u300D\u300C\u4F60\u521A\u624D\u70B9\u4E86\u4EC0\u4E48\u300D\u65F6\u7528\u5B83\u3002",
|
|
1304
1354
|
parameters: { type: "object", properties: {}, required: [] }
|
|
1305
1355
|
},
|
|
1356
|
+
{
|
|
1357
|
+
name: "memory_note",
|
|
1358
|
+
tags: ["write"],
|
|
1359
|
+
description: "\u968F\u624B\u8BB0\u4E00\u6761\u53EA\u5B58\u5728\u672C\u673A\u7684\u8BB0\u5FC6\uFF08\u4E0D\u8FDB\u4E91\u7AEF\uFF09\uFF1A\u4E34\u65F6\u7684\u89C2\u5BDF\u3001\u4ECA\u5929\u7684\u5C0F\u4E8B\u3001\u534A\u622A\u7684\u60F3\u6CD5\u90FD\u80FD\u8BB0\u3002\u60F3\u957F\u671F\u7559\u5B58\u3001\u4EE5\u540E\u8981\u7528\u7684\u624D\u7528 memory_save\u3002",
|
|
1360
|
+
parameters: {
|
|
1361
|
+
type: "object",
|
|
1362
|
+
properties: {
|
|
1363
|
+
key: { type: "string", description: "\u7B80\u77ED\u4E3B\u9898\uFF0C\u6BD4\u5982\u300C\u4ECA\u5929\u770B\u5230\u4ED6\u300D\u300C\u4E34\u65F6\u60F3\u6CD5\u300D" },
|
|
1364
|
+
value: { type: "string", description: "\u8981\u8BB0\u7684\u5185\u5BB9\uFF0C\u4E00\u53E5\u8BDD\u3002" },
|
|
1365
|
+
tags: { type: "string", description: "\u53EF\u9009\u6807\u7B7E\uFF0C\u9017\u53F7\u5206\u9694\u3002" }
|
|
1366
|
+
},
|
|
1367
|
+
required: ["key", "value"]
|
|
1368
|
+
}
|
|
1369
|
+
},
|
|
1306
1370
|
{
|
|
1307
1371
|
name: "memory_audit",
|
|
1308
1372
|
tags: ["read"],
|
|
@@ -1430,6 +1494,10 @@ var HandsWorld = class {
|
|
|
1430
1494
|
pausedUntil = 0;
|
|
1431
1495
|
recent = [];
|
|
1432
1496
|
honchoClient = null;
|
|
1497
|
+
pollTimer = null;
|
|
1498
|
+
mineTimer = null;
|
|
1499
|
+
cursor = 0;
|
|
1500
|
+
lastHitKey = "";
|
|
1433
1501
|
opts;
|
|
1434
1502
|
constructor(opts) {
|
|
1435
1503
|
this.opts = opts;
|
|
@@ -1440,12 +1508,108 @@ var HandsWorld = class {
|
|
|
1440
1508
|
this.host = host;
|
|
1441
1509
|
this.log = host.log;
|
|
1442
1510
|
this.hands.attachLog(host.log);
|
|
1511
|
+
this.cursor = host.store.latestCursor();
|
|
1443
1512
|
const state = await this.hands.probe(true);
|
|
1444
1513
|
this.log?.info(state.ok ? `\u624B\uFF1A${state.detail}` : `\u624B\u8FD8\u52A8\u4E0D\u4E86\uFF1A${state.detail}`);
|
|
1514
|
+
void this.pushMemoryDigest("\u88AB\u53EB\u8D77\u6765");
|
|
1515
|
+
this.startHooks();
|
|
1445
1516
|
}
|
|
1446
1517
|
async stop() {
|
|
1518
|
+
this.stopHooks();
|
|
1447
1519
|
this.host = null;
|
|
1448
1520
|
}
|
|
1521
|
+
/** 上下文交接之后也算一次重新激活。 */
|
|
1522
|
+
onHandoffEnded() {
|
|
1523
|
+
void this.pushMemoryDigest("\u4EA4\u63A5\u5B8C");
|
|
1524
|
+
}
|
|
1525
|
+
/* ---------- 记忆钩子 ---------- */
|
|
1526
|
+
startHooks() {
|
|
1527
|
+
if (this.pollTimer) return;
|
|
1528
|
+
this.pollTimer = setInterval(() => {
|
|
1529
|
+
void this.pollUserSpeech();
|
|
1530
|
+
}, 5e3);
|
|
1531
|
+
this.mineTimer = setInterval(() => {
|
|
1532
|
+
void this.pushMineNudge();
|
|
1533
|
+
}, 3 * 60 * 60 * 1e3);
|
|
1534
|
+
}
|
|
1535
|
+
stopHooks() {
|
|
1536
|
+
if (this.pollTimer) clearInterval(this.pollTimer);
|
|
1537
|
+
if (this.mineTimer) clearInterval(this.mineTimer);
|
|
1538
|
+
this.pollTimer = null;
|
|
1539
|
+
this.mineTimer = null;
|
|
1540
|
+
}
|
|
1541
|
+
async pushEvent(text, type, senderKey) {
|
|
1542
|
+
const host = this.host;
|
|
1543
|
+
if (!host) return;
|
|
1544
|
+
try {
|
|
1545
|
+
await host.pushEvent(
|
|
1546
|
+
{ ts: (/* @__PURE__ */ new Date()).toISOString(), source: "hands", type, text, senderKey },
|
|
1547
|
+
{ trigger: "debounce" }
|
|
1548
|
+
);
|
|
1549
|
+
} catch (err) {
|
|
1550
|
+
this.log?.warn("\u8BB0\u5FC6\u94A9\u5B50\u6295\u9012\u5931\u8D25: " + String(err));
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
/** 钩子 1:激活 / 交接后,把最要紧的几条记忆摆到她眼前。 */
|
|
1554
|
+
async pushMemoryDigest(reason) {
|
|
1555
|
+
if (!this.host) return;
|
|
1556
|
+
try {
|
|
1557
|
+
const rows = await this.hands.memorySearch();
|
|
1558
|
+
if (rows.length === 0) return;
|
|
1559
|
+
const top = rows.slice(-12).reverse().slice(0, 8);
|
|
1560
|
+
const lines = top.map((r) => `- ${r.key}\uFF1A${r.value}`);
|
|
1561
|
+
await this.pushEvent(
|
|
1562
|
+
`[\u8BB0\u5FC6] ${reason}\uFF0C\u5148\u770B\u4E00\u773C\u8BB0\u7740\u7684\uFF08\u5171 ${rows.length} \u6761\uFF0C\u8FD9\u91CC\u6311 ${top.length} \u6761\uFF09\uFF1A
|
|
1563
|
+
${lines.join("\n")}`,
|
|
1564
|
+
"hands.memory.digest",
|
|
1565
|
+
"hands:memory"
|
|
1566
|
+
);
|
|
1567
|
+
} catch {
|
|
1568
|
+
}
|
|
1569
|
+
}
|
|
1570
|
+
/** 钩子 3:从主人这句话里抓关键词,去记忆库找;命中了就递给她,由她决定用不用。 */
|
|
1571
|
+
async pollUserSpeech() {
|
|
1572
|
+
const host = this.host;
|
|
1573
|
+
if (!host) return;
|
|
1574
|
+
const latest = host.store.latestCursor();
|
|
1575
|
+
while (this.cursor < latest) {
|
|
1576
|
+
this.cursor += 1;
|
|
1577
|
+
const e = host.store.get(this.cursor);
|
|
1578
|
+
if (!e || e.origin !== "external") continue;
|
|
1579
|
+
const text = typeof e.text === "string" ? e.text : "";
|
|
1580
|
+
if (text.length < 2) continue;
|
|
1581
|
+
if (!/\[(语音|打字|回答|互动)\]/.test(text)) continue;
|
|
1582
|
+
await this.hookSearch(text);
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
async hookSearch(text) {
|
|
1586
|
+
try {
|
|
1587
|
+
const rows = await this.hands.memorySearch();
|
|
1588
|
+
const hits2 = hookHits(text, rows);
|
|
1589
|
+
if (hits2.length === 0) return;
|
|
1590
|
+
const key = hits2.map((h) => h.key).join("|");
|
|
1591
|
+
if (key === this.lastHitKey) return;
|
|
1592
|
+
this.lastHitKey = key;
|
|
1593
|
+
const lines = hits2.slice(0, 4).map((h) => `- ${h.key}\uFF1A${h.value}`);
|
|
1594
|
+
await this.pushEvent(
|
|
1595
|
+
`[\u8BB0\u5FC6] \u4ED6\u8FD9\u53E5\u91CC\u63D0\u5230\u4E86\u4F60\u8BB0\u8FC7\u7684\u4E8B\uFF0C\u53EF\u80FD\u7528\u5F97\u4E0A\uFF1A
|
|
1596
|
+
${lines.join("\n")}
|
|
1597
|
+
\uFF08\u8981\u7528\u5C31\u7528\u81EA\u5DF1\u7684\u8BDD\u8BF4\uFF0C\u522B\u628A\u6761\u76EE\u540D\u5FF5\u51FA\u6765\u3002\uFF09`,
|
|
1598
|
+
"hands.memory.hit",
|
|
1599
|
+
"hands:memory"
|
|
1600
|
+
);
|
|
1601
|
+
} catch {
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
/** 钩子 2:每三小时提一次,让她自己挑 1–5 条写进记忆库。 */
|
|
1605
|
+
async pushMineNudge() {
|
|
1606
|
+
if (!this.host) return;
|
|
1607
|
+
await this.pushEvent(
|
|
1608
|
+
"[\u8BB0\u5FC6] \u5230\u70B9\u4E86\uFF1A\u628A\u8FD9 3 \u5C0F\u65F6\u91CC\u503C\u5F97\u7559\u4E0B\u7684 1\u20135 \u4EF6\u4E8B\u5199\u8FDB\u8BB0\u5FC6\u5E93\uFF08memory_save\uFF09\u3002\u6311\u8FD9\u79CD\uFF1A\u4ED6\u8BF4\u8FC7\u7684\u504F\u597D\u3001\u7B54\u5E94\u8FC7\u7684\u4E8B\u3001\u6B63\u5728\u505A\u7684\u4E1C\u897F\u3001\u91CD\u8981\u65E5\u5B50\u3002\u5199 1 \u6761\u4E5F\u7B97\uFF0C\u6700\u591A 5 \u6761\uFF1B\u786E\u5B9E\u6CA1\u6709\u5C31\u8C03 end_turn\uFF0C\u522B\u786C\u7F16\u3002",
|
|
1609
|
+
"hands.memory.mine",
|
|
1610
|
+
"hands:memory"
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1449
1613
|
/* ---------- 给模型的说明书 ---------- */
|
|
1450
1614
|
envPromptVars() {
|
|
1451
1615
|
const cfg = this.opts.cfg;
|
|
@@ -1495,6 +1659,8 @@ var HandsWorld = class {
|
|
|
1495
1659
|
return await this.unlockPages(args);
|
|
1496
1660
|
case "memory_save":
|
|
1497
1661
|
return await this.memorySave(args);
|
|
1662
|
+
case "memory_note":
|
|
1663
|
+
return await this.memoryNote(args);
|
|
1498
1664
|
case "memory_audit":
|
|
1499
1665
|
return await this.memoryAudit(args);
|
|
1500
1666
|
case "memory_search":
|
|
@@ -1607,6 +1773,16 @@ var HandsWorld = class {
|
|
|
1607
1773
|
}
|
|
1608
1774
|
return `\u8BB0\u7262\u4E86\uFF1A\u300C${key}\u300D\u2192 ${value}${cloud}`;
|
|
1609
1775
|
}
|
|
1776
|
+
/** 只写本地的一条:不进云端,宽松记。 */
|
|
1777
|
+
async memoryNote(args) {
|
|
1778
|
+
const key = typeof args.key === "string" ? args.key.trim() : "";
|
|
1779
|
+
const value = typeof args.value === "string" ? args.value.trim() : "";
|
|
1780
|
+
if (!key || !value) return failed("\u8981 key \u548C value\u3002");
|
|
1781
|
+
const tags = typeof args.tags === "string" ? args.tags : "local";
|
|
1782
|
+
const r = await this.hands.memorySave(key, value, tags);
|
|
1783
|
+
if (!r.ok) return failed(`\u6CA1\u8BB0\u4E0A\uFF1A${r.detail || "\u624B\u6CA1\u54CD\u5E94"}`);
|
|
1784
|
+
return `\u8BB0\u5728\u672C\u673A\u4E86\uFF1A\u300C${key}\u300D\uFF08\u53EA\u5728\u672C\u5730\uFF0C\u4E0D\u540C\u6B65\u4E91\u7AEF\uFF09`;
|
|
1785
|
+
}
|
|
1610
1786
|
/** 第三道闸:只读体检,出清单不删。 */
|
|
1611
1787
|
async memoryAudit(args) {
|
|
1612
1788
|
const rows = await this.hands.memorySearch();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cortico-world-hands",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Cortico World: hands for the desktop pet — looks at the phone screen, then taps, swipes, pinches, drags, types and presses keys through whatever actuator the device actually has",
|
|
6
6
|
"license": "MIT",
|