@blockrun/franklin 3.15.40 → 3.15.42

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.
@@ -3,8 +3,24 @@
3
3
  * meta.json — single TaskRecord, atomically rewritten
4
4
  * events.jsonl — append-only event log
5
5
  * log.txt — child process stdout/stderr
6
+ *
7
+ * Storage location: defaults to BLOCKRUN_DIR (~/.blockrun), matching
8
+ * every other persistent state in the codebase (sessions, audit, stats,
9
+ * brain, etc.). Earlier releases used ~/.franklin instead, so we
10
+ * lazily fall back to that legacy directory on reads when a task isn't
11
+ * found in the primary location. New tasks always write to the primary.
12
+ *
13
+ * Why a lazy fallback instead of a startup migration: a long-running
14
+ * task runner (`franklin _task-runner <runId>`) captures its task dir
15
+ * path in memory at spawn and continues writing there for the duration
16
+ * of the run. Verified 2026-05-04: an in-flight ETL task at PID 59095
17
+ * had been writing to ~/.franklin/tasks/ for 4 minutes, with ~10 hours
18
+ * of progress still ahead. Moving the directory mid-flight would
19
+ * orphan its writes; the fallback path lets new CLI commands keep
20
+ * reading legacy task state without disturbing an active runner.
6
21
  */
7
22
  export declare function getTasksDir(): string;
23
+ export declare function getLegacyTasksDir(): string;
8
24
  export declare function getTaskDir(runId: string): string;
9
25
  export declare function ensureTaskDir(runId: string): string;
10
26
  export declare function taskMetaPath(runId: string): string;
@@ -3,18 +3,51 @@
3
3
  * meta.json — single TaskRecord, atomically rewritten
4
4
  * events.jsonl — append-only event log
5
5
  * log.txt — child process stdout/stderr
6
+ *
7
+ * Storage location: defaults to BLOCKRUN_DIR (~/.blockrun), matching
8
+ * every other persistent state in the codebase (sessions, audit, stats,
9
+ * brain, etc.). Earlier releases used ~/.franklin instead, so we
10
+ * lazily fall back to that legacy directory on reads when a task isn't
11
+ * found in the primary location. New tasks always write to the primary.
12
+ *
13
+ * Why a lazy fallback instead of a startup migration: a long-running
14
+ * task runner (`franklin _task-runner <runId>`) captures its task dir
15
+ * path in memory at spawn and continues writing there for the duration
16
+ * of the run. Verified 2026-05-04: an in-flight ETL task at PID 59095
17
+ * had been writing to ~/.franklin/tasks/ for 4 minutes, with ~10 hours
18
+ * of progress still ahead. Moving the directory mid-flight would
19
+ * orphan its writes; the fallback path lets new CLI commands keep
20
+ * reading legacy task state without disturbing an active runner.
6
21
  */
7
22
  import fs from 'node:fs';
8
23
  import os from 'node:os';
9
24
  import path from 'node:path';
25
+ import { BLOCKRUN_DIR } from '../config.js';
26
+ const LEGACY_FRANKLIN_HOME = path.join(os.homedir(), '.franklin');
10
27
  function franklinHome() {
11
- return process.env.FRANKLIN_HOME || path.join(os.homedir(), '.franklin');
28
+ return process.env.FRANKLIN_HOME || BLOCKRUN_DIR;
12
29
  }
13
30
  export function getTasksDir() {
14
31
  return path.join(franklinHome(), 'tasks');
15
32
  }
33
+ export function getLegacyTasksDir() {
34
+ return path.join(LEGACY_FRANKLIN_HOME, 'tasks');
35
+ }
16
36
  export function getTaskDir(runId) {
17
- return path.join(getTasksDir(), runId);
37
+ // Prefer the primary location. If a task already exists in the
38
+ // legacy ~/.franklin/tasks/ — either created by an older release or
39
+ // by a runner subprocess started before this version was installed —
40
+ // continue to read/write there until it completes, so we don't strand
41
+ // its in-flight events.jsonl + meta.json writes.
42
+ const primary = path.join(getTasksDir(), runId);
43
+ if (fs.existsSync(primary))
44
+ return primary;
45
+ if (process.env.FRANKLIN_HOME === undefined) {
46
+ const legacy = path.join(getLegacyTasksDir(), runId);
47
+ if (fs.existsSync(legacy))
48
+ return legacy;
49
+ }
50
+ return primary;
18
51
  }
19
52
  export function ensureTaskDir(runId) {
20
53
  const dir = getTaskDir(runId);
@@ -16,7 +16,7 @@
16
16
  * tolerant of a torn last line.
17
17
  */
18
18
  import fs from 'node:fs';
19
- import { ensureTaskDir, taskMetaPath, taskEventsPath, getTasksDir, } from './paths.js';
19
+ import { ensureTaskDir, taskMetaPath, taskEventsPath, getTasksDir, getLegacyTasksDir, } from './paths.js';
20
20
  export function writeTaskMeta(record) {
21
21
  ensureTaskDir(record.runId);
22
22
  const target = taskMetaPath(record.runId);
@@ -103,21 +103,35 @@ export function applyEvent(runId, event) {
103
103
  return next;
104
104
  }
105
105
  export function listTasks() {
106
- let entries;
107
- try {
108
- entries = fs.readdirSync(getTasksDir(), { withFileTypes: true });
109
- }
110
- catch {
111
- return [];
112
- }
106
+ // Walk both the primary tasks dir and the legacy ~/.franklin/tasks/
107
+ // location so `franklin task list` keeps showing legacy tasks until
108
+ // their dirs are cleaned up. Dedupe by runId (first-wins, primary
109
+ // ordered first) — protects against the unlikely case of the same
110
+ // runId existing in both locations.
111
+ const dirs = [getTasksDir()];
112
+ if (process.env.FRANKLIN_HOME === undefined)
113
+ dirs.push(getLegacyTasksDir());
114
+ const seen = new Set();
113
115
  const out = [];
114
- for (const ent of entries) {
115
- // Skip junk like .DS_Store — only real per-task subdirectories are valid.
116
- if (!ent.isDirectory())
116
+ for (const dir of dirs) {
117
+ let entries;
118
+ try {
119
+ entries = fs.readdirSync(dir, { withFileTypes: true });
120
+ }
121
+ catch {
117
122
  continue;
118
- const meta = readTaskMeta(ent.name);
119
- if (meta)
120
- out.push(meta);
123
+ }
124
+ for (const ent of entries) {
125
+ // Skip junk like .DS_Store — only real per-task subdirectories are valid.
126
+ if (!ent.isDirectory())
127
+ continue;
128
+ if (seen.has(ent.name))
129
+ continue;
130
+ seen.add(ent.name);
131
+ const meta = readTaskMeta(ent.name);
132
+ if (meta)
133
+ out.push(meta);
134
+ }
121
135
  }
122
136
  out.sort((a, b) => b.createdAt - a.createdAt);
123
137
  return out;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blockrun/franklin",
3
- "version": "3.15.40",
3
+ "version": "3.15.42",
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": {