cortico-world-hands 0.1.7 → 0.1.8

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 +198 -7
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -15,7 +15,13 @@ var HANDS_DEFAULTS = {
15
15
  maxTextLength: 200,
16
16
  lookAttach: true,
17
17
  lookMaxWidth: 720,
18
- lookQuality: 60
18
+ lookQuality: 60,
19
+ honchoEnabled: false,
20
+ honchoApiKey: "",
21
+ honchoBaseUrl: "https://api.honcho.dev",
22
+ honchoWorkspace: "dsh",
23
+ honchoPeerId: "\u4E3B\u4EBA",
24
+ honchoSessionId: "yixin"
19
25
  };
20
26
  var KEY_CODES = {
21
27
  back: 4,
@@ -37,6 +43,54 @@ var KEY_CODES = {
37
43
  menu: 82
38
44
  };
39
45
  var K = `worlds.${HANDS_ID}`;
46
+ var HK = `worlds.${HANDS_ID}`;
47
+ var HANDS_HONCHO_GROUP = {
48
+ id: `world:${HANDS_ID}:honcho`,
49
+ owner: `world:${HANDS_ID}`,
50
+ schema: {
51
+ type: "object",
52
+ title: "Honcho \u8BB0\u5FC6",
53
+ description: "\u628A\u684C\u5BA0\u7684\u957F\u671F\u8BB0\u5FC6\u540C\u6B65\u5230 Honcho\uFF08plastic-labs \u7684\u8BB0\u5FC6\u670D\u52A1\uFF09\uFF0C\u56DE\u60F3\u65F6\u628A\u4E91\u7AEF\u7ED3\u8BBA\u4E00\u8D77\u5E26\u56DE\u6765\u3002\u4E0D\u586B key \u5C31\u53EA\u7528\u672C\u5730 memory.jsonl\u3002",
54
+ properties: {
55
+ [`${HK}.honchoEnabled`]: {
56
+ type: "boolean",
57
+ title: "\u542F\u7528 Honcho \u8BB0\u5FC6",
58
+ description: "\u6253\u5F00\u540E memory_save \u4F1A\u540C\u65F6\u5199\u672C\u5730\u4E0E Honcho\uFF0Cmemory_search \u4F1A\u5408\u5E76\u4E91\u7AEF\u7ED3\u679C\u3002",
59
+ "x-hot": true
60
+ },
61
+ [`${HK}.honchoApiKey`]: {
62
+ type: "string",
63
+ title: "API Key",
64
+ description: "\u5728 app.honcho.dev \u7533\u8BF7\u3002\u53EA\u586B\u8FD9\u4E00\u9879\u5373\u53EF\uFF0C\u5176\u4F59\u4FDD\u6301\u9ED8\u8BA4\u3002",
65
+ "x-hot": true
66
+ },
67
+ [`${HK}.honchoBaseUrl`]: {
68
+ type: "string",
69
+ title: "\u670D\u52A1\u5730\u5740",
70
+ description: "\u9ED8\u8BA4 https://api.honcho.dev\uFF1B\u81EA\u5EFA\u5B9E\u4F8B\u586B\u81EA\u5DF1\u7684\u5730\u5740\u3002",
71
+ "x-hot": false
72
+ },
73
+ [`${HK}.honchoWorkspace`]: {
74
+ type: "string",
75
+ title: "\u5DE5\u4F5C\u533A",
76
+ description: "\u8BB0\u5FC6\u7684\u9694\u79BB\u8FB9\u754C\u3002\u60F3\u548C\u5176\u4ED6\u7AEF\uFF08claude-honcho \u7B49\uFF09\u5171\u4EAB\u8BB0\u5FC6\u5C31\u586B\u540C\u4E00\u4E2A workspace\u3002",
77
+ "x-hot": false
78
+ },
79
+ [`${HK}.honchoPeerId`]: {
80
+ type: "string",
81
+ title: "\u5173\u4E8E\u8C01",
82
+ description: "\u8BB0\u5FC6\u5F52\u5C5E\u7684 peer\uFF0C\u9ED8\u8BA4\u300C\u4E3B\u4EBA\u300D\u3002",
83
+ "x-hot": false
84
+ },
85
+ [`${HK}.honchoSessionId`]: {
86
+ type: "string",
87
+ title: "\u4F1A\u8BDD\u540D",
88
+ description: "Honcho \u91CC\u7684\u4F1A\u8BDD\u6807\u8BC6\uFF0C\u9ED8\u8BA4 yixin\u3002",
89
+ "x-hot": false
90
+ }
91
+ }
92
+ }
93
+ };
40
94
  var HANDS_CONFIG_GROUP = {
41
95
  id: `world:${HANDS_ID}`,
42
96
  owner: `world:${HANDS_ID}`,
@@ -161,6 +215,98 @@ var HANDS_LOOK_CONFIG_GROUP = {
161
215
  // src/world.ts
162
216
  import { fileURLToPath } from "node:url";
163
217
 
218
+ // src/honcho.ts
219
+ var TIMEOUT_MS = 12e3;
220
+ var Honcho = class {
221
+ cfg;
222
+ peerReady = false;
223
+ sessionReady = false;
224
+ constructor(cfg) {
225
+ this.cfg = cfg;
226
+ }
227
+ get configured() {
228
+ return this.cfg.enabled && this.cfg.apiKey.trim().length > 0;
229
+ }
230
+ base() {
231
+ return this.cfg.baseUrl.replace(/\/+$/, "");
232
+ }
233
+ async call(path, body) {
234
+ const url = `${this.base()}/v3/workspaces/${encodeURIComponent(this.cfg.workspace)}${path}`;
235
+ const controller = new AbortController();
236
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
237
+ try {
238
+ const res = await fetch(url, {
239
+ method: "POST",
240
+ headers: {
241
+ "content-type": "application/json",
242
+ authorization: `Bearer ${this.cfg.apiKey.trim()}`
243
+ },
244
+ body: JSON.stringify(body),
245
+ signal: controller.signal
246
+ });
247
+ const text = await res.text();
248
+ if (!res.ok) throw new Error(`honcho ${res.status}: ${text.slice(0, 160)}`);
249
+ return text ? JSON.parse(text) : {};
250
+ } finally {
251
+ clearTimeout(timer);
252
+ }
253
+ }
254
+ async ensurePeer() {
255
+ if (this.peerReady) return;
256
+ await this.call("/peers", { id: this.cfg.peerId });
257
+ this.peerReady = true;
258
+ }
259
+ async ensureSession() {
260
+ if (this.sessionReady) return;
261
+ await this.ensurePeer();
262
+ await this.call("/sessions", { id: this.cfg.sessionId, peers: [this.cfg.peerId] });
263
+ this.sessionReady = true;
264
+ }
265
+ /** 把一条事实写进 Honcho 的会话里(后台 Deriver 会据此归纳结论)。 */
266
+ async remember(sentence) {
267
+ if (!this.configured) return;
268
+ await this.ensureSession();
269
+ await this.call(`/sessions/${encodeURIComponent(this.cfg.sessionId)}/messages`, {
270
+ messages: [{ peer_id: this.cfg.peerId, content: sentence }]
271
+ });
272
+ }
273
+ /** 在 Honcho 里搜相关记忆(原始消息 + 归纳结论)。 */
274
+ async search(query, limit = 8) {
275
+ if (!this.configured) return [];
276
+ await this.ensurePeer();
277
+ const raw = await this.call(`/peers/${encodeURIComponent(this.cfg.peerId)}/search`, {
278
+ query,
279
+ limit
280
+ });
281
+ const hits = [];
282
+ const push = (item, source) => {
283
+ if (typeof item === "string") {
284
+ hits.push({ text: item, source });
285
+ return;
286
+ }
287
+ if (!item || typeof item !== "object") return;
288
+ const row = item;
289
+ const text = [row.content, row.text, row.conclusion, row.observation].find((v) => typeof v === "string" && v.trim().length > 0);
290
+ if (text) hits.push({ text: text.trim(), source: String(row.session_id || row.type || source) });
291
+ };
292
+ const list = Array.isArray(raw) ? raw : raw.results || raw.messages || raw.conclusions || [];
293
+ for (const item of list) push(item, "\u7ED3\u8BBA");
294
+ return hits.slice(0, limit);
295
+ }
296
+ /** 让 Honcho 用自己的推理回答一个关于主人的问题(慢,但更“懂”)。 */
297
+ async ask(question) {
298
+ if (!this.configured) return "";
299
+ await this.ensurePeer();
300
+ const raw = await this.call(`/peers/${encodeURIComponent(this.cfg.peerId)}/chat`, {
301
+ query: question
302
+ });
303
+ const answer = [raw.content, raw.answer, raw.text, raw.reply].find(
304
+ (v) => typeof v === "string" && v.trim().length > 0
305
+ );
306
+ return answer ? answer.trim() : "";
307
+ }
308
+ };
309
+
164
310
  // src/hands.ts
165
311
  import { execFile } from "node:child_process";
166
312
  import { readFileSync } from "node:fs";
@@ -1112,6 +1258,7 @@ var HandsWorld = class {
1112
1258
  hands;
1113
1259
  pausedUntil = 0;
1114
1260
  recent = [];
1261
+ honchoClient = null;
1115
1262
  opts;
1116
1263
  constructor(opts) {
1117
1264
  this.opts = opts;
@@ -1215,21 +1362,65 @@ var HandsWorld = class {
1215
1362
  return { text: report.text, ...blobs.length ? { blobs } : {} };
1216
1363
  }
1217
1364
  /** 给文字就先在屏幕上找,找到唯一一处才点;给坐标就直接点。 */
1365
+ honcho() {
1366
+ if (!this.honchoClient) {
1367
+ this.honchoClient = new Honcho({
1368
+ enabled: Boolean(this.opts.cfg.honchoEnabled),
1369
+ apiKey: this.opts.cfg.honchoApiKey || "",
1370
+ baseUrl: this.opts.cfg.honchoBaseUrl || "https://api.honcho.dev",
1371
+ workspace: this.opts.cfg.honchoWorkspace || "dsh",
1372
+ peerId: this.opts.cfg.honchoPeerId || "\u4E3B\u4EBA",
1373
+ sessionId: this.opts.cfg.honchoSessionId || "yixin"
1374
+ });
1375
+ }
1376
+ return this.honchoClient;
1377
+ }
1218
1378
  async memorySave(args) {
1219
1379
  const key = typeof args.key === "string" ? args.key.trim() : "";
1220
1380
  const value = typeof args.value === "string" ? args.value.trim() : "";
1221
1381
  if (!key || !value) return failed("\u8981 key \u548C value\u3002");
1222
1382
  const tags = typeof args.tags === "string" ? args.tags : void 0;
1223
1383
  const r = await this.hands.memorySave(key, value, tags);
1224
- return r.ok ? `\u8BB0\u7262\u4E86\uFF1A\u300C${key}\u300D\u2192 ${value}` : failed(`\u6CA1\u8BB0\u4F4F\uFF1A${r.detail || "\u624B\u6CA1\u54CD\u5E94"}`);
1384
+ if (!r.ok) return failed(`\u6CA1\u8BB0\u4F4F\uFF1A${r.detail || "\u624B\u6CA1\u54CD\u5E94"}`);
1385
+ let cloud = "";
1386
+ const honcho = this.honcho();
1387
+ if (honcho.configured) {
1388
+ try {
1389
+ await honcho.remember(`\u5173\u4E8E\u300C${key}\u300D\uFF1A${value}`);
1390
+ cloud = "\uFF08\u5DF2\u540C\u6B65 Honcho\uFF09";
1391
+ } catch (err) {
1392
+ cloud = `\uFF08Honcho \u540C\u6B65\u5931\u8D25\uFF1A${err.message.slice(0, 60)}\uFF09`;
1393
+ }
1394
+ }
1395
+ return `\u8BB0\u7262\u4E86\uFF1A\u300C${key}\u300D\u2192 ${value}${cloud}`;
1225
1396
  }
1226
1397
  async memorySearch(args) {
1227
1398
  const query = typeof args.query === "string" ? args.query.trim() : "";
1228
1399
  const rows = await this.hands.memorySearch(query);
1229
- if (rows.length === 0) return query ? `\u8BB0\u5FC6\u91CC\u6CA1\u6709\u548C\u300C${query}\u300D\u76F8\u5173\u7684\u3002` : "\u957F\u671F\u8BB0\u5FC6\u8FD8\u662F\u7A7A\u7684\u3002";
1230
- const lines = rows.slice(0, 30).map((r) => `- ${r.key}\uFF1A${r.value}${r.ts ? `\uFF08${r.ts}\uFF09` : ""}`).join("\n");
1231
- return `\u60F3\u8D77 ${rows.length} \u6761\uFF1A
1232
- ${lines}`;
1400
+ const lines = rows.slice(0, 30).map((r) => `- ${r.key}\uFF1A${r.value}${r.ts ? `\uFF08${r.ts}\uFF09` : ""}`);
1401
+ let cloudHits = [];
1402
+ let cloudNote = "";
1403
+ const honcho = this.honcho();
1404
+ if (honcho.configured && query) {
1405
+ try {
1406
+ const hits = await honcho.search(query);
1407
+ cloudHits = hits.map((h) => `- ${h.text}`);
1408
+ if (cloudHits.length > 0) cloudNote = `
1409
+
1410
+ Honcho \u8865\u5145 ${cloudHits.length} \u6761\uFF1A`;
1411
+ } catch (err) {
1412
+ cloudNote = `
1413
+
1414
+ \uFF08Honcho \u67E5\u8BE2\u5931\u8D25\uFF1A${err.message.slice(0, 60)}\uFF09`;
1415
+ }
1416
+ }
1417
+ if (lines.length === 0 && cloudHits.length === 0) {
1418
+ return query ? `\u8BB0\u5FC6\u91CC\u6CA1\u6709\u548C\u300C${query}\u300D\u76F8\u5173\u7684\u3002` : "\u957F\u671F\u8BB0\u5FC6\u8FD8\u662F\u7A7A\u7684\u3002";
1419
+ }
1420
+ const local = lines.length > 0 ? `\u60F3\u8D77 ${rows.length} \u6761\uFF1A
1421
+ ${lines.join("\n")}` : "\u672C\u5730\u8BB0\u5FC6\u6CA1\u6709\u76F4\u63A5\u547D\u4E2D\u3002";
1422
+ return local + cloudNote + (cloudHits.length > 0 ? `
1423
+ ${cloudHits.join("\n")}` : "");
1233
1424
  }
1234
1425
  async memoryForget(args) {
1235
1426
  const key = typeof args.key === "string" ? args.key.trim() : "";
@@ -1414,7 +1605,7 @@ ${this.recent.map((r) => `- ${r}`).join("\n")}`);
1414
1605
  { label: zh ? "\u52A8\u8FC7" : "actions", value: stats.acts, tone: "plain" },
1415
1606
  { label: zh ? "\u770B\u8FC7" : "looks", value: stats.looks, tone: "plain" }
1416
1607
  ],
1417
- config: [HANDS_CONFIG_GROUP, HANDS_LOOK_CONFIG_GROUP],
1608
+ config: [HANDS_CONFIG_GROUP, HANDS_LOOK_CONFIG_GROUP, HANDS_HONCHO_GROUP],
1418
1609
  promptDocs: [
1419
1610
  {
1420
1611
  key: `worlds.${HANDS_ID}.envPrompt`,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cortico-world-hands",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
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",