@xpufx/paseo-top 0.4.3 → 0.4.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.
Files changed (130) hide show
  1. package/OVERVIEW.md +17 -0
  2. package/client/host-ui.tsx +184 -0
  3. package/client/multi-host-view.tsx +57 -43
  4. package/client/multi-host.ts +36 -13
  5. package/client/pill-labels.ts +124 -7
  6. package/client/pill.tsx +724 -669
  7. package/client/resources-query.ts +1 -1
  8. package/client/settings-events.ts +33 -0
  9. package/client/settings-ui.tsx +86 -9
  10. package/client/surface.tsx +310 -274
  11. package/client/telemetry-copy.ts +8 -2
  12. package/client/telemetry.tsx +372 -321
  13. package/client/turn-panel.tsx +126 -104
  14. package/client/vendor/paseo-plugin-helper/{host.ts → core/host.ts} +21 -61
  15. package/client/vendor/paseo-plugin-helper/core/index.ts +76 -0
  16. package/client/vendor/paseo-plugin-helper/core/plugin-load-count.ts +89 -0
  17. package/client/vendor/paseo-plugin-helper/{query-refresh.ts → core/query-refresh.ts} +2 -2
  18. package/client/vendor/paseo-plugin-helper/{query.ts → core/query.ts} +2 -2
  19. package/client/vendor/paseo-plugin-helper/{settings.ts → core/settings.ts} +1 -1
  20. package/client/vendor/paseo-plugin-helper/{shared-settings.ts → core/shared-settings.ts} +2 -2
  21. package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/clipboard.ts +2 -2
  22. package/client/vendor/paseo-plugin-helper/lifecycle/flair.ts +26 -0
  23. package/client/vendor/paseo-plugin-helper/{theme/color-utils.ts → lifecycle/host-color.ts} +95 -38
  24. package/client/vendor/paseo-plugin-helper/lifecycle/host-theme.tsx +109 -0
  25. package/client/vendor/paseo-plugin-helper/lifecycle/index.ts +23 -0
  26. package/client/vendor/paseo-plugin-helper/lifecycle/multi-host.ts +66 -0
  27. package/client/vendor/paseo-plugin-helper/{panel.tsx → lifecycle/panel.tsx} +7 -7
  28. package/client/vendor/paseo-plugin-helper/{pill.tsx → lifecycle/pill.tsx} +118 -338
  29. package/client/vendor/paseo-plugin-helper/lifecycle/providers.tsx +46 -0
  30. package/client/vendor/paseo-plugin-helper/lifecycle/scroll-owner.ts +26 -0
  31. package/client/vendor/paseo-plugin-helper/lifecycle/surface.tsx +140 -0
  32. package/client/vendor/paseo-plugin-helper/ui/color.ts +1 -0
  33. package/client/vendor/paseo-plugin-helper/ui/content.tsx +1326 -0
  34. package/client/vendor/paseo-plugin-helper/ui/controls.tsx +1018 -0
  35. package/client/vendor/paseo-plugin-helper/{components/DataTable.tsx → ui/data.tsx} +55 -47
  36. package/client/vendor/paseo-plugin-helper/ui/index.ts +44 -0
  37. package/client/vendor/paseo-plugin-helper/ui/layout.tsx +263 -0
  38. package/client/vendor/paseo-plugin-helper/ui/modal.tsx +111 -0
  39. package/client/vendor/paseo-plugin-helper/ui/plugin-reload-banner.tsx +119 -0
  40. package/client/vendor/paseo-plugin-helper/{settings-screen.tsx → ui/settings-screen.tsx} +3 -3
  41. package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
  42. package/client/vendor/paseo-plugin-helper/ui/text.tsx +821 -0
  43. package/client/vendor/paseo-plugin-helper/ui/theme.tsx +1 -0
  44. package/client/vendor/permission-audit/client.tsx +706 -0
  45. package/client/vendor/permission-audit/index.tsx +1 -0
  46. package/index.client.tsx +58 -37
  47. package/index.server.ts +256 -227
  48. package/package.json +14 -2
  49. package/paseo-plugin.json +2 -0
  50. package/screenshots/1-paseo-top-modal-system.jpg +0 -0
  51. package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
  52. package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
  53. package/screenshots/4-paseo-top-modal-about.jpg +0 -0
  54. package/screenshots/paseo-top-dashboard.jpg +0 -0
  55. package/screenshots/paseo-top-general-view.jpg +0 -0
  56. package/server/resources.ts +98 -0
  57. package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
  58. package/server/vendor/paseo-plugin-helper/index.ts +4 -0
  59. package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
  60. package/server/vendor/paseo-plugin-helper/mcp-injection.ts +20 -2
  61. package/server/vendor/paseo-plugin-helper/plugins.ts +184 -19
  62. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
  63. package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
  64. package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
  65. package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
  66. package/server/vendor/paseo-plugin-helper/tickets.ts +1232 -0
  67. package/server/vendor/permission-audit/index.ts +1 -0
  68. package/server/vendor/permission-audit/server.ts +839 -0
  69. package/shared/helper-version.ts +20 -0
  70. package/shared/multi-host.ts +156 -155
  71. package/shared/resources.ts +146 -1
  72. package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
  73. package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
  74. package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
  75. package/shared/vendor/paseo-plugin-helper/index.ts +2 -0
  76. package/shared/vendor/paseo-plugin-helper/tickets.ts +1104 -0
  77. package/shared/vendor/permission-audit/filter.ts +61 -0
  78. package/shared/vendor/permission-audit/index.ts +2 -0
  79. package/shared/vendor/permission-audit/shared.ts +119 -0
  80. package/shared/version.ts +1 -1
  81. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
  82. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
  83. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -156
  84. package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -184
  85. package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -225
  86. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
  87. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
  88. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
  89. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
  90. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
  91. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
  92. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -75
  93. package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
  94. package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
  95. package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
  96. package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
  97. package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -118
  98. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -80
  99. package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -215
  100. package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
  101. package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
  102. package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
  103. package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
  104. package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
  105. package/client/vendor/paseo-plugin-helper/components/index.ts +0 -25
  106. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -224
  107. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
  108. package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
  109. package/client/vendor/paseo-plugin-helper/index.ts +0 -29
  110. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
  111. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
  112. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
  113. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -378
  114. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
  115. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
  116. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
  117. package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
  118. package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
  119. package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
  120. package/client/vendor/paseo-plugin-helper/surface.tsx +0 -80
  121. package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
  122. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
  123. package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
  124. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
  125. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
  126. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
  127. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
  128. /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
  129. /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
  130. /package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/haptics.ts +0 -0
@@ -0,0 +1,294 @@
1
+ import fs from "node:fs";
2
+ import type { PathLike } from "node:fs";
3
+ import fsp from "node:fs/promises";
4
+ import path from "node:path";
5
+ import os from "node:os";
6
+ import { resolvePaseoHome } from "./storage";
7
+
8
+ export class ScopedStorageViolationError extends Error {
9
+ readonly code = "ERR_SCOPED_STORAGE_VIOLATION";
10
+ readonly attemptedPath: string;
11
+ readonly allowedRoots: string[];
12
+
13
+ constructor(message: string, attemptedPath: string, allowedRoots: string[]) {
14
+ super(message);
15
+ this.name = "ScopedStorageViolationError";
16
+ this.attemptedPath = attemptedPath;
17
+ this.allowedRoots = allowedRoots;
18
+ Object.setPrototypeOf(this, new.target.prototype);
19
+ }
20
+ }
21
+
22
+ export interface InstallStorageGuardOptions {
23
+ pluginId: string;
24
+ namespace?: string; // defaults to "plugin-data/xpufx"
25
+ baseDir?: string;
26
+ allowTmp?: boolean; // defaults to true
27
+ mode?: "enforce" | "warn"; // defaults to "enforce"
28
+ onViolation?: (violation: { path: string; operation: string; allowedRoots: string[] }) => void;
29
+ }
30
+
31
+ function resolvePathString(target: PathLike | number): string | null {
32
+ if (typeof target === "string") {
33
+ return path.resolve(target);
34
+ }
35
+ if (Buffer.isBuffer(target)) {
36
+ return path.resolve(target.toString("utf8"));
37
+ }
38
+ if (target instanceof URL) {
39
+ if (target.protocol === "file:") {
40
+ const pathname = decodeURIComponent(target.pathname);
41
+ return path.resolve(pathname);
42
+ }
43
+ return null;
44
+ }
45
+ return null;
46
+ }
47
+
48
+ function isPathWithinRoot(targetPath: string, rootDir: string): boolean {
49
+ const resolvedTarget = path.resolve(targetPath);
50
+ const resolvedRoot = path.resolve(rootDir);
51
+
52
+ if (resolvedTarget === resolvedRoot) {
53
+ return true;
54
+ }
55
+
56
+ const relative = path.relative(resolvedRoot, resolvedTarget);
57
+ return !relative.startsWith("..") && !path.isAbsolute(relative);
58
+ }
59
+
60
+ export function installStorageGuard(options: InstallStorageGuardOptions): () => void {
61
+ const pluginId = options.pluginId;
62
+ const namespace = options.namespace ?? "plugin-data/xpufx";
63
+ const allowTmp = options.allowTmp ?? true;
64
+ const mode = options.mode ?? "enforce";
65
+
66
+ const pluginStorageDir = options.baseDir
67
+ ? path.resolve(options.baseDir, pluginId)
68
+ : path.resolve(resolvePaseoHome(), namespace, pluginId);
69
+ const allowedRoots: string[] = [pluginStorageDir];
70
+
71
+ if (allowTmp) {
72
+ allowedRoots.push(path.resolve(os.tmpdir()));
73
+ allowedRoots.push(path.resolve(process.cwd(), ".tmp"));
74
+ }
75
+
76
+ function validateTarget(target: PathLike | number, operation: string): void {
77
+ const resolved = resolvePathString(target);
78
+ if (!resolved) {
79
+ return;
80
+ }
81
+
82
+ const isAllowed = allowedRoots.some((root) => isPathWithinRoot(resolved, root));
83
+ if (!isAllowed) {
84
+ options.onViolation?.({
85
+ path: resolved,
86
+ operation,
87
+ allowedRoots: [...allowedRoots],
88
+ });
89
+
90
+ const message = `Scoped storage violation: write operation '${operation}' on '${resolved}' is outside permitted scopes: [${allowedRoots.join(", ")}]`;
91
+
92
+ if (mode === "enforce") {
93
+ throw new ScopedStorageViolationError(message, resolved, [...allowedRoots]);
94
+ } else {
95
+ console.warn(`[WARN] ${message}`);
96
+ }
97
+ }
98
+ }
99
+
100
+ // Backup originals
101
+ const origWriteFile = fs.writeFile;
102
+ const origWriteFileSync = fs.writeFileSync;
103
+ const origAppendFile = fs.appendFile;
104
+ const origAppendFileSync = fs.appendFileSync;
105
+ const origMkdir = fs.mkdir;
106
+ const origMkdirSync = fs.mkdirSync;
107
+ const origRmdir = fs.rmdir;
108
+ const origRmdirSync = fs.rmdirSync;
109
+ const origUnlink = fs.unlink;
110
+ const origUnlinkSync = fs.unlinkSync;
111
+ const origRm = fs.rm;
112
+ const origRmSync = fs.rmSync;
113
+ const origRename = fs.rename;
114
+ const origRenameSync = fs.renameSync;
115
+ const origCopyFile = fs.copyFile;
116
+ const origCopyFileSync = fs.copyFileSync;
117
+ const origCreateWriteStream = fs.createWriteStream;
118
+
119
+ const origPromisesWriteFile = fsp.writeFile;
120
+ const origPromisesAppendFile = fsp.appendFile;
121
+ const origPromisesMkdir = fsp.mkdir;
122
+ const origPromisesRmdir = fsp.rmdir;
123
+ const origPromisesUnlink = fsp.unlink;
124
+ const origPromisesRm = fsp.rm;
125
+ const origPromisesRename = fsp.rename;
126
+ const origPromisesCopyFile = fsp.copyFile;
127
+
128
+ // Intercept node:fs sync & async
129
+ const anyFs = fs as any;
130
+
131
+ anyFs.writeFile = function (file: PathLike | number, ...args: unknown[]) {
132
+ validateTarget(file, "writeFile");
133
+ return Reflect.apply(origWriteFile, fs, [file, ...args]);
134
+ };
135
+
136
+ anyFs.writeFileSync = function (file: PathLike | number, ...args: unknown[]) {
137
+ validateTarget(file, "writeFileSync");
138
+ return Reflect.apply(origWriteFileSync, fs, [file, ...args]);
139
+ };
140
+
141
+ anyFs.appendFile = function (file: PathLike | number, ...args: unknown[]) {
142
+ validateTarget(file, "appendFile");
143
+ return Reflect.apply(origAppendFile, fs, [file, ...args]);
144
+ };
145
+
146
+ anyFs.appendFileSync = function (file: PathLike | number, ...args: unknown[]) {
147
+ validateTarget(file, "appendFileSync");
148
+ return Reflect.apply(origAppendFileSync, fs, [file, ...args]);
149
+ };
150
+
151
+ anyFs.mkdir = function (pathArg: PathLike, ...args: unknown[]) {
152
+ validateTarget(pathArg, "mkdir");
153
+ return Reflect.apply(origMkdir, fs, [pathArg, ...args]);
154
+ };
155
+
156
+ anyFs.mkdirSync = function (pathArg: PathLike, ...args: unknown[]) {
157
+ validateTarget(pathArg, "mkdirSync");
158
+ return Reflect.apply(origMkdirSync, fs, [pathArg, ...args]);
159
+ };
160
+
161
+ anyFs.rmdir = function (pathArg: PathLike, ...args: unknown[]) {
162
+ validateTarget(pathArg, "rmdir");
163
+ return Reflect.apply(origRmdir, fs, [pathArg, ...args]);
164
+ };
165
+
166
+ anyFs.rmdirSync = function (pathArg: PathLike, ...args: unknown[]) {
167
+ validateTarget(pathArg, "rmdirSync");
168
+ return Reflect.apply(origRmdirSync, fs, [pathArg, ...args]);
169
+ };
170
+
171
+ anyFs.unlink = function (pathArg: PathLike, ...args: unknown[]) {
172
+ validateTarget(pathArg, "unlink");
173
+ return Reflect.apply(origUnlink, fs, [pathArg, ...args]);
174
+ };
175
+
176
+ anyFs.unlinkSync = function (pathArg: PathLike, ...args: unknown[]) {
177
+ validateTarget(pathArg, "unlinkSync");
178
+ return Reflect.apply(origUnlinkSync, fs, [pathArg, ...args]);
179
+ };
180
+
181
+ anyFs.rm = function (pathArg: PathLike, ...args: unknown[]) {
182
+ validateTarget(pathArg, "rm");
183
+ return Reflect.apply(origRm, fs, [pathArg, ...args]);
184
+ };
185
+
186
+ anyFs.rmSync = function (pathArg: PathLike, ...args: unknown[]) {
187
+ validateTarget(pathArg, "rmSync");
188
+ return Reflect.apply(origRmSync, fs, [pathArg, ...args]);
189
+ };
190
+
191
+ anyFs.rename = function (oldPath: PathLike, newPath: PathLike, ...args: unknown[]) {
192
+ validateTarget(oldPath, "rename:source");
193
+ validateTarget(newPath, "rename:destination");
194
+ return Reflect.apply(origRename, fs, [oldPath, newPath, ...args]);
195
+ };
196
+
197
+ anyFs.renameSync = function (oldPath: PathLike, newPath: PathLike, ...args: unknown[]) {
198
+ validateTarget(oldPath, "renameSync:source");
199
+ validateTarget(newPath, "renameSync:destination");
200
+ return Reflect.apply(origRenameSync, fs, [oldPath, newPath, ...args]);
201
+ };
202
+
203
+ anyFs.copyFile = function (src: PathLike, dest: PathLike, ...args: unknown[]) {
204
+ validateTarget(dest, "copyFile:destination");
205
+ return Reflect.apply(origCopyFile, fs, [src, dest, ...args]);
206
+ };
207
+
208
+ anyFs.copyFileSync = function (src: PathLike, dest: PathLike, ...args: unknown[]) {
209
+ validateTarget(dest, "copyFileSync:destination");
210
+ return Reflect.apply(origCopyFileSync, fs, [src, dest, ...args]);
211
+ };
212
+
213
+ anyFs.createWriteStream = function (pathArg: PathLike, ...args: unknown[]) {
214
+ validateTarget(pathArg, "createWriteStream");
215
+ return Reflect.apply(origCreateWriteStream, fs, [pathArg, ...args]);
216
+ };
217
+
218
+ // Intercept node:fs/promises
219
+ const anyFsp = fsp as any;
220
+
221
+ anyFsp.writeFile = async function (file: PathLike | number | fsp.FileHandle, ...args: unknown[]) {
222
+ if (typeof file === "string" || Buffer.isBuffer(file) || file instanceof URL) {
223
+ validateTarget(file as unknown as PathLike, "fsp.writeFile");
224
+ }
225
+ return Reflect.apply(origPromisesWriteFile, fsp, [file, ...args]);
226
+ };
227
+
228
+ anyFsp.appendFile = async function (file: PathLike | number | fsp.FileHandle, ...args: unknown[]) {
229
+ if (typeof file === "string" || Buffer.isBuffer(file) || file instanceof URL) {
230
+ validateTarget(file as unknown as PathLike, "fsp.appendFile");
231
+ }
232
+ return Reflect.apply(origPromisesAppendFile, fsp, [file, ...args]);
233
+ };
234
+
235
+ anyFsp.mkdir = async function (pathArg: PathLike, ...args: unknown[]) {
236
+ validateTarget(pathArg, "fsp.mkdir");
237
+ return Reflect.apply(origPromisesMkdir, fsp, [pathArg, ...args]);
238
+ };
239
+
240
+ anyFsp.rmdir = async function (pathArg: PathLike, ...args: unknown[]) {
241
+ validateTarget(pathArg, "fsp.rmdir");
242
+ return Reflect.apply(origPromisesRmdir, fsp, [pathArg, ...args]);
243
+ };
244
+
245
+ anyFsp.unlink = async function (pathArg: PathLike, ...args: unknown[]) {
246
+ validateTarget(pathArg, "fsp.unlink");
247
+ return Reflect.apply(origPromisesUnlink, fsp, [pathArg, ...args]);
248
+ };
249
+
250
+ anyFsp.rm = async function (pathArg: PathLike, ...args: unknown[]) {
251
+ validateTarget(pathArg, "fsp.rm");
252
+ return Reflect.apply(origPromisesRm, fsp, [pathArg, ...args]);
253
+ };
254
+
255
+ anyFsp.rename = async function (oldPath: PathLike, newPath: PathLike) {
256
+ validateTarget(oldPath, "fsp.rename:source");
257
+ validateTarget(newPath, "fsp.rename:destination");
258
+ return Reflect.apply(origPromisesRename, fsp, [oldPath, newPath]);
259
+ };
260
+
261
+ anyFsp.copyFile = async function (src: PathLike, dest: PathLike, ...args: unknown[]) {
262
+ validateTarget(dest, "fsp.copyFile:destination");
263
+ return Reflect.apply(origPromisesCopyFile, fsp, [src, dest, ...args]);
264
+ };
265
+
266
+ return function uninstallStorageGuard(): void {
267
+ fs.writeFile = origWriteFile;
268
+ fs.writeFileSync = origWriteFileSync;
269
+ fs.appendFile = origAppendFile;
270
+ fs.appendFileSync = origAppendFileSync;
271
+ fs.mkdir = origMkdir;
272
+ fs.mkdirSync = origMkdirSync;
273
+ fs.rmdir = origRmdir;
274
+ fs.rmdirSync = origRmdirSync;
275
+ fs.unlink = origUnlink;
276
+ fs.unlinkSync = origUnlinkSync;
277
+ fs.rm = origRm;
278
+ fs.rmSync = origRmSync;
279
+ fs.rename = origRename;
280
+ fs.renameSync = origRenameSync;
281
+ fs.copyFile = origCopyFile;
282
+ fs.copyFileSync = origCopyFileSync;
283
+ fs.createWriteStream = origCreateWriteStream;
284
+
285
+ fsp.writeFile = origPromisesWriteFile;
286
+ fsp.appendFile = origPromisesAppendFile;
287
+ fsp.mkdir = origPromisesMkdir;
288
+ fsp.rmdir = origPromisesRmdir;
289
+ fsp.unlink = origPromisesUnlink;
290
+ fsp.rm = origPromisesRm;
291
+ fsp.rename = origPromisesRename;
292
+ fsp.copyFile = origPromisesCopyFile;
293
+ };
294
+ }
@@ -7,7 +7,7 @@ export interface PluginStorageOptions<T> {
7
7
  defaultData?: T;
8
8
  /**
9
9
  * Base directory override. When provided, storage is located at path.join(baseDir, pluginId).
10
- * When omitted, defaults to path.join(os.homedir(), ".paseo", namespace ?? "plugin-data/xpufx", pluginId).
10
+ * When omitted, defaults to path.join(resolvePaseoHome(), namespace ?? "plugin-data/xpufx", pluginId).
11
11
  */
12
12
  baseDir?: string;
13
13
  /**
@@ -27,6 +27,40 @@ export interface StorageStats {
27
27
  lastModified: Date | null;
28
28
  }
29
29
 
30
+ const AGENT_MUX_PROFILE_HOME_RE = /(?:^|[\\/])\.agent-mux[\\/]profiles[\\/]/;
31
+
32
+ /**
33
+ * Resolve the real user home even when `HOME` points into an agent-mux profile.
34
+ * A profile session has `HOME=~/.agent-mux/profiles/<provider>/<profile>` while
35
+ * the live Paseo state stays on the host home. agent-mux exports `REAL_HOME` for
36
+ * every profile session, so prefer it; otherwise strip the profile prefix so
37
+ * paths under `~/.paseo` still resolve to the host (#1158, #973).
38
+ */
39
+ export function resolveHostHome(env: NodeJS.ProcessEnv = process.env): string {
40
+ const home = (env.HOME || os.homedir() || "").trim();
41
+ if (!home || !AGENT_MUX_PROFILE_HOME_RE.test(home)) {
42
+ return home;
43
+ }
44
+ const realHome = (env.REAL_HOME || "").trim();
45
+ if (realHome) return realHome;
46
+ const marker = home.match(AGENT_MUX_PROFILE_HOME_RE);
47
+ if (marker && typeof marker.index === "number" && marker.index > 0) {
48
+ return home.slice(0, marker.index);
49
+ }
50
+ return home;
51
+ }
52
+
53
+ /**
54
+ * Resolve the canonical `~/.paseo` directory. `PASEO_HOME` is the daemon's own
55
+ * declaration of that root, so prefer it; otherwise anchor to the host home
56
+ * rather than an agent-mux profile (#1158).
57
+ */
58
+ export function resolvePaseoHome(env: NodeJS.ProcessEnv = process.env): string {
59
+ const paseoHome = (env.PASEO_HOME || "").trim();
60
+ if (paseoHome) return paseoHome;
61
+ return path.join(resolveHostHome(env), ".paseo");
62
+ }
63
+
30
64
  export const DEFAULT_NAMESPACE_README = `# Paseo Plugins Storage (xpufx)
31
65
 
32
66
  This directory is managed by \`paseo-plugin-helper\` to store persistent settings, cached metrics, and state for plugins.
@@ -60,7 +94,7 @@ export class PluginStorage<T extends Record<string, any>> {
60
94
  this.namespaceDir = options.baseDir;
61
95
  this.pluginDir = path.join(options.baseDir, pluginId);
62
96
  } else {
63
- this.namespaceDir = path.join(os.homedir(), ".paseo", namespace);
97
+ this.namespaceDir = path.join(resolvePaseoHome(), namespace);
64
98
  this.pluginDir = path.join(this.namespaceDir, pluginId);
65
99
  }
66
100