@pi-harness/pi-harness 0.1.30 → 0.1.31
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/README.md +1 -1
- package/apps/web/dist/assets/index-D9ZcvGXi.js +25 -0
- package/apps/web/dist/assets/{index-xF647pQJ.css → index-DJsNIiIo.css} +1 -1
- package/apps/web/dist/index.html +2 -2
- package/apps/web/package.json +1 -1
- package/apps/web/profile/cordis.yml +0 -221
- package/apps/web/src/style.css +3 -0
- package/node_modules/@pi-harness/api-gateway/dist/index.d.ts.map +1 -1
- package/node_modules/@pi-harness/api-gateway/dist/index.js +42 -11
- package/node_modules/@pi-harness/api-gateway/dist/index.js.map +1 -1
- package/node_modules/@pi-harness/api-gateway/package.json +1 -27
- package/node_modules/@pi-harness/cli/package.json +1 -1
- package/node_modules/@pi-harness/cli/profiles/default/cordis.yml +0 -87
- package/node_modules/@pi-harness/host-webserver/package.json +1 -1
- package/node_modules/@pi-harness/web-app/package.json +1 -1
- package/package.json +6 -79
- package/packages/api-gateway/dist/index.d.ts.map +1 -1
- package/packages/api-gateway/dist/index.js +42 -11
- package/packages/api-gateway/dist/index.js.map +1 -1
- package/packages/api-gateway/package.json +1 -27
- package/packages/api-gateway/src/index.ts +54 -16
- package/packages/api-gateway/test/index.test.ts +157 -2
- package/packages/api-gateway/test/packed-package.test.ts +18 -6
- package/packages/api-gateway/test/release-contract.test.ts +8 -4
- package/packages/bundle-web-app/package.json +1 -1
- package/packages/cli/package.json +1 -1
- package/packages/cli/profiles/default/cordis.yml +0 -87
- package/packages/cli/test/profiles/plugins/cordis.yml +122 -0
- package/packages/cli/test/profiles.integration.test.ts +40 -23
- package/packages/host-webserver/package.json +1 -1
- package/apps/web/dist/assets/index-B7pwZ76Z.js +0 -25
|
@@ -26,231 +26,10 @@
|
|
|
26
26
|
- bash
|
|
27
27
|
- edit
|
|
28
28
|
- write
|
|
29
|
-
- id: agent-teams
|
|
30
|
-
name: "@pi-harness/plugin-agent-teams"
|
|
31
|
-
config: {}
|
|
32
|
-
- id: modlens
|
|
33
|
-
name: "@pi-harness/plugin-modlens"
|
|
34
|
-
config: {}
|
|
35
|
-
- id: git-time-capsule
|
|
36
|
-
name: "@pi-harness/plugin-git-time-capsule"
|
|
37
|
-
config: {}
|
|
38
|
-
- id: dependency-checker
|
|
39
|
-
name: "@pi-harness/plugin-dependency-checker"
|
|
40
|
-
config: {}
|
|
41
|
-
- id: at-file
|
|
42
|
-
name: "@pi-harness/plugin-at-file"
|
|
43
|
-
config: {}
|
|
44
|
-
- id: test-harness
|
|
45
|
-
name: "@pi-harness/plugin-test-harness"
|
|
46
|
-
config: {}
|
|
47
|
-
- id: session-insights
|
|
48
|
-
name: "@pi-harness/plugin-session-insights"
|
|
49
|
-
config: {}
|
|
50
|
-
- id: readme-gen
|
|
51
|
-
name: "@pi-harness/plugin-readme-gen"
|
|
52
|
-
config: {}
|
|
53
|
-
- id: i18n-pair
|
|
54
|
-
name: "@pi-harness/plugin-i18n-pair"
|
|
55
|
-
config: {}
|
|
56
|
-
- id: cleaner
|
|
57
|
-
name: "@pi-harness/plugin-cleaner"
|
|
58
|
-
config: {}
|
|
59
|
-
- id: sql-lens
|
|
60
|
-
name: "@pi-harness/plugin-sql-lens"
|
|
61
|
-
config: {}
|
|
62
|
-
- id: docker-sandbox
|
|
63
|
-
name: "@pi-harness/plugin-docker-sandbox"
|
|
64
|
-
config: {}
|
|
65
|
-
- id: mcp-client
|
|
66
|
-
name: "@pi-harness/plugin-mcp-client"
|
|
67
|
-
config: {}
|
|
68
|
-
- id: mcp-panel
|
|
69
|
-
name: "@pi-harness/plugin-mcp-panel"
|
|
70
|
-
config: {}
|
|
71
|
-
- id: browser-fetch
|
|
72
|
-
name: "@pi-harness/plugin-browser-fetch"
|
|
73
|
-
config: {}
|
|
74
|
-
- id: web-research
|
|
75
|
-
name: "@pi-harness/plugin-web-research"
|
|
76
|
-
config: {}
|
|
77
|
-
- id: browser-session
|
|
78
|
-
name: "@pi-harness/plugin-browser-session"
|
|
79
|
-
config: {}
|
|
80
|
-
- id: yaml-validator
|
|
81
|
-
name: "@pi-harness/plugin-yaml-validator"
|
|
82
|
-
config: {}
|
|
83
|
-
- id: mock-server
|
|
84
|
-
name: "@pi-harness/plugin-mock-server"
|
|
85
|
-
config: {}
|
|
86
|
-
- id: cli-notifier
|
|
87
|
-
name: "@pi-harness/plugin-cli-notifier"
|
|
88
|
-
config: {}
|
|
89
|
-
- id: obsidian-sync
|
|
90
|
-
name: "@pi-harness/plugin-obsidian-sync"
|
|
91
|
-
config: {}
|
|
92
|
-
- id: context-doctor
|
|
93
|
-
name: "@pi-harness/plugin-context-doctor"
|
|
94
|
-
config: {}
|
|
95
|
-
- id: history-compressor
|
|
96
|
-
name: "@pi-harness/plugin-history-compressor"
|
|
97
|
-
config: {}
|
|
98
|
-
- id: reviewer-bot
|
|
99
|
-
name: "@pi-harness/plugin-reviewer-bot"
|
|
100
|
-
config: {}
|
|
101
|
-
- id: auto-mode
|
|
102
|
-
name: "@pi-harness/plugin-auto-mode"
|
|
103
|
-
config: {}
|
|
104
|
-
- id: plan-execute
|
|
105
|
-
name: "@pi-harness/plugin-plan-execute"
|
|
106
|
-
config: {}
|
|
107
|
-
- id: plugin-finder
|
|
108
|
-
name: "@pi-harness/plugin-plugin-finder"
|
|
109
|
-
config: {}
|
|
110
|
-
- id: memory
|
|
111
|
-
name: "@pi-harness/plugin-memory"
|
|
112
|
-
config: {}
|
|
113
|
-
- id: graph-memory
|
|
114
|
-
name: "@pi-harness/plugin-graph-memory"
|
|
115
|
-
config: {}
|
|
116
|
-
- id: taskboard
|
|
117
|
-
name: "@pi-harness/plugin-taskboard"
|
|
118
|
-
config: {}
|
|
119
|
-
- id: synapse
|
|
120
|
-
name: "@pi-harness/plugin-synapse"
|
|
121
|
-
config: {}
|
|
122
|
-
- id: hol-guard
|
|
123
|
-
name: "@pi-harness/plugin-hol-guard"
|
|
124
|
-
config: {}
|
|
125
|
-
- id: plugin-radar
|
|
126
|
-
name: "@pi-harness/plugin-plugin-radar"
|
|
127
|
-
config: {}
|
|
128
|
-
- id: plugin-stars
|
|
129
|
-
name: "@pi-harness/plugin-plugin-stars"
|
|
130
|
-
config: {}
|
|
131
|
-
- id: plugin-check
|
|
132
|
-
name: "@pi-harness/plugin-plugin-check"
|
|
133
|
-
config: {}
|
|
134
|
-
- id: annotation
|
|
135
|
-
name: "@pi-harness/plugin-annotation"
|
|
136
|
-
config: {}
|
|
137
|
-
- id: cost-meter
|
|
138
|
-
name: "@pi-harness/plugin-cost-meter"
|
|
139
|
-
config:
|
|
140
|
-
dailyBudget: 0
|
|
141
|
-
- id: undo-savepoint
|
|
142
|
-
name: "@pi-harness/plugin-undo-savepoint"
|
|
143
|
-
config: {}
|
|
144
|
-
- id: skill-catalog
|
|
145
|
-
name: "@pi-harness/plugin-skill-catalog"
|
|
146
|
-
config: {}
|
|
147
|
-
- id: canvas-draw
|
|
148
|
-
name: "@pi-harness/plugin-canvas-draw"
|
|
149
|
-
config: {}
|
|
150
|
-
- id: image-compressor
|
|
151
|
-
name: "@pi-harness/plugin-image-compressor"
|
|
152
|
-
config: {}
|
|
153
|
-
- id: workspace-search
|
|
154
|
-
name: "@pi-harness/plugin-workspace-search"
|
|
155
|
-
config: {}
|
|
156
|
-
- id: prompt-guard
|
|
157
|
-
name: "@pi-harness/plugin-prompt-guard"
|
|
158
|
-
config: {}
|
|
159
|
-
- id: code2skill
|
|
160
|
-
name: "@pi-harness/plugin-code2skill"
|
|
161
|
-
config: {}
|
|
162
|
-
- id: tab-manager
|
|
163
|
-
name: "@pi-harness/plugin-tab-manager"
|
|
164
|
-
config: {}
|
|
165
|
-
- id: genui
|
|
166
|
-
name: "@pi-harness/plugin-genui"
|
|
167
|
-
config: {}
|
|
168
|
-
- id: anchored-standard
|
|
169
|
-
name: "@pi-harness/plugin-anchored-standard"
|
|
170
|
-
config: {}
|
|
171
|
-
- id: telemetry-blocker
|
|
172
|
-
name: "@pi-harness/plugin-telemetry-blocker"
|
|
173
|
-
config: {}
|
|
174
|
-
- id: change-verifier
|
|
175
|
-
name: "@pi-harness/plugin-change-verifier"
|
|
176
|
-
config: {}
|
|
177
|
-
- id: context
|
|
178
|
-
name: "@pi-harness/plugin-context"
|
|
179
|
-
config: {}
|
|
180
29
|
- id: runtime
|
|
181
30
|
name: "@pi-harness/core/plugins/runtime"
|
|
182
31
|
config:
|
|
183
32
|
thinkingLevel: medium
|
|
184
|
-
- id: plugin-dev
|
|
185
|
-
name: "@pi-harness/plugin-plugin-dev"
|
|
186
|
-
config: {}
|
|
187
|
-
- id: openpets
|
|
188
|
-
name: "@pi-harness/plugin-openpets"
|
|
189
|
-
config: {}
|
|
190
|
-
- id: session-bridge
|
|
191
|
-
name: "@pi-harness/plugin-session-bridge"
|
|
192
|
-
config: {}
|
|
193
|
-
- id: skill-guard
|
|
194
|
-
name: "@pi-harness/plugin-skill-guard"
|
|
195
|
-
config: {}
|
|
196
|
-
- id: recall-unread
|
|
197
|
-
name: "@pi-harness/plugin-recall-unread"
|
|
198
|
-
config: {}
|
|
199
|
-
- id: turn-rewind
|
|
200
|
-
name: "@pi-harness/plugin-turn-rewind"
|
|
201
|
-
config: {}
|
|
202
|
-
- id: session-export
|
|
203
|
-
name: "@pi-harness/plugin-session-export"
|
|
204
|
-
config: {}
|
|
205
|
-
- id: session-search
|
|
206
|
-
name: "@pi-harness/plugin-session-search"
|
|
207
|
-
config: {}
|
|
208
|
-
- id: session-compare
|
|
209
|
-
name: "@pi-harness/plugin-session-compare"
|
|
210
|
-
config: {}
|
|
211
|
-
- id: secure-audit
|
|
212
|
-
name: "@pi-harness/plugin-secure-audit"
|
|
213
|
-
config: {}
|
|
214
|
-
- id: session-bookmarks
|
|
215
|
-
name: "@pi-harness/plugin-session-bookmarks"
|
|
216
|
-
config: {}
|
|
217
|
-
- id: llm-verifier
|
|
218
|
-
name: "@pi-harness/plugin-llm-verifier"
|
|
219
|
-
config: {}
|
|
220
|
-
- id: module-search
|
|
221
|
-
name: "@pi-harness/plugin-module-search"
|
|
222
|
-
config: {}
|
|
223
|
-
- id: workspace-navigator
|
|
224
|
-
name: "@pi-harness/plugin-workspace-navigator"
|
|
225
|
-
config: {}
|
|
226
|
-
- id: better-sidebar
|
|
227
|
-
name: "@pi-harness/plugin-better-sidebar"
|
|
228
|
-
config: {}
|
|
229
|
-
- id: archify
|
|
230
|
-
name: "@pi-harness/plugin-archify"
|
|
231
|
-
config: {}
|
|
232
|
-
- id: reverse-skill
|
|
233
|
-
name: "@pi-harness/plugin-reverse-skill"
|
|
234
|
-
config: {}
|
|
235
|
-
- id: colleague-skill
|
|
236
|
-
name: "@pi-harness/plugin-colleague-skill"
|
|
237
|
-
config: {}
|
|
238
|
-
- id: prompt-library
|
|
239
|
-
name: "@pi-harness/plugin-prompt-library"
|
|
240
|
-
config: {}
|
|
241
|
-
- id: token-guard
|
|
242
|
-
name: "@pi-harness/plugin-token-guard"
|
|
243
|
-
config:
|
|
244
|
-
maxPercent: 90
|
|
245
|
-
- id: fail-logger
|
|
246
|
-
name: "@pi-harness/plugin-fail-logger"
|
|
247
|
-
config: {}
|
|
248
|
-
- id: vision-toolkit
|
|
249
|
-
name: "@pi-harness/plugin-vision-toolkit"
|
|
250
|
-
config: {}
|
|
251
|
-
- id: runtime-doctor
|
|
252
|
-
name: "@pi-harness/plugin-runtime-doctor"
|
|
253
|
-
config: {}
|
|
254
33
|
- id: webserver
|
|
255
34
|
name: "@pi-harness/host-webserver"
|
|
256
35
|
config:
|
package/apps/web/src/style.css
CHANGED
|
@@ -2041,6 +2041,9 @@ button:disabled {
|
|
|
2041
2041
|
.plugin-action-error {
|
|
2042
2042
|
@apply [margin:10px_0_0_37px] [color:var(--color-red)] [font-size:11px];
|
|
2043
2043
|
}
|
|
2044
|
+
.plugin-action-notice {
|
|
2045
|
+
@apply [margin:10px_0_0_37px] [color:#8a5a00] [font-size:11px];
|
|
2046
|
+
}
|
|
2044
2047
|
.plugin-confirm-backdrop {
|
|
2045
2048
|
@apply [position:fixed] [inset:0] [z-index:60] [display:grid] [place-items:center] [padding:24px] [background:rgba(15,_23,_42,_0.32)] [backdrop-filter:blur(6px)];
|
|
2046
2049
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;;;;mBAmuBlC,OAAO;;AAHxB,wBA+4CE"}
|
|
@@ -3,10 +3,11 @@ import { mkdir, mkdtemp, readFile, realpath, rename, rm, stat, unlink, writeFile
|
|
|
3
3
|
import { basename, dirname, isAbsolute, join, posix, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import { SessionManager } from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import { atomicWriteFile } from "@pi-harness/core";
|
|
6
|
+
import { atomicWriteFile, isPiToolRegistryLeasedError } from "@pi-harness/core";
|
|
7
7
|
import { MARKETPLACE_CAPABILITIES, MARKETPLACE_CATEGORIES, MARKETPLACE_PLUGINS, attachMarketplaceStatistics, createMarketplaceStatisticsLoader, paginateMarketplace, searchMarketplace, marketplaceNpmPackageName, sortMarketplaceByRecommendation, } from "./marketplace.js";
|
|
8
8
|
import { parseGitWorktrees } from "./workspaces.js";
|
|
9
9
|
const loadMarketplaceStatistics = createMarketplaceStatisticsLoader();
|
|
10
|
+
const RUNTIME_ENTRY_NAME = "@pi-harness/core/plugins/runtime";
|
|
10
11
|
const DEFAULT_BODY_LIMIT_BYTES = 64 * 1024;
|
|
11
12
|
const IMPORT_CONTENT_LIMIT_BYTES = 10 * 1024 * 1024;
|
|
12
13
|
// The import body carries the JSONL content as a JSON string, so quotes and newlines are escaped; leave headroom above the content limit for that overhead.
|
|
@@ -434,15 +435,23 @@ function runProcess(executable, args, cwd) {
|
|
|
434
435
|
});
|
|
435
436
|
});
|
|
436
437
|
}
|
|
437
|
-
|
|
438
|
+
// A plugin registers its tools before pi-runtime acquires the tool registry, so an installed entry has to sit in the runtime's own group ahead of the runtime. Appended at the end of the profile it would load after the runtime and fail on every subsequent start.
|
|
439
|
+
function marketplaceProfileEntry(plugin, indent) {
|
|
440
|
+
const child = `${indent} `;
|
|
441
|
+
const group = plugin.profile.group === true ? `\n${child}group: true` : "";
|
|
442
|
+
return `${indent}- id: marketplace-${plugin.id}\n${child}name: ${JSON.stringify(plugin.packageName)}${group}\n${child}config: ${JSON.stringify(plugin.profile.config)}\n`;
|
|
443
|
+
}
|
|
444
|
+
async function appendMarketplaceProfile(configPath, plugin, beforeEntryId) {
|
|
438
445
|
const source = await readFile(configPath, "utf8");
|
|
439
446
|
if (source.includes(`name: ${JSON.stringify(plugin.packageName)}`))
|
|
440
447
|
return source;
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
448
|
+
const anchor = beforeEntryId === undefined ? null : source.match(new RegExp(`^([ \\t]*)- id: ${beforeEntryId.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&")}[ \\t]*$`, "mu"));
|
|
449
|
+
if (anchor === null) {
|
|
450
|
+
await writeFile(configPath, `${source.replace(/\s*$/u, "")}\n${marketplaceProfileEntry(plugin, "")}`, "utf8");
|
|
451
|
+
return source;
|
|
452
|
+
}
|
|
453
|
+
const start = anchor.index ?? 0;
|
|
454
|
+
await writeFile(configPath, source.slice(0, start) + marketplaceProfileEntry(plugin, anchor[1] ?? "") + source.slice(start), "utf8");
|
|
446
455
|
return source;
|
|
447
456
|
}
|
|
448
457
|
async function updateMarketplaceProfile(configPath, entryId, update) {
|
|
@@ -602,6 +611,17 @@ async function marketplaceInstallDirectory(configPath, fallback) {
|
|
|
602
611
|
}
|
|
603
612
|
return fallback;
|
|
604
613
|
}
|
|
614
|
+
// Where an installed plugin belongs in the running loader: inside the group that owns pi-runtime, immediately ahead of it. A profile without a runtime entry (the stdio CLI profile does have one, a hand-written profile may not) places the entry at the end of the root instead.
|
|
615
|
+
function runtimePlacement(loader) {
|
|
616
|
+
const entries = [...loader.entries()];
|
|
617
|
+
const runtime = entries.find((entry) => entry.options.name === RUNTIME_ENTRY_NAME);
|
|
618
|
+
if (runtime === undefined)
|
|
619
|
+
return undefined;
|
|
620
|
+
const group = entries.find((entry) => entry.subgroup === runtime.parent);
|
|
621
|
+
const position = runtime.parent.data.indexOf(runtime.options);
|
|
622
|
+
// The loader addresses a nested group by its qualified id (`profile:agent`), while the profile file names the entry by its own id.
|
|
623
|
+
return { groupEntryId: group?.id ?? null, position: position < 0 ? Infinity : position, beforeEntryId: runtime.options.id };
|
|
624
|
+
}
|
|
605
625
|
function pickDirectory() {
|
|
606
626
|
return new Promise((resolvePath, reject) => {
|
|
607
627
|
execFile("osascript", ["-e", 'POSIX path of (choose folder with prompt "选择 Pi 工作区")'], { timeout: 120_000, maxBuffer: 64 * 1024 }, (error, stdout, stderr) => {
|
|
@@ -1057,27 +1077,33 @@ export default {
|
|
|
1057
1077
|
const packageLockPath = join(installDirectory, "package-lock.json");
|
|
1058
1078
|
const packageJsonBefore = await readFile(packageJsonPath, "utf8").catch(() => undefined);
|
|
1059
1079
|
const packageLockBefore = await readFile(packageLockPath, "utf8").catch(() => undefined);
|
|
1080
|
+
const placement = runtimePlacement(loader);
|
|
1060
1081
|
let profileBefore;
|
|
1061
1082
|
let entryId;
|
|
1062
1083
|
try {
|
|
1063
1084
|
const specifier = `${marketplaceNpmPackageName(plugin.packageName)}@${plugin.version}`;
|
|
1064
1085
|
await runProcess("npm", ["install", "--save-exact", "--package-lock=false", specifier], installDirectory);
|
|
1065
|
-
profileBefore = await appendMarketplaceProfile(configPath, plugin);
|
|
1086
|
+
profileBefore = await appendMarketplaceProfile(configPath, plugin, placement?.beforeEntryId);
|
|
1066
1087
|
entryId = await loader.create({
|
|
1067
1088
|
id: `marketplace-${plugin.id}`,
|
|
1068
1089
|
name: plugin.packageName,
|
|
1069
1090
|
...(plugin.profile.group === true ? { group: true } : {}),
|
|
1070
1091
|
config: plugin.profile.config,
|
|
1071
|
-
});
|
|
1092
|
+
}, placement?.groupEntryId ?? null, placement?.position ?? Infinity);
|
|
1072
1093
|
const entry = loader.resolve(entryId);
|
|
1073
1094
|
if (entry.fiber === undefined)
|
|
1074
1095
|
throw new Error(`Plugin ${plugin.packageName} did not create a runtime fiber`);
|
|
1075
1096
|
await entry.fiber.await();
|
|
1076
|
-
sendJson(response, 200, { plugin, installed: true });
|
|
1097
|
+
sendJson(response, 200, { plugin, installed: true, restartRequired: false });
|
|
1077
1098
|
}
|
|
1078
1099
|
catch (error) {
|
|
1079
1100
|
if (entryId !== undefined)
|
|
1080
1101
|
await loader.remove(entryId).catch(() => { });
|
|
1102
|
+
// The runtime holds the tool registry for its whole life and snapshots the tool set when it takes it, so a plugin that contributes tools cannot join a harness that is already running. The package and its profile entry stay in place and the plugin arrives on the next start; rolling the install back would leave the user unable to install it at all.
|
|
1103
|
+
if (isPiToolRegistryLeasedError(error)) {
|
|
1104
|
+
sendJson(response, 200, { plugin, installed: true, restartRequired: true });
|
|
1105
|
+
return;
|
|
1106
|
+
}
|
|
1081
1107
|
if (profileBefore !== undefined)
|
|
1082
1108
|
await writeFile(configPath, profileBefore, "utf8").catch(() => { });
|
|
1083
1109
|
// A manifest npm created for this install is removed rather than left behind; restoring is only possible when one existed before.
|
|
@@ -1138,9 +1164,14 @@ export default {
|
|
|
1138
1164
|
const before = await updateMarketplaceProfile(configPath, profileEntryId, { disabled: !payload.enabled });
|
|
1139
1165
|
try {
|
|
1140
1166
|
await entry.update({ disabled: !payload.enabled });
|
|
1141
|
-
sendJson(response, 200, { plugin: pluginSummary(entry) });
|
|
1167
|
+
sendJson(response, 200, { plugin: pluginSummary(entry), restartRequired: false });
|
|
1142
1168
|
}
|
|
1143
1169
|
catch (error) {
|
|
1170
|
+
// Re-enabling a plugin that contributes tools cannot take effect in a harness the runtime already leased, so the profile keeps the change and the plugin comes back on the next start. Reverting it would leave a plugin that can be switched off but never on again.
|
|
1171
|
+
if (isPiToolRegistryLeasedError(error)) {
|
|
1172
|
+
sendJson(response, 200, { plugin: pluginSummary(entry), restartRequired: true });
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1144
1175
|
await writeFile(configPath, before, "utf8").catch(() => { });
|
|
1145
1176
|
sendJson(response, 502, { error: errorText(error) });
|
|
1146
1177
|
}
|