@gobing-ai/knowledge-kit 0.1.10 → 0.1.12
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/.claude-plugin/marketplace.json +1 -1
- package/dist/index.js +2478 -1737
- package/package.json +1 -1
- package/plugins/generations/image-gen/README.md +108 -0
- package/plugins/generations/image-gen/assets/sample/cover__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/cover__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/daily-news-cover__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/free__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/illustration__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__clean-webapp-ui.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__cute.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__daily-news.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__editorial.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__fresh.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__minimalist.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__photorealistic.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__sketch.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__technical-diagram.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__vibrant.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__warm.jpg +0 -0
- package/plugins/generations/image-gen/assets/sample/xhs-card__xhs-knowledge.jpg +0 -0
- package/plugins/generations/image-gen/dist/index.js +102 -58
- package/plugins/generations/image-gen/scripts/compose-e2e-artifact.ts +5 -21
- package/plugins/generations/image-gen/scripts/gen-samples.ts +208 -0
- package/plugins/generations/image-gen/src/compose/chromium.ts +2 -3
- package/plugins/generations/image-gen/src/presets.ts +6 -3
- package/plugins/generations/image-gen/src/providers/errors.ts +16 -1
- package/plugins/generations/image-gen/src/providers/minimax.ts +46 -17
- package/plugins/ingestions/web-search/src/index.ts +1 -1
- package/plugins/kk/README.md +18 -0
- package/plugins/kk/commands/image-extract.md +1 -0
- package/plugins/kk/commands/image-generate.md +1 -0
- package/plugins/kk/commands/tell-me.md +1 -0
- package/plugins/kk/commands/workflow-run.md +7 -1
- package/plugins/kk/decisions/extract/it-topics.yaml +24 -0
- package/plugins/kk/decisions/kk-itc.yaml +6 -4
- package/plugins/kk/plugin.json +1 -1
- package/plugins/kk/publish-presets/daily-ai-voice-migration.json +146 -0
- package/plugins/kk/publish-presets/daily-ai-voice.json +11 -3
- package/plugins/kk/schedules/kk-collect.grok-bot.md.tmpl +69 -0
- package/plugins/kk/schedules/kk-daily-ai-voice3.cron.tmpl +18 -0
- package/plugins/kk/schedules/kk-daily-publish3-cn-peak.cron.tmpl +15 -5
- package/plugins/kk/schedules/kk-daily-publish3-overseas.cron.tmpl +9 -4
- package/plugins/kk/scripts/daily-scheduled-run.ts +298 -0
- package/plugins/kk/scripts/daily-stages.ts +1403 -156
- package/plugins/kk/scripts/kk-workflow-stages.ts +266 -10
- package/plugins/kk/scripts/safety-artifact-check.ts +277 -0
- package/plugins/kk/scripts/voice3-publish-window.ts +202 -0
- package/plugins/kk/skills/article-adapt/SKILL.md +1 -0
- package/plugins/kk/skills/audio-authoring/SKILL.md +1 -0
- package/plugins/kk/skills/content-judge/SKILL.md +1 -0
- package/plugins/kk/skills/content-safety/SKILL.md +1 -0
- package/plugins/kk/skills/explain-things/SKILL.md +1 -0
- package/plugins/kk/skills/image-authoring/SKILL.md +1 -0
- package/plugins/kk/skills/itc-generating/SKILL.md +1 -0
- package/plugins/kk/skills/publish/SKILL.md +1 -0
- package/plugins/kk/skills/smart-seo/SKILL.md +1 -0
- package/plugins/kk/skills/storm-research/SKILL.md +1 -0
- package/plugins/kk/skills/taste-unslop/SKILL.md +1 -0
- package/plugins/kk/skills/topic/SKILL.md +1 -0
- package/plugins/kk/skills/web-automation/SKILL.md +1 -0
- package/plugins/kk/skills/xhs-images/SKILL.md +1 -0
- package/plugins/kk/workflows/kk-daily-ai-voice2.yaml +177 -128
- package/plugins/kk/workflows/kk-daily-ai-voice3.yaml +161 -61
- package/plugins/kk/workflows/kk-daily-publish3.yaml +82 -26
- package/plugins/kk/workflows/kk-daily-safety.yaml +156 -69
- package/plugins/kk/workflows/kk-itc-author.yaml +75 -0
- package/plugins/kk/workflows/kk-itc-publish-tail.yaml +25 -3
- package/plugins/kk/workflows/kk-itc.yaml +36 -4
- package/plugins/publishings/bilibili-pub/dist/index.js +2 -1
- package/plugins/publishings/bilibili-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/cnblogs-pub/dist/index.js +2 -1
- package/plugins/publishings/cnblogs-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/csdn-pub/dist/index.js +2 -1
- package/plugins/publishings/csdn-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/infoq-pub/dist/index.js +2 -1
- package/plugins/publishings/infoq-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/jike-pub/dist/index.js +2 -1
- package/plugins/publishings/jike-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/juejin-pub/dist/index.js +2 -1
- package/plugins/publishings/juejin-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/linkedin-pub/dist/index.js +21333 -0
- package/plugins/publishings/linkedin-pub/dist/profile-check.js +21005 -0
- package/plugins/publishings/linkedin-pub/package.json +19 -0
- package/plugins/publishings/linkedin-pub/plugin.json +12 -0
- package/plugins/publishings/linkedin-pub/src/index.ts +705 -0
- package/plugins/publishings/linkedin-pub/src/profile-check.ts +17 -0
- package/plugins/publishings/linkedin-pub/tsconfig.json +4 -0
- package/plugins/publishings/podcast-pub/dist/index.js +46 -8
- package/plugins/publishings/podcast-pub/src/index.ts +62 -5
- package/plugins/publishings/shipinhao-pub/dist/index.js +2 -1
- package/plugins/publishings/shipinhao-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/sspai-pub/dist/index.js +2 -1
- package/plugins/publishings/sspai-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/substack-pub/dist/index.js +2 -1
- package/plugins/publishings/substack-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/surfdash-pub/dist/index.js +461 -53
- package/plugins/publishings/surfdash-pub/src/digest-check.ts +85 -19
- package/plugins/publishings/surfdash-pub/src/index.ts +592 -65
- package/plugins/publishings/threads-pub/dist/index.js +7 -0
- package/plugins/publishings/threads-pub/src/index.ts +12 -0
- package/plugins/publishings/wechatmp-pub/dist/index.js +936 -98
- package/plugins/publishings/wechatmp-pub/dist/profile-check.js +60 -3
- package/plugins/publishings/wechatmp-pub/src/flow.ts +1134 -86
- package/plugins/publishings/wechatmp-pub/src/index.ts +112 -30
- package/plugins/publishings/wechatmp-pub/src/page.ts +105 -0
- package/plugins/publishings/weibo-pub/dist/index.js +2 -1
- package/plugins/publishings/weibo-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/x-pub/dist/index.js +9 -1
- package/plugins/publishings/x-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/x-pub/src/index.ts +11 -0
- package/plugins/publishings/xhs-pub/dist/index.js +2 -1
- package/plugins/publishings/xhs-pub/dist/profile-check.js +2 -1
- package/plugins/publishings/xiaoyuzhou-pub/dist/index.js +2 -1
- package/plugins/publishings/xiaoyuzhou-pub/dist/profile-check.js +2 -1
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"name": "daily-ai-voice-migration",
|
|
4
|
+
"targets": [
|
|
5
|
+
{
|
|
6
|
+
"id": "surfdash.article",
|
|
7
|
+
"plugin": "surfdash-pub",
|
|
8
|
+
"operation": "post",
|
|
9
|
+
"variant": "article",
|
|
10
|
+
"tier": 0,
|
|
11
|
+
"window": "after-generate",
|
|
12
|
+
"mode": "draft",
|
|
13
|
+
"required": true
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
"id": "podcast.episode",
|
|
17
|
+
"plugin": "podcast-pub",
|
|
18
|
+
"operation": "episode",
|
|
19
|
+
"variant": "episode",
|
|
20
|
+
"tier": 0,
|
|
21
|
+
"window": "after-generate",
|
|
22
|
+
"mode": "draft",
|
|
23
|
+
"required": true
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "wechatmp.podcast",
|
|
27
|
+
"plugin": "wechatmp-pub",
|
|
28
|
+
"operation": "播客",
|
|
29
|
+
"variant": "episode",
|
|
30
|
+
"tier": 1,
|
|
31
|
+
"window": "after-generate",
|
|
32
|
+
"mode": "draft"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"id": "xhs.podcast",
|
|
36
|
+
"plugin": "xhs-pub",
|
|
37
|
+
"operation": "发播客",
|
|
38
|
+
"variant": "episode",
|
|
39
|
+
"tier": 1,
|
|
40
|
+
"window": "after-generate",
|
|
41
|
+
"mode": "draft"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "wechatmp.article",
|
|
45
|
+
"plugin": "wechatmp-pub",
|
|
46
|
+
"operation": "文章",
|
|
47
|
+
"variant": "article",
|
|
48
|
+
"tier": 2,
|
|
49
|
+
"window": "after-generate",
|
|
50
|
+
"mode": "draft",
|
|
51
|
+
"options": {
|
|
52
|
+
"collection": "每日AI资讯"
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"id": "xhs.article",
|
|
57
|
+
"plugin": "xhs-pub",
|
|
58
|
+
"operation": "写长文",
|
|
59
|
+
"variant": "article",
|
|
60
|
+
"tier": 2,
|
|
61
|
+
"window": "after-generate",
|
|
62
|
+
"mode": "dummy"
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"id": "xhs.cards",
|
|
66
|
+
"plugin": "xhs-pub",
|
|
67
|
+
"operation": "上传图文",
|
|
68
|
+
"variant": "cards",
|
|
69
|
+
"tier": 3,
|
|
70
|
+
"window": "after-generate",
|
|
71
|
+
"mode": "off"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "weibo.article",
|
|
75
|
+
"plugin": "weibo-pub",
|
|
76
|
+
"operation": "article",
|
|
77
|
+
"variant": "article",
|
|
78
|
+
"tier": 2,
|
|
79
|
+
"window": "cn-peak",
|
|
80
|
+
"mode": "draft"
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
"id": "x.article",
|
|
84
|
+
"plugin": "x-pub",
|
|
85
|
+
"operation": "article",
|
|
86
|
+
"variant": "article",
|
|
87
|
+
"tier": 2,
|
|
88
|
+
"window": "cn-peak",
|
|
89
|
+
"mode": "draft"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "x.post",
|
|
93
|
+
"plugin": "x-pub",
|
|
94
|
+
"operation": "post",
|
|
95
|
+
"variant": "social:x",
|
|
96
|
+
"tier": 3,
|
|
97
|
+
"window": "overseas-rss",
|
|
98
|
+
"mode": "dummy"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "weibo.post",
|
|
102
|
+
"plugin": "weibo-pub",
|
|
103
|
+
"operation": "post",
|
|
104
|
+
"variant": "social:weibo",
|
|
105
|
+
"tier": 3,
|
|
106
|
+
"window": "overseas-rss",
|
|
107
|
+
"mode": "dummy"
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"id": "threads.post",
|
|
111
|
+
"plugin": "threads-pub",
|
|
112
|
+
"operation": "post",
|
|
113
|
+
"variant": "social:threads",
|
|
114
|
+
"tier": 3,
|
|
115
|
+
"window": "overseas-rss",
|
|
116
|
+
"mode": "dummy"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"id": "linkedin.post",
|
|
120
|
+
"plugin": "linkedin-pub",
|
|
121
|
+
"operation": "post",
|
|
122
|
+
"variant": "social:linkedin",
|
|
123
|
+
"tier": 3,
|
|
124
|
+
"window": "overseas-rss",
|
|
125
|
+
"mode": "dummy"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"id": "shipinhao.cards",
|
|
129
|
+
"plugin": "shipinhao-pub",
|
|
130
|
+
"operation": "图文",
|
|
131
|
+
"variant": "cards",
|
|
132
|
+
"tier": 3,
|
|
133
|
+
"window": "overseas-rss",
|
|
134
|
+
"mode": "off"
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
"id": "threads.images",
|
|
138
|
+
"plugin": "threads-pub",
|
|
139
|
+
"operation": "post-images",
|
|
140
|
+
"variant": "cards",
|
|
141
|
+
"tier": 3,
|
|
142
|
+
"window": "overseas-rss",
|
|
143
|
+
"mode": "off"
|
|
144
|
+
}
|
|
145
|
+
]
|
|
146
|
+
}
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"variant": "social:x",
|
|
86
86
|
"tier": 3,
|
|
87
87
|
"window": "overseas-rss",
|
|
88
|
-
"mode": "
|
|
88
|
+
"mode": "dummy"
|
|
89
89
|
},
|
|
90
90
|
{
|
|
91
91
|
"plugin": "weibo-pub",
|
|
@@ -93,7 +93,7 @@
|
|
|
93
93
|
"variant": "social:weibo",
|
|
94
94
|
"tier": 3,
|
|
95
95
|
"window": "overseas-rss",
|
|
96
|
-
"mode": "
|
|
96
|
+
"mode": "dummy"
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
"plugin": "threads-pub",
|
|
@@ -101,7 +101,15 @@
|
|
|
101
101
|
"variant": "social:threads",
|
|
102
102
|
"tier": 3,
|
|
103
103
|
"window": "overseas-rss",
|
|
104
|
-
"mode": "
|
|
104
|
+
"mode": "dummy"
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
"plugin": "linkedin-pub",
|
|
108
|
+
"operation": "post",
|
|
109
|
+
"variant": "social:linkedin",
|
|
110
|
+
"tier": 3,
|
|
111
|
+
"window": "overseas-rss",
|
|
112
|
+
"mode": "dummy"
|
|
105
113
|
},
|
|
106
114
|
{
|
|
107
115
|
"plugin": "shipinhao-pub",
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# kk-collect — Grok Bot cloud routine
|
|
2
|
+
|
|
3
|
+
Rendered by `kk schedule render kk-collect --platform grok-bot --bot {{bot}} [--tz {{tz}}] [--cron {{cron}}]`;
|
|
4
|
+
kk never creates the routine. **Operator action:** paste this definition into the Grok Bot's routine
|
|
5
|
+
editor (https://docs.x.ai/grok-bot/skills-routines-and-automations) and create it there.
|
|
6
|
+
|
|
7
|
+
The cloud boundary is ADR-044 (`docs/00_ADR.md`): `kk.db` never leaves the Mac — this routine runs
|
|
8
|
+
store-free, browser-free work only, and pushes its output to the `kk-cloud-sync` branch for the Mac
|
|
9
|
+
to import.
|
|
10
|
+
|
|
11
|
+
## Owner Bot
|
|
12
|
+
|
|
13
|
+
{{bot}}
|
|
14
|
+
|
|
15
|
+
## Schedule
|
|
16
|
+
|
|
17
|
+
`{{cron}}` in `{{tz}}` (the routine's schedule must match this expression and timezone).
|
|
18
|
+
|
|
19
|
+
## Inputs
|
|
20
|
+
|
|
21
|
+
- The Bot needs `kk` and `spur` (sp-bot-bootstrap) in its checkout, plus the three source plugins'
|
|
22
|
+
credentials (`aihot`, `last30days`, `horizon`) provided as Bot secrets.
|
|
23
|
+
- Push access to the `kk-cloud-sync` branch (the Bot's GitHub connector).
|
|
24
|
+
- The fan-in source list below is copied from `plugins/kk/workflows/kk-collect.yaml:116-121` —
|
|
25
|
+
update the rendered routine whenever that file's source list changes (drift hazard).
|
|
26
|
+
|
|
27
|
+
## Steps
|
|
28
|
+
|
|
29
|
+
1. `stamp="$(date -u +%Y%m%dT%H%MZ)"` (UTC, `YYYYMMDDTHHMMZ` — the kk-collect stamp format).
|
|
30
|
+
2. `kk stage collect-prepare "$stamp" 10 "AI news" 6` — the store-free prefix: writes only
|
|
31
|
+
`.works/kk-collect/<stamp>` and `.spur/runs/kk-collect/*`, touching no database.
|
|
32
|
+
3. Fan-in the three sources (no `kk store` — the output file IS the artifact):
|
|
33
|
+
```bash
|
|
34
|
+
work_dir="$(cat .spur/runs/kk-collect/work-dir.txt)"
|
|
35
|
+
kk executor fan-in \
|
|
36
|
+
--source "aihot-ingest:${work_dir}/10-ingest/source.json" \
|
|
37
|
+
--source "last30days-ingest:${work_dir}/10-ingest/last30days.json" \
|
|
38
|
+
--source "horizon-ingest:${work_dir}/10-ingest/horizon.json" \
|
|
39
|
+
--source-timeout-override "horizon-ingest:2700000" \
|
|
40
|
+
--out "sync/kk-collect/$stamp.json"
|
|
41
|
+
```
|
|
42
|
+
4. Commit `sync/kk-collect/$stamp.json` and push it to the `kk-cloud-sync` branch.
|
|
43
|
+
|
|
44
|
+
## Expected result
|
|
45
|
+
|
|
46
|
+
`sync/kk-collect/<stamp>.json` on `kk-cloud-sync`, a blended `Doc[]` from the three sources, with
|
|
47
|
+
`<stamp>` equal to the run's UTC minute stamp.
|
|
48
|
+
|
|
49
|
+
## Approval boundary
|
|
50
|
+
|
|
51
|
+
Collect only — the routine never runs `kk store`, `kk store label`, a generator or a publisher, and
|
|
52
|
+
it never installs anything itself; creating the routine in the Grok Bot is an explicit operator
|
|
53
|
+
action. Publishing stays Mac-local under ADR-022's live-publish consent gate.
|
|
54
|
+
|
|
55
|
+
## Missing / stale (ADR-044)
|
|
56
|
+
|
|
57
|
+
| Situation | Behavior |
|
|
58
|
+
| --------- | -------- |
|
|
59
|
+
| Inputs or credentials missing at run time | print `no-content: <reason>` and exit with no side effects — in particular, no empty artifact is committed |
|
|
60
|
+
| Artifact older than 26 h when the Mac imports | the Mac's `kk store ingest` ignores it (logged) |
|
|
61
|
+
| Same artifact seen again | imported at most once — idempotent by the `cloud-<stamp>` run id |
|
|
62
|
+
|
|
63
|
+
## Mac import
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
git fetch origin kk-cloud-sync
|
|
67
|
+
git show origin/kk-cloud-sync:sync/kk-collect/<stamp>.json > /tmp/kk-collect-cloud.json
|
|
68
|
+
kk store ingest --run cloud-<stamp> --in /tmp/kk-collect-cloud.json
|
|
69
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# kk-daily-ai-voice3 — shadow generation run (task 0334 R1/R6).
|
|
2
|
+
#
|
|
3
|
+
# 04:30 America/Los_Angeles, the same cadence as the voice2 daily run it shadows. The entry is
|
|
4
|
+
# defined in the zone the machine already runs in, so it needs no DST adjustment.
|
|
5
|
+
#
|
|
6
|
+
# Shadow posture is baked into the launch: `auto_publish` stays false, so this entry only
|
|
7
|
+
# prepares, researches and generates — it publishes NOTHING on its own. The nested publish3
|
|
8
|
+
# window runs only when an operator launch flips `publish_window` away from `after-generate`
|
|
9
|
+
# (rehearsal flags in docs/design/kk-daily-ai-voice3.md). The workspace namespace stays
|
|
10
|
+
# kk-daily-ai-voice2 (voice3 prepares under it) until the operator's cutover changes it.
|
|
11
|
+
#
|
|
12
|
+
# Rendered by `kk schedule render kk-daily-ai-voice3 --platform cron --repo <checkout>`;
|
|
13
|
+
# kk never edits the operator's crontab. MERGE, never replace:
|
|
14
|
+
#
|
|
15
|
+
# crontab -l > /tmp/kk-voice3.cron 2>/dev/null || :
|
|
16
|
+
# kk schedule render kk-daily-ai-voice3 --platform cron --repo "$PWD" >> /tmp/kk-voice3.cron
|
|
17
|
+
# crontab /tmp/kk-voice3.cron
|
|
18
|
+
30 4 * * * cd {{repo}} && mkdir -p .works/kk-daily-ai-voice3 && PATH="{{home}}/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" TZ=America/Los_Angeles spur workflow run plugins/kk/workflows/kk-daily-ai-voice3.yaml --vars '{"auto_publish":"false"}' >> {{repo}}/.works/kk-daily-ai-voice3/generation.log 2>&1
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# kk-daily-publish3-cn-peak — domestic live window (
|
|
1
|
+
# kk-daily-publish3-cn-peak — domestic live window (tasks 0323 R5, 0334 R1-R3).
|
|
2
2
|
#
|
|
3
3
|
# Fires at 17:00 local because the audience window is 09:00 Asia/Shanghai and launchd/cron
|
|
4
4
|
# intervals follow the MACHINE's zone; the first node (`publish-matrix-wait-until 09:00
|
|
@@ -6,7 +6,17 @@
|
|
|
6
6
|
# so the entry never needs editing twice a year.
|
|
7
7
|
#
|
|
8
8
|
# `live_channels` is baked in: this installed line IS the operator's ADR-022 consent for a live
|
|
9
|
-
# send. Nothing in the workflow or the preset can produce `live` on its own.
|
|
9
|
+
# send. Nothing in the workflow or the preset can produce `live` on its own. The window is
|
|
10
|
+
# consent-scoped to tokens with a cn-peak row (weibo-pub, x-pub — both draft). Every live launch
|
|
11
|
+
# stops at the `send-confirm` gate and waits for the operator's manual resume; the entry never
|
|
12
|
+
# delivers unattended.
|
|
13
|
+
#
|
|
14
|
+
# The launch goes through plugins/kk/scripts/voice3-publish-window.ts (task 0334): a crontab
|
|
15
|
+
# command ends at the first UNESCAPED percent sign, so inline `date +%F` shell silently truncates
|
|
16
|
+
# the entry — the launcher owns the LA date math, resolves the GENERATION bundle from the
|
|
17
|
+
# effective works root (never the publish3 output directory), refuses a missing/mismatched
|
|
18
|
+
# bundle, validates the token list against the preset's cn-peak rows, and only then execs spur.
|
|
19
|
+
# The rehearsal pins the shipped default preset; change it at cutover.
|
|
10
20
|
#
|
|
11
21
|
# Rendered by `kk schedule render kk-daily-publish3-cn-peak --platform cron --repo <checkout>`;
|
|
12
22
|
# kk never edits the operator's crontab. MERGE, never replace:
|
|
@@ -15,6 +25,6 @@
|
|
|
15
25
|
# kk schedule render kk-daily-publish3-cn-peak --platform cron --repo "$PWD" >> /tmp/kk-cn-peak.cron
|
|
16
26
|
# crontab /tmp/kk-cn-peak.cron
|
|
17
27
|
#
|
|
18
|
-
# `run_date` is computed at tick time in America/Los_Angeles: a run that starts
|
|
19
|
-
# still "today" for the day dir the 04:30 generation wrote.
|
|
20
|
-
0 17 * * * cd {{repo}} && mkdir -p .works/kk-daily-publish3 && PATH="{{home}}/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" TZ=America/Los_Angeles
|
|
28
|
+
# `run_date` is computed at tick time in America/Los_Angeles by the launcher: a run that starts
|
|
29
|
+
# at 17:00 PDT is still "today" for the day dir the 04:30 generation wrote.
|
|
30
|
+
0 17 * * * cd {{repo}} && mkdir -p .works/kk-daily-publish3 && PATH="{{home}}/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" TZ=America/Los_Angeles bun {{repo}}/plugins/kk/scripts/voice3-publish-window.ts --window cn-peak --preset daily-ai-voice --live-channels weibo-pub,x-pub >> {{repo}}/.works/kk-daily-publish3/cn-peak.log 2>&1
|
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
# kk-daily-publish3-overseas — overseas window behind the podcast RSS gate (
|
|
1
|
+
# kk-daily-publish3-overseas — overseas window behind the podcast RSS gate (tasks 0323 R5, 0334 R1-R2).
|
|
2
2
|
#
|
|
3
3
|
# 07:00 America/Los_Angeles, which needs no DST adjustment: the entry is defined in the zone the
|
|
4
4
|
# machine already runs in. The gated date is the PREVIOUS day, because the observed episodes land
|
|
5
|
-
# 13:43–15:22 PDT (after this tick) —
|
|
5
|
+
# 13:43–15:22 PDT (after this tick) — the launcher derives yesterday's LA date for `run_date`.
|
|
6
6
|
#
|
|
7
7
|
# The gate decides whether anything publishes: a miss cancels the overseas targets and runs
|
|
8
|
-
# `diagnose` + the tier-0-only repair. This line carries no
|
|
8
|
+
# `diagnose` + the tier-0-only repair. This line carries no live consent; the overseas channels
|
|
9
9
|
# in the shipped preset are `draft`, so a live overseas post stays an operator launch.
|
|
10
10
|
#
|
|
11
|
+
# plugins/kk/scripts/voice3-publish-window.ts (task 0334) owns the date math because a crontab
|
|
12
|
+
# command ends at the first UNESCAPED percent sign — the old inline `date -v-1d +%Y%m%d` shell
|
|
13
|
+
# truncated silently. The launcher resolves the GENERATION bundle (voice2 namespace, effective
|
|
14
|
+
# works root) and refuses a missing/mismatched bundle before any spur process starts.
|
|
15
|
+
#
|
|
11
16
|
# Rendered by `kk schedule render kk-daily-publish3-overseas --platform cron --repo <checkout>`;
|
|
12
17
|
# kk never edits the operator's crontab. MERGE, never replace:
|
|
13
18
|
#
|
|
14
19
|
# crontab -l > /tmp/kk-overseas.cron 2>/dev/null || :
|
|
15
20
|
# kk schedule render kk-daily-publish3-overseas --platform cron --repo "$PWD" >> /tmp/kk-overseas.cron
|
|
16
21
|
# crontab /tmp/kk-overseas.cron
|
|
17
|
-
0 7 * * * cd {{repo}} && mkdir -p .works/kk-daily-publish3 && PATH="{{home}}/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" TZ=America/Los_Angeles
|
|
22
|
+
0 7 * * * cd {{repo}} && mkdir -p .works/kk-daily-publish3 && PATH="{{home}}/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" TZ=America/Los_Angeles bun {{repo}}/plugins/kk/scripts/voice3-publish-window.ts --window overseas-rss >> {{repo}}/.works/kk-daily-publish3/overseas.log 2>&1
|
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/** Supervise one scheduled daily run and persist its terminal outcome even after a deadline. */
|
|
3
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
4
|
+
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { dirname, join, resolve } from 'node:path';
|
|
6
|
+
import { parseArgs } from 'node:util';
|
|
7
|
+
|
|
8
|
+
const { values } = parseArgs({
|
|
9
|
+
options: {
|
|
10
|
+
repo: { type: 'string', default: process.cwd() },
|
|
11
|
+
'works-dir': { type: 'string' },
|
|
12
|
+
'run-id': { type: 'string' },
|
|
13
|
+
'timeout-ms': { type: 'string', default: '13500000' },
|
|
14
|
+
receipt: { type: 'string' },
|
|
15
|
+
agent: { type: 'string', default: 'inline' },
|
|
16
|
+
'retry-agent': { type: 'string', default: 'claude' },
|
|
17
|
+
'required-channels': { type: 'string', default: 'wechat,locales,cms-resync' },
|
|
18
|
+
'wechat-live': { type: 'boolean', default: false },
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
const timeoutMs = Number(values['timeout-ms']);
|
|
22
|
+
if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
|
|
23
|
+
console.error('daily-scheduled-run: --timeout-ms must be a positive integer');
|
|
24
|
+
process.exit(2);
|
|
25
|
+
}
|
|
26
|
+
const repo = resolve(values.repo);
|
|
27
|
+
const now = new Date();
|
|
28
|
+
const stamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
|
|
29
|
+
const runId = values['run-id'] ?? `daily-ai-voice-${stamp}-${now.getTime()}`;
|
|
30
|
+
const workDir = join(resolve(values['works-dir'] ?? join(repo, '.works')), 'kk-daily-ai-voice2', stamp);
|
|
31
|
+
const receiptPath = resolve(values.receipt ?? join(workDir, 'schedule-receipt.json'));
|
|
32
|
+
const startedAt = now.toISOString();
|
|
33
|
+
mkdirSync(dirname(receiptPath), { recursive: true });
|
|
34
|
+
const vars = {
|
|
35
|
+
publish_enabled: 'true',
|
|
36
|
+
skip_review: 'true',
|
|
37
|
+
agent: values.agent,
|
|
38
|
+
agent_retry: values['retry-agent'],
|
|
39
|
+
wechat_live: values['wechat-live'] === true ? 'true' : 'false',
|
|
40
|
+
work_dir: workDir,
|
|
41
|
+
run_date: stamp,
|
|
42
|
+
};
|
|
43
|
+
writeFileSync(receiptPath, JSON.stringify({ runId, startedAt, status: 'running', workDir }, null, 2));
|
|
44
|
+
let interrupted = false;
|
|
45
|
+
let timedOut = false;
|
|
46
|
+
let spawnError = '';
|
|
47
|
+
let forceKill: ReturnType<typeof setTimeout> | undefined;
|
|
48
|
+
const child = spawn(
|
|
49
|
+
'spur',
|
|
50
|
+
[
|
|
51
|
+
'--no-logo',
|
|
52
|
+
'workflow',
|
|
53
|
+
'run',
|
|
54
|
+
'plugins/kk/workflows/kk-daily-ai-voice2.yaml',
|
|
55
|
+
'--run-id',
|
|
56
|
+
runId,
|
|
57
|
+
'--vars',
|
|
58
|
+
JSON.stringify(vars),
|
|
59
|
+
'--quiet',
|
|
60
|
+
],
|
|
61
|
+
{ cwd: repo, detached: true, stdio: 'inherit' },
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
const ownedPids = new Set<number>();
|
|
65
|
+
/** Snapshot descendants before the root exits, including workers that created their own groups. */
|
|
66
|
+
function rememberDescendants(): void {
|
|
67
|
+
if (child.pid === undefined) return;
|
|
68
|
+
ownedPids.add(child.pid);
|
|
69
|
+
const snapshot = spawnSync('ps', ['-axo', 'pid=,ppid='], { encoding: 'utf-8', timeout: 5000 });
|
|
70
|
+
const rows = snapshot.stdout
|
|
71
|
+
.trim()
|
|
72
|
+
.split('\n')
|
|
73
|
+
.map((line) => line.trim().split(/\s+/).map(Number));
|
|
74
|
+
const descendants = new Set([child.pid]);
|
|
75
|
+
let added = true;
|
|
76
|
+
while (added) {
|
|
77
|
+
added = false;
|
|
78
|
+
for (const [pid, parent] of rows) {
|
|
79
|
+
if (pid !== undefined && parent !== undefined && descendants.has(parent) && !descendants.has(pid)) {
|
|
80
|
+
descendants.add(pid);
|
|
81
|
+
added = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
for (const pid of descendants) ownedPids.add(pid);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Stop only the recorded owned tree; process-group signals also catch late same-group forks. */
|
|
89
|
+
function signalOwned(signal: NodeJS.Signals): void {
|
|
90
|
+
if (child.pid === undefined) return;
|
|
91
|
+
rememberDescendants();
|
|
92
|
+
for (const pid of [...ownedPids].reverse()) {
|
|
93
|
+
try {
|
|
94
|
+
process.kill(-pid, signal);
|
|
95
|
+
} catch {}
|
|
96
|
+
try {
|
|
97
|
+
process.kill(pid, signal);
|
|
98
|
+
} catch {}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function terminate(): void {
|
|
103
|
+
signalOwned('SIGTERM');
|
|
104
|
+
if (forceKill === undefined) {
|
|
105
|
+
forceKill = setTimeout(() => {
|
|
106
|
+
signalOwned('SIGKILL');
|
|
107
|
+
}, 300);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const onSignal = (): void => {
|
|
111
|
+
interrupted = true;
|
|
112
|
+
terminate();
|
|
113
|
+
};
|
|
114
|
+
process.once('SIGTERM', onSignal);
|
|
115
|
+
process.once('SIGINT', onSignal);
|
|
116
|
+
const deadline = setTimeout(() => {
|
|
117
|
+
timedOut = true;
|
|
118
|
+
terminate();
|
|
119
|
+
}, timeoutMs);
|
|
120
|
+
const exitCode = await new Promise<number>((done) => {
|
|
121
|
+
child.once('error', (error) => {
|
|
122
|
+
spawnError = error.message;
|
|
123
|
+
done(1);
|
|
124
|
+
});
|
|
125
|
+
child.once('close', (code) => done(code ?? 1));
|
|
126
|
+
});
|
|
127
|
+
clearTimeout(deadline);
|
|
128
|
+
if (forceKill !== undefined) clearTimeout(forceKill);
|
|
129
|
+
// A killed shell can leave owned descendants that ignore TERM. Always finish the group before
|
|
130
|
+
// recording a terminal outcome; no detached grandchild may continue to publish after a timeout.
|
|
131
|
+
if (child.pid !== undefined && (timedOut || interrupted || exitCode !== 0)) {
|
|
132
|
+
terminate();
|
|
133
|
+
await Bun.sleep(300);
|
|
134
|
+
signalOwned('SIGKILL');
|
|
135
|
+
await Bun.sleep(100);
|
|
136
|
+
}
|
|
137
|
+
const workflowStatus = timedOut ? 'timed-out' : interrupted ? 'interrupted' : exitCode === 0 ? 'done' : 'failed';
|
|
138
|
+
const primaryPublishStatus = readOptional(join(workDir, '31-publish', `${stamp}_17_publish_publish-status.txt`));
|
|
139
|
+
const runSummary = readOptional(join(workDir, 'run-report.summary.txt'));
|
|
140
|
+
const cleanup: { command: string; exitCode: number | null }[] = [];
|
|
141
|
+
let reservationRecovery: { status: string; reason: string; released?: number } = {
|
|
142
|
+
status: 'retained',
|
|
143
|
+
reason: 'workflow completed; publication settlement owns its reservations',
|
|
144
|
+
};
|
|
145
|
+
if (workflowStatus !== 'done' || runSummary?.startsWith('no-content:')) {
|
|
146
|
+
// The caller gives the nested safety run this exact id. Unknown/already-terminal ids are
|
|
147
|
+
// harmless cleanup failures; they never override the original workflow failure.
|
|
148
|
+
if (workflowStatus !== 'done') {
|
|
149
|
+
for (const id of [`${runId}-safety`, runId]) {
|
|
150
|
+
const result = spawnSync('spur', ['workflow', 'cancel', id, '--json'], {
|
|
151
|
+
cwd: repo,
|
|
152
|
+
encoding: 'utf-8',
|
|
153
|
+
timeout: 20000,
|
|
154
|
+
});
|
|
155
|
+
cleanup.push({ command: `spur workflow cancel ${id}`, exitCode: result.status });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Only a complete trace proving no channel send began permits recycling reservations. A
|
|
159
|
+
// missing receipt alone is insufficient: a remote send may have succeeded before it crashed.
|
|
160
|
+
const trace = spawnSync('spur', ['workflow', 'trace', runId, '--json'], {
|
|
161
|
+
cwd: repo,
|
|
162
|
+
encoding: 'utf-8',
|
|
163
|
+
timeout: 20000,
|
|
164
|
+
});
|
|
165
|
+
reservationRecovery = { status: 'retained', reason: 'publication state could not be established' };
|
|
166
|
+
try {
|
|
167
|
+
const parsed = JSON.parse(trace.stdout) as {
|
|
168
|
+
run?: { runId?: string; workflowName?: string; status?: string; startedAt?: string };
|
|
169
|
+
events?: { kind?: string; node?: string; status?: string }[];
|
|
170
|
+
};
|
|
171
|
+
const sendNodes = new Set([
|
|
172
|
+
'publish-surfdash',
|
|
173
|
+
'publish-podcast',
|
|
174
|
+
'xhs-publish',
|
|
175
|
+
'wechat-publish',
|
|
176
|
+
'translate-localize',
|
|
177
|
+
'translate',
|
|
178
|
+
'translate-retry',
|
|
179
|
+
'translate-sync',
|
|
180
|
+
'publish-classify',
|
|
181
|
+
]);
|
|
182
|
+
const validTrace =
|
|
183
|
+
trace.status === 0 &&
|
|
184
|
+
parsed.run?.runId === runId &&
|
|
185
|
+
parsed.run?.workflowName === 'kk-daily-ai-voice2' &&
|
|
186
|
+
parsed.run?.status === (workflowStatus === 'done' ? 'done' : 'failed') &&
|
|
187
|
+
Date.parse(parsed.run.startedAt ?? '') >= Date.parse(startedAt) - 1000 &&
|
|
188
|
+
Date.parse(parsed.run.startedAt ?? '') <= Date.now() &&
|
|
189
|
+
Array.isArray(parsed.events) &&
|
|
190
|
+
parsed.events.length > 0;
|
|
191
|
+
const sendStarted = parsed.events?.some(
|
|
192
|
+
(event) => event.kind === 'action' && sendNodes.has(event.node ?? '') && event.status !== 'skipped',
|
|
193
|
+
);
|
|
194
|
+
if (validTrace && !sendStarted) {
|
|
195
|
+
// Cancellation is durable before reuse. Require a second stable snapshot after the
|
|
196
|
+
// owned processes are gone, so an in-flight or partially finalized row cannot pass.
|
|
197
|
+
await Bun.sleep(200);
|
|
198
|
+
const secondTrace = spawnSync('spur', ['workflow', 'trace', runId, '--json'], {
|
|
199
|
+
cwd: repo,
|
|
200
|
+
encoding: 'utf-8',
|
|
201
|
+
timeout: 20000,
|
|
202
|
+
});
|
|
203
|
+
const second = JSON.parse(secondTrace.stdout) as typeof parsed;
|
|
204
|
+
if (
|
|
205
|
+
secondTrace.status !== 0 ||
|
|
206
|
+
JSON.stringify(second.run) !== JSON.stringify(parsed.run) ||
|
|
207
|
+
JSON.stringify(second.events) !== JSON.stringify(parsed.events)
|
|
208
|
+
) {
|
|
209
|
+
throw new Error('failed-run trace is not stable after cancellation');
|
|
210
|
+
}
|
|
211
|
+
const release = spawnSync('kk', ['store', 'release', '--topic', 'ai-news', '--run', runId], {
|
|
212
|
+
cwd: repo,
|
|
213
|
+
encoding: 'utf-8',
|
|
214
|
+
timeout: 20000,
|
|
215
|
+
});
|
|
216
|
+
cleanup.push({ command: 'kk store release (verified pre-publication failure)', exitCode: release.status });
|
|
217
|
+
const result = release.status === 0 ? (JSON.parse(release.stdout) as { released?: unknown }) : null;
|
|
218
|
+
reservationRecovery =
|
|
219
|
+
result !== null && typeof result.released === 'number'
|
|
220
|
+
? { status: 'released', reason: 'trace proves no channel send began', released: result.released }
|
|
221
|
+
: { status: 'retained', reason: 'reservation release did not produce a valid receipt' };
|
|
222
|
+
} else if (sendStarted) {
|
|
223
|
+
reservationRecovery = { status: 'retained', reason: 'a channel send began; preserve uncertain delivery' };
|
|
224
|
+
}
|
|
225
|
+
} catch {
|
|
226
|
+
// Unreadable/truncated evidence or an unexpected CLI shape must never cause reuse of a
|
|
227
|
+
// potentially aired story. Keep the reservation and the original failure visible.
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
const ledger = spawnSync('kk', ['ledger', 'sync', '--workflow', 'kk-daily-ai-voice2', '--work-dir', workDir], {
|
|
231
|
+
cwd: repo,
|
|
232
|
+
encoding: 'utf-8',
|
|
233
|
+
timeout: 20000,
|
|
234
|
+
});
|
|
235
|
+
cleanup.push({ command: 'kk ledger sync', exitCode: ledger.status });
|
|
236
|
+
function readOptional(path: string): string | null {
|
|
237
|
+
try {
|
|
238
|
+
return readFileSync(path, 'utf-8').trim();
|
|
239
|
+
} catch {
|
|
240
|
+
return null;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const requiredChannels = values['required-channels']
|
|
244
|
+
.split(',')
|
|
245
|
+
.map((channel) => channel.trim())
|
|
246
|
+
.filter(Boolean);
|
|
247
|
+
let channelCompleteness: { channel: string; state: string; detail?: string }[] = [];
|
|
248
|
+
try {
|
|
249
|
+
const parsed = JSON.parse(
|
|
250
|
+
readOptional(join(workDir, '31-publish', `${stamp}_17_publish_completeness.json`)) ?? 'null',
|
|
251
|
+
) as { rows?: unknown } | null;
|
|
252
|
+
if (Array.isArray(parsed?.rows)) {
|
|
253
|
+
channelCompleteness = parsed.rows.filter(
|
|
254
|
+
(row): row is { channel: string; state: string; detail?: string } =>
|
|
255
|
+
row !== null &&
|
|
256
|
+
typeof row === 'object' &&
|
|
257
|
+
typeof row.channel === 'string' &&
|
|
258
|
+
typeof row.state === 'string',
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
} catch {}
|
|
262
|
+
const missingRequiredChannels = requiredChannels.filter((channel) => {
|
|
263
|
+
const rows = channelCompleteness.filter((row) => row.channel === channel);
|
|
264
|
+
return rows.length !== 1 || rows[0]?.state !== 'done';
|
|
265
|
+
});
|
|
266
|
+
// A terminal green workflow can mean "no content" or failed delivery. Make that observable to
|
|
267
|
+
// the scheduler instead of equating the workflow's process exit with a published edition.
|
|
268
|
+
const status =
|
|
269
|
+
workflowStatus !== 'done'
|
|
270
|
+
? workflowStatus
|
|
271
|
+
: runSummary?.startsWith('no-content:')
|
|
272
|
+
? 'no-content'
|
|
273
|
+
: primaryPublishStatus === 'full' && missingRequiredChannels.length === 0
|
|
274
|
+
? 'done'
|
|
275
|
+
: 'incomplete';
|
|
276
|
+
const receipt = {
|
|
277
|
+
runId,
|
|
278
|
+
startedAt,
|
|
279
|
+
completedAt: new Date().toISOString(),
|
|
280
|
+
status,
|
|
281
|
+
exitCode: timedOut ? 124 : interrupted ? 143 : exitCode !== 0 ? exitCode : status === 'done' ? 0 : 4,
|
|
282
|
+
workDir,
|
|
283
|
+
timeoutMs,
|
|
284
|
+
agent: values.agent,
|
|
285
|
+
retryAgent: values['retry-agent'],
|
|
286
|
+
error: spawnError || null,
|
|
287
|
+
primaryPublishStatus,
|
|
288
|
+
channelCompleteness,
|
|
289
|
+
missingRequiredChannels,
|
|
290
|
+
runSummary,
|
|
291
|
+
reservationRecovery,
|
|
292
|
+
cleanup,
|
|
293
|
+
};
|
|
294
|
+
writeFileSync(receiptPath, `${JSON.stringify(receipt, null, 2)}\n`);
|
|
295
|
+
console.log(JSON.stringify(receipt));
|
|
296
|
+
process.removeListener('SIGTERM', onSignal);
|
|
297
|
+
process.removeListener('SIGINT', onSignal);
|
|
298
|
+
process.exit(receipt.exitCode);
|