t3code-cli 0.9.1 → 0.11.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 (126) hide show
  1. package/README.md +52 -14
  2. package/dist/application.js +2 -2
  3. package/dist/auth.js +1 -1
  4. package/dist/bin.js +1556 -341
  5. package/dist/cli.js +1 -1
  6. package/dist/config.js +1 -1
  7. package/dist/connection.js +1 -11
  8. package/dist/index.js +1 -1
  9. package/dist/layout.js +1 -1
  10. package/dist/node.js +2 -0
  11. package/dist/orchestration.js +2 -2
  12. package/dist/rpc.js +2 -2
  13. package/dist/runtime.js +1 -1
  14. package/dist/scope.js +1 -1
  15. package/dist/shared.js +1734 -756
  16. package/dist/src/application/index.d.ts +3 -2
  17. package/dist/src/application/layer.d.ts +82 -841
  18. package/dist/src/application/model-selection.d.ts +15 -0
  19. package/dist/src/application/models.d.ts +1 -87
  20. package/dist/src/application/project-commands.d.ts +9 -0
  21. package/dist/src/application/projects.d.ts +13 -135
  22. package/dist/src/application/service.d.ts +84 -5
  23. package/dist/src/application/shell-sequence.d.ts +2 -12
  24. package/dist/src/application/terminals.d.ts +66 -0
  25. package/dist/src/application/thread-commands.d.ts +47 -0
  26. package/dist/src/application/thread-update.d.ts +8 -0
  27. package/dist/src/application/thread-wait.d.ts +3 -14
  28. package/dist/src/application/threads.d.ts +21 -50
  29. package/dist/src/cli/flags.d.ts +3 -0
  30. package/dist/src/cli/output-format.d.ts +2 -0
  31. package/dist/src/domain/error.d.ts +14 -1
  32. package/dist/src/domain/helpers.d.ts +0 -9
  33. package/dist/src/domain/model-config.d.ts +29 -3
  34. package/dist/src/domain/thread-lifecycle.d.ts +2 -10
  35. package/dist/src/node/index.d.ts +2 -0
  36. package/dist/src/orchestration/index.d.ts +1 -0
  37. package/dist/src/orchestration/layer.d.ts +177 -1032
  38. package/dist/src/orchestration/service.d.ts +1 -0
  39. package/dist/src/rpc/error.d.ts +4 -2
  40. package/dist/src/rpc/index.d.ts +5 -0
  41. package/dist/src/rpc/layer.d.ts +1097 -499
  42. package/dist/src/rpc/operation.d.ts +5752 -0
  43. package/dist/src/rpc/ws-group.d.ts +327 -147
  44. package/dist/src/runtime/layer.d.ts +1 -1
  45. package/dist/t3tools.js +2 -2
  46. package/dist/upstream-t3code/packages/contracts/src/environmentHttp.d.ts +10 -33
  47. package/dist/upstream-t3code/packages/contracts/src/ipc.d.ts +0 -6
  48. package/dist/upstream-t3code/packages/contracts/src/orchestration.d.ts +1 -657
  49. package/dist/upstream-t3code/packages/contracts/src/provider.d.ts +0 -14
  50. package/dist/upstream-t3code/packages/contracts/src/providerRuntime.d.ts +1 -175
  51. package/dist/upstream-t3code/packages/contracts/src/rpc.d.ts +0 -492
  52. package/dist/upstream-t3code/packages/contracts/src/server.d.ts +0 -45
  53. package/dist/upstream-t3code/packages/contracts/src/settings.d.ts +0 -21
  54. package/dist/upstream-t3code/packages/contracts/src/terminal.d.ts +3 -6
  55. package/package.json +6 -2
  56. package/src/application/index.ts +28 -2
  57. package/src/application/layer.ts +34 -5
  58. package/src/application/model-selection.ts +68 -2
  59. package/src/application/models.ts +14 -14
  60. package/src/application/project-commands.ts +15 -0
  61. package/src/application/projects.ts +43 -13
  62. package/src/application/service.ts +196 -77
  63. package/src/application/shell-sequence.ts +5 -7
  64. package/src/application/terminals.ts +207 -0
  65. package/src/application/thread-commands.test.ts +43 -0
  66. package/src/application/thread-commands.ts +81 -0
  67. package/src/application/thread-update.ts +72 -0
  68. package/src/application/thread-wait.ts +46 -47
  69. package/src/application/threads.test.ts +166 -0
  70. package/src/application/threads.ts +123 -40
  71. package/src/bin.ts +2 -0
  72. package/src/cli/app.ts +14 -0
  73. package/src/cli/confirm.ts +31 -0
  74. package/src/cli/error.ts +26 -3
  75. package/src/cli/flags.ts +15 -0
  76. package/src/cli/input/layer.ts +20 -0
  77. package/src/cli/input/service.ts +1 -0
  78. package/src/cli/output-format.ts +6 -2
  79. package/src/cli/project-format.ts +7 -0
  80. package/src/cli/project.ts +2 -1
  81. package/src/cli/projects/delete.ts +50 -0
  82. package/src/cli/self-action.ts +34 -0
  83. package/src/cli/terminal/attach.ts +32 -0
  84. package/src/cli/terminal/commands.test.ts +101 -0
  85. package/src/cli/terminal/create.ts +54 -0
  86. package/src/cli/terminal/destroy.ts +60 -0
  87. package/src/cli/terminal/encoding.test.ts +63 -0
  88. package/src/cli/terminal/encoding.ts +23 -0
  89. package/src/cli/terminal/error.ts +14 -0
  90. package/src/cli/terminal/io-node-layer.ts +82 -0
  91. package/src/cli/terminal/io-service.ts +25 -0
  92. package/src/cli/terminal/list.ts +35 -0
  93. package/src/cli/terminal/read.ts +102 -0
  94. package/src/cli/terminal/scope.ts +30 -0
  95. package/src/cli/terminal/shared.ts +250 -0
  96. package/src/cli/terminal/stream.ts +64 -0
  97. package/src/cli/terminal/wait.test.ts +146 -0
  98. package/src/cli/terminal/wait.ts +222 -0
  99. package/src/cli/terminal/write.ts +132 -0
  100. package/src/cli/terminal-format.ts +167 -0
  101. package/src/cli/terminal.ts +26 -0
  102. package/src/cli/thread-format.test.ts +32 -0
  103. package/src/cli/thread-format.ts +8 -1
  104. package/src/cli/thread.ts +9 -13
  105. package/src/cli/threads/archive.ts +11 -18
  106. package/src/cli/threads/delete.ts +61 -0
  107. package/src/cli/threads/interrupt.ts +52 -0
  108. package/src/cli/threads/list.test.ts +69 -0
  109. package/src/cli/threads/list.ts +15 -3
  110. package/src/cli/threads/messages.ts +3 -3
  111. package/src/cli/threads/send.ts +22 -2
  112. package/src/cli/threads/unarchive.ts +44 -0
  113. package/src/cli/threads/update.ts +150 -0
  114. package/src/domain/error.ts +20 -1
  115. package/src/domain/model-config.ts +4 -0
  116. package/src/domain/thread-lifecycle.test.ts +35 -0
  117. package/src/domain/thread-lifecycle.ts +29 -6
  118. package/src/node/index.ts +2 -0
  119. package/src/orchestration/index.ts +1 -0
  120. package/src/orchestration/layer.ts +33 -85
  121. package/src/orchestration/service.ts +4 -0
  122. package/src/rpc/error.ts +10 -0
  123. package/src/rpc/index.ts +5 -0
  124. package/src/rpc/operation.ts +83 -0
  125. package/src/rpc/ws-group.ts +24 -0
  126. package/src/runtime/layer.ts +7 -2
@@ -12,6 +12,10 @@ export function firstSelectableModel(provider: ServerProvider) {
12
12
  return provider.models[0];
13
13
  }
14
14
 
15
+ export function findSelectableModel(provider: ServerProvider, slug: string) {
16
+ return provider.models.find((model) => model.slug === slug);
17
+ }
18
+
15
19
  export function findSelectableProvider(
16
20
  providers: ReadonlyArray<ServerProvider>,
17
21
  instanceId: string,
@@ -0,0 +1,35 @@
1
+ import "vite-plus/test/config";
2
+
3
+ import { describe, expect, it } from "vite-plus/test";
4
+ import { fromPartial } from "@total-typescript/shoehorn";
5
+
6
+ import type { OrchestrationThread } from "#t3tools/contracts";
7
+
8
+ import { isThreadActive, isThreadCompleteEnough } from "./thread-lifecycle.ts";
9
+
10
+ describe("thread lifecycle", () => {
11
+ it("treats a ready session with a stale running latestTurn as complete", () => {
12
+ const thread = fromPartial<OrchestrationThread>({
13
+ session: {
14
+ status: "ready",
15
+ },
16
+ latestTurn: {
17
+ state: "running",
18
+ },
19
+ messages: [
20
+ {
21
+ role: "user",
22
+ text: "run review",
23
+ },
24
+ {
25
+ role: "assistant",
26
+ text: "verdict: requested changes",
27
+ streaming: false,
28
+ },
29
+ ],
30
+ });
31
+
32
+ expect(isThreadCompleteEnough(thread)).toBe(true);
33
+ expect(isThreadActive(thread)).toBe(false);
34
+ });
35
+ });
@@ -2,18 +2,33 @@ import {
2
2
  OrchestrationMessage,
3
3
  type OrchestrationEvent,
4
4
  type OrchestrationMessage as OrchestrationMessageType,
5
+ type OrchestrationSession,
5
6
  type OrchestrationThread,
6
7
  type OrchestrationThreadShell,
7
8
  } from "#t3tools/contracts";
8
9
  import * as Schema from "effect/Schema";
9
10
 
11
+ type SessionStatusForDelete = OrchestrationSession["status"] | "closed";
12
+
13
+ function isClosedSessionStatus(status: SessionStatusForDelete): status is "closed" {
14
+ return status === "closed";
15
+ }
16
+
17
+ export function sessionNeedsStopBeforeDelete(session: OrchestrationSession | null) {
18
+ if (session === null) {
19
+ return false;
20
+ }
21
+ return !isClosedSessionStatus(session.status);
22
+ }
23
+
10
24
  export function isThreadActive(thread: OrchestrationThreadShell | OrchestrationThread) {
11
- return (
12
- thread.session?.status === "starting" ||
13
- thread.session?.status === "running" ||
14
- thread.latestTurn?.state === "running" ||
15
- isPendingStart(thread)
16
- );
25
+ if (thread.session?.status === "starting" || thread.session?.status === "running") {
26
+ return true;
27
+ }
28
+ if (thread.latestTurn?.state === "running") {
29
+ return !("messages" in thread && hasTerminalSession(thread) && isThreadCompleteEnough(thread));
30
+ }
31
+ return isPendingStart(thread);
17
32
  }
18
33
 
19
34
  export type ThreadLifecycleStatus = ReturnType<typeof threadStatus>;
@@ -91,3 +106,11 @@ function isPendingStart(thread: OrchestrationThreadShell | OrchestrationThread)
91
106
  }
92
107
  return thread.messages.at(-1)?.role === "user";
93
108
  }
109
+
110
+ function hasTerminalSession(thread: OrchestrationThreadShell | OrchestrationThread) {
111
+ return (
112
+ thread.session !== null &&
113
+ thread.session?.status !== "starting" &&
114
+ thread.session?.status !== "running"
115
+ );
116
+ }
@@ -0,0 +1,2 @@
1
+ export { T3CodeNodeRpcLayer } from "../connection/node.ts";
2
+ export { NodeEnvironmentLive } from "../environment/layer.ts";
@@ -4,4 +4,5 @@ export {
4
4
  type Orchestration,
5
5
  type OrchestrationError,
6
6
  } from "./service.ts";
7
+ export { makeT3Orchestration, T3OrchestrationLive } from "./layer.ts";
7
8
  export { T3OrchestrationLayer } from "../runtime/layer.ts";
@@ -1,7 +1,6 @@
1
1
  import * as Effect from "effect/Effect";
2
2
  import * as Layer from "effect/Layer";
3
3
  import * as Option from "effect/Option";
4
- import * as Schedule from "effect/Schedule";
5
4
  import * as Sink from "effect/Sink";
6
5
  import * as Stream from "effect/Stream";
7
6
  import {
@@ -12,86 +11,45 @@ import {
12
11
  type OrchestrationShellStreamItem,
13
12
  type OrchestrationThreadStreamItem,
14
13
  } from "#t3tools/contracts";
15
- import { RpcClientError } from "effect/unstable/rpc";
16
14
 
17
15
  import { RpcError } from "../rpc/error.ts";
18
- import { T3Rpc, type WsClient } from "../rpc/service.ts";
19
- import type { CliRpcRequestError } from "../rpc/ws-group.ts";
20
- import { T3Orchestration, type OpenThread } from "./service.ts";
16
+ import { T3RpcOperations } from "../rpc/operation.ts";
17
+ import { T3Orchestration, type OpenThread, type Orchestration } from "./service.ts";
21
18
 
22
- type RpcRequestError = RpcClientError.RpcClientError | CliRpcRequestError;
23
-
24
- const rpcRetrySchedule = Schedule.exponential("100 millis").pipe(
25
- Schedule.take(4),
26
- Schedule.collectWhile(
27
- (metadata: Schedule.Metadata) => metadata.input instanceof RpcClientError.RpcClientError,
28
- ),
29
- );
19
+ export const makeT3Orchestration = Effect.fn("makeT3Orchestration")(function* () {
20
+ const rpc = yield* T3RpcOperations;
30
21
 
31
- function runRpc<A>(
32
- rpc: T3Rpc["Service"],
33
- method: string,
34
- f: (client: WsClient) => Effect.Effect<A, RpcRequestError>,
35
- ): Effect.Effect<A, RpcError> {
36
- return Effect.gen(function* () {
37
- const client = yield* rpc.getClient;
38
- return yield* f(client);
39
- }).pipe(
40
- Effect.tapError((error) =>
41
- error instanceof RpcClientError.RpcClientError ? rpc.disconnect : Effect.void,
42
- ),
43
- Effect.retry(rpcRetrySchedule),
44
- Effect.catchTags({
45
- RpcError: (error) => Effect.fail(error),
46
- EnvironmentAuthorizationError: (error) => Effect.fail(toRpcRequestError(error, method)),
47
- RpcClientError: (error) => Effect.fail(toRpcRequestError(error, method)),
48
- KeybindingsConfigParseError: (error) => Effect.fail(toRpcRequestError(error, method)),
49
- OrchestrationDispatchCommandError: (error) => Effect.fail(toRpcRequestError(error, method)),
50
- OrchestrationGetSnapshotError: (error) => Effect.fail(toRpcRequestError(error, method)),
51
- ServerSettingsError: (error) => Effect.fail(toRpcRequestError(error, method)),
52
- }),
53
- );
54
- }
22
+ const watchShellSequence: Orchestration["watchShellSequence"] = () =>
23
+ rpc
24
+ .subscribe(ORCHESTRATION_WS_METHODS.subscribeShell, (client) =>
25
+ client[ORCHESTRATION_WS_METHODS.subscribeShell]({}),
26
+ )
27
+ .pipe(
28
+ Stream.map((item: OrchestrationShellStreamItem) =>
29
+ item.kind === "snapshot" ? item.snapshot.snapshotSequence : item.sequence,
30
+ ),
31
+ );
55
32
 
56
- function subscribeRpc<A>(
57
- rpc: T3Rpc["Service"],
58
- method: string,
59
- f: (client: WsClient) => Stream.Stream<A, RpcRequestError>,
60
- ): Stream.Stream<A, RpcError> {
61
- return Stream.unwrap(Effect.map(rpc.getClient, f)).pipe(
62
- Stream.tapError((error) =>
63
- error instanceof RpcClientError.RpcClientError ? rpc.disconnect : Effect.void,
64
- ),
65
- Stream.retry(rpcRetrySchedule),
66
- Stream.catchTags({
67
- RpcError: (error) => Stream.fail(error),
68
- EnvironmentAuthorizationError: (error) => Stream.fail(toRpcRequestError(error, method)),
69
- RpcClientError: (error) => Stream.fail(toRpcRequestError(error, method)),
70
- KeybindingsConfigParseError: (error) => Stream.fail(toRpcRequestError(error, method)),
71
- OrchestrationDispatchCommandError: (error) => Stream.fail(toRpcRequestError(error, method)),
72
- OrchestrationGetSnapshotError: (error) => Stream.fail(toRpcRequestError(error, method)),
73
- ServerSettingsError: (error) => Stream.fail(toRpcRequestError(error, method)),
74
- }),
75
- );
76
- }
33
+ const watchThreadItems: Orchestration["watchThreadItems"] = (threadId: string) =>
34
+ rpc.subscribe(ORCHESTRATION_WS_METHODS.subscribeThread, (client) =>
35
+ client[ORCHESTRATION_WS_METHODS.subscribeThread]({ threadId: ThreadId.make(threadId) }),
36
+ );
77
37
 
78
- export const makeT3Orchestration = Effect.fn("makeT3Orchestration")(function* () {
79
- const rpc = yield* T3Rpc;
80
38
  const dispatch = Effect.fn("T3OrchestrationLive.dispatch")(function* (
81
39
  command: ClientOrchestrationCommand,
82
40
  ) {
83
- return yield* runRpc(rpc, ORCHESTRATION_WS_METHODS.dispatchCommand, (client) =>
41
+ return yield* rpc.run(ORCHESTRATION_WS_METHODS.dispatchCommand, (client) =>
84
42
  client[ORCHESTRATION_WS_METHODS.dispatchCommand](command),
85
43
  );
86
44
  });
87
45
  const getServerConfig = Effect.fn("T3OrchestrationLive.getServerConfig")(function* () {
88
- return yield* runRpc(rpc, WS_METHODS.serverGetConfig, (client) =>
46
+ return yield* rpc.run(WS_METHODS.serverGetConfig, (client) =>
89
47
  client[WS_METHODS.serverGetConfig]({}),
90
48
  );
91
49
  });
92
50
  const getShellSnapshot = Effect.fn("T3OrchestrationLive.getShellSnapshot")(function* () {
93
51
  const item = yield* Stream.runHead(
94
- subscribeRpc(rpc, ORCHESTRATION_WS_METHODS.subscribeShell, (client) =>
52
+ rpc.subscribe(ORCHESTRATION_WS_METHODS.subscribeShell, (client) =>
95
53
  client[ORCHESTRATION_WS_METHODS.subscribeShell]({}),
96
54
  ),
97
55
  );
@@ -106,12 +64,21 @@ export const makeT3Orchestration = Effect.fn("makeT3Orchestration")(function* ()
106
64
  }
107
65
  return value.snapshot;
108
66
  });
67
+ const getArchivedShellSnapshot = Effect.fn("T3OrchestrationLive.getArchivedShellSnapshot")(
68
+ function* () {
69
+ return yield* rpc.run(ORCHESTRATION_WS_METHODS.getArchivedShellSnapshot, (client) =>
70
+ client[ORCHESTRATION_WS_METHODS.getArchivedShellSnapshot]({}),
71
+ );
72
+ },
73
+ );
109
74
  const getThreadSnapshot = Effect.fn("T3OrchestrationLive.getThreadSnapshot")(function* (
110
75
  threadId: string,
111
76
  ) {
112
77
  const item = yield* Stream.runHead(
113
- subscribeRpc(rpc, ORCHESTRATION_WS_METHODS.subscribeThread, (client) =>
114
- client[ORCHESTRATION_WS_METHODS.subscribeThread]({ threadId: ThreadId.make(threadId) }),
78
+ rpc.subscribe(ORCHESTRATION_WS_METHODS.subscribeThread, (client) =>
79
+ client[ORCHESTRATION_WS_METHODS.subscribeThread]({
80
+ threadId: ThreadId.make(threadId),
81
+ }),
115
82
  ),
116
83
  );
117
84
  const value = Option.getOrUndefined(item);
@@ -125,18 +92,6 @@ export const makeT3Orchestration = Effect.fn("makeT3Orchestration")(function* ()
125
92
  }
126
93
  return value.snapshot.thread;
127
94
  });
128
- const watchShellSequence = () =>
129
- subscribeRpc(rpc, ORCHESTRATION_WS_METHODS.subscribeShell, (client) =>
130
- client[ORCHESTRATION_WS_METHODS.subscribeShell]({}),
131
- ).pipe(
132
- Stream.map((item: OrchestrationShellStreamItem) =>
133
- item.kind === "snapshot" ? item.snapshot.snapshotSequence : item.sequence,
134
- ),
135
- );
136
- const watchThreadItems = (threadId: string) =>
137
- subscribeRpc(rpc, ORCHESTRATION_WS_METHODS.subscribeThread, (client) =>
138
- client[ORCHESTRATION_WS_METHODS.subscribeThread]({ threadId: ThreadId.make(threadId) }),
139
- );
140
95
  const openThread = Effect.fn("T3OrchestrationLive.openThread")(function* (threadId: string) {
141
96
  return yield* watchThreadItems(threadId).pipe(
142
97
  Stream.peel(Sink.head<OrchestrationThreadStreamItem>()),
@@ -168,6 +123,7 @@ export const makeT3Orchestration = Effect.fn("makeT3Orchestration")(function* ()
168
123
  dispatch,
169
124
  getServerConfig,
170
125
  getShellSnapshot,
126
+ getArchivedShellSnapshot,
171
127
  getThreadSnapshot,
172
128
  watchShellSequence,
173
129
  watchThreadItems,
@@ -176,11 +132,3 @@ export const makeT3Orchestration = Effect.fn("makeT3Orchestration")(function* ()
176
132
  });
177
133
 
178
134
  export const T3OrchestrationLive = Layer.effect(T3Orchestration, makeT3Orchestration());
179
-
180
- function toRpcRequestError(error: RpcRequestError, method: string) {
181
- return new RpcError({
182
- message: error.message,
183
- method,
184
- cause: error,
185
- });
186
- }
@@ -31,6 +31,10 @@ export type Orchestration = {
31
31
  ) => Effect.Effect<DispatchResult, OrchestrationError>;
32
32
  readonly getServerConfig: () => Effect.Effect<ServerConfigForCli, OrchestrationError>;
33
33
  readonly getShellSnapshot: () => Effect.Effect<OrchestrationShellSnapshot, OrchestrationError>;
34
+ readonly getArchivedShellSnapshot: () => Effect.Effect<
35
+ OrchestrationShellSnapshot,
36
+ OrchestrationError
37
+ >;
34
38
  readonly getThreadSnapshot: (
35
39
  threadId: string,
36
40
  ) => Effect.Effect<OrchestrationThread, OrchestrationError>;
package/src/rpc/error.ts CHANGED
@@ -5,6 +5,10 @@ import {
5
5
  OrchestrationDispatchCommandError,
6
6
  OrchestrationGetSnapshotError,
7
7
  ServerSettingsError,
8
+ TerminalCwdError,
9
+ TerminalHistoryError,
10
+ TerminalNotRunningError,
11
+ TerminalSessionLookupError,
8
12
  } from "#t3tools/contracts";
9
13
  import { HttpClientError } from "effect/unstable/http";
10
14
  import { RpcClientError } from "effect/unstable/rpc";
@@ -20,6 +24,10 @@ const RpcErrorCauseSchema = Schema.Union([
20
24
  OrchestrationDispatchCommandError,
21
25
  OrchestrationGetSnapshotError,
22
26
  ServerSettingsError,
27
+ TerminalCwdError,
28
+ TerminalHistoryError,
29
+ TerminalNotRunningError,
30
+ TerminalSessionLookupError,
23
31
  AuthTransportError,
24
32
  T3CodeConnectionError,
25
33
  HttpClientError.HttpClientErrorSchema,
@@ -27,6 +35,8 @@ const RpcErrorCauseSchema = Schema.Union([
27
35
  Schema.instanceOf(Schema.SchemaError),
28
36
  ]);
29
37
 
38
+ export type RpcKnownCause = Schema.Schema.Type<typeof RpcErrorCauseSchema>;
39
+
30
40
  export class RpcError extends Schema.TaggedErrorClass<RpcError>()("RpcError", {
31
41
  message: Schema.String,
32
42
  method: Schema.optionalKey(Schema.String),
package/src/rpc/index.ts CHANGED
@@ -1 +1,6 @@
1
1
  export { RpcError, type OrchestrationError } from "./error.ts";
2
+ export { makeT3RpcLayer, T3RpcLive } from "./layer.ts";
3
+ export { T3RpcOperations, T3RpcOperationsLive, makeT3RpcOperations } from "./operation.ts";
4
+ export type { T3RpcOperationsService } from "./operation.ts";
5
+ export { T3Rpc } from "./service.ts";
6
+ export type { T3RpcService, WsClient } from "./service.ts";
@@ -0,0 +1,83 @@
1
+ import * as Context from "effect/Context";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as Predicate from "effect/Predicate";
5
+ import * as Schedule from "effect/Schedule";
6
+ import * as Stream from "effect/Stream";
7
+ import { RpcClientError } from "effect/unstable/rpc";
8
+
9
+ import { RpcError } from "./error.ts";
10
+ import { T3Rpc, type WsClient } from "./service.ts";
11
+ import type { CliRpcRequestError } from "./ws-group.ts";
12
+
13
+ export type CliRpcOperationError = CliRpcRequestError | RpcClientError.RpcClientError;
14
+
15
+ export const rpcRetrySchedule = Schedule.exponential("100 millis").pipe(
16
+ Schedule.take(4),
17
+ Schedule.collectWhile((metadata: Schedule.Metadata) =>
18
+ Predicate.isTagged(metadata.input, "RpcClientError"),
19
+ ),
20
+ );
21
+
22
+ export type T3RpcOperationsService = {
23
+ readonly run: <A, R>(
24
+ method: string,
25
+ operation: (client: WsClient) => Effect.Effect<A, CliRpcOperationError, R>,
26
+ ) => Effect.Effect<A, RpcError, R>;
27
+ readonly subscribe: <A>(
28
+ method: string,
29
+ operation: (client: WsClient) => Stream.Stream<A, CliRpcOperationError>,
30
+ ) => Stream.Stream<A, RpcError>;
31
+ };
32
+
33
+ export class T3RpcOperations extends Context.Service<T3RpcOperations, T3RpcOperationsService>()(
34
+ "t3cli/T3RpcOperations",
35
+ ) {}
36
+
37
+ export const makeT3RpcOperations = Effect.fn("makeT3RpcOperations")(function* () {
38
+ const rpc = yield* T3Rpc;
39
+
40
+ const run: T3RpcOperationsService["run"] = <A, R>(
41
+ method: string,
42
+ operation: (client: WsClient) => Effect.Effect<A, CliRpcOperationError, R>,
43
+ ): Effect.Effect<A, RpcError, R> =>
44
+ rpc.getClient.pipe(
45
+ Effect.flatMap((client) => operation(client)),
46
+ Effect.tapError((error) =>
47
+ Predicate.isTagged(error, "RpcClientError") ? rpc.disconnect : Effect.void,
48
+ ),
49
+ Effect.retry(rpcRetrySchedule),
50
+ Effect.mapError((error) =>
51
+ Predicate.isTagged(error, "RpcError") ? error : toRpcError(error, method),
52
+ ),
53
+ );
54
+
55
+ const subscribe: T3RpcOperationsService["subscribe"] = <A>(
56
+ method: string,
57
+ operation: (client: WsClient) => Stream.Stream<A, CliRpcOperationError>,
58
+ ): Stream.Stream<A, RpcError> =>
59
+ Stream.unwrap(rpc.getClient.pipe(Effect.map((client) => operation(client)))).pipe(
60
+ Stream.tapError((error) =>
61
+ Predicate.isTagged(error, "RpcClientError") ? rpc.disconnect : Effect.void,
62
+ ),
63
+ Stream.retry(rpcRetrySchedule),
64
+ Stream.mapError((error) =>
65
+ Predicate.isTagged(error, "RpcError") ? error : toRpcError(error, method),
66
+ ),
67
+ );
68
+
69
+ return {
70
+ run,
71
+ subscribe,
72
+ } satisfies T3RpcOperationsService;
73
+ });
74
+
75
+ export const T3RpcOperationsLive = Layer.effect(T3RpcOperations, makeT3RpcOperations());
76
+
77
+ function toRpcError(error: CliRpcOperationError, method: string): RpcError {
78
+ return new RpcError({
79
+ message: error.message,
80
+ method,
81
+ cause: error,
82
+ });
83
+ }
@@ -6,8 +6,20 @@ import {
6
6
  ServerConfig,
7
7
  ServerProviders,
8
8
  ServerSettingsError,
9
+ TerminalCwdError,
10
+ TerminalHistoryError,
11
+ TerminalNotRunningError,
12
+ TerminalSessionLookupError,
9
13
  WS_METHODS,
14
+ WsSubscribeTerminalEventsRpc,
15
+ WsSubscribeTerminalMetadataRpc,
16
+ WsTerminalAttachRpc,
17
+ WsTerminalCloseRpc,
18
+ WsTerminalOpenRpc,
19
+ WsTerminalResizeRpc,
20
+ WsTerminalWriteRpc,
10
21
  WsOrchestrationDispatchCommandRpc,
22
+ WsOrchestrationGetArchivedShellSnapshotRpc,
11
23
  WsOrchestrationSubscribeShellRpc,
12
24
  WsOrchestrationSubscribeThreadRpc,
13
25
  } from "#t3tools/contracts";
@@ -29,7 +41,15 @@ export const WsServerGetConfigRpc = Rpc.make(WS_METHODS.serverGetConfig, {
29
41
  });
30
42
 
31
43
  export const CliWsRpcGroup = RpcGroup.make(
44
+ WsTerminalOpenRpc,
45
+ WsTerminalAttachRpc,
46
+ WsTerminalWriteRpc,
47
+ WsTerminalResizeRpc,
48
+ WsTerminalCloseRpc,
49
+ WsSubscribeTerminalEventsRpc,
50
+ WsSubscribeTerminalMetadataRpc,
32
51
  WsOrchestrationDispatchCommandRpc,
52
+ WsOrchestrationGetArchivedShellSnapshotRpc,
33
53
  WsOrchestrationSubscribeShellRpc,
34
54
  WsOrchestrationSubscribeThreadRpc,
35
55
  WsServerGetConfigRpc,
@@ -40,4 +60,8 @@ export type CliRpcRequestError =
40
60
  | KeybindingsConfigError
41
61
  | OrchestrationDispatchCommandError
42
62
  | OrchestrationGetSnapshotError
63
+ | TerminalCwdError
64
+ | TerminalHistoryError
65
+ | TerminalNotRunningError
66
+ | TerminalSessionLookupError
43
67
  | ServerSettingsError;
@@ -16,6 +16,7 @@ import { T3CodeConnectionError } from "../connection/error.ts";
16
16
  import { T3CodeConnectionProvider, makeT3CodeConnectionProvider } from "../connection/service.ts";
17
17
  import { T3OrchestrationLive } from "../orchestration/layer.ts";
18
18
  import { T3RpcLive } from "../rpc/layer.ts";
19
+ import { T3RpcOperationsLive } from "../rpc/operation.ts";
19
20
  import { NodeSqlClientFactoryLive } from "../sql/node-sqlite-client.ts";
20
21
  import { NodeCliPathLayer } from "../cli-path/layer.ts";
21
22
 
@@ -66,8 +67,11 @@ const T3RpcLayer = T3RpcLive.pipe(
66
67
  ),
67
68
  ),
68
69
  );
69
- export const T3OrchestrationLayer = T3OrchestrationLive.pipe(Layer.provide(T3RpcLayer));
70
- const T3ApplicationLayer = T3ApplicationLive.pipe(Layer.provide(T3OrchestrationLayer));
70
+ const T3RpcOperationsLayer = T3RpcOperationsLive.pipe(Layer.provide(T3RpcLayer));
71
+ export const T3OrchestrationLayer = T3OrchestrationLive.pipe(Layer.provide(T3RpcOperationsLayer));
72
+ const T3ApplicationLayer = T3ApplicationLive.pipe(
73
+ Layer.provide(Layer.mergeAll(T3RpcOperationsLayer, T3OrchestrationLayer)),
74
+ );
71
75
 
72
76
  export const AuthAppLayer = Layer.mergeAll(T3ConfigLive, T3AuthLayer);
73
77
 
@@ -75,6 +79,7 @@ export const AppLayer = Layer.mergeAll(
75
79
  T3ConfigLive,
76
80
  T3AuthLayer,
77
81
  T3RpcLayer,
82
+ T3RpcOperationsLayer,
78
83
  T3OrchestrationLayer,
79
84
  T3ApplicationLayer,
80
85
  NodeCliPathLayer,