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.
- package/dist/cli.mjs +39910 -26359
- package/dist/engine-startup.mjs +51118 -25041
- package/dist/main.mjs +61139 -35062
- package/dist/mem7-worker.mjs +22969 -0
- package/dist/prompts/atomic-facts-memcell.md +80 -0
- package/dist/prompts/atomic-facts.md +108 -0
- package/dist/prompts/boundary.md +88 -0
- package/dist/prompts/case-compress.md +73 -0
- package/dist/prompts/case-filter.md +34 -0
- package/dist/prompts/case-rerank.md +12 -0
- package/dist/prompts/cluster-assign.md +25 -0
- package/dist/prompts/episode.md +65 -0
- package/dist/prompts/episodic-rerank.md +12 -0
- package/dist/prompts/foresight.md +148 -0
- package/dist/prompts/multi-query.md +61 -0
- package/dist/prompts/profile-compact.md +30 -0
- package/dist/prompts/profile-update.md +66 -0
- package/dist/prompts/profile.md +43 -0
- package/dist/prompts/refined-query.md +43 -0
- package/dist/prompts/skill-failure.md +84 -0
- package/dist/prompts/skill-maturity.md +49 -0
- package/dist/prompts/skill-rerank.md +23 -0
- package/dist/prompts/skill-success.md +103 -0
- package/dist/prompts/sufficiency.md +41 -0
- package/dist/prompts/tool-pre-compress.md +39 -0
- package/package.json +110 -95
- package/templates/config.template.json +25 -0
- package/templates/skills/email/LICENSE.txt +22 -0
- package/templates/skills/email/SKILL.md +115 -0
- package/templates/skills/email/assets/1-account-menu.jpg +0 -0
- package/templates/skills/email/assets/163/1-settings-menu.jpg +0 -0
- package/templates/skills/email/assets/163/2-service-closed.jpg +0 -0
- package/templates/skills/email/assets/163/3-security-warning.jpg +0 -0
- package/templates/skills/email/assets/163/5-imap-code.jpg +0 -0
- package/templates/skills/email/assets/2-security-settings-generate.jpg +0 -0
- package/templates/skills/email/assets/3-sms-verify.jpg +0 -0
- package/templates/skills/email/assets/4-wechat-send-confirm.jpg +0 -0
- package/templates/skills/email/assets/5-code-generated.jpg +0 -0
- package/templates/skills/email/package.json +12 -0
- package/templates/skills/email/scripts/email-config.js +114 -0
- package/templates/skills/email/scripts/email-config.test.js +93 -0
- package/templates/skills/email/scripts/email-gate.js +124 -0
- package/templates/skills/email/scripts/email-gate.test.js +62 -0
- package/templates/skills/email/scripts/imap.js +540 -0
- package/templates/skills/email/scripts/imap.test.js +86 -0
- package/templates/skills/email/scripts/smtp.js +238 -0
- package/templates/skills/email/scripts/smtp.test.js +44 -0
- package/templates/workspace/scripts/emotion_state_machine.mjs +13 -13
- package/src/memory/everos/python/__pycache__/agentic_search.cpython-314.pyc +0 -0
- 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
|
-
|
|
531
|
-
|
|
532
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
`
|
|
Binary file
|