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.
- package/README.md +52 -14
- package/dist/application.js +2 -2
- package/dist/auth.js +1 -1
- package/dist/bin.js +1556 -341
- package/dist/cli.js +1 -1
- package/dist/config.js +1 -1
- package/dist/connection.js +1 -11
- package/dist/index.js +1 -1
- package/dist/layout.js +1 -1
- package/dist/node.js +2 -0
- package/dist/orchestration.js +2 -2
- package/dist/rpc.js +2 -2
- package/dist/runtime.js +1 -1
- package/dist/scope.js +1 -1
- package/dist/shared.js +1734 -756
- package/dist/src/application/index.d.ts +3 -2
- package/dist/src/application/layer.d.ts +82 -841
- package/dist/src/application/model-selection.d.ts +15 -0
- package/dist/src/application/models.d.ts +1 -87
- package/dist/src/application/project-commands.d.ts +9 -0
- package/dist/src/application/projects.d.ts +13 -135
- package/dist/src/application/service.d.ts +84 -5
- package/dist/src/application/shell-sequence.d.ts +2 -12
- package/dist/src/application/terminals.d.ts +66 -0
- package/dist/src/application/thread-commands.d.ts +47 -0
- package/dist/src/application/thread-update.d.ts +8 -0
- package/dist/src/application/thread-wait.d.ts +3 -14
- package/dist/src/application/threads.d.ts +21 -50
- package/dist/src/cli/flags.d.ts +3 -0
- package/dist/src/cli/output-format.d.ts +2 -0
- package/dist/src/domain/error.d.ts +14 -1
- package/dist/src/domain/helpers.d.ts +0 -9
- package/dist/src/domain/model-config.d.ts +29 -3
- package/dist/src/domain/thread-lifecycle.d.ts +2 -10
- package/dist/src/node/index.d.ts +2 -0
- package/dist/src/orchestration/index.d.ts +1 -0
- package/dist/src/orchestration/layer.d.ts +177 -1032
- package/dist/src/orchestration/service.d.ts +1 -0
- package/dist/src/rpc/error.d.ts +4 -2
- package/dist/src/rpc/index.d.ts +5 -0
- package/dist/src/rpc/layer.d.ts +1097 -499
- package/dist/src/rpc/operation.d.ts +5752 -0
- package/dist/src/rpc/ws-group.d.ts +327 -147
- package/dist/src/runtime/layer.d.ts +1 -1
- package/dist/t3tools.js +2 -2
- package/dist/upstream-t3code/packages/contracts/src/environmentHttp.d.ts +10 -33
- package/dist/upstream-t3code/packages/contracts/src/ipc.d.ts +0 -6
- package/dist/upstream-t3code/packages/contracts/src/orchestration.d.ts +1 -657
- package/dist/upstream-t3code/packages/contracts/src/provider.d.ts +0 -14
- package/dist/upstream-t3code/packages/contracts/src/providerRuntime.d.ts +1 -175
- package/dist/upstream-t3code/packages/contracts/src/rpc.d.ts +0 -492
- package/dist/upstream-t3code/packages/contracts/src/server.d.ts +0 -45
- package/dist/upstream-t3code/packages/contracts/src/settings.d.ts +0 -21
- package/dist/upstream-t3code/packages/contracts/src/terminal.d.ts +3 -6
- package/package.json +6 -2
- package/src/application/index.ts +28 -2
- package/src/application/layer.ts +34 -5
- package/src/application/model-selection.ts +68 -2
- package/src/application/models.ts +14 -14
- package/src/application/project-commands.ts +15 -0
- package/src/application/projects.ts +43 -13
- package/src/application/service.ts +196 -77
- package/src/application/shell-sequence.ts +5 -7
- package/src/application/terminals.ts +207 -0
- package/src/application/thread-commands.test.ts +43 -0
- package/src/application/thread-commands.ts +81 -0
- package/src/application/thread-update.ts +72 -0
- package/src/application/thread-wait.ts +46 -47
- package/src/application/threads.test.ts +166 -0
- package/src/application/threads.ts +123 -40
- package/src/bin.ts +2 -0
- package/src/cli/app.ts +14 -0
- package/src/cli/confirm.ts +31 -0
- package/src/cli/error.ts +26 -3
- package/src/cli/flags.ts +15 -0
- package/src/cli/input/layer.ts +20 -0
- package/src/cli/input/service.ts +1 -0
- package/src/cli/output-format.ts +6 -2
- package/src/cli/project-format.ts +7 -0
- package/src/cli/project.ts +2 -1
- package/src/cli/projects/delete.ts +50 -0
- package/src/cli/self-action.ts +34 -0
- package/src/cli/terminal/attach.ts +32 -0
- package/src/cli/terminal/commands.test.ts +101 -0
- package/src/cli/terminal/create.ts +54 -0
- package/src/cli/terminal/destroy.ts +60 -0
- package/src/cli/terminal/encoding.test.ts +63 -0
- package/src/cli/terminal/encoding.ts +23 -0
- package/src/cli/terminal/error.ts +14 -0
- package/src/cli/terminal/io-node-layer.ts +82 -0
- package/src/cli/terminal/io-service.ts +25 -0
- package/src/cli/terminal/list.ts +35 -0
- package/src/cli/terminal/read.ts +102 -0
- package/src/cli/terminal/scope.ts +30 -0
- package/src/cli/terminal/shared.ts +250 -0
- package/src/cli/terminal/stream.ts +64 -0
- package/src/cli/terminal/wait.test.ts +146 -0
- package/src/cli/terminal/wait.ts +222 -0
- package/src/cli/terminal/write.ts +132 -0
- package/src/cli/terminal-format.ts +167 -0
- package/src/cli/terminal.ts +26 -0
- package/src/cli/thread-format.test.ts +32 -0
- package/src/cli/thread-format.ts +8 -1
- package/src/cli/thread.ts +9 -13
- package/src/cli/threads/archive.ts +11 -18
- package/src/cli/threads/delete.ts +61 -0
- package/src/cli/threads/interrupt.ts +52 -0
- package/src/cli/threads/list.test.ts +69 -0
- package/src/cli/threads/list.ts +15 -3
- package/src/cli/threads/messages.ts +3 -3
- package/src/cli/threads/send.ts +22 -2
- package/src/cli/threads/unarchive.ts +44 -0
- package/src/cli/threads/update.ts +150 -0
- package/src/domain/error.ts +20 -1
- package/src/domain/model-config.ts +4 -0
- package/src/domain/thread-lifecycle.test.ts +35 -0
- package/src/domain/thread-lifecycle.ts +29 -6
- package/src/node/index.ts +2 -0
- package/src/orchestration/index.ts +1 -0
- package/src/orchestration/layer.ts +33 -85
- package/src/orchestration/service.ts +4 -0
- package/src/rpc/error.ts +10 -0
- package/src/rpc/index.ts +5 -0
- package/src/rpc/operation.ts +83 -0
- package/src/rpc/ws-group.ts +24 -0
- package/src/runtime/layer.ts +7 -2
|
@@ -11,6 +11,10 @@ import type {
|
|
|
11
11
|
OrchestrationThreadShell,
|
|
12
12
|
ProviderUserInputAnswers,
|
|
13
13
|
ServerProvider,
|
|
14
|
+
TerminalAttachStreamEvent,
|
|
15
|
+
TerminalMetadataStreamEvent,
|
|
16
|
+
TerminalSessionSnapshot,
|
|
17
|
+
TerminalSummary,
|
|
14
18
|
} from "#t3tools/contracts";
|
|
15
19
|
|
|
16
20
|
import type { ApplicationError } from "./error.ts";
|
|
@@ -26,6 +30,22 @@ export type StartThreadInput = {
|
|
|
26
30
|
readonly worktreePath?: string;
|
|
27
31
|
};
|
|
28
32
|
|
|
33
|
+
export type CreateTerminalInput = {
|
|
34
|
+
readonly threadId: string;
|
|
35
|
+
readonly terminalId?: string;
|
|
36
|
+
readonly command?: string;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type TerminalRef = {
|
|
40
|
+
readonly threadId: string;
|
|
41
|
+
readonly terminalId: string;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type TerminalAttachTarget = TerminalRef & {
|
|
45
|
+
readonly cwd: string;
|
|
46
|
+
readonly worktreePath: string | null;
|
|
47
|
+
};
|
|
48
|
+
|
|
29
49
|
export type SendThreadInput = {
|
|
30
50
|
readonly threadId: string;
|
|
31
51
|
readonly message: string;
|
|
@@ -38,6 +58,18 @@ export type CallbackThreadInput = {
|
|
|
38
58
|
readonly prompt: string;
|
|
39
59
|
};
|
|
40
60
|
|
|
61
|
+
export type ListThreadsInclude = "active" | "archived" | "all";
|
|
62
|
+
|
|
63
|
+
export type UpdateThreadInput = {
|
|
64
|
+
readonly threadId: string;
|
|
65
|
+
readonly title?: string;
|
|
66
|
+
readonly provider?: string;
|
|
67
|
+
readonly model?: string;
|
|
68
|
+
readonly options?: NonNullable<ModelSelection["options"]>;
|
|
69
|
+
readonly branch?: string | null;
|
|
70
|
+
readonly worktreePath?: string | null;
|
|
71
|
+
};
|
|
72
|
+
|
|
41
73
|
export type StartThreadPolicy = {
|
|
42
74
|
readonly until: "dispatch" | "visible" | "complete";
|
|
43
75
|
};
|
|
@@ -48,82 +80,169 @@ export type WaitEvent =
|
|
|
48
80
|
| { readonly type: "status"; readonly status: string; readonly threadId: string }
|
|
49
81
|
| { readonly type: "done"; readonly thread: OrchestrationThread };
|
|
50
82
|
|
|
51
|
-
export
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
readonly
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
readonly
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
readonly
|
|
77
|
-
readonly
|
|
83
|
+
export type T3ModelApplicationService = {
|
|
84
|
+
readonly listModels: (input: {
|
|
85
|
+
readonly all?: boolean;
|
|
86
|
+
readonly provider?: string;
|
|
87
|
+
}) => Effect.Effect<ReadonlyArray<ServerProvider>, ApplicationError>;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export class T3ModelApplication extends Context.Service<
|
|
91
|
+
T3ModelApplication,
|
|
92
|
+
T3ModelApplicationService
|
|
93
|
+
>()("t3cli/T3ModelApplication") {}
|
|
94
|
+
|
|
95
|
+
export type T3ProjectApplicationService = {
|
|
96
|
+
readonly loadShell: () => Effect.Effect<OrchestrationShellSnapshot, ApplicationError>;
|
|
97
|
+
readonly addProject: (input: {
|
|
98
|
+
readonly path: string;
|
|
99
|
+
readonly title?: string;
|
|
100
|
+
}) => Effect.Effect<
|
|
101
|
+
{ readonly dispatch: DispatchResult; readonly project: OrchestrationProjectShell },
|
|
102
|
+
ApplicationError
|
|
103
|
+
>;
|
|
104
|
+
readonly resolveProject: (
|
|
105
|
+
projectRef: string,
|
|
106
|
+
) => Effect.Effect<OrchestrationProjectShell, ApplicationError>;
|
|
107
|
+
readonly deleteProject: (input: {
|
|
108
|
+
readonly projectId: string;
|
|
109
|
+
readonly force?: boolean;
|
|
110
|
+
}) => Effect.Effect<
|
|
111
|
+
{ readonly projectId: string; readonly dispatch: DispatchResult },
|
|
112
|
+
ApplicationError
|
|
113
|
+
>;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
export class T3ProjectApplication extends Context.Service<
|
|
117
|
+
T3ProjectApplication,
|
|
118
|
+
T3ProjectApplicationService
|
|
119
|
+
>()("t3cli/T3ProjectApplication") {}
|
|
120
|
+
|
|
121
|
+
export type T3ThreadApplicationService = {
|
|
122
|
+
readonly listThreads: (
|
|
123
|
+
projectRef: string,
|
|
124
|
+
options?: {
|
|
125
|
+
readonly include?: ListThreadsInclude;
|
|
126
|
+
},
|
|
127
|
+
) => Effect.Effect<
|
|
128
|
+
{
|
|
129
|
+
readonly project: OrchestrationProjectShell;
|
|
130
|
+
readonly threads: ReadonlyArray<OrchestrationThreadShell>;
|
|
131
|
+
},
|
|
132
|
+
ApplicationError
|
|
133
|
+
>;
|
|
134
|
+
readonly getThreadMessages: (
|
|
135
|
+
threadId: string,
|
|
136
|
+
) => Effect.Effect<OrchestrationThread, ApplicationError>;
|
|
137
|
+
readonly showThread: (threadId: string) => Effect.Effect<ThreadShow, ApplicationError>;
|
|
138
|
+
readonly approveThread: (input: {
|
|
139
|
+
readonly threadId: string;
|
|
140
|
+
readonly requestId: string;
|
|
141
|
+
readonly decision: "accept" | "decline" | "cancel";
|
|
142
|
+
}) => Effect.Effect<
|
|
143
|
+
{ readonly threadId: string; readonly requestId: string; readonly dispatch: DispatchResult },
|
|
144
|
+
ApplicationError
|
|
145
|
+
>;
|
|
146
|
+
readonly respondToThread: (input: {
|
|
147
|
+
readonly threadId: string;
|
|
148
|
+
readonly requestId: string;
|
|
149
|
+
readonly answers: ProviderUserInputAnswers;
|
|
150
|
+
}) => Effect.Effect<
|
|
151
|
+
{ readonly threadId: string; readonly requestId: string; readonly dispatch: DispatchResult },
|
|
152
|
+
ApplicationError
|
|
153
|
+
>;
|
|
154
|
+
readonly archiveThread: (threadId: string) => Effect.Effect<DispatchResult, ApplicationError>;
|
|
155
|
+
readonly interruptThread: (threadId: string) => Effect.Effect<DispatchResult, ApplicationError>;
|
|
156
|
+
readonly unarchiveThread: (threadId: string) => Effect.Effect<DispatchResult, ApplicationError>;
|
|
157
|
+
readonly deleteThread: (
|
|
158
|
+
threadId: string,
|
|
159
|
+
) => Effect.Effect<
|
|
160
|
+
{ readonly threadId: string; readonly dispatch: DispatchResult },
|
|
161
|
+
ApplicationError
|
|
162
|
+
>;
|
|
163
|
+
readonly updateThread: (
|
|
164
|
+
input: UpdateThreadInput,
|
|
165
|
+
) => Effect.Effect<DispatchResult, ApplicationError>;
|
|
166
|
+
readonly startThread: (
|
|
167
|
+
input: StartThreadInput,
|
|
168
|
+
policy?: StartThreadPolicy,
|
|
169
|
+
) => Effect.Effect<
|
|
170
|
+
{
|
|
171
|
+
readonly dispatch: DispatchResult;
|
|
172
|
+
readonly project: OrchestrationProjectShell;
|
|
78
173
|
readonly threadId: string;
|
|
79
|
-
readonly
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
174
|
+
readonly thread?: OrchestrationThread;
|
|
175
|
+
},
|
|
176
|
+
ApplicationError
|
|
177
|
+
>;
|
|
178
|
+
readonly sendThread: (
|
|
179
|
+
input: SendThreadInput,
|
|
180
|
+
policy?: StartThreadPolicy,
|
|
181
|
+
) => Effect.Effect<
|
|
182
|
+
{
|
|
183
|
+
readonly dispatch: DispatchResult;
|
|
86
184
|
readonly threadId: string;
|
|
87
|
-
readonly
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
readonly
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
185
|
+
readonly thread?: OrchestrationThread;
|
|
186
|
+
},
|
|
187
|
+
ApplicationError
|
|
188
|
+
>;
|
|
189
|
+
readonly watchThread: (threadId: string) => Stream.Stream<WaitEvent, ApplicationError>;
|
|
190
|
+
readonly waitForThread: (
|
|
191
|
+
threadId: string,
|
|
192
|
+
) => Effect.Effect<OrchestrationThread, ApplicationError>;
|
|
193
|
+
readonly callbackThread: (input: CallbackThreadInput) => Effect.Effect<
|
|
194
|
+
{
|
|
195
|
+
readonly dispatch: DispatchResult;
|
|
196
|
+
readonly targetThreadId: string;
|
|
197
|
+
},
|
|
198
|
+
ApplicationError
|
|
199
|
+
>;
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
export class T3ThreadApplication extends Context.Service<
|
|
203
|
+
T3ThreadApplication,
|
|
204
|
+
T3ThreadApplicationService
|
|
205
|
+
>()("t3cli/T3ThreadApplication") {}
|
|
206
|
+
|
|
207
|
+
export type T3TerminalApplicationService = {
|
|
208
|
+
readonly listTerminals: (
|
|
209
|
+
threadId: string,
|
|
210
|
+
) => Effect.Effect<ReadonlyArray<TerminalSummary>, ApplicationError>;
|
|
211
|
+
readonly getTerminal: (terminal: TerminalRef) => Effect.Effect<TerminalSummary, ApplicationError>;
|
|
212
|
+
readonly createTerminal: (
|
|
213
|
+
input: CreateTerminalInput,
|
|
214
|
+
) => Effect.Effect<TerminalSessionSnapshot, ApplicationError>;
|
|
215
|
+
readonly attachTerminal: (input: {
|
|
216
|
+
readonly terminal: TerminalAttachTarget;
|
|
217
|
+
readonly cols?: number;
|
|
218
|
+
readonly rows?: number;
|
|
219
|
+
}) => Stream.Stream<TerminalAttachStreamEvent, ApplicationError>;
|
|
220
|
+
readonly watchTerminalMetadata: () => Stream.Stream<
|
|
221
|
+
TerminalMetadataStreamEvent,
|
|
222
|
+
ApplicationError
|
|
223
|
+
>;
|
|
224
|
+
readonly writeTerminal: (input: {
|
|
225
|
+
readonly terminal: TerminalRef;
|
|
226
|
+
readonly data: string;
|
|
227
|
+
}) => Effect.Effect<void, ApplicationError>;
|
|
228
|
+
readonly resizeTerminal: (input: {
|
|
229
|
+
readonly terminal: TerminalRef;
|
|
230
|
+
readonly cols: number;
|
|
231
|
+
readonly rows: number;
|
|
232
|
+
}) => Effect.Effect<void, ApplicationError>;
|
|
233
|
+
readonly destroyTerminal: (terminal: TerminalRef) => Effect.Effect<void, ApplicationError>;
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
export class T3TerminalApplication extends Context.Service<
|
|
237
|
+
T3TerminalApplication,
|
|
238
|
+
T3TerminalApplicationService
|
|
239
|
+
>()("t3cli/T3TerminalApplication") {}
|
|
240
|
+
|
|
241
|
+
export type T3ApplicationService = T3ModelApplicationService &
|
|
242
|
+
T3ProjectApplicationService &
|
|
243
|
+
T3ThreadApplicationService &
|
|
244
|
+
T3TerminalApplicationService;
|
|
245
|
+
|
|
246
|
+
export class T3Application extends Context.Service<T3Application, T3ApplicationService>()(
|
|
247
|
+
"t3cli/T3Application",
|
|
248
|
+
) {}
|
|
@@ -3,15 +3,13 @@ import * as Option from "effect/Option";
|
|
|
3
3
|
import * as Stream from "effect/Stream";
|
|
4
4
|
import { ORCHESTRATION_WS_METHODS } from "#t3tools/contracts";
|
|
5
5
|
|
|
6
|
-
import
|
|
6
|
+
import { T3Orchestration } from "../orchestration/service.ts";
|
|
7
7
|
import { RpcError } from "../rpc/error.ts";
|
|
8
8
|
|
|
9
|
-
export function waitForShellSequence(input: {
|
|
10
|
-
readonly orchestration: Orchestration;
|
|
11
|
-
readonly sequence: number;
|
|
12
|
-
}) {
|
|
9
|
+
export function waitForShellSequence(input: { readonly sequence: number }) {
|
|
13
10
|
return Effect.gen(function* () {
|
|
14
|
-
const
|
|
11
|
+
const orchestration = yield* T3Orchestration;
|
|
12
|
+
const sequence = yield* orchestration.watchShellSequence().pipe(
|
|
15
13
|
Stream.filter((snapshotSequence) => snapshotSequence >= input.sequence),
|
|
16
14
|
Stream.runHead,
|
|
17
15
|
Effect.scoped,
|
|
@@ -24,6 +22,6 @@ export function waitForShellSequence(input: {
|
|
|
24
22
|
}),
|
|
25
23
|
);
|
|
26
24
|
}
|
|
27
|
-
return yield*
|
|
25
|
+
return yield* orchestration.getShellSnapshot();
|
|
28
26
|
});
|
|
29
27
|
}
|
|
@@ -0,0 +1,207 @@
|
|
|
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
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
if (input.command !== undefined) {
|
|
131
|
+
yield* rpc.run(WS_METHODS.terminalWrite, (client) =>
|
|
132
|
+
client[WS_METHODS.terminalWrite]({
|
|
133
|
+
threadId: thread.id,
|
|
134
|
+
terminalId,
|
|
135
|
+
data: `${input.command}\r`,
|
|
136
|
+
}),
|
|
137
|
+
);
|
|
138
|
+
const refreshed = yield* Stream.runHead(
|
|
139
|
+
attachTerminal({
|
|
140
|
+
terminal: {
|
|
141
|
+
threadId: opened.threadId,
|
|
142
|
+
terminalId: opened.terminalId,
|
|
143
|
+
cwd: opened.cwd,
|
|
144
|
+
worktreePath: opened.worktreePath,
|
|
145
|
+
},
|
|
146
|
+
}),
|
|
147
|
+
);
|
|
148
|
+
const event = Option.getOrUndefined(refreshed);
|
|
149
|
+
if (event !== undefined && (event.type === "snapshot" || event.type === "restarted")) {
|
|
150
|
+
return event.snapshot;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return opened;
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const writeTerminal = Effect.fn("T3ApplicationLive.writeTerminal")(function* (input: {
|
|
158
|
+
readonly terminal: TerminalRef;
|
|
159
|
+
readonly data: string;
|
|
160
|
+
}) {
|
|
161
|
+
yield* rpc.run(WS_METHODS.terminalWrite, (client) =>
|
|
162
|
+
client[WS_METHODS.terminalWrite]({
|
|
163
|
+
threadId: input.terminal.threadId,
|
|
164
|
+
terminalId: input.terminal.terminalId,
|
|
165
|
+
data: input.data,
|
|
166
|
+
}),
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const resizeTerminal = Effect.fn("T3ApplicationLive.resizeTerminal")(function* (input: {
|
|
171
|
+
readonly terminal: TerminalRef;
|
|
172
|
+
readonly cols: number;
|
|
173
|
+
readonly rows: number;
|
|
174
|
+
}) {
|
|
175
|
+
yield* rpc.run(WS_METHODS.terminalResize, (client) =>
|
|
176
|
+
client[WS_METHODS.terminalResize]({
|
|
177
|
+
threadId: input.terminal.threadId,
|
|
178
|
+
terminalId: input.terminal.terminalId,
|
|
179
|
+
cols: input.cols,
|
|
180
|
+
rows: input.rows,
|
|
181
|
+
}),
|
|
182
|
+
);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
const destroyTerminal = Effect.fn("T3ApplicationLive.destroyTerminal")(function* (
|
|
186
|
+
terminal: TerminalRef,
|
|
187
|
+
) {
|
|
188
|
+
yield* rpc.run(WS_METHODS.terminalClose, (client) =>
|
|
189
|
+
client[WS_METHODS.terminalClose]({
|
|
190
|
+
threadId: terminal.threadId,
|
|
191
|
+
terminalId: terminal.terminalId,
|
|
192
|
+
deleteHistory: true,
|
|
193
|
+
}),
|
|
194
|
+
);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
attachTerminal,
|
|
199
|
+
createTerminal,
|
|
200
|
+
destroyTerminal,
|
|
201
|
+
getTerminal,
|
|
202
|
+
listTerminals,
|
|
203
|
+
resizeTerminal,
|
|
204
|
+
watchTerminalMetadata,
|
|
205
|
+
writeTerminal,
|
|
206
|
+
} satisfies T3TerminalApplicationService;
|
|
207
|
+
});
|
|
@@ -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,6 +6,7 @@ import {
|
|
|
6
6
|
CommandId,
|
|
7
7
|
MessageId,
|
|
8
8
|
ThreadId,
|
|
9
|
+
TurnId,
|
|
9
10
|
type ClientOrchestrationCommand,
|
|
10
11
|
type ModelSelection,
|
|
11
12
|
type OrchestrationProjectShell,
|
|
@@ -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
|
+
});
|