@herdctl/core 1.3.0 → 2.0.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 (181) hide show
  1. package/dist/config/__tests__/agent.test.js +12 -12
  2. package/dist/config/__tests__/agent.test.js.map +1 -1
  3. package/dist/config/__tests__/loader.test.js +201 -4
  4. package/dist/config/__tests__/loader.test.js.map +1 -1
  5. package/dist/config/__tests__/merge.test.js +29 -4
  6. package/dist/config/__tests__/merge.test.js.map +1 -1
  7. package/dist/config/__tests__/parser.test.js +13 -13
  8. package/dist/config/__tests__/parser.test.js.map +1 -1
  9. package/dist/config/__tests__/schema.test.js +10 -10
  10. package/dist/config/__tests__/schema.test.js.map +1 -1
  11. package/dist/config/index.d.ts +1 -1
  12. package/dist/config/index.d.ts.map +1 -1
  13. package/dist/config/index.js +2 -2
  14. package/dist/config/index.js.map +1 -1
  15. package/dist/config/loader.d.ts.map +1 -1
  16. package/dist/config/loader.js +71 -0
  17. package/dist/config/loader.js.map +1 -1
  18. package/dist/config/merge.d.ts +4 -1
  19. package/dist/config/merge.d.ts.map +1 -1
  20. package/dist/config/merge.js +16 -0
  21. package/dist/config/merge.js.map +1 -1
  22. package/dist/config/schema.d.ts +906 -89
  23. package/dist/config/schema.d.ts.map +1 -1
  24. package/dist/config/schema.js +109 -7
  25. package/dist/config/schema.js.map +1 -1
  26. package/dist/fleet-manager/__tests__/coverage.test.js +25 -24
  27. package/dist/fleet-manager/__tests__/coverage.test.js.map +1 -1
  28. package/dist/fleet-manager/__tests__/discord-manager.test.js +9 -2
  29. package/dist/fleet-manager/__tests__/discord-manager.test.js.map +1 -1
  30. package/dist/fleet-manager/__tests__/integration.test.js +27 -0
  31. package/dist/fleet-manager/__tests__/integration.test.js.map +1 -1
  32. package/dist/fleet-manager/__tests__/job-control.test.js +66 -0
  33. package/dist/fleet-manager/__tests__/job-control.test.js.map +1 -1
  34. package/dist/fleet-manager/__tests__/status-queries.test.js +12 -11
  35. package/dist/fleet-manager/__tests__/status-queries.test.js.map +1 -1
  36. package/dist/fleet-manager/config-reload.js +9 -9
  37. package/dist/fleet-manager/config-reload.js.map +1 -1
  38. package/dist/fleet-manager/discord-manager.d.ts.map +1 -1
  39. package/dist/fleet-manager/discord-manager.js +27 -4
  40. package/dist/fleet-manager/discord-manager.js.map +1 -1
  41. package/dist/fleet-manager/fleet-manager.d.ts +11 -0
  42. package/dist/fleet-manager/fleet-manager.d.ts.map +1 -1
  43. package/dist/fleet-manager/fleet-manager.js +27 -0
  44. package/dist/fleet-manager/fleet-manager.js.map +1 -1
  45. package/dist/fleet-manager/job-control.d.ts +1 -1
  46. package/dist/fleet-manager/job-control.d.ts.map +1 -1
  47. package/dist/fleet-manager/job-control.js +36 -14
  48. package/dist/fleet-manager/job-control.js.map +1 -1
  49. package/dist/fleet-manager/schedule-executor.d.ts +1 -1
  50. package/dist/fleet-manager/schedule-executor.d.ts.map +1 -1
  51. package/dist/fleet-manager/schedule-executor.js +11 -14
  52. package/dist/fleet-manager/schedule-executor.js.map +1 -1
  53. package/dist/fleet-manager/status-queries.js +7 -7
  54. package/dist/fleet-manager/status-queries.js.map +1 -1
  55. package/dist/fleet-manager/types.d.ts +10 -2
  56. package/dist/fleet-manager/types.d.ts.map +1 -1
  57. package/dist/fleet-manager/working-directory-helper.d.ts +29 -0
  58. package/dist/fleet-manager/working-directory-helper.d.ts.map +1 -0
  59. package/dist/fleet-manager/working-directory-helper.js +36 -0
  60. package/dist/fleet-manager/working-directory-helper.js.map +1 -0
  61. package/dist/index.d.ts +2 -1
  62. package/dist/index.d.ts.map +1 -1
  63. package/dist/index.js +4 -1
  64. package/dist/index.js.map +1 -1
  65. package/dist/runner/__tests__/job-executor.test.js +449 -118
  66. package/dist/runner/__tests__/job-executor.test.js.map +1 -1
  67. package/dist/runner/__tests__/sdk-adapter.test.js +147 -23
  68. package/dist/runner/__tests__/sdk-adapter.test.js.map +1 -1
  69. package/dist/runner/index.d.ts +2 -0
  70. package/dist/runner/index.d.ts.map +1 -1
  71. package/dist/runner/index.js +1 -0
  72. package/dist/runner/index.js.map +1 -1
  73. package/dist/runner/job-executor.d.ts +12 -8
  74. package/dist/runner/job-executor.d.ts.map +1 -1
  75. package/dist/runner/job-executor.js +257 -126
  76. package/dist/runner/job-executor.js.map +1 -1
  77. package/dist/runner/runtime/__tests__/cli-session-path.test.d.ts +2 -0
  78. package/dist/runner/runtime/__tests__/cli-session-path.test.d.ts.map +1 -0
  79. package/dist/runner/runtime/__tests__/cli-session-path.test.js +150 -0
  80. package/dist/runner/runtime/__tests__/cli-session-path.test.js.map +1 -0
  81. package/dist/runner/runtime/__tests__/docker-config.test.d.ts +2 -0
  82. package/dist/runner/runtime/__tests__/docker-config.test.d.ts.map +1 -0
  83. package/dist/runner/runtime/__tests__/docker-config.test.js +352 -0
  84. package/dist/runner/runtime/__tests__/docker-config.test.js.map +1 -0
  85. package/dist/runner/runtime/__tests__/docker-security.test.d.ts +2 -0
  86. package/dist/runner/runtime/__tests__/docker-security.test.d.ts.map +1 -0
  87. package/dist/runner/runtime/__tests__/docker-security.test.js +384 -0
  88. package/dist/runner/runtime/__tests__/docker-security.test.js.map +1 -0
  89. package/dist/runner/runtime/__tests__/factory.test.d.ts +2 -0
  90. package/dist/runner/runtime/__tests__/factory.test.d.ts.map +1 -0
  91. package/dist/runner/runtime/__tests__/factory.test.js +149 -0
  92. package/dist/runner/runtime/__tests__/factory.test.js.map +1 -0
  93. package/dist/runner/runtime/__tests__/integration.test.d.ts +2 -0
  94. package/dist/runner/runtime/__tests__/integration.test.d.ts.map +1 -0
  95. package/dist/runner/runtime/__tests__/integration.test.js +274 -0
  96. package/dist/runner/runtime/__tests__/integration.test.js.map +1 -0
  97. package/dist/runner/runtime/cli-runtime.d.ts +107 -0
  98. package/dist/runner/runtime/cli-runtime.d.ts.map +1 -0
  99. package/dist/runner/runtime/cli-runtime.js +335 -0
  100. package/dist/runner/runtime/cli-runtime.js.map +1 -0
  101. package/dist/runner/runtime/cli-session-path.d.ts +108 -0
  102. package/dist/runner/runtime/cli-session-path.d.ts.map +1 -0
  103. package/dist/runner/runtime/cli-session-path.js +173 -0
  104. package/dist/runner/runtime/cli-session-path.js.map +1 -0
  105. package/dist/runner/runtime/cli-session-watcher.d.ts +55 -0
  106. package/dist/runner/runtime/cli-session-watcher.d.ts.map +1 -0
  107. package/dist/runner/runtime/cli-session-watcher.js +187 -0
  108. package/dist/runner/runtime/cli-session-watcher.js.map +1 -0
  109. package/dist/runner/runtime/container-manager.d.ts +76 -0
  110. package/dist/runner/runtime/container-manager.d.ts.map +1 -0
  111. package/dist/runner/runtime/container-manager.js +229 -0
  112. package/dist/runner/runtime/container-manager.js.map +1 -0
  113. package/dist/runner/runtime/container-runner.d.ts +62 -0
  114. package/dist/runner/runtime/container-runner.d.ts.map +1 -0
  115. package/dist/runner/runtime/container-runner.js +235 -0
  116. package/dist/runner/runtime/container-runner.js.map +1 -0
  117. package/dist/runner/runtime/docker-config.d.ts +100 -0
  118. package/dist/runner/runtime/docker-config.d.ts.map +1 -0
  119. package/dist/runner/runtime/docker-config.js +98 -0
  120. package/dist/runner/runtime/docker-config.js.map +1 -0
  121. package/dist/runner/runtime/factory.d.ts +63 -0
  122. package/dist/runner/runtime/factory.d.ts.map +1 -0
  123. package/dist/runner/runtime/factory.js +68 -0
  124. package/dist/runner/runtime/factory.js.map +1 -0
  125. package/dist/runner/runtime/index.d.ts +20 -0
  126. package/dist/runner/runtime/index.d.ts.map +1 -0
  127. package/dist/runner/runtime/index.js +21 -0
  128. package/dist/runner/runtime/index.js.map +1 -0
  129. package/dist/runner/runtime/interface.d.ts +59 -0
  130. package/dist/runner/runtime/interface.d.ts.map +1 -0
  131. package/dist/runner/runtime/interface.js +12 -0
  132. package/dist/runner/runtime/interface.js.map +1 -0
  133. package/dist/runner/runtime/sdk-runtime.d.ts +46 -0
  134. package/dist/runner/runtime/sdk-runtime.d.ts.map +1 -0
  135. package/dist/runner/runtime/sdk-runtime.js +63 -0
  136. package/dist/runner/runtime/sdk-runtime.js.map +1 -0
  137. package/dist/runner/sdk-adapter.d.ts +4 -0
  138. package/dist/runner/sdk-adapter.d.ts.map +1 -1
  139. package/dist/runner/sdk-adapter.js +35 -16
  140. package/dist/runner/sdk-adapter.js.map +1 -1
  141. package/dist/runner/types.d.ts +12 -6
  142. package/dist/runner/types.d.ts.map +1 -1
  143. package/dist/scheduler/__tests__/schedule-runner.test.js +61 -50
  144. package/dist/scheduler/__tests__/schedule-runner.test.js.map +1 -1
  145. package/dist/scheduler/schedule-runner.d.ts +1 -4
  146. package/dist/scheduler/schedule-runner.d.ts.map +1 -1
  147. package/dist/scheduler/schedule-runner.js +40 -8
  148. package/dist/scheduler/schedule-runner.js.map +1 -1
  149. package/dist/state/__tests__/session-schema.test.js +4 -0
  150. package/dist/state/__tests__/session-schema.test.js.map +1 -1
  151. package/dist/state/__tests__/session-validation.test.d.ts +2 -0
  152. package/dist/state/__tests__/session-validation.test.d.ts.map +1 -0
  153. package/dist/state/__tests__/session-validation.test.js +446 -0
  154. package/dist/state/__tests__/session-validation.test.js.map +1 -0
  155. package/dist/state/__tests__/session.test.js +68 -0
  156. package/dist/state/__tests__/session.test.js.map +1 -1
  157. package/dist/state/__tests__/working-directory-validation.test.d.ts +5 -0
  158. package/dist/state/__tests__/working-directory-validation.test.d.ts.map +1 -0
  159. package/dist/state/__tests__/working-directory-validation.test.js +101 -0
  160. package/dist/state/__tests__/working-directory-validation.test.js.map +1 -0
  161. package/dist/state/index.d.ts +2 -0
  162. package/dist/state/index.d.ts.map +1 -1
  163. package/dist/state/index.js +4 -0
  164. package/dist/state/index.js.map +1 -1
  165. package/dist/state/schemas/session-info.d.ts +32 -0
  166. package/dist/state/schemas/session-info.d.ts.map +1 -1
  167. package/dist/state/schemas/session-info.js +22 -0
  168. package/dist/state/schemas/session-info.js.map +1 -1
  169. package/dist/state/session-validation.d.ts +202 -0
  170. package/dist/state/session-validation.d.ts.map +1 -0
  171. package/dist/state/session-validation.js +407 -0
  172. package/dist/state/session-validation.js.map +1 -0
  173. package/dist/state/session.d.ts +23 -3
  174. package/dist/state/session.d.ts.map +1 -1
  175. package/dist/state/session.js +41 -6
  176. package/dist/state/session.js.map +1 -1
  177. package/dist/state/working-directory-validation.d.ts +52 -0
  178. package/dist/state/working-directory-validation.d.ts.map +1 -0
  179. package/dist/state/working-directory-validation.js +81 -0
  180. package/dist/state/working-directory-validation.js.map +1 -0
  181. package/package.json +7 -2
@@ -0,0 +1,335 @@
1
+ /**
2
+ * CLI Runtime implementation
3
+ *
4
+ * Executes Claude agents via the Claude CLI instead of the SDK, enabling Max plan
5
+ * pricing for agent execution. This runtime spawns the `claude` CLI command and
6
+ * watches the session file for messages (since claude only outputs to TTY).
7
+ *
8
+ * Requirements:
9
+ * - Claude CLI must be installed (`brew install claude-ai/tap/claude`)
10
+ * - CLI must be authenticated (`claude login`)
11
+ * - Uses Max plan pricing when available
12
+ *
13
+ * The CLIRuntime provides identical streaming interface to SDKRuntime, allowing
14
+ * seamless runtime switching via agent configuration.
15
+ */
16
+ import { execa } from "execa";
17
+ import { getCliSessionDir, getCliSessionFile, waitForNewSessionFile, } from "./cli-session-path.js";
18
+ import { CLISessionWatcher } from "./cli-session-watcher.js";
19
+ import { transformMcpServers } from "../sdk-adapter.js";
20
+ /**
21
+ * CLI runtime implementation
22
+ *
23
+ * This runtime uses the Claude CLI to execute agents, providing an alternative
24
+ * backend to the SDK runtime. It spawns `claude` CLI and watches the session file
25
+ * for new messages (since claude only outputs stream-json to TTY).
26
+ *
27
+ * The CLI runtime enables:
28
+ * - Max plan pricing (cost savings vs SDK/API pricing)
29
+ * - Full Claude Code capabilities (identical to manual CLI usage)
30
+ * - AbortController support for process cancellation
31
+ *
32
+ * Supports both local and Docker execution via configurable process spawning.
33
+ *
34
+ * @example
35
+ * ```typescript
36
+ * // Local execution
37
+ * const runtime = new CLIRuntime();
38
+ *
39
+ * // Docker execution
40
+ * const runtime = new CLIRuntime({
41
+ * processSpawner: async (args, cwd, signal) => {
42
+ * return execa("docker", ["exec", containerId, "sh", "-c",
43
+ * `cd /workspace && claude ${args.join(" ")}`],
44
+ * { cancelSignal: signal });
45
+ * },
46
+ * sessionDirOverride: "/path/to/.herdctl/docker-sessions"
47
+ * });
48
+ * ```
49
+ */
50
+ export class CLIRuntime {
51
+ processSpawner;
52
+ sessionDirOverride;
53
+ constructor(options) {
54
+ // Default to local execa spawning with prompt via stdin
55
+ this.processSpawner = options?.processSpawner ?? ((args, cwd, prompt, signal) => execa("claude", args, {
56
+ cwd,
57
+ input: prompt, // Provide prompt via stdin (required for -p mode)
58
+ cancelSignal: signal
59
+ }));
60
+ this.sessionDirOverride = options?.sessionDirOverride;
61
+ }
62
+ /**
63
+ * Execute an agent using the Claude CLI
64
+ *
65
+ * Spawns `claude` CLI and watches the session file for messages. The session
66
+ * file approach is used because claude only outputs stream-json to TTY, not
67
+ * to pipes.
68
+ *
69
+ * Process flow:
70
+ * 1. Build CLI arguments from execution options
71
+ * 2. Spawn claude subprocess (output is ignored)
72
+ * 3. Find the CLI session directory for the workspace
73
+ * 4. Wait briefly for session file to be created
74
+ * 5. Find the newest .jsonl file (the one just created)
75
+ * 6. Watch that file and stream messages as they're appended
76
+ * 7. Handle process completion and exit codes
77
+ *
78
+ * @param options - Execution options including prompt, agent, and session info
79
+ * @returns AsyncIterable of SDK messages
80
+ */
81
+ async *execute(options) {
82
+ // Build CLI arguments
83
+ // Note: -p is --print mode (print response and exit)
84
+ // Prompt is provided via stdin, not as a CLI argument
85
+ const args = ["-p"];
86
+ // Add permission mode from agent config (defaults to acceptEdits)
87
+ const permissionMode = options.agent.permission_mode ?? "acceptEdits";
88
+ args.push("--permission-mode", permissionMode);
89
+ // Add model if specified
90
+ if (options.agent.model) {
91
+ args.push("--model", options.agent.model);
92
+ }
93
+ // Add system prompt if specified
94
+ if (options.agent.system_prompt) {
95
+ args.push("--system-prompt", options.agent.system_prompt);
96
+ }
97
+ // Collect all allowed tools into a single array to avoid multiple --allowedTools flags
98
+ const allAllowedTools = [];
99
+ // Add allowed tools if specified
100
+ if (options.agent.permissions?.allowed_tools?.length) {
101
+ allAllowedTools.push(...options.agent.permissions.allowed_tools);
102
+ }
103
+ // Add bash allowed commands as Bash(command *) patterns
104
+ if (options.agent.permissions?.bash?.allowed_commands?.length) {
105
+ const bashPatterns = options.agent.permissions.bash.allowed_commands.map((cmd) => `Bash(${cmd} *)`);
106
+ allAllowedTools.push(...bashPatterns);
107
+ }
108
+ // Add all allowed tools as comma-separated string to prevent consuming subsequent args
109
+ // Note: --allowedTools accepts "comma or space-separated" but space-separated consumes
110
+ // all following args, so we must use comma-separated
111
+ if (allAllowedTools.length > 0) {
112
+ args.push("--allowedTools", allAllowedTools.join(","));
113
+ }
114
+ // Collect all denied tools into a single array
115
+ const allDeniedTools = [];
116
+ // Add denied tools if specified
117
+ if (options.agent.permissions?.denied_tools?.length) {
118
+ allDeniedTools.push(...options.agent.permissions.denied_tools);
119
+ }
120
+ // Add bash denied patterns as Bash(pattern) patterns
121
+ if (options.agent.permissions?.bash?.denied_patterns?.length) {
122
+ const bashDeniedPatterns = options.agent.permissions.bash.denied_patterns.map((pattern) => `Bash(${pattern})`);
123
+ allDeniedTools.push(...bashDeniedPatterns);
124
+ }
125
+ // Add all denied tools as comma-separated string
126
+ if (allDeniedTools.length > 0) {
127
+ args.push("--disallowedTools", allDeniedTools.join(","));
128
+ }
129
+ // Add setting sources if specified (comma-separated)
130
+ if (options.agent.setting_sources?.length) {
131
+ args.push("--setting-sources", options.agent.setting_sources.join(","));
132
+ }
133
+ // Add MCP servers if specified
134
+ // Transform agent config format to SDK format and serialize to JSON
135
+ if (options.agent.mcp_servers && Object.keys(options.agent.mcp_servers).length > 0) {
136
+ const mcpServers = transformMcpServers(options.agent.mcp_servers);
137
+ const mcpConfig = JSON.stringify(mcpServers);
138
+ args.push("--mcp-config", mcpConfig);
139
+ }
140
+ // Add session options
141
+ if (options.resume) {
142
+ args.push("--resume", options.resume);
143
+ }
144
+ if (options.fork) {
145
+ args.push("--fork-session");
146
+ }
147
+ // Note: Prompt is NOT added to args - it's provided via stdin (see processSpawner call below)
148
+ // DEBUG: Log the command being executed
149
+ console.log("[CLIRuntime] Executing command:", "claude", args);
150
+ console.log("[CLIRuntime] Prompt:", options.prompt);
151
+ // Track process and watcher for cleanup
152
+ let subprocess;
153
+ let watcher;
154
+ let hasError = false;
155
+ try {
156
+ // Determine working directory root for cwd
157
+ const working_directory = options.agent.working_directory;
158
+ const cwd = working_directory
159
+ ? typeof working_directory === "string"
160
+ ? working_directory
161
+ : working_directory.root
162
+ : process.cwd();
163
+ console.log("[CLIRuntime] Working directory:", cwd);
164
+ console.log("[CLIRuntime] Agent working_directory config:", working_directory);
165
+ // Get the CLI session directory where files will be written
166
+ // Use override if provided (for Docker execution with mounted sessions)
167
+ const sessionDir = this.sessionDirOverride ?? getCliSessionDir(cwd);
168
+ console.log("[CLIRuntime] Session directory:", sessionDir);
169
+ // Record start time before spawning process
170
+ const processStartTime = Date.now();
171
+ // Spawn claude subprocess with prompt via stdin
172
+ // Uses custom spawner if provided (e.g., for Docker execution)
173
+ // Note: processSpawner returns Subprocess directly (which is promise-like)
174
+ subprocess = this.processSpawner(args, cwd, options.prompt, options.abortController?.signal);
175
+ console.log("[CLIRuntime] Subprocess spawned, PID:", subprocess.pid);
176
+ // Log subprocess output for debugging
177
+ subprocess.stdout?.on("data", (data) => {
178
+ console.log("[CLIRuntime] stdout:", data.toString());
179
+ });
180
+ subprocess.stderr?.on("data", (data) => {
181
+ console.error("[CLIRuntime] stderr:", data.toString());
182
+ });
183
+ // Track subprocess completion for later
184
+ const processExitPromise = (async () => {
185
+ try {
186
+ return await subprocess;
187
+ }
188
+ catch (error) {
189
+ console.error("[CLIRuntime] Process failed:", error);
190
+ throw error;
191
+ }
192
+ })();
193
+ // Monitor subprocess completion in background (for logging only)
194
+ processExitPromise.then((result) => {
195
+ console.log("[CLIRuntime] Process completed with exit code:", result.exitCode);
196
+ }, () => {
197
+ // Error already logged above
198
+ });
199
+ // Determine which session file to watch
200
+ let sessionFilePath;
201
+ if (options.resume) {
202
+ // When resuming, use the known session ID
203
+ sessionFilePath = getCliSessionFile(cwd, options.resume);
204
+ console.log("[CLIRuntime] Resuming session, watching file:", sessionFilePath);
205
+ }
206
+ else {
207
+ // When starting new session, wait for a NEW file created after process start
208
+ console.log("[CLIRuntime] Waiting for new session file (timeout: 15s)...");
209
+ sessionFilePath = await waitForNewSessionFile(sessionDir, processStartTime, {
210
+ timeoutMs: 15000, // Increase timeout to 15 seconds for debugging
211
+ pollIntervalMs: 200,
212
+ });
213
+ console.log("[CLIRuntime] New session, watching newly created file:", sessionFilePath);
214
+ }
215
+ // Extract session ID from filename (basename without .jsonl extension)
216
+ // For CLI runtime, the session ID is the filename - this matches SDK runtime behavior
217
+ const sessionFileName = sessionFilePath.split("/").pop() || "";
218
+ const extractedSessionId = sessionFileName.replace(/\.jsonl$/, "");
219
+ console.log("[CLIRuntime] Extracted session ID:", extractedSessionId);
220
+ // Watch the session file for messages
221
+ watcher = new CLISessionWatcher(sessionFilePath);
222
+ // When resuming, initialize watcher to skip existing content
223
+ // This prevents replaying the entire conversation history on each message
224
+ if (options.resume) {
225
+ await watcher.initialize();
226
+ console.log("[CLIRuntime] Watcher initialized for resume, will skip existing content");
227
+ }
228
+ // Set up abort handling
229
+ if (options.abortController) {
230
+ options.abortController.signal.addEventListener("abort", () => {
231
+ subprocess?.kill();
232
+ watcher?.stop();
233
+ });
234
+ }
235
+ // Set up process completion handler - stop watcher when process exits
236
+ // This allows the for-await loop to exit naturally
237
+ processExitPromise.then(() => {
238
+ console.log("[CLIRuntime] Process completed, stopping watcher to exit loop");
239
+ watcher?.stop();
240
+ }, (error) => {
241
+ console.log("[CLIRuntime] Process failed, stopping watcher");
242
+ watcher?.stop();
243
+ });
244
+ // Stream messages from the session file
245
+ // Just iterate naturally - the watcher handles all the waiting
246
+ console.log("[CLIRuntime] Starting to stream messages from watcher");
247
+ // Yield synthetic system message with session ID (matches SDK runtime behavior)
248
+ // This allows the message processor to extract the session ID for persistence
249
+ yield {
250
+ type: "system",
251
+ subtype: "init",
252
+ session_id: extractedSessionId,
253
+ content: "CLI session initialized",
254
+ };
255
+ console.log("[CLIRuntime] Yielded synthetic system message with session ID");
256
+ // Stream messages from the watcher as they arrive
257
+ for await (const message of watcher.watch()) {
258
+ console.log(`[CLIRuntime] Received message type: ${message.type}`);
259
+ yield message;
260
+ // Track errors
261
+ if (message.type === "error") {
262
+ hasError = true;
263
+ }
264
+ // If this is a result message, we're done
265
+ if (message.type === "result") {
266
+ console.log("[CLIRuntime] Got result message, stopping");
267
+ break;
268
+ }
269
+ }
270
+ console.log("[CLIRuntime] Watcher iteration complete");
271
+ // Wait for process to complete
272
+ const { exitCode } = await processExitPromise;
273
+ console.log("[CLIRuntime] Process completed, flushing any remaining messages");
274
+ // After process exits, explicitly flush the file one more time
275
+ // This catches any final messages that hadn't triggered chokidar events yet
276
+ const remainingMessages = await watcher.flushRemainingMessages();
277
+ console.log(`[CLIRuntime] Found ${remainingMessages.length} remaining message(s) after process exit`);
278
+ // Stop the watcher now - we've flushed everything we need
279
+ console.log("[CLIRuntime] Stopping watcher after flush");
280
+ watcher.stop();
281
+ // Yield any remaining messages
282
+ for (const message of remainingMessages) {
283
+ console.log(`[CLIRuntime] Yielding remaining message type: ${message.type}`);
284
+ yield message;
285
+ // Track errors
286
+ if (message.type === "error") {
287
+ hasError = true;
288
+ }
289
+ }
290
+ // If process failed and we didn't yield an error message, create one
291
+ if (exitCode !== 0 && !hasError) {
292
+ yield {
293
+ type: "error",
294
+ message: `Claude CLI exited with code ${exitCode}`,
295
+ code: `EXIT_${exitCode}`,
296
+ };
297
+ }
298
+ }
299
+ catch (error) {
300
+ // Handle process errors
301
+ if (error && typeof error === "object" && "code" in error) {
302
+ const execaError = error;
303
+ // CLI not found
304
+ if (execaError.code === "ENOENT") {
305
+ yield {
306
+ type: "error",
307
+ message: "Claude CLI not found. Install with: brew install claude-ai/tap/claude",
308
+ code: "CLI_NOT_FOUND",
309
+ };
310
+ return;
311
+ }
312
+ // Process was killed (likely by AbortController)
313
+ if (execaError.code === "ABORT_ERR") {
314
+ yield {
315
+ type: "error",
316
+ message: "Claude CLI execution was cancelled",
317
+ code: "CANCELLED",
318
+ };
319
+ return;
320
+ }
321
+ }
322
+ // Generic error
323
+ const errorMessage = error instanceof Error ? error.message : String(error);
324
+ yield {
325
+ type: "error",
326
+ message: `CLI execution failed: ${errorMessage}`,
327
+ };
328
+ }
329
+ finally {
330
+ // Cleanup
331
+ watcher?.stop();
332
+ }
333
+ }
334
+ }
335
+ //# sourceMappingURL=cli-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-runtime.js","sourceRoot":"","sources":["../../../src/runner/runtime/cli-runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,108 @@
1
+ /**
2
+ * CLI session path utilities - locate Claude CLI session files
3
+ *
4
+ * The Claude CLI stores session files in ~/.claude/projects/ with workspace paths
5
+ * encoded by replacing slashes with hyphens. These utilities help locate CLI session
6
+ * directories and specific session files.
7
+ */
8
+ /**
9
+ * Encode a workspace path for CLI session storage
10
+ *
11
+ * The CLI encodes workspace paths by replacing all path separators with hyphens.
12
+ * Works on both Unix (/) and Windows (\) paths.
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * encodePathForCli('/Users/ed/Code/myproject')
17
+ * // => '-Users-ed-Code-myproject'
18
+ *
19
+ * encodePathForCli('C:\\Users\\ed\\Code\\myproject')
20
+ * // => 'C:-Users-ed-Code-myproject'
21
+ * ```
22
+ *
23
+ * @param absolutePath - Absolute path to workspace directory
24
+ * @returns Encoded path with slashes replaced by hyphens
25
+ */
26
+ export declare function encodePathForCli(absolutePath: string): string;
27
+ /**
28
+ * Get the CLI session directory for a workspace
29
+ *
30
+ * Returns the directory where Claude CLI stores sessions for the given workspace.
31
+ * Format: ~/.claude/projects/{encoded-workspace-path}/
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * getCliSessionDir('/Users/ed/Code/myproject')
36
+ * // => '/Users/ed/.claude/projects/-Users-ed-Code-myproject'
37
+ * ```
38
+ *
39
+ * @param workspacePath - Absolute path to workspace directory
40
+ * @returns Absolute path to CLI session storage directory
41
+ */
42
+ export declare function getCliSessionDir(workspacePath: string): string;
43
+ /**
44
+ * Get the path to a specific CLI session file
45
+ *
46
+ * Returns the full path to a session's JSONL file in the CLI session directory.
47
+ * Format: ~/.claude/projects/{encoded-workspace-path}/{session-id}.jsonl
48
+ *
49
+ * @example
50
+ * ```typescript
51
+ * getCliSessionFile(
52
+ * '/Users/ed/Code/myproject',
53
+ * 'dda6da5b-8788-4990-a582-d5a2c63fbfba'
54
+ * )
55
+ * // => '/Users/ed/.claude/projects/-Users-ed-Code-myproject/dda6da5b-8788-4990-a582-d5a2c63fbfba.jsonl'
56
+ * ```
57
+ *
58
+ * @param workspacePath - Absolute path to workspace directory
59
+ * @param sessionId - CLI session ID (UUID format)
60
+ * @returns Absolute path to session JSONL file
61
+ */
62
+ export declare function getCliSessionFile(workspacePath: string, sessionId: string): string;
63
+ /**
64
+ * Find the newest session file in a CLI session directory
65
+ *
66
+ * Scans the session directory for .jsonl files and returns the path to the
67
+ * most recently modified one. This is useful when spawning a new CLI session
68
+ * without knowing the session ID upfront - the newest file is typically the
69
+ * one just created.
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * const sessionDir = getCliSessionDir('/Users/ed/Code/myproject');
74
+ * const newestFile = await findNewestSessionFile(sessionDir);
75
+ * // => '/Users/ed/.claude/projects/-Users-ed-Code-myproject/abc123.jsonl'
76
+ * ```
77
+ *
78
+ * @param sessionDir - Absolute path to CLI session directory
79
+ * @returns Promise resolving to path of newest .jsonl file
80
+ * @throws {Error} If directory doesn't exist or contains no .jsonl files
81
+ */
82
+ export declare function findNewestSessionFile(sessionDir: string): Promise<string>;
83
+ /**
84
+ * Wait for a new session file to be created after a given timestamp
85
+ *
86
+ * Polls the session directory until a new .jsonl file appears that was
87
+ * created after the specified start time. This prevents picking up old
88
+ * session files when spawning a new CLI session.
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const startTime = Date.now();
93
+ * // ... spawn claude CLI ...
94
+ * const sessionFile = await waitForNewSessionFile(sessionDir, startTime);
95
+ * // => '/Users/ed/.claude/projects/-Users-ed-Code-myproject/new-session.jsonl'
96
+ * ```
97
+ *
98
+ * @param sessionDir - Absolute path to CLI session directory
99
+ * @param startTime - Timestamp (ms) before which files should be ignored
100
+ * @param options - Optional configuration
101
+ * @returns Promise resolving to path of newly created session file
102
+ * @throws {Error} If timeout exceeded or directory doesn't exist
103
+ */
104
+ export declare function waitForNewSessionFile(sessionDir: string, startTime: number, options?: {
105
+ timeoutMs?: number;
106
+ pollIntervalMs?: number;
107
+ }): Promise<string>;
108
+ //# sourceMappingURL=cli-session-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-session-path.d.ts","sourceRoot":"","sources":["../../../src/runner/runtime/cli-session-path.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,gBAAgB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAG7D;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,MAAM,CAG9D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,iBAAiB,CAC/B,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,GAChB,MAAM,CAGR;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,MAAM,CAAC,CA4BjB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,qBAAqB,CACzC,UAAU,EAAE,MAAM,EAClB,SAAS,EAAE,MAAM,EACjB,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CAAO,GAC5D,OAAO,CAAC,MAAM,CAAC,CAwCjB"}
@@ -0,0 +1,173 @@
1
+ /**
2
+ * CLI session path utilities - locate Claude CLI session files
3
+ *
4
+ * The Claude CLI stores session files in ~/.claude/projects/ with workspace paths
5
+ * encoded by replacing slashes with hyphens. These utilities help locate CLI session
6
+ * directories and specific session files.
7
+ */
8
+ import * as path from "node:path";
9
+ import * as os from "node:os";
10
+ import { readdir, stat } from "node:fs/promises";
11
+ /**
12
+ * Encode a workspace path for CLI session storage
13
+ *
14
+ * The CLI encodes workspace paths by replacing all path separators with hyphens.
15
+ * Works on both Unix (/) and Windows (\) paths.
16
+ *
17
+ * @example
18
+ * ```typescript
19
+ * encodePathForCli('/Users/ed/Code/myproject')
20
+ * // => '-Users-ed-Code-myproject'
21
+ *
22
+ * encodePathForCli('C:\\Users\\ed\\Code\\myproject')
23
+ * // => 'C:-Users-ed-Code-myproject'
24
+ * ```
25
+ *
26
+ * @param absolutePath - Absolute path to workspace directory
27
+ * @returns Encoded path with slashes replaced by hyphens
28
+ */
29
+ export function encodePathForCli(absolutePath) {
30
+ // Replace both forward slashes (Unix) and backslashes (Windows)
31
+ return absolutePath.replace(/[/\\]/g, "-");
32
+ }
33
+ /**
34
+ * Get the CLI session directory for a workspace
35
+ *
36
+ * Returns the directory where Claude CLI stores sessions for the given workspace.
37
+ * Format: ~/.claude/projects/{encoded-workspace-path}/
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * getCliSessionDir('/Users/ed/Code/myproject')
42
+ * // => '/Users/ed/.claude/projects/-Users-ed-Code-myproject'
43
+ * ```
44
+ *
45
+ * @param workspacePath - Absolute path to workspace directory
46
+ * @returns Absolute path to CLI session storage directory
47
+ */
48
+ export function getCliSessionDir(workspacePath) {
49
+ const encoded = encodePathForCli(workspacePath);
50
+ return path.join(os.homedir(), ".claude", "projects", encoded);
51
+ }
52
+ /**
53
+ * Get the path to a specific CLI session file
54
+ *
55
+ * Returns the full path to a session's JSONL file in the CLI session directory.
56
+ * Format: ~/.claude/projects/{encoded-workspace-path}/{session-id}.jsonl
57
+ *
58
+ * @example
59
+ * ```typescript
60
+ * getCliSessionFile(
61
+ * '/Users/ed/Code/myproject',
62
+ * 'dda6da5b-8788-4990-a582-d5a2c63fbfba'
63
+ * )
64
+ * // => '/Users/ed/.claude/projects/-Users-ed-Code-myproject/dda6da5b-8788-4990-a582-d5a2c63fbfba.jsonl'
65
+ * ```
66
+ *
67
+ * @param workspacePath - Absolute path to workspace directory
68
+ * @param sessionId - CLI session ID (UUID format)
69
+ * @returns Absolute path to session JSONL file
70
+ */
71
+ export function getCliSessionFile(workspacePath, sessionId) {
72
+ const sessionDir = getCliSessionDir(workspacePath);
73
+ return path.join(sessionDir, `${sessionId}.jsonl`);
74
+ }
75
+ /**
76
+ * Find the newest session file in a CLI session directory
77
+ *
78
+ * Scans the session directory for .jsonl files and returns the path to the
79
+ * most recently modified one. This is useful when spawning a new CLI session
80
+ * without knowing the session ID upfront - the newest file is typically the
81
+ * one just created.
82
+ *
83
+ * @example
84
+ * ```typescript
85
+ * const sessionDir = getCliSessionDir('/Users/ed/Code/myproject');
86
+ * const newestFile = await findNewestSessionFile(sessionDir);
87
+ * // => '/Users/ed/.claude/projects/-Users-ed-Code-myproject/abc123.jsonl'
88
+ * ```
89
+ *
90
+ * @param sessionDir - Absolute path to CLI session directory
91
+ * @returns Promise resolving to path of newest .jsonl file
92
+ * @throws {Error} If directory doesn't exist or contains no .jsonl files
93
+ */
94
+ export async function findNewestSessionFile(sessionDir) {
95
+ try {
96
+ const files = await readdir(sessionDir);
97
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
98
+ if (jsonlFiles.length === 0) {
99
+ throw new Error(`No session files found in ${sessionDir}`);
100
+ }
101
+ // Get stats for all .jsonl files
102
+ const fileStats = await Promise.all(jsonlFiles.map(async (file) => {
103
+ const filePath = path.join(sessionDir, file);
104
+ const stats = await stat(filePath);
105
+ return { path: filePath, mtime: stats.mtime };
106
+ }));
107
+ // Sort by modification time (newest first)
108
+ fileStats.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
109
+ return fileStats[0].path;
110
+ }
111
+ catch (error) {
112
+ if (error.code === "ENOENT") {
113
+ throw new Error(`Session directory does not exist: ${sessionDir}`);
114
+ }
115
+ throw error;
116
+ }
117
+ }
118
+ /**
119
+ * Wait for a new session file to be created after a given timestamp
120
+ *
121
+ * Polls the session directory until a new .jsonl file appears that was
122
+ * created after the specified start time. This prevents picking up old
123
+ * session files when spawning a new CLI session.
124
+ *
125
+ * @example
126
+ * ```typescript
127
+ * const startTime = Date.now();
128
+ * // ... spawn claude CLI ...
129
+ * const sessionFile = await waitForNewSessionFile(sessionDir, startTime);
130
+ * // => '/Users/ed/.claude/projects/-Users-ed-Code-myproject/new-session.jsonl'
131
+ * ```
132
+ *
133
+ * @param sessionDir - Absolute path to CLI session directory
134
+ * @param startTime - Timestamp (ms) before which files should be ignored
135
+ * @param options - Optional configuration
136
+ * @returns Promise resolving to path of newly created session file
137
+ * @throws {Error} If timeout exceeded or directory doesn't exist
138
+ */
139
+ export async function waitForNewSessionFile(sessionDir, startTime, options = {}) {
140
+ const { timeoutMs = 5000, pollIntervalMs = 100 } = options;
141
+ const deadline = Date.now() + timeoutMs;
142
+ while (Date.now() < deadline) {
143
+ try {
144
+ const files = await readdir(sessionDir);
145
+ const jsonlFiles = files.filter((f) => f.endsWith(".jsonl"));
146
+ // Find files created after startTime
147
+ const newFiles = [];
148
+ for (const file of jsonlFiles) {
149
+ const filePath = path.join(sessionDir, file);
150
+ const stats = await stat(filePath);
151
+ // Check if file was modified after startTime
152
+ if (stats.mtime.getTime() > startTime) {
153
+ newFiles.push({ path: filePath, mtime: stats.mtime });
154
+ }
155
+ }
156
+ // Return the newest file created after startTime
157
+ if (newFiles.length > 0) {
158
+ newFiles.sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
159
+ return newFiles[0].path;
160
+ }
161
+ }
162
+ catch (error) {
163
+ // Directory might not exist yet - keep polling
164
+ if (error.code !== "ENOENT") {
165
+ throw error;
166
+ }
167
+ }
168
+ // Wait before next poll
169
+ await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
170
+ }
171
+ throw new Error(`Timeout waiting for new session file in ${sessionDir} (waited ${timeoutMs}ms)`);
172
+ }
173
+ //# sourceMappingURL=cli-session-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-session-path.js","sourceRoot":"","sources":["../../../src/runner/runtime/cli-session-path.ts"],"names":[],"mappings":"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"}