@getpaseo/server 0.11.0-beta.1 → 0.11.0-beta.2
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/dist/server/builtin-plugins/claude-usage-source/index.server.ts +2 -2
- package/dist/server/builtin-plugins/claude-usage-source/server/stores.ts +132 -0
- package/dist/server/builtin-plugins/claude-usage-source/server/usage.ts +45 -29
- package/dist/server/builtin-plugins/claude-usage-source/shared/input.ts +13 -1
- package/dist/server/builtin-plugins/codex-usage-source/index.server.ts +2 -2
- package/dist/server/builtin-plugins/codex-usage-source/server/stores.ts +132 -0
- package/dist/server/builtin-plugins/codex-usage-source/server/usage.ts +55 -25
- package/dist/server/builtin-plugins/codex-usage-source/shared/input.ts +11 -1
- package/dist/server/builtin-plugins/package.json +1 -1
- package/dist/server/server/orchestration-skills/internal/operations.js +19 -5
- package/dist/server/server/orchestration-skills/internal/sync.d.ts +12 -0
- package/dist/server/server/orchestration-skills/internal/sync.js +48 -2
- package/dist/server/server/plugins/builtin/index.js +6 -0
- package/dist/server/server/plugins/compiler.js +4 -0
- package/dist/server/server/plugins/usage-sources/index.d.ts +1 -1
- package/dist/server/server/plugins/usage-sources/index.js +53 -21
- package/dist/server/web-ui/_expo/static/js/web/{index-87a07caf197cc320e593332ed2d5e20f.js → index-a43718759ebeb27df202e8b9fc2fe58a.js} +6 -6
- package/dist/server/web-ui/_expo/static/js/web/index-a43718759ebeb27df202e8b9fc2fe58a.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-a43718759ebeb27df202e8b9fc2fe58a.js.gz +0 -0
- package/dist/server/web-ui/index.html +1 -1
- package/dist/server/web-ui/index.html.br +0 -0
- package/dist/server/web-ui/index.html.gz +0 -0
- package/package.json +6 -6
- package/dist/server/web-ui/_expo/static/js/web/index-87a07caf197cc320e593332ed2d5e20f.js.br +0 -0
- package/dist/server/web-ui/_expo/static/js/web/index-87a07caf197cc320e593332ed2d5e20f.js.gz +0 -0
|
@@ -31,7 +31,7 @@ export async function listFilesRecursive(rootDir) {
|
|
|
31
31
|
await walk(rootDir);
|
|
32
32
|
return out;
|
|
33
33
|
}
|
|
34
|
-
async function readManagedFilesManifest(dstDir) {
|
|
34
|
+
export async function readManagedFilesManifest(dstDir) {
|
|
35
35
|
const raw = await fs
|
|
36
36
|
.readFile(path.join(dstDir, MANAGED_FILES_MANIFEST), "utf-8")
|
|
37
37
|
.catch(() => null);
|
|
@@ -152,6 +152,49 @@ export async function removeSkill(skillName, targets) {
|
|
|
152
152
|
await fs.rm(p, { recursive: true, force: true });
|
|
153
153
|
}
|
|
154
154
|
}
|
|
155
|
+
async function isSameDirectory(a, b) {
|
|
156
|
+
const [realA, realB] = await Promise.all([a, b].map((p) => fs.realpath(p).catch(() => null)));
|
|
157
|
+
return realA !== null && realA === realB;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Codex also discovers .agents/skills, so its dedicated copy is a leftover. When
|
|
161
|
+
* the Codex skill directory resolves to the shared or Claude copy, it is that
|
|
162
|
+
* copy, and retiring it would delete the install.
|
|
163
|
+
*/
|
|
164
|
+
export async function isLegacyCodexCopy(skillName, targets) {
|
|
165
|
+
const codexSkillDir = path.join(targets.codexDir, skillName);
|
|
166
|
+
for (const root of [targets.agentsDir, targets.claudeDir]) {
|
|
167
|
+
if (await isSameDirectory(codexSkillDir, path.join(root, skillName)))
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
async function retireManagedSkill(dstDir) {
|
|
173
|
+
const info = await fs.lstat(dstDir).catch(() => null);
|
|
174
|
+
if (!info?.isDirectory())
|
|
175
|
+
return 0;
|
|
176
|
+
const manifest = await readManagedFilesManifest(dstDir);
|
|
177
|
+
if (!manifest)
|
|
178
|
+
return 0;
|
|
179
|
+
const files = Object.keys(manifest.files);
|
|
180
|
+
await assertManagedPathsStayInsideSkill(dstDir, [MANAGED_FILES_MANIFEST, ...files]);
|
|
181
|
+
const removed = [];
|
|
182
|
+
for (const [rel, previousHash] of Object.entries(manifest.files)) {
|
|
183
|
+
const filePath = path.join(dstDir, rel);
|
|
184
|
+
const currentHash = await hashFile(filePath).catch(() => null);
|
|
185
|
+
if (currentHash !== previousHash)
|
|
186
|
+
continue;
|
|
187
|
+
await fs.rm(filePath);
|
|
188
|
+
removed.push(rel);
|
|
189
|
+
}
|
|
190
|
+
await pruneEmptyParentDirs(dstDir, removed);
|
|
191
|
+
await fs.rm(path.join(dstDir, MANAGED_FILES_MANIFEST));
|
|
192
|
+
await fs.rmdir(dstDir).catch((error) => {
|
|
193
|
+
if (error.code !== "ENOTEMPTY" && error.code !== "ENOENT")
|
|
194
|
+
throw error;
|
|
195
|
+
});
|
|
196
|
+
return removed.length + 1;
|
|
197
|
+
}
|
|
155
198
|
export async function syncSkills(options) {
|
|
156
199
|
let changedFiles = 0;
|
|
157
200
|
let processedSkills = 0;
|
|
@@ -163,7 +206,10 @@ export async function syncSkills(options) {
|
|
|
163
206
|
try {
|
|
164
207
|
changedFiles += await syncDirectoryFiles(bundleSkillDir, path.join(options.agentsDir, skillName));
|
|
165
208
|
changedFiles += await syncDirectoryFiles(bundleSkillDir, path.join(options.claudeDir, skillName));
|
|
166
|
-
|
|
209
|
+
// Retain user edits to the old dedicated Codex copy.
|
|
210
|
+
if (await isLegacyCodexCopy(skillName, options)) {
|
|
211
|
+
changedFiles += await retireManagedSkill(path.join(options.codexDir, skillName));
|
|
212
|
+
}
|
|
167
213
|
processedSkills++;
|
|
168
214
|
}
|
|
169
215
|
catch (error) {
|
|
@@ -16,6 +16,12 @@ export const builtinPlugins = [
|
|
|
16
16
|
];
|
|
17
17
|
export function resolveBuiltinPluginsRoot(moduleUrl = import.meta.url) {
|
|
18
18
|
const moduleDir = path.dirname(fileURLToPath(moduleUrl));
|
|
19
|
+
const archiveSegment = `${path.sep}app.asar${path.sep}`;
|
|
20
|
+
const archiveIndex = moduleDir.indexOf(archiveSegment);
|
|
21
|
+
if (archiveIndex !== -1) {
|
|
22
|
+
// esbuild runs outside Electron's filesystem shim and cannot read archive entries.
|
|
23
|
+
return path.join(moduleDir.slice(0, archiveIndex), "builtin-plugins");
|
|
24
|
+
}
|
|
19
25
|
const candidates = [
|
|
20
26
|
path.resolve(moduleDir, "..", "..", "..", "builtin-plugins"),
|
|
21
27
|
path.resolve(moduleDir, "..", "..", "..", "..", "..", "..", "plugins"),
|
|
@@ -320,6 +320,10 @@ async function compileTarget(entryPath, target) {
|
|
|
320
320
|
jsx: "automatic",
|
|
321
321
|
platform: target === "server" ? "node" : "neutral",
|
|
322
322
|
target: target === "server" ? "node20" : "es2020",
|
|
323
|
+
// The neutral platform reads no package.json entry fields, so packages without
|
|
324
|
+
// `exports` would not resolve. Read the same fields as esbuild's browser platform
|
|
325
|
+
// minus `browser`, since the bundle also runs in React Native.
|
|
326
|
+
mainFields: target === "client" ? ["module", "main"] : undefined,
|
|
323
327
|
// Metro lowers async syntax before Hermes sees app code. Plugin client bundles bypass Metro,
|
|
324
328
|
// so apply the same compatibility transform before the app evaluates them from source.
|
|
325
329
|
supported: target === "client" ? { "async-await": false } : undefined,
|
|
@@ -10,7 +10,7 @@ export interface UsageSource {
|
|
|
10
10
|
} | null>;
|
|
11
11
|
fetch(input: unknown): Promise<unknown>;
|
|
12
12
|
}
|
|
13
|
-
/** Owns account identity,
|
|
13
|
+
/** Owns account identity, ordered logins for each account, and the five-minute fetch cache. */
|
|
14
14
|
export declare class UsageSourceRegistry {
|
|
15
15
|
private readonly now;
|
|
16
16
|
private readonly ttlMs;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { UsageReportSchema, } from "@getpaseo/protocol/messages";
|
|
2
|
-
/** Owns account identity,
|
|
2
|
+
/** Owns account identity, ordered logins for each account, and the five-minute fetch cache. */
|
|
3
3
|
export class UsageSourceRegistry {
|
|
4
4
|
constructor(now = Date.now, ttlMs = 300000) {
|
|
5
5
|
this.now = now;
|
|
@@ -33,14 +33,12 @@ export class UsageSourceRegistry {
|
|
|
33
33
|
throw new Error(`Invalid account identity from ${source.id}`);
|
|
34
34
|
if (typeof account.key !== "string" || !/^[A-Za-z0-9._-]{1,128}$/.test(account.key))
|
|
35
35
|
throw new Error(`Invalid account key from ${source.id}`);
|
|
36
|
-
|
|
37
|
-
this.known.set(id, { source, input, label: account.label });
|
|
38
|
-
return id;
|
|
36
|
+
return { id: `${source.id}:${account.key}`, label: account.label };
|
|
39
37
|
}
|
|
40
38
|
catch (error) {
|
|
41
39
|
const id = `${source.id}:!error`;
|
|
42
40
|
this.writeCache(id, this.errorEntry(source, id, error));
|
|
43
|
-
return id;
|
|
41
|
+
return { id, error: true };
|
|
44
42
|
}
|
|
45
43
|
}
|
|
46
44
|
async listReports(options = {}) {
|
|
@@ -55,7 +53,33 @@ export class UsageSourceRegistry {
|
|
|
55
53
|
const inputs = await source.discover();
|
|
56
54
|
if (!Array.isArray(inputs))
|
|
57
55
|
throw new Error("Usage discovery must return an array");
|
|
58
|
-
|
|
56
|
+
const accounts = await Promise.all(inputs.map((input) => this.identify(source, input)));
|
|
57
|
+
// Group in discovery order so an account's logins keep the source's preference.
|
|
58
|
+
const knownReports = new Map();
|
|
59
|
+
const ids = [];
|
|
60
|
+
accounts.forEach((account, index) => {
|
|
61
|
+
if (account === null)
|
|
62
|
+
return;
|
|
63
|
+
ids.push(account.id);
|
|
64
|
+
if (account.error)
|
|
65
|
+
return;
|
|
66
|
+
const known = knownReports.get(account.id);
|
|
67
|
+
if (known)
|
|
68
|
+
known.inputs.push(inputs[index]);
|
|
69
|
+
else
|
|
70
|
+
knownReports.set(account.id, {
|
|
71
|
+
source,
|
|
72
|
+
inputs: [inputs[index]],
|
|
73
|
+
label: account.label,
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
for (const key of this.known.keys()) {
|
|
77
|
+
if (key.startsWith(`${source.id}:`))
|
|
78
|
+
this.known.delete(key);
|
|
79
|
+
}
|
|
80
|
+
for (const [id, known] of knownReports)
|
|
81
|
+
this.known.set(id, known);
|
|
82
|
+
return ids;
|
|
59
83
|
}
|
|
60
84
|
catch (error) {
|
|
61
85
|
const id = `${source.id}:!error`;
|
|
@@ -97,21 +121,29 @@ export class UsageSourceRegistry {
|
|
|
97
121
|
if (pending)
|
|
98
122
|
return pending;
|
|
99
123
|
const request = (async () => {
|
|
100
|
-
let entry
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
124
|
+
let entry = {
|
|
125
|
+
id,
|
|
126
|
+
sourceId: known.source.id,
|
|
127
|
+
sourceLabel: known.source.label,
|
|
128
|
+
icon: known.source.icon,
|
|
129
|
+
account: { label: known.label },
|
|
130
|
+
fetchedAt: new Date(this.now()).toISOString(),
|
|
131
|
+
report: { status: "unavailable", windows: [] },
|
|
132
|
+
};
|
|
133
|
+
for (const input of known.inputs) {
|
|
134
|
+
try {
|
|
135
|
+
// A harness can switch accounts between discovery and a targeted refresh.
|
|
136
|
+
const account = await known.source.identify(input);
|
|
137
|
+
if (!account || `${known.source.id}:${account.key}` !== id)
|
|
138
|
+
continue;
|
|
139
|
+
const report = UsageReportSchema.parse(await known.source.fetch(input));
|
|
140
|
+
entry.report = report;
|
|
141
|
+
if (report.status === "available")
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
entry = this.errorEntry(known.source, id, error, known.label);
|
|
146
|
+
}
|
|
115
147
|
}
|
|
116
148
|
this.writeCache(id, entry);
|
|
117
149
|
return entry;
|