@herbcaudill/ralph 1.0.2 → 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 +93 -0
- package/dist/index.js +488 -260
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/templates/skills/manage-tasks/SKILL.md +18 -12
- package/templates/core-prompt.md +0 -77
- package/templates/workflow.md +0 -46
package/dist/index.js
CHANGED
|
@@ -134,14 +134,183 @@ var EnhancedTextInput = ({
|
|
|
134
134
|
};
|
|
135
135
|
|
|
136
136
|
// src/components/SessionRunner.tsx
|
|
137
|
-
import { appendFileSync,
|
|
138
|
-
import { join as
|
|
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
|
|
139
308
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
140
309
|
|
|
141
310
|
// src/lib/addTodo.ts
|
|
142
311
|
import { execSync } from "child_process";
|
|
143
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
144
|
-
import { join } from "path";
|
|
312
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
313
|
+
import { join as join3 } from "path";
|
|
145
314
|
|
|
146
315
|
// src/lib/insertTodo.ts
|
|
147
316
|
var insertTodo = (content, description) => {
|
|
@@ -164,8 +333,8 @@ ${content}`;
|
|
|
164
333
|
|
|
165
334
|
// src/lib/addTodo.ts
|
|
166
335
|
var addTodo = (description, cwd = process.cwd()) => {
|
|
167
|
-
const todoPath =
|
|
168
|
-
const content =
|
|
336
|
+
const todoPath = join3(cwd, ".ralph", "todo.md");
|
|
337
|
+
const content = existsSync2(todoPath) ? readFileSync2(todoPath, "utf-8") : "";
|
|
169
338
|
const newContent = insertTodo(content, description);
|
|
170
339
|
writeFileSync(todoPath, newContent);
|
|
171
340
|
let indexContent = "";
|
|
@@ -196,8 +365,8 @@ var addTodo = (description, cwd = process.cwd()) => {
|
|
|
196
365
|
};
|
|
197
366
|
|
|
198
367
|
// src/lib/getProgress.ts
|
|
199
|
-
import { existsSync as
|
|
200
|
-
import { join as
|
|
368
|
+
import { existsSync as existsSync3 } from "fs";
|
|
369
|
+
import { join as join5 } from "path";
|
|
201
370
|
|
|
202
371
|
// src/lib/getBeadsProgress.ts
|
|
203
372
|
import { execSync as execSync2 } from "child_process";
|
|
@@ -234,13 +403,13 @@ var getBeadsProgress = (initialCount, startupTimestamp) => {
|
|
|
234
403
|
};
|
|
235
404
|
|
|
236
405
|
// src/lib/getTodoProgress.ts
|
|
237
|
-
import { readFileSync as
|
|
238
|
-
import { join as
|
|
239
|
-
var ralphDir =
|
|
240
|
-
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");
|
|
241
410
|
var getTodoProgress = () => {
|
|
242
411
|
try {
|
|
243
|
-
const content =
|
|
412
|
+
const content = readFileSync3(todoFile, "utf-8");
|
|
244
413
|
const uncheckedMatches = content.match(/- \[ \]/g);
|
|
245
414
|
const unchecked = uncheckedMatches ? uncheckedMatches.length : 0;
|
|
246
415
|
const checkedMatches = content.match(/- \[[xX]\]/g);
|
|
@@ -253,22 +422,22 @@ var getTodoProgress = () => {
|
|
|
253
422
|
};
|
|
254
423
|
|
|
255
424
|
// src/lib/getProgress.ts
|
|
256
|
-
var beadsDir =
|
|
257
|
-
var ralphDir2 =
|
|
258
|
-
var todoFile2 =
|
|
425
|
+
var beadsDir = join5(process.cwd(), ".beads");
|
|
426
|
+
var ralphDir2 = join5(process.cwd(), ".ralph");
|
|
427
|
+
var todoFile2 = join5(ralphDir2, "todo.md");
|
|
259
428
|
var getProgress = (initialCount, startupTimestamp) => {
|
|
260
|
-
if (
|
|
429
|
+
if (existsSync3(beadsDir)) {
|
|
261
430
|
return getBeadsProgress(initialCount, startupTimestamp);
|
|
262
431
|
}
|
|
263
|
-
if (
|
|
432
|
+
if (existsSync3(todoFile2)) {
|
|
264
433
|
return getTodoProgress();
|
|
265
434
|
}
|
|
266
435
|
return { type: "none", completed: 0, total: 0 };
|
|
267
436
|
};
|
|
268
437
|
|
|
269
438
|
// src/lib/captureStartupSnapshot.ts
|
|
270
|
-
import { existsSync as
|
|
271
|
-
import { join as
|
|
439
|
+
import { existsSync as existsSync4 } from "fs";
|
|
440
|
+
import { join as join7 } from "path";
|
|
272
441
|
|
|
273
442
|
// src/lib/captureBeadsSnapshot.ts
|
|
274
443
|
import { execSync as execSync3 } from "child_process";
|
|
@@ -300,13 +469,13 @@ var captureBeadsSnapshot = () => {
|
|
|
300
469
|
};
|
|
301
470
|
|
|
302
471
|
// src/lib/captureTodoSnapshot.ts
|
|
303
|
-
import { readFileSync as
|
|
304
|
-
import { join as
|
|
305
|
-
var ralphDir3 =
|
|
306
|
-
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");
|
|
307
476
|
var captureTodoSnapshot = () => {
|
|
308
477
|
try {
|
|
309
|
-
const content =
|
|
478
|
+
const content = readFileSync4(todoFile3, "utf-8");
|
|
310
479
|
const uncheckedMatches = content.match(/- \[ \]/g);
|
|
311
480
|
const unchecked = uncheckedMatches ? uncheckedMatches.length : 0;
|
|
312
481
|
const checkedMatches = content.match(/- \[[xX]\]/g);
|
|
@@ -322,14 +491,14 @@ var captureTodoSnapshot = () => {
|
|
|
322
491
|
};
|
|
323
492
|
|
|
324
493
|
// src/lib/captureStartupSnapshot.ts
|
|
325
|
-
var beadsDir2 =
|
|
326
|
-
var ralphDir4 =
|
|
327
|
-
var todoFile4 =
|
|
494
|
+
var beadsDir2 = join7(process.cwd(), ".beads");
|
|
495
|
+
var ralphDir4 = join7(process.cwd(), ".ralph");
|
|
496
|
+
var todoFile4 = join7(ralphDir4, "todo.md");
|
|
328
497
|
var captureStartupSnapshot = () => {
|
|
329
|
-
if (
|
|
498
|
+
if (existsSync4(beadsDir2)) {
|
|
330
499
|
return captureBeadsSnapshot();
|
|
331
500
|
}
|
|
332
|
-
if (
|
|
501
|
+
if (existsSync4(todoFile4)) {
|
|
333
502
|
return captureTodoSnapshot();
|
|
334
503
|
}
|
|
335
504
|
return void 0;
|
|
@@ -350,54 +519,66 @@ var ProgressBar = ({ completed, total, width = 12, repoName: repoName3 }) => {
|
|
|
350
519
|
return /* @__PURE__ */ React2.createElement(Text2, null, repoName3 && /* @__PURE__ */ React2.createElement(React2.Fragment, null, /* @__PURE__ */ React2.createElement(Text2, { color: "cyan" }, repoName3), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " \u2502 ")), /* @__PURE__ */ React2.createElement(Text2, { color: "yellow" }, filled), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, empty), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " ", completed, "/", total, " "));
|
|
351
520
|
};
|
|
352
521
|
|
|
353
|
-
//
|
|
522
|
+
// ../../../beads-sdk/dist/exec.js
|
|
523
|
+
import { spawn } from "child_process";
|
|
524
|
+
|
|
525
|
+
// ../../../beads-sdk/dist/socket.js
|
|
354
526
|
import { createConnection } from "net";
|
|
355
|
-
import { join as
|
|
356
|
-
import { existsSync as
|
|
357
|
-
var
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
527
|
+
import { join as join8 } from "path";
|
|
528
|
+
import { existsSync as existsSync5 } from "fs";
|
|
529
|
+
var DaemonSocket = class {
|
|
530
|
+
constructor(options = {}) {
|
|
531
|
+
this.socket = null;
|
|
532
|
+
this.connected = false;
|
|
533
|
+
const cwd = options.cwd ?? process.cwd();
|
|
534
|
+
this.socketPath = join8(cwd, ".beads", "bd.sock");
|
|
535
|
+
this.connectTimeout = options.connectTimeout ?? 2e3;
|
|
536
|
+
this.requestTimeout = options.requestTimeout ?? 5e3;
|
|
537
|
+
}
|
|
538
|
+
/** Check if the beads daemon socket file exists. */
|
|
539
|
+
socketExists() {
|
|
540
|
+
return existsSync5(this.socketPath);
|
|
541
|
+
}
|
|
542
|
+
/** Check if connected to the daemon. */
|
|
543
|
+
get isConnected() {
|
|
544
|
+
return this.connected && this.socket !== null;
|
|
545
|
+
}
|
|
546
|
+
/** Connect to the beads daemon. Returns true if connected, false otherwise. */
|
|
370
547
|
async connect() {
|
|
371
|
-
if (!
|
|
548
|
+
if (!this.socketExists())
|
|
372
549
|
return false;
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
550
|
+
if (this.connected && this.socket)
|
|
551
|
+
return true;
|
|
552
|
+
return new Promise((resolve2) => {
|
|
553
|
+
this.socket = createConnection(this.socketPath);
|
|
376
554
|
const timeout = setTimeout(() => {
|
|
377
555
|
this.socket?.destroy();
|
|
378
556
|
this.socket = null;
|
|
379
|
-
|
|
380
|
-
},
|
|
557
|
+
resolve2(false);
|
|
558
|
+
}, this.connectTimeout);
|
|
381
559
|
this.socket.on("connect", () => {
|
|
382
560
|
clearTimeout(timeout);
|
|
383
561
|
this.connected = true;
|
|
384
|
-
|
|
562
|
+
resolve2(true);
|
|
385
563
|
});
|
|
386
564
|
this.socket.on("error", () => {
|
|
387
565
|
clearTimeout(timeout);
|
|
388
566
|
this.socket = null;
|
|
389
|
-
|
|
567
|
+
this.connected = false;
|
|
568
|
+
resolve2(false);
|
|
569
|
+
});
|
|
570
|
+
this.socket.on("close", () => {
|
|
571
|
+
this.socket = null;
|
|
572
|
+
this.connected = false;
|
|
390
573
|
});
|
|
391
574
|
});
|
|
392
575
|
}
|
|
393
|
-
/**
|
|
394
|
-
* Send an RPC request and wait for response.
|
|
395
|
-
*/
|
|
576
|
+
/** Send an RPC request and wait for response. */
|
|
396
577
|
async execute(operation, args = {}) {
|
|
397
578
|
if (!this.socket || !this.connected) {
|
|
398
579
|
return null;
|
|
399
580
|
}
|
|
400
|
-
return new Promise((
|
|
581
|
+
return new Promise((resolve2) => {
|
|
401
582
|
const request = { operation, args };
|
|
402
583
|
const requestLine = JSON.stringify(request) + "\n";
|
|
403
584
|
let responseData = "";
|
|
@@ -407,24 +588,24 @@ var BeadsClient = class _BeadsClient {
|
|
|
407
588
|
cleanup();
|
|
408
589
|
try {
|
|
409
590
|
const response = JSON.parse(responseData.trim());
|
|
410
|
-
if (response.success && response.data) {
|
|
411
|
-
|
|
591
|
+
if (response.success && response.data !== void 0) {
|
|
592
|
+
resolve2(response.data);
|
|
412
593
|
} else {
|
|
413
|
-
|
|
594
|
+
resolve2(null);
|
|
414
595
|
}
|
|
415
596
|
} catch {
|
|
416
|
-
|
|
597
|
+
resolve2(null);
|
|
417
598
|
}
|
|
418
599
|
}
|
|
419
600
|
};
|
|
420
601
|
const onError = () => {
|
|
421
602
|
cleanup();
|
|
422
|
-
|
|
603
|
+
resolve2(null);
|
|
423
604
|
};
|
|
424
605
|
const timeout = setTimeout(() => {
|
|
425
606
|
cleanup();
|
|
426
|
-
|
|
427
|
-
},
|
|
607
|
+
resolve2(null);
|
|
608
|
+
}, this.requestTimeout);
|
|
428
609
|
const cleanup = () => {
|
|
429
610
|
clearTimeout(timeout);
|
|
430
611
|
this.socket?.off("data", onData);
|
|
@@ -437,21 +618,18 @@ var BeadsClient = class _BeadsClient {
|
|
|
437
618
|
}
|
|
438
619
|
/**
|
|
439
620
|
* Get mutations since a given timestamp.
|
|
621
|
+
* Returns all mutation events that occurred after the specified timestamp.
|
|
440
622
|
*/
|
|
441
623
|
async getMutations(since = 0) {
|
|
442
624
|
const result = await this.execute("get_mutations", { since });
|
|
443
625
|
return result ?? [];
|
|
444
626
|
}
|
|
445
|
-
/**
|
|
446
|
-
* Get ready issues (no blockers).
|
|
447
|
-
*/
|
|
627
|
+
/** Get ready issues (open and unblocked). */
|
|
448
628
|
async getReady() {
|
|
449
629
|
const result = await this.execute("ready", {});
|
|
450
630
|
return result ?? [];
|
|
451
631
|
}
|
|
452
|
-
/**
|
|
453
|
-
* Close the connection.
|
|
454
|
-
*/
|
|
632
|
+
/** Close the connection to the daemon. */
|
|
455
633
|
close() {
|
|
456
634
|
if (this.socket) {
|
|
457
635
|
this.socket.destroy();
|
|
@@ -460,50 +638,63 @@ var BeadsClient = class _BeadsClient {
|
|
|
460
638
|
}
|
|
461
639
|
}
|
|
462
640
|
};
|
|
463
|
-
function
|
|
464
|
-
|
|
641
|
+
function watchMutations(onMutation, options = {}) {
|
|
642
|
+
const { cwd, interval = 1e3, since } = options;
|
|
643
|
+
let lastTimestamp = since ?? Date.now();
|
|
465
644
|
let client = null;
|
|
466
645
|
let timeoutId = null;
|
|
467
646
|
let stopped = false;
|
|
468
647
|
const poll = async () => {
|
|
469
|
-
if (stopped)
|
|
648
|
+
if (stopped)
|
|
649
|
+
return;
|
|
470
650
|
if (!client) {
|
|
471
|
-
client = new
|
|
651
|
+
client = new DaemonSocket({ cwd });
|
|
472
652
|
const connected = await client.connect();
|
|
473
653
|
if (!connected) {
|
|
474
|
-
|
|
654
|
+
if (!stopped)
|
|
655
|
+
timeoutId = setTimeout(poll, interval);
|
|
475
656
|
return;
|
|
476
657
|
}
|
|
477
658
|
}
|
|
478
659
|
try {
|
|
479
660
|
const mutations = await client.getMutations(lastTimestamp);
|
|
480
661
|
for (const mutation of mutations) {
|
|
481
|
-
|
|
482
|
-
onNewIssue(mutation);
|
|
483
|
-
}
|
|
662
|
+
onMutation(mutation);
|
|
484
663
|
const mutationTime = new Date(mutation.Timestamp).getTime();
|
|
485
|
-
if (mutationTime > lastTimestamp)
|
|
664
|
+
if (mutationTime > lastTimestamp)
|
|
486
665
|
lastTimestamp = mutationTime;
|
|
487
|
-
}
|
|
488
666
|
}
|
|
489
667
|
} catch {
|
|
490
668
|
client?.close();
|
|
491
669
|
client = null;
|
|
492
670
|
}
|
|
493
|
-
if (!stopped)
|
|
671
|
+
if (!stopped)
|
|
494
672
|
timeoutId = setTimeout(poll, interval);
|
|
495
|
-
}
|
|
496
673
|
};
|
|
497
674
|
poll();
|
|
498
675
|
return () => {
|
|
499
676
|
stopped = true;
|
|
500
677
|
if (timeoutId) {
|
|
501
678
|
clearTimeout(timeoutId);
|
|
679
|
+
timeoutId = null;
|
|
502
680
|
}
|
|
503
681
|
client?.close();
|
|
682
|
+
client = null;
|
|
504
683
|
};
|
|
505
684
|
}
|
|
506
685
|
|
|
686
|
+
// src/lib/beadsClient.ts
|
|
687
|
+
function watchForNewIssues(onNewIssue, interval = 5e3) {
|
|
688
|
+
return watchMutations(
|
|
689
|
+
(event) => {
|
|
690
|
+
if (event.Type === "create") {
|
|
691
|
+
onNewIssue(event);
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
{ interval }
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
|
|
507
698
|
// src/lib/debug.ts
|
|
508
699
|
var isDebugEnabled = (namespace) => {
|
|
509
700
|
const debugEnv = process.env.RALPH_DEBUG;
|
|
@@ -561,9 +752,9 @@ var MessageQueue = class {
|
|
|
561
752
|
return;
|
|
562
753
|
}
|
|
563
754
|
if (this.resolvers.length > 0) {
|
|
564
|
-
const
|
|
755
|
+
const resolve2 = this.resolvers.shift();
|
|
565
756
|
log(`push() resolving pending next() call (${this.resolvers.length} resolvers remaining)`);
|
|
566
|
-
|
|
757
|
+
resolve2({ value: message, done: false });
|
|
567
758
|
} else {
|
|
568
759
|
this.queue.push(message);
|
|
569
760
|
log(`push() added to queue (queue length: ${this.queue.length})`);
|
|
@@ -579,9 +770,9 @@ var MessageQueue = class {
|
|
|
579
770
|
}
|
|
580
771
|
log(`close() called - resolving ${this.resolvers.length} pending resolvers`);
|
|
581
772
|
this.closed = true;
|
|
582
|
-
for (const
|
|
773
|
+
for (const resolve2 of this.resolvers) {
|
|
583
774
|
log(`close() resolving pending resolver with done=true`);
|
|
584
|
-
|
|
775
|
+
resolve2({ value: void 0, done: true });
|
|
585
776
|
}
|
|
586
777
|
this.resolvers = [];
|
|
587
778
|
log(`close() complete`);
|
|
@@ -620,8 +811,8 @@ var MessageQueue = class {
|
|
|
620
811
|
log(
|
|
621
812
|
`next() #${callId}: queue empty, creating pending resolver (${this.resolvers.length + 1} total)`
|
|
622
813
|
);
|
|
623
|
-
return new Promise((
|
|
624
|
-
this.resolvers.push(
|
|
814
|
+
return new Promise((resolve2) => {
|
|
815
|
+
this.resolvers.push(resolve2);
|
|
625
816
|
});
|
|
626
817
|
}
|
|
627
818
|
};
|
|
@@ -656,43 +847,6 @@ var useTerminalSize = () => {
|
|
|
656
847
|
return size;
|
|
657
848
|
};
|
|
658
849
|
|
|
659
|
-
// src/lib/getNextLogFile.ts
|
|
660
|
-
import { existsSync as existsSync6, mkdirSync } from "fs";
|
|
661
|
-
import { join as join8 } from "path";
|
|
662
|
-
|
|
663
|
-
// src/lib/findMaxLogNumber.ts
|
|
664
|
-
import { readdirSync, existsSync as existsSync5 } from "fs";
|
|
665
|
-
import { join as join7 } from "path";
|
|
666
|
-
var EVENT_LOG_PATTERN = /^events-(\d+)\.jsonl$/;
|
|
667
|
-
var findMaxLogNumber = () => {
|
|
668
|
-
const ralphDir6 = join7(process.cwd(), ".ralph");
|
|
669
|
-
if (!existsSync5(ralphDir6)) {
|
|
670
|
-
return 0;
|
|
671
|
-
}
|
|
672
|
-
const files = readdirSync(ralphDir6);
|
|
673
|
-
let maxNumber = 0;
|
|
674
|
-
for (const file of files) {
|
|
675
|
-
const match = file.match(EVENT_LOG_PATTERN);
|
|
676
|
-
if (match) {
|
|
677
|
-
const num = parseInt(match[1], 10);
|
|
678
|
-
if (num > maxNumber) {
|
|
679
|
-
maxNumber = num;
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
|
-
return maxNumber;
|
|
684
|
-
};
|
|
685
|
-
|
|
686
|
-
// src/lib/getNextLogFile.ts
|
|
687
|
-
var getNextLogFile = () => {
|
|
688
|
-
const ralphDir6 = join8(process.cwd(), ".ralph");
|
|
689
|
-
if (!existsSync6(ralphDir6)) {
|
|
690
|
-
mkdirSync(ralphDir6, { recursive: true });
|
|
691
|
-
}
|
|
692
|
-
const maxNumber = findMaxLogNumber();
|
|
693
|
-
return join8(ralphDir6, `events-${maxNumber + 1}.jsonl`);
|
|
694
|
-
};
|
|
695
|
-
|
|
696
850
|
// src/lib/parseTaskLifecycle.ts
|
|
697
851
|
function parseTaskLifecycleEvent(text) {
|
|
698
852
|
const startingMatch = text.match(/<start_task>([a-z]+-[a-z0-9]+(?:\.[a-z0-9]+)*)<\/start_task>/i);
|
|
@@ -713,31 +867,95 @@ function parseTaskLifecycleEvent(text) {
|
|
|
713
867
|
}
|
|
714
868
|
|
|
715
869
|
// src/lib/getPromptContent.ts
|
|
716
|
-
import {
|
|
717
|
-
import { join as
|
|
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";
|
|
873
|
+
|
|
874
|
+
// ../shared/dist/prompts/loadPrompt.js
|
|
875
|
+
import { existsSync as existsSync7, readFileSync as readFileSync5, copyFileSync, mkdirSync as mkdirSync2 } from "fs";
|
|
876
|
+
import { join as join9, dirname as dirname2 } from "path";
|
|
877
|
+
|
|
878
|
+
// ../shared/dist/prompts/getWorkspaceRoot.js
|
|
879
|
+
import { existsSync as existsSync6 } from "fs";
|
|
880
|
+
import { dirname, resolve } from "path";
|
|
881
|
+
function getWorkspaceRoot(cwd = process.cwd()) {
|
|
882
|
+
const start = resolve(cwd);
|
|
883
|
+
let current = start;
|
|
884
|
+
while (true) {
|
|
885
|
+
const gitPath = resolve(current, ".git");
|
|
886
|
+
if (existsSync6(gitPath)) {
|
|
887
|
+
return current;
|
|
888
|
+
}
|
|
889
|
+
const parent = dirname(current);
|
|
890
|
+
if (parent === current) {
|
|
891
|
+
return start;
|
|
892
|
+
}
|
|
893
|
+
current = parent;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// ../shared/dist/prompts/loadPrompt.js
|
|
898
|
+
var WORKFLOW_PLACEHOLDER = "{WORKFLOW}";
|
|
899
|
+
function loadSessionPrompt(options) {
|
|
900
|
+
const { templatesDir, cwd = process.cwd() } = options;
|
|
901
|
+
const workspaceRoot = getWorkspaceRoot(cwd);
|
|
902
|
+
const corePromptPath = join9(templatesDir, "core.prompt.md");
|
|
903
|
+
if (!existsSync7(corePromptPath)) {
|
|
904
|
+
throw new Error(`Core prompt not found at ${corePromptPath}`);
|
|
905
|
+
}
|
|
906
|
+
const corePrompt = readFileSync5(corePromptPath, "utf-8");
|
|
907
|
+
const customWorkflowPath = join9(workspaceRoot, ".ralph", "workflow.prompt.md");
|
|
908
|
+
const defaultWorkflowPath = join9(templatesDir, "workflow.prompt.md");
|
|
909
|
+
let workflowContent;
|
|
910
|
+
let hasCustomWorkflow2;
|
|
911
|
+
let workflowPath;
|
|
912
|
+
if (existsSync7(customWorkflowPath)) {
|
|
913
|
+
workflowContent = readFileSync5(customWorkflowPath, "utf-8");
|
|
914
|
+
hasCustomWorkflow2 = true;
|
|
915
|
+
workflowPath = customWorkflowPath;
|
|
916
|
+
} else if (existsSync7(defaultWorkflowPath)) {
|
|
917
|
+
workflowContent = readFileSync5(defaultWorkflowPath, "utf-8");
|
|
918
|
+
hasCustomWorkflow2 = false;
|
|
919
|
+
workflowPath = defaultWorkflowPath;
|
|
920
|
+
} else {
|
|
921
|
+
throw new Error(`Workflow file not found at ${customWorkflowPath} or ${defaultWorkflowPath}`);
|
|
922
|
+
}
|
|
923
|
+
const content = corePrompt.replace(WORKFLOW_PLACEHOLDER, workflowContent);
|
|
924
|
+
return {
|
|
925
|
+
content,
|
|
926
|
+
hasCustomWorkflow: hasCustomWorkflow2,
|
|
927
|
+
workflowPath
|
|
928
|
+
};
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// ../shared/dist/prompts/templatesDir.js
|
|
932
|
+
import { dirname as dirname3, join as join10 } from "path";
|
|
718
933
|
import { fileURLToPath } from "url";
|
|
934
|
+
var TEMPLATES_DIR = join10(dirname3(fileURLToPath(import.meta.url)), "..", "..", "templates");
|
|
935
|
+
|
|
936
|
+
// src/lib/getPromptContent.ts
|
|
719
937
|
var getPromptContent = () => {
|
|
720
|
-
const __dirname =
|
|
721
|
-
const
|
|
722
|
-
const
|
|
723
|
-
const
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
const
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
}
|
|
735
|
-
return
|
|
938
|
+
const __dirname = dirname4(fileURLToPath2(import.meta.url));
|
|
939
|
+
const workspaceRoot = getWorkspaceRoot(process.cwd());
|
|
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");
|
|
948
|
+
}
|
|
949
|
+
const { content } = loadSessionPrompt({
|
|
950
|
+
templatesDir,
|
|
951
|
+
cwd: workspaceRoot
|
|
952
|
+
});
|
|
953
|
+
return content;
|
|
736
954
|
};
|
|
737
955
|
|
|
738
956
|
// src/lib/sdkMessageToEvent.ts
|
|
739
957
|
var sdkMessageToEvent = (message) => {
|
|
740
|
-
if (message.type === "assistant" || message.type === "
|
|
958
|
+
if (message.type === "assistant" || message.type === "result") {
|
|
741
959
|
return message;
|
|
742
960
|
}
|
|
743
961
|
return null;
|
|
@@ -1083,7 +1301,7 @@ var renderStaticItem = (item) => {
|
|
|
1083
1301
|
return /* @__PURE__ */ React4.createElement(Header, { claudeVersion: item.claudeVersion, ralphVersion: item.ralphVersion });
|
|
1084
1302
|
}
|
|
1085
1303
|
if (item.type === "session") {
|
|
1086
|
-
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, " ")));
|
|
1087
1305
|
}
|
|
1088
1306
|
const lines = formatContentBlock(item.block);
|
|
1089
1307
|
return /* @__PURE__ */ React4.createElement(Box2, { flexDirection: "column", marginBottom: 1 }, lines.map((line, i) => /* @__PURE__ */ React4.createElement(Text4, { key: i }, line || " ")));
|
|
@@ -1091,8 +1309,8 @@ var renderStaticItem = (item) => {
|
|
|
1091
1309
|
|
|
1092
1310
|
// src/components/SessionRunner.tsx
|
|
1093
1311
|
var log2 = createDebugLogger("session");
|
|
1094
|
-
var ralphDir5 =
|
|
1095
|
-
var todoFile5 =
|
|
1312
|
+
var ralphDir5 = join12(process.cwd(), ".ralph");
|
|
1313
|
+
var todoFile5 = join12(ralphDir5, "todo.md");
|
|
1096
1314
|
var repoName = basename(process.cwd());
|
|
1097
1315
|
var SessionRunner = ({
|
|
1098
1316
|
totalSessions,
|
|
@@ -1129,7 +1347,9 @@ var SessionRunner = ({
|
|
|
1129
1347
|
const [isPaused, setIsPaused] = useState3(false);
|
|
1130
1348
|
const isPausedRef = useRef(false);
|
|
1131
1349
|
const [currentTaskId, setCurrentTaskId] = useState3(null);
|
|
1132
|
-
const [
|
|
1350
|
+
const [sessionId, setSessionId] = useState3(null);
|
|
1351
|
+
const sessionIdRef = useRef(null);
|
|
1352
|
+
const taskCompletedAbortRef = useRef(false);
|
|
1133
1353
|
const [staticItems, setStaticItems] = useState3([
|
|
1134
1354
|
{ type: "header", claudeVersion, ralphVersion, key: "header" }
|
|
1135
1355
|
]);
|
|
@@ -1261,10 +1481,11 @@ var SessionRunner = ({
|
|
|
1261
1481
|
}, [isWatching, startupSnapshot]);
|
|
1262
1482
|
useEffect3(() => {
|
|
1263
1483
|
const newItems = [];
|
|
1264
|
-
if (currentSession > lastSessionRef.current) {
|
|
1484
|
+
if (currentSession > lastSessionRef.current && sessionId) {
|
|
1265
1485
|
newItems.push({
|
|
1266
1486
|
type: "session",
|
|
1267
1487
|
session: currentSession,
|
|
1488
|
+
sessionId,
|
|
1268
1489
|
key: `session-${currentSession}`
|
|
1269
1490
|
});
|
|
1270
1491
|
lastSessionRef.current = currentSession;
|
|
@@ -1280,7 +1501,7 @@ var SessionRunner = ({
|
|
|
1280
1501
|
if (newItems.length > 0) {
|
|
1281
1502
|
setStaticItems((prev) => [...prev, ...newItems]);
|
|
1282
1503
|
}
|
|
1283
|
-
}, [events, currentSession]);
|
|
1504
|
+
}, [events, currentSession, sessionId]);
|
|
1284
1505
|
useEffect3(() => {
|
|
1285
1506
|
if (currentSession > totalSessions) {
|
|
1286
1507
|
exit();
|
|
@@ -1296,16 +1517,12 @@ var SessionRunner = ({
|
|
|
1296
1517
|
}
|
|
1297
1518
|
return;
|
|
1298
1519
|
}
|
|
1299
|
-
|
|
1300
|
-
logFileRef.current = getNextLogFile();
|
|
1301
|
-
writeFileSync2(logFileRef.current, "");
|
|
1302
|
-
}
|
|
1303
|
-
const logFile = logFileRef.current;
|
|
1520
|
+
logFileRef.current = null;
|
|
1304
1521
|
setEvents([]);
|
|
1305
1522
|
const promptContent = getPromptContent();
|
|
1306
|
-
const todoExists =
|
|
1523
|
+
const todoExists = existsSync9(todoFile5);
|
|
1307
1524
|
setHasTodoFile(todoExists);
|
|
1308
|
-
const todoContent = todoExists ?
|
|
1525
|
+
const todoContent = todoExists ? readFileSync7(todoFile5, "utf-8") : "";
|
|
1309
1526
|
const roundHeader = `# Ralph, round ${currentSession}
|
|
1310
1527
|
|
|
1311
1528
|
`;
|
|
@@ -1315,7 +1532,10 @@ var SessionRunner = ({
|
|
|
1315
1532
|
|
|
1316
1533
|
${todoContent}` : `${roundHeader}${promptContent}`;
|
|
1317
1534
|
const abortController = new AbortController();
|
|
1535
|
+
taskCompletedAbortRef.current = false;
|
|
1318
1536
|
setIsRunning(true);
|
|
1537
|
+
sessionIdRef.current = null;
|
|
1538
|
+
setSessionId(null);
|
|
1319
1539
|
const messageQueue = new MessageQueue();
|
|
1320
1540
|
messageQueueRef.current = messageQueue;
|
|
1321
1541
|
messageQueue.push(createUserMessage(fullPrompt));
|
|
@@ -1341,7 +1561,18 @@ ${todoContent}` : `${roundHeader}${promptContent}`;
|
|
|
1341
1561
|
}
|
|
1342
1562
|
})) {
|
|
1343
1563
|
log2(`Received message type: ${message.type}`);
|
|
1344
|
-
|
|
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
|
+
}
|
|
1345
1576
|
const event = sdkMessageToEvent(message);
|
|
1346
1577
|
if (event) {
|
|
1347
1578
|
setEvents((prev) => [...prev, event]);
|
|
@@ -1356,28 +1587,33 @@ ${todoContent}` : `${roundHeader}${promptContent}`;
|
|
|
1356
1587
|
if (taskInfo) {
|
|
1357
1588
|
if (taskInfo.action === "starting") {
|
|
1358
1589
|
setCurrentTaskId(taskInfo.taskId ?? null);
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
}
|
|
1369
|
-
appendFileSync(logFile, JSON.stringify(taskStartedEvent) + "\n");
|
|
1590
|
+
log2(`Task started: ${taskInfo.taskId}`);
|
|
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
|
+
}
|
|
1370
1600
|
} else if (taskInfo.action === "completed") {
|
|
1371
|
-
log2(
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1601
|
+
log2(`Task completed: ${taskInfo.taskId}`);
|
|
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();
|
|
1381
1617
|
}
|
|
1382
1618
|
}
|
|
1383
1619
|
}
|
|
@@ -1423,6 +1659,22 @@ ${todoContent}` : `${roundHeader}${promptContent}`;
|
|
|
1423
1659
|
messageQueue.close();
|
|
1424
1660
|
messageQueueRef.current = null;
|
|
1425
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
|
+
}
|
|
1426
1678
|
log2(`Abort signal detected`);
|
|
1427
1679
|
return;
|
|
1428
1680
|
}
|
|
@@ -1459,7 +1711,7 @@ ${todoContent}` : `${roundHeader}${promptContent}`;
|
|
|
1459
1711
|
color: userMessageStatus.type === "success" ? "green" : userMessageStatus.type === "error" ? "red" : "yellow"
|
|
1460
1712
|
},
|
|
1461
1713
|
userMessageStatus.text
|
|
1462
|
-
), /* @__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(
|
|
1463
1715
|
ProgressBar,
|
|
1464
1716
|
{
|
|
1465
1717
|
completed: progressData.completed,
|
|
@@ -1472,7 +1724,7 @@ ${todoContent}` : `${roundHeader}${promptContent}`;
|
|
|
1472
1724
|
// src/components/ReplayLog.tsx
|
|
1473
1725
|
import React8, { useState as useState5, useEffect as useEffect5 } from "react";
|
|
1474
1726
|
import { Box as Box6, Text as Text8, useApp as useApp2 } from "ink";
|
|
1475
|
-
import { readFileSync as
|
|
1727
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
1476
1728
|
|
|
1477
1729
|
// src/components/EventDisplay.tsx
|
|
1478
1730
|
import React6, { useMemo, useState as useState4, useEffect as useEffect4, useRef as useRef2 } from "react";
|
|
@@ -1711,7 +1963,7 @@ var ReplayLog = ({
|
|
|
1711
1963
|
const [error, setError] = useState5();
|
|
1712
1964
|
useEffect5(() => {
|
|
1713
1965
|
try {
|
|
1714
|
-
const content =
|
|
1966
|
+
const content = readFileSync8(filePath, "utf-8");
|
|
1715
1967
|
const eventStrings = content.split(/\n\n+/).filter((s) => s.trim());
|
|
1716
1968
|
const parsedEvents = [];
|
|
1717
1969
|
for (const eventStr of eventStrings) {
|
|
@@ -1745,8 +1997,8 @@ var ReplayLog = ({
|
|
|
1745
1997
|
// src/components/JsonOutput.tsx
|
|
1746
1998
|
import React9, { useState as useState6, useEffect as useEffect6, useRef as useRef3 } from "react";
|
|
1747
1999
|
import { useApp as useApp3, Text as Text9 } from "ink";
|
|
1748
|
-
import {
|
|
1749
|
-
import { join as
|
|
2000
|
+
import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
|
|
2001
|
+
import { join as join13, basename as basename2 } from "path";
|
|
1750
2002
|
import { query as query2 } from "@anthropic-ai/claude-agent-sdk";
|
|
1751
2003
|
|
|
1752
2004
|
// src/lib/parseStdinCommand.ts
|
|
@@ -1838,7 +2090,7 @@ var outputEvent = (event) => {
|
|
|
1838
2090
|
|
|
1839
2091
|
// src/components/JsonOutput.tsx
|
|
1840
2092
|
var log5 = createDebugLogger("session");
|
|
1841
|
-
var todoFile6 =
|
|
2093
|
+
var todoFile6 = join13(process.cwd(), ".ralph", "todo.md");
|
|
1842
2094
|
var repoName2 = basename2(process.cwd());
|
|
1843
2095
|
var JsonOutput = ({ totalSessions, agent }) => {
|
|
1844
2096
|
const { exit } = useApp3();
|
|
@@ -1847,14 +2099,13 @@ var JsonOutput = ({ totalSessions, agent }) => {
|
|
|
1847
2099
|
const [isRunning, setIsRunning] = useState6(false);
|
|
1848
2100
|
const [startupSnapshot] = useState6(() => captureStartupSnapshot());
|
|
1849
2101
|
const messageQueueRef = useRef3(null);
|
|
1850
|
-
const logFileRef = useRef3(null);
|
|
1851
2102
|
const [stopAfterCurrent, setStopAfterCurrent] = useState6(false);
|
|
1852
2103
|
const stopAfterCurrentRef = useRef3(false);
|
|
1853
2104
|
const [isPaused, setIsPaused] = useState6(false);
|
|
1854
2105
|
const isPausedRef = useRef3(false);
|
|
1855
2106
|
const stdinCleanupRef = useRef3(null);
|
|
1856
2107
|
const currentTaskIdRef = useRef3(null);
|
|
1857
|
-
const
|
|
2108
|
+
const sessionIdRef = useRef3(null);
|
|
1858
2109
|
useEffect6(() => {
|
|
1859
2110
|
stopAfterCurrentRef.current = stopAfterCurrent;
|
|
1860
2111
|
}, [stopAfterCurrent]);
|
|
@@ -1906,13 +2157,9 @@ var JsonOutput = ({ totalSessions, agent }) => {
|
|
|
1906
2157
|
process.exit(0);
|
|
1907
2158
|
return;
|
|
1908
2159
|
}
|
|
1909
|
-
if (!logFileRef.current) {
|
|
1910
|
-
logFileRef.current = getNextLogFile();
|
|
1911
|
-
writeFileSync3(logFileRef.current, "");
|
|
1912
|
-
}
|
|
1913
2160
|
const promptContent = getPromptContent();
|
|
1914
|
-
const todoExists =
|
|
1915
|
-
const todoContent = todoExists ?
|
|
2161
|
+
const todoExists = existsSync10(todoFile6);
|
|
2162
|
+
const todoContent = todoExists ? readFileSync9(todoFile6, "utf-8") : "";
|
|
1916
2163
|
const fullPrompt = todoContent ? `${promptContent}
|
|
1917
2164
|
|
|
1918
2165
|
## Current Todo List
|
|
@@ -1920,14 +2167,7 @@ var JsonOutput = ({ totalSessions, agent }) => {
|
|
|
1920
2167
|
${todoContent}` : promptContent;
|
|
1921
2168
|
const abortController = new AbortController();
|
|
1922
2169
|
setIsRunning(true);
|
|
1923
|
-
|
|
1924
|
-
type: "ralph_session_start",
|
|
1925
|
-
session: currentSession,
|
|
1926
|
-
totalSessions,
|
|
1927
|
-
repo: repoName2,
|
|
1928
|
-
taskId: currentTaskIdRef.current,
|
|
1929
|
-
taskTitle: currentTaskTitleRef.current
|
|
1930
|
-
});
|
|
2170
|
+
sessionIdRef.current = null;
|
|
1931
2171
|
const messageQueue = new MessageQueue();
|
|
1932
2172
|
messageQueueRef.current = messageQueue;
|
|
1933
2173
|
messageQueue.push(createUserMessage(fullPrompt));
|
|
@@ -1952,6 +2192,17 @@ ${todoContent}` : promptContent;
|
|
|
1952
2192
|
}
|
|
1953
2193
|
})) {
|
|
1954
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
|
+
}
|
|
1955
2206
|
outputEvent(message);
|
|
1956
2207
|
if (message.type === "assistant") {
|
|
1957
2208
|
const assistantMessage = message.message;
|
|
@@ -1963,25 +2214,20 @@ ${todoContent}` : promptContent;
|
|
|
1963
2214
|
if (taskInfo) {
|
|
1964
2215
|
if (taskInfo.action === "starting") {
|
|
1965
2216
|
currentTaskIdRef.current = taskInfo.taskId ?? null;
|
|
1966
|
-
|
|
1967
|
-
log5(
|
|
1968
|
-
`Task started: ${taskInfo.taskId}${taskInfo.taskTitle ? ` - ${taskInfo.taskTitle}` : ""}`
|
|
1969
|
-
);
|
|
2217
|
+
log5(`Task started: ${taskInfo.taskId}`);
|
|
1970
2218
|
outputEvent({
|
|
1971
2219
|
type: "ralph_task_started",
|
|
1972
2220
|
taskId: taskInfo.taskId,
|
|
1973
|
-
|
|
1974
|
-
|
|
2221
|
+
session: currentSession,
|
|
2222
|
+
sessionId: sessionIdRef.current
|
|
1975
2223
|
});
|
|
1976
2224
|
} else if (taskInfo.action === "completed") {
|
|
1977
|
-
log5(
|
|
1978
|
-
`Task completed: ${taskInfo.taskId}${taskInfo.taskTitle ? ` - ${taskInfo.taskTitle}` : ""}`
|
|
1979
|
-
);
|
|
2225
|
+
log5(`Task completed: ${taskInfo.taskId}`);
|
|
1980
2226
|
outputEvent({
|
|
1981
2227
|
type: "ralph_task_completed",
|
|
1982
2228
|
taskId: taskInfo.taskId,
|
|
1983
|
-
|
|
1984
|
-
|
|
2229
|
+
session: currentSession,
|
|
2230
|
+
sessionId: sessionIdRef.current
|
|
1985
2231
|
});
|
|
1986
2232
|
}
|
|
1987
2233
|
}
|
|
@@ -2005,7 +2251,7 @@ ${todoContent}` : promptContent;
|
|
|
2005
2251
|
type: "ralph_session_end",
|
|
2006
2252
|
session: currentSession,
|
|
2007
2253
|
taskId: currentTaskIdRef.current,
|
|
2008
|
-
|
|
2254
|
+
sessionId: sessionIdRef.current
|
|
2009
2255
|
});
|
|
2010
2256
|
if (stopAfterCurrentRef.current) {
|
|
2011
2257
|
log5(`Stop after current requested - exiting gracefully`);
|
|
@@ -2090,26 +2336,26 @@ var App = ({
|
|
|
2090
2336
|
// src/components/InitRalph.tsx
|
|
2091
2337
|
import { Text as Text10, Box as Box7 } from "ink";
|
|
2092
2338
|
import React11, { useEffect as useEffect7, useState as useState7 } from "react";
|
|
2093
|
-
import { existsSync as
|
|
2094
|
-
import { join as
|
|
2095
|
-
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";
|
|
2096
2342
|
|
|
2097
2343
|
// src/lib/copyTemplates.ts
|
|
2098
|
-
import { existsSync as
|
|
2099
|
-
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";
|
|
2100
2346
|
function copyTemplates(templatesDir, destDir, files) {
|
|
2101
2347
|
const result = { created: [], skipped: [], errors: [] };
|
|
2102
2348
|
for (const { src, dest } of files) {
|
|
2103
|
-
const srcPath =
|
|
2104
|
-
const destPath =
|
|
2105
|
-
const destDirPath =
|
|
2106
|
-
if (!
|
|
2107
|
-
|
|
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 });
|
|
2108
2354
|
}
|
|
2109
|
-
if (
|
|
2355
|
+
if (existsSync11(destPath)) {
|
|
2110
2356
|
result.skipped.push(dest);
|
|
2111
|
-
} else if (
|
|
2112
|
-
|
|
2357
|
+
} else if (existsSync11(srcPath)) {
|
|
2358
|
+
copyFileSync2(srcPath, destPath);
|
|
2113
2359
|
result.created.push(dest);
|
|
2114
2360
|
} else {
|
|
2115
2361
|
result.errors.push(`Template not found: ${src}`);
|
|
@@ -2120,20 +2366,20 @@ function copyTemplates(templatesDir, destDir, files) {
|
|
|
2120
2366
|
|
|
2121
2367
|
// src/components/InitRalph.tsx
|
|
2122
2368
|
function InitRalph() {
|
|
2123
|
-
const __dirname =
|
|
2369
|
+
const __dirname = dirname6(fileURLToPath3(import.meta.url));
|
|
2124
2370
|
const [status, setStatus] = useState7("checking");
|
|
2125
2371
|
const [createdFiles, setCreatedFiles] = useState7([]);
|
|
2126
2372
|
const [skippedFiles, setSkippedFiles] = useState7([]);
|
|
2127
2373
|
const [errors, setErrors] = useState7([]);
|
|
2128
2374
|
useEffect7(() => {
|
|
2129
|
-
const ralphDir6 =
|
|
2130
|
-
const claudeDir =
|
|
2131
|
-
if (
|
|
2375
|
+
const ralphDir6 = join15(process.cwd(), ".ralph");
|
|
2376
|
+
const claudeDir = join15(process.cwd(), ".claude");
|
|
2377
|
+
if (existsSync12(join15(ralphDir6, "workflow.md"))) {
|
|
2132
2378
|
setStatus("exists");
|
|
2133
2379
|
return;
|
|
2134
2380
|
}
|
|
2135
2381
|
const initialize = async () => {
|
|
2136
|
-
const templatesDir =
|
|
2382
|
+
const templatesDir = join15(__dirname, "..", "..", "templates");
|
|
2137
2383
|
setStatus("creating");
|
|
2138
2384
|
try {
|
|
2139
2385
|
const allCreated = [];
|
|
@@ -2159,21 +2405,6 @@ function InitRalph() {
|
|
|
2159
2405
|
allCreated.push(...agentsResult.created.map((f) => `.claude/${f}`));
|
|
2160
2406
|
allSkipped.push(...agentsResult.skipped.map((f) => `.claude/${f}`));
|
|
2161
2407
|
allErrors.push(...agentsResult.errors);
|
|
2162
|
-
const gitignorePath = join13(process.cwd(), ".gitignore");
|
|
2163
|
-
const eventsLogEntry = ".ralph/events-*.jsonl";
|
|
2164
|
-
if (existsSync11(gitignorePath)) {
|
|
2165
|
-
const content = readFileSync8(gitignorePath, "utf-8");
|
|
2166
|
-
if (!content.includes(eventsLogEntry)) {
|
|
2167
|
-
const newline = content.endsWith("\n") ? "" : "\n";
|
|
2168
|
-
appendFileSync2(gitignorePath, `${newline}${eventsLogEntry}
|
|
2169
|
-
`);
|
|
2170
|
-
allCreated.push("(added .ralph/events-*.jsonl to .gitignore)");
|
|
2171
|
-
}
|
|
2172
|
-
} else {
|
|
2173
|
-
writeFileSync4(gitignorePath, `${eventsLogEntry}
|
|
2174
|
-
`);
|
|
2175
|
-
allCreated.push("(created .gitignore with .ralph/events-*.jsonl)");
|
|
2176
|
-
}
|
|
2177
2408
|
setCreatedFiles(allCreated);
|
|
2178
2409
|
setSkippedFiles(allSkipped);
|
|
2179
2410
|
setErrors(allErrors);
|
|
@@ -2240,20 +2471,17 @@ var getDefaultSessions = () => {
|
|
|
2240
2471
|
};
|
|
2241
2472
|
|
|
2242
2473
|
// src/lib/getLatestLogFile.ts
|
|
2243
|
-
import { join as join14 } from "path";
|
|
2244
2474
|
var getLatestLogFile = () => {
|
|
2245
|
-
const
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
const ralphDir6 = join14(process.cwd(), ".ralph");
|
|
2250
|
-
return join14(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");
|
|
2251
2479
|
};
|
|
2252
2480
|
|
|
2253
2481
|
// package.json
|
|
2254
2482
|
var package_default = {
|
|
2255
2483
|
name: "@herbcaudill/ralph",
|
|
2256
|
-
version: "1.0
|
|
2484
|
+
version: "1.2.0",
|
|
2257
2485
|
description: "Autonomous AI session engine for Claude CLI",
|
|
2258
2486
|
type: "module",
|
|
2259
2487
|
main: "./dist/index.js",
|
|
@@ -2271,9 +2499,8 @@ var package_default = {
|
|
|
2271
2499
|
dev: "tsc --watch",
|
|
2272
2500
|
typecheck: "tsc --noEmit",
|
|
2273
2501
|
ralph: "tsx src/index.ts",
|
|
2274
|
-
"test:all": "pnpm typecheck && vitest run",
|
|
2275
2502
|
test: "vitest run",
|
|
2276
|
-
"test:
|
|
2503
|
+
"test:pw": "vitest --config vitest.e2e.config.ts",
|
|
2277
2504
|
"test:watch": "vitest --watch",
|
|
2278
2505
|
"test:ui": "vitest --ui",
|
|
2279
2506
|
format: "prettier --write . --log-level silent",
|
|
@@ -2294,7 +2521,7 @@ var package_default = {
|
|
|
2294
2521
|
url: "https://github.com/HerbCaudill/ralph.git"
|
|
2295
2522
|
},
|
|
2296
2523
|
dependencies: {
|
|
2297
|
-
"@anthropic-ai/claude-agent-sdk": "^0.2.
|
|
2524
|
+
"@anthropic-ai/claude-agent-sdk": "^0.2.29",
|
|
2298
2525
|
chalk: "^5.6.2",
|
|
2299
2526
|
commander: "^14.0.2",
|
|
2300
2527
|
ink: "^6.6.0",
|
|
@@ -2306,6 +2533,7 @@ var package_default = {
|
|
|
2306
2533
|
react: "^19.2.3"
|
|
2307
2534
|
},
|
|
2308
2535
|
devDependencies: {
|
|
2536
|
+
"@herbcaudill/beads-sdk": "link:../../../beads-sdk",
|
|
2309
2537
|
"@herbcaudill/ralph-shared": "workspace:*",
|
|
2310
2538
|
"@types/node": "^24.10.1",
|
|
2311
2539
|
"@types/react": "^19.2.8",
|
|
@@ -2372,10 +2600,10 @@ program.command("todo [description...]").description("add a todo item and commit
|
|
|
2372
2600
|
input: process.stdin,
|
|
2373
2601
|
output: process.stdout
|
|
2374
2602
|
});
|
|
2375
|
-
description = await new Promise((
|
|
2603
|
+
description = await new Promise((resolve2) => {
|
|
2376
2604
|
rl.question("Todo: ", (answer) => {
|
|
2377
2605
|
rl.close();
|
|
2378
|
-
|
|
2606
|
+
resolve2(answer.trim());
|
|
2379
2607
|
});
|
|
2380
2608
|
});
|
|
2381
2609
|
if (!description) {
|