@markusylisiurunen/tau 0.2.129 → 0.3.1

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 (208) hide show
  1. package/README.md +111 -59
  2. package/dist/core/async/cli.js +12 -2
  3. package/dist/core/async/cli.js.map +1 -1
  4. package/dist/core/async/http_protocol.js +7 -1
  5. package/dist/core/async/http_protocol.js.map +1 -1
  6. package/dist/core/async/http_server.js +9 -1
  7. package/dist/core/async/http_server.js.map +1 -1
  8. package/dist/core/async/index.js.map +1 -1
  9. package/dist/core/async/session_manager.js +94 -51
  10. package/dist/core/async/session_manager.js.map +1 -1
  11. package/dist/core/cli.js +12 -15
  12. package/dist/core/cli.js.map +1 -1
  13. package/dist/core/commands/registry.js +9 -55
  14. package/dist/core/commands/registry.js.map +1 -1
  15. package/dist/core/config/index.js.map +1 -1
  16. package/dist/core/config/runtime.js +0 -1
  17. package/dist/core/config/runtime.js.map +1 -1
  18. package/dist/core/config/schema.js +102 -17
  19. package/dist/core/config/schema.js.map +1 -1
  20. package/dist/core/debug.js +1 -20
  21. package/dist/core/debug.js.map +1 -1
  22. package/dist/core/diff_review/{session.js → bridge.js} +57 -114
  23. package/dist/core/diff_review/bridge.js.map +1 -0
  24. package/dist/core/diff_review/index.js +2 -3
  25. package/dist/core/diff_review/index.js.map +1 -1
  26. package/dist/core/diff_review/review_thread.js +48 -23
  27. package/dist/core/diff_review/review_thread.js.map +1 -1
  28. package/dist/core/diff_review/snapshot.js +40 -11
  29. package/dist/core/diff_review/snapshot.js.map +1 -1
  30. package/dist/core/events/parser.js +43 -12
  31. package/dist/core/events/parser.js.map +1 -1
  32. package/dist/core/index.js +0 -1
  33. package/dist/core/index.js.map +1 -1
  34. package/dist/core/modes/index.js +1 -2
  35. package/dist/core/modes/index.js.map +1 -1
  36. package/dist/core/modes/rpc_server.js +15 -321
  37. package/dist/core/modes/rpc_server.js.map +1 -1
  38. package/dist/core/modes/websocket_server.js +181 -0
  39. package/dist/core/modes/websocket_server.js.map +1 -0
  40. package/dist/core/personas.js +11 -1
  41. package/dist/core/personas.js.map +1 -1
  42. package/dist/core/runtime/chat_runtime.js +19 -12
  43. package/dist/core/runtime/chat_runtime.js.map +1 -1
  44. package/dist/core/runtime/conversation_turn_runtime.js +16 -1
  45. package/dist/core/runtime/conversation_turn_runtime.js.map +1 -1
  46. package/dist/core/runtime/runtime_bootstrap.js +326 -0
  47. package/dist/core/runtime/runtime_bootstrap.js.map +1 -1
  48. package/dist/core/session/compaction.js +13 -11
  49. package/dist/core/session/compaction.js.map +1 -1
  50. package/dist/core/session/core_session.js +9 -0
  51. package/dist/core/session/core_session.js.map +1 -1
  52. package/dist/core/session/direct_bash.js +34 -0
  53. package/dist/core/session/direct_bash.js.map +1 -0
  54. package/dist/core/session/pruning.js +595 -0
  55. package/dist/core/session/pruning.js.map +1 -0
  56. package/dist/core/session/runner.js +45 -5
  57. package/dist/core/session/runner.js.map +1 -1
  58. package/dist/core/session/session_engine.js +84 -10
  59. package/dist/core/session/session_engine.js.map +1 -1
  60. package/dist/core/static/prompts/diff-review-wrapper.md +1 -1
  61. package/dist/core/subagents/subagent_engine.js +1 -1
  62. package/dist/core/subagents/subagent_engine.js.map +1 -1
  63. package/dist/core/tools/catalog.js.map +1 -1
  64. package/dist/core/tools/diff_review.js +12 -12
  65. package/dist/core/tools/diff_review.js.map +1 -1
  66. package/dist/core/tools/execution_backend.js +9 -2
  67. package/dist/core/tools/execution_backend.js.map +1 -1
  68. package/dist/core/tools/registry.js.map +1 -1
  69. package/dist/core/usage/logs.js.map +1 -1
  70. package/dist/core/utils/context.js +1 -1
  71. package/dist/core/utils/context.js.map +1 -1
  72. package/dist/core/utils/context_builder.js +1 -18
  73. package/dist/core/utils/context_builder.js.map +1 -1
  74. package/dist/core/utils/gemini_speech.js +1 -1
  75. package/dist/core/utils/gemini_speech.js.map +1 -1
  76. package/dist/core/utils/project_files.js +11 -16
  77. package/dist/core/utils/project_files.js.map +1 -1
  78. package/dist/core/utils/spawn_capture.js +2 -2
  79. package/dist/core/utils/spawn_capture.js.map +1 -1
  80. package/dist/core/version.js +1 -1
  81. package/dist/core/version.js.map +1 -1
  82. package/dist/execution/cloudflare_sandbox_execution_environment.js +400 -0
  83. package/dist/execution/cloudflare_sandbox_execution_environment.js.map +1 -0
  84. package/dist/execution/execution_environment.js +24 -0
  85. package/dist/execution/execution_environment.js.map +1 -0
  86. package/dist/execution/fly_sprite_execution_environment.js +559 -0
  87. package/dist/execution/fly_sprite_execution_environment.js.map +1 -0
  88. package/dist/execution/local_execution_environment.js +93 -0
  89. package/dist/execution/local_execution_environment.js.map +1 -0
  90. package/dist/execution/runtime_config_snapshot.js +209 -0
  91. package/dist/execution/runtime_config_snapshot.js.map +1 -0
  92. package/dist/execution/sandbox_tool_helpers.js +55 -0
  93. package/dist/execution/sandbox_tool_helpers.js.map +1 -0
  94. package/dist/execution/tool_backend_execution_environment.js +55 -0
  95. package/dist/execution/tool_backend_execution_environment.js.map +1 -0
  96. package/dist/host/hosted_ephemeral_agent_session.js +286 -0
  97. package/dist/host/hosted_ephemeral_agent_session.js.map +1 -0
  98. package/dist/host/index.js +3 -0
  99. package/dist/host/index.js.map +1 -0
  100. package/dist/host/local_session_host.js +1464 -0
  101. package/dist/host/local_session_host.js.map +1 -0
  102. package/dist/host/session_host.js +2 -0
  103. package/dist/host/session_host.js.map +1 -0
  104. package/dist/host/session_protocol_handler.js +873 -0
  105. package/dist/host/session_protocol_handler.js.map +1 -0
  106. package/dist/main.js +537 -77
  107. package/dist/main.js.map +1 -1
  108. package/dist/protocol/index.d.ts +2 -0
  109. package/dist/protocol/index.js +2 -0
  110. package/dist/protocol/index.js.map +1 -0
  111. package/dist/protocol/session_protocol.d.ts +691 -0
  112. package/dist/protocol/session_protocol.js +2117 -0
  113. package/dist/protocol/session_protocol.js.map +1 -0
  114. package/dist/sdk/client.d.ts +2 -1
  115. package/dist/sdk/client.js +136 -392
  116. package/dist/sdk/client.js.map +1 -1
  117. package/dist/sdk/index.d.ts +7 -4
  118. package/dist/sdk/index.js +4 -2
  119. package/dist/sdk/index.js.map +1 -1
  120. package/dist/sdk/session.d.ts +5 -0
  121. package/dist/sdk/session.js +406 -0
  122. package/dist/sdk/session.js.map +1 -0
  123. package/dist/sdk/types.d.ts +97 -78
  124. package/dist/store/file_session_store.js +157 -0
  125. package/dist/store/file_session_store.js.map +1 -0
  126. package/dist/store/memory_session_store.js +24 -0
  127. package/dist/store/memory_session_store.js.map +1 -0
  128. package/dist/store/session_store.js +32 -0
  129. package/dist/store/session_store.js.map +1 -0
  130. package/dist/transport/errors.d.ts +29 -0
  131. package/dist/{sdk → transport}/errors.js +5 -5
  132. package/dist/transport/errors.js.map +1 -0
  133. package/dist/transport/in_process_session_transport.js +102 -0
  134. package/dist/transport/in_process_session_transport.js.map +1 -0
  135. package/dist/transport/index.d.ts +7 -0
  136. package/dist/transport/index.js +4 -0
  137. package/dist/transport/index.js.map +1 -0
  138. package/dist/transport/pending_session_protocol_requests.js +67 -0
  139. package/dist/transport/pending_session_protocol_requests.js.map +1 -0
  140. package/dist/transport/session_protocol_transport_helpers.js +98 -0
  141. package/dist/transport/session_protocol_transport_helpers.js.map +1 -0
  142. package/dist/transport/session_transport.d.ts +11 -0
  143. package/dist/transport/session_transport.js +2 -0
  144. package/dist/transport/session_transport.js.map +1 -0
  145. package/dist/transport/stdio_session_transport.d.ts +38 -0
  146. package/dist/transport/stdio_session_transport.js +243 -0
  147. package/dist/transport/stdio_session_transport.js.map +1 -0
  148. package/dist/transport/websocket_session_transport.d.ts +52 -0
  149. package/dist/transport/websocket_session_transport.js +235 -0
  150. package/dist/transport/websocket_session_transport.js.map +1 -0
  151. package/dist/tui/chat_controller/assistant_clipboard.js +39 -0
  152. package/dist/tui/chat_controller/assistant_clipboard.js.map +1 -0
  153. package/dist/tui/chat_controller/command_hints.js +43 -0
  154. package/dist/tui/chat_controller/command_hints.js.map +1 -0
  155. package/dist/tui/chat_controller/diff_review_service.js +9 -9
  156. package/dist/tui/chat_controller/diff_review_service.js.map +1 -1
  157. package/dist/tui/chat_controller/diff_review_user_message.js +17 -0
  158. package/dist/tui/chat_controller/diff_review_user_message.js.map +1 -0
  159. package/dist/tui/chat_controller/history_labels.js +8 -0
  160. package/dist/tui/chat_controller/history_labels.js.map +1 -0
  161. package/dist/tui/chat_controller/history_message_model.js +36 -0
  162. package/dist/tui/chat_controller/history_message_model.js.map +1 -0
  163. package/dist/tui/chat_controller/status_format.js +22 -0
  164. package/dist/tui/chat_controller/status_format.js.map +1 -0
  165. package/dist/tui/chat_view.js +0 -1
  166. package/dist/tui/chat_view.js.map +1 -1
  167. package/dist/tui/index.js +1 -1
  168. package/dist/tui/index.js.map +1 -1
  169. package/dist/tui/listen_capture.js +87 -0
  170. package/dist/tui/listen_capture.js.map +1 -0
  171. package/dist/tui/memory_mode.js +23 -0
  172. package/dist/tui/memory_mode.js.map +1 -0
  173. package/dist/tui/session_chat_app.js +227 -0
  174. package/dist/tui/session_chat_app.js.map +1 -0
  175. package/dist/tui/session_chat_controller.js +2176 -0
  176. package/dist/tui/session_chat_controller.js.map +1 -0
  177. package/dist/tui/session_tool_execution_backend.js +170 -0
  178. package/dist/tui/session_tool_execution_backend.js.map +1 -0
  179. package/dist/tui/speech_playback.js +107 -0
  180. package/dist/tui/speech_playback.js.map +1 -0
  181. package/dist/tui/ui/assistant_message.js +27 -21
  182. package/dist/tui/ui/assistant_message.js.map +1 -1
  183. package/dist/tui/ui/chat_container.js +4 -0
  184. package/dist/tui/ui/chat_container.js.map +1 -1
  185. package/dist/tui/ui/custom_editor.js +0 -5
  186. package/dist/tui/ui/custom_editor.js.map +1 -1
  187. package/dist/tui/ui/slash_autocomplete.js +14 -36
  188. package/dist/tui/ui/slash_autocomplete.js.map +1 -1
  189. package/dist/tui/ui/tool_ui_registry.js +21 -0
  190. package/dist/tui/ui/tool_ui_registry.js.map +1 -1
  191. package/package.json +6 -3
  192. package/dist/core/config/bash_commands.js +0 -44
  193. package/dist/core/config/bash_commands.js.map +0 -1
  194. package/dist/core/diff_review/session.js.map +0 -1
  195. package/dist/core/modes/rpc_adapter.js +0 -17
  196. package/dist/core/modes/rpc_adapter.js.map +0 -1
  197. package/dist/core/modes/rpc_protocol.js +0 -500
  198. package/dist/core/modes/rpc_protocol.js.map +0 -1
  199. package/dist/core/session/checkpoint.js +0 -34
  200. package/dist/core/session/checkpoint.js.map +0 -1
  201. package/dist/sdk/errors.d.ts +0 -33
  202. package/dist/sdk/errors.js.map +0 -1
  203. package/dist/tui/app.js +0 -64
  204. package/dist/tui/app.js.map +0 -1
  205. package/dist/tui/chat_controller/session_maintenance_service.js +0 -634
  206. package/dist/tui/chat_controller/session_maintenance_service.js.map +0 -1
  207. package/dist/tui/chat_controller.js +0 -2579
  208. package/dist/tui/chat_controller.js.map +0 -1
package/dist/main.js CHANGED
@@ -1,20 +1,28 @@
1
1
  #!/usr/bin/env node
2
2
  import { readFileSync, writeSync } from "node:fs";
3
- import { readFile } from "node:fs/promises";
4
3
  import { homedir } from "node:os";
5
- import { resolve } from "node:path";
4
+ import { isAbsolute } from "node:path";
6
5
  import { createInterface } from "node:readline";
7
6
  import { fileURLToPath } from "node:url";
8
- import { AsyncCliError, AuthStorage, ChatRuntime, CliError, createDefaultConfigDeps, createDefaultCoreDeps, createLocalToolExecutionBackend, getAuthPath, InstallCliError, loadConfig, loadRuntimeBootstrap, loadRuntimeConfig, parseCheckpoint, parseCliArgs, parsePersonaString, printAsyncHelp, printDebugInfo, printDiffToolHelp, printHelp, printInstallHelp, printUsageHelp, resolveRuntimePromptBootstrap, runAsyncCommand, runInstallCommand, runListCommand, runLoginCommand, runLogoutCommand, runRpcServer, runToolCommand, runUsageCommand, ToolCatalog, ToolCliError, UsageCliError, } from "./core/index.js";
7
+ import { AsyncCliError, AuthStorage, CliError, createDefaultConfigDeps, createDefaultCoreDeps, createLocalToolExecutionBackend, getAuthPath, InstallCliError, loadConfig, loadRuntimeBootstrap, loadRuntimeConfig, parseCliArgs, parsePersonaString, printAsyncHelp, printDebugInfo, printDiffToolHelp, printHelp, printInstallHelp, printUsageHelp, runAsyncCommand, runInstallCommand, runListCommand, runLoginCommand, runLogoutCommand, runRpcServer, runToolCommand, runUsageCommand, runWebSocketSessionServer, ToolCatalog, ToolCliError, UsageCliError, } from "./core/index.js";
9
8
  import { getStartupPlatformError } from "./core/platform_support.js";
10
9
  import { TOOL_NAME_DIFF_REVIEW } from "./core/tools/tool_names.js";
11
10
  import { createBuiltInDiffToolConfig, DiffToolLaunchEnvironmentError, runBuiltInDiffToolCommand, } from "./diff_tool/index.js";
12
- import { ChatApp } from "./tui/index.js";
11
+ import { CloudflareSandboxExecutionEnvironmentResolver } from "./execution/cloudflare_sandbox_execution_environment.js";
12
+ import { CompositeExecutionEnvironmentResolver } from "./execution/execution_environment.js";
13
+ import { FlySpriteExecutionEnvironmentResolver } from "./execution/fly_sprite_execution_environment.js";
14
+ import { LocalExecutionEnvironmentResolver } from "./execution/local_execution_environment.js";
15
+ import { LocalSessionHost } from "./host/local_session_host.js";
16
+ import { createTauSdkClient } from "./sdk/client.js";
17
+ import { FileSessionStore, getDefaultSessionStoreDirectory } from "./store/file_session_store.js";
18
+ import { SessionChatApp } from "./tui/index.js";
13
19
  import { detectTerminalAppearance } from "./tui/terminal_appearance.js";
14
20
  const cwd = process.cwd();
15
21
  const configDeps = createDefaultConfigDeps();
16
22
  const argv = process.argv.slice(2);
17
23
  const isRpcSubcommand = argv[0] === "rpc";
24
+ const isAttachSubcommand = argv[0] === "attach";
25
+ const isServeSubcommand = argv[0] === "serve";
18
26
  const isDiffToolSubcommand = argv[0] === "diff-tool";
19
27
  const startupPlatformError = getStartupPlatformError(process.platform);
20
28
  if (startupPlatformError) {
@@ -53,9 +61,286 @@ function printAuthHelp() {
53
61
  " tau auth logout codex --account user@example.com",
54
62
  ].join("\n"));
55
63
  }
64
+ function parseAttachArgs(args) {
65
+ let help = false;
66
+ let sessionId;
67
+ let createNew = false;
68
+ let cwd;
69
+ let executionKind;
70
+ let cloudflareBridgeId;
71
+ let cloudflareSandboxId;
72
+ let flyApiId;
73
+ let flySpriteName;
74
+ let authToken;
75
+ let target;
76
+ for (let i = 0; i < args.length; i += 1) {
77
+ const arg = args[i];
78
+ if (arg === "--") {
79
+ target = { transport: "stdio", command: args.slice(i + 1) };
80
+ break;
81
+ }
82
+ if (arg === "--help" || arg === "-h") {
83
+ help = true;
84
+ continue;
85
+ }
86
+ if (arg === "--session" || arg.startsWith("--session=")) {
87
+ const parsed = parseAttachValue(arg, args, i);
88
+ sessionId = parsed.value;
89
+ i = parsed.nextIndex;
90
+ continue;
91
+ }
92
+ if (arg === "--new") {
93
+ createNew = true;
94
+ continue;
95
+ }
96
+ if (arg === "--cwd" || arg.startsWith("--cwd=")) {
97
+ const parsed = parseAttachValue(arg, args, i);
98
+ cwd = parsed.value;
99
+ i = parsed.nextIndex;
100
+ continue;
101
+ }
102
+ if (arg === "--execution-kind" || arg.startsWith("--execution-kind=")) {
103
+ const parsed = parseAttachValue(arg, args, i);
104
+ if (parsed.value !== "local" &&
105
+ parsed.value !== "cloudflare-sandbox" &&
106
+ parsed.value !== "fly-sprite") {
107
+ throw new CliError("--execution-kind must be local, cloudflare-sandbox, or fly-sprite");
108
+ }
109
+ executionKind = parsed.value;
110
+ i = parsed.nextIndex;
111
+ continue;
112
+ }
113
+ if (arg === "--cloudflare-bridge" || arg.startsWith("--cloudflare-bridge=")) {
114
+ const parsed = parseAttachValue(arg, args, i);
115
+ cloudflareBridgeId = parsed.value;
116
+ executionKind ??= "cloudflare-sandbox";
117
+ i = parsed.nextIndex;
118
+ continue;
119
+ }
120
+ if (arg === "--cloudflare-sandbox" || arg.startsWith("--cloudflare-sandbox=")) {
121
+ const parsed = parseAttachValue(arg, args, i);
122
+ cloudflareSandboxId = parsed.value;
123
+ executionKind ??= "cloudflare-sandbox";
124
+ i = parsed.nextIndex;
125
+ continue;
126
+ }
127
+ if (arg === "--fly-api" || arg.startsWith("--fly-api=")) {
128
+ const parsed = parseAttachValue(arg, args, i);
129
+ flyApiId = parsed.value;
130
+ executionKind ??= "fly-sprite";
131
+ i = parsed.nextIndex;
132
+ continue;
133
+ }
134
+ if (arg === "--fly-sprite" || arg.startsWith("--fly-sprite=")) {
135
+ const parsed = parseAttachValue(arg, args, i);
136
+ flySpriteName = parsed.value;
137
+ executionKind ??= "fly-sprite";
138
+ i = parsed.nextIndex;
139
+ continue;
140
+ }
141
+ if (arg === "--auth-token" || arg.startsWith("--auth-token=")) {
142
+ const parsed = parseAttachValue(arg, args, i);
143
+ authToken = parsed.value;
144
+ i = parsed.nextIndex;
145
+ continue;
146
+ }
147
+ if (isWebSocketUrl(arg)) {
148
+ target = { transport: "websocket", url: arg };
149
+ if (i !== args.length - 1) {
150
+ throw new CliError("websocket attach target does not accept trailing arguments");
151
+ }
152
+ break;
153
+ }
154
+ throw new CliError(`unknown attach option: ${arg}`);
155
+ }
156
+ if (!help && !target) {
157
+ throw new CliError("missing attach target");
158
+ }
159
+ if (target?.transport === "stdio" && target.command.length === 0) {
160
+ throw new CliError("missing attach command after --");
161
+ }
162
+ if (sessionId !== undefined && createNew) {
163
+ throw new CliError("--session and --new cannot be used together");
164
+ }
165
+ if (!help && createNew && !cwd) {
166
+ throw new CliError("--new requires --cwd <absolute-path>");
167
+ }
168
+ if (!help && createNew && cwd && !isAbsolute(cwd)) {
169
+ throw new CliError("--new requires --cwd <absolute-path>");
170
+ }
171
+ if (!help && createNew) {
172
+ const kind = executionKind ?? "local";
173
+ if (kind === "cloudflare-sandbox" && (!cloudflareBridgeId || !cloudflareSandboxId)) {
174
+ throw new CliError("--new --execution-kind cloudflare-sandbox requires --cloudflare-bridge and --cloudflare-sandbox");
175
+ }
176
+ if (kind === "fly-sprite" && (!flyApiId || !flySpriteName)) {
177
+ throw new CliError("--new --execution-kind fly-sprite requires --fly-api and --fly-sprite");
178
+ }
179
+ }
180
+ return {
181
+ help,
182
+ sessionId,
183
+ createNew,
184
+ cwd,
185
+ executionKind,
186
+ cloudflareBridgeId,
187
+ cloudflareSandboxId,
188
+ flyApiId,
189
+ flySpriteName,
190
+ target,
191
+ authToken,
192
+ };
193
+ }
194
+ function parseServeArgs(args) {
195
+ let help = false;
196
+ let hostname = "127.0.0.1";
197
+ let port = 8787;
198
+ let authToken;
199
+ const cliArgs = [];
200
+ for (let i = 0; i < args.length; i += 1) {
201
+ const arg = args[i];
202
+ if (arg === "--help" || arg === "-h") {
203
+ help = true;
204
+ continue;
205
+ }
206
+ if (arg === "--host" || arg.startsWith("--host=")) {
207
+ const parsed = parseAttachValue(arg, args, i);
208
+ hostname = parsed.value;
209
+ i = parsed.nextIndex;
210
+ continue;
211
+ }
212
+ if (arg === "--port" || arg.startsWith("--port=")) {
213
+ const parsed = parseAttachValue(arg, args, i);
214
+ port = parsePort(parsed.value);
215
+ i = parsed.nextIndex;
216
+ continue;
217
+ }
218
+ if (arg === "--auth-token" || arg.startsWith("--auth-token=")) {
219
+ const parsed = parseAttachValue(arg, args, i);
220
+ authToken = parsed.value;
221
+ i = parsed.nextIndex;
222
+ continue;
223
+ }
224
+ cliArgs.push(arg);
225
+ }
226
+ return { help, hostname, port, authToken, cliArgs };
227
+ }
228
+ function parseAttachValue(arg, argv, index) {
229
+ const eqIndex = arg.indexOf("=");
230
+ if (eqIndex !== -1) {
231
+ const value = arg.slice(eqIndex + 1);
232
+ if (!value) {
233
+ throw new CliError(`missing value for ${arg.slice(0, eqIndex)}`);
234
+ }
235
+ return { value, nextIndex: index };
236
+ }
237
+ const next = argv[index + 1];
238
+ if (!next || next.startsWith("-")) {
239
+ throw new CliError(`missing value for ${arg}`);
240
+ }
241
+ return { value: next, nextIndex: index + 1 };
242
+ }
243
+ function buildAttachCreateInput(attach) {
244
+ const cwd = attach.cwd;
245
+ if (!cwd) {
246
+ throw new CliError("--new requires --cwd <absolute-path>");
247
+ }
248
+ const kind = attach.executionKind ?? "local";
249
+ switch (kind) {
250
+ case "local":
251
+ return { executionEnvironment: { kind: "local", cwd } };
252
+ case "cloudflare-sandbox":
253
+ if (!attach.cloudflareBridgeId || !attach.cloudflareSandboxId) {
254
+ throw new CliError("--new --execution-kind cloudflare-sandbox requires --cloudflare-bridge and --cloudflare-sandbox");
255
+ }
256
+ return {
257
+ executionEnvironment: {
258
+ kind: "cloudflare-sandbox",
259
+ bridgeId: attach.cloudflareBridgeId,
260
+ sandboxId: attach.cloudflareSandboxId,
261
+ cwd,
262
+ },
263
+ };
264
+ case "fly-sprite":
265
+ if (!attach.flyApiId || !attach.flySpriteName) {
266
+ throw new CliError("--new --execution-kind fly-sprite requires --fly-api and --fly-sprite");
267
+ }
268
+ return {
269
+ executionEnvironment: {
270
+ kind: "fly-sprite",
271
+ apiId: attach.flyApiId,
272
+ spriteName: attach.flySpriteName,
273
+ cwd,
274
+ },
275
+ };
276
+ }
277
+ }
278
+ function printAttachHelp() {
279
+ console.log([
280
+ "tau attach - terminal TUI over a session protocol transport",
281
+ "",
282
+ "usage:",
283
+ " tau attach [--session <id> | --new --cwd <path> [execution options]] [--auth-token <token>] ws://host:port",
284
+ " tau attach [--session <id> | --new --cwd <path> [execution options]] -- <command> [args...]",
285
+ "",
286
+ "options:",
287
+ " --session <id> attach to an existing hosted session.",
288
+ " --new create and attach to a new hosted session.",
289
+ " --cwd <path> absolute cwd for a new session's execution environment.",
290
+ " --execution-kind <kind> local, cloudflare-sandbox, or fly-sprite. default: local.",
291
+ " --cloudflare-bridge <id> configured Cloudflare Sandbox bridge id.",
292
+ " --cloudflare-sandbox <id> already-provisioned Cloudflare sandbox id.",
293
+ " --fly-api <id> configured Fly Sprites API id.",
294
+ " --fly-sprite <name> already-provisioned Fly Sprite name.",
295
+ " --auth-token <token> token for websocket servers started with --auth-token.",
296
+ " --help, -h show this help and exit.",
297
+ "",
298
+ "examples:",
299
+ " tau attach ws://127.0.0.1:8787",
300
+ " tau attach --new --cwd /srv/workspaces/repo ws://127.0.0.1:8787",
301
+ " tau attach --new --execution-kind cloudflare-sandbox --cloudflare-bridge default --cloudflare-sandbox sandbox-1 --cwd /workspace/repo ws://127.0.0.1:8787",
302
+ " tau attach --new --execution-kind fly-sprite --fly-api default --fly-sprite sprite-1 --cwd /home/sprite/repo ws://127.0.0.1:8787",
303
+ " tau attach --session 0195d6e4-4cf9-7f44-a2d8-f8f7f49ee9d3 --auth-token $TAU_WS_AUTH_TOKEN ws://vps:8787",
304
+ " tau attach -- tau rpc --risk read-only",
305
+ " tau attach --new --cwd /repo -- tau rpc --risk read-only",
306
+ " tau attach --session 0195d6e4-4cf9-7f44-a2d8-f8f7f49ee9d3 -- ssh vps 'cd /repo && tau rpc --risk read-only'",
307
+ "",
308
+ "without --session or --new, attach lists hosted sessions and prompts for a selection.",
309
+ "stdio commands and websocket servers both speak Tau's session protocol.",
310
+ ].join("\n"));
311
+ }
312
+ function printServeHelp() {
313
+ console.log([
314
+ "tau serve - host Tau sessions over WebSocket",
315
+ "",
316
+ "usage:",
317
+ " tau serve [--host <host>] [--port <port>] [--auth-token <token>] [options]",
318
+ "",
319
+ "options:",
320
+ " --host <host> bind hostname or address. default: 127.0.0.1",
321
+ " --port <port> bind TCP port. default: 8787",
322
+ " --auth-token <token> require tau attach / SDK websocket clients to provide this token.",
323
+ " --help, -h show this help and exit.",
324
+ "",
325
+ "tau session options such as --risk and --persona are accepted too.",
326
+ "",
327
+ "examples:",
328
+ " tau serve --risk read-only",
329
+ " tau serve --host 0.0.0.0 --port 8787 --auth-token $TAU_WS_AUTH_TOKEN --risk read-only",
330
+ ].join("\n"));
331
+ }
332
+ function isWebSocketUrl(value) {
333
+ return value.startsWith("ws://") || value.startsWith("wss://");
334
+ }
335
+ function parsePort(value) {
336
+ const port = Number(value);
337
+ if (!Number.isInteger(port) || port < 0 || port > 65535) {
338
+ throw new CliError(`invalid port: ${value}`);
339
+ }
340
+ return port;
341
+ }
56
342
  // Load configuration + content from file
57
343
  let config;
58
- let bashCommands = [];
59
344
  async function readPipedStdin() {
60
345
  if (process.stdin.isTTY)
61
346
  return undefined;
@@ -81,34 +366,75 @@ function omitTuiOnlyTools(persona) {
81
366
  tools: persona.tools?.filter((tool) => tool !== TOOL_NAME_DIFF_REVIEW),
82
367
  };
83
368
  }
84
- async function runRpcMode(options) {
369
+ async function resolveHostedSessionBootstrap(options) {
370
+ const runtime = options.runtime;
371
+ if (runtime.personas.length === 0) {
372
+ throw new Error(`no personas available for execution environment cwd '${options.cwd}'. add a custom persona or enable built-ins.`);
373
+ }
374
+ let personaId = options.cli.personaId;
375
+ let reasoningOverride = options.cli.reasoningOverride;
376
+ if (!personaId && runtime.config.defaultPersona) {
377
+ const parsedDefaultPersona = parsePersonaString(runtime.config.defaultPersona, runtime.personas);
378
+ personaId = parsedDefaultPersona.personaId;
379
+ if (reasoningOverride === undefined && parsedDefaultPersona.reasoning !== undefined) {
380
+ reasoningOverride = parsedDefaultPersona.reasoning;
381
+ }
382
+ }
383
+ const personaBase = personaId
384
+ ? runtime.personas.find((persona) => persona.id === personaId)
385
+ : runtime.personas[0];
386
+ if (!personaBase) {
387
+ throw new Error(`persona '${personaId}' is not available for execution environment cwd '${options.cwd}'`);
388
+ }
389
+ const persona = omitTuiOnlyTools(clonePersonaForSession(personaBase));
390
+ if (reasoningOverride !== undefined) {
391
+ persona.settings.reasoning = reasoningOverride;
392
+ }
393
+ return {
394
+ persona,
395
+ riskLevel: options.cli.riskLevel ?? runtime.config.defaultRisk ?? "read-only",
396
+ discoveredSkills: runtime.skills,
397
+ personas: runtime.personas.map(omitTuiOnlyTools),
398
+ prompts: runtime.prompts,
399
+ config: runtime.config,
400
+ };
401
+ }
402
+ function createLocalSessionHost(options) {
85
403
  const deps = createDefaultCoreDeps();
86
404
  const persona = omitTuiOnlyTools(options.persona);
87
- const runtimeCwd = deps.env.cwd();
88
405
  const home = deps.env.home() || process.env.HOME || homedir();
89
- const bootstrap = resolveRuntimePromptBootstrap({
90
- persona,
91
- discoveredSkills: options.skills,
92
- cwd: runtimeCwd,
406
+ const toolBackend = createLocalToolExecutionBackend();
407
+ const localExecutionEnvironmentResolver = new LocalExecutionEnvironmentResolver({
93
408
  home,
94
- includeAgentContext: !options.cli.noAgentContextFiles,
95
409
  readFile: (path) => readFileSync(path, "utf-8"),
410
+ toolBackend,
96
411
  });
97
- if (bootstrap.warnings.length > 0) {
98
- // eslint-disable-next-line no-console
99
- console.error("config warnings:");
100
- for (const warning of bootstrap.warnings) {
101
- // eslint-disable-next-line no-console
102
- console.error(`- ${warning}`);
103
- }
104
- // eslint-disable-next-line no-console
105
- console.error("");
106
- }
107
- const runtime = ChatRuntime.create({
412
+ const executionEnvironmentResolver = new CompositeExecutionEnvironmentResolver({
413
+ local: localExecutionEnvironmentResolver,
414
+ ...(options.config.cloudflareSandbox?.bridges
415
+ ? {
416
+ "cloudflare-sandbox": new CloudflareSandboxExecutionEnvironmentResolver({
417
+ bridges: options.config.cloudflareSandbox.bridges,
418
+ }),
419
+ }
420
+ : {}),
421
+ ...(options.config.flySprites?.apis
422
+ ? {
423
+ "fly-sprite": new FlySpriteExecutionEnvironmentResolver({
424
+ apis: options.config.flySprites.apis,
425
+ }),
426
+ }
427
+ : {}),
428
+ });
429
+ return new LocalSessionHost({
430
+ store: new FileSessionStore({ directory: getDefaultSessionStoreDirectory(home) }),
108
431
  persona,
109
432
  riskLevel: options.riskLevel,
110
- toolRegistry: ToolCatalog.createRegistry(createLocalToolExecutionBackend()),
111
- promptContext: bootstrap.promptContext,
433
+ discoveredSkills: options.skills,
434
+ personas: [persona],
435
+ prompts: [],
436
+ executionEnvironmentResolver,
437
+ includeAgentContext: !options.cli.noAgentContextFiles,
112
438
  environment: {
113
439
  now: () => deps.clock.now(),
114
440
  platform: () => deps.env.platform(),
@@ -116,10 +442,19 @@ async function runRpcMode(options) {
116
442
  },
117
443
  config: options.config,
118
444
  deps,
445
+ resolveSessionBootstrap: async ({ executionEnvironment }) => {
446
+ const snapshot = executionEnvironment.snapshot();
447
+ const runtime = await executionEnvironment.resolveRuntimeConfig();
448
+ return await resolveHostedSessionBootstrap({
449
+ cli: options.cli,
450
+ runtime,
451
+ cwd: snapshot.cwd,
452
+ });
453
+ },
119
454
  });
120
- for (const message of options.history ?? []) {
121
- runtime.session.addMessage(message);
122
- }
455
+ }
456
+ async function runRpcMode(options) {
457
+ const sessionHost = createLocalSessionHost(options);
123
458
  const abortController = new AbortController();
124
459
  const requestShutdown = () => {
125
460
  if (!abortController.signal.aborted) {
@@ -132,7 +467,7 @@ async function runRpcMode(options) {
132
467
  process.on("SIGTERM", onSigTerm);
133
468
  try {
134
469
  await runRpcServer({
135
- runtime,
470
+ host: sessionHost,
136
471
  input: process.stdin,
137
472
  output: process.stdout,
138
473
  signal: abortController.signal,
@@ -244,6 +579,7 @@ if (argv[0] === "async") {
244
579
  cwd,
245
580
  env: process.env,
246
581
  config: asyncConfig,
582
+ createSessionClient: createTauSdkClient,
247
583
  });
248
584
  process.exit(0);
249
585
  }
@@ -305,7 +641,7 @@ if (isDiffToolSubcommand) {
305
641
  catch (err) {
306
642
  if (err instanceof DiffToolLaunchEnvironmentError) {
307
643
  // eslint-disable-next-line no-console
308
- console.error("tau diff-tool must be launched by Tau during /diff.");
644
+ console.error("tau diff-tool must be launched with a Tau diff-review session environment.");
309
645
  // eslint-disable-next-line no-console
310
646
  console.error(err.message);
311
647
  // eslint-disable-next-line no-console
@@ -326,7 +662,6 @@ try {
326
662
  prompts = runtime.prompts;
327
663
  skills = runtime.skills;
328
664
  themes = runtime.themes;
329
- bashCommands = runtime.bashCommands;
330
665
  if (runtime.warnings.length > 0) {
331
666
  // eslint-disable-next-line no-console
332
667
  console.error("config warnings:");
@@ -344,7 +679,6 @@ catch (err) {
344
679
  console.error(`failed to load user content: ${err.message}`);
345
680
  runtimeBootstrap = loadRuntimeBootstrap(cwd, configDeps);
346
681
  config = runtimeBootstrap.config;
347
- bashCommands = config.bashCommands ?? [];
348
682
  const { virtualBundle } = runtimeBootstrap;
349
683
  const hasBuiltins = virtualBundle.personas.length > 0 ||
350
684
  virtualBundle.prompts.length > 0 ||
@@ -359,7 +693,109 @@ catch (err) {
359
693
  skills = virtualBundle.skills;
360
694
  themes = virtualBundle.themes;
361
695
  }
362
- const cliArgv = isRpcSubcommand ? argv.slice(1) : argv;
696
+ if (isAttachSubcommand) {
697
+ let attach;
698
+ try {
699
+ attach = parseAttachArgs(argv.slice(1));
700
+ }
701
+ catch (err) {
702
+ if (err instanceof CliError) {
703
+ // eslint-disable-next-line no-console
704
+ console.error(err.message);
705
+ // eslint-disable-next-line no-console
706
+ console.error("");
707
+ printAttachHelp();
708
+ process.exit(1);
709
+ }
710
+ throw err;
711
+ }
712
+ if (attach.help) {
713
+ printAttachHelp();
714
+ process.exit(0);
715
+ }
716
+ if (!attach.target) {
717
+ throw new Error("missing attach target");
718
+ }
719
+ const terminalAppearance = await detectTerminalAppearance();
720
+ const defaultDiffTool = createBuiltInDiffToolConfig({
721
+ nodeExecutablePath: process.execPath,
722
+ cliEntryPath: fileURLToPath(import.meta.url),
723
+ codeTheme: config.builtInDiffTool?.codeTheme,
724
+ });
725
+ const sessionSelection = attach.sessionId
726
+ ? { mode: "attach", sessionId: attach.sessionId }
727
+ : attach.createNew
728
+ ? {
729
+ mode: "create",
730
+ input: buildAttachCreateInput(attach),
731
+ }
732
+ : { mode: "select" };
733
+ const app = attach.target.transport === "stdio"
734
+ ? await SessionChatApp.connect({
735
+ transport: "stdio",
736
+ command: attach.target.command[0],
737
+ args: attach.target.command.slice(1),
738
+ sessionSelection,
739
+ terminalAppearance,
740
+ themeId: config.defaultTheme,
741
+ themes,
742
+ config,
743
+ defaultDiffTool,
744
+ })
745
+ : await SessionChatApp.connect({
746
+ transport: "websocket",
747
+ url: attach.target.url,
748
+ authToken: attach.authToken ?? process.env.TAU_WS_AUTH_TOKEN,
749
+ sessionSelection,
750
+ terminalAppearance,
751
+ themeId: config.defaultTheme,
752
+ themes,
753
+ config,
754
+ defaultDiffTool,
755
+ });
756
+ let isShuttingDown = false;
757
+ const shutdown = async (code = 0) => {
758
+ if (isShuttingDown)
759
+ return;
760
+ isShuttingDown = true;
761
+ await app.stop();
762
+ process.exit(code);
763
+ };
764
+ process.on("SIGINT", () => void shutdown(0));
765
+ process.on("SIGTERM", () => void shutdown(0));
766
+ try {
767
+ await app.start();
768
+ await new Promise(() => undefined);
769
+ }
770
+ catch (err) {
771
+ await app.stop();
772
+ // eslint-disable-next-line no-console
773
+ console.error(err);
774
+ process.exit(1);
775
+ }
776
+ }
777
+ let serve;
778
+ if (isServeSubcommand) {
779
+ try {
780
+ serve = parseServeArgs(argv.slice(1));
781
+ }
782
+ catch (err) {
783
+ if (err instanceof CliError) {
784
+ // eslint-disable-next-line no-console
785
+ console.error(err.message);
786
+ // eslint-disable-next-line no-console
787
+ console.error("");
788
+ printServeHelp();
789
+ process.exit(1);
790
+ }
791
+ throw err;
792
+ }
793
+ if (serve.help) {
794
+ printServeHelp();
795
+ process.exit(0);
796
+ }
797
+ }
798
+ const cliArgv = isRpcSubcommand ? argv.slice(1) : isServeSubcommand ? serve.cliArgs : argv;
363
799
  let cli;
364
800
  try {
365
801
  cli = parseCliArgs(cliArgv, personas);
@@ -384,43 +820,16 @@ if (isRpcSubcommand && cli.caffeinated) {
384
820
  console.error("--caffeinated is only supported in TUI mode.");
385
821
  process.exit(1);
386
822
  }
387
- let checkpointPersonaId;
388
- let checkpointReasoning;
389
- let checkpointRiskLevel;
390
- let checkpointHistory;
391
- if (cli.loadPath) {
392
- const checkpointPath = resolve(cwd, cli.loadPath);
393
- let checkpoint;
394
- try {
395
- const raw = await readFile(checkpointPath, "utf8");
396
- checkpoint = parseCheckpoint(raw);
397
- }
398
- catch (err) {
399
- // eslint-disable-next-line no-console
400
- console.error(`failed to load checkpoint: ${err.message}`);
401
- process.exit(1);
402
- }
403
- const parsedPersona = parsePersonaString(checkpoint.personaId, personas);
404
- checkpointPersonaId = parsedPersona.personaId;
405
- if (!checkpointPersonaId) {
406
- // eslint-disable-next-line no-console
407
- console.error(`checkpoint persona '${checkpoint.personaId}' not found. falling back to default persona.`);
408
- }
409
- checkpointReasoning = checkpoint.reasoning;
410
- checkpointRiskLevel = checkpoint.riskLevel;
411
- checkpointHistory = checkpoint.history;
823
+ if (isServeSubcommand && cli.caffeinated) {
824
+ // eslint-disable-next-line no-console
825
+ console.error("--caffeinated is only supported in TUI mode.");
826
+ process.exit(1);
412
827
  }
413
828
  let initialPersonaId;
414
829
  let reasoningOverride = cli.reasoningOverride;
415
830
  if (cli.personaId) {
416
831
  initialPersonaId = cli.personaId;
417
832
  }
418
- else if (checkpointPersonaId) {
419
- initialPersonaId = checkpointPersonaId;
420
- if (reasoningOverride === undefined && checkpointReasoning !== undefined) {
421
- reasoningOverride = checkpointReasoning;
422
- }
423
- }
424
833
  else if (config.defaultPersona) {
425
834
  const parsedDefaultPersona = parsePersonaString(config.defaultPersona, personas);
426
835
  initialPersonaId = parsedDefaultPersona.personaId;
@@ -430,7 +839,7 @@ else if (config.defaultPersona) {
430
839
  reasoningOverride = parsedDefaultPersona.reasoning;
431
840
  }
432
841
  }
433
- const initialRiskLevel = cli.riskLevel ?? checkpointRiskLevel ?? config.defaultRisk;
842
+ const initialRiskLevel = cli.riskLevel ?? config.defaultRisk;
434
843
  if (cli.debug) {
435
844
  let debugPersona;
436
845
  if (personas.length > 0) {
@@ -448,7 +857,6 @@ if (cli.debug) {
448
857
  printDebugInfo({
449
858
  personas,
450
859
  prompts,
451
- bashCommands,
452
860
  skills,
453
861
  virtualBundle,
454
862
  selectedPersona: debugPersona,
@@ -471,6 +879,48 @@ if (reasoningOverride !== undefined) {
471
879
  initialPersona.settings.reasoning = reasoningOverride;
472
880
  }
473
881
  const effectiveRiskLevel = initialRiskLevel ?? "read-only";
882
+ if (isServeSubcommand) {
883
+ const sessionHost = createLocalSessionHost({
884
+ cli,
885
+ config,
886
+ persona: initialPersona,
887
+ riskLevel: effectiveRiskLevel,
888
+ skills,
889
+ });
890
+ const abortController = new AbortController();
891
+ const requestShutdown = () => {
892
+ if (!abortController.signal.aborted) {
893
+ abortController.abort();
894
+ }
895
+ };
896
+ const onSigInt = () => requestShutdown();
897
+ const onSigTerm = () => requestShutdown();
898
+ process.on("SIGINT", onSigInt);
899
+ process.on("SIGTERM", onSigTerm);
900
+ try {
901
+ await runWebSocketSessionServer({
902
+ host: sessionHost,
903
+ hostname: serve.hostname,
904
+ port: serve.port,
905
+ authToken: serve.authToken ?? process.env.TAU_WS_AUTH_TOKEN,
906
+ signal: abortController.signal,
907
+ onListening: (address) => {
908
+ // eslint-disable-next-line no-console
909
+ console.error(`tau websocket server listening on ws://${address.hostname}:${address.port}`);
910
+ },
911
+ });
912
+ process.exit(0);
913
+ }
914
+ catch (err) {
915
+ // eslint-disable-next-line no-console
916
+ console.error(err.message);
917
+ process.exit(1);
918
+ }
919
+ finally {
920
+ process.off("SIGINT", onSigInt);
921
+ process.off("SIGTERM", onSigTerm);
922
+ }
923
+ }
474
924
  if (isRpcSubcommand) {
475
925
  try {
476
926
  await runRpcMode({
@@ -479,7 +929,6 @@ if (isRpcSubcommand) {
479
929
  persona: initialPersona,
480
930
  riskLevel: effectiveRiskLevel,
481
931
  skills,
482
- history: checkpointHistory,
483
932
  });
484
933
  process.exit(0);
485
934
  }
@@ -496,19 +945,30 @@ const defaultDiffTool = createBuiltInDiffToolConfig({
496
945
  cliEntryPath: fileURLToPath(import.meta.url),
497
946
  codeTheme: config.builtInDiffTool?.codeTheme,
498
947
  });
499
- const app = new ChatApp({
500
- personas,
501
- prompts,
502
- skills,
948
+ const sessionClient = await createTauSdkClient({
949
+ cwd,
950
+ persona: initialPersonaId,
951
+ reasoning: reasoningOverride,
952
+ riskLevel: effectiveRiskLevel,
953
+ noAgentContextFiles: cli.noAgentContextFiles,
954
+ initialize: { client: { name: "tau-tui", version: "1" } },
955
+ });
956
+ const app = await SessionChatApp.open({
957
+ client: sessionClient,
958
+ targetLabel: "in-process",
959
+ sessionSelection: {
960
+ mode: "create",
961
+ input: {
962
+ executionEnvironment: { kind: "local", cwd },
963
+ ...(initialPersonaId !== undefined ? { personaId: initialPersonaId } : {}),
964
+ riskLevel: effectiveRiskLevel,
965
+ ...(reasoningOverride !== undefined ? { reasoning: reasoningOverride } : {}),
966
+ },
967
+ },
503
968
  themes,
504
- bashCommands,
505
969
  terminalAppearance,
506
- initialPersonaId,
507
- initialReasoningOverride: reasoningOverride,
970
+ themeId: config.defaultTheme,
508
971
  initialUserMessage,
509
- initialRiskLevel: effectiveRiskLevel,
510
- initialHistory: checkpointHistory,
511
- noAgentContextFiles: cli.noAgentContextFiles,
512
972
  config,
513
973
  defaultDiffTool,
514
974
  caffeinated: cli.caffeinated,