@blockrun/franklin 3.6.20 → 3.6.21

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.
@@ -28,9 +28,12 @@ export function classifyAgentError(message) {
28
28
  if (includesAny(err, [
29
29
  '401',
30
30
  'unauthorized',
31
+ 'unauthenticated',
32
+ 'not authenticated',
31
33
  'invalid api key',
32
34
  'invalid x-api-key',
33
35
  'authentication failed',
36
+ 'authentication required',
34
37
  ])) {
35
38
  return {
36
39
  category: 'auth', label: 'Auth', isTransient: false,
@@ -22,15 +22,24 @@ function isRunning(pid) {
22
22
  catch {
23
23
  return false;
24
24
  }
25
- // PID may have been recycled to an unrelated process. Confirm the
26
- // command line actually looks like Franklin before trusting the PID.
25
+ // PID may have been recycled. Try to confirm command line looks like Franklin.
26
+ // Platform fallbacks (some env lack `ps`): Linux /proc ps → give up.
27
+ try {
28
+ // Linux (including Alpine/busybox containers) exposes /proc/<pid>/cmdline
29
+ const procCmdline = `/proc/${pid}/cmdline`;
30
+ if (fs.existsSync(procCmdline)) {
31
+ const raw = fs.readFileSync(procCmdline, 'utf-8').replace(/\0/g, ' ').trim();
32
+ return /franklin|runcode|node.*dist\/index/.test(raw);
33
+ }
34
+ }
35
+ catch { /* fall through to ps */ }
27
36
  try {
28
37
  const { execSync } = require('node:child_process');
29
38
  const cmd = execSync(`ps -p ${pid} -o command=`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
30
39
  return /franklin|runcode|node.*dist\/index/.test(cmd);
31
40
  }
32
41
  catch {
33
- // ps failedfall back to assuming PID is ours (conservative, avoids false "running")
42
+ // No ps, no /proc give up and trust the kill-0 check
34
43
  return true;
35
44
  }
36
45
  }
@@ -5,13 +5,13 @@ import chalk from 'chalk';
5
5
  import { createPanelServer } from '../panel/server.js';
6
6
  export async function panelCommand(options) {
7
7
  const requestedPort = parseInt(options.port || '3100', 10);
8
- // Handle port-in-use by trying up to 20 subsequent ports.
8
+ // Handle port-in-use by trying up to 20 subsequent ports silently.
9
+ // Only log when we finally bind (or fail completely) — no per-attempt spam.
9
10
  const MAX_ATTEMPTS = 20;
10
11
  const tryListen = (port, attempt) => {
11
12
  const server = createPanelServer(port);
12
13
  server.on('error', (err) => {
13
14
  if (err.code === 'EADDRINUSE' && attempt < MAX_ATTEMPTS) {
14
- console.log(chalk.yellow(` Port ${port} busy — trying ${port + 1}...`));
15
15
  tryListen(port + 1, attempt + 1);
16
16
  return;
17
17
  }
@@ -25,7 +25,8 @@ export async function panelCommand(options) {
25
25
  server.listen(port, () => {
26
26
  console.log('');
27
27
  console.log(chalk.bold(' Franklin Panel'));
28
- console.log(chalk.dim(` http://localhost:${port}`));
28
+ console.log(chalk.dim(` http://localhost:${port}`) +
29
+ (port !== requestedPort ? chalk.yellow(` (fell back from ${requestedPort})`) : ''));
29
30
  console.log('');
30
31
  console.log(chalk.dim(' Press Ctrl+C to stop.'));
31
32
  console.log('');
@@ -95,10 +95,27 @@ export function updateSessionMeta(sessionId, meta) {
95
95
  };
96
96
  // Atomic write: tmp file + rename. Prevents corruption when parent
97
97
  // and sub-agent update the same session meta concurrently.
98
+ // On Windows, renameSync can throw EEXIST/EPERM on older filesystems —
99
+ // fall back to a direct write (non-atomic but still functional) and
100
+ // clean up the orphan tmp file.
98
101
  const target = metaPath(sessionId);
99
102
  const tmp = target + '.tmp';
100
- fs.writeFileSync(tmp, JSON.stringify(updated, null, 2));
101
- fs.renameSync(tmp, target);
103
+ const payload = JSON.stringify(updated, null, 2);
104
+ try {
105
+ fs.writeFileSync(tmp, payload);
106
+ fs.renameSync(tmp, target);
107
+ }
108
+ catch {
109
+ // Best-effort: clean up the orphan tmp, then write target directly.
110
+ try {
111
+ fs.unlinkSync(tmp);
112
+ }
113
+ catch { /* may not exist */ }
114
+ try {
115
+ fs.writeFileSync(target, payload);
116
+ }
117
+ catch { /* give up; stats just get stale */ }
118
+ }
102
119
  });
103
120
  }
104
121
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.6.20",
3
+ "version": "3.6.21",
4
4
  "description": "Franklin — The AI agent with a wallet. Spends USDC autonomously to get real work done. Pay per action, no subscriptions.",
5
5
  "type": "module",
6
6
  "exports": {