ragent-cli 1.6.1 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +121 -19
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -31,7 +31,7 @@ var require_package = __commonJS({
31
31
  "package.json"(exports2, module2) {
32
32
  module2.exports = {
33
33
  name: "ragent-cli",
34
- version: "1.6.1",
34
+ version: "1.7.0",
35
35
  description: "CLI agent for rAgent Live \u2014 browser-first terminal control plane for AI coding agents",
36
36
  main: "dist/index.js",
37
37
  bin: {
@@ -262,7 +262,7 @@ var os9 = __toESM(require("os"));
262
262
  // src/agent.ts
263
263
  var fs4 = __toESM(require("fs"));
264
264
  var os8 = __toESM(require("os"));
265
- var path3 = __toESM(require("path"));
265
+ var path4 = __toESM(require("path"));
266
266
  var import_ws5 = __toESM(require("ws"));
267
267
 
268
268
  // src/auth.ts
@@ -682,6 +682,43 @@ function decodeJwtExp(token) {
682
682
  return null;
683
683
  }
684
684
  }
685
+ async function startSessionWithMachineSecret(params) {
686
+ console.log("[rAgent] Attempting session recovery with machine credential...");
687
+ const response = await fetch(`${params.portal}/api/agent/session/start`, {
688
+ method: "POST",
689
+ headers: { "Content-Type": "application/json" },
690
+ body: JSON.stringify({
691
+ hostId: params.hostId,
692
+ machineSecret: params.machineSecret
693
+ })
694
+ });
695
+ if (response.status === 401 || response.status === 403) {
696
+ throw new AuthError(
697
+ "Machine credential rejected \u2014 agent may be revoked. Re-connect with: ragent connect --token <token>"
698
+ );
699
+ }
700
+ if (!response.ok) {
701
+ const data2 = await response.json().catch(() => ({}));
702
+ throw new Error(
703
+ `Session start failed: ${data2.error || response.status}`
704
+ );
705
+ }
706
+ const data = await response.json();
707
+ if (!data.agentToken) {
708
+ throw new Error("Session start response missing agentToken");
709
+ }
710
+ const patch = {
711
+ agentToken: data.agentToken,
712
+ tokenExpiresAt: data.expiresAt || ""
713
+ };
714
+ if (data.refreshToken) {
715
+ patch.refreshToken = data.refreshToken;
716
+ patch.refreshExpiresAt = data.refreshExpiresAt || "";
717
+ }
718
+ saveConfigPatch(patch);
719
+ console.log("[rAgent] Session recovered via machine credential.");
720
+ return data.agentToken;
721
+ }
685
722
  async function refreshTokenIfNeeded(params) {
686
723
  const config = loadConfig();
687
724
  const refreshToken = config.refreshToken;
@@ -707,10 +744,23 @@ async function refreshTokenIfNeeded(params) {
707
744
  body: JSON.stringify(body)
708
745
  });
709
746
  if (!response.ok) {
710
- const data2 = await response.json().catch(() => ({}));
747
+ const errorData = await response.json().catch(() => ({}));
711
748
  console.warn(
712
- `[rAgent] Token refresh failed: ${data2.error || response.status}`
749
+ `[rAgent] Token refresh failed: ${errorData.error || response.status}`
713
750
  );
751
+ if (config.machineSecret && config.hostId) {
752
+ try {
753
+ return await startSessionWithMachineSecret({
754
+ portal: params.portal,
755
+ hostId: config.hostId,
756
+ machineSecret: config.machineSecret
757
+ });
758
+ } catch (mcError) {
759
+ if (mcError instanceof AuthError) throw mcError;
760
+ const mcMessage = mcError instanceof Error ? mcError.message : String(mcError);
761
+ console.warn(`[rAgent] Machine credential recovery failed: ${mcMessage}`);
762
+ }
763
+ }
714
764
  return params.agentToken;
715
765
  }
716
766
  const data = await response.json();
@@ -723,10 +773,14 @@ async function refreshTokenIfNeeded(params) {
723
773
  patch.refreshToken = data.refreshToken;
724
774
  patch.refreshExpiresAt = data.refreshExpiresAt || "";
725
775
  }
776
+ if (data.machineSecret) {
777
+ patch.machineSecret = data.machineSecret;
778
+ }
726
779
  saveConfigPatch(patch);
727
780
  console.log("[rAgent] Token refreshed successfully.");
728
781
  return data.agentToken;
729
782
  } catch (error) {
783
+ if (error instanceof AuthError) throw error;
730
784
  const message = error instanceof Error ? error.message : String(error);
731
785
  console.warn(`[rAgent] Token refresh error: ${message}`);
732
786
  return params.agentToken;
@@ -753,6 +807,9 @@ async function claimHost(params) {
753
807
  if (!data.agentToken) {
754
808
  throw new Error("Missing connector token in claim response");
755
809
  }
810
+ if (data.machineSecret) {
811
+ saveConfigPatch({ machineSecret: data.machineSecret });
812
+ }
756
813
  return data;
757
814
  }
758
815
  async function negotiateAgent(params) {
@@ -2030,6 +2087,7 @@ var import_ws4 = __toESM(require("ws"));
2030
2087
  var import_child_process2 = require("child_process");
2031
2088
  var fs2 = __toESM(require("fs"));
2032
2089
  var os6 = __toESM(require("os"));
2090
+ var path2 = __toESM(require("path"));
2033
2091
  function assertConfiguredAgentToken() {
2034
2092
  const config = loadConfig();
2035
2093
  if (!config.agentToken) {
@@ -2216,8 +2274,32 @@ async function stopService() {
2216
2274
  }
2217
2275
  await stopPidfileService();
2218
2276
  }
2277
+ function killStaleAgentProcesses() {
2278
+ try {
2279
+ const entries = fs2.readdirSync(CONFIG_DIR);
2280
+ for (const entry of entries) {
2281
+ if (!entry.startsWith("agent-") || !entry.endsWith(".pid")) continue;
2282
+ const pidPath = path2.join(CONFIG_DIR, entry);
2283
+ try {
2284
+ const raw = fs2.readFileSync(pidPath, "utf8").trim();
2285
+ const pid = Number.parseInt(raw, 10);
2286
+ if (!Number.isInteger(pid) || pid <= 0) continue;
2287
+ try {
2288
+ process.kill(pid, 0);
2289
+ process.kill(pid, "SIGTERM");
2290
+ console.log(`[rAgent] Stopped stale agent process (pid ${pid})`);
2291
+ } catch {
2292
+ }
2293
+ fs2.unlinkSync(pidPath);
2294
+ } catch {
2295
+ }
2296
+ }
2297
+ } catch {
2298
+ }
2299
+ }
2219
2300
  async function restartService() {
2220
2301
  const backend = getConfiguredServiceBackend();
2302
+ killStaleAgentProcesses();
2221
2303
  if (backend === "systemd") {
2222
2304
  await runSystemctlUser(["restart", SERVICE_NAME]);
2223
2305
  console.log("[rAgent] Restarted service via systemd.");
@@ -2231,13 +2313,12 @@ async function printServiceStatus() {
2231
2313
  if (backend === "systemd") {
2232
2314
  const status = await execAsync(
2233
2315
  `systemctl --user status ${shellQuote(SERVICE_NAME)} --no-pager --lines=20`,
2234
- { timeout: 1e4 }
2235
- ).catch((error) => {
2236
- console.log(error.message);
2237
- return "";
2238
- });
2316
+ { timeout: 1e4, allowNonZeroExit: true }
2317
+ );
2239
2318
  if (status) {
2240
2319
  process.stdout.write(status);
2320
+ } else {
2321
+ console.log("[rAgent] systemd service is not installed or has no status.");
2241
2322
  }
2242
2323
  return;
2243
2324
  }
@@ -2842,7 +2923,7 @@ var ControlDispatcher = class {
2842
2923
 
2843
2924
  // src/transcript-watcher.ts
2844
2925
  var fs3 = __toESM(require("fs"));
2845
- var path2 = __toESM(require("path"));
2926
+ var path3 = __toESM(require("path"));
2846
2927
  var os7 = __toESM(require("os"));
2847
2928
  var import_child_process5 = require("child_process");
2848
2929
  var ClaudeCodeParser = class {
@@ -2978,7 +3059,7 @@ function discoverViaProc(panePid) {
2978
3059
  const fds = fs3.readdirSync(fdDir);
2979
3060
  for (const fd of fds) {
2980
3061
  try {
2981
- const target = fs3.readlinkSync(path2.join(fdDir, fd));
3062
+ const target = fs3.readlinkSync(path3.join(fdDir, fd));
2982
3063
  if (target.endsWith(".jsonl") && target.includes("/.claude/")) {
2983
3064
  return target;
2984
3065
  }
@@ -2998,7 +3079,7 @@ function discoverViaProc(panePid) {
2998
3079
  const fds = fs3.readdirSync(gcFdDir);
2999
3080
  for (const fd of fds) {
3000
3081
  try {
3001
- const target = fs3.readlinkSync(path2.join(gcFdDir, fd));
3082
+ const target = fs3.readlinkSync(path3.join(gcFdDir, fd));
3002
3083
  if (target.endsWith(".jsonl") && target.includes("/.claude/")) {
3003
3084
  return target;
3004
3085
  }
@@ -3016,15 +3097,15 @@ function discoverViaProc(panePid) {
3016
3097
  return null;
3017
3098
  }
3018
3099
  function discoverViaCwd(paneCwd) {
3019
- const claudeProjectsDir = path2.join(os7.homedir(), ".claude", "projects");
3100
+ const claudeProjectsDir = path3.join(os7.homedir(), ".claude", "projects");
3020
3101
  if (!fs3.existsSync(claudeProjectsDir)) return null;
3021
3102
  const resolvedCwd = fs3.realpathSync(paneCwd);
3022
3103
  const expectedDirName = resolvedCwd.replace(/\//g, "-");
3023
- const projectDir = path2.join(claudeProjectsDir, expectedDirName);
3104
+ const projectDir = path3.join(claudeProjectsDir, expectedDirName);
3024
3105
  if (!fs3.existsSync(projectDir)) return null;
3025
3106
  try {
3026
3107
  const jsonlFiles = fs3.readdirSync(projectDir).filter((f) => f.endsWith(".jsonl")).map((f) => {
3027
- const fullPath = path2.join(projectDir, f);
3108
+ const fullPath = path3.join(projectDir, f);
3028
3109
  const stat = fs3.statSync(fullPath);
3029
3110
  return { path: fullPath, mtime: stat.mtimeMs };
3030
3111
  }).sort((a, b) => b.mtime - a.mtime);
@@ -3034,14 +3115,14 @@ function discoverViaCwd(paneCwd) {
3034
3115
  }
3035
3116
  }
3036
3117
  function discoverCodexTranscript() {
3037
- const codexSessionsDir = path2.join(os7.homedir(), ".codex", "sessions");
3118
+ const codexSessionsDir = path3.join(os7.homedir(), ".codex", "sessions");
3038
3119
  if (!fs3.existsSync(codexSessionsDir)) return null;
3039
3120
  try {
3040
3121
  const dateDirs = fs3.readdirSync(codexSessionsDir).filter((d) => /^\d{4}-\d{2}-\d{2}/.test(d)).sort().reverse();
3041
3122
  for (const dateDir of dateDirs.slice(0, 3)) {
3042
- const fullDir = path2.join(codexSessionsDir, dateDir);
3123
+ const fullDir = path3.join(codexSessionsDir, dateDir);
3043
3124
  const jsonlFiles = fs3.readdirSync(fullDir).filter((f) => f.startsWith("rollout-") && f.endsWith(".jsonl")).map((f) => {
3044
- const fp = path2.join(fullDir, f);
3125
+ const fp = path3.join(fullDir, f);
3045
3126
  const stat = fs3.statSync(fp);
3046
3127
  return { path: fp, mtime: stat.mtimeMs };
3047
3128
  }).sort((a, b) => b.mtime - a.mtime);
@@ -3377,7 +3458,7 @@ var TranscriptWatcherManager = class {
3377
3458
 
3378
3459
  // src/agent.ts
3379
3460
  function pidFilePath(hostId) {
3380
- return path3.join(CONFIG_DIR, `agent-${hostId}.pid`);
3461
+ return path4.join(CONFIG_DIR, `agent-${hostId}.pid`);
3381
3462
  }
3382
3463
  function readPidFile(filePath) {
3383
3464
  try {
@@ -3643,6 +3724,27 @@ async function runAgent(rawOptions) {
3643
3724
  });
3644
3725
  } catch (error) {
3645
3726
  if (error instanceof AuthError) {
3727
+ const cfg = loadConfig();
3728
+ if (cfg.machineSecret && cfg.hostId) {
3729
+ try {
3730
+ options.agentToken = await startSessionWithMachineSecret({
3731
+ portal: options.portal,
3732
+ hostId: cfg.hostId,
3733
+ machineSecret: cfg.machineSecret
3734
+ });
3735
+ inventory.updateOptions(options);
3736
+ dispatcher.updateOptions(options);
3737
+ console.log("[rAgent] Recovered from auth failure via machine credential. Reconnecting...");
3738
+ continue;
3739
+ } catch (mcError) {
3740
+ if (mcError instanceof AuthError) {
3741
+ console.error(`[rAgent] ${mcError.message}`);
3742
+ } else {
3743
+ const mcMsg = mcError instanceof Error ? mcError.message : String(mcError);
3744
+ console.error(`[rAgent] Machine credential recovery failed: ${mcMsg}`);
3745
+ }
3746
+ }
3747
+ }
3646
3748
  console.error(`[rAgent] ${error.message}`);
3647
3749
  console.error(
3648
3750
  "[rAgent] Connector token is invalid or revoked. Stopping. Re-connect with: ragent connect --token <token>"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ragent-cli",
3
- "version": "1.6.1",
3
+ "version": "1.7.0",
4
4
  "description": "CLI agent for rAgent Live — browser-first terminal control plane for AI coding agents",
5
5
  "main": "dist/index.js",
6
6
  "bin": {