@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,717 @@
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, type Store } from "@ocas/core";
5
+ import type { CasRef, ThreadId } from "@united-workforce/protocol";
6
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
7
+ import { cmdThreadRead, THREAD_READ_DEFAULT_QUOTA } from "../commands/thread.js";
8
+ import type { UwfStore } from "../store.js";
9
+ import { createUwfStore } from "../store.js";
10
+ import { seedThreads } from "./thread-test-helpers.js";
11
+
12
+ // ── schemas used in tests ────────────────────────────────────────────────────
13
+
14
+ const TURN_SCHEMA = {
15
+ title: "hermes-turn",
16
+ type: "object" as const,
17
+ required: ["index", "role", "content"],
18
+ properties: {
19
+ index: { type: "integer" as const },
20
+ role: { type: "string" as const },
21
+ content: { type: "string" as const },
22
+ toolCalls: {
23
+ anyOf: [
24
+ { type: "array" as const, items: { type: "object" as const } },
25
+ { type: "null" as const },
26
+ ],
27
+ },
28
+ reasoning: { anyOf: [{ type: "string" as const }, { type: "null" as const }] },
29
+ },
30
+ additionalProperties: false,
31
+ };
32
+
33
+ const DETAIL_SCHEMA = {
34
+ title: "hermes-detail",
35
+ type: "object" as const,
36
+ required: ["sessionId", "model", "duration", "turnCount", "turns"],
37
+ properties: {
38
+ sessionId: { type: "string" as const },
39
+ model: { type: "string" as const },
40
+ duration: { type: "integer" as const },
41
+ turnCount: { type: "integer" as const },
42
+ turns: {
43
+ type: "array" as const,
44
+ items: { type: "string" as const, format: "ocas_ref" },
45
+ },
46
+ },
47
+ additionalProperties: false,
48
+ };
49
+
50
+ // ── helpers ───────────────────────────────────────────────────────────────────
51
+
52
+ async function makeUwfStore(storageRoot: string): Promise<UwfStore> {
53
+ const casDir = join(storageRoot, "cas");
54
+ await mkdir(casDir, { recursive: true });
55
+ process.env.OCAS_HOME = casDir;
56
+ return createUwfStore(storageRoot);
57
+ }
58
+
59
+ async function registerDetailSchemas(store: Store) {
60
+ await bootstrap(store);
61
+ const [turn, detail] = await Promise.all([
62
+ putSchema(store, TURN_SCHEMA),
63
+ putSchema(store, DETAIL_SCHEMA),
64
+ ]);
65
+ return { turn, detail };
66
+ }
67
+
68
+ // ── fixture ───────────────────────────────────────────────────────────────────
69
+
70
+ let tmpDir: string;
71
+
72
+ beforeEach(async () => {
73
+ tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-test-"));
74
+ });
75
+
76
+ afterEach(async () => {
77
+ await rm(tmpDir, { recursive: true, force: true });
78
+ });
79
+
80
+ // ── thread read XML tag isolation ─────────────────────────────────────────────
81
+
82
+ describe("thread read XML tag isolation", () => {
83
+ test("scenario 1: wraps output in XML tags instead of heading", async () => {
84
+ const uwf = await makeUwfStore(tmpDir);
85
+ const detailSchemas = await registerDetailSchemas(uwf.store);
86
+
87
+ const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
88
+ name: "test-wf",
89
+ description: "desc",
90
+ roles: {
91
+ planner: {
92
+ description: "Planner",
93
+ goal: "You are a planning agent. Your task is to...",
94
+ capabilities: [],
95
+ procedure: "Plan the work.",
96
+ output: "Summarize the plan.",
97
+ meta: "placeholder00" as CasRef,
98
+ },
99
+ },
100
+ conditions: {},
101
+ graph: {},
102
+ });
103
+
104
+ const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
105
+ workflow: workflowHash,
106
+ prompt: "Fix issue #459",
107
+ });
108
+
109
+ const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
110
+ name: "out",
111
+ description: "",
112
+ roles: {},
113
+ conditions: {},
114
+ graph: {},
115
+ });
116
+
117
+ const turnHash = await uwf.store.cas.put(detailSchemas.turn, {
118
+ index: 0,
119
+ role: "assistant",
120
+ content:
121
+ "---\nstatus: ready\nplan: CMWGHQKT58RY4\n---\n\n# Analysis Complete\n## Issue Summary\nThe issue requires XML tag isolation.",
122
+ toolCalls: null,
123
+ reasoning: null,
124
+ });
125
+ const detailHash = await uwf.store.cas.put(detailSchemas.detail, {
126
+ sessionId: "sx",
127
+ model: "mx",
128
+ duration: 500,
129
+ turnCount: 1,
130
+ turns: [turnHash],
131
+ });
132
+
133
+ const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
134
+ start: startHash,
135
+ prev: null,
136
+ role: "planner",
137
+ output: outputHash,
138
+ detail: detailHash,
139
+ agent: "uwf-claude-code",
140
+ startedAtMs: 1000000000000,
141
+ completedAtMs: 1000000005000,
142
+ assembledPrompt: null,
143
+ });
144
+
145
+ const threadId = "01JTEST0000000000000001" as ThreadId;
146
+ await seedThreads(tmpDir, { [threadId]: stepHash });
147
+
148
+ const markdown = await cmdThreadRead(tmpDir, threadId, THREAD_READ_DEFAULT_QUOTA, null, false);
149
+
150
+ // Should wrap output in XML tags
151
+ expect(markdown).toContain("<output>");
152
+ expect(markdown).toContain("</output>");
153
+
154
+ // Should not have ### Content heading
155
+ expect(markdown).not.toContain("### Content");
156
+
157
+ // Should preserve markdown headings inside output tags
158
+ expect(markdown).toContain("# Analysis Complete");
159
+ expect(markdown).toContain("## Issue Summary");
160
+ });
161
+
162
+ test("scenario 2: wraps prompt in XML tags", async () => {
163
+ const uwf = await makeUwfStore(tmpDir);
164
+ const detailSchemas = await registerDetailSchemas(uwf.store);
165
+
166
+ const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
167
+ name: "test-wf",
168
+ description: "desc",
169
+ roles: {
170
+ planner: {
171
+ description: "Planner",
172
+ goal: "You are a planning agent. Your task is to analyze and plan.",
173
+ capabilities: [],
174
+ procedure: "Plan the work.",
175
+ output: "Summarize the plan.",
176
+ meta: "placeholder00" as CasRef,
177
+ },
178
+ },
179
+ conditions: {},
180
+ graph: {},
181
+ });
182
+
183
+ const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
184
+ workflow: workflowHash,
185
+ prompt: "Fix issue",
186
+ });
187
+
188
+ const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
189
+ name: "out",
190
+ description: "",
191
+ roles: {},
192
+ conditions: {},
193
+ graph: {},
194
+ });
195
+
196
+ const turnHash = await uwf.store.cas.put(detailSchemas.turn, {
197
+ index: 0,
198
+ role: "assistant",
199
+ content: "---\nstatus: ready\n---\n\nContent here...",
200
+ toolCalls: null,
201
+ reasoning: null,
202
+ });
203
+ const detailHash = await uwf.store.cas.put(detailSchemas.detail, {
204
+ sessionId: "sx",
205
+ model: "mx",
206
+ duration: 500,
207
+ turnCount: 1,
208
+ turns: [turnHash],
209
+ });
210
+
211
+ const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
212
+ start: startHash,
213
+ prev: null,
214
+ role: "planner",
215
+ output: outputHash,
216
+ detail: detailHash,
217
+ agent: "uwf-claude-code",
218
+ startedAtMs: 1000000000000,
219
+ completedAtMs: 1000000005000,
220
+ assembledPrompt: null,
221
+ });
222
+
223
+ const threadId = "01JTEST0000000000000002" as ThreadId;
224
+ await seedThreads(tmpDir, { [threadId]: stepHash });
225
+
226
+ const markdown = await cmdThreadRead(tmpDir, threadId, THREAD_READ_DEFAULT_QUOTA, null, false);
227
+
228
+ // Should wrap prompt in XML tags
229
+ expect(markdown).toContain("<prompt>");
230
+ expect(markdown).toContain("</prompt>");
231
+ expect(markdown).toContain("You are a planning agent. Your task is to analyze and plan.");
232
+
233
+ // Should not have ### Prompt heading
234
+ expect(markdown).not.toContain("### Prompt");
235
+
236
+ // Should wrap output in XML tags
237
+ expect(markdown).toContain("<output>");
238
+ expect(markdown).toContain("</output>");
239
+ });
240
+
241
+ test("scenario 3: same role repeated does not show prompt twice", async () => {
242
+ const uwf = await makeUwfStore(tmpDir);
243
+
244
+ const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
245
+ name: "test-wf",
246
+ description: "desc",
247
+ roles: {
248
+ writer: {
249
+ description: "Writer",
250
+ goal: "You are a writer agent.",
251
+ capabilities: [],
252
+ procedure: "Write content.",
253
+ output: "Summarize writing.",
254
+ meta: "placeholder00" as CasRef,
255
+ },
256
+ },
257
+ conditions: {},
258
+ graph: {},
259
+ });
260
+
261
+ const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
262
+ workflow: workflowHash,
263
+ prompt: "Write something",
264
+ });
265
+
266
+ const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
267
+ name: "out",
268
+ description: "",
269
+ roles: {},
270
+ conditions: {},
271
+ graph: {},
272
+ });
273
+
274
+ const step1 = await uwf.store.cas.put(uwf.schemas.stepNode, {
275
+ start: startHash,
276
+ prev: null,
277
+ role: "writer",
278
+ output: outputHash,
279
+ detail: null,
280
+ agent: "uwf-test",
281
+ startedAtMs: 1000000000000,
282
+ completedAtMs: 1000000005000,
283
+ assembledPrompt: null,
284
+ });
285
+
286
+ const step2 = await uwf.store.cas.put(uwf.schemas.stepNode, {
287
+ start: startHash,
288
+ prev: step1 as CasRef,
289
+ role: "writer",
290
+ output: outputHash,
291
+ detail: null,
292
+ agent: "uwf-test",
293
+ startedAtMs: 1000000000000,
294
+ completedAtMs: 1000000005000,
295
+ assembledPrompt: null,
296
+ });
297
+
298
+ const threadId = "01JTEST0000000000000003" as ThreadId;
299
+ await seedThreads(tmpDir, { [threadId]: step2 });
300
+
301
+ const markdown = await cmdThreadRead(tmpDir, threadId, THREAD_READ_DEFAULT_QUOTA, null, false);
302
+
303
+ // Should only show prompt tags once
304
+ const promptCount = (markdown.match(/<prompt>/g) ?? []).length;
305
+ expect(promptCount).toBe(1);
306
+ });
307
+
308
+ test("scenario 4: step with no detail shows no output tags", async () => {
309
+ const uwf = await makeUwfStore(tmpDir);
310
+
311
+ const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
312
+ name: "test-wf",
313
+ description: "desc",
314
+ roles: {
315
+ worker: {
316
+ description: "Worker",
317
+ goal: "You are a worker agent.",
318
+ capabilities: [],
319
+ procedure: "Do work.",
320
+ output: "Summarize work.",
321
+ meta: "placeholder00" as CasRef,
322
+ },
323
+ },
324
+ conditions: {},
325
+ graph: {},
326
+ });
327
+
328
+ const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
329
+ workflow: workflowHash,
330
+ prompt: "Do stuff",
331
+ });
332
+
333
+ const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
334
+ name: "out",
335
+ description: "",
336
+ roles: {},
337
+ conditions: {},
338
+ graph: {},
339
+ });
340
+
341
+ const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
342
+ start: startHash,
343
+ prev: null,
344
+ role: "worker",
345
+ output: outputHash,
346
+ detail: null,
347
+ agent: "uwf-test",
348
+ startedAtMs: 1000000000000,
349
+ completedAtMs: 1000000005000,
350
+ assembledPrompt: null,
351
+ });
352
+
353
+ const threadId = "01JTEST0000000000000004" as ThreadId;
354
+ await seedThreads(tmpDir, { [threadId]: stepHash });
355
+
356
+ const markdown = await cmdThreadRead(tmpDir, threadId, THREAD_READ_DEFAULT_QUOTA, null, false);
357
+
358
+ // Should not have output tags
359
+ expect(markdown).not.toContain("<output>");
360
+ expect(markdown).not.toContain("</output>");
361
+
362
+ // Step header should still be displayed
363
+ expect(markdown).toContain("## Step 1: worker");
364
+
365
+ // Prompt should still be shown
366
+ expect(markdown).toContain("<prompt>");
367
+ });
368
+
369
+ test("scenario 5: empty content shows no output tags", async () => {
370
+ const uwf = await makeUwfStore(tmpDir);
371
+
372
+ const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
373
+ name: "test-wf",
374
+ description: "desc",
375
+ roles: {},
376
+ conditions: {},
377
+ graph: {},
378
+ });
379
+
380
+ const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
381
+ workflow: workflowHash,
382
+ prompt: "Do stuff",
383
+ });
384
+
385
+ const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
386
+ name: "out",
387
+ description: "",
388
+ roles: {},
389
+ conditions: {},
390
+ graph: {},
391
+ });
392
+
393
+ // A detail ref that doesn't exist → extractLastAssistantContent returns null
394
+ const missingDetailRef = "missingdetail0" as CasRef;
395
+
396
+ const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
397
+ start: startHash,
398
+ prev: null,
399
+ role: "worker",
400
+ output: outputHash,
401
+ detail: missingDetailRef,
402
+ agent: "uwf-test",
403
+ startedAtMs: 1000000000000,
404
+ completedAtMs: 1000000005000,
405
+ assembledPrompt: null,
406
+ });
407
+
408
+ const threadId = "01JTEST0000000000000005" as ThreadId;
409
+ await seedThreads(tmpDir, { [threadId]: stepHash });
410
+
411
+ const markdown = await cmdThreadRead(tmpDir, threadId, THREAD_READ_DEFAULT_QUOTA, null, false);
412
+
413
+ // Should not have output tags
414
+ expect(markdown).not.toContain("<output>");
415
+ expect(markdown).not.toContain("</output>");
416
+ });
417
+
418
+ test("scenario 6: thread read with --start flag shows task section", async () => {
419
+ const uwf = await makeUwfStore(tmpDir);
420
+
421
+ const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
422
+ name: "test-wf",
423
+ description: "desc",
424
+ roles: {
425
+ roleA: {
426
+ description: "Role A",
427
+ goal: "Goal for roleA",
428
+ capabilities: [],
429
+ procedure: "Do stuff.",
430
+ output: "Output.",
431
+ meta: "placeholder00" as CasRef,
432
+ },
433
+ },
434
+ conditions: {},
435
+ graph: {},
436
+ });
437
+
438
+ const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
439
+ workflow: workflowHash,
440
+ prompt: "Initial prompt",
441
+ });
442
+
443
+ const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
444
+ name: "out",
445
+ description: "",
446
+ roles: {},
447
+ conditions: {},
448
+ graph: {},
449
+ });
450
+
451
+ const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
452
+ start: startHash,
453
+ prev: null,
454
+ role: "roleA",
455
+ output: outputHash,
456
+ detail: null,
457
+ agent: "uwf-test",
458
+ startedAtMs: 1000000000000,
459
+ completedAtMs: 1000000005000,
460
+ assembledPrompt: null,
461
+ });
462
+
463
+ const threadId = "01JTEST0000000000000006" as ThreadId;
464
+ await seedThreads(tmpDir, { [threadId]: stepHash });
465
+
466
+ const markdown = await cmdThreadRead(tmpDir, threadId, THREAD_READ_DEFAULT_QUOTA, null, true);
467
+
468
+ // Should include task section
469
+ expect(markdown).toContain("# Thread");
470
+ expect(markdown).toContain("## Task");
471
+ expect(markdown).toContain("Initial prompt");
472
+
473
+ // Prompts should use XML tags
474
+ expect(markdown).toContain("<prompt>");
475
+ });
476
+
477
+ test("scenario 7: thread read with --before parameter", async () => {
478
+ const uwf = await makeUwfStore(tmpDir);
479
+
480
+ const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
481
+ name: "test-wf",
482
+ description: "desc",
483
+ roles: {
484
+ roleA: {
485
+ description: "Role A",
486
+ goal: "Goal for roleA",
487
+ capabilities: [],
488
+ procedure: "Do stuff.",
489
+ output: "Output.",
490
+ meta: "placeholder00" as CasRef,
491
+ },
492
+ roleB: {
493
+ description: "Role B",
494
+ goal: "Goal for roleB",
495
+ capabilities: [],
496
+ procedure: "Do stuff.",
497
+ output: "Output.",
498
+ meta: "placeholder00" as CasRef,
499
+ },
500
+ roleC: {
501
+ description: "Role C",
502
+ goal: "Goal for roleC",
503
+ capabilities: [],
504
+ procedure: "Do stuff.",
505
+ output: "Output.",
506
+ meta: "placeholder00" as CasRef,
507
+ },
508
+ },
509
+ conditions: {},
510
+ graph: {},
511
+ });
512
+
513
+ const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
514
+ workflow: workflowHash,
515
+ prompt: "Initial prompt",
516
+ });
517
+
518
+ const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
519
+ name: "out",
520
+ description: "",
521
+ roles: {},
522
+ conditions: {},
523
+ graph: {},
524
+ });
525
+
526
+ const step1 = await uwf.store.cas.put(uwf.schemas.stepNode, {
527
+ start: startHash,
528
+ prev: null,
529
+ role: "roleA",
530
+ output: outputHash,
531
+ detail: null,
532
+ agent: "uwf-test",
533
+ startedAtMs: 1000000000000,
534
+ completedAtMs: 1000000005000,
535
+ assembledPrompt: null,
536
+ });
537
+
538
+ const step2 = await uwf.store.cas.put(uwf.schemas.stepNode, {
539
+ start: startHash,
540
+ prev: step1 as CasRef,
541
+ role: "roleB",
542
+ output: outputHash,
543
+ detail: null,
544
+ agent: "uwf-test",
545
+ startedAtMs: 1000000000000,
546
+ completedAtMs: 1000000005000,
547
+ assembledPrompt: null,
548
+ });
549
+
550
+ const step3 = await uwf.store.cas.put(uwf.schemas.stepNode, {
551
+ start: startHash,
552
+ prev: step2 as CasRef,
553
+ role: "roleC",
554
+ output: outputHash,
555
+ detail: null,
556
+ agent: "uwf-test",
557
+ startedAtMs: 1000000000000,
558
+ completedAtMs: 1000000005000,
559
+ assembledPrompt: null,
560
+ });
561
+
562
+ const threadId = "01JTEST0000000000000007" as ThreadId;
563
+ await seedThreads(tmpDir, { [threadId]: step3 });
564
+
565
+ const markdown = await cmdThreadRead(
566
+ tmpDir,
567
+ threadId,
568
+ THREAD_READ_DEFAULT_QUOTA,
569
+ step2 as CasRef,
570
+ false,
571
+ );
572
+
573
+ // Should only show roleA
574
+ expect(markdown).toContain("roleA");
575
+ expect(markdown).not.toContain("roleB");
576
+ expect(markdown).not.toContain("roleC");
577
+
578
+ // Should use XML tags
579
+ expect(markdown).toContain("<prompt>");
580
+ });
581
+
582
+ test("scenario 9: special characters in content are preserved", async () => {
583
+ const uwf = await makeUwfStore(tmpDir);
584
+ const detailSchemas = await registerDetailSchemas(uwf.store);
585
+
586
+ const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
587
+ name: "test-wf",
588
+ description: "desc",
589
+ roles: {
590
+ writer: {
591
+ description: "Writer",
592
+ goal: "You are a writer.",
593
+ capabilities: [],
594
+ procedure: "Write content.",
595
+ output: "Summarize.",
596
+ meta: "placeholder00" as CasRef,
597
+ },
598
+ },
599
+ conditions: {},
600
+ graph: {},
601
+ });
602
+
603
+ const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
604
+ workflow: workflowHash,
605
+ prompt: "Write something",
606
+ });
607
+
608
+ const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
609
+ name: "out",
610
+ description: "",
611
+ roles: {},
612
+ conditions: {},
613
+ graph: {},
614
+ });
615
+
616
+ const turnHash = await uwf.store.cas.put(detailSchemas.turn, {
617
+ index: 0,
618
+ role: "assistant",
619
+ content: "Content with <special> & characters > like <this>",
620
+ toolCalls: null,
621
+ reasoning: null,
622
+ });
623
+ const detailHash = await uwf.store.cas.put(detailSchemas.detail, {
624
+ sessionId: "sx",
625
+ model: "mx",
626
+ duration: 500,
627
+ turnCount: 1,
628
+ turns: [turnHash],
629
+ });
630
+
631
+ const stepHash = await uwf.store.cas.put(uwf.schemas.stepNode, {
632
+ start: startHash,
633
+ prev: null,
634
+ role: "writer",
635
+ output: outputHash,
636
+ detail: detailHash,
637
+ agent: "uwf-test",
638
+ startedAtMs: 1000000000000,
639
+ completedAtMs: 1000000005000,
640
+ assembledPrompt: null,
641
+ });
642
+
643
+ const threadId = "01JTEST0000000000000008" as ThreadId;
644
+ await seedThreads(tmpDir, { [threadId]: stepHash });
645
+
646
+ const markdown = await cmdThreadRead(tmpDir, threadId, THREAD_READ_DEFAULT_QUOTA, null, false);
647
+
648
+ // Special characters should be preserved as-is
649
+ expect(markdown).toContain("Content with <special> & characters > like <this>");
650
+ });
651
+
652
+ test("scenario 10: quota limit with XML tags", async () => {
653
+ const uwf = await makeUwfStore(tmpDir);
654
+
655
+ const workflowHash = await uwf.store.cas.put(uwf.schemas.workflow, {
656
+ name: "test-wf",
657
+ description: "desc",
658
+ roles: {
659
+ roleA: {
660
+ description: "Role A",
661
+ goal: "Goal for roleA",
662
+ capabilities: [],
663
+ procedure: "Do stuff.",
664
+ output: "Output.",
665
+ meta: "placeholder00" as CasRef,
666
+ },
667
+ },
668
+ conditions: {},
669
+ graph: {},
670
+ });
671
+
672
+ const startHash = await uwf.store.cas.put(uwf.schemas.startNode, {
673
+ workflow: workflowHash,
674
+ prompt: "Initial prompt",
675
+ });
676
+
677
+ const outputHash = await uwf.store.cas.put(uwf.schemas.workflow, {
678
+ name: "out",
679
+ description: "",
680
+ roles: {},
681
+ conditions: {},
682
+ graph: {},
683
+ });
684
+
685
+ const steps: CasRef[] = [];
686
+ let prev: CasRef | null = null;
687
+ for (let i = 0; i < 5; i++) {
688
+ const step = (await uwf.store.cas.put(uwf.schemas.stepNode, {
689
+ start: startHash,
690
+ prev,
691
+ role: "roleA",
692
+ output: outputHash,
693
+ detail: null,
694
+ agent: "uwf-test",
695
+ startedAtMs: 1000000000000,
696
+ completedAtMs: 1000000005000,
697
+ assembledPrompt: null,
698
+ })) as CasRef;
699
+ steps.push(step);
700
+ prev = step;
701
+ }
702
+
703
+ const threadId = "01JTEST0000000000000009" as ThreadId;
704
+ await seedThreads(tmpDir, { [threadId]: steps[steps.length - 1]! });
705
+
706
+ // Use very small quota
707
+ const markdown = await cmdThreadRead(tmpDir, threadId, 1, null, false);
708
+
709
+ // Should have skip hint
710
+ expect(markdown).toContain("earlier step");
711
+
712
+ // Should have XML tags for displayed steps
713
+ if (markdown.includes("<prompt>")) {
714
+ expect(markdown).toContain("</prompt>");
715
+ }
716
+ });
717
+ });