@rynx-ai/daemon 0.1.10 → 0.1.11-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/bundled-plugins/.rynx-plugin/marketplace.json +10 -0
- package/bundled-plugins/plugins/lark/.rynx-plugin/plugin.json +37 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.d.ts +48 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/mirror-index.js +54 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-mirror.d.ts +85 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-mirror.js +333 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-watcher.d.ts +109 -0
- package/bundled-plugins/plugins/lark/dist/cli-mirror/rollout-watcher.js +347 -0
- package/bundled-plugins/plugins/lark/dist/host-adapters.d.ts +95 -0
- package/bundled-plugins/plugins/lark/dist/host-adapters.js +504 -0
- package/bundled-plugins/plugins/lark/dist/index.d.ts +7 -0
- package/bundled-plugins/plugins/lark/dist/index.js +7 -0
- package/bundled-plugins/plugins/lark/dist/lark-card-stream.d.ts +363 -0
- package/bundled-plugins/plugins/lark/dist/lark-card-stream.js +1335 -0
- package/bundled-plugins/plugins/lark/dist/lark-config-store.d.ts +30 -0
- package/bundled-plugins/plugins/lark/dist/lark-config-store.js +135 -0
- package/bundled-plugins/plugins/lark/dist/lark-content.d.ts +30 -0
- package/bundled-plugins/plugins/lark/dist/lark-content.js +138 -0
- package/bundled-plugins/plugins/lark/dist/lark-conversation-directory.d.ts +65 -0
- package/bundled-plugins/plugins/lark/dist/lark-conversation-directory.js +106 -0
- package/bundled-plugins/plugins/lark/dist/lark-diff-card.d.ts +25 -0
- package/bundled-plugins/plugins/lark/dist/lark-diff-card.js +58 -0
- package/bundled-plugins/plugins/lark/dist/lark-fork-transcript.d.ts +16 -0
- package/bundled-plugins/plugins/lark/dist/lark-fork-transcript.js +141 -0
- package/bundled-plugins/plugins/lark/dist/lark-resume-card.d.ts +31 -0
- package/bundled-plugins/plugins/lark/dist/lark-resume-card.js +105 -0
- package/bundled-plugins/plugins/lark/dist/lark-runtime-state-store.d.ts +18 -0
- package/bundled-plugins/plugins/lark/dist/lark-runtime-state-store.js +95 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/client.d.ts +8 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/client.js +32 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/index.d.ts +1 -0
- package/bundled-plugins/plugins/lark/dist/lark-sdk/index.js +1 -0
- package/bundled-plugins/plugins/lark/dist/lark-sender.d.ts +114 -0
- package/bundled-plugins/plugins/lark/dist/lark-sender.js +323 -0
- package/bundled-plugins/plugins/lark/dist/lark-session-store.d.ts +13 -0
- package/bundled-plugins/plugins/lark/dist/lark-session-store.js +14 -0
- package/bundled-plugins/plugins/lark/dist/lark-settings-card.d.ts +55 -0
- package/bundled-plugins/plugins/lark/dist/lark-settings-card.js +158 -0
- package/bundled-plugins/plugins/lark/dist/lark-setup.d.ts +39 -0
- package/bundled-plugins/plugins/lark/dist/lark-setup.js +103 -0
- package/bundled-plugins/plugins/lark/dist/lark.d.ts +507 -0
- package/bundled-plugins/plugins/lark/dist/lark.js +1977 -0
- package/bundled-plugins/plugins/lark/dist/runtime.d.ts +13 -0
- package/bundled-plugins/plugins/lark/dist/runtime.js +131316 -0
- package/bundled-plugins/plugins/lark/dist/settings.d.ts +7 -0
- package/bundled-plugins/plugins/lark/dist/settings.js +126958 -0
- package/bundled-plugins/plugins/lark/package.json +57 -0
- package/bundled-plugins/plugins/lark/ui/assets/index-B2dhMlMU.css +1 -0
- package/bundled-plugins/plugins/lark/ui/assets/index-D9N-AkLX.js +201 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
- package/bundled-plugins/plugins/lark/ui/index.html +13 -0
- package/dist/bundled-plugins.d.ts +15 -0
- package/dist/bundled-plugins.js +134 -0
- package/dist/cli.js +26 -208
- package/dist/control-deps.js +2 -79
- package/dist/daemon-server.d.ts +4 -1
- package/dist/daemon-server.js +167 -57
- package/dist/db.js +145 -124
- package/dist/index-daemon.js +25 -4
- package/dist/migrations/cleanup-legacy-sessions.d.ts +1 -1
- package/dist/migrations/cleanup-legacy-sessions.js +33 -15
- package/dist/official-plugins.d.ts +2 -0
- package/dist/official-plugins.js +2 -0
- package/dist/plugin-cli.d.ts +1 -3
- package/dist/plugin-cli.js +56 -45
- package/dist/plugin-host-leases.d.ts +4 -0
- package/dist/plugin-host-leases.js +7 -0
- package/dist/plugin-host-rpc.d.ts +42 -4
- package/dist/plugin-host-rpc.js +144 -34
- package/dist/plugin-installer.d.ts +24 -55
- package/dist/plugin-installer.js +202 -415
- package/dist/plugin-management-service.d.ts +30 -23
- package/dist/plugin-management-service.js +290 -105
- package/dist/plugin-marketplace.d.ts +35 -0
- package/dist/plugin-marketplace.js +299 -0
- package/dist/plugin-package.d.ts +22 -24
- package/dist/plugin-package.js +520 -571
- package/dist/plugin-skill-context.d.ts +4 -0
- package/dist/plugin-skill-context.js +35 -0
- package/dist/plugin-store.d.ts +36 -37
- package/dist/plugin-store.js +140 -92
- package/dist/plugin-supervisor.d.ts +45 -125
- package/dist/plugin-supervisor.js +516 -1053
- package/dist/pm2.js +15 -10
- package/dist/runtime-project-store.d.ts +9 -0
- package/dist/runtime-project-store.js +83 -0
- package/dist/session-fallback-workspace.d.ts +22 -0
- package/dist/session-fallback-workspace.js +120 -0
- package/dist/session-fork-store.d.ts +41 -0
- package/dist/session-fork-store.js +288 -0
- package/dist/session-launch-operations.d.ts +14 -2
- package/dist/session-launch-operations.js +34 -2
- package/dist/session-meta-store.d.ts +7 -9
- package/dist/session-meta-store.js +28 -49
- package/dist/setup.js +26 -14
- package/package.json +14 -11
- package/dist/channel-store.d.ts +0 -22
- package/dist/channel-store.js +0 -65
- package/dist/instance-store.d.ts +0 -38
- package/dist/instance-store.js +0 -85
- package/dist/plugin-install-restart.d.ts +0 -13
- package/dist/plugin-install-restart.js +0 -24
- package/dist/plugin-runtime-control.d.ts +0 -2
- package/dist/plugin-runtime-control.js +0 -37
- package/dist/registry.d.ts +0 -10
- package/dist/registry.js +0 -52
package/dist/plugin-installer.js
CHANGED
|
@@ -1,81 +1,30 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
* installer reads only static JSON metadata, validates every declared entry,
|
|
6
|
-
* hashes the package, and atomically renames the validated tree into an
|
|
7
|
-
* immutable installation path. Plugin code is never imported here.
|
|
8
|
-
*/
|
|
9
|
-
import { randomUUID } from "node:crypto";
|
|
10
|
-
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, writeFileSync, } from "node:fs";
|
|
11
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
12
|
-
import { PLUGIN_ID_PATTERN, pluginCliInvocationLeasePid, readPluginRuntimeLeases, removePluginRuntimeLease, } from "@rynx-ai/plugin-sdk";
|
|
1
|
+
/** Transactional, package-manager-neutral plugin installation. */
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, mkdtempSync, readdirSync, realpathSync, renameSync, rmSync, } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { MARKETPLACE_ID_PATTERN, pluginCliInvocationLeasePid, readPluginRuntimeLeases, removePluginRuntimeLease, } from "@rynx-ai/plugin-sdk";
|
|
13
5
|
import { rynxHome } from "@rynx-ai/core";
|
|
14
|
-
import { getPlugin, isRunnablePluginRecord, removePlugin, upsertPlugin, } from "./plugin-store.js";
|
|
15
|
-
import { detectPluginSource, inspectPluginPackage, materializePluginPackage, normalizePluginSpec, rebuildPluginPackage, } from "./plugin-package.js";
|
|
16
|
-
import { pluginsDir } from "./registry.js";
|
|
17
6
|
import { db } from "./db.js";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const PROMOTION_MARKER_SUFFIX = ".rynx-promoted";
|
|
22
|
-
const MAX_HOST_MARKER_BYTES = 4 * 1024;
|
|
23
|
-
const DEFAULT_STAGING_GC_AGE_MS = 24 * 60 * 60 * 1_000;
|
|
24
|
-
/**
|
|
25
|
-
* Install or replace one arbitrary npm/local/git/URL plugin package.
|
|
26
|
-
*
|
|
27
|
-
* A failed validation, materialization, atomic move, or registry update leaves
|
|
28
|
-
* the previously registered installation untouched.
|
|
29
|
-
*/
|
|
7
|
+
import { isBuiltInPluginMarketplace } from "./plugin-marketplace.js";
|
|
8
|
+
import { detectPluginSource, inspectPluginPackage, materializePluginPackage, } from "./plugin-package.js";
|
|
9
|
+
import { canonicalPluginId, getPlugin, removePlugin, upsertPlugin, } from "./plugin-store.js";
|
|
30
10
|
export async function installPlugin(spec, onProgress = () => { }, opts = {}) {
|
|
31
11
|
const prepared = await preparePluginInstallation(spec, onProgress, opts);
|
|
32
12
|
try {
|
|
33
|
-
if (opts.expectedDigest !==
|
|
34
|
-
|
|
35
|
-
throw new Error("expected plugin digest is invalid");
|
|
13
|
+
if (opts.expectedDigest && opts.expectedDigest !== prepared.integrity) {
|
|
14
|
+
throw new Error(`prepared plugin digest does not match ${opts.expectedDigest}`);
|
|
36
15
|
}
|
|
37
|
-
if (opts.
|
|
38
|
-
opts.expectedDigest !== prepared.integrity) {
|
|
39
|
-
throw new Error(`prepared plugin digest ${prepared.integrity} does not match ` +
|
|
40
|
-
`expected digest ${opts.expectedDigest}`);
|
|
41
|
-
}
|
|
42
|
-
if (prepared.manifest.requiresBuildScripts) {
|
|
43
|
-
if (!opts.allowScripts) {
|
|
44
|
-
throw new Error(`plugin "${prepared.name}" requires package build scripts; review digest ` +
|
|
45
|
-
`${prepared.integrity} and re-run with --allow-scripts ` +
|
|
46
|
-
`--expect-digest ${prepared.integrity}`);
|
|
47
|
-
}
|
|
48
|
-
if (opts.expectedDigest === undefined) {
|
|
49
|
-
throw new Error(`build-script approval must be bound to the prepared artifact; ` +
|
|
50
|
-
`re-run with --expect-digest ${prepared.integrity}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
const existing = prepared.existing;
|
|
54
|
-
if (existing && opts.onConflict && !(await opts.onConflict(existing))) {
|
|
16
|
+
if (prepared.existing && opts.onConflict && !(await opts.onConflict(prepared.existing))) {
|
|
55
17
|
return {
|
|
56
|
-
name: prepared.
|
|
18
|
+
name: prepared.canonicalId,
|
|
19
|
+
canonicalId: prepared.canonicalId,
|
|
20
|
+
marketplaceId: prepared.marketplaceId,
|
|
21
|
+
pluginId: prepared.pluginId,
|
|
57
22
|
source: prepared.source,
|
|
58
|
-
|
|
23
|
+
version: prepared.version,
|
|
59
24
|
skipped: true,
|
|
60
25
|
};
|
|
61
26
|
}
|
|
62
|
-
if (opts.signal?.aborted)
|
|
63
|
-
throw new Error("plugin installation aborted");
|
|
64
|
-
const requested = new Set(prepared.manifest.requestedCapabilities);
|
|
65
|
-
if (!opts.approveCapabilities &&
|
|
66
|
-
opts.grants === undefined &&
|
|
67
|
-
!existing &&
|
|
68
|
-
requested.size > 0) {
|
|
69
|
-
throw new Error(`plugin "${prepared.name}" requests host capabilities; explicit grants or approval are required`);
|
|
70
|
-
}
|
|
71
|
-
const grants = opts.approveCapabilities
|
|
72
|
-
? await opts.approveCapabilities(prepared.manifest, existing)
|
|
73
|
-
: opts.grants ??
|
|
74
|
-
(existing
|
|
75
|
-
? existing.grants.filter((capability) => requested.has(capability))
|
|
76
|
-
: []);
|
|
77
27
|
return await prepared.commit({
|
|
78
|
-
grants,
|
|
79
28
|
expectedDigest: opts.expectedDigest ?? prepared.integrity,
|
|
80
29
|
signal: opts.signal,
|
|
81
30
|
});
|
|
@@ -84,163 +33,124 @@ export async function installPlugin(spec, onProgress = () => { }, opts = {}) {
|
|
|
84
33
|
prepared.discard();
|
|
85
34
|
}
|
|
86
35
|
}
|
|
87
|
-
/**
|
|
88
|
-
* Materialize, build (only when explicitly allowed), inspect, and hash one
|
|
89
|
-
* plugin into host-owned staging without mutating the registry.
|
|
90
|
-
*/
|
|
91
36
|
export async function preparePluginInstallation(spec, onProgress = () => { }, opts = {}) {
|
|
92
37
|
if (!spec.trim())
|
|
93
|
-
throw new Error("plugin
|
|
94
|
-
|
|
38
|
+
throw new Error("plugin source must not be empty");
|
|
39
|
+
opts.signal?.throwIfAborted();
|
|
95
40
|
const source = detectPluginSource(spec);
|
|
96
|
-
const storedSpec = normalizePluginSpec(spec, source);
|
|
97
41
|
const storage = ensureStorageRoots();
|
|
98
42
|
const transactionDir = mkdtempSync(join(storage.stagingRoot, "install-"));
|
|
99
|
-
try {
|
|
100
|
-
writeStagingOwner(transactionDir);
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
rmSync(transactionDir, { recursive: true, force: true });
|
|
104
|
-
throw error;
|
|
105
|
-
}
|
|
106
43
|
const stagedPackage = join(transactionDir, "package");
|
|
107
|
-
let finalPath;
|
|
108
|
-
let registryUpdated = false;
|
|
109
44
|
let consumed = false;
|
|
45
|
+
let promotedPath;
|
|
46
|
+
let promotedByThisTransaction = false;
|
|
110
47
|
try {
|
|
111
|
-
await (opts.materializer ?? materializePluginPackage)({
|
|
112
|
-
spec
|
|
48
|
+
const materialized = await (opts.materializer ?? materializePluginPackage)({
|
|
49
|
+
spec,
|
|
113
50
|
source,
|
|
114
51
|
destination: stagedPackage,
|
|
115
|
-
onProgress:
|
|
52
|
+
onProgress: safeProgress(onProgress),
|
|
116
53
|
signal: opts.signal,
|
|
117
54
|
});
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
if (opts.allowScripts && !inspected.manifest.requiresBuildScripts) {
|
|
123
|
-
throw new Error(`plugin "${inspected.manifest.id}" does not declare requiresBuildScripts; ` +
|
|
124
|
-
"refusing unnecessary --allow-scripts approval");
|
|
55
|
+
validateMaterialized(materialized);
|
|
56
|
+
const inspected = await inspectPluginPackage(stagedPackage, { signal: opts.signal });
|
|
57
|
+
if (opts.expectedId && inspected.manifest.id !== opts.expectedId) {
|
|
58
|
+
throw new Error(`plugin update id mismatch: expected "${opts.expectedId}", received "${inspected.manifest.id}"`);
|
|
125
59
|
}
|
|
126
|
-
const
|
|
127
|
-
if (!
|
|
128
|
-
|
|
60
|
+
const marketplaceId = opts.marketplaceId ?? `direct-${materialized.sourceFingerprint}`;
|
|
61
|
+
if (!MARKETPLACE_ID_PATTERN.test(marketplaceId)) {
|
|
62
|
+
throw new Error(`invalid marketplace id "${marketplaceId}"`);
|
|
129
63
|
}
|
|
130
|
-
const
|
|
131
|
-
if (opts.
|
|
132
|
-
throw new Error(`plugin update
|
|
64
|
+
const canonicalId = canonicalPluginId(inspected.manifest.id, marketplaceId);
|
|
65
|
+
if (opts.expectedIdentity && opts.expectedIdentity !== canonicalId) {
|
|
66
|
+
throw new Error(`plugin update identity mismatch: expected "${opts.expectedIdentity}", received "${canonicalId}"`);
|
|
133
67
|
}
|
|
134
|
-
const existing = getPlugin(
|
|
68
|
+
const existing = getPlugin(canonicalId);
|
|
69
|
+
const manifest = cloneManifest(inspected.manifest);
|
|
135
70
|
const cleanup = () => {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
rmSync(transactionDir, { recursive: true, force: true });
|
|
141
|
-
}
|
|
142
|
-
catch {
|
|
143
|
-
/* orphaned staging is inactive and can be collected later */
|
|
71
|
+
rmSync(transactionDir, { recursive: true, force: true });
|
|
72
|
+
if (promotedByThisTransaction && promotedPath && !getPlugin(canonicalId)) {
|
|
73
|
+
rmSync(promotedPath, { recursive: true, force: true });
|
|
144
74
|
}
|
|
145
75
|
};
|
|
146
|
-
const preparedManifest = cloneManifest(inspected.manifest);
|
|
147
|
-
const preparedExisting = existing ? clonePluginRecord(existing) : undefined;
|
|
148
76
|
return {
|
|
149
|
-
name,
|
|
77
|
+
name: canonicalId,
|
|
78
|
+
canonicalId,
|
|
79
|
+
marketplaceId,
|
|
80
|
+
pluginId: manifest.id,
|
|
150
81
|
source,
|
|
151
|
-
|
|
152
|
-
manifest: cloneManifest(
|
|
82
|
+
version: manifest.version,
|
|
83
|
+
manifest: cloneManifest(manifest),
|
|
153
84
|
integrity: inspected.integrity,
|
|
154
|
-
|
|
155
|
-
|
|
85
|
+
requestedSource: structuredClone(materialized.requestedSource),
|
|
86
|
+
resolvedRevision: materialized.resolvedRevision,
|
|
87
|
+
sourceFingerprint: materialized.sourceFingerprint,
|
|
88
|
+
...(existing ? { existing: clonePluginRecord(existing) } : {}),
|
|
89
|
+
commit: async ({ expectedDigest, signal }) => {
|
|
156
90
|
if (consumed)
|
|
157
91
|
throw new Error("prepared plugin installation was already consumed");
|
|
158
92
|
consumed = true;
|
|
159
93
|
try {
|
|
160
|
-
|
|
161
|
-
throw new Error("plugin installation aborted");
|
|
94
|
+
signal?.throwIfAborted();
|
|
162
95
|
if (expectedDigest !== inspected.integrity) {
|
|
163
96
|
throw new Error("prepared plugin digest does not match expected digest");
|
|
164
97
|
}
|
|
165
|
-
const rechecked = await inspectPluginPackage(stagedPackage, {
|
|
166
|
-
...(requiresBuildScripts ? { requireEntries: false } : {}),
|
|
167
|
-
signal,
|
|
168
|
-
});
|
|
98
|
+
const rechecked = await inspectPluginPackage(stagedPackage, { signal });
|
|
169
99
|
if (rechecked.integrity !== inspected.integrity ||
|
|
170
|
-
rechecked.
|
|
171
|
-
|
|
172
|
-
throw new Error("prepared plugin artifact changed after approval");
|
|
100
|
+
JSON.stringify(rechecked.manifest) !== JSON.stringify(manifest)) {
|
|
101
|
+
throw new Error("prepared plugin artifact changed after preparation");
|
|
173
102
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (JSON.stringify(built.manifest) !== JSON.stringify(preparedManifest) ||
|
|
184
|
-
built.version !== inspected.version) {
|
|
185
|
-
throw new Error("plugin build scripts changed static package identity or manifest metadata");
|
|
186
|
-
}
|
|
187
|
-
promoted = built;
|
|
188
|
-
}
|
|
189
|
-
const requested = new Set(preparedManifest.requestedCapabilities);
|
|
190
|
-
for (const grant of grants) {
|
|
191
|
-
if (!requested.has(grant)) {
|
|
192
|
-
throw new Error(`plugin capability was not requested: ${grant}`);
|
|
103
|
+
const target = pluginCachePath(marketplaceId, manifest.id, inspected.integrity, storage.cacheRoot);
|
|
104
|
+
mkdirSync(join(storage.cacheRoot, marketplaceStorageSegment(marketplaceId), manifest.id), {
|
|
105
|
+
recursive: true,
|
|
106
|
+
mode: 0o700,
|
|
107
|
+
});
|
|
108
|
+
if (existsSync(target)) {
|
|
109
|
+
const cached = await inspectPluginPackage(target, { signal });
|
|
110
|
+
if (cached.integrity !== inspected.integrity) {
|
|
111
|
+
throw new Error("plugin cache integrity collision");
|
|
193
112
|
}
|
|
113
|
+
rmSync(stagedPackage, { recursive: true, force: true });
|
|
194
114
|
}
|
|
195
|
-
|
|
196
|
-
|
|
115
|
+
else {
|
|
116
|
+
renameSync(stagedPackage, target);
|
|
117
|
+
promotedByThisTransaction = true;
|
|
197
118
|
}
|
|
119
|
+
promotedPath = realpathSync(target);
|
|
198
120
|
db().transaction(() => {
|
|
199
|
-
const current = getPlugin(
|
|
121
|
+
const current = getPlugin(canonicalId);
|
|
200
122
|
if (!samePluginGeneration(existing, current)) {
|
|
201
|
-
throw new Error(`plugin "${
|
|
202
|
-
}
|
|
203
|
-
const leases = activeRuntimeLeases(name, storage.installationsRoot);
|
|
204
|
-
if (leases.length > 0) {
|
|
205
|
-
throw new Error(`plugin "${name}" has active runtime lease(s): ${leases
|
|
206
|
-
.map((lease) => lease.leaseId)
|
|
207
|
-
.join(", ")}; stop its managed services or CLI commands before updating`);
|
|
123
|
+
throw new Error(`plugin "${canonicalId}" changed concurrently; retry`);
|
|
208
124
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
try {
|
|
215
|
-
renameSync(stagedPackage, finalPath);
|
|
216
|
-
}
|
|
217
|
-
catch (error) {
|
|
218
|
-
rmSync(marker, { force: true });
|
|
219
|
-
finalPath = undefined;
|
|
220
|
-
throw error;
|
|
221
|
-
}
|
|
222
|
-
progress(`staged validated plugin "${name}" → ${finalPath}`);
|
|
125
|
+
// Keep the generation check and lease check in the same synchronous
|
|
126
|
+
// transaction as the registry pointer switch. A CLI invocation
|
|
127
|
+
// acquires its lease in its own immediate transaction, so the two
|
|
128
|
+
// operations cannot pass each other's checks.
|
|
129
|
+
assertNoActiveLeases(current);
|
|
223
130
|
upsertPlugin({
|
|
224
|
-
|
|
225
|
-
|
|
131
|
+
canonicalId,
|
|
132
|
+
marketplaceId,
|
|
133
|
+
pluginId: manifest.id,
|
|
226
134
|
source,
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
135
|
+
requestedSource: materialized.requestedSource,
|
|
136
|
+
resolvedRevision: materialized.resolvedRevision,
|
|
137
|
+
sourceFingerprint: materialized.sourceFingerprint,
|
|
138
|
+
version: manifest.version,
|
|
139
|
+
installPath: promotedPath,
|
|
140
|
+
manifest,
|
|
141
|
+
integrity: inspected.integrity,
|
|
232
142
|
enabled: current?.enabled,
|
|
233
143
|
});
|
|
234
144
|
}).immediate();
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
progress("retained superseded installation until the daemon switches versions");
|
|
238
|
-
}
|
|
239
|
-
progress(`installed plugin "${name}"${promoted.version ? `@${promoted.version}` : ""}`);
|
|
145
|
+
promotedByThisTransaction = false;
|
|
146
|
+
onProgress(`installed plugin "${canonicalId}" at ${manifest.version}`);
|
|
240
147
|
return {
|
|
241
|
-
name,
|
|
148
|
+
name: canonicalId,
|
|
149
|
+
canonicalId,
|
|
150
|
+
marketplaceId,
|
|
151
|
+
pluginId: manifest.id,
|
|
242
152
|
source,
|
|
243
|
-
|
|
153
|
+
version: manifest.version,
|
|
244
154
|
};
|
|
245
155
|
}
|
|
246
156
|
finally {
|
|
@@ -256,138 +166,105 @@ export async function preparePluginInstallation(spec, onProgress = () => { }, op
|
|
|
256
166
|
};
|
|
257
167
|
}
|
|
258
168
|
catch (error) {
|
|
259
|
-
if (!registryUpdated && finalPath) {
|
|
260
|
-
removeOwnedInstallation(storage.installationsRoot, basename(dirname(finalPath)), finalPath);
|
|
261
|
-
}
|
|
262
169
|
rmSync(transactionDir, { recursive: true, force: true });
|
|
263
170
|
throw error;
|
|
264
171
|
}
|
|
265
172
|
}
|
|
266
|
-
|
|
267
|
-
* Remove the registry row but retain the immutable package until the next
|
|
268
|
-
* daemon boot. A currently running supervisor may still be executing it.
|
|
269
|
-
*/
|
|
270
|
-
export async function uninstallPlugin(name, onProgress = () => { }) {
|
|
271
|
-
const progress = safeProgress(onProgress);
|
|
173
|
+
export async function uninstallPlugin(identity, onProgress = () => { }, options = {}) {
|
|
272
174
|
let record;
|
|
273
175
|
const removed = db().transaction(() => {
|
|
274
|
-
record = getPlugin(
|
|
176
|
+
record = getPlugin(identity);
|
|
275
177
|
if (!record)
|
|
276
178
|
return false;
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
throw new Error(`plugin "${name}" has active runtime lease(s): ${leases
|
|
280
|
-
.map((lease) => lease.leaseId)
|
|
281
|
-
.join(", ")}; stop its managed services or CLI commands before uninstalling`);
|
|
179
|
+
if (isBuiltInPluginMarketplace(record.marketplaceId)) {
|
|
180
|
+
throw new Error(`bundled plugin "${record.canonicalId}" cannot be uninstalled; disable it instead`);
|
|
282
181
|
}
|
|
283
|
-
|
|
182
|
+
assertNoActiveLeases(record);
|
|
183
|
+
return removePlugin(record.canonicalId);
|
|
284
184
|
}).immediate();
|
|
285
|
-
if (!removed
|
|
185
|
+
if (!removed)
|
|
286
186
|
return false;
|
|
287
|
-
if (record
|
|
288
|
-
|
|
289
|
-
|
|
187
|
+
if (!record)
|
|
188
|
+
return false;
|
|
189
|
+
if (options.purgeData) {
|
|
190
|
+
rmSync(pluginDataDir(record.marketplaceId, record.pluginId), { recursive: true, force: true });
|
|
191
|
+
onProgress(`removed plugin data for "${record.canonicalId}"`);
|
|
192
|
+
}
|
|
193
|
+
onProgress(`uninstalled plugin "${record.canonicalId}"`);
|
|
290
194
|
return true;
|
|
291
195
|
}
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
* A short grace window prevents a concurrently materializing install (renamed
|
|
296
|
-
* just before its registry transaction) from being mistaken for an orphan.
|
|
297
|
-
*/
|
|
298
|
-
export function pruneInactivePluginInstallations({ minAgeMs = 60_000, stagingMinAgeMs = DEFAULT_STAGING_GC_AGE_MS, now = Date.now(), } = {}) {
|
|
299
|
-
const { stagingRoot, installationsRoot } = ensureStorageRoots();
|
|
300
|
-
const minimumAge = Math.max(0, minAgeMs);
|
|
196
|
+
export function pruneInactivePluginInstallations() {
|
|
197
|
+
const { cacheRoot } = ensureStorageRoots();
|
|
198
|
+
const active = new Set(listActivePaths().map(canonicalFilesystemPath));
|
|
301
199
|
const removed = [];
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const active = new Set();
|
|
308
|
-
const current = getPlugin(pluginId);
|
|
309
|
-
if (current && isRunnablePluginRecord(current))
|
|
310
|
-
active.add(resolve(current.installPath));
|
|
311
|
-
for (const lease of activeRuntimeLeases(pluginId, installationsRoot)) {
|
|
312
|
-
active.add(resolve(lease.installPath));
|
|
313
|
-
}
|
|
314
|
-
const idRoot = join(installationsRoot, pluginId);
|
|
315
|
-
let idStat;
|
|
316
|
-
try {
|
|
317
|
-
idStat = lstatSync(idRoot);
|
|
318
|
-
}
|
|
319
|
-
catch {
|
|
320
|
-
return;
|
|
321
|
-
}
|
|
322
|
-
if (!idStat.isDirectory() || idStat.isSymbolicLink())
|
|
323
|
-
return;
|
|
324
|
-
const entries = readdirSync(idRoot);
|
|
325
|
-
for (const installationId of entries) {
|
|
326
|
-
if (!INSTALLATION_ID_PATTERN.test(installationId))
|
|
327
|
-
continue;
|
|
328
|
-
const candidate = join(idRoot, installationId);
|
|
329
|
-
if (active.has(resolve(candidate)))
|
|
330
|
-
continue;
|
|
331
|
-
let candidateStat;
|
|
332
|
-
try {
|
|
333
|
-
candidateStat = lstatSync(candidate);
|
|
334
|
-
}
|
|
335
|
-
catch {
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
if (!candidateStat.isDirectory() ||
|
|
339
|
-
candidateStat.isSymbolicLink() ||
|
|
340
|
-
now - promotionTimestampMs(idRoot, installationId, candidateStat.ctimeMs) < minimumAge) {
|
|
200
|
+
for (const marketplace of safeDirectories(cacheRoot)) {
|
|
201
|
+
for (const plugin of safeDirectories(join(cacheRoot, marketplace))) {
|
|
202
|
+
for (const integrity of safeDirectories(join(cacheRoot, marketplace, plugin))) {
|
|
203
|
+
const candidate = resolve(join(cacheRoot, marketplace, plugin, integrity));
|
|
204
|
+
if (active.has(canonicalFilesystemPath(candidate)))
|
|
341
205
|
continue;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
removed.push(candidate);
|
|
345
|
-
}
|
|
206
|
+
rmSync(candidate, { recursive: true, force: true });
|
|
207
|
+
removed.push(candidate);
|
|
346
208
|
}
|
|
347
|
-
|
|
348
|
-
const installationId = promotionMarkerInstallationId(entry);
|
|
349
|
-
if (!installationId || existsSync(join(idRoot, installationId)))
|
|
350
|
-
continue;
|
|
351
|
-
const marker = join(idRoot, entry);
|
|
352
|
-
try {
|
|
353
|
-
const stat = lstatSync(marker);
|
|
354
|
-
if (stat.isFile() &&
|
|
355
|
-
!stat.isSymbolicLink() &&
|
|
356
|
-
now - stat.mtimeMs >= minimumAge) {
|
|
357
|
-
rmSync(marker, { force: true });
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
catch {
|
|
361
|
-
// A concurrently removed orphan marker needs no further work.
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
}).immediate();
|
|
209
|
+
}
|
|
365
210
|
}
|
|
366
211
|
return removed;
|
|
367
212
|
}
|
|
368
|
-
function
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
213
|
+
export function pluginDataDir(marketplaceId, pluginId) {
|
|
214
|
+
return join(rynxHome(), "plugins", "data", marketplaceStorageSegment(marketplaceId), pluginId);
|
|
215
|
+
}
|
|
216
|
+
export function pluginRuntimeCacheDir(marketplaceId, pluginId, integrity) {
|
|
217
|
+
return join(rynxHome(), "plugins", "runtime-cache", marketplaceStorageSegment(marketplaceId), pluginId, integrityPath(integrity));
|
|
218
|
+
}
|
|
219
|
+
function pluginCachePath(marketplaceId, pluginId, integrity, root = join(rynxHome(), "plugins", "cache")) {
|
|
220
|
+
return join(root, marketplaceStorageSegment(marketplaceId), pluginId, integrityPath(integrity));
|
|
221
|
+
}
|
|
222
|
+
export function marketplaceStorageSegment(marketplaceId) {
|
|
223
|
+
if (!MARKETPLACE_ID_PATTERN.test(marketplaceId)) {
|
|
224
|
+
throw new Error(`invalid marketplace id "${marketplaceId}"`);
|
|
225
|
+
}
|
|
226
|
+
return encodeURIComponent(marketplaceId);
|
|
227
|
+
}
|
|
228
|
+
function integrityPath(integrity) {
|
|
229
|
+
const match = /^sha256-([A-Za-z0-9+/]{43}=?)$/.exec(integrity);
|
|
230
|
+
if (!match)
|
|
231
|
+
throw new Error("plugin integrity is invalid");
|
|
232
|
+
return `sha256-${match[1].replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "")}`;
|
|
233
|
+
}
|
|
234
|
+
function ensureStorageRoots() {
|
|
235
|
+
const root = join(rynxHome(), "plugins");
|
|
236
|
+
const cacheRoot = join(root, "cache");
|
|
237
|
+
const stagingRoot = join(root, "staging");
|
|
238
|
+
mkdirSync(cacheRoot, { recursive: true, mode: 0o700 });
|
|
239
|
+
mkdirSync(stagingRoot, { recursive: true, mode: 0o700 });
|
|
240
|
+
return { cacheRoot, stagingRoot };
|
|
241
|
+
}
|
|
242
|
+
function assertNoActiveLeases(record) {
|
|
243
|
+
if (!record)
|
|
244
|
+
return;
|
|
245
|
+
const dataDir = pluginDataDir(record.marketplaceId, record.pluginId);
|
|
246
|
+
const active = readPluginRuntimeLeases(dataDir).filter((lease) => {
|
|
247
|
+
const pid = pluginCliInvocationLeasePid(lease.leaseId);
|
|
248
|
+
if (pid !== undefined && !isProcessAlive(pid)) {
|
|
375
249
|
removePluginRuntimeLease(dataDir, lease.leaseId);
|
|
376
250
|
return false;
|
|
377
251
|
}
|
|
378
|
-
|
|
379
|
-
if (dirname(candidate) !== join(installationsRoot, pluginId) ||
|
|
380
|
-
!INSTALLATION_ID_PATTERN.test(basename(candidate))) {
|
|
381
|
-
return false;
|
|
382
|
-
}
|
|
383
|
-
try {
|
|
384
|
-
const stat = lstatSync(candidate);
|
|
385
|
-
return stat.isDirectory() && !stat.isSymbolicLink() && realpathSync(candidate) === candidate;
|
|
386
|
-
}
|
|
387
|
-
catch {
|
|
388
|
-
return false;
|
|
389
|
-
}
|
|
252
|
+
return sameFilesystemPath(lease.installPath, record.installPath);
|
|
390
253
|
});
|
|
254
|
+
if (active.length > 0) {
|
|
255
|
+
throw new Error(`plugin "${record.canonicalId}" has active generation lease(s): ${active.map((lease) => lease.leaseId).join(", ")}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
function sameFilesystemPath(left, right) {
|
|
259
|
+
return canonicalFilesystemPath(left) === canonicalFilesystemPath(right);
|
|
260
|
+
}
|
|
261
|
+
function canonicalFilesystemPath(value) {
|
|
262
|
+
try {
|
|
263
|
+
return realpathSync(value);
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return resolve(value);
|
|
267
|
+
}
|
|
391
268
|
}
|
|
392
269
|
function isProcessAlive(pid) {
|
|
393
270
|
try {
|
|
@@ -401,144 +278,54 @@ function isProcessAlive(pid) {
|
|
|
401
278
|
function samePluginGeneration(before, current) {
|
|
402
279
|
if (!before || !current)
|
|
403
280
|
return before === current;
|
|
404
|
-
return
|
|
281
|
+
return before.canonicalId === current.canonicalId &&
|
|
282
|
+
before.integrity === current.integrity &&
|
|
283
|
+
before.updatedAt === current.updatedAt;
|
|
284
|
+
}
|
|
285
|
+
function validateMaterialized(value) {
|
|
286
|
+
if (!value ||
|
|
287
|
+
typeof value.resolvedRevision !== "string" ||
|
|
288
|
+
!value.resolvedRevision ||
|
|
289
|
+
!/^[0-9a-f]{32}$/.test(value.sourceFingerprint) ||
|
|
290
|
+
!value.requestedSource) {
|
|
291
|
+
throw new Error("plugin source materializer returned invalid provenance");
|
|
292
|
+
}
|
|
405
293
|
}
|
|
406
294
|
function cloneManifest(manifest) {
|
|
407
|
-
return
|
|
295
|
+
return structuredClone(manifest);
|
|
408
296
|
}
|
|
409
297
|
function clonePluginRecord(record) {
|
|
410
|
-
return
|
|
411
|
-
...record,
|
|
412
|
-
...(record.manifest ? { manifest: cloneManifest(record.manifest) } : {}),
|
|
413
|
-
grants: [...record.grants],
|
|
414
|
-
};
|
|
298
|
+
return structuredClone(record);
|
|
415
299
|
}
|
|
416
|
-
function
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
for (const entry of readdirSync(stagingRoot)) {
|
|
421
|
-
if (!STAGING_TRANSACTION_PATTERN.test(entry))
|
|
422
|
-
continue;
|
|
423
|
-
const candidate = join(stagingRoot, entry);
|
|
300
|
+
function safeDirectories(root) {
|
|
301
|
+
if (!existsSync(root))
|
|
302
|
+
return [];
|
|
303
|
+
return readdirSync(root).filter((entry) => {
|
|
424
304
|
try {
|
|
425
|
-
const
|
|
426
|
-
|
|
427
|
-
stat.isSymbolicLink() ||
|
|
428
|
-
dirname(realpathSync(candidate)) !== realpathSync(stagingRoot)) {
|
|
429
|
-
continue;
|
|
430
|
-
}
|
|
431
|
-
const owner = readStagingOwner(candidate);
|
|
432
|
-
if (owner?.pid && isProcessAlive(owner.pid))
|
|
433
|
-
continue;
|
|
434
|
-
const createdAt = owner?.createdAtMs ?? Math.max(stat.ctimeMs, stat.mtimeMs);
|
|
435
|
-
if (now - createdAt < minAgeMs)
|
|
436
|
-
continue;
|
|
437
|
-
rmSync(candidate, { recursive: true, force: true });
|
|
438
|
-
removed.push(candidate);
|
|
305
|
+
const info = lstatSync(join(root, entry));
|
|
306
|
+
return info.isDirectory() && !info.isSymbolicLink();
|
|
439
307
|
}
|
|
440
308
|
catch {
|
|
441
|
-
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
function readStagingOwner(transactionDir) {
|
|
446
|
-
const file = join(transactionDir, STAGING_OWNER_FILE);
|
|
447
|
-
try {
|
|
448
|
-
const stat = lstatSync(file);
|
|
449
|
-
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > MAX_HOST_MARKER_BYTES)
|
|
450
|
-
return undefined;
|
|
451
|
-
const parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
452
|
-
if (parsed.schemaVersion !== 1 ||
|
|
453
|
-
!Number.isSafeInteger(parsed.pid) ||
|
|
454
|
-
Number(parsed.pid) <= 0 ||
|
|
455
|
-
typeof parsed.createdAt !== "string" ||
|
|
456
|
-
!Number.isFinite(Date.parse(parsed.createdAt))) {
|
|
457
|
-
return undefined;
|
|
458
|
-
}
|
|
459
|
-
return { pid: Number(parsed.pid), createdAtMs: stat.mtimeMs };
|
|
460
|
-
}
|
|
461
|
-
catch {
|
|
462
|
-
return undefined;
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
function promotionMarkerPath(idRoot, installationId) {
|
|
466
|
-
return join(idRoot, `${installationId}${PROMOTION_MARKER_SUFFIX}`);
|
|
467
|
-
}
|
|
468
|
-
function writePromotionMarker(marker) {
|
|
469
|
-
writeFileSync(marker, `${JSON.stringify({ schemaVersion: 1, promotedAt: new Date().toISOString() })}\n`, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
470
|
-
}
|
|
471
|
-
function promotionTimestampMs(idRoot, installationId, conservativeFallback) {
|
|
472
|
-
try {
|
|
473
|
-
const stat = lstatSync(promotionMarkerPath(idRoot, installationId));
|
|
474
|
-
if (stat.isFile() && !stat.isSymbolicLink() && stat.size <= MAX_HOST_MARKER_BYTES) {
|
|
475
|
-
return stat.mtimeMs;
|
|
309
|
+
return false;
|
|
476
310
|
}
|
|
477
|
-
}
|
|
478
|
-
catch {
|
|
479
|
-
// Installations predating promotion markers fall back to rename-sensitive ctime.
|
|
480
|
-
}
|
|
481
|
-
return conservativeFallback;
|
|
482
|
-
}
|
|
483
|
-
function promotionMarkerInstallationId(entry) {
|
|
484
|
-
const match = /^([0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12})\.rynx-promoted$/i.exec(entry);
|
|
485
|
-
return match?.[1];
|
|
486
|
-
}
|
|
487
|
-
function ensureStorageRoots() {
|
|
488
|
-
const configuredRoot = resolve(pluginsDir());
|
|
489
|
-
mkdirSync(configuredRoot, { recursive: true, mode: 0o700 });
|
|
490
|
-
const root = realpathSync(configuredRoot);
|
|
491
|
-
return {
|
|
492
|
-
stagingRoot: ensureDirectDirectory(root, "staging", "plugin staging directory"),
|
|
493
|
-
installationsRoot: ensureDirectDirectory(root, "installed", "plugin installations directory"),
|
|
494
|
-
};
|
|
495
|
-
}
|
|
496
|
-
/** Create/validate one non-symlink direct child of a canonical parent. */
|
|
497
|
-
function ensureDirectDirectory(parent, child, label) {
|
|
498
|
-
const path = join(parent, child);
|
|
499
|
-
if (!existsSync(path))
|
|
500
|
-
mkdirSync(path, { mode: 0o700 });
|
|
501
|
-
const stat = lstatSync(path);
|
|
502
|
-
if (!stat.isDirectory() || stat.isSymbolicLink()) {
|
|
503
|
-
throw new Error(`${label} must be a host-owned directory, not a symlink`);
|
|
504
|
-
}
|
|
505
|
-
const real = realpathSync(path);
|
|
506
|
-
if (dirname(real) !== realpathSync(parent)) {
|
|
507
|
-
throw new Error(`${label} resolves outside its host-owned parent`);
|
|
508
|
-
}
|
|
509
|
-
return real;
|
|
311
|
+
});
|
|
510
312
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
const candidate = resolve(installPath);
|
|
522
|
-
if (dirname(candidate) !== idRoot || !INSTALLATION_ID_PATTERN.test(basename(candidate))) {
|
|
523
|
-
return false;
|
|
313
|
+
function listActivePaths() {
|
|
314
|
+
const connection = db();
|
|
315
|
+
const paths = connection.prepare("SELECT install_path FROM plugin_installations").all().map((row) => resolve(row.install_path));
|
|
316
|
+
for (const row of connection.prepare("SELECT execution FROM sessions").all()) {
|
|
317
|
+
try {
|
|
318
|
+
const execution = JSON.parse(row.execution);
|
|
319
|
+
for (const skill of execution.pluginSkills ?? []) {
|
|
320
|
+
if (typeof skill.packageRoot === "string")
|
|
321
|
+
paths.push(resolve(skill.packageRoot));
|
|
322
|
+
}
|
|
524
323
|
}
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
if (!idStat.isDirectory() ||
|
|
528
|
-
idStat.isSymbolicLink() ||
|
|
529
|
-
!candidateStat.isDirectory() ||
|
|
530
|
-
candidateStat.isSymbolicLink()) {
|
|
531
|
-
return false;
|
|
324
|
+
catch {
|
|
325
|
+
// Invalid Session snapshots pin nothing; the Session store rejects them.
|
|
532
326
|
}
|
|
533
|
-
if (realpathSync(idRoot) !== idRoot || realpathSync(candidate) !== candidate)
|
|
534
|
-
return false;
|
|
535
|
-
rmSync(candidate, { recursive: true, force: true });
|
|
536
|
-
rmSync(promotionMarkerPath(idRoot, basename(candidate)), { force: true });
|
|
537
|
-
return true;
|
|
538
|
-
}
|
|
539
|
-
catch {
|
|
540
|
-
return false;
|
|
541
327
|
}
|
|
328
|
+
return paths;
|
|
542
329
|
}
|
|
543
330
|
function safeProgress(callback) {
|
|
544
331
|
return (line) => {
|
|
@@ -546,7 +333,7 @@ function safeProgress(callback) {
|
|
|
546
333
|
callback(line);
|
|
547
334
|
}
|
|
548
335
|
catch {
|
|
549
|
-
|
|
336
|
+
// Progress reporting never changes installation correctness.
|
|
550
337
|
}
|
|
551
338
|
};
|
|
552
339
|
}
|