cortico-world-hands 0.1.7 → 0.1.9
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.
- package/dist/index.js +203 -7
- 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: "morse",
|
|
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\u53EA\u80FD\u7528\u5B57\u6BCD/\u6570\u5B57/\u4E0B\u5212\u7EBF/\u8FDE\u5B57\u7B26\uFF08\u9ED8\u8BA4 morse\uFF0C\u4E2D\u6587\u4F1A\u88AB\u6362\u6210 morse\uFF09\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,103 @@ 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
|
+
/** Honcho 只接受 ^[a-zA-Z0-9_-]+$ 的 peer id,中文名要退化成 morse 这类。 */
|
|
231
|
+
safePeer() {
|
|
232
|
+
const raw = (this.cfg.peerId || "").trim();
|
|
233
|
+
return /^[a-zA-Z0-9_-]+$/.test(raw) ? raw : "morse";
|
|
234
|
+
}
|
|
235
|
+
base() {
|
|
236
|
+
return this.cfg.baseUrl.replace(/\/+$/, "");
|
|
237
|
+
}
|
|
238
|
+
async call(path, body) {
|
|
239
|
+
const url = `${this.base()}/v3/workspaces/${encodeURIComponent(this.cfg.workspace)}${path}`;
|
|
240
|
+
const controller = new AbortController();
|
|
241
|
+
const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
242
|
+
try {
|
|
243
|
+
const res = await fetch(url, {
|
|
244
|
+
method: "POST",
|
|
245
|
+
headers: {
|
|
246
|
+
"content-type": "application/json",
|
|
247
|
+
authorization: `Bearer ${this.cfg.apiKey.trim()}`
|
|
248
|
+
},
|
|
249
|
+
body: JSON.stringify(body),
|
|
250
|
+
signal: controller.signal
|
|
251
|
+
});
|
|
252
|
+
const text = await res.text();
|
|
253
|
+
if (!res.ok) throw new Error(`honcho ${res.status}: ${text.slice(0, 160)}`);
|
|
254
|
+
return text ? JSON.parse(text) : {};
|
|
255
|
+
} finally {
|
|
256
|
+
clearTimeout(timer);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async ensurePeer() {
|
|
260
|
+
if (this.peerReady) return;
|
|
261
|
+
await this.call("/peers", { id: this.safePeer() });
|
|
262
|
+
this.peerReady = true;
|
|
263
|
+
}
|
|
264
|
+
async ensureSession() {
|
|
265
|
+
if (this.sessionReady) return;
|
|
266
|
+
await this.ensurePeer();
|
|
267
|
+
await this.call("/sessions", { id: this.cfg.sessionId });
|
|
268
|
+
this.sessionReady = true;
|
|
269
|
+
}
|
|
270
|
+
/** 把一条事实写进 Honcho 的会话里(后台 Deriver 会据此归纳结论)。 */
|
|
271
|
+
async remember(sentence) {
|
|
272
|
+
if (!this.configured) return;
|
|
273
|
+
await this.ensureSession();
|
|
274
|
+
await this.call(`/sessions/${encodeURIComponent(this.cfg.sessionId)}/messages`, {
|
|
275
|
+
messages: [{ peer_id: this.safePeer(), content: sentence }]
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
/** 在 Honcho 里搜相关记忆(原始消息 + 归纳结论)。 */
|
|
279
|
+
async search(query, limit = 8) {
|
|
280
|
+
if (!this.configured) return [];
|
|
281
|
+
await this.ensurePeer();
|
|
282
|
+
const raw = await this.call(`/peers/${encodeURIComponent(this.safePeer())}/search`, {
|
|
283
|
+
query,
|
|
284
|
+
limit
|
|
285
|
+
});
|
|
286
|
+
const hits = [];
|
|
287
|
+
const push = (item, source) => {
|
|
288
|
+
if (typeof item === "string") {
|
|
289
|
+
hits.push({ text: item, source });
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (!item || typeof item !== "object") return;
|
|
293
|
+
const row = item;
|
|
294
|
+
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) });
|
|
296
|
+
};
|
|
297
|
+
const list = Array.isArray(raw) ? raw : raw.results || raw.messages || raw.conclusions || [];
|
|
298
|
+
for (const item of list) push(item, "\u7ED3\u8BBA");
|
|
299
|
+
return hits.slice(0, limit);
|
|
300
|
+
}
|
|
301
|
+
/** 让 Honcho 用自己的推理回答一个关于主人的问题(慢,但更“懂”)。 */
|
|
302
|
+
async ask(question) {
|
|
303
|
+
if (!this.configured) return "";
|
|
304
|
+
await this.ensurePeer();
|
|
305
|
+
const raw = await this.call(`/peers/${encodeURIComponent(this.safePeer())}/chat`, {
|
|
306
|
+
query: question
|
|
307
|
+
});
|
|
308
|
+
const answer = [raw.content, raw.answer, raw.text, raw.reply].find(
|
|
309
|
+
(v) => typeof v === "string" && v.trim().length > 0
|
|
310
|
+
);
|
|
311
|
+
return answer ? answer.trim() : "";
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
164
315
|
// src/hands.ts
|
|
165
316
|
import { execFile } from "node:child_process";
|
|
166
317
|
import { readFileSync } from "node:fs";
|
|
@@ -1112,6 +1263,7 @@ var HandsWorld = class {
|
|
|
1112
1263
|
hands;
|
|
1113
1264
|
pausedUntil = 0;
|
|
1114
1265
|
recent = [];
|
|
1266
|
+
honchoClient = null;
|
|
1115
1267
|
opts;
|
|
1116
1268
|
constructor(opts) {
|
|
1117
1269
|
this.opts = opts;
|
|
@@ -1215,21 +1367,65 @@ var HandsWorld = class {
|
|
|
1215
1367
|
return { text: report.text, ...blobs.length ? { blobs } : {} };
|
|
1216
1368
|
}
|
|
1217
1369
|
/** 给文字就先在屏幕上找,找到唯一一处才点;给坐标就直接点。 */
|
|
1370
|
+
honcho() {
|
|
1371
|
+
if (!this.honchoClient) {
|
|
1372
|
+
this.honchoClient = new Honcho({
|
|
1373
|
+
enabled: Boolean(this.opts.cfg.honchoEnabled),
|
|
1374
|
+
apiKey: this.opts.cfg.honchoApiKey || "",
|
|
1375
|
+
baseUrl: this.opts.cfg.honchoBaseUrl || "https://api.honcho.dev",
|
|
1376
|
+
workspace: this.opts.cfg.honchoWorkspace || "dsh",
|
|
1377
|
+
peerId: this.opts.cfg.honchoPeerId || "\u4E3B\u4EBA",
|
|
1378
|
+
sessionId: this.opts.cfg.honchoSessionId || "yixin"
|
|
1379
|
+
});
|
|
1380
|
+
}
|
|
1381
|
+
return this.honchoClient;
|
|
1382
|
+
}
|
|
1218
1383
|
async memorySave(args) {
|
|
1219
1384
|
const key = typeof args.key === "string" ? args.key.trim() : "";
|
|
1220
1385
|
const value = typeof args.value === "string" ? args.value.trim() : "";
|
|
1221
1386
|
if (!key || !value) return failed("\u8981 key \u548C value\u3002");
|
|
1222
1387
|
const tags = typeof args.tags === "string" ? args.tags : void 0;
|
|
1223
1388
|
const r = await this.hands.memorySave(key, value, tags);
|
|
1224
|
-
|
|
1389
|
+
if (!r.ok) return failed(`\u6CA1\u8BB0\u4F4F\uFF1A${r.detail || "\u624B\u6CA1\u54CD\u5E94"}`);
|
|
1390
|
+
let cloud = "";
|
|
1391
|
+
const honcho = this.honcho();
|
|
1392
|
+
if (honcho.configured) {
|
|
1393
|
+
try {
|
|
1394
|
+
await honcho.remember(`\u5173\u4E8E\u300C${key}\u300D\uFF1A${value}`);
|
|
1395
|
+
cloud = "\uFF08\u5DF2\u540C\u6B65 Honcho\uFF09";
|
|
1396
|
+
} catch (err) {
|
|
1397
|
+
cloud = `\uFF08Honcho \u540C\u6B65\u5931\u8D25\uFF1A${err.message.slice(0, 60)}\uFF09`;
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
return `\u8BB0\u7262\u4E86\uFF1A\u300C${key}\u300D\u2192 ${value}${cloud}`;
|
|
1225
1401
|
}
|
|
1226
1402
|
async memorySearch(args) {
|
|
1227
1403
|
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
1228
1404
|
const rows = await this.hands.memorySearch(query);
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1405
|
+
const lines = rows.slice(0, 30).map((r) => `- ${r.key}\uFF1A${r.value}${r.ts ? `\uFF08${r.ts}\uFF09` : ""}`);
|
|
1406
|
+
let cloudHits = [];
|
|
1407
|
+
let cloudNote = "";
|
|
1408
|
+
const honcho = this.honcho();
|
|
1409
|
+
if (honcho.configured && query) {
|
|
1410
|
+
try {
|
|
1411
|
+
const hits = await honcho.search(query);
|
|
1412
|
+
cloudHits = hits.map((h) => `- ${h.text}`);
|
|
1413
|
+
if (cloudHits.length > 0) cloudNote = `
|
|
1414
|
+
|
|
1415
|
+
Honcho \u8865\u5145 ${cloudHits.length} \u6761\uFF1A`;
|
|
1416
|
+
} catch (err) {
|
|
1417
|
+
cloudNote = `
|
|
1418
|
+
|
|
1419
|
+
\uFF08Honcho \u67E5\u8BE2\u5931\u8D25\uFF1A${err.message.slice(0, 60)}\uFF09`;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
if (lines.length === 0 && cloudHits.length === 0) {
|
|
1423
|
+
return query ? `\u8BB0\u5FC6\u91CC\u6CA1\u6709\u548C\u300C${query}\u300D\u76F8\u5173\u7684\u3002` : "\u957F\u671F\u8BB0\u5FC6\u8FD8\u662F\u7A7A\u7684\u3002";
|
|
1424
|
+
}
|
|
1425
|
+
const local = lines.length > 0 ? `\u60F3\u8D77 ${rows.length} \u6761\uFF1A
|
|
1426
|
+
${lines.join("\n")}` : "\u672C\u5730\u8BB0\u5FC6\u6CA1\u6709\u76F4\u63A5\u547D\u4E2D\u3002";
|
|
1427
|
+
return local + cloudNote + (cloudHits.length > 0 ? `
|
|
1428
|
+
${cloudHits.join("\n")}` : "");
|
|
1233
1429
|
}
|
|
1234
1430
|
async memoryForget(args) {
|
|
1235
1431
|
const key = typeof args.key === "string" ? args.key.trim() : "";
|
|
@@ -1414,7 +1610,7 @@ ${this.recent.map((r) => `- ${r}`).join("\n")}`);
|
|
|
1414
1610
|
{ label: zh ? "\u52A8\u8FC7" : "actions", value: stats.acts, tone: "plain" },
|
|
1415
1611
|
{ label: zh ? "\u770B\u8FC7" : "looks", value: stats.looks, tone: "plain" }
|
|
1416
1612
|
],
|
|
1417
|
-
config: [HANDS_CONFIG_GROUP, HANDS_LOOK_CONFIG_GROUP],
|
|
1613
|
+
config: [HANDS_CONFIG_GROUP, HANDS_LOOK_CONFIG_GROUP, HANDS_HONCHO_GROUP],
|
|
1418
1614
|
promptDocs: [
|
|
1419
1615
|
{
|
|
1420
1616
|
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.
|
|
3
|
+
"version": "0.1.9",
|
|
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",
|