cortico-world-hands 0.3.1 → 0.4.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 +150 -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;
@@ -1430,6 +1480,10 @@ var HandsWorld = class {
1430
1480
  pausedUntil = 0;
1431
1481
  recent = [];
1432
1482
  honchoClient = null;
1483
+ pollTimer = null;
1484
+ mineTimer = null;
1485
+ cursor = 0;
1486
+ lastHitKey = "";
1433
1487
  opts;
1434
1488
  constructor(opts) {
1435
1489
  this.opts = opts;
@@ -1440,12 +1494,108 @@ var HandsWorld = class {
1440
1494
  this.host = host;
1441
1495
  this.log = host.log;
1442
1496
  this.hands.attachLog(host.log);
1497
+ this.cursor = host.store.latestCursor();
1443
1498
  const state = await this.hands.probe(true);
1444
1499
  this.log?.info(state.ok ? `\u624B\uFF1A${state.detail}` : `\u624B\u8FD8\u52A8\u4E0D\u4E86\uFF1A${state.detail}`);
1500
+ void this.pushMemoryDigest("\u88AB\u53EB\u8D77\u6765");
1501
+ this.startHooks();
1445
1502
  }
1446
1503
  async stop() {
1504
+ this.stopHooks();
1447
1505
  this.host = null;
1448
1506
  }
1507
+ /** 上下文交接之后也算一次重新激活。 */
1508
+ onHandoffEnded() {
1509
+ void this.pushMemoryDigest("\u4EA4\u63A5\u5B8C");
1510
+ }
1511
+ /* ---------- 记忆钩子 ---------- */
1512
+ startHooks() {
1513
+ if (this.pollTimer) return;
1514
+ this.pollTimer = setInterval(() => {
1515
+ void this.pollUserSpeech();
1516
+ }, 5e3);
1517
+ this.mineTimer = setInterval(() => {
1518
+ void this.pushMineNudge();
1519
+ }, 3 * 60 * 60 * 1e3);
1520
+ }
1521
+ stopHooks() {
1522
+ if (this.pollTimer) clearInterval(this.pollTimer);
1523
+ if (this.mineTimer) clearInterval(this.mineTimer);
1524
+ this.pollTimer = null;
1525
+ this.mineTimer = null;
1526
+ }
1527
+ async pushEvent(text, type, senderKey) {
1528
+ const host = this.host;
1529
+ if (!host) return;
1530
+ try {
1531
+ await host.pushEvent(
1532
+ { ts: (/* @__PURE__ */ new Date()).toISOString(), source: "hands", type, text, senderKey },
1533
+ { trigger: "debounce" }
1534
+ );
1535
+ } catch (err) {
1536
+ this.log?.warn("\u8BB0\u5FC6\u94A9\u5B50\u6295\u9012\u5931\u8D25: " + String(err));
1537
+ }
1538
+ }
1539
+ /** 钩子 1:激活 / 交接后,把最要紧的几条记忆摆到她眼前。 */
1540
+ async pushMemoryDigest(reason) {
1541
+ if (!this.host) return;
1542
+ try {
1543
+ const rows = await this.hands.memorySearch();
1544
+ if (rows.length === 0) return;
1545
+ const top = rows.slice(-12).reverse().slice(0, 8);
1546
+ const lines = top.map((r) => `- ${r.key}\uFF1A${r.value}`);
1547
+ await this.pushEvent(
1548
+ `[\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
1549
+ ${lines.join("\n")}`,
1550
+ "hands.memory.digest",
1551
+ "hands:memory"
1552
+ );
1553
+ } catch {
1554
+ }
1555
+ }
1556
+ /** 钩子 3:从主人这句话里抓关键词,去记忆库找;命中了就递给她,由她决定用不用。 */
1557
+ async pollUserSpeech() {
1558
+ const host = this.host;
1559
+ if (!host) return;
1560
+ const latest = host.store.latestCursor();
1561
+ while (this.cursor < latest) {
1562
+ this.cursor += 1;
1563
+ const e = host.store.get(this.cursor);
1564
+ if (!e || e.origin !== "external") continue;
1565
+ const text = typeof e.text === "string" ? e.text : "";
1566
+ if (text.length < 2) continue;
1567
+ if (!/\[(语音|打字|回答|互动)\]/.test(text)) continue;
1568
+ await this.hookSearch(text);
1569
+ }
1570
+ }
1571
+ async hookSearch(text) {
1572
+ try {
1573
+ const rows = await this.hands.memorySearch();
1574
+ const hits2 = hookHits(text, rows);
1575
+ if (hits2.length === 0) return;
1576
+ const key = hits2.map((h) => h.key).join("|");
1577
+ if (key === this.lastHitKey) return;
1578
+ this.lastHitKey = key;
1579
+ const lines = hits2.slice(0, 4).map((h) => `- ${h.key}\uFF1A${h.value}`);
1580
+ await this.pushEvent(
1581
+ `[\u8BB0\u5FC6] \u4ED6\u8FD9\u53E5\u91CC\u63D0\u5230\u4E86\u4F60\u8BB0\u8FC7\u7684\u4E8B\uFF0C\u53EF\u80FD\u7528\u5F97\u4E0A\uFF1A
1582
+ ${lines.join("\n")}
1583
+ \uFF08\u8981\u7528\u5C31\u7528\u81EA\u5DF1\u7684\u8BDD\u8BF4\uFF0C\u522B\u628A\u6761\u76EE\u540D\u5FF5\u51FA\u6765\u3002\uFF09`,
1584
+ "hands.memory.hit",
1585
+ "hands:memory"
1586
+ );
1587
+ } catch {
1588
+ }
1589
+ }
1590
+ /** 钩子 2:每三小时提一次,让她自己挑 1–5 条写进记忆库。 */
1591
+ async pushMineNudge() {
1592
+ if (!this.host) return;
1593
+ await this.pushEvent(
1594
+ "[\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",
1595
+ "hands.memory.mine",
1596
+ "hands:memory"
1597
+ );
1598
+ }
1449
1599
  /* ---------- 给模型的说明书 ---------- */
1450
1600
  envPromptVars() {
1451
1601
  const cfg = this.opts.cfg;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortico-world-hands",
3
- "version": "0.3.1",
3
+ "version": "0.4.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",