gentle-pi 4.3.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 +8 -5
- package/assets/agents/gentle-ai-worker.md +1 -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 +2 -2
- package/assets/orchestrator-verification.md +1 -1
- package/assets/orchestrator.md +2 -2
- package/bin/engram-guard +1 -1
- package/bin/gentle-shell.mjs +101 -151
- package/docs/bundled-install.md +47 -42
- package/docs/delegated-verification.md +6 -0
- package/docs/gentle-agents-activity.md +5 -1
- package/docs/gentle-music.md +61 -0
- package/docs/gentle-shell.md +77 -7
- package/docs/install-wizard.md +30 -6
- package/docs/permission-modes.md +7 -2
- package/docs/readme-reference.md +19 -15
- package/docs/session-profile-format.md +26 -0
- package/docs/voice.md +259 -0
- package/docs/yolo-mode.md +17 -2
- package/extensions/ask-user-choice.ts +4 -1
- package/extensions/gentle-agents.ts +173 -38
- package/extensions/gentle-ai.ts +214 -69
- package/extensions/gentle-design.ts +158 -0
- package/extensions/gentle-music.ts +289 -0
- package/extensions/gentle-shell.ts +332 -73
- package/extensions/gentle-voice.ts +11 -0
- package/extensions/session-scratch.ts +41 -22
- package/extensions/skill-registry.ts +63 -8
- package/lib/agent-profiles.ts +26 -0
- package/lib/agents-config.ts +35 -0
- package/lib/agents-protocol.ts +57 -1
- package/lib/agents-runner.ts +52 -3
- package/lib/agents-session-transport.ts +101 -6
- 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/clipboard-image.ts +147 -0
- package/lib/command-palette-catalog.ts +2 -0
- package/lib/commit-slice-signal.ts +1 -1
- package/lib/context-backstop.ts +36 -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/external-editor.ts +31 -0
- package/lib/gentle-design.ts +513 -0
- package/lib/gentle-shell-bundled.ts +123 -49
- package/lib/gentle-shell-launcher.ts +9 -4
- package/lib/gentle-shell-startup-update.ts +61 -9
- package/lib/gentle-shell-uninstall.ts +33 -1
- 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 +11 -4
- 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-presence.ts +78 -8
- package/lib/orchestrator-scope.ts +10 -0
- package/lib/orchestrator-state.ts +11 -3
- package/lib/permission-mode-policy.ts +2 -2
- package/lib/review-host-relay.ts +11 -2
- package/lib/review-reminder-receipt.ts +24 -1
- package/lib/session-profile-append-controller.ts +713 -0
- package/lib/session-profile-disk-reader.ts +182 -0
- package/lib/session-profile-persistence.ts +15 -7
- package/lib/session-scratch.ts +52 -3
- package/lib/session-worktree-registry.ts +7 -0
- package/lib/shell-bar.ts +4 -3
- package/lib/shell-changes.ts +81 -5
- package/lib/shell-prompt.ts +4 -4
- package/lib/shell-sidebar-layout.ts +26 -6
- package/lib/shell-todo.ts +1 -1
- package/lib/verifier-admission.ts +5 -4
- 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/package.json +1 -1
- package/runtime/gentle-shell-bundled.mjs +122 -48
- package/runtime/gentle-shell-launcher.mjs +9 -4
- package/runtime/gentle-shell-startup-update.mjs +61 -9
- package/runtime/gentle-shell-uninstall.mjs +33 -1
- package/runtime/native-review-cli.mjs +11 -4
- package/scripts/bundled-engram-migration.mjs +0 -6
- 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-windows.mjs +37 -7
- package/scripts/main-channel.mjs +91 -17
- package/scripts/pi-update.mjs +34 -6
- package/scripts/verify-package-files.mjs +26 -2
- package/scripts/voice-worker.cjs +476 -0
- package/skills/design/SKILL.md +110 -0
- package/tests/agent-profiles.test.ts +24 -0
- 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 +226 -5
- 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 +20 -0
- package/tests/bundled-install.test.ts +3 -3
- package/tests/clipboard-image.test.ts +114 -0
- package/tests/command-palette.test.ts +1 -1
- package/tests/context-backstop.test.ts +70 -4
- package/tests/delegate-for-reason-contract.test.ts +2 -1
- 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/engram-safety.test.ts +132 -57
- package/tests/external-editor.test.ts +84 -0
- package/tests/fixtures/primary-session-prompt/gentleman.golden.md +3 -3
- package/tests/gentle-agents.test.ts +365 -20
- package/tests/gentle-ai-binary.test.ts +1 -1
- 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-bundled.test.ts +301 -248
- package/tests/gentle-shell-launcher.test.ts +28 -2
- package/tests/gentle-shell-startup-update.test.ts +77 -1
- package/tests/gentle-shell-uninstall.test.ts +59 -0
- package/tests/gentle-shell.test.ts +183 -8
- package/tests/gentle-todo.test.ts +11 -0
- 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 +2 -2
- package/tests/installer-windows-bootstrap.test.ts +61 -3
- 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 +11 -4
- package/tests/native-review-parity.test.ts +1 -1
- package/tests/notification-audio-native.test.ts +128 -9
- package/tests/notification-customize.test.ts +17 -5
- package/tests/odd-routing-canonical-ratchet.test.ts +2 -1
- package/tests/orchestrator-budget.test.ts +5 -2
- package/tests/orchestrator-presence.test.ts +100 -2
- package/tests/orchestrator-rdd-ownership.test.ts +2 -2
- package/tests/orchestrator-scope.test.ts +14 -0
- package/tests/orchestrator-state.test.ts +29 -0
- package/tests/package-files.test.ts +34 -0
- package/tests/package-manifest.test.ts +6 -6
- package/tests/permission-mode-policy.test.ts +10 -1
- package/tests/pi-update.test.ts +99 -4
- package/tests/rdd-aware-verification-contract.test.ts +1 -1
- package/tests/review-host-relay-routing.test.ts +88 -1
- 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 +23 -2
- package/tests/review-relay-transport-agent.test.ts +26 -0
- package/tests/review-reminder-receipt.test.ts +31 -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-scratch.test.ts +68 -2
- package/tests/session-worktree-registry.test.ts +13 -1
- package/tests/shell-bar.test.ts +14 -2
- package/tests/shell-changes.test.ts +148 -1
- 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/task-size-routing-contract.test.ts +1 -1
- package/tests/test-strength-contract.test.ts +1 -1
- package/tests/verifier-admission.test.ts +18 -1
- 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 +25 -2
- package/tests/yolo-customize.test.ts +5 -1
- package/tests/yolo-mode.test.ts +1 -1
- package/tests/yolo-session-policy-capture.test.ts +104 -0
|
@@ -203,10 +203,10 @@ export const NATIVE_REVIEW_MODE_SCOPE = {
|
|
|
203
203
|
// stream while still succeeding. That last qualifier is what keeps this list
|
|
204
204
|
// honest: v2.4.0 deleted reviewConsentSkippedDefaultProvenance ("Reviews are
|
|
205
205
|
// on by default; this was never explicitly chosen. ..."), which used to ride
|
|
206
|
-
// with the skip notice whenever the resolved mode source was `default`.
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
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-
|
|
210
210
|
// line stderr is still expected in principle — these are separate Fprintln
|
|
211
211
|
// calls, never one joined string — which is why membership is per line.
|
|
212
212
|
export const REVIEW_CONSENT_NOTICES = Object.freeze([
|
|
@@ -1091,6 +1091,13 @@ export const NATIVE_CLI_CONTRACTS = Object.freeze({
|
|
|
1091
1091
|
// 9d2927a84ba9a596f69029e3f854b16f86ca4c8b32cc5d63683764ccf2fb394b.
|
|
1092
1092
|
// No new negotiated capability identity is asserted.
|
|
1093
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 }),
|
|
1094
1101
|
});
|
|
1095
1102
|
|
|
1096
1103
|
|
|
@@ -43,12 +43,6 @@ function coreHelp(core, env) {
|
|
|
43
43
|
function supportsMigration(core, env) {
|
|
44
44
|
return coreHelp(core, env).includes("migrate-copy --data-dir <absolute-source> --output-dir <new-absolute-destination> --confirm-quiescent --json");
|
|
45
45
|
}
|
|
46
|
-
export function supportsBundledSessionOwned(core, env) {
|
|
47
|
-
if (!core) return false;
|
|
48
|
-
const clean = Object.fromEntries(Object.entries(env).filter(([key]) => !key.startsWith("ENGRAM_") && key !== "GH_TOKEN" && key !== "GITHUB_TOKEN"));
|
|
49
|
-
try { return coreHelp(core, { ...clean, ENGRAM_NO_UPDATE_CHECK: "1" }).some((line) => /^serve --session-owned(?:\s|$)/.test(line)); }
|
|
50
|
-
catch { return false; }
|
|
51
|
-
}
|
|
52
46
|
|
|
53
47
|
export function migrateBundledEngram({ prefix, core, home, env }) {
|
|
54
48
|
let markerOwned = false;
|
|
@@ -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
|
|
@@ -107,6 +107,36 @@ function exists(path) {
|
|
|
107
107
|
try { lstatSync(path); return true; }
|
|
108
108
|
catch (error) { if (error.code === "ENOENT") return false; throw error; }
|
|
109
109
|
}
|
|
110
|
+
// Failure-only diagnostics, in UTF-16 code units. Suppress entire sensitive
|
|
111
|
+
// streams (including continuation lines), and individual path/SID/opaque lines.
|
|
112
|
+
// This is conservative diagnostic filtering, not a general-purpose secret parser.
|
|
113
|
+
function processDiagnosticText(value, limit) {
|
|
114
|
+
if (typeof value !== "string") return "";
|
|
115
|
+
if (/(?:^|[^a-z0-9])(?:authorization|bearer|token|password|passphrase|secret|credential|api[_-]?key|access[_-]?key|cookie)(?:$|[^a-z0-9])/i.test(value)) return "[redacted]";
|
|
116
|
+
return value.split(/\r\n?|\n/).map(line =>
|
|
117
|
+
/[A-Za-z]:[\\/]|\\|(?:^|[\s"'=])\/\S+|\bS-\d-(?:\d+-)+\d+\b|unsafe:[^|]*\||[A-Za-z0-9+/=_-]{64,}|[\x00-\x08\x0b\x0c\x0e-\x1f\x7f]/i.test(line) ? "[redacted]" : line
|
|
118
|
+
).join("\n").slice(0, limit);
|
|
119
|
+
}
|
|
120
|
+
function prerequisiteProcessCause(result) {
|
|
121
|
+
const failure = result.error;
|
|
122
|
+
const code = processDiagnosticText(failure?.code, 64);
|
|
123
|
+
const details = {
|
|
124
|
+
status: Number.isSafeInteger(result.status) ? result.status : null,
|
|
125
|
+
signal: processDiagnosticText(result.signal, 32) || null,
|
|
126
|
+
error: failure ? {
|
|
127
|
+
code, name: processDiagnosticText(failure.name, 64),
|
|
128
|
+
// Spawn text may contain a bare private executable name, not just paths.
|
|
129
|
+
message: /^E[A-Z0-9_]+$/.test(code) ? `Windows prerequisite process error: ${code}` : "Windows prerequisite process error",
|
|
130
|
+
errno: Number.isSafeInteger(failure.errno) ? failure.errno : processDiagnosticText(failure.errno, 64),
|
|
131
|
+
} : null,
|
|
132
|
+
stdout: processDiagnosticText(result.stdout, 1024), stderr: processDiagnosticText(result.stderr, 1024),
|
|
133
|
+
};
|
|
134
|
+
const cause = Object.assign(new Error("Windows prerequisite process diagnostics"), details);
|
|
135
|
+
// Existing evidence consumers read cause.stack only; omit local callsites and
|
|
136
|
+
// never attach the raw spawn Error (which carries command/path/argv fields).
|
|
137
|
+
cause.stack = `Error: ${cause.message}\n${JSON.stringify(details)}`.slice(0, 8192);
|
|
138
|
+
return cause;
|
|
139
|
+
}
|
|
110
140
|
export function windowsProcessCheck(command, args, env) {
|
|
111
141
|
// Owned direct children only, not a tree/global kill. Descendant-held stdio
|
|
112
142
|
// is outside this guarantee. Never execute .cmd through a command interpreter.
|
|
@@ -114,7 +144,7 @@ export function windowsProcessCheck(command, args, env) {
|
|
|
114
144
|
// A neutral cwd: inside a project (the bundle itself), pnpm 11 reports, and may
|
|
115
145
|
// fetch, the version that project's packageManager pins instead of its own.
|
|
116
146
|
const result = spawnSync(command, args, { cwd: parse(command).root || undefined, env, shell: false, encoding: "utf8", timeout: 15000, killSignal: "SIGKILL", maxBuffer: 1024 * 1024, windowsHide: true });
|
|
117
|
-
if (result.error || result.status !== 0) throw new Error("Windows prerequisite process failed");
|
|
147
|
+
if (result.error || result.status !== 0) throw new Error("Windows prerequisite process failed", { cause: prerequisiteProcessCause(result) });
|
|
118
148
|
return result.stdout.trim();
|
|
119
149
|
}
|
|
120
150
|
|
|
@@ -151,9 +181,9 @@ try {
|
|
|
151
181
|
$depth = 0;
|
|
152
182
|
while ($path) {
|
|
153
183
|
$role = 'ancestor'; if ($depth -eq 0) { $role = 'target' } elseif ($depth -eq 1) { $role = 'parent' };
|
|
154
|
-
$
|
|
155
|
-
if ($
|
|
156
|
-
if ($
|
|
184
|
+
$attributes = [IO.File]::GetAttributes($path);
|
|
185
|
+
if ($attributes -band [IO.FileAttributes]::ReparsePoint) { $detail = $path + '|||'; throw ($role + '-reparse') };
|
|
186
|
+
if ($attributes -band [IO.FileAttributes]::Directory) { $acl = [IO.Directory]::GetAccessControl($path) } else { $acl = [IO.File]::GetAccessControl($path) };
|
|
157
187
|
$owner = $acl.GetOwner([Security.Principal.SecurityIdentifier]);
|
|
158
188
|
if ($trusted -notcontains $owner.Value) { $detail = $path + '|' + $owner.Value + '|' + (& $account $owner) + '|'; throw ($role + '-owner') };
|
|
159
189
|
$allowedRights = 0x1200a9; if ($depth -ge 2) { $allowedRights = 0x1200ad };
|
|
@@ -186,9 +216,9 @@ foreach ($start in $env:GENTLE_WINDOWS_CHECKS.Split('|')) {
|
|
|
186
216
|
$depth = 0;
|
|
187
217
|
while ($path) {
|
|
188
218
|
$role = 'ancestor'; if ($depth -eq 0) { $role = 'target' } elseif ($depth -eq 1) { $role = 'parent' };
|
|
189
|
-
$
|
|
190
|
-
if ($
|
|
191
|
-
if ($
|
|
219
|
+
$attributes = [IO.File]::GetAttributes($path);
|
|
220
|
+
if ($attributes -band [IO.FileAttributes]::ReparsePoint) { $detail = $path + '|||'; throw ($role + '-reparse') };
|
|
221
|
+
if ($attributes -band [IO.FileAttributes]::Directory) { $acl = [IO.Directory]::GetAccessControl($path) } else { $acl = [IO.File]::GetAccessControl($path) };
|
|
192
222
|
$owner = $acl.GetOwner([Security.Principal.SecurityIdentifier]);
|
|
193
223
|
if ($trusted -notcontains $owner.Value) { $detail = $path + '|' + $owner.Value + '|' + (& $account $owner) + '|'; throw ($role + '-owner') };
|
|
194
224
|
$allowedRights = 0x1200a9; if ($depth -ge 2) { $allowedRights = 0x1200ad };
|
package/scripts/main-channel.mjs
CHANGED
|
@@ -4,9 +4,11 @@
|
|
|
4
4
|
// an exact commit SHA resolved at install or upgrade time:
|
|
5
5
|
// - Gentle AI: `go install <module>@<sha>` in a sealed Go environment, so the
|
|
6
6
|
// module source is verified by Go's checksum database (sum.golang.org), as
|
|
7
|
-
// the Windows release build already is.
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// the Windows release build already is. Go must resolve the commit to a
|
|
8
|
+
// module version (a tag or a pseudo-version) whose origin is that commit,
|
|
9
|
+
// and the binary must embed that version and report it, then it is
|
|
10
|
+
// activated through the existing dev-binary override, which never falls
|
|
11
|
+
// back to the pinned binary silently.
|
|
10
12
|
// - Gentle Shell: the exact commit's source tarball, versioned
|
|
11
13
|
// `<version>-main.<sha12>` and packed without `prepack` (which runs the full
|
|
12
14
|
// test suite), then installed globally like any local tarball.
|
|
@@ -18,13 +20,14 @@ import { goAcquisition, pnpmGlobalBin, requirements } from "./installer-prefligh
|
|
|
18
20
|
|
|
19
21
|
export const SHELL_REPOSITORY = "Gentleman-Programming/gentle-shell";
|
|
20
22
|
export const GENTLE_AI_REPOSITORY = "Gentleman-Programming/gentle-ai";
|
|
23
|
+
export const GENTLE_AI_MODULE = "github.com/gentleman-programming/gentle-ai/v4";
|
|
21
24
|
export const GENTLE_AI_MAIN_PACKAGE = "github.com/gentleman-programming/gentle-ai/v4/cmd/gentle-ai";
|
|
22
25
|
export const CHANNEL_SCHEMA = "gentle-shell.channel/v1";
|
|
23
26
|
export const CHANNELS = Object.freeze(["release", "main"]);
|
|
24
27
|
|
|
25
28
|
const SHA = /^[0-9a-f]{40}$/;
|
|
26
29
|
const MINUTE = 60_000;
|
|
27
|
-
const deadlines = Object.freeze({ build: 15 * MINUTE, version: 30_000, extract: 2 * MINUTE, pack: 5 * MINUTE });
|
|
30
|
+
const deadlines = Object.freeze({ build: 15 * MINUTE, resolve: 2 * MINUTE, version: 30_000, extract: 2 * MINUTE, pack: 5 * MINUTE });
|
|
28
31
|
const MAX_SOURCE_BYTES = 100 * 1024 * 1024;
|
|
29
32
|
|
|
30
33
|
export class MainChannelError extends Error {
|
|
@@ -136,23 +139,57 @@ export async function buildMainGentleAi({ commit, ctx, platform, goPath, run, fs
|
|
|
136
139
|
const executable = platform === "win32" ? "gentle-ai.exe" : "gentle-ai";
|
|
137
140
|
const directory = join(configHome(ctx), "main", "gentle-ai", commit);
|
|
138
141
|
const buildDirectory = join(configHome(ctx), "main", ".build");
|
|
142
|
+
// Verified in the work folder before it replaces anything, so a failed rebuild of
|
|
143
|
+
// an already registered commit never touches the override; the final move stays
|
|
144
|
+
// on one filesystem, and uninstall already accepts a leftover .build.
|
|
145
|
+
const staging = join(buildDirectory, commit);
|
|
139
146
|
await fs.rm(buildDirectory, { recursive: true, force: true });
|
|
140
147
|
const env = sealedGoEnvironment(goPath, buildDirectory, platform);
|
|
141
148
|
for (const path of [env.GOBIN, env.GOPATH, env.GOMODCACHE, env.GOCACHE, join(buildDirectory, "tmp")]) await fs.mkdir(path, { recursive: true });
|
|
142
149
|
try {
|
|
143
150
|
const install = await run(goPath, ["install", `${GENTLE_AI_MAIN_PACKAGE}@${commit}`], { env, cwd: buildDirectory, deadlineMs: deadlines.build, stderrTail: 4096 });
|
|
144
151
|
if (!succeeded(install)) throw new MainChannelError("main-gentle-ai-build-failed", `go install ${GENTLE_AI_MAIN_PACKAGE}@${commit} failed`, stderrCause(install));
|
|
145
|
-
await fs.mkdir(
|
|
146
|
-
const
|
|
147
|
-
await fs.copyFile(join(env.GOBIN, executable),
|
|
148
|
-
if (platform !== "win32") await fs.chmod(
|
|
149
|
-
|
|
150
|
-
|
|
152
|
+
await fs.mkdir(staging, { recursive: true });
|
|
153
|
+
const stagedPath = join(staging, executable);
|
|
154
|
+
await fs.copyFile(join(env.GOBIN, executable), stagedPath);
|
|
155
|
+
if (platform !== "win32") await fs.chmod(stagedPath, 0o755);
|
|
156
|
+
const reject = async (message, cause) => {
|
|
157
|
+
throw new MainChannelError("main-gentle-ai-unverified", message, cause);
|
|
158
|
+
};
|
|
159
|
+
const short = commit.slice(0, 12);
|
|
160
|
+
const goOptions = { env, cwd: buildDirectory, deadlineMs: deadlines.version, stderrTail: 4096 };
|
|
161
|
+
// Go resolves the commit through the proxy and checksum database: a tag commit is
|
|
162
|
+
// stamped with its tag, any other with a pseudo-version, so the version is not guessed.
|
|
163
|
+
// Resolving may reach the proxy, so it gets a network deadline, not the local one.
|
|
164
|
+
const listed = await run(goPath, ["list", "-m", "-json", `${GENTLE_AI_MODULE}@${commit}`], { ...goOptions, deadlineMs: deadlines.resolve });
|
|
165
|
+
if (!succeeded(listed)) return await reject(`Go could not resolve Gentle AI commit ${short}`, stderrCause(listed));
|
|
166
|
+
let resolved = null;
|
|
167
|
+
try {
|
|
168
|
+
resolved = JSON.parse(String(listed.stdout ?? ""));
|
|
169
|
+
} catch {
|
|
170
|
+
return await reject(`Go returned unreadable module information for Gentle AI commit ${short}`);
|
|
171
|
+
}
|
|
172
|
+
const resolvedVersion = typeof resolved?.Version === "string" ? resolved.Version : "";
|
|
173
|
+
const originHash = resolved?.Origin?.Hash;
|
|
174
|
+
if (resolvedVersion === "") return await reject(`Go resolved Gentle AI commit ${short} to no module version`);
|
|
175
|
+
if (originHash !== commit) {
|
|
176
|
+
return await reject(`Go resolved Gentle AI commit ${short} to ${originHash ? `commit ${originHash}` : "no commit"}, not the requested commit`);
|
|
177
|
+
}
|
|
178
|
+
const embedded = await run(goPath, ["version", "-m", stagedPath], goOptions);
|
|
179
|
+
if (!succeeded(embedded)) return await reject("the built Gentle AI's embedded module version could not be read", stderrCause(embedded));
|
|
180
|
+
const modLine = String(embedded.stdout ?? "").split(/\r?\n/).map((line) => line.trim().split(/\s+/)).find((fields) => fields[0] === "mod" && fields[1] === GENTLE_AI_MODULE);
|
|
181
|
+
if (modLine?.[2] !== resolvedVersion) {
|
|
182
|
+
return await reject(`the built Gentle AI embeds module version ${modLine?.[2] ?? "none"}, expected ${resolvedVersion} for commit ${short}`);
|
|
183
|
+
}
|
|
184
|
+
const probe = await run(stagedPath, ["version"], { env: { PATH: env.PATH }, deadlineMs: deadlines.version });
|
|
151
185
|
const match = succeeded(probe) && /^gentle-ai (\S+)$/m.exec(String(probe.stdout ?? ""));
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
186
|
+
const expectedVersion = resolvedVersion.replace(/^v/, "");
|
|
187
|
+
if (!match || match[1] !== expectedVersion) {
|
|
188
|
+
return await reject(`the built Gentle AI reports version ${match ? match[1] : "none"}, expected ${expectedVersion} for commit ${short}`);
|
|
155
189
|
}
|
|
190
|
+
await fs.mkdir(directory, { recursive: true });
|
|
191
|
+
const binaryPath = join(directory, executable);
|
|
192
|
+
await fs.rename(stagedPath, binaryPath);
|
|
156
193
|
registerGentleAiDevBinary(binaryPath, ctx, platform);
|
|
157
194
|
return { binaryPath, version: match[1] };
|
|
158
195
|
} finally {
|
|
@@ -387,6 +424,21 @@ async function mainGo({ userGo, pinnedGo, acquire, run }) {
|
|
|
387
424
|
}
|
|
388
425
|
}
|
|
389
426
|
|
|
427
|
+
/** A thrown value's message when it has one (an Error or not), otherwise the value as text. */
|
|
428
|
+
function describe(error) {
|
|
429
|
+
return typeof error?.message === "string" ? error.message : String(error);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** `error` with `detail` appended to its message, keeping its code and cause; any
|
|
433
|
+
* other error becomes `main-upgrade-incomplete` with its message and itself as cause. */
|
|
434
|
+
function withDetail(error, detail) {
|
|
435
|
+
if (!(error instanceof MainChannelError)) {
|
|
436
|
+
return new MainChannelError("main-upgrade-incomplete", `${describe(error)}; ${detail}`, error);
|
|
437
|
+
}
|
|
438
|
+
const message = error.message.slice(`${error.code}: `.length);
|
|
439
|
+
return new MainChannelError(error.code, `${message}; ${detail}`, error.cause);
|
|
440
|
+
}
|
|
441
|
+
|
|
390
442
|
/**
|
|
391
443
|
* Updates Gentle Shell along its recorded channel (or the one `--channel` selects):
|
|
392
444
|
* release goes to the latest npm release, main to the latest `main` commits of
|
|
@@ -441,11 +493,33 @@ export async function runUpgrade({ args, ctx, platform, arch = process.arch, pac
|
|
|
441
493
|
out(`gentle-shell is already at the latest main: ${summary}.`);
|
|
442
494
|
return 0;
|
|
443
495
|
}
|
|
444
|
-
|
|
496
|
+
// Fail forward (#2356): a Gentle AI step that fails still installs a main Shell that
|
|
497
|
+
// moved, so the retry runs the new Shell's code; the channel is recorded only when
|
|
498
|
+
// both steps succeed, so that retry rebuilds Gentle AI. Verification stays fail-closed.
|
|
499
|
+
// A flag, not the caught value: a rejection may be any value, even a falsy one.
|
|
500
|
+
let aiFailed = false;
|
|
501
|
+
let aiError;
|
|
502
|
+
if (!aiCurrent) {
|
|
503
|
+
try {
|
|
504
|
+
await buildMainGentleAi({ commit: gentleAiCommit, ctx, platform, goPath: await mainGo({ userGo, pinnedGo, acquire, run }), run, fs });
|
|
505
|
+
} catch (error) {
|
|
506
|
+
if (shellCurrent) throw error;
|
|
507
|
+
aiFailed = true;
|
|
508
|
+
aiError = error;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
445
511
|
if (!shellCurrent) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
512
|
+
try {
|
|
513
|
+
const manager = await ownerManager({ ctx, platform, packageRoot, adapters });
|
|
514
|
+
const tgz = await packMainShell({ commit: shellCommit, ctx, fetch, run, pnpm, fs, platform });
|
|
515
|
+
await installGlobal(manager, tgz, run);
|
|
516
|
+
} catch (error) {
|
|
517
|
+
throw aiFailed ? withDetail(error, `Gentle AI was not updated either (${describe(aiError)})`) : error;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (aiFailed) {
|
|
521
|
+
out(`Updated Gentle Shell to the latest main (${shellCommit.slice(0, 12)}); Gentle AI was not updated.`);
|
|
522
|
+
throw withDetail(aiError, `Gentle Shell was updated to main ${shellCommit.slice(0, 12)} but Gentle AI was not: run gentle-shell upgrade --channel main to finish`);
|
|
449
523
|
}
|
|
450
524
|
await writeChannel(ctx, { channel: "main", shellCommit, gentleAiCommit }, fs);
|
|
451
525
|
out(`Updated to the latest main: ${summary}.`);
|
package/scripts/pi-update.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// Narrow update ownership proof. Deliberately independent of Shell upgrade/uninstall.
|
|
2
2
|
import { posix, win32 } from 'node:path';
|
|
3
3
|
import { bundledUpdatePlan } from '../runtime/gentle-shell-bundled.mjs';
|
|
4
|
+
import { setupErrorDetail } from './installer-runner.mjs';
|
|
4
5
|
|
|
5
6
|
const PI = '@earendil-works/pi-coding-agent';
|
|
6
7
|
const SHELL = 'gentle-pi';
|
|
@@ -8,6 +9,9 @@ const object = value => value !== null && typeof value === 'object' && !Array.is
|
|
|
8
9
|
const succeeded = result => result?.code === 0 && !result.timedOut && result.signal == null;
|
|
9
10
|
const refusal = detail => new Error(`gentle-shell update: ownership ${detail}; update Pi with the package manager or source checkout that supplies the selected runtime. No installation changed.`);
|
|
10
11
|
const LINKED = /^(?:file|link|workspace):/i;
|
|
12
|
+
// A main-channel Shell (`<version>-main.<sha12>`, scripts/main-channel.mjs
|
|
13
|
+
// mainVersion) is never published; it exists only as the packed tarball.
|
|
14
|
+
const MAIN_BUILD = /^[0-9A-Za-z.+-]+-main\.[0-9a-f]{12}$/;
|
|
11
15
|
const CLI_TAIL = /[\\/]dist[\\/]bundle[\\/]cli\.js$/i;
|
|
12
16
|
// A quoted cli.js target inside a package-manager shim: pnpm's POSIX shim
|
|
13
17
|
// (`"$basedir/../global/.../cli.js"`), its `.CMD`/npm's `.cmd` shim
|
|
@@ -136,7 +140,19 @@ async function listedEntries(groups, {adapters, path, key, canonical}) {
|
|
|
136
140
|
* ownership. An adjacent runtime is owned by the Shell group that resolved
|
|
137
141
|
* it: updating a separate global Pi never changes what that Shell runs.
|
|
138
142
|
*/
|
|
139
|
-
|
|
143
|
+
/** Where `gentle-shell upgrade --channel main` packed this Shell version: the
|
|
144
|
+
* same config home as scripts/main-channel.mjs configHome. A relative config
|
|
145
|
+
* home is refused, not resolved: `upgrade` resolved it from its own working
|
|
146
|
+
* directory, so it names no single folder, and the package manager runs from
|
|
147
|
+
* the neutral cwd.
|
|
148
|
+
*/
|
|
149
|
+
function mainBuildTarball(version, {env, home, path}) {
|
|
150
|
+
const config = env?.GENTLE_PI_CONFIG_HOME || (typeof home === 'string' ? path.join(home, '.pi', 'gentle-ai') : null);
|
|
151
|
+
if (!config || !path.isAbsolute(config)) throw refusal('cannot locate the main-channel build without an absolute config home');
|
|
152
|
+
return path.join(config, 'main', 'packages', `${SHELL}-${version}.tgz`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function resolveUpdateOwner({runtime, packageRoot, platform = process.platform, adapters, cwd, env, home}) {
|
|
140
156
|
const {path, key} = pathsFor(platform);
|
|
141
157
|
const canonical = canonicalWith(adapters, path);
|
|
142
158
|
const selected = await selectedCliOf(runtime, {adapters, path, canonical});
|
|
@@ -154,7 +170,7 @@ export async function resolveUpdateOwner({runtime, packageRoot, platform = proce
|
|
|
154
170
|
const root = await canonical(dependency.path);
|
|
155
171
|
const manifest = await manifestOf(adapters, path, root);
|
|
156
172
|
if (manifest.name !== pkg) throw refusal('listed package identity differs');
|
|
157
|
-
let cli, spec;
|
|
173
|
+
let cli, spec, artifact = false;
|
|
158
174
|
if (pkg === PI) {
|
|
159
175
|
if (manifest.bin?.pi !== 'dist/bundle/cli.js') throw refusal('listed Pi bin is unsupported');
|
|
160
176
|
cli = await canonical(path.join(root, manifest.bin.pi));
|
|
@@ -169,11 +185,12 @@ export async function resolveUpdateOwner({runtime, packageRoot, platform = proce
|
|
|
169
185
|
// Reinstalling the same Shell version re-resolves its peer; `update`
|
|
170
186
|
// never upgrades Shell itself (that is `gentle-shell upgrade`).
|
|
171
187
|
if (typeof manifest.version !== 'string') throw refusal('listed Shell version is missing');
|
|
172
|
-
|
|
188
|
+
artifact = MAIN_BUILD.test(manifest.version);
|
|
189
|
+
spec = artifact ? mainBuildTarball(manifest.version, {env, home, path}) : `${SHELL}@${manifest.version}`;
|
|
173
190
|
}
|
|
174
191
|
if (key(cli) !== key(selected)) continue;
|
|
175
192
|
if (name === 'pnpm' && members.get(key(groupRoot)).size !== 1) throw refusal('selected supplier shares its global install group');
|
|
176
|
-
candidates.push({name, ...invocation, package:pkg, spec});
|
|
193
|
+
candidates.push({name, ...invocation, package:pkg, spec, artifact});
|
|
177
194
|
}
|
|
178
195
|
}
|
|
179
196
|
if (candidates.length !== 1) throw refusal(candidates.length > 1 ? 'is ambiguous' : 'could not be verified from global listings');
|
|
@@ -207,11 +224,22 @@ export async function updateSelectedPi(input) {
|
|
|
207
224
|
let owner;
|
|
208
225
|
try { owner = await resolveUpdateOwner(input); }
|
|
209
226
|
catch (error) { throw error.message?.includes('ownership') ? error : refusal('could not be resolved'); }
|
|
227
|
+
if (owner.artifact) {
|
|
228
|
+
try { await input.adapters.fs.realpath(owner.spec); }
|
|
229
|
+
catch { throw new Error(`gentle-shell update: the main-channel build ${owner.spec} is missing, and this unpublished build cannot be reinstalled from the registry; run 'gentle-shell upgrade --channel release' and then 'gentle-shell upgrade --channel main' to rebuild it. No installation changed.`); }
|
|
230
|
+
}
|
|
210
231
|
// Exactly one approved package per command; never create a comma group or
|
|
211
232
|
// reinstall arbitrary dependencies merely because they share a store.
|
|
212
233
|
const args = owner.name === 'pnpm' ? ['add', '-g', owner.spec, ...(owner.package === SHELL ? ['--allow-build=gentle-pi'] : [])] : ['install', '-g', owner.spec, ...(owner.package === PI ? ['--ignore-scripts'] : [])];
|
|
213
|
-
const result = await input.adapters.run(owner.command, [...owner.prefix, ...args], {deadlineMs:20 * 60_000, cwd: input.cwd});
|
|
214
|
-
if (!succeeded(result))
|
|
234
|
+
const result = await input.adapters.run(owner.command, [...owner.prefix, ...args], {deadlineMs:20 * 60_000, cwd: input.cwd, stderrTail:4096});
|
|
235
|
+
if (!succeeded(result)) {
|
|
236
|
+
// One sanitized line of the manager's own error: npm prints it on stderr,
|
|
237
|
+
// pnpm 11 prints ERR_PNPM_* on stdout. Read as one text, stderr last, an
|
|
238
|
+
// error line in either stream wins over a stderr warning, stderr's on a tie.
|
|
239
|
+
const output = [result?.stdout, result?.stderrTail].filter(text => typeof text === 'string').join('\n');
|
|
240
|
+
const detail = setupErrorDetail(output, input.home, input.platform);
|
|
241
|
+
throw new Error(`gentle-shell update: ${owner.name} (${[owner.command, ...owner.prefix].join(' ')}) could not install ${owner.spec}${detail ? `\n${detail}` : ''}`);
|
|
242
|
+
}
|
|
215
243
|
const after = await suppliedPiVersion(owner, input);
|
|
216
244
|
const before = input.currentVersion;
|
|
217
245
|
if (after === null) input.out?.(`${owner.name} installed ${owner.spec}. The Pi version it now supplies could not be read back; run 'gentle-shell --version' to check it.`);
|
|
@@ -50,6 +50,29 @@ export const requiredPaths = [
|
|
|
50
50
|
"assets/sounds/LICENSE.md",
|
|
51
51
|
"docs/sound-notifications.md",
|
|
52
52
|
"docs/sound-notifications-proposal.md",
|
|
53
|
+
"extensions/gentle-voice.ts",
|
|
54
|
+
"lib/voice-artifacts.ts",
|
|
55
|
+
"lib/voice-command.ts",
|
|
56
|
+
"lib/voice-install.ts",
|
|
57
|
+
"lib/voice-platform.ts",
|
|
58
|
+
"lib/voice-session.ts",
|
|
59
|
+
"lib/voice-ui.ts",
|
|
60
|
+
// The dictation worker is spawned by path, never imported, so nothing else
|
|
61
|
+
// would notice it missing from the packed tarball.
|
|
62
|
+
"scripts/voice-worker.cjs",
|
|
63
|
+
"docs/voice.md",
|
|
64
|
+
"extensions/gentle-design.ts",
|
|
65
|
+
"lib/gentle-design.ts",
|
|
66
|
+
// The design canvas server reads its page and scripts from assets/ by path.
|
|
67
|
+
"lib/design-canvas-server.ts",
|
|
68
|
+
"lib/design-open-browser.ts",
|
|
69
|
+
"lib/design-comments.ts",
|
|
70
|
+
"lib/design-handoffs.ts",
|
|
71
|
+
"lib/design-sources.ts",
|
|
72
|
+
"assets/design-canvas/index.html",
|
|
73
|
+
"assets/design-canvas/canvas.js",
|
|
74
|
+
"assets/design-canvas/canvas.css",
|
|
75
|
+
"assets/design-canvas/inspector.js",
|
|
53
76
|
"scripts/npm-pack-result.mjs",
|
|
54
77
|
"assets/orchestrator.md",
|
|
55
78
|
"assets/orchestrator-delegation.md",
|
|
@@ -154,6 +177,7 @@ export const requiredPaths = [
|
|
|
154
177
|
"skills/chained-pr/SKILL.md",
|
|
155
178
|
"skills/cognitive-doc-design/SKILL.md",
|
|
156
179
|
"skills/comment-writer/SKILL.md",
|
|
180
|
+
"skills/design/SKILL.md",
|
|
157
181
|
"skills/gentle-ai/SKILL.md",
|
|
158
182
|
"skills/issue-creation/SKILL.md",
|
|
159
183
|
"skills/judgment-day/SKILL.md",
|
|
@@ -401,7 +425,7 @@ async function main() {
|
|
|
401
425
|
});
|
|
402
426
|
|
|
403
427
|
if (driftedContracts.length > 0) {
|
|
404
|
-
console.error("gentle-pi packaged review-integration/v1 and review-integration/v2 contract bytes drifted from the pinned v4.
|
|
428
|
+
console.error("gentle-pi packaged review-integration/v1 and review-integration/v2 contract bytes drifted from the pinned v4.3.1 runtime's vendored Gentle AI contract artifacts:");
|
|
405
429
|
for (const drift of driftedContracts) console.error(`- ${drift.relativePath}: expected ${drift.expected}, got ${drift.actual}`);
|
|
406
430
|
process.exit(1);
|
|
407
431
|
}
|
|
@@ -446,7 +470,7 @@ async function main() {
|
|
|
446
470
|
process.exit(1);
|
|
447
471
|
}
|
|
448
472
|
|
|
449
|
-
console.log(`gentle-pi package resource check passed (${requiredPaths.length} files; ${Object.keys(contractHashes).length} exact byte-pinned contract artifacts for the v4.
|
|
473
|
+
console.log(`gentle-pi package resource check passed (${requiredPaths.length} files; ${Object.keys(contractHashes).length} exact byte-pinned contract artifacts for the v4.3.1 runtime).`);
|
|
450
474
|
}
|
|
451
475
|
|
|
452
476
|
const isMainModule = process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href;
|