luckytri 1.0.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 (235) hide show
  1. package/.github/SECURITY.md +71 -0
  2. package/LICENSE +21 -0
  3. package/README.en.md +250 -0
  4. package/README.md +250 -0
  5. package/bin/luckytri.js +107 -0
  6. package/docs/brand/banner.png +0 -0
  7. package/docs/brand/palette.png +0 -0
  8. package/docs/brand/portrait.png +0 -0
  9. package/docs/en/CHANGELOG.md +113 -0
  10. package/docs/en/architecture.md +152 -0
  11. package/docs/en/chat-style.md +62 -0
  12. package/docs/en/connect.md +63 -0
  13. package/docs/en/guide.md +216 -0
  14. package/docs/en/her-life.md +189 -0
  15. package/docs/en/npm-release.md +25 -0
  16. package/docs/en/plugins.md +82 -0
  17. package/docs/en/recovery.md +28 -0
  18. package/docs/en/time.md +71 -0
  19. package/docs/en/webui.md +73 -0
  20. package/docs/zh/CHANGELOG.md +113 -0
  21. package/docs/zh/architecture.md +152 -0
  22. package/docs/zh/chat-style.md +62 -0
  23. package/docs/zh/connect.md +63 -0
  24. package/docs/zh/guide.md +216 -0
  25. package/docs/zh/her-life.md +189 -0
  26. package/docs/zh/npm-release.md +33 -0
  27. package/docs/zh/plugins.md +82 -0
  28. package/docs/zh/recovery.md +28 -0
  29. package/docs/zh/time.md +71 -0
  30. package/docs/zh/webui.md +73 -0
  31. package/package.json +108 -0
  32. package/plugins/sdk/index.d.ts +155 -0
  33. package/plugins/sdk/testing.js +58 -0
  34. package/plugins/weather/index.js +82 -0
  35. package/plugins/weather/luckytri-plugin.json +29 -0
  36. package/public/app/assets/ChatsPage-C-FcRapR.js +3 -0
  37. package/public/app/assets/ChatsPage-DfyPCS9N.css +1 -0
  38. package/public/app/assets/Empty-CpP5kQHH.js +2 -0
  39. package/public/app/assets/Empty-DmUB2-Bk.css +1 -0
  40. package/public/app/assets/Field-D4gY-INU.js +1 -0
  41. package/public/app/assets/HeartPage-Bft9wZp5.css +1 -0
  42. package/public/app/assets/HeartPage-CPy_thQd.js +1 -0
  43. package/public/app/assets/LifePage-BUi5euBF.css +1 -0
  44. package/public/app/assets/LifePage-CxWvwmSf.js +1 -0
  45. package/public/app/assets/MemoryPage-DC4rhQ68.js +1 -0
  46. package/public/app/assets/MemoryPage-cZvXfkXb.css +1 -0
  47. package/public/app/assets/NaturePage-CTXHCE2D.css +1 -0
  48. package/public/app/assets/NaturePage-IvYw04Zq.js +3 -0
  49. package/public/app/assets/PeoplePage-BmUxkrOU.css +1 -0
  50. package/public/app/assets/PeoplePage-Cqefm9-q.js +1 -0
  51. package/public/app/assets/PluginsPage-DKSaKvXb.js +2 -0
  52. package/public/app/assets/PluginsPage-HyBe5D8w.css +1 -0
  53. package/public/app/assets/SystemPage-CAMPoYqM.css +1 -0
  54. package/public/app/assets/SystemPage-CJmtJkrK.js +1 -0
  55. package/public/app/assets/TimePage-BOms_n4I.css +1 -0
  56. package/public/app/assets/TimePage-Cw44inM4.js +3 -0
  57. package/public/app/assets/formatters-ZmW_xFHw.js +1 -0
  58. package/public/app/assets/index-BheRW-1t.js +4 -0
  59. package/public/app/assets/index-YDz1ree1.css +1 -0
  60. package/public/app/assets/knowledge-DG0e0uKw.js +1 -0
  61. package/public/app/assets/liquid-garden-IMoHBwzc.png +0 -0
  62. package/public/app/assets/luckytri-home-95QKeMtc.png +0 -0
  63. package/public/app/assets/media-BzPDBm7e.js +1 -0
  64. package/public/app/index.html +27 -0
  65. package/public/guide.en.html +1053 -0
  66. package/public/guide.html +893 -0
  67. package/public/plugin-kit/v1/bridge.js +30 -0
  68. package/public/plugin-kit/v1/kit.css +14 -0
  69. package/public/tutorial.en.md +214 -0
  70. package/public/tutorial.md +214 -0
  71. package/scripts/backup.js +311 -0
  72. package/scripts/browser.js +41 -0
  73. package/scripts/launch.js +94 -0
  74. package/scripts/plugin.js +150 -0
  75. package/scripts/proxy-config.js +167 -0
  76. package/scripts/recover.js +26 -0
  77. package/scripts/runtime.js +25 -0
  78. package/scripts/service.js +47 -0
  79. package/scripts/setup.js +50 -0
  80. package/scripts/start-server.js +41 -0
  81. package/scripts/stop.js +36 -0
  82. package/server/app.js +96 -0
  83. package/server/channels/adapters.js +27 -0
  84. package/server/channels/contract.js +57 -0
  85. package/server/channels/hub.js +165 -0
  86. package/server/channels/index.js +25 -0
  87. package/server/channels/onebot/adapter.js +288 -0
  88. package/server/channels/onebot/index.js +336 -0
  89. package/server/channels/onebot/token.js +3 -0
  90. package/server/channels/qqbot/adapter.js +253 -0
  91. package/server/channels/qqbot/api.js +199 -0
  92. package/server/channels/qqbot/credentials.js +11 -0
  93. package/server/channels/qqbot/gateway.js +274 -0
  94. package/server/channels/qqbot/index.js +389 -0
  95. package/server/channels/segments.js +40 -0
  96. package/server/channels/session-key.js +153 -0
  97. package/server/channels/state.js +42 -0
  98. package/server/core/api.js +564 -0
  99. package/server/core/capabilities.js +208 -0
  100. package/server/core/context-builder.js +288 -0
  101. package/server/core/context-compactor.js +454 -0
  102. package/server/core/conversation-cues.js +325 -0
  103. package/server/core/conversation-manager.js +87 -0
  104. package/server/core/events.js +60 -0
  105. package/server/core/feedback.js +8 -0
  106. package/server/core/group-style.js +72 -0
  107. package/server/core/image-preprocessor.js +208 -0
  108. package/server/core/initiative-context.js +66 -0
  109. package/server/core/input-budget.js +32 -0
  110. package/server/core/local-demo.js +98 -0
  111. package/server/core/message-manager.js +118 -0
  112. package/server/core/message-scheduler.js +78 -0
  113. package/server/core/model-json.js +190 -0
  114. package/server/core/model-manager.js +1213 -0
  115. package/server/core/model-presets.js +900 -0
  116. package/server/core/name-call.js +29 -0
  117. package/server/core/net-guard.js +49 -0
  118. package/server/core/network.js +157 -0
  119. package/server/core/orchestrator.js +569 -0
  120. package/server/core/perceivers.js +91 -0
  121. package/server/core/person-name.js +22 -0
  122. package/server/core/persona-manager.js +243 -0
  123. package/server/core/reply-delivery.js +393 -0
  124. package/server/core/reply-target-resolver.js +50 -0
  125. package/server/core/repository.js +293 -0
  126. package/server/core/response-generator.js +50 -0
  127. package/server/core/response-validator.js +508 -0
  128. package/server/core/retired-prompts.js +51 -0
  129. package/server/core/sessions.js +109 -0
  130. package/server/core/speaker-names.js +92 -0
  131. package/server/core/topic-tracker.js +65 -0
  132. package/server/core/turn-processor.js +630 -0
  133. package/server/core/turn.js +236 -0
  134. package/server/core/vision-manager.js +633 -0
  135. package/server/http.js +18 -0
  136. package/server/i18n/decisions.js +206 -0
  137. package/server/i18n/errors.js +497 -0
  138. package/server/i18n/index.js +144 -0
  139. package/server/i18n/labels.js +653 -0
  140. package/server/i18n/models.js +111 -0
  141. package/server/index.js +102 -0
  142. package/server/knowledge/api.js +342 -0
  143. package/server/knowledge/embedding-profile.js +114 -0
  144. package/server/knowledge/index.js +13 -0
  145. package/server/knowledge/manager.js +386 -0
  146. package/server/knowledge/retrieval.js +93 -0
  147. package/server/knowledge/schema.js +154 -0
  148. package/server/knowledge/vector-index.js +90 -0
  149. package/server/mind/affect.js +265 -0
  150. package/server/mind/anticipations.js +507 -0
  151. package/server/mind/api.js +703 -0
  152. package/server/mind/attention.js +200 -0
  153. package/server/mind/bonds.js +558 -0
  154. package/server/mind/budget.js +152 -0
  155. package/server/mind/clock.js +83 -0
  156. package/server/mind/context-selection.js +61 -0
  157. package/server/mind/continuity.js +562 -0
  158. package/server/mind/conversation-origin.js +175 -0
  159. package/server/mind/day-planner.js +324 -0
  160. package/server/mind/days.js +210 -0
  161. package/server/mind/evidence-read.js +45 -0
  162. package/server/mind/evidence.js +148 -0
  163. package/server/mind/faces.js +138 -0
  164. package/server/mind/guard.js +60 -0
  165. package/server/mind/index.js +445 -0
  166. package/server/mind/initiative.js +333 -0
  167. package/server/mind/life/activities.js +21 -0
  168. package/server/mind/life/context.js +309 -0
  169. package/server/mind/life/diary.js +786 -0
  170. package/server/mind/life/growth.js +252 -0
  171. package/server/mind/life/index.js +1049 -0
  172. package/server/mind/life/presence.js +305 -0
  173. package/server/mind/meetings.js +1069 -0
  174. package/server/mind/memory-policy.js +48 -0
  175. package/server/mind/memory.js +888 -0
  176. package/server/mind/nature.js +262 -0
  177. package/server/mind/novelty.js +19 -0
  178. package/server/mind/observations.js +84 -0
  179. package/server/mind/own-day.js +324 -0
  180. package/server/mind/own-voice.js +318 -0
  181. package/server/mind/periods.js +118 -0
  182. package/server/mind/reading.js +120 -0
  183. package/server/mind/salience.js +201 -0
  184. package/server/mind/schema.js +562 -0
  185. package/server/mind/self.js +484 -0
  186. package/server/mind/senses.js +53 -0
  187. package/server/mind/thoughts.js +274 -0
  188. package/server/mind/time/activity-clock.js +196 -0
  189. package/server/mind/time/agenda.js +242 -0
  190. package/server/mind/time/api.js +200 -0
  191. package/server/mind/time/attention.js +34 -0
  192. package/server/mind/time/availability.js +69 -0
  193. package/server/mind/time/commitment-review.js +198 -0
  194. package/server/mind/time/delivery-links.js +130 -0
  195. package/server/mind/time/executor.js +512 -0
  196. package/server/mind/time/experiences.js +128 -0
  197. package/server/mind/time/games.js +562 -0
  198. package/server/mind/time/index.js +739 -0
  199. package/server/mind/time/intent.js +166 -0
  200. package/server/mind/time/kinds.js +171 -0
  201. package/server/mind/time/model-material.js +77 -0
  202. package/server/mind/time/plugin-activity.js +207 -0
  203. package/server/mind/time/presentation.js +51 -0
  204. package/server/mind/time/schedule.js +83 -0
  205. package/server/mind/time/schema.js +84 -0
  206. package/server/mind/time/search.js +383 -0
  207. package/server/mind/time/sharing.js +472 -0
  208. package/server/mind/time/task-links.js +311 -0
  209. package/server/mind/time/tasks.js +587 -0
  210. package/server/mind/time/works.js +432 -0
  211. package/server/mind/traits.js +191 -0
  212. package/server/mind/util.js +110 -0
  213. package/server/mind/view.js +273 -0
  214. package/server/paths.js +102 -0
  215. package/server/plugins/api.js +204 -0
  216. package/server/plugins/host.js +967 -0
  217. package/server/plugins/install.js +295 -0
  218. package/server/plugins/manifest.js +201 -0
  219. package/server/plugins/permissions.js +42 -0
  220. package/server/plugins/runner/guard.mjs +36 -0
  221. package/server/plugins/runner/worker.mjs +250 -0
  222. package/server/plugins/schema.js +91 -0
  223. package/server/plugins/zip.js +112 -0
  224. package/server/startup-lock.js +118 -0
  225. package/server/storage/archive.js +241 -0
  226. package/server/storage/backup-cleanup-worker.js +8 -0
  227. package/server/storage/backup-cleanup.js +414 -0
  228. package/server/storage/backup-lock.js +76 -0
  229. package/server/storage/backup-scheduler.js +131 -0
  230. package/server/storage/store.js +199 -0
  231. package/server/studio/display-names.js +138 -0
  232. package/server/studio/management.js +102 -0
  233. package/server/studio/readiness.js +190 -0
  234. package/server/studio/settings.js +236 -0
  235. package/server/version.js +10 -0
@@ -0,0 +1,305 @@
1
+ import { isNameCall } from "../../core/name-call.js";
2
+ import { agoLabel } from "../clock.js";
3
+ import { isPrivateSession } from "../memory.js";
4
+ import { messageSeqs, parse, text } from "../util.js";
5
+ const MINUTE = 60000;
6
+
7
+ // Owns this part of the lifecycle; the facade keeps the shared runtime state.
8
+ export class LifePresence {
9
+ constructor(owner) {
10
+ this.owner = owner;
11
+ }
12
+ async considerPresence(now = this.owner.now()) {
13
+ const s = this.owner.settings();
14
+ if (!s.proactive || !this.owner.online()) return null;
15
+ if (this.owner.phase(now).key === "asleep") return null;
16
+ if (!this.owner.mind.budget.allows("inner", now)) return null;
17
+ const last = this.owner.repo.config("own-voice", {}).attemptedAt;
18
+ if (last && now - last < s.initiativeIntervalMinutes * MINUTE) return null;
19
+ const contacts = this.owner.initiative.contacts(now, { ready: true });
20
+ if (!contacts.length) return null;
21
+ const resting = this.owner.ownVoice.resting(now);
22
+ this.owner.repo.saveConfig("own-voice", {
23
+ ...this.owner.repo.config("own-voice", {}),
24
+ attemptedAt: now,
25
+ });
26
+ if (resting) return { status: "presence-silent", reason: resting };
27
+ this.owner.busy = true;
28
+ let formed;
29
+ try {
30
+ formed = await this.owner.ownVoice.form(now);
31
+ } finally {
32
+ this.owner.busy = false;
33
+ }
34
+ if (!formed.note?.share)
35
+ return {
36
+ status:
37
+ formed.status === "error" ? "presence-error" : "presence-silent",
38
+ reason: formed.reason,
39
+ };
40
+ // The thought already exists. Only now do we choose where it might fit;
41
+ // someone else's last question cannot become its reason for existing.
42
+ const contact = this.owner.initiative.chooseContact(
43
+ formed.note,
44
+ contacts,
45
+ this.owner.now(),
46
+ );
47
+ if (!contact)
48
+ return {
49
+ status: "presence-silent",
50
+ reason: "此刻没有适合分享这个念头的地方",
51
+ };
52
+ this.owner.mind.thoughts.planOutreach(formed.note.id, {
53
+ session: contact.session,
54
+ words: formed.note.words,
55
+ reason: formed.note.reason,
56
+ time: now,
57
+ });
58
+ const result = await this.owner.reachOut(this.owner.now());
59
+ return result
60
+ ? {
61
+ ...result,
62
+ status:
63
+ result.status === "outreach-declined"
64
+ ? "presence-silent"
65
+ : result.status.replace(/^outreach-/, "presence-"),
66
+ }
67
+ : {
68
+ status: "presence-deferred",
69
+ reason: "念头已留下,等待合适的交流空隙",
70
+ session: contact.session,
71
+ };
72
+ }
73
+ presenceCooling(session, now, s) {
74
+ const tried = this.owner.repo.config("presence", {});
75
+ return !!(
76
+ tried[session] &&
77
+ now - tried[session] < s.initiativeIntervalMinutes * MINUTE
78
+ );
79
+ }
80
+ markPresence(session, now) {
81
+ const tried = { ...this.owner.repo.config("presence", {}), [session]: now };
82
+ this.owner.repo.saveConfig("presence", tried);
83
+ }
84
+ wasHere(session, now, names, aside = null) {
85
+ const skipped =
86
+ aside ||
87
+ new Set(
88
+ this.owner.db
89
+ .prepare("SELECT seq FROM mind_unlived")
90
+ .all()
91
+ .map((row) => row.seq),
92
+ );
93
+ const lived = this.owner.db
94
+ .prepare(
95
+ "SELECT 1 FROM core_events WHERE session_id=? AND seq NOT IN (SELECT seq FROM mind_unlived) AND COALESCE(json_extract(payload,'$.simulated'),0)=0 AND (role='assistant' OR ?=1) LIMIT 1",
96
+ )
97
+ .get(session, +isPrivateSession(session));
98
+ if (lived) return true;
99
+ if (
100
+ this.owner.db
101
+ .prepare("SELECT 1 FROM mind_meetings WHERE session_id=? LIMIT 1")
102
+ .get(session)
103
+ )
104
+ return true;
105
+ return this.owner.repo
106
+ .recentEvents(session, 80, { simulated: false })
107
+ .some((m) => {
108
+ if (skipped.has(m.seq)) return false;
109
+ if (m.role === "assistant") return true;
110
+ const mentions = (m.mentions || []).map(String);
111
+ if (m.accountId && mentions.includes(String(m.accountId))) return true;
112
+ if (String(m.text || "").includes("@我")) return true;
113
+ return isNameCall(m.text, names);
114
+ });
115
+ }
116
+ async reachOut(now = this.owner.now()) {
117
+ const s = this.owner.settings();
118
+ if (!s.proactive || !this.owner.online()) return null;
119
+ if (this.owner.phase(now).key === "asleep") return null;
120
+ for (const t of this.owner.mind.thoughts.dueOutreach(now)) {
121
+ const session = t.outreach_session;
122
+ const audience = this.owner.initiative.audience(t, session, now);
123
+ if (!audience.allowed) {
124
+ this.owner.mind.thoughts.setOutreach(t.id, "skipped", audience.reason);
125
+ continue;
126
+ }
127
+ if (this.owner.initiative.recentlyToldAnotherGroup(t, now)) {
128
+ this.owner.mind.thoughts.setOutreach(t.id, "declined");
129
+ continue;
130
+ }
131
+ const block = this.owner.outreachBlocked(session, now, s);
132
+ if (block) {
133
+ if (block.permanent)
134
+ this.owner.mind.thoughts.setOutreach(t.id, "skipped");
135
+ else
136
+ this.owner.mind.thoughts.deferOutreach(
137
+ t.id,
138
+ block.reason,
139
+ block.retryAt,
140
+ );
141
+ continue;
142
+ }
143
+ this.owner.mind.thoughts.setOutreach(t.id, "sending");
144
+ this.owner.busy = true;
145
+ try {
146
+ const returned = this.owner.returnedSince(t, now);
147
+ const trace = await this.owner.chat.initiate(session, {
148
+ type: t.kind === "expression" ? "presence" : "outreach",
149
+ data: {
150
+ thought: t.content,
151
+ planned: t.outreach,
152
+ wantedBecause: t.outreach_reason,
153
+ expression: {
154
+ ref: `t:${t.id}`,
155
+ origin:
156
+ t.kind === "expression" ? "self_expression" : "earlier_wish",
157
+ formedAt: t.created,
158
+ thought: t.content,
159
+ words: t.outreach_draft?.length ? t.outreach_draft : [t.outreach],
160
+ reason: t.outreach_reason,
161
+ sources: t.sources,
162
+ audience,
163
+ sourceMaterial: this.owner.initiative.sourceMaterial(t, now),
164
+ },
165
+ initiative: this.owner.initiative.context(session, now),
166
+ ...(returned.length ? { returned } : {}),
167
+ },
168
+ });
169
+ if (!trace) {
170
+ this.owner.mind.thoughts.deferOutreach(
171
+ t.id,
172
+ "对话忙着,稍后重新看看",
173
+ now + MINUTE,
174
+ );
175
+ return {
176
+ status: "outreach-deferred",
177
+ reason: "愿望保留,等待对话空隙",
178
+ session,
179
+ };
180
+ }
181
+ this.owner.markPresence(session, now);
182
+ const uncertain =
183
+ trace &&
184
+ this.owner.db
185
+ .prepare(
186
+ "SELECT 1 FROM core_outbox WHERE trace_id=? AND status='uncertain'",
187
+ )
188
+ .get(trace.id);
189
+ if (
190
+ ["error", "stale"].includes(trace.status) &&
191
+ !trace.sent?.length &&
192
+ !uncertain &&
193
+ !this.owner.db
194
+ .prepare(
195
+ "SELECT 1 FROM core_outbox WHERE trace_id=? AND status IN ('sending','confirmed') LIMIT 1",
196
+ )
197
+ .get(trace.id)
198
+ ) {
199
+ this.owner.mind.thoughts.deferOutreach(
200
+ t.id,
201
+ trace.reason || "调用失败,稍后重新决定",
202
+ this.owner.now() + (trace.status === "stale" ? 1 : 15) * MINUTE,
203
+ );
204
+ return {
205
+ status: "outreach-deferred",
206
+ reason: "尚未发送,想说的话仍保留",
207
+ session,
208
+ };
209
+ }
210
+ const status = uncertain
211
+ ? "uncertain"
212
+ : trace?.status === "sent" || trace?.sent?.length
213
+ ? "sent"
214
+ : trace?.status === "silent"
215
+ ? "declined"
216
+ : "cancelled";
217
+ this.owner.mind.thoughts.setOutreach(
218
+ t.id,
219
+ status,
220
+ status === "declined" ? trace?.reason || "此刻不想聊这件事" : "",
221
+ );
222
+ return {
223
+ status: `outreach-${status}`,
224
+ reason: trace?.reason || "没能联系",
225
+ session,
226
+ };
227
+ } catch {
228
+ // Retry only when no delivery could have happened. An ambiguous
229
+ // network acknowledgement must never produce duplicate QQ messages.
230
+ const maybeSent = this.owner.db
231
+ .prepare(
232
+ "SELECT 1 FROM core_outbox WHERE session_id=? AND time>=? AND status IN ('sending','confirmed','uncertain') LIMIT 1",
233
+ )
234
+ .get(session, now);
235
+ if (!maybeSent) {
236
+ this.owner.mind.thoughts.deferOutreach(
237
+ t.id,
238
+ "调用未完成,稍后重新决定",
239
+ now + 15 * MINUTE,
240
+ );
241
+ return {
242
+ status: "outreach-deferred",
243
+ reason: "调用未完成,想说的话仍保留",
244
+ session,
245
+ };
246
+ }
247
+ this.owner.mind.thoughts.setOutreach(t.id, "uncertain");
248
+ return {
249
+ status: "outreach-uncertain",
250
+ reason: "主动联系的投递不确定,不再重试",
251
+ };
252
+ } finally {
253
+ this.owner.busy = false;
254
+ }
255
+ }
256
+ return null;
257
+ }
258
+ returnedSince(thought, now) {
259
+ const sources = parse(thought?.sources, []);
260
+ const ids = new Set();
261
+ const meetingIds = sources
262
+ .filter((ref) => String(ref).startsWith("g:"))
263
+ .map((ref) => String(ref).slice(2));
264
+ if (meetingIds.length) {
265
+ const rows = this.owner.db
266
+ .prepare(
267
+ `SELECT people, will_people FROM mind_meetings WHERE id IN (${meetingIds.map(() => "?").join(",")})`,
268
+ )
269
+ .all(...meetingIds);
270
+ for (const row of rows) {
271
+ const named = parse(row.will_people, []);
272
+ const who = named.length ? named : parse(row.people, []);
273
+ for (const id of who) ids.add(String(id));
274
+ }
275
+ }
276
+ const seqs = messageSeqs(sources);
277
+ if (seqs.length) {
278
+ const events = this.owner.db
279
+ .prepare(
280
+ `SELECT payload, role FROM core_events WHERE seq IN (${seqs.map(() => "?").join(",")})`,
281
+ )
282
+ .all(...seqs);
283
+ for (const row of events) {
284
+ if (row.role === "assistant") continue;
285
+ const payload = parse(row.payload, {});
286
+ if (payload.userId && payload.userId !== "bot")
287
+ ids.add(String(payload.userId));
288
+ }
289
+ }
290
+ const out = [];
291
+ for (const id of ids) {
292
+ const person = this.owner.mind.bonds.person(id, now);
293
+ if (!person?.seenAt || person.seenAt <= thought.created) continue;
294
+ out.push({
295
+ name: person.name || id,
296
+ since: agoLabel(now - person.seenAt),
297
+ });
298
+ if (out.length >= 3) break;
299
+ }
300
+ return out;
301
+ }
302
+ outreachBlocked(session, now, s) {
303
+ return this.owner.initiative.blocked(session, now, s);
304
+ }
305
+ }