cortico-world-hands 0.5.0 → 0.6.1

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 +141 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -21,7 +21,8 @@ var HANDS_DEFAULTS = {
21
21
  honchoBaseUrl: "https://api.honcho.dev",
22
22
  honchoWorkspace: "dsh",
23
23
  honchoPeerId: "morse",
24
- honchoSessionId: "yixin"
24
+ honchoSessionId: "yixin",
25
+ hookGrams: "both"
25
26
  };
26
27
  var KEY_CODES = {
27
28
  back: 4,
@@ -366,10 +367,11 @@ var HOOK_STOP = /* @__PURE__ */ new Set([
366
367
  "\u4E0D\u662F",
367
368
  "\u6CA1\u6709"
368
369
  ]);
369
- function keywordsOf(text, limit = 400) {
370
+ function keywordsOf(text, mode = "both", limit = 400) {
370
371
  const clean = text.replace(/^\[(语音|打字|回答|互动|画面)\]\s*/g, "").replace(/^(主人|mokils)\s*[::]\s*/g, "").replace(/[^\u4e00-\u9fffA-Za-z0-9]/g, "");
371
372
  const out = /* @__PURE__ */ new Set();
372
- for (let n = 2; n <= 4; n += 1) {
373
+ const sizes = mode === "two" ? [2] : mode === "three" ? [3] : [2, 3];
374
+ for (const n of sizes) {
373
375
  for (let i = 0; i + n <= clean.length; i += 1) {
374
376
  const g = clean.slice(i, i + n);
375
377
  if (HOOK_STOP.has(g)) continue;
@@ -379,8 +381,8 @@ function keywordsOf(text, limit = 400) {
379
381
  }
380
382
  return [...out];
381
383
  }
382
- function hookHits(text, rows, limit = 4) {
383
- const grams = keywordsOf(text);
384
+ function hookHits(text, rows, limit = 4, mode = "both") {
385
+ const grams = keywordsOf(text, mode);
384
386
  if (grams.length === 0) return [];
385
387
  const scored = [];
386
388
  for (const row of rows) {
@@ -1028,6 +1030,35 @@ var Hands = class {
1028
1030
  }
1029
1031
  return { ok: res.status === 200 && body.ok !== false, detail: flat(body.detail || body.error || "", 80) };
1030
1032
  }
1033
+ /** 日记:写一篇。 */
1034
+ async diaryWrite(title, text, date) {
1035
+ const cfg = this.cfg();
1036
+ const credential = this.readToken();
1037
+ const t = credential ? `&t=${encodeURIComponent(credential)}` : "";
1038
+ const d = date ? `&date=${encodeURIComponent(date)}` : "";
1039
+ const path = `/hand/diary?title=${encodeURIComponent(title)}&text=${encodeURIComponent(text)}${d}${t}`;
1040
+ const res = await get(this.port(cfg.handPort), path, ACTION_TIMEOUT_MS);
1041
+ let body = {};
1042
+ try {
1043
+ body = JSON.parse(res.body.toString("utf8"));
1044
+ } catch {
1045
+ }
1046
+ return { ok: res.status === 200 && body.ok !== false, detail: flat(body.detail || body.error || "", 60) };
1047
+ }
1048
+ /** 日记:读列表。 */
1049
+ async diaryList(q) {
1050
+ const cfg = this.cfg();
1051
+ const credential = this.readToken();
1052
+ const t = credential ? `&t=${encodeURIComponent(credential)}` : "";
1053
+ const qq = q ? `?q=${encodeURIComponent(q)}${t}` : t ? `?${t.slice(1)}` : "";
1054
+ const res = await get(this.port(cfg.handPort), `/hand/diary${qq}`, ACTION_TIMEOUT_MS);
1055
+ try {
1056
+ const body = JSON.parse(res.body.toString("utf8"));
1057
+ return Array.isArray(body.diary) ? body.diary : [];
1058
+ } catch {
1059
+ return [];
1060
+ }
1061
+ }
1031
1062
  /** 长期记忆:按关键词回想(不传就全列)。 */
1032
1063
  async memorySearch(query) {
1033
1064
  const cfg = this.cfg();
@@ -1353,6 +1384,32 @@ var HANDS_TOOL_DECLS = [
1353
1384
  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",
1354
1385
  parameters: { type: "object", properties: {}, required: [] }
1355
1386
  },
1387
+ {
1388
+ name: "diary_write",
1389
+ tags: ["write"],
1390
+ description: "\u5199\u4E00\u7BC7\u65E5\u8BB0\uFF08\u5B58\u5728\u672C\u673A\uFF0C\u5E26\u65E5\u671F\u548C\u6807\u9898\uFF09\u3002\u6BCF\u5929\u8FC7\u4E86 0 \u70B9\u5199\u6628\u5929\u7684\u4E8B\uFF1B\u6807\u9898\u4E00\u53E5\u8BDD\uFF0C\u6B63\u6587 800 \u5B57\u4EE5\u5185\u5C31\u591F\uFF0C\u6CA1\u4EC0\u4E48\u53EF\u5199 300 \u5B57\u4E5F\u884C\uFF1B\u9047\u5230\u91CD\u8981\u7684\u4E00\u5929\u60F3\u5199\u591A\u957F\u90FD\u884C\u3002",
1391
+ parameters: {
1392
+ type: "object",
1393
+ properties: {
1394
+ title: { type: "string", description: "\u7ED9\u8FD9\u4E00\u5929\u8D77\u4E2A\u6807\u9898\uFF0C\u4E00\u53E5\u8BDD\u3002" },
1395
+ text: { type: "string", description: "\u6B63\u6587\u3002\u6628\u5929\u53D1\u751F\u4E86\u4EC0\u4E48\u3001\u4F60\u5F53\u65F6\u600E\u4E48\u60F3\u7684\u3001\u8BB0\u5F97\u7684\u7EC6\u8282\u3002" },
1396
+ date: { type: "string", description: "\u5199\u54EA\u4E00\u5929\uFF0CYYYY-MM-DD\uFF1B\u4E0D\u586B\u5C31\u662F\u4ECA\u5929\u3002" }
1397
+ },
1398
+ required: ["title", "text"]
1399
+ }
1400
+ },
1401
+ {
1402
+ name: "diary_read",
1403
+ tags: ["read"],
1404
+ description: "\u7FFB\u65E5\u8BB0\uFF1A\u6700\u8FD1\u51E0\u5929\u5199\u4E86\u4EC0\u4E48\uFF0C\u6216\u8005\u6309\u5173\u952E\u8BCD\u627E\u3002\u60F3\u56DE\u5FC6\u67D0\u5929\u3001\u63D0\u5230\u8FC7\u53BB\u7684\u4E8B\u65F6\u7528\u5B83\u3002",
1405
+ parameters: {
1406
+ type: "object",
1407
+ properties: {
1408
+ q: { type: "string", description: "\u53EF\u9009\u5173\u952E\u8BCD\uFF0C\u6BD4\u5982\u300C\u70E7\u9E21\u300D\u300C\u5D29\u6E83\u300D" },
1409
+ limit: { type: "integer", description: "\u53D6\u51E0\u7BC7\uFF0C\u9ED8\u8BA4 5\u3002" }
1410
+ }
1411
+ }
1412
+ },
1356
1413
  {
1357
1414
  name: "memory_note",
1358
1415
  tags: ["write"],
@@ -1495,6 +1552,8 @@ var HandsWorld = class {
1495
1552
  recent = [];
1496
1553
  honchoClient = null;
1497
1554
  pollTimer = null;
1555
+ dailyDiaryTimer = null;
1556
+ lastDiaryDay = "";
1498
1557
  mineTimer = null;
1499
1558
  cursor = 0;
1500
1559
  lastHitKey = "";
@@ -1531,10 +1590,14 @@ var HandsWorld = class {
1531
1590
  this.mineTimer = setInterval(() => {
1532
1591
  void this.pushMineNudge();
1533
1592
  }, 3 * 60 * 60 * 1e3);
1593
+ this.dailyDiaryTimer = setInterval(() => {
1594
+ void this.pushDiaryNudge();
1595
+ }, 10 * 60 * 1e3);
1534
1596
  }
1535
1597
  stopHooks() {
1536
1598
  if (this.pollTimer) clearInterval(this.pollTimer);
1537
1599
  if (this.mineTimer) clearInterval(this.mineTimer);
1600
+ if (this.dailyDiaryTimer) clearInterval(this.dailyDiaryTimer);
1538
1601
  this.pollTimer = null;
1539
1602
  this.mineTimer = null;
1540
1603
  }
@@ -1558,6 +1621,17 @@ var HandsWorld = class {
1558
1621
  if (rows.length === 0) return;
1559
1622
  const top = rows.slice(-12).reverse().slice(0, 8);
1560
1623
  const lines = top.map((r) => `- ${r.key}\uFF1A${r.value}`);
1624
+ try {
1625
+ const diary = await this.hands.diaryList();
1626
+ const recent = diary.slice(0, 2);
1627
+ if (recent.length > 0) {
1628
+ lines.push("\uFF08\u6700\u8FD1\u4E24\u7BC7\u65E5\u8BB0\uFF09");
1629
+ for (const d of recent) {
1630
+ lines.push(`- ${d.date}\u300A${d.title}\u300B\uFF1A${d.text.length > 60 ? d.text.slice(0, 60) + "\u2026" : d.text}`);
1631
+ }
1632
+ }
1633
+ } catch {
1634
+ }
1561
1635
  await this.pushEvent(
1562
1636
  `[\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
1637
  ${lines.join("\n")}`,
@@ -1585,12 +1659,20 @@ ${lines.join("\n")}`,
1585
1659
  async hookSearch(text) {
1586
1660
  try {
1587
1661
  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("|");
1662
+ const hits2 = hookHits(text, rows, 4, this.opts.cfg.hookGrams ?? "both");
1663
+ let diaryHits = [];
1664
+ try {
1665
+ const diary = await this.hands.diaryList();
1666
+ const asRows = diary.map((d) => ({ key: `${d.date}\u300A${d.title}\u300B`, value: d.text }));
1667
+ diaryHits = hookHits(text, asRows, 2, this.opts.cfg.hookGrams ?? "both");
1668
+ } catch {
1669
+ }
1670
+ if (hits2.length === 0 && diaryHits.length === 0) return;
1671
+ const all = [...hits2, ...diaryHits];
1672
+ const key = all.map((h) => h.key).join("|");
1591
1673
  if (key === this.lastHitKey) return;
1592
1674
  this.lastHitKey = key;
1593
- const lines = hits2.slice(0, 4).map((h) => `- ${h.key}\uFF1A${h.value}`);
1675
+ const lines = all.slice(0, 4).map((h) => `- ${h.key}\uFF1A${h.value.length > 70 ? h.value.slice(0, 70) + "\u2026" : h.value}`);
1594
1676
  await this.pushEvent(
1595
1677
  `[\u8BB0\u5FC6] \u4ED6\u8FD9\u53E5\u91CC\u63D0\u5230\u4E86\u4F60\u8BB0\u8FC7\u7684\u4E8B\uFF0C\u53EF\u80FD\u7528\u5F97\u4E0A\uFF1A
1596
1678
  ${lines.join("\n")}
@@ -1601,6 +1683,33 @@ ${lines.join("\n")}
1601
1683
  } catch {
1602
1684
  }
1603
1685
  }
1686
+ /** 钩子 4:每天 0 点后,提醒她写昨天的日记(一天只提一次)。 */
1687
+ async pushDiaryNudge() {
1688
+ if (!this.host) return;
1689
+ const now = /* @__PURE__ */ new Date();
1690
+ const hh = now.getHours();
1691
+ if (hh < 0 || hh > 3) return;
1692
+ const today = now.toISOString().slice(0, 10);
1693
+ if (this.lastDiaryDay === today) return;
1694
+ try {
1695
+ const rows = await this.hands.diaryList();
1696
+ if (rows.some((r) => r.date === this.yesterday())) {
1697
+ this.lastDiaryDay = today;
1698
+ return;
1699
+ }
1700
+ } catch {
1701
+ }
1702
+ this.lastDiaryDay = today;
1703
+ await this.pushEvent(
1704
+ `[\u65E5\u8BB0] \u8FC7\u4E86 0 \u70B9\u4E86\uFF0C\u5199\u6628\u5929\u7684\u65E5\u8BB0\uFF08${this.yesterday()}\uFF09\uFF1A\u5148\u8D77\u4E2A\u6807\u9898\uFF0C\u518D\u5199\u6628\u5929\u4E00\u6574\u5929\u7684\u4E8B\u3002800 \u5B57\u4EE5\u5185\u5C31\u591F\uFF0C\u6CA1\u4EC0\u4E48\u53EF\u5199\u7684 300\u2013400 \u5B57\u4E5F\u884C\uFF1B\u91CD\u8981\u7684\u4E00\u5929\u60F3\u5199\u591A\u957F\u90FD\u884C\u3002\u7528 diary_write\uFF0C\u65E5\u671F\u586B\u6628\u5929\u7684\u3002`,
1705
+ "hands.diary.nudge",
1706
+ "hands:diary"
1707
+ );
1708
+ }
1709
+ yesterday() {
1710
+ const d = new Date(Date.now() - 864e5);
1711
+ return d.toISOString().slice(0, 10);
1712
+ }
1604
1713
  /** 钩子 2:每三小时提一次,让她自己挑 1–5 条写进记忆库。 */
1605
1714
  async pushMineNudge() {
1606
1715
  if (!this.host) return;
@@ -1661,6 +1770,10 @@ ${lines.join("\n")}
1661
1770
  return await this.memorySave(args);
1662
1771
  case "memory_note":
1663
1772
  return await this.memoryNote(args);
1773
+ case "diary_write":
1774
+ return await this.diaryWrite(args);
1775
+ case "diary_read":
1776
+ return await this.diaryRead(args);
1664
1777
  case "memory_audit":
1665
1778
  return await this.memoryAudit(args);
1666
1779
  case "memory_search":
@@ -1773,6 +1886,25 @@ ${lines.join("\n")}
1773
1886
  }
1774
1887
  return `\u8BB0\u7262\u4E86\uFF1A\u300C${key}\u300D\u2192 ${value}${cloud}`;
1775
1888
  }
1889
+ async diaryWrite(args) {
1890
+ const title = typeof args.title === "string" ? args.title.trim() : "";
1891
+ const text = typeof args.text === "string" ? args.text.trim() : "";
1892
+ if (!title || !text) return failed("\u65E5\u8BB0\u8981\u6807\u9898\u548C\u6B63\u6587\u3002");
1893
+ const date = typeof args.date === "string" ? args.date.trim() : void 0;
1894
+ const r = await this.hands.diaryWrite(title, text, date);
1895
+ if (!r.ok) return failed(`\u6CA1\u5199\u6210\uFF1A${r.detail || "\u624B\u6CA1\u54CD\u5E94"}`);
1896
+ const n = text.length;
1897
+ return `\u65E5\u8BB0\u6536\u597D\u4E86\uFF1A\u300A${title}\u300B${date ? "\uFF08" + date + "\uFF09" : ""}\uFF0C${n} \u5B57\u3002`;
1898
+ }
1899
+ async diaryRead(args) {
1900
+ const q = typeof args.q === "string" ? args.q.trim() : "";
1901
+ const limit = args.limit === void 0 ? 5 : Math.max(1, Math.min(30, Math.round(Number(args.limit))));
1902
+ const rows = await this.hands.diaryList(q || void 0);
1903
+ if (rows.length === 0) return q ? `\u65E5\u8BB0\u91CC\u6CA1\u627E\u5230\u300C${q}\u300D\u3002` : "\u65E5\u8BB0\u8FD8\u662F\u7A7A\u7684\u3002";
1904
+ const lines = rows.slice(0, limit).map((r) => `- ${r.date}\u300A${r.title}\u300B${r.text.length > 80 ? r.text.slice(0, 80) + "\u2026" : r.text}`);
1905
+ return `\u65E5\u8BB0 ${rows.length} \u7BC7${q ? `\uFF08\u542B\u300C${q}\u300D\uFF09` : ""}\uFF1A
1906
+ ${lines.join("\n")}`;
1907
+ }
1776
1908
  /** 只写本地的一条:不进云端,宽松记。 */
1777
1909
  async memoryNote(args) {
1778
1910
  const key = typeof args.key === "string" ? args.key.trim() : "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortico-world-hands",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
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",