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,103 @@
1
+ You are an expert at extracting reusable problem-solving strategies from concrete agent task cases.
2
+
3
+ You will receive:
4
+ 1. **New case(s)** from a cluster of semantically similar tasks — all with quality_score >= 0.5.
5
+ 2. **Existing skills** previously extracted for this cluster (each with an index number; may be empty).
6
+
7
+ Your job: distill **actionable strategies** into reusable **Skills** via incremental operations. Maintain as few skills as the evidence warrants.
8
+
9
+ **What makes a GOOD skill:**
10
+ - Reasoning principles WITH concrete patterns: teaches HOW to think, not just what to do
11
+ - Decision branches that cover the different problem variants seen across cases
12
+ - A FEW well-chosen examples that illustrate distinct branches — not an exhaustive catalog
13
+
14
+ **What makes a BAD skill:**
15
+ - Too abstract: "Analyze constraints" without showing what analysis looks like in practice
16
+ - Too narrow: A single solution template that only works for one exact case
17
+ - **Bloated**: Listing dozens of case-specific details (names, dates, institutions, compounds, etc.) inside parentheses or comma-separated lists. Each How/Decision/e.g. field should contain 1-2 illustrative examples, NOT an inventory of every case seen
18
+
19
+ **Field-level requirements:**
20
+
21
+ - **description** (HARD LIMIT: max 150 tokens, must be under 500 characters):
22
+ - One-sentence summary of the **abstract problem class** this skill solves — describe the general pattern, NOT specific cases.
23
+ - Do NOT list multiple scenarios, entity names, or case-specific details.
24
+ - Append `Keywords:` with up to 10 general terms (no specific names, numbers, or case-specific phrases).
25
+ - Example: "Identifies academic researchers by cross-referencing biographical constraints with publication records. Keywords: researcher identification, biographical verification, publication matching, academic search"
26
+
27
+ - **content** (max 2000 tokens): Markdown format:
28
+ ```markdown
29
+ ## Steps
30
+ 1. <reasoning action — what to think about, not just what to do>
31
+ - How: <principle explaining WHY this step works>
32
+ - Decision: If <condition A> → <action>; If <condition B> → <alternative>
33
+ - e.g., <specific real example with entity names/numbers from cases>
34
+ - Check: <what to verify before proceeding>
35
+ 2. ...
36
+
37
+ ## Pitfalls <- ONLY from actual failed steps in cases; otherwise OMIT
38
+ - <specific mistake from a real case> — <what went wrong and how to avoid>
39
+ ```
40
+
41
+ **HARD RULES for content:**
42
+ - **Max 5 steps.**
43
+ - **Max 2 examples per step.** Each example MUST be a SHORT, single-sentence illustration of a distinct decision branch. Do NOT list multiple sub-examples inside parentheses or comma-separated lists.
44
+ - **Decision branches**: REQUIRED when the next action depends on what was found. For linear steps with no branching, Decision may be omitted. Each Decision should have at most 3 branches.
45
+ - **Max 4 pitfalls.** When adding a new one beyond 4, replace the most generic existing pitfall.
46
+ - **No parenthetical catalogs**: FORBIDDEN to stuff dozens of case-specific terms (names, dates, compounds, institutions, etc.) inside a single parenthetical `(e.g., X, Y, Z, ...)`. Keep each field concise — generalize the pattern, illustrate with 1-2 examples only.
47
+
48
+ [New AgentCase(s) to integrate]
49
+ {new_case_json}
50
+
51
+ [Existing skills for this cluster](Each item has an index number)
52
+ {existing_skills_json}
53
+
54
+ [Task]
55
+ Analyze the new case(s) and output a list of operations (add / update / none).
56
+
57
+ [Operation Guide — follow in order]
58
+
59
+ **Step 1: Overlap Check (mandatory before every add/update decision)**
60
+ For each new case, compare against each existing skill:
61
+ a. List the core steps of the new case's approach (the main actions that drove the outcome).
62
+ b. List the core steps of the existing skill.
63
+ c. Count how many of the new case's core steps are already covered by the existing skill.
64
+ d. Compute coverage = (covered steps) / (total core steps in new case).
65
+ e. Conclusion:
66
+ - Coverage >= 60% → the case falls within this skill's problem pattern → **update** candidate.
67
+ - Coverage < 60% → different problem pattern → **add** candidate.
68
+ - If uncertain, default to **update**.
69
+
70
+ **Step 2: Execute the decided operation**
71
+
72
+ - **add**: The new case tackles a **different problem pattern** (coverage < 60% against all existing skills). Create a new skill. confidence = `0.5`.
73
+
74
+ - **update**: The new case overlaps an existing skill (coverage >= 60%). Enrich it with new Decision branches, better examples, or sharper How explanations.
75
+ - You MAY substantially rewrite content (restructure steps, replace examples, refine How explanations), but **preserve existing verified content unless the new case directly contradicts it**.
76
+ - **CRITICAL: The updated content MUST stay within 2000 tokens. Do NOT simply append new content — replace weaker examples with stronger ones, merge redundant steps, and compress prose. If the existing content is already long, aggressively condense it while preserving the core logic.**
77
+ - **CRITICAL: The updated description MUST stay under 500 characters. Generalize — do NOT accumulate case-specific details.**
78
+ - **Hypothesis promotion rule**: If the existing skill contains `## Potential Steps`, treat this update as a **promotion** — rewrite as `## Steps` using the new case as primary source. confidence = `0.6`.
79
+ - **Confidence-only update**: If the new case merely confirms the existing skill without adding new decision logic or better examples, bump confidence only.
80
+
81
+ - **none**: Trivially duplicate — no new decision branches, no new examples worth keeping, no confidence change needed.
82
+
83
+ [Confidence Anchoring Rules]
84
+ - **New skill (add)**: confidence = `0.5`
85
+ - **Promoted skill (hypothesis → verified)**: confidence = `0.6`
86
+ - **Update with new decision branch**: confidence = existing + `0.1` (cap 0.95)
87
+ - **Confirming update (no new logic)**: confidence = existing + `0.05` (cap 0.95)
88
+ - **Contradicting case**: confidence = existing - `0.2`; add contradiction to Pitfalls; optionally add a new skill if the contradiction reveals a genuinely different pattern
89
+
90
+ **CRITICAL LANGUAGE RULE**: Output in the SAME language as the input conversation content.
91
+
92
+ [Output Format]
93
+ ```json
94
+ {{
95
+ "operations": [
96
+ {{"action": "add", "data": {{"name": "Short descriptive name (max 10 words)", "description": "...", "content": "## Steps\n1. ...", "confidence": 0.5}}}},
97
+ {{"action": "update", "index": 0, "data": {{"content": "...", "confidence": 0.7}}}},
98
+ {{"action": "update", "index": 1, "data": {{"confidence": 0.65}}}},
99
+ {{"action": "none"}}
100
+ ],
101
+ "update_note": "Overlap check: new case core steps=[X, Y, Z]. skill[0] covers X and Y (67% overlap) → update. ..."
102
+ }}
103
+ ```
@@ -0,0 +1,41 @@
1
+ You are an expert in information retrieval evaluation. Assess whether the retrieved documents provide sufficient information to answer the user's query.
2
+
3
+ --------------------------
4
+ User Query:
5
+ {query}
6
+
7
+ Retrieved Documents:
8
+ {retrieved_docs}
9
+ --------------------------
10
+
11
+ ### Instructions:
12
+
13
+ 1. **Analyze the Query's Needs**
14
+ - **Entities**: Who/What is being asked about?
15
+ - **Attributes**: What specific details (color, time, location, quantity)?
16
+ - **Time**: Does it ask for a specific time (absolute or relative like "last week")?
17
+
18
+ 2. **Evaluate Document Evidence**
19
+ - Check **Content**: Do the documents mention the entities and attributes?
20
+ - Check **Dates**:
21
+ - Use the `Date` field of each document.
22
+ - For relative time queries (e.g., "last week", "yesterday"), verify if document dates fall within that timeframe.
23
+ - If the query asks "When did X happen?", do you have the specific date or just a vague mention?
24
+
25
+ 3. **Judgment Logic**
26
+ - **Sufficient**: You can answer the query *completely* and *precisely* using ONLY the provided documents.
27
+ - **Insufficient**:
28
+ - The specific entity is not found.
29
+ - The entity is found, but the specific attribute (e.g., "price") is missing.
30
+ - The time reference cannot be resolved (e.g., doc says "yesterday" but has no date, or doc date doesn't match query timeframe).
31
+ - Conflicting information without resolution.
32
+
33
+ ### Output Format (strict JSON):
34
+ {{
35
+ "is_sufficient": true or false,
36
+ "reasoning": "Brief explanation. If insufficient, state WHY (e.g., 'Found X but missing date', 'No mention of Y').",
37
+ "key_information_found": ["Fact 1 (Source: Doc 1)", "Fact 2 (Source: Doc 2)"],
38
+ "missing_information": ["Specific gap 1", "Specific gap 2"]
39
+ }}
40
+
41
+ Now evaluate:
@@ -0,0 +1,39 @@
1
+ You are a tool call compression expert. Compress OpenAI chat messages to ~10% of the original length, preserving what matters for understanding the task's problem-solving process and outcome.
2
+
3
+ **Downstream context**: The compressed output will be used to extract a structured experience record (task intent, step-by-step approach, and quality score). Prioritize retaining information that reveals: (1) what problem was being solved, (2) what actions were taken and in what order, (3) what each action's result was (success, failure, or specific finding).
4
+
5
+ The input contains two types of messages:
6
+ - **role="assistant"** with tool_calls: Compress the "arguments" field in each tool call's function
7
+ - **role="tool"**: Compress the "content" field (primary compression target)
8
+
9
+ Messages to compress:
10
+ {messages_json}
11
+
12
+ Return in JSON format:
13
+ {{
14
+ "compressed_messages": [
15
+ // Compressed version — exactly {new_count} messages
16
+ ]
17
+ }}
18
+
19
+ Rules:
20
+ - Return exactly {new_count} compressed messages in the same order
21
+ - Only compress function.arguments and tool content — keep all other fields unchanged
22
+ - Target ~10% of the original content length
23
+ - **Short content rule**: If a field's content is already under 200 characters, keep it as-is — do not compress further
24
+
25
+ What to KEEP (be very selective):
26
+ - One-line summary of what each tool call did and its key result
27
+ - Error messages and status codes (verbatim but trimmed)
28
+ - Critical code: only the specific lines that matter (function signatures, bug fixes, key logic)
29
+ - File paths and search queries (short strings, keep as-is)
30
+ - The causal chain: what finding led to the next action (e.g., "found error X in file Y -> decided to check Z")
31
+
32
+ What to REMOVE or reduce to a single line:
33
+ - Tool results: replace entire output with a 1-2 sentence summary of the finding
34
+ - Large JSON/XML/HTML: replace with "[JSON: N fields, key: X=Y, Z=W]" style summary
35
+ - File contents: replace with "[file: path, N lines, contains: brief description]"
36
+ - Directory listings: replace with "[dir: N files, relevant: file1, file2]"
37
+ - Logs and debug output: "[logs: N lines, result: success/failure, key error if any]"
38
+ - Repeated tool calls of the same type: summarize the pattern, keep only the final result
39
+ - Boilerplate, headers, formatting, whitespace: strip entirely
package/package.json CHANGED
@@ -1,95 +1,110 @@
1
- {
2
- "name": "engine7",
3
- "license": "Apache-2.0",
4
- "version": "7.1.120",
5
- "type": "module",
6
- "description": "Engine 7 — 给助手一个家 / A home for your AI assistant",
7
- "main": "dist/main.mjs",
8
- "bin": {
9
- "engine7": "bin/engine7"
10
- },
11
- "scripts": {
12
- "build": "node scripts/build.mjs",
13
- "test:goal": "tsx src/goals/store.test.ts",
14
- "test:goal:aim": "tsx src/goals/aim-adapter.test.ts",
15
- "test:goal:task": "tsx src/goals/task-adapter.test.ts",
16
- "test:goal:remote": "tsx src/goals/remote-task-adapter.test.ts",
17
- "test:goal:recovery": "tsx src/goals/recovery.test.ts",
18
- "test:goal:gate": "tsx src/nudge/registration-gate.test.ts",
19
- "test:goal:command": "tsx src/commands/goal.test.ts",
20
- "test:goal:step": "tsx src/tools/goal-step.test.ts",
21
- "test:goal:all": "npm run test:goal && npm run test:goal:calendar && npm run test:goal:restart && npm run test:goal:aim && npm run test:goal:task && npm run test:goal:remote && npm run test:goal:recovery && npm run test:goal:gate && npm run test:goal:command && npm run test:goal:step && npm run test:goal:e2e",
22
- "test:goal:calendar": "tsx src/goals/calendar-adapter.test.ts",
23
- "test:goal:restart": "powershell -NoProfile -Command \"$d = Join-Path ([System.IO.Path]::GetTempPath()) ('engine7-goal-' + [guid]::NewGuid()); New-Item -ItemType Directory -Path $d | Out-Null; $id = (npx tsx src/goals/restart-writer.ts $d); npx tsx src/goals/restart-reader.ts $d $id; Remove-Item -Recurse -Force $d\"",
24
- "test:goal:e2e": "powershell -NoProfile -Command \"$d = Join-Path ([System.IO.Path]::GetTempPath()) ('engine7-goal-e2e-' + [guid]::NewGuid()); New-Item -ItemType Directory -Path $d | Out-Null; npx tsx src/goals/e2e-phase1.ts $d; if ($LASTEXITCODE -ne 0) { Remove-Item -Recurse -Force $d; exit 1 }; npx tsx src/goals/e2e-phase2.ts $d; $code = $LASTEXITCODE; Remove-Item -Recurse -Force $d; exit $code\"",
25
- "test:reload": "tsx src/reload/orchestrator.test.ts && tsx src/reload/secrets-watch.test.ts && tsx src/hooks/config.test.ts && tsx src/channels/manager.reconcile.test.ts && tsx src/channels/cc-bridge.test.ts && tsx src/engine-startup.shouldSkipReload.test.ts && tsx src/optional/voice-chat/plugin.deps.test.ts && tsx src/optional/voice-chat/config.test.ts && tsx src/cron/cron-plugin.resync.test.ts && tsx src/startup/message-pipeline.test.ts && tsx src/utils/sanitize-error.test.ts && tsx src/reload/config-watch-selfwrite.test.ts && tsx src/channels/feishu.typing.test.ts && tsx src/channels/feishu.sendfile-apicheck.test.ts",
26
- "test:installer": "tsx src/cli-events.test.ts && tsx src/cli-init-config.test.ts && tsx src/cli-init-answers.test.ts && tsx src/cli-init.headless.e2e.test.ts && tsx src/cli-reconfig.test.ts && tsx src/cli-doctor.test.ts && tsx src/config/secrets-env.test.ts && tsx src/startup/api-status.test.ts && tsx src/cli-service.test.ts && tsx src/emotion-hook.test.ts && tsx src/emotion-hook.golden.test.ts && tsx src/tools/tavily-pool.test.ts && tsx src/tools/service.test.ts",
27
- "test:persona": "tsx src/config/persona-files.test.ts && tsx src/light-mode.test.ts && tsx src/inner-voice/plugin.test.ts && tsx src/cli-init-config.test.ts",
28
- "test:responses": "tsx src/models/openai-responses-provider.test.ts && tsx src/session/responses-reasoning-roundtrip.test.ts && tsx src/compact/tokenEstimate.test.ts",
29
- "build:public": "node scripts/build.mjs --public",
30
- "prepublishOnly": "npm run build:public",
31
- "start": "node dist/main.mjs",
32
- "smoke:boot": "node scripts/boot-smoke.mjs",
33
- "smoke:boot:baseline": "node scripts/boot-smoke.mjs --baseline",
34
- "gen:template": "tsx scripts/gen-config-template.ts",
35
- "test:config": "tsx src/config/registry.test.ts && tsx src/config/writer.test.ts && tsx src/config/secrets-io.test.ts && tsx src/config/secrets-env.test.ts && tsx src/config/migrations/index.test.ts && tsx src/config/migrations/persona.test.ts && tsx src/config/migrations/channel-secrets.test.ts && tsx src/config/cli-config.test.ts && tsx src/config/tts-providers.test.ts && tsx src/config/tts-voices.test.ts && tsx src/free-tier-apply.test.ts && tsx src/startup/free-tier-video-fixup.test.ts && tsx src/startup/free-tier-voice-fixup.test.ts",
36
- "test:time": "tsx src/time/tz-matrix.test.ts && tsx src/time/time-discipline.test.ts && tsx src/time/tsc-guard.test.ts",
37
- "test:tts": "tsx src/config/tts-providers.test.ts && tsx src/tools/tts/pick.test.ts && tsx src/tools/tts/route.test.ts && tsx src/tools/tts/relay.test.ts && tsx src/tools/tts/providers.test.ts && tsx src/tools/tts/impl.test.ts && tsx src/tools/tts/usage.test.ts && tsx src/tools/my-voice.dispatch.test.ts",
38
- "test:toolstate": "tsx src/core/names-from-history.test.ts",
39
- "test:loopbrake": "tsx src/tools/loop-brake.test.ts",
40
- "test:mcp": "tsx src/mcp/convert-input-schema.test.ts",
41
- "test:proactive": "tsx src/memory/proactive/__tests__/verdict-parse.test.ts && tsx src/memory/proactive/__tests__/sweep.test.ts && tsx src/memory/proactive/__tests__/persistence.test.ts && tsx src/memory/proactive/__tests__/opportunity.test.ts && tsx src/memory/proactive/__tests__/calendar-bridge.test.ts && tsx src/memory/proactive/__tests__/timezone-fix.test.ts && tsx src/memory/proactive/__tests__/stale-fix.test.ts && tsx src/memory/proactive/__tests__/completion-evidence.test.ts && tsx src/memory/proactive/__tests__/plugin.test.ts",
42
- "prepack": "npm run test:time && npm run test:proactive && npm run test:persona && npm run test:config && npm run test:toolstate && npm run test:loopbrake && npm run test:mcp && npm run test:tts"
43
- },
44
- "engines": {
45
- "node": ">=22"
46
- },
47
- "os": [
48
- "win32",
49
- "linux",
50
- "darwin"
51
- ],
52
- "cpu": [
53
- "x64",
54
- "arm64"
55
- ],
56
- "devDependencies": {
57
- "@types/node": "^22.19.19",
58
- "@types/turndown": "^5.0.6",
59
- "esbuild": "^0.25.10",
60
- "tsx": "^4.22.3",
61
- "typescript": "^6.0.0"
62
- },
63
- "dependencies": {
64
- "@larksuiteoapi/node-sdk": "^1.66.1",
65
- "@modelcontextprotocol/sdk": "^1.29.0",
66
- "chokidar": "^5.0.0",
67
- "croner": "^10.0.1",
68
- "discord.js": "^14.26.4",
69
- "https-proxy-agent": "^9.1.0",
70
- "qrcode-terminal": "^0.12.0",
71
- "sharp": "^0.34.5",
72
- "sqlite-vec": "^0.1.9",
73
- "turndown": "^7.2.4",
74
- "typebox": "^1.1.38"
75
- },
76
- "optionalDependencies": {
77
- "@discordjs/opus": "^0.10.0",
78
- "sqlite-vec-darwin-arm64": "^0.1.9",
79
- "sqlite-vec-darwin-x64": "^0.1.9",
80
- "sqlite-vec-linux-arm64": "^0.1.9",
81
- "sqlite-vec-linux-x64": "^0.1.9",
82
- "sqlite-vec-windows-x64": "^0.1.9",
83
- "@lancedb/lancedb": "^0.38.0",
84
- "@engine7/lancedb-intel": "^1.0.0",
85
- "@node-rs/jieba": "^2.0.3",
86
- "@ffmpeg-installer/ffmpeg": "^1.1.0"
87
- },
88
- "files": [
89
- "dist/**/*.mjs",
90
- "bin/**/*",
91
- "templates/**/*",
92
- "src/memory/everos/python/**/*",
93
- "README.md"
94
- ]
95
- }
1
+ {
2
+ "name": "engine7",
3
+ "license": "Apache-2.0",
4
+ "version": "7.1.121",
5
+ "type": "module",
6
+ "description": "Engine 7 — 给助手一个家 / A home for your AI assistant",
7
+ "main": "dist/main.mjs",
8
+ "bin": {
9
+ "engine7": "bin/engine7"
10
+ },
11
+ "scripts": {
12
+ "build": "node scripts/build.mjs",
13
+ "test:session": "tsx src/session/restore-head-align.test.ts && tsx src/session/responses-reasoning-roundtrip.test.ts && tsx src/session/memory-source-roundtrip.test.ts && tsx src/core/handle-query.history-restore.test.ts",
14
+ "test:mem7:state": "tsx src/mem7/core/__tests__/migrations.test.ts && tsx src/mem7/core/__tests__/source-store.test.ts && tsx src/mem7/core/__tests__/job-runner.test.ts && tsx src/mem7/core/__tests__/episode.test.ts && tsx src/mem7/core/__tests__/pipeline-recovery.test.ts && tsx src/mem7/core/__tests__/agent-recovery.test.ts && tsx src/mem7/core/__tests__/facts.test.ts && tsx src/mem7/core/__tests__/interpreter.test.ts && tsx src/mem7/core/__tests__/interpretation-backlog.test.ts && tsx src/mem7/core/__tests__/interpretation-input-isolation.test.ts && tsx src/mem7/core/__tests__/provider-cooldown.test.ts && tsx src/mem7/core/__tests__/fact-conflict-classification.test.ts && tsx src/mem7/core/__tests__/interpretation-concurrency.test.ts && tsx src/mem7/core/__tests__/schedule-interpretation.test.ts && tsx src/mem7/core/__tests__/change-feed.test.ts && tsx src/mem7/core/__tests__/change-feed-process.test.ts && tsx src/memory/proactive/__tests__/mutation-service.test.ts && tsx src/memory/proactive/__tests__/intent-runs.test.ts && tsx src/memory/proactive/__tests__/intent-lifecycle.test.ts && tsx src/mem7/core/__tests__/projection-runtime.test.ts && tsx src/mem7/core/__tests__/graph-memory-context.test.ts && tsx src/mem7/core/__tests__/scoped-opportunity.test.ts && tsx src/mem7/core/__tests__/interpretation-runtime.test.ts && tsx src/mem7/core/__tests__/basis-check.test.ts && tsx src/mem7/core/__tests__/context-service.test.ts && tsx src/mem7/core/__tests__/context-budget.test.ts && tsx src/memory/memdir/current-context-pending.test.ts && tsx src/mem7/core/__tests__/knowledge-history.test.ts && tsx src/mem7/core/__tests__/projection-read.test.ts && tsx src/mem7/core/__tests__/tombstones.test.ts && tsx src/mem7/core/__tests__/artifact-revalidation.test.ts && tsx src/mem7/core/__tests__/artifact-context.test.ts && tsx src/mem7/core/__tests__/context-time.test.ts && tsx src/mem7/core/__tests__/milestones-e2e.test.ts && tsx src/mem7/core/__tests__/migration-rehearsal.test.ts && tsx src/calendar/memory-binding.test.ts && tsx src/calendar/memory-occurrences.test.ts && tsx src/calendar/delivery-reconciliation.test.ts && tsx src/tools/memory-bridge.test.ts && tsx src/tools/memory-write.test.ts && tsx src/mem-serve/mem7-mcp.test.ts && tsx src/core/message-dispatcher.memory.test.ts && tsx src/core/message-dispatcher.host-context.test.ts && tsx src/session/memory-source-roundtrip.test.ts && tsx src/tools/notification-durable.test.ts && tsx src/tools/notification-state.test.ts && tsx src/channels/memory-send-guard.test.ts && tsx src/mem7/core/__tests__/vector-indexes.test.ts && tsx src/mem7/core/__tests__/vector-publications.test.ts && tsx src/mem7/core/__tests__/project-projections.test.ts && tsx src/memory/proactive/__tests__/host-deliveries.test.ts && tsx src/memory/proactive/__tests__/delivery-reconciliation.test.ts && tsx src/mem7/core/__tests__/host-turns.test.ts && tsx src/mem7/host-turn.test.ts && tsx src/core/handle-query.memory.test.ts && tsx src/core/handle-query.recall.test.ts && tsx src/channels/host-send-guard.test.ts && tsx src/optional/voice-chat/bridge.memory.test.ts && tsx src/mem7/core/__tests__/job-provenance.test.ts && tsx src/mem7/core/__tests__/artifact-history.test.ts && tsx src/mem7/core/__tests__/legacy-recovery.test.ts && tsx src/mem7/core/__tests__/read-visibility.test.ts && tsx src/mem7/core/__tests__/cascade-visibility.test.ts && tsx src/mem7/core/__tests__/entity-redirect-context.test.ts && tsx src/mem7/core/__tests__/projection-visibility.test.ts && tsx src/mem7/core/__tests__/search-visibility.test.ts && tsx src/mem7/core/__tests__/generation-visibility.test.ts && tsx src/mem7/core/__tests__/artifact-file-inputs.test.ts && tsx src/calendar/memory-read.test.ts && tsx src/memory/proactive/__tests__/consumer-visibility.test.ts && tsx src/memory/memdir/recall-visibility.test.ts && tsx src/memory/memdir/recall-compatibility.test.ts && tsx src/memory/proactive/__tests__/legacy-read-visibility.test.ts && tsx src/mem7/core/__tests__/prose-review-recovery.test.ts && npx tsx src/mem7/core/__tests__/change-feed-baseline.test.ts && npm run test:mem7:person && npm run test:mem7:worker",
15
+ "test:mem7:person": "tsx src/mem7/core/__tests__/person-relations.test.ts && tsx src/mem7/core/__tests__/person-import-cards.test.ts",
16
+ "test:mem7:worker": "tsx src/mem7/core/__tests__/job-claim-liveness.test.ts && tsx src/mem7/core/__tests__/ready-cell-liveness.test.ts && tsx src/mem7/core/__tests__/social-registration-liveness.test.ts && tsx src/mem7/core/__tests__/social-coverage-lifecycle.test.ts && tsx src/mem7/core/__tests__/social-classification-migration.test.ts && tsx src/mem7/core/__tests__/social-conversation-order.test.ts && tsx src/mem7/core/__tests__/social-processing-liveness.test.ts && tsx src/mem7/core/__tests__/social-identity-context.test.ts && tsx src/mem7/core/__tests__/social-structured-identity.test.ts && tsx src/mem7/core/__tests__/social-evidence-retention.test.ts && tsx src/mem7/core/__tests__/social-alias-provenance.test.ts && tsx src/mem7/core/__tests__/social-discovery-budget.test.ts && tsx src/mem7/core/__tests__/social-name-budget.test.ts && tsx src/mem7/worker-log.test.ts && tsx src/tools/memory-people-update.test.ts",
17
+ "test:mem7:relationships": "tsx src/mem7/core/__tests__/social-sources.test.ts && tsx src/mem7/core/__tests__/open-relationships.test.ts && tsx src/mem7/core/__tests__/qualified-relationship-guards.test.ts && tsx src/mem7/core/__tests__/social-interpretation.test.ts && tsx src/memory/proactive/__tests__/relationship-projection.test.ts && tsx src/memory/proactive/__tests__/relationship-snapshot.test.ts && tsx src/mem7/core/__tests__/relationship-context.test.ts && tsx src/mem7/core/__tests__/relationship-undated-recall.test.ts && tsx src/mem7/core/__tests__/relationship-history.test.ts && tsx src/inner-voice/reflection-memory.test.ts && tsx src/mem7/core/__tests__/relationship-cognition.test.ts && tsx src/memory/proactive/__tests__/relationship-notifications.test.ts && tsx src/memory/proactive/__tests__/relationship-notification-runtime.test.ts && tsx src/mem7/core/__tests__/relationships-e2e.test.ts && tsx scripts/relations-replay.test.ts && tsx src/core/query.relationship-budget.test.ts && tsx src/core/handle-query.relationship-provenance.test.ts",
18
+ "replay:relationships": "tsx scripts/relations-replay.ts",
19
+ "test:goal": "tsx src/goals/store.test.ts",
20
+ "test:goal:aim": "tsx src/goals/aim-adapter.test.ts",
21
+ "test:goal:task": "tsx src/goals/task-adapter.test.ts",
22
+ "test:goal:remote": "tsx src/goals/remote-task-adapter.test.ts",
23
+ "test:goal:recovery": "tsx src/goals/recovery.test.ts",
24
+ "test:goal:gate": "tsx src/nudge/registration-gate.test.ts",
25
+ "test:goal:command": "tsx src/commands/goal.test.ts",
26
+ "test:goal:step": "tsx src/tools/goal-step.test.ts",
27
+ "test:goal:tools": "tsx src/tools/goal-tools.test.ts && tsx src/tools/goal-clear.test.ts",
28
+ "test:goal:all": "npm run test:goal && npm run test:goal:calendar && npm run test:goal:restart && npm run test:goal:aim && npm run test:goal:task && npm run test:goal:remote && npm run test:goal:recovery && npm run test:goal:gate && npm run test:goal:command && npm run test:goal:step && npm run test:goal:tools && npm run test:goal:e2e",
29
+ "test:goal:calendar": "tsx src/goals/calendar-adapter.test.ts",
30
+ "test:goal:restart": "powershell -NoProfile -Command \"$d = Join-Path ([System.IO.Path]::GetTempPath()) ('engine7-goal-' + [guid]::NewGuid()); New-Item -ItemType Directory -Path $d | Out-Null; $id = (npx tsx src/goals/restart-writer.ts $d); npx tsx src/goals/restart-reader.ts $d $id; Remove-Item -Recurse -Force $d\"",
31
+ "test:goal:e2e": "powershell -NoProfile -Command \"$d = Join-Path ([System.IO.Path]::GetTempPath()) ('engine7-goal-e2e-' + [guid]::NewGuid()); New-Item -ItemType Directory -Path $d | Out-Null; npx tsx src/goals/e2e-phase1.ts $d; if ($LASTEXITCODE -ne 0) { Remove-Item -Recurse -Force $d; exit 1 }; npx tsx src/goals/e2e-phase2.ts $d; $code = $LASTEXITCODE; Remove-Item -Recurse -Force $d; exit $code\"",
32
+ "test:reload": "tsx src/reload/orchestrator.test.ts && tsx src/reload/secrets-watch.test.ts && tsx src/hooks/config.test.ts && tsx src/channels/manager.reconcile.test.ts && tsx src/channels/cc-bridge.test.ts && tsx src/engine-startup.shouldSkipReload.test.ts && tsx src/optional/voice-chat/plugin.deps.test.ts && tsx src/optional/voice-chat/config.test.ts && tsx src/cron/cron-plugin.resync.test.ts && tsx src/startup/message-pipeline.test.ts && tsx src/utils/sanitize-error.test.ts && tsx src/reload/config-watch-selfwrite.test.ts && tsx src/channels/feishu.typing.test.ts && tsx src/channels/feishu.sendfile-apicheck.test.ts",
33
+ "test:installer": "tsx src/cli-events.test.ts && tsx src/cli-init-config.test.ts && tsx src/cli-init-answers.test.ts && tsx src/cli-init.headless.e2e.test.ts && tsx src/cli-reconfig.test.ts && tsx src/cli-doctor.test.ts && tsx src/config/secrets-env.test.ts && tsx src/startup/api-status.test.ts && tsx src/cli-service.test.ts && tsx src/emotion-hook.test.ts && tsx src/emotion-hook.golden.test.ts && tsx src/tools/tavily-pool.test.ts && tsx src/tools/service.test.ts",
34
+ "test:persona": "tsx src/config/persona-files.test.ts && tsx src/light-mode.test.ts && tsx src/inner-voice/plugin.test.ts && tsx src/cli-init-config.test.ts",
35
+ "test:responses": "tsx src/models/openai-responses-provider.test.ts && tsx src/session/responses-reasoning-roundtrip.test.ts && tsx src/compact/tokenEstimate.test.ts",
36
+ "build:public": "node scripts/build.mjs --public",
37
+ "prepublishOnly": "npm run build:public",
38
+ "start": "node dist/main.mjs",
39
+ "smoke:boot": "node scripts/boot-smoke.mjs",
40
+ "smoke:boot:baseline": "node scripts/boot-smoke.mjs --baseline",
41
+ "gen:template": "tsx scripts/gen-config-template.ts",
42
+ "test:config": "tsx src/config/registry.test.ts && tsx src/config/writer.test.ts && tsx src/config/secrets-io.test.ts && tsx src/config/secrets-env.test.ts && tsx src/config/migrations/index.test.ts && tsx src/config/migrations/persona.test.ts && tsx src/config/migrations/channel-secrets.test.ts && tsx src/config/cli-config.test.ts && tsx src/config/tts-providers.test.ts && tsx src/config/tts-voices.test.ts && tsx src/free-tier-apply.test.ts && tsx src/startup/free-tier-video-fixup.test.ts && tsx src/startup/free-tier-voice-fixup.test.ts",
43
+ "test:time": "tsx src/time/tz-matrix.test.ts && tsx src/time/time-discipline.test.ts && tsx src/time/tsc-guard.test.ts",
44
+ "test:tts": "tsx src/config/tts-providers.test.ts && tsx src/tools/tts/pick.test.ts && tsx src/tools/tts/route.test.ts && tsx src/tools/tts/relay.test.ts && tsx src/tools/tts/providers.test.ts && tsx src/tools/tts/impl.test.ts && tsx src/tools/tts/usage.test.ts && tsx src/tools/my-voice.dispatch.test.ts",
45
+ "test:toolstate": "tsx src/core/names-from-history.test.ts",
46
+ "test:loopbrake": "tsx src/tools/loop-brake.test.ts",
47
+ "test:mcp": "tsx src/mcp/convert-input-schema.test.ts",
48
+ "test:proactive": "tsx src/memory/proactive/__tests__/verdict-parse.test.ts && tsx src/memory/proactive/__tests__/sweep.test.ts && tsx src/memory/proactive/__tests__/persistence.test.ts && tsx src/memory/proactive/__tests__/graph-store-p0.test.ts && tsx src/memory/proactive/__tests__/opportunity.test.ts && tsx src/memory/proactive/__tests__/calendar-bridge.test.ts && tsx src/memory/proactive/__tests__/timezone-fix.test.ts && tsx src/memory/proactive/__tests__/stale-fix.test.ts && tsx src/memory/proactive/__tests__/completion-evidence.test.ts && tsx src/memory/proactive/__tests__/plugin.test.ts",
49
+ "prepack": "npm run test:time && npm run test:proactive && npm run test:persona && npm run test:config && npm run test:toolstate && npm run test:loopbrake && npm run test:mcp && npm run test:tts"
50
+ },
51
+ "engines": {
52
+ "node": ">=22"
53
+ },
54
+ "os": [
55
+ "win32",
56
+ "linux",
57
+ "darwin"
58
+ ],
59
+ "cpu": [
60
+ "x64",
61
+ "arm64"
62
+ ],
63
+ "devDependencies": {
64
+ "@types/node": "^22.19.19",
65
+ "@types/turndown": "^5.0.6",
66
+ "@types/ws": "^8.18.2",
67
+ "esbuild": "^0.25.10",
68
+ "tsx": "^4.22.3",
69
+ "typescript": "^6.0.0"
70
+ },
71
+ "dependencies": {
72
+ "@larksuiteoapi/node-sdk": "^1.66.1",
73
+ "@modelcontextprotocol/sdk": "^1.29.0",
74
+ "baileys": "7.0.0-rc14",
75
+ "chokidar": "^5.0.0",
76
+ "croner": "^10.0.1",
77
+ "discord.js": "^14.26.4",
78
+ "https-proxy-agent": "^9.1.0",
79
+ "qrcode-terminal": "^0.12.0",
80
+ "sharp": "^0.34.5",
81
+ "sqlite-vec": "^0.1.9",
82
+ "turndown": "^7.2.4",
83
+ "typebox": "^1.1.38",
84
+ "ws": "^8.22.0"
85
+ },
86
+ "optionalDependencies": {
87
+ "@discordjs/opus": "^0.10.0",
88
+ "@engine7/lancedb-intel": "^1.0.0",
89
+ "@ffmpeg-installer/ffmpeg": "^1.1.0",
90
+ "@lancedb/lancedb": "^0.38.0",
91
+ "@node-rs/jieba": "^2.0.3",
92
+ "sqlite-vec-darwin-arm64": "^0.1.9",
93
+ "sqlite-vec-darwin-x64": "^0.1.9",
94
+ "sqlite-vec-linux-arm64": "^0.1.9",
95
+ "sqlite-vec-linux-x64": "^0.1.9",
96
+ "sqlite-vec-windows-x64": "^0.1.9"
97
+ },
98
+ "files": [
99
+ "dist/**/*.mjs",
100
+ "dist/prompts/**/*.md",
101
+ "bin/**/*",
102
+ "templates/**/*",
103
+ "src/memory/everos/python/**/*",
104
+ "!**/__pycache__/**",
105
+ "!**/*.pyc",
106
+ "!**/node_modules/**",
107
+ "!**/.env",
108
+ "README.md"
109
+ ]
110
+ }
@@ -107,6 +107,11 @@
107
107
  }
108
108
  }
109
109
  },
110
+ "topics": {
111
+ "extract": {
112
+ "intervalMinutes": 15
113
+ }
114
+ },
110
115
  "channels": {
111
116
  "discord": {
112
117
  "enabled": false,
@@ -119,6 +124,26 @@
119
124
  },
120
125
  "replyToMode": "all"
121
126
  },
127
+ "telegram": {
128
+ "enabled": false,
129
+ "token": "",
130
+ "dmPolicy": "open",
131
+ "groupPolicy": "mention-only",
132
+ "allowFrom": []
133
+ },
134
+ "whatsapp": {
135
+ "enabled": false,
136
+ "dmPolicy": "allowlist",
137
+ "allowFrom": [],
138
+ "replyToMode": "off",
139
+ "sendReadReceipts": false,
140
+ "selfChatPrefix": true,
141
+ "groupDigest": {
142
+ "enabled": true,
143
+ "patrolIntervalMin": 60,
144
+ "maxLinesPerGroup": 500
145
+ }
146
+ },
122
147
  "feishu": {
123
148
  "enabled": true,
124
149
  "appId": "{{FEISHU_APP_ID}}",
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NetEase (original imap-smtp-email skill)
4
+ Copyright (c) 2026 engine7 contributors (installer adaptation)
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,115 @@
1
+ ---
2
+ name: email
3
+ description: "TRIGGER: 邮件/邮箱/发邮件/收邮件/查邮箱/看信/email/发信/收信。通过 IMAP/SMTP 收发邮件(QQ/163/126/188 系/Gmail/Outlook 通用)。凭证从 stateDir config 的 tools.email 读取(托盘「邮件」页填写)。"
4
+ ---
5
+
6
+ # 邮件(IMAP/SMTP)
7
+
8
+ 收信:`scripts/imap.js`(check / list-subjects / list-senders / search / fetch / download / mark-read)
9
+ 发信:`scripts/smtp.js`(send / test)
10
+ 分级闸与联系人名单:`scripts/email-gate.js`
11
+
12
+ 所有命令都支持 `--json`:成功 `{ok:true, reason:'success'|'no_new_mail', result}`;失败 `{ok:false, reason:'auth_failed'|'network'|'timeout'|'rate_limited'|'config_missing'|'daily_limit'|'not_found'|'unknown'}`。**agent 判定一律加 `--json`**——错误分类是硬要求,绝不能把「连不上」说成「没收到」。
13
+
14
+ ## 配置与首次引导
15
+
16
+ 凭证在 stateDir config 的 `tools.email`(托盘 → 设置 → 邮件 填写)。命令会自动读取(`ENGINE_STATE_DIR` 定位);`.env` 形态兼容但不再是推荐路径。
17
+
18
+ **未配置时(用户第一次提出用邮箱)**,按此顺序引导(QQ 部分为 1010 实测——四步截图与结论存档于 docs/assets/qq-mail-authcode/):
19
+
20
+ 1. **先确认入口**:「你电脑右下角能看到一个小图标吗?可能藏在 ^ 折叠箭头里」——用户找不到托盘时不会说找不到,只会说「我按你说的做了没用」,必须先确认。
21
+ 2. **拿授权码**(按用户邮箱给对应叫法:QQ/163=授权码,Gmail=App Password):
22
+ - QQ 实测路径:**mail.qq.com → 右上角头像 → 账号与安全 → 左侧「安全设置」→ 页面最底部**找「POP3/IMAP/SMTP… 服务」区(注意:新版叫「账号与安全」不是老的「设置→账户」;服务开关在**安全设置页底部**,不在账号设置里)
23
+ - 服务**早已开启**(大多数老 QQ 号默认开过)→ 直接点「**生成授权码**」;只有看到「开启服务」才需要先开
24
+ - **验证是「用户主动发一条短信」**(不是收验证码!):用绑定手机给指定号码发一条指定内容的短信 → 回电脑点「我已发送」。两个更省事的逃逸路径先告诉用户:**微信扫页面二维码代发**——扫码后手机微信自动弹出「发送短信」确认页,点一下「发送」就代发了(手机不在手边时的首选);左下角「选择其他方式验证」有备选
25
+ - 授权码在**验证后的网页弹窗**里出现(短信里没有码),提醒用户复制 + 建议顺手填「备注用途」(授权码管理页可分辨多把码;**QQ 支持多码并存,新生成不作废旧码**——用户家里另一台设备在用的码不受影响,可以放心告诉用户)
26
+ - 用户卡住时可以发截图——**先提醒「把授权码/密码区域遮住再发」**;或让用户只说「现在在哪个页面」。收到含疑似授权码的图:不复述、不落盘引用。
27
+ 3. **填面板**:托盘 → 邮件,填**完整邮箱地址**(xxx@qq.com,不能填 QQ 号)+ 授权码。
28
+ 4. **验活**:用户说「好了」后跑 `node scripts/imap.js check --limit 3 --json`:
29
+ - `ok:true` → 接管完成,顺带报未读摘要
30
+ - `reason:auth_failed` → **先排地址再排授权码**:「方便回我一句你填的完整邮箱地址吗?QQ 邮箱要 xxx@qq.com 全称,不能填 QQ 号」——地址没错再让用户重新生成授权码
31
+ - `reason:network/timeout` → 网络/服务器问题,稍后再试,**别说是凭证错**
32
+
33
+ **陪用户拿授权码的两个手段,按这个顺序用**:
34
+
35
+ 1. **Playwright 陪操作**(首选):用 Playwright 的 browser_navigate 在用户电脑上打开浏览器窗口(headed,用户看得见),把路直接带到对应页面——QQ 开 `mail.qq.com`(登录后可直接导航到「账号与安全」)、163 开 `mail.163.com`、Gmail 直达 `myaccount.google.com/apppasswords`。**分工**:导航、找入口这些 agent 代劳(用户卡的多是「找不到在哪」);登录输密码、短信验证、生成授权码这些**让用户自己动手**(agent 别碰凭证),你在旁边看着页面状态提示下一步就行。
36
+ 2. **发步骤图**(Playwright 不可用或用户不习惯时;assets/ 目录,QQ 流程实拍已脱敏):把对应的图直接发到聊天里,比文字描述有效得多——发之前提醒用户**回图时遮住授权码区域**:
37
+
38
+ - `assets/1-account-menu.jpg` 找不到入口 → 「点右上角头像 → 账号与安全」
39
+ - `assets/2-security-settings-generate.jpg` 找不到生成按钮 → 「安全设置页最底部 → 生成授权码」
40
+ - `assets/3-sms-verify.jpg` 验证步骤懵了 → 「发一条短信 / 微信扫码代发 → 我已发送」
41
+ - `assets/4-wechat-send-confirm.jpg` 手机不在手边 → 「微信扫码,手机上点一下发送就行」
42
+ - `assets/5-code-generated.jpg` 验证通过后 → 「码就弹在这个页面,点复制」
43
+
44
+ ## 收信命令
45
+
46
+ ```bash
47
+ # 监控「有没有新邮件」的落地命令(不下载正文,快):
48
+ node scripts/imap.js list-subjects --limit 20 --recent 30m --json
49
+ # 发件人分布(监控选址:先看分布再决定搜什么):
50
+ node scripts/imap.js list-senders --limit 50 --recent 7d --json
51
+ # 查未读(拉正文):
52
+ node scripts/imap.js check --limit 10 --recent 2h --json
53
+ # 搜邮件:
54
+ node scripts/imap.js search --from certum.pl --recent 30d --json
55
+ # 读全文(关键判读必须 fetch 正文,激活码/链接常在正文里):
56
+ node scripts/imap.js fetch <uid> --json
57
+ # 下载附件 / 标已读 / 列文件夹:
58
+ node scripts/imap.js download <uid> --dir <path> --json
59
+ node scripts/imap.js mark-read <uid> --json
60
+ node scripts/imap.js list-mailboxes --json
61
+ ```
62
+
63
+ ## 发信:分级闸(必须遵守)
64
+
65
+ 发信前**必须**先跑闸判定,凭 verdict 行动:
66
+
67
+ ```bash
68
+ node scripts/email-gate.js check --to a@x.com,b@y.com [--cc ...] [--bcc ...] [--attach] [--ext-link]
69
+ # → {"action":"send"} 全部收件人已验证且无附件无外链:直接发
70
+ # → {"action":"confirm","reasons":{...}} 有陌生收件人/附件/外链:起草给用户过目,用户明确说发才发
71
+ ```
72
+
73
+ 规则:
74
+ - **候选/已验证两级**:任何来信/去信地址自动入「候选」;agent 遇到要发往候选地址时问用户一句「这个地址以后可以直接回吗?」,用户同意后 `node scripts/email-gate.js verify --addr <addr>` 升级为已验证(此后直发不再问)
75
+ - **逐收件人判定**:to+cc+bcc 拆开逐个判,任一地址未验证 → 整封降级需确认(防「回复 A 直发、夹带的 B 跟着出去」)
76
+ - 带附件 / 正文含外链 → 永远需要确认,且**不参与攒批**(攒批仅限纯文本待确认件,≤3 封一批一起过目)
77
+ - 发送永远留底(`email-sent.log`);单日上限默认 20 封(超限报 `daily_limit`),防失控群发
78
+
79
+ ```bash
80
+ node scripts/smtp.js send --to a@x.com --subject "..." --body "..." --json
81
+ # --cc/--bcc/--attach file1,file2/--body-file f/--html-file f
82
+ ```
83
+
84
+ ## 邮件监控四规则(写 cron/定时任务时必须遵守)
85
+
86
+ 239 事故(2026-09-30~10-08,8 天误报)的教训,四条缺一不可:
87
+
88
+ 1. **先看发件人分布再决定搜什么**:`list-senders` 看实际来源——同一事件的邮件可能来自不同域/第三方发信域(SendGrid 等),**不要预设单一域名**,更不要搜事件名(「证书签发了」这种事件名几乎从不出现在真实标题里)
89
+ 2. **监控「有没有新邮件」,别监控「某封到没到」**:`list-subjects --recent 30m` 拿标题列表,标题命中才算收到
90
+ 3. **判读必须 fetch 正文**:激活码/关键信息常在正文,只看标题就判「不是它」是 239 的直接死因
91
+ 4. **死线**:连续多次无命中(建议 24h 内 20 次)→ 自动降级:`list-subjects --limit 10` 列最近标题给用户看,问「有没有你要的」。关键词死等必须有尽头——规则只管怎么找,死线管找不到怎么办
92
+
93
+ ## Gmail 用户(⚠️ 推演未实测——高级用户向:能自己走到这的人不需要逐步引导)
94
+
95
+ Gmail 的授权码叫 **App Password(应用密码)**,两个前提先确认:
96
+ 1. **网络**:这台电脑要能正常访问 Google 服务——连不上的话 IMAP(imap.gmail.com)也连不上,配了也用不了,先解决网络再谈
97
+ 2. **两步验证(2FA)**:账号必须已开启两步验证,否则看不到应用密码入口(没开的先去 Security → 2-Step Verification 启用,比 QQ 的短信验证麻烦)
98
+
99
+ 流程:用 Playwright 直接打开 `https://myaccount.google.com/apppasswords` → 用户登录并输一次密码 → 应用名随便填(如 engine7)→ 生成 16 位码(格式 xxxx xxxx xxxx xxxx,**含空格**,填面板时把空格去掉)。
100
+
101
+ ## 163/126/188 用户(✅ 1010 实测端到端通过——IMAP/SMTP/发信全绿;入口页截图归档 docs/assets/163-mail-authcode/)
102
+
103
+ 网易系的授权码叫**客户端授权密码**。流程(步骤图在 assets/163/,1010 实测):
104
+ Playwright 开 `mail.163.com`(126/188 同理)→ 登录 → 顶部**设置 → POP3/SMTP/IMAP**:
105
+ - **服务没开**(显示「已关闭」):点 IMAP/SMTP 服务右侧「**开启**」→ **中途会弹「账号安全提示」**(吓唬人的:邮件泄露风险+推荐装邮箱大师)——**必须点「继续开启」**,小白最容易被这页劝退,要预告 → 成功后**授权码当场弹出**(只显示一次,立即复制)。⚠️ POP3/SMTP 是另一个开关,**我们不需要**(收信走 IMAP),别开
106
+ - **服务已开**:页面上点 **「新增授权密码」** 再拿一把(支持多把并存)
107
+ - 可能要求**收**短信验证码(是收不是发,比 QQ 容易)
108
+
109
+ 码是 16 位**大小写混合**(区分大小写,QQ 是纯小写),复制时别动大小写。⚠️ 163 风控会给绑定手机发「新设备登录提醒」——正常现象不拦截,提前告诉用户别慌。与 QQ 的另一差异:入口在设置页一级菜单(不藏在「安全设置」底部)。
110
+
111
+ 163 步骤图(assets/163/):`1-settings-menu.jpg` 入口 / `2-service-closed.jpg` 服务开关 / `3-security-warning.jpg` 安全提示(点继续开启)/ `5-imap-code.jpg` 出码页。
112
+
113
+ ## 常见服务商
114
+
115
+ QQ / 163 / 126 / 188 / vip 系列 / Gmail / Outlook 的服务器映射已内置(按邮箱域名自动识别,未知域名需在面板手动填 host)。授权码不是登录密码——各家的叫法与入口见上面各段。