@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.
Files changed (31) hide show
  1. package/README.md +1 -1
  2. package/apps/web/dist/assets/index-D9ZcvGXi.js +25 -0
  3. package/apps/web/dist/assets/{index-xF647pQJ.css → index-DJsNIiIo.css} +1 -1
  4. package/apps/web/dist/index.html +2 -2
  5. package/apps/web/package.json +1 -1
  6. package/apps/web/profile/cordis.yml +0 -221
  7. package/apps/web/src/style.css +3 -0
  8. package/node_modules/@pi-harness/api-gateway/dist/index.d.ts.map +1 -1
  9. package/node_modules/@pi-harness/api-gateway/dist/index.js +42 -11
  10. package/node_modules/@pi-harness/api-gateway/dist/index.js.map +1 -1
  11. package/node_modules/@pi-harness/api-gateway/package.json +1 -27
  12. package/node_modules/@pi-harness/cli/package.json +1 -1
  13. package/node_modules/@pi-harness/cli/profiles/default/cordis.yml +0 -87
  14. package/node_modules/@pi-harness/host-webserver/package.json +1 -1
  15. package/node_modules/@pi-harness/web-app/package.json +1 -1
  16. package/package.json +6 -79
  17. package/packages/api-gateway/dist/index.d.ts.map +1 -1
  18. package/packages/api-gateway/dist/index.js +42 -11
  19. package/packages/api-gateway/dist/index.js.map +1 -1
  20. package/packages/api-gateway/package.json +1 -27
  21. package/packages/api-gateway/src/index.ts +54 -16
  22. package/packages/api-gateway/test/index.test.ts +157 -2
  23. package/packages/api-gateway/test/packed-package.test.ts +18 -6
  24. package/packages/api-gateway/test/release-contract.test.ts +8 -4
  25. package/packages/bundle-web-app/package.json +1 -1
  26. package/packages/cli/package.json +1 -1
  27. package/packages/cli/profiles/default/cordis.yml +0 -87
  28. package/packages/cli/test/profiles/plugins/cordis.yml +122 -0
  29. package/packages/cli/test/profiles.integration.test.ts +40 -23
  30. package/packages/host-webserver/package.json +1 -1
  31. 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:
@@ -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;;;;mBA4sBlC,OAAO;;AAHxB,wBAg4CE"}
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
- async function appendMarketplaceProfile(configPath, plugin) {
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 entryId = `marketplace-${plugin.id}`;
442
- const config = JSON.stringify(plugin.profile.config);
443
- const group = plugin.profile.group === true ? "\n group: true" : "";
444
- const entry = `\n- id: ${entryId}\n name: ${JSON.stringify(plugin.packageName)}${group}\n config: ${config}\n`;
445
- await writeFile(configPath, source.replace(/\s*$/, "") + entry, "utf8");
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
  }