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.
Files changed (2) hide show
  1. package/dist/index.js +176 -0
  2. 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.1",
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",