gentle-pi 4.2.0 → 4.4.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 +340 -138
- package/assets/agents/gentle-ai-explore.md +1 -0
- package/assets/agents/gentle-ai-verify.md +6 -4
- package/assets/agents/gentle-ai-worker.md +7 -1
- package/assets/design-canvas/canvas.css +648 -0
- package/assets/design-canvas/canvas.js +1497 -0
- package/assets/design-canvas/index.html +88 -0
- package/assets/design-canvas/inspector.js +290 -0
- package/assets/orchestrator-delegation-reference.md +163 -0
- package/assets/orchestrator-delegation.md +25 -176
- package/assets/orchestrator-tracking.md +1 -1
- package/assets/orchestrator-verification.md +14 -8
- package/assets/orchestrator-writer.md +5 -1
- package/assets/orchestrator.md +11 -11
- package/bin/engram-guard +1 -1
- package/bin/gentle-shell.mjs +237 -139
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +8 -7
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/README.md +7 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/manifest.json +11 -2
- package/contracts/review-provider-contract-mirror/v1.4.0/bundle/routing/pi.md +92 -0
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-capabilities.baseline.json +8 -1
- package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/assets/diagrams/agent-orchestration.svg +54 -15
- package/docs/assets/diagrams/gentleman-workflow.svg +69 -11
- package/docs/assets/diagrams/measured-routing.svg +49 -0
- package/docs/assets/diagrams/native-review.svg +52 -12
- package/docs/assets/diagrams/odd-workflow.svg +128 -66
- package/docs/assets/diagrams/permission-modes.svg +51 -0
- package/docs/assets/features/audio-notifications.png +0 -0
- package/docs/assets/features/command-palette.png +0 -0
- package/docs/bundled-install.md +65 -30
- package/docs/delegated-verification.md +16 -8
- package/docs/gentle-agents-activity.md +16 -6
- package/docs/gentle-music.md +61 -0
- package/docs/gentle-shell-uninstall.md +8 -1
- package/docs/gentle-shell.md +80 -9
- package/docs/install-wizard.md +37 -6
- package/docs/permission-modes.md +8 -3
- package/docs/readme-reference.md +120 -28
- package/docs/session-profile-format.md +26 -0
- package/docs/voice.md +259 -0
- package/docs/yolo-mode.md +38 -10
- package/extensions/ask-user-choice.ts +4 -1
- package/extensions/codegraph-tools.ts +68 -6
- package/extensions/gentle-agents.ts +270 -48
- package/extensions/gentle-ai.ts +490 -113
- package/extensions/gentle-design.ts +158 -0
- package/extensions/gentle-herdr-activity.ts +2 -3
- package/extensions/gentle-music.ts +289 -0
- package/extensions/gentle-shell.ts +359 -85
- package/extensions/gentle-todo.ts +43 -9
- package/extensions/gentle-voice.ts +11 -0
- package/extensions/quiet-tools.ts +1 -1
- package/extensions/session-scratch.ts +384 -0
- package/extensions/skill-registry.ts +63 -8
- package/extensions/startup-banner.ts +2 -3
- package/lib/agent-home.ts +8 -3
- package/lib/agent-profiles.ts +26 -0
- package/lib/agents-config.ts +35 -0
- package/lib/agents-protocol.ts +60 -1
- package/lib/agents-runner.ts +107 -9
- package/lib/agents-session-transport.ts +101 -6
- package/lib/agents-view.ts +3 -1
- package/lib/agents-widget.ts +14 -1
- package/lib/auto-permission-memory.ts +48 -18
- package/lib/auto-permission-runtime.ts +21 -6
- package/lib/bounded-writer-admission.ts +51 -0
- package/lib/clipboard-image.ts +147 -0
- package/lib/command-palette-catalog.ts +2 -4
- package/lib/commit-slice-signal.ts +100 -0
- package/lib/context-backstop.ts +120 -0
- package/lib/delivered-test-lint.ts +80 -0
- package/lib/design-canvas-server.ts +947 -0
- package/lib/design-comments.ts +437 -0
- package/lib/design-handoffs.ts +313 -0
- package/lib/design-open-browser.ts +58 -0
- package/lib/design-sources.ts +120 -0
- package/lib/destructive-command-guard.ts +120 -9
- package/lib/external-editor.ts +31 -0
- package/lib/gauge-theme.ts +146 -0
- package/lib/gentle-ai-binary.ts +28 -7
- package/lib/gentle-design.ts +513 -0
- package/lib/gentle-shell-bundled.ts +146 -36
- package/lib/gentle-shell-import-pi.ts +233 -0
- package/lib/gentle-shell-launcher.ts +154 -15
- package/lib/gentle-shell-startup-update.ts +61 -9
- package/lib/gentle-shell-uninstall.ts +51 -2
- package/lib/herdr-activity.ts +24 -0
- package/lib/image-attachments.ts +150 -0
- package/lib/inprocess-reviewer.ts +33 -2
- package/lib/nan-provider.ts +3 -0
- package/lib/native-review-cli.ts +20 -5
- package/lib/notification-audio-native.ts +149 -13
- package/lib/notification-audio.ts +4 -0
- package/lib/notification-pulse-worker.ts +82 -18
- package/lib/notification-service.ts +5 -1
- package/lib/orchestrator-discovery.ts +5 -1
- package/lib/orchestrator-presence.ts +87 -8
- package/lib/orchestrator-scope.ts +31 -3
- package/lib/orchestrator-state.ts +11 -3
- package/lib/permission-mode-policy.ts +2 -2
- package/lib/provider-contract-bundle.ts +99 -53
- package/lib/review-candidate-view-owner.ts +29 -19
- package/lib/review-candidate-view.ts +36 -2
- package/lib/review-host-relay.ts +11 -2
- package/lib/review-reminder-receipt.ts +24 -1
- package/lib/review-risk-assessment.ts +43 -38
- package/lib/review-session-standing-permission.ts +3 -1
- package/lib/session-profile-append-controller.ts +713 -0
- package/lib/session-profile-disk-reader.ts +182 -0
- package/lib/session-profile-freeze.ts +30 -1
- package/lib/session-profile-persistence.ts +15 -7
- package/lib/session-scratch.ts +523 -0
- package/lib/session-worktree-registry.ts +7 -0
- package/lib/shell-bar.ts +29 -28
- package/lib/shell-changes-view.ts +80 -13
- package/lib/shell-changes.ts +81 -5
- package/lib/shell-gauge.ts +49 -6
- package/lib/shell-prompt.ts +4 -4
- package/lib/shell-sidebar-layout.ts +26 -6
- package/lib/shell-todo.ts +24 -5
- package/lib/shell-usage-view.ts +5 -1
- package/lib/shell-usage.ts +7 -7
- package/lib/verifier-admission.ts +30 -0
- package/lib/vim-editor-adapter.ts +11 -8
- package/lib/voice-artifacts.ts +188 -0
- package/lib/voice-command.ts +576 -0
- package/lib/voice-install.ts +455 -0
- package/lib/voice-platform.ts +99 -0
- package/lib/voice-push-to-talk.ts +262 -0
- package/lib/voice-session.ts +460 -0
- package/lib/voice-settings.ts +50 -0
- package/lib/voice-ui.ts +158 -0
- package/lib/yolo-session-policy.ts +58 -22
- package/lib/yolo-temp-scratch.ts +69 -0
- package/package.json +1 -2
- package/runtime/gentle-ai-binary.mjs +28 -7
- package/runtime/gentle-shell-bundled.mjs +145 -35
- package/runtime/gentle-shell-import-pi.mjs +234 -0
- package/runtime/gentle-shell-launcher.mjs +152 -13
- package/runtime/gentle-shell-startup-update.mjs +61 -9
- package/runtime/gentle-shell-uninstall.mjs +51 -2
- package/runtime/native-review-cli.mjs +19 -4
- package/runtime/review-risk-assessment.mjs +43 -38
- package/scripts/build-runtime-modules.mjs +1 -0
- package/scripts/bundled-engram-migration.mjs +119 -0
- package/scripts/gentle-ai-installer.mjs +10 -10
- package/scripts/installer-downloads.mjs +7 -7
- package/scripts/installer-preflight.mjs +5 -3
- package/scripts/installer-server.mjs +14 -4
- package/scripts/installer-windows.mjs +37 -7
- package/scripts/main-channel.mjs +94 -19
- package/scripts/pi-update.mjs +34 -6
- package/scripts/run-test-suite.mjs +5 -0
- package/scripts/verify-package-files.mjs +43 -13
- package/scripts/voice-worker.cjs +476 -0
- package/skills/design/SKILL.md +110 -0
- package/skills/gentle-ai/SKILL.md +3 -3
- package/skills/work-unit-commits/SKILL.md +5 -3
- package/tests/agent-profiles.test.ts +24 -0
- package/tests/agent-risk-escalation.test.ts +37 -10
- package/tests/agents-config.test.ts +61 -0
- package/tests/agents-fake-child.ts +8 -1
- package/tests/agents-protocol.test.ts +32 -0
- package/tests/agents-runner.test.ts +485 -7
- package/tests/agents-session-transport.test.ts +53 -3
- package/tests/ask-user-choice.test.ts +30 -0
- package/tests/auto-mode.test.ts +17 -0
- package/tests/auto-permission-memory-diagnostics.test.ts +56 -0
- package/tests/auto-permission-runtime.test.ts +85 -1
- package/tests/autonomous-guard.test.ts +142 -0
- package/tests/bounded-writer-admission.test.ts +11 -3
- package/tests/bundled-install.test.ts +3 -3
- package/tests/card-content-policy.test.ts +19 -9
- package/tests/child-safety.test.ts +39 -0
- package/tests/clipboard-image.test.ts +114 -0
- package/tests/codegraph-tools.test.ts +123 -2
- package/tests/command-palette.test.ts +15 -5
- package/tests/commit-slice-signal.test.ts +162 -0
- package/tests/context-backstop.test.ts +225 -0
- package/tests/delegate-for-reason-contract.test.ts +92 -24
- package/tests/delivered-test-lint.test.ts +105 -0
- package/tests/design-canvas-assets.test.ts +710 -0
- package/tests/design-canvas-server.test.ts +1096 -0
- package/tests/design-comments.test.ts +201 -0
- package/tests/design-handoffs.test.ts +282 -0
- package/tests/design-open-browser.test.ts +70 -0
- package/tests/design-sources.test.ts +132 -0
- package/tests/destructive-command-guard.test.ts +85 -1
- package/tests/engram-safety.test.ts +132 -57
- package/tests/external-editor.test.ts +84 -0
- package/tests/fixtures/primary-session-prompt/gentleman.golden.md +143 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/README.md +29 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/manifest.json +83 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/orchestration/pi.md +70 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/routing/pi.md +92 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/lens.schema.json +16 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/refuter.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/schemas/targeted-validator.schema.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/lens.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/refuter.json +1 -0
- package/tests/fixtures/provider-contract-bundle/v1.4.0/vectors/targeted-validator.json +1 -0
- package/tests/gauge-theme.test.ts +120 -0
- package/tests/gentle-agents.test.ts +645 -21
- package/tests/gentle-ai-binary.test.ts +1 -1
- package/tests/gentle-ai-child-guards.test.ts +61 -0
- package/tests/gentle-ai-doctor-herdr.test.ts +94 -0
- package/tests/gentle-ai-installer.test.ts +51 -51
- package/tests/gentle-ai.test.ts +292 -13
- package/tests/gentle-design.test.ts +1313 -0
- package/tests/gentle-music.test.ts +335 -0
- package/tests/gentle-shell-bin.test.ts +163 -0
- package/tests/gentle-shell-bundled.test.ts +392 -176
- package/tests/gentle-shell-import-pi.test.ts +262 -0
- package/tests/gentle-shell-launcher.test.ts +107 -2
- package/tests/gentle-shell-startup-update.test.ts +77 -1
- package/tests/gentle-shell-uninstall.test.ts +89 -0
- package/tests/gentle-shell.test.ts +225 -17
- package/tests/gentle-todo.test.ts +190 -0
- package/tests/headless-delivery-question.test.ts +58 -0
- package/tests/herdr-activity.test.ts +34 -1
- package/tests/image-attachments.test.ts +119 -0
- package/tests/image-paste-chips.test.ts +236 -0
- package/tests/inprocess-reviewer.test.ts +55 -2
- package/tests/installer-bundles.test.ts +12 -6
- package/tests/installer-posix-bootstrap.test.ts +8 -8
- package/tests/installer-preflight.test.ts +16 -0
- package/tests/installer-server.test.ts +10 -2
- package/tests/installer-windows-bootstrap.test.ts +94 -7
- package/tests/installer-windows-diagnostics.test.ts +111 -0
- package/tests/main-channel-upgrade.test.ts +232 -2
- package/tests/main-channel.test.ts +140 -21
- package/tests/nan-provider.test.ts +36 -0
- package/tests/native-review-capability-contract.test.ts +17 -4
- package/tests/native-review-parity.test.ts +23 -2
- package/tests/no-self-pause-contract.test.ts +5 -2
- package/tests/notification-audio-native.test.ts +128 -9
- package/tests/notification-customize.test.ts +17 -5
- package/tests/odd-integration.test.ts +5 -1
- package/tests/odd-routing-canonical-ratchet.test.ts +128 -162
- package/tests/odd-routing-contract.test.ts +115 -30
- package/tests/orchestrator-budget.test.ts +22 -8
- package/tests/orchestrator-discovery.test.ts +29 -0
- package/tests/orchestrator-presence.test.ts +100 -2
- package/tests/orchestrator-rdd-ownership.test.ts +3 -1
- package/tests/orchestrator-scope.test.ts +80 -0
- package/tests/orchestrator-state.test.ts +29 -0
- package/tests/orchestrator-status-publication-contract.test.ts +12 -8
- package/tests/package-files.test.ts +34 -0
- package/tests/package-manifest.test.ts +24 -20
- package/tests/permission-mode-policy.test.ts +10 -1
- package/tests/pi-update-launcher.test.ts +21 -2
- package/tests/pi-update.test.ts +99 -4
- package/tests/primary-session-prompt.test.ts +101 -0
- package/tests/provider-contract-bundle.test.ts +118 -0
- package/tests/provider-contract-mirror.test.ts +24 -0
- package/tests/quiet-tool-rendering.test.ts +23 -0
- package/tests/rdd-aware-verification-contract.test.ts +29 -11
- package/tests/rdd-status-line.test.ts +79 -0
- package/tests/review-agent-end-preflight.test.ts +6 -0
- package/tests/review-candidate-view.test.ts +49 -0
- package/tests/review-contract-prompt.test.ts +5 -2
- package/tests/review-controller-native-routing.test.ts +14 -1
- package/tests/review-controller.test.ts +1 -1
- package/tests/review-host-relay-routing.test.ts +147 -3
- package/tests/review-host-relay.test.ts +9 -2
- package/tests/review-json-arguments.test.ts +20 -3
- package/tests/review-loadout-rdd-off-sdk.test.ts +258 -0
- package/tests/review-owner-identity.test.ts +132 -0
- package/tests/review-relay-transport-agent.test.ts +26 -0
- package/tests/review-reminder-receipt.test.ts +31 -1
- package/tests/review-risk-assessment.test.ts +163 -76
- package/tests/review-session-standing-permission-controller.test.ts +14 -1
- package/tests/review-session-standing-permission.test.ts +27 -1
- package/tests/run-test-suite.test.ts +15 -0
- package/tests/runtime-harness.mjs +16 -6
- package/tests/session-changes-shell.test.ts +1 -1
- package/tests/session-changes.test.ts +2 -1
- package/tests/session-profile-append-controller-fork.test.ts +458 -0
- package/tests/session-profile-append-controller-pi.test.ts +290 -0
- package/tests/session-profile-append-controller.test.ts +731 -0
- package/tests/session-profile-disk-reader-pi.test.ts +183 -0
- package/tests/session-profile-disk-reader.test.ts +311 -0
- package/tests/session-profile-freeze.test.ts +23 -0
- package/tests/session-scratch.test.ts +1011 -0
- package/tests/session-subject-routing-contract.test.ts +2 -1
- package/tests/session-worktree-registry.test.ts +13 -1
- package/tests/shell-bar.test.ts +21 -2
- package/tests/shell-changes-view.test.ts +137 -1
- package/tests/shell-changes.test.ts +148 -1
- package/tests/shell-gauge.test.ts +42 -0
- package/tests/shell-prompt.test.ts +3 -3
- package/tests/shell-render-cache.test.ts +134 -1
- package/tests/shell-sidebar-layout.test.ts +50 -0
- package/tests/short-check-routing-contract.test.ts +1 -1
- package/tests/skill-collision-prefixes.test.ts +3 -1
- package/tests/skill-registry-watcher.test.ts +17 -5
- package/tests/skill-registry.test.ts +47 -3
- package/tests/small-task-inline-contract.test.ts +37 -0
- package/tests/standalone-config-home.test.ts +324 -0
- package/tests/startup-banner.test.ts +5 -4
- package/tests/startup-home-repo.test.ts +34 -0
- package/tests/support/orchestrator-modules.ts +1 -0
- package/tests/task-size-routing-contract.test.ts +70 -10
- package/tests/test-scratch-contract.test.ts +102 -0
- package/tests/test-strength-contract.test.ts +36 -1
- package/tests/tool-batching-contract.test.ts +64 -0
- package/tests/verification-proportionality.test.ts +46 -0
- package/tests/verifier-admission.test.ts +154 -0
- package/tests/verify-agent-contract.test.ts +8 -8
- package/tests/voice-command.test.ts +773 -0
- package/tests/voice-install.test.ts +323 -0
- package/tests/voice-platform.test.ts +176 -0
- package/tests/voice-push-to-talk.test.ts +444 -0
- package/tests/voice-session.test.ts +434 -0
- package/tests/voice-settings.test.ts +49 -0
- package/tests/voice-ui.test.ts +97 -0
- package/tests/voice-worker.test.ts +445 -0
- package/tests/weak-model-economy-prompt.test.ts +32 -0
- package/tests/windows-hidden-processes.test.ts +30 -2
- package/tests/writer-edit-surface-scope.test.ts +3 -0
- package/tests/writer-feature-document-admission.test.ts +156 -0
- package/tests/yolo-customize.test.ts +5 -1
- package/tests/yolo-mode.test.ts +19 -1
- package/tests/yolo-session-policy-capture.test.ts +104 -0
- package/tests/yolo-temp-scratch.test.ts +71 -0
- package/scripts/mirror-odd-routing.mjs +0 -242
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/orchestration/pi.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.3.0 → v1.4.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// Generated by scripts/build-runtime-modules.mjs. Do not edit.
|
|
2
|
-
import { lstatSync, readdirSync, readFileSync, readlinkSync, realpathSync, rmdirSync, rmSync } from "node:fs";
|
|
2
|
+
import { lstatSync, readdirSync, readFileSync, readlinkSync, realpathSync, renameSync, rmdirSync, rmSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
3
4
|
import { createInterface } from "node:readline";
|
|
4
5
|
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
6
|
+
import { withoutBundledNpmCommand } from "./gentle-shell-bundled.mjs";
|
|
5
7
|
import { isolatedDir, launcherConfigPath, parseRawLauncherConfig, provisionedEntry, userPiHome } from "./gentle-shell-launcher.mjs";
|
|
6
8
|
|
|
7
9
|
// `gentle-shell self-uninstall`: removes what Gentle Shell itself created, then
|
|
@@ -264,6 +266,14 @@ async function planSelfUninstall(input , configHome ,
|
|
|
264
266
|
{ path: gentleAiState, label: "Gentle AI's state", inside: true },
|
|
265
267
|
];
|
|
266
268
|
const defaultConfigHome = canonical(join(homedir, ".pi", "gentle-ai"));
|
|
269
|
+
// #2201: the standalone launcher keeps the isolated home's config in a
|
|
270
|
+
// gentle-ai folder next to it (standaloneConfigHome). Next to the default
|
|
271
|
+
// home it lies inside ~/.gentle-shell and is Gentle Shell's by location;
|
|
272
|
+
// next to a GENTLE_SHELL_HOME it may be anyone's, so it is only listed.
|
|
273
|
+
// When GENTLE_PI_CONFIG_HOME names it, the config home's rules apply instead.
|
|
274
|
+
const defaultHome = resolve(isolated) === resolve(join(homedir, ".gentle-shell", "agent"));
|
|
275
|
+
const ownConfig = join(dirname(isolated), "gentle-ai");
|
|
276
|
+
const ownConfigSeparate = comparable(canonical(ownConfig), platform) !== comparable(canonical(configHome), platform);
|
|
267
277
|
const candidates = [
|
|
268
278
|
{
|
|
269
279
|
who: env.GENTLE_SHELL_HOME ? `GENTLE_SHELL_HOME points at ${isolated}` : `The isolated home ${isolated}`,
|
|
@@ -279,6 +289,12 @@ async function planSelfUninstall(input , configHome ,
|
|
|
279
289
|
allowedInsidePi: defaultConfigHome,
|
|
280
290
|
},
|
|
281
291
|
{ who: `The launcher folder ${shellDirectory}`, path: shellDirectory, guards, allowedInsidePi: undefined },
|
|
292
|
+
...(defaultHome && ownConfigSeparate ? [{
|
|
293
|
+
who: `Gentle Shell's config folder ${ownConfig}`,
|
|
294
|
+
path: ownConfig,
|
|
295
|
+
guards: [...guards, { path: configHome, label: "the Gentle AI config home", inside: true }],
|
|
296
|
+
allowedInsidePi: undefined,
|
|
297
|
+
}] : []),
|
|
282
298
|
];
|
|
283
299
|
for (const candidate of candidates) {
|
|
284
300
|
const real = canonical(candidate.path);
|
|
@@ -313,11 +329,14 @@ async function planSelfUninstall(input , configHome ,
|
|
|
313
329
|
|
|
314
330
|
// The default isolated home is Gentle Shell's by location; a GENTLE_SHELL_HOME
|
|
315
331
|
// directory only with the launcher's ownership marker or setup record.
|
|
316
|
-
const defaultHome = resolve(isolated) === resolve(join(homedir, ".gentle-shell", "agent"));
|
|
317
332
|
const owned = defaultHome || inspect(join(isolated, HOME_OWNERSHIP_MARKER), "file") === "ok"
|
|
318
333
|
|| provisionedEntry(launcherConfig, isolatedReal) !== undefined;
|
|
319
334
|
if (owned || inspect(isolated, "dir") !== "ok") consider({ path: isolated, label: "isolated home: its sign-ins, chats and settings", kind: "dir" });
|
|
320
335
|
else plan.kept.push(`${isolated}: not created by Gentle Shell (no ${HOME_OWNERSHIP_MARKER} marker or setup record)`);
|
|
336
|
+
if (defaultHome && ownConfigSeparate) consider({ path: ownConfig, label: "Gentle Shell's own configuration: profiles, banner and other preferences", kind: "dir" });
|
|
337
|
+
else if (ownConfigSeparate && inspect(ownConfig, "dir") !== "missing") {
|
|
338
|
+
plan.kept.push(`${ownConfig}: next to GENTLE_SHELL_HOME, so it may not be Gentle Shell's; remove it yourself if it holds only Gentle Shell settings`);
|
|
339
|
+
}
|
|
321
340
|
consider({ path: configPath, label: "launcher settings", kind: "file" });
|
|
322
341
|
|
|
323
342
|
for (const name of SHARED_CONFIG_FILES) {
|
|
@@ -762,6 +781,18 @@ function printBundledPlan(plan , input , o
|
|
|
762
781
|
}
|
|
763
782
|
}
|
|
764
783
|
|
|
784
|
+
/** Replaces `path`'s text through a same-directory temporary file, so a crash never leaves it half-written. */
|
|
785
|
+
function writeTextAtomically(path , text ) {
|
|
786
|
+
const temporary = join(dirname(path), `.${basename(path)}.${randomUUID()}.tmp`);
|
|
787
|
+
try {
|
|
788
|
+
writeFileSync(temporary, text);
|
|
789
|
+
renameSync(temporary, path);
|
|
790
|
+
} catch (error) {
|
|
791
|
+
try { unlinkSync(temporary); } catch { /* The temporary file is best effort. */ }
|
|
792
|
+
throw error;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
|
|
765
796
|
/**
|
|
766
797
|
* Runs `gentle-shell self-uninstall` for the bundled install at `root`. Exit
|
|
767
798
|
* codes as runSelfUninstall: 0 done or dry run, 1 refused, cancelled or failed,
|
|
@@ -821,6 +852,24 @@ export async function runBundledSelfUninstall(input )
|
|
|
821
852
|
} catch {
|
|
822
853
|
// A leftover empty folder is harmless.
|
|
823
854
|
}
|
|
855
|
+
// A home that stays must not keep Pi pointed at the npm this uninstall removed
|
|
856
|
+
// (#2469): an `npmCommand` naming the prefix fails every later `pi install`
|
|
857
|
+
// with ENOENT naming the deleted node.exe.
|
|
858
|
+
if (!confirmation.includeShared) {
|
|
859
|
+
const settingsPath = join(plan.shellHome, "agent", "settings.json");
|
|
860
|
+
let settingsText ;
|
|
861
|
+
try { settingsText = readFileSync(settingsPath, "utf8"); } catch { settingsText = undefined; }
|
|
862
|
+
const removal = withoutBundledNpmCommand(settingsText, (path) => within(root, path, platform));
|
|
863
|
+
if (removal.status === "removed") {
|
|
864
|
+
try {
|
|
865
|
+
writeTextAtomically(settingsPath, removal.text);
|
|
866
|
+
out(`Removed npmCommand from ${settingsPath}: it named the removed install.`);
|
|
867
|
+
} catch (error) {
|
|
868
|
+
err(`gentle-shell self-uninstall: could not remove npmCommand from ${settingsPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
869
|
+
err(`Pi package installs keep failing until the npmCommand naming ${root} is deleted from it.`);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
824
873
|
if (plan.held.length > 0) {
|
|
825
874
|
try {
|
|
826
875
|
if (!input.deferRemoval) throw new Error("no deferred removal on this system");
|
|
@@ -124,6 +124,7 @@ export const NATIVE_REVIEW_ERROR_CODE = {
|
|
|
124
124
|
|
|
125
125
|
|
|
126
126
|
|
|
127
|
+
|
|
127
128
|
export const NATIVE_REVIEW_MODE_OPERATION = {
|
|
128
129
|
STATUS: "status",
|
|
129
130
|
ENABLE: "enable",
|
|
@@ -202,10 +203,10 @@ export const NATIVE_REVIEW_MODE_SCOPE = {
|
|
|
202
203
|
// stream while still succeeding. That last qualifier is what keeps this list
|
|
203
204
|
// honest: v2.4.0 deleted reviewConsentSkippedDefaultProvenance ("Reviews are
|
|
204
205
|
// on by default; this was never explicitly chosen. ..."), which used to ride
|
|
205
|
-
// with the skip notice whenever the resolved mode source was `default`.
|
|
206
|
-
//
|
|
207
|
-
//
|
|
208
|
-
//
|
|
206
|
+
// with the skip notice whenever the resolved mode source was `default`. Reviews
|
|
207
|
+
// are on by default again since gentle-ai v4.0.0, but the pinned binary no
|
|
208
|
+
// longer has that function and cannot emit the line, so it is removed here
|
|
209
|
+
// rather than left as dead tolerance. Multi-
|
|
209
210
|
// line stderr is still expected in principle — these are separate Fprintln
|
|
210
211
|
// calls, never one joined string — which is why membership is per line.
|
|
211
212
|
export const REVIEW_CONSENT_NOTICES = Object.freeze([
|
|
@@ -1083,6 +1084,20 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
|
|
|
1083
1084
|
// b04b6b75d1a446c942a816fbf92932d16f8c3d3001f93159dee2a92bbf2ebe44.
|
|
1084
1085
|
// No new negotiated capability identity is asserted.
|
|
1085
1086
|
"4.1.1": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
1087
|
+
// v4.2.0 repeats 4.1.1: the published binary still advertises
|
|
1088
|
+
// capabilities/v2.6 under review-integration/v2. The provider contract moved
|
|
1089
|
+
// from 1.3.0 to 1.4.0 (routing/pi.md, gentle-ai#5536), with published
|
|
1090
|
+
// tarball SHA-256
|
|
1091
|
+
// 9d2927a84ba9a596f69029e3f854b16f86ca4c8b32cc5d63683764ccf2fb394b.
|
|
1092
|
+
// No new negotiated capability identity is asserted.
|
|
1093
|
+
"4.2.0": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
1094
|
+
// v4.3.1 repeats 4.2.0: the published binary still advertises
|
|
1095
|
+
// capabilities/v2.6 under review-integration/v2, and the provider contract
|
|
1096
|
+
// stays at 1.4.0, with published tarball SHA-256
|
|
1097
|
+
// 58450471280aeb357443d6a28c819132d8c8eb78dd25b12bac64296b2f8c27c2.
|
|
1098
|
+
// The v4.3.0 tag has no release and is never pinned.
|
|
1099
|
+
// No new negotiated capability identity is asserted.
|
|
1100
|
+
"4.3.1": Object.freeze({ start: true, finalize: true, validate: true, bindSdd: true, status: true, inventory: true, reclaim: true, recover: true, abandon: true, quarantineLegacy: true, reconcileAuthority: true, repairLegacyAlias: true, mode: true, riskEvidence: false, hint: false, delivery: true }),
|
|
1086
1101
|
});
|
|
1087
1102
|
|
|
1088
1103
|
|
|
@@ -268,8 +268,9 @@ export const VERIFICATION_TIER = {
|
|
|
268
268
|
// ---------------------------------------------------------------------------
|
|
269
269
|
// Agent-raised risk (gentle-shell#1494). The agent that made the change may
|
|
270
270
|
// raise its risk to high by citing one item of the high-risk list in the
|
|
271
|
-
// always-on Task Size section; it can never lower a tier.
|
|
272
|
-
// the
|
|
271
|
+
// always-on Task Size section; it can never lower a tier. That escalation is
|
|
272
|
+
// the only way a plan gets an independent verifier: a native high or a failed
|
|
273
|
+
// assessment informs but never adds one by itself.
|
|
273
274
|
// ---------------------------------------------------------------------------
|
|
274
275
|
|
|
275
276
|
export const HIGH_RISK_ITEMS = Object.freeze({
|
|
@@ -306,10 +307,17 @@ export function decodeAgentRiskEscalation(value ) {
|
|
|
306
307
|
return Object.freeze({ item: item , reason });
|
|
307
308
|
}
|
|
308
309
|
|
|
309
|
-
/**
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
310
|
+
/**
|
|
311
|
+
* The risk tier the verification plan follows. The independent verifier is
|
|
312
|
+
* for concrete occasions only: the plan is high only when the agent escalated
|
|
313
|
+
* a named Task Size high-risk item. Without an escalation a native passive
|
|
314
|
+
* stays passive, and every other native tier -- medium, high, or unassessable
|
|
315
|
+
* (a failed or unavailable assessment) -- is medium: it informs, but adds no
|
|
316
|
+
* verifier by itself. The native tier stays visible to callers as `nativeRisk`.
|
|
317
|
+
*/
|
|
318
|
+
export function verificationRisk(nativeRisk , escalation ) {
|
|
319
|
+
if (escalation !== undefined) return VERIFICATION_TIER.HIGH;
|
|
320
|
+
return nativeRisk === VERIFICATION_TIER.PASSIVE ? VERIFICATION_TIER.PASSIVE : VERIFICATION_TIER.MEDIUM;
|
|
313
321
|
}
|
|
314
322
|
|
|
315
323
|
export const RDD_LINE = {
|
|
@@ -357,6 +365,8 @@ export const NATIVE_REVIEW_OUTCOME = {
|
|
|
357
365
|
|
|
358
366
|
|
|
359
367
|
|
|
368
|
+
|
|
369
|
+
|
|
360
370
|
|
|
361
371
|
|
|
362
372
|
|
|
@@ -367,7 +377,7 @@ export const NATIVE_REVIEW_OUTCOME = {
|
|
|
367
377
|
* naming which branch fired, up to and including the word "and"; the
|
|
368
378
|
* tier-specific clause is appended after it.
|
|
369
379
|
*/
|
|
370
|
-
function riskGatedPlan(gatePrefix , risk
|
|
380
|
+
function riskGatedPlan(gatePrefix , risk ) {
|
|
371
381
|
if (risk === VERIFICATION_TIER.PASSIVE) {
|
|
372
382
|
return Object.freeze({
|
|
373
383
|
writerSelfVerification: false,
|
|
@@ -377,26 +387,22 @@ function riskGatedPlan(gatePrefix , risk , writerProfile
|
|
|
377
387
|
});
|
|
378
388
|
}
|
|
379
389
|
|
|
380
|
-
if (risk === VERIFICATION_TIER.
|
|
381
|
-
const smallModelBias = writerProfile === WRITER_PROFILE.SMALL;
|
|
390
|
+
if (risk === VERIFICATION_TIER.HIGH) {
|
|
382
391
|
return Object.freeze({
|
|
383
392
|
writerSelfVerification: true,
|
|
384
393
|
structuralReadbackOnly: false,
|
|
385
|
-
independentVerifier:
|
|
386
|
-
reason:
|
|
387
|
-
? `${gatePrefix} the native assessment reports medium risk, but the small-model bias raises it one tier to high for verification purposes: the writer self-verifies and a separate independent verifier also runs.`
|
|
388
|
-
: `${gatePrefix} the native assessment reports medium risk with a large writer profile: the writer's self-verification stands and no separate verifier is required.`,
|
|
394
|
+
independentVerifier: true,
|
|
395
|
+
reason: `${gatePrefix} an agent escalation names a Task Size high-risk item: the writer self-verifies and a separate independent verifier runs.`,
|
|
389
396
|
});
|
|
390
397
|
}
|
|
391
398
|
|
|
392
|
-
//
|
|
399
|
+
// medium, or an unassessable tier that did not go through verificationRisk:
|
|
400
|
+
// both inform only.
|
|
393
401
|
return Object.freeze({
|
|
394
402
|
writerSelfVerification: true,
|
|
395
403
|
structuralReadbackOnly: false,
|
|
396
|
-
independentVerifier:
|
|
397
|
-
reason: risk
|
|
398
|
-
? `${gatePrefix} the native risk assessment could not be produced, so the candidate is treated as high risk: the writer self-verifies and a separate independent verifier always runs.`
|
|
399
|
-
: `${gatePrefix} the native assessment reports high risk: the writer self-verifies and a separate independent verifier always runs.`,
|
|
404
|
+
independentVerifier: false,
|
|
405
|
+
reason: `${gatePrefix} no Task Size high-risk item was escalated: the writer's self-verification stands and no separate verifier runs, whatever the native tier or the model that wrote the change.`,
|
|
400
406
|
});
|
|
401
407
|
}
|
|
402
408
|
|
|
@@ -417,24 +423,23 @@ function nonClosedOutcomeClause(outcome ) {
|
|
|
417
423
|
|
|
418
424
|
/**
|
|
419
425
|
* Computes who verifies a delegated writer's change. Verification follows the
|
|
420
|
-
*
|
|
426
|
+
* plan risk tier whatever the RDD line and native review outcome
|
|
421
427
|
* (verify-always-rdd-high S1, superseding the gentle-pi#662/#668 on-path): the
|
|
422
428
|
* native review is an additional outside view and never replaces or skips the
|
|
423
429
|
* tier's verification. `nativeReviewOutcome` only shapes the reason text.
|
|
424
430
|
*
|
|
431
|
+
* `risk` is the plan risk from `verificationRisk`; `writerProfile` is never
|
|
432
|
+
* consulted, so the model or effort that wrote the change never adds a
|
|
433
|
+
* verifier.
|
|
434
|
+
*
|
|
425
435
|
* - `passive`: structural readback by the parent only; no verifier, no tests.
|
|
426
|
-
* - `medium
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
* function (see `resolveWriterProfile`), so an unrecognized profile never
|
|
431
|
-
* lowers a tier below what a known small model would get.
|
|
432
|
-
* - `high` or `unassessable` (a failed or unrecognized native assessment is
|
|
433
|
-
* always treated as high): the writer self-verifies and an independent
|
|
434
|
-
* verifier always runs.
|
|
436
|
+
* - `medium` (also an `unassessable` tier passed directly): the writer
|
|
437
|
+
* self-verifies; no separate verifier.
|
|
438
|
+
* - `high` (only through an agent escalation naming a Task Size high-risk
|
|
439
|
+
* item): the writer self-verifies and an independent verifier runs.
|
|
435
440
|
*/
|
|
436
441
|
export function verificationPlan(input ) {
|
|
437
|
-
const { rddLine, risk
|
|
442
|
+
const { rddLine, risk } = input;
|
|
438
443
|
|
|
439
444
|
if (rddLine === RDD_LINE.ON) {
|
|
440
445
|
const outcome = input.nativeReviewOutcome ?? NATIVE_REVIEW_OUTCOME.UNKNOWN;
|
|
@@ -444,14 +449,13 @@ export function verificationPlan(input )
|
|
|
444
449
|
return riskGatedPlan(
|
|
445
450
|
`receipt-driven development is on and ${clause}; the native review is an additional outside view that never replaces verification, so the risk-gated path applies exactly as receipt-driven development off, and`,
|
|
446
451
|
risk,
|
|
447
|
-
writerProfile,
|
|
448
452
|
);
|
|
449
453
|
}
|
|
450
454
|
|
|
451
455
|
// rddLine is "off" or "unknown" here -- both gate on the native risk
|
|
452
456
|
// assessment identically, so an unknown RDD line never lowers a tier, and
|
|
453
457
|
// neither line ever consults nativeReviewOutcome.
|
|
454
|
-
return riskGatedPlan(`receipt-driven development is ${rddLine} and`, risk
|
|
458
|
+
return riskGatedPlan(`receipt-driven development is ${rddLine} and`, risk);
|
|
455
459
|
}
|
|
456
460
|
|
|
457
461
|
// ---------------------------------------------------------------------------
|
|
@@ -476,15 +480,16 @@ export function verificationPlan(input )
|
|
|
476
480
|
const SMALL_MODEL_ID_TOKEN = /(?:^|[-_./:\s])mini(?:$|[-_./:\s])/i;
|
|
477
481
|
|
|
478
482
|
/**
|
|
479
|
-
* A writer profile counts as a "small model"
|
|
483
|
+
* A writer profile counts as a "small model" (reported by ASSESS as
|
|
484
|
+
* `writerProfile`; it never changes the verification plan) when:
|
|
480
485
|
* - its resolved effort is `low`, or
|
|
481
486
|
* - its resolved model id carries `mini` as a whole token (see
|
|
482
487
|
* `SMALL_MODEL_ID_TOKEN`), or
|
|
483
488
|
* - it carries NO signal at all -- no profile object, or a profile with
|
|
484
489
|
* neither a model id nor a resolved effort. An unknown or omitted writer
|
|
485
|
-
* profile fails closed to small rather than defaulting to large
|
|
486
|
-
*
|
|
487
|
-
*
|
|
490
|
+
* profile fails closed to small rather than defaulting to large. The
|
|
491
|
+
* result is reported in the assessment only: the verification plan never
|
|
492
|
+
* reads the writer profile, so it cannot add or remove a verifier.
|
|
488
493
|
*
|
|
489
494
|
* A profile that carries a model id or an effort, just not a matching one
|
|
490
495
|
* (for example `{model: {id: "claude-sonnet-5"}, thinking: "high"}`), is
|
|
@@ -502,9 +507,9 @@ export function isSmallWriterProfile(profile
|
|
|
502
507
|
}
|
|
503
508
|
|
|
504
509
|
/**
|
|
505
|
-
* Maps a resolved writer profile to the `writerProfile
|
|
506
|
-
*
|
|
507
|
-
* `
|
|
510
|
+
* Maps a resolved writer profile to the reported `writerProfile`. An unknown
|
|
511
|
+
* or omitted profile resolves to `"small"`, exactly like
|
|
512
|
+
* `isSmallWriterProfile` (gentle-pi#662).
|
|
508
513
|
*/
|
|
509
514
|
export function resolveWriterProfile(profile ) {
|
|
510
515
|
return isSmallWriterProfile(profile) ? WRITER_PROFILE.SMALL : WRITER_PROFILE.LARGE;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// Explicit bundled-store recovery. The provider owns catalog validation and
|
|
2
|
+
// migration; Shell owns promotion and its core-bound provenance. Never called
|
|
3
|
+
// from ordinary startup or upgrade.
|
|
4
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
5
|
+
import { closeSync, fsyncSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { tmpdir } from "node:os";
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { engramProbeAllowed, engramProvenancePath, parseEngramProvenance } from "../runtime/gentle-shell-bundled.mjs";
|
|
10
|
+
|
|
11
|
+
export const MIGRATION_USAGE = "Usage: gentle-shell memory migrate --confirm-quiescent\nStop every Engram client before running this command.\n";
|
|
12
|
+
export const MIGRATION_PENDING_NOTICE = "Engram memory disabled: an incomplete memory migration needs recovery. Keep every Engram client stopped, retain the original backup, and follow docs/bundled-install.md#recovering-an-interrupted-migration. Do not remove the migration marker or create a new store before recovery.";
|
|
13
|
+
export const migrationMarkerPath = (prefix) => join(prefix, "config", "engram-migration.json");
|
|
14
|
+
export function migrationPending(prefix) {
|
|
15
|
+
try { lstatSync(migrationMarkerPath(prefix)); return true; }
|
|
16
|
+
catch (error) { return error.code !== "ENOENT"; }
|
|
17
|
+
}
|
|
18
|
+
function regularText(path, limit) {
|
|
19
|
+
const info = lstatSync(path);
|
|
20
|
+
if (!info.isFile() || info.size > limit) throw new Error("unsafe migration metadata");
|
|
21
|
+
return readFileSync(path, "utf8");
|
|
22
|
+
}
|
|
23
|
+
const digest = (path) => createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
24
|
+
// Durably record a file or a directory entry change. Windows cannot open a
|
|
25
|
+
// directory for fsync; there NTFS metadata journaling covers the renames.
|
|
26
|
+
function syncPath(path, directory = false) {
|
|
27
|
+
if (directory && process.platform === "win32") return;
|
|
28
|
+
const fd = openSync(path, "r");
|
|
29
|
+
try { fsyncSync(fd); } finally { closeSync(fd); }
|
|
30
|
+
}
|
|
31
|
+
function coreHelp(core, env) {
|
|
32
|
+
// Older cores route unknown commands through legacy startup. Inspect their
|
|
33
|
+
// config-free help in an isolated home, never try an unknown data command.
|
|
34
|
+
const scratch = mkdtempSync(join(tmpdir(), "gentle-engram-capability-"));
|
|
35
|
+
try {
|
|
36
|
+
const probe = spawnSync(core, ["--help"], {
|
|
37
|
+
cwd: scratch, env: { ...env, HOME: scratch, USERPROFILE: scratch, ENGRAM_DATA_DIR: join(scratch, "store") },
|
|
38
|
+
encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000, maxBuffer: 65536, windowsHide: true,
|
|
39
|
+
});
|
|
40
|
+
return !probe.error && !probe.signal && probe.status === 0 && probe.stderr === "" ? probe.stdout.split("\n").map((line) => line.trim()) : [];
|
|
41
|
+
} finally { rmSync(scratch, { recursive: true, force: true }); }
|
|
42
|
+
}
|
|
43
|
+
function supportsMigration(core, env) {
|
|
44
|
+
return coreHelp(core, env).includes("migrate-copy --data-dir <absolute-source> --output-dir <new-absolute-destination> --confirm-quiescent --json");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function migrateBundledEngram({ prefix, core, home, env }) {
|
|
48
|
+
let markerOwned = false;
|
|
49
|
+
let promotionStarted = false;
|
|
50
|
+
let output;
|
|
51
|
+
let backup;
|
|
52
|
+
try {
|
|
53
|
+
if (!core) throw new Error("the verified bundled core is unavailable; run gentle-shell setup first");
|
|
54
|
+
if (!engramProbeAllowed(env)) throw new Error("explicit Engram selections or a disabled startup policy must be resolved before bundled migration");
|
|
55
|
+
if (migrationPending(prefix)) throw new Error(MIGRATION_PENDING_NOTICE);
|
|
56
|
+
const source = join(realpathSync(home), ".engram");
|
|
57
|
+
if (!lstatSync(source).isDirectory() || realpathSync(source) !== source) throw new Error("the source must be a regular default store directory");
|
|
58
|
+
const provenancePath = engramProvenancePath(prefix);
|
|
59
|
+
const original = regularText(provenancePath, 4096);
|
|
60
|
+
const record = parseEngramProvenance(original);
|
|
61
|
+
const identity = regularText(join(source, ".instance-id"), 128).trim();
|
|
62
|
+
if (!record || record.dataDir !== source || record.instanceId !== identity) throw new Error("the store does not match Shell's recorded identity; it cannot be adopted");
|
|
63
|
+
const coreHash = digest(core);
|
|
64
|
+
const childEnv = Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("ENGRAM_") && key !== "GH_TOKEN" && key !== "GITHUB_TOKEN"));
|
|
65
|
+
childEnv.ENGRAM_NO_UPDATE_CHECK = "1";
|
|
66
|
+
if (!supportsMigration(core, childEnv)) throw new Error("the bundled core does not support migrate-copy; wait for a supporting published bundle. The source and provenance are unchanged");
|
|
67
|
+
const suffix = randomUUID();
|
|
68
|
+
output = `${source}.migrating-${suffix}`;
|
|
69
|
+
backup = `${source}.before-${suffix}`;
|
|
70
|
+
const marker = migrationMarkerPath(prefix);
|
|
71
|
+
mkdirSync(dirname(marker), { recursive: true, mode: 0o700 });
|
|
72
|
+
// A recorded refusal precedes copying/promotion. A killed launcher must not
|
|
73
|
+
// mistake the rename gap for a fresh install and provision an empty store.
|
|
74
|
+
writeFileSync(marker, `${JSON.stringify({ source, output, backup, originalProvenance: record })}\n`, { flag: "wx", mode: 0o600 });
|
|
75
|
+
markerOwned = true;
|
|
76
|
+
syncPath(marker);
|
|
77
|
+
syncPath(dirname(marker), true);
|
|
78
|
+
const result = spawnSync(core, ["migrate-copy", "--data-dir", source, "--output-dir", output, "--confirm-quiescent", "--json"], {
|
|
79
|
+
env: childEnv, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 120000, maxBuffer: 16384, windowsHide: true,
|
|
80
|
+
});
|
|
81
|
+
if (result.error || result.signal) throw new Error("the provider failed or timed out; no source promotion was attempted");
|
|
82
|
+
if (result.status !== 0) throw new Error(`the provider rejected migration (exit ${result.status}): ${result.stderr.trim().slice(0, 2048) || "no diagnostic"}`);
|
|
83
|
+
if (result.stderr !== "") throw new Error("unexpected provider diagnostic with success evidence");
|
|
84
|
+
let proof;
|
|
85
|
+
try { proof = JSON.parse(result.stdout); } catch { throw new Error("invalid provider migration evidence"); }
|
|
86
|
+
if (!proof || proof.migration_completed !== true || proof.source_dir !== source || proof.output_dir !== output || proof.instance_id !== identity) {
|
|
87
|
+
throw new Error("provider migration evidence does not match the requested store and copy");
|
|
88
|
+
}
|
|
89
|
+
if (!lstatSync(output).isDirectory() || realpathSync(output) !== output ||
|
|
90
|
+
regularText(join(output, ".instance-id"), 128).trim() !== identity ||
|
|
91
|
+
regularText(join(source, ".instance-id"), 128).trim() !== identity ||
|
|
92
|
+
regularText(provenancePath, 4096) !== original || digest(core) !== coreHash) {
|
|
93
|
+
throw new Error("store, provenance or bundled core changed during migration");
|
|
94
|
+
}
|
|
95
|
+
// The original is retained, not deleted. From this point any failure keeps
|
|
96
|
+
// the marker: recovery must reconcile both paths before another launch.
|
|
97
|
+
promotionStarted = true;
|
|
98
|
+
renameSync(source, backup);
|
|
99
|
+
renameSync(output, source);
|
|
100
|
+
syncPath(dirname(source), true);
|
|
101
|
+
const temporary = `${provenancePath}.${suffix}.tmp`;
|
|
102
|
+
writeFileSync(temporary, `${JSON.stringify({ schema: 1, dataDir: source, instanceId: identity, coreSha256: coreHash })}\n`, { flag: "wx", mode: 0o600 });
|
|
103
|
+
syncPath(temporary);
|
|
104
|
+
renameSync(temporary, provenancePath);
|
|
105
|
+
syncPath(dirname(provenancePath), true);
|
|
106
|
+
// Only a durable promotion and provenance may release the fail-closed marker.
|
|
107
|
+
rmSync(marker);
|
|
108
|
+
// The migration is complete; a failed final sync must not report failure.
|
|
109
|
+
try { syncPath(dirname(marker), true); } catch { /* Best effort. */ }
|
|
110
|
+
return { code: 0, stdout: `${JSON.stringify({ status: "migrated", instance_id: identity, backup_dir: backup })}\n`, stderr: "" };
|
|
111
|
+
} catch (error) {
|
|
112
|
+
if (markerOwned && !promotionStarted) {
|
|
113
|
+
try { rmSync(migrationMarkerPath(prefix)); } catch { /* Keep the fail-closed marker. */ }
|
|
114
|
+
}
|
|
115
|
+
const retained = output ? ` Any partial copy at ${output} is retained; do not use it.` : "";
|
|
116
|
+
const recovery = promotionStarted ? ` Promotion may be incomplete; original backup: ${backup}. ${MIGRATION_PENDING_NOTICE}` : "";
|
|
117
|
+
return { code: 1, stdout: "", stderr: `gentle-shell: memory migration failed: ${error.message}.${retained}${recovery}\n` };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -37,7 +37,7 @@ const WINDOWS_SYSTEM_ROOT = "C:\\Windows";
|
|
|
37
37
|
// version check below) derives from this constant instead of repeating the
|
|
38
38
|
// literal, so a pin bump cannot leave a stale copy behind. See
|
|
39
39
|
// scripts/install-gentle-ai.mjs for the incident that motivated this.
|
|
40
|
-
export const INSTALLER_VERSION = "4.
|
|
40
|
+
export const INSTALLER_VERSION = "4.3.1";
|
|
41
41
|
export const RELEASE_BASE_URL = `https://github.com/Gentleman-Programming/gentle-ai/releases/download/v${INSTALLER_VERSION}/`;
|
|
42
42
|
export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
|
|
43
43
|
SIGNED_RELEASE_ASSET: "signed-release-asset",
|
|
@@ -46,11 +46,11 @@ export const GENTLE_AI_INSTALL_METHOD = Object.freeze({
|
|
|
46
46
|
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH = "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai";
|
|
47
47
|
export const GENTLE_AI_WINDOWS_SOURCE_MODULE = "github.com/gentleman-programming/gentle-ai/v4";
|
|
48
48
|
export const GENTLE_AI_WINDOWS_SOURCE_TAG = `v${INSTALLER_VERSION}`;
|
|
49
|
-
// `go mod download -json github.com/gentleman-programming/gentle-ai/v4@v4.
|
|
49
|
+
// `go mod download -json github.com/gentleman-programming/gentle-ai/v4@v4.3.1`
|
|
50
50
|
// with GOSUMDB=sum.golang.org reports this exact module SumDB checksum, and the
|
|
51
|
-
// tag resolves to commit
|
|
51
|
+
// tag resolves to commit 2dba7784114fff57864519c4c0d067e952a8c98b, the published v4.3.1 release head.
|
|
52
52
|
// v4.0.0 moved the Go module path to the /v4 major-version suffix.
|
|
53
|
-
export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1
|
|
53
|
+
export const GENTLE_AI_WINDOWS_SOURCE_MODULE_CHECKSUM = "h1:00p8dnjJrhYFq1Qikk2oY6K/UXIty9A/IBUUOUiX9XA=";
|
|
54
54
|
export const GENTLE_AI_WINDOWS_SOURCE_PACKAGE = `${GENTLE_AI_WINDOWS_SOURCE_PACKAGE_PATH}@${GENTLE_AI_WINDOWS_SOURCE_TAG}`;
|
|
55
55
|
export const GENTLE_AI_WINDOWS_MINIMUM_GO_VERSION = "1.25.10";
|
|
56
56
|
export const GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE_CODE = "GENTLE_AI_GO_TOOLCHAIN_UNAVAILABLE";
|
|
@@ -69,7 +69,7 @@ export class GentleAiInstallerError extends Error {
|
|
|
69
69
|
// Sentinel used while a re-pinned gentle-ai release is not yet published. A
|
|
70
70
|
// sentinel digest can never match a real SHA-256, so installation fails closed,
|
|
71
71
|
// and verify-package-files.mjs refuses to pack/publish while any digest below
|
|
72
|
-
// still holds it. The v4.
|
|
72
|
+
// still holds it. The v4.3.1 digests are pinned from the published release:
|
|
73
73
|
// archive sha256 values verified against the minisign-signed checksums.txt and
|
|
74
74
|
// freshly computed hashes; binary sha256 values computed from the extracted
|
|
75
75
|
// executables.
|
|
@@ -111,15 +111,15 @@ async function downloadPinnedGentleAiAsset(asset, destination, options) {
|
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
// Windows is absent from signed release archives on purpose. gentle-ai stopped
|
|
114
|
-
// distributing unsigned Windows builds in c4b764d0, so v4.
|
|
114
|
+
// distributing unsigned Windows builds in c4b764d0, so v4.3.1 publishes signed
|
|
115
115
|
// Darwin/Linux archives only. Windows x64/arm64 uses the separately verified
|
|
116
116
|
// exact-tag Go SumDB source-build path below; restore archive rows only when
|
|
117
117
|
// upstream ships signed Windows assets.
|
|
118
118
|
export const GENTLE_AI_RELEASE_ASSETS = Object.freeze({
|
|
119
|
-
"darwin/amd64": asset("gentle-ai_4.
|
|
120
|
-
"darwin/arm64": asset("gentle-ai_4.
|
|
121
|
-
"linux/amd64": asset("gentle-ai_4.
|
|
122
|
-
"linux/arm64": asset("gentle-ai_4.
|
|
119
|
+
"darwin/amd64": asset("gentle-ai_4.3.1_darwin_amd64.tar.gz", "6cf57fee0574daef2abed07f54a355fec3b93ac8f01733dc6c4d3674f1746112", "ca44c3fce186b63a7e7ad455c338133ba1982f4a13485301ce4d66edb73d2928", "gentle-ai"),
|
|
120
|
+
"darwin/arm64": asset("gentle-ai_4.3.1_darwin_arm64.tar.gz", "13e8828447f9081465ee0c35a61b24fb49296d51a310c6999227e2aed178956b", "749d08062b0dec13681678a1a1c018613d223ddcca47b5f27681b1d8ab5d14c2", "gentle-ai"),
|
|
121
|
+
"linux/amd64": asset("gentle-ai_4.3.1_linux_amd64.tar.gz", "04ec685c56b42c29dff6db10fc7dc2c730217dc9c96736e53ad2acf3226dafb7", "86248792bde3fe5e9ca93b128c07e7caa6a5b31fac0f9fc84b5b3011b123cb90", "gentle-ai"),
|
|
122
|
+
"linux/arm64": asset("gentle-ai_4.3.1_linux_arm64.tar.gz", "99e7d313a2ff85eaf23a6415e9b3aae67bf74c2fa169b21b93070ada7613f6a0", "df3580506bb5ea63cc4ef5ccebe8b68cfedf66b72f3b51f84ff393183d8cea2b", "gentle-ai"),
|
|
123
123
|
});
|
|
124
124
|
|
|
125
125
|
// A pinned asset is either a signed archive or, for a prerelease pin only,
|
|
@@ -44,15 +44,15 @@ export const goPinVersion = go.version;
|
|
|
44
44
|
// checksums.txt and GitHub's asset digests; every archive was downloaded and
|
|
45
45
|
// re-hashed before pinning. GitHub serves the assets from its asset hosts.
|
|
46
46
|
const engram = Object.freeze({
|
|
47
|
-
version: "3.
|
|
47
|
+
version: "3.5.2",
|
|
48
48
|
redirectHosts: Object.freeze(["objects.githubusercontent.com", "release-assets.githubusercontent.com"]),
|
|
49
49
|
archives: Object.freeze({
|
|
50
|
-
"darwin-arm64": ["darwin_arm64.tar.gz", "
|
|
51
|
-
"darwin-x64": ["darwin_amd64.tar.gz", "
|
|
52
|
-
"linux-x64": ["linux_amd64.tar.gz", "
|
|
53
|
-
"linux-arm64": ["linux_arm64.tar.gz", "
|
|
54
|
-
"win32-x64": ["windows_amd64.zip", "
|
|
55
|
-
"win32-arm64": ["windows_arm64.zip", "
|
|
50
|
+
"darwin-arm64": ["darwin_arm64.tar.gz", "43d398b2fe3327a4a6436c26b2a17d3285ba80bf2f11a22d0796ea2a7fd2b78c", 7749520],
|
|
51
|
+
"darwin-x64": ["darwin_amd64.tar.gz", "aea28ebf461a4ea86c52b793c29311539d7123875354ed7411eae982fc82ec87", 8181300],
|
|
52
|
+
"linux-x64": ["linux_amd64.tar.gz", "106a43ca8f43a27c2c260a6787151c3be6d91c5ed3d5e454e6140527adf92e01", 8064598],
|
|
53
|
+
"linux-arm64": ["linux_arm64.tar.gz", "b0ba49cf5a7d5af436c9744a9a0d638b3f9ebb2a7321c8736d30e96305c2993c", 7472279],
|
|
54
|
+
"win32-x64": ["windows_amd64.zip", "ba56a36d37b2da85fcb1581bbdbed9ae1ee690e642aab8dbe1e18209bf264afe", 8272809],
|
|
55
|
+
"win32-arm64": ["windows_arm64.zip", "df5acc5dd6ee407d484074e570c77a79567c0b8dccf55e25ae25a644c1e7db5f", 7530282],
|
|
56
56
|
}),
|
|
57
57
|
});
|
|
58
58
|
/** The Engram version the bundled install publishes under runtime/. */
|
|
@@ -248,9 +248,11 @@ export function planPreflight(inventory, { channel = "release" } = {}) {
|
|
|
248
248
|
record("gentleAi", classify(inventory.gentleAi, requirements.gentleAi, (o) => o.compatible, true), requirements.gentleAi);
|
|
249
249
|
const needsNative = tools.gentleAi.status === "unavailable";
|
|
250
250
|
// The main channel builds Gentle AI from source on every platform.
|
|
251
|
-
// gentle-pi's postinstall
|
|
252
|
-
//
|
|
253
|
-
|
|
251
|
+
// gentle-pi's postinstall always builds Gentle AI from source on Windows, even
|
|
252
|
+
// next to an installed gentle-ai, so installing or updating the Shell needs Go,
|
|
253
|
+
// as does provisioning a missing native binary; main always does.
|
|
254
|
+
const installsShell = tools.shell.status === "unavailable";
|
|
255
|
+
const windowsBuild = platform === "win32" && (update !== null || (!npmCurrent && (installsShell || needsNative)));
|
|
254
256
|
const goStatus = windowsBuild || main ? classify(inventory.go, requirements.go) : "not-required";
|
|
255
257
|
if (goStatus === "unavailable" || goStatus === "incompatible" || goStatus === "unknown") {
|
|
256
258
|
// A missing, older or unreadable Go is left as it is: the build gets the
|
|
@@ -170,6 +170,9 @@ export const guidance = Object.freeze({
|
|
|
170
170
|
setupRateLimit: "`gentle-shell setup` could not finish because GitHub's limit for anonymous API requests was reached on this network. Wait up to an hour, then run the installer again.",
|
|
171
171
|
// A failed acquire-go whose detail names a Go folder the installer did not publish.
|
|
172
172
|
goDestinationConflict: `A Go folder from an earlier, interrupted installer run is in the way, and the installer never replaces a folder it cannot prove it published. Remove that folder (shown below), then run the installer again. Nothing was installed.`,
|
|
173
|
+
// A failed install whose detail shows Windows refused to start the gentle-ai.exe gentle-pi's postinstall built.
|
|
174
|
+
codeIntegrityBlocked: "Windows refused to run the Gentle AI program (gentle-ai.exe) that the installation builds on this computer, most likely because Smart App Control or another code integrity policy blocks unsigned programs. " +
|
|
175
|
+
"Windows offers no exception for a single program. Keep using Gentle Shell in WSL, or turn Smart App Control off in Windows Security and run the installer again; on Windows 11 it cannot be turned back on without reinstalling Windows.",
|
|
173
176
|
// A failed persist-path whose detail shows pnpm could not tell the shell (POSIX SHELL missing or unsupported).
|
|
174
177
|
persistPathShell: "`pnpm setup` could not tell which shell profile to edit because the SHELL environment variable is missing or names an unsupported shell. Open a regular terminal and run the installer again, or add `$PNPM_HOME/bin` to your PATH yourself.",
|
|
175
178
|
fallback: "The installation stopped for an unexpected reason. Nothing else will run; check the terminal and run the installer again.",
|
|
@@ -186,16 +189,20 @@ function setupDetail(value) {
|
|
|
186
189
|
const rateLimited = (detail) => /rate limit/i.test(detail) || (/GitHub API/i.test(detail) && /\b403\b/.test(detail));
|
|
187
190
|
const unknownShell = (detail) => /ERR_PNPM_(?:UNKNOWN|UNSUPPORTED)_SHELL/.test(detail);
|
|
188
191
|
const goConflict = (detail) => detail.startsWith("Conflicting Go destination: ");
|
|
192
|
+
// libuv reports a CreateProcess refusal it has no code for, such as a Windows code integrity block
|
|
193
|
+
// (Smart App Control, event 3077), as spawn UNKNOWN.
|
|
194
|
+
const codeIntegrityBlocked = (detail) => /\bspawn UNKNOWN\b/.test(detail);
|
|
189
195
|
// The main channel's typed error when GitHub refused the latest main commit with HTTP 403.
|
|
190
196
|
const githubLimited = (detail) => rateLimited(detail) || (/\bmain-commit-unavailable\b/.test(detail) && /\bHTTP 403\b/.test(detail));
|
|
191
197
|
// Fixed steps whose sanitized last error line may reach the browser.
|
|
192
198
|
// The bundled steps send a detail only for a migration (its error message); a new install's send none.
|
|
193
199
|
const bundledDetail = Object.fromEntries(["claim-prefix", "copy-npm-settings", "install-runtime", "activate-version", "write-launcher", "verify-launcher", "bundled-path"]
|
|
194
200
|
.map((step) => [step, () => false]));
|
|
195
|
-
const detailSteps = Object.freeze({ ...bundledDetail, "shell-setup": rateLimited, "install-version":
|
|
196
|
-
"install-global": githubLimited, "install-shell-main": githubLimited, "build-gentle-ai-main": githubLimited, "update-shell": githubLimited });
|
|
201
|
+
const detailSteps = Object.freeze({ ...bundledDetail, "shell-setup": rateLimited, "install-version": codeIntegrityBlocked, "persist-path": unknownShell, "acquire-go": goConflict,
|
|
202
|
+
"install-global": (detail) => githubLimited(detail) || codeIntegrityBlocked(detail), "install-shell-main": githubLimited, "build-gentle-ai-main": githubLimited, "update-shell": githubLimited });
|
|
203
|
+
// A step whose detail can match more than one pattern names its guidance by detail.
|
|
197
204
|
const detailGuidance = Object.freeze({ "shell-setup": "setupRateLimit", "persist-path": "persistPathShell", "acquire-go": "goDestinationConflict",
|
|
198
|
-
"install-global": "githubRateLimit", "install-shell-main": "githubRateLimit", "build-gentle-ai-main": "githubRateLimit", "update-shell": "githubRateLimit" });
|
|
205
|
+
"install-version": "codeIntegrityBlocked", "install-global": (detail) => (githubLimited(detail) ? "githubRateLimit" : "codeIntegrityBlocked"), "install-shell-main": "githubRateLimit", "build-gentle-ai-main": "githubRateLimit", "update-shell": "githubRateLimit" });
|
|
199
206
|
|
|
200
207
|
const ID = /^[a-z][a-z0-9-]{0,63}$/;
|
|
201
208
|
function identifier(value, fallback = "unknown") {
|
|
@@ -543,7 +550,10 @@ function outcomeView(result) {
|
|
|
543
550
|
const detail = step ? setupDetail(result.detail) : null;
|
|
544
551
|
if (detail) {
|
|
545
552
|
view.detail = detail;
|
|
546
|
-
if (detailSteps[step](detail))
|
|
553
|
+
if (detailSteps[step](detail)) {
|
|
554
|
+
const key = detailGuidance[step];
|
|
555
|
+
view.guidance = guidance[typeof key === "function" ? key(detail) : key];
|
|
556
|
+
}
|
|
547
557
|
}
|
|
548
558
|
} else {
|
|
549
559
|
view.guidance = guidance.outcomes[result.outcome];
|