cortico-world-hands 0.2.1 → 0.3.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 +206 -18
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -215,6 +215,134 @@ var HANDS_LOOK_CONFIG_GROUP = {
215
215
  // src/world.ts
216
216
  import { fileURLToPath } from "node:url";
217
217
 
218
+ // src/policy.ts
219
+ var PASS_RULES = [
220
+ ["\u504F\u597D", /(喜欢|爱吃|爱喝|偏好|口味|不喜欢|讨厌|习惯|只喝|只吃|过敏)/],
221
+ ["\u7EA6\u5B9A", /(约定|约好|说好|规矩|不许|别再|不要再|以后都|每次都|提醒我|叫我)/],
222
+ ["\u91CD\u8981\u65E5\u5B50", /(生日|纪念日|周年|节日|那天|周[一二三四五六日天]|每月|每年)/],
223
+ ["\u6B63\u5728\u505A\u7684\u4E8B", /(正在做|在做|最近在|这几天在|手上在做|进度|项目|在弄|在做的东西)/],
224
+ ["\u7B54\u5E94\u8FC7\u7684\u4E8B", /(答应|承诺|保证|说好了|我会|我给你|给你做|欠你)/],
225
+ ["\u8EAB\u4EFD\u8BBE\u5B9A", /(我是谁|人格|自称|称呼|名字|叫我|身份|人设|性格|口癖|说话的样子)/]
226
+ ];
227
+ var DROP_RULES = [
228
+ ["\u64CD\u4F5C\u56DE\u6267", /(已安装|安装成功|已重启|重启完成|已卸载|装好|loaded=|exit code|退出码|回执|已同步|同步失败)/i],
229
+ ["\u6280\u672F\u7EC6\u8282", /(端口|pnpm|lockfile|ERR_[A-Z_]+|版本号|v?\d+\.\d+\.\d+|API key|token|\/root\/|\/assets\/|\/sdcard\/)/i],
230
+ ["\u5750\u6807\u4E0E\u5206\u8FA8\u7387", /(\d{3,4}\s*[×x]\s*\d{3,4}|坐标|分辨率|截图渲染|×2\.15)/],
231
+ ["\u4E00\u6B21\u6027\u4E8B\u4EF6", /(刚才|刚刚|今天|昨天|还剩|第\s*\d+\s*次|已经提醒|提醒过|配额)/],
232
+ ["\u8FC7\u7A0B\u53D9\u8FF0", /(试了|计划要|打算把|当时想|中间方案|做法是|先改成|原来是)/],
233
+ ["\u754C\u9762\u63CF\u8FF0", /(状态栏|面板|按钮|页签|界面写着|显示着)/]
234
+ ];
235
+ var PROMISE_OVERRIDE = /(答应|约好|说好|承诺|周五|周六|周日|下周|明天|生日|纪念日)/;
236
+ function hits(rules, text) {
237
+ const out = [];
238
+ for (const [name, re] of rules) {
239
+ const m = text.match(re);
240
+ if (m) out.push([name, m[0]]);
241
+ }
242
+ return out;
243
+ }
244
+ function classify(key, value) {
245
+ const text = `${key} ${value}`;
246
+ if (PROMISE_OVERRIDE.test(text)) {
247
+ return { verdict: "pass", category: "\u7B54\u5E94\u8FC7\u7684\u4E8B", reason: `\u547D\u4E2D\u627F\u8BFA\u53E5\u5F0F\u300C${text.match(PROMISE_OVERRIDE)[0]}\u300D` };
248
+ }
249
+ const pass = hits(PASS_RULES, text);
250
+ const drop = hits(DROP_RULES, text);
251
+ if (drop.length > 0 && pass.length === 0) {
252
+ return { verdict: "drop", category: drop[0][0], reason: `\u6D41\u6C34\u7279\u5F81\u300C${drop[0][1]}\u300D` };
253
+ }
254
+ if (drop.length > 0 && pass.length > 0) {
255
+ return { verdict: "buffer", category: pass[0][0], reason: `\u540C\u65F6\u50CF\u4E8B\u5B9E\u4E0E\u6D41\u6C34\uFF08${pass[0][1]} / ${drop[0][1]}\uFF09` };
256
+ }
257
+ if (pass.length > 0) {
258
+ return { verdict: "pass", category: pass[0][0], reason: `\u547D\u4E2D\u300C${pass[0][1]}\u300D` };
259
+ }
260
+ return { verdict: "buffer", category: "\u672A\u5206\u7C7B", reason: "\u6CA1\u6709\u547D\u4E2D\u653E\u884C\u6216\u4E22\u5F03\u89C4\u5219" };
261
+ }
262
+ var MemoryDigest = class {
263
+ items = [];
264
+ cap;
265
+ constructor(cap = 200) {
266
+ this.cap = cap;
267
+ }
268
+ push(key, value, d) {
269
+ this.items.push({ key, value, category: d.category, reason: d.reason, at: Date.now() });
270
+ while (this.items.length > this.cap) this.items.shift();
271
+ return this.items.length;
272
+ }
273
+ size() {
274
+ return this.items.length;
275
+ }
276
+ /** 攒够时间就把同一类的拼成一句,返回一条摘要;没有可传的就返回 null。 */
277
+ buildDigest(now = Date.now()) {
278
+ if (this.items.length === 0) return null;
279
+ const byCat = /* @__PURE__ */ new Map();
280
+ for (const it of this.items) {
281
+ const list = byCat.get(it.category) ?? [];
282
+ list.push(it);
283
+ byCat.set(it.category, list);
284
+ }
285
+ const parts = [];
286
+ for (const [cat, list] of byCat) {
287
+ const uniq = dedupe(list.map((x) => `${x.key}\uFF1A${x.value}`));
288
+ parts.push(`\u3010${cat}\u3011${uniq.slice(0, 6).join("\uFF1B")}`);
289
+ }
290
+ const day = new Date(now).toISOString().slice(0, 10);
291
+ return { key: `\u8FD1\u51B5\u6458\u8981 ${day}`, sentence: parts.join(" ") };
292
+ }
293
+ drain() {
294
+ const out = this.items;
295
+ this.items = [];
296
+ return out;
297
+ }
298
+ };
299
+ function dedupe(lines) {
300
+ const seen = /* @__PURE__ */ new Set();
301
+ const out = [];
302
+ for (const line of lines) {
303
+ const k = line.replace(/\s+/g, "");
304
+ if (seen.has(k)) continue;
305
+ seen.add(k);
306
+ out.push(line);
307
+ }
308
+ return out;
309
+ }
310
+ function audit(rows, opts = {}) {
311
+ const keepDays = opts.keepDays ?? 30;
312
+ const now = Date.now();
313
+ const seen = /* @__PURE__ */ new Map();
314
+ const out = [];
315
+ for (const row of rows) {
316
+ const content = (row.content || "").trim();
317
+ if (!content) {
318
+ out.push({ id: row.id, verdict: "delete", why: "\u7A7A\u5185\u5BB9" });
319
+ continue;
320
+ }
321
+ if (/(紫月月|口令|暗号|深夜模式|猫耳|外搭)/.test(content)) {
322
+ out.push({ id: row.id, verdict: "delete", why: "\u6D89\u53CA\u5DF2\u64A4\u9500\u7684\u529F\u80FD" });
323
+ continue;
324
+ }
325
+ if (/(已安装|已重启|loaded=|退出码|端口|pnpm|lockfile)/i.test(content)) {
326
+ out.push({ id: row.id, verdict: "delete", why: "\u64CD\u4F5C\u56DE\u6267/\u6280\u672F\u6D41\u6C34" });
327
+ continue;
328
+ }
329
+ const norm = content.replace(/[\s,。,.!?!?…::;;、「」“”()()【】]/g, "").slice(0, 30);
330
+ const prev = seen.get(norm);
331
+ if (prev) {
332
+ out.push({ id: row.id, verdict: "merge", why: `\u4E0E\u524D\u4E00\u6761\u91CD\u590D\uFF08${prev}\uFF09` });
333
+ continue;
334
+ }
335
+ seen.set(norm, row.id);
336
+ const created = row.created_at ? Date.parse(row.created_at) : Number.NaN;
337
+ if (Number.isFinite(created) && now - created > keepDays * 864e5 && /(今天|昨天|刚才|还剩)/.test(content)) {
338
+ out.push({ id: row.id, verdict: "delete", why: `\u8D85\u8FC7 ${keepDays} \u5929\u7684\u4E00\u6B21\u6027\u63CF\u8FF0` });
339
+ continue;
340
+ }
341
+ out.push({ id: row.id, verdict: "keep", why: "\u50CF\u957F\u671F\u4E8B\u5B9E" });
342
+ }
343
+ return out;
344
+ }
345
+
218
346
  // src/honcho.ts
219
347
  var TIMEOUT_MS = 12e3;
220
348
  var Honcho = class {
@@ -283,20 +411,20 @@ var Honcho = class {
283
411
  query,
284
412
  limit
285
413
  });
286
- const hits = [];
414
+ const hits2 = [];
287
415
  const push = (item, source) => {
288
416
  if (typeof item === "string") {
289
- hits.push({ text: item, source });
417
+ hits2.push({ text: item, source });
290
418
  return;
291
419
  }
292
420
  if (!item || typeof item !== "object") return;
293
421
  const row = item;
294
422
  const text = [row.content, row.text, row.conclusion, row.observation].find((v) => typeof v === "string" && v.trim().length > 0);
295
- if (text) hits.push({ text: text.trim(), source: String(row.session_id || row.type || source) });
423
+ if (text) hits2.push({ text: text.trim(), source: String(row.session_id || row.type || source) });
296
424
  };
297
425
  const list = Array.isArray(raw) ? raw : raw.results || raw.messages || raw.conclusions || [];
298
426
  for (const item of list) push(item, "\u7ED3\u8BBA");
299
- return hits.slice(0, limit);
427
+ return hits2.slice(0, limit);
300
428
  }
301
429
  /** 让 Honcho 用自己的推理回答一个关于主人的问题(慢,但更“懂”)。 */
302
430
  async ask(question) {
@@ -664,14 +792,14 @@ var Hands = class {
664
792
  if (!query) return [];
665
793
  const ui = await this.eyes.ui();
666
794
  if (!ui.ok) throw new HandError(ui.error ?? "\u8BFB\u4E0D\u5230\u5C4F\u5E55\u4E0A\u7684\u5B57\u3002");
667
- const hits = [];
795
+ const hits2 = [];
668
796
  for (const item of ui.items) {
669
797
  if (!item.center) continue;
670
798
  if (!item.text.toLowerCase().includes(query)) continue;
671
- hits.push({ text: item.text, center: item.center });
672
- if (hits.length >= 20) break;
799
+ hits2.push({ text: item.text, center: item.center });
800
+ if (hits2.length >= 20) break;
673
801
  }
674
- return hits;
802
+ return hits2;
675
803
  }
676
804
  /** 手势令牌:文件里有就用它;读不到就是空(服务那边也不校验)。 */
677
805
  tokenCache = { value: "", at: 0 };
@@ -1175,6 +1303,17 @@ var HANDS_TOOL_DECLS = [
1175
1303
  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",
1176
1304
  parameters: { type: "object", properties: {}, required: [] }
1177
1305
  },
1306
+ {
1307
+ name: "memory_audit",
1308
+ tags: ["read"],
1309
+ description: "\u8BB0\u5FC6\u4F53\u68C0\uFF08\u53EA\u8BFB\uFF09\uFF1A\u628A\u4E91\u7AEF\u957F\u671F\u8BB0\u5FC6\u626B\u4E00\u904D\uFF0C\u5217\u51FA\u91CD\u590D/\u8FC7\u671F/\u50CF\u6D41\u6C34\u7684\u6761\u76EE\uFF0C\u53EA\u7ED9\u6E05\u5355\uFF0C\u4E0D\u5220\u4EFB\u4F55\u4E1C\u897F\u3002",
1310
+ parameters: {
1311
+ type: "object",
1312
+ properties: {
1313
+ cloud: { type: "boolean", description: "true \u8868\u793A\u8FDE\u4E91\u7AEF\u4E00\u8D77\u67E5\uFF08\u9700\u8981 Honcho \u5DF2\u914D\u7F6E\uFF09\u3002" }
1314
+ }
1315
+ }
1316
+ },
1178
1317
  {
1179
1318
  name: "unlock_pages",
1180
1319
  tags: ["act"],
@@ -1356,6 +1495,8 @@ var HandsWorld = class {
1356
1495
  return await this.unlockPages(args);
1357
1496
  case "memory_save":
1358
1497
  return await this.memorySave(args);
1498
+ case "memory_audit":
1499
+ return await this.memoryAudit(args);
1359
1500
  case "memory_search":
1360
1501
  return await this.memorySearch(args);
1361
1502
  case "memory_forget":
@@ -1416,13 +1557,44 @@ var HandsWorld = class {
1416
1557
  const r = await this.hands.unlockPages(page, Math.max(1, Math.min(60, minutes)));
1417
1558
  return r.ok ? `\u6253\u5F00\u4E86\uFF08${page}\uFF09\uFF0C\u4FDD\u6301 ${minutes} \u5206\u949F\u3002` : failed(`\u6CA1\u6253\u5F00\uFF1A${r.detail || "\u624B\u6CA1\u54CD\u5E94"}`);
1418
1559
  }
1560
+ digest = new MemoryDigest();
1561
+ digestTimer = null;
1562
+ /** 第二道闸:暂存区每 10 分钟聚合成一条摘要上传,回执类直接丢。 */
1563
+ startDigestLoop() {
1564
+ if (this.digestTimer) return;
1565
+ this.digestTimer = setInterval(() => {
1566
+ void this.flushDigest();
1567
+ }, 10 * 60 * 1e3);
1568
+ }
1569
+ async flushDigest() {
1570
+ const honcho = this.honcho();
1571
+ const built = this.digest.buildDigest();
1572
+ if (!built) return "";
1573
+ this.digest.drain();
1574
+ if (!honcho.configured) return "";
1575
+ try {
1576
+ await honcho.remember(built.sentence);
1577
+ return built.key;
1578
+ } catch {
1579
+ return "";
1580
+ }
1581
+ }
1419
1582
  async memorySave(args) {
1420
1583
  const key = typeof args.key === "string" ? args.key.trim() : "";
1421
1584
  const value = typeof args.value === "string" ? args.value.trim() : "";
1422
1585
  if (!key || !value) return failed("\u8981 key \u548C value\u3002");
1423
1586
  const tags = typeof args.tags === "string" ? args.tags : void 0;
1587
+ const decision = classify(key, value);
1424
1588
  const r = await this.hands.memorySave(key, value, tags);
1425
1589
  if (!r.ok) return failed(`\u6CA1\u8BB0\u4F4F\uFF1A${r.detail || "\u624B\u6CA1\u54CD\u5E94"}`);
1590
+ this.startDigestLoop();
1591
+ if (decision.verdict === "drop") {
1592
+ return `\u672C\u5730\u8BB0\u4E0B\u4E86\u300C${key}\u300D\uFF0C\u4F46\u8FD9\u6761\u50CF${decision.category}\uFF08${decision.reason}\uFF09\uFF0C\u4E0D\u4E0A\u4E91\u3002`;
1593
+ }
1594
+ if (decision.verdict === "buffer") {
1595
+ const n = this.digest.push(key, value, decision);
1596
+ return `\u672C\u5730\u8BB0\u4E0B\u4E86\u300C${key}\u300D\uFF0C\u5148\u653E\u6682\u5B58\u533A\uFF08${decision.reason}\uFF0C\u5DF2\u5B58 ${n} \u6761\uFF09\uFF0C10 \u5206\u949F\u805A\u5408\u6210\u4E00\u6761\u518D\u4E0A\u4F20\u3002`;
1597
+ }
1426
1598
  let cloud = "";
1427
1599
  const honcho = this.honcho();
1428
1600
  if (honcho.configured) {
@@ -1435,6 +1607,22 @@ var HandsWorld = class {
1435
1607
  }
1436
1608
  return `\u8BB0\u7262\u4E86\uFF1A\u300C${key}\u300D\u2192 ${value}${cloud}`;
1437
1609
  }
1610
+ /** 第三道闸:只读体检,出清单不删。 */
1611
+ async memoryAudit(args) {
1612
+ const rows = await this.hands.memorySearch();
1613
+ const local = rows.map((r) => ({ id: r.key, content: `${r.key}\uFF1A${r.value}`, created_at: r.ts }));
1614
+ const findings = audit(local.map((r) => ({ id: r.id, content: r.content, created_at: r.created_at })));
1615
+ const del = findings.filter((f) => f.verdict === "delete");
1616
+ const merge = findings.filter((f) => f.verdict === "merge");
1617
+ const lines = [
1618
+ `\u672C\u5730\u8BB0\u5FC6 ${findings.length} \u6761\uFF1A\u5EFA\u8BAE\u5220 ${del.length}\uFF0C\u5EFA\u8BAE\u5408\u5E76 ${merge.length}\uFF0C\u5176\u4F59\u4FDD\u7559\u3002`,
1619
+ ...del.slice(0, 20).map((f) => `\u5220 ${f.id}\uFF08${f.why}\uFF09`),
1620
+ ...merge.slice(0, 20).map((f) => `\u5E76 ${f.id}\uFF08${f.why}\uFF09`),
1621
+ "\u4E91\u7AEF\u90A3\u4EFD\u8981\u5355\u72EC\u8DD1\uFF08\u8FD9\u91CC\u53EA\u8BFB\u672C\u5730\uFF09\u3002"
1622
+ ];
1623
+ if (args.cloud === true) lines.push("\uFF08\u4E91\u7AEF\u4F53\u68C0\u6211\u8FD9\u8FB9\u8FD8\u6CA1\u63A5\uFF0C\u5148\u7ED9\u672C\u5730\u7684\u3002\uFF09");
1624
+ return lines.join("\n");
1625
+ }
1438
1626
  async memorySearch(args) {
1439
1627
  const query = typeof args.query === "string" ? args.query.trim() : "";
1440
1628
  const rows = await this.hands.memorySearch(query);
@@ -1444,8 +1632,8 @@ var HandsWorld = class {
1444
1632
  const honcho = this.honcho();
1445
1633
  if (honcho.configured && query) {
1446
1634
  try {
1447
- const hits = await honcho.search(query);
1448
- cloudHits = hits.map((h) => `- ${h.text}`);
1635
+ const hits2 = await honcho.search(query);
1636
+ cloudHits = hits2.map((h) => `- ${h.text}`);
1449
1637
  if (cloudHits.length > 0) cloudNote = `
1450
1638
 
1451
1639
  Honcho \u8865\u5145 ${cloudHits.length} \u6761\uFF1A`;
@@ -1535,22 +1723,22 @@ ${lines || "\uFF08\u90FD\u5212\u6389\u4E86\uFF09"}`;
1535
1723
  async tap(args) {
1536
1724
  const text = typeof args.text === "string" ? args.text.trim() : "";
1537
1725
  if (text) {
1538
- const hits = await this.hands.findText(text);
1539
- if (hits.length === 0) return failed(`\u5C4F\u5E55\u4E0A\u6CA1\u627E\u5230\u300C${text}\u300D\uFF0C\u5148 hand_look \u770B\u770B\u73B0\u5728\u662F\u4EC0\u4E48\u753B\u9762\u3002`);
1726
+ const hits2 = await this.hands.findText(text);
1727
+ if (hits2.length === 0) return failed(`\u5C4F\u5E55\u4E0A\u6CA1\u627E\u5230\u300C${text}\u300D\uFF0C\u5148 hand_look \u770B\u770B\u73B0\u5728\u662F\u4EC0\u4E48\u753B\u9762\u3002`);
1540
1728
  const nth = args.nth === void 0 ? void 0 : Math.max(0, Math.round(Number(args.nth)));
1541
1729
  if (nth === void 0) {
1542
- if (hits.length > 1) {
1543
- const list = hits.slice(0, 8).map((h, i) => `${i}. \u300C${h.text}\u300D@(${h.center.x},${h.center.y})`).join("\n");
1544
- return failed(`\u300C${text}\u300D\u5728\u5C4F\u5E55\u4E0A\u6709 ${hits.length} \u5904\uFF0C\u8BF4\u6E05\u695A\u70B9\u54EA\u4E2A\uFF08nth\uFF09\uFF1A
1730
+ if (hits2.length > 1) {
1731
+ const list = hits2.slice(0, 8).map((h, i) => `${i}. \u300C${h.text}\u300D@(${h.center.x},${h.center.y})`).join("\n");
1732
+ return failed(`\u300C${text}\u300D\u5728\u5C4F\u5E55\u4E0A\u6709 ${hits2.length} \u5904\uFF0C\u8BF4\u6E05\u695A\u70B9\u54EA\u4E2A\uFF08nth\uFF09\uFF1A
1545
1733
  ${list}`);
1546
1734
  }
1547
- const only = hits[0];
1735
+ const only = hits2[0];
1548
1736
  const receipt3 = await this.hands.tap(only.center.x, only.center.y);
1549
1737
  this.note(receipt3);
1550
1738
  return `${receipt3.text}\uFF08\u70B9\u7684\u662F\u300C${only.text}\u300D\uFF09`;
1551
1739
  }
1552
- const hit = hits[nth];
1553
- if (!hit) return failed(`\u300C${text}\u300D\u53EA\u6709 ${hits.length} \u5904\uFF0C\u6CA1\u6709\u7B2C ${nth} \u4E2A\u3002`);
1740
+ const hit = hits2[nth];
1741
+ if (!hit) return failed(`\u300C${text}\u300D\u53EA\u6709 ${hits2.length} \u5904\uFF0C\u6CA1\u6709\u7B2C ${nth} \u4E2A\u3002`);
1554
1742
  const receipt2 = await this.hands.tap(hit.center.x, hit.center.y);
1555
1743
  this.note(receipt2);
1556
1744
  return `${receipt2.text}\uFF08\u70B9\u7684\u662F\u300C${hit.text}\u300D\uFF09`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortico-world-hands",
3
- "version": "0.2.1",
3
+ "version": "0.3.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",