thoth-agents 0.5.0 → 0.6.0
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 +81 -10
- package/dist/{chunk-VUNYH263.js → chunk-7YCSKK2X.js} +203 -40
- package/dist/{chunk-V7HSGRMB.js → chunk-U7FNFTM5.js} +31 -136
- package/dist/cli/index.js +2 -2
- package/dist/cli/pi-install.d.ts +34 -67
- package/dist/cli/pi-tool-config.d.ts +19 -0
- package/dist/cli/tui/index.js +2 -2
- package/dist/pi.d.ts +6 -35
- package/dist/pi.js +36 -432
- package/package.json +6 -3
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/dist/pi/tools-panel.d.ts +0 -51
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ The commands below install at **global/user scope**.
|
|
|
65
65
|
| <a href="https://github.com/anomalyco/opencode"><picture><source media="(prefers-color-scheme: dark)" srcset="https://svgl.app/library/opencode-dark.svg"><img src="https://svgl.app/library/opencode.svg" alt="OpenCode logo" width="48" height="48"></picture></a><br>**OpenCode** | Native plugin, agent team, workflow skills, and memory setup. | `npx thoth-agents@latest install --agent=opencode` |
|
|
66
66
|
| <a href="https://github.com/openai/codex"><img src="https://github.com/openai.png?size=120" alt="OpenAI logo — Codex" width="48" height="48"></a><br>**Codex** | Native plugin plus the required global agent and instruction setup. **Close Codex first.** | `npx thoth-agents@latest install --agent=codex` |
|
|
67
67
|
| <a href="https://claude.com/product/claude-code"><img src="https://github.com/anthropics.png?size=120" alt="Anthropic logo — Claude Code" width="48" height="48"></a><br>**Claude Code** | Marketplace agents and skills, completed by the CLI's external skills and memory setup. **Run the prerequisites below first.** | `npx thoth-agents@latest install --agent=claude` |
|
|
68
|
-
| <a href="https://github.com/earendil-works/pi"><img src="https://raw.githubusercontent.com/EremesNG/thoth-mem/master/img/pi.svg" alt="Pi logo" width="48" height="48"></a><br>**Pi** | Native package, five specialists, delegation and
|
|
68
|
+
| <a href="https://github.com/earendil-works/pi"><img src="https://raw.githubusercontent.com/EremesNG/thoth-mem/master/img/pi.svg" alt="Pi logo" width="48" height="48"></a><br>**Pi** | Native package, five specialists, delegation, research, theme, sidebar and background-task extensions, workflow skills, and memory setup. **Recommended for the best experience.** | `npx thoth-agents@latest install --agent=pi` |
|
|
69
69
|
|
|
70
70
|
### Claude Code prerequisites
|
|
71
71
|
|
|
@@ -83,8 +83,11 @@ claude plugin install thoth-agents@thoth-plugins --scope user
|
|
|
83
83
|
|
|
84
84
|
Inside Pi, use `/subagents-model` to edit model and effort profiles for global
|
|
85
85
|
and project subagent definitions. See [subagent profile configuration](docs/installation.md#configure-subagent-model-profiles-inside-pi).
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
Both commands are registered by pi-subagents on pi-core's shared list-editor
|
|
87
|
+
shell, with native CSI-u/kitty and legacy navigation keys and wheel support in
|
|
88
|
+
regular and fullscreen TUI mode. Use `/subagents-tools` to edit resolved global/project definitions' tool
|
|
89
|
+
lists, including registered active and inactive extension and MCP tools. Thoth
|
|
90
|
+
supplies managed-specialist validation and reset defaults through an optional adapter. Globs (including `*`) are
|
|
88
91
|
manual advanced selections over all registered root tools; the panel preserves
|
|
89
92
|
them read-only. Edit `disallowed_tools` manually for injected tools and trimming
|
|
90
93
|
globs; only Oracle denies `ask_orchestrator` by default. See
|
|
@@ -93,14 +96,37 @@ globs; only Oracle denies `ask_orchestrator` by default. See
|
|
|
93
96
|
Pi setup currently supports the default `~/.pi/agent` root. See the
|
|
94
97
|
[Pi installation guide](docs/installation.md#pi) for runtime requirements,
|
|
95
98
|
existing-package conflicts, and recovery. Thoth manages its separate Pi
|
|
96
|
-
delegation runtime as package `@thoth-agents/pi-subagents`
|
|
97
|
-
`npm:@thoth-agents/pi-subagents@>=0.
|
|
99
|
+
delegation runtime as package `@thoth-agents/pi-subagents` through
|
|
100
|
+
`npm:@thoth-agents/pi-subagents@>=0.3.0`, the first release owning
|
|
101
|
+
`/subagents-tools`. Upgrade both packages together and `/reload` if Pi reports
|
|
102
|
+
missing or duplicate command ownership. Existing `pi-subagents` and
|
|
98
103
|
`pi-subagents-j0k3r` installs need manual recovery through Pi's package manager
|
|
99
104
|
before setup. Local checkout development uses `pnpm run setup:pi:local`, which
|
|
100
105
|
points Pi at the fork under `pi-packages/pi-subagents`; publishing the fork is
|
|
101
106
|
not required for that path. Pi extensions run with your user's system
|
|
102
107
|
permissions; agent tool allowlists are not an OS sandbox.
|
|
103
108
|
|
|
109
|
+
### Optional Pi provider extensions (manual install)
|
|
110
|
+
|
|
111
|
+
The installer does **not** install or manage the Claude/Antigravity bridges or
|
|
112
|
+
pi-openai-fast. Install only those you want, then restart Pi:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
pi install npm:@thoth-agents/pi-claude-bridge
|
|
116
|
+
pi install npm:@thoth-agents/pi-antigravity-bridge
|
|
117
|
+
pi install npm:@thoth-agents/pi-openai-fast
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Authenticate Claude Code or the `agy` CLI separately. Claude rate-limit events
|
|
121
|
+
from root and child sessions share pi-core's process-wide, account-wide registry
|
|
122
|
+
(not a bus channel); affected subagent rows/details show active warnings and reset
|
|
123
|
+
times without changing task status. Quota is on demand: `/claude quota` uses an
|
|
124
|
+
experimental SDK API without sending a prompt; `/agy quota` shows Antigravity
|
|
125
|
+
usage. Antigravity costs are API-equivalent estimates from an explicit catalog
|
|
126
|
+
mapping, not subscription charges. The theme marks the total `(sub)` by default
|
|
127
|
+
for both `claude-bridge` and `antigravity`. See
|
|
128
|
+
[provider commands and caveats](docs/installation.md#manual-pi-provider-extensions).
|
|
129
|
+
|
|
104
130
|
For scopes, troubleshooting, or local checkout installation, see the
|
|
105
131
|
[installation guide](docs/installation.md). Local Pi checkout installs keep
|
|
106
132
|
thoth-mem setup separate.
|
|
@@ -245,13 +271,58 @@ do not prove those other pieces are current. Use `status` to inspect the last
|
|
|
245
271
|
complete CLI-managed installation and follow any reported recovery actions.
|
|
246
272
|
|
|
247
273
|
For Pi, the first-party `thoth-agents` package remains exact and receipt-verified.
|
|
248
|
-
The
|
|
274
|
+
The nine selected extensions use stable minimum-only `>=` ranges, so
|
|
249
275
|
Pi's native package manager can update them independently without waiting for a
|
|
250
|
-
Thoth release.
|
|
251
|
-
|
|
276
|
+
Thoth release. Install and applied Update now include
|
|
277
|
+
`@thoth-agents/pi-thoth-theme`, `@thoth-agents/pi-background-tasks` and
|
|
278
|
+
`@thoth-agents/pi-sidebar` at `>=0.3.0`.
|
|
279
|
+
Existing copies of these three packages from any source are preserved and verified
|
|
280
|
+
without reinstalling when compatible; older copies are left untouched and block
|
|
281
|
+
completion with manual upgrade guidance. Ambiguous sources fail closed. Status
|
|
282
|
+
validates each installed manifest's package name and SemVer floor; newer stable
|
|
283
|
+
versions are healthy, while prerelease, malformed,
|
|
252
284
|
missing, or older versions are not. Re-running Install or applying Update
|
|
253
|
-
migrates legacy exact
|
|
254
|
-
resource filters and unrelated settings.
|
|
285
|
+
migrates legacy exact sources for the other selected extensions through Pi while
|
|
286
|
+
preserving package resource filters and unrelated settings.
|
|
287
|
+
|
|
288
|
+
Subagents (v2) and background tasks (v1) publish summary-only snapshots through
|
|
289
|
+
[pi-core's task channels](pi-packages/pi-core/README.md#task-summary-and-usage-channels).
|
|
290
|
+
The status line uses `thoth:subagents:usage`, not the removed raw
|
|
291
|
+
`thoth:subagent-usage` bus event. Upgrade pi-subagents and pi-thoth-theme to
|
|
292
|
+
`>=0.3.0` together and `/reload` to retain subagent cost display; the usage
|
|
293
|
+
checkpoint discriminator is unchanged.
|
|
294
|
+
|
|
295
|
+
The shared Pi Work panel uses pi-core's v2 data-only provider contract and
|
|
296
|
+
[discovery/action API](pi-packages/pi-core/README.md#discovery-and-actions).
|
|
297
|
+
Upgrade pi-subagents, pi-background-tasks and pi-todo together to `>=0.3.0`, then
|
|
298
|
+
`/reload`; first-owner arbitration hides incompatible sections in mixed v1/v2
|
|
299
|
+
installs. Subagent state is v2; background state, usage, requests and Render KIT remain v1.
|
|
300
|
+
|
|
301
|
+
The [Pi sidebar](pi-packages/pi-sidebar/README.md) adds read-only Session,
|
|
302
|
+
Workspace, Cost and discovered Todos/Subagents/Background panels in fullscreen and
|
|
303
|
+
regular mode. `/sidebar` toggles, `/sidebar panels` lists ids for show/hide/order
|
|
304
|
+
commands, and `/sidebar resize` resizes. `/sidebar settings` edits panel order,
|
|
305
|
+
visibility, startup and default width; `/sidebar cost` opens task-cost curves
|
|
306
|
+
against elapsed time per task, labeled by display name or a short task-summary name.
|
|
307
|
+
Themed Agents/Background headers show static status-icon counts, omitting zero
|
|
308
|
+
counts (Todos keeps completed/total), and detail commands; Workspace shows a
|
|
309
|
+
`~`-abbreviated path and tracked file/line changes versus HEAD. Agent metrics
|
|
310
|
+
include all available metrics with semantic icons (ASCII labels) in shared
|
|
311
|
+
aligned columns that wrap without dropping metrics, stably across digit changes. Only displayed source panels absorb their
|
|
312
|
+
Work sections; hiding the sidebar restores them. Panel order/visibility and
|
|
313
|
+
startup policy/default width live in `~/.pi/agent/thoth-sidebar.json`. Subagent
|
|
314
|
+
state v2 adds up to 100 cost-ranked persisted session summaries in `history`. Private Pi layout seams
|
|
315
|
+
have guarded fallback; regular mode covers the live viewport, not historical
|
|
316
|
+
scrollback. Update related extensions together to avoid mixed old UI owners.
|
|
317
|
+
|
|
318
|
+
Pi's question UI replaces the editor without covering chat. `Ctrl+]` collapses it
|
|
319
|
+
to a one-line dock and returns input to the editor; during an active run, `Enter`
|
|
320
|
+
queues a steering message without answering the question. `Ctrl+]` re-expands
|
|
321
|
+
with answers and drafts preserved, and the open tool-call card shows collapsed
|
|
322
|
+
state. While open, `Ctrl+]` shadows editor `jumpForward`; collapsed `Esc` belongs
|
|
323
|
+
to the editor or focused overlay. History/detail overlays close back to the
|
|
324
|
+
expanded question or otherwise the editor; collapse and teardown preserve
|
|
325
|
+
visible foreign-overlay focus. See [question controls](pi-packages/pi-questions-user/README.md#keyboard-controls).
|
|
255
326
|
|
|
256
327
|
## Documentation
|
|
257
328
|
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
savePiModelConfig,
|
|
17
17
|
syncPiSpecialists,
|
|
18
18
|
writePiManagedText
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-U7FNFTM5.js";
|
|
20
20
|
import {
|
|
21
21
|
ALL_AGENT_NAMES,
|
|
22
22
|
CLAUDE_CODE_PROMPT_DIALECT,
|
|
@@ -7174,6 +7174,21 @@ import { createHash as createHash6 } from "crypto";
|
|
|
7174
7174
|
import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
|
|
7175
7175
|
import { basename as basename4, dirname as dirname11, isAbsolute as isAbsolute5, join as join13 } from "path";
|
|
7176
7176
|
|
|
7177
|
+
// src/cli/pi-install.ts
|
|
7178
|
+
import { spawnSync as spawnSync6 } from "child_process";
|
|
7179
|
+
import { createHash as createHash5 } from "crypto";
|
|
7180
|
+
import {
|
|
7181
|
+
existsSync as existsSync14,
|
|
7182
|
+
lstatSync as lstatSync4,
|
|
7183
|
+
readdirSync as readdirSync3,
|
|
7184
|
+
readFileSync as readFileSync13,
|
|
7185
|
+
realpathSync as realpathSync3,
|
|
7186
|
+
statSync as statSync3
|
|
7187
|
+
} from "fs";
|
|
7188
|
+
import { basename as basename3, dirname as dirname10, isAbsolute as isAbsolute4, join as join12, resolve as resolve8, sep as sep3 } from "path";
|
|
7189
|
+
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7190
|
+
import { lt } from "semver";
|
|
7191
|
+
|
|
7177
7192
|
// src/cli/pi-external-package.ts
|
|
7178
7193
|
import { readFileSync as readFileSync9, realpathSync as realpathSync2 } from "fs";
|
|
7179
7194
|
import { isAbsolute as isAbsolute2, join as join7 } from "path";
|
|
@@ -7289,21 +7304,6 @@ function inspectPiExternalPackage(packages, spec, requireManagedSource = true) {
|
|
|
7289
7304
|
};
|
|
7290
7305
|
}
|
|
7291
7306
|
|
|
7292
|
-
// src/cli/pi-install.ts
|
|
7293
|
-
import { spawnSync as spawnSync6 } from "child_process";
|
|
7294
|
-
import { createHash as createHash5 } from "crypto";
|
|
7295
|
-
import {
|
|
7296
|
-
existsSync as existsSync14,
|
|
7297
|
-
lstatSync as lstatSync4,
|
|
7298
|
-
readdirSync as readdirSync3,
|
|
7299
|
-
readFileSync as readFileSync13,
|
|
7300
|
-
realpathSync as realpathSync3,
|
|
7301
|
-
statSync as statSync3
|
|
7302
|
-
} from "fs";
|
|
7303
|
-
import { basename as basename3, dirname as dirname10, isAbsolute as isAbsolute4, join as join12, resolve as resolve8, sep as sep3 } from "path";
|
|
7304
|
-
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
7305
|
-
import { lt } from "semver";
|
|
7306
|
-
|
|
7307
7307
|
// src/cli/pi-git-source.ts
|
|
7308
7308
|
import { resolve as resolve4, sep as sep2 } from "path";
|
|
7309
7309
|
import hostedGitInfo from "hosted-git-info";
|
|
@@ -7945,9 +7945,9 @@ var PI_COMMAND_TIMEOUT_MS = 12e4;
|
|
|
7945
7945
|
var PI_PACKAGE_SPECS = [
|
|
7946
7946
|
{
|
|
7947
7947
|
id: "delegation",
|
|
7948
|
-
source: "npm:@thoth-agents/pi-subagents@>=0.
|
|
7948
|
+
source: "npm:@thoth-agents/pi-subagents@>=0.3.0",
|
|
7949
7949
|
packageName: "@thoth-agents/pi-subagents",
|
|
7950
|
-
version: "0.
|
|
7950
|
+
version: "0.3.0"
|
|
7951
7951
|
},
|
|
7952
7952
|
{
|
|
7953
7953
|
id: "context7",
|
|
@@ -7969,15 +7969,36 @@ var PI_PACKAGE_SPECS = [
|
|
|
7969
7969
|
},
|
|
7970
7970
|
{
|
|
7971
7971
|
id: "ask-user-question",
|
|
7972
|
-
source: "npm:@thoth-agents/pi-questions-user@>=0.
|
|
7972
|
+
source: "npm:@thoth-agents/pi-questions-user@>=0.3.0",
|
|
7973
7973
|
packageName: "@thoth-agents/pi-questions-user",
|
|
7974
|
-
version: "0.
|
|
7974
|
+
version: "0.3.0"
|
|
7975
7975
|
},
|
|
7976
7976
|
{
|
|
7977
7977
|
id: "todo",
|
|
7978
|
-
source: "npm:@thoth-agents/pi-todo@>=0.
|
|
7978
|
+
source: "npm:@thoth-agents/pi-todo@>=0.3.0",
|
|
7979
7979
|
packageName: "@thoth-agents/pi-todo",
|
|
7980
|
-
version: "0.
|
|
7980
|
+
version: "0.3.0"
|
|
7981
|
+
},
|
|
7982
|
+
{
|
|
7983
|
+
id: "theme",
|
|
7984
|
+
source: "npm:@thoth-agents/pi-thoth-theme@>=0.3.0",
|
|
7985
|
+
packageName: "@thoth-agents/pi-thoth-theme",
|
|
7986
|
+
version: "0.3.0",
|
|
7987
|
+
preserveUserCopy: true
|
|
7988
|
+
},
|
|
7989
|
+
{
|
|
7990
|
+
id: "background-tasks",
|
|
7991
|
+
source: "npm:@thoth-agents/pi-background-tasks@>=0.3.0",
|
|
7992
|
+
packageName: "@thoth-agents/pi-background-tasks",
|
|
7993
|
+
version: "0.3.0",
|
|
7994
|
+
preserveUserCopy: true
|
|
7995
|
+
},
|
|
7996
|
+
{
|
|
7997
|
+
id: "sidebar",
|
|
7998
|
+
source: "npm:@thoth-agents/pi-sidebar@>=0.3.0",
|
|
7999
|
+
packageName: "@thoth-agents/pi-sidebar",
|
|
8000
|
+
version: "0.3.0",
|
|
8001
|
+
preserveUserCopy: true
|
|
7981
8002
|
}
|
|
7982
8003
|
];
|
|
7983
8004
|
var PI_GREP_MCP_ENTRY = {
|
|
@@ -8096,6 +8117,47 @@ function getPiExternalPackageSpecs(options = {}) {
|
|
|
8096
8117
|
(spec) => spec.id === "delegation" ? { ...spec, source: runtimePackageRoot } : spec
|
|
8097
8118
|
);
|
|
8098
8119
|
}
|
|
8120
|
+
function sourceHintsAtPackage(source, spec) {
|
|
8121
|
+
return source.includes(
|
|
8122
|
+
spec.packageName.split("/").at(-1) ?? spec.packageName
|
|
8123
|
+
);
|
|
8124
|
+
}
|
|
8125
|
+
function hasPiResourceSelection(value) {
|
|
8126
|
+
return ["prompts", "skills", "themes"].some(
|
|
8127
|
+
(key) => Array.isArray(value[key]) && value[key].length > 0
|
|
8128
|
+
);
|
|
8129
|
+
}
|
|
8130
|
+
function inspectPiSetupPackage(packages, spec, requireManagedSource = true) {
|
|
8131
|
+
if (spec.preserveUserCopy) {
|
|
8132
|
+
const unidentified = packages.find((candidate) => {
|
|
8133
|
+
const manifest = configuredPackageManifest(candidate);
|
|
8134
|
+
if (typeof manifest?.name === "string" && manifest.name.trim())
|
|
8135
|
+
return false;
|
|
8136
|
+
const pi = manifest?.pi;
|
|
8137
|
+
const manifestIsResourceOnly = isRecord6(pi) && (pi.extensions === void 0 || Array.isArray(pi.extensions) && pi.extensions.length === 0) && hasPiResourceSelection(pi);
|
|
8138
|
+
return sourceHintsAtPackage(candidate.source, spec) || !candidate.resourceOnly && !manifestIsResourceOnly;
|
|
8139
|
+
});
|
|
8140
|
+
if (unidentified)
|
|
8141
|
+
return {
|
|
8142
|
+
state: "drift",
|
|
8143
|
+
source: unidentified.source,
|
|
8144
|
+
installedPath: unidentified.installedPath,
|
|
8145
|
+
reason: `installed manifest identity is unavailable for ${unidentified.source}${unidentified.installedPath ? ` at ${unidentified.installedPath}` : ""}`
|
|
8146
|
+
};
|
|
8147
|
+
}
|
|
8148
|
+
return inspectPiExternalPackage(
|
|
8149
|
+
packages,
|
|
8150
|
+
spec,
|
|
8151
|
+
requireManagedSource && !spec.preserveUserCopy
|
|
8152
|
+
);
|
|
8153
|
+
}
|
|
8154
|
+
function piPreservedPackageRecovery(spec, evidence) {
|
|
8155
|
+
if (!spec.preserveUserCopy || evidence.state !== "drift") return void 0;
|
|
8156
|
+
const diagnostic = `Cannot preserve and verify ${spec.packageName}: ${evidence.reason}. Existing copies are left untouched.`;
|
|
8157
|
+
if (evidence.version && evidence.source)
|
|
8158
|
+
return `${diagnostic} Upgrade this copy to >=${spec.version} (for local/git sources, upgrade the checkout), or review its ownership and switch with: pi remove ${evidence.source} --no-approve, then pi install ${spec.source} --no-approve. Verify with pi list --no-approve and the installed manifest, then rerun setup.`;
|
|
8159
|
+
return `${diagnostic} Inspect pi list --no-approve and the configured package manifests; resolve the package identity or source ambiguity, then rerun setup.`;
|
|
8160
|
+
}
|
|
8099
8161
|
var PI_INCUMBENT_TODO_NAME = "@juicesharp/rpiv-todo";
|
|
8100
8162
|
var PI_INCUMBENT_QUESTIONS_NAME = "@juicesharp/rpiv-ask-user-question";
|
|
8101
8163
|
function piQuestionSourceName(source) {
|
|
@@ -8233,6 +8295,11 @@ function parsePiPackageSource(source, baseDir) {
|
|
|
8233
8295
|
return gitPath ? { type: "git", path: gitPath } : { type: "local", path: source };
|
|
8234
8296
|
}
|
|
8235
8297
|
function resolvePiPackage(candidate, options, userSettings) {
|
|
8298
|
+
const declaration = Array.isArray(userSettings.packages) ? userSettings.packages.find(
|
|
8299
|
+
(entry) => isRecord6(entry) && entry.source === candidate.source
|
|
8300
|
+
) : void 0;
|
|
8301
|
+
const resourceOnly = isRecord6(declaration) && Array.isArray(declaration.extensions) && declaration.extensions.length === 0 && hasPiResourceSelection(declaration);
|
|
8302
|
+
candidate = { ...candidate, resourceOnly };
|
|
8236
8303
|
if (candidate.installedPath) return candidate;
|
|
8237
8304
|
const paths = resolvePiPaths(options);
|
|
8238
8305
|
const baseDir = candidate.scope === "user" ? paths.piRoot : join12(resolve8(options.cwd ?? process.cwd()), ".pi");
|
|
@@ -8274,9 +8341,15 @@ function resolvePiListedPackages(output, options) {
|
|
|
8274
8341
|
`Pi package manifest identity cannot be verified: malformed pi list source ${source}. Rerun pi list --no-approve and inspect the configured sources before setup.`
|
|
8275
8342
|
);
|
|
8276
8343
|
}
|
|
8277
|
-
return packages.map(
|
|
8278
|
-
|
|
8279
|
-
|
|
8344
|
+
return packages.map((candidate) => {
|
|
8345
|
+
if (candidate.scope === "project")
|
|
8346
|
+
projectSettings ??= readJsonObject(join12(projectRoot, "settings.json"));
|
|
8347
|
+
return resolvePiPackage(
|
|
8348
|
+
candidate,
|
|
8349
|
+
options,
|
|
8350
|
+
candidate.scope === "project" ? projectSettings ?? {} : settings
|
|
8351
|
+
);
|
|
8352
|
+
});
|
|
8280
8353
|
}
|
|
8281
8354
|
function readPiProjectPackages(options) {
|
|
8282
8355
|
const projectRoot = join12(resolve8(options.cwd ?? process.cwd()), ".pi");
|
|
@@ -8286,6 +8359,26 @@ function readPiProjectPackages(options) {
|
|
|
8286
8359
|
options
|
|
8287
8360
|
);
|
|
8288
8361
|
}
|
|
8362
|
+
function resolvePiPreservationPackages(output, options) {
|
|
8363
|
+
const listed = resolvePiListedPackages(output, options);
|
|
8364
|
+
const paths = resolvePiPaths(options);
|
|
8365
|
+
const configured = [
|
|
8366
|
+
...resolvePiConfiguredPackages(
|
|
8367
|
+
readJsonObject(paths.settingsPath),
|
|
8368
|
+
"user",
|
|
8369
|
+
options
|
|
8370
|
+
),
|
|
8371
|
+
...readPiProjectPackages(options)
|
|
8372
|
+
];
|
|
8373
|
+
return [
|
|
8374
|
+
...listed,
|
|
8375
|
+
...configured.filter(
|
|
8376
|
+
(candidate) => !listed.some(
|
|
8377
|
+
({ scope, source }) => scope === candidate.scope && source === candidate.source
|
|
8378
|
+
)
|
|
8379
|
+
)
|
|
8380
|
+
];
|
|
8381
|
+
}
|
|
8289
8382
|
function scanPiProjectInstalledIncumbents(options, configuredPackages) {
|
|
8290
8383
|
const projectRoot = join12(resolve8(options.cwd ?? process.cwd()), ".pi");
|
|
8291
8384
|
const incumbents = [];
|
|
@@ -8425,6 +8518,7 @@ function buildPiSetupPlan(options = {}) {
|
|
|
8425
8518
|
);
|
|
8426
8519
|
}
|
|
8427
8520
|
}
|
|
8521
|
+
let userPackages = [];
|
|
8428
8522
|
let plannedQuestionRemovals = [];
|
|
8429
8523
|
let subagentsConfigContent;
|
|
8430
8524
|
let mcpContent;
|
|
@@ -8449,11 +8543,7 @@ function buildPiSetupPlan(options = {}) {
|
|
|
8449
8543
|
);
|
|
8450
8544
|
if (incumbentBlockers.length > 0)
|
|
8451
8545
|
throw new Error(incumbentBlockers.join("\n"));
|
|
8452
|
-
|
|
8453
|
-
userSettings,
|
|
8454
|
-
"user",
|
|
8455
|
-
options
|
|
8456
|
-
);
|
|
8546
|
+
userPackages = resolvePiConfiguredPackages(userSettings, "user", options);
|
|
8457
8547
|
blockers.push(...piQuestionIdentityBlockers(userPackages));
|
|
8458
8548
|
plannedQuestionRemovals = findPiIncumbentQuestions(userPackages);
|
|
8459
8549
|
subagentsConfigContent = `${JSON.stringify(
|
|
@@ -8490,6 +8580,19 @@ function buildPiSetupPlan(options = {}) {
|
|
|
8490
8580
|
blockers.push(error instanceof Error ? error.message : String(error));
|
|
8491
8581
|
}
|
|
8492
8582
|
blockers.push(...piQuestionIdentityBlockers(projectPackages));
|
|
8583
|
+
for (const pkg of getPiExternalPackageSpecs(options)) {
|
|
8584
|
+
if (!pkg.preserveUserCopy) continue;
|
|
8585
|
+
const recovery = piPreservedPackageRecovery(
|
|
8586
|
+
pkg,
|
|
8587
|
+
inspectPiSetupPackage(
|
|
8588
|
+
[...userPackages, ...projectPackages].filter(
|
|
8589
|
+
({ source, installedPath }) => installedPath !== void 0 || sourceHintsAtPackage(source, pkg)
|
|
8590
|
+
),
|
|
8591
|
+
pkg
|
|
8592
|
+
)
|
|
8593
|
+
);
|
|
8594
|
+
if (recovery) blockers.push(recovery);
|
|
8595
|
+
}
|
|
8493
8596
|
const projectIncumbentTodos = findPiIncumbentTodos(projectPackages);
|
|
8494
8597
|
for (const incumbent of projectIncumbentTodos) {
|
|
8495
8598
|
const location = incumbent.unmappedSource ? "was found in the project install roots" : `is configured in ${projectSettingsPath}`;
|
|
@@ -8542,7 +8645,7 @@ function buildPiSetupPlan(options = {}) {
|
|
|
8542
8645
|
})),
|
|
8543
8646
|
...getPiExternalPackageSpecs(options).map((pkg) => ({
|
|
8544
8647
|
kind: "package",
|
|
8545
|
-
description: `Install and verify Pi package ${pkg.source}`,
|
|
8648
|
+
description: pkg.preserveUserCopy ? `Install Pi package ${pkg.source} if absent; otherwise preserve and verify its user copy` : `Install and verify Pi package ${pkg.source}`,
|
|
8546
8649
|
target: pkg.source,
|
|
8547
8650
|
command: { command: "pi", args: ["install", pkg.source, "--no-approve"] }
|
|
8548
8651
|
})),
|
|
@@ -8948,6 +9051,25 @@ function applyPiSetup(plan) {
|
|
|
8948
9051
|
installedPackages,
|
|
8949
9052
|
manualRecovery: identityRecovery
|
|
8950
9053
|
};
|
|
9054
|
+
const preservationPackages = resolvePiPreservationPackages(
|
|
9055
|
+
before.stdout,
|
|
9056
|
+
plan.options
|
|
9057
|
+
);
|
|
9058
|
+
for (const pkg of getPiExternalPackageSpecs(plan.options)) {
|
|
9059
|
+
if (!pkg.preserveUserCopy) continue;
|
|
9060
|
+
const evidence = inspectPiSetupPackage(preservationPackages, pkg, false);
|
|
9061
|
+
const recovery = piPreservedPackageRecovery(pkg, evidence);
|
|
9062
|
+
if (recovery)
|
|
9063
|
+
return {
|
|
9064
|
+
success: false,
|
|
9065
|
+
changed,
|
|
9066
|
+
diagnostics,
|
|
9067
|
+
error: recovery,
|
|
9068
|
+
failedStep: "preflight",
|
|
9069
|
+
installedPackages,
|
|
9070
|
+
manualRecovery: recovery
|
|
9071
|
+
};
|
|
9072
|
+
}
|
|
8951
9073
|
const installed = execute("pi", ["install", desiredSource, "--no-approve"]);
|
|
8952
9074
|
if (installed.exitCode !== 0)
|
|
8953
9075
|
throw new Error(
|
|
@@ -9117,16 +9239,22 @@ function applyPiSetup(plan) {
|
|
|
9117
9239
|
);
|
|
9118
9240
|
}
|
|
9119
9241
|
for (const pkg of getPiExternalPackageSpecs(plan.options)) {
|
|
9242
|
+
const preserveUserCopy = pkg.preserveUserCopy === true;
|
|
9120
9243
|
const beforeInstall = execute("pi", ["list", "--no-approve"]);
|
|
9121
9244
|
if (beforeInstall.exitCode !== 0)
|
|
9122
9245
|
throw new Error(
|
|
9123
9246
|
`Unable to inspect ${pkg.packageName} before installation: ${beforeInstall.stderr.trim() || "pi list unavailable"}`
|
|
9124
9247
|
);
|
|
9125
|
-
const prior =
|
|
9126
|
-
parsePiPackageList(beforeInstall.stdout),
|
|
9248
|
+
const prior = inspectPiSetupPackage(
|
|
9249
|
+
preserveUserCopy ? resolvePiPreservationPackages(beforeInstall.stdout, plan.options) : parsePiPackageList(beforeInstall.stdout),
|
|
9127
9250
|
pkg,
|
|
9128
9251
|
false
|
|
9129
9252
|
);
|
|
9253
|
+
const recovery = piPreservedPackageRecovery(pkg, prior);
|
|
9254
|
+
if (recovery) {
|
|
9255
|
+
manualRecovery = recovery;
|
|
9256
|
+
throw new Error(recovery);
|
|
9257
|
+
}
|
|
9130
9258
|
const priorSourceMatches = prior.state === "installed" && prior.source !== void 0 && prior.installedPath !== void 0 && piExternalSourceMatches(
|
|
9131
9259
|
{
|
|
9132
9260
|
scope: "user",
|
|
@@ -9135,7 +9263,8 @@ function applyPiSetup(plan) {
|
|
|
9135
9263
|
},
|
|
9136
9264
|
pkg.source
|
|
9137
9265
|
);
|
|
9138
|
-
|
|
9266
|
+
const preserving = preserveUserCopy && prior.state === "installed";
|
|
9267
|
+
if (!preserving && !priorSourceMatches) {
|
|
9139
9268
|
const result = execute("pi", ["install", pkg.source, "--no-approve"]);
|
|
9140
9269
|
if (result.exitCode !== 0)
|
|
9141
9270
|
throw new Error(
|
|
@@ -9143,12 +9272,15 @@ function applyPiSetup(plan) {
|
|
|
9143
9272
|
);
|
|
9144
9273
|
}
|
|
9145
9274
|
const listed = execute("pi", ["list", "--no-approve"]);
|
|
9146
|
-
const verified = listed.exitCode === 0 ?
|
|
9275
|
+
const verified = listed.exitCode === 0 ? inspectPiSetupPackage(
|
|
9276
|
+
preserveUserCopy ? resolvePiPreservationPackages(listed.stdout, plan.options) : parsePiPackageList(listed.stdout),
|
|
9277
|
+
pkg
|
|
9278
|
+
) : {
|
|
9147
9279
|
state: "drift",
|
|
9148
9280
|
version: void 0,
|
|
9149
9281
|
reason: listed.stderr.trim() || "pi list unavailable"
|
|
9150
9282
|
};
|
|
9151
|
-
if (prior.state === "installed" && verified.version !== void 0 && lt(verified.version, prior.version)) {
|
|
9283
|
+
if (!preserveUserCopy && prior.state === "installed" && verified.version !== void 0 && lt(verified.version, prior.version)) {
|
|
9152
9284
|
const restoreSource = prior.source ?? `npm:${pkg.packageName}@${prior.version}`;
|
|
9153
9285
|
const restored = execute("pi", [
|
|
9154
9286
|
"install",
|
|
@@ -9168,8 +9300,21 @@ function applyPiSetup(plan) {
|
|
|
9168
9300
|
`Pi attempted to downgrade ${pkg.packageName} from ${prior.version} to ${verified.version}; exact-version recovery ${recoverySucceeded ? "verified" : "failed or unverifiable"}. ${recoveryGuidance}`
|
|
9169
9301
|
);
|
|
9170
9302
|
}
|
|
9171
|
-
if (verified.state !== "installed")
|
|
9172
|
-
|
|
9303
|
+
if (verified.state !== "installed") {
|
|
9304
|
+
manualRecovery = piPreservedPackageRecovery(pkg, verified);
|
|
9305
|
+
throw new Error(
|
|
9306
|
+
`Pi did not verify ${pkg.source}: ${verified.reason}.${manualRecovery ? ` ${manualRecovery}` : ""}`
|
|
9307
|
+
);
|
|
9308
|
+
}
|
|
9309
|
+
if (preserving) {
|
|
9310
|
+
if (verified.source !== prior.source || !piPackagePathsEqual(verified.installedPath, prior.installedPath))
|
|
9311
|
+
throw new Error(
|
|
9312
|
+
`Preserved Pi package ${pkg.packageName} changed source or directory during verification; inspect pi list --no-approve, then rerun setup.`
|
|
9313
|
+
);
|
|
9314
|
+
diagnostics.push(
|
|
9315
|
+
`Preserved and verified ${pkg.packageName}@${verified.version} from ${verified.source}; no reinstall.`
|
|
9316
|
+
);
|
|
9317
|
+
}
|
|
9173
9318
|
installedPackages.push(pkg.source);
|
|
9174
9319
|
}
|
|
9175
9320
|
for (const item of plan.items.filter(
|
|
@@ -9422,6 +9567,18 @@ function statusFromPlan(plan, context2, evidence = {}) {
|
|
|
9422
9567
|
...plan.projectIncumbentTodos ?? []
|
|
9423
9568
|
];
|
|
9424
9569
|
const packageSpecs = getPiExternalPackageSpecs(plan.options);
|
|
9570
|
+
let preservationPackages = configuredPackages;
|
|
9571
|
+
let preservationError;
|
|
9572
|
+
if (packages.exitCode === 0) {
|
|
9573
|
+
try {
|
|
9574
|
+
preservationPackages = resolvePiPreservationPackages(packages.stdout, {
|
|
9575
|
+
...plan.options,
|
|
9576
|
+
commandExecutor: execute
|
|
9577
|
+
});
|
|
9578
|
+
} catch (error) {
|
|
9579
|
+
preservationError = error instanceof Error ? error.message : String(error);
|
|
9580
|
+
}
|
|
9581
|
+
}
|
|
9425
9582
|
for (const target of targets.filter(
|
|
9426
9583
|
(candidate) => candidate.kind === "package" && packageSpecs.some(({ source }) => source === candidate.path)
|
|
9427
9584
|
)) {
|
|
@@ -9432,8 +9589,14 @@ function statusFromPlan(plan, context2, evidence = {}) {
|
|
|
9432
9589
|
target.state = "unknown";
|
|
9433
9590
|
target.observed = packages.stderr.trim() || "pi list unavailable";
|
|
9434
9591
|
} else if (externalSpec) {
|
|
9435
|
-
const
|
|
9436
|
-
|
|
9592
|
+
const preserveUserCopy = externalSpec.preserveUserCopy === true;
|
|
9593
|
+
if (preserveUserCopy && preservationError) {
|
|
9594
|
+
target.state = "drift";
|
|
9595
|
+
target.observed = preservationError;
|
|
9596
|
+
continue;
|
|
9597
|
+
}
|
|
9598
|
+
const inspected = inspectPiSetupPackage(
|
|
9599
|
+
preserveUserCopy ? preservationPackages : configuredPackages,
|
|
9437
9600
|
externalSpec,
|
|
9438
9601
|
externalSpec.id !== "delegation" || plan.options.runtimePackageRoot !== void 0
|
|
9439
9602
|
);
|