t3code-cli 0.10.0 → 0.12.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 (328) hide show
  1. package/README.md +75 -11
  2. package/dist/application.js +2 -2
  3. package/dist/auth.js +1 -1
  4. package/dist/bin.js +86754 -2675
  5. package/dist/cli.js +15 -2
  6. package/dist/config.js +120 -2
  7. package/dist/connection.js +2 -12
  8. package/dist/index.js +1 -1
  9. package/dist/node.js +6 -0
  10. package/dist/orchestration.js +2 -2
  11. package/dist/rpc.js +2 -2
  12. package/dist/runtime.js +2 -2
  13. package/dist/shared.js +43850 -38040
  14. package/dist/src/application/actions.d.ts +25 -0
  15. package/dist/src/application/index.d.ts +3 -2
  16. package/dist/src/application/layer.d.ts +99 -842
  17. package/dist/src/application/model-selection.d.ts +17 -2
  18. package/dist/src/application/models.d.ts +1 -87
  19. package/dist/src/application/project-commands.d.ts +31 -2
  20. package/dist/src/application/projects.d.ts +15 -137
  21. package/dist/src/application/service.d.ts +147 -5
  22. package/dist/src/application/shell-sequence.d.ts +5 -4
  23. package/dist/src/application/terminals.d.ts +66 -0
  24. package/dist/src/application/thread-commands.d.ts +48 -1
  25. package/dist/src/application/thread-update.d.ts +8 -0
  26. package/dist/src/application/thread-wait.d.ts +4 -6
  27. package/dist/src/application/threads.d.ts +36 -14
  28. package/dist/src/auth/error.d.ts +2 -1
  29. package/dist/src/auth/index.d.ts +1 -1
  30. package/dist/src/auth/layer.d.ts +34 -6
  31. package/dist/src/auth/local-base-dir.d.ts +5 -2
  32. package/dist/src/auth/local-origin.d.ts +3 -3
  33. package/dist/src/auth/local-token.d.ts +3 -3
  34. package/dist/src/auth/service.d.ts +15 -3
  35. package/dist/src/auth/type.d.ts +40 -1
  36. package/dist/src/cli/env/flag.d.ts +2 -0
  37. package/dist/src/cli/env/index.d.ts +2 -0
  38. package/dist/src/cli/env/selection-layer.d.ts +3 -0
  39. package/dist/src/cli/flags.d.ts +5 -0
  40. package/dist/src/cli/format/index.d.ts +1 -0
  41. package/dist/src/cli/format/output.d.ts +12 -0
  42. package/dist/src/cli/index.d.ts +4 -0
  43. package/dist/src/cli/runtime/index.d.ts +1 -0
  44. package/dist/src/cli/runtime/service.d.ts +16 -0
  45. package/dist/src/{scope → cli/scope}/resolve.d.ts +5 -4
  46. package/dist/src/config/config.d.ts +49 -0
  47. package/dist/src/config/credential/cipher-node.d.ts +3 -0
  48. package/dist/src/config/credential/cipher-web.d.ts +3 -0
  49. package/dist/src/config/credential/cipher.d.ts +29 -0
  50. package/dist/src/config/credential/env.d.ts +3 -0
  51. package/dist/src/config/credential/error.d.ts +8 -0
  52. package/dist/src/config/credential/index.d.ts +6 -0
  53. package/dist/src/config/credential/service.d.ts +34 -0
  54. package/dist/src/config/env/env.d.ts +55 -0
  55. package/dist/src/config/env/index.d.ts +2 -0
  56. package/dist/src/{layout/base-dir.d.ts → config/env/layout.d.ts} +0 -1
  57. package/dist/src/config/environment-name/index.d.ts +1 -0
  58. package/dist/src/config/environment-name/name.d.ts +10 -0
  59. package/dist/src/config/error.d.ts +4 -11
  60. package/dist/src/config/index.d.ts +10 -5
  61. package/dist/src/config/keystore/error.d.ts +8 -0
  62. package/dist/src/config/keystore/file.d.ts +9 -0
  63. package/dist/src/config/keystore/index.d.ts +4 -0
  64. package/dist/src/config/keystore/keyring-node.d.ts +4 -0
  65. package/dist/src/config/keystore/service.d.ts +33 -0
  66. package/dist/src/config/paths/index.d.ts +1 -0
  67. package/dist/src/config/paths/paths.d.ts +6 -0
  68. package/dist/src/config/persist/file-mode.d.ts +4 -0
  69. package/dist/src/config/persist/migration.d.ts +68 -0
  70. package/dist/src/config/persist/persist.d.ts +40 -0
  71. package/dist/src/config/persist/schema.d.ts +68 -0
  72. package/dist/src/config/resolve/resolve.d.ts +40 -0
  73. package/dist/src/config/selection/index.d.ts +1 -0
  74. package/dist/src/config/selection/resolve.d.ts +4 -0
  75. package/dist/src/config/selection/service.d.ts +10 -0
  76. package/dist/src/config/types.d.ts +36 -0
  77. package/dist/src/config/url/error.d.ts +10 -0
  78. package/dist/src/config/url/index.d.ts +2 -0
  79. package/dist/src/connection/index.d.ts +0 -1
  80. package/dist/src/contracts/index.d.ts +1 -1
  81. package/dist/src/domain/error.d.ts +27 -1
  82. package/dist/src/domain/helpers.d.ts +8 -2
  83. package/dist/src/domain/model-config.d.ts +30 -1
  84. package/dist/src/domain/thread-activities.d.ts +1 -1
  85. package/dist/src/domain/thread-lifecycle.d.ts +3 -2
  86. package/dist/src/index.d.ts +1 -2
  87. package/dist/src/{connection/node.d.ts → node/connection.d.ts} +1 -1
  88. package/dist/src/node/index.d.ts +1 -0
  89. package/dist/src/orchestration/index.d.ts +1 -1
  90. package/dist/src/orchestration/layer.d.ts +120 -829
  91. package/dist/src/orchestration/service.d.ts +2 -1
  92. package/dist/src/rpc/error.d.ts +5 -3
  93. package/dist/src/rpc/index.d.ts +5 -1
  94. package/dist/src/rpc/layer.d.ts +1166 -40
  95. package/dist/src/rpc/operation.d.ts +6280 -0
  96. package/dist/src/rpc/ws-group.d.ts +372 -27
  97. package/dist/src/runtime/index.d.ts +1 -2
  98. package/dist/src/runtime/layer.d.ts +12 -8
  99. package/dist/src/t3tools/index.d.ts +1 -1
  100. package/dist/t3tools.js +2 -2
  101. package/package.json +27 -19
  102. package/src/application/actions.test.ts +328 -0
  103. package/src/application/actions.ts +475 -0
  104. package/src/application/index.ts +32 -2
  105. package/src/application/layer.ts +43 -5
  106. package/src/application/model-selection.ts +70 -4
  107. package/src/application/models.ts +14 -14
  108. package/src/application/project-commands.ts +38 -5
  109. package/src/application/projects.ts +49 -20
  110. package/src/application/service.ts +275 -78
  111. package/src/application/shell-sequence.ts +6 -8
  112. package/src/application/terminals.ts +208 -0
  113. package/src/application/thread-commands.test.ts +43 -0
  114. package/src/application/thread-commands.ts +82 -1
  115. package/src/application/thread-update.ts +72 -0
  116. package/src/application/thread-wait.ts +47 -48
  117. package/src/application/threads.test.ts +172 -0
  118. package/src/application/threads.ts +128 -45
  119. package/src/auth/error.ts +2 -1
  120. package/src/auth/index.ts +7 -0
  121. package/src/auth/layer.test-utils.ts +34 -0
  122. package/src/auth/layer.ts +116 -34
  123. package/src/auth/local-base-dir.ts +13 -6
  124. package/src/auth/local-origin.ts +10 -7
  125. package/src/auth/local-token.ts +28 -23
  126. package/src/auth/service.test.ts +200 -0
  127. package/src/auth/service.ts +27 -3
  128. package/src/auth/transport.ts +2 -2
  129. package/src/auth/type.ts +47 -1
  130. package/src/bin.ts +26 -6
  131. package/src/cli/action-format.ts +110 -0
  132. package/src/cli/action.ts +398 -0
  133. package/src/cli/app.ts +8 -0
  134. package/src/cli/auth.ts +76 -23
  135. package/src/cli/env/flag.ts +8 -0
  136. package/src/cli/env/index.ts +2 -0
  137. package/src/cli/env/selection-layer.test.ts +49 -0
  138. package/src/cli/env/selection-layer.ts +27 -0
  139. package/src/cli/env.ts +107 -0
  140. package/src/cli/error.ts +26 -3
  141. package/src/cli/extra-args.test.ts +32 -0
  142. package/src/cli/extra-args.ts +22 -0
  143. package/src/cli/flags.ts +26 -1
  144. package/src/cli/format/auth.ts +92 -0
  145. package/src/cli/format/human.ts +131 -0
  146. package/src/cli/format/index.ts +12 -0
  147. package/src/cli/format/model.ts +29 -0
  148. package/src/cli/format/output.ts +51 -0
  149. package/src/cli/format/project.ts +32 -0
  150. package/src/cli/format/terminal.ts +87 -0
  151. package/src/cli/format/thread.test.ts +34 -0
  152. package/src/cli/format/thread.ts +185 -0
  153. package/src/cli/index.ts +20 -0
  154. package/src/cli/input/layer.ts +20 -0
  155. package/src/cli/input/service.ts +1 -0
  156. package/src/cli/interaction/confirm.ts +64 -0
  157. package/src/cli/interaction/self-action.ts +36 -0
  158. package/src/cli/message-input.ts +1 -1
  159. package/src/cli/model.ts +9 -5
  160. package/src/cli/project.ts +15 -8
  161. package/src/cli/projects/delete.ts +51 -0
  162. package/src/cli/require.ts +9 -7
  163. package/src/cli/runtime/index.ts +1 -0
  164. package/src/cli/runtime/service.ts +28 -0
  165. package/src/{scope → cli/scope}/resolve.ts +11 -9
  166. package/src/cli/terminal/attach.ts +29 -0
  167. package/src/cli/terminal/create.ts +55 -0
  168. package/src/cli/terminal/destroy.ts +62 -0
  169. package/src/cli/terminal/encoding.test.ts +63 -0
  170. package/src/cli/terminal/encoding.ts +23 -0
  171. package/src/cli/terminal/error.ts +14 -0
  172. package/src/cli/terminal/io-node-layer.ts +82 -0
  173. package/src/cli/terminal/io-service.ts +25 -0
  174. package/src/cli/terminal/list.ts +36 -0
  175. package/src/cli/terminal/read.test-utils.ts +48 -0
  176. package/src/cli/terminal/read.test.ts +35 -0
  177. package/src/cli/terminal/read.ts +99 -0
  178. package/src/cli/terminal/scope.ts +35 -0
  179. package/src/cli/terminal/shared.ts +250 -0
  180. package/src/cli/terminal/stream.ts +61 -0
  181. package/src/cli/terminal/wait.test.ts +148 -0
  182. package/src/cli/terminal/wait.ts +223 -0
  183. package/src/cli/terminal/write.test.ts +34 -0
  184. package/src/cli/terminal/write.ts +133 -0
  185. package/src/cli/terminal.ts +26 -0
  186. package/src/cli/thread.ts +8 -0
  187. package/src/cli/threads/approve.ts +11 -7
  188. package/src/cli/threads/archive.ts +23 -25
  189. package/src/cli/threads/callback.ts +6 -4
  190. package/src/cli/threads/delete.ts +67 -0
  191. package/src/cli/threads/interrupt.ts +57 -0
  192. package/src/cli/threads/list.test.ts +71 -0
  193. package/src/cli/threads/list.ts +25 -13
  194. package/src/cli/threads/messages.ts +11 -7
  195. package/src/cli/threads/respond.ts +11 -7
  196. package/src/cli/threads/send.ts +39 -9
  197. package/src/cli/threads/show.ts +11 -7
  198. package/src/cli/threads/start.ts +18 -13
  199. package/src/cli/threads/unarchive.ts +48 -0
  200. package/src/cli/threads/update.ts +155 -0
  201. package/src/cli/threads/wait.ts +11 -7
  202. package/src/cli/wait-events.ts +1 -1
  203. package/src/config/config.ts +260 -0
  204. package/src/config/credential/cipher-node.ts +40 -0
  205. package/src/config/credential/cipher-web.ts +82 -0
  206. package/src/config/credential/cipher.ts +39 -0
  207. package/src/config/credential/env.ts +13 -0
  208. package/src/config/credential/error.ts +9 -0
  209. package/src/config/credential/index.ts +6 -0
  210. package/src/config/credential/service.test-utils.ts +25 -0
  211. package/src/config/credential/service.test.ts +116 -0
  212. package/src/config/credential/service.ts +205 -0
  213. package/src/config/env/env.test-utils.ts +14 -0
  214. package/src/config/env/env.ts +81 -0
  215. package/src/config/env/index.ts +2 -0
  216. package/src/{layout/base-dir.ts → config/env/layout.ts} +0 -9
  217. package/src/config/environment-name/index.ts +7 -0
  218. package/src/config/environment-name/name.test.ts +66 -0
  219. package/src/config/environment-name/name.ts +49 -0
  220. package/src/config/error.ts +6 -8
  221. package/src/config/index.ts +16 -5
  222. package/src/config/keystore/error.ts +9 -0
  223. package/src/config/keystore/file.ts +96 -0
  224. package/src/config/keystore/index.ts +4 -0
  225. package/src/config/keystore/keyring-node.test.ts +12 -0
  226. package/src/config/keystore/keyring-node.ts +100 -0
  227. package/src/config/keystore/service.test-utils.ts +15 -0
  228. package/src/config/keystore/service.test.ts +18 -0
  229. package/src/config/keystore/service.ts +29 -0
  230. package/src/config/layer.test-utils.ts +45 -0
  231. package/src/config/layer.test.ts +209 -0
  232. package/src/config/paths/index.ts +1 -0
  233. package/src/config/paths/paths.ts +36 -0
  234. package/src/config/persist/file-mode.ts +21 -0
  235. package/src/config/persist/migration.test.ts +71 -0
  236. package/src/config/persist/migration.ts +82 -0
  237. package/src/config/persist/persist.test.ts +113 -0
  238. package/src/config/persist/persist.ts +65 -0
  239. package/src/config/persist/schema.ts +37 -0
  240. package/src/config/platform.test-utils.ts +20 -0
  241. package/src/config/resolve/resolve.test-utils.ts +20 -0
  242. package/src/config/resolve/resolve.test.ts +137 -0
  243. package/src/config/resolve/resolve.ts +104 -0
  244. package/src/config/selection/index.ts +1 -0
  245. package/src/config/selection/resolve.ts +14 -0
  246. package/src/config/selection/service.ts +29 -0
  247. package/src/config/temp-home.test-utils.ts +7 -0
  248. package/src/config/types.ts +49 -0
  249. package/src/config/url/error.ts +8 -0
  250. package/src/config/url/index.ts +7 -0
  251. package/src/config/url/url.test.ts +31 -0
  252. package/src/connection/index.ts +0 -1
  253. package/src/contracts/index.ts +1 -1
  254. package/src/domain/error.ts +39 -1
  255. package/src/domain/helpers.test.ts +1 -1
  256. package/src/domain/helpers.ts +1 -1
  257. package/src/domain/model-config.ts +5 -1
  258. package/src/domain/thread-activities.test.ts +1 -1
  259. package/src/domain/thread-activities.ts +1 -1
  260. package/src/domain/thread-lifecycle.test.ts +1 -1
  261. package/src/domain/thread-lifecycle.ts +15 -1
  262. package/src/effect.test-utils.ts +25 -0
  263. package/src/index.ts +1 -1
  264. package/src/{connection/node.ts → node/connection.ts} +1 -1
  265. package/src/node/index.ts +1 -0
  266. package/src/orchestration/index.ts +1 -1
  267. package/src/orchestration/layer.ts +34 -86
  268. package/src/orchestration/service.ts +5 -1
  269. package/src/rpc/error.ts +12 -2
  270. package/src/rpc/index.ts +5 -1
  271. package/src/rpc/layer.ts +1 -1
  272. package/src/rpc/operation.ts +83 -0
  273. package/src/rpc/ws-group.ts +25 -1
  274. package/src/runtime/index.ts +2 -1
  275. package/src/runtime/layer.test-utils.ts +32 -0
  276. package/src/runtime/layer.test.ts +66 -0
  277. package/src/runtime/layer.ts +22 -11
  278. package/src/t3tools/index.ts +1 -1
  279. package/src/types/marked-terminal.d.ts +11 -0
  280. package/dist/layout.js +0 -2
  281. package/dist/scope.js +0 -2
  282. package/dist/src/cli/output-format.d.ts +0 -9
  283. package/dist/src/config/layer.d.ts +0 -22
  284. package/dist/src/config/service.d.ts +0 -22
  285. package/dist/src/environment/layer.d.ts +0 -3
  286. package/dist/src/environment/service.d.ts +0 -12
  287. package/dist/src/layout/index.d.ts +0 -1
  288. package/dist/upstream-t3code/packages/contracts/src/auth.d.ts +0 -441
  289. package/dist/upstream-t3code/packages/contracts/src/baseSchemas.d.ts +0 -38
  290. package/dist/upstream-t3code/packages/contracts/src/desktopBootstrap.d.ts +0 -14
  291. package/dist/upstream-t3code/packages/contracts/src/editor.d.ts +0 -124
  292. package/dist/upstream-t3code/packages/contracts/src/environment.d.ts +0 -64
  293. package/dist/upstream-t3code/packages/contracts/src/environmentHttp.d.ts +0 -785
  294. package/dist/upstream-t3code/packages/contracts/src/filesystem.d.ts +0 -26
  295. package/dist/upstream-t3code/packages/contracts/src/git.d.ts +0 -500
  296. package/dist/upstream-t3code/packages/contracts/src/index.d.ts +0 -25
  297. package/dist/upstream-t3code/packages/contracts/src/ipc.d.ts +0 -496
  298. package/dist/upstream-t3code/packages/contracts/src/keybindings.d.ts +0 -133
  299. package/dist/upstream-t3code/packages/contracts/src/model.d.ts +0 -112
  300. package/dist/upstream-t3code/packages/contracts/src/orchestration.d.ts +0 -7338
  301. package/dist/upstream-t3code/packages/contracts/src/project.d.ts +0 -45
  302. package/dist/upstream-t3code/packages/contracts/src/provider.d.ts +0 -130
  303. package/dist/upstream-t3code/packages/contracts/src/providerInstance.d.ts +0 -99
  304. package/dist/upstream-t3code/packages/contracts/src/providerRuntime.d.ts +0 -4450
  305. package/dist/upstream-t3code/packages/contracts/src/relay.d.ts +0 -1262
  306. package/dist/upstream-t3code/packages/contracts/src/relayClient.d.ts +0 -48
  307. package/dist/upstream-t3code/packages/contracts/src/remoteAccess.d.ts +0 -45
  308. package/dist/upstream-t3code/packages/contracts/src/review.d.ts +0 -37
  309. package/dist/upstream-t3code/packages/contracts/src/rpc.d.ts +0 -8710
  310. package/dist/upstream-t3code/packages/contracts/src/server.d.ts +0 -2336
  311. package/dist/upstream-t3code/packages/contracts/src/settings.d.ts +0 -292
  312. package/dist/upstream-t3code/packages/contracts/src/sourceControl.d.ts +0 -177
  313. package/dist/upstream-t3code/packages/contracts/src/terminal.d.ts +0 -333
  314. package/dist/upstream-t3code/packages/contracts/src/vcs.d.ts +0 -148
  315. package/src/cli/auth-format.ts +0 -45
  316. package/src/cli/model-format.ts +0 -18
  317. package/src/cli/output-format.ts +0 -41
  318. package/src/cli/project-format.ts +0 -11
  319. package/src/cli/thread-format.ts +0 -121
  320. package/src/config/layer.ts +0 -105
  321. package/src/config/service.ts +0 -26
  322. package/src/environment/layer.ts +0 -12
  323. package/src/environment/service.ts +0 -13
  324. package/src/layout/index.ts +0 -1
  325. /package/dist/src/{scope → cli/scope}/index.d.ts +0 -0
  326. /package/dist/src/config/{url.d.ts → url/url.d.ts} +0 -0
  327. /package/src/{scope → cli/scope}/index.ts +0 -0
  328. /package/src/config/{url.ts → url/url.ts} +0 -0
@@ -0,0 +1,208 @@
1
+ import * as Crypto from "effect/Crypto";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Option from "effect/Option";
4
+ import * as Stream from "effect/Stream";
5
+ import { WS_METHODS, type TerminalSummary } from "@t3tools/contracts";
6
+
7
+ import { ProjectLookupError, TerminalLookupError, ThreadLookupError } from "../domain/error.ts";
8
+ import { findProjectById } from "../domain/helpers.ts";
9
+ import { T3Orchestration } from "../orchestration/service.ts";
10
+ import { RpcError } from "../rpc/error.ts";
11
+ import { T3RpcOperations } from "../rpc/operation.ts";
12
+ import type { T3TerminalApplicationService, CreateTerminalInput, TerminalRef } from "./service.ts";
13
+
14
+ export const makeTerminalApplication = Effect.fn("makeTerminalApplication")(function* () {
15
+ const crypto = yield* Crypto.Crypto;
16
+ const orchestration = yield* T3Orchestration;
17
+ const rpc = yield* T3RpcOperations;
18
+
19
+ const attachTerminal: T3TerminalApplicationService["attachTerminal"] = (input) =>
20
+ rpc.subscribe(WS_METHODS.terminalAttach, (client) =>
21
+ client[WS_METHODS.terminalAttach]({
22
+ threadId: input.terminal.threadId,
23
+ terminalId: input.terminal.terminalId,
24
+ cwd: input.terminal.cwd,
25
+ worktreePath: input.terminal.worktreePath,
26
+ ...(input.cols !== undefined ? { cols: input.cols } : {}),
27
+ ...(input.rows !== undefined ? { rows: input.rows } : {}),
28
+ }),
29
+ );
30
+
31
+ const watchTerminalMetadata: T3TerminalApplicationService["watchTerminalMetadata"] = () =>
32
+ rpc.subscribe(WS_METHODS.subscribeTerminalMetadata, (client) =>
33
+ client[WS_METHODS.subscribeTerminalMetadata]({}),
34
+ );
35
+
36
+ const getTerminalMetadataSnapshot = (): Effect.Effect<ReadonlyArray<TerminalSummary>, RpcError> =>
37
+ Effect.gen(function* () {
38
+ const item = yield* Stream.runHead(
39
+ rpc.subscribe(WS_METHODS.subscribeTerminalMetadata, (client) =>
40
+ client[WS_METHODS.subscribeTerminalMetadata]({}),
41
+ ),
42
+ );
43
+ const value = Option.getOrUndefined(item);
44
+ if (value === undefined || value.type !== "snapshot") {
45
+ return yield* Effect.fail(
46
+ new RpcError({
47
+ message: "server did not return terminal metadata snapshot",
48
+ method: WS_METHODS.subscribeTerminalMetadata,
49
+ }),
50
+ );
51
+ }
52
+ return value.terminals;
53
+ });
54
+
55
+ const listTerminals = Effect.fn("T3ApplicationLive.listTerminals")(function* (threadId: string) {
56
+ const snapshot = yield* orchestration.getShellSnapshot();
57
+ if (snapshot.threads.find((candidate) => candidate.id === threadId) === undefined) {
58
+ return yield* Effect.fail(
59
+ new ThreadLookupError({
60
+ message: `thread not found: ${threadId}`,
61
+ threadId,
62
+ }),
63
+ );
64
+ }
65
+ const terminals = yield* getTerminalMetadataSnapshot();
66
+ return terminals.filter((terminal) => terminal.threadId === threadId);
67
+ });
68
+
69
+ const getTerminal = Effect.fn("T3ApplicationLive.getTerminal")(function* (terminal: TerminalRef) {
70
+ const snapshot = yield* orchestration.getShellSnapshot();
71
+ if (snapshot.threads.find((candidate) => candidate.id === terminal.threadId) === undefined) {
72
+ return yield* Effect.fail(
73
+ new ThreadLookupError({
74
+ message: `thread not found: ${terminal.threadId}`,
75
+ threadId: terminal.threadId,
76
+ }),
77
+ );
78
+ }
79
+ const terminals = yield* getTerminalMetadataSnapshot();
80
+ const match = terminals.find(
81
+ (candidate) =>
82
+ candidate.threadId === terminal.threadId && candidate.terminalId === terminal.terminalId,
83
+ );
84
+ if (match !== undefined) {
85
+ return match;
86
+ }
87
+ return yield* Effect.fail(
88
+ new TerminalLookupError({
89
+ message: `terminal not found: ${terminal.terminalId} in thread ${terminal.threadId}`,
90
+ threadId: terminal.threadId,
91
+ terminalId: terminal.terminalId,
92
+ }),
93
+ );
94
+ });
95
+
96
+ const createTerminal = Effect.fn("T3ApplicationLive.createTerminal")(function* (
97
+ input: CreateTerminalInput,
98
+ ) {
99
+ const snapshot = yield* orchestration.getShellSnapshot();
100
+ const thread = snapshot.threads.find((candidate) => candidate.id === input.threadId);
101
+ if (thread === undefined) {
102
+ return yield* Effect.fail(
103
+ new ThreadLookupError({
104
+ message: `thread not found: ${input.threadId}`,
105
+ threadId: input.threadId,
106
+ }),
107
+ );
108
+ }
109
+ const project = findProjectById(snapshot, thread.projectId);
110
+ if (project === null) {
111
+ return yield* Effect.fail(
112
+ new ProjectLookupError({
113
+ message: `project not found for thread: ${input.threadId}`,
114
+ ref: thread.projectId,
115
+ }),
116
+ );
117
+ }
118
+
119
+ const terminalId = input.terminalId ?? `t3cli-${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`;
120
+ const cwd = thread.worktreePath ?? project.workspaceRoot;
121
+ const opened = yield* rpc.run(WS_METHODS.terminalOpen, (client) =>
122
+ client[WS_METHODS.terminalOpen]({
123
+ threadId: thread.id,
124
+ terminalId,
125
+ cwd,
126
+ worktreePath: thread.worktreePath ?? null,
127
+ ...(input.env !== undefined ? { env: input.env } : {}),
128
+ }),
129
+ );
130
+
131
+ if (input.command !== undefined) {
132
+ yield* rpc.run(WS_METHODS.terminalWrite, (client) =>
133
+ client[WS_METHODS.terminalWrite]({
134
+ threadId: thread.id,
135
+ terminalId,
136
+ data: `${input.command}\r`,
137
+ }),
138
+ );
139
+ const refreshed = yield* Stream.runHead(
140
+ attachTerminal({
141
+ terminal: {
142
+ threadId: opened.threadId,
143
+ terminalId: opened.terminalId,
144
+ cwd: opened.cwd,
145
+ worktreePath: opened.worktreePath,
146
+ },
147
+ }),
148
+ );
149
+ const event = Option.getOrUndefined(refreshed);
150
+ if (event !== undefined && (event.type === "snapshot" || event.type === "restarted")) {
151
+ return event.snapshot;
152
+ }
153
+ }
154
+
155
+ return opened;
156
+ });
157
+
158
+ const writeTerminal = Effect.fn("T3ApplicationLive.writeTerminal")(function* (input: {
159
+ readonly terminal: TerminalRef;
160
+ readonly data: string;
161
+ }) {
162
+ yield* rpc.run(WS_METHODS.terminalWrite, (client) =>
163
+ client[WS_METHODS.terminalWrite]({
164
+ threadId: input.terminal.threadId,
165
+ terminalId: input.terminal.terminalId,
166
+ data: input.data,
167
+ }),
168
+ );
169
+ });
170
+
171
+ const resizeTerminal = Effect.fn("T3ApplicationLive.resizeTerminal")(function* (input: {
172
+ readonly terminal: TerminalRef;
173
+ readonly cols: number;
174
+ readonly rows: number;
175
+ }) {
176
+ yield* rpc.run(WS_METHODS.terminalResize, (client) =>
177
+ client[WS_METHODS.terminalResize]({
178
+ threadId: input.terminal.threadId,
179
+ terminalId: input.terminal.terminalId,
180
+ cols: input.cols,
181
+ rows: input.rows,
182
+ }),
183
+ );
184
+ });
185
+
186
+ const destroyTerminal = Effect.fn("T3ApplicationLive.destroyTerminal")(function* (
187
+ terminal: TerminalRef,
188
+ ) {
189
+ yield* rpc.run(WS_METHODS.terminalClose, (client) =>
190
+ client[WS_METHODS.terminalClose]({
191
+ threadId: terminal.threadId,
192
+ terminalId: terminal.terminalId,
193
+ deleteHistory: true,
194
+ }),
195
+ );
196
+ });
197
+
198
+ return {
199
+ attachTerminal,
200
+ createTerminal,
201
+ destroyTerminal,
202
+ getTerminal,
203
+ listTerminals,
204
+ resizeTerminal,
205
+ watchTerminalMetadata,
206
+ writeTerminal,
207
+ } satisfies T3TerminalApplicationService;
208
+ });
@@ -0,0 +1,43 @@
1
+ import "vite-plus/test/config";
2
+
3
+ import * as Effect from "effect/Effect";
4
+ import * as NodeServices from "@effect/platform-node/NodeServices";
5
+ import { assert, describe, it } from "@effect/vitest";
6
+
7
+ import { makeThreadInterruptCommand, makeThreadUnarchiveCommand } from "./thread-commands.ts";
8
+
9
+ describe("thread command builders", () => {
10
+ it.layer(NodeServices.layer)("thread command builders", (t) => {
11
+ t.effect("makeThreadInterruptCommand includes turnId when provided", () =>
12
+ Effect.gen(function* () {
13
+ const command = yield* makeThreadInterruptCommand({
14
+ threadId: "thread-1",
15
+ turnId: "turn-1",
16
+ });
17
+ assert.equal(command.type, "thread.turn.interrupt");
18
+ assert.equal(command.threadId, "thread-1");
19
+ assert.equal(command.turnId, "turn-1");
20
+ assert.equal(typeof command.createdAt, "string");
21
+ }),
22
+ );
23
+
24
+ t.effect("makeThreadInterruptCommand omits turnId when not provided", () =>
25
+ Effect.gen(function* () {
26
+ const command = yield* makeThreadInterruptCommand({
27
+ threadId: "thread-1",
28
+ });
29
+ assert.equal(command.type, "thread.turn.interrupt");
30
+ assert.equal(command.threadId, "thread-1");
31
+ assert.equal("turnId" in command, false);
32
+ }),
33
+ );
34
+
35
+ t.effect("makeThreadUnarchiveCommand builds thread.unarchive command", () =>
36
+ Effect.gen(function* () {
37
+ const command = yield* makeThreadUnarchiveCommand("thread-1");
38
+ assert.equal(command.type, "thread.unarchive");
39
+ assert.equal(command.threadId, "thread-1");
40
+ }),
41
+ );
42
+ });
43
+ });
@@ -6,12 +6,13 @@ import {
6
6
  CommandId,
7
7
  MessageId,
8
8
  ThreadId,
9
+ TurnId,
9
10
  type ClientOrchestrationCommand,
10
11
  type ModelSelection,
11
12
  type OrchestrationProjectShell,
12
13
  type ProviderApprovalDecision,
13
14
  type ProviderUserInputAnswers,
14
- } from "#t3tools/contracts";
15
+ } from "@t3tools/contracts";
15
16
  import type { ServerConfigForCli } from "../orchestration/service.ts";
16
17
  import { resolveModelSelection } from "./model-selection.ts";
17
18
  import type { SendThreadInput, StartThreadInput } from "./service.ts";
@@ -101,6 +102,63 @@ export const makeThreadArchiveCommand = Effect.fn("makeThreadArchiveCommand")(fu
101
102
  } satisfies Extract<ClientOrchestrationCommand, { readonly type: "thread.archive" }>;
102
103
  });
103
104
 
105
+ export const makeThreadUnarchiveCommand = Effect.fn("makeThreadUnarchiveCommand")(function* (
106
+ threadId: string,
107
+ ) {
108
+ const crypto = yield* Crypto.Crypto;
109
+ return {
110
+ type: "thread.unarchive",
111
+ commandId: CommandId.make(
112
+ `t3cli:thread-unarchive:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
113
+ ),
114
+ threadId: ThreadId.make(threadId),
115
+ } satisfies Extract<ClientOrchestrationCommand, { readonly type: "thread.unarchive" }>;
116
+ });
117
+
118
+ export const makeThreadSessionStopCommand = Effect.fn("makeThreadSessionStopCommand")(function* (
119
+ threadId: string,
120
+ ) {
121
+ const crypto = yield* Crypto.Crypto;
122
+ const createdAt = DateTime.formatIso(yield* DateTime.now);
123
+ return {
124
+ type: "thread.session.stop",
125
+ commandId: CommandId.make(
126
+ `t3cli:thread-session-stop:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
127
+ ),
128
+ threadId: ThreadId.make(threadId),
129
+ createdAt,
130
+ } satisfies Extract<ClientOrchestrationCommand, { readonly type: "thread.session.stop" }>;
131
+ });
132
+
133
+ export const makeThreadDeleteCommand = Effect.fn("makeThreadDeleteCommand")(function* (
134
+ threadId: string,
135
+ ) {
136
+ const crypto = yield* Crypto.Crypto;
137
+ return {
138
+ type: "thread.delete",
139
+ commandId: CommandId.make(
140
+ `t3cli:thread-delete:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
141
+ ),
142
+ threadId: ThreadId.make(threadId),
143
+ } satisfies Extract<ClientOrchestrationCommand, { readonly type: "thread.delete" }>;
144
+ });
145
+
146
+ export const makeThreadInterruptCommand = Effect.fn("makeThreadInterruptCommand")(
147
+ function* (input: { readonly threadId: string; readonly turnId?: string }) {
148
+ const crypto = yield* Crypto.Crypto;
149
+ const createdAt = DateTime.formatIso(yield* DateTime.now);
150
+ return {
151
+ type: "thread.turn.interrupt",
152
+ commandId: CommandId.make(
153
+ `t3cli:thread-interrupt:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
154
+ ),
155
+ threadId: ThreadId.make(input.threadId),
156
+ ...(input.turnId !== undefined ? { turnId: TurnId.make(input.turnId) } : {}),
157
+ createdAt,
158
+ } satisfies Extract<ClientOrchestrationCommand, { readonly type: "thread.turn.interrupt" }>;
159
+ },
160
+ );
161
+
104
162
  export const makeThreadApprovalRespondCommand = Effect.fn("makeThreadApprovalRespondCommand")(
105
163
  function* (input: {
106
164
  readonly threadId: string;
@@ -142,3 +200,26 @@ export const makeThreadUserInputRespondCommand = Effect.fn("makeThreadUserInputR
142
200
  } satisfies Extract<ClientOrchestrationCommand, { readonly type: "thread.user-input.respond" }>;
143
201
  },
144
202
  );
203
+
204
+ export const makeThreadMetaUpdateCommand = Effect.fn("makeThreadMetaUpdateCommand")(function* (
205
+ threadId: string,
206
+ input: {
207
+ readonly title?: string;
208
+ readonly modelSelection?: ModelSelection;
209
+ readonly branch?: string | null;
210
+ readonly worktreePath?: string | null;
211
+ },
212
+ ) {
213
+ const crypto = yield* Crypto.Crypto;
214
+ return {
215
+ type: "thread.meta.update",
216
+ commandId: CommandId.make(
217
+ `t3cli:thread-meta-update:${yield* crypto.randomUUIDv4.pipe(Effect.orDie)}`,
218
+ ),
219
+ threadId: ThreadId.make(threadId),
220
+ ...(input.title !== undefined ? { title: input.title } : {}),
221
+ ...(input.modelSelection !== undefined ? { modelSelection: input.modelSelection } : {}),
222
+ ...(input.branch !== undefined ? { branch: input.branch } : {}),
223
+ ...(input.worktreePath !== undefined ? { worktreePath: input.worktreePath } : {}),
224
+ } satisfies Extract<ClientOrchestrationCommand, { readonly type: "thread.meta.update" }>;
225
+ });
@@ -0,0 +1,72 @@
1
+ import * as Crypto from "effect/Crypto";
2
+ import * as Effect from "effect/Effect";
3
+ import type { ModelSelection } from "@t3tools/contracts";
4
+
5
+ import { ModelSelectionError, ThreadEventError } from "../domain/error.ts";
6
+ import { T3Orchestration } from "../orchestration/service.ts";
7
+ import { mergeModelOptions, resolveUpdateModelSelection } from "./model-selection.ts";
8
+ import type { UpdateThreadInput } from "./service.ts";
9
+ import { makeThreadMetaUpdateCommand } from "./thread-commands.ts";
10
+
11
+ export function makeUpdateThread() {
12
+ return Effect.fn("T3ApplicationLive.updateThread")(function* (input: UpdateThreadInput) {
13
+ const orchestration = yield* T3Orchestration;
14
+ const crypto = yield* Crypto.Crypto;
15
+ const hasProvider = input.provider !== undefined && input.provider.length > 0;
16
+ const hasModel = input.model !== undefined && input.model.length > 0;
17
+ const options = input.options;
18
+ const hasOptions = options !== undefined && options.length > 0;
19
+ const thread = yield* orchestration.getThreadSnapshot(input.threadId);
20
+ let modelSelection: ModelSelection | undefined;
21
+ if (hasProvider || hasModel) {
22
+ const snapshot = yield* orchestration.getShellSnapshot();
23
+ const project = snapshot.projects.find((entry) => entry.id === thread.projectId);
24
+ if (project === undefined) {
25
+ return yield* Effect.fail(
26
+ new ThreadEventError({
27
+ message: `project not found for thread: ${input.threadId}`,
28
+ }),
29
+ );
30
+ }
31
+ const serverConfig = yield* orchestration.getServerConfig();
32
+ if (hasProvider && thread.session !== null && thread.session.status !== "stopped") {
33
+ const sessionProviderId =
34
+ thread.session.providerInstanceId ?? thread.modelSelection.instanceId;
35
+ const sessionProvider = serverConfig.providers.find(
36
+ (provider) => provider.instanceId === sessionProviderId,
37
+ );
38
+ const targetProvider = serverConfig.providers.find(
39
+ (provider) => provider.instanceId === input.provider,
40
+ );
41
+ if (
42
+ sessionProvider !== undefined &&
43
+ targetProvider !== undefined &&
44
+ targetProvider.driver !== sessionProvider.driver
45
+ ) {
46
+ return yield* Effect.fail(
47
+ new ModelSelectionError({
48
+ message: `thread ${input.threadId} is bound to ${sessionProvider.driver} provider ${sessionProviderId}; cannot update provider to ${targetProvider.driver} provider ${input.provider}`,
49
+ }),
50
+ );
51
+ }
52
+ }
53
+ modelSelection = yield* resolveUpdateModelSelection({
54
+ current: thread.modelSelection,
55
+ ...(hasProvider ? { provider: input.provider } : {}),
56
+ ...(hasModel ? { model: input.model } : {}),
57
+ ...(hasOptions ? { options } : {}),
58
+ project,
59
+ serverConfig,
60
+ });
61
+ } else if (hasOptions) {
62
+ modelSelection = mergeModelOptions(thread.modelSelection, options);
63
+ }
64
+ const command = yield* makeThreadMetaUpdateCommand(input.threadId, {
65
+ ...(input.title !== undefined ? { title: input.title } : {}),
66
+ ...(modelSelection !== undefined ? { modelSelection } : {}),
67
+ ...(input.branch !== undefined ? { branch: input.branch } : {}),
68
+ ...(input.worktreePath !== undefined ? { worktreePath: input.worktreePath } : {}),
69
+ }).pipe(Effect.provideService(Crypto.Crypto, crypto));
70
+ return yield* orchestration.dispatch(command);
71
+ });
72
+ }
@@ -1,10 +1,10 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Option from "effect/Option";
3
3
  import * as Stream from "effect/Stream";
4
- import type { OrchestrationMessage, OrchestrationThread } from "#t3tools/contracts";
4
+ import type { OrchestrationMessage, OrchestrationThread } from "@t3tools/contracts";
5
5
 
6
6
  import { ThreadSessionError } from "../domain/error.ts";
7
- import type { Orchestration } from "../orchestration/service.ts";
7
+ import { T3Orchestration } from "../orchestration/service.ts";
8
8
  import type { WaitEvent } from "./service.ts";
9
9
  import {
10
10
  applyThreadEvent,
@@ -15,60 +15,59 @@ import {
15
15
  threadStatus,
16
16
  } from "../domain/thread-lifecycle.ts";
17
17
 
18
- export function watchThread(input: {
19
- readonly orchestration: Orchestration;
20
- readonly threadId: string;
21
- }) {
18
+ export function watchThread(input: { readonly threadId: string }) {
22
19
  let current: OrchestrationThread | undefined;
23
20
  let currentMessages: Map<string, OrchestrationMessage> | undefined;
24
- return Stream.scoped(
25
- input.orchestration.watchThreadItems(input.threadId).pipe(
26
- Stream.flatMap((item) => {
27
- if (item.kind === "snapshot") {
28
- const messages = new Map<string, OrchestrationMessage>();
29
- for (const message of item.snapshot.thread.messages) {
30
- messages.set(messageKey(message), message);
31
- }
32
- current = item.snapshot.thread;
33
- currentMessages = messages;
21
+ return Stream.unwrap(
22
+ Effect.gen(function* () {
23
+ const orchestration = yield* T3Orchestration;
24
+ return Stream.scoped(
25
+ orchestration.watchThreadItems(input.threadId).pipe(
26
+ Stream.flatMap((item) => {
27
+ if (item.kind === "snapshot") {
28
+ const messages = new Map<string, OrchestrationMessage>();
29
+ for (const message of item.snapshot.thread.messages) {
30
+ messages.set(messageKey(message), message);
31
+ }
32
+ current = item.snapshot.thread;
33
+ currentMessages = messages;
34
34
 
35
- const events: Array<WaitEvent> = [
36
- { type: "thread", thread: current },
37
- { type: "status", status: threadStatus(current), threadId: current.id },
38
- ];
39
- if (!isThreadActive(current) && isThreadCompleteEnough(current)) {
40
- events.push({ type: "done", thread: current });
41
- }
42
- return Stream.fromIterable(events);
43
- }
35
+ const events: Array<WaitEvent> = [
36
+ { type: "thread", thread: current },
37
+ { type: "status", status: threadStatus(current), threadId: current.id },
38
+ ];
39
+ if (!isThreadActive(current) && isThreadCompleteEnough(current)) {
40
+ events.push({ type: "done", thread: current });
41
+ }
42
+ return Stream.fromIterable(events);
43
+ }
44
44
 
45
- if (current === undefined || currentMessages === undefined) {
46
- return Stream.fail(
47
- new ThreadSessionError({
48
- message: `thread stream event received before snapshot: ${input.threadId}`,
49
- threadId: input.threadId,
50
- }),
51
- );
52
- }
45
+ if (current === undefined || currentMessages === undefined) {
46
+ return Stream.fail(
47
+ new ThreadSessionError({
48
+ message: `thread stream event received before snapshot: ${input.threadId}`,
49
+ threadId: input.threadId,
50
+ }),
51
+ );
52
+ }
53
53
 
54
- current = applyThreadEvent(current, item.event, currentMessages);
55
- const message = messageFromEvent(item.event, currentMessages);
56
- const events: Array<WaitEvent> = message !== null ? [{ type: "message", message }] : [];
57
- events.push({ type: "status", status: threadStatus(current), threadId: current.id });
58
- if (!isThreadActive(current) && isThreadCompleteEnough(current)) {
59
- events.push({ type: "done", thread: current });
60
- }
61
- return Stream.fromIterable(events);
62
- }),
63
- Stream.takeUntil((event) => event.type === "done"),
64
- ),
54
+ current = applyThreadEvent(current, item.event, currentMessages);
55
+ const message = messageFromEvent(item.event, currentMessages);
56
+ const events: Array<WaitEvent> = message !== null ? [{ type: "message", message }] : [];
57
+ events.push({ type: "status", status: threadStatus(current), threadId: current.id });
58
+ if (!isThreadActive(current) && isThreadCompleteEnough(current)) {
59
+ events.push({ type: "done", thread: current });
60
+ }
61
+ return Stream.fromIterable(events);
62
+ }),
63
+ Stream.takeUntil((event) => event.type === "done"),
64
+ ),
65
+ );
66
+ }),
65
67
  );
66
68
  }
67
69
 
68
- export function waitForThread(input: {
69
- readonly orchestration: Orchestration;
70
- readonly threadId: string;
71
- }) {
70
+ export function waitForThread(input: { readonly threadId: string }) {
72
71
  return watchThread(input).pipe(
73
72
  Stream.runLast,
74
73
  Effect.flatMap((event) => {