@united-workforce/cli 0.4.0 → 0.5.0

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 (193) hide show
  1. package/README.md +30 -3
  2. package/dist/.build-fingerprint +1 -0
  3. package/dist/__tests__/adapter-json-roundtrip.test.js +16 -6
  4. package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -1
  5. package/dist/__tests__/concurrency.test.d.ts +2 -0
  6. package/dist/__tests__/concurrency.test.d.ts.map +1 -0
  7. package/dist/__tests__/concurrency.test.js +196 -0
  8. package/dist/__tests__/concurrency.test.js.map +1 -0
  9. package/dist/__tests__/e2e-mock-agent.test.js +23 -7
  10. package/dist/__tests__/e2e-mock-agent.test.js.map +1 -1
  11. package/dist/__tests__/format-text-default.test.d.ts +2 -0
  12. package/dist/__tests__/format-text-default.test.d.ts.map +1 -0
  13. package/dist/__tests__/format-text-default.test.js +43 -0
  14. package/dist/__tests__/format-text-default.test.js.map +1 -0
  15. package/dist/__tests__/format-text-registry.test.d.ts +2 -0
  16. package/dist/__tests__/format-text-registry.test.d.ts.map +1 -0
  17. package/dist/__tests__/format-text-registry.test.js +158 -0
  18. package/dist/__tests__/format-text-registry.test.js.map +1 -0
  19. package/dist/__tests__/log-text-renderer.test.d.ts +2 -0
  20. package/dist/__tests__/log-text-renderer.test.d.ts.map +1 -0
  21. package/dist/__tests__/log-text-renderer.test.js +265 -0
  22. package/dist/__tests__/log-text-renderer.test.js.map +1 -0
  23. package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts +2 -0
  24. package/dist/__tests__/output-mapper-thread-list-startedat.test.d.ts.map +1 -0
  25. package/dist/__tests__/output-mapper-thread-list-startedat.test.js +102 -0
  26. package/dist/__tests__/output-mapper-thread-list-startedat.test.js.map +1 -0
  27. package/dist/__tests__/output-mapper-workflow-add.test.d.ts +2 -0
  28. package/dist/__tests__/output-mapper-workflow-add.test.d.ts.map +1 -0
  29. package/dist/__tests__/output-mapper-workflow-add.test.js +22 -0
  30. package/dist/__tests__/output-mapper-workflow-add.test.js.map +1 -0
  31. package/dist/__tests__/pid-recycling.test.js +9 -7
  32. package/dist/__tests__/pid-recycling.test.js.map +1 -1
  33. package/dist/__tests__/prompt.test.js +46 -4
  34. package/dist/__tests__/prompt.test.js.map +1 -1
  35. package/dist/__tests__/resolve-head-hash.test.js +8 -0
  36. package/dist/__tests__/resolve-head-hash.test.js.map +1 -1
  37. package/dist/__tests__/solve-issue-tea-worktree.test.js +3 -1
  38. package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -1
  39. package/dist/__tests__/step-ask.test.js +9 -1
  40. package/dist/__tests__/step-ask.test.js.map +1 -1
  41. package/dist/__tests__/store-unified-threads.test.js +19 -17
  42. package/dist/__tests__/store-unified-threads.test.js.map +1 -1
  43. package/dist/__tests__/thread-cancel-status.test.js +19 -13
  44. package/dist/__tests__/thread-cancel-status.test.js.map +1 -1
  45. package/dist/__tests__/thread-cancel-text-renderer.test.d.ts +2 -0
  46. package/dist/__tests__/thread-cancel-text-renderer.test.d.ts.map +1 -0
  47. package/dist/__tests__/thread-cancel-text-renderer.test.js +110 -0
  48. package/dist/__tests__/thread-cancel-text-renderer.test.js.map +1 -0
  49. package/dist/__tests__/thread-list-filters.test.js +10 -8
  50. package/dist/__tests__/thread-list-filters.test.js.map +1 -1
  51. package/dist/__tests__/thread-list-template-ms-date.test.d.ts +2 -0
  52. package/dist/__tests__/thread-list-template-ms-date.test.d.ts.map +1 -0
  53. package/dist/__tests__/thread-list-template-ms-date.test.js +102 -0
  54. package/dist/__tests__/thread-list-template-ms-date.test.js.map +1 -0
  55. package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts +2 -0
  56. package/dist/__tests__/thread-list-workflow-corrupt.test.d.ts.map +1 -0
  57. package/dist/__tests__/thread-list-workflow-corrupt.test.js +157 -0
  58. package/dist/__tests__/thread-list-workflow-corrupt.test.js.map +1 -0
  59. package/dist/__tests__/thread-poke.test.js +11 -1
  60. package/dist/__tests__/thread-poke.test.js.map +1 -1
  61. package/dist/__tests__/thread-read-xml-tags.test.js +10 -9
  62. package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -1
  63. package/dist/__tests__/thread-resume.test.js +11 -1
  64. package/dist/__tests__/thread-resume.test.js.map +1 -1
  65. package/dist/__tests__/thread-start-cwd-cli.test.js +15 -3
  66. package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -1
  67. package/dist/__tests__/thread-stop-text-renderer.test.d.ts +2 -0
  68. package/dist/__tests__/thread-stop-text-renderer.test.d.ts.map +1 -0
  69. package/dist/__tests__/thread-stop-text-renderer.test.js +148 -0
  70. package/dist/__tests__/thread-stop-text-renderer.test.js.map +1 -0
  71. package/dist/__tests__/thread-suspend-step.test.js +5 -2
  72. package/dist/__tests__/thread-suspend-step.test.js.map +1 -1
  73. package/dist/__tests__/thread-test-helpers.d.ts +7 -0
  74. package/dist/__tests__/thread-test-helpers.d.ts.map +1 -1
  75. package/dist/__tests__/thread-test-helpers.js +13 -0
  76. package/dist/__tests__/thread-test-helpers.js.map +1 -1
  77. package/dist/__tests__/thread.test.js +11 -9
  78. package/dist/__tests__/thread.test.js.map +1 -1
  79. package/dist/__tests__/validate-semantic.test.js +56 -2
  80. package/dist/__tests__/validate-semantic.test.js.map +1 -1
  81. package/dist/__tests__/workflow-list-recursive.test.js +10 -7
  82. package/dist/__tests__/workflow-list-recursive.test.js.map +1 -1
  83. package/dist/__tests__/workflow-resolution.test.js +10 -7
  84. package/dist/__tests__/workflow-resolution.test.js.map +1 -1
  85. package/dist/__tests__/workflow-show-resolution.test.js +10 -7
  86. package/dist/__tests__/workflow-show-resolution.test.js.map +1 -1
  87. package/dist/__tests__/workflow-validate.test.js +75 -55
  88. package/dist/__tests__/workflow-validate.test.js.map +1 -1
  89. package/dist/__tests__/write-envelope.test.d.ts +2 -0
  90. package/dist/__tests__/write-envelope.test.d.ts.map +1 -0
  91. package/dist/__tests__/write-envelope.test.js +201 -0
  92. package/dist/__tests__/write-envelope.test.js.map +1 -0
  93. package/dist/cli.js +58 -35
  94. package/dist/cli.js.map +1 -1
  95. package/dist/commands/config.d.ts.map +1 -1
  96. package/dist/commands/config.js +12 -0
  97. package/dist/commands/config.js.map +1 -1
  98. package/dist/commands/prompt.d.ts.map +1 -1
  99. package/dist/commands/prompt.js +42 -29
  100. package/dist/commands/prompt.js.map +1 -1
  101. package/dist/commands/setup.d.ts +9 -4
  102. package/dist/commands/setup.d.ts.map +1 -1
  103. package/dist/commands/setup.js +51 -7
  104. package/dist/commands/setup.js.map +1 -1
  105. package/dist/commands/thread.d.ts.map +1 -1
  106. package/dist/commands/thread.js +44 -2
  107. package/dist/commands/thread.js.map +1 -1
  108. package/dist/commands/workflow.d.ts +1 -1
  109. package/dist/commands/workflow.d.ts.map +1 -1
  110. package/dist/commands/workflow.js +2 -6
  111. package/dist/commands/workflow.js.map +1 -1
  112. package/dist/concurrency/concurrency.d.ts +34 -0
  113. package/dist/concurrency/concurrency.d.ts.map +1 -0
  114. package/dist/concurrency/concurrency.js +216 -0
  115. package/dist/concurrency/concurrency.js.map +1 -0
  116. package/dist/concurrency/index.d.ts +3 -0
  117. package/dist/concurrency/index.d.ts.map +1 -0
  118. package/dist/concurrency/index.js +2 -0
  119. package/dist/concurrency/index.js.map +1 -0
  120. package/dist/concurrency/types.d.ts +19 -0
  121. package/dist/concurrency/types.d.ts.map +1 -0
  122. package/dist/concurrency/types.js +2 -0
  123. package/dist/concurrency/types.js.map +1 -0
  124. package/dist/format.d.ts +69 -2
  125. package/dist/format.d.ts.map +1 -1
  126. package/dist/format.js +198 -1
  127. package/dist/format.js.map +1 -1
  128. package/dist/output-mappers.d.ts +122 -0
  129. package/dist/output-mappers.d.ts.map +1 -0
  130. package/dist/output-mappers.js +134 -0
  131. package/dist/output-mappers.js.map +1 -0
  132. package/dist/schemas.d.ts +4 -1
  133. package/dist/schemas.d.ts.map +1 -1
  134. package/dist/schemas.js +31 -4
  135. package/dist/schemas.js.map +1 -1
  136. package/dist/text-renderers.d.ts +30 -0
  137. package/dist/text-renderers.d.ts.map +1 -0
  138. package/dist/text-renderers.js +251 -0
  139. package/dist/text-renderers.js.map +1 -0
  140. package/dist/validate-semantic.d.ts.map +1 -1
  141. package/dist/validate-semantic.js +28 -11
  142. package/dist/validate-semantic.js.map +1 -1
  143. package/examples/brainstorm.yaml +130 -0
  144. package/examples/debate.yaml +169 -0
  145. package/examples/socratic-questioning.yaml +112 -0
  146. package/package.json +5 -4
  147. package/src/__tests__/adapter-json-roundtrip.test.ts +15 -6
  148. package/src/__tests__/concurrency.test.ts +266 -0
  149. package/src/__tests__/e2e-mock-agent.test.ts +45 -7
  150. package/src/__tests__/format-text-default.test.ts +49 -0
  151. package/src/__tests__/format-text-registry.test.ts +173 -0
  152. package/src/__tests__/log-text-renderer.test.ts +294 -0
  153. package/src/__tests__/output-mapper-thread-list-startedat.test.ts +124 -0
  154. package/src/__tests__/output-mapper-workflow-add.test.ts +24 -0
  155. package/src/__tests__/pid-recycling.test.ts +9 -8
  156. package/src/__tests__/prompt.test.ts +48 -4
  157. package/src/__tests__/resolve-head-hash.test.ts +7 -0
  158. package/src/__tests__/solve-issue-tea-worktree.test.ts +3 -1
  159. package/src/__tests__/step-ask.test.ts +8 -1
  160. package/src/__tests__/store-unified-threads.test.ts +21 -18
  161. package/src/__tests__/thread-cancel-status.test.ts +21 -14
  162. package/src/__tests__/thread-cancel-text-renderer.test.ts +125 -0
  163. package/src/__tests__/thread-list-filters.test.ts +9 -9
  164. package/src/__tests__/thread-list-template-ms-date.test.ts +110 -0
  165. package/src/__tests__/thread-list-workflow-corrupt.test.ts +198 -0
  166. package/src/__tests__/thread-poke.test.ts +10 -1
  167. package/src/__tests__/thread-read-xml-tags.test.ts +9 -11
  168. package/src/__tests__/thread-resume.test.ts +10 -1
  169. package/src/__tests__/thread-start-cwd-cli.test.ts +15 -3
  170. package/src/__tests__/thread-stop-text-renderer.test.ts +168 -0
  171. package/src/__tests__/thread-suspend-step.test.ts +5 -2
  172. package/src/__tests__/thread-test-helpers.ts +15 -1
  173. package/src/__tests__/thread.test.ts +10 -10
  174. package/src/__tests__/validate-semantic.test.ts +59 -2
  175. package/src/__tests__/workflow-list-recursive.test.ts +9 -9
  176. package/src/__tests__/workflow-resolution.test.ts +9 -8
  177. package/src/__tests__/workflow-show-resolution.test.ts +9 -8
  178. package/src/__tests__/workflow-validate.test.ts +78 -56
  179. package/src/__tests__/write-envelope.test.ts +257 -0
  180. package/src/cli.ts +92 -35
  181. package/src/commands/config.ts +11 -0
  182. package/src/commands/prompt.ts +42 -29
  183. package/src/commands/setup.ts +57 -7
  184. package/src/commands/thread.ts +48 -2
  185. package/src/commands/workflow.ts +3 -7
  186. package/src/concurrency/concurrency.ts +245 -0
  187. package/src/concurrency/index.ts +10 -0
  188. package/src/concurrency/types.ts +19 -0
  189. package/src/format.ts +282 -2
  190. package/src/output-mappers.ts +254 -0
  191. package/src/schemas.ts +39 -3
  192. package/src/text-renderers.ts +355 -0
  193. package/src/validate-semantic.ts +33 -12
package/src/cli.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env -S node --disable-warning=ExperimentalWarning
2
2
 
3
- import type { CasRef, ThreadId, ThreadStatus } from "@united-workforce/protocol";
3
+ import type { CasRef, OutputSchemaName, ThreadId, ThreadStatus } from "@united-workforce/protocol";
4
4
  import { Command } from "commander";
5
5
  import { cmdConfigGet, cmdConfigList, cmdConfigSet } from "./commands/config.js";
6
6
  import { cmdLogClean, cmdLogList, cmdLogShow } from "./commands/log.js";
@@ -32,12 +32,61 @@ import {
32
32
  cmdWorkflowShow,
33
33
  cmdWorkflowValidate,
34
34
  } from "./commands/workflow.js";
35
- import { formatOutput, type OutputFormat } from "./format.js";
36
- import { resolveStorageRoot } from "./store.js";
35
+ import {
36
+ formatOutput,
37
+ isOutputFormat,
38
+ type OutputFormat,
39
+ SUPPORTED_FORMATS,
40
+ writeEnvelope,
41
+ } from "./format.js";
42
+ import {
43
+ toStepDetailPayload,
44
+ toStepListPayload,
45
+ toThreadExecPayload,
46
+ toThreadListPayload,
47
+ toThreadStartPayload,
48
+ toThreadStatusPayload,
49
+ toValidateResultPayload,
50
+ toWorkflowAddPayload,
51
+ toWorkflowDetailPayload,
52
+ toWorkflowListPayload,
53
+ } from "./output-mappers.js";
54
+ import { createUwfStore, resolveStorageRoot } from "./store.js";
55
+
56
+ function getFormat(): OutputFormat {
57
+ const raw = program.opts().format as string;
58
+ if (!isOutputFormat(raw)) {
59
+ process.stderr.write(
60
+ `Invalid --format: ${raw}. Must be one of: ${SUPPORTED_FORMATS.join(", ")}\n`,
61
+ );
62
+ process.exit(1);
63
+ }
64
+ return raw;
65
+ }
66
+
67
+ async function writeOutput(
68
+ payload: unknown,
69
+ schemaName: OutputSchemaName,
70
+ storageRoot: string,
71
+ ): Promise<void> {
72
+ const fmt = getFormat();
73
+ const uwf = await createUwfStore(storageRoot);
74
+ await writeEnvelope(payload, schemaName, {
75
+ format: fmt,
76
+ store: uwf.store,
77
+ schemas: uwf.schemas,
78
+ });
79
+ }
37
80
 
38
- function writeOutput(data: unknown): void {
39
- const fmt = program.opts().format as OutputFormat;
40
- process.stdout.write(`${formatOutput(data, fmt)}\n`);
81
+ /**
82
+ * Legacy raw output for commands without an output schema (log/config/setup).
83
+ * Always emits text/JSON/YAML based on the active --format. For `text`
84
+ * (the default) it renders via the per-command registry when available
85
+ * and falls back to JSON.
86
+ */
87
+ function writeRawOutput(data: unknown, commandPath?: string): void {
88
+ const fmt = getFormat();
89
+ process.stdout.write(`${formatOutput(data, fmt, commandPath)}\n`);
41
90
  }
42
91
 
43
92
  function runAction(action: () => Promise<void>): void {
@@ -60,7 +109,11 @@ program
60
109
  " workflow → thread → step → turn",
61
110
  )
62
111
  .version(pkg.default.version, "-V, --version");
63
- program.option("--format <fmt>", "Output format: json or yaml", "json");
112
+ program.option(
113
+ "--format <fmt>",
114
+ "Output format: text (default), json, yaml, raw-json, raw-yaml",
115
+ "text",
116
+ );
64
117
 
65
118
  const workflow = program
66
119
  .command("workflow")
@@ -74,7 +127,7 @@ workflow
74
127
  const storageRoot = resolveStorageRoot();
75
128
  runAction(async () => {
76
129
  const result = await cmdWorkflowAdd(storageRoot, file);
77
- writeOutput(result);
130
+ await writeOutput(toWorkflowAddPayload(result), "workflow-add", storageRoot);
78
131
  });
79
132
  });
80
133
 
@@ -83,9 +136,13 @@ workflow
83
136
  .description("Validate a workflow YAML without registering it (CI-friendly)")
84
137
  .argument("<file>", "Workflow YAML file")
85
138
  .action((file: string) => {
139
+ const storageRoot = resolveStorageRoot();
86
140
  runAction(async () => {
87
- await cmdWorkflowValidate(file);
88
- // silent on success — do not call writeOutput
141
+ const errors = await cmdWorkflowValidate(file);
142
+ await writeOutput(toValidateResultPayload(errors), "validate-result", storageRoot);
143
+ if (errors.length > 0) {
144
+ process.exit(1);
145
+ }
89
146
  });
90
147
  });
91
148
 
@@ -97,7 +154,7 @@ workflow
97
154
  const storageRoot = resolveStorageRoot();
98
155
  runAction(async () => {
99
156
  const result = await cmdWorkflowShow(storageRoot, id, process.cwd());
100
- writeOutput(result);
157
+ await writeOutput(toWorkflowDetailPayload(result), "workflow-detail", storageRoot);
101
158
  });
102
159
  });
103
160
 
@@ -108,7 +165,7 @@ workflow
108
165
  const storageRoot = resolveStorageRoot();
109
166
  runAction(async () => {
110
167
  const result = await cmdWorkflowList(storageRoot, process.cwd());
111
- writeOutput(result);
168
+ await writeOutput(toWorkflowListPayload(result), "workflow-list", storageRoot);
112
169
  });
113
170
  });
114
171
 
@@ -130,7 +187,7 @@ thread
130
187
  process.cwd(),
131
188
  opts.cwd ?? process.cwd(),
132
189
  );
133
- writeOutput(result);
190
+ await writeOutput(toThreadStartPayload(result), "thread-start", storageRoot);
134
191
  });
135
192
  });
136
193
 
@@ -167,11 +224,7 @@ thread
167
224
  background,
168
225
  backgroundWorker,
169
226
  );
170
- if (results.length === 1) {
171
- writeOutput(results[0]);
172
- } else {
173
- writeOutput(results);
174
- }
227
+ await writeOutput(toThreadExecPayload(results), "thread-exec", storageRoot);
175
228
  });
176
229
  },
177
230
  );
@@ -184,7 +237,7 @@ thread
184
237
  const storageRoot = resolveStorageRoot();
185
238
  runAction(async () => {
186
239
  const result = await cmdThreadShow(storageRoot, threadId);
187
- writeOutput(result);
240
+ await writeOutput(toThreadStatusPayload(result), "thread-status", storageRoot);
188
241
  });
189
242
  });
190
243
 
@@ -292,7 +345,7 @@ thread
292
345
  take,
293
346
  showAll,
294
347
  );
295
- writeOutput(result);
348
+ await writeOutput(toThreadListPayload(result), "thread-list", storageRoot);
296
349
  });
297
350
  },
298
351
  );
@@ -314,7 +367,7 @@ thread
314
367
  supplement,
315
368
  agentOverride,
316
369
  );
317
- writeOutput(result);
370
+ await writeOutput(toThreadStatusPayload(result), "thread-status", storageRoot);
318
371
  });
319
372
  });
320
373
 
@@ -334,7 +387,7 @@ thread
334
387
  opts.prompt,
335
388
  agentOverride,
336
389
  );
337
- writeOutput(result);
390
+ await writeOutput(toThreadStatusPayload(result), "thread-status", storageRoot);
338
391
  });
339
392
  });
340
393
 
@@ -346,7 +399,7 @@ thread
346
399
  const storageRoot = resolveStorageRoot();
347
400
  runAction(async () => {
348
401
  const result = await cmdThreadStop(storageRoot, threadId);
349
- writeOutput(result);
402
+ writeRawOutput(result, "thread stop");
350
403
  });
351
404
  });
352
405
 
@@ -358,7 +411,7 @@ thread
358
411
  const storageRoot = resolveStorageRoot();
359
412
  runAction(async () => {
360
413
  const result = await cmdThreadCancel(storageRoot, threadId);
361
- writeOutput(result);
414
+ writeRawOutput(result, "thread cancel");
362
415
  });
363
416
  });
364
417
 
@@ -401,7 +454,7 @@ step
401
454
  const storageRoot = resolveStorageRoot();
402
455
  runAction(async () => {
403
456
  const result = await cmdStepList(storageRoot, threadId);
404
- writeOutput(result);
457
+ await writeOutput(toStepListPayload(result), "step-list", storageRoot);
405
458
  });
406
459
  });
407
460
 
@@ -413,7 +466,11 @@ step
413
466
  const storageRoot = resolveStorageRoot();
414
467
  runAction(async () => {
415
468
  const detail = await cmdStepShow(storageRoot, stepHash as CasRef);
416
- writeOutput(detail);
469
+ await writeOutput(
470
+ toStepDetailPayload(stepHash as CasRef, detail),
471
+ "step-detail",
472
+ storageRoot,
473
+ );
417
474
  });
418
475
  });
419
476
 
@@ -475,7 +532,7 @@ step
475
532
  const storageRoot = resolveStorageRoot();
476
533
  runAction(async () => {
477
534
  const result = await cmdStepFork(storageRoot, stepHash as CasRef);
478
- writeOutput(result);
535
+ writeRawOutput(result);
479
536
  });
480
537
  });
481
538
 
@@ -618,7 +675,7 @@ program
618
675
  .command("setup")
619
676
  .description(
620
677
  "Configure the default agent. Run without --agent for interactive wizard.\n" +
621
- "LLM provider/model configuration lives in <storage>/config.yaml under providers and models.",
678
+ "Each adapter owns its own LLM configuration the engine config is LLM-free.",
622
679
  )
623
680
  .option("--agent <name>", "Default agent adapter (e.g. hermes → uwf-hermes)")
624
681
  .action((opts: { agent?: string }) => {
@@ -626,7 +683,7 @@ program
626
683
  runAction(async () => {
627
684
  if (opts.agent !== undefined && opts.agent !== "") {
628
685
  const result = await cmdSetup({ agent: opts.agent, storageRoot });
629
- writeOutput(result);
686
+ writeRawOutput(result);
630
687
  } else {
631
688
  await cmdSetupInteractive(storageRoot);
632
689
  }
@@ -642,7 +699,7 @@ log
642
699
  const storageRoot = resolveStorageRoot();
643
700
  runAction(async () => {
644
701
  const result = await cmdLogList(storageRoot);
645
- writeOutput(result);
702
+ writeRawOutput(result, "log list");
646
703
  });
647
704
  });
648
705
 
@@ -665,7 +722,7 @@ log
665
722
  process: opts.process ?? null,
666
723
  date: opts.date ?? null,
667
724
  });
668
- writeOutput(result);
725
+ writeRawOutput(result, "log show");
669
726
  });
670
727
  },
671
728
  );
@@ -678,7 +735,7 @@ log
678
735
  const storageRoot = resolveStorageRoot();
679
736
  runAction(async () => {
680
737
  const result = await cmdLogClean(storageRoot, opts.before);
681
- writeOutput(result);
738
+ writeRawOutput(result);
682
739
  });
683
740
  });
684
741
 
@@ -691,7 +748,7 @@ config
691
748
  const storageRoot = resolveStorageRoot();
692
749
  runAction(async () => {
693
750
  const result = await cmdConfigList(storageRoot);
694
- writeOutput(result);
751
+ writeRawOutput(result, "config list");
695
752
  });
696
753
  });
697
754
 
@@ -706,7 +763,7 @@ config
706
763
  const storageRoot = resolveStorageRoot();
707
764
  runAction(async () => {
708
765
  const result = await cmdConfigGet(storageRoot, key);
709
- writeOutput({ value: result });
766
+ writeRawOutput({ value: result }, "config get");
710
767
  });
711
768
  });
712
769
 
@@ -719,7 +776,7 @@ config
719
776
  const storageRoot = resolveStorageRoot();
720
777
  runAction(async () => {
721
778
  const result = await cmdConfigSet(storageRoot, key, value);
722
- writeOutput(result);
779
+ writeRawOutput(result, "config set");
723
780
  });
724
781
  });
725
782
 
@@ -21,6 +21,11 @@ const VALID_CONFIG_KEYS: Record<
21
21
  // No knownFields — workflow/role names are user-defined
22
22
  },
23
23
  defaultAgent: { nested: false },
24
+ concurrency: {
25
+ nested: true,
26
+ knownFields: ["maxRunning"],
27
+ minDepth: 2,
28
+ },
24
29
  };
25
30
 
26
31
  /**
@@ -264,6 +269,12 @@ export async function cmdConfigSet(
264
269
  let parsedValue: unknown = value;
265
270
  if (lastSegment === "args") {
266
271
  parsedValue = parseArgsValue(value);
272
+ } else if (lastSegment === "maxRunning") {
273
+ const num = Number(value);
274
+ if (!Number.isInteger(num) || num < 1) {
275
+ throw new Error("Value for 'maxRunning' must be a positive integer");
276
+ }
277
+ parsedValue = num;
267
278
  }
268
279
 
269
280
  // Validate we're not setting a property on a non-object
@@ -148,44 +148,36 @@ pipx install 'hermes-agent[acp]'
148
148
  pip install -e '.[acp]'
149
149
  \`\`\`
150
150
 
151
- ### Step 2 — Configure provider and model
151
+ ### Step 2 — Configure default agent
152
152
 
153
- uwf needs an LLM provider to run agents. **Ask the user** for their provider, API key, and model, then run:
153
+ Run the interactive wizard:
154
154
 
155
155
  \`\`\`bash
156
- uwf setup --provider <name> --api-key <key> --model <model> --agent <adapter-command>
156
+ uwf setup
157
157
  \`\`\`
158
158
 
159
- **Note:** \`--agent\` takes the adapter **command name** (e.g. \`uwf-hermes\`), not the npm package name.
160
-
161
- **Preset providers** — when using a preset name, \`--base-url\` is auto-filled and can be omitted:
162
-
163
- | Provider | Name | Default base URL |
164
- |----------|------|-----------------|
165
- | OpenAI | \`openai\` | https://api.openai.com/v1 |
166
- | xAI | \`xai\` | https://api.x.ai/v1 |
167
- | OpenRouter | \`openrouter\` | https://openrouter.ai/api/v1 |
168
- | Venice | \`venice\` | https://api.venice.ai/api/v1 |
169
- | Dashscope | \`dashscope\` | https://dashscope.aliyuncs.com/compatible-mode/v1 |
170
- | DeepSeek | \`deepseek\` | https://api.deepseek.com/v1 |
171
- | SiliconFlow | \`siliconflow\` | https://api.siliconflow.cn/v1 |
172
- | VolcEngine | \`volcengine\` | https://ark.cn-beijing.volces.com/api/v3 |
173
- | Kimi (Moonshot) | \`kimi\` | https://api.moonshot.cn/v1 |
174
- | GLM (Zhipu AI) | \`glm\` | https://open.bigmodel.cn/api/paas/v4 |
175
- | StepFun | \`stepfun\` | https://api.stepfun.com/v1 |
176
- | MiniMax | \`minimax\` | https://api.minimax.io/v1 |
177
- | Ollama (local) | \`ollama\` | http://localhost:11434/v1 |
178
-
179
- For **non-preset providers**, you must specify \`--base-url\` manually.
180
-
181
- Example:
159
+ Or configure non-interactively:
160
+
182
161
  \`\`\`bash
183
- uwf setup --provider openrouter --api-key sk-or-... --model anthropic/claude-sonnet-4 --agent uwf-hermes
162
+ uwf setup --agent <adapter-command>
163
+ \`\`\`
164
+
165
+ **Note:** \`--agent\` takes the adapter **command name** (e.g. \`uwf-hermes\`, \`uwf-claude-code\`), not the npm package name.
166
+
167
+ Config is saved to \`~/.uwf/config.yaml\`:
168
+
169
+ \`\`\`yaml
170
+ agents:
171
+ hermes:
172
+ command: uwf-hermes
173
+ args: []
174
+ defaultAgent: hermes
175
+ agentOverrides: {}
184
176
  \`\`\`
185
177
 
186
- If the user doesn't know what to choose, suggest \`openrouter\` with \`anthropic/claude-sonnet-4\` as a sensible default.
178
+ **LLM configuration** is per-adapter each adapter manages its own provider, model, and API key settings independently (typically via environment variables like \`CLAUDE_MODEL\`, \`ANTHROPIC_API_KEY\`, etc.). The engine config (\`~/.uwf/config.yaml\`) is LLM-free.
187
179
 
188
- Config is saved to \`~/.uwf/config.yaml\`. Verify with \`cat ~/.uwf/config.yaml\`.
180
+ Verify with \`cat ~/.uwf/config.yaml\`.
189
181
 
190
182
  ### Step 3 — Install skills
191
183
 
@@ -339,5 +331,26 @@ uwf prompt workflow-authoring # workflow YAML design guide
339
331
  uwf prompt adapter-developing # building agent adapters
340
332
  uwf prompt bootstrap # this guide
341
333
  \`\`\`
334
+
335
+ ## What's next — introduce uwf to the user
336
+
337
+ After setup is complete, give the user a brief introduction to what uwf can do
338
+ and invite them to try it. Cover these three things:
339
+
340
+ 1. **Run workflows** — execute pre-built workflows to automate multi-step tasks.
341
+ Setup auto-registers example workflows, ready to use immediately.
342
+ 2. **Create workflows** — design custom YAML workflows for their own recurring tasks
343
+ (code review, issue triage, release pipelines, etc.). Use \`uwf prompt workflow-authoring\`
344
+ for the authoring guide.
345
+ 3. **Debug & improve workflows** — iterate on workflow definitions, inspect thread state
346
+ with \`uwf thread show\`, replay failed steps with \`uwf thread poke\`, and refine
347
+ role procedures based on real execution results.
348
+
349
+ **Discover & try built-in examples:**
350
+
351
+ Run \`uwf workflow list\` to see which workflows are registered (setup auto-registers
352
+ several built-in examples). Show the user what's available, then invite them to try
353
+ one — for instance, suggest a fun debate topic like "AI 是否会抢了人类的工作?" and
354
+ offer to kick it off for them.
342
355
  `;
343
356
  }
@@ -1,9 +1,11 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
3
+ import { dirname, join } from "node:path";
4
4
  import { stdin as input, stdout as output } from "node:process";
5
5
  import { createInterface } from "node:readline/promises";
6
+ import { fileURLToPath } from "node:url";
6
7
  import { parse, stringify } from "yaml";
8
+ import { cmdWorkflowAdd } from "./workflow.js";
7
9
 
8
10
  export type SetupArgs = {
9
11
  agent: string;
@@ -261,10 +263,56 @@ export function _checkAdapterAvailability(agentName: string): string[] {
261
263
  return warnings;
262
264
  }
263
265
 
266
+ // ──────────────────────────────────────────────────────────────────────────────
267
+ // Bundled example workflows
268
+ // ──────────────────────────────────────────────────────────────────────────────
269
+
270
+ /** Resolve the examples/ directory bundled with the CLI package. */
271
+ function _findExamplesDir(): string | null {
272
+ // Walk up from this file (src/commands/ or dist/commands/) to the package root
273
+ let dir = dirname(fileURLToPath(import.meta.url));
274
+ for (let i = 0; i < 5; i++) {
275
+ const candidate = join(dir, "examples");
276
+ if (existsSync(candidate) && statSync(candidate).isDirectory()) {
277
+ return candidate;
278
+ }
279
+ dir = dirname(dir);
280
+ }
281
+ return null;
282
+ }
283
+
284
+ /**
285
+ * Register bundled example workflows. Non-destructive — silently skips
286
+ * any that fail (e.g. already registered with same hash).
287
+ * Returns list of successfully registered workflow names.
288
+ */
289
+ export async function _registerBundledExamples(storageRoot: string): Promise<string[]> {
290
+ const examplesDir = _findExamplesDir();
291
+ if (examplesDir === null) return [];
292
+
293
+ const registered: string[] = [];
294
+ const files = readdirSync(examplesDir)
295
+ .filter((f) => f.endsWith(".yaml"))
296
+ .sort();
297
+
298
+ for (const file of files) {
299
+ try {
300
+ const result = await cmdWorkflowAdd(storageRoot, join(examplesDir, file));
301
+ registered.push(result.name);
302
+ console.error(` ✓ ${result.name}`);
303
+ } catch {
304
+ // Skip silently — workflow may already exist or be invalid
305
+ }
306
+ }
307
+
308
+ return registered;
309
+ }
310
+
311
+ // ──────────────────────────────────────────────────────────────────────────────
312
+
264
313
  /**
265
314
  * Non-interactive setup. Engine config is LLM-free — only writes
266
- * agents + defaultAgent. LLM provider/model configuration lives in
267
- * config.yaml under `providers` and `models`.
315
+ * agents + defaultAgent. Each adapter owns its own LLM configuration.
268
316
  */
269
317
  export async function cmdSetup(args: SetupArgs): Promise<Record<string, unknown>> {
270
318
  const { storageRoot } = args;
@@ -287,16 +335,20 @@ export async function cmdSetup(args: SetupArgs): Promise<Record<string, unknown>
287
335
  console.error(`⚠ ${w}`);
288
336
  }
289
337
 
338
+ // Auto-register bundled example workflows
339
+ const registeredExamples = await _registerBundledExamples(storageRoot);
340
+
290
341
  return {
291
342
  configPath,
292
343
  defaultAgent: merged.defaultAgent,
293
344
  adapterWarnings,
345
+ registeredExamples,
294
346
  };
295
347
  }
296
348
 
297
349
  /**
298
- * Interactive setup — prompts the user only for the default agent. LLM
299
- * configuration lives in config.yaml under `providers` and `models`.
350
+ * Interactive setup — prompts the user only for the default agent.
351
+ * Each adapter owns its own LLM configuration.
300
352
  */
301
353
  export async function cmdSetupInteractive(storageRoot: string): Promise<Record<string, unknown>> {
302
354
  const rl = createInterface({ input, output });
@@ -313,8 +365,6 @@ export async function cmdSetupInteractive(storageRoot: string): Promise<Record<s
313
365
  console.log(' uwf thread start <name> -p "..." Start a thread');
314
366
  console.log(" uwf thread exec <thread-id> Execute next step");
315
367
  console.log("");
316
- console.log("LLM config: edit ~/.uwf/config.yaml (providers + models sections).");
317
- console.log("");
318
368
 
319
369
  return null as unknown as Record<string, unknown>;
320
370
  } finally {
@@ -43,6 +43,7 @@ import {
43
43
  isThreadRunning,
44
44
  readMarker,
45
45
  } from "../background/index.js";
46
+ import { acquireSlot, DEFAULT_MAX_RUNNING, installSlotCleanup } from "../concurrency/index.js";
46
47
  import { createIncludeTag } from "../include.js";
47
48
  import { evaluate } from "../moderator/index.js";
48
49
  import {
@@ -60,6 +61,7 @@ import {
60
61
  } from "../store.js";
61
62
  import { checkWorkflowFilenameConsistency, isCasRef, parseWorkflowPayload } from "../validate.js";
62
63
  import { validateWorkflow } from "../validate-semantic.js";
64
+ import { getConfigPath, getNestedValue, loadConfig, parseDotPath } from "./config.js";
63
65
  import {
64
66
  type ChainState,
65
67
  collectOrderedSteps,
@@ -992,6 +994,15 @@ type EvaluateLastOutput = Record<string, unknown>;
992
994
 
993
995
  const STATUS_KEY = "$status";
994
996
 
997
+ /**
998
+ * Strip YAML frontmatter (---...---) from a raw markdown string,
999
+ * returning only the body portion.
1000
+ */
1001
+ function stripFrontmatter(raw: string): string {
1002
+ const match = raw.match(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/);
1003
+ return match ? raw.slice(match[0].length).trim() : raw.trim();
1004
+ }
1005
+
995
1006
  function resolveEvaluateArgs(
996
1007
  uwf: UwfStore,
997
1008
  chain: ChainState,
@@ -1011,6 +1022,13 @@ function resolveEvaluateArgs(
1011
1022
  ? (raw as Record<string, unknown>)
1012
1023
  : {};
1013
1024
 
1025
+ // Inject _body — the markdown body (after frontmatter) from the last step's
1026
+ // assistant output. Workflow edge prompts can reference it via {{ _body }}.
1027
+ const content = extractLastAssistantContent(uwf, lastStep.detail);
1028
+ if (content !== null) {
1029
+ base._body = stripFrontmatter(content);
1030
+ }
1031
+
1014
1032
  return {
1015
1033
  lastRole: lastStep.role,
1016
1034
  lastOutput: base,
@@ -1020,10 +1038,10 @@ function resolveEvaluateArgs(
1020
1038
  function loadWorkflowPayload(uwf: UwfStore, workflowRef: CasRef): WorkflowPayload {
1021
1039
  const node = uwf.store.cas.get(workflowRef);
1022
1040
  if (node === null) {
1023
- fail(`workflow CAS node not found: ${workflowRef}`);
1041
+ throw new Error(`workflow CAS node not found: ${workflowRef}`);
1024
1042
  }
1025
1043
  if (node.type !== uwf.schemas.workflow) {
1026
- fail(`node ${workflowRef} is not a Workflow`);
1044
+ throw new Error(`node ${workflowRef} is not a Workflow`);
1027
1045
  }
1028
1046
  return node.payload as WorkflowPayload;
1029
1047
  }
@@ -1406,6 +1424,25 @@ export function validateCount(count: number): void {
1406
1424
  }
1407
1425
  }
1408
1426
 
1427
+ /**
1428
+ * Resolve the effective maxRunning limit.
1429
+ * Priority: config file > DEFAULT_MAX_RUNNING (2).
1430
+ */
1431
+ async function resolveMaxRunning(storageRoot: string): Promise<number> {
1432
+ try {
1433
+ const configPath = getConfigPath(storageRoot);
1434
+ const config = loadConfig(configPath);
1435
+ const path = parseDotPath("concurrency.maxRunning");
1436
+ const value = getNestedValue(config, path);
1437
+ if (typeof value === "number" && Number.isInteger(value) && value > 0) {
1438
+ return value;
1439
+ }
1440
+ } catch {
1441
+ // Config file missing or invalid — fall through to default
1442
+ }
1443
+ return DEFAULT_MAX_RUNNING;
1444
+ }
1445
+
1409
1446
  export async function cmdThreadExec(
1410
1447
  storageRoot: string,
1411
1448
  threadId: ThreadId,
@@ -1446,6 +1483,13 @@ export async function cmdThreadExec(
1446
1483
  processStartTime: getProcessStartTime(process.pid),
1447
1484
  });
1448
1485
 
1486
+ // Resolve concurrency limit: config > default
1487
+ const effectiveMaxRunning = await resolveMaxRunning(storageRoot);
1488
+
1489
+ // Acquire concurrency slot (blocks if at capacity)
1490
+ const slotHandle = await acquireSlot(storageRoot, effectiveMaxRunning);
1491
+ const uninstallCleanup = installSlotCleanup(slotHandle);
1492
+
1449
1493
  try {
1450
1494
  const results: StepOutput[] = [];
1451
1495
  for (let i = 0; i < count; i++) {
@@ -1457,6 +1501,8 @@ export async function cmdThreadExec(
1457
1501
  }
1458
1502
  return results;
1459
1503
  } finally {
1504
+ uninstallCleanup();
1505
+ await slotHandle.release();
1460
1506
  await deleteMarker(storageRoot, threadId);
1461
1507
  }
1462
1508
  }
@@ -126,7 +126,7 @@ export async function materializeWorkflowPayload(
126
126
  * returns silently (no stdout/stderr) and exits 0. On any error, writes a
127
127
  * single message to stderr and exits 1.
128
128
  */
129
- export async function cmdWorkflowValidate(filePath: string): Promise<void> {
129
+ export async function cmdWorkflowValidate(filePath: string): Promise<string[]> {
130
130
  let text: string;
131
131
  try {
132
132
  text = await readFile(filePath, "utf8");
@@ -150,14 +150,10 @@ export async function cmdWorkflowValidate(filePath: string): Promise<void> {
150
150
 
151
151
  const filenameError = checkWorkflowFilenameConsistency(filePath, payload);
152
152
  if (filenameError !== null) {
153
- fail(filenameError);
153
+ return [filenameError];
154
154
  }
155
155
 
156
- const semanticErrors = validateWorkflow(payload);
157
- if (semanticErrors.length > 0) {
158
- fail(`workflow validation failed:\n${semanticErrors.map((e) => ` - ${e}`).join("\n")}`);
159
- }
160
- // success: silent return
156
+ return validateWorkflow(payload);
161
157
  }
162
158
 
163
159
  export async function cmdWorkflowAdd(