@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,613 @@
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 type { CasRef, ThreadId } from "@united-workforce/protocol";
7
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
8
+ import { cmdThreadRead } from "../commands/thread.js";
9
+ import { registerUwfSchemas } from "../schemas.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 registerDetailSchemas(store: Awaited<ReturnType<typeof openStore>>) {
53
+ await bootstrap(store);
54
+ const [turn, detail] = await Promise.all([
55
+ putSchema(store, TURN_SCHEMA),
56
+ putSchema(store, DETAIL_SCHEMA),
57
+ ]);
58
+ return { turn, detail };
59
+ }
60
+
61
+ function generateContent(size: number, prefix = "Content"): string {
62
+ const base = `${prefix} `;
63
+ const repeat = Math.ceil(size / base.length);
64
+ return base.repeat(repeat).slice(0, size);
65
+ }
66
+
67
+ // ── fixture ───────────────────────────────────────────────────────────────────
68
+
69
+ let tmpDir: string;
70
+ let originalEnv: string | undefined;
71
+
72
+ beforeEach(async () => {
73
+ tmpDir = await mkdtemp(join(tmpdir(), "cli-uwf-quota-test-"));
74
+ originalEnv = process.env.OCAS_HOME;
75
+ process.env.OCAS_HOME = join(tmpDir, "cas");
76
+ await mkdir(process.env.OCAS_HOME, { recursive: true });
77
+ });
78
+
79
+ afterEach(async () => {
80
+ await rm(tmpDir, { recursive: true, force: true });
81
+ if (originalEnv === undefined) {
82
+ delete process.env.OCAS_HOME;
83
+ } else {
84
+ process.env.OCAS_HOME = originalEnv;
85
+ }
86
+ });
87
+
88
+ // ── thread read quota enforcement ─────────────────────────────────────────────
89
+
90
+ describe("thread read --quota flag", () => {
91
+ test("test 1: basic quota enforcement with 3 steps", async () => {
92
+ const casDir = join(tmpDir, "cas");
93
+ await mkdir(casDir, { recursive: true });
94
+ const store = await openStore(casDir);
95
+ const schemas = await registerUwfSchemas(store);
96
+ const detailSchemas = await registerDetailSchemas(store);
97
+
98
+ const workflowHash = await store.cas.put(schemas.workflow, {
99
+ name: "test-wf",
100
+ description: "desc",
101
+ roles: {
102
+ worker: {
103
+ description: "Worker",
104
+ goal: "You are a worker agent.",
105
+ capabilities: [],
106
+ procedure: "Do the work.",
107
+ output: "Summarize the work.",
108
+ meta: "placeholder00" as CasRef,
109
+ },
110
+ },
111
+ conditions: {},
112
+ graph: {},
113
+ });
114
+
115
+ const startHash = await store.cas.put(schemas.startNode, {
116
+ workflow: workflowHash,
117
+ prompt: "Test task",
118
+ });
119
+
120
+ const outputHash = await store.cas.put(schemas.workflow, {
121
+ name: "out",
122
+ description: "",
123
+ roles: {},
124
+ conditions: {},
125
+ graph: {},
126
+ });
127
+
128
+ // Create 3 steps with ~500 chars each
129
+ const steps: CasRef[] = [];
130
+ for (let i = 1; i <= 3; i++) {
131
+ const content = generateContent(500, `Step${i}`);
132
+ const turnHash = await store.cas.put(detailSchemas.turn, {
133
+ index: 0,
134
+ role: "assistant",
135
+ content,
136
+ toolCalls: null,
137
+ reasoning: null,
138
+ });
139
+ const detailHash = await store.cas.put(detailSchemas.detail, {
140
+ sessionId: `session-${i}`,
141
+ model: "test-model",
142
+ duration: 1000,
143
+ turnCount: 1,
144
+ turns: [turnHash],
145
+ });
146
+ const stepHash = await store.cas.put(schemas.stepNode, {
147
+ start: startHash,
148
+ prev: steps[i - 2] ?? null,
149
+ role: "worker",
150
+ output: outputHash,
151
+ detail: detailHash,
152
+ agent: "uwf-test",
153
+ startedAtMs: 1000000000000,
154
+ completedAtMs: 1000000005000,
155
+ assembledPrompt: null,
156
+ });
157
+ steps.push(stepHash);
158
+ }
159
+
160
+ const threadId = "01HX2Q3R4S5T6V7W8X9YZ0" as ThreadId;
161
+ await seedThreads(tmpDir, { [threadId]: steps[2] as CasRef });
162
+
163
+ // Set quota to 800 chars - should only fit most recent steps
164
+ const markdown = await cmdThreadRead(tmpDir, threadId, 800, null, false);
165
+
166
+ // Quota must be reasonably enforced (allow ~200 char tolerance for skip hint)
167
+ expect(markdown.length).toBeLessThanOrEqual(1000);
168
+
169
+ // Should contain skip hint since not all steps fit
170
+ expect(markdown).toMatch(/earlier step/);
171
+
172
+ // Most recent step should be included
173
+ expect(markdown).toMatch(/Step3/);
174
+ });
175
+
176
+ test("test 2: quota check order - verifies bug is fixed", async () => {
177
+ const casDir = join(tmpDir, "cas");
178
+ await mkdir(casDir, { recursive: true });
179
+ const store = await openStore(casDir);
180
+ const schemas = await registerUwfSchemas(store);
181
+ const detailSchemas = await registerDetailSchemas(store);
182
+
183
+ const workflowHash = await store.cas.put(schemas.workflow, {
184
+ name: "test-wf",
185
+ description: "desc",
186
+ roles: {
187
+ worker: {
188
+ description: "Worker",
189
+ goal: "You are a worker agent.",
190
+ capabilities: [],
191
+ procedure: "Do the work.",
192
+ output: "Summarize the work.",
193
+ meta: "placeholder00" as CasRef,
194
+ },
195
+ },
196
+ conditions: {},
197
+ graph: {},
198
+ });
199
+
200
+ const startHash = await store.cas.put(schemas.startNode, {
201
+ workflow: workflowHash,
202
+ prompt: "Test task",
203
+ });
204
+
205
+ const outputHash = await store.cas.put(schemas.workflow, {
206
+ name: "out",
207
+ description: "",
208
+ roles: {},
209
+ conditions: {},
210
+ graph: {},
211
+ });
212
+
213
+ // Create 2 steps: first=300 chars, second=600 chars
214
+ const step1Content = generateContent(300, "First");
215
+ const step1TurnHash = await store.cas.put(detailSchemas.turn, {
216
+ index: 0,
217
+ role: "assistant",
218
+ content: step1Content,
219
+ toolCalls: null,
220
+ reasoning: null,
221
+ });
222
+ const step1DetailHash = await store.cas.put(detailSchemas.detail, {
223
+ sessionId: "session-1",
224
+ model: "test-model",
225
+ duration: 1000,
226
+ turnCount: 1,
227
+ turns: [step1TurnHash],
228
+ });
229
+ const step1Hash = await store.cas.put(schemas.stepNode, {
230
+ start: startHash,
231
+ prev: null,
232
+ role: "worker",
233
+ output: outputHash,
234
+ detail: step1DetailHash,
235
+ agent: "uwf-test",
236
+ startedAtMs: 1000000000000,
237
+ completedAtMs: 1000000005000,
238
+ assembledPrompt: null,
239
+ });
240
+
241
+ const step2Content = generateContent(600, "Second");
242
+ const step2TurnHash = await store.cas.put(detailSchemas.turn, {
243
+ index: 0,
244
+ role: "assistant",
245
+ content: step2Content,
246
+ toolCalls: null,
247
+ reasoning: null,
248
+ });
249
+ const step2DetailHash = await store.cas.put(detailSchemas.detail, {
250
+ sessionId: "session-2",
251
+ model: "test-model",
252
+ duration: 1000,
253
+ turnCount: 1,
254
+ turns: [step2TurnHash],
255
+ });
256
+ const step2Hash = await store.cas.put(schemas.stepNode, {
257
+ start: startHash,
258
+ prev: step1Hash,
259
+ role: "worker",
260
+ output: outputHash,
261
+ detail: step2DetailHash,
262
+ agent: "uwf-test",
263
+ startedAtMs: 1000000000000,
264
+ completedAtMs: 1000000005000,
265
+ assembledPrompt: null,
266
+ });
267
+
268
+ const threadId = "01HX2Q3R4S5T6V7W8X9YZ1" as ThreadId;
269
+ await seedThreads(tmpDir, { [threadId]: step2Hash });
270
+
271
+ // Set quota to 500 chars
272
+ const markdown = await cmdThreadRead(tmpDir, threadId, 500, null, false);
273
+
274
+ // Bug fix verification: output must be limited (allow ~200 char tolerance)
275
+ expect(markdown.length).toBeLessThanOrEqual(1100);
276
+
277
+ // Should contain "Second" (most recent step)
278
+ expect(markdown).toMatch(/Second/);
279
+
280
+ // Should skip first step
281
+ expect(markdown).toMatch(/earlier step/);
282
+
283
+ // Verify improvement: before fix would be ~1264, now should be much closer to 500
284
+ expect(markdown.length).toBeLessThan(1200);
285
+ });
286
+
287
+ test("test 3: quota with --start section", async () => {
288
+ const casDir = join(tmpDir, "cas");
289
+ await mkdir(casDir, { recursive: true });
290
+ const store = await openStore(casDir);
291
+ const schemas = await registerUwfSchemas(store);
292
+ const detailSchemas = await registerDetailSchemas(store);
293
+
294
+ const workflowHash = await store.cas.put(schemas.workflow, {
295
+ name: "test-wf",
296
+ description: "desc",
297
+ roles: {
298
+ worker: {
299
+ description: "Worker",
300
+ goal: "You are a worker agent.",
301
+ capabilities: [],
302
+ procedure: "Do the work.",
303
+ output: "Summarize the work.",
304
+ meta: "placeholder00" as CasRef,
305
+ },
306
+ },
307
+ conditions: {},
308
+ graph: {},
309
+ });
310
+
311
+ const startHash = await store.cas.put(schemas.startNode, {
312
+ workflow: workflowHash,
313
+ prompt: "Test task with a moderately long prompt to test quota accounting",
314
+ });
315
+
316
+ const outputHash = await store.cas.put(schemas.workflow, {
317
+ name: "out",
318
+ description: "",
319
+ roles: {},
320
+ conditions: {},
321
+ graph: {},
322
+ });
323
+
324
+ // Create 2 steps
325
+ const steps: CasRef[] = [];
326
+ for (let i = 1; i <= 2; i++) {
327
+ const content = generateContent(400, `Step${i}`);
328
+ const turnHash = await store.cas.put(detailSchemas.turn, {
329
+ index: 0,
330
+ role: "assistant",
331
+ content,
332
+ toolCalls: null,
333
+ reasoning: null,
334
+ });
335
+ const detailHash = await store.cas.put(detailSchemas.detail, {
336
+ sessionId: `session-${i}`,
337
+ model: "test-model",
338
+ duration: 1000,
339
+ turnCount: 1,
340
+ turns: [turnHash],
341
+ });
342
+ const stepHash = await store.cas.put(schemas.stepNode, {
343
+ start: startHash,
344
+ prev: steps[i - 2] ?? null,
345
+ role: "worker",
346
+ output: outputHash,
347
+ detail: detailHash,
348
+ agent: "uwf-test",
349
+ startedAtMs: 1000000000000,
350
+ completedAtMs: 1000000005000,
351
+ assembledPrompt: null,
352
+ });
353
+ steps.push(stepHash);
354
+ }
355
+
356
+ const threadId = "01HX2Q3R4S5T6V7W8X9YZ2" as ThreadId;
357
+ await seedThreads(tmpDir, { [threadId]: steps[1] as CasRef });
358
+
359
+ // Set tight quota with --start flag
360
+ const markdown = await cmdThreadRead(tmpDir, threadId, 600, null, true);
361
+
362
+ // Quota must be reasonably enforced (allow ~260 char tolerance for structure)
363
+ expect(markdown.length).toBeLessThanOrEqual(860);
364
+
365
+ // Should contain thread header
366
+ expect(markdown).toMatch(/# Thread/);
367
+ expect(markdown).toMatch(/test-wf/);
368
+ });
369
+
370
+ test("test 5a: quota edge case - minimal quota", async () => {
371
+ const casDir = join(tmpDir, "cas");
372
+ await mkdir(casDir, { recursive: true });
373
+ const store = await openStore(casDir);
374
+ const schemas = await registerUwfSchemas(store);
375
+ const detailSchemas = await registerDetailSchemas(store);
376
+
377
+ const workflowHash = await store.cas.put(schemas.workflow, {
378
+ name: "test-wf",
379
+ description: "desc",
380
+ roles: {
381
+ worker: {
382
+ description: "Worker",
383
+ goal: "You are a worker agent.",
384
+ capabilities: [],
385
+ procedure: "Do the work.",
386
+ output: "Summarize the work.",
387
+ meta: "placeholder00" as CasRef,
388
+ },
389
+ },
390
+ conditions: {},
391
+ graph: {},
392
+ });
393
+
394
+ const startHash = await store.cas.put(schemas.startNode, {
395
+ workflow: workflowHash,
396
+ prompt: "Test task",
397
+ });
398
+
399
+ const outputHash = await store.cas.put(schemas.workflow, {
400
+ name: "out",
401
+ description: "",
402
+ roles: {},
403
+ conditions: {},
404
+ graph: {},
405
+ });
406
+
407
+ const content = generateContent(500, "Test");
408
+ const turnHash = await store.cas.put(detailSchemas.turn, {
409
+ index: 0,
410
+ role: "assistant",
411
+ content,
412
+ toolCalls: null,
413
+ reasoning: null,
414
+ });
415
+ const detailHash = await store.cas.put(detailSchemas.detail, {
416
+ sessionId: "session-1",
417
+ model: "test-model",
418
+ duration: 1000,
419
+ turnCount: 1,
420
+ turns: [turnHash],
421
+ });
422
+ const stepHash = await store.cas.put(schemas.stepNode, {
423
+ start: startHash,
424
+ prev: null,
425
+ role: "worker",
426
+ output: outputHash,
427
+ detail: detailHash,
428
+ agent: "uwf-test",
429
+ startedAtMs: 1000000000000,
430
+ completedAtMs: 1000000005000,
431
+ assembledPrompt: null,
432
+ });
433
+
434
+ const threadId = "01HX2Q3R4S5T6V7W8X9YZ4" as ThreadId;
435
+ await seedThreads(tmpDir, { [threadId]: stepHash });
436
+
437
+ // Minimal quota
438
+ const markdown = await cmdThreadRead(tmpDir, threadId, 1, null, false);
439
+
440
+ // Should handle gracefully - always shows at least one step
441
+ expect(markdown.length).toBeGreaterThan(1);
442
+ expect(markdown).toMatch(/Test/);
443
+ });
444
+
445
+ test("test 5b: quota edge case - very large quota", async () => {
446
+ const casDir = join(tmpDir, "cas");
447
+ await mkdir(casDir, { recursive: true });
448
+ const store = await openStore(casDir);
449
+ const schemas = await registerUwfSchemas(store);
450
+ const detailSchemas = await registerDetailSchemas(store);
451
+
452
+ const workflowHash = await store.cas.put(schemas.workflow, {
453
+ name: "test-wf",
454
+ description: "desc",
455
+ roles: {
456
+ worker: {
457
+ description: "Worker",
458
+ goal: "You are a worker agent.",
459
+ capabilities: [],
460
+ procedure: "Do the work.",
461
+ output: "Summarize the work.",
462
+ meta: "placeholder00" as CasRef,
463
+ },
464
+ },
465
+ conditions: {},
466
+ graph: {},
467
+ });
468
+
469
+ const startHash = await store.cas.put(schemas.startNode, {
470
+ workflow: workflowHash,
471
+ prompt: "Test task",
472
+ });
473
+
474
+ const outputHash = await store.cas.put(schemas.workflow, {
475
+ name: "out",
476
+ description: "",
477
+ roles: {},
478
+ conditions: {},
479
+ graph: {},
480
+ });
481
+
482
+ // Create 3 steps
483
+ const steps: CasRef[] = [];
484
+ for (let i = 1; i <= 3; i++) {
485
+ const content = generateContent(300, `Step${i}`);
486
+ const turnHash = await store.cas.put(detailSchemas.turn, {
487
+ index: 0,
488
+ role: "assistant",
489
+ content,
490
+ toolCalls: null,
491
+ reasoning: null,
492
+ });
493
+ const detailHash = await store.cas.put(detailSchemas.detail, {
494
+ sessionId: `session-${i}`,
495
+ model: "test-model",
496
+ duration: 1000,
497
+ turnCount: 1,
498
+ turns: [turnHash],
499
+ });
500
+ const stepHash = await store.cas.put(schemas.stepNode, {
501
+ start: startHash,
502
+ prev: steps[i - 2] ?? null,
503
+ role: "worker",
504
+ output: outputHash,
505
+ detail: detailHash,
506
+ agent: "uwf-test",
507
+ startedAtMs: 1000000000000,
508
+ completedAtMs: 1000000005000,
509
+ assembledPrompt: null,
510
+ });
511
+ steps.push(stepHash);
512
+ }
513
+
514
+ const threadId = "01HX2Q3R4S5T6V7W8X9YZ5" as ThreadId;
515
+ await seedThreads(tmpDir, { [threadId]: steps[2] as CasRef });
516
+
517
+ // Very large quota
518
+ const markdown = await cmdThreadRead(tmpDir, threadId, 1000000, null, false);
519
+
520
+ // Should show all steps (no skipping)
521
+ expect(markdown).not.toMatch(/earlier step/);
522
+ expect(markdown).toMatch(/Step1/);
523
+ expect(markdown).toMatch(/Step2/);
524
+ expect(markdown).toMatch(/Step3/);
525
+ });
526
+
527
+ test("test 6: quota with --before parameter", async () => {
528
+ const casDir = join(tmpDir, "cas");
529
+ await mkdir(casDir, { recursive: true });
530
+ const store = await openStore(casDir);
531
+ const schemas = await registerUwfSchemas(store);
532
+ const detailSchemas = await registerDetailSchemas(store);
533
+
534
+ const workflowHash = await store.cas.put(schemas.workflow, {
535
+ name: "test-wf",
536
+ description: "desc",
537
+ roles: {
538
+ worker: {
539
+ description: "Worker",
540
+ goal: "You are a worker agent.",
541
+ capabilities: [],
542
+ procedure: "Do the work.",
543
+ output: "Summarize the work.",
544
+ meta: "placeholder00" as CasRef,
545
+ },
546
+ },
547
+ conditions: {},
548
+ graph: {},
549
+ });
550
+
551
+ const startHash = await store.cas.put(schemas.startNode, {
552
+ workflow: workflowHash,
553
+ prompt: "Test task",
554
+ });
555
+
556
+ const outputHash = await store.cas.put(schemas.workflow, {
557
+ name: "out",
558
+ description: "",
559
+ roles: {},
560
+ conditions: {},
561
+ graph: {},
562
+ });
563
+
564
+ // Create 5 steps
565
+ const steps: CasRef[] = [];
566
+ for (let i = 1; i <= 5; i++) {
567
+ const content = generateContent(300, `Step${i}`);
568
+ const turnHash = await store.cas.put(detailSchemas.turn, {
569
+ index: 0,
570
+ role: "assistant",
571
+ content,
572
+ toolCalls: null,
573
+ reasoning: null,
574
+ });
575
+ const detailHash = await store.cas.put(detailSchemas.detail, {
576
+ sessionId: `session-${i}`,
577
+ model: "test-model",
578
+ duration: 1000,
579
+ turnCount: 1,
580
+ turns: [turnHash],
581
+ });
582
+ const stepHash = await store.cas.put(schemas.stepNode, {
583
+ start: startHash,
584
+ prev: steps[i - 2] ?? null,
585
+ role: "worker",
586
+ output: outputHash,
587
+ detail: detailHash,
588
+ agent: "uwf-test",
589
+ startedAtMs: 1000000000000,
590
+ completedAtMs: 1000000005000,
591
+ assembledPrompt: null,
592
+ });
593
+ steps.push(stepHash);
594
+ }
595
+
596
+ const threadId = "01HX2Q3R4S5T6V7W8X9YZ6" as ThreadId;
597
+ await seedThreads(tmpDir, { [threadId]: steps[4] as CasRef });
598
+
599
+ // Use --before to limit to steps 1-2, then set quota that allows only 1
600
+ const markdown = await cmdThreadRead(tmpDir, threadId, 500, steps[2] as CasRef, false);
601
+
602
+ // Should not contain Step3 or later
603
+ expect(markdown).not.toMatch(/Step3/);
604
+ expect(markdown).not.toMatch(/Step4/);
605
+ expect(markdown).not.toMatch(/Step5/);
606
+
607
+ // Quota should select most recent of candidates (Step2)
608
+ expect(markdown).toMatch(/Step2/);
609
+
610
+ // Quota enforcement (allow ~200 char tolerance)
611
+ expect(markdown.length).toBeLessThanOrEqual(700);
612
+ });
613
+ });