@ebowwa/coder 0.7.64 → 0.7.65

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 (101) hide show
  1. package/dist/index.js +36168 -32
  2. package/dist/interfaces/ui/terminal/cli/index.js +34253 -158
  3. package/dist/interfaces/ui/terminal/native/README.md +53 -0
  4. package/dist/interfaces/ui/terminal/native/claude_code_native.darwin-x64.node +0 -0
  5. package/dist/interfaces/ui/terminal/native/claude_code_native.dylib +0 -0
  6. package/dist/interfaces/ui/terminal/native/index.d.ts +0 -0
  7. package/dist/interfaces/ui/terminal/native/index.darwin-arm64.node +0 -0
  8. package/dist/interfaces/ui/terminal/native/index.js +43 -0
  9. package/dist/interfaces/ui/terminal/native/index.node +0 -0
  10. package/dist/interfaces/ui/terminal/native/package.json +34 -0
  11. package/dist/native/README.md +53 -0
  12. package/dist/native/claude_code_native.darwin-x64.node +0 -0
  13. package/dist/native/claude_code_native.dylib +0 -0
  14. package/dist/native/index.d.ts +0 -480
  15. package/dist/native/index.darwin-arm64.node +0 -0
  16. package/dist/native/index.js +43 -1625
  17. package/dist/native/index.node +0 -0
  18. package/dist/native/package.json +34 -0
  19. package/native/index.darwin-arm64.node +0 -0
  20. package/native/index.js +33 -19
  21. package/package.json +3 -2
  22. package/packages/src/core/agent-loop/__tests__/compaction.test.ts +17 -14
  23. package/packages/src/core/agent-loop/compaction.ts +6 -2
  24. package/packages/src/core/agent-loop/index.ts +2 -0
  25. package/packages/src/core/agent-loop/loop-state.ts +1 -1
  26. package/packages/src/core/agent-loop/turn-executor.ts +4 -0
  27. package/packages/src/core/agent-loop/types.ts +4 -0
  28. package/packages/src/core/api-client-impl.ts +283 -173
  29. package/packages/src/core/cognitive-security/hooks.ts +2 -1
  30. package/packages/src/core/config/todo +7 -0
  31. package/packages/src/core/context/__tests__/integration.test.ts +334 -0
  32. package/packages/src/core/context/compaction.ts +170 -0
  33. package/packages/src/core/context/constants.ts +58 -0
  34. package/packages/src/core/context/extraction.ts +85 -0
  35. package/packages/src/core/context/index.ts +66 -0
  36. package/packages/src/core/context/summarization.ts +251 -0
  37. package/packages/src/core/context/token-estimation.ts +98 -0
  38. package/packages/src/core/context/types.ts +59 -0
  39. package/packages/src/core/models.ts +81 -4
  40. package/packages/src/core/normalizers/todo +5 -1
  41. package/packages/src/core/providers/README.md +230 -0
  42. package/packages/src/core/providers/__tests__/providers.test.ts +135 -0
  43. package/packages/src/core/providers/index.ts +419 -0
  44. package/packages/src/core/providers/types.ts +132 -0
  45. package/packages/src/core/retry.ts +10 -0
  46. package/packages/src/ecosystem/tools/index.ts +174 -0
  47. package/packages/src/index.ts +23 -2
  48. package/packages/src/interfaces/ui/index.ts +17 -20
  49. package/packages/src/interfaces/ui/spinner.ts +2 -2
  50. package/packages/src/interfaces/ui/terminal/bridge/index.ts +370 -0
  51. package/packages/src/interfaces/ui/terminal/bridge/ipc.ts +829 -0
  52. package/packages/src/interfaces/ui/terminal/bridge/screen-export.ts +968 -0
  53. package/packages/src/interfaces/ui/terminal/bridge/types.ts +226 -0
  54. package/packages/src/interfaces/ui/terminal/bridge/useBridge.ts +210 -0
  55. package/packages/src/interfaces/ui/terminal/cli/bootstrap.ts +132 -0
  56. package/packages/src/interfaces/ui/terminal/cli/index.ts +200 -13
  57. package/packages/src/interfaces/ui/terminal/cli/interactive/index.ts +110 -0
  58. package/packages/src/interfaces/ui/terminal/cli/interactive/input-handler.ts +393 -0
  59. package/packages/src/interfaces/ui/terminal/cli/interactive/interactive-runner.ts +820 -0
  60. package/packages/src/interfaces/ui/terminal/cli/interactive/message-store.ts +299 -0
  61. package/packages/src/interfaces/ui/terminal/cli/interactive/types.ts +274 -0
  62. package/packages/src/interfaces/ui/terminal/shared/index.ts +13 -0
  63. package/packages/src/interfaces/ui/terminal/shared/query.ts +9 -3
  64. package/packages/src/interfaces/ui/terminal/shared/setup.ts +5 -1
  65. package/packages/src/interfaces/ui/terminal/shared/spinner-frames.ts +73 -0
  66. package/packages/src/interfaces/ui/terminal/shared/status-line.ts +10 -2
  67. package/packages/src/native/index.ts +404 -27
  68. package/packages/src/native/tui_v2_types.ts +39 -0
  69. package/packages/src/teammates/coordination.test.ts +279 -0
  70. package/packages/src/teammates/coordination.ts +646 -0
  71. package/packages/src/teammates/index.ts +95 -25
  72. package/packages/src/teammates/integration.test.ts +272 -0
  73. package/packages/src/teammates/runner.test.ts +235 -0
  74. package/packages/src/teammates/runner.ts +750 -0
  75. package/packages/src/teammates/schemas.ts +673 -0
  76. package/packages/src/types/index.ts +1 -0
  77. package/packages/src/core/context-compaction.ts +0 -578
  78. package/packages/src/interfaces/ui/Screenshot 2026-03-02 at 9.23.10/342/200/257PM.png +0 -0
  79. package/packages/src/interfaces/ui/Screenshot 2026-03-03 at 10.55.11/342/200/257AM.png +0 -0
  80. package/packages/src/interfaces/ui/terminal/tui/HelpPanel.tsx +0 -262
  81. package/packages/src/interfaces/ui/terminal/tui/InputContext.tsx +0 -232
  82. package/packages/src/interfaces/ui/terminal/tui/InputField.tsx +0 -62
  83. package/packages/src/interfaces/ui/terminal/tui/InteractiveTUI.tsx +0 -537
  84. package/packages/src/interfaces/ui/terminal/tui/MessageArea.tsx +0 -107
  85. package/packages/src/interfaces/ui/terminal/tui/MessageStore.tsx +0 -240
  86. package/packages/src/interfaces/ui/terminal/tui/StatusBar.tsx +0 -54
  87. package/packages/src/interfaces/ui/terminal/tui/commands.ts +0 -438
  88. package/packages/src/interfaces/ui/terminal/tui/components/InteractiveElements.tsx +0 -584
  89. package/packages/src/interfaces/ui/terminal/tui/components/MultilineInput.tsx +0 -614
  90. package/packages/src/interfaces/ui/terminal/tui/components/PaneManager.tsx +0 -333
  91. package/packages/src/interfaces/ui/terminal/tui/components/Sidebar.tsx +0 -604
  92. package/packages/src/interfaces/ui/terminal/tui/components/index.ts +0 -118
  93. package/packages/src/interfaces/ui/terminal/tui/console.ts +0 -49
  94. package/packages/src/interfaces/ui/terminal/tui/index.ts +0 -90
  95. package/packages/src/interfaces/ui/terminal/tui/run.tsx +0 -42
  96. package/packages/src/interfaces/ui/terminal/tui/spinner.ts +0 -69
  97. package/packages/src/interfaces/ui/terminal/tui/tui-app.tsx +0 -390
  98. package/packages/src/interfaces/ui/terminal/tui/tui-footer.ts +0 -422
  99. package/packages/src/interfaces/ui/terminal/tui/types.ts +0 -186
  100. package/packages/src/interfaces/ui/terminal/tui/useInputHandler.ts +0 -104
  101. package/packages/src/interfaces/ui/terminal/tui/useNativeInput.ts +0 -239
@@ -6,19 +6,130 @@
6
6
  * This is a thin orchestrator that:
7
7
  * 1. Parses arguments
8
8
  * 2. Sets up session (config, MCP, hooks)
9
- * 3. Dispatches to TUI or single-query mode
9
+ * 3. Initializes teammate mode if enabled
10
+ * 4. Dispatches to TUI or single-query mode
10
11
  */
11
12
 
13
+ // ============================================
14
+ // PTY WRAPPER DETECTION (must run before any other code)
15
+ // ============================================
16
+
17
+ import { spawn, spawnSync } from "node:child_process";
18
+ import process from "node:process";
19
+
20
+ /**
21
+ * Check if we're running under doppler without a TTY and need a PTY wrapper
22
+ * This must run before any other code to properly handle the TTY requirement
23
+ *
24
+ * When successful wrapping occurs, this function NEVER returns - it exits the process
25
+ * when the wrapped child exits.
26
+ */
27
+ function checkPtyWrapper(): void {
28
+ const isTTY = process.stdin.isTTY;
29
+ const isDoppler = !!process.env.DOPPLER_TOKEN;
30
+ const forceInteractive = process.env.CLAUDE_FORCE_INTERACTIVE === "true";
31
+ const hasQuery = process.argv.includes("-q") || process.argv.includes("--query");
32
+ const hasVersion = process.argv.includes("--version") || process.argv.includes("-v");
33
+ const hasHelp = process.argv.includes("--help") || process.argv.includes("-h");
34
+ const alreadyWrapped = process.env.CODER_PTY_WRAPPED === "1";
35
+
36
+ // Skip if we have a TTY, force interactive, have a query, version, help, or already wrapped
37
+ if (isTTY || forceInteractive || hasQuery || hasVersion || hasHelp || alreadyWrapped) {
38
+ return;
39
+ }
40
+
41
+ // If under doppler without TTY, try to wrap with unbuffer
42
+ if (isDoppler) {
43
+ const result = tryWrapWithUnbuffer();
44
+ if (result.wrapped && result.child) {
45
+ // Successfully wrapped - child is running
46
+ // Wait for child to exit, then exit parent with same code
47
+ result.child.on("exit", (code: number | null) => {
48
+ process.exit(code ?? 1);
49
+ });
50
+ // Prevent further execution by keeping parent alive
51
+ // The exit handler above will terminate this process
52
+ setInterval(() => {
53
+ /* keep alive */
54
+ }, 10000);
55
+ return; // TypeScript needs this
56
+ }
57
+
58
+ // Failed to wrap - show helpful error
59
+ console.error("Error: Interactive mode requires a TTY.");
60
+ console.error("");
61
+ console.error("When using 'doppler run', TTY is not passed through.");
62
+ console.error("");
63
+ console.error("Install 'expect' package for automatic PTY support:");
64
+ console.error(" macOS: brew install expect");
65
+ console.error(" Ubuntu: sudo apt install expect");
66
+ console.error(" Fedora: sudo dnf install expect");
67
+ console.error("");
68
+ console.error("Or use one of these alternatives:");
69
+ console.error(" 1. Single query: doppler run -- coder -q \"your question\"");
70
+ console.error(" 2. Export secrets: doppler secrets download --no-file && coder");
71
+ console.error(" 3. Force simple: CLAUDE_FORCE_INTERACTIVE=true doppler run -- coder");
72
+ console.error(" 4. With unbuffer: unbuffer doppler run -- coder");
73
+ process.exit(1);
74
+ }
75
+ }
76
+
77
+ /**
78
+ * Try to wrap execution with unbuffer to provide a PTY
79
+ * @returns Object with wrapped status and child process if successful
80
+ */
81
+ function tryWrapWithUnbuffer(): { wrapped: boolean; child?: ReturnType<typeof spawn> } {
82
+ try {
83
+ // Check if unbuffer is available
84
+ const checkResult = spawnSync("which", ["unbuffer"], {
85
+ stdio: "ignore",
86
+ timeout: 1000,
87
+ });
88
+
89
+ if (checkResult.status !== 0) {
90
+ return { wrapped: false };
91
+ }
92
+
93
+ // Re-exec ourselves with unbuffer
94
+ const args = [process.execPath, ...process.argv.slice(1)];
95
+ const child = spawn("unbuffer", args, {
96
+ stdio: "inherit",
97
+ env: {
98
+ ...process.env,
99
+ CODER_PTY_WRAPPED: "1",
100
+ },
101
+ });
102
+
103
+ return { wrapped: true, child };
104
+ } catch {
105
+ return { wrapped: false };
106
+ }
107
+ }
108
+
109
+ // Run PTY check before any other code
110
+ checkPtyWrapper();
111
+
112
+ // ============================================
113
+ // MAIN IMPORTS
114
+ // ============================================
115
+
12
116
  import type { Message } from "../../../../types/index.js";
13
117
  import type { ToolDefinition, PermissionMode } from "../../../../types/index.js";
14
118
  import type { builtInTools } from "../../../../ecosystem/tools/index.js";
15
119
  import type { HookManager } from "../../../../ecosystem/hooks/index.js";
16
120
  import type { SkillManager } from "../../../../ecosystem/skills/index.js";
17
121
  import type { MCPClientImpl } from "../../../mcp/client.js";
122
+ import type { TeammateMessage } from "../../../../types/index.js";
18
123
  import { SessionStore, printSessionsList } from "../../../../core/session-store.js";
19
124
  import { getGitStatus } from "../../../../core/git-status.js";
20
125
  import { renderStatusLine, getContextWindow, VERSION, type StatusLineOptions } from "../shared/status-line.js";
21
- import { runInteractiveTUI } from "../tui/index.js";
126
+ import { InteractiveRunner } from "./interactive/index.js";
127
+ import type { InteractiveRunnerProps } from "./interactive/types.js";
128
+ import {
129
+ TeammateModeRunner,
130
+ setTeammateRunner,
131
+ isTeammateModeActive,
132
+ } from "../../../../teammates/runner.js";
22
133
 
23
134
  // Shared modules
24
135
  import {
@@ -59,11 +170,15 @@ async function main(): Promise<void> {
59
170
  // Get API key
60
171
  const apiKey = requireApiKey();
61
172
 
173
+ // Determine if we'll be in interactive TUI mode (no query = interactive)
174
+ const isInteractiveMode = !args.query;
175
+
62
176
  // Setup session (config, MCP, hooks, skills)
63
177
  const setup = await setupSession({
64
178
  args,
65
179
  apiKey,
66
180
  workingDirectory: process.cwd(),
181
+ isTuiMode: isInteractiveMode, // Disable console logging in TUI mode
67
182
  });
68
183
 
69
184
  // Get git status for system prompt
@@ -119,6 +234,64 @@ async function main(): Promise<void> {
119
234
  query = process.argv.slice(2).join(" ");
120
235
  }
121
236
 
237
+ // ============================================
238
+ // TEAMMATE MODE INITIALIZATION
239
+ // ============================================
240
+
241
+ let teammateRunner: TeammateModeRunner | null = null;
242
+ let pendingTeammateMessages: TeammateMessage[] = [];
243
+
244
+ if (args.teammateMode && args.teamName) {
245
+ try {
246
+ teammateRunner = new TeammateModeRunner({
247
+ teamName: args.teamName,
248
+ agentId: args.agentId,
249
+ agentName: args.agentName,
250
+ agentColor: args.agentColor,
251
+ prompt: args.systemPrompt,
252
+ workingDirectory: process.cwd(),
253
+ onMessage: (message: TeammateMessage) => {
254
+ // Queue message for injection into conversation
255
+ pendingTeammateMessages.push(message);
256
+ console.log(`\x1b[90m[Teammate] Message from ${message.from}: ${message.content.slice(0, 50)}...\x1b[0m`);
257
+ },
258
+ onIdle: () => {
259
+ console.log(`\x1b[90m[Teammate] Now idle, waiting for tasks...\x1b[0m`);
260
+ },
261
+ pollInterval: 2000,
262
+ });
263
+
264
+ const teammate = await teammateRunner.start();
265
+
266
+ // Set global reference for tools to access
267
+ setTeammateRunner(teammateRunner);
268
+
269
+ console.log(`\x1b[90m[Teammate] Joined team "${args.teamName}" as ${teammate.name} (${teammate.teammateId})\x1b[0m`);
270
+ } catch (error) {
271
+ console.error(`\x1b[31mError starting teammate mode: ${error}\x1b[0m`);
272
+ teammateRunner = null;
273
+ }
274
+ }
275
+
276
+ // Cleanup handler
277
+ const cleanup = async () => {
278
+ if (teammateRunner) {
279
+ console.log(`\x1b[90m[Teammate] Stopping teammate mode...\x1b[0m`);
280
+ await teammateRunner.stop();
281
+ setTeammateRunner(null);
282
+ }
283
+ };
284
+
285
+ process.on("SIGINT", async () => {
286
+ await cleanup();
287
+ process.exit(0);
288
+ });
289
+
290
+ process.on("SIGTERM", async () => {
291
+ await cleanup();
292
+ process.exit(0);
293
+ });
294
+
122
295
  if (!query) {
123
296
  // Show initial status line
124
297
  const initialStatusOptions: StatusLineOptions = {
@@ -130,6 +303,9 @@ async function main(): Promise<void> {
130
303
  };
131
304
  console.log(`\x1b[90m${renderStatusLine(initialStatusOptions)}\x1b[0m`);
132
305
  console.log(`\x1b[90mSession: ${sessionId}\x1b[0m`);
306
+ if (teammateRunner) {
307
+ console.log(`\x1b[90mTeammate Mode: Active | Team: ${args.teamName}\x1b[0m`);
308
+ }
133
309
  console.log("\x1b[90mType your message, ? for help, or /help for commands.\x1b[0m\n");
134
310
 
135
311
  // Interactive mode
@@ -144,7 +320,8 @@ async function main(): Promise<void> {
144
320
  messages,
145
321
  sessionId,
146
322
  setup.mcpClients,
147
- setup.permissionMode
323
+ setup.permissionMode,
324
+ teammateRunner
148
325
  );
149
326
  } else {
150
327
  // Single query mode
@@ -159,6 +336,7 @@ async function main(): Promise<void> {
159
336
  hookManager: setup.hookManager,
160
337
  workingDirectory: process.cwd(),
161
338
  gitStatus,
339
+ permissionMode: setup.permissionMode,
162
340
  });
163
341
  }
164
342
  }
@@ -178,28 +356,29 @@ async function runInteractiveMode(
178
356
  messages: Message[],
179
357
  sessionId: string,
180
358
  mcpClients: Map<string, MCPClientImpl>,
181
- permissionMode: PermissionMode
359
+ permissionMode: PermissionMode,
360
+ teammateRunner: TeammateModeRunner | null = null
182
361
  ): Promise<void> {
183
- // Check if stdin is a TTY (interactive terminal)
362
+ // Note: PTY wrapper check already ran at module load time
363
+ // This check is for non-doppler environments without TTY
184
364
  const isInteractive = process.stdin.isTTY;
185
-
186
- // Allow force-interactive mode for testing
187
365
  const forceInteractive = process.env.CLAUDE_FORCE_INTERACTIVE === "true";
188
366
 
189
367
  if (!isInteractive && !forceInteractive) {
190
- console.error("Error: Interactive mode requires a TTY. Use -q for single query mode.");
191
- console.error(" Or set CLAUDE_FORCE_INTERACTIVE=true for testing.");
368
+ console.error("Error: Interactive mode requires a TTY.");
369
+ console.error("");
370
+ console.error("Use -q for single query mode, or set CLAUDE_FORCE_INTERACTIVE=true.");
192
371
  return;
193
372
  }
194
373
 
195
- // Create a mutable session ID holder for the TUI
374
+ // Create a mutable session ID holder for the interactive runner
196
375
  let currentSessionId = sessionId;
197
376
  const setSessionId = (newId: string) => {
198
377
  currentSessionId = newId;
199
378
  };
200
379
 
201
- // Run the Ink-based React TUI
202
- await runInteractiveTUI({
380
+ // Run the non-React interactive CLI
381
+ const runner = new InteractiveRunner({
203
382
  apiKey,
204
383
  model: args.model,
205
384
  permissionMode,
@@ -212,10 +391,18 @@ async function runInteractiveMode(
212
391
  setSessionId,
213
392
  initialMessages: messages,
214
393
  workingDirectory: process.cwd(),
215
- onExit: () => {
394
+ teammateRunner,
395
+ onExit: async () => {
396
+ // Cleanup teammate mode
397
+ if (teammateRunner) {
398
+ await teammateRunner.stop();
399
+ setTeammateRunner(null);
400
+ }
216
401
  console.log("\n\x1b[90mGoodbye!\x1b[0m");
217
402
  },
218
403
  });
404
+
405
+ await runner.start();
219
406
  }
220
407
 
221
408
  // ============================================
@@ -0,0 +1,110 @@
1
+ /**
2
+ * CLI Interactive Module
3
+ *
4
+ * Non-React implementation of the interactive CLI mode.
5
+ * Extracts core patterns from v1 TUI without the Ink dependency.
6
+ *
7
+ * This module provides:
8
+ * - MessageStore: Centralized message state management
9
+ * - InputManager: Priority-based keyboard input handling
10
+ * - InteractiveRunner: Main interactive loop with agent integration
11
+ * - Type definitions for all components
12
+ *
13
+ * Usage:
14
+ * ```ts
15
+ * import { InteractiveRunner, MessageStoreImpl, InputManagerImpl } from "./interactive/index.js";
16
+ *
17
+ * // Create and run interactive mode
18
+ * const runner = new InteractiveRunner({
19
+ * apiKey: process.env.API_KEY!,
20
+ * model: "claude-sonnet-4-6",
21
+ * // ... other props
22
+ * });
23
+ *
24
+ * await runner.start();
25
+ * ```
26
+ *
27
+ * Architecture:
28
+ * - MessageStore: Single source of truth for messages (non-React)
29
+ * - InputManager: Centralized keyboard input with priority system
30
+ * - InteractiveRunner: Main loop that orchestrates everything
31
+ *
32
+ * Patterns from v1 TUI:
33
+ * - MessageStore pattern: addMessage, addApiMessages, addSystem, clear, replace
34
+ * - InputContext pattern: register, focus, dispatch, blocked state
35
+ * - Clean separation of concerns
36
+ * - Well-defined TypeScript interfaces
37
+ */
38
+
39
+ // ============================================
40
+ // TYPES
41
+ // ============================================
42
+
43
+ export type {
44
+ // Message types
45
+ UIMessage,
46
+ MessageSubType,
47
+ MessageStore,
48
+
49
+ // Input types
50
+ InputManager,
51
+ InputHandler,
52
+ InputHandlerOptions,
53
+ NativeKeyEvent,
54
+
55
+ // Runner types
56
+ InteractiveRunnerProps,
57
+ InteractiveState,
58
+ SessionStore,
59
+ SessionInfo,
60
+ CommandContext,
61
+
62
+ // Context types
63
+ ContextInfo,
64
+ } from "./types.js";
65
+
66
+ export { InputPriority } from "./types.js";
67
+
68
+ // ============================================
69
+ // MESSAGE STORE
70
+ // ============================================
71
+
72
+ export {
73
+ MessageStoreImpl,
74
+ getMessageStore,
75
+ resetMessageStore,
76
+ } from "./message-store.js";
77
+
78
+ // ============================================
79
+ // INPUT HANDLER
80
+ // ============================================
81
+
82
+ export {
83
+ InputManagerImpl,
84
+ getInputManager,
85
+ resetInputManager,
86
+ KeyEvents,
87
+ } from "./input-handler.js";
88
+
89
+ // ============================================
90
+ // INTERACTIVE RUNNER
91
+ // ============================================
92
+
93
+ import { InteractiveRunner as _InteractiveRunner } from "./interactive-runner.js";
94
+
95
+ export { _InteractiveRunner as InteractiveRunner };
96
+
97
+ // ============================================
98
+ // CONVENIENCE FUNCTIONS
99
+ // ============================================
100
+
101
+ /**
102
+ * Run interactive mode with the given props
103
+ * Convenience function that creates and starts an InteractiveRunner
104
+ */
105
+ export async function runInteractiveMode(
106
+ props: import("./types.js").InteractiveRunnerProps
107
+ ): Promise<void> {
108
+ const runner = new _InteractiveRunner(props);
109
+ await runner.start();
110
+ }