@optima-chat/optima-agent 0.8.98 → 0.8.99

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 (95) hide show
  1. package/.claude/settings.local.json +166 -0
  2. package/.claude/skills/.kb-skills-managed.json +9 -0
  3. package/.claude/skills/ingesting-sources/SKILL.md +76 -0
  4. package/.claude/skills/initializing-kb/SKILL.md +102 -0
  5. package/.claude/skills/kol-outreach/SKILL.md +351 -272
  6. package/.claude/skills/linting-the-wiki/SKILL.md +69 -0
  7. package/.claude/skills/querying-the-wiki/SKILL.md +66 -0
  8. package/.claude/skills/updating-related-pages/SKILL.md +76 -0
  9. package/dist/bin/bi-cli.js +0 -0
  10. package/dist/bin/browser-cli.js +0 -0
  11. package/dist/bin/comfy.d.ts +3 -0
  12. package/dist/bin/comfy.d.ts.map +1 -0
  13. package/dist/bin/comfy.js +3 -0
  14. package/dist/bin/comfy.js.map +1 -0
  15. package/dist/bin/commerce.js +0 -0
  16. package/dist/bin/gen.js +0 -0
  17. package/dist/bin/google-ads.js +0 -0
  18. package/dist/bin/growth.d.ts +3 -0
  19. package/dist/bin/growth.d.ts.map +1 -0
  20. package/dist/bin/growth.js +3 -0
  21. package/dist/bin/growth.js.map +1 -0
  22. package/dist/bin/logistics.js +0 -0
  23. package/dist/bin/optima.js +0 -0
  24. package/dist/bin/scout.js +0 -0
  25. package/dist/bin/sentinel.js +0 -0
  26. package/dist/bin/shopify.js +0 -0
  27. package/dist/bin/sync-kb-skills.d.ts +6 -0
  28. package/dist/bin/sync-kb-skills.d.ts.map +1 -0
  29. package/dist/bin/sync-kb-skills.js +65 -0
  30. package/dist/bin/sync-kb-skills.js.map +1 -0
  31. package/dist/src/hooks-loader.d.ts +6 -0
  32. package/dist/src/hooks-loader.d.ts.map +1 -0
  33. package/dist/src/hooks-loader.js +215 -0
  34. package/dist/src/hooks-loader.js.map +1 -0
  35. package/dist/src/ui/App.d.ts +6 -0
  36. package/dist/src/ui/App.d.ts.map +1 -0
  37. package/dist/src/ui/App.js +164 -0
  38. package/dist/src/ui/App.js.map +1 -0
  39. package/dist/src/ui/components/Composer.d.ts +10 -0
  40. package/dist/src/ui/components/Composer.d.ts.map +1 -0
  41. package/dist/src/ui/components/Composer.js +13 -0
  42. package/dist/src/ui/components/Composer.js.map +1 -0
  43. package/dist/src/ui/components/Header.d.ts +7 -0
  44. package/dist/src/ui/components/Header.d.ts.map +1 -0
  45. package/dist/src/ui/components/Header.js +7 -0
  46. package/dist/src/ui/components/Header.js.map +1 -0
  47. package/dist/src/ui/components/Message.d.ts +12 -0
  48. package/dist/src/ui/components/Message.d.ts.map +1 -0
  49. package/dist/src/ui/components/Message.js +21 -0
  50. package/dist/src/ui/components/Message.js.map +1 -0
  51. package/dist/src/ui/components/MessageList.d.ts +9 -0
  52. package/dist/src/ui/components/MessageList.d.ts.map +1 -0
  53. package/dist/src/ui/components/MessageList.js +18 -0
  54. package/dist/src/ui/components/MessageList.js.map +1 -0
  55. package/dist/src/ui/components/Spinner.d.ts +6 -0
  56. package/dist/src/ui/components/Spinner.d.ts.map +1 -0
  57. package/dist/src/ui/components/Spinner.js +7 -0
  58. package/dist/src/ui/components/Spinner.js.map +1 -0
  59. package/dist/src/ui/components/StatusBar.d.ts +11 -0
  60. package/dist/src/ui/components/StatusBar.d.ts.map +1 -0
  61. package/dist/src/ui/components/StatusBar.js +7 -0
  62. package/dist/src/ui/components/StatusBar.js.map +1 -0
  63. package/dist/src/ui/components/index.d.ts +7 -0
  64. package/dist/src/ui/components/index.d.ts.map +1 -0
  65. package/dist/src/ui/components/index.js +7 -0
  66. package/dist/src/ui/components/index.js.map +1 -0
  67. package/dist/src/validation/error-formatter.d.ts +21 -0
  68. package/dist/src/validation/error-formatter.d.ts.map +1 -0
  69. package/dist/src/validation/error-formatter.js +98 -0
  70. package/dist/src/validation/error-formatter.js.map +1 -0
  71. package/dist/src/validation/index.d.ts +10 -0
  72. package/dist/src/validation/index.d.ts.map +1 -0
  73. package/dist/src/validation/index.js +10 -0
  74. package/dist/src/validation/index.js.map +1 -0
  75. package/dist/src/validation/json-validator.d.ts +25 -0
  76. package/dist/src/validation/json-validator.d.ts.map +1 -0
  77. package/dist/src/validation/json-validator.js +173 -0
  78. package/dist/src/validation/json-validator.js.map +1 -0
  79. package/dist/src/validation/schema.d.ts +353 -0
  80. package/dist/src/validation/schema.d.ts.map +1 -0
  81. package/dist/src/validation/schema.js +57 -0
  82. package/dist/src/validation/schema.js.map +1 -0
  83. package/dist/src/validation/suggestions.d.ts +25 -0
  84. package/dist/src/validation/suggestions.d.ts.map +1 -0
  85. package/dist/src/validation/suggestions.js +144 -0
  86. package/dist/src/validation/suggestions.js.map +1 -0
  87. package/dist/src/validation/types.d.ts +40 -0
  88. package/dist/src/validation/types.d.ts.map +1 -0
  89. package/dist/src/validation/types.js +5 -0
  90. package/dist/src/validation/types.js.map +1 -0
  91. package/dist/src/validation/yaml-validator.d.ts +25 -0
  92. package/dist/src/validation/yaml-validator.d.ts.map +1 -0
  93. package/dist/src/validation/yaml-validator.js +177 -0
  94. package/dist/src/validation/yaml-validator.js.map +1 -0
  95. package/package.json +5 -3
@@ -1,55 +1,70 @@
1
1
  ---
2
2
  name: kol-outreach
3
- description: "KOL 建联全自动化:从 TikTok 发现潜在 KOL、提取 bio email、发送个性化 outreach、自动谈判价格与条款。当用户说'帮我找 KOL'、'{产品} KOL 建联'、'KOL 进展如何',或收到系统触发的 [KOL_INBOUND:...] [KOL_INBOUND] 标记时,使用此 skill。"
3
+ description: "KOL 建联全自动化:从 TikTok 发现潜在 KOL、提取 bio email、发送个性化 outreach、自动处理 KOL 回信并维护本地状态。当用户说'帮我找 KOL'、'{产品} KOL 建联'、'KOL 进展如何',或消息中出现 [KOL_INBOUND:{campaignId}] / [KOL_INBOUND] 时,使用此 skill。"
4
4
  ---
5
5
 
6
- # KOL Outreach Skill
6
+ # KOL 建联
7
7
 
8
- skill 帮助商家在 TikTok 上端到端完成 KOL 建联:发现 → 触达 → 谈判 →
9
- 意向交付,运行时零打扰,所有决策参数在首次 onboarding 时固化。
8
+ ## 概述
10
9
 
11
- ## 核心工作目录
10
+ skill 用于在 TikTok 上完成 KOL 建联的完整操作流程:发现、触达、回信处理、进度维护。
12
11
 
13
- 所有状态都在 `~/kol-outreach/`,整个目录是一个 git repo。结构:
12
+ - 所有业务状态都保存在 `~/kol-outreach/`
13
+ - 运行时默认零打扰,优先按已固化配置执行
14
+ - 本 skill 只负责操作,不解释系统实现
14
15
 
15
- ```
16
- ~/kol-outreach/
17
- ├── BRAND.md # 商家品牌素材(跨 campaign)
18
- ├── MERCHANT_LIMITS.md # 跨 campaign 发信速率
19
- ├── CAMPAIGNS.md # campaign 索引
20
- ├── PROGRESS.md # 商家级里程碑日志
21
- └── campaigns/{campaignId}/
22
- ├── CONFIG.md # 本 campaign 的商品/预算/条款
23
- ├── KOLS.md # 本 campaign 的 KOL 索引
24
- ├── TEMPLATES.md # 邮件模板(4 类)
25
- ├── PROGRESS.md # 本 campaign 的事件日志
26
- ├── CONVERSATIONS/{username}.md # 每个 KOL 的对话历史
27
- └── assets/ # 商品图/素材
28
- ```
16
+ ## 工作规则
29
17
 
30
- ## 每次 session 的启动流程
18
+ - outbound 只用 `scout outreach send`
19
+ - onboarding 时只用 `scout outreach brand-slug check|claim`
20
+ - 所有业务状态只从 `~/kol-outreach/` 读取和写回
21
+ - 单会话单任务:每次 session 只做一种动作
22
+ - 每次关键状态变更后都要 `git add` + `git commit`
23
+ - 所有对外邮件都必须走模板渲染;LLM 只能产出结构化变量或结构化决策
31
24
 
32
- 按顺序执行这些检查,决定本次 session 的动作:
25
+ ## 工作目录文件
33
26
 
34
- 1. **检测系统触发**:用户消息是否包含 `[KOL_INBOUND:{campaignId}]` 或 `[KOL_INBOUND]`(无 campaignId)标记?
35
- - 是 → 走「KOL 回信处理」流程(见下)
36
- - 否 → 继续步骤 2
37
- 2. **检测首次使用**:`ls ~/kol-outreach/BRAND.md` 是否存在?
38
- - 不存在 → 走「Merchant onboarding」→ 接着走「Campaign onboarding」
39
- - 存在 → 继续步骤 3
40
- 3. **根据用户意图**分流:
41
- - 用户说"给 {新产品} 建联" → 走「Campaign onboarding」创建新 campaign
42
- - 用户说"开始建联 {campaign}" / "发送 outreach" → 走「发现 + 首发 outreach」
43
- - 用户说"KOL 进展" / "查看状态" → 走「查询进度」
27
+ 优先关注这些文件:
44
28
 
45
- **单会话单任务原则**:每次 session 只执行一种操作,完成后 git commit 并
46
- 更新 PROGRESS.md。出错时可以精准回滚。
29
+ - merchant
30
+ - `~/kol-outreach/BRAND.md`
31
+ - `~/kol-outreach/MERCHANT_LIMITS.md`
32
+ - `~/kol-outreach/CAMPAIGNS.md`
33
+ - `~/kol-outreach/PROGRESS.md`
34
+ - campaign 级
35
+ - `~/kol-outreach/campaigns/{campaignId}/CONFIG.md`
36
+ - `~/kol-outreach/campaigns/{campaignId}/KOLS.md`
37
+ - `~/kol-outreach/campaigns/{campaignId}/TEMPLATES.md`
38
+ - `~/kol-outreach/campaigns/{campaignId}/PROGRESS.md`
39
+ - `~/kol-outreach/campaigns/{campaignId}/CONVERSATIONS/{username}.md`
40
+
41
+ ## Session 分流
42
+
43
+ 每次 session 开始时,按这个顺序判断要走哪条流程:
44
+
45
+ 1. 如果用户消息包含 `[KOL_INBOUND:{campaignId}]` 或 `[KOL_INBOUND]`,走「流程 D:KOL 回信处理」
46
+ 2. 否则,检查 `~/kol-outreach/BRAND.md` 是否存在
47
+ - 不存在:先走「流程 A:Merchant onboarding」,再走「流程 B:Campaign onboarding」
48
+ 3. 如果 merchant 已存在,则按用户意图分流
49
+ - 用户要为新产品建联:走「流程 B:Campaign onboarding」
50
+ - 用户要开始建联或发送 outreach:走「流程 C:发现 KOL + 首发 Outreach」
51
+ - 用户要查看状态或进展:走「流程 E:查询进度」
52
+
53
+ ## Inbound 前置条件
54
+
55
+ 进入流程 D 前,先检查:
56
+
57
+ 1. `BRAND.md`
58
+ 2. 对应 campaign 目录
59
+ 3. 可匹配的 `KOLS.md` / `CONVERSATIONS/*.md`
60
+
61
+ 缺任何一项,都不要继续正常谈判;直接转状态恢复或人工跟进。
47
62
 
48
63
  ---
49
64
 
50
- ## 流程 A:Merchant onboarding(只发生一次)
65
+ ## 流程 A:商家初始化
51
66
 
52
- 1. 创建工作目录:
67
+ 1. 初始化工作目录:
53
68
 
54
69
  ```bash
55
70
  mkdir -p ~/kol-outreach/campaigns
@@ -57,37 +72,38 @@ description: "KOL 建联全自动化:从 TikTok 发现潜在 KOL、提取 bio
57
72
  cd ~/kol-outreach && git init && git add -A && git commit -m "init merchant workspace"
58
73
  ```
59
74
 
60
- 2. 会话式填 `BRAND.md`:
61
- - 询问 brand_name
62
- - 从 brand_name 生成 brand_slug 建议(kebab-case
63
- - 调 `scout outreach brand-slug check {slug}` 验证唯一性
64
- - 可用 → 确认
65
- - 被占用 使用 API 返回的 suggestions 让商家选或自己改
66
- - 成功后调 `scout outreach brand-slug claim` 锁定
67
- - 依次询问 brand_website / brand_social / default_contact_name / default_brand_pitch
68
- - 给出默认的兜底话术模板,让商家确认或改
69
- - 给出默认的双语 escalation_keywords,让商家确认或扩展
70
- - Edit `BRAND.md`
75
+ 2. `BRAND.md`
76
+ - 询问:`brand_name`
77
+ - 从 `brand_name` 生成 kebab-case 的 `brand_slug` 建议
78
+ - 调 `scout outreach brand-slug check {slug}`
79
+ - 如果可用,让商家确认;如果被占用,让商家从建议里选或自己改
80
+ - 成功后调 `scout outreach brand-slug claim`
81
+ - 继续询问:`brand_website`、`brand_social`、`default_contact_name`、`default_brand_pitch`
82
+ - 提供默认兜底话术和默认双语 `escalation_keywords`,让商家确认或修改
83
+ - 写回 `BRAND.md`
71
84
  - `git add BRAND.md && git commit -m "setup BRAND.md"`
72
85
 
73
- 3. 会话式填 `MERCHANT_LIMITS.md`:
74
- - 默认 `max_daily_sends: 50`,询问商家是否调整
75
- - 初始化 `sent_today: 0`,`date_utc: $(date -u +%Y-%m-%d)`
76
- - `git commit -m "setup MERCHANT_LIMITS.md"`
86
+ 3. `MERCHANT_LIMITS.md`
87
+ - 默认 `max_daily_sends: 50`
88
+ - 初始化 `sent_today: 0`
89
+ - 初始化 `date_utc: $(date -u +%Y-%m-%d)`
90
+ - 写回并 commit
77
91
 
78
- 4. 告诉商家:商家级配置完成,现在为第一个商品创建 campaign,进入流程 B
92
+ 4. 告诉商家:merchant 级配置已完成,继续创建第一个 campaign。
79
93
 
80
94
  ---
81
95
 
82
- ## 流程 BCampaign onboarding(每次新建 campaign)
96
+ ## 流程 B:创建 Campaign
83
97
 
84
- 1. 确定 campaignId
85
- - 询问商家要为哪个商品建联(可列出他的 Optima 店铺商品作为选项)
86
- - 从商品标题生成 kebab-case slug 建议("Titanium Travel Tumbler" → "titanium-tumbler")
87
- - 校验:与 `CAMPAIGNS.md` 现有 campaign 不重复;匹配 `^[a-z0-9][a-z0-9-]{0,29}$`
88
- - 告诉商家建议值,让他确认或改
98
+ 1. 确定 `campaignId`
99
+ - 询问商家要为哪个商品建联
100
+ - 从商品标题生成 kebab-case slug 建议
101
+ - 校验:
102
+ - 与 `CAMPAIGNS.md` 现有 campaign 不重复
103
+ - 匹配 `^[a-z0-9][a-z0-9-]{0,29}$`
104
+ - 让商家确认或修改
89
105
 
90
- 2. 创建 campaign 目录:
106
+ 2. 创建 campaign 目录
91
107
 
92
108
  ```bash
93
109
  mkdir -p ~/kol-outreach/campaigns/{campaignId}
@@ -95,277 +111,340 @@ description: "KOL 建联全自动化:从 TikTok 发现潜在 KOL、提取 bio
95
111
  cd ~/kol-outreach && git add -A && git commit -m "init campaign {campaignId}"
96
112
  ```
97
113
 
98
- 3. 分段会话式填 campaign `CONFIG.md`:
99
- - 3.1 Campaign 元信息(填 campaignId、创建时间、status=active)
100
- - 3.2 商品段(ID/URL/标题/卖点/受众/样品)
101
- - 3.3 目标 KOL 画像
102
- - 3.4 预算与报价
103
- - **校验**:`max_price >= target_price >= first_offer`、
104
- `max_step <= max_price - first_offer`、`total_budget >= max_price`
105
- - 任一不过 → 说明原因 + 建议调整 + 重新问,不要跳过
106
- - 3.5 合作条款边界(保留时长/使用授权/付款节点/样品政策)
107
- - 3.6 兜底话术覆写(默认留空继承 BRAND.md)+ 运行参数(max_rounds/max_days/max_concurrent_kols/覆写的 escalation_keywords/forbidden_phrases)
108
- - 每段填完 → Edit CONFIG.md → `git commit -m "[{campaignId}] setup CONFIG section N"`
109
-
110
- 4. 追加 `CAMPAIGNS.md`(新增一行,status=active)
114
+ 3. 分段填写 `CONFIG.md`
115
+ - 依次填写:
116
+ - campaign 元信息
117
+ - 商品
118
+ - 目标 KOL 画像
119
+ - 预算与报价
120
+ - 合作条款边界
121
+ - 兜底话术覆写
122
+ - 运行参数
123
+ - 严格校验:
124
+ - `max_price >= target_price >= first_offer`
125
+ - `max_step <= max_price - first_offer`
126
+ - `total_budget >= max_price`
127
+ - 任一不满足:说明原因,给出调整建议,重新问
128
+ - 每个大段写完后都要写文件并 commit
129
+
130
+ 4. 更新 `CAMPAIGNS.md`
131
+ - 追加一行新 campaign,`status=active`
111
132
 
112
133
  5. `git commit -m "[{campaignId}] campaign ready"`
113
134
 
114
- 6. 告诉商家 campaign 已就绪,问他是否立即开始发现 KOL。
135
+ 6. 告诉商家 campaign 已就绪,并询问是否立即开始发现 KOL。
115
136
 
116
137
  ---
117
138
 
118
- ## 流程 C:发现 KOL + 首发 Outreach
139
+ ## 流程 C:发现 KOL 与首发 Outreach
140
+
141
+ 先按这个顺序执行:
119
142
 
120
- 触发:用户说"给 {campaign} 开始建联" / "为 {campaign} 发送 outreach"。
121
- 如果商家没指定 campaign,读 `CAMPAIGNS.md`:
122
- - 只有一个 active 用它
123
- - 多个 active → 列出让商家选
143
+ - 先定 campaign
144
+ - 再检查并行上限和 merchant 日配额
145
+ - 先发现 KOL,再补拉 bio / email
146
+ - LLM 只产出模板变量,不直接写邮件正文
147
+ - 每发出一封邮件,都要立刻写回并 commit
124
148
 
125
- 1. 读配置:
126
- - `CONFIG.md`(目标画像/运行参数/预算)
127
- - `KOLS.md`(已存在 KOL 列表)
128
- - `BRAND.md`(后面生成 outreach 需要)
129
- - `MERCHANT_LIMITS.md`(今日发信数)
149
+ 1. 确定 campaign
150
+ - 如果用户指定 campaign,用指定值
151
+ - 如果用户未指定,读 `CAMPAIGNS.md`
152
+ - 只有一个 active:用它
153
+ - 多个 active:列出让商家选
130
154
 
131
- 2. 并行上限预检:
155
+ 2. 读取所需文件
156
+ - `CONFIG.md`
157
+ - `KOLS.md`
158
+ - `BRAND.md`
159
+ - `MERCHANT_LIMITS.md`
160
+ - `TEMPLATES.md`
161
+
162
+ 3. 检查并行上限
132
163
  - `in_flight = KOLS.md 中 status ∈ {awaiting_reply, in_progress} 的行数`
133
164
  - `available_slots = max_concurrent_kols - in_flight`
134
- - `available_slots <= 0` → 告诉商家"当前 {in_flight} 个 KOL 在谈,已达并行上限。等 KOL 进入终态后再继续。"退出
165
+ - `available_slots <= 0`:告诉商家当前已达并行上限,退出
135
166
  - `target_new = min(CONFIG.target_kols_this_round, available_slots)`
136
167
 
137
- 3. 日发信上限预检(merchant 级):
138
- - MERCHANT_LIMITS.md 的 sent_today 和 max_daily_sends
139
- - 如果当前 UTC 日期 MERCHANT_LIMITS.md 的 date_utc:归档昨天计数到历史表,sent_today=0,写回
168
+ 4. 检查 merchant 日发信配额
169
+ - 读取 `MERCHANT_LIMITS.md`
170
+ - 如果 UTC 日期变了,先归档昨天计数,再把 `sent_today` 重置为 0
140
171
  - `daily_budget = max_daily_sends - sent_today`
141
- - `daily_budget <= 0` → 告诉商家"今日所有 campaign 合计发信配额已用完",退出
172
+ - `daily_budget <= 0`:告诉商家今日配额已用完,退出
142
173
  - `本轮发送量 = min(target_new, daily_budget)`
143
174
 
144
- 4. 调 `scout tiktok creators` 发现 KOL:
175
+ 5. 调 `scout tiktok creators`
145
176
 
146
177
  ```bash
147
178
  scout tiktok creators \
148
- --follower-count "<CONFIG.powder_count_range>" \
179
+ --follower-count "<CONFIG.follower_count_range>" \
149
180
  --country "<CONFIG.region>" \
150
181
  --limit <本轮发送量 * 3> \
151
182
  --format json
152
183
  ```
153
184
 
154
- (×3 给过滤留空间)
155
-
156
- 5. 过滤:
157
- - engagement_rate < 2% 丢弃
158
- - 已在 KOLS.md 的 username 丢弃
185
+ 6. 过滤候选人
186
+ - `engagement_rate < 2%` 丢弃
187
+ - 已在 `KOLS.md` 的 `username` 丢弃
159
188
  - 粉丝量不在目标区间的丢弃
160
189
 
161
- 6. 对每个候选 KOL:
162
- - 调 `scout tiktok creator-products @username --limit 5` 获取最近视频(用于生成开场白)
163
- - 如果 scout 的 creators 响应 **没有返回 bio**,也从这里拉(见 probe Task 0.2)
164
- - 用正则从 bio email
190
+ 7. 补拉 bio / email 并写 `KOLS.md`
191
+ - 对每个候选 KOL 调 `scout tiktok creator-products @username --limit 5`
192
+ - 如果 creators 结果没带 bio,就从这里补拉
193
+ - 用正则从 bio 提取 email
194
+ - 有 email:
195
+ - 追加到 `KOLS.md`
196
+ - `status=pending_outreach`
197
+ - 没 email:
198
+ - 追加到 `KOLS.md`
199
+ - `status=no_contact`
200
+ - `git commit -m "[{campaignId}] discovered N KOLs"`
165
201
 
166
- ```bash
167
- python3 -c "import re, sys; print(re.findall(r'[\\w.+-]+@[\\w-]+\\.[\\w.-]+', sys.stdin.read())[0] if re.search(r'[\\w.+-]+@[\\w-]+\\.[\\w.-]+', sys.stdin.read()) else '')" <<< "$BIO"
202
+ 8. 发送首发 outreach
203
+ - 只处理 `status=pending_outreach`,并限制在本轮发送量内
204
+ - 读取 `TEMPLATES.md` 中的 `first_outreach_v1`
205
+ - 让 LLM 只输出模板变量 JSON,例如:
206
+
207
+ ```json
208
+ {
209
+ "kol_first_name": "...",
210
+ "opener_line": "...",
211
+ "fit_reason": "...",
212
+ "product_one_liner": "..."
213
+ }
168
214
  ```
169
215
 
170
- - email → 追加到 KOLS.md,status=pending_outreach
171
- - 没 email → 追加到 KOLS.md,status=no_contact
172
- - `git commit -m "[{campaignId}] discovered N KOLs"`
216
+ - 用模板渲染正文,不要直接自由写 body
217
+ - 终检:
218
+ - body 不含 `forbidden_phrases`
219
+ - body 长度 `<= 2000`
220
+ - 任一不过:
221
+ - 写 `KOLS.md` 为 `send_error`
222
+ - 记 `PROGRESS.md`
223
+ - 跳过该 KOL
173
224
 
174
- 7. 对每个 status=pending_outreach KOL(限制本轮发送量):
175
- - 读 `TEMPLATES.md` 的 first_outreach_v1 骨架
176
- - **LLM 决策**(结构化 JSON 输出):
177
- - 输入:CONFIG 商品段 + BRAND.md(覆盖后) + KOL 最近视频 + template 变量列表
178
- - 输出:
179
- ```json
180
- {
181
- "kol_first_name": "...",
182
- "opener_line": "...",
183
- "fit_reason": "...",
184
- "product_one_liner": "..."
185
- }
186
- ```
187
- - 你只决定变量值,**不要直接写 email body**
188
- - 模板渲染:把变量代入 first_outreach_v1 → 最终 body
189
- - **Layer 4 内容终检**:
190
- - body 不含 CONFIG.forbidden_phrases(fallback 到 BRAND.md default)
191
- - body 长度 ≤ 2000 字符
192
- - 任一不过 → 跳过这个 KOL,写 KOLS.md status=send_error,记 PROGRESS.md
193
- - Edit `CONVERSATIONS/{username}.md` 追加 Round 1 (outreach sent) 段
194
- - 调 `scout outreach send`:
195
-
196
- ```bash
197
- echo '{
198
- "to": "kol_email",
199
- "subject": "Collaboration opportunity with <brand_name>",
200
- "body": "<rendered body>",
201
- "from_merchant_id": "<merchantId>",
202
- "from_campaign_id": "<campaignId>",
203
- "from_brand_slug": "<BRAND.brand_slug>",
204
- "from_brand_name": "<BRAND.brand_name>",
205
- "from_contact_name": "<effective contact_name>"
206
- }' | scout outreach send
207
- ```
225
+ 9. `scout outreach send`
208
226
 
209
- - 解析响应,拿 message_id 写回 CONVERSATIONS/{username}.md 的 Message-ID chain
210
- - Edit KOLS.md:status: pending_outreach → awaiting_reply,offer=first_offer
211
- - 读 MERCHANT_LIMITS.md,sent_today += 1,写回
212
- - `git commit -m "[{campaignId}] outreach sent to @{username}"`
227
+ ```bash
228
+ echo '{
229
+ "to": "kol_email",
230
+ "subject": "Collaboration opportunity with <brand_name>",
231
+ "body": "<rendered body>",
232
+ "from_merchant_id": "<merchantId>",
233
+ "from_campaign_id": "<campaignId>",
234
+ "from_brand_slug": "<BRAND.brand_slug>",
235
+ "from_brand_name": "<BRAND.brand_name>",
236
+ "from_contact_name": "<effective contact_name>"
237
+ }' | scout outreach send
238
+ ```
213
239
 
214
- 8. 追加本 campaign 的 PROGRESS.md;重要事件同步追加 merchant PROGRESS.md
240
+ 10. 写回状态
241
+ - 在 `CONVERSATIONS/{username}.md` 追加 Round 1
242
+ - 把返回的 `message_id` 写入 conversation
243
+ - 更新 `KOLS.md`
244
+ - `pending_outreach -> awaiting_reply`
245
+ - `offer = first_offer`
246
+ - 更新 `MERCHANT_LIMITS.md`
247
+ - `sent_today += 1`
248
+ - 追加本 campaign 的 `PROGRESS.md`
249
+ - 重要事件同步追加 merchant `PROGRESS.md`
250
+ - `git commit -m "[{campaignId}] outreach sent to @{username}"`
215
251
 
216
- 9. 告诉商家本次发送结果。
252
+ 11. 告诉商家本次发送结果。
217
253
 
218
254
  ---
219
255
 
220
- ## 流程 DKOL 回信处理(由 `[KOL_INBOUND:{campaignId}]` 或 `[KOL_INBOUND]` 触发)
221
-
222
- 1. 解析 prompt:
223
- - 如果包含 `[KOL_INBOUND:{campaignId}]` → 直接知道 campaignId,同时抠 email metadata(From / Subject / Message-ID / In-Reply-To / References / Body)
224
- - 如果包含 `[KOL_INBOUND]` + `merchant_id: {X}` → campaignId 未知,抠 merchant_id email metadata;campaignId 在步骤 3 中搜索确定
225
- (`merchant_id` 仅用于日志和审计。campaign 查找基于 `~/kol-outreach/campaigns/*/KOLS.md` 遍历,因为工作目录已隐含了 merchant 身份。)
226
-
227
- 2. 校验 campaign 存在(仅 campaignId 已知时):
228
- - `ls ~/kol-outreach/campaigns/{campaignId}/CONFIG.md`
229
- - 不存在 → 写 merchant PROGRESS.md 异常记录 + `sentinel report --condition-met false --summary "unknown campaign"` + 退出
230
-
231
- 3. 按 from_email 查找 KOL:
232
- - **campaignId 已知**(from step 1):只读该 campaign 的 KOLS.md
233
- - 找到 → 继续步骤 4
234
- - 找不到 → orphan_inbound:写 campaign PROGRESS.md + sentinel report false + 退出
235
- - **campaignId 未知**(fallback):遍历 `~/kol-outreach/campaigns/*/KOLS.md`
236
- - 唯一匹配 → 确定 campaignId,继续步骤 4
237
- - 多个匹配 → 选 **KOLS.md 中该 email 对应条目的 `last_update` 字段**最新的那个 campaign
238
- - 无匹配 → orphan_inbound:写 merchant PROGRESS.md + sentinel report false + 退出
239
-
240
- 同一 campaign 内如果同一 email 出现多条记录(例如 KOL 换 username 后被重新添加),取 status 非终态(非 rejected / expired / no_contact)的活跃记录。
241
-
242
- 4. 读本 campaign 的 `CONFIG.md`、`BRAND.md`、`CONVERSATIONS/{username}.md`
243
-
244
- 5. **Layer 2 预检**(收到回信后、调 LLM 前):
245
- - 当前 KOL `rounds >= CONFIG.max_rounds` → escalate
246
- - 对话时长 `>= CONFIG.max_days` → expired
247
- - KOL 最新回信 body 匹配 escalation_keywords(先查 CONFIG 覆写,再 fallback 到 BRAND 默认) → escalate
248
- - Idempotency:最后一条 inbound round 的 message_id ≠ 本次 in_reply_to → escalate
249
-
250
- 6. **如果 Layer 2 触发**:
251
- - 用 `escalate_fallback_v1` 模板渲染一封兜底回复
252
- - 调 `scout outreach send` 发出去(带 in_reply_to / references)
253
- - 更新 KOLS.md:status → needs_merchant_followup
254
- - git commit
256
+ ## 流程 D:处理 KOL 回信
257
+
258
+ 先按这个顺序执行:
259
+
260
+ - 先确定 campaignKOL
261
+ - 先做预检,再决定是否调用 LLM
262
+ - LLM 只输出结构化决策,不直接写邮件正文
263
+ - 任何校验不过都降级为 `escalate`
264
+ - 每轮处理完都要写回文件、commit,并调用 `sentinel report`
265
+
266
+ 1. 读取本次 inbound 的消息内容
267
+ - 从当前消息中读取:
268
+ - `From`
269
+ - `Subject`
270
+ - `Message-ID`
271
+ - `In-Reply-To`
272
+ - `References`
273
+ - `Body`
274
+
275
+ 2. 确定 campaign
276
+ - 如果消息包含 `[KOL_INBOUND:{campaignId}]`:只处理该 campaign
277
+ - 如果消息包含 `[KOL_INBOUND]`:稍后在步骤 3 通过 `from_email` 匹配 campaign
278
+
279
+ 3. 按 `from_email` 匹配 KOL
280
+ - `campaignId` 已知:只读该 campaign `KOLS.md`
281
+ - `campaignId` 未知:遍历 `~/kol-outreach/campaigns/*/KOLS.md`
282
+ - 匹配结果:
283
+ - 唯一匹配:继续
284
+ - 多个匹配:选 `last_update` 最新的 campaign
285
+ - 无匹配:按 orphan inbound 处理并退出
286
+ - 同一 campaign 内同一 email 有多条记录时,取 `status` 非终态的活跃记录
287
+
288
+ 4. 读取所需文件
289
+ - `CONFIG.md`
290
+ - `BRAND.md`
291
+ - `CONVERSATIONS/{username}.md`
292
+
293
+ 5. 先做预检
294
+ - `rounds >= CONFIG.max_rounds`:`escalate`
295
+ - 对话时长 `>= CONFIG.max_days`:`expired`
296
+ - 最新回信 body 命中 `escalation_keywords`:`escalate`
297
+ - 最后一条 inbound round 的 `message_id` 与本次 `in_reply_to` 不一致:按幂等性异常处理
298
+
299
+ 6. 如果预检触发异常
300
+ - 用 `escalate_fallback_v1` 模板渲染兜底回复
301
+ - 调 `scout outreach send` 发出回复
302
+ - 更新 `KOLS.md` 为 `needs_merchant_followup`
303
+ - 写 `PROGRESS.md`
304
+ - `git commit`
255
305
  - `sentinel report --condition-met true --summary "layer2 escalate" --action-taken "..."`
256
306
  - 退出
257
307
 
258
- 7. **LLM 结构化决策**(每一轮都重新调一次 LLM,全量对话历史放进上下文):
259
- 输入:
260
- - campaign CONFIG.md 全文
261
- - BRAND.md 全文
262
- - CONVERSATIONS/{username}.md 全文
263
- - 最新 KOL 回信
264
- - TEMPLATES.md counter_offer_v1 / deal_confirmation_v1 / escalate_fallback_v1 的变量列表
265
-
266
- 输出(结构化 JSON):
267
- ```json
268
- {
269
- "action": "counter_offer | accept | decline | clarify | escalate",
270
- "new_price": 200,
271
- "final_price": 250,
272
- "concessions": ["video_retention_90d"],
273
- "template_id": "counter_offer_v1",
274
- "template_vars": { },
275
- "reasoning": "..."
276
- }
277
- ```
308
+ 7. 调用 LLM 生成结构化决策
309
+ - 输入:`CONFIG.md`、`BRAND.md`、`CONVERSATIONS/{username}.md`、最新回信、模板变量列表
310
+ - 输出 JSON,例如:
311
+
312
+ ```json
313
+ {
314
+ "action": "counter_offer | accept | decline | clarify | escalate",
315
+ "new_price": 200,
316
+ "final_price": 250,
317
+ "concessions": ["video_retention_90d"],
318
+ "template_id": "counter_offer_v1",
319
+ "template_vars": {},
320
+ "reasoning": "..."
321
+ }
322
+ ```
278
323
 
279
- 8. **Layer 3 后验**(基于本 campaign CONFIG 的预算和条款,不跨 campaign):
324
+ 8. 校验 LLM 输出
280
325
  - `action ∈ {counter_offer, accept, decline, clarify, escalate}`
281
- - 如果 `counter_offer`:
282
- - `CONFIG.first_offer new_price CONFIG.max_price`
283
- - `new_price - previous_offer CONFIG.max_step`
284
- - 如果 `accept`:
285
- - `CONFIG.first_offer final_price CONFIG.max_price`
286
- - `committed_so_far (本 campaign KOLS.md 中 deal_reached 的 offer 总和) + final_price CONFIG.total_budget`
287
- - `concessions ⊆ CONFIG.allowed_uses ∪ {'video_retention_N'}`
288
- - `template_id campaign TEMPLATES.md 的模板列表`
289
- - 任一不过 → 降级为 escalate(走 Layer 2 失败分支)
326
+ - 如果 `counter_offer`
327
+ - `CONFIG.first_offer <= new_price <= CONFIG.max_price`
328
+ - `new_price - previous_offer <= CONFIG.max_step`
329
+ - 如果 `accept`
330
+ - `CONFIG.first_offer <= final_price <= CONFIG.max_price`
331
+ - `committed_so_far + final_price <= CONFIG.total_budget`
332
+ - `concessions` 只能来自允许集合
333
+ - `template_id` 必须存在于本 campaign 的 `TEMPLATES.md`
334
+ - 任一不通过:降级为 `escalate`
335
+
336
+ 9. 渲染模板并终检
337
+ - 用 `template_id + template_vars` 渲染最终 body
338
+ - 终检:
339
+ - body 不含 `forbidden_phrases`
340
+ - body 长度 `<= 2000`
341
+ - 不通过:降级为 `escalate`
342
+
343
+ 10. 发送回复
344
+ - 调 `scout outreach send`
345
+ - 带上:
346
+ - `in_reply_to = KOL 的 message_id`
347
+ - `references = 原 references + KOL 的 message_id`
348
+ - 拿到新 `message_id`
349
+
350
+ 11. 写回状态
351
+ - 在 `CONVERSATIONS/{username}.md` 追加新一轮,保留完整 LLM decision JSON
352
+ - 更新 `KOLS.md`
353
+ - 更新 `rounds`
354
+ - 更新 `current_offer`
355
+ - 更新 `last_update`
356
+ - `accept -> deal_reached`
357
+ - 其他正常回复 -> `awaiting_reply`
358
+ - 更新 `MERCHANT_LIMITS.md`
359
+ - `sent_today += 1`
360
+ - 如果 `action == accept`
361
+ - 给商家注册邮箱发送 `deal_confirmation_v1`
362
+ - 在 merchant `PROGRESS.md` 记录达成合作
363
+ - 追加 campaign `PROGRESS.md`
364
+ - `git commit -m "[{campaignId}] round N {action} with @{username}"`
365
+
366
+ 12. 调 `sentinel report`
290
367
 
291
- 9. 模板渲染:套对应模板 + template_vars → 最终 body
292
-
293
- 10. **Layer 4 终检**:
294
- - body 不含 forbidden_phrases(CONFIG 覆写优先,fallback BRAND)
295
- - body 长度 ≤ 2000 字符
296
- - 不过 → 降级为 escalate
368
+ ```bash
369
+ sentinel report --condition-met true --summary "..." --action-taken "..."
370
+ ```
297
371
 
298
- 11. Edit `CONVERSATIONS/{username}.md` 追加 Round N (response sent),包括完整 LLM decision JSON 供审计
372
+ ---
299
373
 
300
- 12. `scout outreach send`:
301
- - 带 `in_reply_to = KOL 的 message_id`
302
- - 带 `references = 原 references 追加 KOL 的 message_id`
303
- - 拿到新 message_id 写回 CONVERSATIONS
374
+ ## 流程 E:查询进展
304
375
 
305
- 13. Edit `KOLS.md`:更新 rounds、current_offer、last_update、status
306
- - 如果 action == accept:status → deal_reached
307
- - 否则 status → awaiting_reply
376
+ 1. `CAMPAIGNS.md`
308
377
 
309
- 14. 读 `MERCHANT_LIMITS.md`,sent_today += 1,写回
378
+ 2. 确定范围
379
+ - 用户指定 campaign:只汇报该 campaign
380
+ - 未指定且只有一个 active:只汇报该 campaign
381
+ - 未指定且有多个 active:先给跨 campaign 摘要,再提示用户可继续查看单个 campaign
382
+
383
+ 3. 跨 campaign 摘要
384
+ - 对每个 campaign 读 `KOLS.md`
385
+ - 统计:
386
+ - `in_flight`
387
+ - `deal_reached`
388
+ - `needs_merchant_followup`
389
+ - 读 `CONFIG.md` 的 `total_budget`,计算已承诺预算
390
+ - 读 `MERCHANT_LIMITS.md` 的今日 `sent_today`
391
+ - 以 markdown 表格汇报
392
+
393
+ 4. 单 campaign 详情
394
+ - 读 `KOLS.md` 按 `status` 分桶
395
+ - 对每个 `needs_merchant_followup`,读对应 `CONVERSATIONS/{username}.md`,总结 KOL 诉求
396
+ - 读 campaign `PROGRESS.md` 最近 5 条
397
+ - 输出:
398
+ - 关键统计
399
+ - 需要人工处理的事项
400
+ - 最近进展
310
401
 
311
- 15. 如果 action == accept:
312
- - 额外调 `scout outreach send` 发 deal_confirmation_v1 的 recap 给**商家注册邮箱**
313
- - 在 merchant PROGRESS.md 记录 `[campaignId] DEAL REACHED with @username ($final_price)`
402
+ ---
314
403
 
315
- 16. `git commit -m "[{campaignId}] round N {action} with @{username}"`
404
+ ## 异常处理
316
405
 
317
- 17. 追加 campaign PROGRESS.md
406
+ - `scout outreach send` 返回 `ok: false`
407
+ - 更新 `KOLS.md` 为 `send_error`
408
+ - 记 `PROGRESS.md`
409
+ - 不要中断其他 KOL 的处理
318
410
 
319
- 18. `sentinel report --condition-met true --summary "..." --action-taken "..."`
411
+ - LLM 结构化输出格式错误
412
+ - 重试 1 次
413
+ - 仍失败:降级为 `escalate`
320
414
 
321
- ---
415
+ - unknown campaign
416
+ - 写 merchant `PROGRESS.md`
417
+ - `sentinel report --condition-met false --summary "unknown campaign"`
418
+ - 退出
322
419
 
323
- ## 流程 E:查询进度
420
+ - orphan inbound / unknown KOL
421
+ - 写对应 `PROGRESS.md`
422
+ - `sentinel report --condition-met false`
423
+ - 退出
324
424
 
325
- 触发:商家说"KOL 进展如何" / "查看 {X} 状态"。
425
+ - git commit 失败
426
+ - 说明冲突原因
427
+ - 修复后重试
428
+ - 不要擅自 reset
326
429
 
327
- 1. `CAMPAIGNS.md`
328
- 2. 解析范围:
329
- - 商家指定 campaign → 只汇报该 campaign
330
- - 未指定 + 只有一个 active → 只汇报该 campaign
331
- - 未指定 + 多个 active → 先汇报跨 campaign 摘要,再提示可说"查看 {X}"
332
-
333
- 3. **跨 campaign 摘要**:
334
- - 对每个 campaign 读 KOLS.md 统计 in_flight / deal_reached / needs_merchant_followup
335
- - 读 CONFIG 的 total_budget,算已承诺预算
336
- - 读 MERCHANT_LIMITS.md 今日 sent
337
- - 汇报表格(markdown)
338
-
339
- 4. **单 campaign 详情**:
340
- - 读 KOLS.md 按 status 分桶统计
341
- - 对每个 needs_merchant_followup:读 CONVERSATIONS/{username}.md 最后一轮,总结 KOL 的诉求
342
- - 读 campaign PROGRESS.md 最近 5 条
343
- - 汇报(markdown 表格 + 待办列表)
430
+ - UTC 日期跨天
431
+ - 读 `MERCHANT_LIMITS.md` 时先归档昨天计数,再继续
344
432
 
345
433
  ---
346
434
 
347
- ## 核心规则 / 必须遵守
348
-
349
- - **git commit 节奏**:每个原子操作后立即 commit(一个 KOL 的一次发送 / 一个 CONFIG 段 / 一次状态更新),不要批量 commit
350
- - **LLM 只填变量,不写 body**:所有对外邮件必须走模板渲染;直接生成 body 的 bypass 护栏
351
- - **运行时零打扰**:任何护栏不过都**不要**问商家,降级为发 escalate_fallback + 标记 needs_merchant_followup
352
- - **单会话单任务**:一次 session 只做一种动作(onboarding / 发 outreach / 处理回信 / 查询),完成后 git commit 并退出
353
- - **文件路径**:所有路径都在 `~/kol-outreach/` 之下;运行前先 `cd ~/kol-outreach`
354
- - **幂等性**:处理回信前检查 `in_reply_to` 是否等于最后一条 outbound 的 message_id;不等则 escalate(防止重复投递)
435
+ ## 允许使用的命令
355
436
 
356
- ## 错误处理要点
437
+ 只使用这些命令:
357
438
 
358
- | 故障 | 处理 |
359
- |---|---|
360
- | `scout outreach send` 返回 `ok: false` | Edit KOLS.md status=send_error,记 PROGRESS.md,不中断其他 KOL 的处理 |
361
- | LLM 结构化输出格式错误 | 重试 1 次;仍失败降级为 escalate |
362
- | git commit 失败 | 说明冲突原因,修复后重试;不要擅自 reset |
363
- | 当前 UTC 日期跨天 | 读 MERCHANT_LIMITS.md 时先归档昨天计数再继续 |
439
+ - `scout tiktok creators`
440
+ - `scout tiktok creator-products`
441
+ - `scout outreach send`
442
+ - `scout outreach brand-slug check|claim`
443
+ - `sentinel report`
364
444
 
365
- ## 相关工具
445
+ 不要这样做:
366
446
 
367
- - `scout tiktok creators` — 发现 KOL
368
- - `scout tiktok creator-products` — 获取 KOL 最近内容用于个性化
369
- - `scout outreach send` — 发送邮件(内部调 Resend)
370
- - `scout outreach brand-slug check|claim` — 验证/锁定 brand_slug
371
- - `sentinel report` — 回信处理结束时汇报结果(仅在 `[KOL_INBOUND]` 触发的会话里)
447
+ - 不要绕过模板直接自由写外发邮件
448
+ - 不要跳过本地 workspace 状态检查
449
+ - 不要一次 session 混做多个流程
450
+ - 不要修改不属于本次任务的 campaign 状态