jorgex-stack 1.9.31 → 1.9.33
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 +17 -4
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +303 -106
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -42,6 +42,14 @@ pnpm dlx jorgex-stack install
|
|
|
42
42
|
pnpm dlx jorgex-stack sync
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
For a fresh Engram installation, always consult the current published Stack and bypass only the `pnpm dlx` cache:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm --config.dlx-cache-max-age=0 dlx jorgex-stack@latest install --engram
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`dlx-cache-max-age` is separate from the pnpm 11 dependency-age filter: it controls only the cached `dlx` package, while `minimumReleaseAgeExclude` applies only to the named package resolution. An explicit Stack version such as `@1.9.30` does not reuse the cache entry for another version. Do not use `@latest` for Pi; Pi consumption still requires its exact validated pin.
|
|
52
|
+
|
|
45
53
|
Other important commands:
|
|
46
54
|
|
|
47
55
|
```bash
|
|
@@ -120,7 +128,7 @@ pnpm dlx jorgex-stack@1.9.7 sync --agents pi
|
|
|
120
128
|
pnpm dlx jorgex-stack@1.9.7 uninstall --agents pi
|
|
121
129
|
```
|
|
122
130
|
|
|
123
|
-
Stack downloads the frozen registry tarball, verifies its exact size plus SHA-256/SHA-512, backs up Pi's `settings.json`, and only then asks Pi to install that local file. The historical `0.8.0` tarball was `89128340` bytes; the exact adopted artifact and integrity values are authoritative in `src/lib/pi-runtime-pin.json`, while the lifecycle remains authoritative in `src/lib/pi-runtime.ts`. Pi's own package-manager invocation is the narrow runtime exception to the repository's pnpm-only rule; the Stack lifecycle never launches npm directly. After the package is healthy, Stack projects the shared resources into Pi: marked `jorgex:system-prompt` and `jorgex:engram-protocol` sections in `~/.pi/agent/AGENTS.md`, canonical skills under `~/.agents/skills`, and `~/.pi/agent/prompts/lean-audit.md`. When the managed Playwright preference is active, the projection also adds or removes the marked `jorgex:browser` section dynamically. The
|
|
131
|
+
Stack downloads the frozen registry tarball, verifies its exact size plus SHA-256/SHA-512, backs up Pi's `settings.json`, and only then asks Pi to install that local file. The historical `0.8.0` tarball was `89128340` bytes; the exact adopted artifact and integrity values are authoritative in `src/lib/pi-runtime-pin.json`, while the lifecycle remains authoritative in `src/lib/pi-runtime.ts`. Pi's own package-manager invocation is the narrow runtime exception to the repository's pnpm-only rule; the Stack lifecycle never launches npm directly. After the package is healthy, Stack projects the shared resources into Pi: marked `jorgex:system-prompt` and `jorgex:engram-protocol` sections in `~/.pi/agent/AGENTS.md`, canonical skills under `~/.agents/skills`, and `~/.pi/agent/prompts/lean-audit.md`. When the managed Playwright preference is active, the projection also adds or removes the marked `jorgex:browser` section dynamically. The global Playwright package and Chromium cache are shared by the machine; `--playwright-runtimes` controls which runtime receives the guide. The pin vigente declara `playwright-handoff-v1`, por lo que Pi puede seleccionarse y recibe su handoff de Playwright además de la guía. El paquete Pi fijado también soporta el handoff opcional de Chrome DevTools; Stack persiste cada handoff como parte de la proyección y registra su SHA-256 en el receipt correspondiente tras una operación correcta. Context7 remains outside the Pi scope. The published `1.9.7` entry `{ "source": "npm:jorgex-pi@0.8.4", "skills": [], "prompts": [] }` and the `0.8.5` snapshot reference are historical; they do not describe the adopted pin. Filters are applied only after this projection exists, so the package does not duplicate shared resources. Package ownership is recorded separately in `~/.jorgex-stack/pi-receipt.json`; projection ownership is recorded in `~/.jorgex-stack/pi-projection-receipt.json`. Package receipts reject manual, duplicate, divergent, partial, corrupt, copied-to-another-scope, or unknown-history state. Projection cleanup requires an exact scope-bound ownership receipt; DevTools conflicts preserve the handoff for review.
|
|
124
132
|
|
|
125
133
|
Historically, the published Pi 0.8.0 direct-package snapshot added `work-audit`: the snapshot grew from **17 to 18 skill trees** (96 to 97 files), and the active runtime allowlist grew from **16 to 17 skills**. The historical Pi 0.8.5 snapshot reference records 18 skill trees and 98 files; it does not describe the adopted pin. Reference F2-A is included while the private F1 skills remain preserved. `playwright-cli` remains in the snapshot but inactive because browser automation is a separate opt-in integration.
|
|
126
134
|
|
|
@@ -136,7 +144,7 @@ Engram remains mandatory and user-owned. An existing binary is preserved. `insta
|
|
|
136
144
|
|
|
137
145
|
Históricamente, Stack `1.9.7` reconocía el receipt exacto de Pi `npm:jorgex-pi@0.8.4`. Usa versiones exactas, nunca `latest`, y no edites receipts o hashes ni borres `HOME`, Engram o la proyección de otro runtime para forzar confianza. El pin y los comandos actuales de transición y rollback están en [docs/references/pi-runtime.md](docs/references/pi-runtime.md).
|
|
138
146
|
|
|
139
|
-
|
|
147
|
+
Pi puede consumirse inmediatamente después de publicar y verificar el artefacto adoptado. El pin exacto, la procedencia, los SHA-256/SHA-512, el SRI, la compatibilidad y el procedimiento de rollback siguen siendo obligatorios; no uses `latest` ni una versión aproximada.
|
|
140
148
|
|
|
141
149
|
`update --agents pi` only runs the Pi package lifecycle; it does not enter the global Stack updater. `update --check --agents pi` is a read-only Pi doctor. Uninstall runs package cleanup, backs up Pi's settings before removal, removes only the exact receipt-owned package after verifying absence, and preserves all companion/user state. Full behavior, failure states and troubleshooting are in [docs/references/pi-runtime.md](docs/references/pi-runtime.md).
|
|
142
150
|
|
|
@@ -156,6 +164,9 @@ pnpm dlx jorgex-stack install
|
|
|
156
164
|
# Non-interactive / agent: --playwright authorizes the global install.
|
|
157
165
|
pnpm dlx jorgex-stack install --yes --playwright
|
|
158
166
|
|
|
167
|
+
# Instala el paquete compartido y activa la guía solo en estos runtimes.
|
|
168
|
+
pnpm dlx jorgex-stack install --playwright --playwright-runtimes=opencode,claude-code
|
|
169
|
+
|
|
159
170
|
# Enable Chrome DevTools MCP explicitly per runtime.
|
|
160
171
|
pnpm dlx jorgex-stack install --devtools
|
|
161
172
|
pnpm dlx jorgex-stack install --no-devtools
|
|
@@ -163,7 +174,7 @@ pnpm dlx jorgex-stack install --agents pi --devtools
|
|
|
163
174
|
pnpm dlx jorgex-stack sync --agents pi --no-devtools
|
|
164
175
|
```
|
|
165
176
|
|
|
166
|
-
Under the hood, `--playwright` runs two `pnpm` argv-only plans back to back: `pnpm add --global @playwright/cli@0.1.18` (the package) and then `pnpm dlx @playwright/cli@0.1.18 install-browser` (the
|
|
177
|
+
Under the hood, `--playwright` runs two `pnpm` argv-only plans back to back: `pnpm add --global @playwright/cli@0.1.18` (the package) and then `pnpm dlx @playwright/cli@0.1.18 install-browser chromium` (the Chromium cache). It then verifies that the pinned package launches Chromium headless against `about:blank`. Removal is `pnpm remove --global @playwright/cli` (no version suffix). If installation fails, the error identifies the failed phase — global package, browser download, browser launch, or preference persistence — and recommends `jorgex-stack install --playwright`; the preference is not marked enabled unless the complete plan succeeds.
|
|
167
178
|
|
|
168
179
|
Daily operation:
|
|
169
180
|
|
|
@@ -179,7 +190,7 @@ Daily operation:
|
|
|
179
190
|
|
|
180
191
|
1. **Stack** (jorgex-stack): detects whether it is a git clone or a global install, then offers an update with confirmation.
|
|
181
192
|
2. **Engram** (binary): detects the installed version and offers an update through the **native channel** (brew -> `go install` -> release URL). Nothing needs to be stopped: as in upstream macOS/Linux, live processes keep using the old version until clients restart; on Windows, the in-use `.exe` is rotated by rename before installation. **Automatic DB backup before updating**. The database and memories are never touched.
|
|
182
|
-
3. **Playwright CLI** (only when explicitly enabled): compares the detected binary with the approved bundle pin and offers to realign it with explicit confirmation. The realignment re-applies **both** plans — `pnpm add --global @playwright/cli@0.1.18` (package) and `pnpm dlx @playwright/cli@0.1.18 install-browser` (
|
|
193
|
+
3. **Playwright CLI** (only when explicitly enabled): compares the detected binary with the approved bundle pin and offers to realign it with explicit confirmation. The realignment re-applies **both** plans — `pnpm add --global @playwright/cli@0.1.18` (package) and `pnpm dlx @playwright/cli@0.1.18 install-browser chromium` (Chromium cache) — and fails closed if either step returns non-zero. The error identifies whether the package-update or browser-download phase failed and recommends `jorgex-stack install --playwright` to retry both; a Playwright update does not require `sync`.
|
|
183
194
|
4. **Vendored skills** (maintainer only): third-party skills ship **pinned** with the stack version, so the installed package never reaches out to their upstreams. Only when running from a git clone (`pnpm cli update`) does `update` scan the upstreams in `upstreams.json`, download to a temp directory, **show a mandatory diff**, and ask for confirmation. A moved upstream is only a candidate until that review is accepted and a deliberate re-pin is made for a future release; it is never treated as an accepted official update automatically. Skills with local changes (`modified: true`) warn and require double confirmation.
|
|
184
195
|
|
|
185
196
|
Usage:
|
|
@@ -237,6 +248,8 @@ La integración de este App pertenece al release del repositorio Stack. No conce
|
|
|
237
248
|
|
|
238
249
|
Requirements: Node >= 22.5 and pnpm (never npm).
|
|
239
250
|
|
|
251
|
+
pnpm 11 dependency resolution uses `minimumReleaseAge=1440` by default, with `minimumReleaseAgeExclude` for `jorgex-stack` and `jorgex-pi`. Keep that exclusion in the user-level pnpm config for `pnpm dlx` launched from HOME and in each repository workspace config for local commands; preserve every unrelated existing setting. This documents the maintainer environment and does not change other users' global configuration.
|
|
252
|
+
|
|
240
253
|
```
|
|
241
254
|
pnpm install
|
|
242
255
|
pnpm build # tsup -> dist/
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -2165,6 +2165,8 @@ function resolvePnpmFailureRemedy(reason) {
|
|
|
2165
2165
|
return PNPM_GLOBAL_BIN_REMEDY;
|
|
2166
2166
|
case "action-failed":
|
|
2167
2167
|
return null;
|
|
2168
|
+
case "browser-launch":
|
|
2169
|
+
return "Chromium se descarg\xF3, pero no ha podido arrancar. Revisa el error de lanzamiento y las dependencias de tu sistema antes de reintentar.";
|
|
2168
2170
|
}
|
|
2169
2171
|
}
|
|
2170
2172
|
function hasNonzeroProcessStatus(error) {
|
|
@@ -2186,6 +2188,18 @@ function resolvePlaywrightCliState({ binPath, versionOutput }) {
|
|
|
2186
2188
|
}
|
|
2187
2189
|
function detectPlaywrightCli() {
|
|
2188
2190
|
const binPath = lookPath(PLAYWRIGHT_CLI.bin);
|
|
2191
|
+
if (binPath === null) {
|
|
2192
|
+
const pnpmHome = resolvePnpmHome();
|
|
2193
|
+
const name = process.platform === "win32" ? `${PLAYWRIGHT_CLI.bin}.cmd` : PLAYWRIGHT_CLI.bin;
|
|
2194
|
+
const known = [path18.join(pnpmHome, name), path18.join(pnpmHome, "bin", name)].find((file) => {
|
|
2195
|
+
try {
|
|
2196
|
+
return fs13.statSync(file).isFile();
|
|
2197
|
+
} catch {
|
|
2198
|
+
return false;
|
|
2199
|
+
}
|
|
2200
|
+
});
|
|
2201
|
+
if (known) return { status: "not-in-path", binPath: known, detectedVersion: null };
|
|
2202
|
+
}
|
|
2189
2203
|
return resolvePlaywrightCliState({
|
|
2190
2204
|
binPath,
|
|
2191
2205
|
versionOutput: binPath ? runDetectedBin(binPath, ["--version"], 5e3, { NO_UPDATE_NOTIFIER: "1" }) : null
|
|
@@ -2248,7 +2262,44 @@ function planPlaywrightCliCommand(action, pnpmBin) {
|
|
|
2248
2262
|
case "remove":
|
|
2249
2263
|
return { command: pnpmBin, args: ["remove", "--global", PLAYWRIGHT_CLI.packageName] };
|
|
2250
2264
|
case "install-browser":
|
|
2251
|
-
return { command: pnpmBin, args: ["dlx", pinnedPackage, PLAYWRIGHT_CLI.browserInstallAction] };
|
|
2265
|
+
return { command: pnpmBin, args: ["dlx", pinnedPackage, PLAYWRIGHT_CLI.browserInstallAction, "chromium"] };
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
function verifyPlaywrightBrowser(pnpmBin, env = process.env, cwd) {
|
|
2269
|
+
const rootCommand = planDetectedBinCommand(pnpmBin, ["root", "--global"]);
|
|
2270
|
+
if (rootCommand === null) return false;
|
|
2271
|
+
try {
|
|
2272
|
+
const root = execFileSync2(rootCommand.command, rootCommand.args, {
|
|
2273
|
+
encoding: "utf8",
|
|
2274
|
+
timeout: 5e3,
|
|
2275
|
+
env,
|
|
2276
|
+
cwd,
|
|
2277
|
+
stdio: ["ignore", "pipe", "inherit"]
|
|
2278
|
+
}).trim();
|
|
2279
|
+
if (!path18.isAbsolute(root)) return false;
|
|
2280
|
+
const packageFile = fs13.realpathSync(path18.join(root, "@playwright", "cli", "package.json"));
|
|
2281
|
+
const installed = JSON.parse(fs13.readFileSync(packageFile, "utf8"));
|
|
2282
|
+
if (installed.name !== PLAYWRIGHT_CLI.packageName || installed.version !== PLAYWRIGHT_CLI.version) return false;
|
|
2283
|
+
const probe = `
|
|
2284
|
+
const { createRequire } = require('node:module');
|
|
2285
|
+
const { chromium } = createRequire(process.argv[1])('playwright');
|
|
2286
|
+
(async () => {
|
|
2287
|
+
const browser = await chromium.launch({ headless: true, timeout: 15000 });
|
|
2288
|
+
try {
|
|
2289
|
+
const page = await browser.newPage();
|
|
2290
|
+
await page.goto('about:blank', { timeout: 5000 });
|
|
2291
|
+
} finally { await browser.close(); }
|
|
2292
|
+
})().catch(error => { console.error(error.message); process.exitCode = 1; });
|
|
2293
|
+
`;
|
|
2294
|
+
execFileSync2(process.execPath, ["-e", probe, packageFile], {
|
|
2295
|
+
timeout: 25e3,
|
|
2296
|
+
stdio: "inherit",
|
|
2297
|
+
cwd,
|
|
2298
|
+
env: { ...env, NO_UPDATE_NOTIFIER: "1" }
|
|
2299
|
+
});
|
|
2300
|
+
return true;
|
|
2301
|
+
} catch {
|
|
2302
|
+
return false;
|
|
2252
2303
|
}
|
|
2253
2304
|
}
|
|
2254
2305
|
function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
|
|
@@ -2270,14 +2321,22 @@ function executePlaywrightToolAction(action, pnpmBin = resolvePnpmBin(), env) {
|
|
|
2270
2321
|
const command = planPlaywrightCliCommand(action, pnpmBin);
|
|
2271
2322
|
const invocation = planDetectedBinCommand(command.command, command.args);
|
|
2272
2323
|
if (invocation === null) return { ok: false, reason: "pnpm-command" };
|
|
2324
|
+
const cwd = action === "install-browser" ? fs13.mkdtempSync(path18.join(os.tmpdir(), "jorgex-playwright-")) : void 0;
|
|
2325
|
+
const childEnv = action === "install-browser" ? { ...env ?? process.env, NO_UPDATE_NOTIFIER: "1" } : env ?? process.env;
|
|
2273
2326
|
try {
|
|
2274
2327
|
execFileSync2(invocation.command, invocation.args, {
|
|
2275
2328
|
stdio: "inherit",
|
|
2276
|
-
|
|
2329
|
+
env: childEnv,
|
|
2330
|
+
...cwd === void 0 ? {} : { cwd }
|
|
2277
2331
|
});
|
|
2332
|
+
if (action === "install-browser" && !verifyPlaywrightBrowser(pnpmBin, childEnv, cwd)) {
|
|
2333
|
+
return { ok: false, reason: "browser-launch" };
|
|
2334
|
+
}
|
|
2278
2335
|
return { ok: true };
|
|
2279
2336
|
} catch {
|
|
2280
2337
|
return { ok: false, reason: "action-failed" };
|
|
2338
|
+
} finally {
|
|
2339
|
+
if (cwd !== void 0) fs13.rmSync(cwd, { recursive: true, force: true });
|
|
2281
2340
|
}
|
|
2282
2341
|
}
|
|
2283
2342
|
|
|
@@ -2287,6 +2346,7 @@ import path19 from "path";
|
|
|
2287
2346
|
var PLAYWRIGHT_CLI_PREFERENCE_VERSION = 1;
|
|
2288
2347
|
var DEVTOOLS_MCP_PREFERENCE_VERSION = 1;
|
|
2289
2348
|
var PRIMARY_MODEL_OWNERSHIP_VERSION = 1;
|
|
2349
|
+
var PLAYWRIGHT_RUNTIMES = ["opencode", "claude-code", "codex", "pi"];
|
|
2290
2350
|
function readPreference(file) {
|
|
2291
2351
|
try {
|
|
2292
2352
|
return { raw: fs14.readFileSync(file, "utf8"), errorCode: null };
|
|
@@ -2301,8 +2361,11 @@ function playwrightCliPreferenceFile(stateDir = dataDir()) {
|
|
|
2301
2361
|
function parsePlaywrightCliPreference(raw) {
|
|
2302
2362
|
try {
|
|
2303
2363
|
const value = JSON.parse(raw);
|
|
2304
|
-
if (value
|
|
2305
|
-
return value.enabled;
|
|
2364
|
+
if (!isRecord2(value)) return void 0;
|
|
2365
|
+
if (value.version === 1 && typeof value.enabled === "boolean") return { version: 1, enabled: value.enabled };
|
|
2366
|
+
if (value.version !== 2 || !isRecord2(value.enabled)) return void 0;
|
|
2367
|
+
if (Object.entries(value.enabled).some(([runtime, enabled]) => !PLAYWRIGHT_RUNTIMES.includes(runtime) || typeof enabled !== "boolean")) return void 0;
|
|
2368
|
+
return { version: 2, enabled: value.enabled };
|
|
2306
2369
|
} catch {
|
|
2307
2370
|
return void 0;
|
|
2308
2371
|
}
|
|
@@ -2315,15 +2378,28 @@ function playwrightCliPreferenceError(file = playwrightCliPreferenceFile()) {
|
|
|
2315
2378
|
if (raw === null || parsePlaywrightCliPreference(raw) !== void 0) return null;
|
|
2316
2379
|
return `Playwright CLI: preferencia inv\xE1lida en ${file}. Corrige o borra ese archivo antes de reintentar.`;
|
|
2317
2380
|
}
|
|
2318
|
-
function loadPlaywrightCliPreference(file = playwrightCliPreferenceFile()) {
|
|
2381
|
+
function loadPlaywrightCliPreference(file = playwrightCliPreferenceFile(), runtime) {
|
|
2319
2382
|
const { raw } = readPreference(file);
|
|
2320
|
-
|
|
2321
|
-
|
|
2383
|
+
const state = raw === null ? void 0 : parsePlaywrightCliPreference(raw);
|
|
2384
|
+
if (state === void 0) return void 0;
|
|
2385
|
+
if (state.version === 1) return state.enabled;
|
|
2386
|
+
return runtime === void 0 ? Object.values(state.enabled).some(Boolean) : state.enabled[runtime] === true;
|
|
2322
2387
|
}
|
|
2323
|
-
function savePlaywrightCliPreference(file, enabled) {
|
|
2388
|
+
function savePlaywrightCliPreference(file, enabled, selection) {
|
|
2324
2389
|
const error = playwrightCliPreferenceError(file);
|
|
2325
2390
|
if (error !== null) throw new Error(error);
|
|
2326
|
-
|
|
2391
|
+
const { raw } = readPreference(file);
|
|
2392
|
+
const previous = raw === null ? void 0 : parsePlaywrightCliPreference(raw);
|
|
2393
|
+
if (selection === void 0 && previous?.version !== 2) {
|
|
2394
|
+
writeText(file, JSON.stringify({ version: PLAYWRIGHT_CLI_PREFERENCE_VERSION, enabled }) + "\n");
|
|
2395
|
+
return;
|
|
2396
|
+
}
|
|
2397
|
+
const inherited = previous?.version === 2 ? previous.enabled : Object.fromEntries(PLAYWRIGHT_RUNTIMES.map((runtime) => [runtime, previous?.enabled === true]));
|
|
2398
|
+
const choices = { ...inherited, ...selection };
|
|
2399
|
+
const state = { version: 2, enabled: enabled ? choices : Object.fromEntries(Object.keys(choices).map((runtime) => [runtime, false])) };
|
|
2400
|
+
const content = JSON.stringify(state) + "\n";
|
|
2401
|
+
if (parsePlaywrightCliPreference(content) === void 0) throw new Error("Playwright CLI: selecci\xF3n de runtimes inv\xE1lida.");
|
|
2402
|
+
writeText(file, content);
|
|
2327
2403
|
}
|
|
2328
2404
|
function devtoolsMcpPreferenceFile(stateDir = dataDir()) {
|
|
2329
2405
|
return path19.join(stateDir, "devtools-mcp.json");
|
|
@@ -2536,7 +2612,7 @@ function makeContext(adapter, configDir, mode = DEFAULT_INSTALL_MODE_PREFERENCE,
|
|
|
2536
2612
|
models,
|
|
2537
2613
|
warnings: [],
|
|
2538
2614
|
enabledMcpServers: enabledMcpServers(adapter.id, void 0, useBrowserPreferences),
|
|
2539
|
-
playwrightCliEnabled: useBrowserPreferences && loadPlaywrightCliPreference() === true,
|
|
2615
|
+
playwrightCliEnabled: useBrowserPreferences && loadPlaywrightCliPreference(playwrightCliPreferenceFile(), adapter.id) === true,
|
|
2540
2616
|
ownedMcpServers: ownedMcpServers(adapter.id, useBrowserPreferences),
|
|
2541
2617
|
ownedPrimaryModelFields: useBrowserPreferences ? loadPrimaryModelOwnership(primaryModelOwnershipFile(), adapter.id, configDir) : /* @__PURE__ */ new Set()
|
|
2542
2618
|
};
|
|
@@ -2655,7 +2731,7 @@ async function runInstall(opts) {
|
|
|
2655
2731
|
models,
|
|
2656
2732
|
warnings: [],
|
|
2657
2733
|
enabledMcpServers: enabledMcpServers(id, opts.devtoolsMcpSelection?.[id], useManifest),
|
|
2658
|
-
playwrightCliEnabled: projectPlaywrightPrompt
|
|
2734
|
+
playwrightCliEnabled: projectPlaywrightPrompt ? opts.playwrightToolConsent?.runtimeSelection?.[id] ?? true : useManifest && loadPlaywrightCliPreference(playwrightCliPreferenceFile(), id) === true,
|
|
2659
2735
|
ownedMcpServers: ownedMcpServers(id, useManifest),
|
|
2660
2736
|
ownedPrimaryModelFields: useManifest ? loadPrimaryModelOwnership(primaryModelOwnershipFile(), id, configDir) : /* @__PURE__ */ new Set()
|
|
2661
2737
|
};
|
|
@@ -2750,7 +2826,11 @@ async function runInstall(opts) {
|
|
|
2750
2826
|
const pnpmBin = resolvePnpmBin();
|
|
2751
2827
|
return executePlaywrightToolAction2(action, pnpmBin, env);
|
|
2752
2828
|
},
|
|
2753
|
-
persistEnabled: (enabled) =>
|
|
2829
|
+
persistEnabled: (enabled) => {
|
|
2830
|
+
const selected = opts.playwrightToolConsent?.runtimeSelection;
|
|
2831
|
+
const fileSelection = selected === void 0 ? void 0 : Object.fromEntries(Object.entries(selected).filter(([runtime]) => runtime !== "pi"));
|
|
2832
|
+
savePlaywrightCliPreference(playwrightCliPreferenceFile(), enabled, fileSelection);
|
|
2833
|
+
},
|
|
2754
2834
|
setupPnpm: (pnpmBin) => setupPnpmGlobal(pnpmBin)
|
|
2755
2835
|
};
|
|
2756
2836
|
let setupAttempted = false;
|
|
@@ -2778,6 +2858,7 @@ async function runInstall(opts) {
|
|
|
2778
2858
|
return first;
|
|
2779
2859
|
}
|
|
2780
2860
|
preparedEnv = setup.env;
|
|
2861
|
+
p.log.info("pnpm preparado para esta instalaci\xF3n. Abre una terminal nueva al terminar para que otras herramientas y doctor reciban el PATH actualizado.");
|
|
2781
2862
|
return baseDeps.run(action, preparedEnv);
|
|
2782
2863
|
}
|
|
2783
2864
|
});
|
|
@@ -2789,7 +2870,11 @@ async function runInstall(opts) {
|
|
|
2789
2870
|
} else {
|
|
2790
2871
|
let promptReconciliationFailed = false;
|
|
2791
2872
|
for (const { adapter, ctx } of successfulContexts) {
|
|
2792
|
-
const browserCtx = {
|
|
2873
|
+
const browserCtx = {
|
|
2874
|
+
...ctx,
|
|
2875
|
+
playwrightCliEnabled: opts.playwrightToolConsent?.runtimeSelection?.[adapter.id] ?? true,
|
|
2876
|
+
warnings: []
|
|
2877
|
+
};
|
|
2793
2878
|
try {
|
|
2794
2879
|
const browserChanges = diffPlan(planSystemPrompt(adapter, browserCtx)).filter((change) => change.status !== "unchanged");
|
|
2795
2880
|
if (browserChanges.length === 0) continue;
|
|
@@ -2812,7 +2897,7 @@ async function runInstall(opts) {
|
|
|
2812
2897
|
exitCode = 1;
|
|
2813
2898
|
p.log.error("Playwright CLI y navegador se han instalado y la preferencia est\xE1 activada, pero la gu\xEDa de navegador qued\xF3 en estado parcial. Ejecuta 'jorgex-stack sync' para repararla.");
|
|
2814
2899
|
} else {
|
|
2815
|
-
p.log.success("Playwright CLI y
|
|
2900
|
+
p.log.success("Playwright CLI instalado y arranque de Chromium verificado.");
|
|
2816
2901
|
}
|
|
2817
2902
|
}
|
|
2818
2903
|
}
|
|
@@ -3276,6 +3361,10 @@ function runPiPackageManagedOperation(input, deps) {
|
|
|
3276
3361
|
}
|
|
3277
3362
|
|
|
3278
3363
|
// src/lib/pi-projection-lifecycle.ts
|
|
3364
|
+
var HANDOFF_KINDS = ["devtools", "playwright"];
|
|
3365
|
+
function handoffEnabled(input, kind) {
|
|
3366
|
+
return (kind === "devtools" ? input.devtoolsMcpEnabled : input.playwrightHandoffEnabled) === true;
|
|
3367
|
+
}
|
|
3279
3368
|
var preparedPiProjectionUninstalls = /* @__PURE__ */ new WeakMap();
|
|
3280
3369
|
function projectionScope(scope) {
|
|
3281
3370
|
return {
|
|
@@ -3322,18 +3411,27 @@ function projectionPlan(input, scope) {
|
|
|
3322
3411
|
if (input.devtoolsMcpEnabled && input.pnpmBin) {
|
|
3323
3412
|
const server = loadCanonicalMcp(input.stackDir).servers[DEVTOOLS_MCP_SERVER];
|
|
3324
3413
|
if (server?.transport !== "stdio" || !Array.isArray(server.args)) throw new Error("Falta la configuraci\xF3n can\xF3nica de DevTools.");
|
|
3325
|
-
actions.push({ kind: "write", target:
|
|
3414
|
+
actions.push({ kind: "write", target: handoffPath(scope, "devtools"), content: `${JSON.stringify({
|
|
3326
3415
|
schemaVersion: 1,
|
|
3327
3416
|
enabled: true,
|
|
3328
3417
|
command: input.pnpmBin,
|
|
3329
3418
|
args: server.args
|
|
3330
3419
|
}, null, 2)}
|
|
3420
|
+
` });
|
|
3421
|
+
}
|
|
3422
|
+
if (input.playwrightHandoffEnabled && input.playwrightCliCommand) {
|
|
3423
|
+
actions.push({ kind: "write", target: handoffPath(scope, "playwright"), content: `${JSON.stringify({
|
|
3424
|
+
schemaVersion: 1,
|
|
3425
|
+
enabled: true,
|
|
3426
|
+
command: input.playwrightCliCommand,
|
|
3427
|
+
version: PLAYWRIGHT_CLI.version
|
|
3428
|
+
}, null, 2)}
|
|
3331
3429
|
` });
|
|
3332
3430
|
}
|
|
3333
3431
|
return actions;
|
|
3334
3432
|
}
|
|
3335
|
-
function
|
|
3336
|
-
return path23.resolve(scope.codingAgentDir, "jorgex-pi",
|
|
3433
|
+
function handoffPath(scope, kind) {
|
|
3434
|
+
return path23.resolve(scope.codingAgentDir, "jorgex-pi", `${kind}.v1.json`);
|
|
3337
3435
|
}
|
|
3338
3436
|
function contentHash(content) {
|
|
3339
3437
|
return createHash("sha256").update(content).digest("hex");
|
|
@@ -3344,13 +3442,14 @@ function canonicalActionContent(action) {
|
|
|
3344
3442
|
function hasActionDrift(action, deps) {
|
|
3345
3443
|
return deps.readText(action.target) !== canonicalActionContent(action);
|
|
3346
3444
|
}
|
|
3347
|
-
function applyActions(actions, deps,
|
|
3445
|
+
function applyActions(actions, deps, handoffs = []) {
|
|
3348
3446
|
for (const action of actions) {
|
|
3349
|
-
|
|
3447
|
+
const handoff = handoffs.find((entry) => path23.resolve(action.target) === entry.file);
|
|
3448
|
+
if (handoff) {
|
|
3350
3449
|
try {
|
|
3351
|
-
if (deps.readText(handoff.file) !== handoff.previous) return
|
|
3450
|
+
if (deps.readText(handoff.file) !== handoff.previous) return handoffConflict(handoff.kind, handoff.file);
|
|
3352
3451
|
} catch {
|
|
3353
|
-
return
|
|
3452
|
+
return handoffConflict(handoff.kind, handoff.file);
|
|
3354
3453
|
}
|
|
3355
3454
|
}
|
|
3356
3455
|
if (action.kind === "write") deps.writeText(action.target, action.content);
|
|
@@ -3369,8 +3468,7 @@ function receiptFor(plan, scope) {
|
|
|
3369
3468
|
const owned = [...new Set(
|
|
3370
3469
|
plan.map((action) => path23.resolve(action.target)).filter((target) => target !== systemPrompt)
|
|
3371
3470
|
)];
|
|
3372
|
-
const
|
|
3373
|
-
return {
|
|
3471
|
+
const receipt = {
|
|
3374
3472
|
schemaVersion: 1,
|
|
3375
3473
|
scope: {
|
|
3376
3474
|
kind: scope.kind,
|
|
@@ -3378,9 +3476,13 @@ function receiptFor(plan, scope) {
|
|
|
3378
3476
|
codingAgentDir: scope.codingAgentDir,
|
|
3379
3477
|
receiptFile: scope.receiptFile
|
|
3380
3478
|
},
|
|
3381
|
-
owned
|
|
3382
|
-
...handoff ? { devtools: { sha256: contentHash(canonicalActionContent(handoff)) } } : {}
|
|
3479
|
+
owned
|
|
3383
3480
|
};
|
|
3481
|
+
for (const kind of HANDOFF_KINDS) {
|
|
3482
|
+
const action = plan.find((entry) => path23.resolve(entry.target) === handoffPath(scope, kind));
|
|
3483
|
+
if (action) receipt[kind] = { sha256: contentHash(canonicalActionContent(action)) };
|
|
3484
|
+
}
|
|
3485
|
+
return receipt;
|
|
3384
3486
|
}
|
|
3385
3487
|
function receiptContent(receipt) {
|
|
3386
3488
|
return `${JSON.stringify(receipt, null, 2)}
|
|
@@ -3398,25 +3500,24 @@ function parseReceipt2(raw, expected) {
|
|
|
3398
3500
|
const version = Reflect.get(receipt, "schemaVersion");
|
|
3399
3501
|
const receivedScope = Reflect.get(receipt, "scope");
|
|
3400
3502
|
const owned = Reflect.get(receipt, "owned");
|
|
3401
|
-
const
|
|
3402
|
-
|
|
3403
|
-
if (version !== expected.schemaVersion || !hasExactKeys(receipt, ["schemaVersion", "scope", "owned", ...hasDevtools ? ["devtools"] : []]) || receivedScope === null || typeof receivedScope !== "object" || Array.isArray(receivedScope) || !hasExactKeys(receivedScope, ["kind", "home", "codingAgentDir", "receiptFile"])) {
|
|
3503
|
+
const presentKinds = HANDOFF_KINDS.filter((kind) => Object.hasOwn(receipt, kind));
|
|
3504
|
+
if (version !== expected.schemaVersion || !hasExactKeys(receipt, ["schemaVersion", "scope", "owned", ...presentKinds]) || receivedScope === null || typeof receivedScope !== "object" || Array.isArray(receivedScope) || !hasExactKeys(receivedScope, ["kind", "home", "codingAgentDir", "receiptFile"])) {
|
|
3404
3505
|
return null;
|
|
3405
3506
|
}
|
|
3406
3507
|
if (Reflect.get(receivedScope, "kind") !== expected.scope.kind || Reflect.get(receivedScope, "home") !== expected.scope.home || Reflect.get(receivedScope, "codingAgentDir") !== expected.scope.codingAgentDir || Reflect.get(receivedScope, "receiptFile") !== expected.scope.receiptFile || !Array.isArray(owned)) {
|
|
3407
3508
|
return null;
|
|
3408
3509
|
}
|
|
3409
|
-
|
|
3410
|
-
const
|
|
3411
|
-
|
|
3412
|
-
|
|
3510
|
+
const digests = {};
|
|
3511
|
+
for (const kind of presentKinds) {
|
|
3512
|
+
const value = Reflect.get(receipt, kind);
|
|
3513
|
+
if (value === null || typeof value !== "object" || Array.isArray(value) || !hasExactKeys(value, ["sha256"]) || typeof Reflect.get(value, "sha256") !== "string" || !/^[a-f0-9]{64}$/.test(Reflect.get(value, "sha256"))) return null;
|
|
3514
|
+
digests[kind] = { sha256: Reflect.get(value, "sha256") };
|
|
3515
|
+
}
|
|
3516
|
+
const handoffPaths = HANDOFF_KINDS.map((kind) => handoffPath(expected.scope, kind));
|
|
3517
|
+
const expectedOwned = expected.owned.filter((file) => !handoffPaths.includes(file));
|
|
3518
|
+
for (const kind of presentKinds) expectedOwned.push(handoffPath(expected.scope, kind));
|
|
3413
3519
|
if (owned.length !== expectedOwned.length || !owned.every((file, index) => typeof file === "string" && file === expectedOwned[index])) return null;
|
|
3414
|
-
return {
|
|
3415
|
-
schemaVersion: 1,
|
|
3416
|
-
scope: expected.scope,
|
|
3417
|
-
owned: expectedOwned,
|
|
3418
|
-
...hasDevtools ? { devtools: { sha256: Reflect.get(devtools, "sha256") } } : {}
|
|
3419
|
-
};
|
|
3520
|
+
return { schemaVersion: 1, scope: expected.scope, owned: expectedOwned, ...digests };
|
|
3420
3521
|
} catch {
|
|
3421
3522
|
return null;
|
|
3422
3523
|
}
|
|
@@ -3542,34 +3643,39 @@ function readProjectionReceipt(receiptFile, deps) {
|
|
|
3542
3643
|
return { kind: "unreadable" };
|
|
3543
3644
|
}
|
|
3544
3645
|
}
|
|
3545
|
-
function
|
|
3646
|
+
function handoffConflict(kind, file) {
|
|
3647
|
+
const label = kind === "devtools" ? "DevTools" : "Playwright";
|
|
3546
3648
|
return blocked2(
|
|
3547
|
-
|
|
3649
|
+
`projection-${kind}-conflict`,
|
|
3548
3650
|
[file],
|
|
3549
|
-
|
|
3651
|
+
`El handoff de ${label} es ajeno o fue modificado. Cons\xE9rvalo y revisa su propiedad antes de reintentar; Stack no lo sobrescribe ni elimina.`
|
|
3550
3652
|
);
|
|
3551
3653
|
}
|
|
3552
|
-
function
|
|
3553
|
-
const file = devtoolsPath(scope);
|
|
3654
|
+
function inspectHandoffs(input, scope, expected, deps) {
|
|
3554
3655
|
const read = readProjectionReceipt(scope.receiptFile, deps);
|
|
3555
3656
|
if (read.kind === "unreadable") return receiptUnreadable(scope.receiptFile);
|
|
3556
3657
|
const previous = read.kind === "present" ? parseReceipt2(read.content, expected) : null;
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
return devtoolsConflict(file);
|
|
3562
|
-
}
|
|
3563
|
-
let recordedDevtools = false;
|
|
3564
|
-
if (read.kind === "present") {
|
|
3658
|
+
const handoffs = [];
|
|
3659
|
+
for (const kind of HANDOFF_KINDS) {
|
|
3660
|
+
const file = handoffPath(scope, kind);
|
|
3661
|
+
let content;
|
|
3565
3662
|
try {
|
|
3566
|
-
|
|
3663
|
+
content = deps.readText(file);
|
|
3567
3664
|
} catch {
|
|
3665
|
+
return handoffConflict(kind, file);
|
|
3568
3666
|
}
|
|
3569
|
-
|
|
3667
|
+
let recorded = false;
|
|
3668
|
+
if (read.kind === "present") {
|
|
3669
|
+
try {
|
|
3670
|
+
recorded = Object.hasOwn(JSON.parse(read.content), kind);
|
|
3671
|
+
} catch {
|
|
3672
|
+
}
|
|
3673
|
+
if (!previous && (handoffEnabled(input, kind) || content !== null || recorded)) return receiptInvalid(scope.receiptFile);
|
|
3674
|
+
}
|
|
3675
|
+
if (content !== null && (!previous?.[kind] || contentHash(content) !== previous[kind].sha256)) return handoffConflict(kind, file);
|
|
3676
|
+
handoffs.push({ kind, file, content });
|
|
3570
3677
|
}
|
|
3571
|
-
|
|
3572
|
-
return { kind: "checked", previous, content };
|
|
3678
|
+
return { kind: "checked", previous, handoffs };
|
|
3573
3679
|
}
|
|
3574
3680
|
function preparePiProjectionUninstall(input, deps) {
|
|
3575
3681
|
if (input.operation !== "uninstall") {
|
|
@@ -3606,14 +3712,19 @@ function preparePiProjectionUninstall(input, deps) {
|
|
|
3606
3712
|
receiptFile = path23.resolve(scope.receiptFile);
|
|
3607
3713
|
backupTargets = [...backupTargets, ...receipt.owned, receiptFile];
|
|
3608
3714
|
}
|
|
3609
|
-
const
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3715
|
+
const handoffs = [];
|
|
3716
|
+
for (const kind of HANDOFF_KINDS) {
|
|
3717
|
+
const file = handoffPath(scope, kind);
|
|
3718
|
+
let content;
|
|
3719
|
+
try {
|
|
3720
|
+
content = deps.readText(file);
|
|
3721
|
+
} catch {
|
|
3722
|
+
return handoffConflict(kind, file);
|
|
3723
|
+
}
|
|
3724
|
+
const digest = ownedReceipt?.[kind]?.sha256;
|
|
3725
|
+
if (content !== null && (digest === void 0 || contentHash(content) !== digest)) return handoffConflict(kind, file);
|
|
3726
|
+
if (digest !== void 0) handoffs.push({ kind, file, sha256: digest });
|
|
3615
3727
|
}
|
|
3616
|
-
if (handoffContent !== null && (!ownedReceipt?.devtools || contentHash(handoffContent) !== ownedReceipt.devtools.sha256)) return devtoolsConflict(handoff);
|
|
3617
3728
|
const backup = backupExisting(backupTargets, deps);
|
|
3618
3729
|
if (backup.kind === "backup-failed") return backupFailure(backup.paths);
|
|
3619
3730
|
const token = {};
|
|
@@ -3621,7 +3732,7 @@ function preparePiProjectionUninstall(input, deps) {
|
|
|
3621
3732
|
prompt: promptUpdate,
|
|
3622
3733
|
ownedToRemove,
|
|
3623
3734
|
receiptFile,
|
|
3624
|
-
|
|
3735
|
+
handoffs
|
|
3625
3736
|
});
|
|
3626
3737
|
return { kind: "prepared", plan: token };
|
|
3627
3738
|
}
|
|
@@ -3633,14 +3744,14 @@ function completePiProjectionUninstall(plan, deps) {
|
|
|
3633
3744
|
if (prepared === void 0) {
|
|
3634
3745
|
return cleanupFailure([]);
|
|
3635
3746
|
}
|
|
3636
|
-
|
|
3747
|
+
for (const handoff of prepared.handoffs) {
|
|
3637
3748
|
let current;
|
|
3638
3749
|
try {
|
|
3639
|
-
current = deps.readText(
|
|
3750
|
+
current = deps.readText(handoff.file);
|
|
3640
3751
|
} catch {
|
|
3641
|
-
return
|
|
3752
|
+
return handoffConflict(handoff.kind, handoff.file);
|
|
3642
3753
|
}
|
|
3643
|
-
if (current !== null && contentHash(current) !==
|
|
3754
|
+
if (current !== null && contentHash(current) !== handoff.sha256) return handoffConflict(handoff.kind, handoff.file);
|
|
3644
3755
|
}
|
|
3645
3756
|
if (prepared.prompt !== null) {
|
|
3646
3757
|
try {
|
|
@@ -3651,9 +3762,10 @@ function completePiProjectionUninstall(plan, deps) {
|
|
|
3651
3762
|
}
|
|
3652
3763
|
for (const owned of prepared.ownedToRemove) {
|
|
3653
3764
|
try {
|
|
3654
|
-
|
|
3765
|
+
const handoff = prepared.handoffs.find((entry) => entry.file === owned);
|
|
3766
|
+
if (handoff) {
|
|
3655
3767
|
const current = deps.readText(owned);
|
|
3656
|
-
if (current !== null && contentHash(current) !==
|
|
3768
|
+
if (current !== null && contentHash(current) !== handoff.sha256) return handoffConflict(handoff.kind, owned);
|
|
3657
3769
|
}
|
|
3658
3770
|
deps.removeFile(owned);
|
|
3659
3771
|
} catch {
|
|
@@ -3676,22 +3788,27 @@ function runPiProjectionLifecycle(input, deps) {
|
|
|
3676
3788
|
return prepared.kind === "blocked" ? prepared : completePiProjectionUninstall(prepared.plan, deps);
|
|
3677
3789
|
}
|
|
3678
3790
|
if (input.devtoolsMcpEnabled && (!input.pnpmBin || !path23.isAbsolute(input.pnpmBin))) {
|
|
3679
|
-
return blocked2("projection-devtools-command", [
|
|
3791
|
+
return blocked2("projection-devtools-command", [handoffPath(scope, "devtools")], "DevTools requiere un ejecutable pnpm absoluto disponible en PATH.");
|
|
3792
|
+
}
|
|
3793
|
+
if (input.playwrightHandoffEnabled && (!input.playwrightCliCommand || !path23.isAbsolute(input.playwrightCliCommand) || /[\u0000-\u001f\u007f]/.test(input.playwrightCliCommand))) {
|
|
3794
|
+
return blocked2("projection-playwright-command", [handoffPath(scope, "playwright")], "Playwright requiere un ejecutable absoluto verificado. Abre una terminal nueva tras pnpm setup y reintenta install --playwright.");
|
|
3680
3795
|
}
|
|
3681
3796
|
const plan = projectionPlan(input, scope);
|
|
3682
3797
|
assertPlanContained(plan, scope);
|
|
3683
3798
|
const receipt = receiptFor(plan, scope);
|
|
3684
|
-
const
|
|
3685
|
-
if (
|
|
3686
|
-
const
|
|
3799
|
+
const checked = inspectHandoffs(input, scope, receipt, deps);
|
|
3800
|
+
if (checked.kind === "blocked") return input.operation === "doctor" ? { kind: "drift", paths: checked.paths } : checked;
|
|
3801
|
+
const removedHandoffs = checked.handoffs.filter(({ kind, content }) => !handoffEnabled(input, kind) && checked.previous?.[kind] !== void 0 && content !== null);
|
|
3687
3802
|
const expectedReceipt2 = receiptContent(receipt);
|
|
3688
3803
|
const drifted = plan.filter((action) => hasActionDrift(action, deps));
|
|
3689
|
-
|
|
3690
|
-
|
|
3804
|
+
for (const { kind, file } of checked.handoffs) {
|
|
3805
|
+
if (handoffEnabled(input, kind) && checked.previous?.[kind]?.sha256 !== receipt[kind]?.sha256 && !drifted.some((action) => path23.resolve(action.target) === file)) {
|
|
3806
|
+
return input.operation === "doctor" ? { kind: "drift", paths: [file] } : handoffConflict(kind, file);
|
|
3807
|
+
}
|
|
3691
3808
|
}
|
|
3692
3809
|
if (input.operation === "doctor") {
|
|
3693
3810
|
const paths = drifted.map((action) => path23.resolve(action.target));
|
|
3694
|
-
|
|
3811
|
+
paths.push(...removedHandoffs.map(({ file }) => file));
|
|
3695
3812
|
const currentSettings2 = deps.readText(scope.settingsFile);
|
|
3696
3813
|
if (currentSettings2 === null || filterProjectedPiPackage(currentSettings2, input.packageSource) !== currentSettings2) {
|
|
3697
3814
|
paths.push(scope.settingsFile);
|
|
@@ -3707,30 +3824,38 @@ function runPiProjectionLifecycle(input, deps) {
|
|
|
3707
3824
|
}
|
|
3708
3825
|
const packageWillChange = filteredSettings !== null && filteredSettings !== currentSettings;
|
|
3709
3826
|
const receiptChanged = deps.readText(scope.receiptFile) !== expectedReceipt2;
|
|
3710
|
-
if (drifted.length > 0 || packageWillChange || receiptChanged ||
|
|
3827
|
+
if (drifted.length > 0 || packageWillChange || receiptChanged || removedHandoffs.length > 0) {
|
|
3711
3828
|
const backup = backupExisting([
|
|
3712
3829
|
...drifted.map((action) => action.target),
|
|
3713
|
-
...
|
|
3830
|
+
...removedHandoffs.map(({ file }) => file),
|
|
3714
3831
|
...packageWillChange ? [scope.settingsFile] : [],
|
|
3715
3832
|
...receiptChanged ? [scope.receiptFile] : []
|
|
3716
3833
|
], deps);
|
|
3717
3834
|
if (backup.kind === "backup-failed") return backupFailure(backup.paths);
|
|
3718
3835
|
}
|
|
3719
|
-
|
|
3836
|
+
for (const { kind, file } of removedHandoffs) {
|
|
3720
3837
|
try {
|
|
3721
|
-
const current = deps.readText(
|
|
3722
|
-
if (current !== null && contentHash(current) !==
|
|
3723
|
-
deps.removeFile(devtoolsPath(scope));
|
|
3838
|
+
const current = deps.readText(file);
|
|
3839
|
+
if (current !== null && contentHash(current) !== checked.previous?.[kind]?.sha256) return handoffConflict(kind, file);
|
|
3724
3840
|
} catch {
|
|
3725
|
-
return
|
|
3841
|
+
return handoffConflict(kind, file);
|
|
3726
3842
|
}
|
|
3727
3843
|
}
|
|
3728
|
-
|
|
3844
|
+
for (const { kind, file } of removedHandoffs) {
|
|
3845
|
+
try {
|
|
3846
|
+
const current = deps.readText(file);
|
|
3847
|
+
if (current !== null && contentHash(current) !== checked.previous?.[kind]?.sha256) return handoffConflict(kind, file);
|
|
3848
|
+
deps.removeFile(file);
|
|
3849
|
+
} catch {
|
|
3850
|
+
return cleanupFailure([file]);
|
|
3851
|
+
}
|
|
3852
|
+
}
|
|
3853
|
+
const writeResult = applyActions(drifted, deps, checked.handoffs.filter(({ kind }) => handoffEnabled(input, kind)).map(({ kind, file, content }) => ({ kind, file, previous: content })));
|
|
3729
3854
|
if (writeResult) return writeResult;
|
|
3730
3855
|
if (packageWillChange && filteredSettings !== null) deps.writeText(scope.settingsFile, filteredSettings);
|
|
3731
3856
|
if (receiptChanged) deps.writeText(scope.receiptFile, expectedReceipt2);
|
|
3732
3857
|
if (input.operation === "install") return { kind: "installed", receipt };
|
|
3733
|
-
return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged ||
|
|
3858
|
+
return { kind: "synced", changed: drifted.length > 0 || packageWillChange || receiptChanged || removedHandoffs.length > 0 };
|
|
3734
3859
|
}
|
|
3735
3860
|
function systemProjectionLifecycle(input) {
|
|
3736
3861
|
const targetRoot = input.targetDir === void 0 ? null : path23.resolve(input.targetDir);
|
|
@@ -3749,7 +3874,9 @@ function systemProjectionLifecycle(input) {
|
|
|
3749
3874
|
engramBin: input.engramBin ?? "",
|
|
3750
3875
|
playwrightCliEnabled: input.playwrightCliEnabled,
|
|
3751
3876
|
devtoolsMcpEnabled: input.devtoolsMcpEnabled,
|
|
3752
|
-
pnpmBin: input.pnpmBin
|
|
3877
|
+
pnpmBin: input.pnpmBin,
|
|
3878
|
+
playwrightHandoffEnabled: input.playwrightHandoffEnabled,
|
|
3879
|
+
playwrightCliCommand: input.playwrightCliCommand
|
|
3753
3880
|
},
|
|
3754
3881
|
deps: {
|
|
3755
3882
|
readText: readTextOnlyIfMissing,
|
|
@@ -3954,6 +4081,7 @@ function engramVersion(bin) {
|
|
|
3954
4081
|
}
|
|
3955
4082
|
function resolvePlaywrightDoctorState(input) {
|
|
3956
4083
|
if (input.enabled !== true) return { status: "disabled" };
|
|
4084
|
+
if (input.cli.status === "not-in-path") return { status: "not-in-path" };
|
|
3957
4085
|
if (input.cli.status === "absent") return { status: "missing", missing: "package" };
|
|
3958
4086
|
if (input.cli.status === "broken") return { status: "broken" };
|
|
3959
4087
|
if (input.cli.status === "outdated") return { status: "outdated" };
|
|
@@ -4014,7 +4142,10 @@ async function runDoctor() {
|
|
|
4014
4142
|
if (playwright.status === "disabled") {
|
|
4015
4143
|
p3.log.info("Playwright CLI: deshabilitado (opcional). Usa 'install --playwright' para instalarlo de forma expl\xEDcita.");
|
|
4016
4144
|
} else if (playwright.status === "healthy") {
|
|
4017
|
-
p3.log.success("Playwright CLI: paquete y
|
|
4145
|
+
p3.log.success("Playwright CLI: paquete y cach\xE9 de Chromium detectados (doctor no prueba el arranque).");
|
|
4146
|
+
} else if (playwright.status === "not-in-path") {
|
|
4147
|
+
p3.log.warn("Playwright CLI: instalado en el directorio de pnpm, pero fuera del PATH de esta terminal. Abre una terminal nueva tras pnpm setup; no hace falta reinstalarlo.");
|
|
4148
|
+
problems++;
|
|
4018
4149
|
} else if (playwright.status === "missing") {
|
|
4019
4150
|
const target = playwright.missing === "package" ? "el paquete global" : "el navegador de Playwright";
|
|
4020
4151
|
p3.log.warn(`Playwright CLI: habilitado, pero falta ${target} \u2192 ejecuta 'jorgex-stack install --playwright'.`);
|
|
@@ -5340,16 +5471,16 @@ import { createHash as createHash2 } from "crypto";
|
|
|
5340
5471
|
var pi_runtime_pin_default = {
|
|
5341
5472
|
package: {
|
|
5342
5473
|
name: "jorgex-pi",
|
|
5343
|
-
version: "0.8.
|
|
5344
|
-
source: "npm:jorgex-pi@0.8.
|
|
5474
|
+
version: "0.8.15",
|
|
5475
|
+
source: "npm:jorgex-pi@0.8.15"
|
|
5345
5476
|
},
|
|
5346
5477
|
provenance: {
|
|
5347
|
-
commit: "
|
|
5478
|
+
commit: "9d619e205685df0b8b122aa24b81e8694182e3ca"
|
|
5348
5479
|
},
|
|
5349
5480
|
tarball: {
|
|
5350
|
-
bytes:
|
|
5351
|
-
sha256: "
|
|
5352
|
-
sha512: "
|
|
5481
|
+
bytes: 89145074,
|
|
5482
|
+
sha256: "a866da924e0d7ad2b5a5ef285671c06b742d5b64cd62d463a8a39d8e7b5d4453",
|
|
5483
|
+
sha512: "ac5a70bcf336bd28eccb06360a8874c2a3bb2887fe420b6f97a0b61ca1b1c97d2db26fd28033fa296ab273423b1b5884d123dfc56ad1d6e274aefd828d9d25c6"
|
|
5353
5484
|
}
|
|
5354
5485
|
};
|
|
5355
5486
|
|
|
@@ -5372,6 +5503,7 @@ var PI_RUNTIME_CANDIDATE = {
|
|
|
5372
5503
|
"mcp-adapter-v1",
|
|
5373
5504
|
"engram-runtime-tools-v1",
|
|
5374
5505
|
"chrome-devtools-handoff-v1",
|
|
5506
|
+
"playwright-handoff-v1",
|
|
5375
5507
|
"runner-json-v1",
|
|
5376
5508
|
"tui-branding-v1",
|
|
5377
5509
|
"managed-primary-model-v1",
|
|
@@ -5480,9 +5612,12 @@ function normalizeInstalledSource(settingsJson, alias, canonical) {
|
|
|
5480
5612
|
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
5481
5613
|
const packages = Reflect.get(parsed, "packages");
|
|
5482
5614
|
if (!Array.isArray(packages)) return null;
|
|
5483
|
-
const
|
|
5484
|
-
if (packages.filter((entry) => entry === alias).length !== 1 ||
|
|
5485
|
-
Reflect.set(parsed, "packages", packages.map((entry) =>
|
|
5615
|
+
const sourceOf = (entry) => entry !== null && typeof entry === "object" && !Array.isArray(entry) ? Reflect.get(entry, "source") : entry;
|
|
5616
|
+
if (packages.filter((entry) => sourceOf(entry) === alias).length !== 1 || packages.some((entry) => sourceOf(entry) === canonical)) return null;
|
|
5617
|
+
Reflect.set(parsed, "packages", packages.map((entry) => {
|
|
5618
|
+
if (sourceOf(entry) !== alias) return entry;
|
|
5619
|
+
return typeof entry === "string" ? canonical : { ...entry, source: canonical };
|
|
5620
|
+
}));
|
|
5486
5621
|
return JSON.stringify(parsed);
|
|
5487
5622
|
} catch {
|
|
5488
5623
|
return null;
|
|
@@ -5943,14 +6078,18 @@ function managedPackageResult(result) {
|
|
|
5943
6078
|
return result;
|
|
5944
6079
|
}
|
|
5945
6080
|
async function runManagedPiSystem(input) {
|
|
5946
|
-
const { devtoolsMcpEnabled: explicitDevtools, ...runtimeInput } = input;
|
|
6081
|
+
const { devtoolsMcpEnabled: explicitDevtools, playwrightCliEnabled: explicitPlaywright, ...runtimeInput } = input;
|
|
5947
6082
|
const devtoolsMcpEnabled = explicitDevtools ?? (input.targetDir === void 0 && loadDevtoolsMcpPreference(devtoolsMcpPreferenceFile(), "pi"));
|
|
5948
|
-
const
|
|
6083
|
+
const supportsPlaywright = PI_RUNTIME_CANDIDATE.contract.capabilities.includes("playwright-handoff-v1");
|
|
6084
|
+
const playwrightCliEnabled = input.targetDir === void 0 && supportsPlaywright && (explicitPlaywright ?? loadPlaywrightCliPreference(void 0, "pi") === true);
|
|
6085
|
+
const playwrightCliCommand = playwrightCliEnabled && input.operation !== "uninstall" && input.operation !== "models" ? detectPlaywrightCli().binPath : null;
|
|
5949
6086
|
const projectionInput = {
|
|
5950
6087
|
targetDir: input.targetDir,
|
|
5951
6088
|
packageSource: PI_RUNTIME_CANDIDATE.package.source,
|
|
5952
6089
|
engramBin: input.engramBin,
|
|
5953
6090
|
playwrightCliEnabled,
|
|
6091
|
+
playwrightHandoffEnabled: playwrightCliEnabled,
|
|
6092
|
+
playwrightCliCommand,
|
|
5954
6093
|
devtoolsMcpEnabled,
|
|
5955
6094
|
pnpmBin: devtoolsMcpEnabled && input.operation !== "uninstall" ? resolvePnpmBin() : null
|
|
5956
6095
|
};
|
|
@@ -5980,6 +6119,9 @@ async function runManagedPiSystem(input) {
|
|
|
5980
6119
|
if (input.targetDir === void 0 && explicitDevtools !== void 0 && (input.operation === "install" || input.operation === "sync") && result.kind !== "blocked") {
|
|
5981
6120
|
saveDevtoolsMcpPreference(devtoolsMcpPreferenceFile(), "pi", explicitDevtools);
|
|
5982
6121
|
}
|
|
6122
|
+
if (input.targetDir === void 0 && supportsPlaywright && explicitPlaywright !== void 0 && (input.operation === "install" || input.operation === "sync") && result.kind !== "blocked") {
|
|
6123
|
+
savePlaywrightCliPreference(playwrightCliPreferenceFile(), true, { pi: explicitPlaywright });
|
|
6124
|
+
}
|
|
5983
6125
|
return result;
|
|
5984
6126
|
}
|
|
5985
6127
|
|
|
@@ -6472,6 +6614,7 @@ var VERSION = readPackageVersion();
|
|
|
6472
6614
|
var COMMANDS = ["install", "sync", "models", "update", "doctor", "restore", "uninstall", "quality"];
|
|
6473
6615
|
var QUALITY_REJECTED_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
6474
6616
|
"--agents",
|
|
6617
|
+
"--playwright-runtimes",
|
|
6475
6618
|
"-a",
|
|
6476
6619
|
"--target-dir",
|
|
6477
6620
|
"--mode",
|
|
@@ -6557,7 +6700,13 @@ function parseFlags(args, allowReceipt = false) {
|
|
|
6557
6700
|
flags.subagentConcurrency = value ?? "";
|
|
6558
6701
|
i = nextIndex;
|
|
6559
6702
|
} else if (arg.startsWith("--subagent-concurrency=")) flags.subagentConcurrency = arg.slice(23);
|
|
6560
|
-
else if (arg === "--
|
|
6703
|
+
else if (arg === "--playwright-runtimes") {
|
|
6704
|
+
const [value, nextIndex] = readValue(i);
|
|
6705
|
+
flags.playwrightRuntimes = (value ?? "").split(",").filter(Boolean);
|
|
6706
|
+
i = nextIndex;
|
|
6707
|
+
} else if (arg.startsWith("--playwright-runtimes=")) {
|
|
6708
|
+
flags.playwrightRuntimes = arg.slice("--playwright-runtimes=".length).split(",").filter(Boolean);
|
|
6709
|
+
} else if (arg === "--dry-run") flags.dryRun = true;
|
|
6561
6710
|
else if (arg === "--yes" || arg === "-y") flags.yes = true;
|
|
6562
6711
|
else if (arg === "--help" || arg === "-h") flags.help = true;
|
|
6563
6712
|
else if (arg === "--version" || arg === "-v") flags.version = true;
|
|
@@ -6635,8 +6784,25 @@ Corrige o borra ${preferenceFile}, o vuelve a ejecutar con --mode human|programm
|
|
|
6635
6784
|
subagentConcurrency: concurrency
|
|
6636
6785
|
};
|
|
6637
6786
|
}
|
|
6638
|
-
async function resolvePlaywrightToolConsent(command, flags) {
|
|
6787
|
+
async function resolvePlaywrightToolConsent(command, flags, runtimes) {
|
|
6639
6788
|
const interactive = Boolean(process.stdout.isTTY);
|
|
6789
|
+
const supported = runtimes.filter((runtime) => runtime !== "pi" || PI_RUNTIME_CANDIDATE.contract.capabilities.includes("playwright-handoff-v1"));
|
|
6790
|
+
if (flags.playwrightRuntimes !== void 0) {
|
|
6791
|
+
const requested = flags.playwrightRuntimes;
|
|
6792
|
+
let error;
|
|
6793
|
+
if (requested.length === 0) error = "--playwright-runtimes requiere al menos un runtime.";
|
|
6794
|
+
for (const runtime of requested) {
|
|
6795
|
+
if (!["opencode", "claude-code", "codex", "pi"].includes(runtime)) error = `Runtime Playwright desconocido: ${runtime}.`;
|
|
6796
|
+
else if (!runtimes.includes(runtime)) error = `El runtime ${runtime} no est\xE1 en --agents/destinos de esta instalaci\xF3n.`;
|
|
6797
|
+
else if (!supported.includes(runtime)) error = "Pi requiere un candidato con playwright-handoff-v1 para activar Playwright.";
|
|
6798
|
+
if (error) break;
|
|
6799
|
+
}
|
|
6800
|
+
if (error) {
|
|
6801
|
+
console.error(error);
|
|
6802
|
+
process.exitCode = 1;
|
|
6803
|
+
return null;
|
|
6804
|
+
}
|
|
6805
|
+
}
|
|
6640
6806
|
let confirmed = false;
|
|
6641
6807
|
if (command === "install" && interactive && !flags.yes && !flags.dryRun && flags.targetDir === void 0) {
|
|
6642
6808
|
const answer = await p6.confirm({
|
|
@@ -6646,13 +6812,33 @@ async function resolvePlaywrightToolConsent(command, flags) {
|
|
|
6646
6812
|
if (p6.isCancel(answer)) return null;
|
|
6647
6813
|
confirmed = answer === true;
|
|
6648
6814
|
}
|
|
6815
|
+
let runtimeSelection;
|
|
6816
|
+
const approved = interactive && !flags.yes ? confirmed : flags.yes && flags.playwright;
|
|
6817
|
+
if (command === "install" && approved && supported.length > 0) {
|
|
6818
|
+
let selected = flags.playwrightRuntimes ?? supported;
|
|
6819
|
+
if (interactive && !flags.yes && !flags.dryRun && flags.targetDir === void 0 && flags.playwrightRuntimes === void 0) {
|
|
6820
|
+
const answer = await p6.multiselect({
|
|
6821
|
+
message: "\xBFEn qu\xE9 runtimes activar la gu\xEDa de Playwright? (instalaci\xF3n global compartida)",
|
|
6822
|
+
required: false,
|
|
6823
|
+
options: supported.map((runtime) => ({ value: runtime, label: runtime === "pi" ? "Pi" : ADAPTERS[runtime]?.name ?? runtime })),
|
|
6824
|
+
initialValues: supported.filter((runtime) => loadPlaywrightCliPreference(void 0, runtime) === true)
|
|
6825
|
+
});
|
|
6826
|
+
if (p6.isCancel(answer)) return null;
|
|
6827
|
+
selected = answer;
|
|
6828
|
+
}
|
|
6829
|
+
runtimeSelection = Object.fromEntries(supported.map((runtime) => [runtime, selected.includes(runtime)]));
|
|
6830
|
+
}
|
|
6831
|
+
if (command === "install" && approved && runtimes.includes("pi") && !supported.includes("pi")) {
|
|
6832
|
+
p6.log.info("La activaci\xF3n de Playwright en Pi requiere la pr\xF3xima adopci\xF3n del paquete Pi; el binario global s\xED puede instalarse.");
|
|
6833
|
+
}
|
|
6649
6834
|
return {
|
|
6650
6835
|
command,
|
|
6651
6836
|
interactive,
|
|
6652
6837
|
yes: flags.yes,
|
|
6653
6838
|
targetDir: flags.targetDir !== void 0,
|
|
6654
6839
|
explicitToolSelection: flags.playwright,
|
|
6655
|
-
confirmed
|
|
6840
|
+
confirmed,
|
|
6841
|
+
...runtimeSelection === void 0 ? {} : { runtimeSelection }
|
|
6656
6842
|
};
|
|
6657
6843
|
}
|
|
6658
6844
|
async function resolveDevtoolsMcpSelection(command, flags, runtimes) {
|
|
@@ -6743,7 +6929,7 @@ async function resolveHostEngramForInstall(flags) {
|
|
|
6743
6929
|
return { ok: false, message: `Engram: ${error instanceof Error ? error.message : String(error)}` };
|
|
6744
6930
|
}
|
|
6745
6931
|
}
|
|
6746
|
-
async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin, devtoolsMcpEnabled) {
|
|
6932
|
+
async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBin, devtoolsMcpEnabled, playwrightCliEnabled) {
|
|
6747
6933
|
if (targetDir === void 0 && operation !== "models") {
|
|
6748
6934
|
const preferenceErrors = browserPreferenceErrors();
|
|
6749
6935
|
if (preferenceErrors.length > 0) {
|
|
@@ -6786,7 +6972,8 @@ async function runSelectedPi(operation, targetDir, yes = false, resolvedEngramBi
|
|
|
6786
6972
|
targetDir,
|
|
6787
6973
|
detected: { executable: detected.executable, version: detected.version },
|
|
6788
6974
|
engramBin,
|
|
6789
|
-
...devtoolsMcpEnabled === void 0 ? {} : { devtoolsMcpEnabled }
|
|
6975
|
+
...devtoolsMcpEnabled === void 0 ? {} : { devtoolsMcpEnabled },
|
|
6976
|
+
...playwrightCliEnabled === void 0 ? {} : { playwrightCliEnabled }
|
|
6790
6977
|
});
|
|
6791
6978
|
if (result.kind === "blocked") {
|
|
6792
6979
|
const paths = "paths" in result ? `: ${result.paths.join(", ")}` : "";
|
|
@@ -6827,6 +7014,7 @@ Opciones:
|
|
|
6827
7014
|
--no-devtools (install/sync) desactiva Chrome DevTools MCP (incompatible con --devtools)
|
|
6828
7015
|
--remove-engram (uninstall) desregistra Engram de los runtimes;
|
|
6829
7016
|
memorias y binario quedan intactos igualmente
|
|
7017
|
+
--playwright-runtimes <csv> Activa su gu\xEDa s\xF3lo en estos runtimes de --agents (con --playwright)
|
|
6830
7018
|
--remove-playwright (uninstall) retira solo el paquete global de Playwright;
|
|
6831
7019
|
nunca perfiles, cach\xE9 ni navegadores
|
|
6832
7020
|
--receipt <path> (quality) escribe el receipt en ese path de forma at\xF3mica
|
|
@@ -6861,6 +7049,11 @@ Flags disponibles: jorgex-stack --help`
|
|
|
6861
7049
|
process.exitCode = 1;
|
|
6862
7050
|
return;
|
|
6863
7051
|
}
|
|
7052
|
+
if (flags.playwrightRuntimes !== void 0 && (command !== "install" || !flags.playwright)) {
|
|
7053
|
+
console.error("--playwright-runtimes requiere install --playwright.");
|
|
7054
|
+
process.exitCode = 1;
|
|
7055
|
+
return;
|
|
7056
|
+
}
|
|
6864
7057
|
if (command !== "quality" && flags.targetDir !== void 0 && flags.agents.length !== 1) {
|
|
6865
7058
|
console.error("--target-dir requiere exactamente un runtime en --agents.");
|
|
6866
7059
|
process.exitCode = 1;
|
|
@@ -6927,8 +7120,11 @@ Flags disponibles: jorgex-stack --help`
|
|
|
6927
7120
|
exitCode = process.exitCode === 1 ? 1 : 0;
|
|
6928
7121
|
return;
|
|
6929
7122
|
}
|
|
6930
|
-
const playwrightToolConsent = await resolvePlaywrightToolConsent(command, flags);
|
|
6931
|
-
if (playwrightToolConsent === null)
|
|
7123
|
+
const playwrightToolConsent = await resolvePlaywrightToolConsent(command, flags, runtimes);
|
|
7124
|
+
if (playwrightToolConsent === null) {
|
|
7125
|
+
exitCode = process.exitCode === 1 ? 1 : 0;
|
|
7126
|
+
return;
|
|
7127
|
+
}
|
|
6932
7128
|
if (!await ensureOpenCodeModelsForInstall(command, flags, fileRuntimes)) {
|
|
6933
7129
|
exitCode = 1;
|
|
6934
7130
|
return;
|
|
@@ -6980,7 +7176,8 @@ Flags disponibles: jorgex-stack --help`
|
|
|
6980
7176
|
flags.targetDir,
|
|
6981
7177
|
flags.yes,
|
|
6982
7178
|
flags.targetDir === void 0 ? engramBin : void 0,
|
|
6983
|
-
devtoolsMcpSelection.pi
|
|
7179
|
+
devtoolsMcpSelection.pi,
|
|
7180
|
+
flags.targetDir === void 0 && exitCode === 0 && resolvePlaywrightToolPlan(playwrightToolConsent).actions.length > 0 ? playwrightToolConsent.runtimeSelection?.pi : void 0
|
|
6984
7181
|
));
|
|
6985
7182
|
}
|
|
6986
7183
|
completed = true;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jorgex-stack",
|
|
3
|
-
"version": "1.9.
|
|
3
|
+
"version": "1.9.33",
|
|
4
4
|
"description": "Harness multi-agente portable: instala la config JorgeX (agentes, skills, hooks, Engram, MCPs) en Claude Code, Codex CLI, OpenCode y Pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|