@swarmclawai/swarmclaw 1.9.38 → 1.9.39

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.
@@ -0,0 +1,60 @@
1
+ import path from 'path'
2
+ import os from 'os'
3
+ import { DATA_DIR, WORKSPACE_DIR } from './data-dir'
4
+
5
+ export interface NormalizeLegacyWorkspacePathOptions {
6
+ workspaceRoot?: string
7
+ taskId?: string | null
8
+ }
9
+
10
+ function splitSegments(p: string): string[] {
11
+ return path.normalize(p).split(path.sep).filter(Boolean)
12
+ }
13
+
14
+ /**
15
+ * Remaps a path persisted under a previous workspace root (e.g.
16
+ * /root/.swarmclaw/workspace/tasks/<id>) onto the current WORKSPACE_DIR.
17
+ *
18
+ * Only remaps when a safety signal identifies the path as a SwarmClaw-managed
19
+ * workspace location; intentional custom cwds pass through unchanged:
20
+ * - the prefix before a `workspace` segment contains a `.swarmclaw` segment
21
+ * - the tail after a `workspace` segment is `tasks/<taskId>` (or under it)
22
+ * - the prefix matches a known default workspace root that differs from the
23
+ * current one (`~/.swarmclaw/workspace`, `DATA_DIR/workspace`)
24
+ */
25
+ export function normalizeLegacyWorkspacePath(
26
+ raw: string | null | undefined,
27
+ options: NormalizeLegacyWorkspacePathOptions = {},
28
+ ): string {
29
+ const input = typeof raw === 'string' ? raw.trim() : ''
30
+ if (!input || !path.isAbsolute(input)) return input
31
+
32
+ const workspaceRoot = path.resolve(options.workspaceRoot ?? WORKSPACE_DIR)
33
+ const resolved = path.resolve(input)
34
+ const rel = path.relative(workspaceRoot, resolved)
35
+ if (rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel))) return input
36
+
37
+ const segments = splitSegments(resolved)
38
+ const taskId = typeof options.taskId === 'string' ? options.taskId.trim() : ''
39
+ const knownDefaultRoots = new Set(
40
+ [path.join(os.homedir(), '.swarmclaw', 'workspace'), path.join(DATA_DIR, 'workspace')]
41
+ .map((p) => path.resolve(p))
42
+ .filter((p) => p !== workspaceRoot),
43
+ )
44
+
45
+ for (let i = segments.length - 1; i >= 0; i--) {
46
+ if (segments[i] !== 'workspace') continue
47
+ const prefixSegments = segments.slice(0, i)
48
+ const tailSegments = segments.slice(i + 1)
49
+ const legacyRoot = tailSegments.length > 0
50
+ ? path.resolve(resolved, ...tailSegments.map(() => '..'))
51
+ : resolved
52
+ const hasSwarmclawMarker = prefixSegments.includes('.swarmclaw')
53
+ const matchesTaskTail = Boolean(taskId) && tailSegments[0] === 'tasks' && tailSegments[1] === taskId
54
+ const isKnownDefaultRoot = knownDefaultRoots.has(legacyRoot)
55
+ if (!hasSwarmclawMarker && !matchesTaskTail && !isKnownDefaultRoot) continue
56
+ return tailSegments.length > 0 ? path.join(workspaceRoot, ...tailSegments) : workspaceRoot
57
+ }
58
+
59
+ return input
60
+ }