@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,49 @@
1
+ import { homedir } from "node:os";
2
+ import { join } from "node:path";
3
+ import { afterEach, beforeEach, describe, expect, test } from "vitest";
4
+ import { getDefaultStorageRoot, getGlobalCasDir, resolveStorageRoot } from "../store.js";
5
+
6
+ describe("Storage root resolution", () => {
7
+ const envKeys = ["UWF_HOME", "OCAS_HOME"] as const;
8
+ const savedEnv: Partial<Record<(typeof envKeys)[number], string | undefined>> = {};
9
+
10
+ beforeEach(() => {
11
+ for (const key of envKeys) {
12
+ savedEnv[key] = process.env[key];
13
+ delete process.env[key];
14
+ }
15
+ });
16
+
17
+ afterEach(() => {
18
+ for (const key of envKeys) {
19
+ if (savedEnv[key] === undefined) {
20
+ delete process.env[key];
21
+ } else {
22
+ process.env[key] = savedEnv[key];
23
+ }
24
+ }
25
+ });
26
+
27
+ test("getDefaultStorageRoot returns ~/.uwf", () => {
28
+ expect(getDefaultStorageRoot()).toBe(join(homedir(), ".uwf"));
29
+ });
30
+
31
+ test("resolveStorageRoot uses UWF_HOME", () => {
32
+ process.env.UWF_HOME = "/tmp/uwf-primary";
33
+ expect(resolveStorageRoot()).toBe("/tmp/uwf-primary");
34
+ });
35
+
36
+ test("resolveStorageRoot falls back to default when UWF_HOME unset", () => {
37
+ expect(resolveStorageRoot()).toBe(getDefaultStorageRoot());
38
+ });
39
+
40
+ test("getGlobalCasDir returns ~/.ocas by default", () => {
41
+ const casDir = getGlobalCasDir();
42
+ expect(casDir).toBe(join(homedir(), ".ocas"));
43
+ });
44
+
45
+ test("getGlobalCasDir respects OCAS_HOME", () => {
46
+ process.env.OCAS_HOME = "/tmp/ocas-primary";
47
+ expect(getGlobalCasDir()).toBe("/tmp/ocas-primary");
48
+ });
49
+ });
@@ -0,0 +1,235 @@
1
+ import { mkdir, mkdtemp } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import type { CasRef, ThreadId } from "@united-workforce/protocol";
5
+ import { describe, expect, test } from "vitest";
6
+ import {
7
+ completeThread,
8
+ createUwfStore,
9
+ getThread,
10
+ loadActiveThreads,
11
+ loadHistoryThreads,
12
+ setThread,
13
+ } from "../store.js";
14
+
15
+ async function makeUwfStore(storageRoot: string) {
16
+ const casDir = join(storageRoot, "cas");
17
+ await mkdir(casDir, { recursive: true });
18
+ process.env.OCAS_HOME = casDir;
19
+ return createUwfStore(storageRoot);
20
+ }
21
+
22
+ async function seedThreadHead(
23
+ uwf: Awaited<ReturnType<typeof createUwfStore>>,
24
+ label: string,
25
+ ): Promise<CasRef> {
26
+ return (await uwf.store.cas.put(uwf.schemas.text, label)) as CasRef;
27
+ }
28
+
29
+ describe("unified thread storage", () => {
30
+ test("loadActiveThreads excludes completed threads", async () => {
31
+ const tmpDir = await mkdtemp(join(tmpdir(), "uwf-active-test-"));
32
+ const uwf = await makeUwfStore(tmpDir);
33
+
34
+ const threadId1 = "01JTEST000000000000ACTIVE1" as ThreadId;
35
+ const threadId2 = "01JTEST000000000000ACTIVE2" as ThreadId;
36
+ const head1 = await seedThreadHead(uwf, "active-head");
37
+ const head2 = await seedThreadHead(uwf, "completed-head");
38
+
39
+ setThread(uwf.varStore, threadId1, {
40
+ head: head1,
41
+ status: "idle",
42
+ suspendedRole: null,
43
+ suspendMessage: null,
44
+ completedAt: null,
45
+ });
46
+
47
+ setThread(uwf.varStore, threadId2, {
48
+ head: head2,
49
+ status: "completed",
50
+ suspendedRole: null,
51
+ suspendMessage: null,
52
+ completedAt: Date.now(),
53
+ });
54
+
55
+ const active = loadActiveThreads(uwf.varStore);
56
+ expect(Object.keys(active)).toHaveLength(1);
57
+ expect(active[threadId1]).toBeDefined();
58
+ expect(active[threadId2]).toBeUndefined();
59
+ });
60
+
61
+ test("loadActiveThreads excludes cancelled threads", async () => {
62
+ const tmpDir = await mkdtemp(join(tmpdir(), "uwf-active-test-"));
63
+ const uwf = await makeUwfStore(tmpDir);
64
+
65
+ const threadId1 = "01JTEST000000000000ACTIVE3" as ThreadId;
66
+ const threadId2 = "01JTEST000000000000ACTIVE4" as ThreadId;
67
+ const head1 = await seedThreadHead(uwf, "active-head");
68
+ const head2 = await seedThreadHead(uwf, "cancelled-head");
69
+
70
+ setThread(uwf.varStore, threadId1, {
71
+ head: head1,
72
+ status: "idle",
73
+ suspendedRole: null,
74
+ suspendMessage: null,
75
+ completedAt: null,
76
+ });
77
+
78
+ setThread(uwf.varStore, threadId2, {
79
+ head: head2,
80
+ status: "cancelled",
81
+ suspendedRole: null,
82
+ suspendMessage: null,
83
+ completedAt: Date.now(),
84
+ });
85
+
86
+ const active = loadActiveThreads(uwf.varStore);
87
+ expect(Object.keys(active)).toHaveLength(1);
88
+ expect(active[threadId1]).toBeDefined();
89
+ expect(active[threadId2]).toBeUndefined();
90
+ });
91
+
92
+ test("loadHistoryThreads only returns completed and cancelled", async () => {
93
+ const tmpDir = await mkdtemp(join(tmpdir(), "uwf-history-test-"));
94
+ const uwf = await makeUwfStore(tmpDir);
95
+
96
+ const threadId1 = "01JTEST000000000000HISTOR1" as ThreadId;
97
+ const threadId2 = "01JTEST000000000000HISTOR2" as ThreadId;
98
+ const threadId3 = "01JTEST000000000000HISTOR3" as ThreadId;
99
+ const head1 = await seedThreadHead(uwf, "active-head");
100
+ const head2 = await seedThreadHead(uwf, "completed-head");
101
+ const head3 = await seedThreadHead(uwf, "cancelled-head");
102
+
103
+ setThread(uwf.varStore, threadId1, {
104
+ head: head1,
105
+ status: "idle",
106
+ suspendedRole: null,
107
+ suspendMessage: null,
108
+ completedAt: null,
109
+ });
110
+
111
+ setThread(uwf.varStore, threadId2, {
112
+ head: head2,
113
+ status: "completed",
114
+ suspendedRole: null,
115
+ suspendMessage: null,
116
+ completedAt: Date.now(),
117
+ });
118
+
119
+ setThread(uwf.varStore, threadId3, {
120
+ head: head3,
121
+ status: "cancelled",
122
+ suspendedRole: null,
123
+ suspendMessage: null,
124
+ completedAt: Date.now(),
125
+ });
126
+
127
+ const history = loadHistoryThreads(uwf.varStore);
128
+ expect(Object.keys(history)).toHaveLength(2);
129
+ expect(history[threadId1]).toBeUndefined();
130
+ expect(history[threadId2]).toBeDefined();
131
+ expect(history[threadId3]).toBeDefined();
132
+ });
133
+
134
+ test("completeThread marks thread as completed", async () => {
135
+ const tmpDir = await mkdtemp(join(tmpdir(), "uwf-complete-test-"));
136
+ const uwf = await makeUwfStore(tmpDir);
137
+ const threadId = "01JTEST000000000000COMPLE1" as ThreadId;
138
+ const head = await seedThreadHead(uwf, "active-head");
139
+
140
+ setThread(uwf.varStore, threadId, {
141
+ head,
142
+ status: "idle",
143
+ suspendedRole: null,
144
+ suspendMessage: null,
145
+ completedAt: null,
146
+ });
147
+
148
+ completeThread(uwf.varStore, threadId, "completed");
149
+
150
+ const entry = getThread(uwf.varStore, threadId);
151
+ expect(entry).not.toBeNull();
152
+ expect(entry?.status).toBe("completed");
153
+ expect(entry?.completedAt).toBeDefined();
154
+ expect(entry?.completedAt).toBeGreaterThan(0);
155
+ });
156
+
157
+ test("completeThread marks thread as cancelled", async () => {
158
+ const tmpDir = await mkdtemp(join(tmpdir(), "uwf-complete-test-"));
159
+ const uwf = await makeUwfStore(tmpDir);
160
+ const threadId = "01JTEST000000000000COMPLE2" as ThreadId;
161
+ const head = await seedThreadHead(uwf, "active-head");
162
+
163
+ setThread(uwf.varStore, threadId, {
164
+ head,
165
+ status: "idle",
166
+ suspendedRole: null,
167
+ suspendMessage: null,
168
+ completedAt: null,
169
+ });
170
+
171
+ completeThread(uwf.varStore, threadId, "cancelled");
172
+
173
+ const entry = getThread(uwf.varStore, threadId);
174
+ expect(entry).not.toBeNull();
175
+ expect(entry?.status).toBe("cancelled");
176
+ expect(entry?.completedAt).toBeDefined();
177
+ expect(entry?.completedAt).toBeGreaterThan(0);
178
+ });
179
+
180
+ test("completeThread clears suspend metadata", async () => {
181
+ const tmpDir = await mkdtemp(join(tmpdir(), "uwf-complete-test-"));
182
+ const uwf = await makeUwfStore(tmpDir);
183
+ const threadId = "01JTEST000000000000COMPLE3" as ThreadId;
184
+ const head = await seedThreadHead(uwf, "suspended-head");
185
+
186
+ setThread(uwf.varStore, threadId, {
187
+ head,
188
+ status: "suspended",
189
+ suspendedRole: "test-role",
190
+ suspendMessage: "test message",
191
+ completedAt: null,
192
+ });
193
+
194
+ completeThread(uwf.varStore, threadId, "completed");
195
+
196
+ const entry = getThread(uwf.varStore, threadId);
197
+ expect(entry).not.toBeNull();
198
+ expect(entry?.status).toBe("completed");
199
+ expect(entry?.suspendedRole).toBeNull();
200
+ expect(entry?.suspendMessage).toBeNull();
201
+ });
202
+
203
+ test("completeThread handles non-existent thread gracefully", async () => {
204
+ const tmpDir = await mkdtemp(join(tmpdir(), "uwf-complete-test-"));
205
+ const uwf = await makeUwfStore(tmpDir);
206
+ const threadId = "01JTEST000000000000NOEXIST" as ThreadId;
207
+
208
+ // Should not throw
209
+ completeThread(uwf.varStore, threadId, "completed");
210
+
211
+ const entry = getThread(uwf.varStore, threadId);
212
+ expect(entry).toBeNull();
213
+ });
214
+
215
+ test("status and completedAt tags are persisted and loaded", async () => {
216
+ const tmpDir = await mkdtemp(join(tmpdir(), "uwf-tags-test-"));
217
+ const uwf = await makeUwfStore(tmpDir);
218
+ const threadId = "01JTEST000000000000TAGTEST" as ThreadId;
219
+ const head = await seedThreadHead(uwf, "test-head");
220
+ const now = Date.now();
221
+
222
+ setThread(uwf.varStore, threadId, {
223
+ head,
224
+ status: "completed",
225
+ suspendedRole: null,
226
+ suspendMessage: null,
227
+ completedAt: now,
228
+ });
229
+
230
+ const entry = getThread(uwf.varStore, threadId);
231
+ expect(entry).not.toBeNull();
232
+ expect(entry?.status).toBe("completed");
233
+ expect(entry?.completedAt).toBe(now);
234
+ });
235
+ });
@@ -0,0 +1,138 @@
1
+ import { mkdir, mkdtemp } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import type { CasRef, ThreadId } from "@united-workforce/protocol";
5
+ import { describe, expect, test } from "vitest";
6
+ import {
7
+ completeThread,
8
+ createUwfStore,
9
+ getThread,
10
+ loadHistoryThreads,
11
+ setThread,
12
+ } from "../store.js";
13
+
14
+ async function makeUwfStore(storageRoot: string) {
15
+ const casDir = join(storageRoot, "cas");
16
+ await mkdir(casDir, { recursive: true });
17
+ process.env.OCAS_HOME = casDir;
18
+ return createUwfStore(storageRoot);
19
+ }
20
+
21
+ async function seedHistoryHead(
22
+ uwf: Awaited<ReturnType<typeof createUwfStore>>,
23
+ label: string,
24
+ ): Promise<CasRef> {
25
+ return (await uwf.store.cas.put(uwf.schemas.text, label)) as CasRef;
26
+ }
27
+
28
+ describe("thread cancel status", () => {
29
+ test("cancelled thread has status 'cancelled'", async () => {
30
+ const tmpDir = await mkdtemp(join(tmpdir(), "uwf-cancel-test-"));
31
+ const threadId = "01JTEST000000000000CANCEL1" as ThreadId;
32
+ const uwf = await makeUwfStore(tmpDir);
33
+ const head = await seedHistoryHead(uwf, "cancelled-head");
34
+
35
+ setThread(uwf.varStore, threadId, {
36
+ head,
37
+ status: "idle",
38
+ suspendedRole: null,
39
+ suspendMessage: null,
40
+ completedAt: null,
41
+ });
42
+
43
+ completeThread(uwf.varStore, threadId, "cancelled");
44
+
45
+ const entry = getThread(uwf.varStore, threadId);
46
+ expect(entry).not.toBeNull();
47
+ expect(entry?.status).toBe("cancelled");
48
+ });
49
+
50
+ test("completed thread has status 'completed'", async () => {
51
+ const tmpDir = await mkdtemp(join(tmpdir(), "uwf-cancel-test-"));
52
+ const threadId = "01JTEST000000000000CANCEL2" as ThreadId;
53
+ const uwf = await makeUwfStore(tmpDir);
54
+ const head = await seedHistoryHead(uwf, "completed-head");
55
+
56
+ setThread(uwf.varStore, threadId, {
57
+ head,
58
+ status: "idle",
59
+ suspendedRole: null,
60
+ suspendMessage: null,
61
+ completedAt: null,
62
+ });
63
+
64
+ completeThread(uwf.varStore, threadId, "completed");
65
+
66
+ const entry = getThread(uwf.varStore, threadId);
67
+ expect(entry).not.toBeNull();
68
+ expect(entry?.status).toBe("completed");
69
+ });
70
+
71
+ test("loadHistoryThreads returns completed and cancelled", async () => {
72
+ const tmpDir = await mkdtemp(join(tmpdir(), "uwf-cancel-test-"));
73
+ const uwf = await makeUwfStore(tmpDir);
74
+ const head1 = await seedHistoryHead(uwf, "head1");
75
+ const head2 = await seedHistoryHead(uwf, "head2");
76
+
77
+ const threadId1 = "01JTEST000000000000CANCEL4" as ThreadId;
78
+ setThread(uwf.varStore, threadId1, {
79
+ head: head1,
80
+ status: "idle",
81
+ suspendedRole: null,
82
+ suspendMessage: null,
83
+ completedAt: null,
84
+ });
85
+ completeThread(uwf.varStore, threadId1, "completed");
86
+
87
+ const threadId2 = "01JTEST000000000000CANCEL5" as ThreadId;
88
+ setThread(uwf.varStore, threadId2, {
89
+ head: head2,
90
+ status: "idle",
91
+ suspendedRole: null,
92
+ suspendMessage: null,
93
+ completedAt: null,
94
+ });
95
+ completeThread(uwf.varStore, threadId2, "cancelled");
96
+
97
+ const history = loadHistoryThreads(uwf.varStore);
98
+ expect(Object.keys(history)).toHaveLength(2);
99
+ const statuses = Object.values(history)
100
+ .map((entry) => entry.status)
101
+ .sort();
102
+ expect(statuses).toEqual(["cancelled", "completed"]);
103
+ });
104
+
105
+ test("mixed completed and cancelled entries preserve distinct statuses", async () => {
106
+ const tmpDir = await mkdtemp(join(tmpdir(), "uwf-cancel-test-"));
107
+ const uwf = await makeUwfStore(tmpDir);
108
+ const head1 = await seedHistoryHead(uwf, "head1");
109
+ const head2 = await seedHistoryHead(uwf, "head2");
110
+
111
+ const threadId1 = "01JTEST000000000000CANCEL6" as ThreadId;
112
+ setThread(uwf.varStore, threadId1, {
113
+ head: head1,
114
+ status: "idle",
115
+ suspendedRole: null,
116
+ suspendMessage: null,
117
+ completedAt: null,
118
+ });
119
+ completeThread(uwf.varStore, threadId1, "completed");
120
+
121
+ const threadId2 = "01JTEST000000000000CANCEL7" as ThreadId;
122
+ setThread(uwf.varStore, threadId2, {
123
+ head: head2,
124
+ status: "idle",
125
+ suspendedRole: null,
126
+ suspendMessage: null,
127
+ completedAt: null,
128
+ });
129
+ completeThread(uwf.varStore, threadId2, "cancelled");
130
+
131
+ const history = loadHistoryThreads(uwf.varStore);
132
+ expect(Object.keys(history)).toHaveLength(2);
133
+ const statuses = Object.values(history)
134
+ .map((entry) => entry.status)
135
+ .sort();
136
+ expect(statuses).toEqual(["cancelled", "completed"]);
137
+ });
138
+ });