@herbcaudill/ralph 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -134,15 +134,183 @@ var EnhancedTextInput = ({
134
134
  };
135
135
 
136
136
  // src/components/SessionRunner.tsx
137
- import { appendFileSync, writeFileSync as writeFileSync2, readFileSync as readFileSync6, existsSync as existsSync10 } from "fs";
138
- import { join as join11, basename } from "path";
139
- import { randomUUID } from "crypto";
137
+ import { appendFileSync, readFileSync as readFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync3 } from "fs";
138
+ import { join as join12, basename } from "path";
139
+
140
+ // ../shared/dist/persistence/SessionPersister.js
141
+ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync } from "fs";
142
+ import { appendFile, readFile } from "fs/promises";
143
+ import { join } from "path";
144
+ var SessionPersister = class {
145
+ constructor(storageDir) {
146
+ this.storageDir = storageDir;
147
+ if (!existsSync(storageDir)) {
148
+ mkdirSync(storageDir, { recursive: true });
149
+ }
150
+ }
151
+ /** Append an event to a session's JSONL file. */
152
+ async appendEvent(sessionId, event, app) {
153
+ const filePath = this.sessionPath(sessionId, app);
154
+ const dir = this.getAppDir(app);
155
+ if (!existsSync(dir)) {
156
+ mkdirSync(dir, { recursive: true });
157
+ }
158
+ const line = JSON.stringify(event) + "\n";
159
+ await appendFile(filePath, line, "utf-8");
160
+ }
161
+ /** Read all events for a session. */
162
+ async readEvents(sessionId, app) {
163
+ const filePath = this.sessionPath(sessionId, app);
164
+ if (!existsSync(filePath))
165
+ return [];
166
+ const content = await readFile(filePath, "utf-8");
167
+ return content.split("\n").filter((line) => line.trim()).map((line) => JSON.parse(line));
168
+ }
169
+ /** Read events since a given timestamp. */
170
+ async readEventsSince(sessionId, since, app) {
171
+ const events = await this.readEvents(sessionId, app);
172
+ return events.filter((e) => e.timestamp >= since);
173
+ }
174
+ /**
175
+ * List all session IDs (derived from JSONL filenames).
176
+ * If app is provided, lists sessions only from that app's directory.
177
+ * If app is undefined, lists sessions from all directories including root.
178
+ */
179
+ listSessions(app) {
180
+ return this.listSessionsWithApp(app).map((s) => s.sessionId);
181
+ }
182
+ /**
183
+ * List all sessions with their app namespace.
184
+ * If app is provided, lists sessions only from that app's directory.
185
+ * If app is undefined, lists sessions from all directories including root.
186
+ */
187
+ listSessionsWithApp(app) {
188
+ if (!existsSync(this.storageDir))
189
+ return [];
190
+ if (app !== void 0) {
191
+ const appDir = this.getAppDir(app);
192
+ if (!existsSync(appDir))
193
+ return [];
194
+ return readdirSync(appDir).filter((f) => f.endsWith(".jsonl")).map((f) => ({ sessionId: f.replace(/\.jsonl$/, ""), app }));
195
+ }
196
+ const sessions = [];
197
+ const entries = readdirSync(this.storageDir, { withFileTypes: true });
198
+ for (const entry of entries) {
199
+ if (entry.isFile() && entry.name.endsWith(".jsonl")) {
200
+ sessions.push({ sessionId: entry.name.replace(/\.jsonl$/, ""), app: void 0 });
201
+ } else if (entry.isDirectory()) {
202
+ const appDir = join(this.storageDir, entry.name);
203
+ for (const file of readdirSync(appDir)) {
204
+ if (file.endsWith(".jsonl")) {
205
+ sessions.push({ sessionId: file.replace(/\.jsonl$/, ""), app: entry.name });
206
+ }
207
+ }
208
+ }
209
+ }
210
+ return sessions;
211
+ }
212
+ /** Get the most recently created session ID, or null if none. */
213
+ getLatestSessionId(app) {
214
+ const sessions = this.listSessions(app);
215
+ if (sessions.length === 0)
216
+ return null;
217
+ let latest = null;
218
+ for (const id of sessions) {
219
+ const filePath = app ? this.sessionPath(id, app) : this.findSessionPath(id);
220
+ if (!filePath || !existsSync(filePath))
221
+ continue;
222
+ const stat = statSync(filePath);
223
+ if (!latest || stat.birthtimeMs > latest.birthtime) {
224
+ latest = { id, birthtime: stat.birthtimeMs };
225
+ }
226
+ }
227
+ return latest?.id ?? null;
228
+ }
229
+ /** Delete a session's JSONL file. */
230
+ deleteSession(sessionId, app) {
231
+ const filePath = this.sessionPath(sessionId, app);
232
+ if (existsSync(filePath)) {
233
+ unlinkSync(filePath);
234
+ }
235
+ }
236
+ /** Read session metadata from the first event (session_created) in the JSONL file. */
237
+ readSessionMetadata(sessionId, app) {
238
+ const filePath = this.sessionPath(sessionId, app);
239
+ if (!existsSync(filePath))
240
+ return null;
241
+ try {
242
+ const content = readFileSync(filePath, "utf-8");
243
+ const firstLine = content.split("\n").find((line) => line.trim());
244
+ if (!firstLine)
245
+ return null;
246
+ const event = JSON.parse(firstLine);
247
+ if (event.type === "session_created") {
248
+ return {
249
+ adapter: event.adapter ?? "claude",
250
+ cwd: event.cwd,
251
+ createdAt: event.timestamp ?? 0,
252
+ app: event.app,
253
+ systemPrompt: event.systemPrompt
254
+ };
255
+ }
256
+ return null;
257
+ } catch {
258
+ return null;
259
+ }
260
+ }
261
+ /** Check if a session exists. */
262
+ hasSession(sessionId, app) {
263
+ return existsSync(this.sessionPath(sessionId, app));
264
+ }
265
+ /** Get the full file path for a session. */
266
+ getSessionPath(sessionId, app) {
267
+ return this.sessionPath(sessionId, app);
268
+ }
269
+ /** Get the directory for an app. */
270
+ getAppDir(app) {
271
+ return app ? join(this.storageDir, app) : this.storageDir;
272
+ }
273
+ /** Get the file path for a session. */
274
+ sessionPath(sessionId, app) {
275
+ return join(this.getAppDir(app), `${sessionId}.jsonl`);
276
+ }
277
+ /** Find the session path by searching root and app directories. */
278
+ findSessionPath(sessionId) {
279
+ const rootPath = join(this.storageDir, `${sessionId}.jsonl`);
280
+ if (existsSync(rootPath))
281
+ return rootPath;
282
+ if (existsSync(this.storageDir)) {
283
+ const entries = readdirSync(this.storageDir, { withFileTypes: true });
284
+ for (const entry of entries) {
285
+ if (entry.isDirectory()) {
286
+ const appPath = join(this.storageDir, entry.name, `${sessionId}.jsonl`);
287
+ if (existsSync(appPath))
288
+ return appPath;
289
+ }
290
+ }
291
+ }
292
+ return null;
293
+ }
294
+ };
295
+
296
+ // ../shared/dist/persistence/getDefaultStorageDir.js
297
+ import { homedir, platform } from "os";
298
+ import { join as join2 } from "path";
299
+ function getDefaultStorageDir() {
300
+ if (platform() === "win32") {
301
+ const localAppData = process.env.LOCALAPPDATA ?? join2(homedir(), "AppData", "Local");
302
+ return join2(localAppData, "ralph", "agent-sessions");
303
+ }
304
+ return join2(homedir(), ".local", "share", "ralph", "agent-sessions");
305
+ }
306
+
307
+ // src/components/SessionRunner.tsx
140
308
  import { query } from "@anthropic-ai/claude-agent-sdk";
141
309
 
142
310
  // src/lib/addTodo.ts
143
311
  import { execSync } from "child_process";
144
- import { existsSync, readFileSync, writeFileSync } from "fs";
145
- import { join } from "path";
312
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
313
+ import { join as join3 } from "path";
146
314
 
147
315
  // src/lib/insertTodo.ts
148
316
  var insertTodo = (content, description) => {
@@ -165,8 +333,8 @@ ${content}`;
165
333
 
166
334
  // src/lib/addTodo.ts
167
335
  var addTodo = (description, cwd = process.cwd()) => {
168
- const todoPath = join(cwd, ".ralph", "todo.md");
169
- const content = existsSync(todoPath) ? readFileSync(todoPath, "utf-8") : "";
336
+ const todoPath = join3(cwd, ".ralph", "todo.md");
337
+ const content = existsSync2(todoPath) ? readFileSync2(todoPath, "utf-8") : "";
170
338
  const newContent = insertTodo(content, description);
171
339
  writeFileSync(todoPath, newContent);
172
340
  let indexContent = "";
@@ -197,8 +365,8 @@ var addTodo = (description, cwd = process.cwd()) => {
197
365
  };
198
366
 
199
367
  // src/lib/getProgress.ts
200
- import { existsSync as existsSync2 } from "fs";
201
- import { join as join3 } from "path";
368
+ import { existsSync as existsSync3 } from "fs";
369
+ import { join as join5 } from "path";
202
370
 
203
371
  // src/lib/getBeadsProgress.ts
204
372
  import { execSync as execSync2 } from "child_process";
@@ -235,13 +403,13 @@ var getBeadsProgress = (initialCount, startupTimestamp) => {
235
403
  };
236
404
 
237
405
  // src/lib/getTodoProgress.ts
238
- import { readFileSync as readFileSync2 } from "fs";
239
- import { join as join2 } from "path";
240
- var ralphDir = join2(process.cwd(), ".ralph");
241
- var todoFile = join2(ralphDir, "todo.md");
406
+ import { readFileSync as readFileSync3 } from "fs";
407
+ import { join as join4 } from "path";
408
+ var ralphDir = join4(process.cwd(), ".ralph");
409
+ var todoFile = join4(ralphDir, "todo.md");
242
410
  var getTodoProgress = () => {
243
411
  try {
244
- const content = readFileSync2(todoFile, "utf-8");
412
+ const content = readFileSync3(todoFile, "utf-8");
245
413
  const uncheckedMatches = content.match(/- \[ \]/g);
246
414
  const unchecked = uncheckedMatches ? uncheckedMatches.length : 0;
247
415
  const checkedMatches = content.match(/- \[[xX]\]/g);
@@ -254,22 +422,22 @@ var getTodoProgress = () => {
254
422
  };
255
423
 
256
424
  // src/lib/getProgress.ts
257
- var beadsDir = join3(process.cwd(), ".beads");
258
- var ralphDir2 = join3(process.cwd(), ".ralph");
259
- var todoFile2 = join3(ralphDir2, "todo.md");
425
+ var beadsDir = join5(process.cwd(), ".beads");
426
+ var ralphDir2 = join5(process.cwd(), ".ralph");
427
+ var todoFile2 = join5(ralphDir2, "todo.md");
260
428
  var getProgress = (initialCount, startupTimestamp) => {
261
- if (existsSync2(beadsDir)) {
429
+ if (existsSync3(beadsDir)) {
262
430
  return getBeadsProgress(initialCount, startupTimestamp);
263
431
  }
264
- if (existsSync2(todoFile2)) {
432
+ if (existsSync3(todoFile2)) {
265
433
  return getTodoProgress();
266
434
  }
267
435
  return { type: "none", completed: 0, total: 0 };
268
436
  };
269
437
 
270
438
  // src/lib/captureStartupSnapshot.ts
271
- import { existsSync as existsSync3 } from "fs";
272
- import { join as join5 } from "path";
439
+ import { existsSync as existsSync4 } from "fs";
440
+ import { join as join7 } from "path";
273
441
 
274
442
  // src/lib/captureBeadsSnapshot.ts
275
443
  import { execSync as execSync3 } from "child_process";
@@ -301,13 +469,13 @@ var captureBeadsSnapshot = () => {
301
469
  };
302
470
 
303
471
  // src/lib/captureTodoSnapshot.ts
304
- import { readFileSync as readFileSync3 } from "fs";
305
- import { join as join4 } from "path";
306
- var ralphDir3 = join4(process.cwd(), ".ralph");
307
- var todoFile3 = join4(ralphDir3, "todo.md");
472
+ import { readFileSync as readFileSync4 } from "fs";
473
+ import { join as join6 } from "path";
474
+ var ralphDir3 = join6(process.cwd(), ".ralph");
475
+ var todoFile3 = join6(ralphDir3, "todo.md");
308
476
  var captureTodoSnapshot = () => {
309
477
  try {
310
- const content = readFileSync3(todoFile3, "utf-8");
478
+ const content = readFileSync4(todoFile3, "utf-8");
311
479
  const uncheckedMatches = content.match(/- \[ \]/g);
312
480
  const unchecked = uncheckedMatches ? uncheckedMatches.length : 0;
313
481
  const checkedMatches = content.match(/- \[[xX]\]/g);
@@ -323,14 +491,14 @@ var captureTodoSnapshot = () => {
323
491
  };
324
492
 
325
493
  // src/lib/captureStartupSnapshot.ts
326
- var beadsDir2 = join5(process.cwd(), ".beads");
327
- var ralphDir4 = join5(process.cwd(), ".ralph");
328
- var todoFile4 = join5(ralphDir4, "todo.md");
494
+ var beadsDir2 = join7(process.cwd(), ".beads");
495
+ var ralphDir4 = join7(process.cwd(), ".ralph");
496
+ var todoFile4 = join7(ralphDir4, "todo.md");
329
497
  var captureStartupSnapshot = () => {
330
- if (existsSync3(beadsDir2)) {
498
+ if (existsSync4(beadsDir2)) {
331
499
  return captureBeadsSnapshot();
332
500
  }
333
- if (existsSync3(todoFile4)) {
501
+ if (existsSync4(todoFile4)) {
334
502
  return captureTodoSnapshot();
335
503
  }
336
504
  return void 0;
@@ -356,20 +524,20 @@ import { spawn } from "child_process";
356
524
 
357
525
  // ../../../beads-sdk/dist/socket.js
358
526
  import { createConnection } from "net";
359
- import { join as join6 } from "path";
360
- import { existsSync as existsSync4 } from "fs";
527
+ import { join as join8 } from "path";
528
+ import { existsSync as existsSync5 } from "fs";
361
529
  var DaemonSocket = class {
362
530
  constructor(options = {}) {
363
531
  this.socket = null;
364
532
  this.connected = false;
365
533
  const cwd = options.cwd ?? process.cwd();
366
- this.socketPath = join6(cwd, ".beads", "bd.sock");
534
+ this.socketPath = join8(cwd, ".beads", "bd.sock");
367
535
  this.connectTimeout = options.connectTimeout ?? 2e3;
368
536
  this.requestTimeout = options.requestTimeout ?? 5e3;
369
537
  }
370
538
  /** Check if the beads daemon socket file exists. */
371
539
  socketExists() {
372
- return existsSync4(this.socketPath);
540
+ return existsSync5(this.socketPath);
373
541
  }
374
542
  /** Check if connected to the daemon. */
375
543
  get isConnected() {
@@ -377,12 +545,10 @@ var DaemonSocket = class {
377
545
  }
378
546
  /** Connect to the beads daemon. Returns true if connected, false otherwise. */
379
547
  async connect() {
380
- if (!this.socketExists()) {
548
+ if (!this.socketExists())
381
549
  return false;
382
- }
383
- if (this.connected && this.socket) {
550
+ if (this.connected && this.socket)
384
551
  return true;
385
- }
386
552
  return new Promise((resolve2) => {
387
553
  this.socket = createConnection(this.socketPath);
388
554
  const timeout = setTimeout(() => {
@@ -485,9 +651,8 @@ function watchMutations(onMutation, options = {}) {
485
651
  client = new DaemonSocket({ cwd });
486
652
  const connected = await client.connect();
487
653
  if (!connected) {
488
- if (!stopped) {
654
+ if (!stopped)
489
655
  timeoutId = setTimeout(poll, interval);
490
- }
491
656
  return;
492
657
  }
493
658
  }
@@ -496,17 +661,15 @@ function watchMutations(onMutation, options = {}) {
496
661
  for (const mutation of mutations) {
497
662
  onMutation(mutation);
498
663
  const mutationTime = new Date(mutation.Timestamp).getTime();
499
- if (mutationTime > lastTimestamp) {
664
+ if (mutationTime > lastTimestamp)
500
665
  lastTimestamp = mutationTime;
501
- }
502
666
  }
503
667
  } catch {
504
668
  client?.close();
505
669
  client = null;
506
670
  }
507
- if (!stopped) {
671
+ if (!stopped)
508
672
  timeoutId = setTimeout(poll, interval);
509
- }
510
673
  };
511
674
  poll();
512
675
  return () => {
@@ -684,43 +847,6 @@ var useTerminalSize = () => {
684
847
  return size;
685
848
  };
686
849
 
687
- // src/lib/getNextLogFile.ts
688
- import { existsSync as existsSync6, mkdirSync } from "fs";
689
- import { join as join8 } from "path";
690
-
691
- // src/lib/findMaxLogNumber.ts
692
- import { readdirSync, existsSync as existsSync5 } from "fs";
693
- import { join as join7 } from "path";
694
- var EVENT_LOG_PATTERN = /^events-(\d+)\.jsonl$/;
695
- var findMaxLogNumber = () => {
696
- const ralphDir6 = join7(process.cwd(), ".ralph");
697
- if (!existsSync5(ralphDir6)) {
698
- return 0;
699
- }
700
- const files = readdirSync(ralphDir6);
701
- let maxNumber = 0;
702
- for (const file of files) {
703
- const match = file.match(EVENT_LOG_PATTERN);
704
- if (match) {
705
- const num = parseInt(match[1], 10);
706
- if (num > maxNumber) {
707
- maxNumber = num;
708
- }
709
- }
710
- }
711
- return maxNumber;
712
- };
713
-
714
- // src/lib/getNextLogFile.ts
715
- var getNextLogFile = () => {
716
- const ralphDir6 = join8(process.cwd(), ".ralph");
717
- if (!existsSync6(ralphDir6)) {
718
- mkdirSync(ralphDir6, { recursive: true });
719
- }
720
- const maxNumber = findMaxLogNumber();
721
- return join8(ralphDir6, `events-${maxNumber + 1}.jsonl`);
722
- };
723
-
724
850
  // src/lib/parseTaskLifecycle.ts
725
851
  function parseTaskLifecycleEvent(text) {
726
852
  const startingMatch = text.match(/<start_task>([a-z]+-[a-z0-9]+(?:\.[a-z0-9]+)*)<\/start_task>/i);
@@ -741,23 +867,23 @@ function parseTaskLifecycleEvent(text) {
741
867
  }
742
868
 
743
869
  // src/lib/getPromptContent.ts
744
- import { existsSync as existsSync9, readFileSync as readFileSync5 } from "fs";
745
- import { join as join10, dirname as dirname3 } from "path";
746
- import { fileURLToPath } from "url";
870
+ import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
871
+ import { join as join11, dirname as dirname4 } from "path";
872
+ import { fileURLToPath as fileURLToPath2 } from "url";
747
873
 
748
874
  // ../shared/dist/prompts/loadPrompt.js
749
- import { existsSync as existsSync8, readFileSync as readFileSync4, copyFileSync, mkdirSync as mkdirSync2 } from "fs";
875
+ import { existsSync as existsSync7, readFileSync as readFileSync5, copyFileSync, mkdirSync as mkdirSync2 } from "fs";
750
876
  import { join as join9, dirname as dirname2 } from "path";
751
877
 
752
878
  // ../shared/dist/prompts/getWorkspaceRoot.js
753
- import { existsSync as existsSync7 } from "fs";
879
+ import { existsSync as existsSync6 } from "fs";
754
880
  import { dirname, resolve } from "path";
755
881
  function getWorkspaceRoot(cwd = process.cwd()) {
756
882
  const start = resolve(cwd);
757
883
  let current = start;
758
884
  while (true) {
759
885
  const gitPath = resolve(current, ".git");
760
- if (existsSync7(gitPath)) {
886
+ if (existsSync6(gitPath)) {
761
887
  return current;
762
888
  }
763
889
  const parent = dirname(current);
@@ -773,22 +899,22 @@ var WORKFLOW_PLACEHOLDER = "{WORKFLOW}";
773
899
  function loadSessionPrompt(options) {
774
900
  const { templatesDir, cwd = process.cwd() } = options;
775
901
  const workspaceRoot = getWorkspaceRoot(cwd);
776
- const corePromptPath = join9(templatesDir, "core-prompt.md");
777
- if (!existsSync8(corePromptPath)) {
902
+ const corePromptPath = join9(templatesDir, "core.prompt.md");
903
+ if (!existsSync7(corePromptPath)) {
778
904
  throw new Error(`Core prompt not found at ${corePromptPath}`);
779
905
  }
780
- const corePrompt = readFileSync4(corePromptPath, "utf-8");
781
- const customWorkflowPath = join9(workspaceRoot, ".ralph", "workflow.md");
782
- const defaultWorkflowPath = join9(templatesDir, "workflow.md");
906
+ const corePrompt = readFileSync5(corePromptPath, "utf-8");
907
+ const customWorkflowPath = join9(workspaceRoot, ".ralph", "workflow.prompt.md");
908
+ const defaultWorkflowPath = join9(templatesDir, "workflow.prompt.md");
783
909
  let workflowContent;
784
910
  let hasCustomWorkflow2;
785
911
  let workflowPath;
786
- if (existsSync8(customWorkflowPath)) {
787
- workflowContent = readFileSync4(customWorkflowPath, "utf-8");
912
+ if (existsSync7(customWorkflowPath)) {
913
+ workflowContent = readFileSync5(customWorkflowPath, "utf-8");
788
914
  hasCustomWorkflow2 = true;
789
915
  workflowPath = customWorkflowPath;
790
- } else if (existsSync8(defaultWorkflowPath)) {
791
- workflowContent = readFileSync4(defaultWorkflowPath, "utf-8");
916
+ } else if (existsSync7(defaultWorkflowPath)) {
917
+ workflowContent = readFileSync5(defaultWorkflowPath, "utf-8");
792
918
  hasCustomWorkflow2 = false;
793
919
  workflowPath = defaultWorkflowPath;
794
920
  } else {
@@ -802,15 +928,23 @@ function loadSessionPrompt(options) {
802
928
  };
803
929
  }
804
930
 
931
+ // ../shared/dist/prompts/templatesDir.js
932
+ import { dirname as dirname3, join as join10 } from "path";
933
+ import { fileURLToPath } from "url";
934
+ var TEMPLATES_DIR = join10(dirname3(fileURLToPath(import.meta.url)), "..", "..", "templates");
935
+
805
936
  // src/lib/getPromptContent.ts
806
937
  var getPromptContent = () => {
807
- const __dirname = dirname3(fileURLToPath(import.meta.url));
938
+ const __dirname = dirname4(fileURLToPath2(import.meta.url));
808
939
  const workspaceRoot = getWorkspaceRoot(process.cwd());
809
- const ralphDir6 = join10(workspaceRoot, ".ralph");
810
- const promptFile = join10(ralphDir6, "prompt.md");
811
- const templatesDir = join10(__dirname, "..", "..", "templates");
812
- if (existsSync9(promptFile)) {
813
- return readFileSync5(promptFile, "utf-8");
940
+ const ralphDir6 = join11(workspaceRoot, ".ralph");
941
+ const promptFile = join11(ralphDir6, "prompt.prompt.md");
942
+ let templatesDir = join11(__dirname, "..", "..", "templates");
943
+ if (!existsSync8(join11(templatesDir, "core.prompt.md"))) {
944
+ templatesDir = join11(__dirname, "..", "templates");
945
+ }
946
+ if (existsSync8(promptFile)) {
947
+ return readFileSync6(promptFile, "utf-8");
814
948
  }
815
949
  const { content } = loadSessionPrompt({
816
950
  templatesDir,
@@ -821,7 +955,7 @@ var getPromptContent = () => {
821
955
 
822
956
  // src/lib/sdkMessageToEvent.ts
823
957
  var sdkMessageToEvent = (message) => {
824
- if (message.type === "assistant" || message.type === "user" || message.type === "result") {
958
+ if (message.type === "assistant" || message.type === "result") {
825
959
  return message;
826
960
  }
827
961
  return null;
@@ -1167,7 +1301,7 @@ var renderStaticItem = (item) => {
1167
1301
  return /* @__PURE__ */ React4.createElement(Header, { claudeVersion: item.claudeVersion, ralphVersion: item.ralphVersion });
1168
1302
  }
1169
1303
  if (item.type === "session") {
1170
- return /* @__PURE__ */ React4.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Gradient2, { colors: ["#30A6E4", "#EBC635"] }, /* @__PURE__ */ React4.createElement(BigText2, { text: `R${item.session}`, font: "tiny" })));
1304
+ return /* @__PURE__ */ React4.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React4.createElement(Gradient2, { colors: ["#30A6E4", "#EBC635"] }, /* @__PURE__ */ React4.createElement(BigText2, { text: `R${item.session}`, font: "tiny" })), item.sessionId && /* @__PURE__ */ React4.createElement(React4.Fragment, null, /* @__PURE__ */ React4.createElement(Text4, { dimColor: true }, "session ", item.sessionId.slice(0, 8)), /* @__PURE__ */ React4.createElement(Text4, null, " ")));
1171
1305
  }
1172
1306
  const lines = formatContentBlock(item.block);
1173
1307
  return /* @__PURE__ */ React4.createElement(Box2, { flexDirection: "column", marginBottom: 1 }, lines.map((line, i) => /* @__PURE__ */ React4.createElement(Text4, { key: i }, line || " ")));
@@ -1175,8 +1309,8 @@ var renderStaticItem = (item) => {
1175
1309
 
1176
1310
  // src/components/SessionRunner.tsx
1177
1311
  var log2 = createDebugLogger("session");
1178
- var ralphDir5 = join11(process.cwd(), ".ralph");
1179
- var todoFile5 = join11(ralphDir5, "todo.md");
1312
+ var ralphDir5 = join12(process.cwd(), ".ralph");
1313
+ var todoFile5 = join12(ralphDir5, "todo.md");
1180
1314
  var repoName = basename(process.cwd());
1181
1315
  var SessionRunner = ({
1182
1316
  totalSessions,
@@ -1213,7 +1347,9 @@ var SessionRunner = ({
1213
1347
  const [isPaused, setIsPaused] = useState3(false);
1214
1348
  const isPausedRef = useRef(false);
1215
1349
  const [currentTaskId, setCurrentTaskId] = useState3(null);
1350
+ const [sessionId, setSessionId] = useState3(null);
1216
1351
  const sessionIdRef = useRef(null);
1352
+ const taskCompletedAbortRef = useRef(false);
1217
1353
  const [staticItems, setStaticItems] = useState3([
1218
1354
  { type: "header", claudeVersion, ralphVersion, key: "header" }
1219
1355
  ]);
@@ -1345,10 +1481,11 @@ var SessionRunner = ({
1345
1481
  }, [isWatching, startupSnapshot]);
1346
1482
  useEffect3(() => {
1347
1483
  const newItems = [];
1348
- if (currentSession > lastSessionRef.current) {
1484
+ if (currentSession > lastSessionRef.current && sessionId) {
1349
1485
  newItems.push({
1350
1486
  type: "session",
1351
1487
  session: currentSession,
1488
+ sessionId,
1352
1489
  key: `session-${currentSession}`
1353
1490
  });
1354
1491
  lastSessionRef.current = currentSession;
@@ -1364,7 +1501,7 @@ var SessionRunner = ({
1364
1501
  if (newItems.length > 0) {
1365
1502
  setStaticItems((prev) => [...prev, ...newItems]);
1366
1503
  }
1367
- }, [events, currentSession]);
1504
+ }, [events, currentSession, sessionId]);
1368
1505
  useEffect3(() => {
1369
1506
  if (currentSession > totalSessions) {
1370
1507
  exit();
@@ -1380,16 +1517,12 @@ var SessionRunner = ({
1380
1517
  }
1381
1518
  return;
1382
1519
  }
1383
- if (!logFileRef.current) {
1384
- logFileRef.current = getNextLogFile();
1385
- writeFileSync2(logFileRef.current, "");
1386
- }
1387
- const logFile = logFileRef.current;
1520
+ logFileRef.current = null;
1388
1521
  setEvents([]);
1389
1522
  const promptContent = getPromptContent();
1390
- const todoExists = existsSync10(todoFile5);
1523
+ const todoExists = existsSync9(todoFile5);
1391
1524
  setHasTodoFile(todoExists);
1392
- const todoContent = todoExists ? readFileSync6(todoFile5, "utf-8") : "";
1525
+ const todoContent = todoExists ? readFileSync7(todoFile5, "utf-8") : "";
1393
1526
  const roundHeader = `# Ralph, round ${currentSession}
1394
1527
 
1395
1528
  `;
@@ -1399,9 +1532,10 @@ var SessionRunner = ({
1399
1532
 
1400
1533
  ${todoContent}` : `${roundHeader}${promptContent}`;
1401
1534
  const abortController = new AbortController();
1535
+ taskCompletedAbortRef.current = false;
1402
1536
  setIsRunning(true);
1403
- const sessionId = randomUUID();
1404
- sessionIdRef.current = sessionId;
1537
+ sessionIdRef.current = null;
1538
+ setSessionId(null);
1405
1539
  const messageQueue = new MessageQueue();
1406
1540
  messageQueueRef.current = messageQueue;
1407
1541
  messageQueue.push(createUserMessage(fullPrompt));
@@ -1427,7 +1561,18 @@ ${todoContent}` : `${roundHeader}${promptContent}`;
1427
1561
  }
1428
1562
  })) {
1429
1563
  log2(`Received message type: ${message.type}`);
1430
- appendFileSync(logFile, JSON.stringify(message) + "\n");
1564
+ if (!logFileRef.current && message.type === "system" && "session_id" in message && typeof message.session_id === "string") {
1565
+ const storageDir = join12(getDefaultStorageDir(), "ralph");
1566
+ if (!existsSync9(storageDir)) {
1567
+ mkdirSync3(storageDir, { recursive: true });
1568
+ }
1569
+ logFileRef.current = join12(storageDir, `${message.session_id.slice(0, 8)}.jsonl`);
1570
+ sessionIdRef.current = message.session_id;
1571
+ setSessionId(message.session_id);
1572
+ }
1573
+ if (logFileRef.current) {
1574
+ appendFileSync(logFileRef.current, JSON.stringify(message) + "\n");
1575
+ }
1431
1576
  const event = sdkMessageToEvent(message);
1432
1577
  if (event) {
1433
1578
  setEvents((prev) => [...prev, event]);
@@ -1443,22 +1588,32 @@ ${todoContent}` : `${roundHeader}${promptContent}`;
1443
1588
  if (taskInfo.action === "starting") {
1444
1589
  setCurrentTaskId(taskInfo.taskId ?? null);
1445
1590
  log2(`Task started: ${taskInfo.taskId}`);
1446
- const taskStartedEvent = {
1447
- type: "ralph_task_started",
1448
- taskId: taskInfo.taskId,
1449
- session: currentSession,
1450
- sessionId: sessionIdRef.current
1451
- };
1452
- appendFileSync(logFile, JSON.stringify(taskStartedEvent) + "\n");
1591
+ if (logFileRef.current) {
1592
+ const taskStartedEvent = {
1593
+ type: "ralph_task_started",
1594
+ taskId: taskInfo.taskId,
1595
+ session: currentSession,
1596
+ sessionId: sessionIdRef.current
1597
+ };
1598
+ appendFileSync(logFileRef.current, JSON.stringify(taskStartedEvent) + "\n");
1599
+ }
1453
1600
  } else if (taskInfo.action === "completed") {
1454
1601
  log2(`Task completed: ${taskInfo.taskId}`);
1455
- const taskCompletedEvent = {
1456
- type: "ralph_task_completed",
1457
- taskId: taskInfo.taskId,
1458
- session: currentSession,
1459
- sessionId: sessionIdRef.current
1460
- };
1461
- appendFileSync(logFile, JSON.stringify(taskCompletedEvent) + "\n");
1602
+ if (logFileRef.current) {
1603
+ const taskCompletedEvent = {
1604
+ type: "ralph_task_completed",
1605
+ taskId: taskInfo.taskId,
1606
+ session: currentSession,
1607
+ sessionId: sessionIdRef.current
1608
+ };
1609
+ appendFileSync(
1610
+ logFileRef.current,
1611
+ JSON.stringify(taskCompletedEvent) + "\n"
1612
+ );
1613
+ }
1614
+ log2(`Aborting session after task completion to enforce session boundary`);
1615
+ taskCompletedAbortRef.current = true;
1616
+ abortController.abort();
1462
1617
  }
1463
1618
  }
1464
1619
  }
@@ -1504,6 +1659,22 @@ ${todoContent}` : `${roundHeader}${promptContent}`;
1504
1659
  messageQueue.close();
1505
1660
  messageQueueRef.current = null;
1506
1661
  if (abortController.signal.aborted) {
1662
+ if (taskCompletedAbortRef.current) {
1663
+ log2(`Session aborted after task completion \u2014 advancing to next session`);
1664
+ taskCompletedAbortRef.current = false;
1665
+ if (stopAfterCurrentRef.current) {
1666
+ log2(`Stop after current requested - exiting gracefully`);
1667
+ exit();
1668
+ process.exit(0);
1669
+ return;
1670
+ }
1671
+ if (isPausedRef.current) {
1672
+ log2(`Paused after session ${currentSession}`);
1673
+ return;
1674
+ }
1675
+ setTimeout(() => setCurrentSession((i) => i + 1), 500);
1676
+ return;
1677
+ }
1507
1678
  log2(`Abort signal detected`);
1508
1679
  return;
1509
1680
  }
@@ -1540,7 +1711,7 @@ ${todoContent}` : `${roundHeader}${promptContent}`;
1540
1711
  color: userMessageStatus.type === "success" ? "green" : userMessageStatus.type === "error" ? "red" : "yellow"
1541
1712
  },
1542
1713
  userMessageStatus.text
1543
- ), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "\u2500".repeat(columns))), /* @__PURE__ */ React5.createElement(Box3, { marginTop: 1, justifyContent: "space-between" }, isWatching ? detectedIssue ? /* @__PURE__ */ React5.createElement(Text5, { color: "green" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" }), " New issue: ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, detectedIssue.IssueID), detectedIssue.Title ? ` - ${detectedIssue.Title}` : "") : /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, "Waiting for new issues ", /* @__PURE__ */ React5.createElement(Spinner, { type: "simpleDotsScrolling" })) : isPaused && !isRunning ? /* @__PURE__ */ React5.createElement(Text5, { color: "magenta" }, "\u23F8 Paused after round ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, currentSession), " ", /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(Ctrl-P to resume)")) : isRunning ? stopAfterCurrent ? /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" }), " Stopping after round", " ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, currentSession), " completes...", " ", /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(Ctrl-S pressed)")) : isPaused ? /* @__PURE__ */ React5.createElement(Text5, { color: "magenta" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" }), " Pausing after round", " ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, currentSession), " completes...", " ", /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(Ctrl-P pressed)")) : /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" }), " Running round ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, currentSession), " ", "(max ", totalSessions, ")") : /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "simpleDotsScrolling" }), " Waiting for Ralph to start..."), progressData.type !== "none" && progressData.total > 0 && /* @__PURE__ */ React5.createElement(
1714
+ ), /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "\u2500".repeat(columns))), /* @__PURE__ */ React5.createElement(Box3, { marginTop: 1, justifyContent: "space-between" }, isWatching ? detectedIssue ? /* @__PURE__ */ React5.createElement(Text5, { color: "green" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" }), " New issue: ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, detectedIssue.IssueID), detectedIssue.Title ? ` - ${detectedIssue.Title}` : "") : /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, "Waiting for new issues ", /* @__PURE__ */ React5.createElement(Spinner, { type: "simpleDotsScrolling" })) : isPaused && !isRunning ? /* @__PURE__ */ React5.createElement(Text5, { color: "magenta" }, "\u23F8 Paused after round ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, currentSession), " ", /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(Ctrl-P to resume)")) : isRunning ? stopAfterCurrent ? /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" }), " Stopping after round", " ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, currentSession), " completes...", " ", /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(Ctrl-S pressed)")) : isPaused ? /* @__PURE__ */ React5.createElement(Text5, { color: "magenta" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" }), " Pausing after round", " ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, currentSession), " completes...", " ", /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, "(Ctrl-P pressed)")) : /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "dots" }), " Running round ", /* @__PURE__ */ React5.createElement(Text5, { color: "yellow" }, currentSession), " ", "(max ", totalSessions, ")", sessionId && /* @__PURE__ */ React5.createElement(Text5, { dimColor: true }, " ", sessionId.slice(0, 8))) : /* @__PURE__ */ React5.createElement(Text5, { color: "cyan" }, /* @__PURE__ */ React5.createElement(Spinner, { type: "simpleDotsScrolling" }), " Waiting for Ralph to start..."), progressData.type !== "none" && progressData.total > 0 && /* @__PURE__ */ React5.createElement(
1544
1715
  ProgressBar,
1545
1716
  {
1546
1717
  completed: progressData.completed,
@@ -1553,7 +1724,7 @@ ${todoContent}` : `${roundHeader}${promptContent}`;
1553
1724
  // src/components/ReplayLog.tsx
1554
1725
  import React8, { useState as useState5, useEffect as useEffect5 } from "react";
1555
1726
  import { Box as Box6, Text as Text8, useApp as useApp2 } from "ink";
1556
- import { readFileSync as readFileSync7 } from "fs";
1727
+ import { readFileSync as readFileSync8 } from "fs";
1557
1728
 
1558
1729
  // src/components/EventDisplay.tsx
1559
1730
  import React6, { useMemo, useState as useState4, useEffect as useEffect4, useRef as useRef2 } from "react";
@@ -1792,7 +1963,7 @@ var ReplayLog = ({
1792
1963
  const [error, setError] = useState5();
1793
1964
  useEffect5(() => {
1794
1965
  try {
1795
- const content = readFileSync7(filePath, "utf-8");
1966
+ const content = readFileSync8(filePath, "utf-8");
1796
1967
  const eventStrings = content.split(/\n\n+/).filter((s) => s.trim());
1797
1968
  const parsedEvents = [];
1798
1969
  for (const eventStr of eventStrings) {
@@ -1826,9 +1997,8 @@ var ReplayLog = ({
1826
1997
  // src/components/JsonOutput.tsx
1827
1998
  import React9, { useState as useState6, useEffect as useEffect6, useRef as useRef3 } from "react";
1828
1999
  import { useApp as useApp3, Text as Text9 } from "ink";
1829
- import { writeFileSync as writeFileSync3, readFileSync as readFileSync8, existsSync as existsSync11 } from "fs";
1830
- import { join as join12, basename as basename2 } from "path";
1831
- import { randomUUID as randomUUID2 } from "crypto";
2000
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
2001
+ import { join as join13, basename as basename2 } from "path";
1832
2002
  import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
1833
2003
 
1834
2004
  // src/lib/parseStdinCommand.ts
@@ -1920,7 +2090,7 @@ var outputEvent = (event) => {
1920
2090
 
1921
2091
  // src/components/JsonOutput.tsx
1922
2092
  var log5 = createDebugLogger("session");
1923
- var todoFile6 = join12(process.cwd(), ".ralph", "todo.md");
2093
+ var todoFile6 = join13(process.cwd(), ".ralph", "todo.md");
1924
2094
  var repoName2 = basename2(process.cwd());
1925
2095
  var JsonOutput = ({ totalSessions, agent }) => {
1926
2096
  const { exit } = useApp3();
@@ -1929,7 +2099,6 @@ var JsonOutput = ({ totalSessions, agent }) => {
1929
2099
  const [isRunning, setIsRunning] = useState6(false);
1930
2100
  const [startupSnapshot] = useState6(() => captureStartupSnapshot());
1931
2101
  const messageQueueRef = useRef3(null);
1932
- const logFileRef = useRef3(null);
1933
2102
  const [stopAfterCurrent, setStopAfterCurrent] = useState6(false);
1934
2103
  const stopAfterCurrentRef = useRef3(false);
1935
2104
  const [isPaused, setIsPaused] = useState6(false);
@@ -1988,13 +2157,9 @@ var JsonOutput = ({ totalSessions, agent }) => {
1988
2157
  process.exit(0);
1989
2158
  return;
1990
2159
  }
1991
- if (!logFileRef.current) {
1992
- logFileRef.current = getNextLogFile();
1993
- writeFileSync3(logFileRef.current, "");
1994
- }
1995
2160
  const promptContent = getPromptContent();
1996
- const todoExists = existsSync11(todoFile6);
1997
- const todoContent = todoExists ? readFileSync8(todoFile6, "utf-8") : "";
2161
+ const todoExists = existsSync10(todoFile6);
2162
+ const todoContent = todoExists ? readFileSync9(todoFile6, "utf-8") : "";
1998
2163
  const fullPrompt = todoContent ? `${promptContent}
1999
2164
 
2000
2165
  ## Current Todo List
@@ -2002,16 +2167,7 @@ var JsonOutput = ({ totalSessions, agent }) => {
2002
2167
  ${todoContent}` : promptContent;
2003
2168
  const abortController = new AbortController();
2004
2169
  setIsRunning(true);
2005
- const sessionId = randomUUID2();
2006
- sessionIdRef.current = sessionId;
2007
- outputEvent({
2008
- type: "ralph_session_start",
2009
- session: currentSession,
2010
- totalSessions,
2011
- repo: repoName2,
2012
- taskId: currentTaskIdRef.current,
2013
- sessionId
2014
- });
2170
+ sessionIdRef.current = null;
2015
2171
  const messageQueue = new MessageQueue();
2016
2172
  messageQueueRef.current = messageQueue;
2017
2173
  messageQueue.push(createUserMessage(fullPrompt));
@@ -2036,6 +2192,17 @@ ${todoContent}` : promptContent;
2036
2192
  }
2037
2193
  })) {
2038
2194
  log5(`Received message type: ${message.type}`);
2195
+ if (!sessionIdRef.current && message.type === "system" && "session_id" in message && typeof message.session_id === "string") {
2196
+ sessionIdRef.current = message.session_id;
2197
+ outputEvent({
2198
+ type: "ralph_session_start",
2199
+ session: currentSession,
2200
+ totalSessions,
2201
+ repo: repoName2,
2202
+ taskId: currentTaskIdRef.current,
2203
+ sessionId: message.session_id
2204
+ });
2205
+ }
2039
2206
  outputEvent(message);
2040
2207
  if (message.type === "assistant") {
2041
2208
  const assistantMessage = message.message;
@@ -2169,25 +2336,25 @@ var App = ({
2169
2336
  // src/components/InitRalph.tsx
2170
2337
  import { Text as Text10, Box as Box7 } from "ink";
2171
2338
  import React11, { useEffect as useEffect7, useState as useState7 } from "react";
2172
- import { existsSync as existsSync13, readFileSync as readFileSync9, appendFileSync as appendFileSync2, writeFileSync as writeFileSync4 } from "fs";
2173
- import { join as join14, dirname as dirname5 } from "path";
2174
- import { fileURLToPath as fileURLToPath2 } from "url";
2339
+ import { existsSync as existsSync12 } from "fs";
2340
+ import { join as join15, dirname as dirname6 } from "path";
2341
+ import { fileURLToPath as fileURLToPath3 } from "url";
2175
2342
 
2176
2343
  // src/lib/copyTemplates.ts
2177
- import { existsSync as existsSync12, mkdirSync as mkdirSync3, copyFileSync as copyFileSync2 } from "fs";
2178
- import { join as join13, dirname as dirname4 } from "path";
2344
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, copyFileSync as copyFileSync2 } from "fs";
2345
+ import { join as join14, dirname as dirname5 } from "path";
2179
2346
  function copyTemplates(templatesDir, destDir, files) {
2180
2347
  const result = { created: [], skipped: [], errors: [] };
2181
2348
  for (const { src, dest } of files) {
2182
- const srcPath = join13(templatesDir, src);
2183
- const destPath = join13(destDir, dest);
2184
- const destDirPath = dirname4(destPath);
2185
- if (!existsSync12(destDirPath)) {
2186
- mkdirSync3(destDirPath, { recursive: true });
2349
+ const srcPath = join14(templatesDir, src);
2350
+ const destPath = join14(destDir, dest);
2351
+ const destDirPath = dirname5(destPath);
2352
+ if (!existsSync11(destDirPath)) {
2353
+ mkdirSync4(destDirPath, { recursive: true });
2187
2354
  }
2188
- if (existsSync12(destPath)) {
2355
+ if (existsSync11(destPath)) {
2189
2356
  result.skipped.push(dest);
2190
- } else if (existsSync12(srcPath)) {
2357
+ } else if (existsSync11(srcPath)) {
2191
2358
  copyFileSync2(srcPath, destPath);
2192
2359
  result.created.push(dest);
2193
2360
  } else {
@@ -2199,20 +2366,20 @@ function copyTemplates(templatesDir, destDir, files) {
2199
2366
 
2200
2367
  // src/components/InitRalph.tsx
2201
2368
  function InitRalph() {
2202
- const __dirname = dirname5(fileURLToPath2(import.meta.url));
2369
+ const __dirname = dirname6(fileURLToPath3(import.meta.url));
2203
2370
  const [status, setStatus] = useState7("checking");
2204
2371
  const [createdFiles, setCreatedFiles] = useState7([]);
2205
2372
  const [skippedFiles, setSkippedFiles] = useState7([]);
2206
2373
  const [errors, setErrors] = useState7([]);
2207
2374
  useEffect7(() => {
2208
- const ralphDir6 = join14(process.cwd(), ".ralph");
2209
- const claudeDir = join14(process.cwd(), ".claude");
2210
- if (existsSync13(join14(ralphDir6, "workflow.md"))) {
2375
+ const ralphDir6 = join15(process.cwd(), ".ralph");
2376
+ const claudeDir = join15(process.cwd(), ".claude");
2377
+ if (existsSync12(join15(ralphDir6, "workflow.md"))) {
2211
2378
  setStatus("exists");
2212
2379
  return;
2213
2380
  }
2214
2381
  const initialize = async () => {
2215
- const templatesDir = join14(__dirname, "..", "..", "templates");
2382
+ const templatesDir = join15(__dirname, "..", "..", "templates");
2216
2383
  setStatus("creating");
2217
2384
  try {
2218
2385
  const allCreated = [];
@@ -2238,21 +2405,6 @@ function InitRalph() {
2238
2405
  allCreated.push(...agentsResult.created.map((f) => `.claude/${f}`));
2239
2406
  allSkipped.push(...agentsResult.skipped.map((f) => `.claude/${f}`));
2240
2407
  allErrors.push(...agentsResult.errors);
2241
- const gitignorePath = join14(process.cwd(), ".gitignore");
2242
- const eventsLogEntry = ".ralph/events-*.jsonl";
2243
- if (existsSync13(gitignorePath)) {
2244
- const content = readFileSync9(gitignorePath, "utf-8");
2245
- if (!content.includes(eventsLogEntry)) {
2246
- const newline = content.endsWith("\n") ? "" : "\n";
2247
- appendFileSync2(gitignorePath, `${newline}${eventsLogEntry}
2248
- `);
2249
- allCreated.push("(added .ralph/events-*.jsonl to .gitignore)");
2250
- }
2251
- } else {
2252
- writeFileSync4(gitignorePath, `${eventsLogEntry}
2253
- `);
2254
- allCreated.push("(created .gitignore with .ralph/events-*.jsonl)");
2255
- }
2256
2408
  setCreatedFiles(allCreated);
2257
2409
  setSkippedFiles(allSkipped);
2258
2410
  setErrors(allErrors);
@@ -2319,20 +2471,17 @@ var getDefaultSessions = () => {
2319
2471
  };
2320
2472
 
2321
2473
  // src/lib/getLatestLogFile.ts
2322
- import { join as join15 } from "path";
2323
2474
  var getLatestLogFile = () => {
2324
- const maxNumber = findMaxLogNumber();
2325
- if (maxNumber === 0) {
2326
- return void 0;
2327
- }
2328
- const ralphDir6 = join15(process.cwd(), ".ralph");
2329
- return join15(ralphDir6, `events-${maxNumber}.jsonl`);
2475
+ const persister = new SessionPersister(getDefaultStorageDir());
2476
+ const latestId = persister.getLatestSessionId("ralph");
2477
+ if (!latestId) return void 0;
2478
+ return persister.getSessionPath(latestId, "ralph");
2330
2479
  };
2331
2480
 
2332
2481
  // package.json
2333
2482
  var package_default = {
2334
2483
  name: "@herbcaudill/ralph",
2335
- version: "1.1.0",
2484
+ version: "1.2.0",
2336
2485
  description: "Autonomous AI session engine for Claude CLI",
2337
2486
  type: "module",
2338
2487
  main: "./dist/index.js",
@@ -2351,7 +2500,7 @@ var package_default = {
2351
2500
  typecheck: "tsc --noEmit",
2352
2501
  ralph: "tsx src/index.ts",
2353
2502
  test: "vitest run",
2354
- "test:e2e": "vitest --config vitest.e2e.config.ts",
2503
+ "test:pw": "vitest --config vitest.e2e.config.ts",
2355
2504
  "test:watch": "vitest --watch",
2356
2505
  "test:ui": "vitest --ui",
2357
2506
  format: "prettier --write . --log-level silent",
@@ -2372,7 +2521,7 @@ var package_default = {
2372
2521
  url: "https://github.com/HerbCaudill/ralph.git"
2373
2522
  },
2374
2523
  dependencies: {
2375
- "@anthropic-ai/claude-agent-sdk": "^0.2.7",
2524
+ "@anthropic-ai/claude-agent-sdk": "^0.2.29",
2376
2525
  chalk: "^5.6.2",
2377
2526
  commander: "^14.0.2",
2378
2527
  ink: "^6.6.0",