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.
Files changed (139) hide show
  1. package/.github/SECURITY.md +4 -4
  2. package/README.en.md +252 -250
  3. package/README.md +252 -250
  4. package/bin/luckytri.js +5 -1
  5. package/docs/en/CHANGELOG.md +8 -0
  6. package/docs/en/connect.md +18 -18
  7. package/docs/en/guide.md +202 -216
  8. package/docs/en/npm-release.md +25 -25
  9. package/docs/zh/CHANGELOG.md +8 -0
  10. package/docs/zh/connect.md +17 -17
  11. package/docs/zh/guide.md +202 -216
  12. package/docs/zh/npm-release.md +33 -33
  13. package/package.json +110 -108
  14. package/plugins/sdk/index.d.ts +155 -155
  15. package/plugins/sdk/testing.js +58 -58
  16. package/plugins/weather/index.js +82 -82
  17. package/plugins/weather/luckytri-plugin.json +29 -29
  18. package/public/app/assets/ChatsPage-BhlJHseG.js +3 -0
  19. package/public/app/assets/Empty-DP6WMGHi.js +2 -0
  20. package/public/app/assets/Field-CtbdqaiB.js +1 -0
  21. package/public/app/assets/HeartPage-32BeTPn2.js +1 -0
  22. package/public/app/assets/LifePage-CxSm0XKI.js +1 -0
  23. package/public/app/assets/MemoryPage-BeKBcPCY.js +1 -0
  24. package/public/app/assets/NaturePage-NEPYl2HS.js +3 -0
  25. package/public/app/assets/PeoplePage-DxtpekwM.js +1 -0
  26. package/public/app/assets/PluginsPage-CVVQwEBI.css +1 -0
  27. package/public/app/assets/PluginsPage-DksEZbUU.js +2 -0
  28. package/public/app/assets/{SystemPage-CAMPoYqM.css → SystemPage-BkRu0Rvx.css} +1 -1
  29. package/public/app/assets/SystemPage-BvzJlHuY.js +1 -0
  30. package/public/app/assets/TimePage-7-awH5XL.js +3 -0
  31. package/public/app/assets/index-4rItIkD-.js +4 -0
  32. package/public/app/assets/{index-YDz1ree1.css → index-DY4e15Kh.css} +1 -1
  33. package/public/app/assets/{knowledge-DG0e0uKw.js → knowledge-C6_HvoyB.js} +1 -1
  34. package/public/app/assets/media-SMfTT-yc.js +1 -0
  35. package/public/app/index.html +3 -3
  36. package/public/guide.en.html +72 -99
  37. package/public/guide.html +44 -86
  38. package/public/tutorial.en.md +25 -39
  39. package/public/tutorial.md +25 -39
  40. package/scripts/backup.js +311 -311
  41. package/scripts/browser.js +41 -41
  42. package/scripts/import-data.js +128 -0
  43. package/scripts/launch.js +101 -94
  44. package/scripts/plugin.js +150 -150
  45. package/scripts/reset-password.js +22 -0
  46. package/scripts/runtime.js +25 -25
  47. package/scripts/service.js +56 -47
  48. package/scripts/setup.js +50 -50
  49. package/scripts/start-server.js +41 -41
  50. package/scripts/stop.js +36 -36
  51. package/server/app.js +112 -96
  52. package/server/auth.js +217 -0
  53. package/server/channels/contract.js +57 -57
  54. package/server/channels/hub.js +165 -165
  55. package/server/channels/index.js +28 -25
  56. package/server/channels/onebot/index.js +335 -336
  57. package/server/channels/onebot/token.js +22 -3
  58. package/server/core/capabilities.js +208 -208
  59. package/server/core/events.js +64 -60
  60. package/server/core/image-preprocessor.js +208 -208
  61. package/server/core/model-manager.js +1213 -1213
  62. package/server/core/net-guard.js +49 -49
  63. package/server/core/orchestrator.js +569 -569
  64. package/server/core/perceivers.js +91 -91
  65. package/server/core/persona-manager.js +243 -243
  66. package/server/core/repository.js +293 -293
  67. package/server/core/response-validator.js +508 -508
  68. package/server/core/turn-processor.js +630 -630
  69. package/server/core/turn.js +236 -236
  70. package/server/core/vision-manager.js +633 -633
  71. package/server/i18n/errors.js +548 -497
  72. package/server/i18n/labels.js +649 -653
  73. package/server/index.js +151 -102
  74. package/server/knowledge/manager.js +386 -386
  75. package/server/knowledge/schema.js +12 -8
  76. package/server/local-access.js +32 -0
  77. package/server/mind/anticipations.js +507 -507
  78. package/server/mind/api.js +703 -703
  79. package/server/mind/budget.js +152 -152
  80. package/server/mind/context-selection.js +61 -61
  81. package/server/mind/day-planner.js +324 -324
  82. package/server/mind/evidence.js +148 -148
  83. package/server/mind/index.js +445 -445
  84. package/server/mind/life/diary.js +786 -786
  85. package/server/mind/life/index.js +1049 -1049
  86. package/server/mind/observations.js +84 -84
  87. package/server/mind/own-day.js +324 -324
  88. package/server/mind/own-voice.js +318 -318
  89. package/server/mind/senses.js +53 -53
  90. package/server/mind/time/activity-clock.js +196 -196
  91. package/server/mind/time/availability.js +69 -69
  92. package/server/mind/time/commitment-review.js +198 -198
  93. package/server/mind/time/executor.js +512 -512
  94. package/server/mind/time/experiences.js +128 -128
  95. package/server/mind/time/games.js +562 -562
  96. package/server/mind/time/index.js +739 -739
  97. package/server/mind/time/intent.js +166 -166
  98. package/server/mind/time/kinds.js +171 -171
  99. package/server/mind/time/plugin-activity.js +207 -207
  100. package/server/mind/time/tasks.js +587 -587
  101. package/server/mind/util.js +110 -110
  102. package/server/mind/view.js +273 -273
  103. package/server/paths.js +102 -102
  104. package/server/plugins/api.js +204 -204
  105. package/server/plugins/host.js +967 -967
  106. package/server/plugins/install.js +295 -295
  107. package/server/plugins/manifest.js +201 -201
  108. package/server/plugins/permissions.js +42 -42
  109. package/server/plugins/runner/guard.mjs +36 -36
  110. package/server/plugins/runner/worker.mjs +250 -250
  111. package/server/plugins/schema.js +91 -91
  112. package/server/plugins/zip.js +112 -112
  113. package/server/startup-lock.js +118 -118
  114. package/server/storage/archive.js +242 -241
  115. package/server/storage/backup-cleanup-worker.js +8 -8
  116. package/server/storage/backup-cleanup.js +415 -414
  117. package/server/storage/backup-lock.js +76 -76
  118. package/server/storage/backup-scheduler.js +131 -131
  119. package/server/storage/data-package.js +298 -0
  120. package/server/storage/data-transfer-api.js +50 -0
  121. package/server/storage/data-transfer-worker.js +18 -0
  122. package/server/storage/data-transfer.js +415 -0
  123. package/server/storage/store.js +202 -199
  124. package/server/studio/readiness.js +181 -190
  125. package/server/studio/settings.js +236 -236
  126. package/public/app/assets/ChatsPage-C-FcRapR.js +0 -3
  127. package/public/app/assets/Empty-CpP5kQHH.js +0 -2
  128. package/public/app/assets/Field-D4gY-INU.js +0 -1
  129. package/public/app/assets/HeartPage-CPy_thQd.js +0 -1
  130. package/public/app/assets/LifePage-CxWvwmSf.js +0 -1
  131. package/public/app/assets/MemoryPage-DC4rhQ68.js +0 -1
  132. package/public/app/assets/NaturePage-IvYw04Zq.js +0 -3
  133. package/public/app/assets/PeoplePage-Cqefm9-q.js +0 -1
  134. package/public/app/assets/PluginsPage-DKSaKvXb.js +0 -2
  135. package/public/app/assets/PluginsPage-HyBe5D8w.css +0 -1
  136. package/public/app/assets/SystemPage-CJmtJkrK.js +0 -1
  137. package/public/app/assets/TimePage-Cw44inM4.js +0 -3
  138. package/public/app/assets/index-BheRW-1t.js +0 -4
  139. package/public/app/assets/media-BzPDBm7e.js +0 -1
@@ -1,3 +1,22 @@
1
- export function effectiveOneBotToken(store) {
2
- return process.env.ONEBOT_TOKEN || store.settings().onebotToken || "";
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
+ }
@@ -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
- const db = system.repo.db;
13
- const value = session
14
- ? {
15
- messages:
16
- db
17
- .prepare(
18
- "SELECT MAX(seq) n FROM core_events WHERE session_id=?",
19
- )
20
- .get(session).n || 0,
21
- latestTrace:
22
- db
23
- .prepare(
24
- "SELECT id,status FROM core_traces WHERE session_id=? ORDER BY time DESC LIMIT 1",
25
- )
26
- .get(session) || null,
27
- running: db
28
- .prepare(
29
- "SELECT COUNT(*) n FROM core_traces WHERE session_id=? AND status='running'",
30
- )
31
- .get(session).n,
32
- }
33
- : {
34
- messages:
35
- db.prepare("SELECT MAX(seq) n FROM core_events").get().n || 0,
36
- latestTrace:
37
- db
38
- .prepare(
39
- "SELECT id,status FROM core_traces ORDER BY rowid DESC LIMIT 1",
40
- )
41
- .get() || null,
42
- running: db
43
- .prepare(
44
- "SELECT COUNT(*) n FROM core_traces WHERE status='running'",
45
- )
46
- .get().n,
47
- revision: system.store.revision,
48
- };
49
- const current = JSON.stringify(value);
50
- if (current !== previous) {
51
- res.write(`data: ${current}\n\n`);
52
- previous = current;
53
- } else res.write(": heartbeat\n\n");
54
- };
55
- tick();
56
- const timer = setInterval(tick, session ? 500 : 1500);
57
- timer.unref();
58
- res.on("close", () => clearInterval(timer));
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
+ }