@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/README.md +3 -3
- package/dist/index.js +348 -199
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
- package/templates/skills/manage-tasks/SKILL.md +18 -9
- package/templates/core-prompt.md +0 -61
- package/templates/workflow.md +0 -32
package/dist/index.js
CHANGED
|
@@ -134,15 +134,183 @@ var EnhancedTextInput = ({
|
|
|
134
134
|
};
|
|
135
135
|
|
|
136
136
|
// src/components/SessionRunner.tsx
|
|
137
|
-
import { appendFileSync,
|
|
138
|
-
import { join as
|
|
139
|
-
|
|
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 =
|
|
169
|
-
const content =
|
|
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
|
|
201
|
-
import { join as
|
|
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
|
|
239
|
-
import { join as
|
|
240
|
-
var ralphDir =
|
|
241
|
-
var todoFile =
|
|
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 =
|
|
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 =
|
|
258
|
-
var ralphDir2 =
|
|
259
|
-
var todoFile2 =
|
|
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 (
|
|
429
|
+
if (existsSync3(beadsDir)) {
|
|
262
430
|
return getBeadsProgress(initialCount, startupTimestamp);
|
|
263
431
|
}
|
|
264
|
-
if (
|
|
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
|
|
272
|
-
import { join as
|
|
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
|
|
305
|
-
import { join as
|
|
306
|
-
var ralphDir3 =
|
|
307
|
-
var todoFile3 =
|
|
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 =
|
|
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 =
|
|
327
|
-
var ralphDir4 =
|
|
328
|
-
var todoFile4 =
|
|
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 (
|
|
498
|
+
if (existsSync4(beadsDir2)) {
|
|
331
499
|
return captureBeadsSnapshot();
|
|
332
500
|
}
|
|
333
|
-
if (
|
|
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
|
|
360
|
-
import { existsSync as
|
|
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 =
|
|
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
|
|
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
|
|
745
|
-
import { join as
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
777
|
-
if (!
|
|
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 =
|
|
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 (
|
|
787
|
-
workflowContent =
|
|
912
|
+
if (existsSync7(customWorkflowPath)) {
|
|
913
|
+
workflowContent = readFileSync5(customWorkflowPath, "utf-8");
|
|
788
914
|
hasCustomWorkflow2 = true;
|
|
789
915
|
workflowPath = customWorkflowPath;
|
|
790
|
-
} else if (
|
|
791
|
-
workflowContent =
|
|
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 =
|
|
938
|
+
const __dirname = dirname4(fileURLToPath2(import.meta.url));
|
|
808
939
|
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
809
|
-
const ralphDir6 =
|
|
810
|
-
const promptFile =
|
|
811
|
-
|
|
812
|
-
if (
|
|
813
|
-
|
|
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 === "
|
|
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 =
|
|
1179
|
-
var todoFile5 =
|
|
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
|
-
|
|
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 =
|
|
1523
|
+
const todoExists = existsSync9(todoFile5);
|
|
1391
1524
|
setHasTodoFile(todoExists);
|
|
1392
|
-
const todoContent = todoExists ?
|
|
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
|
-
|
|
1404
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
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
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
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
|
|
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 =
|
|
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 {
|
|
1830
|
-
import { join as
|
|
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 =
|
|
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 =
|
|
1997
|
-
const todoContent = todoExists ?
|
|
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
|
-
|
|
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
|
|
2173
|
-
import { join as
|
|
2174
|
-
import { fileURLToPath as
|
|
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
|
|
2178
|
-
import { join as
|
|
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 =
|
|
2183
|
-
const destPath =
|
|
2184
|
-
const destDirPath =
|
|
2185
|
-
if (!
|
|
2186
|
-
|
|
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 (
|
|
2355
|
+
if (existsSync11(destPath)) {
|
|
2189
2356
|
result.skipped.push(dest);
|
|
2190
|
-
} else if (
|
|
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 =
|
|
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 =
|
|
2209
|
-
const claudeDir =
|
|
2210
|
-
if (
|
|
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 =
|
|
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
|
|
2325
|
-
|
|
2326
|
-
|
|
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.
|
|
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:
|
|
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.
|
|
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",
|