cortico-world-hands 0.3.0 → 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 +156 -6
  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;
@@ -444,7 +494,7 @@ var Honcho = class {
444
494
  import { execFile } from "node:child_process";
445
495
  import { readFileSync } from "node:fs";
446
496
  import { get as httpGet } from "node:http";
447
- var ACTION_TIMEOUT_MS = 15e3;
497
+ var ACTION_TIMEOUT_MS = 6e3;
448
498
  var PROBE_TTL_MS = 2e4;
449
499
  var COORD_LIMIT = 2e4;
450
500
  var MIN_GESTURE_MS = 20;
@@ -912,14 +962,14 @@ var Hands = class {
912
962
  const px = coord(x);
913
963
  const py = coord(y);
914
964
  await this.bounds(px, py);
915
- const ms = milliseconds(opts.ms, 60);
965
+ const ms = milliseconds(opts.ms, 45);
916
966
  return this.perform({ kind: "tap", x: px, y: py, ms });
917
967
  }
918
968
  async longPress(x, y, opts = {}) {
919
969
  const px = coord(x);
920
970
  const py = coord(y);
921
971
  await this.bounds(px, py);
922
- const ms = milliseconds(opts.ms, 800);
972
+ const ms = milliseconds(opts.ms, 600);
923
973
  return this.perform({ kind: "tap", x: px, y: py, ms });
924
974
  }
925
975
  async swipe(x1, y1, x2, y2, opts = {}) {
@@ -929,13 +979,13 @@ var Hands = class {
929
979
  const d = coord(y2);
930
980
  await this.bounds(a, b);
931
981
  await this.bounds(c, d);
932
- const ms = milliseconds(opts.ms, 300);
982
+ const ms = milliseconds(opts.ms, 200);
933
983
  return this.perform({ kind: "swipe", x1: a, y1: b, x2: c, y2: d, ms });
934
984
  }
935
985
  async pinch(cx, cy, scale, opts = {}) {
936
986
  const x = coord(cx);
937
987
  const y = coord(cy);
938
- const ms = milliseconds(opts.ms, 400);
988
+ const ms = milliseconds(opts.ms, 300);
939
989
  const factor = Math.max(0.25, Math.min(4, Number(scale) || 1));
940
990
  return this.perform({ kind: "pinch", cx: x, cy: y, scale: factor, ms });
941
991
  }
@@ -946,7 +996,7 @@ var Hands = class {
946
996
  const d = coord(y2);
947
997
  await this.bounds(a, b);
948
998
  await this.bounds(c, d);
949
- const ms = milliseconds(opts.ms, 900);
999
+ const ms = milliseconds(opts.ms, 600);
950
1000
  return this.perform({ kind: "drag", x1: a, y1: b, x2: c, y2: d, ms });
951
1001
  }
952
1002
  /** 口令通过后解锁(并可打开)懿心的受保护页面。 */
@@ -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.0",
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",