talon-agent 5.26.3 → 5.26.5
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.
- package/package.json +1 -1
- package/src/app.ts +8 -0
- package/src/backend/agy/auth.ts +2 -2
- package/src/backend/agy/mcp/config.ts +3 -3
- package/src/backend/claude-sdk/usage/plan-usage.ts +2 -2
- package/src/backend/codex/discovery.ts +2 -2
- package/src/backend/codex/plan-usage.ts +2 -2
- package/src/backend/codex/token-usage.ts +2 -2
- package/src/backend/remote-server/sessions.ts +82 -13
- package/src/backend/runtime/turn/handle-retry.ts +6 -5
- package/src/cli/commands/backup.ts +7 -3
- package/src/core/auth/status.ts +3 -3
- package/src/core/backup/archive/verify.ts +53 -0
- package/src/core/backup/plan.ts +7 -4
- package/src/core/backup/restore.ts +3 -3
- package/src/core/backup/retention/policy.ts +195 -0
- package/src/core/backup/scheduler.ts +10 -4
- package/src/core/backup/snapshot.ts +6 -2
- package/src/core/backup/sources/sessions.ts +29 -0
- package/src/core/backup/status.ts +7 -0
- package/src/core/backup/store.ts +20 -25
- package/src/core/backup/types.ts +14 -0
- package/src/core/backup/upload.ts +37 -20
- package/src/core/config/index.ts +26 -1
- package/src/core/engine/gateway-actions/native/params.ts +3 -3
- package/src/core/errors.ts +13 -2
- package/src/core/layout/claude-relink.ts +131 -0
- package/src/core/layout/index.ts +170 -0
- package/src/core/layout/mounts.ts +129 -0
- package/src/core/layout/persistence.ts +132 -0
- package/src/frontend/presentation/backup-panel.ts +7 -1
- package/src/plugins/mempalace/provision.ts +4 -4
- package/src/plugins/playwright/provision.ts +2 -2
- package/src/util/fs-path.ts +103 -3
- package/src/util/paths.ts +6 -2
package/src/util/fs-path.ts
CHANGED
|
@@ -10,9 +10,12 @@
|
|
|
10
10
|
* gateway action bodies) into a `fs.*` or send-media API call, route
|
|
11
11
|
* it through `expandFsPath` first so the leading `~/` is replaced
|
|
12
12
|
* with the actual home directory.
|
|
13
|
+
*
|
|
14
|
+
* Also home to `userHome()` and the test-only home guard (below) that every
|
|
15
|
+
* home-derived default path goes through.
|
|
13
16
|
*/
|
|
14
17
|
import { homedir } from "node:os";
|
|
15
|
-
import { isAbsolute, resolve } from "node:path";
|
|
18
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
16
19
|
|
|
17
20
|
/**
|
|
18
21
|
* Resolve a model-supplied path to an absolute on-disk path.
|
|
@@ -25,8 +28,105 @@ import { isAbsolute, resolve } from "node:path";
|
|
|
25
28
|
*/
|
|
26
29
|
export function expandFsPath(input: string): string {
|
|
27
30
|
if (!input) return input;
|
|
28
|
-
if (input === "~") return
|
|
29
|
-
if (input.startsWith("~/")) return resolve(
|
|
31
|
+
if (input === "~") return userHome();
|
|
32
|
+
if (input.startsWith("~/")) return resolve(userHome(), input.slice(2));
|
|
30
33
|
if (isAbsolute(input)) return input;
|
|
31
34
|
return resolve(process.cwd(), input);
|
|
32
35
|
}
|
|
36
|
+
|
|
37
|
+
/*
|
|
38
|
+
* ── Home guard ──────────────────────────────────────────────────────────
|
|
39
|
+
*
|
|
40
|
+
* Test-only tripwire: refuse to resolve a path into the real user home.
|
|
41
|
+
*
|
|
42
|
+
* The suite runs on developer machines and on the production host (the
|
|
43
|
+
* pre-flight lane), where `~/.talon` is a live install. Every vitest worker
|
|
44
|
+
* gets a throwaway HOME / TALON_HOME (src/__tests__/setup/home-isolation.ts),
|
|
45
|
+
* but a test that resets the env, mocks `os.homedir()` or spawns a child
|
|
46
|
+
* with a stripped environment can still land back on the real tree, and
|
|
47
|
+
* functions such as backup prune/restore default to `dirs.root`. So the
|
|
48
|
+
* resolvers themselves check: under vitest, a root that is the real home,
|
|
49
|
+
* an ancestor of it, or anything inside the real `~/.talon` is an error
|
|
50
|
+
* before a single byte is read or written.
|
|
51
|
+
*
|
|
52
|
+
* The real home comes from the password database (`os.userInfo()`), which
|
|
53
|
+
* on Node ignores HOME/USERPROFILE, and `os` is fetched with
|
|
54
|
+
* `process.getBuiltinModule` so a `vi.mock("node:os")` in the calling suite
|
|
55
|
+
* cannot hide it. The global setup also records the pre-override home in
|
|
56
|
+
* TALON_TEST_REAL_HOME, the fallback for hosts with no passwd entry.
|
|
57
|
+
*
|
|
58
|
+
* Outside vitest this is a no-op.
|
|
59
|
+
*/
|
|
60
|
+
|
|
61
|
+
/** Env var the vitest global setup fills with the home it replaced. */
|
|
62
|
+
export const REAL_HOME_ENV = "TALON_TEST_REAL_HOME";
|
|
63
|
+
|
|
64
|
+
function underVitest(): boolean {
|
|
65
|
+
return Boolean(process.env.VITEST);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The homes this process must never resolve into (deduplicated). */
|
|
69
|
+
export function realHomes(): string[] {
|
|
70
|
+
const homes = new Set<string>();
|
|
71
|
+
// Bun's userInfo().homedir follows $HOME, so it would report the temp
|
|
72
|
+
// home as the real one; under Bun only the recorded value is trusted.
|
|
73
|
+
if (!process.versions.bun) {
|
|
74
|
+
try {
|
|
75
|
+
const os = process.getBuiltinModule("node:os");
|
|
76
|
+
const home = os.userInfo().homedir;
|
|
77
|
+
if (home) homes.add(resolve(home));
|
|
78
|
+
} catch {
|
|
79
|
+
// No passwd entry (arbitrary container uid) — fall back to the env.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const recorded = process.env[REAL_HOME_ENV];
|
|
83
|
+
if (recorded) homes.add(resolve(recorded));
|
|
84
|
+
return [...homes];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function norm(p: string): string {
|
|
88
|
+
return process.platform === "win32" ? p.toLowerCase() : p;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** True when `child` is `parent` or lies inside it. */
|
|
92
|
+
function within(child: string, parent: string): boolean {
|
|
93
|
+
const rel = relative(norm(parent), norm(child));
|
|
94
|
+
return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* True when `path` is a real home, an ancestor of one, or inside a real
|
|
99
|
+
* `~/.talon`. A path elsewhere under the real home is allowed: on Windows the
|
|
100
|
+
* temp dir itself lives in `%USERPROFILE%\AppData\Local\Temp`.
|
|
101
|
+
*/
|
|
102
|
+
export function touchesRealHome(path: string): boolean {
|
|
103
|
+
const target = resolve(path);
|
|
104
|
+
return realHomes().some(
|
|
105
|
+
(home) => within(home, target) || within(target, resolve(home, ".talon")),
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Throw when running under vitest and `path` resolves into the real home.
|
|
111
|
+
* `what` names the resolver in the error.
|
|
112
|
+
*/
|
|
113
|
+
export function assertNotRealHome(path: string, what: string): void {
|
|
114
|
+
if (!underVitest()) return;
|
|
115
|
+
if (!touchesRealHome(path)) return;
|
|
116
|
+
throw new Error(
|
|
117
|
+
`[home-guard] ${what} resolved to ${resolve(path)}, which is the real ` +
|
|
118
|
+
`user home or its ~/.talon. Tests must run against the per-worker ` +
|
|
119
|
+
`temp home (src/__tests__/setup/home-isolation.ts); set TALON_HOME / ` +
|
|
120
|
+
`HOME to a temp dir instead.`,
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* `os.homedir()`, guarded: under vitest it throws rather than hand back the
|
|
126
|
+
* real home. Use it wherever a default path is built from the user home.
|
|
127
|
+
*/
|
|
128
|
+
export function userHome(): string {
|
|
129
|
+
const home = homedir();
|
|
130
|
+
assertNotRealHome(home, "os.homedir()");
|
|
131
|
+
return home;
|
|
132
|
+
}
|
package/src/util/paths.ts
CHANGED
|
@@ -32,16 +32,20 @@
|
|
|
32
32
|
*/
|
|
33
33
|
|
|
34
34
|
import { resolve } from "node:path";
|
|
35
|
-
import {
|
|
35
|
+
import { assertNotRealHome, userHome } from "./fs-path.js";
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
38
|
* Root of the Talon data directory: ~/.talon/, relocatable via the
|
|
39
39
|
* TALON_HOME environment variable (containers, systemd units, tests).
|
|
40
40
|
* Resolved once at import time — a mid-process override does nothing.
|
|
41
|
+
* Under vitest, resolving to the real ~/.talon throws (util/fs-path.ts):
|
|
42
|
+
* the check runs on every evaluation of this module, so a suite that
|
|
43
|
+
* re-imports it after changing the env is checked again.
|
|
41
44
|
*/
|
|
42
45
|
const TALON_ROOT = resolve(
|
|
43
|
-
process.env.TALON_HOME || resolve(
|
|
46
|
+
process.env.TALON_HOME || resolve(userHome(), ".talon"),
|
|
44
47
|
);
|
|
48
|
+
assertNotRealHome(TALON_ROOT, "dirs.root (TALON_HOME / ~/.talon)");
|
|
45
49
|
|
|
46
50
|
// ── Directories ────────────────────────────────────────────────────────────
|
|
47
51
|
|