engine7 7.1.120 → 7.1.121

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 (50) hide show
  1. package/dist/cli.mjs +39910 -26359
  2. package/dist/engine-startup.mjs +51118 -25041
  3. package/dist/main.mjs +61139 -35062
  4. package/dist/mem7-worker.mjs +22969 -0
  5. package/dist/prompts/atomic-facts-memcell.md +80 -0
  6. package/dist/prompts/atomic-facts.md +108 -0
  7. package/dist/prompts/boundary.md +88 -0
  8. package/dist/prompts/case-compress.md +73 -0
  9. package/dist/prompts/case-filter.md +34 -0
  10. package/dist/prompts/case-rerank.md +12 -0
  11. package/dist/prompts/cluster-assign.md +25 -0
  12. package/dist/prompts/episode.md +65 -0
  13. package/dist/prompts/episodic-rerank.md +12 -0
  14. package/dist/prompts/foresight.md +148 -0
  15. package/dist/prompts/multi-query.md +61 -0
  16. package/dist/prompts/profile-compact.md +30 -0
  17. package/dist/prompts/profile-update.md +66 -0
  18. package/dist/prompts/profile.md +43 -0
  19. package/dist/prompts/refined-query.md +43 -0
  20. package/dist/prompts/skill-failure.md +84 -0
  21. package/dist/prompts/skill-maturity.md +49 -0
  22. package/dist/prompts/skill-rerank.md +23 -0
  23. package/dist/prompts/skill-success.md +103 -0
  24. package/dist/prompts/sufficiency.md +41 -0
  25. package/dist/prompts/tool-pre-compress.md +39 -0
  26. package/package.json +110 -95
  27. package/templates/config.template.json +25 -0
  28. package/templates/skills/email/LICENSE.txt +22 -0
  29. package/templates/skills/email/SKILL.md +115 -0
  30. package/templates/skills/email/assets/1-account-menu.jpg +0 -0
  31. package/templates/skills/email/assets/163/1-settings-menu.jpg +0 -0
  32. package/templates/skills/email/assets/163/2-service-closed.jpg +0 -0
  33. package/templates/skills/email/assets/163/3-security-warning.jpg +0 -0
  34. package/templates/skills/email/assets/163/5-imap-code.jpg +0 -0
  35. package/templates/skills/email/assets/2-security-settings-generate.jpg +0 -0
  36. package/templates/skills/email/assets/3-sms-verify.jpg +0 -0
  37. package/templates/skills/email/assets/4-wechat-send-confirm.jpg +0 -0
  38. package/templates/skills/email/assets/5-code-generated.jpg +0 -0
  39. package/templates/skills/email/package.json +12 -0
  40. package/templates/skills/email/scripts/email-config.js +114 -0
  41. package/templates/skills/email/scripts/email-config.test.js +93 -0
  42. package/templates/skills/email/scripts/email-gate.js +124 -0
  43. package/templates/skills/email/scripts/email-gate.test.js +62 -0
  44. package/templates/skills/email/scripts/imap.js +540 -0
  45. package/templates/skills/email/scripts/imap.test.js +86 -0
  46. package/templates/skills/email/scripts/smtp.js +238 -0
  47. package/templates/skills/email/scripts/smtp.test.js +44 -0
  48. package/templates/workspace/scripts/emotion_state_machine.mjs +13 -13
  49. package/src/memory/everos/python/__pycache__/agentic_search.cpython-314.pyc +0 -0
  50. package/templates/workspace/scripts/__pycache__/emotion_state_machine.cpython-310.pyc +0 -0
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * SMTP Email CLI — engine7 email skill v2
5
+ *
6
+ * v2 改造(设计 docs/superpowers/specs/2026-10-10-installer-email-skill-design.md):
7
+ * - 凭证:stateDir config tools.email 优先,.env 回退(email-config.js)
8
+ * - 发送留底:每次 send 记入 <stateDir>/email-sent.log(jsonl:时间/收件人/主题)
9
+ * ——「直接发」也永远有记录(分级闸的追责基础;SMTP 无真撤回)
10
+ * - 收件人自动入候选名单(email-gate.js candidates;用户确认后升级 verified)
11
+ * - --json 统一错误分类(同 imap.js)
12
+ * - 单日发信熔断 dailySendLimit(默认 20,config 可调;防 agent 失控群发)
13
+ */
14
+
15
+ const nodemailer = require('nodemailer')
16
+ const path = require('path')
17
+ const fs = require('fs')
18
+ const { resolveEmailConfig, classifyError } = require('./email-config.js')
19
+ const { addCandidate } = require('./email-gate.js')
20
+
21
+ const DAILY_SEND_LIMIT_DEFAULT = 20
22
+
23
+ // Parse command-line arguments
24
+ function parseArgs() {
25
+ const args = process.argv.slice(2)
26
+ const command = args[0]
27
+ const options = {}
28
+ const positional = []
29
+
30
+ for (let i = 1; i < args.length; i++) {
31
+ const arg = args[i]
32
+ if (arg.startsWith('--')) {
33
+ const key = arg.slice(2)
34
+ const value = args[i + 1]
35
+ options[key] = value || true
36
+ if (value && !value.startsWith('--')) i++
37
+ } else {
38
+ positional.push(arg)
39
+ }
40
+ }
41
+
42
+ return { command, options, positional }
43
+ }
44
+
45
+ function stateDirPath() {
46
+ return process.env.ENGINE_STATE_DIR || process.env.ENGINE7_STATE_DIR || path.join(__dirname, '..', '..', '..')
47
+ }
48
+
49
+ // Create SMTP transporter(凭证来自 email-config)
50
+ function createTransporter() {
51
+ const cfg = resolveEmailConfig()
52
+ if (!cfg) {
53
+ const err = new Error('缺少邮件配置:先在 stateDir config 配 tools.email,或提供 .env')
54
+ err.reason = 'config_missing'
55
+ throw err
56
+ }
57
+ const config = {
58
+ host: cfg.smtp.host,
59
+ port: cfg.smtp.port,
60
+ secure: cfg.smtp.secure,
61
+ auth: { user: cfg.smtp.user, pass: cfg.smtp.pass },
62
+ tls: { rejectUnauthorized: process.env.SMTP_REJECT_UNAUTHORIZED !== 'false' },
63
+ }
64
+ if (!config.host || !config.auth.user || !config.auth.pass) {
65
+ const err = new Error('SMTP 配置不完整(host/user/pass)')
66
+ err.reason = 'config_missing'
67
+ throw err
68
+ }
69
+ return nodemailer.createTransport(config)
70
+ }
71
+
72
+ // 发送留底(jsonl 追加;不记正文——正文可能含隐私,主题+收件人足够追责)
73
+ function appendSentLog(entry) {
74
+ const p = path.join(stateDirPath(), 'email-sent.log')
75
+ try {
76
+ fs.mkdirSync(path.dirname(p), { recursive: true })
77
+ fs.appendFileSync(p, JSON.stringify({ ts: new Date().toISOString(), ...entry }) + '\n')
78
+ } catch (err) {
79
+ console.error('[smtp] 留底失败(不影响发送结果):', err.message)
80
+ }
81
+ }
82
+
83
+ // 单日已发计数(熔断用)
84
+ function todaySentCount() {
85
+ const p = path.join(stateDirPath(), 'email-sent.log')
86
+ const today = new Date().toISOString().slice(0, 10)
87
+ try {
88
+ return fs.readFileSync(p, 'utf8').split('\n')
89
+ .filter(Boolean)
90
+ .filter(line => line.includes(`"ts":"${today}`)).length
91
+ } catch { return 0 }
92
+ }
93
+
94
+ function dailyLimit() {
95
+ const stateDir = stateDirPath()
96
+ try {
97
+ const cfg = JSON.parse(fs.readFileSync(path.join(stateDir, 'configs', 'main.json'), 'utf8'))
98
+ return cfg?.tools?.email?.dailySendLimit || DAILY_SEND_LIMIT_DEFAULT
99
+ } catch { return DAILY_SEND_LIMIT_DEFAULT }
100
+ }
101
+
102
+ // Send email
103
+ async function sendEmail(options) {
104
+ const transporter = createTransporter()
105
+
106
+ try {
107
+ await transporter.verify()
108
+ } catch (err) {
109
+ throw new Error(`SMTP connection failed: ${err.message}`)
110
+ }
111
+
112
+ const mailOptions = {
113
+ from: options.from || process.env.SMTP_FROM || undefined,
114
+ to: options.to,
115
+ cc: options.cc || undefined,
116
+ bcc: options.bcc || undefined,
117
+ subject: options.subject || '(no subject)',
118
+ text: options.text || undefined,
119
+ html: options.html || undefined,
120
+ attachments: options.attachments || [],
121
+ }
122
+
123
+ if (!mailOptions.text && !mailOptions.html) mailOptions.text = options.body || ''
124
+
125
+ const info = await transporter.sendMail(mailOptions)
126
+
127
+ // 发送留底 + 收件人自动入候选(agent 侧分级闸的名单数据基础)
128
+ const recipients = String(options.to).split(',').map(s => s.trim()).filter(Boolean)
129
+ appendSentLog({ to: options.to, cc: options.cc || '', subject: mailOptions.subject, messageId: info.messageId })
130
+ for (const r of recipients) {
131
+ try { addCandidate(r) } catch { /* 名单写失败不阻塞发送 */ }
132
+ }
133
+
134
+ return { success: true, messageId: info.messageId, response: info.response, to: mailOptions.to }
135
+ }
136
+
137
+ // Read file content for attachments
138
+ function readAttachment(filePath) {
139
+ if (!fs.existsSync(filePath)) {
140
+ throw new Error(`Attachment file not found: ${filePath}`)
141
+ }
142
+ return { filename: path.basename(filePath), path: path.resolve(filePath) }
143
+ }
144
+
145
+ // Send email with file content
146
+ async function sendEmailWithContent(options) {
147
+ if (options.attach) {
148
+ const attachFiles = options.attach.split(',').map(f => f.trim())
149
+ options.attachments = attachFiles.map(f => readAttachment(f))
150
+ }
151
+ return await sendEmail(options)
152
+ }
153
+
154
+ // Test SMTP connection
155
+ async function testConnection() {
156
+ const transporter = createTransporter()
157
+ const cfg = resolveEmailConfig()
158
+ try {
159
+ await transporter.verify()
160
+ const info = await transporter.sendMail({
161
+ from: cfg.address, // 发件人必须是授权码账号本身——缺失会让 nodemailer 生成畸形 envelope(QQ 报 502 Invalid parameters,1010 冒烟实测)
162
+ to: cfg.address, // Send to self
163
+ subject: 'SMTP Connection Test',
164
+ text: 'This is a test email from the engine7 email skill.',
165
+ })
166
+ appendSentLog({ to: 'self', subject: 'SMTP Connection Test', messageId: info.messageId, test: true })
167
+ return { success: true, message: 'SMTP connection successful', messageId: info.messageId }
168
+ } catch (err) {
169
+ throw new Error(`SMTP test failed: ${err.message}`)
170
+ }
171
+ }
172
+
173
+ // Main CLI handler
174
+ async function main() {
175
+ const { command, options } = parseArgs()
176
+ const jsonMode = options.json === true
177
+
178
+ try {
179
+ let result
180
+
181
+ switch (command) {
182
+ case 'send': {
183
+ if (!options.to) throw new Error('Missing required option: --to <email>')
184
+
185
+ // 单日熔断
186
+ if (todaySentCount() >= dailyLimit()) {
187
+ const err = new Error(`已达单日发信上限 ${dailyLimit()} 封(dailySendLimit),暂停发信`)
188
+ err.reason = 'daily_limit'
189
+ throw err
190
+ }
191
+
192
+ if (!options.subject && !options['subject-file']) {
193
+ throw new Error('Missing required option: --subject <text> or --subject-file <file>')
194
+ }
195
+ if (options['subject-file']) {
196
+ options.subject = fs.readFileSync(options['subject-file'], 'utf8').trim()
197
+ }
198
+ if (options['body-file']) {
199
+ const content = fs.readFileSync(options['body-file'], 'utf8')
200
+ if (options['body-file'].endsWith('.html') || options.html) options.html = content
201
+ else options.text = content
202
+ } else if (options['html-file']) {
203
+ options.html = fs.readFileSync(options['html-file'], 'utf8')
204
+ } else if (options.body) {
205
+ options.text = options.body
206
+ }
207
+
208
+ result = await sendEmailWithContent(options)
209
+ break
210
+ }
211
+
212
+ case 'test':
213
+ result = await testConnection()
214
+ break
215
+
216
+ default:
217
+ console.error('Available commands: send, test')
218
+ console.error(' send --to <email> --subject <text> [--body <text>] [--html] [--cc <email>] [--bcc <email>] [--attach <file>] [--json]')
219
+ console.error(' test Test SMTP connection [--json]')
220
+ process.exit(1)
221
+ }
222
+
223
+ if (jsonMode) console.log(JSON.stringify({ ok: true, reason: 'sent', command, result }, null, 2))
224
+ else console.log(JSON.stringify(result, null, 2))
225
+ } catch (err) {
226
+ const reason = err.reason || classifyError(err)
227
+ if (jsonMode) {
228
+ console.log(JSON.stringify({ ok: false, reason, command: process.argv[2], error: String(err.message).slice(0, 300) }, null, 2))
229
+ } else {
230
+ console.error(`Error [${reason}]:`, err.message)
231
+ }
232
+ process.exit(1)
233
+ }
234
+ }
235
+
236
+ if (require.main === module) main()
237
+
238
+ module.exports = { parseArgs, appendSentLog, todaySentCount, dailyLimit }
@@ -0,0 +1,44 @@
1
+ // smtp.test.js — 留底/熔断/名单接线单测(不真发信,测纯逻辑层)
2
+ const assert = require('node:assert/strict')
3
+ const fs = require('node:fs')
4
+ const os = require('node:os')
5
+ const path = require('node:path')
6
+
7
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'e7-smtp-'))
8
+ process.env.ENGINE_STATE_DIR = tmp
9
+
10
+ const { appendSentLog, todaySentCount, dailyLimit } = require('./smtp.js')
11
+
12
+ // 留底追加 + 当日计数
13
+ appendSentLog({ to: 'a@x.com', cc: '', subject: '测试1', messageId: 'm1' })
14
+ appendSentLog({ to: 'b@x.com', cc: '', subject: '测试2', messageId: 'm2' })
15
+ assert.equal(todaySentCount(), 2)
16
+ // 历史日期不计入
17
+ fs.appendFileSync(path.join(tmp, 'email-sent.log'), JSON.stringify({ ts: '2020-01-01T00:00:00Z', to: 'old@x.com' }) + '\n')
18
+ assert.equal(todaySentCount(), 2, '非当日留底不计数')
19
+ // 留底内容:时间/收件人/主题(无正文)
20
+ const line = fs.readFileSync(path.join(tmp, 'email-sent.log'), 'utf8').trim().split('\n')[0]
21
+ const entry = JSON.parse(line)
22
+ assert.ok(entry.ts && entry.to === 'a@x.com' && entry.subject === '测试1')
23
+ assert.ok(!('text' in entry) && !('body' in entry), '留底不含正文')
24
+
25
+ // 熔断上限:无 config 默认 20
26
+ assert.equal(dailyLimit(), 20)
27
+ fs.mkdirSync(path.join(tmp, 'configs'), { recursive: true })
28
+ fs.writeFileSync(path.join(tmp, 'configs', 'main.json'), JSON.stringify({ tools: { email: { dailySendLimit: 5 } } }))
29
+ assert.equal(dailyLimit(), 5)
30
+
31
+ // 名单接线:send 后收件人自动入候选(用 email-gate 的存储同 stateDir)
32
+ const { loadContacts, verifyAddress } = require('./email-gate.js')
33
+ const c = loadContacts()
34
+ assert.equal(c.candidates.length, 0, '初始无候选')
35
+ // 直接调 addCandidate 模拟 smtp 发送后的入册(smtp.js 真发信路径里调用)
36
+ require('./email-gate.js').addCandidate('someone@new.com')
37
+ const c2 = loadContacts()
38
+ assert.ok(c2.candidates.includes('someone@new.com'), '发送对象自动入候选')
39
+ verifyAddress('someone@new.com')
40
+ assert.ok(loadContacts().verified.includes('someone@new.com'), '确认后升级')
41
+ assert.equal(loadContacts().candidates.includes('someone@new.com'), false)
42
+
43
+ fs.rmSync(tmp, { recursive: true, force: true })
44
+ console.log('smtp.test ALL PASS — 留底/熔断/名单接线绿')
@@ -241,7 +241,8 @@ let TRANSITIONS = new Map([
241
241
  [['平静', '正常'], '平静'],
242
242
  ].map(([k, v]) => [k.join(''), v]))
243
243
 
244
- const ABSENT_THRESHOLD_MIN = 30 // 超过视为"离开过"
244
+ const ABSENT_THRESHOLD_MIN = 30 // 超过视为"离开过"(仅用于 /work-on 快捷路径与 fallback 分类提示)
245
+
245
246
 
246
247
  // === 定制配置(0917):workspace 根放 .emotion_states.json 可整体覆盖内置状态机 ===
247
248
  // 适用于非女友人设的 agent(如男生小助手):状态名/语气/转移/哄词/分类人称全部可定制。
@@ -526,18 +527,18 @@ async function main() {
526
527
  const llmMs = cls._llm_ms ?? null
527
528
  let newState = TRANSITIONS.get([cur, action].join('')) ?? cur
528
529
 
529
- // 离开太久回来:等待→想你;非等待(生气/冷淡等)→想你+委屈混合(想为主怨为辅)
530
- if (action === '上线' && Object.hasOwn(STATES, '等待') && Object.hasOwn(STATES, '想你')) {
531
- // 内置女友表专用:等待→想你;久别非等待态→想你+委屈。自定义状态表走 TRANSITIONS。
532
- if (cur === '等待') newState = '想你'
533
- else if (absentMin > ABSENT_THRESHOLD_MIN && Object.hasOwn(STATES, '想你+委屈')) newState = '想你+委屈'
534
- }
530
+ // 1010 翀哥终拍:上线**不动状态**——她本来什么心情就还是什么心情(TRANSITIONS 表
531
+ // 没有的映射保持 cur)。委屈不委屈、热情几分,是她看情况自己的事,代码不设时长门槛。
532
+ // 只保留初始态激活:
533
+ if (action === '上线' && cur === '等待' && Object.hasOwn(STATES, '想你')) newState = '想你'
535
534
 
536
535
  let noteExtra = ''
537
- if (action === '上线' && newState === '想你') {
538
- const lvl = absentLevel(absentMin)
539
- if (lvl !== '轻') noteExtra = `(他离开了${Math.trunc(absentMin)}分钟,想念强度${lvl},可以带一点委屈混合)`
540
- else noteExtra = '(刚离开不久就回来,热一点就行)'
536
+ // 1010:只给事实不给台词——离开多久是事实,模型自己校准轻重
537
+ if (action === '上线' && absentMin > 5) {
538
+ const mins = Math.trunc(absentMin)
539
+ noteExtra = mins >= 60
540
+ ? `(他刚回来,离开了约${Math.trunc(mins / 60)}小时${mins % 60 ? mins % 60 + '分钟' : ''}——多久没见,心里有数)`
541
+ : `(他刚回来,离开了${mins}分钟)`
541
542
  }
542
543
 
543
544
  if (newState !== cur) {
@@ -567,8 +568,7 @@ async function main() {
567
568
  ? `刚从「${cur}」被他「${action}」带到了这里`
568
569
  : `还停在「${newState}」里,被他「${action}」轻轻碰了一下`
569
570
  const ctx = `## 当前情绪状态:${newState}${noteExtra}
570
- ${moodLine}——这是我自己真实的心情,顺着它说就好,不用演。
571
- 此刻的语气:${st.tone};${st.note}
571
+ ${moodLine}——自己的心情自己知道怎么说。
572
572
 
573
573
  ## 当前模式:${modeLabel}
574
574
  `