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
package/bin/gentle-shell.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
chmodSync,
|
|
10
10
|
closeSync,
|
|
11
11
|
constants as fsConstants,
|
|
12
|
+
copyFileSync,
|
|
12
13
|
existsSync,
|
|
13
14
|
fstatSync,
|
|
14
15
|
lstatSync,
|
|
@@ -27,7 +28,6 @@ import {
|
|
|
27
28
|
import { constants as osConstants, homedir, tmpdir } from "node:os";
|
|
28
29
|
import { delimiter, dirname, join, resolve as resolvePath } from "node:path";
|
|
29
30
|
import { spawn, spawnSync } from "node:child_process";
|
|
30
|
-
import { createHash } from "node:crypto";
|
|
31
31
|
import { fileURLToPath } from "node:url";
|
|
32
32
|
import {
|
|
33
33
|
buildPiInvocation,
|
|
@@ -50,26 +50,30 @@ import {
|
|
|
50
50
|
parseRawLauncherConfig,
|
|
51
51
|
planSpawn,
|
|
52
52
|
POST_INSTALL_REMOVAL_SOURCES,
|
|
53
|
+
withLauncherPackagesExcluded,
|
|
53
54
|
postInstallRemovals,
|
|
54
55
|
provisionedEntry,
|
|
56
|
+
migrateStandaloneConfig,
|
|
55
57
|
recordProvisioned,
|
|
56
58
|
resolveHome,
|
|
57
59
|
resolvePiRuntime,
|
|
58
60
|
restoreJsonField,
|
|
61
|
+
separateHomeHint,
|
|
59
62
|
shellQuote,
|
|
63
|
+
standaloneConfigHome,
|
|
64
|
+
userPiHome,
|
|
60
65
|
} from "../runtime/gentle-shell-launcher.mjs";
|
|
66
|
+
import { formatImportPiReport, importPi } from "../runtime/gentle-shell-import-pi.mjs";
|
|
61
67
|
import {
|
|
62
68
|
BUNDLED_VERIFY_ENV,
|
|
63
|
-
bundledEngramSchema,
|
|
64
69
|
classifyEngramStartup,
|
|
65
70
|
ENGRAM_COMPANION_SOURCE,
|
|
66
|
-
|
|
71
|
+
ENGRAM_DEFAULT_URL,
|
|
67
72
|
ENGRAM_RECOVERY_NOTICE,
|
|
68
|
-
|
|
73
|
+
ENGRAM_START_FAILED_NOTICE,
|
|
74
|
+
GENTLE_SHELL_CONFIG_HOME_ENV,
|
|
69
75
|
engramProbeAllowed,
|
|
70
|
-
|
|
71
|
-
engramProvenancePath,
|
|
72
|
-
parseEngramProvenance,
|
|
76
|
+
sharedEngramEnv,
|
|
73
77
|
piVersionOrHelpRequested,
|
|
74
78
|
probeEngramEndpoint,
|
|
75
79
|
sqliteHeaderUserVersion,
|
|
@@ -84,6 +88,7 @@ import {
|
|
|
84
88
|
parseBundledUpgradeArgs,
|
|
85
89
|
resolveBundledInstall,
|
|
86
90
|
withBundledNpmCommand,
|
|
91
|
+
withoutMissingBundledNpmCommand,
|
|
87
92
|
} from "../runtime/gentle-shell-bundled.mjs";
|
|
88
93
|
import {
|
|
89
94
|
parseResumeHandoff,
|
|
@@ -95,7 +100,8 @@ import {
|
|
|
95
100
|
import { bundledGentleAiPaths, GENTLE_AI_VERSION, gentleAiBinaryPath, gentleAiIsolationEnv, PackageLocalGentleAiBinaryMissingError } from "../runtime/gentle-ai-binary.mjs";
|
|
96
101
|
import { DEFAULT_THEME_NAME, installIsolatedTuiModeSetting } from "../scripts/install-tui-mode-setting.mjs";
|
|
97
102
|
import { artifactFor, engramPinVersion } from "../scripts/installer-downloads.mjs";
|
|
98
|
-
import {
|
|
103
|
+
import { migrateBundledEngram, migrationPending, MIGRATION_PENDING_NOTICE, MIGRATION_USAGE } from "../scripts/bundled-engram-migration.mjs";
|
|
104
|
+
import { findStartupUpdate, parseStartupReleaseCache, shouldCheckStartupUpdate, writeStartupReleaseCache } from "../runtime/gentle-shell-startup-update.mjs";
|
|
99
105
|
import { promptStartupUpdate } from "../runtime/gentle-shell-update-panel.mjs";
|
|
100
106
|
import { applyStartupChoice, omitStartupVersion, resolveStartupRelaunch } from "../runtime/gentle-shell-startup-flow.mjs";
|
|
101
107
|
|
|
@@ -192,19 +198,6 @@ function verifiedBundledCore() {
|
|
|
192
198
|
return undefined;
|
|
193
199
|
}
|
|
194
200
|
|
|
195
|
-
function sha256File(path) {
|
|
196
|
-
return createHash("sha256").update(readFileSync(path)).digest("hex");
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function readSmallRegularFile(path, limit) {
|
|
200
|
-
try {
|
|
201
|
-
const info = lstatSync(path);
|
|
202
|
-
return info.isFile() && info.size <= limit ? readFileSync(path, "utf8") : undefined;
|
|
203
|
-
} catch {
|
|
204
|
-
return undefined;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
|
|
208
201
|
// The database's raw SQLite header, read as a plain file (never through
|
|
209
202
|
// SQLite): a regular, unlinked engram.db whose opened identity is the one seen.
|
|
210
203
|
function readSqliteUserVersion(path) {
|
|
@@ -224,100 +217,49 @@ function readSqliteUserVersion(path) {
|
|
|
224
217
|
}
|
|
225
218
|
}
|
|
226
219
|
|
|
227
|
-
//
|
|
228
|
-
// the .
|
|
229
|
-
|
|
230
|
-
const dir = join(homedir(), ".engram");
|
|
231
|
-
let dataDir;
|
|
232
|
-
try {
|
|
233
|
-
if (!lstatSync(dir).isDirectory()) return { store: "unknown" };
|
|
234
|
-
dataDir = realpathSync(dir);
|
|
235
|
-
} catch (error) {
|
|
236
|
-
return { store: error.code === "ENOENT" ? "absent" : "unknown" };
|
|
237
|
-
}
|
|
238
|
-
return { store: "present", dir, dataDir, instanceId: readSmallRegularFile(join(dir, ".instance-id"), 128)?.trim() };
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
// Only the identity files Engram's `instance-id` writes: a store Shell
|
|
242
|
-
// provisioned whose session has not created its database yet.
|
|
243
|
-
function identityOnlyStore(dir) {
|
|
244
|
-
try {
|
|
245
|
-
return readdirSync(dir).every((name) => name === ".instance-id" || name === ".instance-id.lock" || name.startsWith(".instance-id-"));
|
|
246
|
-
} catch {
|
|
247
|
-
return false;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Shell's own store: the record Shell wrote when it provisioned this exact
|
|
252
|
-
// directory's identity, the same core digest, and a database header no newer
|
|
253
|
-
// than the bundled schema (or no database yet).
|
|
254
|
-
function ownEngramStore(observed, core) {
|
|
255
|
-
let record;
|
|
256
|
-
try {
|
|
257
|
-
record = parseEngramProvenance(readJsonIfExists(engramProvenancePath(bundledInstall.root, process.platform)));
|
|
258
|
-
} catch {
|
|
259
|
-
return undefined;
|
|
260
|
-
}
|
|
261
|
-
if (record === undefined || observed.store !== "present" || core === undefined) return undefined;
|
|
262
|
-
if (!engramProvenanceMatches(record, { dataDir: observed.dataDir, instanceId: observed.instanceId, coreSha256: sha256File(core) })) return undefined;
|
|
263
|
-
const headerAllows = engramHeaderAllows(readSqliteUserVersion(join(observed.dir, "engram.db")), bundledEngramSchema(engramPinVersion));
|
|
264
|
-
return headerAllows || identityOnlyStore(observed.dir) ? { instanceId: record.instanceId } : undefined;
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
// Evidence only: file metadata, the raw header and a bounded read-only probe
|
|
268
|
-
// of the default endpoint. No Engram executable, SQLite, identity repair, or
|
|
269
|
-
// contact with an explicitly selected endpoint.
|
|
220
|
+
// Evidence only: the default store's raw header and a bounded read-only probe
|
|
221
|
+
// of the default endpoint. No Engram executable, SQLite, or contact with an
|
|
222
|
+
// explicitly selected endpoint. A `spawn` decision starts nothing here.
|
|
270
223
|
async function classifyBundledMemory() {
|
|
271
|
-
|
|
272
|
-
const decision = { env: process.env, core:
|
|
273
|
-
if (!engramProbeAllowed(process.env)) return classifyEngramStartup({ ...decision,
|
|
274
|
-
const observed = observeEngramStore();
|
|
224
|
+
if (migrationPending(bundledInstall.root)) return { policy: "disabled", notice: MIGRATION_PENDING_NOTICE };
|
|
225
|
+
const decision = { env: process.env, core: verifiedBundledCore() !== undefined, coreVersion: engramPinVersion };
|
|
226
|
+
if (!engramProbeAllowed(process.env)) return classifyEngramStartup({ ...decision, endpoint: { kind: "unknown" } });
|
|
275
227
|
const endpoint = await probeEngramEndpoint();
|
|
276
|
-
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
//
|
|
281
|
-
//
|
|
282
|
-
//
|
|
283
|
-
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
228
|
+
const userVersion = endpoint.kind === "refused" ? readSqliteUserVersion(join(homedir(), ".engram", "engram.db")) : undefined;
|
|
229
|
+
return classifyEngramStartup({ ...decision, endpoint, userVersion });
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Like other agents' hooks, Shell starts the shared server when nothing
|
|
233
|
+
// listens: the verified core's plain `serve` on Engram's own default port and
|
|
234
|
+
// data directory, detached so it outlives Pi. Its environment drops the user's
|
|
235
|
+
// Engram settings and GitHub tokens and skips the update check. Ready means the
|
|
236
|
+
// same /compatibility contract any reused server must answer, so a concurrent
|
|
237
|
+
// launch that wins the bind converges too: after the child exits, polling
|
|
238
|
+
// continues for a short grace within the overall bound.
|
|
239
|
+
const ENGRAM_START_TIMEOUT_MS = 10000;
|
|
240
|
+
const ENGRAM_START_EXIT_GRACE_MS = 2000;
|
|
241
|
+
|
|
242
|
+
async function startSharedEngram() {
|
|
243
|
+
const failed = { policy: "disabled", notice: ENGRAM_START_FAILED_NOTICE };
|
|
289
244
|
const core = verifiedBundledCore();
|
|
290
|
-
if (core === undefined
|
|
291
|
-
let
|
|
245
|
+
if (core === undefined) return failed;
|
|
246
|
+
let deadline = Date.now() + ENGRAM_START_TIMEOUT_MS;
|
|
247
|
+
const exited = () => { deadline = Math.min(deadline, Date.now() + ENGRAM_START_EXIT_GRACE_MS); };
|
|
292
248
|
try {
|
|
293
|
-
|
|
249
|
+
const env = sharedEngramEnv(process.env);
|
|
250
|
+
const child = spawn(core, ["serve"], { cwd: homedir(), env, detached: true, stdio: "ignore", windowsHide: true });
|
|
251
|
+
child.once("error", exited);
|
|
252
|
+
child.once("exit", exited);
|
|
253
|
+
child.unref();
|
|
294
254
|
} catch {
|
|
295
|
-
return
|
|
255
|
+
return failed;
|
|
296
256
|
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
delete env.GITHUB_TOKEN;
|
|
302
|
-
const result = spawnSync(core, ["instance-id"], { env, encoding: "utf8", timeout: ENGRAM_PROVISION_TIMEOUT_MS, stdio: ["ignore", "pipe", "pipe"], windowsHide: true });
|
|
303
|
-
const observed = observeEngramStore();
|
|
304
|
-
if (result.status !== 0 || observed.store !== "present" || observed.dataDir !== target || !/^[a-f0-9]{32}$/.test(observed.instanceId ?? "") ||
|
|
305
|
-
String(result.stdout).trim() !== observed.instanceId) return false;
|
|
306
|
-
return writeEngramProvenance({ schema: 1, dataDir: target, instanceId: observed.instanceId, coreSha256: sha256File(core) });
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function writeEngramProvenance(record) {
|
|
310
|
-
try {
|
|
311
|
-
const path = engramProvenancePath(bundledInstall.root, process.platform);
|
|
312
|
-
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
313
|
-
const temporary = `${path}.${process.pid}.tmp`;
|
|
314
|
-
writeFileSync(temporary, `${JSON.stringify(record, null, 2)}\n`, { mode: 0o600, flag: "wx" });
|
|
315
|
-
renameSync(temporary, path);
|
|
316
|
-
return true;
|
|
317
|
-
} catch (error) {
|
|
318
|
-
process.stderr.write(`gentle-shell: could not record Engram provenance (${error.message})\n`);
|
|
319
|
-
return false;
|
|
257
|
+
while (Date.now() < deadline) {
|
|
258
|
+
const endpoint = await probeEngramEndpoint({ timeoutMs: 500 });
|
|
259
|
+
if (endpoint.kind === "listening" && endpoint.compatible) return { policy: "allow", url: ENGRAM_DEFAULT_URL };
|
|
260
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
320
261
|
}
|
|
262
|
+
return failed;
|
|
321
263
|
}
|
|
322
264
|
|
|
323
265
|
// Use Pi's supported configuration-only install command, never an alternate
|
|
@@ -627,6 +569,42 @@ function handleHomeCommand(commandArgs) {
|
|
|
627
569
|
process.exit(0);
|
|
628
570
|
}
|
|
629
571
|
|
|
572
|
+
// `gentle-shell [home selectors] home import-pi [--dry-run]`: copies the
|
|
573
|
+
// user's pi sign-ins, MCP servers and default model into the resolved
|
|
574
|
+
// separate home (lib/gentle-shell-import-pi.ts). A home that is new or empty
|
|
575
|
+
// is first bootstrapped exactly as a first launch would, so the files this
|
|
576
|
+
// adds never make it look foreign to auto-provisioning (homeIsForeign).
|
|
577
|
+
async function handleImportPiCommand(args) {
|
|
578
|
+
const options = args.commandArgs.slice(1);
|
|
579
|
+
if (options.some((option) => option !== "--dry-run")) fail("gentle-shell home import-pi accepts only --dry-run. Run 'gentle-shell --help'.", 2);
|
|
580
|
+
const dryRun = options.includes("--dry-run");
|
|
581
|
+
let home = resolveHome({ args, env: process.env, homedir: homedir(), config: loadConfig() });
|
|
582
|
+
if (home.mode === "path") home = { ...home, dir: resolvePath(home.dir) };
|
|
583
|
+
const source = resolvePath(userPiHome(process.env, homedir()));
|
|
584
|
+
const refusal = "Choose a separate home with --isolated or --home <path>.";
|
|
585
|
+
if (home.mode === "link") {
|
|
586
|
+
fail(`gentle-shell home import-pi: the selected home is your pi home itself (--link), so there is nothing to import. ${refusal}`, 1);
|
|
587
|
+
}
|
|
588
|
+
if ([source, join(homedir(), ".pi", "agent")].some((piHome) => safeRealpath(piHome) === safeRealpath(home.dir))) {
|
|
589
|
+
fail(`gentle-shell home import-pi: ${home.dir} is your pi home itself, so there is nothing to import. ${refusal}`, 1);
|
|
590
|
+
}
|
|
591
|
+
if (homeNeedsBootstrap(home.dir)) {
|
|
592
|
+
if (dryRun) {
|
|
593
|
+
process.stdout.write(`Would prepare ${home.dir} as a new Gentle Shell home\n`);
|
|
594
|
+
} else {
|
|
595
|
+
try {
|
|
596
|
+
await bootstrapOwnedHome(home);
|
|
597
|
+
} catch (error) {
|
|
598
|
+
fail(`gentle-shell home import-pi: could not prepare ${home.dir} (${error.message}); nothing was imported`, 1);
|
|
599
|
+
}
|
|
600
|
+
process.stdout.write(`Prepared ${home.dir} as a new Gentle Shell home\n`);
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
const result = importPi({ source, target: home.dir, dryRun });
|
|
604
|
+
for (const line of formatImportPiReport(result, { source, target: home.dir, dryRun })) process.stdout.write(`${line}\n`);
|
|
605
|
+
process.exitCode = result.failed ? 1 : 0;
|
|
606
|
+
}
|
|
607
|
+
|
|
630
608
|
// Test/development-only override for the setup subcommand's gentle-ai
|
|
631
609
|
// executable path. Lets a test point at a stub script (or a deliberately
|
|
632
610
|
// missing path) without touching the real pinned .gentle-ai/v<version>/gentle-ai
|
|
@@ -799,12 +777,18 @@ function buildSetupEnv(home, runtime, piLauncherDir) {
|
|
|
799
777
|
const env = bundledSetupEnv({ ...bundledEnv, PI_CODING_AGENT_DIR: home.dir, GENTLE_PI_AGENT_HOME: home.dir }, { dirs: bundledSetupDirs, platform: process.platform });
|
|
800
778
|
return gentleAiIsolationEnv(env, { packageRoot });
|
|
801
779
|
}
|
|
802
|
-
|
|
780
|
+
const env = {
|
|
803
781
|
...process.env,
|
|
804
782
|
PI_CODING_AGENT_DIR: home.dir,
|
|
805
783
|
GENTLE_PI_AGENT_HOME: home.dir,
|
|
806
784
|
PATH: [...(piLauncherDir ? [piLauncherDir] : []), dirname(runtime.command), process.env.PATH ?? ""].join(delimiter),
|
|
807
785
|
};
|
|
786
|
+
// #2201: the isolated home's own config folder, given to Gentle AI the
|
|
787
|
+
// bundled way (gentleAiIsolationEnv); never an outer session's marker.
|
|
788
|
+
const configHome = standaloneConfigHome(home);
|
|
789
|
+
if (configHome === undefined) delete env[GENTLE_SHELL_CONFIG_HOME_ENV];
|
|
790
|
+
else env[GENTLE_SHELL_CONFIG_HOME_ENV] = configHome;
|
|
791
|
+
return gentleAiIsolationEnv(env, { packageRoot });
|
|
808
792
|
}
|
|
809
793
|
|
|
810
794
|
// Gentle AI requires a `pi` on its PATH and runs `pi install npm:gentle-pi`
|
|
@@ -847,15 +831,34 @@ function sharedPersonaPath() {
|
|
|
847
831
|
return join(homedir(), ".pi", "gentle-ai", "persona.json");
|
|
848
832
|
}
|
|
849
833
|
|
|
850
|
-
// The persona files a setup run may rewrite. Bundled (S9)
|
|
851
|
-
// which a Gentle AI with
|
|
852
|
-
// still the user's,
|
|
853
|
-
// unchanged file
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
return [join(bundledGentleAiPaths(bundledInstall.root, process.platform).piConfig, "persona.json"), sharedPersonaPath()];
|
|
834
|
+
// The persona files a setup run may rewrite. Bundled (S9) and the standalone
|
|
835
|
+
// isolated home (#2201): its own config folder's, which a Gentle AI with
|
|
836
|
+
// GENTLE_AI_PI_CONFIG_DIR (gentle-ai #5462) writes, and still the user's,
|
|
837
|
+
// which a pinned Gentle AI without it writes; restoring an unchanged file
|
|
838
|
+
// does nothing.
|
|
839
|
+
function setupPersonaPaths(home) {
|
|
840
|
+
if (bundledInstall) return [join(bundledGentleAiPaths(bundledInstall.root, process.platform).piConfig, "persona.json"), sharedPersonaPath()];
|
|
841
|
+
const configHome = standaloneConfigHome(home);
|
|
842
|
+
return configHome === undefined ? [sharedPersonaPath()] : [join(configHome, "persona.json"), sharedPersonaPath()];
|
|
857
843
|
}
|
|
858
844
|
|
|
845
|
+
// The real filesystem behind migrateStandaloneConfig (#2201).
|
|
846
|
+
const configMigrationFs = {
|
|
847
|
+
kind: (path) => {
|
|
848
|
+
try {
|
|
849
|
+
const stat = lstatSync(path);
|
|
850
|
+
return stat.isDirectory() ? "dir" : stat.isFile() ? "file" : "other";
|
|
851
|
+
} catch {
|
|
852
|
+
return "missing";
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
list: (path) => readdirSync(path),
|
|
856
|
+
mkdirPrivate: (path) => {
|
|
857
|
+
mkdirSync(path, { recursive: true, mode: 0o700 });
|
|
858
|
+
},
|
|
859
|
+
copyNew: (from, to) => copyFileSync(from, to, fsConstants.COPYFILE_EXCL),
|
|
860
|
+
};
|
|
861
|
+
|
|
859
862
|
// Bundled setup (S9): the pinned Engram GENTLE_AI_ENGRAM_BIN names, published
|
|
860
863
|
// once in runtime/ (verified, never brew or the user's PATH). A failure stops
|
|
861
864
|
// setup rather than let Gentle AI acquire another Engram.
|
|
@@ -1123,7 +1126,7 @@ async function runSetupFlow(home, runtime, { dryRun, stdio, timeoutMs }) {
|
|
|
1123
1126
|
if (!engram.ok) return engram;
|
|
1124
1127
|
}
|
|
1125
1128
|
// The memory decision never skips setup: it only selects the Engram
|
|
1126
|
-
// environment (
|
|
1129
|
+
// environment (guard, policy, ENGRAM_URL) setup's children and the
|
|
1127
1130
|
// launched Pi get. Gentle AI's `install --agent pi` and `pi-engram init` open
|
|
1128
1131
|
// no store. Reclassified now that the verified core may just be published.
|
|
1129
1132
|
if (bundledInstall) {
|
|
@@ -1134,8 +1137,10 @@ async function runSetupFlow(home, runtime, { dryRun, stdio, timeoutMs }) {
|
|
|
1134
1137
|
|
|
1135
1138
|
const setupArgs = ["install", "--agent", "pi", "--scope", "global", ...(dryRun ? ["--dry-run"] : [])];
|
|
1136
1139
|
const piLauncherDir = writeSetupPiLauncher(runtime);
|
|
1137
|
-
|
|
1138
|
-
|
|
1140
|
+
// #2143: a Gentle AI with package exclusion never installs the launcher-owned
|
|
1141
|
+
// gentle-pi; an older one ignores this and runPostInstallCleanup removes it.
|
|
1142
|
+
const env = withLauncherPackagesExcluded(buildSetupEnv(home, runtime, piLauncherDir));
|
|
1143
|
+
const personaSnapshots = setupPersonaPaths(home).map((path) => [path, safely("snapshot your Pi persona file", path, undefined, () => snapshotFile(path))]);
|
|
1139
1144
|
const statePath = sharedGentleAiStatePath();
|
|
1140
1145
|
const originalStateText = safely("read your Gentle AI state file", statePath, undefined, () => readParsableJsonText(statePath));
|
|
1141
1146
|
// Theme restore (never for --link — that home is the user's own
|
|
@@ -1387,6 +1392,25 @@ function writeHomeOwnershipMarker(home) {
|
|
|
1387
1392
|
writeFileSync(homeOwnershipMarkerPath(home), `${content}\n`, "utf8");
|
|
1388
1393
|
}
|
|
1389
1394
|
|
|
1395
|
+
// The isolated/--home bootstrap: the first launch's own (main, below) and
|
|
1396
|
+
// `home import-pi`'s before it adds files to a new or empty home.
|
|
1397
|
+
async function bootstrapOwnedHome(home) {
|
|
1398
|
+
// 0700: under umask 002 a default mode would leave ~/.gentle-shell group-writable,
|
|
1399
|
+
// which the bundled install's prefix claim must tighten or refuse.
|
|
1400
|
+
mkdirSync(home.dir, { recursive: true, mode: 0o700 });
|
|
1401
|
+
await installIsolatedTuiModeSetting(home.dir);
|
|
1402
|
+
writeHomeOwnershipMarker(home); // R3-001: lets a failed first auto-provision attempt still be retried later
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
function homeNeedsBootstrap(dir) {
|
|
1406
|
+
if (!existsSync(dir)) return true;
|
|
1407
|
+
try {
|
|
1408
|
+
return isDirectory(dir) && readdirSync(dir).length === 0;
|
|
1409
|
+
} catch {
|
|
1410
|
+
return false;
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
|
|
1390
1414
|
// `homeHadContentBeforeBootstrap` must be read by the caller (main, below)
|
|
1391
1415
|
// before the isolated/--home bootstrap runs: that bootstrap itself creates
|
|
1392
1416
|
// and seeds a brand-new directory (settings.json with tuiMode, and now the
|
|
@@ -1580,7 +1604,9 @@ async function packageManagerAdapters() {
|
|
|
1580
1604
|
fs: await import("node:fs/promises"),
|
|
1581
1605
|
which: async (name) => findOnPath(name) ?? null,
|
|
1582
1606
|
...(invocation ? { invocation } : {}),
|
|
1583
|
-
|
|
1607
|
+
// stderrTail: a caller that asks for it, such as a failed `update` install, gets the bounded stderr.
|
|
1608
|
+
run: (command, argv, options = {}) => run(command, argv, { env: options.env ?? process.env, cwd: options.cwd, deadlineMs: options.deadlineMs ?? 20 * 60_000,
|
|
1609
|
+
...(options.stderrTail === undefined ? {} : { stderrTail: options.stderrTail }) }),
|
|
1584
1610
|
};
|
|
1585
1611
|
}
|
|
1586
1612
|
|
|
@@ -1679,6 +1705,26 @@ async function ensureBundledNpmCommand(bundled, home, homeHadContentBeforeBootst
|
|
|
1679
1705
|
}
|
|
1680
1706
|
}
|
|
1681
1707
|
|
|
1708
|
+
// The npmCommand a bundled install wrote into a home it owns must not outlive
|
|
1709
|
+
// it (#2469): Pi runs it for every package install, so a key naming a deleted
|
|
1710
|
+
// node.exe fails them all with ENOENT. A main-channel bootstrap drops the key
|
|
1711
|
+
// when it has the shape a bundled install writes and its node is gone; a
|
|
1712
|
+
// command the user set is never touched, and neither are an existing
|
|
1713
|
+
// executable, a link home and a foreign home.
|
|
1714
|
+
async function removeMissingNpmCommand(home, homeHadContentBeforeBootstrap) {
|
|
1715
|
+
if (home.mode === "link") return;
|
|
1716
|
+
const previous = provisionedEntry(readRawConfig(resolveConfigPath()), safeRealpath(home.dir));
|
|
1717
|
+
if (homeIsForeign(home, previous, homeHadContentBeforeBootstrap) || !isDirectory(home.dir)) return;
|
|
1718
|
+
const settingsPath = join(home.dir, "settings.json");
|
|
1719
|
+
const edit = withoutMissingBundledNpmCommand(readJsonIfExists(settingsPath), (path) => existsSync(path));
|
|
1720
|
+
if (edit.status === "malformed") {
|
|
1721
|
+
process.stderr.write(`gentle-shell: ${settingsPath} is not a JSON object, so a stale npmCommand in it is not repaired\n`);
|
|
1722
|
+
} else if (edit.status === "removed") {
|
|
1723
|
+
writeFileAtomically(settingsPath, edit.text);
|
|
1724
|
+
process.stderr.write(`gentle-shell: removed the bundled npmCommand naming a missing executable from ${settingsPath}; Pi installs packages with the npm on your PATH\n`);
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1682
1728
|
// `gentle-shell self-uninstall` in a bundled install (S6, S7): the prefix's own
|
|
1683
1729
|
// folders and exactly the PATH entry its receipt records; on Windows the running
|
|
1684
1730
|
// node.exe and launcher are removed by a detached process after this one exits.
|
|
@@ -1736,11 +1782,27 @@ async function startupUpdate(bundled, args, startupEnv) {
|
|
|
1736
1782
|
if (!shouldCheckStartupUpdate({ args, env: startupEnv, bundled: Boolean(bundled),
|
|
1737
1783
|
inputTTY: process.stdin.isTTY === true, outputTTY: process.stdout.isTTY === true })) return undefined;
|
|
1738
1784
|
const configPath = resolveConfigPath();
|
|
1739
|
-
let skipped;
|
|
1740
|
-
try {
|
|
1785
|
+
let skipped, cache;
|
|
1786
|
+
try {
|
|
1787
|
+
const raw = readRawConfig(configPath);
|
|
1788
|
+
skipped = raw.skippedStartupRelease;
|
|
1789
|
+
// The last recorded lookup answers today's check without the network; a
|
|
1790
|
+
// stale or malformed record just means this launch looks again.
|
|
1791
|
+
cache = parseStartupReleaseCache(raw.startupReleaseCheck);
|
|
1792
|
+
} catch { /* A notice must not prevent opening. */ }
|
|
1741
1793
|
const { latestRelease } = await import("../scripts/main-channel.mjs");
|
|
1742
|
-
const offer = await findStartupUpdate({ current: ownPackageVersion(), skipped,
|
|
1743
|
-
lookup: (signal) => latestRelease((url, options) => fetch(url, { ...options, signal }))
|
|
1794
|
+
const offer = await findStartupUpdate({ current: ownPackageVersion(), skipped, cache,
|
|
1795
|
+
lookup: (signal) => latestRelease((url, options) => fetch(url, { ...options, signal })),
|
|
1796
|
+
// Only a live lookup reaches this: persisting it keeps the next launches
|
|
1797
|
+
// off the network. A failed write is retried by tomorrow's stale cache.
|
|
1798
|
+
cacheWrite: (latest) => {
|
|
1799
|
+
try {
|
|
1800
|
+
writeStartupReleaseCache(() => readJsonIfExists(configPath), (text) => {
|
|
1801
|
+
mkdirSync(dirname(configPath), { recursive: true, mode: 0o700 });
|
|
1802
|
+
writeFileAtomically(configPath, text, { mode: 0o600 });
|
|
1803
|
+
}, latest);
|
|
1804
|
+
} catch { /* A notice must not prevent opening. */ }
|
|
1805
|
+
}, });
|
|
1744
1806
|
if (!offer) return undefined;
|
|
1745
1807
|
let choice;
|
|
1746
1808
|
try { choice = await promptStartupUpdate(offer, { color: process.stdout.hasColors?.() === true }); }
|
|
@@ -1778,16 +1840,31 @@ async function main() {
|
|
|
1778
1840
|
if (args.error !== undefined) fail(`${args.error}\nRun 'gentle-shell --help' for usage.`, 2);
|
|
1779
1841
|
if (args.help) {
|
|
1780
1842
|
process.stdout.write("Pi updates: update reinstalls the verified supplier of the Pi this launcher runs (a global Pi, or the\nShell package whose adjacent peer it is, at the same Shell version) and reads the resulting Pi version back.\nUnknown ownership refuses without installing; update --extensions-only stays Pi-only.\nPrefix-bundled Pi updates with gentle-shell upgrade.\n");
|
|
1781
|
-
process.stdout.write(`${helpText()}\nBundled Engram
|
|
1843
|
+
process.stdout.write(`${helpText()}\nBundled Engram memory: Shell shares the global Engram server on 127.0.0.1:7437, like other agents' hooks.\nA running server is reused only with verified /compatibility evidence; when nothing listens, Shell starts the\nbundled core's engram serve (default ~/.engram) and waits for that evidence. Otherwise ENGRAM_BIN is Shell's\nguard and memory stays off for that launch. Matching versions or updating alone do not prove compatibility.\nBack up existing data before manual changes; do not delete it or bypass the safety check.\nShell rechecks on every launch and remains usable without memory.\nAn explicit ENGRAM_BIN or ENGRAM_URL is kept and bypasses the guard.\n`);
|
|
1782
1844
|
process.exit(0);
|
|
1783
1845
|
}
|
|
1784
1846
|
if (args.command === "home") {
|
|
1785
|
-
|
|
1847
|
+
if (args.commandArgs[0] === "import-pi") await handleImportPiCommand(args);
|
|
1848
|
+
else handleHomeCommand(args.commandArgs);
|
|
1786
1849
|
return;
|
|
1787
1850
|
}
|
|
1788
1851
|
// The verification input applies to `--version` only (scripts/bundled-install.mjs upgradeBundled).
|
|
1789
1852
|
const verifying = args.version && args.command === undefined ? process.env[BUNDLED_VERIFY_ENV] : undefined;
|
|
1790
1853
|
const bundled = resolveBundledInstall({ packageRoot, platform: process.platform, fs: bundledFs, verifying });
|
|
1854
|
+
if (args.command === "memory") {
|
|
1855
|
+
if (args.commandArgs.length !== 2 || args.commandArgs[0] !== "migrate" || args.commandArgs[1] !== "--confirm-quiescent") {
|
|
1856
|
+
process.stderr.write(MIGRATION_USAGE);
|
|
1857
|
+
process.exitCode = 2;
|
|
1858
|
+
return;
|
|
1859
|
+
}
|
|
1860
|
+
if (!bundled) fail("gentle-shell: memory migrate requires a prefix-bundled installation", 1);
|
|
1861
|
+
bundledInstall = bundled;
|
|
1862
|
+
const result = migrateBundledEngram({ prefix: bundled.root, core: verifiedBundledCore(), home: homedir(), env: process.env });
|
|
1863
|
+
process.stdout.write(result.stdout);
|
|
1864
|
+
process.stderr.write(result.stderr);
|
|
1865
|
+
process.exitCode = result.code;
|
|
1866
|
+
return;
|
|
1867
|
+
}
|
|
1791
1868
|
if (args.command === "upgrade") {
|
|
1792
1869
|
if (bundled) process.exitCode = await handleBundledUpgrade(bundled, args.commandArgs);
|
|
1793
1870
|
else await handleUpgradeCommand(args.commandArgs);
|
|
@@ -1810,7 +1887,7 @@ async function main() {
|
|
|
1810
1887
|
if (home.mode === "path") home = { ...home, dir: resolvePath(home.dir) };
|
|
1811
1888
|
|
|
1812
1889
|
// Version and help runs (Shell's own or forwarded to Pi) never probe Engram,
|
|
1813
|
-
// ensure the companion, print memory notices or
|
|
1890
|
+
// ensure the companion, print memory notices or start a server.
|
|
1814
1891
|
const engramQuiet = args.version || piVersionOrHelpRequested(args.passthrough);
|
|
1815
1892
|
let runtime;
|
|
1816
1893
|
if (bundled) {
|
|
@@ -1843,7 +1920,11 @@ async function main() {
|
|
|
1843
1920
|
});
|
|
1844
1921
|
if (versionProbe.error) fail(`Could not run the pi runtime at "${runtime.command}": ${versionProbe.error.message}`, 1);
|
|
1845
1922
|
const versionCheck = checkPiVersion(versionProbe.stdout ?? "");
|
|
1846
|
-
|
|
1923
|
+
// A managed self update is how an old Pi gets replaced, so the minimum-version
|
|
1924
|
+
// gate must not block it; everything else still requires the minimum.
|
|
1925
|
+
const managedSelfUpdate = !bundled && args.piSubcommand === "update" && versionCheck.version !== undefined
|
|
1926
|
+
&& (await import("../scripts/pi-update.mjs")).managedUpdatePlan(args.passthrough).self;
|
|
1927
|
+
if (!versionCheck.ok && !managedSelfUpdate) fail(versionCheck.message, 1);
|
|
1847
1928
|
|
|
1848
1929
|
if (args.version) {
|
|
1849
1930
|
process.stdout.write(`${describeVersion({ gentlePiVersion: ownPackageVersion(), piVersion: versionCheck.version, home })}\n`);
|
|
@@ -1869,7 +1950,7 @@ async function main() {
|
|
|
1869
1950
|
const { managedUpdatePlan, updateSelectedPi } = await import("../scripts/pi-update.mjs");
|
|
1870
1951
|
const plan = managedUpdatePlan(args.passthrough);
|
|
1871
1952
|
if (plan.self) {
|
|
1872
|
-
process.exitCode = await updateSelectedPi({ runtime, packageRoot, platform: process.platform, currentVersion: versionCheck.version, cwd: tmpdir(),
|
|
1953
|
+
process.exitCode = await updateSelectedPi({ runtime, packageRoot, platform: process.platform, currentVersion: versionCheck.version, cwd: tmpdir(), env: process.env, home: homedir(),
|
|
1873
1954
|
adapters: await packageManagerAdapters(), out: (line) => process.stdout.write(`${line}\n`) });
|
|
1874
1955
|
if (plan.piArgs === undefined) return;
|
|
1875
1956
|
}
|
|
@@ -1893,12 +1974,22 @@ async function main() {
|
|
|
1893
1974
|
// Isolated-home bootstrap: only on a home gentle-shell has not seen before
|
|
1894
1975
|
// (link never bootstraps — it reuses the user's own pi agent home as-is).
|
|
1895
1976
|
if ((home.mode === "isolated" || home.mode === "path") && !existsSync(home.dir)) {
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1977
|
+
await bootstrapOwnedHome(home);
|
|
1978
|
+
const piSignIns = existsSync(join(userPiHome(process.env, homedir()), "auth.json"));
|
|
1979
|
+
process.stderr.write(`${separateHomeHint(home, { piSignIns })}\n`);
|
|
1980
|
+
}
|
|
1981
|
+
|
|
1982
|
+
// #2201: the standalone isolated home's own config folder, seeded once from
|
|
1983
|
+
// the shared ~/.pi/gentle-ai. Not when an explicit GENTLE_PI_CONFIG_HOME
|
|
1984
|
+
// decides the config home; never blocks the launch.
|
|
1985
|
+
const configHome = bundled || process.env.GENTLE_PI_CONFIG_HOME ? undefined : standaloneConfigHome(home);
|
|
1986
|
+
if (configHome !== undefined) {
|
|
1987
|
+
try {
|
|
1988
|
+
const migration = migrateStandaloneConfig({ source: join(homedir(), ".pi", "gentle-ai"), target: configHome, fs: configMigrationFs });
|
|
1989
|
+
for (const line of migration.lines) process.stderr.write(`${line}\n`);
|
|
1990
|
+
} catch (error) {
|
|
1991
|
+
process.stderr.write(`gentle-shell: could not prepare ${configHome} (${error.message}); continuing\n`);
|
|
1992
|
+
}
|
|
1902
1993
|
}
|
|
1903
1994
|
|
|
1904
1995
|
if (bundled) {
|
|
@@ -1907,6 +1998,12 @@ async function main() {
|
|
|
1907
1998
|
} catch (error) {
|
|
1908
1999
|
process.stderr.write(`gentle-shell: could not give Pi the bundled npm in ${join(home.dir, "settings.json")} (${error.message}); continuing\n`);
|
|
1909
2000
|
}
|
|
2001
|
+
} else {
|
|
2002
|
+
try {
|
|
2003
|
+
await removeMissingNpmCommand(home, homeHadContentBeforeBootstrap);
|
|
2004
|
+
} catch (error) {
|
|
2005
|
+
process.stderr.write(`gentle-shell: could not repair the npmCommand in ${join(home.dir, "settings.json")} (${error.message}); continuing\n`);
|
|
2006
|
+
}
|
|
1910
2007
|
}
|
|
1911
2008
|
|
|
1912
2009
|
if (args.command === "setup") {
|
|
@@ -2040,7 +2137,7 @@ async function main() {
|
|
|
2040
2137
|
|
|
2041
2138
|
if (bundled) {
|
|
2042
2139
|
// Recheck after setup and before provider loading: never carry a stale
|
|
2043
|
-
//
|
|
2140
|
+
// decision across an installer that could create data.
|
|
2044
2141
|
if (args.piSubcommand === undefined && !engramQuiet) {
|
|
2045
2142
|
bundledMemory = await classifyBundledMemory();
|
|
2046
2143
|
bundledEnv = bundledProcessEnv();
|
|
@@ -2048,9 +2145,9 @@ async function main() {
|
|
|
2048
2145
|
// could not be verified is reported, never a reason to stop Shell.
|
|
2049
2146
|
const companion = await ensureEngramCompanion(home, runtime, ["ignore", 2, 2], 10000);
|
|
2050
2147
|
if (!companion.ok) process.stderr.write(`${companion.message}\n`);
|
|
2051
|
-
//
|
|
2052
|
-
//
|
|
2053
|
-
if (bundledMemory.
|
|
2148
|
+
// Only this final decision starts the shared server, and Pi gets its
|
|
2149
|
+
// URL only once the server verified.
|
|
2150
|
+
if (bundledMemory.spawn) bundledMemory = await startSharedEngram();
|
|
2054
2151
|
if (bundledMemory.notice) process.stderr.write(`gentle-shell: ${bundledMemory.notice}\n`);
|
|
2055
2152
|
}
|
|
2056
2153
|
bundledEnv = bundledProcessEnv();
|
|
@@ -2066,6 +2163,7 @@ async function main() {
|
|
|
2066
2163
|
passthrough: [...managedHerdrExtensionArgs(home, args), ...passthrough],
|
|
2067
2164
|
piSubcommand: args.piSubcommand,
|
|
2068
2165
|
baseEnv: bundledEnv ?? process.env,
|
|
2166
|
+
bundled: Boolean(bundled),
|
|
2069
2167
|
homedir: homedir(),
|
|
2070
2168
|
// The spawn below sets no cwd, so pi runs in the launcher's own.
|
|
2071
2169
|
cwd: process.cwd(),
|
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schema": "gentle-pi.review-provider-contract-mirror-lock/v1",
|
|
3
3
|
"acquisition": "field-test-local",
|
|
4
|
-
"contract_semver": "1.
|
|
4
|
+
"contract_semver": "1.4.0",
|
|
5
5
|
"source": {
|
|
6
6
|
"kind": "archive",
|
|
7
|
-
"archive_sha256": "
|
|
7
|
+
"archive_sha256": "9d2927a84ba9a596f69029e3f854b16f86ca4c8b32cc5d63683764ccf2fb394b"
|
|
8
8
|
},
|
|
9
|
-
"tree_sha256": "
|
|
9
|
+
"tree_sha256": "aab5ab7fe7ee6bb180d8bbce21c034cebb0c0d284afcc8664a9b3bb57323c35d",
|
|
10
10
|
"entries": {
|
|
11
|
-
"README.md": "
|
|
12
|
-
"manifest.json": "
|
|
11
|
+
"README.md": "3a655c2f19aee02636da062f0cae7a189b7e5f691c8ba8010f2ed50e5d8e8d70",
|
|
12
|
+
"manifest.json": "d2b5765e28df5b2459848379e91a27beb806916fabc96207060ad1cc5e911972",
|
|
13
13
|
"orchestration/pi.md": "2d7ddd99f1c739a97f4a6b3929e5e2bdddcbf69aed69356395038d90bca25b2f",
|
|
14
|
+
"routing/pi.md": "1c7466e2333c08e386b4b5afb081531146d181030692463431e7695df7f90690",
|
|
14
15
|
"schemas/lens.schema.json": "9b8a2354a570888486015434501b66b51441a3baf5bf858635e4fff56b2e8298",
|
|
15
16
|
"schemas/refuter.schema.json": "36f8267c6c3f04600b8f1cb77df70d90858611af822cc81922efd6b3502fb646",
|
|
16
17
|
"schemas/targeted-validator.schema.json": "40e397195bdfbc6782fc3090f20c5be082e4d14d2855314f99ecb8506133eddc",
|
|
@@ -19,8 +20,8 @@
|
|
|
19
20
|
"vectors/targeted-validator.json": "7b5a5165e3a913863fb98d2ba5387811860c21333b5c37e6ed3b4e0d121cf2fa"
|
|
20
21
|
},
|
|
21
22
|
"generated": {
|
|
22
|
-
"generated/provider-capabilities.baseline.json": "
|
|
23
|
-
"generated/provider-roles.baseline.json": "
|
|
23
|
+
"generated/provider-capabilities.baseline.json": "bca708f5f67f506b4acb2cfdcb775b02a51468579fc7d6b4d169785ff411a215",
|
|
24
|
+
"generated/provider-roles.baseline.json": "aa36ec6f7469ac6faab756eec681f68f234cbadc1e12b345a93c6c34aa01f7aa"
|
|
24
25
|
},
|
|
25
26
|
"runtimes": [
|
|
26
27
|
"claude-code",
|
|
@@ -19,4 +19,11 @@ It carries no executable content, same as every other file in this bundle: a
|
|
|
19
19
|
runtime mirrors and delivers the text as-is and still relies on Go for every
|
|
20
20
|
review decision, prompt, receipt, and delivery gate.
|
|
21
21
|
|
|
22
|
+
## Routing
|
|
23
|
+
|
|
24
|
+
manifest.json's routing array lists, for closed runtimes only, one
|
|
25
|
+
routing/<runtime>.md file: the exact ODD implementation-routing block Gentle
|
|
26
|
+
AI renders for that runtime, whose prompt Gentle AI does not install. It is
|
|
27
|
+
data only as well: a runtime mirrors and delivers the text as-is.
|
|
28
|
+
|
|
22
29
|
Go remains the admission authority for prompts, results, receipts, and delivery gates.
|