t3code-cli 0.10.0 → 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 (124) hide show
  1. package/README.md +33 -1
  2. package/dist/application.js +2 -2
  3. package/dist/auth.js +1 -1
  4. package/dist/bin.js +1566 -351
  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 +1740 -767
  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 +2 -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 +8 -0
  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/send.ts +22 -2
  111. package/src/cli/threads/unarchive.ts +44 -0
  112. package/src/cli/threads/update.ts +150 -0
  113. package/src/domain/error.ts +20 -1
  114. package/src/domain/model-config.ts +4 -0
  115. package/src/domain/thread-lifecycle.ts +14 -0
  116. package/src/node/index.ts +2 -0
  117. package/src/orchestration/index.ts +1 -0
  118. package/src/orchestration/layer.ts +33 -85
  119. package/src/orchestration/service.ts +4 -0
  120. package/src/rpc/error.ts +10 -0
  121. package/src/rpc/index.ts +5 -0
  122. package/src/rpc/operation.ts +83 -0
  123. package/src/rpc/ws-group.ts +24 -0
  124. package/src/runtime/layer.ts +7 -2
@@ -0,0 +1,61 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import { Command } from "effect/unstable/cli";
4
+
5
+ import { requireDestructiveConfirmation } from "../confirm.ts";
6
+ import { formatFlag, selfActionForceFlag, threadFlag, yesFlag } from "../flags.ts";
7
+ import { formatThreadDeletedHuman } from "../thread-format.ts";
8
+ import { MissingThreadError } from "../error.ts";
9
+ import { requireSelfActionConfirmation } from "../self-action.ts";
10
+ import { resolveThreadId } from "../../scope/index.ts";
11
+ import { T3Application } from "../../application/service.ts";
12
+ import { Environment } from "../../environment/service.ts";
13
+ import { resolveOutputFormat } from "../output-format.ts";
14
+ import { T3Output } from "../output/service.ts";
15
+
16
+ export const deleteThreadCommand = Command.make(
17
+ "delete",
18
+ {
19
+ thread: threadFlag,
20
+ force: selfActionForceFlag,
21
+ yes: yesFlag,
22
+ format: formatFlag,
23
+ },
24
+ ({ thread, force, yes, format }) =>
25
+ Effect.gen(function* () {
26
+ const application = yield* T3Application;
27
+ const environment = yield* Environment;
28
+ const output = yield* T3Output;
29
+ const threadId = resolveThreadId({
30
+ value: Option.getOrUndefined(thread),
31
+ env: environment.env,
32
+ });
33
+ if (threadId === undefined) {
34
+ return yield* Effect.fail(
35
+ new MissingThreadError({
36
+ message: "thread id is required: pass --thread or set T3CODE_THREAD_ID",
37
+ }),
38
+ );
39
+ }
40
+ yield* requireSelfActionConfirmation({
41
+ threadId,
42
+ force,
43
+ environment,
44
+ action: "delete",
45
+ });
46
+ yield* requireDestructiveConfirmation({
47
+ message: `Delete thread ${threadId}?`,
48
+ yes,
49
+ environment,
50
+ });
51
+ const resolvedFormat = resolveOutputFormat(format, environment, "json");
52
+ const result = yield* application.deleteThread(threadId);
53
+ if (resolvedFormat === "json") {
54
+ return yield* output.printJson({
55
+ threadId: result.threadId,
56
+ dispatch: result.dispatch,
57
+ });
58
+ }
59
+ return yield* output.printInfo(formatThreadDeletedHuman(result));
60
+ }),
61
+ ).pipe(Command.withDescription("delete thread"));
@@ -0,0 +1,52 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import { Command } from "effect/unstable/cli";
4
+
5
+ import { formatFlag, selfActionForceFlag, threadFlag } from "../flags.ts";
6
+ import { MissingThreadError } from "../error.ts";
7
+ import { requireSelfActionConfirmation } from "../self-action.ts";
8
+ import { resolveThreadId } from "../../scope/index.ts";
9
+ import { T3Application } from "../../application/service.ts";
10
+ import { Environment } from "../../environment/service.ts";
11
+ import { resolveOutputFormat } from "../output-format.ts";
12
+ import { T3Output } from "../output/service.ts";
13
+
14
+ export const interruptThreadCommand = Command.make(
15
+ "interrupt",
16
+ {
17
+ thread: threadFlag,
18
+ force: selfActionForceFlag,
19
+ format: formatFlag,
20
+ },
21
+ ({ thread, force, format }) =>
22
+ Effect.gen(function* () {
23
+ const application = yield* T3Application;
24
+ const environment = yield* Environment;
25
+ const output = yield* T3Output;
26
+ const threadId = resolveThreadId({
27
+ value: Option.getOrUndefined(thread),
28
+ env: environment.env,
29
+ });
30
+ if (threadId === undefined) {
31
+ return yield* Effect.fail(
32
+ new MissingThreadError({
33
+ message: "thread id is required: pass --thread or set T3CODE_THREAD_ID",
34
+ }),
35
+ );
36
+ }
37
+ yield* requireSelfActionConfirmation({
38
+ threadId,
39
+ force,
40
+ environment,
41
+ action: "interrupt",
42
+ });
43
+ const resolvedFormat = resolveOutputFormat(format, environment, "json");
44
+ const dispatch = yield* application.interruptThread(threadId);
45
+ if (resolvedFormat === "json") {
46
+ return yield* output.printJson({ threadId, dispatch });
47
+ }
48
+ return yield* output.printInfo(
49
+ `thread interrupted: ${threadId}\nsequence: ${dispatch.sequence}`,
50
+ );
51
+ }),
52
+ ).pipe(Command.withDescription("interrupt running thread turn"));
@@ -0,0 +1,69 @@
1
+ import "vite-plus/test/config";
2
+
3
+ import * as Cause from "effect/Cause";
4
+ import * as Effect from "effect/Effect";
5
+ import * as Exit from "effect/Exit";
6
+ import * as Layer from "effect/Layer";
7
+ import * as NodeServices from "@effect/platform-node/NodeServices";
8
+ import * as Option from "effect/Option";
9
+ import { assert, describe, it } from "@effect/vitest";
10
+ import { Command } from "effect/unstable/cli";
11
+ import { fromPartial } from "@total-typescript/shoehorn";
12
+
13
+ import { T3Application } from "../../application/service.ts";
14
+ import { T3Config } from "../../config/service.ts";
15
+ import { NodeEnvironmentLive } from "../../environment/layer.ts";
16
+ import { InvalidFlagCombinationError } from "../error.ts";
17
+ import { T3Output } from "../output/service.ts";
18
+ import { listThreadsCommand } from "./list.ts";
19
+
20
+ const testLayer = Layer.mergeAll(
21
+ Layer.succeed(
22
+ T3Application,
23
+ fromPartial({
24
+ listThreads: () => Effect.die("listThreads should not be called"),
25
+ }),
26
+ ),
27
+ Layer.succeed(
28
+ T3Config,
29
+ fromPartial({
30
+ resolve: () =>
31
+ Effect.succeed({
32
+ url: "ws://localhost",
33
+ token: "token",
34
+ source: "config",
35
+ local: true,
36
+ }),
37
+ }),
38
+ ),
39
+ Layer.succeed(T3Output, {
40
+ writeStdout: () => Effect.void,
41
+ writeStderr: () => Effect.void,
42
+ printJson: () => Effect.void,
43
+ printNdjson: () => Effect.void,
44
+ printInfo: () => Effect.void,
45
+ }),
46
+ NodeServices.layer,
47
+ NodeEnvironmentLive,
48
+ );
49
+
50
+ describe("listThreadsCommand", () => {
51
+ it.layer(testLayer)("listThreadsCommand", (t) => {
52
+ t.effect("rejects --archived and --all together", () =>
53
+ Effect.gen(function* () {
54
+ const run = Command.runWith(listThreadsCommand, { version: "0.0.0-test" });
55
+ const exit = yield* run(["--archived", "--all", "--project", "proj-1"]).pipe(Effect.exit);
56
+ assert.isTrue(Exit.isFailure(exit));
57
+ if (!Exit.isFailure(exit)) {
58
+ return;
59
+ }
60
+ const error = Cause.findErrorOption(exit.cause);
61
+ assert.isTrue(Option.isSome(error));
62
+ if (Option.isSome(error)) {
63
+ assert.instanceOf(error.value, InvalidFlagCombinationError);
64
+ assert.equal(error.value.message, "--archived and --all are mutually exclusive");
65
+ }
66
+ }),
67
+ );
68
+ });
69
+ });
@@ -1,7 +1,9 @@
1
1
  import * as Effect from "effect/Effect";
2
- import { Command } from "effect/unstable/cli";
2
+ import { Command, Flag } from "effect/unstable/cli";
3
3
 
4
+ import type { ListThreadsInclude } from "../../application/service.ts";
4
5
  import { formatFlag, projectFlag } from "../flags.ts";
6
+ import { InvalidFlagCombinationError } from "../error.ts";
5
7
  import { requireCommandProjectRef } from "../require.ts";
6
8
  import { formatThreadsHuman } from "../thread-format.ts";
7
9
  import { T3Application } from "../../application/service.ts";
@@ -13,20 +15,30 @@ export const listThreadsCommand = Command.make(
13
15
  "list",
14
16
  {
15
17
  project: projectFlag,
18
+ archived: Flag.boolean("archived").pipe(Flag.withDescription("List archived threads only")),
19
+ all: Flag.boolean("all").pipe(Flag.withDescription("List active and archived threads")),
16
20
  format: formatFlag,
17
21
  },
18
- ({ project, format }) =>
22
+ ({ project, archived, all, format }) =>
19
23
  Effect.gen(function* () {
20
24
  const application = yield* T3Application;
21
25
  const environment = yield* Environment;
22
26
  const output = yield* T3Output;
27
+ if (archived && all) {
28
+ yield* Effect.fail(
29
+ new InvalidFlagCombinationError({
30
+ message: "--archived and --all are mutually exclusive",
31
+ }),
32
+ );
33
+ }
34
+ const include: ListThreadsInclude = archived ? "archived" : all ? "all" : "active";
23
35
  const resolvedFormat = resolveOutputFormat(format, environment, "json");
24
36
  const projectRef = yield* requireCommandProjectRef({
25
37
  project,
26
38
  env: environment.env,
27
39
  cwd: environment.cwd,
28
40
  });
29
- const result = yield* application.listThreads(projectRef);
41
+ const result = yield* application.listThreads(projectRef, { include });
30
42
  if (resolvedFormat === "json") {
31
43
  yield* output.printJson(result.threads);
32
44
  } else {
@@ -2,10 +2,11 @@ import * as Effect from "effect/Effect";
2
2
  import * as Option from "effect/Option";
3
3
  import { Argument, Command, Flag } from "effect/unstable/cli";
4
4
 
5
- import { modelFlags, threadFlag, threadFormatFlag } from "../flags.ts";
5
+ import { modelFlags, selfActionForceFlag, threadFlag, threadFormatFlag } from "../flags.ts";
6
6
  import { readInitialMessage } from "../message-input.ts";
7
7
  import { buildModelOptions } from "../model-options.ts";
8
8
  import { MissingThreadError } from "../error.ts";
9
+ import { requireSelfActionConfirmation } from "../self-action.ts";
9
10
  import { resolveThreadId } from "../../scope/index.ts";
10
11
  import { T3Application } from "../../application/service.ts";
11
12
  import { Environment } from "../../environment/service.ts";
@@ -18,13 +19,26 @@ export const sendThreadCommand = Command.make(
18
19
  "send",
19
20
  {
20
21
  thread: threadFlag,
22
+ force: selfActionForceFlag,
21
23
  message: Argument.string("message").pipe(Argument.optional),
22
24
  stdin: Flag.boolean("stdin"),
23
25
  ...modelFlags,
24
26
  wait: Flag.boolean("wait"),
25
27
  format: threadFormatFlag,
26
28
  },
27
- ({ thread, message, stdin, option, reasoningEffort, effort, fastMode, thinking, wait, format }) =>
29
+ ({
30
+ thread,
31
+ force,
32
+ message,
33
+ stdin,
34
+ option,
35
+ reasoningEffort,
36
+ effort,
37
+ fastMode,
38
+ thinking,
39
+ wait,
40
+ format,
41
+ }) =>
28
42
  Effect.gen(function* () {
29
43
  const inputService = yield* T3Input;
30
44
  const text = yield* readInitialMessage({
@@ -53,6 +67,12 @@ export const sendThreadCommand = Command.make(
53
67
  }),
54
68
  );
55
69
  }
70
+ yield* requireSelfActionConfirmation({
71
+ threadId,
72
+ force,
73
+ environment,
74
+ action: "send a message to",
75
+ });
56
76
  const input = {
57
77
  message: text,
58
78
  threadId,
@@ -0,0 +1,44 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import { Command } from "effect/unstable/cli";
4
+
5
+ import { formatFlag, threadFlag } from "../flags.ts";
6
+ import { MissingThreadError } from "../error.ts";
7
+ import { resolveThreadId } from "../../scope/index.ts";
8
+ import { T3Application } from "../../application/service.ts";
9
+ import { Environment } from "../../environment/service.ts";
10
+ import { resolveOutputFormat } from "../output-format.ts";
11
+ import { T3Output } from "../output/service.ts";
12
+
13
+ export const unarchiveThreadCommand = Command.make(
14
+ "unarchive",
15
+ {
16
+ thread: threadFlag,
17
+ format: formatFlag,
18
+ },
19
+ ({ thread, format }) =>
20
+ Effect.gen(function* () {
21
+ const application = yield* T3Application;
22
+ const environment = yield* Environment;
23
+ const output = yield* T3Output;
24
+ const threadId = resolveThreadId({
25
+ value: Option.getOrUndefined(thread),
26
+ env: environment.env,
27
+ });
28
+ if (threadId === undefined) {
29
+ return yield* Effect.fail(
30
+ new MissingThreadError({
31
+ message: "thread id is required: pass --thread or set T3CODE_THREAD_ID",
32
+ }),
33
+ );
34
+ }
35
+ const resolvedFormat = resolveOutputFormat(format, environment, "json");
36
+ const dispatch = yield* application.unarchiveThread(threadId);
37
+ if (resolvedFormat === "json") {
38
+ return yield* output.printJson(dispatch);
39
+ }
40
+ return yield* output.printInfo(
41
+ `thread unarchived: ${threadId}\nsequence: ${dispatch.sequence}`,
42
+ );
43
+ }),
44
+ ).pipe(Command.withDescription("unarchive thread"));
@@ -0,0 +1,150 @@
1
+ import * as Effect from "effect/Effect";
2
+ import * as Option from "effect/Option";
3
+ import { Command, Flag } from "effect/unstable/cli";
4
+
5
+ import { formatFlag, modelFlags, selfActionForceFlag, threadFlag } from "../flags.ts";
6
+ import {
7
+ ConflictingUpdateFlagsError,
8
+ MissingThreadError,
9
+ MissingUpdateFieldsError,
10
+ } from "../error.ts";
11
+ import { requireSelfActionConfirmation } from "../self-action.ts";
12
+ import { buildModelOptions } from "../model-options.ts";
13
+ import { resolveThreadId } from "../../scope/index.ts";
14
+ import { T3Application } from "../../application/service.ts";
15
+ import { Environment } from "../../environment/service.ts";
16
+ import { resolveOutputFormat } from "../output-format.ts";
17
+ import { T3Output } from "../output/service.ts";
18
+
19
+ export const updateThreadCommand = Command.make(
20
+ "update",
21
+ {
22
+ thread: threadFlag,
23
+ force: selfActionForceFlag,
24
+ title: Flag.string("title").pipe(Flag.optional),
25
+ provider: Flag.string("provider").pipe(Flag.optional),
26
+ model: Flag.string("model").pipe(Flag.optional),
27
+ ...modelFlags,
28
+ branch: Flag.string("branch").pipe(Flag.optional),
29
+ clearBranch: Flag.boolean("clear-branch").pipe(Flag.optional),
30
+ worktree: Flag.string("worktree").pipe(Flag.optional),
31
+ clearWorktree: Flag.boolean("clear-worktree").pipe(Flag.optional),
32
+ format: formatFlag,
33
+ },
34
+ ({
35
+ thread,
36
+ force,
37
+ title,
38
+ provider,
39
+ model,
40
+ option,
41
+ reasoningEffort,
42
+ effort,
43
+ fastMode,
44
+ thinking,
45
+ branch,
46
+ clearBranch,
47
+ worktree,
48
+ clearWorktree,
49
+ format,
50
+ }) =>
51
+ Effect.gen(function* () {
52
+ const application = yield* T3Application;
53
+ const environment = yield* Environment;
54
+ const output = yield* T3Output;
55
+ const threadId = resolveThreadId({
56
+ value: Option.getOrUndefined(thread),
57
+ env: environment.env,
58
+ });
59
+ if (threadId === undefined) {
60
+ return yield* Effect.fail(
61
+ new MissingThreadError({
62
+ message: "thread id is required: pass --thread or set T3CODE_THREAD_ID",
63
+ }),
64
+ );
65
+ }
66
+
67
+ yield* requireSelfActionConfirmation({
68
+ threadId,
69
+ force,
70
+ environment,
71
+ action: "update",
72
+ });
73
+
74
+ const titleValue = Option.getOrUndefined(title);
75
+ const providerValue = Option.getOrUndefined(provider);
76
+ const modelValue = Option.getOrUndefined(model);
77
+ const branchValue = Option.getOrUndefined(branch);
78
+ const worktreeValue = Option.getOrUndefined(worktree);
79
+ const clearBranchValue = Option.getOrUndefined(clearBranch);
80
+ const clearWorktreeValue = Option.getOrUndefined(clearWorktree);
81
+ const options = buildModelOptions({
82
+ option,
83
+ reasoningEffort,
84
+ effort,
85
+ fastMode,
86
+ thinking,
87
+ });
88
+
89
+ if (clearBranchValue === true && branchValue !== undefined) {
90
+ return yield* Effect.fail(
91
+ new ConflictingUpdateFlagsError({
92
+ message: "--branch and --clear-branch are mutually exclusive",
93
+ }),
94
+ );
95
+ }
96
+ if (clearWorktreeValue === true && worktreeValue !== undefined) {
97
+ return yield* Effect.fail(
98
+ new ConflictingUpdateFlagsError({
99
+ message: "--worktree and --clear-worktree are mutually exclusive",
100
+ }),
101
+ );
102
+ }
103
+
104
+ const hasTitle = titleValue !== undefined && titleValue.length > 0;
105
+ const hasProvider = providerValue !== undefined && providerValue.length > 0;
106
+ const hasModel = modelValue !== undefined && modelValue.length > 0;
107
+ const hasBranch = branchValue !== undefined && branchValue.length > 0;
108
+ const hasWorktree = worktreeValue !== undefined && worktreeValue.length > 0;
109
+ if (
110
+ !hasTitle &&
111
+ !hasProvider &&
112
+ !hasModel &&
113
+ options.length === 0 &&
114
+ !hasBranch &&
115
+ clearBranchValue !== true &&
116
+ !hasWorktree &&
117
+ clearWorktreeValue !== true
118
+ ) {
119
+ return yield* Effect.fail(
120
+ new MissingUpdateFieldsError({
121
+ message:
122
+ "at least one update field is required: --title, model flags, --branch, --clear-branch, --worktree, or --clear-worktree",
123
+ }),
124
+ );
125
+ }
126
+
127
+ const dispatch = yield* application.updateThread({
128
+ threadId,
129
+ ...(hasTitle ? { title: titleValue } : {}),
130
+ ...(hasProvider ? { provider: providerValue } : {}),
131
+ ...(hasModel ? { model: modelValue } : {}),
132
+ ...(options.length > 0 ? { options } : {}),
133
+ ...(clearBranchValue === true
134
+ ? { branch: null }
135
+ : hasBranch
136
+ ? { branch: branchValue }
137
+ : {}),
138
+ ...(clearWorktreeValue === true
139
+ ? { worktreePath: null }
140
+ : hasWorktree
141
+ ? { worktreePath: worktreeValue }
142
+ : {}),
143
+ });
144
+ const resolvedFormat = resolveOutputFormat(format, environment, "json");
145
+ if (resolvedFormat === "json") {
146
+ return yield* output.printJson(dispatch);
147
+ }
148
+ return yield* output.printInfo(`thread updated: ${threadId}\nsequence: ${dispatch.sequence}`);
149
+ }),
150
+ ).pipe(Command.withDescription("update thread metadata"));
@@ -30,6 +30,14 @@ export class ThreadSessionError extends Schema.TaggedErrorClass<ThreadSessionErr
30
30
  },
31
31
  ) {}
32
32
 
33
+ export class ThreadLookupError extends Schema.TaggedErrorClass<ThreadLookupError>()(
34
+ "ThreadLookupError",
35
+ {
36
+ message: Schema.String,
37
+ threadId: Schema.String,
38
+ },
39
+ ) {}
40
+
33
41
  export class ProjectCreateVisibilityError extends Schema.TaggedErrorClass<ProjectCreateVisibilityError>()(
34
42
  "ProjectCreateVisibilityError",
35
43
  {
@@ -38,9 +46,20 @@ export class ProjectCreateVisibilityError extends Schema.TaggedErrorClass<Projec
38
46
  },
39
47
  ) {}
40
48
 
49
+ export class TerminalLookupError extends Schema.TaggedErrorClass<TerminalLookupError>()(
50
+ "TerminalLookupError",
51
+ {
52
+ message: Schema.String,
53
+ threadId: Schema.String,
54
+ terminalId: Schema.String,
55
+ },
56
+ ) {}
57
+
41
58
  export type DomainError =
42
59
  | ProjectLookupError
43
60
  | ModelSelectionError
44
61
  | ThreadEventError
45
62
  | ThreadSessionError
46
- | ProjectCreateVisibilityError;
63
+ | ThreadLookupError
64
+ | ProjectCreateVisibilityError
65
+ | TerminalLookupError;
@@ -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,
@@ -2,11 +2,25 @@ 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
25
  if (thread.session?.status === "starting" || thread.session?.status === "running") {
12
26
  return true;
@@ -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";