ax-agents 0.1.9 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/ax.js +89 -10
  2. package/package.json +1 -1
package/ax.js CHANGED
@@ -308,7 +308,7 @@ ${progress || "(No progress yet)"}
308
308
  ## Your Task
309
309
  ${userPrompt}
310
310
 
311
- Remember: Work on ONE thing, update ${relProgressPath}, run tests, commit.
311
+ Remember: Work on ONE thing, update ${relProgressPath}, then verify it works.
312
312
  When ALL tasks are complete, output <promise>COMPLETE</promise>`;
313
313
  }
314
314
 
@@ -639,7 +639,7 @@ const RFP_PREAMBLE = `## Guidelines
639
639
  // Note: DO_PREAMBLE is a template - {progressPath} gets replaced at runtime
640
640
  const DO_PREAMBLE = `You are an autonomous coding agent in a loop. Each iteration:
641
641
 
642
- 1. Read {progressPath} to see what's done.
642
+ 1. Read {progressPath} to see what's done (empty means nothing done yet).
643
643
  2. Choose the next task:
644
644
  - Start with trivial/mechanistic work. It banks progress, builds context, and constrains nothing.
645
645
  - Then do foundational work that makes harder problems easier and safer to approach.
@@ -1075,6 +1075,44 @@ function findClaudeLogPath(sessionId, sessionName) {
1075
1075
  return directPath;
1076
1076
  }
1077
1077
 
1078
+ // Fallback: most recently modified session from index (handles /new creating new sessions)
1079
+ if (existsSync(indexPath)) {
1080
+ try {
1081
+ const index = JSON.parse(readFileSync(indexPath, "utf-8"));
1082
+ if (index.entries?.length) {
1083
+ const sorted = [...index.entries].sort((a, b) => {
1084
+ const aTime = a.modified ? new Date(a.modified).getTime() : 0;
1085
+ const bTime = b.modified ? new Date(b.modified).getTime() : 0;
1086
+ return bTime - aTime;
1087
+ });
1088
+ const newest = sorted[0];
1089
+ if (newest?.fullPath && existsSync(newest.fullPath)) {
1090
+ debug("log", `findClaudeLogPath: fallback to most recent via index -> ${newest.fullPath}`);
1091
+ return newest.fullPath;
1092
+ }
1093
+ }
1094
+ } catch (err) {
1095
+ debugError("findClaudeLogPath:index-fallback", err);
1096
+ }
1097
+ }
1098
+
1099
+ // Final fallback: most recently modified .jsonl file (for projects without index)
1100
+ try {
1101
+ const files = readdirSync(claudeProjectDir)
1102
+ .filter((f) => f.endsWith(".jsonl"))
1103
+ .map((f) => {
1104
+ const fullPath = path.join(claudeProjectDir, f);
1105
+ return { path: fullPath, mtime: statSync(fullPath).mtimeMs };
1106
+ })
1107
+ .sort((a, b) => b.mtime - a.mtime);
1108
+ if (files.length > 0) {
1109
+ debug("log", `findClaudeLogPath: fallback to most recent file -> ${files[0].path}`);
1110
+ return files[0].path;
1111
+ }
1112
+ } catch (err) {
1113
+ debugError("findClaudeLogPath:file-fallback", err);
1114
+ }
1115
+
1078
1116
  debug("log", `findClaudeLogPath: not found`);
1079
1117
  return null;
1080
1118
  }
@@ -1120,13 +1158,54 @@ function findNewestClaudeSessionUuid(sessionName) {
1120
1158
  }
1121
1159
 
1122
1160
  /**
1161
+ * Find Codex log path by checking which .jsonl file the Codex process has open.
1162
+ * Uses lsof as the source of truth - whatever file the process has open is the current log.
1163
+ * Falls back to timestamp-based matching if lsof fails.
1123
1164
  * @param {string} sessionName
1124
1165
  * @returns {string | null}
1125
1166
  */
1126
1167
  function findCodexLogPath(sessionName) {
1127
1168
  debug("log", `findCodexLogPath: sessionName=${sessionName}`);
1128
- // For Codex, we need to match by timing since we can't control the session ID
1129
- // Get tmux session creation time
1169
+
1170
+ // Primary method: find the log file via lsof (what file does Codex have open?)
1171
+ try {
1172
+ // Get tmux pane PID
1173
+ const paneResult = spawnSync(
1174
+ "tmux",
1175
+ ["list-panes", "-t", sessionName, "-F", "#{pane_pid}"],
1176
+ { encoding: "utf-8" }
1177
+ );
1178
+ if (paneResult.status === 0 && paneResult.stdout.trim()) {
1179
+ const panePid = parseInt(paneResult.stdout.trim().split("\n")[0], 10);
1180
+ if (!isNaN(panePid)) {
1181
+ // Find child process named "codex"
1182
+ const pgrepResult = spawnSync("pgrep", ["-P", panePid.toString(), "-x", "codex"], {
1183
+ encoding: "utf-8",
1184
+ });
1185
+ if (pgrepResult.status === 0 && pgrepResult.stdout.trim()) {
1186
+ const codexPid = parseInt(pgrepResult.stdout.trim().split("\n")[0], 10);
1187
+ if (!isNaN(codexPid)) {
1188
+ // Use lsof to find which .jsonl file it has open
1189
+ const lsofResult = spawnSync("lsof", ["-p", codexPid.toString()], {
1190
+ encoding: "utf-8",
1191
+ });
1192
+ if (lsofResult.status === 0) {
1193
+ const match = lsofResult.stdout.match(/(\S+\.jsonl)\s*$/m);
1194
+ if (match) {
1195
+ debug("log", `findCodexLogPath: lsof found ${match[1]}`);
1196
+ return match[1];
1197
+ }
1198
+ }
1199
+ }
1200
+ }
1201
+ }
1202
+ }
1203
+ debug("log", `findCodexLogPath: lsof method failed, falling back to timestamp`);
1204
+ } catch (err) {
1205
+ debug("log", `findCodexLogPath: lsof exception, falling back to timestamp`);
1206
+ }
1207
+
1208
+ // Fallback: timestamp-based matching (for when process isn't running or lsof fails)
1130
1209
  try {
1131
1210
  const result = spawnSync(
1132
1211
  "tmux",
@@ -1428,17 +1507,17 @@ function formatClaudeLogEntry(entry) {
1428
1507
  const input = part.input || part.arguments || {};
1429
1508
  let summary;
1430
1509
  if (name === "Bash" && input.command) {
1431
- summary = input.command.slice(0, 50);
1510
+ summary = truncate(input.command, 50);
1432
1511
  } else if (
1433
1512
  name === "Task" &&
1434
1513
  (input.description || input.subagent_type)
1435
1514
  ) {
1436
1515
  // Task tool: show description or subagent type
1437
1516
  summary = input.description || input.subagent_type || "";
1438
- summary = summary.slice(0, 40);
1517
+ summary = truncate(summary, 40);
1439
1518
  } else {
1440
1519
  const target = input.file_path || input.path || input.pattern || "";
1441
- summary = target.split("/").pop() || target.slice(0, 30);
1520
+ summary = target.split("/").pop() || truncate(target, 30);
1442
1521
  }
1443
1522
  output.push({ type: "tool", content: `> ${name}(${summary})` });
1444
1523
  }
@@ -1476,14 +1555,14 @@ function formatCodexLogEntry(entry) {
1476
1555
  try {
1477
1556
  const args = JSON.parse(entry.payload.arguments || "{}");
1478
1557
  if (name === "shell_command" && args.command) {
1479
- summary = args.command.slice(0, 50);
1558
+ summary = truncate(args.command, 50);
1480
1559
  } else if (name === "Task" && (args.description || args.subagent_type)) {
1481
1560
  // Task tool: show description or subagent type
1482
1561
  summary = args.description || args.subagent_type || "";
1483
- summary = summary.slice(0, 40);
1562
+ summary = truncate(summary, 40);
1484
1563
  } else {
1485
1564
  const target = args.file_path || args.path || args.pattern || "";
1486
- summary = target.split("/").pop() || target.slice(0, 30);
1565
+ summary = target.split("/").pop() || truncate(target, 30);
1487
1566
  }
1488
1567
  } catch {
1489
1568
  summary = "...";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ax-agents",
3
- "version": "0.1.9",
3
+ "version": "0.1.11",
4
4
  "description": "A CLI for orchestrating AI coding agents via tmux",
5
5
  "bin": {
6
6
  "ax": "ax.js",