@stonerzju/opencode 1.2.17 → 1.2.18

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 (261) hide show
  1. package/bin/opencode +29 -157
  2. package/package.json +29 -29
  3. package/src/acp/agent.ts +4 -4
  4. package/src/acp/session.ts +1 -1
  5. package/src/agent/agent.ts +3 -3
  6. package/src/bun/index.ts +2 -2
  7. package/src/cli/cmd/acp.ts +3 -3
  8. package/src/cli/cmd/debug/file.ts +1 -1
  9. package/src/cli/cmd/github.ts +2 -2
  10. package/src/cli/cmd/pr.ts +1 -1
  11. package/src/cli/cmd/tui/app.tsx +24 -24
  12. package/src/cli/cmd/tui/attach.ts +3 -3
  13. package/src/cli/cmd/tui/component/dialog-agent.tsx +3 -3
  14. package/src/cli/cmd/tui/component/dialog-command.tsx +3 -3
  15. package/src/cli/cmd/tui/component/dialog-mcp.tsx +5 -5
  16. package/src/cli/cmd/tui/component/dialog-model.tsx +4 -4
  17. package/src/cli/cmd/tui/component/dialog-provider.tsx +4 -4
  18. package/src/cli/cmd/tui/component/dialog-session-list.tsx +5 -5
  19. package/src/cli/cmd/tui/component/dialog-session-rename.tsx +3 -3
  20. package/src/cli/cmd/tui/component/dialog-skill.tsx +3 -3
  21. package/src/cli/cmd/tui/component/dialog-stash.tsx +3 -3
  22. package/src/cli/cmd/tui/component/dialog-status.tsx +2 -2
  23. package/src/cli/cmd/tui/component/dialog-tag.tsx +3 -3
  24. package/src/cli/cmd/tui/component/logo.tsx +2 -2
  25. package/src/cli/cmd/tui/component/prompt/autocomplete.tsx +6 -6
  26. package/src/cli/cmd/tui/component/prompt/frecency.tsx +2 -2
  27. package/src/cli/cmd/tui/component/prompt/history.tsx +2 -2
  28. package/src/cli/cmd/tui/component/prompt/index.tsx +14 -14
  29. package/src/cli/cmd/tui/component/prompt/stash.tsx +2 -2
  30. package/src/cli/cmd/tui/component/textarea-keybindings.ts +1 -1
  31. package/src/cli/cmd/tui/component/tips.tsx +1 -1
  32. package/src/cli/cmd/tui/context/directory.ts +1 -1
  33. package/src/cli/cmd/tui/context/exit.tsx +1 -1
  34. package/src/cli/cmd/tui/context/keybind.tsx +2 -2
  35. package/src/cli/cmd/tui/context/kv.tsx +2 -2
  36. package/src/cli/cmd/tui/context/local.tsx +6 -6
  37. package/src/cli/cmd/tui/context/sync.tsx +4 -4
  38. package/src/cli/cmd/tui/context/theme/opencode.json +245 -0
  39. package/src/cli/cmd/tui/context/theme.tsx +2 -2
  40. package/src/cli/cmd/tui/context/tui-config.tsx +1 -1
  41. package/src/cli/cmd/tui/event.ts +2 -2
  42. package/src/cli/cmd/tui/routes/home.tsx +6 -6
  43. package/src/cli/cmd/tui/routes/session/dialog-fork-from-timeline.tsx +6 -6
  44. package/src/cli/cmd/tui/routes/session/dialog-message.tsx +6 -6
  45. package/src/cli/cmd/tui/routes/session/dialog-subagent.tsx +2 -2
  46. package/src/cli/cmd/tui/routes/session/dialog-timeline.tsx +3 -3
  47. package/src/cli/cmd/tui/routes/session/header.tsx +5 -5
  48. package/src/cli/cmd/tui/routes/session/index.tsx +32 -32
  49. package/src/cli/cmd/tui/routes/session/permission.tsx +4 -4
  50. package/src/cli/cmd/tui/routes/session/sidebar.tsx +4 -4
  51. package/src/cli/cmd/tui/thread.ts +9 -9
  52. package/src/cli/cmd/tui/ui/dialog-confirm.tsx +1 -1
  53. package/src/cli/cmd/tui/ui/dialog-help.tsx +2 -2
  54. package/src/cli/cmd/tui/ui/dialog-select.tsx +5 -5
  55. package/src/cli/cmd/tui/ui/dialog.tsx +3 -3
  56. package/src/cli/cmd/tui/ui/toast.tsx +1 -1
  57. package/src/cli/cmd/tui/util/editor.ts +3 -3
  58. package/src/cli/cmd/tui/util/transcript.ts +1 -1
  59. package/src/cli/cmd/tui/worker.ts +10 -10
  60. package/src/cli/error.ts +1 -1
  61. package/src/cli/ui.ts +1 -1
  62. package/src/cli/upgrade.ts +4 -4
  63. package/src/command/index.ts +1 -1
  64. package/src/config/config.ts +10 -10
  65. package/src/config/markdown.ts +1 -1
  66. package/src/config/migrate-tui-config.ts +5 -5
  67. package/src/config/paths.ts +4 -4
  68. package/src/config/tui.ts +4 -4
  69. package/src/control/control.sql.ts +1 -1
  70. package/src/control/index.ts +1 -1
  71. package/src/control-plane/adaptors/worktree.ts +1 -1
  72. package/src/control-plane/session-proxy-middleware.ts +1 -1
  73. package/src/control-plane/workspace.sql.ts +1 -1
  74. package/src/control-plane/workspace.ts +7 -7
  75. package/src/file/index.ts +1 -1
  76. package/src/file/ripgrep.ts +2 -2
  77. package/src/file/watcher.ts +5 -5
  78. package/src/format/formatter.ts +1 -1
  79. package/src/ide/index.ts +3 -3
  80. package/src/index.ts +1 -1
  81. package/src/installation/index.ts +3 -3
  82. package/src/lsp/client.ts +3 -3
  83. package/src/lsp/index.ts +3 -3
  84. package/src/mcp/index.ts +4 -4
  85. package/src/permission/index.ts +2 -2
  86. package/src/permission/next.ts +10 -10
  87. package/src/plugin/codex.ts +1 -1
  88. package/src/plugin/copilot.ts +2 -2
  89. package/src/plugin/index.ts +1 -1
  90. package/src/project/bootstrap.ts +2 -2
  91. package/src/project/instance.ts +4 -4
  92. package/src/project/project.sql.ts +1 -1
  93. package/src/project/project.ts +5 -5
  94. package/src/project/state.ts +1 -1
  95. package/src/project/vcs.ts +4 -4
  96. package/src/provider/auth.ts +4 -4
  97. package/src/provider/error.ts +1 -1
  98. package/src/provider/models-snapshot.ts +2 -0
  99. package/src/provider/models.ts +1 -1
  100. package/src/provider/provider.ts +2 -2
  101. package/src/provider/transform.ts +2 -2
  102. package/src/pty/index.ts +5 -5
  103. package/src/question/index.ts +5 -5
  104. package/src/server/event.ts +1 -1
  105. package/src/server/mdns.ts +1 -1
  106. package/src/server/routes/global.ts +3 -3
  107. package/src/server/routes/permission.ts +1 -1
  108. package/src/server/routes/pty.ts +1 -1
  109. package/src/server/routes/session.ts +4 -4
  110. package/src/server/routes/tui.ts +1 -1
  111. package/src/server/server.ts +3 -3
  112. package/src/session/compaction.ts +7 -7
  113. package/src/session/index.ts +10 -10
  114. package/src/session/instruction.ts +1 -1
  115. package/src/session/llm.ts +11 -11
  116. package/src/session/message-v2.ts +10 -10
  117. package/src/session/message.ts +1 -1
  118. package/src/session/processor.ts +10 -10
  119. package/src/session/prompt.ts +8 -8
  120. package/src/session/retry.ts +2 -2
  121. package/src/session/revert.ts +1 -1
  122. package/src/session/session.sql.ts +3 -3
  123. package/src/session/status.ts +3 -3
  124. package/src/session/summary.ts +5 -5
  125. package/src/session/system.ts +1 -1
  126. package/src/session/todo.ts +2 -2
  127. package/src/share/share-next.ts +7 -7
  128. package/src/share/share.sql.ts +1 -1
  129. package/src/shell/shell.ts +3 -3
  130. package/src/skill/skill.ts +6 -6
  131. package/src/storage/db.ts +1 -1
  132. package/src/storage/storage.ts +1 -1
  133. package/src/tool/bash.ts +6 -6
  134. package/src/tool/edit.ts +1 -1
  135. package/src/tool/registry.ts +2 -2
  136. package/src/tool/skill.ts +1 -1
  137. package/src/tool/task.ts +3 -3
  138. package/src/util/array.ts +10 -0
  139. package/src/util/binary.ts +41 -0
  140. package/src/util/encode.ts +51 -0
  141. package/src/util/error.ts +54 -0
  142. package/src/util/identifier.ts +48 -0
  143. package/src/util/lazy.ts +4 -16
  144. package/src/util/path.ts +37 -0
  145. package/src/util/retry.ts +41 -0
  146. package/src/util/slug.ts +74 -0
  147. package/src/worktree/index.ts +3 -3
  148. package/AGENTS.md +0 -10
  149. package/BUN_SHELL_MIGRATION_PLAN.md +0 -136
  150. package/Dockerfile +0 -18
  151. package/README.md +0 -15
  152. package/bunfig.toml +0 -7
  153. package/drizzle.config.ts +0 -10
  154. package/script/build.ts +0 -224
  155. package/script/check-migrations.ts +0 -16
  156. package/script/postinstall.mjs +0 -131
  157. package/script/publish.ts +0 -181
  158. package/script/schema.ts +0 -63
  159. package/script/seed-e2e.ts +0 -50
  160. package/sst-env.d.ts +0 -10
  161. package/test/AGENTS.md +0 -81
  162. package/test/acp/agent-interface.test.ts +0 -51
  163. package/test/acp/event-subscription.test.ts +0 -683
  164. package/test/agent/agent.test.ts +0 -689
  165. package/test/bun.test.ts +0 -53
  166. package/test/cli/github-action.test.ts +0 -197
  167. package/test/cli/github-remote.test.ts +0 -80
  168. package/test/cli/import.test.ts +0 -38
  169. package/test/cli/plugin-auth-picker.test.ts +0 -120
  170. package/test/cli/tui/transcript.test.ts +0 -322
  171. package/test/config/agent-color.test.ts +0 -71
  172. package/test/config/config.test.ts +0 -1886
  173. package/test/config/fixtures/empty-frontmatter.md +0 -4
  174. package/test/config/fixtures/frontmatter.md +0 -28
  175. package/test/config/fixtures/markdown-header.md +0 -11
  176. package/test/config/fixtures/no-frontmatter.md +0 -1
  177. package/test/config/fixtures/weird-model-id.md +0 -13
  178. package/test/config/markdown.test.ts +0 -228
  179. package/test/config/tui.test.ts +0 -510
  180. package/test/control-plane/session-proxy-middleware.test.ts +0 -147
  181. package/test/control-plane/sse.test.ts +0 -56
  182. package/test/control-plane/workspace-server-sse.test.ts +0 -65
  183. package/test/control-plane/workspace-sync.test.ts +0 -97
  184. package/test/file/ignore.test.ts +0 -10
  185. package/test/file/index.test.ts +0 -394
  186. package/test/file/path-traversal.test.ts +0 -198
  187. package/test/file/ripgrep.test.ts +0 -39
  188. package/test/file/time.test.ts +0 -361
  189. package/test/fixture/db.ts +0 -11
  190. package/test/fixture/fixture.ts +0 -45
  191. package/test/fixture/lsp/fake-lsp-server.js +0 -77
  192. package/test/fixture/skills/agents-sdk/SKILL.md +0 -152
  193. package/test/fixture/skills/agents-sdk/references/callable.md +0 -92
  194. package/test/fixture/skills/cloudflare/SKILL.md +0 -211
  195. package/test/fixture/skills/index.json +0 -6
  196. package/test/ide/ide.test.ts +0 -82
  197. package/test/keybind.test.ts +0 -421
  198. package/test/lsp/client.test.ts +0 -95
  199. package/test/mcp/headers.test.ts +0 -153
  200. package/test/mcp/oauth-browser.test.ts +0 -249
  201. package/test/memory/abort-leak.test.ts +0 -136
  202. package/test/patch/patch.test.ts +0 -348
  203. package/test/permission/arity.test.ts +0 -33
  204. package/test/permission/next.test.ts +0 -689
  205. package/test/permission-task.test.ts +0 -319
  206. package/test/plugin/auth-override.test.ts +0 -44
  207. package/test/plugin/codex.test.ts +0 -123
  208. package/test/preload.ts +0 -80
  209. package/test/project/project.test.ts +0 -348
  210. package/test/project/worktree-remove.test.ts +0 -65
  211. package/test/provider/amazon-bedrock.test.ts +0 -446
  212. package/test/provider/copilot/convert-to-copilot-messages.test.ts +0 -523
  213. package/test/provider/copilot/copilot-chat-model.test.ts +0 -592
  214. package/test/provider/gitlab-duo.test.ts +0 -262
  215. package/test/provider/provider.test.ts +0 -2220
  216. package/test/provider/transform.test.ts +0 -2353
  217. package/test/pty/pty-output-isolation.test.ts +0 -140
  218. package/test/question/question.test.ts +0 -300
  219. package/test/scheduler.test.ts +0 -73
  220. package/test/server/global-session-list.test.ts +0 -89
  221. package/test/server/session-list.test.ts +0 -90
  222. package/test/server/session-select.test.ts +0 -78
  223. package/test/session/compaction.test.ts +0 -423
  224. package/test/session/instruction.test.ts +0 -170
  225. package/test/session/llm.test.ts +0 -667
  226. package/test/session/message-v2.test.ts +0 -924
  227. package/test/session/prompt.test.ts +0 -211
  228. package/test/session/retry.test.ts +0 -188
  229. package/test/session/revert-compact.test.ts +0 -285
  230. package/test/session/session.test.ts +0 -71
  231. package/test/session/structured-output-integration.test.ts +0 -233
  232. package/test/session/structured-output.test.ts +0 -385
  233. package/test/skill/discovery.test.ts +0 -110
  234. package/test/skill/skill.test.ts +0 -388
  235. package/test/snapshot/snapshot.test.ts +0 -1180
  236. package/test/storage/json-migration.test.ts +0 -846
  237. package/test/tool/__snapshots__/tool.test.ts.snap +0 -9
  238. package/test/tool/apply_patch.test.ts +0 -566
  239. package/test/tool/bash.test.ts +0 -402
  240. package/test/tool/edit.test.ts +0 -496
  241. package/test/tool/external-directory.test.ts +0 -127
  242. package/test/tool/fixtures/large-image.png +0 -0
  243. package/test/tool/fixtures/models-api.json +0 -38413
  244. package/test/tool/grep.test.ts +0 -110
  245. package/test/tool/question.test.ts +0 -107
  246. package/test/tool/read.test.ts +0 -504
  247. package/test/tool/registry.test.ts +0 -122
  248. package/test/tool/skill.test.ts +0 -112
  249. package/test/tool/truncation.test.ts +0 -160
  250. package/test/tool/webfetch.test.ts +0 -100
  251. package/test/tool/write.test.ts +0 -348
  252. package/test/util/filesystem.test.ts +0 -443
  253. package/test/util/format.test.ts +0 -59
  254. package/test/util/glob.test.ts +0 -164
  255. package/test/util/iife.test.ts +0 -36
  256. package/test/util/lazy.test.ts +0 -50
  257. package/test/util/lock.test.ts +0 -72
  258. package/test/util/process.test.ts +0 -59
  259. package/test/util/timeout.test.ts +0 -21
  260. package/test/util/wildcard.test.ts +0 -90
  261. package/tsconfig.json +0 -16
@@ -1,140 +0,0 @@
1
- import { describe, expect, test } from "bun:test"
2
- import { Instance } from "../../src/project/instance"
3
- import { Pty } from "../../src/pty"
4
- import { tmpdir } from "../fixture/fixture"
5
-
6
- describe("pty", () => {
7
- test("does not leak output when websocket objects are reused", async () => {
8
- await using dir = await tmpdir({ git: true })
9
-
10
- await Instance.provide({
11
- directory: dir.path,
12
- fn: async () => {
13
- const a = await Pty.create({ command: "cat", title: "a" })
14
- const b = await Pty.create({ command: "cat", title: "b" })
15
- try {
16
- const outA: string[] = []
17
- const outB: string[] = []
18
-
19
- const ws = {
20
- readyState: 1,
21
- data: { events: { connection: "a" } },
22
- send: (data: unknown) => {
23
- outA.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
24
- },
25
- close: () => {
26
- // no-op (simulate abrupt drop)
27
- },
28
- }
29
-
30
- // Connect "a" first with ws.
31
- Pty.connect(a.id, ws as any)
32
-
33
- // Now "reuse" the same ws object for another connection.
34
- ws.data = { events: { connection: "b" } }
35
- ws.send = (data: unknown) => {
36
- outB.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
37
- }
38
- Pty.connect(b.id, ws as any)
39
-
40
- // Clear connect metadata writes.
41
- outA.length = 0
42
- outB.length = 0
43
-
44
- // Output from a must never show up in b.
45
- Pty.write(a.id, "AAA\n")
46
- await Bun.sleep(100)
47
-
48
- expect(outB.join("")).not.toContain("AAA")
49
- } finally {
50
- await Pty.remove(a.id)
51
- await Pty.remove(b.id)
52
- }
53
- },
54
- })
55
- })
56
-
57
- test("does not leak output when Bun recycles websocket objects before re-connect", async () => {
58
- await using dir = await tmpdir({ git: true })
59
-
60
- await Instance.provide({
61
- directory: dir.path,
62
- fn: async () => {
63
- const a = await Pty.create({ command: "cat", title: "a" })
64
- try {
65
- const outA: string[] = []
66
- const outB: string[] = []
67
-
68
- const ws = {
69
- readyState: 1,
70
- data: { events: { connection: "a" } },
71
- send: (data: unknown) => {
72
- outA.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
73
- },
74
- close: () => {
75
- // no-op (simulate abrupt drop)
76
- },
77
- }
78
-
79
- // Connect "a" first.
80
- Pty.connect(a.id, ws as any)
81
- outA.length = 0
82
-
83
- // Simulate Bun reusing the same websocket object for another
84
- // connection before the next onOpen calls Pty.connect.
85
- ws.data = { events: { connection: "b" } }
86
- ws.send = (data: unknown) => {
87
- outB.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
88
- }
89
-
90
- Pty.write(a.id, "AAA\n")
91
- await Bun.sleep(100)
92
-
93
- expect(outB.join("")).not.toContain("AAA")
94
- } finally {
95
- await Pty.remove(a.id)
96
- }
97
- },
98
- })
99
- })
100
-
101
- test("treats in-place socket data mutation as the same connection", async () => {
102
- await using dir = await tmpdir({ git: true })
103
-
104
- await Instance.provide({
105
- directory: dir.path,
106
- fn: async () => {
107
- const a = await Pty.create({ command: "cat", title: "a" })
108
- try {
109
- const out: string[] = []
110
-
111
- const ctx = { connId: 1 }
112
- const ws = {
113
- readyState: 1,
114
- data: ctx,
115
- send: (data: unknown) => {
116
- out.push(typeof data === "string" ? data : Buffer.from(data as Uint8Array).toString("utf8"))
117
- },
118
- close: () => {
119
- // no-op
120
- },
121
- }
122
-
123
- Pty.connect(a.id, ws as any)
124
- out.length = 0
125
-
126
- // Mutating fields on ws.data should not look like a new
127
- // connection lifecycle when the object identity stays stable.
128
- ctx.connId = 2
129
-
130
- Pty.write(a.id, "AAA\n")
131
- await Bun.sleep(100)
132
-
133
- expect(out.join("")).toContain("AAA")
134
- } finally {
135
- await Pty.remove(a.id)
136
- }
137
- },
138
- })
139
- })
140
- })
@@ -1,300 +0,0 @@
1
- import { test, expect } from "bun:test"
2
- import { Question } from "../../src/question"
3
- import { Instance } from "../../src/project/instance"
4
- import { tmpdir } from "../fixture/fixture"
5
-
6
- test("ask - returns pending promise", async () => {
7
- await using tmp = await tmpdir({ git: true })
8
- await Instance.provide({
9
- directory: tmp.path,
10
- fn: async () => {
11
- const promise = Question.ask({
12
- sessionID: "ses_test",
13
- questions: [
14
- {
15
- question: "What would you like to do?",
16
- header: "Action",
17
- options: [
18
- { label: "Option 1", description: "First option" },
19
- { label: "Option 2", description: "Second option" },
20
- ],
21
- },
22
- ],
23
- })
24
- expect(promise).toBeInstanceOf(Promise)
25
- },
26
- })
27
- })
28
-
29
- test("ask - adds to pending list", async () => {
30
- await using tmp = await tmpdir({ git: true })
31
- await Instance.provide({
32
- directory: tmp.path,
33
- fn: async () => {
34
- const questions = [
35
- {
36
- question: "What would you like to do?",
37
- header: "Action",
38
- options: [
39
- { label: "Option 1", description: "First option" },
40
- { label: "Option 2", description: "Second option" },
41
- ],
42
- },
43
- ]
44
-
45
- Question.ask({
46
- sessionID: "ses_test",
47
- questions,
48
- })
49
-
50
- const pending = await Question.list()
51
- expect(pending.length).toBe(1)
52
- expect(pending[0].questions).toEqual(questions)
53
- },
54
- })
55
- })
56
-
57
- // reply tests
58
-
59
- test("reply - resolves the pending ask with answers", async () => {
60
- await using tmp = await tmpdir({ git: true })
61
- await Instance.provide({
62
- directory: tmp.path,
63
- fn: async () => {
64
- const questions = [
65
- {
66
- question: "What would you like to do?",
67
- header: "Action",
68
- options: [
69
- { label: "Option 1", description: "First option" },
70
- { label: "Option 2", description: "Second option" },
71
- ],
72
- },
73
- ]
74
-
75
- const askPromise = Question.ask({
76
- sessionID: "ses_test",
77
- questions,
78
- })
79
-
80
- const pending = await Question.list()
81
- const requestID = pending[0].id
82
-
83
- await Question.reply({
84
- requestID,
85
- answers: [["Option 1"]],
86
- })
87
-
88
- const answers = await askPromise
89
- expect(answers).toEqual([["Option 1"]])
90
- },
91
- })
92
- })
93
-
94
- test("reply - removes from pending list", async () => {
95
- await using tmp = await tmpdir({ git: true })
96
- await Instance.provide({
97
- directory: tmp.path,
98
- fn: async () => {
99
- Question.ask({
100
- sessionID: "ses_test",
101
- questions: [
102
- {
103
- question: "What would you like to do?",
104
- header: "Action",
105
- options: [
106
- { label: "Option 1", description: "First option" },
107
- { label: "Option 2", description: "Second option" },
108
- ],
109
- },
110
- ],
111
- })
112
-
113
- const pending = await Question.list()
114
- expect(pending.length).toBe(1)
115
-
116
- await Question.reply({
117
- requestID: pending[0].id,
118
- answers: [["Option 1"]],
119
- })
120
-
121
- const pendingAfter = await Question.list()
122
- expect(pendingAfter.length).toBe(0)
123
- },
124
- })
125
- })
126
-
127
- test("reply - does nothing for unknown requestID", async () => {
128
- await using tmp = await tmpdir({ git: true })
129
- await Instance.provide({
130
- directory: tmp.path,
131
- fn: async () => {
132
- await Question.reply({
133
- requestID: "que_unknown",
134
- answers: [["Option 1"]],
135
- })
136
- // Should not throw
137
- },
138
- })
139
- })
140
-
141
- // reject tests
142
-
143
- test("reject - throws RejectedError", async () => {
144
- await using tmp = await tmpdir({ git: true })
145
- await Instance.provide({
146
- directory: tmp.path,
147
- fn: async () => {
148
- const askPromise = Question.ask({
149
- sessionID: "ses_test",
150
- questions: [
151
- {
152
- question: "What would you like to do?",
153
- header: "Action",
154
- options: [
155
- { label: "Option 1", description: "First option" },
156
- { label: "Option 2", description: "Second option" },
157
- ],
158
- },
159
- ],
160
- })
161
-
162
- const pending = await Question.list()
163
- await Question.reject(pending[0].id)
164
-
165
- await expect(askPromise).rejects.toBeInstanceOf(Question.RejectedError)
166
- },
167
- })
168
- })
169
-
170
- test("reject - removes from pending list", async () => {
171
- await using tmp = await tmpdir({ git: true })
172
- await Instance.provide({
173
- directory: tmp.path,
174
- fn: async () => {
175
- const askPromise = Question.ask({
176
- sessionID: "ses_test",
177
- questions: [
178
- {
179
- question: "What would you like to do?",
180
- header: "Action",
181
- options: [
182
- { label: "Option 1", description: "First option" },
183
- { label: "Option 2", description: "Second option" },
184
- ],
185
- },
186
- ],
187
- })
188
-
189
- const pending = await Question.list()
190
- expect(pending.length).toBe(1)
191
-
192
- await Question.reject(pending[0].id)
193
- askPromise.catch(() => {}) // Ignore rejection
194
-
195
- const pendingAfter = await Question.list()
196
- expect(pendingAfter.length).toBe(0)
197
- },
198
- })
199
- })
200
-
201
- test("reject - does nothing for unknown requestID", async () => {
202
- await using tmp = await tmpdir({ git: true })
203
- await Instance.provide({
204
- directory: tmp.path,
205
- fn: async () => {
206
- await Question.reject("que_unknown")
207
- // Should not throw
208
- },
209
- })
210
- })
211
-
212
- // multiple questions tests
213
-
214
- test("ask - handles multiple questions", async () => {
215
- await using tmp = await tmpdir({ git: true })
216
- await Instance.provide({
217
- directory: tmp.path,
218
- fn: async () => {
219
- const questions = [
220
- {
221
- question: "What would you like to do?",
222
- header: "Action",
223
- options: [
224
- { label: "Build", description: "Build the project" },
225
- { label: "Test", description: "Run tests" },
226
- ],
227
- },
228
- {
229
- question: "Which environment?",
230
- header: "Env",
231
- options: [
232
- { label: "Dev", description: "Development" },
233
- { label: "Prod", description: "Production" },
234
- ],
235
- },
236
- ]
237
-
238
- const askPromise = Question.ask({
239
- sessionID: "ses_test",
240
- questions,
241
- })
242
-
243
- const pending = await Question.list()
244
-
245
- await Question.reply({
246
- requestID: pending[0].id,
247
- answers: [["Build"], ["Dev"]],
248
- })
249
-
250
- const answers = await askPromise
251
- expect(answers).toEqual([["Build"], ["Dev"]])
252
- },
253
- })
254
- })
255
-
256
- // list tests
257
-
258
- test("list - returns all pending requests", async () => {
259
- await using tmp = await tmpdir({ git: true })
260
- await Instance.provide({
261
- directory: tmp.path,
262
- fn: async () => {
263
- Question.ask({
264
- sessionID: "ses_test1",
265
- questions: [
266
- {
267
- question: "Question 1?",
268
- header: "Q1",
269
- options: [{ label: "A", description: "A" }],
270
- },
271
- ],
272
- })
273
-
274
- Question.ask({
275
- sessionID: "ses_test2",
276
- questions: [
277
- {
278
- question: "Question 2?",
279
- header: "Q2",
280
- options: [{ label: "B", description: "B" }],
281
- },
282
- ],
283
- })
284
-
285
- const pending = await Question.list()
286
- expect(pending.length).toBe(2)
287
- },
288
- })
289
- })
290
-
291
- test("list - returns empty when no pending", async () => {
292
- await using tmp = await tmpdir({ git: true })
293
- await Instance.provide({
294
- directory: tmp.path,
295
- fn: async () => {
296
- const pending = await Question.list()
297
- expect(pending.length).toBe(0)
298
- },
299
- })
300
- })
@@ -1,73 +0,0 @@
1
- import { describe, expect, test } from "bun:test"
2
- import { Scheduler } from "../src/scheduler"
3
- import { Instance } from "../src/project/instance"
4
- import { tmpdir } from "./fixture/fixture"
5
-
6
- describe("Scheduler.register", () => {
7
- const hour = 60 * 60 * 1000
8
-
9
- test("defaults to instance scope per directory", async () => {
10
- await using one = await tmpdir({ git: true })
11
- await using two = await tmpdir({ git: true })
12
- const runs = { count: 0 }
13
- const id = "scheduler.instance." + Math.random().toString(36).slice(2)
14
- const task = {
15
- id,
16
- interval: hour,
17
- run: async () => {
18
- runs.count += 1
19
- },
20
- }
21
-
22
- await Instance.provide({
23
- directory: one.path,
24
- fn: async () => {
25
- Scheduler.register(task)
26
- await Instance.dispose()
27
- },
28
- })
29
- expect(runs.count).toBe(1)
30
-
31
- await Instance.provide({
32
- directory: two.path,
33
- fn: async () => {
34
- Scheduler.register(task)
35
- await Instance.dispose()
36
- },
37
- })
38
- expect(runs.count).toBe(2)
39
- })
40
-
41
- test("global scope runs once across instances", async () => {
42
- await using one = await tmpdir({ git: true })
43
- await using two = await tmpdir({ git: true })
44
- const runs = { count: 0 }
45
- const id = "scheduler.global." + Math.random().toString(36).slice(2)
46
- const task = {
47
- id,
48
- interval: hour,
49
- run: async () => {
50
- runs.count += 1
51
- },
52
- scope: "global" as const,
53
- }
54
-
55
- await Instance.provide({
56
- directory: one.path,
57
- fn: async () => {
58
- Scheduler.register(task)
59
- await Instance.dispose()
60
- },
61
- })
62
- expect(runs.count).toBe(1)
63
-
64
- await Instance.provide({
65
- directory: two.path,
66
- fn: async () => {
67
- Scheduler.register(task)
68
- await Instance.dispose()
69
- },
70
- })
71
- expect(runs.count).toBe(1)
72
- })
73
- })
@@ -1,89 +0,0 @@
1
- import { describe, expect, test } from "bun:test"
2
- import { Instance } from "../../src/project/instance"
3
- import { Project } from "../../src/project/project"
4
- import { Session } from "../../src/session"
5
- import { Log } from "../../src/util/log"
6
- import { tmpdir } from "../fixture/fixture"
7
-
8
- Log.init({ print: false })
9
-
10
- describe("Session.listGlobal", () => {
11
- test("lists sessions across projects with project metadata", async () => {
12
- await using first = await tmpdir({ git: true })
13
- await using second = await tmpdir({ git: true })
14
-
15
- const firstSession = await Instance.provide({
16
- directory: first.path,
17
- fn: async () => Session.create({ title: "first-session" }),
18
- })
19
- const secondSession = await Instance.provide({
20
- directory: second.path,
21
- fn: async () => Session.create({ title: "second-session" }),
22
- })
23
-
24
- const sessions = [...Session.listGlobal({ limit: 200 })]
25
- const ids = sessions.map((session) => session.id)
26
-
27
- expect(ids).toContain(firstSession.id)
28
- expect(ids).toContain(secondSession.id)
29
-
30
- const firstProject = Project.get(firstSession.projectID)
31
- const secondProject = Project.get(secondSession.projectID)
32
-
33
- const firstItem = sessions.find((session) => session.id === firstSession.id)
34
- const secondItem = sessions.find((session) => session.id === secondSession.id)
35
-
36
- expect(firstItem?.project?.id).toBe(firstProject?.id)
37
- expect(firstItem?.project?.worktree).toBe(firstProject?.worktree)
38
- expect(secondItem?.project?.id).toBe(secondProject?.id)
39
- expect(secondItem?.project?.worktree).toBe(secondProject?.worktree)
40
- })
41
-
42
- test("excludes archived sessions by default", async () => {
43
- await using tmp = await tmpdir({ git: true })
44
-
45
- const archived = await Instance.provide({
46
- directory: tmp.path,
47
- fn: async () => Session.create({ title: "archived-session" }),
48
- })
49
-
50
- await Instance.provide({
51
- directory: tmp.path,
52
- fn: async () => Session.setArchived({ sessionID: archived.id, time: Date.now() }),
53
- })
54
-
55
- const sessions = [...Session.listGlobal({ limit: 200 })]
56
- const ids = sessions.map((session) => session.id)
57
-
58
- expect(ids).not.toContain(archived.id)
59
-
60
- const allSessions = [...Session.listGlobal({ limit: 200, archived: true })]
61
- const allIds = allSessions.map((session) => session.id)
62
-
63
- expect(allIds).toContain(archived.id)
64
- })
65
-
66
- test("supports cursor pagination", async () => {
67
- await using tmp = await tmpdir({ git: true })
68
-
69
- const first = await Instance.provide({
70
- directory: tmp.path,
71
- fn: async () => Session.create({ title: "page-one" }),
72
- })
73
- await new Promise((resolve) => setTimeout(resolve, 5))
74
- const second = await Instance.provide({
75
- directory: tmp.path,
76
- fn: async () => Session.create({ title: "page-two" }),
77
- })
78
-
79
- const page = [...Session.listGlobal({ directory: tmp.path, limit: 1 })]
80
- expect(page.length).toBe(1)
81
- expect(page[0].id).toBe(second.id)
82
-
83
- const next = [...Session.listGlobal({ directory: tmp.path, limit: 10, cursor: page[0].time.updated })]
84
- const ids = next.map((session) => session.id)
85
-
86
- expect(ids).toContain(first.id)
87
- expect(ids).not.toContain(second.id)
88
- })
89
- })
@@ -1,90 +0,0 @@
1
- import { describe, expect, test } from "bun:test"
2
- import path from "path"
3
- import { Instance } from "../../src/project/instance"
4
- import { Session } from "../../src/session"
5
- import { Log } from "../../src/util/log"
6
-
7
- const projectRoot = path.join(__dirname, "../..")
8
- Log.init({ print: false })
9
-
10
- describe("Session.list", () => {
11
- test("filters by directory", async () => {
12
- await Instance.provide({
13
- directory: projectRoot,
14
- fn: async () => {
15
- const first = await Session.create({})
16
-
17
- const otherDir = path.join(projectRoot, "..", "__session_list_other")
18
- const second = await Instance.provide({
19
- directory: otherDir,
20
- fn: async () => Session.create({}),
21
- })
22
-
23
- const sessions = [...Session.list({ directory: projectRoot })]
24
- const ids = sessions.map((s) => s.id)
25
-
26
- expect(ids).toContain(first.id)
27
- expect(ids).not.toContain(second.id)
28
- },
29
- })
30
- })
31
-
32
- test("filters root sessions", async () => {
33
- await Instance.provide({
34
- directory: projectRoot,
35
- fn: async () => {
36
- const root = await Session.create({ title: "root-session" })
37
- const child = await Session.create({ title: "child-session", parentID: root.id })
38
-
39
- const sessions = [...Session.list({ roots: true })]
40
- const ids = sessions.map((s) => s.id)
41
-
42
- expect(ids).toContain(root.id)
43
- expect(ids).not.toContain(child.id)
44
- },
45
- })
46
- })
47
-
48
- test("filters by start time", async () => {
49
- await Instance.provide({
50
- directory: projectRoot,
51
- fn: async () => {
52
- const session = await Session.create({ title: "new-session" })
53
- const futureStart = Date.now() + 86400000
54
-
55
- const sessions = [...Session.list({ start: futureStart })]
56
- expect(sessions.length).toBe(0)
57
- },
58
- })
59
- })
60
-
61
- test("filters by search term", async () => {
62
- await Instance.provide({
63
- directory: projectRoot,
64
- fn: async () => {
65
- await Session.create({ title: "unique-search-term-abc" })
66
- await Session.create({ title: "other-session-xyz" })
67
-
68
- const sessions = [...Session.list({ search: "unique-search" })]
69
- const titles = sessions.map((s) => s.title)
70
-
71
- expect(titles).toContain("unique-search-term-abc")
72
- expect(titles).not.toContain("other-session-xyz")
73
- },
74
- })
75
- })
76
-
77
- test("respects limit parameter", async () => {
78
- await Instance.provide({
79
- directory: projectRoot,
80
- fn: async () => {
81
- await Session.create({ title: "session-1" })
82
- await Session.create({ title: "session-2" })
83
- await Session.create({ title: "session-3" })
84
-
85
- const sessions = [...Session.list({ limit: 2 })]
86
- expect(sessions.length).toBe(2)
87
- },
88
- })
89
- })
90
- })