@a9i5k4/dsh-auto-memory 0.1.4 → 0.1.6
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/lib/client.js +223 -1
- package/lib/index.js +201 -6
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -130,6 +130,8 @@ window.__ModuleLoader__.load({
|
|
|
130
130
|
connectHint: '把其他 AI 工具(CodeBuddy / Claude Code / Codex / 项目约定文件等)积累的记忆接入当前 DSH 工作。接入后内容写入本地记忆并自动标注来源,后续会随会话自动注入。',
|
|
131
131
|
importAll: '一键接入全部', rescan: '重新扫描',
|
|
132
132
|
styleAuto: '由内容决定', styleLife: '生活化', styleProfessional: '专业性',
|
|
133
|
+
todayGreetingTitle: '问候',
|
|
134
|
+
yesterdayTimeline: '昨天(', pendingReflectionShort: '昨天的工作还没复盘(',
|
|
133
135
|
saved: '已保存',
|
|
134
136
|
settingsHeader: '记忆存储与行为设置(保存到 DSH 主目录 dsh-auto-memory.json):',
|
|
135
137
|
fUserDir: '用户记忆目录', fUserDirHint: '跨项目规则存放处,支持 ~ 开头;需有文件写权限。',
|
|
@@ -142,6 +144,11 @@ window.__ModuleLoader__.load({
|
|
|
142
144
|
fLocale: '界面语言', fLocaleHint: '切换插件面板与设置页的显示语言。',
|
|
143
145
|
saveSettings: '保存设置',
|
|
144
146
|
zh: '中文', en: 'English',
|
|
147
|
+
calendar: '日历', addItem: '添加', save: '保存', cancel: '取消', needTitle: '请填写事项标题', itemTitle: '事项标题…',
|
|
148
|
+
segMorning: '早晨', segForenoon: '上午', segNoon: '中午', segAfternoon: '下午', segEvening: '晚上',
|
|
149
|
+
segPrefix: '今日', segMorningHint: '(昨日摘要)',
|
|
150
|
+
welcomeBack: '欢迎回来!这段时间你完成了这些工作:',
|
|
151
|
+
qUrgentImportant: '重要紧急', qImportant: '重要不紧急', qUrgent: '紧急不重要', qNone: '不重要不紧急', qUncategorized: '未分类',
|
|
145
152
|
},
|
|
146
153
|
en: {
|
|
147
154
|
loading: 'Loading…',
|
|
@@ -181,6 +188,8 @@ window.__ModuleLoader__.load({
|
|
|
181
188
|
connectHint: 'Import memories accumulated by other AI tools (CodeBuddy / Claude Code / Codex / project convention files) into the current DSH workspace. Imported content is written to local memory with its source noted, and is auto-injected in future sessions.',
|
|
182
189
|
importAll: 'Import all', rescan: 'Rescan',
|
|
183
190
|
styleAuto: 'Auto', styleLife: 'Life-style', styleProfessional: 'Professional',
|
|
191
|
+
todayGreetingTitle: 'Greeting',
|
|
192
|
+
yesterdayTimeline: 'Yesterday (', pendingReflectionShort: 'Yesterday\'s work not reviewed (',
|
|
184
193
|
saved: 'Saved',
|
|
185
194
|
settingsHeader: 'Memory storage & behavior (saved to ~/.dsh/dsh-auto-memory.json):',
|
|
186
195
|
fUserDir: 'User memory dir', fUserDirHint: 'Cross-project rules; supports ~ prefix; needs write permission.',
|
|
@@ -193,6 +202,11 @@ window.__ModuleLoader__.load({
|
|
|
193
202
|
fLocale: 'UI language', fLocaleHint: 'Switch the display language of the panel and settings page.',
|
|
194
203
|
saveSettings: 'Save settings',
|
|
195
204
|
zh: '中文', en: 'English',
|
|
205
|
+
calendar: 'Calendar', addItem: 'Add', save: 'Save', cancel: 'Cancel', needTitle: 'Title is required', itemTitle: 'Item title…',
|
|
206
|
+
segMorning: 'Morning', segForenoon: 'Forenoon', segNoon: 'Noon', segAfternoon: 'Afternoon', segEvening: 'Evening',
|
|
207
|
+
segPrefix: 'Today ', segMorningHint: ' (yesterday summary)',
|
|
208
|
+
welcomeBack: 'Welcome back! While you were away, you finished:',
|
|
209
|
+
qUrgentImportant: 'Urgent & Important', qImportant: 'Important', qUrgent: 'Urgent', qNone: 'Neither', qUncategorized: 'Uncategorized',
|
|
196
210
|
}
|
|
197
211
|
}
|
|
198
212
|
var locale = 'zh'
|
|
@@ -332,6 +346,65 @@ window.__ModuleLoader__.load({
|
|
|
332
346
|
return n + ' B'
|
|
333
347
|
}
|
|
334
348
|
|
|
349
|
+
function GreetingCard(props) {
|
|
350
|
+
var g = props.greeting
|
|
351
|
+
var ps = props.periodSummary
|
|
352
|
+
if (!g) return null
|
|
353
|
+
// 智能时段判定
|
|
354
|
+
var hour = new Date().getHours()
|
|
355
|
+
var seg = hour < 9 ? 'morning' : hour < 12 ? 'forenoon' : hour < 14 ? 'noon' : hour < 18 ? 'afternoon' : 'evening'
|
|
356
|
+
var segLabel = { morning: t('segMorning'), forenoon: t('segForenoon'), noon: t('segNoon'), afternoon: t('segAfternoon'), evening: t('segEvening') }[seg]
|
|
357
|
+
var title = (g.period || '') + ' · ' + segLabel
|
|
358
|
+
var rows = []
|
|
359
|
+
// 离开>1小时后回来 → 欢迎回来提示
|
|
360
|
+
var lastSeen = 0
|
|
361
|
+
try { lastSeen = Number(localStorage.getItem('dsh-auto-memory.lastActive') || 0) } catch (e) {}
|
|
362
|
+
var now = Date.now()
|
|
363
|
+
var away = lastSeen > 0 && (now - lastSeen) > 3600000
|
|
364
|
+
if (away) {
|
|
365
|
+
rows.push(h('div', { 'data-dam-content': '' }, t('welcomeBack')))
|
|
366
|
+
}
|
|
367
|
+
// 时段摘要(今天该时段之前的工作)
|
|
368
|
+
if (ps && ps.entries && ps.entries.length) {
|
|
369
|
+
var seen = { '凌晨': true, '早晨': seg === 'morning' ? false : true, '上午': (seg === 'forenoon' || seg === 'noon' || seg === 'afternoon' || seg === 'evening'), '中午': (seg === 'noon' || seg === 'afternoon' || seg === 'evening'), '下午': (seg === 'afternoon' || seg === 'evening'), '晚上': seg === 'evening' }
|
|
370
|
+
var segRows = []
|
|
371
|
+
var segOrder = ['早晨', '上午', '中午', '下午', '晚上']
|
|
372
|
+
for (var si = 0; si < segOrder.length; si++) {
|
|
373
|
+
var sname = segOrder[si]
|
|
374
|
+
if (!seen[sname]) continue
|
|
375
|
+
var items = (ps.groups && ps.groups[sname]) || []
|
|
376
|
+
if (!items.length) continue
|
|
377
|
+
if (segRows.length) segRows.push(h('div', { 'data-dam-hint': '' }, ''))
|
|
378
|
+
segRows.push(h('div', { 'data-dam-hint': '' }, t('segPrefix') + sname + (sname === '早晨' ? t('segMorningHint') : '') + ':'))
|
|
379
|
+
for (var ii = 0; ii < items.length; ii++) {
|
|
380
|
+
(function (txt) {
|
|
381
|
+
segRows.push(h('div', { 'data-dam-row': '', style: { marginBottom: '3px', alignItems: 'flex-start' } },
|
|
382
|
+
h('span', { 'data-dam-muted': '', style: { flex: '0 0 10px', marginTop: '2px' } }, '·'),
|
|
383
|
+
h('span', { style: { flex: '1', wordBreak: 'break-word' } }, txt)))
|
|
384
|
+
})(items[ii])
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (segRows.length) rows.push(h('div', null, segRows))
|
|
388
|
+
}
|
|
389
|
+
// 昨天时间轴(早晨时段显示昨天摘要)
|
|
390
|
+
if (seg === 'morning' && g.entries.length) {
|
|
391
|
+
rows.push(h('div', { 'data-dam-hint': '' }, t('yesterdayTimeline') + (g.yesterdayDate || '') + ')'))
|
|
392
|
+
for (var i = 0; i < g.entries.length; i++) {
|
|
393
|
+
(function (en) {
|
|
394
|
+
rows.push(h('div', { 'data-dam-row': '', style: { marginBottom: '4px', alignItems: 'flex-start' } },
|
|
395
|
+
h('span', { 'data-dam-muted': '', style: { flex: '0 0 44px', fontFamily: 'monospace', fontSize: '11.5px', marginTop: '2px' } }, en.time),
|
|
396
|
+
h('span', { style: { flex: '1', wordBreak: 'break-word' } }, en.text)))
|
|
397
|
+
})(g.entries[i])
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
// 待反思提醒
|
|
401
|
+
if (g.pendingReflectionDate) {
|
|
402
|
+
rows.push(h('div', { 'data-dam-hint': '' }, t('pendingReflectionShort') + g.pendingReflectionDate))
|
|
403
|
+
}
|
|
404
|
+
if (!rows.length) return null
|
|
405
|
+
return h(Card, { title: title }, rows)
|
|
406
|
+
}
|
|
407
|
+
|
|
335
408
|
function OverviewTab() {
|
|
336
409
|
var statePair = useState(null)
|
|
337
410
|
var state = statePair[0]
|
|
@@ -350,6 +423,10 @@ window.__ModuleLoader__.load({
|
|
|
350
423
|
apiGet(API.state).then(function (s) { if (alive) setState(s) }).catch(function () {})
|
|
351
424
|
return function () { alive = false }
|
|
352
425
|
}, [])
|
|
426
|
+
useEffect(function () {
|
|
427
|
+
// 记录本次活动时间;下次打开若相隔>1小时,GreetingCard 显示"欢迎回来"
|
|
428
|
+
try { localStorage.setItem('dsh-auto-memory.lastActive', String(Date.now())) } catch (e) {}
|
|
429
|
+
}, [])
|
|
353
430
|
if (!state) return h(Loading)
|
|
354
431
|
function oneClickReflect() {
|
|
355
432
|
if (reflectBusy) return
|
|
@@ -360,6 +437,8 @@ window.__ModuleLoader__.load({
|
|
|
360
437
|
}).catch(function (e) { setActMsg(t('failed') + e.message); setReflectBusy(false) })
|
|
361
438
|
}
|
|
362
439
|
return h('div', null,
|
|
440
|
+
// 今日问候卡:问候语 + 昨天时间轴 + 提醒(纯 GUI 渲染,不干扰对话流)
|
|
441
|
+
h(GreetingCard, { greeting: state.greeting, periodSummary: state.periodSummary, t: t }),
|
|
363
442
|
state.pendingReflection
|
|
364
443
|
? h(Banner, null, t('pendingReflection') + state.pendingReflection + t('pendingReflectionHint'))
|
|
365
444
|
: null,
|
|
@@ -543,6 +622,148 @@ window.__ModuleLoader__.load({
|
|
|
543
622
|
result ? h(Card, { title: t('resultTitle') }, h('div', { 'data-dam-content': '' }, result)) : null)
|
|
544
623
|
}
|
|
545
624
|
|
|
625
|
+
// ───────────────────────── 日历页签 ─────────────────────────
|
|
626
|
+
var QUADRANT_STYLE = {
|
|
627
|
+
'重要紧急': { color: 'var(--dsw-alias-state-error-primary, #d64545)', label: t('qUrgentImportant') },
|
|
628
|
+
'重要不紧急': { color: 'var(--dsw-alias-brand-primary, #4f7cff)', label: t('qImportant') },
|
|
629
|
+
'紧急不重要': { color: 'var(--dsw-alias-state-warn-primary, #e6a23c)', label: t('qUrgent') },
|
|
630
|
+
'不重要不紧急': { color: 'var(--dsw-alias-label-secondary, #8a94a6)', label: t('qNone') },
|
|
631
|
+
'未分类': { color: 'var(--dsw-alias-label-secondary, #8a94a6)', label: t('qUncategorized') },
|
|
632
|
+
}
|
|
633
|
+
function CalendarTab() {
|
|
634
|
+
var dataPair = useState(null)
|
|
635
|
+
var data = dataPair[0]
|
|
636
|
+
var setData = dataPair[1]
|
|
637
|
+
var monthPair = useState(null)
|
|
638
|
+
var month = monthPair[0] // {year, mon} 1-12
|
|
639
|
+
var setMonth = monthPair[1]
|
|
640
|
+
var draftPair = useState(null)
|
|
641
|
+
var draft = draftPair[0] // {date, time, quadrant, title}
|
|
642
|
+
var setDraft = draftPair[1]
|
|
643
|
+
var msgPair = useState('')
|
|
644
|
+
var msg = msgPair[0]
|
|
645
|
+
var setMsg = msgPair[1]
|
|
646
|
+
function load() {
|
|
647
|
+
apiGet(API.calendar).then(function (d) { if (d) setData(d) }).catch(function () {})
|
|
648
|
+
}
|
|
649
|
+
useEffect(function () {
|
|
650
|
+
load()
|
|
651
|
+
var now = new Date()
|
|
652
|
+
setMonth({ year: now.getFullYear(), mon: now.getMonth() + 1 })
|
|
653
|
+
}, [])
|
|
654
|
+
if (!data || !month) return h(Loading)
|
|
655
|
+
function dayEntries(date) {
|
|
656
|
+
return (data.entries || []).filter(function (en) { return en.date === date })
|
|
657
|
+
}
|
|
658
|
+
function moveMonth(delta) {
|
|
659
|
+
var y = month.year, m = month.mon + delta
|
|
660
|
+
if (m < 1) { m = 12; y-- }
|
|
661
|
+
if (m > 12) { m = 1; y++ }
|
|
662
|
+
setMonth({ year: y, mon: m })
|
|
663
|
+
}
|
|
664
|
+
function fmtDate(y, m, d) { return y + '-' + String(m).padStart(2, '0') + '-' + String(d).padStart(2, '0') }
|
|
665
|
+
// 月网格
|
|
666
|
+
var firstDay = new Date(month.year, month.mon - 1, 1)
|
|
667
|
+
var startDow = firstDay.getDay()
|
|
668
|
+
var daysInMonth = new Date(month.year, month.mon, 0).getDate()
|
|
669
|
+
var cells = []
|
|
670
|
+
var today = fmtDate(new Date().getFullYear(), new Date().getMonth() + 1, new Date().getDate())
|
|
671
|
+
for (var i = 0; i < startDow; i++) cells.push(null)
|
|
672
|
+
for (var d = 1; d <= daysInMonth; d++) cells.push(fmtDate(month.year, month.mon, d))
|
|
673
|
+
var dowLabels = locale === 'zh' ? ['日', '一', '二', '三', '四', '五', '六'] : ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa']
|
|
674
|
+
var rows = []
|
|
675
|
+
// 顶部:月份切换 + 添加按钮
|
|
676
|
+
rows.push(h('div', { 'data-dam-row': '' },
|
|
677
|
+
h('button', { 'data-dam-btn': '', onClick: function () { moveMonth(-1) } }, '◀'),
|
|
678
|
+
h('span', { style: { fontWeight: 700, flex: 1, textAlign: 'center' } }, month.year + ' / ' + String(month.mon).padStart(2, '0')),
|
|
679
|
+
h('button', { 'data-dam-btn': '', onClick: function () { moveMonth(1) } }, '▶'),
|
|
680
|
+
h('button', { 'data-dam-btn': '', onClick: function () { setDraft({ date: today, time: '09:00', quadrant: '重要不紧急', title: '' }) } }, '+ ' + t('addItem'))))
|
|
681
|
+
// 图例
|
|
682
|
+
rows.push(h('div', { 'data-dam-row': '', style: { flexWrap: 'wrap' } },
|
|
683
|
+
Object.keys(QUADRANT_STYLE).map(function (q) {
|
|
684
|
+
return h('span', { key: q, style: { fontSize: '11px', opacity: 0.8, marginRight: '10px' } },
|
|
685
|
+
h('span', { style: { display: 'inline-block', width: 8, height: 8, borderRadius: 2, background: QUADRANT_STYLE[q].color, marginRight: 4 } }), QUADRANT_STYLE[q].label)
|
|
686
|
+
})))
|
|
687
|
+
// 星期表头
|
|
688
|
+
rows.push(h('div', { style: { display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '3px', marginBottom: '4px' } },
|
|
689
|
+
dowLabels.map(function (dl) { return h('div', { key: dl, style: { textAlign: 'center', fontSize: '11px', opacity: 0.55 } }, dl) })))
|
|
690
|
+
// 日历格子
|
|
691
|
+
var gridRows = []
|
|
692
|
+
for (var ci = 0; ci < cells.length; ci += 7) {
|
|
693
|
+
var week = []
|
|
694
|
+
for (var cj = 0; cj < 7; cj++) {
|
|
695
|
+
(function (cell) {
|
|
696
|
+
if (!cell) { week.push(h('div', { key: 'empty' + cj })); return }
|
|
697
|
+
var es = dayEntries(cell)
|
|
698
|
+
var isToday = cell === today
|
|
699
|
+
var dayNum = Number(cell.slice(8))
|
|
700
|
+
week.push(h('div', {
|
|
701
|
+
key: cell,
|
|
702
|
+
onClick: function () { setDraft({ date: cell, time: '09:00', quadrant: '重要不紧急', title: '' }) },
|
|
703
|
+
style: {
|
|
704
|
+
minHeight: '64px', padding: '4px', cursor: 'pointer', borderRadius: '8px',
|
|
705
|
+
border: '1px solid ' + (isToday ? 'var(--dsw-alias-brand-primary, #4f7cff)' : 'color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.3)) 55%, transparent)'),
|
|
706
|
+
background: isToday ? 'color-mix(in srgb, var(--dsw-alias-brand-primary, #4f7cff) 10%, transparent)' : 'color-mix(in srgb, var(--dsw-alias-bg-layer-1, rgba(128,128,128,.06)) 40%, transparent)',
|
|
707
|
+
overflow: 'hidden',
|
|
708
|
+
},
|
|
709
|
+
},
|
|
710
|
+
h('div', { style: { fontSize: '11.5px', fontWeight: isToday ? 700 : 500, opacity: isToday ? 1 : 0.7, marginBottom: '3px' } }, dayNum),
|
|
711
|
+
es.slice(0, 3).map(function (en) {
|
|
712
|
+
var qs = QUADRANT_STYLE[en.quadrant] || QUADRANT_STYLE['未分类']
|
|
713
|
+
return h('div', {
|
|
714
|
+
key: en.time + en.title,
|
|
715
|
+
title: en.time + ' ' + en.title,
|
|
716
|
+
onClick: function (ev) { ev.stopPropagation(); toggleDone(en) },
|
|
717
|
+
style: {
|
|
718
|
+
fontSize: '10.5px', lineHeight: 1.35, padding: '1px 4px', borderRadius: 4, marginBottom: 2,
|
|
719
|
+
background: 'color-mix(in srgb, ' + qs.color + ' 18%, transparent)',
|
|
720
|
+
color: qs.color, textDecoration: en.done ? 'line-through' : 'none',
|
|
721
|
+
opacity: en.done ? 0.5 : 1, cursor: 'pointer', whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
|
|
722
|
+
},
|
|
723
|
+
}, (en.time && en.time !== '--:--' ? en.time + ' ' : '') + en.title)
|
|
724
|
+
}),
|
|
725
|
+
es.length > 3 ? h('div', { style: { fontSize: '10px', opacity: 0.5 } }, '+' + (es.length - 3)) : null))
|
|
726
|
+
})(cells[ci + cj])
|
|
727
|
+
}
|
|
728
|
+
gridRows.push(h('div', { key: 'w' + ci, style: { display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: '3px' } }, week))
|
|
729
|
+
}
|
|
730
|
+
rows.push(h('div', null, gridRows))
|
|
731
|
+
function toggleDone(en) {
|
|
732
|
+
apiPost(API.calendar, { action: en.done ? 'remove' : 'done', date: en.date, time: en.time, title: en.title }).then(function (d) {
|
|
733
|
+
setMsg(d.result || ''); load()
|
|
734
|
+
}).catch(function (e) { setMsg(t('failed') + e.message) })
|
|
735
|
+
}
|
|
736
|
+
function saveDraft() {
|
|
737
|
+
if (!draft || !draft.title.trim()) { setMsg(t('needTitle')); return }
|
|
738
|
+
apiPost(API.calendar, { date: draft.date, time: draft.time, quadrant: draft.quadrant, title: draft.title.trim(), note: draft.note || '' }).then(function (d) {
|
|
739
|
+
setMsg(d.result || t('saved')); setDraft(null); load()
|
|
740
|
+
}).catch(function (e) { setMsg(t('failed') + e.message) })
|
|
741
|
+
}
|
|
742
|
+
// 添加/编辑浮层(液态玻璃)
|
|
743
|
+
if (draft) {
|
|
744
|
+
rows.push(h('div', {
|
|
745
|
+
style: {
|
|
746
|
+
position: 'absolute', inset: 0, zIndex: 10, borderRadius: '16px',
|
|
747
|
+
background: 'color-mix(in srgb, var(--dsw-alias-bg-overlay, rgba(255,255,255,.9)) 70%, transparent)',
|
|
748
|
+
backdropFilter: 'blur(20px) saturate(1.4)', WebkitBackdropFilter: 'blur(20px) saturate(1.4)',
|
|
749
|
+
display: 'flex', flexDirection: 'column', padding: '18px', gap: '10px',
|
|
750
|
+
border: '1px solid color-mix(in srgb, var(--dsw-alias-border-l1, rgba(128,128,128,.35)) 60%, transparent)',
|
|
751
|
+
},
|
|
752
|
+
},
|
|
753
|
+
h('div', { style: { fontWeight: 700 } }, t('addItem') + ' · ' + draft.date),
|
|
754
|
+
h('input', { 'data-dam-input': '', placeholder: t('itemTitle'), value: draft.title, autoFocus: true, onChange: function (e) { var n = Object.assign({}, draft); n.title = e.target.value; setDraft(n) } }),
|
|
755
|
+
h('div', { 'data-dam-row': '' },
|
|
756
|
+
h('input', { 'data-dam-input': '', type: 'time', value: draft.time, onChange: function (e) { var n = Object.assign({}, draft); n.time = e.target.value || '09:00'; setDraft(n) } }),
|
|
757
|
+
h('select', { 'data-dam-select': '', value: draft.quadrant, onChange: function (e) { var n = Object.assign({}, draft); n.quadrant = e.target.value; setDraft(n) } },
|
|
758
|
+
['重要紧急', '重要不紧急', '紧急不重要', '不重要不紧急'].map(function (q) { return h('option', { key: q, value: q }, QUADRANT_STYLE[q].label) }))),
|
|
759
|
+
h('div', { 'data-dam-row': '' },
|
|
760
|
+
h('button', { 'data-dam-btn': '', onClick: saveDraft }, t('save')),
|
|
761
|
+
h('button', { 'data-dam-btn': '', onClick: function () { setDraft(null) } }, t('cancel')))))
|
|
762
|
+
}
|
|
763
|
+
msg ? h('div', { 'data-dam-hint': '' }, msg) : null
|
|
764
|
+
return h('div', { style: { position: 'relative' } }, rows)
|
|
765
|
+
}
|
|
766
|
+
|
|
546
767
|
var TOOL_LABEL = { workbuddy: t('aiAssistant'), codebuddy: 'CodeBuddy', claude: 'Claude Code', codex: 'Codex', 'project-files': t('projectFiles') }
|
|
547
768
|
function ConnectTab() {
|
|
548
769
|
var sourcesPair = useState(null)
|
|
@@ -650,8 +871,9 @@ window.__ModuleLoader__.load({
|
|
|
650
871
|
else if (tab === 'notes') body = h(NotesTab)
|
|
651
872
|
else if (tab === 'reflections') body = h(ReflectionsTab)
|
|
652
873
|
else if (tab === 'connect') body = h(ConnectTab)
|
|
874
|
+
else if (tab === 'calendar') body = h(CalendarTab)
|
|
653
875
|
else body = h(SearchTab)
|
|
654
|
-
var tabs = [['overview', t('overview')], ['logs', t('logs')], ['notes', t('notes')], ['reflections', t('reflections')], ['connect', t('connect')], ['search', t('search')]]
|
|
876
|
+
var tabs = [['overview', t('overview')], ['logs', t('logs')], ['notes', t('notes')], ['reflections', t('reflections')], ['connect', t('connect')], ['calendar', t('calendar')], ['search', t('search')]]
|
|
655
877
|
var style = {
|
|
656
878
|
left: g.left + 'px',
|
|
657
879
|
top: g.top + 'px',
|
package/lib/index.js
CHANGED
|
@@ -44,6 +44,7 @@ export const API = {
|
|
|
44
44
|
note: '/api/dsh-auto-memory/note',
|
|
45
45
|
external: '/api/dsh-auto-memory/external',
|
|
46
46
|
'external-import': '/api/dsh-auto-memory/external-import',
|
|
47
|
+
calendar: '/api/dsh-auto-memory/calendar',
|
|
47
48
|
}
|
|
48
49
|
|
|
49
50
|
const DEFAULT_CONFIG = {
|
|
@@ -106,6 +107,8 @@ class MemoryEngine {
|
|
|
106
107
|
latestReflection: '', latestReflectionDate: '',
|
|
107
108
|
pendingReflection: undefined, // {date, text}
|
|
108
109
|
reflectionShownSession: undefined,
|
|
110
|
+
todayGreeting: '', greetingShownSession: undefined,
|
|
111
|
+
calendarText: '', calendarPath: undefined,
|
|
109
112
|
loadedAt: 0, loading: undefined, configLoaded: false,
|
|
110
113
|
}
|
|
111
114
|
this._configPath = path.join(dshHome(), 'dsh-auto-memory.json')
|
|
@@ -164,10 +167,13 @@ class MemoryEngine {
|
|
|
164
167
|
ws,
|
|
165
168
|
userDir,
|
|
166
169
|
userFile: path.join(userDir, 'MEMORY.md'),
|
|
170
|
+
calendarPath: path.join(userDir, 'CALENDAR.md'),
|
|
167
171
|
projectDir,
|
|
168
172
|
notesPath: path.join(projectDir, 'MEMORY.md'),
|
|
169
173
|
logPath: path.join(projectDir, `${todayStr()}.md`),
|
|
170
174
|
reflectDir: path.join(projectDir, 'reflections'),
|
|
175
|
+
greetDir: path.join(projectDir, 'greetings'),
|
|
176
|
+
greetPath: path.join(projectDir, 'greetings', `${todayStr()}.md`),
|
|
171
177
|
}
|
|
172
178
|
}
|
|
173
179
|
|
|
@@ -275,6 +281,11 @@ class MemoryEngine {
|
|
|
275
281
|
const pending = await this.detectPendingReflection(p.projectDir, p.reflectDir)
|
|
276
282
|
if (pending) this.state.pendingReflection = pending
|
|
277
283
|
}
|
|
284
|
+
// 今日拟人化问候(每天首会话展示一次)
|
|
285
|
+
this.state.todayGreeting = await this.readTextSafe(p.greetPath)
|
|
286
|
+
// 日历/日程(用户级,跨工作区与重装保留)
|
|
287
|
+
this.state.calendarPath = p.calendarPath
|
|
288
|
+
this.state.calendarText = await this.readTextSafe(p.calendarPath)
|
|
278
289
|
// 外部记忆探测(后台,结果进缓存)
|
|
279
290
|
if (this.config.externalSources) void this.external.discover(true)
|
|
280
291
|
this.state.loadedAt = Date.now()
|
|
@@ -322,13 +333,21 @@ class MemoryEngine {
|
|
|
322
333
|
lines.push('· 历史会话索引: ' + sess.map((x) => x.name + ' ' + x.files.length + ' 个').join(', ') + ' —— 需要时用 memory_recall 检索。')
|
|
323
334
|
}
|
|
324
335
|
}
|
|
336
|
+
// 日历/日程注入(让 AI 主动感知 deadline/约定)
|
|
337
|
+
if (this.state.calendarText && this.state.calendarText.trim()) {
|
|
338
|
+
const calEntries = this.parseCalendar(this.state.calendarText).filter((en) => !en.done && en.date >= todayStr()).slice(0, 10)
|
|
339
|
+
if (calEntries.length) {
|
|
340
|
+
const calLines = calEntries.map((en) => '· ' + en.date + ' ' + en.time + ' | ' + en.quadrant + ' | ' + en.title).join('\n')
|
|
341
|
+
lines.push('\n[日历与日程(未完成)]\n' + calLines + '\n主动关注这些安排:对话中若提及相关时间点,主动用 calendar_add 补充新事项、calendar_done 标记完成;回复正文中向用户转述日历变更。')
|
|
342
|
+
}
|
|
343
|
+
}
|
|
325
344
|
lines.push('\n[记忆写入纪律 — 必须遵守]')
|
|
326
345
|
lines.push('- 会话开始:若任务与历史工作/历史决策相关,先回顾以上记忆;**遇到不熟悉的代码、领域或项目时,主动调用 memory_recall 检索本机所有 AI 工具的历史记忆(CodeBuddy/Claude Code/Codex 等会话),不要凭空猜测**。')
|
|
327
346
|
lines.push('- 新工作区(无历史日志/笔记):主动用 memory_recall 探索本机历史,判断该项目是否曾在其他 AI 工具中工作过;也可调用 memory_external 查看并接入外部记忆;检索时在正文中说明"我先查一下之前的记录"。')
|
|
328
347
|
lines.push('- 完成实质性工作后立即调用 memory_log 追加今日日志(append-only,绝不覆盖):建/改应用、修 bug、写文档、重构、技术选型、用户约定或偏好。')
|
|
329
348
|
lines.push('- progress 与 memory 一起写:写日志的同时,把有跨会话长期价值的内容一并写入记忆——跨项目规则 → memory_user,仅本项目 → memory_note;两者在同一轮完成,互不冲突、不遗漏。')
|
|
330
349
|
lines.push('- 只记录有跨会话长期价值的;不记临时信息(搜索结果、临时路径、工具报错)。')
|
|
331
|
-
lines.push('-
|
|
350
|
+
lines.push('- **记忆操作必须在正文可见(摘要链)**:调用 memory_log/note/user/reflect 更新记忆后,必须把结果写进本轮回复的正文文本(用户直接看到的那段文字,不是工具调用区),例如:"已更新今日日志,新增:修复了XXX";调用 memory_recall/memory_external 检索时,同样在正文开头写明"我查了记忆,发现..."。工具返回值只是辅助,正文转述是强制要求。')
|
|
332
351
|
lines.push('- 用户明确要求长期记住:跨项目规则 → memory_user;仅本项目 → memory_note。')
|
|
333
352
|
lines.push('- 定期调用 memory_maintain 归档 30 天前日志;不存密钥,除非用户明确要求。')
|
|
334
353
|
lines.push('- 记忆仅作补充,不替代正常回复与交付物。')
|
|
@@ -358,6 +377,28 @@ class MemoryEngine {
|
|
|
358
377
|
].join('\n')
|
|
359
378
|
}
|
|
360
379
|
|
|
380
|
+
/** 今日问候数据(纯数据,供 GUI 概览页渲染,不注入对话流)。 */
|
|
381
|
+
greetingData() {
|
|
382
|
+
const hour = new Date().getHours()
|
|
383
|
+
const period = hour < 6 ? '凌晨' : hour < 9 ? '早上好' : hour < 12 ? '上午好' : hour < 14 ? '中午好' : hour < 18 ? '下午好' : hour < 22 ? '晚上好' : '夜深了'
|
|
384
|
+
// 昨天 = 最近一条日志(今天之前的);今天有条目也算最近
|
|
385
|
+
const recent = this.state.recentLogs[0] || null
|
|
386
|
+
const entries = recent ? recent.text.split('\n').map((l) => l.trim()).filter((l) => l.startsWith('- ')).map((l) => {
|
|
387
|
+
const m = l.match(/^- (\d{2}:\d{2}) (.*)$/)
|
|
388
|
+
return m ? { time: m[1], text: m[2] } : { time: '', text: l.replace(/^- /, '') }
|
|
389
|
+
}) : []
|
|
390
|
+
return {
|
|
391
|
+
period,
|
|
392
|
+
date: todayStr(),
|
|
393
|
+
hasGreeting: !!this.state.todayGreeting,
|
|
394
|
+
greeting: this.state.todayGreeting,
|
|
395
|
+
yesterdayDate: recent ? recent.date : '',
|
|
396
|
+
entries,
|
|
397
|
+
hasPendingReflection: !!this.state.pendingReflection,
|
|
398
|
+
pendingReflectionDate: this.state.pendingReflection ? this.state.pendingReflection.date : '',
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
361
402
|
// ---------- 写操作 ----------
|
|
362
403
|
async appendText(p, text) {
|
|
363
404
|
const existing = await this.readTextSafe(p)
|
|
@@ -454,6 +495,98 @@ class MemoryEngine {
|
|
|
454
495
|
return '已保存反思 ' + file
|
|
455
496
|
}
|
|
456
497
|
|
|
498
|
+
// ---------- 日历/日程(用户级 CALENDAR.md) ----------
|
|
499
|
+
/** 解析 CALENDAR.md 为条目数组。 */
|
|
500
|
+
parseCalendar(text) {
|
|
501
|
+
const out = []
|
|
502
|
+
let curDate = ''
|
|
503
|
+
for (const raw of String(text || '').split('\n')) {
|
|
504
|
+
const line = raw.trim()
|
|
505
|
+
if (!line) continue
|
|
506
|
+
const dm = line.match(/^## (\d{4}-\d{2}-\d{2})/)
|
|
507
|
+
if (dm) { curDate = dm[1]; continue }
|
|
508
|
+
// - [x] HH:MM | 象限 | 标题 | (备注)
|
|
509
|
+
const m = line.match(/^- \[([ xX])\] (\d{1,2}:\d{2}) \| (重要紧急|重要不紧急|紧急不重要|不重要不紧急|未分类) \| (.+?)(?: \| (.*))?$/)
|
|
510
|
+
if (m) {
|
|
511
|
+
out.push({
|
|
512
|
+
date: curDate, done: m[1] !== ' ', time: m[2], quadrant: m[3], title: m[4].trim(), note: (m[5] || '').trim(),
|
|
513
|
+
})
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
return out
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
/** 序列化条目为 CALENDAR.md 文本。 */
|
|
520
|
+
renderCalendar(entries) {
|
|
521
|
+
const byDate = {}
|
|
522
|
+
for (const en of entries) { (byDate[en.date] ||= []).push(en) }
|
|
523
|
+
const dates = Object.keys(byDate).sort()
|
|
524
|
+
const lines = ['# 日历与日程 (CALENDAR)', '', '> 由 dsh-auto-memory 维护;AI 可从对话中提取 deadline/约定写入,用户也可在 GUI 操作。', '']
|
|
525
|
+
for (const date of dates) {
|
|
526
|
+
lines.push('## ' + date)
|
|
527
|
+
for (const en of byDate[date].sort((a, b) => (a.time || '').localeCompare(b.time || ''))) {
|
|
528
|
+
const mark = en.done ? 'x' : ' '
|
|
529
|
+
const note = en.note ? ' | ' + en.note : ''
|
|
530
|
+
lines.push('- [' + mark + '] ' + (en.time || '--:--') + ' | ' + (en.quadrant || '未分类') + ' | ' + en.title + note)
|
|
531
|
+
}
|
|
532
|
+
lines.push('')
|
|
533
|
+
}
|
|
534
|
+
return lines.join('\n')
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/** 添加/更新日历条目并落盘(用户级)。 */
|
|
538
|
+
async calendarAdd(item, agent) {
|
|
539
|
+
const p = await this.resolvePaths(agent)
|
|
540
|
+
const entries = this.parseCalendar(this.state.calendarText || await this.readTextSafe(p.calendarPath))
|
|
541
|
+
entries.push({
|
|
542
|
+
date: item.date || todayStr(), done: !!item.done, time: item.time || '--:--',
|
|
543
|
+
quadrant: item.quadrant || '未分类', title: String(item.title || '').trim(), note: String(item.note || '').trim(),
|
|
544
|
+
})
|
|
545
|
+
const body = this.renderCalendar(entries)
|
|
546
|
+
await this.writeFull(p.calendarPath, body)
|
|
547
|
+
this.state.calendarText = body; this.state.loadedAt = Date.now()
|
|
548
|
+
return '已加入日历: ' + item.date + ' ' + (item.time || '') + ' ' + item.title + ' (' + (item.quadrant || '未分类') + ')'
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
/** 标记条目完成。 */
|
|
552
|
+
async calendarDone(date, time, title, agent) {
|
|
553
|
+
const p = await this.resolvePaths(agent)
|
|
554
|
+
const entries = this.parseCalendar(this.state.calendarText || await this.readTextSafe(p.calendarPath))
|
|
555
|
+
const hit = entries.find((en) => en.date === date && en.time === time && en.title === title)
|
|
556
|
+
if (!hit) return '未找到该日历条目: ' + date + ' ' + time + ' ' + title
|
|
557
|
+
hit.done = true
|
|
558
|
+
const body = this.renderCalendar(entries)
|
|
559
|
+
await this.writeFull(p.calendarPath, body)
|
|
560
|
+
this.state.calendarText = body; this.state.loadedAt = Date.now()
|
|
561
|
+
return '已标记完成: ' + date + ' ' + title
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/** 删除条目。 */
|
|
565
|
+
async calendarRemove(date, time, title, agent) {
|
|
566
|
+
const p = await this.resolvePaths(agent)
|
|
567
|
+
const entries = this.parseCalendar(this.state.calendarText || await this.readTextSafe(p.calendarPath))
|
|
568
|
+
const before = entries.length
|
|
569
|
+
const kept = entries.filter((en) => !(en.date === date && en.time === time && en.title === title))
|
|
570
|
+
if (kept.length === before) return '未找到该日历条目: ' + date + ' ' + time + ' ' + title
|
|
571
|
+
const body = this.renderCalendar(kept)
|
|
572
|
+
await this.writeFull(p.calendarPath, body)
|
|
573
|
+
this.state.calendarText = body; this.state.loadedAt = Date.now()
|
|
574
|
+
return '已删除日历条目: ' + date + ' ' + title
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/** 时段摘要:把今日日志按 时段(早晨/上午/下午/晚上)切分。 */
|
|
578
|
+
periodSummary() {
|
|
579
|
+
const today = this.state.logText || ''
|
|
580
|
+
const entries = today.split('\n').map((l) => l.trim()).filter((l) => l.startsWith('- ')).map((l) => {
|
|
581
|
+
const m = l.match(/^- (\d{2}):(\d{2}) (.*)$/)
|
|
582
|
+
return m ? { h: Number(m[1]), text: m[3] } : null
|
|
583
|
+
}).filter(Boolean)
|
|
584
|
+
const bucket = (h) => h < 5 ? '凌晨' : h < 9 ? '早晨' : h < 12 ? '上午' : h < 14 ? '中午' : h < 18 ? '下午' : '晚上'
|
|
585
|
+
const groups = { '凌晨': [], '早晨': [], '上午': [], '中午': [], '下午': [], '晚上': [] }
|
|
586
|
+
for (const en of entries) { (groups[bucket(en.h)] ||= []).push(en.text) }
|
|
587
|
+
return { entries, groups, todayDate: todayStr() }
|
|
588
|
+
}
|
|
589
|
+
|
|
457
590
|
/** 一键反思:自动取"有日志但无反思"的最早日期,按日志条目生成反思草稿并落盘。 */
|
|
458
591
|
async reflectAuto(agent) {
|
|
459
592
|
const p = await this.resolvePaths(agent)
|
|
@@ -534,6 +667,10 @@ class MemoryEngine {
|
|
|
534
667
|
todayEntries,
|
|
535
668
|
latestReflectionDate: this.state.latestReflectionDate,
|
|
536
669
|
pendingReflection: this.state.pendingReflection ? this.state.pendingReflection.date : undefined,
|
|
670
|
+
greeting: this.greetingData(),
|
|
671
|
+
calendar: this.parseCalendar(this.state.calendarText),
|
|
672
|
+
calendarPath: this.state.calendarPath,
|
|
673
|
+
periodSummary: this.periodSummary(),
|
|
537
674
|
refreshedAt: this.state.loadedAt,
|
|
538
675
|
configReadError: this._readError,
|
|
539
676
|
}
|
|
@@ -858,7 +995,7 @@ async function globOne(dir, re, limit) {
|
|
|
858
995
|
return out
|
|
859
996
|
}
|
|
860
997
|
|
|
861
|
-
/** 从一条 jsonl 会话行提取文本片段(兼容 claude/codex
|
|
998
|
+
/** 从一条 jsonl 会话行提取文本片段(兼容 claude/codex/workbuddy 格式)。 */
|
|
862
999
|
function extractJsonText(line) {
|
|
863
1000
|
try {
|
|
864
1001
|
const obj = JSON.parse(line)
|
|
@@ -915,7 +1052,7 @@ export function apply(ctx, config) {
|
|
|
915
1052
|
|
|
916
1053
|
// ---------- 工具 ----------
|
|
917
1054
|
const tools = [
|
|
918
|
-
defineTool('memory_log', '向当前工作区的 .dsh-memory/ 今日日志追加一条工作记录(append-only,自动建目录/文件)。完成实质性工作(改代码/修 bug/写文档/重构/技术选型/用户偏好约定)后必须调用;有跨会话长期价值的内容在同一轮内一并写入记忆(memory_note 项目/ memory_user 跨项目),progress 与 memory
|
|
1055
|
+
defineTool('memory_log', '向当前工作区的 .dsh-memory/ 今日日志追加一条工作记录(append-only,自动建目录/文件)。完成实质性工作(改代码/修 bug/写文档/重构/技术选型/用户偏好约定)后必须调用;有跨会话长期价值的内容在同一轮内一并写入记忆(memory_note 项目/ memory_user 跨项目),progress 与 memory 一起写;不要记录临时信息。**调用后必须在本轮回复正文(摘要可见的正文,不是工具调用区)中向用户转述一句:如"已把 X 记入今日日志"**。', {
|
|
919
1056
|
note: { type: 'string', required: true, description: '简短条目:一句话概括做了什么、结果如何。' },
|
|
920
1057
|
date: { type: 'string', description: '日志日期 YYYY-MM-DD,缺省今天。' },
|
|
921
1058
|
}, async (args, exec) => {
|
|
@@ -928,7 +1065,7 @@ export function apply(ctx, config) {
|
|
|
928
1065
|
return '已更新记忆文档: ' + logPath + '\n' + entry
|
|
929
1066
|
}),
|
|
930
1067
|
|
|
931
|
-
defineTool('memory_note', '更新当前项目长期笔记 .dsh-memory/MEMORY.md(本项目专属的约定、决策、架构要点)。action=append 追加一段(自动带日期标题);action=replace 整体替换(需先基于注入内容或 memory_recall 结果给出完整新内容)
|
|
1068
|
+
defineTool('memory_note', '更新当前项目长期笔记 .dsh-memory/MEMORY.md(本项目专属的约定、决策、架构要点)。action=append 追加一段(自动带日期标题);action=replace 整体替换(需先基于注入内容或 memory_recall 结果给出完整新内容)。**调用后必须在本轮回复正文中向用户转述:更新了项目笔记、加入什么要点**。', {
|
|
932
1069
|
content: { type: 'string', required: true, description: '笔记内容。' },
|
|
933
1070
|
action: { type: 'string', enum: ['append', 'replace'], required: true, description: 'append=追加, replace=整体替换。' },
|
|
934
1071
|
}, async (args, exec) => {
|
|
@@ -946,7 +1083,7 @@ export function apply(ctx, config) {
|
|
|
946
1083
|
return '已更新项目笔记: ' + p.notesPath + '\n追加内容:\n' + content
|
|
947
1084
|
}),
|
|
948
1085
|
|
|
949
|
-
defineTool('memory_user', '更新用户级记忆 ~/.dsh/memory/MEMORY.md(跨所有项目的长期规则/偏好,用户明确要求记住时用)。action=append 追加;action=replace
|
|
1086
|
+
defineTool('memory_user', '更新用户级记忆 ~/.dsh/memory/MEMORY.md(跨所有项目的长期规则/偏好,用户明确要求记住时用)。action=append 追加;action=replace 整体替换。**调用后必须在本轮回复正文中向用户转述:已记住该规则/偏好**。', {
|
|
950
1087
|
content: { type: 'string', required: true, description: '要记住的规则或偏好内容。' },
|
|
951
1088
|
action: { type: 'string', enum: ['append', 'replace'], required: true, description: 'append=追加, replace=整体替换。' },
|
|
952
1089
|
}, async (args, exec) => {
|
|
@@ -964,7 +1101,7 @@ export function apply(ctx, config) {
|
|
|
964
1101
|
return '已更新用户级记忆: ' + p.userFile + '\n追加内容:\n' + content
|
|
965
1102
|
}),
|
|
966
1103
|
|
|
967
|
-
defineTool('memory_recall', '检索记忆:本地记忆文件(每日日志、项目笔记、用户级记忆、反思)关键词匹配 + 历史 DSH 会话全文检索(如部署启用)
|
|
1104
|
+
defineTool('memory_recall', '检索记忆:本地记忆文件(每日日志、项目笔记、用户级记忆、反思)关键词匹配 + 历史 DSH 会话全文检索(如部署启用)。用户提到过去的做法/讨论/决定而当前上下文没有时调用,查询必须自包含。**检索后必须在本轮回复正文中向用户转述:检索了什么、找到什么(或没找到)**。', {
|
|
968
1105
|
query: { type: 'string', required: true, description: '检索关键词或自包含描述。' },
|
|
969
1106
|
limit: { type: 'integer', description: '最多返回条数,缺省 8。' },
|
|
970
1107
|
}, async (args, exec) => engine.recall(args.query, args.limit, exec.agent)),
|
|
@@ -1010,6 +1147,40 @@ export function apply(ctx, config) {
|
|
|
1010
1147
|
}
|
|
1011
1148
|
return engine.external.importInto(String(args.source || ''), args.target === 'user' ? 'user' : 'project', engine, exec.agent)
|
|
1012
1149
|
}),
|
|
1150
|
+
|
|
1151
|
+
defineTool('calendar_add', '向用户级日历(~/.dsh/memory/CALENDAR.md)添加日程/事项。主动从对话中提取 deadline、约定时间、任务节点等信息写入日历(跨对话有效、重装不丢)。调用后必须在本轮回复正文中向用户转述:已把 X 记入日历。', {
|
|
1152
|
+
date: { type: 'string', description: '日期 YYYY-MM-DD,缺省今天。' },
|
|
1153
|
+
time: { type: 'string', description: '时间 HH:MM,无则 --:--。' },
|
|
1154
|
+
quadrant: { type: 'string', enum: ['重要紧急', '重要不紧急', '紧急不重要', '不重要不紧急'], description: '四象限分类,缺省重要不紧急。' },
|
|
1155
|
+
title: { type: 'string', required: true, description: '事项标题。' },
|
|
1156
|
+
note: { type: 'string', description: '备注/来源,如"来自对话:用户说周五交报告"。' },
|
|
1157
|
+
}, async (args, exec) => engine.calendarAdd({ date: args.date, time: args.time, quadrant: args.quadrant, title: args.title, note: args.note }, exec.agent)),
|
|
1158
|
+
|
|
1159
|
+
defineTool('calendar_list', '列出日历条目(可按日期过滤、含完成状态)。用于查看已有安排、回答"我最近有什么安排"等问题。', {
|
|
1160
|
+
date: { type: 'string', description: '过滤日期 YYYY-MM-DD,缺省全部(近 60 天)。' },
|
|
1161
|
+
}, async (args, exec) => {
|
|
1162
|
+
const entries = engine.parseCalendar(engine.state.calendarText)
|
|
1163
|
+
const target = args.date
|
|
1164
|
+
const list = entries.filter((en) => !target || en.date === target)
|
|
1165
|
+
if (!list.length) return '日历为空' + (target ? ' (' + target + ')' : '') + '。'
|
|
1166
|
+
const lines = ['日历条目(' + list.length + ' 个):']
|
|
1167
|
+
for (const en of list.sort((a, b) => (a.date + a.time).localeCompare(b.date + b.time)).slice(0, 40)) {
|
|
1168
|
+
lines.push('· ' + (en.done ? '[完成] ' : '[待办] ') + en.date + ' ' + en.time + ' | ' + en.quadrant + ' | ' + en.title + (en.note ? ' (' + en.note + ')' : ''))
|
|
1169
|
+
}
|
|
1170
|
+
return lines.join('\n')
|
|
1171
|
+
}),
|
|
1172
|
+
|
|
1173
|
+
defineTool('calendar_done', '标记日历条目完成。参数需与 calendar_list 结果一致(date/time/title)。', {
|
|
1174
|
+
date: { type: 'string', required: true, description: '日期 YYYY-MM-DD。' },
|
|
1175
|
+
time: { type: 'string', required: true, description: '时间 HH:MM 或 --:--。' },
|
|
1176
|
+
title: { type: 'string', required: true, description: '事项标题。' },
|
|
1177
|
+
}, async (args, exec) => engine.calendarDone(args.date, args.time, args.title, exec.agent)),
|
|
1178
|
+
|
|
1179
|
+
defineTool('calendar_remove', '删除日历条目。', {
|
|
1180
|
+
date: { type: 'string', required: true, description: '日期 YYYY-MM-DD。' },
|
|
1181
|
+
time: { type: 'string', required: true, description: '时间 HH:MM 或 --:--。' },
|
|
1182
|
+
title: { type: 'string', required: true, description: '事项标题。' },
|
|
1183
|
+
}, async (args, exec) => engine.calendarRemove(args.date, args.time, args.title, exec.agent)),
|
|
1013
1184
|
]
|
|
1014
1185
|
|
|
1015
1186
|
// ---------- 路由 ----------
|
|
@@ -1160,6 +1331,30 @@ export function apply(ctx, config) {
|
|
|
1160
1331
|
try { writeJson(res, 200, { result: await engine.reflectAuto() }) } catch (e) { writeJson(res, 500, { error: String(e && e.message ? e.message : e) }) }
|
|
1161
1332
|
},
|
|
1162
1333
|
},
|
|
1334
|
+
{
|
|
1335
|
+
kind: 'exact',
|
|
1336
|
+
path: API.calendar,
|
|
1337
|
+
handler: async (req, res) => {
|
|
1338
|
+
if (!isLoopbackRequest(req)) return writeJson(res, 403, { error: 'forbidden: loopback-only' })
|
|
1339
|
+
const method = req.method || 'GET'
|
|
1340
|
+
if (method === 'GET') {
|
|
1341
|
+
const entries = engine.parseCalendar(engine.state.calendarText)
|
|
1342
|
+
writeJson(res, 200, { entries, path: engine.state.calendarPath || '' })
|
|
1343
|
+
return
|
|
1344
|
+
}
|
|
1345
|
+
if (method === 'POST') {
|
|
1346
|
+
const body = await readJsonBody(req)
|
|
1347
|
+
if (!body) return writeJson(res, 400, { error: 'invalid body' })
|
|
1348
|
+
try {
|
|
1349
|
+
if (body.action === 'done') writeJson(res, 200, { result: await engine.calendarDone(body.date, body.time, body.title) })
|
|
1350
|
+
else if (body.action === 'remove') writeJson(res, 200, { result: await engine.calendarRemove(body.date, body.time, body.title) })
|
|
1351
|
+
else writeJson(res, 200, { result: await engine.calendarAdd(body) })
|
|
1352
|
+
} catch (e) { writeJson(res, 500, { error: String(e && e.message ? e.message : e) }) }
|
|
1353
|
+
return
|
|
1354
|
+
}
|
|
1355
|
+
writeJson(res, 405, { error: 'method not allowed' })
|
|
1356
|
+
},
|
|
1357
|
+
},
|
|
1163
1358
|
]
|
|
1164
1359
|
|
|
1165
1360
|
// ---------- 注册与清理 ----------
|