luckytri 1.0.0 → 1.0.2
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/.github/SECURITY.md +4 -4
- package/README.en.md +252 -250
- package/README.md +252 -250
- package/bin/luckytri.js +5 -1
- package/docs/en/CHANGELOG.md +8 -0
- package/docs/en/connect.md +18 -18
- package/docs/en/guide.md +202 -216
- package/docs/en/npm-release.md +25 -25
- package/docs/zh/CHANGELOG.md +8 -0
- package/docs/zh/connect.md +17 -17
- package/docs/zh/guide.md +202 -216
- package/docs/zh/npm-release.md +33 -33
- package/package.json +110 -108
- package/plugins/sdk/index.d.ts +155 -155
- package/plugins/sdk/testing.js +58 -58
- package/plugins/weather/index.js +82 -82
- package/plugins/weather/luckytri-plugin.json +29 -29
- package/public/app/assets/ChatsPage-BhlJHseG.js +3 -0
- package/public/app/assets/Empty-DP6WMGHi.js +2 -0
- package/public/app/assets/Field-CtbdqaiB.js +1 -0
- package/public/app/assets/HeartPage-32BeTPn2.js +1 -0
- package/public/app/assets/LifePage-CxSm0XKI.js +1 -0
- package/public/app/assets/MemoryPage-BeKBcPCY.js +1 -0
- package/public/app/assets/NaturePage-NEPYl2HS.js +3 -0
- package/public/app/assets/PeoplePage-DxtpekwM.js +1 -0
- package/public/app/assets/PluginsPage-CVVQwEBI.css +1 -0
- package/public/app/assets/PluginsPage-DksEZbUU.js +2 -0
- package/public/app/assets/{SystemPage-CAMPoYqM.css → SystemPage-BkRu0Rvx.css} +1 -1
- package/public/app/assets/SystemPage-BvzJlHuY.js +1 -0
- package/public/app/assets/TimePage-7-awH5XL.js +3 -0
- package/public/app/assets/index-4rItIkD-.js +4 -0
- package/public/app/assets/{index-YDz1ree1.css → index-DY4e15Kh.css} +1 -1
- package/public/app/assets/{knowledge-DG0e0uKw.js → knowledge-C6_HvoyB.js} +1 -1
- package/public/app/assets/media-SMfTT-yc.js +1 -0
- package/public/app/index.html +3 -3
- package/public/guide.en.html +72 -99
- package/public/guide.html +44 -86
- package/public/tutorial.en.md +25 -39
- package/public/tutorial.md +25 -39
- package/scripts/backup.js +311 -311
- package/scripts/browser.js +41 -41
- package/scripts/import-data.js +128 -0
- package/scripts/launch.js +101 -94
- package/scripts/plugin.js +150 -150
- package/scripts/reset-password.js +22 -0
- package/scripts/runtime.js +25 -25
- package/scripts/service.js +56 -47
- package/scripts/setup.js +50 -50
- package/scripts/start-server.js +41 -41
- package/scripts/stop.js +36 -36
- package/server/app.js +112 -96
- package/server/auth.js +217 -0
- package/server/channels/contract.js +57 -57
- package/server/channels/hub.js +165 -165
- package/server/channels/index.js +28 -25
- package/server/channels/onebot/index.js +335 -336
- package/server/channels/onebot/token.js +22 -3
- package/server/core/capabilities.js +208 -208
- package/server/core/events.js +64 -60
- package/server/core/image-preprocessor.js +208 -208
- package/server/core/model-manager.js +1213 -1213
- package/server/core/net-guard.js +49 -49
- package/server/core/orchestrator.js +569 -569
- package/server/core/perceivers.js +91 -91
- package/server/core/persona-manager.js +243 -243
- package/server/core/repository.js +293 -293
- package/server/core/response-validator.js +508 -508
- package/server/core/turn-processor.js +630 -630
- package/server/core/turn.js +236 -236
- package/server/core/vision-manager.js +633 -633
- package/server/i18n/errors.js +548 -497
- package/server/i18n/labels.js +649 -653
- package/server/index.js +151 -102
- package/server/knowledge/manager.js +386 -386
- package/server/knowledge/schema.js +12 -8
- package/server/local-access.js +32 -0
- package/server/mind/anticipations.js +507 -507
- package/server/mind/api.js +703 -703
- package/server/mind/budget.js +152 -152
- package/server/mind/context-selection.js +61 -61
- package/server/mind/day-planner.js +324 -324
- package/server/mind/evidence.js +148 -148
- package/server/mind/index.js +445 -445
- package/server/mind/life/diary.js +786 -786
- package/server/mind/life/index.js +1049 -1049
- package/server/mind/observations.js +84 -84
- package/server/mind/own-day.js +324 -324
- package/server/mind/own-voice.js +318 -318
- package/server/mind/senses.js +53 -53
- package/server/mind/time/activity-clock.js +196 -196
- package/server/mind/time/availability.js +69 -69
- package/server/mind/time/commitment-review.js +198 -198
- package/server/mind/time/executor.js +512 -512
- package/server/mind/time/experiences.js +128 -128
- package/server/mind/time/games.js +562 -562
- package/server/mind/time/index.js +739 -739
- package/server/mind/time/intent.js +166 -166
- package/server/mind/time/kinds.js +171 -171
- package/server/mind/time/plugin-activity.js +207 -207
- package/server/mind/time/tasks.js +587 -587
- package/server/mind/util.js +110 -110
- package/server/mind/view.js +273 -273
- package/server/paths.js +102 -102
- package/server/plugins/api.js +204 -204
- package/server/plugins/host.js +967 -967
- package/server/plugins/install.js +295 -295
- package/server/plugins/manifest.js +201 -201
- package/server/plugins/permissions.js +42 -42
- package/server/plugins/runner/guard.mjs +36 -36
- package/server/plugins/runner/worker.mjs +250 -250
- package/server/plugins/schema.js +91 -91
- package/server/plugins/zip.js +112 -112
- package/server/startup-lock.js +118 -118
- package/server/storage/archive.js +242 -241
- package/server/storage/backup-cleanup-worker.js +8 -8
- package/server/storage/backup-cleanup.js +415 -414
- package/server/storage/backup-lock.js +76 -76
- package/server/storage/backup-scheduler.js +131 -131
- package/server/storage/data-package.js +298 -0
- package/server/storage/data-transfer-api.js +50 -0
- package/server/storage/data-transfer-worker.js +18 -0
- package/server/storage/data-transfer.js +415 -0
- package/server/storage/store.js +202 -199
- package/server/studio/readiness.js +181 -190
- package/server/studio/settings.js +236 -236
- package/public/app/assets/ChatsPage-C-FcRapR.js +0 -3
- package/public/app/assets/Empty-CpP5kQHH.js +0 -2
- package/public/app/assets/Field-D4gY-INU.js +0 -1
- package/public/app/assets/HeartPage-CPy_thQd.js +0 -1
- package/public/app/assets/LifePage-CxWvwmSf.js +0 -1
- package/public/app/assets/MemoryPage-DC4rhQ68.js +0 -1
- package/public/app/assets/NaturePage-IvYw04Zq.js +0 -3
- package/public/app/assets/PeoplePage-Cqefm9-q.js +0 -1
- package/public/app/assets/PluginsPage-DKSaKvXb.js +0 -2
- package/public/app/assets/PluginsPage-HyBe5D8w.css +0 -1
- package/public/app/assets/SystemPage-CJmtJkrK.js +0 -1
- package/public/app/assets/TimePage-Cw44inM4.js +0 -3
- package/public/app/assets/index-BheRW-1t.js +0 -4
- package/public/app/assets/media-BzPDBm7e.js +0 -1
|
@@ -1,3 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { isLoopback } from "../../local-access.js";
|
|
2
|
+
import { tokenEqual } from "../../http.js";
|
|
3
|
+
|
|
4
|
+
export async function authorizeOneBot(
|
|
5
|
+
req,
|
|
6
|
+
store,
|
|
7
|
+
verifyPassword = async () => false,
|
|
8
|
+
) {
|
|
9
|
+
if (req.headers.origin) return false;
|
|
10
|
+
if (isLoopback(req.socket.remoteAddress)) return true;
|
|
11
|
+
const supplied = String(req.headers.authorization || "").replace(
|
|
12
|
+
/^Bearer /,
|
|
13
|
+
"",
|
|
14
|
+
);
|
|
15
|
+
const legacy = effectiveOneBotToken(store);
|
|
16
|
+
if (legacy && tokenEqual(supplied, legacy)) return true;
|
|
17
|
+
return verifyPassword(supplied, req.socket.remoteAddress);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function effectiveOneBotToken(store) {
|
|
21
|
+
return process.env.ONEBOT_TOKEN || store.settings().onebotToken || "";
|
|
22
|
+
}
|
|
@@ -1,208 +1,208 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { text } from "../mind/util.js";
|
|
3
|
-
|
|
4
|
-
const DAY = 86400000;
|
|
5
|
-
const NAME = /^[a-z][a-z0-9-]*\.[a-z][a-z0-9-]{0,40}$/;
|
|
6
|
-
const INPUT_LIMIT = 4000;
|
|
7
|
-
|
|
8
|
-
// Something she may choose to do during a conversation. The handler does the
|
|
9
|
-
// work; this ledger only records that she asked, and whether it happened.
|
|
10
|
-
// A high-risk action waits for the owner. A crashed run is never repeated.
|
|
11
|
-
export class Capabilities {
|
|
12
|
-
constructor(repo, { now = () => Date.now() } = {}) {
|
|
13
|
-
this.repo = repo;
|
|
14
|
-
this.db = repo.db;
|
|
15
|
-
this.now = now;
|
|
16
|
-
this.handlers = new Map();
|
|
17
|
-
}
|
|
18
|
-
register(def, run) {
|
|
19
|
-
if (!NAME.test(def?.name || "") || typeof run !== "function")
|
|
20
|
-
throw Error("行动无效");
|
|
21
|
-
this.handlers.set(def.name, {
|
|
22
|
-
label: text(def.label, 40) || def.name,
|
|
23
|
-
describe: text(def.describe, 160),
|
|
24
|
-
risk: def.risk === "high" ? "high" : "low",
|
|
25
|
-
confirm: def.confirm === "owner" ? "owner" : "none",
|
|
26
|
-
where: def.where === "any" ? "any" : "private",
|
|
27
|
-
dailyLimit: Number.isInteger(def.dailyLimit)
|
|
28
|
-
? Math.max(0, Math.min(1000, def.dailyLimit))
|
|
29
|
-
: 20,
|
|
30
|
-
pluginId: def.pluginId,
|
|
31
|
-
input: Array.isArray(def.input) ? def.input.slice(0, 12) : [],
|
|
32
|
-
run,
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
unregister(pluginId) {
|
|
36
|
-
for (const [name, def] of this.handlers)
|
|
37
|
-
if (def.pluginId === pluginId) this.handlers.delete(name);
|
|
38
|
-
}
|
|
39
|
-
// What she may be offered in this room, at most eight.
|
|
40
|
-
offered({ kind = "group", now = this.now() } = {}) {
|
|
41
|
-
const out = [];
|
|
42
|
-
for (const [name, def] of this.handlers) {
|
|
43
|
-
if (def.where === "private" && kind !== "private") continue;
|
|
44
|
-
if (def.dailyLimit && this.#used(name, now) >= def.dailyLimit) continue;
|
|
45
|
-
out.push({
|
|
46
|
-
action: name,
|
|
47
|
-
label: def.label,
|
|
48
|
-
describe: def.describe,
|
|
49
|
-
risk: def.risk,
|
|
50
|
-
confirm: def.confirm,
|
|
51
|
-
...(def.input.length ? { input: def.input } : {}),
|
|
52
|
-
});
|
|
53
|
-
if (out.length >= 8) break;
|
|
54
|
-
}
|
|
55
|
-
return out;
|
|
56
|
-
}
|
|
57
|
-
#used(name, now) {
|
|
58
|
-
return this.db
|
|
59
|
-
.prepare(
|
|
60
|
-
"SELECT COUNT(*) n FROM mind_actions WHERE action=? AND created>? AND created<=? AND state NOT IN ('rejected','expired','failed')",
|
|
61
|
-
)
|
|
62
|
-
.get(name, now - DAY, now).n;
|
|
63
|
-
}
|
|
64
|
-
#insert(act, def, state, { session, traceId, now }) {
|
|
65
|
-
const id = randomUUID();
|
|
66
|
-
this.db
|
|
67
|
-
.prepare(
|
|
68
|
-
"INSERT INTO mind_actions(id,created,plugin_id,action,label,input,reason,risk,confirm,state,session_id,trace_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
69
|
-
)
|
|
70
|
-
.run(
|
|
71
|
-
id,
|
|
72
|
-
now,
|
|
73
|
-
def.pluginId || "",
|
|
74
|
-
act.action,
|
|
75
|
-
def.label,
|
|
76
|
-
JSON.stringify(act.input || {}).slice(0, INPUT_LIMIT),
|
|
77
|
-
text(act.reason, 200),
|
|
78
|
-
def.risk,
|
|
79
|
-
def.confirm,
|
|
80
|
-
state,
|
|
81
|
-
session || null,
|
|
82
|
-
traceId || null,
|
|
83
|
-
);
|
|
84
|
-
this.repo.store.revision++;
|
|
85
|
-
return id;
|
|
86
|
-
}
|
|
87
|
-
// She chose this in a turn. Low risk runs now; the rest wait.
|
|
88
|
-
async accept(act, { session = "", kind = "group", traceId = null } = {}) {
|
|
89
|
-
const now = this.now();
|
|
90
|
-
const def = this.handlers.get(act?.action);
|
|
91
|
-
if (!def) return { state: "rejected", reason: "没有这个行动" };
|
|
92
|
-
if (def.where === "private" && kind !== "private")
|
|
93
|
-
return { state: "rejected", reason: "这件事不能在这里做" };
|
|
94
|
-
if (def.dailyLimit && this.#used(act.action, now) >= def.dailyLimit)
|
|
95
|
-
return { state: "rejected", reason: "这件事今天已经做够了" };
|
|
96
|
-
const immediate = def.risk === "low" && def.confirm !== "owner";
|
|
97
|
-
const id = this.#insert(act, def, immediate ? "running" : "awaiting", {
|
|
98
|
-
session,
|
|
99
|
-
traceId,
|
|
100
|
-
now,
|
|
101
|
-
});
|
|
102
|
-
if (!immediate)
|
|
103
|
-
return {
|
|
104
|
-
id,
|
|
105
|
-
ref: `e:${id}`,
|
|
106
|
-
state: "awaiting",
|
|
107
|
-
label: def.label,
|
|
108
|
-
reason: "要等主人同意",
|
|
109
|
-
};
|
|
110
|
-
return this.#run(id, def, act.input || {});
|
|
111
|
-
}
|
|
112
|
-
async approve(id) {
|
|
113
|
-
const row = this.#row(id);
|
|
114
|
-
if (!row || row.state !== "awaiting") throw Error("没有等待同意的行动");
|
|
115
|
-
if (this.now() - row.created > DAY) {
|
|
116
|
-
this.#finish(id, "expired", "过了太久,这件事不作数了");
|
|
117
|
-
throw Error("这件事已经过期");
|
|
118
|
-
}
|
|
119
|
-
const def = this.handlers.get(row.action);
|
|
120
|
-
if (!def) {
|
|
121
|
-
this.#finish(id, "failed", "所需插件已停用");
|
|
122
|
-
throw Error("所需插件已停用");
|
|
123
|
-
}
|
|
124
|
-
this.db
|
|
125
|
-
.prepare("UPDATE mind_actions SET state='running' WHERE id=?")
|
|
126
|
-
.run(id);
|
|
127
|
-
return this.#run(id, def, JSON.parse(row.input || "{}"));
|
|
128
|
-
}
|
|
129
|
-
reject(id, reason = "主人没有同意") {
|
|
130
|
-
const row = this.#row(id);
|
|
131
|
-
if (!row || row.state !== "awaiting") throw Error("没有等待同意的行动");
|
|
132
|
-
this.#finish(id, "rejected", text(reason, 200) || "主人没有同意");
|
|
133
|
-
return this.#public(this.#row(id));
|
|
134
|
-
}
|
|
135
|
-
async #run(id, def, input) {
|
|
136
|
-
let outcome;
|
|
137
|
-
try {
|
|
138
|
-
outcome = await Promise.race([
|
|
139
|
-
Promise.resolve().then(() => def.run(input)),
|
|
140
|
-
new Promise((_, reject) =>
|
|
141
|
-
setTimeout(() => reject(Error("行动超时")), 15000),
|
|
142
|
-
),
|
|
143
|
-
]);
|
|
144
|
-
} catch (error) {
|
|
145
|
-
this.#finish(id, "failed", text(error.message, 300) || "没有做成");
|
|
146
|
-
const row = this.#public(this.#row(id));
|
|
147
|
-
return row;
|
|
148
|
-
}
|
|
149
|
-
const ok = outcome?.ok !== false && outcome?.state !== "failed";
|
|
150
|
-
this.#finish(
|
|
151
|
-
id,
|
|
152
|
-
ok ? "done" : "failed",
|
|
153
|
-
text(
|
|
154
|
-
outcome?.text || outcome?.reason || (ok ? "做完了" : "没有做成"),
|
|
155
|
-
500,
|
|
156
|
-
),
|
|
157
|
-
);
|
|
158
|
-
return this.#public(this.#row(id));
|
|
159
|
-
}
|
|
160
|
-
#finish(id, state, result) {
|
|
161
|
-
this.db
|
|
162
|
-
.prepare("UPDATE mind_actions SET state=?,result=?,finished=? WHERE id=?")
|
|
163
|
-
.run(state, text(result, 500), this.now(), id);
|
|
164
|
-
this.repo.store.revision++;
|
|
165
|
-
}
|
|
166
|
-
#row(id) {
|
|
167
|
-
return this.db.prepare("SELECT * FROM mind_actions WHERE id=?").get(id);
|
|
168
|
-
}
|
|
169
|
-
#public(row) {
|
|
170
|
-
if (!row) return null;
|
|
171
|
-
return {
|
|
172
|
-
id: row.id,
|
|
173
|
-
ref: `e:${row.id}`,
|
|
174
|
-
action: row.action,
|
|
175
|
-
label: row.label,
|
|
176
|
-
state: row.state,
|
|
177
|
-
result: row.result || "",
|
|
178
|
-
reason: row.reason || "",
|
|
179
|
-
created: row.created,
|
|
180
|
-
sessionId: row.session_id,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
pending(limit = 20) {
|
|
184
|
-
return this.db
|
|
185
|
-
.prepare(
|
|
186
|
-
"SELECT * FROM mind_actions WHERE state='awaiting' ORDER BY created LIMIT ?",
|
|
187
|
-
)
|
|
188
|
-
.all(limit)
|
|
189
|
-
.map((row) => this.#public(row));
|
|
190
|
-
}
|
|
191
|
-
recent({ session = "", now = this.now(), limit = 3 } = {}) {
|
|
192
|
-
return this.db
|
|
193
|
-
.prepare(
|
|
194
|
-
"SELECT * FROM mind_actions WHERE created>? AND created<=? AND state IN ('done','failed','awaiting','uncertain') ORDER BY created DESC LIMIT 20",
|
|
195
|
-
)
|
|
196
|
-
.all(now - DAY, now)
|
|
197
|
-
.filter(
|
|
198
|
-
(row) => !session || row.session_id === session || !row.session_id,
|
|
199
|
-
)
|
|
200
|
-
.slice(0, limit)
|
|
201
|
-
.map((row) => ({
|
|
202
|
-
ref: `e:${row.id}`,
|
|
203
|
-
label: row.label,
|
|
204
|
-
state: row.state,
|
|
205
|
-
result: text(row.result, 160),
|
|
206
|
-
}));
|
|
207
|
-
}
|
|
208
|
-
}
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { text } from "../mind/util.js";
|
|
3
|
+
|
|
4
|
+
const DAY = 86400000;
|
|
5
|
+
const NAME = /^[a-z][a-z0-9-]*\.[a-z][a-z0-9-]{0,40}$/;
|
|
6
|
+
const INPUT_LIMIT = 4000;
|
|
7
|
+
|
|
8
|
+
// Something she may choose to do during a conversation. The handler does the
|
|
9
|
+
// work; this ledger only records that she asked, and whether it happened.
|
|
10
|
+
// A high-risk action waits for the owner. A crashed run is never repeated.
|
|
11
|
+
export class Capabilities {
|
|
12
|
+
constructor(repo, { now = () => Date.now() } = {}) {
|
|
13
|
+
this.repo = repo;
|
|
14
|
+
this.db = repo.db;
|
|
15
|
+
this.now = now;
|
|
16
|
+
this.handlers = new Map();
|
|
17
|
+
}
|
|
18
|
+
register(def, run) {
|
|
19
|
+
if (!NAME.test(def?.name || "") || typeof run !== "function")
|
|
20
|
+
throw Error("行动无效");
|
|
21
|
+
this.handlers.set(def.name, {
|
|
22
|
+
label: text(def.label, 40) || def.name,
|
|
23
|
+
describe: text(def.describe, 160),
|
|
24
|
+
risk: def.risk === "high" ? "high" : "low",
|
|
25
|
+
confirm: def.confirm === "owner" ? "owner" : "none",
|
|
26
|
+
where: def.where === "any" ? "any" : "private",
|
|
27
|
+
dailyLimit: Number.isInteger(def.dailyLimit)
|
|
28
|
+
? Math.max(0, Math.min(1000, def.dailyLimit))
|
|
29
|
+
: 20,
|
|
30
|
+
pluginId: def.pluginId,
|
|
31
|
+
input: Array.isArray(def.input) ? def.input.slice(0, 12) : [],
|
|
32
|
+
run,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
unregister(pluginId) {
|
|
36
|
+
for (const [name, def] of this.handlers)
|
|
37
|
+
if (def.pluginId === pluginId) this.handlers.delete(name);
|
|
38
|
+
}
|
|
39
|
+
// What she may be offered in this room, at most eight.
|
|
40
|
+
offered({ kind = "group", now = this.now() } = {}) {
|
|
41
|
+
const out = [];
|
|
42
|
+
for (const [name, def] of this.handlers) {
|
|
43
|
+
if (def.where === "private" && kind !== "private") continue;
|
|
44
|
+
if (def.dailyLimit && this.#used(name, now) >= def.dailyLimit) continue;
|
|
45
|
+
out.push({
|
|
46
|
+
action: name,
|
|
47
|
+
label: def.label,
|
|
48
|
+
describe: def.describe,
|
|
49
|
+
risk: def.risk,
|
|
50
|
+
confirm: def.confirm,
|
|
51
|
+
...(def.input.length ? { input: def.input } : {}),
|
|
52
|
+
});
|
|
53
|
+
if (out.length >= 8) break;
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|
|
57
|
+
#used(name, now) {
|
|
58
|
+
return this.db
|
|
59
|
+
.prepare(
|
|
60
|
+
"SELECT COUNT(*) n FROM mind_actions WHERE action=? AND created>? AND created<=? AND state NOT IN ('rejected','expired','failed')",
|
|
61
|
+
)
|
|
62
|
+
.get(name, now - DAY, now).n;
|
|
63
|
+
}
|
|
64
|
+
#insert(act, def, state, { session, traceId, now }) {
|
|
65
|
+
const id = randomUUID();
|
|
66
|
+
this.db
|
|
67
|
+
.prepare(
|
|
68
|
+
"INSERT INTO mind_actions(id,created,plugin_id,action,label,input,reason,risk,confirm,state,session_id,trace_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)",
|
|
69
|
+
)
|
|
70
|
+
.run(
|
|
71
|
+
id,
|
|
72
|
+
now,
|
|
73
|
+
def.pluginId || "",
|
|
74
|
+
act.action,
|
|
75
|
+
def.label,
|
|
76
|
+
JSON.stringify(act.input || {}).slice(0, INPUT_LIMIT),
|
|
77
|
+
text(act.reason, 200),
|
|
78
|
+
def.risk,
|
|
79
|
+
def.confirm,
|
|
80
|
+
state,
|
|
81
|
+
session || null,
|
|
82
|
+
traceId || null,
|
|
83
|
+
);
|
|
84
|
+
this.repo.store.revision++;
|
|
85
|
+
return id;
|
|
86
|
+
}
|
|
87
|
+
// She chose this in a turn. Low risk runs now; the rest wait.
|
|
88
|
+
async accept(act, { session = "", kind = "group", traceId = null } = {}) {
|
|
89
|
+
const now = this.now();
|
|
90
|
+
const def = this.handlers.get(act?.action);
|
|
91
|
+
if (!def) return { state: "rejected", reason: "没有这个行动" };
|
|
92
|
+
if (def.where === "private" && kind !== "private")
|
|
93
|
+
return { state: "rejected", reason: "这件事不能在这里做" };
|
|
94
|
+
if (def.dailyLimit && this.#used(act.action, now) >= def.dailyLimit)
|
|
95
|
+
return { state: "rejected", reason: "这件事今天已经做够了" };
|
|
96
|
+
const immediate = def.risk === "low" && def.confirm !== "owner";
|
|
97
|
+
const id = this.#insert(act, def, immediate ? "running" : "awaiting", {
|
|
98
|
+
session,
|
|
99
|
+
traceId,
|
|
100
|
+
now,
|
|
101
|
+
});
|
|
102
|
+
if (!immediate)
|
|
103
|
+
return {
|
|
104
|
+
id,
|
|
105
|
+
ref: `e:${id}`,
|
|
106
|
+
state: "awaiting",
|
|
107
|
+
label: def.label,
|
|
108
|
+
reason: "要等主人同意",
|
|
109
|
+
};
|
|
110
|
+
return this.#run(id, def, act.input || {});
|
|
111
|
+
}
|
|
112
|
+
async approve(id) {
|
|
113
|
+
const row = this.#row(id);
|
|
114
|
+
if (!row || row.state !== "awaiting") throw Error("没有等待同意的行动");
|
|
115
|
+
if (this.now() - row.created > DAY) {
|
|
116
|
+
this.#finish(id, "expired", "过了太久,这件事不作数了");
|
|
117
|
+
throw Error("这件事已经过期");
|
|
118
|
+
}
|
|
119
|
+
const def = this.handlers.get(row.action);
|
|
120
|
+
if (!def) {
|
|
121
|
+
this.#finish(id, "failed", "所需插件已停用");
|
|
122
|
+
throw Error("所需插件已停用");
|
|
123
|
+
}
|
|
124
|
+
this.db
|
|
125
|
+
.prepare("UPDATE mind_actions SET state='running' WHERE id=?")
|
|
126
|
+
.run(id);
|
|
127
|
+
return this.#run(id, def, JSON.parse(row.input || "{}"));
|
|
128
|
+
}
|
|
129
|
+
reject(id, reason = "主人没有同意") {
|
|
130
|
+
const row = this.#row(id);
|
|
131
|
+
if (!row || row.state !== "awaiting") throw Error("没有等待同意的行动");
|
|
132
|
+
this.#finish(id, "rejected", text(reason, 200) || "主人没有同意");
|
|
133
|
+
return this.#public(this.#row(id));
|
|
134
|
+
}
|
|
135
|
+
async #run(id, def, input) {
|
|
136
|
+
let outcome;
|
|
137
|
+
try {
|
|
138
|
+
outcome = await Promise.race([
|
|
139
|
+
Promise.resolve().then(() => def.run(input)),
|
|
140
|
+
new Promise((_, reject) =>
|
|
141
|
+
setTimeout(() => reject(Error("行动超时")), 15000),
|
|
142
|
+
),
|
|
143
|
+
]);
|
|
144
|
+
} catch (error) {
|
|
145
|
+
this.#finish(id, "failed", text(error.message, 300) || "没有做成");
|
|
146
|
+
const row = this.#public(this.#row(id));
|
|
147
|
+
return row;
|
|
148
|
+
}
|
|
149
|
+
const ok = outcome?.ok !== false && outcome?.state !== "failed";
|
|
150
|
+
this.#finish(
|
|
151
|
+
id,
|
|
152
|
+
ok ? "done" : "failed",
|
|
153
|
+
text(
|
|
154
|
+
outcome?.text || outcome?.reason || (ok ? "做完了" : "没有做成"),
|
|
155
|
+
500,
|
|
156
|
+
),
|
|
157
|
+
);
|
|
158
|
+
return this.#public(this.#row(id));
|
|
159
|
+
}
|
|
160
|
+
#finish(id, state, result) {
|
|
161
|
+
this.db
|
|
162
|
+
.prepare("UPDATE mind_actions SET state=?,result=?,finished=? WHERE id=?")
|
|
163
|
+
.run(state, text(result, 500), this.now(), id);
|
|
164
|
+
this.repo.store.revision++;
|
|
165
|
+
}
|
|
166
|
+
#row(id) {
|
|
167
|
+
return this.db.prepare("SELECT * FROM mind_actions WHERE id=?").get(id);
|
|
168
|
+
}
|
|
169
|
+
#public(row) {
|
|
170
|
+
if (!row) return null;
|
|
171
|
+
return {
|
|
172
|
+
id: row.id,
|
|
173
|
+
ref: `e:${row.id}`,
|
|
174
|
+
action: row.action,
|
|
175
|
+
label: row.label,
|
|
176
|
+
state: row.state,
|
|
177
|
+
result: row.result || "",
|
|
178
|
+
reason: row.reason || "",
|
|
179
|
+
created: row.created,
|
|
180
|
+
sessionId: row.session_id,
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
pending(limit = 20) {
|
|
184
|
+
return this.db
|
|
185
|
+
.prepare(
|
|
186
|
+
"SELECT * FROM mind_actions WHERE state='awaiting' ORDER BY created LIMIT ?",
|
|
187
|
+
)
|
|
188
|
+
.all(limit)
|
|
189
|
+
.map((row) => this.#public(row));
|
|
190
|
+
}
|
|
191
|
+
recent({ session = "", now = this.now(), limit = 3 } = {}) {
|
|
192
|
+
return this.db
|
|
193
|
+
.prepare(
|
|
194
|
+
"SELECT * FROM mind_actions WHERE created>? AND created<=? AND state IN ('done','failed','awaiting','uncertain') ORDER BY created DESC LIMIT 20",
|
|
195
|
+
)
|
|
196
|
+
.all(now - DAY, now)
|
|
197
|
+
.filter(
|
|
198
|
+
(row) => !session || row.session_id === session || !row.session_id,
|
|
199
|
+
)
|
|
200
|
+
.slice(0, limit)
|
|
201
|
+
.map((row) => ({
|
|
202
|
+
ref: `e:${row.id}`,
|
|
203
|
+
label: row.label,
|
|
204
|
+
state: row.state,
|
|
205
|
+
result: text(row.result, 160),
|
|
206
|
+
}));
|
|
207
|
+
}
|
|
208
|
+
}
|
package/server/core/events.js
CHANGED
|
@@ -1,60 +1,64 @@
|
|
|
1
|
-
export function mountEvents(app, system) {
|
|
2
|
-
app.get("/api/core/stream", (req, res) => {
|
|
3
|
-
const session = String(req.query.session || "");
|
|
4
|
-
res.set({
|
|
5
|
-
"Content-Type": "text/event-stream",
|
|
6
|
-
"Cache-Control": "no-cache",
|
|
7
|
-
Connection: "keep-alive",
|
|
8
|
-
});
|
|
9
|
-
res.flushHeaders();
|
|
10
|
-
let previous = "";
|
|
11
|
-
const tick = () => {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
1
|
+
export function mountEvents(app, system, authorized = () => true) {
|
|
2
|
+
app.get("/api/core/stream", (req, res) => {
|
|
3
|
+
const session = String(req.query.session || "");
|
|
4
|
+
res.set({
|
|
5
|
+
"Content-Type": "text/event-stream",
|
|
6
|
+
"Cache-Control": "no-cache",
|
|
7
|
+
Connection: "keep-alive",
|
|
8
|
+
});
|
|
9
|
+
res.flushHeaders();
|
|
10
|
+
let previous = "";
|
|
11
|
+
const tick = () => {
|
|
12
|
+
if (!authorized(req)) {
|
|
13
|
+
res.end();
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const db = system.repo.db;
|
|
17
|
+
const value = session
|
|
18
|
+
? {
|
|
19
|
+
messages:
|
|
20
|
+
db
|
|
21
|
+
.prepare(
|
|
22
|
+
"SELECT MAX(seq) n FROM core_events WHERE session_id=?",
|
|
23
|
+
)
|
|
24
|
+
.get(session).n || 0,
|
|
25
|
+
latestTrace:
|
|
26
|
+
db
|
|
27
|
+
.prepare(
|
|
28
|
+
"SELECT id,status FROM core_traces WHERE session_id=? ORDER BY time DESC LIMIT 1",
|
|
29
|
+
)
|
|
30
|
+
.get(session) || null,
|
|
31
|
+
running: db
|
|
32
|
+
.prepare(
|
|
33
|
+
"SELECT COUNT(*) n FROM core_traces WHERE session_id=? AND status='running'",
|
|
34
|
+
)
|
|
35
|
+
.get(session).n,
|
|
36
|
+
}
|
|
37
|
+
: {
|
|
38
|
+
messages:
|
|
39
|
+
db.prepare("SELECT MAX(seq) n FROM core_events").get().n || 0,
|
|
40
|
+
latestTrace:
|
|
41
|
+
db
|
|
42
|
+
.prepare(
|
|
43
|
+
"SELECT id,status FROM core_traces ORDER BY rowid DESC LIMIT 1",
|
|
44
|
+
)
|
|
45
|
+
.get() || null,
|
|
46
|
+
running: db
|
|
47
|
+
.prepare(
|
|
48
|
+
"SELECT COUNT(*) n FROM core_traces WHERE status='running'",
|
|
49
|
+
)
|
|
50
|
+
.get().n,
|
|
51
|
+
revision: system.store.revision,
|
|
52
|
+
};
|
|
53
|
+
const current = JSON.stringify(value);
|
|
54
|
+
if (current !== previous) {
|
|
55
|
+
res.write(`data: ${current}\n\n`);
|
|
56
|
+
previous = current;
|
|
57
|
+
} else res.write(": heartbeat\n\n");
|
|
58
|
+
};
|
|
59
|
+
tick();
|
|
60
|
+
const timer = setInterval(tick, session ? 500 : 1500);
|
|
61
|
+
timer.unref();
|
|
62
|
+
res.on("close", () => clearInterval(timer));
|
|
63
|
+
});
|
|
64
|
+
}
|