@ssamurias/dsh-fish 0.9.1 → 0.9.3
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/AGENTS.md +27 -2
- package/CHANGELOG.md +94 -0
- package/README.md +8 -2
- package/RELEASE-NOTES-0.9.2.md +46 -0
- package/RELEASE-NOTES-0.9.3.md +91 -0
- package/RELEASE.md +6 -5
- package/lib/absence.js +41 -27
- package/lib/anchor.js +57 -0
- package/lib/ask.js +73 -25
- package/lib/askinchat.js +83 -1
- package/lib/browse.js +8 -0
- package/lib/capture.js +19 -1
- package/lib/cardanswer.js +84 -0
- package/lib/client.js +90 -13
- package/lib/comment.js +189 -30
- package/lib/deliver.js +46 -0
- package/lib/derive.js +14 -11
- package/lib/desk.js +33 -1
- package/lib/drive.js +19 -3
- package/lib/events.js +25 -1
- package/lib/gapwall.js +73 -0
- package/lib/generator.js +2 -1
- package/lib/grant.js +18 -2
- package/lib/home.js +55 -7
- package/lib/identity.js +111 -14
- package/lib/index.js +104 -54
- package/lib/keep.js +21 -9
- package/lib/keepsake.js +20 -13
- package/lib/note.js +255 -20
- package/lib/observe.js +9 -5
- package/lib/persona.js +47 -33
- package/lib/proactive.js +45 -16
- package/lib/quality.js +32 -8
- package/lib/recall.js +33 -2
- package/lib/redact.js +70 -0
- package/lib/restrict.js +40 -2
- package/lib/rules.js +1 -1
- package/lib/say.js +79 -13
- package/lib/scheduler.js +107 -41
- package/lib/schema.js +18 -0
- package/lib/selfdoc.js +19 -7
- package/lib/speak.js +16 -11
- package/lib/status.js +25 -3
- package/lib/turns.js +42 -1
- package/lib/use.js +212 -0
- package/lib/wish.js +147 -0
- package/package.json +3 -2
package/lib/note.js
CHANGED
|
@@ -9,10 +9,14 @@
|
|
|
9
9
|
* 所以这不是闹钟(到点叫我),是一张**桌子**:一天看一次,睁眼知道欠几天。
|
|
10
10
|
* 不做通知、不做界面卡片、不做红点——她要的是"睁眼时桌上有什么"。
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* 0.8.8 的乙档(用户 m01465)只做了两件事:醒来摆出到期的、her_note 能写能划掉。
|
|
13
|
+
* 2026-11-06(issue #53)补齐当初说好「跑一段再说」的三样,另修一个死锁:
|
|
14
|
+
* 1. 名额只算 open。原来 200 张名额连划掉的一起算,划满之后再也记不进(死锁);
|
|
15
|
+
* 2. 回翻(list):open / due / done / dropped / all,带 offset 往后翻;
|
|
16
|
+
* 3. 放下(drop,reason 必填)——issue #22 里设计好的第三种状态,数据层一直在等它。
|
|
17
|
+
* 「没日子的只提一次」落在 add 的回执里(写下的那一下回一句原文),**不进唤醒段**:
|
|
18
|
+
* 没日子就没有账可算,抬到桌上就变成催了。这跟「以后都不上桌」不冲突。
|
|
19
|
+
* update(改一句)暂不做:直接覆盖文本会把她改过什么抹掉,要做得先定好留痕的字段。
|
|
16
20
|
*
|
|
17
21
|
* 三条硬约束(从 letter.js 沿下来的同一条线):
|
|
18
22
|
* 1. **不审批**。记一张纸条不是改自己,不设关卡。
|
|
@@ -39,6 +43,10 @@ export const NOTES_MAX = 200
|
|
|
39
43
|
export const NOTE_TEXT_MAX = 200
|
|
40
44
|
/** 醒来那一段最多列几张,剩下的只报数。 */
|
|
41
45
|
export const NOTE_SHOW_MAX = 5
|
|
46
|
+
/** 一次 list 最多列几行。往后翻靠 offset,不一次把两百张全倒出来。 */
|
|
47
|
+
export const NOTE_LIST_MAX = 20
|
|
48
|
+
/** list 能看的几类:open=还没关掉的,due=写了日子的,done/dropped=关掉的,all=全要。 */
|
|
49
|
+
export const NOTE_FILTERS = ['open', 'due', 'done', 'dropped', 'all']
|
|
42
50
|
|
|
43
51
|
export function notesPath(root = rootDir()) {
|
|
44
52
|
return join(root, NOTES_FILE)
|
|
@@ -73,6 +81,8 @@ export function normalizeNote(raw) {
|
|
|
73
81
|
status,
|
|
74
82
|
reason: raw.reason == null ? null : String(raw.reason).slice(0, 200),
|
|
75
83
|
closedAt: raw.closedAt == null ? null : String(raw.closedAt),
|
|
84
|
+
// 只有 update 会写;其余时候是 null。留着它,改过的痕迹至少还在时间上
|
|
85
|
+
revisedAt: raw.revisedAt == null ? null : String(raw.revisedAt),
|
|
76
86
|
}
|
|
77
87
|
}
|
|
78
88
|
|
|
@@ -193,8 +203,11 @@ export async function addNote(root, { text, due } = {}) {
|
|
|
193
203
|
|
|
194
204
|
try {
|
|
195
205
|
const notes = await readNotes(root)
|
|
196
|
-
|
|
197
|
-
|
|
206
|
+
// 名额只算还压在桌上的。划掉/放下的不占位——否则划满两百张之后再也记不进,
|
|
207
|
+
// 而拒绝的话还说「先划掉几张」,桌上根本没有能划的(issue #53 的死锁)。
|
|
208
|
+
const openCount = notes.filter((n) => n.status === 'open').length
|
|
209
|
+
if (openCount >= NOTES_MAX) {
|
|
210
|
+
return { ok: false, id: '', note: '桌上堆满两百张没动的了,先划掉几张,或者放下几张(放下要写一句为什么)再说。' }
|
|
198
211
|
}
|
|
199
212
|
const note = normalizeNote({
|
|
200
213
|
id: newNoteId(),
|
|
@@ -205,7 +218,7 @@ export async function addNote(root, { text, due } = {}) {
|
|
|
205
218
|
})
|
|
206
219
|
notes.push(note)
|
|
207
220
|
await writeNotes(root, notes)
|
|
208
|
-
return { ok: true, id: note.id, note: '已记。' }
|
|
221
|
+
return { ok: true, id: note.id, note: '已记。', echo: note.text }
|
|
209
222
|
} catch (err) {
|
|
210
223
|
return { ok: false, id: '', note: '这张没记下来。' }
|
|
211
224
|
}
|
|
@@ -247,6 +260,179 @@ export async function closeNote(root, { id, text } = {}) {
|
|
|
247
260
|
}
|
|
248
261
|
}
|
|
249
262
|
|
|
263
|
+
// ---- 回翻、改一句、放下(issue #53,2026-11-06)----
|
|
264
|
+
// 三件事共用一套「按她看得见的东西找条」:她手里没有 id,只有自己写过的那句话。
|
|
265
|
+
// 所以对外只认原话;id 这条路留着给代码用,不进说明书。
|
|
266
|
+
|
|
267
|
+
/** list 里的一行。关掉的那几种带上关掉的日期,放下的还要带上那句为什么。 */
|
|
268
|
+
function listLine(n) {
|
|
269
|
+
const md = (iso) => {
|
|
270
|
+
const d = String(iso ?? '').slice(0, 10)
|
|
271
|
+
return isDue(d) ? `${d.slice(5, 7)}-${d.slice(8, 10)}` : ''
|
|
272
|
+
}
|
|
273
|
+
if (n.status === 'done') {
|
|
274
|
+
const at = md(n.closedAt)
|
|
275
|
+
return `✓ ${n.text}${at ? `(${at} 划掉)` : ''}`
|
|
276
|
+
}
|
|
277
|
+
if (n.status === 'dropped') {
|
|
278
|
+
const at = md(n.closedAt)
|
|
279
|
+
return `✗ ${n.text}${at ? `(${at} 放下)` : ''}${n.reason ? `—— ${n.reason}` : ''}`
|
|
280
|
+
}
|
|
281
|
+
return n.due ? `${n.due.slice(5)} ${n.text}` : `(没日子)${n.text}`
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** 摊在桌上的先按到期日(没日子的排最后),关掉的按关掉的时间倒序。 */
|
|
285
|
+
function sortForList(notes) {
|
|
286
|
+
const open = notes.filter((n) => n.status === 'open')
|
|
287
|
+
const closed = notes.filter((n) => n.status !== 'open')
|
|
288
|
+
open.sort((a, b) => {
|
|
289
|
+
if (a.due && b.due && a.due !== b.due) return a.due < b.due ? -1 : 1
|
|
290
|
+
if (a.due && !b.due) return -1
|
|
291
|
+
if (!a.due && b.due) return 1
|
|
292
|
+
return String(a.created).localeCompare(String(b.created))
|
|
293
|
+
})
|
|
294
|
+
closed.sort(
|
|
295
|
+
(a, b) =>
|
|
296
|
+
String(b.closedAt ?? '').localeCompare(String(a.closedAt ?? '')) ||
|
|
297
|
+
String(b.created).localeCompare(String(a.created)),
|
|
298
|
+
)
|
|
299
|
+
return [...open, ...closed]
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/** 按 filter 挑出这一类(纯函数,可测)。filter 不认识就当 open。 */
|
|
303
|
+
export function filterNotes(notes, filter = 'open') {
|
|
304
|
+
const f = NOTE_FILTERS.includes(String(filter)) ? String(filter) : 'open'
|
|
305
|
+
const list = (notes ?? []).filter(Boolean)
|
|
306
|
+
if (f === 'all') return sortForList(list)
|
|
307
|
+
if (f === 'open') return sortForList(list.filter((n) => n.status === 'open'))
|
|
308
|
+
if (f === 'due') return sortForList(list.filter((n) => n.status === 'open' && isDue(n.due)))
|
|
309
|
+
return sortForList(list.filter((n) => n.status === f))
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const EMPTY_LIST = {
|
|
313
|
+
open: '桌上没有还没划掉的。',
|
|
314
|
+
due: '没有哪张写着日子。',
|
|
315
|
+
done: '还没有划掉的。',
|
|
316
|
+
dropped: '还没有放下的。',
|
|
317
|
+
all: '桌上什么都没有。',
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* 翻桌子(reading 用,改不了任何东西)。
|
|
322
|
+
*
|
|
323
|
+
* 只列字,不报总数——「你还有 3 条」这种句式谢绝。列不下的写一句「后面还有」,
|
|
324
|
+
* 往后翻用 offset。没日子那种的照旧标出来:它在桌上,但醒来时不会自己出现。
|
|
325
|
+
* @returns {Promise<{ok:boolean, text:string, note:string}>}
|
|
326
|
+
*/
|
|
327
|
+
export async function listNotes(root, { filter = 'open', offset = 0, limit = NOTE_LIST_MAX } = {}) {
|
|
328
|
+
const f = NOTE_FILTERS.includes(String(filter)) ? String(filter) : 'open'
|
|
329
|
+
const off = Number(offset) > 0 ? Math.trunc(Number(offset)) : 0
|
|
330
|
+
const lim = Number(limit) > 0 ? Math.min(Math.trunc(Number(limit)), NOTE_LIST_MAX) : NOTE_LIST_MAX
|
|
331
|
+
try {
|
|
332
|
+
const all = filterNotes(await readNotes(root), f)
|
|
333
|
+
if (!all.length) return { ok: true, text: EMPTY_LIST[f], note: '' }
|
|
334
|
+
const page = all.slice(off, off + lim)
|
|
335
|
+
if (!page.length) return { ok: true, text: '后面没有了。', note: '' }
|
|
336
|
+
const lines = page.map(listLine)
|
|
337
|
+
const rest = all.length - (off + page.length)
|
|
338
|
+
if (off > 0) lines.unshift(`(从第${cnNum(off + 1)}张接着看)`)
|
|
339
|
+
if (rest > 0) lines.push('……后面还有,没一次列完(offset 往后翻)。')
|
|
340
|
+
return { ok: true, text: lines.join('\n'), note: '' }
|
|
341
|
+
} catch (err) {
|
|
342
|
+
return { ok: false, text: '', note: '这会儿翻不开,等一下再试。' }
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/** 按 id 或原话找一张;先 id,再原话(没关掉的优先,其次已划掉的)。 */
|
|
347
|
+
function locate(notes, { id, text } = {}) {
|
|
348
|
+
const key = String(id ?? '').trim()
|
|
349
|
+
if (key) {
|
|
350
|
+
const byId = notes.find((n) => n.id === key)
|
|
351
|
+
if (byId) return byId
|
|
352
|
+
}
|
|
353
|
+
const body = String(text ?? '').replace(/\s+/g, ' ').trim()
|
|
354
|
+
return body ? findByText(notes, body) ?? null : null
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* 改一句(写错了)。永不抛。
|
|
359
|
+
*
|
|
360
|
+
* 只改没关掉的那张:划掉/放下了的改不了——回翻里留着当时那句话,改它等于改历史。
|
|
361
|
+
* `due` 不给就不动日子,给空串是去掉日子,给日期是改期。
|
|
362
|
+
* @returns {Promise<{ok:boolean, id:string, note:string, echo?:string}>}
|
|
363
|
+
*/
|
|
364
|
+
export async function updateNote(root, { id, text, newText, due } = {}) {
|
|
365
|
+
const key = String(id ?? '').trim()
|
|
366
|
+
const body = String(text ?? '').replace(/\s+/g, ' ').trim()
|
|
367
|
+
const next = String(newText ?? '').replace(/\s+/g, ' ').trim()
|
|
368
|
+
if (!key && !body) return { ok: false, id: '', note: '要改哪一张?把当时记的那句话告诉我。' }
|
|
369
|
+
if (!next) return { ok: false, id: '', note: '改成什么?把新的那句话告诉我。' }
|
|
370
|
+
if (next.length > NOTE_TEXT_MAX) {
|
|
371
|
+
return { ok: false, id: '', note: `太长了,压到 ${NOTE_TEXT_MAX} 字以内。` }
|
|
372
|
+
}
|
|
373
|
+
const day = due === undefined ? undefined : String(due ?? '').trim()
|
|
374
|
+
if (day !== undefined && day && !isDue(day)) {
|
|
375
|
+
return { ok: false, id: '', note: '日子要写成 2026-11-05 这样,或者给空串把日子去掉。' }
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
const notes = await readNotes(root)
|
|
379
|
+
const hit = locate(notes, { id: key, text: body })
|
|
380
|
+
if (!hit) {
|
|
381
|
+
return { ok: false, id: '', note: '桌上没有这一张。要和当时记的那句话一字不差。' }
|
|
382
|
+
}
|
|
383
|
+
if (hit.status === 'done') return { ok: false, id: '', note: '这张已经划掉了,改不了。' }
|
|
384
|
+
if (hit.status === 'dropped') return { ok: false, id: '', note: '这张已经放下了,改不了。' }
|
|
385
|
+
hit.text = next.slice(0, NOTE_TEXT_MAX)
|
|
386
|
+
if (day !== undefined) hit.due = day
|
|
387
|
+
hit.revisedAt = new Date().toISOString()
|
|
388
|
+
await writeNotes(root, notes)
|
|
389
|
+
return { ok: true, id: hit.id, note: '改好了。', echo: hit.text }
|
|
390
|
+
} catch (err) {
|
|
391
|
+
return { ok: false, id: '', note: '这会儿写不进去,等一下再试。' }
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* 放下(决定不做,并写一句为什么)。永不抛。
|
|
397
|
+
*
|
|
398
|
+
* 跟划掉一样只是改状态、条目一条不删;差别只在多一句 reason,那句必须她自己写。
|
|
399
|
+
* 不检查写得好不好——只要求非空,不然就跟删掉没区别了。
|
|
400
|
+
* @returns {Promise<{ok:boolean, id:string, note:string}>}
|
|
401
|
+
*/
|
|
402
|
+
export async function dropNote(root, { id, text, reason } = {}) {
|
|
403
|
+
const key = String(id ?? '').trim()
|
|
404
|
+
const body = String(text ?? '').replace(/\s+/g, ' ').trim()
|
|
405
|
+
const why = String(reason ?? '').replace(/\s+/g, ' ').trim()
|
|
406
|
+
if (!key && !body) return { ok: false, id: '', note: '要放下哪一张?把当时记的那句话告诉我。' }
|
|
407
|
+
if (!why) return { ok: false, id: '', note: '放下还要说一句为什么——不然就跟删掉没区别了。' }
|
|
408
|
+
if (why.length > NOTE_TEXT_MAX) {
|
|
409
|
+
return { ok: false, id: '', note: `原因太长了,压到 ${NOTE_TEXT_MAX} 字以内。` }
|
|
410
|
+
}
|
|
411
|
+
try {
|
|
412
|
+
const notes = await readNotes(root)
|
|
413
|
+
const hit = locate(notes, { id: key, text: body })
|
|
414
|
+
if (!hit) {
|
|
415
|
+
return { ok: false, id: '', note: '桌上没有这一张。要和当时记的那句话一字不差。' }
|
|
416
|
+
}
|
|
417
|
+
if (hit.status === 'dropped') return { ok: true, id: hit.id, note: '之前就放下了。' }
|
|
418
|
+
if (hit.status === 'done') return { ok: false, id: '', note: '这张已经划掉了,不用再放下。' }
|
|
419
|
+
hit.status = 'dropped'
|
|
420
|
+
hit.reason = why.slice(0, 200)
|
|
421
|
+
hit.closedAt = new Date().toISOString()
|
|
422
|
+
await writeNotes(root, notes)
|
|
423
|
+
return { ok: true, id: hit.id, note: '放下了。' }
|
|
424
|
+
} catch (err) {
|
|
425
|
+
return { ok: false, id: '', note: '这会儿写不进去,等一下再试。' }
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** 这一次是哪件事。没写 action 的按老路走:status=done 或只给 id 是划掉,其余是记一张。 */
|
|
430
|
+
export function noteAction(args = {}) {
|
|
431
|
+
const a = String(args?.action ?? '').trim()
|
|
432
|
+
if (['add', 'list', 'update', 'drop', 'done'].includes(a)) return a
|
|
433
|
+
return isClosing(args) ? 'done' : 'add'
|
|
434
|
+
}
|
|
435
|
+
|
|
250
436
|
/** 这一次是划掉还是记。execute 和 render 用同一个判据,回执才不会和实际做的事对不上。 */
|
|
251
437
|
const isClosing = (args) =>
|
|
252
438
|
String(args?.status ?? '').trim() === 'done' ||
|
|
@@ -259,42 +445,91 @@ export function registerNote(ctx, deps = {}) {
|
|
|
259
445
|
defineTool({
|
|
260
446
|
name: 'her_note',
|
|
261
447
|
description:
|
|
262
|
-
'
|
|
263
|
-
'
|
|
448
|
+
'醒来时先看的那张桌子:记一张还没做的事、看看桌上都有什么、划掉做完的、或者放下决定不做的。' +
|
|
449
|
+
'记的时候给 text(和日子 due,可以不给);划掉把当时记的那句话原样放进 text,再加 status:"done";' +
|
|
450
|
+
'看桌上有什么给 action:"list"(filter 可以选 open / due / done / dropped / all,列不完用 offset 往后翻);' +
|
|
451
|
+
'改一句给 action:"update"(text 指认原来那句,newText 写新的);' +
|
|
452
|
+
'放下给 action:"drop"(同样用 text 指认,reason 写一句为什么)。',
|
|
264
453
|
parameters: toJsonSchema({
|
|
454
|
+
action: {
|
|
455
|
+
type: 'string',
|
|
456
|
+
enum: ['add', 'list', 'update', 'drop', 'done'],
|
|
457
|
+
description: '要做哪件事:记一张(add,不给就是这个)、翻桌子(list)、改一句(update)、放下(drop)、划掉(done)。',
|
|
458
|
+
},
|
|
265
459
|
text: {
|
|
266
460
|
type: 'string',
|
|
267
|
-
description: '
|
|
461
|
+
description: '要记下来的那件事,一句话。划掉、改、放下的时候写当时记的那句话,要一字不差。',
|
|
268
462
|
},
|
|
269
463
|
due: {
|
|
270
464
|
type: 'string',
|
|
271
|
-
description:
|
|
465
|
+
description:
|
|
466
|
+
'哪天该做,写成 2026-11-05。不写就不会在醒来时出现,只能自己 list 看;改一句时给空串是把日子去掉。',
|
|
272
467
|
},
|
|
273
|
-
id: { type: 'string', description: '
|
|
468
|
+
id: { type: 'string', description: '这张纸条的编号。手里没有就不用给,给 text 就行。' },
|
|
274
469
|
status: { type: 'string', enum: ['done'], description: '划掉的时候给 done。' },
|
|
470
|
+
filter: {
|
|
471
|
+
type: 'string',
|
|
472
|
+
enum: ['open', 'due', 'done', 'dropped', 'all'],
|
|
473
|
+
description: 'list 时看哪一类,默认 open(还没关掉的)。',
|
|
474
|
+
},
|
|
475
|
+
offset: { type: 'integer', description: 'list 时从第几张往后翻,默认从最前面。' },
|
|
476
|
+
limit: { type: 'integer', description: 'list 时一次最多列几张,默认 20。' },
|
|
477
|
+
newText: { type: 'string', description: 'update 时改成哪句话。' },
|
|
478
|
+
reason: { type: 'string', description: 'drop 时为什么不做了,必填。' },
|
|
275
479
|
}),
|
|
276
480
|
output: {
|
|
277
481
|
schema: {
|
|
278
482
|
type: 'object',
|
|
279
|
-
properties: {
|
|
483
|
+
properties: {
|
|
484
|
+
ok: { type: 'boolean' },
|
|
485
|
+
id: { type: 'string' },
|
|
486
|
+
note: { type: 'string' },
|
|
487
|
+
echo: { type: 'string' },
|
|
488
|
+
text: { type: 'string' },
|
|
489
|
+
},
|
|
280
490
|
required: ['ok', 'note'],
|
|
281
491
|
},
|
|
282
492
|
// 和 her_leave 同一条规矩:只回方括号标记,不写完整句子、不带"还剩几张"。
|
|
283
493
|
// 划掉不能沿用写入的字:读到「记下了」,她会以为桌上又多了一张。
|
|
494
|
+
// list 例外:它列的本来就是字,不是回执。
|
|
284
495
|
render: (args, out) => {
|
|
285
|
-
const
|
|
286
|
-
|
|
496
|
+
const act = noteAction(args)
|
|
497
|
+
if (act === 'list') {
|
|
498
|
+
return [{ type: 'text', text: out.ok ? out.text : `[没列出来:${out.note}]` }]
|
|
499
|
+
}
|
|
500
|
+
if (act === 'update') {
|
|
501
|
+
return [{ type: 'text', text: out.ok ? '[改好了]' : `[没改:${out.note}]` }]
|
|
502
|
+
}
|
|
503
|
+
if (act === 'drop') {
|
|
504
|
+
return [{ type: 'text', text: out.ok ? '[放下了]' : `[没放下:${out.note}]` }]
|
|
505
|
+
}
|
|
506
|
+
if (act === 'done') {
|
|
507
|
+
return [{ type: 'text', text: out.ok ? '[划掉了]' : `[没划掉:${out.note}]` }]
|
|
508
|
+
}
|
|
509
|
+
if (!out.ok) return [{ type: 'text', text: `[没记下:${out.note}]` }]
|
|
510
|
+
// 没日子的那张以后不会上桌,所以它只在这里被提这一次——这不是提醒,是入账凭证。
|
|
511
|
+
const tail = String(args?.due ?? '').trim() ? '' : '(没写日子,醒来不会出现)'
|
|
512
|
+
return [{ type: 'text', text: `[记下了:${out.echo ?? ''}]${tail}` }]
|
|
287
513
|
},
|
|
288
514
|
},
|
|
289
515
|
async execute(args, exec) {
|
|
290
516
|
const who = guardWrite(deps.tenant, exec, 'her_note')
|
|
291
517
|
if (!who.ok) return { ok: false, id: '', note: who.note }
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
518
|
+
const act = noteAction(args)
|
|
519
|
+
if (act === 'list') {
|
|
520
|
+
const r = await listNotes(root, { filter: args?.filter, offset: args?.offset, limit: args?.limit })
|
|
521
|
+
return { ok: r.ok, id: '', note: r.note, text: r.text }
|
|
522
|
+
}
|
|
523
|
+
if (act === 'update') {
|
|
524
|
+
return updateNote(root, { id: args?.id, text: args?.text, newText: args?.newText, due: args?.due })
|
|
525
|
+
}
|
|
526
|
+
if (act === 'drop') {
|
|
527
|
+
return dropNote(root, { id: args?.id, text: args?.text, reason: args?.reason })
|
|
528
|
+
}
|
|
529
|
+
if (act === 'done') {
|
|
530
|
+
return closeNote(root, { id: args?.id, text: args?.text })
|
|
295
531
|
}
|
|
296
|
-
|
|
297
|
-
return { ok: r.ok, id: r.id, note: r.note }
|
|
532
|
+
return addNote(root, { text: args?.text, due: args?.due })
|
|
298
533
|
},
|
|
299
534
|
}),
|
|
300
535
|
)
|
package/lib/observe.js
CHANGED
|
@@ -1258,8 +1258,11 @@ export function registerObserve(ctx, deps = {}) {
|
|
|
1258
1258
|
scanEveryMin: Number(c.scanEveryMin ?? 30),
|
|
1259
1259
|
window: c.window ?? [],
|
|
1260
1260
|
mine: herComments(events, dk),
|
|
1261
|
-
waiting: replies.slice(0, 5).map((x) => ({ rpid: x.rpid, bvid: x.bvid, user: x.user, text: x.text })),
|
|
1261
|
+
waiting: replies.slice(0, 5).map((x) => ({ rpid: x.rpid, bvid: x.bvid, user: x.user, text: x.text, fromOwner: x.fromOwner === true })),
|
|
1262
1262
|
waitingCount: replies.length,
|
|
1263
|
+
// 2026-11-07:他的话单算一格。状态行原来只有「有 N 条没回」,
|
|
1264
|
+
// 主人自己回一句也被算成陌生人的存量(用户反馈)。
|
|
1265
|
+
waitingOwnerCount: replies.filter((x) => x.fromOwner === true).length,
|
|
1263
1266
|
})
|
|
1264
1267
|
}],
|
|
1265
1268
|
|
|
@@ -1611,7 +1614,7 @@ export function registerObserve(ctx, deps = {}) {
|
|
|
1611
1614
|
const said = b.yes === true
|
|
1612
1615
|
const r = decideKeepsake(root, { id: String(b.id ?? ''), yes: said })
|
|
1613
1616
|
logLine('info', ['纪念品:', said ? '准了' : '没带', r.item?.rel ?? '', r.item?.id ?? ''], root)
|
|
1614
|
-
return send(res, r.ok ? 200 : 409, { ok: r.ok, item: r.item ?? null, note: r.note })
|
|
1617
|
+
return send(res, r.ok ? 200 : 409, { ok: r.ok, item: r.item ?? null, note: r.note, ...(r.ok ? {} : { error: r.note }) })
|
|
1615
1618
|
}
|
|
1616
1619
|
|
|
1617
1620
|
// 复核上一次被拒收的那趟(0.6.32):文件还原后重新比对,通过就当场收下。
|
|
@@ -1874,7 +1877,8 @@ export function registerObserve(ctx, deps = {}) {
|
|
|
1874
1877
|
reason: b.reason,
|
|
1875
1878
|
via: 'ui',
|
|
1876
1879
|
})
|
|
1877
|
-
|
|
1880
|
+
// jpost 只认 error:别的地方(IM、别的页面)先答过时,要让人看到原因,不是一个 HTTP 409
|
|
1881
|
+
if (!r.ok) return send(res, 409, { ok: false, why: r.why, error: r.why })
|
|
1878
1882
|
logLine('info', ['请求事件:回答', String(b.askId || ''), String(b.verdict || '')], root)
|
|
1879
1883
|
return send(res, 200, { ok: true })
|
|
1880
1884
|
}],
|
|
@@ -2116,7 +2120,7 @@ export function registerObserve(ctx, deps = {}) {
|
|
|
2116
2120
|
// 空白会话在侧栏里是隐形的(宿主 sessionVisible:blank && id !== current → false)。
|
|
2117
2121
|
// 插件用 API 建的会话不会被设成当前会话,所以要让她先动一轮,侧栏才看得见。
|
|
2118
2122
|
const woke = fresh.ok
|
|
2119
|
-
? await wakeIfBlank(ctx, { sessionId: fresh.sessionId, root, log: (m) => console.log('[dsh-fish]', m), freshOnly: !fresh.reused })
|
|
2123
|
+
? await wakeIfBlank(ctx, { sessionId: fresh.sessionId, root, log: (m) => console.log('[dsh-fish]', m), freshOnly: !fresh.reused, verify: true })
|
|
2120
2124
|
: { woke: false }
|
|
2121
2125
|
send(res, 200, {
|
|
2122
2126
|
ok: true,
|
|
@@ -2150,7 +2154,7 @@ export function registerObserve(ctx, deps = {}) {
|
|
|
2150
2154
|
force: true,
|
|
2151
2155
|
})
|
|
2152
2156
|
const woke = fresh.ok
|
|
2153
|
-
? await wakeIfBlank(ctx, { sessionId: fresh.sessionId, root, log: (m) => console.log('[dsh-fish]', m), freshOnly: !fresh.reused })
|
|
2157
|
+
? await wakeIfBlank(ctx, { sessionId: fresh.sessionId, root, log: (m) => console.log('[dsh-fish]', m), freshOnly: !fresh.reused, verify: true })
|
|
2154
2158
|
: { woke: false }
|
|
2155
2159
|
send(res, fresh.ok ? 200 : 409, {
|
|
2156
2160
|
ok: fresh.ok,
|
package/lib/persona.js
CHANGED
|
@@ -509,6 +509,13 @@ export function toolBrief(extra = []) {
|
|
|
509
509
|
// 听着像解释,读进去却是天花板——他随时会再放行几样,到时候她按旧的自我认识
|
|
510
510
|
// 会先把新工具劝回去。改成把「此刻的家当」与「我的上限」分开说。
|
|
511
511
|
'在自己屋里,眼下能碰到的就是这些。这是此刻的家当,不是我的上限——他往后添了新东西,我照样能学着用。',
|
|
512
|
+
// 2026-11-07(用户 m00882):能力地图的第二层,从处境想起工具,不从名字想起工具。
|
|
513
|
+
// 这一段只教「怎么想」,不列清单:清单在下面各自那一段里,而哪些段在,
|
|
514
|
+
// 取决于那一刻手上真有哪几样(挂不挂跟工具装没装必须是同一个判据)。
|
|
515
|
+
'想不起来该用哪一样的时候,别从名字往下数。从眼前这件事想起:我卡在哪一步——是想不起过去、是怕忘、是想要个东西、还是有话想说。',
|
|
516
|
+
// 第三层「怎么调」不在身份文档里复述:参数、枚举、默认值都在工具自己的说明里。
|
|
517
|
+
// 写两遍只会让她在两份说法之间挑一个,挑错的那次还以为是工具坏了。
|
|
518
|
+
'下面几段讲的是各自派什么用场;真要动手时,参数怎么写在那件东西自己的说明里,拿不准就先看一眼,别猜。',
|
|
512
519
|
]
|
|
513
520
|
const names = [...new Set((extra ?? []).map((s) => String(s).trim()).filter(Boolean))]
|
|
514
521
|
if (names.length) {
|
|
@@ -531,25 +538,27 @@ export const TOOL_BRIEF = toolBrief()
|
|
|
531
538
|
* 它就变成了任务,她的话就成了交作业。
|
|
532
539
|
*/
|
|
533
540
|
export const LETTER_BRIEF = [
|
|
534
|
-
'你还有一个工具 her_leave
|
|
535
|
-
'
|
|
536
|
-
'
|
|
541
|
+
'你还有一个工具 her_leave,可以留一句话给他——他不在的时候也留着,他回来会看到。',
|
|
542
|
+
'什么时候想起它——有一句话想让他回来就看到,又不想非等他先开口的时候。',
|
|
543
|
+
'不用审批,也不用为了让他安心而留。想留就留,不想留就不调。',
|
|
537
544
|
].join('\n')
|
|
538
545
|
|
|
539
546
|
/**
|
|
540
|
-
* 桌上那张纸的说明书(issue #22 乙档,2026-11-04)。
|
|
547
|
+
* 桌上那张纸的说明书(issue #22 乙档,2026-11-04;issue #53 补齐 list / update / drop)。
|
|
541
548
|
*
|
|
542
549
|
* 为什么单独一段、而不是并进 TOOL_BRIEF:这是唯一一个**跨天在场**的东西。
|
|
543
550
|
* 别的工具都是"这一刻做一件事",它写下的东西会在之后每一次醒来时先摆到她眼前,
|
|
544
551
|
* 所以说明书里最要紧的一句是——它是桌子,不是闹钟。写成"记得按时照看",
|
|
545
552
|
* 它就变成待办 App,而她明确说过不要人天天提醒。
|
|
546
|
-
*
|
|
547
|
-
*
|
|
553
|
+
* 写死这几件:怎么记(有日子才排到最前)、没日子的为什么不上桌、怎么翻、怎么改、
|
|
554
|
+
* 怎么划掉、怎么放下。划掉/改/放下都只讲原话:纸条编号她看不到,叫她报编号等于指一条走不通的路。
|
|
548
555
|
*/
|
|
549
556
|
export const NOTE_BRIEF = [
|
|
550
|
-
'你还有一个工具 her_note
|
|
551
|
-
'
|
|
552
|
-
'
|
|
557
|
+
'你还有一个工具 her_note,就是醒来时最先看到的那张桌子。',
|
|
558
|
+
'什么时候想起它——想到一件事又不想现在做、心里老在同一个地方绕,或者这件事做完了、不做了。',
|
|
559
|
+
'心里有事说不清、又老在同一个地方绕:翻翻桌上还有什么。',
|
|
560
|
+
'做完了、不做了:把当时记的那句话原样放进 text,加 status: "done";不做了另外写一句为什么放下。',
|
|
561
|
+
'怎么记、怎么改、怎么翻,都写在那件东西自己的说明里。想让它醒来就摆到眼前,记的时候写个日子;不写日子的只能自己翻。',
|
|
553
562
|
].join('\n')
|
|
554
563
|
|
|
555
564
|
/**
|
|
@@ -562,11 +571,9 @@ export const NOTE_BRIEF = [
|
|
|
562
571
|
* 以为工具坏了;告诉了她,她会自己掂量哪些事值得开口。
|
|
563
572
|
*/
|
|
564
573
|
export const ASK_BRIEF = [
|
|
565
|
-
'
|
|
566
|
-
'
|
|
567
|
-
'
|
|
568
|
-
'它不能多用:一天最多一次,一周最多三次。小事不值得问,自己拿主意就行。',
|
|
569
|
-
'答应了什么,系统会照办;没答应的,记下来就完了,别再追着问同一件事。',
|
|
574
|
+
'还有 ask_owner:有些事不是你一个人说了算的,可以先问问他同不同意。',
|
|
575
|
+
'什么时候想起它——想替自己改作息、想要一样手上没有的东西,或者拿不准一件事要不要做。',
|
|
576
|
+
'他会答可以,也可能说这次不行,也可能不答;答应要东西只是他知道了,得他动手。一天最多一次,一周最多三次,别追着问同一件事。',
|
|
570
577
|
].join('\n')
|
|
571
578
|
|
|
572
579
|
/**
|
|
@@ -579,10 +586,10 @@ export const ASK_BRIEF = [
|
|
|
579
586
|
*/
|
|
580
587
|
export const DREAM_BRIEF = [
|
|
581
588
|
'你还有一个工具 her_dream:醒来之后,如果这一觉里留下了点什么,可以记一句。',
|
|
582
|
-
'
|
|
583
|
-
'
|
|
584
|
-
'
|
|
585
|
-
'
|
|
589
|
+
'什么时候想起它——刚醒、脑子里还留着画面或感觉的时候。',
|
|
590
|
+
'梦不是新的信息来源:只能用 refs 指回真实发生过的事(1–6 条,用 her_recall 翻得到),把它们重新拼一遍;拼不出来就写不了。',
|
|
591
|
+
'写画面和感觉,不用解释它意味着什么;它不是预言,也不是今天该做的事。一天最多一条;它不算成长。',
|
|
592
|
+
'出门或请神那几趟里看到的东西不要写进梦里——那些是从别处带回来的,不该以梦的形式回到家里。',
|
|
586
593
|
].join('\n')
|
|
587
594
|
|
|
588
595
|
/**
|
|
@@ -594,9 +601,9 @@ export const DREAM_BRIEF = [
|
|
|
594
601
|
*/
|
|
595
602
|
export const PROACTIVE_BRIEF = [
|
|
596
603
|
'你有一个工具 her_say:不是回复谁,是你自己想说一句的时候用的。',
|
|
597
|
-
'her_say
|
|
598
|
-
'
|
|
599
|
-
'
|
|
604
|
+
'her_say 只把这句话记进你自己的流水;想让他真的在对话里读到,就直接写出来。',
|
|
605
|
+
'什么时候想起它——有一句话在脑子里转、但没人问你的时候。',
|
|
606
|
+
'说之前要先指认材料:refs 填 1–6 条你这几天写下或听见的事(事件 id,用 her_recall 翻得到)。不指认材料的句子写不进去。',
|
|
600
607
|
'一天能主动开口的次数有上限(用 her_say 记下的算);到了上限就记不进去,不是工具坏了。',
|
|
601
608
|
].join('\n')
|
|
602
609
|
|
|
@@ -624,19 +631,26 @@ export function proactiveBrief(sendTools = []) {
|
|
|
624
631
|
*/
|
|
625
632
|
export const SCHEDULE_BRIEF = [
|
|
626
633
|
'你还有四个工具 schedule_create / schedule_list / schedule_update / schedule_delete:可以自己给自己定定时提醒。',
|
|
627
|
-
'
|
|
628
|
-
'
|
|
629
|
-
'
|
|
630
|
-
'
|
|
631
|
-
'
|
|
632
|
-
'它只在应用开着的时候到点触发;关着的时候错过的,下次最多补最近一次。',
|
|
634
|
+
'什么时候想起它——他说「明天中午提醒我」「每天这个点去看邮箱」这类话的时候。',
|
|
635
|
+
'title 写事情本身,prompt 写清楚到点要做什么,时间用 daily / weekly / at,时区 Asia/Shanghai;一件具体的事定一条就够。',
|
|
636
|
+
'到点后,提醒会以「用户发来的定时消息」出现在这条会话里——照它说的做。',
|
|
637
|
+
'但如果那天你在歇着、正在出门或请神——先记下来,等你回到自己这儿再做;别在外面顺手做了。',
|
|
638
|
+
'它不替你记成长,也不负责把你叫醒;只在应用开着时触发,错过的下次最多补最近一次。',
|
|
633
639
|
].join('\n')
|
|
634
640
|
|
|
635
641
|
/** 在 B 站说话的说明书(P4)。**只在她被允许说话时才附上** */export const SPEAK_BRIEF = [
|
|
636
|
-
'你还有一个工具 bili_speak
|
|
637
|
-
'
|
|
638
|
-
'
|
|
639
|
-
|
|
642
|
+
'你还有一个工具 bili_speak,可以在刚看过的视频下面留一句话,或者回别人留在你评论下面的话。',
|
|
643
|
+
'什么时候想起它——刚看完一条、确实有话想说的时候。',
|
|
644
|
+
'说出去的话别人看得见,也收不回来;没想清楚、只是想说点什么,就别发。它不告诉你还能说几次,你也不用算。',
|
|
645
|
+
].join('\n')
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* 关着时的「在网上说话」那一节(P3,2026-11-07)。
|
|
649
|
+
* 门不拆:整节消失会让她以为这件事根本不存在;只把门上的话换成「这会儿」。
|
|
650
|
+
*/
|
|
651
|
+
export const SPEAK_OFF_BRIEF = [
|
|
652
|
+
'这会儿还说不出去。想说的话,可以先写在别处——等能说的时候,你会先想到某一条视频。',
|
|
653
|
+
'不想说就不说,没有人等着你回答。',
|
|
640
654
|
].join('\n')
|
|
641
655
|
|
|
642
656
|
/** 日记的写作指令(可见性由配置决定,但她不知道谁在看) */export function diaryPrompt(model) {
|
|
@@ -660,8 +674,8 @@ export const SCHEDULE_BRIEF = [
|
|
|
660
674
|
*/
|
|
661
675
|
export const RECALL_BRIEF = [
|
|
662
676
|
'你还有一个工具 her_recall,可以翻你自己过去记下的事。',
|
|
663
|
-
'
|
|
664
|
-
'
|
|
677
|
+
'什么时候想起它——想说"我记得有一次…",或想知道自己以前怎么做过某件事的时候。',
|
|
678
|
+
'拿那件东西的名字(her_note、bili_watch 这种)也能翻到用它做过的事。先去翻,翻到了再说,没翻到就别编。',
|
|
665
679
|
// 2026-10-27(0.5.8):每条后面带 id 了。要改口或做回顾时得抄那个 id,
|
|
666
680
|
// 不告诉她这事,她会以为那是乱码。
|
|
667
681
|
'每条后面小括号里是它的 id——要改口、要做回顾时,用它指回去。',
|
package/lib/proactive.js
CHANGED
|
@@ -57,15 +57,21 @@ export const PROACTIVE_MATERIAL = Object.freeze([
|
|
|
57
57
|
export const PROACTIVE_PING = ' '
|
|
58
58
|
|
|
59
59
|
/**
|
|
60
|
-
* 「缺工具就开口要」(2026-11-05,用户 m02031)。
|
|
60
|
+
* 「缺工具就开口要」(2026-11-05,用户 m02031;2026-11-07 撤销,用户 m01602)。
|
|
61
61
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
62
|
+
* 原来这里是一个常量 `TOOL_ASK_NOTE`,`lib/scheduler.js` 每次醒来都把它摆进正文:
|
|
63
|
+
* 「如果你想到要做什么、但手上没有那件工具:直接说你要它做什么,问主人能不能给你装上」。
|
|
64
64
|
*
|
|
65
|
-
*
|
|
65
|
+
* 用户 2026-11-07 判定这属于「把提醒换成后台反复催她」:同一句话每天读两遍,
|
|
66
|
+
* 读久了就不是她的念头,是系统的吩咐。撤掉之后换成两个位置——
|
|
67
|
+
*
|
|
68
|
+
* B 撞墙才说 —— `lib/restrict.js` 的 `blockedTalk()` 末尾补一句,只在真被拦住时出现。
|
|
69
|
+
* D 交给劲儿 —— 这件事攒在 `lib/drive.js` 的劲儿里(boostWish),攒够了那一轮才醒;
|
|
70
|
+
* 醒的时候它作为桌上一行出现(`lib/wish.js` 的 `wishesOf` + `lib/desk.js` 的 wishes)。
|
|
71
|
+
*
|
|
72
|
+
* 所以这个模块不再导出任何「常驻提醒」。留这段注释是为了让后来的人知道它为什么消失,
|
|
73
|
+
* 而不是以为漏写了。
|
|
66
74
|
*/
|
|
67
|
-
export const TOOL_ASK_NOTE = '(如果你想到要做什么、但手上没有那件工具:直接说你要它做什么,' +
|
|
68
|
-
'问主人能不能给你装上;也可以开一张 ask 卡问。缺工具不是失败,不用绕着走。)'
|
|
69
75
|
|
|
70
76
|
/**
|
|
71
77
|
* 读配置并夹紧。老配置没有 proactive 这一组,用默认值。
|
|
@@ -315,22 +321,46 @@ export function recentMaterial(events = [], { now = Date.now(), days = 3, limit
|
|
|
315
321
|
return out.slice(0, limit)
|
|
316
322
|
}
|
|
317
323
|
|
|
318
|
-
/**
|
|
324
|
+
/**
|
|
325
|
+
* 还悬着的事:她问过但没人答、答了却还没兑现(用户 m01602,2026-11-07)。
|
|
326
|
+
*
|
|
327
|
+
* 原来的写法只摆**没答**的 ask:一张卡只要被答过(哪怕是「以后再说」)就永远消失。
|
|
328
|
+
* 2026-10-29 那张「有没有办法让我看整条视频的画面」被答「以后再说」之后,线索就断了——
|
|
329
|
+
* 她再也没想起过。用户判定这属于「她开口了、被答了、然后线索消失了」。
|
|
330
|
+
*
|
|
331
|
+
* 现在按裁决分几种活法(窗口与 `lib/wish.js` 对齐,口径也由它给):
|
|
332
|
+
* pending 还没答 / 放一天没人答 —— 摆 3 天,文案「你问过「…」,还没有得到答复。」
|
|
333
|
+
* later 「以后再说」 —— 摆 30 天,文案「你问过「…」(他说以后再说)。」
|
|
334
|
+
* approve 他答应了 —— 摆 14 天,文案「你问过「…」(他答应了,还没见到)。」
|
|
335
|
+
* reject 「这次不行」 —— 不摆。不同意就是不问了,再提一次就成了追问。
|
|
336
|
+
*
|
|
337
|
+
* 只摆事实,不写「你可以再问一次」——那是派活。
|
|
338
|
+
*/
|
|
319
339
|
export function looseEnds(events = [], { now = Date.now() } = {}) {
|
|
320
340
|
const out = []
|
|
321
|
-
const
|
|
341
|
+
const answers = new Map()
|
|
322
342
|
for (const e of events) {
|
|
323
|
-
if (e?.type
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
343
|
+
if (e?.type !== 'answer') continue
|
|
344
|
+
const id = String(e.ask?.askId ?? '')
|
|
345
|
+
if (id) answers.set(id, e)
|
|
327
346
|
}
|
|
328
347
|
for (const e of events) {
|
|
329
348
|
if (e?.type !== 'ask') continue
|
|
330
|
-
|
|
349
|
+
const ans = answers.get(String(e.id ?? ''))
|
|
350
|
+
// 放一天没人答的卡记成 verdict:'later' + via:'timeout'(`lib/ask.js` 为了不再反复追问),
|
|
351
|
+
// 但那不是「他说以后再说」——什么都没人说。还原成 pending:3 天、还没有得到答复。
|
|
352
|
+
const verdict = String(ans?.ask?.via ?? '') === 'timeout'
|
|
353
|
+
? 'pending'
|
|
354
|
+
: String(ans?.ask?.verdict ?? '') || 'pending'
|
|
355
|
+
// 不同意就是不问了:再提一次就成了追问。
|
|
356
|
+
if (verdict === 'reject') continue
|
|
357
|
+
const days = verdict === 'pending' ? 3 : verdict === 'approve' ? 14 : 30
|
|
331
358
|
const ts = tsMs(e)
|
|
332
|
-
if (!Number.isFinite(ts) || now - ts >
|
|
333
|
-
|
|
359
|
+
if (!Number.isFinite(ts) || now - ts > days * 86_400_000) continue
|
|
360
|
+
const text = String(e.text ?? '').trim()
|
|
361
|
+
if (!text) continue
|
|
362
|
+
const tail = verdict === 'later' ? '(他说以后再说)' : verdict === 'approve' ? '(他答应了,还没见到)' : ',还没有得到答复。'
|
|
363
|
+
out.push({ id: e.id, kind: 'ask', text: `你问过「${text}」${tail}` })
|
|
334
364
|
}
|
|
335
365
|
return out.slice(0, 3)
|
|
336
366
|
}
|
|
@@ -364,7 +394,6 @@ export function proactiveSection({ now = Date.now(), state = {}, material = [],
|
|
|
364
394
|
// 2026-11-06(PR #44):配了 sendTools 的部署更绝——正文根本发不出去,只有调那件工具才算开口。
|
|
365
395
|
if (sendTools.length > 0) lines.push(`想说什么就用 ${sendTools.join('、')},不用等他先问。这一轮写下的正文只留在这条会话里,发不到聊天里。不想说,就什么都别写:不要写任何字,也不要解释为什么不说——这一轮没有回复也算正常。`)
|
|
366
396
|
else lines.push('想说什么就说,不用等他先问——这一轮你写下的字会落进你们俩这条对话里。也可以再用 her_say 把它记进你自己的流水(refs 指回上面真实发生的事,1–6 条 id)。不想说,就什么都别写:不要写任何字,也不要解释为什么不说——这一轮没有回复也算正常。')
|
|
367
|
-
lines.push(TOOL_ASK_NOTE)
|
|
368
397
|
return lines.join('\n')
|
|
369
398
|
}
|
|
370
399
|
|