@gobing-ai/knowledge-kit 0.0.7 → 0.0.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +21604 -11844
- package/package.json +4 -1
- package/plugins/generations/content-gen/package.json +2 -1
- package/plugins/generations/content-gen/src/index.ts +9 -8
- package/plugins/generations/content-gen/src/storm.ts +12 -4
- package/plugins/generations/dailynews-gen/package.json +17 -0
- package/plugins/generations/dailynews-gen/plugin.json +7 -0
- package/plugins/generations/dailynews-gen/src/index.ts +111 -0
- package/plugins/generations/dailynews-gen/src/script-builder.ts +121 -0
- package/plugins/generations/dailynews-gen/tsconfig.json +4 -0
- package/plugins/generations/voice-gen/package.json +17 -0
- package/plugins/generations/voice-gen/plugin.json +6 -0
- package/plugins/generations/voice-gen/src/concat.ts +218 -0
- package/plugins/generations/voice-gen/src/index.ts +228 -0
- package/plugins/generations/voice-gen/src/mp3.ts +65 -0
- package/plugins/generations/voice-gen/src/voicebox-client.ts +223 -0
- package/plugins/generations/voice-gen/src/voicescript.ts +365 -0
- package/plugins/generations/voice-gen/tsconfig.json +8 -0
- package/plugins/ingestions/aihot-ingest/package.json +17 -0
- package/plugins/ingestions/aihot-ingest/plugin.json +7 -0
- package/plugins/ingestions/aihot-ingest/src/client.ts +185 -0
- package/plugins/ingestions/aihot-ingest/src/index.ts +137 -0
- package/plugins/ingestions/aihot-ingest/src/mapper.ts +42 -0
- package/plugins/ingestions/aihot-ingest/tsconfig.json +4 -0
- package/plugins/ingestions/karakeep-local/package.json +17 -0
- package/plugins/ingestions/karakeep-local/src/index.ts +31 -26
- package/plugins/ingestions/karakeep-local/tsconfig.json +4 -0
- package/plugins/ingestions/web-search/package.json +4 -1
- package/plugins/ingestions/web-search/src/index.ts +139 -16
- package/plugins/kk/README.md +9 -3
- package/plugins/kk/commands/workflow-run.md +100 -28
- package/plugins/kk/config.example.yaml +34 -0
- package/plugins/kk/scripts/render-md.ts +8 -3
- package/plugins/kk/skills/{judge → content-judge}/SKILL.md +13 -14
- package/plugins/kk/skills/{judge → content-judge}/references/workflow-integration.md +13 -11
- package/plugins/kk/skills/itc-generating/SKILL.md +147 -0
- package/plugins/kk/skills/itc-generating/references/generic-craft.md +80 -0
- package/plugins/kk/skills/itc-generating/references/platform-english.md +72 -0
- package/plugins/kk/skills/itc-generating/references/platform-wechat.md +60 -0
- package/plugins/kk/skills/itc-generating/references/skill-authoring.md +62 -0
- package/plugins/kk/skills/storm-research/SKILL.md +10 -3
- package/plugins/kk/workflows/judge-gated-publish-example.yaml +101 -0
- package/plugins/kk/workflows/kk-daily-ai-voice.yaml +144 -0
- package/plugins/kk/workflows/kk-ingest-generate-publish.yaml +72 -0
- package/plugins/kk/workflows/kk-itc.yaml +285 -0
- package/plugins/kk/workflows/kk-solo-podcast.yaml +374 -0
- package/plugins/kk/workflows/validate-voicescript.ts +226 -0
- package/plugins/publishings/emdash-pub/package.json +17 -0
- package/plugins/publishings/emdash-pub/plugin.json +7 -0
- package/plugins/publishings/emdash-pub/src/index.ts +450 -0
- package/plugins/publishings/emdash-pub/tsconfig.json +4 -0
- package/plugins/publishings/qiita-pub/package.json +2 -1
- package/plugins/publishings/qiita-pub/src/index.ts +9 -9
- package/plugins/publishings/surfdash-pub/package.json +2 -1
- package/plugins/publishings/surfdash-pub/src/index.ts +17 -12
- package/plugins/publishings/zenn-pub/package.json +2 -1
- package/plugins/publishings/zenn-pub/src/index.ts +11 -11
- package/plugins/kk/agents/judge-compliance.md +0 -37
- package/plugins/kk/agents/judge-tech.md +0 -35
- package/plugins/kk/agents/judge-tone.md +0 -37
- /package/plugins/kk/skills/{judge → content-judge}/references/rubrics.md +0 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
---
|
|
2
|
+
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
3
|
+
name: kk-itc
|
|
4
|
+
kind: state-machine
|
|
5
|
+
description: >-
|
|
6
|
+
IT content authoring pipeline using topic/itc-generating, Spur HITL gates, and optional judging
|
|
7
|
+
initialState: prepare
|
|
8
|
+
terminalStates:
|
|
9
|
+
- done
|
|
10
|
+
- failed
|
|
11
|
+
failureStates:
|
|
12
|
+
- failed
|
|
13
|
+
|
|
14
|
+
vars:
|
|
15
|
+
topic: ""
|
|
16
|
+
input_file: ""
|
|
17
|
+
work_dir: ""
|
|
18
|
+
writer: "itc-generating"
|
|
19
|
+
playbook: "generic"
|
|
20
|
+
research: "false"
|
|
21
|
+
judge: "false"
|
|
22
|
+
outline: ""
|
|
23
|
+
force: "false"
|
|
24
|
+
rubric: "tech-accuracy"
|
|
25
|
+
verdictFile: ".spur/run/${vars.__runId}-itc-verdict.json"
|
|
26
|
+
agent: ""
|
|
27
|
+
__runId: "itc"
|
|
28
|
+
__hitlAnswer: ""
|
|
29
|
+
__hitlInput: ""
|
|
30
|
+
|
|
31
|
+
states:
|
|
32
|
+
- id: prepare
|
|
33
|
+
description: "mkdir work_dir, 2-outline, 3-draft; validate XOR topic/input_file; write brief.md"
|
|
34
|
+
onEnter:
|
|
35
|
+
- kind: shell
|
|
36
|
+
options:
|
|
37
|
+
command: >-
|
|
38
|
+
set -e;
|
|
39
|
+
if [ -z "${vars.work_dir}" ]; then
|
|
40
|
+
echo "missing work_dir" >&2; exit 1;
|
|
41
|
+
fi;
|
|
42
|
+
mkdir -p "${vars.work_dir}" "${vars.work_dir}/2-outline" "${vars.work_dir}/3-draft";
|
|
43
|
+
if [ -n "${vars.topic}" ] && [ -n "${vars.input_file}" ]; then
|
|
44
|
+
echo "XOR violation: both topic and input_file set" >&2; exit 1;
|
|
45
|
+
fi;
|
|
46
|
+
if [ -z "${vars.topic}" ] && [ -z "${vars.input_file}" ]; then
|
|
47
|
+
echo "XOR violation: neither topic nor input_file set" >&2; exit 1;
|
|
48
|
+
fi;
|
|
49
|
+
if [ -f "${vars.work_dir}/brief.md" ] && [ "${vars.force}" != "true" ]; then
|
|
50
|
+
echo "initialization target already exists at ${vars.work_dir}/brief.md; pass force=true to replace" >&2; exit 1;
|
|
51
|
+
fi;
|
|
52
|
+
if [ -n "${vars.topic}" ]; then
|
|
53
|
+
printf '%s\n' "${vars.topic}" > "${vars.work_dir}/brief.md";
|
|
54
|
+
else
|
|
55
|
+
if [ ! -f "${vars.input_file}" ]; then
|
|
56
|
+
echo "input file not found: ${vars.input_file}" >&2; exit 1;
|
|
57
|
+
fi;
|
|
58
|
+
cp "${vars.input_file}" "${vars.work_dir}/brief.md";
|
|
59
|
+
fi;
|
|
60
|
+
|
|
61
|
+
- id: research
|
|
62
|
+
description: "Conduct preliminary topic research via storm-research (agent.run)"
|
|
63
|
+
onEnter:
|
|
64
|
+
- kind: agent.run
|
|
65
|
+
options:
|
|
66
|
+
role: scribe
|
|
67
|
+
agent: ${vars.agent}
|
|
68
|
+
input: |
|
|
69
|
+
Load plugins/kk/skills/storm-research/SKILL.md.
|
|
70
|
+
Run storm-research on the topic or input file specified in the brief at ${vars.work_dir}/brief.md.
|
|
71
|
+
|
|
72
|
+
- id: outline-write
|
|
73
|
+
description: "Generate outline options a, b (and optional c) via writer skill (agent.run)"
|
|
74
|
+
onEnter:
|
|
75
|
+
- kind: agent.run
|
|
76
|
+
options:
|
|
77
|
+
role: scribe
|
|
78
|
+
agent: ${vars.agent}
|
|
79
|
+
input: |
|
|
80
|
+
Load plugins/kk/skills/${vars.writer}/SKILL.md.
|
|
81
|
+
Generate outline options (outline-option-a.md, outline-option-b.md, and optionally outline-option-c.md) in ${vars.work_dir}/2-outline/ from the brief at ${vars.work_dir}/brief.md using playbook ${vars.playbook}.
|
|
82
|
+
Do not wait for operator selection; write the option files.
|
|
83
|
+
expectFile: ${vars.work_dir}/2-outline/outline-option-a.md
|
|
84
|
+
- kind: shell
|
|
85
|
+
options:
|
|
86
|
+
command: >-
|
|
87
|
+
test -f "${vars.work_dir}/2-outline/outline-option-b.md" || { echo "missing required outline-option-b.md" >&2; exit 1; }
|
|
88
|
+
|
|
89
|
+
- id: outline-select
|
|
90
|
+
description: "Operator selects preferred outline option"
|
|
91
|
+
pause: true
|
|
92
|
+
onEnter:
|
|
93
|
+
- kind: hitl.select
|
|
94
|
+
options:
|
|
95
|
+
prompt: "Select an approved outline option from ${vars.work_dir}/2-outline/ (outline-option-a.md, outline-option-b.md, or outline-option-c.md):"
|
|
96
|
+
options:
|
|
97
|
+
- a
|
|
98
|
+
- b
|
|
99
|
+
- c
|
|
100
|
+
var: __hitlAnswer
|
|
101
|
+
|
|
102
|
+
- id: outline-commit
|
|
103
|
+
description: "Copy selected outline to outline-approved.md"
|
|
104
|
+
onEnter:
|
|
105
|
+
- kind: shell
|
|
106
|
+
options:
|
|
107
|
+
command: >-
|
|
108
|
+
sel="${vars.outline}";
|
|
109
|
+
if [ -z "$sel" ]; then sel="${vars.__hitlAnswer}"; fi;
|
|
110
|
+
if [ -z "$sel" ]; then sel="a"; fi;
|
|
111
|
+
src="${vars.work_dir}/2-outline/outline-option-$sel.md";
|
|
112
|
+
if [ ! -f "$src" ]; then
|
|
113
|
+
echo "selected outline file not found: $src" >&2; exit 1;
|
|
114
|
+
fi;
|
|
115
|
+
cp "$src" "${vars.work_dir}/2-outline/outline-approved.md";
|
|
116
|
+
|
|
117
|
+
- id: draft-write
|
|
118
|
+
description: "Author full technical draft from approved outline (agent.run)"
|
|
119
|
+
onEnter:
|
|
120
|
+
- kind: agent.run
|
|
121
|
+
options:
|
|
122
|
+
role: scribe
|
|
123
|
+
agent: ${vars.agent}
|
|
124
|
+
input: |
|
|
125
|
+
Load plugins/kk/skills/${vars.writer}/SKILL.md.
|
|
126
|
+
Author the technical draft at ${vars.work_dir}/3-draft/draft-article.md based on approved outline ${vars.work_dir}/2-outline/outline-approved.md and brief ${vars.work_dir}/brief.md using playbook ${vars.playbook}.
|
|
127
|
+
Revision feedback: ${vars.__hitlInput}
|
|
128
|
+
expectFile: ${vars.work_dir}/3-draft/draft-article.md
|
|
129
|
+
|
|
130
|
+
- id: draft-review
|
|
131
|
+
description: "Operator reviews and approves or requests revision on draft-article.md"
|
|
132
|
+
pause: true
|
|
133
|
+
onEnter:
|
|
134
|
+
- kind: hitl.confirm
|
|
135
|
+
options:
|
|
136
|
+
prompt: "Review draft at ${vars.work_dir}/3-draft/draft-article.md. Approve?"
|
|
137
|
+
var: __hitlAnswer
|
|
138
|
+
|
|
139
|
+
- id: draft-revise
|
|
140
|
+
description: "Collect revision feedback from operator (bounded by 3 rounds)"
|
|
141
|
+
pause: true
|
|
142
|
+
onEnter:
|
|
143
|
+
- kind: shell
|
|
144
|
+
options:
|
|
145
|
+
command: >-
|
|
146
|
+
mkdir -p .spur/run;
|
|
147
|
+
run_id="${vars.__runId}";
|
|
148
|
+
if [ -z "$run_id" ]; then run_id="itc"; fi;
|
|
149
|
+
count_file=".spur/run/$run_id-itc-revise-count";
|
|
150
|
+
curr=$(cat "$count_file" 2>/dev/null || echo 0);
|
|
151
|
+
echo $((curr + 1)) > "$count_file";
|
|
152
|
+
- kind: hitl.input
|
|
153
|
+
options:
|
|
154
|
+
prompt: "Provide specific revision feedback for ${vars.work_dir}/3-draft/draft-article.md:"
|
|
155
|
+
var: __hitlInput
|
|
156
|
+
|
|
157
|
+
- id: judge
|
|
158
|
+
description: "Evaluate technical draft with content-judge (agent.run)"
|
|
159
|
+
onEnter:
|
|
160
|
+
- kind: agent.run
|
|
161
|
+
options:
|
|
162
|
+
role: reviewer
|
|
163
|
+
agent: ${vars.agent}
|
|
164
|
+
input: |
|
|
165
|
+
Load plugins/kk/skills/content-judge/SKILL.md and its references/rubrics.md.
|
|
166
|
+
Evaluate the draft at ${vars.work_dir}/3-draft/draft-article.md against the ${vars.rubric} rubric.
|
|
167
|
+
Write the verdict JSON to ${vars.verdictFile} per the contract in SKILL.md.
|
|
168
|
+
Print the verdict path as the last line of stdout.
|
|
169
|
+
expectFile: ${vars.verdictFile}
|
|
170
|
+
|
|
171
|
+
- id: done
|
|
172
|
+
description: "Terminal — IT content draft generated and approved successfully"
|
|
173
|
+
- id: failed
|
|
174
|
+
description: "Terminal — IT content workflow failed or rejected"
|
|
175
|
+
|
|
176
|
+
transitions:
|
|
177
|
+
- from: prepare
|
|
178
|
+
to: research
|
|
179
|
+
description: "Research enabled -> proceed to storm-research"
|
|
180
|
+
guard:
|
|
181
|
+
kind: shell
|
|
182
|
+
options:
|
|
183
|
+
command: 'test "${vars.research}" = "true"'
|
|
184
|
+
|
|
185
|
+
- from: prepare
|
|
186
|
+
to: outline-write
|
|
187
|
+
description: "Research disabled -> proceed directly to outline generation"
|
|
188
|
+
guard:
|
|
189
|
+
kind: shell
|
|
190
|
+
options:
|
|
191
|
+
command: 'test "${vars.research}" != "true"'
|
|
192
|
+
|
|
193
|
+
- from: research
|
|
194
|
+
to: outline-write
|
|
195
|
+
description: "Research completed -> proceed to outline generation"
|
|
196
|
+
guard:
|
|
197
|
+
kind: always
|
|
198
|
+
|
|
199
|
+
- from: outline-write
|
|
200
|
+
to: outline-select
|
|
201
|
+
description: "Outline not pre-selected -> pause for operator selection"
|
|
202
|
+
guard:
|
|
203
|
+
kind: shell
|
|
204
|
+
options:
|
|
205
|
+
command: 'test -z "${vars.outline}"'
|
|
206
|
+
|
|
207
|
+
- from: outline-write
|
|
208
|
+
to: outline-commit
|
|
209
|
+
description: "Outline pre-selected -> skip interactive selection"
|
|
210
|
+
guard:
|
|
211
|
+
kind: shell
|
|
212
|
+
options:
|
|
213
|
+
command: 'test -n "${vars.outline}"'
|
|
214
|
+
|
|
215
|
+
- from: outline-select
|
|
216
|
+
to: outline-commit
|
|
217
|
+
description: "Outline selected -> commit approved outline"
|
|
218
|
+
guard:
|
|
219
|
+
kind: always
|
|
220
|
+
|
|
221
|
+
- from: outline-commit
|
|
222
|
+
to: draft-write
|
|
223
|
+
description: "Approved outline committed -> write draft"
|
|
224
|
+
guard:
|
|
225
|
+
kind: always
|
|
226
|
+
|
|
227
|
+
- from: draft-write
|
|
228
|
+
to: draft-review
|
|
229
|
+
description: "Draft written -> pause for operator review"
|
|
230
|
+
guard:
|
|
231
|
+
kind: always
|
|
232
|
+
|
|
233
|
+
- from: draft-review
|
|
234
|
+
to: judge
|
|
235
|
+
description: "Draft approved and judge enabled -> evaluate quality"
|
|
236
|
+
guard:
|
|
237
|
+
kind: shell
|
|
238
|
+
options:
|
|
239
|
+
command: '{ test -z "${vars.__hitlAnswer}" || test "${vars.__hitlAnswer}" = "yes"; } && test "${vars.judge}" = "true"'
|
|
240
|
+
|
|
241
|
+
- from: draft-review
|
|
242
|
+
to: done
|
|
243
|
+
description: "Draft approved and judge disabled -> complete workflow"
|
|
244
|
+
guard:
|
|
245
|
+
kind: shell
|
|
246
|
+
options:
|
|
247
|
+
command: '{ test -z "${vars.__hitlAnswer}" || test "${vars.__hitlAnswer}" = "yes"; } && test "${vars.judge}" != "true"'
|
|
248
|
+
|
|
249
|
+
- from: draft-review
|
|
250
|
+
to: draft-revise
|
|
251
|
+
description: "Draft rejected with revision budget remaining -> collect feedback"
|
|
252
|
+
guard:
|
|
253
|
+
kind: shell
|
|
254
|
+
options:
|
|
255
|
+
command: 'test "${vars.__hitlAnswer}" = "no" && { run_id="${vars.__runId}"; if [ -z "$run_id" ]; then run_id="itc"; fi; test "$(cat .spur/run/$run_id-itc-revise-count 2>/dev/null || echo 0)" -lt 3; }'
|
|
256
|
+
|
|
257
|
+
- from: draft-review
|
|
258
|
+
to: failed
|
|
259
|
+
description: "Draft rejected with revision budget exhausted -> fail"
|
|
260
|
+
guard:
|
|
261
|
+
kind: shell
|
|
262
|
+
options:
|
|
263
|
+
command: 'test "${vars.__hitlAnswer}" = "no" && { run_id="${vars.__runId}"; if [ -z "$run_id" ]; then run_id="itc"; fi; test "$(cat .spur/run/$run_id-itc-revise-count 2>/dev/null || echo 0)" -ge 3; }'
|
|
264
|
+
|
|
265
|
+
- from: draft-revise
|
|
266
|
+
to: draft-write
|
|
267
|
+
description: "Feedback collected -> rewrite draft"
|
|
268
|
+
guard:
|
|
269
|
+
kind: always
|
|
270
|
+
|
|
271
|
+
- from: judge
|
|
272
|
+
to: done
|
|
273
|
+
description: "Judge verdict is PASS (or empty in dry-run) -> complete workflow"
|
|
274
|
+
guard:
|
|
275
|
+
kind: shell
|
|
276
|
+
options:
|
|
277
|
+
command: 'v="$(jq -r .verdict ${vars.verdictFile} 2>/dev/null)"; test -z "$v" -o "$v" = PASS'
|
|
278
|
+
|
|
279
|
+
- from: judge
|
|
280
|
+
to: failed
|
|
281
|
+
description: "Judge verdict is not PASS -> fail"
|
|
282
|
+
guard:
|
|
283
|
+
kind: shell
|
|
284
|
+
options:
|
|
285
|
+
command: 'v="$(jq -r .verdict ${vars.verdictFile} 2>/dev/null)"; test -n "$v" -a "$v" != PASS'
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
---
|
|
2
|
+
"$schema": "@gobing-ai/spur/schemas/state-machine-workflow.schema.json"
|
|
3
|
+
name: kk-solo-podcast
|
|
4
|
+
kind: state-machine
|
|
5
|
+
description: >-
|
|
6
|
+
Solo podcast authoring pipeline using VoiceScript authoring, Spur HITL gates, and voice-gen audio generation
|
|
7
|
+
initialState: prepare
|
|
8
|
+
terminalStates:
|
|
9
|
+
- done
|
|
10
|
+
- failed
|
|
11
|
+
failureStates:
|
|
12
|
+
- failed
|
|
13
|
+
|
|
14
|
+
vars:
|
|
15
|
+
topic: ""
|
|
16
|
+
input_file: ""
|
|
17
|
+
work_dir: ""
|
|
18
|
+
force: "false"
|
|
19
|
+
outline: ""
|
|
20
|
+
script_approved: ""
|
|
21
|
+
target_duration_min: "8"
|
|
22
|
+
words_per_min: "150"
|
|
23
|
+
cjk_chars_per_min: "220"
|
|
24
|
+
voice_profile: ""
|
|
25
|
+
language: "en"
|
|
26
|
+
plugins_path: ""
|
|
27
|
+
validate_script: ""
|
|
28
|
+
agent: ""
|
|
29
|
+
__runId: "solo"
|
|
30
|
+
__hitlAnswer: ""
|
|
31
|
+
__hitlInput: ""
|
|
32
|
+
|
|
33
|
+
states:
|
|
34
|
+
- id: prepare
|
|
35
|
+
description: "mkdir work_dir and stage subdirectories; validate XOR topic/input_file and voice_profile; write brief.md"
|
|
36
|
+
onEnter:
|
|
37
|
+
- kind: shell
|
|
38
|
+
options:
|
|
39
|
+
command: >-
|
|
40
|
+
set -e;
|
|
41
|
+
if [ -z "${vars.work_dir}" ]; then
|
|
42
|
+
echo "missing work_dir" >&2; exit 1;
|
|
43
|
+
fi;
|
|
44
|
+
mkdir -p "${vars.work_dir}" "${vars.work_dir}/1-briefing" "${vars.work_dir}/2-outline" "${vars.work_dir}/3-script" "${vars.work_dir}/4-audio";
|
|
45
|
+
if [ -n "${vars.topic}" ] && [ -n "${vars.input_file}" ]; then
|
|
46
|
+
echo "XOR violation: both topic and input_file set" >&2; exit 1;
|
|
47
|
+
fi;
|
|
48
|
+
if [ -z "${vars.topic}" ] && [ -z "${vars.input_file}" ]; then
|
|
49
|
+
echo "XOR violation: neither topic nor input_file set" >&2; exit 1;
|
|
50
|
+
fi;
|
|
51
|
+
if [ -z "${vars.voice_profile}" ] && [ -z "$VOICEBOX_DEFAULT_PROFILE" ]; then
|
|
52
|
+
echo "missing voice_profile (set vars.voice_profile or VOICEBOX_DEFAULT_PROFILE)" >&2; exit 1;
|
|
53
|
+
fi;
|
|
54
|
+
if [ -f "${vars.work_dir}/brief.md" ] && [ "${vars.force}" != "true" ]; then
|
|
55
|
+
echo "initialization target already exists at ${vars.work_dir}/brief.md; pass force=true to replace" >&2; exit 1;
|
|
56
|
+
fi;
|
|
57
|
+
if [ -n "${vars.topic}" ]; then
|
|
58
|
+
printf '%s\n' "${vars.topic}" > "${vars.work_dir}/brief.md";
|
|
59
|
+
else
|
|
60
|
+
if [ ! -f "${vars.input_file}" ]; then
|
|
61
|
+
echo "input file not found: ${vars.input_file}" >&2; exit 1;
|
|
62
|
+
fi;
|
|
63
|
+
cp "${vars.input_file}" "${vars.work_dir}/brief.md";
|
|
64
|
+
fi;
|
|
65
|
+
|
|
66
|
+
- id: understand
|
|
67
|
+
description: "Analyze brief and generate podcast briefing (agent.run)"
|
|
68
|
+
onEnter:
|
|
69
|
+
- kind: agent.run
|
|
70
|
+
options:
|
|
71
|
+
role: scribe
|
|
72
|
+
agent: ${vars.agent}
|
|
73
|
+
input: |
|
|
74
|
+
Read the source brief at ${vars.work_dir}/brief.md.
|
|
75
|
+
Write a concise podcast briefing at ${vars.work_dir}/1-briefing/briefing.md.
|
|
76
|
+
Identify core thesis, key listener takeaways, audience profile, target spoken duration (${vars.target_duration_min} minutes), tone, and what material from the brief to emphasize or cut.
|
|
77
|
+
expectFile: ${vars.work_dir}/1-briefing/briefing.md
|
|
78
|
+
|
|
79
|
+
- id: outline-write
|
|
80
|
+
description: "Generate spoken podcast outline options (agent.run)"
|
|
81
|
+
onEnter:
|
|
82
|
+
- kind: agent.run
|
|
83
|
+
options:
|
|
84
|
+
role: scribe
|
|
85
|
+
agent: ${vars.agent}
|
|
86
|
+
input: |
|
|
87
|
+
Read the briefing at ${vars.work_dir}/1-briefing/briefing.md and brief at ${vars.work_dir}/brief.md.
|
|
88
|
+
Generate spoken podcast outline options in ${vars.work_dir}/2-outline/:
|
|
89
|
+
- outline-option-a.md (e.g. narrative / storytelling arc)
|
|
90
|
+
- outline-option-b.md (e.g. analytical / explainer breakdown)
|
|
91
|
+
- optional outline-option-c.md
|
|
92
|
+
Target total episode spoken duration is ${vars.target_duration_min} minutes.
|
|
93
|
+
Do not wait for operator selection; write the option files.
|
|
94
|
+
expectFile: ${vars.work_dir}/2-outline/outline-option-a.md
|
|
95
|
+
- kind: shell
|
|
96
|
+
options:
|
|
97
|
+
command: >-
|
|
98
|
+
test -f "${vars.work_dir}/2-outline/outline-option-b.md" || { echo "missing required outline-option-b.md" >&2; exit 1; }
|
|
99
|
+
|
|
100
|
+
- id: outline-select
|
|
101
|
+
description: "Operator selects preferred outline option"
|
|
102
|
+
pause: true
|
|
103
|
+
onEnter:
|
|
104
|
+
- kind: hitl.select
|
|
105
|
+
options:
|
|
106
|
+
prompt: "Select an approved outline option from ${vars.work_dir}/2-outline/ (outline-option-a.md, outline-option-b.md, or outline-option-c.md):"
|
|
107
|
+
options:
|
|
108
|
+
- a
|
|
109
|
+
- b
|
|
110
|
+
- c
|
|
111
|
+
var: __hitlAnswer
|
|
112
|
+
|
|
113
|
+
- id: outline-commit
|
|
114
|
+
description: "Copy selected outline to outline-approved.md"
|
|
115
|
+
onEnter:
|
|
116
|
+
- kind: shell
|
|
117
|
+
options:
|
|
118
|
+
command: >-
|
|
119
|
+
sel="${vars.outline}";
|
|
120
|
+
if [ -z "$sel" ]; then sel="${vars.__hitlAnswer}"; fi;
|
|
121
|
+
if [ -z "$sel" ]; then sel="a"; fi;
|
|
122
|
+
src="${vars.work_dir}/2-outline/outline-option-$sel.md";
|
|
123
|
+
if [ ! -f "$src" ]; then
|
|
124
|
+
echo "selected outline file not found: $src" >&2; exit 1;
|
|
125
|
+
fi;
|
|
126
|
+
cp "$src" "${vars.work_dir}/2-outline/outline-approved.md";
|
|
127
|
+
|
|
128
|
+
- id: script-write
|
|
129
|
+
description: "Author complete VoiceScript YAML from approved outline (agent.run)"
|
|
130
|
+
onEnter:
|
|
131
|
+
- kind: agent.run
|
|
132
|
+
options:
|
|
133
|
+
role: scribe
|
|
134
|
+
agent: ${vars.agent}
|
|
135
|
+
input: |
|
|
136
|
+
Read the approved outline at ${vars.work_dir}/2-outline/outline-approved.md and briefing at ${vars.work_dir}/1-briefing/briefing.md.
|
|
137
|
+
Target duration is ${vars.target_duration_min} minutes.
|
|
138
|
+
Spoken budget: if language (${vars.language}) is zh, ja, or ko, target characters = ${vars.target_duration_min} * ${vars.cjk_chars_per_min}; otherwise target words = ${vars.target_duration_min} * ${vars.words_per_min}. Land inside ±20%, spoken prose, one speaker.
|
|
139
|
+
Author the complete solo-podcast VoiceScript YAML at ${vars.work_dir}/3-script/voicescript.yaml.
|
|
140
|
+
Emit ONLY valid YAML (no markdown fences, no repo/docs paths). Contract:
|
|
141
|
+
title: string
|
|
142
|
+
language: "${vars.language}"
|
|
143
|
+
default_profile: "${vars.voice_profile}"
|
|
144
|
+
default_engine: qwen|qwen_custom_voice|luxtts|chatterbox|chatterbox_turbo|tada|kokoro # optional
|
|
145
|
+
segments:
|
|
146
|
+
- text: "spoken sentence" # required, 1..50000 characters
|
|
147
|
+
gap_ms: 400 # optional, >= 0, silence before next clip
|
|
148
|
+
instruct: "warm" # optional, <= 500 characters
|
|
149
|
+
Do not invent fields. Revision feedback: ${vars.__hitlInput}
|
|
150
|
+
expectFile: ${vars.work_dir}/3-script/voicescript.yaml
|
|
151
|
+
|
|
152
|
+
- id: script-validate
|
|
153
|
+
description: "Validate authored VoiceScript YAML structure and constraints"
|
|
154
|
+
onEnter:
|
|
155
|
+
- kind: shell
|
|
156
|
+
options:
|
|
157
|
+
command: >-
|
|
158
|
+
set -e;
|
|
159
|
+
vs="${vars.validate_script}";
|
|
160
|
+
if [ -z "$vs" ]; then
|
|
161
|
+
if [ -n "$KK_WORKFLOWS_DIR" ]; then vs="$KK_WORKFLOWS_DIR/validate-voicescript.ts";
|
|
162
|
+
else vs="$HOME/.config/kk/workflows/validate-voicescript.ts"; fi;
|
|
163
|
+
fi;
|
|
164
|
+
if [ ! -f "$vs" ]; then
|
|
165
|
+
echo "validate_script not found: $vs (install with /workflow-run kk-solo-podcast into $HOME/.config/kk/workflows)" >&2;
|
|
166
|
+
exit 1;
|
|
167
|
+
fi;
|
|
168
|
+
bun "$vs" --in "${vars.work_dir}/3-script/voicescript.yaml"
|
|
169
|
+
|
|
170
|
+
- id: script-review
|
|
171
|
+
description: "Operator reviews and approves VoiceScript before live TTS generation"
|
|
172
|
+
pause: true
|
|
173
|
+
onEnter:
|
|
174
|
+
- kind: hitl.confirm
|
|
175
|
+
options:
|
|
176
|
+
prompt: "Review VoiceScript at ${vars.work_dir}/3-script/voicescript.yaml. Approve for voice generation?"
|
|
177
|
+
var: __hitlAnswer
|
|
178
|
+
|
|
179
|
+
- id: script-revise
|
|
180
|
+
description: "Collect revision feedback from operator (bounded by 3 rounds)"
|
|
181
|
+
pause: true
|
|
182
|
+
onEnter:
|
|
183
|
+
- kind: shell
|
|
184
|
+
options:
|
|
185
|
+
command: >-
|
|
186
|
+
mkdir -p .spur/run;
|
|
187
|
+
run_id="${vars.__runId}";
|
|
188
|
+
if [ -z "$run_id" ]; then run_id="solo"; fi;
|
|
189
|
+
count_file=".spur/run/$run_id-solo-revise-count";
|
|
190
|
+
curr=$(cat "$count_file" 2>/dev/null || echo 0);
|
|
191
|
+
echo $((curr + 1)) > "$count_file";
|
|
192
|
+
- kind: hitl.input
|
|
193
|
+
options:
|
|
194
|
+
prompt: "Provide specific revision feedback for ${vars.work_dir}/3-script/voicescript.yaml:"
|
|
195
|
+
var: __hitlInput
|
|
196
|
+
|
|
197
|
+
- id: wrap-docs
|
|
198
|
+
description: "Wrap VoiceScript into Doc[] JSON for voice-gen generator plugin"
|
|
199
|
+
onEnter:
|
|
200
|
+
- kind: shell
|
|
201
|
+
options:
|
|
202
|
+
command: >-
|
|
203
|
+
bun -e '
|
|
204
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
205
|
+
import { createHash } from "node:crypto";
|
|
206
|
+
import { resolve } from "node:path";
|
|
207
|
+
const scriptPath = resolve(process.argv[1]);
|
|
208
|
+
const outPath = resolve(process.argv[2]);
|
|
209
|
+
const voiceProfile = process.argv[3] || process.env.VOICEBOX_DEFAULT_PROFILE || "";
|
|
210
|
+
const body = readFileSync(scriptPath, "utf-8");
|
|
211
|
+
const sourceUri = "file://" + scriptPath;
|
|
212
|
+
const id = createHash("sha256").update(sourceUri).digest("hex").slice(0, 16);
|
|
213
|
+
const doc = {
|
|
214
|
+
id,
|
|
215
|
+
title: "Podcast VoiceScript",
|
|
216
|
+
sourceUri,
|
|
217
|
+
body,
|
|
218
|
+
mediaType: "application/yaml",
|
|
219
|
+
metadata: voiceProfile ? { voiceProfile } : {},
|
|
220
|
+
};
|
|
221
|
+
writeFileSync(outPath, JSON.stringify([doc], null, 2));
|
|
222
|
+
' "${vars.work_dir}/3-script/voicescript.yaml" "${vars.work_dir}/4-audio/docs.json" "${vars.voice_profile}"
|
|
223
|
+
|
|
224
|
+
- id: generate
|
|
225
|
+
description: "Invoke voice-gen generator plugin to generate audio"
|
|
226
|
+
onEnter:
|
|
227
|
+
- kind: shell
|
|
228
|
+
options:
|
|
229
|
+
command: >-
|
|
230
|
+
set -e;
|
|
231
|
+
extra="";
|
|
232
|
+
if [ -n "${vars.plugins_path}" ]; then extra="--plugins-path ${vars.plugins_path}"; fi;
|
|
233
|
+
kk executor run voice-gen --in "${vars.work_dir}/4-audio/docs.json" --out "${vars.work_dir}/4-audio/content.json" $extra
|
|
234
|
+
|
|
235
|
+
- id: duration-check
|
|
236
|
+
description: "Validate generated audio duration against target budget"
|
|
237
|
+
|
|
238
|
+
- id: done
|
|
239
|
+
description: "Terminal — Solo podcast VoiceScript and audio generated successfully"
|
|
240
|
+
- id: failed
|
|
241
|
+
description: "Terminal — Solo podcast workflow failed or rejected"
|
|
242
|
+
|
|
243
|
+
transitions:
|
|
244
|
+
- from: prepare
|
|
245
|
+
to: understand
|
|
246
|
+
description: "Prepare completed -> begin briefing analysis"
|
|
247
|
+
guard:
|
|
248
|
+
kind: always
|
|
249
|
+
|
|
250
|
+
- from: understand
|
|
251
|
+
to: outline-write
|
|
252
|
+
description: "Briefing completed -> generate outline options"
|
|
253
|
+
guard:
|
|
254
|
+
kind: always
|
|
255
|
+
|
|
256
|
+
- from: outline-write
|
|
257
|
+
to: outline-select
|
|
258
|
+
description: "Outline not pre-selected -> pause for operator selection"
|
|
259
|
+
guard:
|
|
260
|
+
kind: shell
|
|
261
|
+
options:
|
|
262
|
+
command: 'test -z "${vars.outline}"'
|
|
263
|
+
|
|
264
|
+
- from: outline-write
|
|
265
|
+
to: outline-commit
|
|
266
|
+
description: "Outline pre-selected -> skip interactive selection"
|
|
267
|
+
guard:
|
|
268
|
+
kind: shell
|
|
269
|
+
options:
|
|
270
|
+
command: 'test -n "${vars.outline}"'
|
|
271
|
+
|
|
272
|
+
- from: outline-select
|
|
273
|
+
to: outline-commit
|
|
274
|
+
description: "Outline selected -> commit approved outline"
|
|
275
|
+
guard:
|
|
276
|
+
kind: always
|
|
277
|
+
|
|
278
|
+
- from: outline-commit
|
|
279
|
+
to: script-write
|
|
280
|
+
description: "Approved outline committed -> write VoiceScript"
|
|
281
|
+
guard:
|
|
282
|
+
kind: always
|
|
283
|
+
|
|
284
|
+
- from: script-write
|
|
285
|
+
to: script-validate
|
|
286
|
+
description: "VoiceScript written -> validate structure and caps"
|
|
287
|
+
guard:
|
|
288
|
+
kind: always
|
|
289
|
+
|
|
290
|
+
- from: script-validate
|
|
291
|
+
to: wrap-docs
|
|
292
|
+
description: "script_approved pre-set -> skip script review (CI/dry-run)"
|
|
293
|
+
guard:
|
|
294
|
+
kind: shell
|
|
295
|
+
options:
|
|
296
|
+
command: 'test "${vars.script_approved}" = "true"'
|
|
297
|
+
- from: script-validate
|
|
298
|
+
to: script-review
|
|
299
|
+
description: "VoiceScript validation passed -> pause for operator review"
|
|
300
|
+
guard:
|
|
301
|
+
kind: always
|
|
302
|
+
|
|
303
|
+
- from: script-review
|
|
304
|
+
to: wrap-docs
|
|
305
|
+
description: "VoiceScript approved -> wrap into Doc[] for generation"
|
|
306
|
+
guard:
|
|
307
|
+
kind: shell
|
|
308
|
+
options:
|
|
309
|
+
command: '{ test -z "${vars.__hitlAnswer}" || test "${vars.__hitlAnswer}" = "yes"; }'
|
|
310
|
+
|
|
311
|
+
- from: script-review
|
|
312
|
+
to: script-revise
|
|
313
|
+
description: "VoiceScript rejected with revision budget remaining -> collect feedback"
|
|
314
|
+
guard:
|
|
315
|
+
kind: shell
|
|
316
|
+
options:
|
|
317
|
+
command: 'test "${vars.__hitlAnswer}" = "no" && { run_id="${vars.__runId}"; if [ -z "$run_id" ]; then run_id="solo"; fi; test "$(cat .spur/run/$run_id-solo-revise-count 2>/dev/null || echo 0)" -lt 3; }'
|
|
318
|
+
|
|
319
|
+
- from: script-review
|
|
320
|
+
to: failed
|
|
321
|
+
description: "VoiceScript rejected with revision budget exhausted -> fail"
|
|
322
|
+
guard:
|
|
323
|
+
kind: shell
|
|
324
|
+
options:
|
|
325
|
+
command: 'test "${vars.__hitlAnswer}" = "no" && { run_id="${vars.__runId}"; if [ -z "$run_id" ]; then run_id="solo"; fi; test "$(cat .spur/run/$run_id-solo-revise-count 2>/dev/null || echo 0)" -ge 3; }'
|
|
326
|
+
|
|
327
|
+
- from: script-revise
|
|
328
|
+
to: script-write
|
|
329
|
+
description: "Feedback collected -> rewrite VoiceScript"
|
|
330
|
+
guard:
|
|
331
|
+
kind: always
|
|
332
|
+
|
|
333
|
+
- from: wrap-docs
|
|
334
|
+
to: generate
|
|
335
|
+
description: "Doc[] wrapped -> invoke voice-gen"
|
|
336
|
+
guard:
|
|
337
|
+
kind: always
|
|
338
|
+
|
|
339
|
+
- from: generate
|
|
340
|
+
to: duration-check
|
|
341
|
+
description: "Voice generation finished -> verify duration"
|
|
342
|
+
guard:
|
|
343
|
+
kind: always
|
|
344
|
+
|
|
345
|
+
- from: duration-check
|
|
346
|
+
to: done
|
|
347
|
+
description: "Audio duration within [0.5x, 2.0x] band or missing content.json in dry-run -> complete"
|
|
348
|
+
guard:
|
|
349
|
+
kind: shell
|
|
350
|
+
options:
|
|
351
|
+
command: >-
|
|
352
|
+
test ! -f "${vars.work_dir}/4-audio/content.json" || {
|
|
353
|
+
dur=$(jq -r '.metadata.duration // empty' "${vars.work_dir}/4-audio/content.json" 2>/dev/null);
|
|
354
|
+
test -n "$dur" && target_s=$((${vars.target_duration_min} * 60)) && min_s=$(awk -v t="$target_s" 'BEGIN {print t * 0.5}') && max_s=$(awk -v t="$target_s" 'BEGIN {print t * 2.0}') && awk -v d="$dur" -v min="$min_s" -v max="$max_s" 'BEGIN { if (d >= min && d <= max) exit 0; else exit 1 }';
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
- from: duration-check
|
|
358
|
+
to: failed
|
|
359
|
+
description: "Audio duration missing or outside [0.5x, 2.0x] target range -> fail"
|
|
360
|
+
guard:
|
|
361
|
+
kind: shell
|
|
362
|
+
options:
|
|
363
|
+
command: >-
|
|
364
|
+
test -f "${vars.work_dir}/4-audio/content.json" && {
|
|
365
|
+
dur=$(jq -r '.metadata.duration // empty' "${vars.work_dir}/4-audio/content.json" 2>/dev/null);
|
|
366
|
+
if [ -z "$dur" ]; then
|
|
367
|
+
echo "duration-check failed: metadata.duration missing or not a number" >&2;
|
|
368
|
+
exit 0;
|
|
369
|
+
fi;
|
|
370
|
+
target_s=$((${vars.target_duration_min} * 60));
|
|
371
|
+
min_s=$(awk -v t="$target_s" 'BEGIN {print t * 0.5}');
|
|
372
|
+
max_s=$(awk -v t="$target_s" 'BEGIN {print t * 2.0}');
|
|
373
|
+
awk -v d="$dur" -v min="$min_s" -v max="$max_s" -v ts="$target_s" 'BEGIN { if (d < min || d > max) { printf "duration-check failed: duration %ss outside 0.5x–2x of %ss\n", d, ts > "/dev/stderr"; exit 0 } else exit 1 }';
|
|
374
|
+
}
|