@united-workforce/cli 0.1.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 (310) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +221 -0
  3. package/dist/__tests__/adapter-json-roundtrip.test.d.ts +2 -0
  4. package/dist/__tests__/adapter-json-roundtrip.test.d.ts.map +1 -0
  5. package/dist/__tests__/adapter-json-roundtrip.test.js +147 -0
  6. package/dist/__tests__/adapter-json-roundtrip.test.js.map +1 -0
  7. package/dist/__tests__/config.test.d.ts +2 -0
  8. package/dist/__tests__/config.test.d.ts.map +1 -0
  9. package/dist/__tests__/config.test.js +685 -0
  10. package/dist/__tests__/config.test.js.map +1 -0
  11. package/dist/__tests__/current-role.test.d.ts +2 -0
  12. package/dist/__tests__/current-role.test.d.ts.map +1 -0
  13. package/dist/__tests__/current-role.test.js +401 -0
  14. package/dist/__tests__/current-role.test.js.map +1 -0
  15. package/dist/__tests__/e2e-mock-agent.test.d.ts +2 -0
  16. package/dist/__tests__/e2e-mock-agent.test.d.ts.map +1 -0
  17. package/dist/__tests__/e2e-mock-agent.test.js +401 -0
  18. package/dist/__tests__/e2e-mock-agent.test.js.map +1 -0
  19. package/dist/__tests__/include-tag.test.d.ts +2 -0
  20. package/dist/__tests__/include-tag.test.d.ts.map +1 -0
  21. package/dist/__tests__/include-tag.test.js +69 -0
  22. package/dist/__tests__/include-tag.test.js.map +1 -0
  23. package/dist/__tests__/log.test.d.ts +2 -0
  24. package/dist/__tests__/log.test.d.ts.map +1 -0
  25. package/dist/__tests__/log.test.js +161 -0
  26. package/dist/__tests__/log.test.js.map +1 -0
  27. package/dist/__tests__/moderator-evaluate.test.d.ts +2 -0
  28. package/dist/__tests__/moderator-evaluate.test.d.ts.map +1 -0
  29. package/dist/__tests__/moderator-evaluate.test.js +170 -0
  30. package/dist/__tests__/moderator-evaluate.test.js.map +1 -0
  31. package/dist/__tests__/preload.d.ts +3 -0
  32. package/dist/__tests__/preload.d.ts.map +1 -0
  33. package/dist/__tests__/preload.js +6 -0
  34. package/dist/__tests__/preload.js.map +1 -0
  35. package/dist/__tests__/prompt.test.d.ts +2 -0
  36. package/dist/__tests__/prompt.test.d.ts.map +1 -0
  37. package/dist/__tests__/prompt.test.js +111 -0
  38. package/dist/__tests__/prompt.test.js.map +1 -0
  39. package/dist/__tests__/resolve-head-hash.test.d.ts +2 -0
  40. package/dist/__tests__/resolve-head-hash.test.d.ts.map +1 -0
  41. package/dist/__tests__/resolve-head-hash.test.js +66 -0
  42. package/dist/__tests__/resolve-head-hash.test.js.map +1 -0
  43. package/dist/__tests__/setup-agent-discovery.test.d.ts +2 -0
  44. package/dist/__tests__/setup-agent-discovery.test.d.ts.map +1 -0
  45. package/dist/__tests__/setup-agent-discovery.test.js +119 -0
  46. package/dist/__tests__/setup-agent-discovery.test.js.map +1 -0
  47. package/dist/__tests__/setup-complexity.test.d.ts +2 -0
  48. package/dist/__tests__/setup-complexity.test.d.ts.map +1 -0
  49. package/dist/__tests__/setup-complexity.test.js +314 -0
  50. package/dist/__tests__/setup-complexity.test.js.map +1 -0
  51. package/dist/__tests__/setup-validate.test.d.ts +2 -0
  52. package/dist/__tests__/setup-validate.test.d.ts.map +1 -0
  53. package/dist/__tests__/setup-validate.test.js +108 -0
  54. package/dist/__tests__/setup-validate.test.js.map +1 -0
  55. package/dist/__tests__/solve-issue-tea-worktree.test.d.ts +2 -0
  56. package/dist/__tests__/solve-issue-tea-worktree.test.d.ts.map +1 -0
  57. package/dist/__tests__/solve-issue-tea-worktree.test.js +107 -0
  58. package/dist/__tests__/solve-issue-tea-worktree.test.js.map +1 -0
  59. package/dist/__tests__/spawn-agent-json.test.d.ts +2 -0
  60. package/dist/__tests__/spawn-agent-json.test.d.ts.map +1 -0
  61. package/dist/__tests__/spawn-agent-json.test.js +79 -0
  62. package/dist/__tests__/spawn-agent-json.test.js.map +1 -0
  63. package/dist/__tests__/step-read.test.d.ts +2 -0
  64. package/dist/__tests__/step-read.test.d.ts.map +1 -0
  65. package/dist/__tests__/step-read.test.js +561 -0
  66. package/dist/__tests__/step-read.test.js.map +1 -0
  67. package/dist/__tests__/step-show-json.test.d.ts +2 -0
  68. package/dist/__tests__/step-show-json.test.d.ts.map +1 -0
  69. package/dist/__tests__/step-show-json.test.js +311 -0
  70. package/dist/__tests__/step-show-json.test.js.map +1 -0
  71. package/dist/__tests__/step-timing.test.d.ts +2 -0
  72. package/dist/__tests__/step-timing.test.d.ts.map +1 -0
  73. package/dist/__tests__/step-timing.test.js +345 -0
  74. package/dist/__tests__/step-timing.test.js.map +1 -0
  75. package/dist/__tests__/store-global-cas.test.d.ts +2 -0
  76. package/dist/__tests__/store-global-cas.test.d.ts.map +1 -0
  77. package/dist/__tests__/store-global-cas.test.js +235 -0
  78. package/dist/__tests__/store-global-cas.test.js.map +1 -0
  79. package/dist/__tests__/store-storage-root.test.d.ts +2 -0
  80. package/dist/__tests__/store-storage-root.test.d.ts.map +1 -0
  81. package/dist/__tests__/store-storage-root.test.js +43 -0
  82. package/dist/__tests__/store-storage-root.test.js.map +1 -0
  83. package/dist/__tests__/store-unified-threads.test.d.ts +2 -0
  84. package/dist/__tests__/store-unified-threads.test.d.ts.map +1 -0
  85. package/dist/__tests__/store-unified-threads.test.js +189 -0
  86. package/dist/__tests__/store-unified-threads.test.js.map +1 -0
  87. package/dist/__tests__/thread-cancel-status.test.d.ts +2 -0
  88. package/dist/__tests__/thread-cancel-status.test.d.ts.map +1 -0
  89. package/dist/__tests__/thread-cancel-status.test.js +111 -0
  90. package/dist/__tests__/thread-cancel-status.test.js.map +1 -0
  91. package/dist/__tests__/thread-list-filters.test.d.ts +2 -0
  92. package/dist/__tests__/thread-list-filters.test.d.ts.map +1 -0
  93. package/dist/__tests__/thread-list-filters.test.js +442 -0
  94. package/dist/__tests__/thread-list-filters.test.js.map +1 -0
  95. package/dist/__tests__/thread-location.test.d.ts +2 -0
  96. package/dist/__tests__/thread-location.test.d.ts.map +1 -0
  97. package/dist/__tests__/thread-location.test.js +159 -0
  98. package/dist/__tests__/thread-location.test.js.map +1 -0
  99. package/dist/__tests__/thread-read-quota.test.d.ts +2 -0
  100. package/dist/__tests__/thread-read-quota.test.d.ts.map +1 -0
  101. package/dist/__tests__/thread-read-quota.test.js +546 -0
  102. package/dist/__tests__/thread-read-quota.test.js.map +1 -0
  103. package/dist/__tests__/thread-read-xml-tags.test.d.ts +2 -0
  104. package/dist/__tests__/thread-read-xml-tags.test.d.ts.map +1 -0
  105. package/dist/__tests__/thread-read-xml-tags.test.js +610 -0
  106. package/dist/__tests__/thread-read-xml-tags.test.js.map +1 -0
  107. package/dist/__tests__/thread-resume.test.d.ts +2 -0
  108. package/dist/__tests__/thread-resume.test.d.ts.map +1 -0
  109. package/dist/__tests__/thread-resume.test.js +592 -0
  110. package/dist/__tests__/thread-resume.test.js.map +1 -0
  111. package/dist/__tests__/thread-show-status.test.d.ts +2 -0
  112. package/dist/__tests__/thread-show-status.test.d.ts.map +1 -0
  113. package/dist/__tests__/thread-show-status.test.js +267 -0
  114. package/dist/__tests__/thread-show-status.test.js.map +1 -0
  115. package/dist/__tests__/thread-start-cwd-cli.test.d.ts +2 -0
  116. package/dist/__tests__/thread-start-cwd-cli.test.d.ts.map +1 -0
  117. package/dist/__tests__/thread-start-cwd-cli.test.js +130 -0
  118. package/dist/__tests__/thread-start-cwd-cli.test.js.map +1 -0
  119. package/dist/__tests__/thread-step-count.test.d.ts +2 -0
  120. package/dist/__tests__/thread-step-count.test.d.ts.map +1 -0
  121. package/dist/__tests__/thread-step-count.test.js +55 -0
  122. package/dist/__tests__/thread-step-count.test.js.map +1 -0
  123. package/dist/__tests__/thread-suspend-step.test.d.ts +2 -0
  124. package/dist/__tests__/thread-suspend-step.test.d.ts.map +1 -0
  125. package/dist/__tests__/thread-suspend-step.test.js +155 -0
  126. package/dist/__tests__/thread-suspend-step.test.js.map +1 -0
  127. package/dist/__tests__/thread-suspended-display.test.d.ts +2 -0
  128. package/dist/__tests__/thread-suspended-display.test.d.ts.map +1 -0
  129. package/dist/__tests__/thread-suspended-display.test.js +247 -0
  130. package/dist/__tests__/thread-suspended-display.test.js.map +1 -0
  131. package/dist/__tests__/thread-test-helpers.d.ts +4 -0
  132. package/dist/__tests__/thread-test-helpers.d.ts.map +1 -0
  133. package/dist/__tests__/thread-test-helpers.js +23 -0
  134. package/dist/__tests__/thread-test-helpers.js.map +1 -0
  135. package/dist/__tests__/thread.test.d.ts +2 -0
  136. package/dist/__tests__/thread.test.d.ts.map +1 -0
  137. package/dist/__tests__/thread.test.js +883 -0
  138. package/dist/__tests__/thread.test.js.map +1 -0
  139. package/dist/__tests__/validate-semantic.test.d.ts +2 -0
  140. package/dist/__tests__/validate-semantic.test.d.ts.map +1 -0
  141. package/dist/__tests__/validate-semantic.test.js +408 -0
  142. package/dist/__tests__/validate-semantic.test.js.map +1 -0
  143. package/dist/__tests__/workflow-resolution.test.d.ts +2 -0
  144. package/dist/__tests__/workflow-resolution.test.d.ts.map +1 -0
  145. package/dist/__tests__/workflow-resolution.test.js +308 -0
  146. package/dist/__tests__/workflow-resolution.test.js.map +1 -0
  147. package/dist/background/background.d.ts +38 -0
  148. package/dist/background/background.d.ts.map +1 -0
  149. package/dist/background/background.js +123 -0
  150. package/dist/background/background.js.map +1 -0
  151. package/dist/background/index.d.ts +3 -0
  152. package/dist/background/index.d.ts.map +1 -0
  153. package/dist/background/index.js +2 -0
  154. package/dist/background/index.js.map +1 -0
  155. package/dist/background/types.d.ts +9 -0
  156. package/dist/background/types.d.ts.map +1 -0
  157. package/dist/background/types.js +2 -0
  158. package/dist/background/types.js.map +1 -0
  159. package/dist/cli.d.ts +3 -0
  160. package/dist/cli.d.ts.map +1 -0
  161. package/dist/cli.js +535 -0
  162. package/dist/cli.js.map +1 -0
  163. package/dist/commands/config.d.ts +41 -0
  164. package/dist/commands/config.d.ts.map +1 -0
  165. package/dist/commands/config.js +252 -0
  166. package/dist/commands/config.js.map +1 -0
  167. package/dist/commands/log.d.ts +26 -0
  168. package/dist/commands/log.d.ts.map +1 -0
  169. package/dist/commands/log.js +79 -0
  170. package/dist/commands/log.js.map +1 -0
  171. package/dist/commands/prompt.d.ts +6 -0
  172. package/dist/commands/prompt.d.ts.map +1 -0
  173. package/dist/commands/prompt.js +67 -0
  174. package/dist/commands/prompt.js.map +1 -0
  175. package/dist/commands/setup.d.ts +73 -0
  176. package/dist/commands/setup.d.ts.map +1 -0
  177. package/dist/commands/setup.js +522 -0
  178. package/dist/commands/setup.js.map +1 -0
  179. package/dist/commands/shared.d.ts +31 -0
  180. package/dist/commands/shared.d.ts.map +1 -0
  181. package/dist/commands/shared.js +154 -0
  182. package/dist/commands/shared.js.map +1 -0
  183. package/dist/commands/step.d.ts +18 -0
  184. package/dist/commands/step.d.ts.map +1 -0
  185. package/dist/commands/step.js +257 -0
  186. package/dist/commands/step.js.map +1 -0
  187. package/dist/commands/thread-time-parser.d.ts +6 -0
  188. package/dist/commands/thread-time-parser.d.ts.map +1 -0
  189. package/dist/commands/thread-time-parser.js +22 -0
  190. package/dist/commands/thread-time-parser.js.map +1 -0
  191. package/dist/commands/thread.d.ts +38 -0
  192. package/dist/commands/thread.d.ts.map +1 -0
  193. package/dist/commands/thread.js +1087 -0
  194. package/dist/commands/thread.js.map +1 -0
  195. package/dist/commands/workflow.d.ts +24 -0
  196. package/dist/commands/workflow.d.ts.map +1 -0
  197. package/dist/commands/workflow.js +138 -0
  198. package/dist/commands/workflow.js.map +1 -0
  199. package/dist/format.d.ts +3 -0
  200. package/dist/format.d.ts.map +1 -0
  201. package/dist/format.js +10 -0
  202. package/dist/format.js.map +1 -0
  203. package/dist/include.d.ts +12 -0
  204. package/dist/include.d.ts.map +1 -0
  205. package/dist/include.js +35 -0
  206. package/dist/include.js.map +1 -0
  207. package/dist/moderator/__tests__/evaluate.test.d.ts +2 -0
  208. package/dist/moderator/__tests__/evaluate.test.d.ts.map +1 -0
  209. package/dist/moderator/__tests__/evaluate.test.js +167 -0
  210. package/dist/moderator/__tests__/evaluate.test.js.map +1 -0
  211. package/dist/moderator/evaluate.d.ts +6 -0
  212. package/dist/moderator/evaluate.d.ts.map +1 -0
  213. package/dist/moderator/evaluate.js +65 -0
  214. package/dist/moderator/evaluate.js.map +1 -0
  215. package/dist/moderator/index.d.ts +4 -0
  216. package/dist/moderator/index.d.ts.map +1 -0
  217. package/dist/moderator/index.js +3 -0
  218. package/dist/moderator/index.js.map +1 -0
  219. package/dist/moderator/types.d.ts +25 -0
  220. package/dist/moderator/types.d.ts.map +1 -0
  221. package/dist/moderator/types.js +4 -0
  222. package/dist/moderator/types.js.map +1 -0
  223. package/dist/schemas.d.ts +16 -0
  224. package/dist/schemas.d.ts.map +1 -0
  225. package/dist/schemas.js +17 -0
  226. package/dist/schemas.js.map +1 -0
  227. package/dist/store.d.ts +77 -0
  228. package/dist/store.d.ts.map +1 -0
  229. package/dist/store.js +392 -0
  230. package/dist/store.js.map +1 -0
  231. package/dist/validate-semantic.d.ts +7 -0
  232. package/dist/validate-semantic.d.ts.map +1 -0
  233. package/dist/validate-semantic.js +263 -0
  234. package/dist/validate-semantic.js.map +1 -0
  235. package/dist/validate.d.ts +16 -0
  236. package/dist/validate.d.ts.map +1 -0
  237. package/dist/validate.js +115 -0
  238. package/dist/validate.js.map +1 -0
  239. package/package.json +44 -0
  240. package/src/__tests__/adapter-json-roundtrip.test.ts +181 -0
  241. package/src/__tests__/config.test.ts +740 -0
  242. package/src/__tests__/current-role.test.ts +438 -0
  243. package/src/__tests__/e2e-mock-agent.test.ts +498 -0
  244. package/src/__tests__/fixtures/e2e-completed-resume.mock.yaml +15 -0
  245. package/src/__tests__/fixtures/e2e-count.mock.yaml +19 -0
  246. package/src/__tests__/fixtures/e2e-count.workflow.yaml +45 -0
  247. package/src/__tests__/fixtures/e2e-linear.mock.yaml +13 -0
  248. package/src/__tests__/fixtures/e2e-linear.workflow.yaml +32 -0
  249. package/src/__tests__/fixtures/e2e-loop.mock.yaml +25 -0
  250. package/src/__tests__/fixtures/e2e-loop.workflow.yaml +36 -0
  251. package/src/__tests__/fixtures/e2e-mismatch.mock.yaml +16 -0
  252. package/src/__tests__/fixtures/e2e-mustache.mock.yaml +15 -0
  253. package/src/__tests__/fixtures/e2e-mustache.workflow.yaml +34 -0
  254. package/src/__tests__/fixtures/e2e-suspend.mock.yaml +14 -0
  255. package/src/__tests__/fixtures/e2e-suspend.workflow.yaml +24 -0
  256. package/src/__tests__/include-tag.test.ts +84 -0
  257. package/src/__tests__/log.test.ts +181 -0
  258. package/src/__tests__/moderator-evaluate.test.ts +186 -0
  259. package/src/__tests__/preload.ts +7 -0
  260. package/src/__tests__/prompt.test.ts +129 -0
  261. package/src/__tests__/resolve-head-hash.test.ts +86 -0
  262. package/src/__tests__/setup-agent-discovery.test.ts +167 -0
  263. package/src/__tests__/setup-complexity.test.ts +381 -0
  264. package/src/__tests__/setup-validate.test.ts +148 -0
  265. package/src/__tests__/solve-issue-tea-worktree.test.ts +144 -0
  266. package/src/__tests__/spawn-agent-json.test.ts +100 -0
  267. package/src/__tests__/step-read.test.ts +632 -0
  268. package/src/__tests__/step-show-json.test.ts +373 -0
  269. package/src/__tests__/step-timing.test.ts +392 -0
  270. package/src/__tests__/store-global-cas.test.ts +308 -0
  271. package/src/__tests__/store-storage-root.test.ts +49 -0
  272. package/src/__tests__/store-unified-threads.test.ts +235 -0
  273. package/src/__tests__/thread-cancel-status.test.ts +138 -0
  274. package/src/__tests__/thread-list-filters.test.ts +572 -0
  275. package/src/__tests__/thread-location.test.ts +186 -0
  276. package/src/__tests__/thread-read-quota.test.ts +613 -0
  277. package/src/__tests__/thread-read-xml-tags.test.ts +717 -0
  278. package/src/__tests__/thread-resume.test.ts +710 -0
  279. package/src/__tests__/thread-show-status.test.ts +317 -0
  280. package/src/__tests__/thread-start-cwd-cli.test.ts +164 -0
  281. package/src/__tests__/thread-step-count.test.ts +70 -0
  282. package/src/__tests__/thread-suspend-step.test.ts +181 -0
  283. package/src/__tests__/thread-suspended-display.test.ts +287 -0
  284. package/src/__tests__/thread-test-helpers.ts +37 -0
  285. package/src/__tests__/thread.test.ts +1025 -0
  286. package/src/__tests__/validate-semantic.test.ts +474 -0
  287. package/src/__tests__/workflow-resolution.test.ts +421 -0
  288. package/src/background/background.ts +147 -0
  289. package/src/background/index.ts +11 -0
  290. package/src/background/types.ts +9 -0
  291. package/src/cli.ts +692 -0
  292. package/src/commands/config.ts +304 -0
  293. package/src/commands/log.ts +116 -0
  294. package/src/commands/prompt.ts +81 -0
  295. package/src/commands/setup.ts +603 -0
  296. package/src/commands/shared.ts +227 -0
  297. package/src/commands/step.ts +343 -0
  298. package/src/commands/thread-time-parser.ts +23 -0
  299. package/src/commands/thread.ts +1575 -0
  300. package/src/commands/workflow.ts +213 -0
  301. package/src/format.ts +12 -0
  302. package/src/include.ts +37 -0
  303. package/src/moderator/__tests__/evaluate.test.ts +199 -0
  304. package/src/moderator/evaluate.ts +80 -0
  305. package/src/moderator/index.ts +7 -0
  306. package/src/moderator/types.ts +24 -0
  307. package/src/schemas.ts +26 -0
  308. package/src/store.ts +479 -0
  309. package/src/validate-semantic.ts +304 -0
  310. package/src/validate.ts +137 -0
@@ -0,0 +1,311 @@
1
+ import { mkdir, mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { bootstrap, putSchema } from "@ocas/core";
5
+ import { openStore } from "@ocas/fs";
6
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
7
+ import { cmdStepShow } from "../commands/step.js";
8
+ import { formatOutput } from "../format.js";
9
+ import { registerUwfSchemas } from "../schemas.js";
10
+ const TURN_SCHEMA = {
11
+ title: "test-turn",
12
+ type: "object",
13
+ required: ["index", "role", "content"],
14
+ properties: {
15
+ index: { type: "integer" },
16
+ role: { type: "string", enum: ["assistant", "tool"] },
17
+ content: { type: "string" },
18
+ toolCalls: {
19
+ anyOf: [
20
+ {
21
+ type: "array",
22
+ items: {
23
+ type: "object",
24
+ required: ["name", "args"],
25
+ properties: {
26
+ name: { type: "string" },
27
+ args: { type: "string" },
28
+ },
29
+ additionalProperties: false,
30
+ },
31
+ },
32
+ { type: "null" },
33
+ ],
34
+ },
35
+ },
36
+ additionalProperties: false,
37
+ };
38
+ const DETAIL_SCHEMA = {
39
+ title: "test-detail",
40
+ type: "object",
41
+ required: ["turns"],
42
+ properties: {
43
+ turns: {
44
+ type: "array",
45
+ items: { type: "string", format: "ocas_ref" },
46
+ },
47
+ },
48
+ additionalProperties: false,
49
+ };
50
+ async function setupTest(casDir) {
51
+ const store = await openStore(casDir);
52
+ await bootstrap(store);
53
+ const schemas = await registerUwfSchemas(store);
54
+ const [turnType, detailType] = await Promise.all([
55
+ putSchema(store, TURN_SCHEMA),
56
+ putSchema(store, DETAIL_SCHEMA),
57
+ ]);
58
+ return { store, schemas, turnType, detailType };
59
+ }
60
+ async function createTestStep(setup, turnPayloads) {
61
+ const { store, schemas, turnType, detailType } = setup;
62
+ // Create turn nodes
63
+ const turnHashes = [];
64
+ for (const payload of turnPayloads) {
65
+ const turnHash = await store.cas.put(turnType, payload);
66
+ turnHashes.push(turnHash);
67
+ }
68
+ // Create detail node
69
+ const detailHash = await store.cas.put(detailType, { turns: turnHashes });
70
+ // Create dummy start node
71
+ const startHash = await store.cas.put(schemas.startNode, {
72
+ workflow: "0000000000000",
73
+ prompt: "test prompt",
74
+ cwd: "/tmp",
75
+ });
76
+ // Create dummy output node
77
+ const outputHash = await store.cas.put(schemas.text, { $status: "done" });
78
+ // Create step node
79
+ const stepPayload = {
80
+ prev: null,
81
+ start: startHash,
82
+ role: "test-role",
83
+ agent: "test-agent",
84
+ output: outputHash,
85
+ detail: detailHash,
86
+ edgePrompt: "",
87
+ startedAtMs: Date.now(),
88
+ completedAtMs: Date.now() + 1000,
89
+ assembledPrompt: null,
90
+ cwd: "/tmp",
91
+ usage: null,
92
+ };
93
+ return store.cas.put(schemas.stepNode, stepPayload);
94
+ }
95
+ describe("cmdStepShow JSON serialization", () => {
96
+ let testDir;
97
+ let casDir;
98
+ let originalEnv;
99
+ beforeEach(async () => {
100
+ testDir = await mkdtemp(join(tmpdir(), "uwf-test-"));
101
+ casDir = join(testDir, "cas");
102
+ await mkdir(casDir, { recursive: true });
103
+ originalEnv = process.env.OCAS_HOME;
104
+ process.env.OCAS_HOME = casDir;
105
+ });
106
+ afterEach(async () => {
107
+ await rm(testDir, { recursive: true, force: true });
108
+ if (originalEnv === undefined) {
109
+ delete process.env.OCAS_HOME;
110
+ }
111
+ else {
112
+ process.env.OCAS_HOME = originalEnv;
113
+ }
114
+ });
115
+ test("escapes newlines in tool call args", async () => {
116
+ const setup = await setupTest(casDir);
117
+ const stepHash = await createTestStep(setup, [
118
+ {
119
+ index: 0,
120
+ role: "assistant",
121
+ content: "Running command",
122
+ toolCalls: [
123
+ {
124
+ name: "Bash",
125
+ args: "echo 'line1'\necho 'line2'",
126
+ },
127
+ ],
128
+ },
129
+ ]);
130
+ const result = await cmdStepShow(testDir, stepHash);
131
+ const jsonOutput = formatOutput(result, "json");
132
+ expect(() => JSON.parse(jsonOutput)).not.toThrow();
133
+ expect(jsonOutput).toContain("\\n");
134
+ const parsed = JSON.parse(jsonOutput);
135
+ expect(parsed.turns[0].toolCalls[0].args).toContain("\n");
136
+ });
137
+ test("escapes tabs in tool call args", async () => {
138
+ const setup = await setupTest(casDir);
139
+ const stepHash = await createTestStep(setup, [
140
+ {
141
+ index: 0,
142
+ role: "assistant",
143
+ content: "",
144
+ toolCalls: [
145
+ {
146
+ name: "Bash",
147
+ args: "cat <<EOF\nfield1\tfield2\tfield3\nEOF",
148
+ },
149
+ ],
150
+ },
151
+ ]);
152
+ const result = await cmdStepShow(testDir, stepHash);
153
+ const jsonOutput = formatOutput(result, "json");
154
+ expect(() => JSON.parse(jsonOutput)).not.toThrow();
155
+ expect(jsonOutput).toContain("\\t");
156
+ });
157
+ test("escapes carriage returns", async () => {
158
+ const setup = await setupTest(casDir);
159
+ const stepHash = await createTestStep(setup, [
160
+ {
161
+ index: 0,
162
+ role: "assistant",
163
+ content: "Committing changes",
164
+ toolCalls: [
165
+ {
166
+ name: "Bash",
167
+ args: 'git commit -m "First line\r\nSecond line"',
168
+ },
169
+ ],
170
+ },
171
+ ]);
172
+ const result = await cmdStepShow(testDir, stepHash);
173
+ const jsonOutput = formatOutput(result, "json");
174
+ expect(() => JSON.parse(jsonOutput)).not.toThrow();
175
+ expect(jsonOutput).toContain("\\r\\n");
176
+ });
177
+ test("escapes backslashes and quotes", async () => {
178
+ const setup = await setupTest(casDir);
179
+ const stepHash = await createTestStep(setup, [
180
+ {
181
+ index: 0,
182
+ role: "assistant",
183
+ content: "",
184
+ toolCalls: [
185
+ {
186
+ name: "Bash",
187
+ args: 'echo "He said \\"hello\\""',
188
+ },
189
+ ],
190
+ },
191
+ ]);
192
+ const result = await cmdStepShow(testDir, stepHash);
193
+ const jsonOutput = formatOutput(result, "json");
194
+ expect(() => JSON.parse(jsonOutput)).not.toThrow();
195
+ const parsed = JSON.parse(jsonOutput);
196
+ expect(parsed.turns).toBeDefined();
197
+ });
198
+ test("handles Unicode control characters", async () => {
199
+ const setup = await setupTest(casDir);
200
+ const stepHash = await createTestStep(setup, [
201
+ {
202
+ index: 0,
203
+ role: "assistant",
204
+ content: "",
205
+ toolCalls: [
206
+ {
207
+ name: "Bash",
208
+ args: "echo '\u0001\u001F'",
209
+ },
210
+ ],
211
+ },
212
+ ]);
213
+ const result = await cmdStepShow(testDir, stepHash);
214
+ const jsonOutput = formatOutput(result, "json");
215
+ expect(() => JSON.parse(jsonOutput)).not.toThrow();
216
+ });
217
+ test("handles nested CAS refs with control characters", async () => {
218
+ const setup = await setupTest(casDir);
219
+ const stepHash = await createTestStep(setup, [
220
+ {
221
+ index: 0,
222
+ role: "assistant",
223
+ content: "First turn\nwith newline",
224
+ toolCalls: [
225
+ {
226
+ name: "Bash",
227
+ args: "cmd1\nline2",
228
+ },
229
+ ],
230
+ },
231
+ {
232
+ index: 1,
233
+ role: "assistant",
234
+ content: "Second turn\twith tab",
235
+ toolCalls: null,
236
+ },
237
+ ]);
238
+ const result = await cmdStepShow(testDir, stepHash);
239
+ const jsonOutput = formatOutput(result, "json");
240
+ expect(() => JSON.parse(jsonOutput)).not.toThrow();
241
+ const parsed = JSON.parse(jsonOutput);
242
+ expect(parsed.turns).toHaveLength(2);
243
+ });
244
+ test("YAML output format is unaffected", async () => {
245
+ const setup = await setupTest(casDir);
246
+ const stepHash = await createTestStep(setup, [
247
+ {
248
+ index: 0,
249
+ role: "assistant",
250
+ content: "Running command",
251
+ toolCalls: [
252
+ {
253
+ name: "Bash",
254
+ args: "echo 'line1'\necho 'line2'",
255
+ },
256
+ ],
257
+ },
258
+ ]);
259
+ const result = await cmdStepShow(testDir, stepHash);
260
+ const yamlOutput = formatOutput(result, "yaml");
261
+ expect(yamlOutput).toContain("turns:");
262
+ expect(yamlOutput.length).toBeGreaterThan(0);
263
+ });
264
+ test("handles empty and null values", async () => {
265
+ const setup = await setupTest(casDir);
266
+ const stepHash = await createTestStep(setup, [
267
+ {
268
+ index: 0,
269
+ role: "assistant",
270
+ content: "",
271
+ toolCalls: null,
272
+ },
273
+ ]);
274
+ const result = await cmdStepShow(testDir, stepHash);
275
+ const jsonOutput = formatOutput(result, "json");
276
+ expect(() => JSON.parse(jsonOutput)).not.toThrow();
277
+ const parsed = JSON.parse(jsonOutput);
278
+ expect(parsed.turns).toBeDefined();
279
+ });
280
+ test("handles large step with multiple tool calls", async () => {
281
+ const setup = await setupTest(casDir);
282
+ const turns = [];
283
+ for (let i = 0; i < 25; i++) {
284
+ turns.push({
285
+ index: i,
286
+ role: "assistant",
287
+ content: `Turn ${i}\nwith newline`,
288
+ toolCalls: [
289
+ {
290
+ name: "Bash",
291
+ args: `command${i}\nline2\tfield${i}`,
292
+ },
293
+ {
294
+ name: "Read",
295
+ args: `/path/to/file${i}`,
296
+ },
297
+ ],
298
+ });
299
+ }
300
+ const stepHash = await createTestStep(setup, turns);
301
+ const startTime = Date.now();
302
+ const result = await cmdStepShow(testDir, stepHash);
303
+ const jsonOutput = formatOutput(result, "json");
304
+ const duration = Date.now() - startTime;
305
+ expect(duration).toBeLessThan(2000);
306
+ expect(() => JSON.parse(jsonOutput)).not.toThrow();
307
+ const parsed = JSON.parse(jsonOutput);
308
+ expect(parsed.turns).toHaveLength(25);
309
+ });
310
+ });
311
+ //# sourceMappingURL=step-show-json.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step-show-json.test.js","sourceRoot":"","sources":["../../src/__tests__/step-show-json.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=step-timing.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"step-timing.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/step-timing.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,345 @@
1
+ import { mkdir, mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { bootstrap, putSchema } from "@ocas/core";
5
+ import { openStore } from "@ocas/fs";
6
+ import { STEP_NODE_SCHEMA } from "@united-workforce/protocol";
7
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
8
+ import { cmdStepList } from "../commands/step.js";
9
+ import { cmdThreadRead } from "../commands/thread.js";
10
+ import { registerUwfSchemas } from "../schemas.js";
11
+ import { seedThreads } from "./thread-test-helpers.js";
12
+ // ── schemas ──────────────────────────────────────────────────────────────────
13
+ const TURN_SCHEMA = {
14
+ title: "hermes-turn",
15
+ type: "object",
16
+ required: ["index", "role", "content"],
17
+ properties: {
18
+ index: { type: "integer" },
19
+ role: { type: "string" },
20
+ content: { type: "string" },
21
+ toolCalls: {
22
+ anyOf: [
23
+ { type: "array", items: { type: "object" } },
24
+ { type: "null" },
25
+ ],
26
+ },
27
+ reasoning: { anyOf: [{ type: "string" }, { type: "null" }] },
28
+ },
29
+ additionalProperties: false,
30
+ };
31
+ const DETAIL_SCHEMA = {
32
+ title: "hermes-detail",
33
+ type: "object",
34
+ required: ["sessionId", "model", "duration", "turnCount", "turns"],
35
+ properties: {
36
+ sessionId: { type: "string" },
37
+ model: { type: "string" },
38
+ duration: { type: "integer" },
39
+ turnCount: { type: "integer" },
40
+ turns: {
41
+ type: "array",
42
+ items: { type: "string", format: "ocas_ref" },
43
+ },
44
+ },
45
+ additionalProperties: false,
46
+ };
47
+ // ── helpers ──────────────────────────────────────────────────────────────────
48
+ async function registerDetailSchemas(store) {
49
+ await bootstrap(store);
50
+ const [turn, detail] = await Promise.all([
51
+ putSchema(store, TURN_SCHEMA),
52
+ putSchema(store, DETAIL_SCHEMA),
53
+ ]);
54
+ return { turn, detail };
55
+ }
56
+ // ── fixture ──────────────────────────────────────────────────────────────────
57
+ let tmpDir;
58
+ let originalEnv;
59
+ beforeEach(async () => {
60
+ tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-step-timing-test-"));
61
+ originalEnv = process.env.OCAS_HOME;
62
+ process.env.OCAS_HOME = join(tmpDir, "cas");
63
+ await mkdir(process.env.OCAS_HOME, { recursive: true });
64
+ });
65
+ afterEach(async () => {
66
+ await rm(tmpDir, { recursive: true, force: true });
67
+ if (originalEnv === undefined) {
68
+ delete process.env.OCAS_HOME;
69
+ }
70
+ else {
71
+ process.env.OCAS_HOME = originalEnv;
72
+ }
73
+ });
74
+ // ── 1. Protocol types (compile-time) ─────────────────────────────────────────
75
+ describe("protocol types", () => {
76
+ test("StepRecord has startedAtMs and completedAtMs as required fields", () => {
77
+ // Type-level test: this block compiles only if fields exist and are number
78
+ const record = {
79
+ role: "test",
80
+ output: "hash1",
81
+ detail: "hash2",
82
+ agent: "uwf-test",
83
+ edgePrompt: "",
84
+ startedAtMs: 1000,
85
+ completedAtMs: 2000,
86
+ assembledPrompt: null,
87
+ cwd: "/test/path",
88
+ usage: null,
89
+ };
90
+ expect(record.startedAtMs).toBe(1000);
91
+ expect(record.completedAtMs).toBe(2000);
92
+ });
93
+ test("StepEntry has durationMs as required field", () => {
94
+ const entry = {
95
+ hash: "hash",
96
+ role: "test",
97
+ output: {},
98
+ detail: "hash2",
99
+ agent: "uwf-test",
100
+ timestamp: 123,
101
+ durationMs: 5000,
102
+ usage: null,
103
+ };
104
+ expect(entry.durationMs).toBe(5000);
105
+ });
106
+ });
107
+ // ── 2. JSON Schema ───────────────────────────────────────────────────────────
108
+ describe("StepNode JSON schema", () => {
109
+ test("schema requires startedAtMs and completedAtMs", () => {
110
+ const required = STEP_NODE_SCHEMA.required;
111
+ expect(required).toContain("startedAtMs");
112
+ expect(required).toContain("completedAtMs");
113
+ });
114
+ test("schema defines timing fields as integer", () => {
115
+ const props = STEP_NODE_SCHEMA.properties;
116
+ expect(props.startedAtMs.type).toBe("integer");
117
+ expect(props.completedAtMs.type).toBe("integer");
118
+ });
119
+ test("StepNode with timing fields passes CAS validation", async () => {
120
+ const casDir = join(tmpDir, "cas");
121
+ await mkdir(casDir, { recursive: true });
122
+ const store = await openStore(casDir);
123
+ const schemas = await registerUwfSchemas(store);
124
+ const startHash = await store.cas.put(schemas.startNode, {
125
+ workflow: "placeholder0000",
126
+ prompt: "test",
127
+ });
128
+ const outputHash = await store.cas.put(schemas.text, "output text");
129
+ const detailSchemas = await registerDetailSchemas(store);
130
+ const detailHash = await store.cas.put(detailSchemas.detail, {
131
+ sessionId: "s1",
132
+ model: "m1",
133
+ duration: 100,
134
+ turnCount: 0,
135
+ turns: [],
136
+ });
137
+ // Should succeed — valid timing fields
138
+ const hash = await store.cas.put(schemas.stepNode, {
139
+ start: startHash,
140
+ prev: null,
141
+ role: "worker",
142
+ output: outputHash,
143
+ detail: detailHash,
144
+ agent: "uwf-test",
145
+ edgePrompt: "",
146
+ startedAtMs: 1000000000000,
147
+ completedAtMs: 1000000005000,
148
+ assembledPrompt: null,
149
+ });
150
+ expect(hash).toBeTruthy();
151
+ });
152
+ });
153
+ // ── 3. step list — durationMs computed ───────────────────────────────────────
154
+ describe("step list timing", () => {
155
+ test("step list includes durationMs = completedAtMs - startedAtMs", async () => {
156
+ const casDir = join(tmpDir, "cas");
157
+ await mkdir(casDir, { recursive: true });
158
+ const store = await openStore(casDir);
159
+ const schemas = await registerUwfSchemas(store);
160
+ const detailSchemas = await registerDetailSchemas(store);
161
+ const workflowHash = await store.cas.put(schemas.workflow, {
162
+ name: "test-wf",
163
+ description: "desc",
164
+ roles: {},
165
+ graph: {},
166
+ });
167
+ const startHash = await store.cas.put(schemas.startNode, {
168
+ workflow: workflowHash,
169
+ prompt: "test",
170
+ });
171
+ const outputHash = await store.cas.put(schemas.text, "output");
172
+ const detailHash = await store.cas.put(detailSchemas.detail, {
173
+ sessionId: "s1",
174
+ model: "m1",
175
+ duration: 100,
176
+ turnCount: 0,
177
+ turns: [],
178
+ });
179
+ const startedAt = 1716600000000;
180
+ const completedAt = 1716600003500;
181
+ const stepHash = await store.cas.put(schemas.stepNode, {
182
+ start: startHash,
183
+ prev: null,
184
+ role: "worker",
185
+ output: outputHash,
186
+ detail: detailHash,
187
+ agent: "uwf-test",
188
+ edgePrompt: "",
189
+ startedAtMs: startedAt,
190
+ completedAtMs: completedAt,
191
+ });
192
+ const threadId = "01HX2Q3R4S5T6V7W8X9YZ1";
193
+ await seedThreads(tmpDir, { [threadId]: stepHash });
194
+ const result = await cmdStepList(tmpDir, threadId);
195
+ const stepEntries = result.steps.slice(1); // skip start entry
196
+ expect(stepEntries).toHaveLength(1);
197
+ const step = stepEntries[0];
198
+ expect(step.durationMs).toBe(3500);
199
+ });
200
+ });
201
+ // ── 4. thread read — duration in header ──────────────────────────────────────
202
+ describe("thread read timing", () => {
203
+ test("thread read header includes Duration", async () => {
204
+ const casDir = join(tmpDir, "cas");
205
+ await mkdir(casDir, { recursive: true });
206
+ const store = await openStore(casDir);
207
+ const schemas = await registerUwfSchemas(store);
208
+ const detailSchemas = await registerDetailSchemas(store);
209
+ const workflowHash = await store.cas.put(schemas.workflow, {
210
+ name: "test-wf",
211
+ description: "desc",
212
+ roles: {
213
+ worker: {
214
+ description: "Worker",
215
+ goal: "Do work",
216
+ capabilities: [],
217
+ procedure: "work",
218
+ output: "result",
219
+ frontmatter: "placeholder0000",
220
+ },
221
+ },
222
+ graph: {
223
+ $START: { _: { role: "worker", prompt: "go", location: null } },
224
+ worker: { done: { role: "$END", prompt: "", location: null } },
225
+ },
226
+ });
227
+ const startHash = await store.cas.put(schemas.startNode, {
228
+ workflow: workflowHash,
229
+ prompt: "test task",
230
+ });
231
+ const turnHash = await store.cas.put(detailSchemas.turn, {
232
+ index: 0,
233
+ role: "assistant",
234
+ content: "Done.",
235
+ toolCalls: null,
236
+ reasoning: null,
237
+ });
238
+ const detailHash = await store.cas.put(detailSchemas.detail, {
239
+ sessionId: "s1",
240
+ model: "m1",
241
+ duration: 100,
242
+ turnCount: 1,
243
+ turns: [turnHash],
244
+ });
245
+ const outputHash = await store.cas.put(schemas.text, "output");
246
+ const stepHash = await store.cas.put(schemas.stepNode, {
247
+ start: startHash,
248
+ prev: null,
249
+ role: "worker",
250
+ output: outputHash,
251
+ detail: detailHash,
252
+ agent: "uwf-test",
253
+ edgePrompt: "",
254
+ startedAtMs: 1716600000000,
255
+ completedAtMs: 1716600042000,
256
+ });
257
+ const threadId = "01HX2Q3R4S5T6V7W8X9YZ3";
258
+ await seedThreads(tmpDir, { [threadId]: stepHash });
259
+ const markdown = await cmdThreadRead(tmpDir, threadId, 10000, null, false);
260
+ expect(markdown).toContain("**Duration:** 42.0s");
261
+ });
262
+ test("thread read shows sub-second duration as ms", async () => {
263
+ const casDir = join(tmpDir, "cas");
264
+ await mkdir(casDir, { recursive: true });
265
+ const store = await openStore(casDir);
266
+ const schemas = await registerUwfSchemas(store);
267
+ const detailSchemas = await registerDetailSchemas(store);
268
+ const workflowHash = await store.cas.put(schemas.workflow, {
269
+ name: "test-wf",
270
+ description: "desc",
271
+ roles: {
272
+ worker: {
273
+ description: "Worker",
274
+ goal: "Do work",
275
+ capabilities: [],
276
+ procedure: "work",
277
+ output: "result",
278
+ frontmatter: "placeholder0000",
279
+ },
280
+ },
281
+ graph: {
282
+ $START: { _: { role: "worker", prompt: "go", location: null } },
283
+ worker: { done: { role: "$END", prompt: "", location: null } },
284
+ },
285
+ });
286
+ const startHash = await store.cas.put(schemas.startNode, {
287
+ workflow: workflowHash,
288
+ prompt: "test",
289
+ });
290
+ const turnHash = await store.cas.put(detailSchemas.turn, {
291
+ index: 0,
292
+ role: "assistant",
293
+ content: "Done.",
294
+ toolCalls: null,
295
+ reasoning: null,
296
+ });
297
+ const detailHash = await store.cas.put(detailSchemas.detail, {
298
+ sessionId: "s1",
299
+ model: "m1",
300
+ duration: 100,
301
+ turnCount: 1,
302
+ turns: [turnHash],
303
+ });
304
+ const outputHash = await store.cas.put(schemas.text, "output");
305
+ const stepHash = await store.cas.put(schemas.stepNode, {
306
+ start: startHash,
307
+ prev: null,
308
+ role: "worker",
309
+ output: outputHash,
310
+ detail: detailHash,
311
+ agent: "uwf-test",
312
+ edgePrompt: "",
313
+ startedAtMs: 1716600000000,
314
+ completedAtMs: 1716600000350,
315
+ });
316
+ const threadId = "01HX2Q3R4S5T6V7W8X9YZ4";
317
+ await seedThreads(tmpDir, { [threadId]: stepHash });
318
+ const markdown = await cmdThreadRead(tmpDir, threadId, 10000, null, false);
319
+ expect(markdown).toContain("**Duration:** 350ms");
320
+ });
321
+ });
322
+ // ── 6. Breaking change — old data without timing fails ───────────────────────
323
+ describe("breaking change", () => {
324
+ test("StepNode schema rejects payload without timing fields", () => {
325
+ const required = STEP_NODE_SCHEMA.required;
326
+ // Both fields must be in the required array
327
+ expect(required).toContain("startedAtMs");
328
+ expect(required).toContain("completedAtMs");
329
+ // Payload without timing fields would fail schema validation
330
+ // because the schema marks them as required
331
+ const payloadWithoutTiming = {
332
+ start: "hash1",
333
+ prev: null,
334
+ role: "worker",
335
+ output: "hash2",
336
+ detail: "hash3",
337
+ agent: "uwf-test",
338
+ edgePrompt: "",
339
+ };
340
+ // Verify the payload is missing required fields
341
+ expect(payloadWithoutTiming).not.toHaveProperty("startedAtMs");
342
+ expect(payloadWithoutTiming).not.toHaveProperty("completedAtMs");
343
+ });
344
+ });
345
+ //# sourceMappingURL=step-timing.test.js.map