gentle-pi 4.0.0 → 4.2.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 +157 -18
- package/assets/agents/gentle-ai-explore.md +11 -4
- package/assets/agents/gentle-ai-verify.md +23 -4
- package/assets/agents/gentle-ai-worker.md +6 -6
- package/assets/install-wizard/index.html +47 -0
- package/assets/install-wizard/wizard.css +1279 -0
- package/assets/install-wizard/wizard.js +951 -0
- package/assets/orchestrator-delegation.md +70 -163
- package/assets/orchestrator-memory.md +11 -4
- package/assets/orchestrator-prompts.md +48 -0
- package/assets/orchestrator-tracking.md +53 -0
- package/assets/orchestrator-verification.md +28 -0
- package/assets/orchestrator-writer.md +38 -0
- package/assets/orchestrator.md +29 -25
- package/assets/sounds/LICENSE.md +52 -0
- package/assets/sounds/attention.wav +0 -0
- package/assets/sounds/error.wav +0 -0
- package/assets/sounds/success.wav +0 -0
- package/bin/engram-guard +9 -0
- package/bin/gentle-shell-install.mjs +302 -0
- package/bin/gentle-shell.mjs +708 -22
- package/contracts/review-provider-contract-mirror/provider-contract.lock.json +7 -7
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/manifest.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/orchestration/pi.md +16 -4
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-capabilities.baseline.json +2 -2
- package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/generated/provider-roles.baseline.json +1 -1
- package/docs/auto-permission-answers.md +25 -0
- package/docs/auto-permission-evidence.md +39 -0
- package/docs/bundled-install.md +399 -0
- package/docs/delegated-verification.md +3 -5
- package/docs/gentle-agents-activity.md +513 -0
- package/docs/gentle-shell-uninstall.md +136 -0
- package/docs/gentle-shell.md +90 -33
- package/docs/install-wizard.md +2040 -0
- package/docs/native-authority-architecture.md +1 -1
- package/docs/permission-modes.md +127 -0
- package/docs/prompt-history.md +1 -1
- package/docs/readme-reference.md +310 -47
- package/docs/render-cache-performance.md +28 -0
- package/docs/session-profile-format.md +39 -0
- package/docs/sound-notifications-proposal.md +191 -0
- package/docs/sound-notifications.md +114 -0
- package/docs/yolo-mode.md +11 -10
- package/extensions/ask-user-choice.ts +4 -0
- package/extensions/ask-user-question.ts +6 -1
- package/extensions/child-context.ts +11 -2
- package/extensions/child-safety.ts +4 -2
- package/extensions/codegraph-tools.ts +18 -5
- package/extensions/gentle-agents.ts +718 -87
- package/extensions/gentle-ai.ts +991 -263
- package/extensions/gentle-herdr-activity.ts +81 -0
- package/extensions/gentle-notifications.ts +104 -0
- package/extensions/gentle-shell.ts +271 -80
- package/extensions/gentle-todo.ts +123 -18
- package/extensions/history/index.ts +11 -6
- package/extensions/pi-pretty.ts +39 -3
- package/extensions/quiet-tools.ts +91 -21
- package/extensions/skill-registry.ts +175 -46
- package/extensions/startup-banner.ts +11 -2
- package/lib/agent-home.ts +10 -1
- package/lib/agent-model-resolution.ts +80 -0
- package/lib/agent-profiles.ts +13 -3
- package/lib/agents-completion-delivery.ts +11 -3
- package/lib/agents-messaging.ts +1 -1
- package/lib/agents-protocol.ts +40 -1
- package/lib/agents-runner.ts +90 -15
- package/lib/auto-permission-answers.ts +133 -0
- package/lib/auto-permission-classifier.ts +459 -0
- package/lib/auto-permission-evidence.ts +88 -0
- package/lib/auto-permission-memory.ts +69 -0
- package/lib/auto-permission-runtime.ts +484 -0
- package/lib/auto-permission-tool-context.ts +61 -0
- package/lib/background-jobs-tools.ts +202 -0
- package/lib/background-jobs.ts +253 -0
- package/lib/background-monitor.ts +154 -0
- package/lib/bounded-writer-admission.ts +83 -10
- package/lib/builtin-codemode-optout.ts +166 -0
- package/lib/card-content-policy.ts +85 -0
- package/lib/child-package-injection.ts +62 -0
- package/lib/codemode-renderer.ts +14 -4
- package/lib/command-palette-catalog.ts +3 -2
- package/lib/context-threshold.ts +26 -0
- package/lib/destructive-command-guard.ts +104 -28
- package/lib/gentle-ai-binary.ts +109 -4
- package/lib/gentle-shell-bundled.ts +535 -0
- package/lib/gentle-shell-launcher.ts +75 -24
- package/lib/gentle-shell-startup-flow.ts +54 -0
- package/lib/gentle-shell-startup-update.ts +92 -0
- package/lib/gentle-shell-uninstall.ts +840 -0
- package/lib/gentle-shell-update-panel.ts +231 -0
- package/lib/herdr-activity.ts +183 -0
- package/lib/inprocess-reviewer.ts +3 -0
- package/lib/jobs-sidebar-state.ts +20 -0
- package/lib/jobs-view.ts +161 -0
- package/lib/model-price-ratio.ts +83 -0
- package/lib/model-routing-authority.ts +6 -1
- package/lib/nan-provider.ts +33 -11
- package/lib/native-review-cli.ts +194 -73
- package/lib/notification-audio-native.ts +162 -0
- package/lib/notification-audio-windows.ts +399 -0
- package/lib/notification-audio.ts +332 -0
- package/lib/notification-customize.ts +241 -0
- package/lib/notification-events.ts +113 -0
- package/lib/notification-policy.ts +148 -0
- package/lib/notification-pulse-client.ts +367 -0
- package/lib/notification-pulse-protocol.ts +305 -0
- package/lib/notification-pulse-stream.ts +213 -0
- package/lib/notification-pulse-worker.ts +66 -0
- package/lib/notification-scheduler.ts +173 -0
- package/lib/notification-service.ts +202 -0
- package/lib/notification-ui.ts +87 -0
- package/lib/odd-phase.ts +1 -1
- package/lib/orchestrator-alias.ts +20 -0
- package/lib/orchestrator-catalog.ts +79 -0
- package/lib/orchestrator-consultation.ts +61 -0
- package/lib/orchestrator-discovery.ts +49 -0
- package/lib/orchestrator-helper-consent.ts +122 -0
- package/lib/orchestrator-helper.ts +167 -0
- package/lib/orchestrator-message-card.ts +105 -0
- package/lib/orchestrator-presence.ts +115 -14
- package/lib/orchestrator-scope.ts +80 -0
- package/lib/orchestrator-state.ts +107 -0
- package/lib/orchestrator-tool-card.ts +280 -0
- package/lib/orchestrator-work-search.ts +218 -0
- package/lib/orchestrator-work.ts +79 -0
- package/lib/permission-mode-policy.ts +59 -0
- package/lib/review-candidate-view-owner.ts +8 -6
- package/lib/review-candidate-view.ts +3 -2
- package/lib/review-compact-contract.ts +2 -2
- package/lib/review-host-relay.ts +7 -2
- package/lib/review-integration-v2.ts +100 -22
- package/lib/review-publication-gate.ts +1 -1
- package/lib/review-risk-assessment.ts +77 -51
- package/lib/review-sidebar-state.ts +2 -1
- package/lib/review-transaction.ts +72 -3
- package/lib/runtime-metrics-children.ts +1 -1
- package/lib/runtime-metrics-native.ts +3 -2
- package/lib/selection-engine.ts +22 -7
- package/lib/session-change-capture.ts +5 -4
- package/lib/session-changes.ts +27 -22
- package/lib/session-profile-binding.ts +83 -0
- package/lib/session-profile-freeze.ts +261 -0
- package/lib/session-profile-persistence.ts +187 -0
- package/lib/session-worktree-registry.ts +74 -8
- package/lib/shell-bar.ts +41 -11
- package/lib/shell-changes-view.ts +40 -13
- package/lib/shell-changes.ts +88 -71
- package/lib/shell-prompt.ts +6 -4
- package/lib/shell-sidebar-layout.ts +5 -1
- package/lib/shell-todo.ts +81 -26
- package/lib/shell-usage.ts +4 -3
- package/lib/status-timing-diagnostics.ts +201 -0
- package/lib/telemetry-trigger.ts +3 -1
- package/lib/vim-editor-adapter.ts +27 -5
- package/lib/visual-customization-policy.ts +20 -9
- package/lib/visual-customize-view.ts +171 -20
- package/lib/writer-surfaces.ts +162 -0
- package/lib/yolo-session-policy.ts +198 -121
- package/package.json +5 -5
- package/runtime/child-package-injection.mjs +63 -0
- package/runtime/gentle-ai-binary.mjs +109 -4
- package/runtime/gentle-shell-bundled.mjs +536 -0
- package/runtime/gentle-shell-launcher.mjs +74 -23
- package/runtime/gentle-shell-startup-flow.mjs +55 -0
- package/runtime/gentle-shell-startup-update.mjs +93 -0
- package/runtime/gentle-shell-uninstall.mjs +841 -0
- package/runtime/gentle-shell-update-panel.mjs +232 -0
- package/runtime/native-review-cli.mjs +191 -70
- package/runtime/review-integration-v2.mjs +100 -22
- package/runtime/review-risk-assessment.mjs +77 -51
- package/runtime/status-timing-diagnostics.mjs +202 -0
- package/runtime/telemetry-trigger.mjs +3 -1
- package/scripts/bootstrap.cmd +255 -0
- package/scripts/bootstrap.sh +168 -0
- package/scripts/build-distribution.mjs +157 -0
- package/scripts/build-installer-bundles.mjs +136 -0
- package/scripts/build-runtime-modules.mjs +7 -0
- package/scripts/bundled-install.mjs +1308 -0
- package/scripts/bundled-wizard.mjs +399 -0
- package/scripts/check-types.mjs +4 -6
- package/scripts/gentle-ai-installer.mjs +79 -32
- package/scripts/install-gentle-ai.mjs +48 -1
- package/scripts/install-tui-mode-setting.mjs +10 -1
- package/scripts/install-wizard-preview.mjs +154 -0
- package/scripts/installer-downloads.mjs +648 -0
- package/scripts/installer-preflight.mjs +387 -0
- package/scripts/installer-probes.mjs +564 -0
- package/scripts/installer-runner.mjs +1102 -0
- package/scripts/installer-server.mjs +881 -0
- package/scripts/installer-windows-artifacts.json +15 -0
- package/scripts/installer-windows.mjs +752 -0
- package/scripts/main-channel.mjs +452 -0
- package/scripts/npm-pack-result.mjs +31 -0
- package/scripts/pi-update.mjs +223 -0
- package/scripts/render-cache-performance-worker.mjs +205 -0
- package/scripts/render-cache-performance.mjs +115 -0
- package/scripts/run-test-suite.mjs +22 -8
- package/scripts/test-installer-acceptance.sh +161 -0
- package/scripts/test-packed-runner.mjs +23 -5
- package/scripts/types-baseline.json +2 -92
- package/scripts/verify-package-files.mjs +82 -18
- package/skills/gentle-ai/SKILL.md +12 -11
- package/tests/agent-home.test.ts +56 -3
- package/tests/agent-model-resolution.test.ts +113 -0
- package/tests/agent-profiles.test.ts +12 -0
- package/tests/agent-risk-escalation.test.ts +123 -0
- package/tests/agents-completion-delivery.test.ts +17 -0
- package/tests/agents-protocol.test.ts +57 -0
- package/tests/agents-queries.test.ts +9 -9
- package/tests/agents-runner-process.test.ts +31 -0
- package/tests/agents-runner.test.ts +89 -4
- package/tests/append-system-prompt-route.test.ts +18 -1
- package/tests/auto-mode-human-answers.test.ts +68 -0
- package/tests/auto-mode-invariants.test.ts +276 -0
- package/tests/auto-mode-sdk.test.ts +84 -0
- package/tests/auto-mode.test.ts +49 -0
- package/tests/auto-permission-answers.test.ts +170 -0
- package/tests/auto-permission-classifier.test.ts +634 -0
- package/tests/auto-permission-compound.test.ts +24 -0
- package/tests/auto-permission-evidence.test.ts +88 -0
- package/tests/auto-permission-memory.test.ts +46 -0
- package/tests/auto-permission-public-regressions.test.ts +111 -0
- package/tests/auto-permission-runtime.test.ts +454 -0
- package/tests/autonomous-guard.test.ts +86 -16
- package/tests/background-cache-warming.test.ts +2 -1
- package/tests/background-jobs.test.ts +227 -0
- package/tests/background-monitor.test.ts +195 -0
- package/tests/background-subagents-default-mode.test.ts +28 -5
- package/tests/bounded-stop-contract.test.ts +85 -0
- package/tests/bounded-writer-admission.test.ts +52 -1
- package/tests/bridge-wake-lifecycle.test.ts +129 -0
- package/tests/bridge-wake-rendering.test.ts +56 -0
- package/tests/build-distribution.test.ts +269 -0
- package/tests/builtin-codemode-optout.test.ts +242 -0
- package/tests/bundled-install.test.ts +1319 -0
- package/tests/bundled-wizard.test.ts +505 -0
- package/tests/card-content-policy.test.ts +68 -0
- package/tests/card-style-policy.test.ts +34 -2
- package/tests/child-codegraph-bootstrap.test.ts +74 -0
- package/tests/child-package-entrypoints.test.ts +65 -0
- package/tests/child-package-injection.test.ts +74 -0
- package/tests/child-safety.test.ts +12 -0
- package/tests/child-web-package.test.ts +224 -0
- package/tests/codegraph-tools.test.ts +35 -0
- package/tests/codemode-rendering.test.ts +107 -2
- package/tests/command-palette.test.ts +5 -5
- package/tests/context-threshold.test.ts +31 -0
- package/tests/delegate-for-reason-contract.test.ts +327 -0
- package/tests/delegated-key-learnings-contract.test.ts +3 -2
- package/tests/devbinary/native-review-parity.devtest.ts +1 -1
- package/tests/devbinary/pi-host-relay.devtest.ts +173 -3
- package/tests/engram-safety.test.ts +316 -0
- package/tests/generic-agent-tools.test.ts +16 -1
- package/tests/gentle-agents.test.ts +1895 -76
- package/tests/gentle-ai-binary-isolation.test.ts +135 -0
- package/tests/gentle-ai-binary.test.ts +19 -2
- package/tests/gentle-ai-child-guards.test.ts +202 -0
- package/tests/gentle-ai-dev-binary-surfacing.test.ts +125 -15
- package/tests/gentle-ai-installer.test.ts +548 -58
- package/tests/gentle-ai-prompt-model-routing.test.ts +123 -0
- package/tests/gentle-ai-renderer.test.ts +6 -6
- package/tests/gentle-ai.test.ts +999 -31
- package/tests/gentle-herdr-activity.test.ts +100 -0
- package/tests/gentle-notifications.test.ts +427 -0
- package/tests/gentle-shell-bin.test.ts +263 -19
- package/tests/gentle-shell-bundled.test.ts +1036 -0
- package/tests/gentle-shell-launcher.test.ts +251 -25
- package/tests/gentle-shell-startup-flow.test.ts +75 -0
- package/tests/gentle-shell-startup-update.test.ts +85 -0
- package/tests/gentle-shell-uninstall.test.ts +978 -0
- package/tests/gentle-shell-update-panel.test.ts +158 -0
- package/tests/gentle-shell.test.ts +611 -100
- package/tests/gentle-todo.test.ts +317 -14
- package/tests/herdr-activity.test.ts +243 -0
- package/tests/history-agent-home.test.ts +81 -0
- package/tests/history-child-guard.test.ts +70 -0
- package/tests/inprocess-reviewer.test.ts +20 -0
- package/tests/install-gentle-ai-failure.test.ts +69 -0
- package/tests/install-tui-mode-setting.test.ts +15 -0
- package/tests/install-wizard.test.ts +1223 -0
- package/tests/installer-bundles.test.ts +122 -0
- package/tests/installer-posix-bootstrap.test.ts +1201 -0
- package/tests/installer-preflight.test.ts +750 -0
- package/tests/installer-probes.test.ts +1106 -0
- package/tests/installer-runner.test.ts +2159 -0
- package/tests/installer-server.test.ts +1567 -0
- package/tests/installer-windows-bootstrap.test.ts +2410 -0
- package/tests/jobs-view.test.ts +160 -0
- package/tests/main-channel-upgrade.test.ts +395 -0
- package/tests/main-channel.test.ts +344 -0
- package/tests/maintainer/provider-relay.maintest.ts +13 -12
- package/tests/model-price-ratio.test.ts +116 -0
- package/tests/model-profiles-preservation.test.ts +153 -0
- package/tests/model-routing-package.test.ts +10 -0
- package/tests/nan-provider-runtime.test.ts +156 -0
- package/tests/nan-provider.test.ts +28 -18
- package/tests/native-binary-gate.test.ts +2 -0
- package/tests/native-review-capability-contract.test.ts +7 -1
- package/tests/native-review-cli.test.ts +236 -1
- package/tests/native-review-consent.test.ts +2 -2
- package/tests/native-review-parity-runtime.test.ts +1 -1
- package/tests/native-review-parity.test.ts +12 -48
- package/tests/native-risk-evidence-parity.test.ts +21 -0
- package/tests/no-self-pause-contract.test.ts +87 -0
- package/tests/notification-audio-native.test.ts +281 -0
- package/tests/notification-audio-windows.test.ts +581 -0
- package/tests/notification-audio.test.ts +548 -0
- package/tests/notification-customize.test.ts +655 -0
- package/tests/notification-events.test.ts +115 -0
- package/tests/notification-policy.test.ts +201 -0
- package/tests/notification-pulse-client.test.ts +356 -0
- package/tests/notification-pulse-protocol.test.ts +213 -0
- package/tests/notification-pulse-stream.test.ts +320 -0
- package/tests/notification-scheduler.test.ts +258 -0
- package/tests/notification-ui.test.ts +157 -0
- package/tests/npm-pack-result.test.ts +41 -0
- package/tests/odd-integration.test.ts +14 -5
- package/tests/odd-routing-canonical-ratchet.test.ts +53 -12
- package/tests/odd-routing-contract.test.ts +80 -23
- package/tests/odd-runtime-delegation-gate.test.ts +4 -1
- package/tests/orchestrator-budget.test.ts +29 -6
- package/tests/orchestrator-catalog.test.ts +146 -0
- package/tests/orchestrator-consultation-sdk.test.ts +614 -0
- package/tests/orchestrator-consultation.test.ts +111 -0
- package/tests/orchestrator-discovery.test.ts +241 -0
- package/tests/orchestrator-helper-consent.test.ts +171 -0
- package/tests/orchestrator-helper.test.ts +297 -0
- package/tests/orchestrator-message-card.test.ts +190 -0
- package/tests/orchestrator-presence.test.ts +23 -0
- package/tests/orchestrator-rdd-ownership.test.ts +6 -5
- package/tests/orchestrator-scope.test.ts +107 -0
- package/tests/orchestrator-state.test.ts +114 -0
- package/tests/orchestrator-status-publication-contract.test.ts +34 -0
- package/tests/orchestrator-tool-card.test.ts +398 -0
- package/tests/orchestrator-work-search.test.ts +318 -0
- package/tests/orchestrator-work.test.ts +147 -0
- package/tests/package-manifest.test.ts +105 -21
- package/tests/packed-runner-owned-path.test.ts +35 -1
- package/tests/parallel-reason-contract.test.ts +21 -0
- package/tests/parallel-writer-admission.test.ts +210 -0
- package/tests/permission-mode-policy.test.ts +194 -0
- package/tests/persona-single-channel.test.ts +4 -12
- package/tests/pi-pretty.test.ts +56 -0
- package/tests/pi-update-launcher.test.ts +87 -0
- package/tests/pi-update.test.ts +186 -0
- package/tests/provider-defect-handoff.test.ts +4 -3
- package/tests/quiet-tool-rendering.test.ts +160 -3
- package/tests/rdd-aware-verification-contract.test.ts +29 -41
- package/tests/rdd-status-line.test.ts +52 -0
- package/tests/render-cache-performance.test.ts +48 -0
- package/tests/review-agent-end-preflight.test.ts +62 -10
- package/tests/review-candidate-view.test.ts +101 -12
- package/tests/review-contract-prompt.test.ts +33 -4
- package/tests/review-controller-native-recovery.test.ts +327 -17
- package/tests/review-controller-native-routing.test.ts +255 -1
- package/tests/review-controller-workspace-root.test.ts +43 -2
- package/tests/review-controller.test.ts +2 -2
- package/tests/review-correction-lifecycle.test.ts +2 -1
- package/tests/review-gate.test.ts +7 -4
- package/tests/review-graph-schema.test.ts +2 -2
- package/tests/review-host-relay-routing.test.ts +5 -3
- package/tests/review-host-relay.test.ts +49 -2
- package/tests/review-integration-v2-forward.test.ts +21 -1
- package/tests/review-json-arguments.test.ts +167 -0
- package/tests/review-last-event-closure.test.ts +157 -8
- package/tests/review-ledger-contract.test.ts +5 -4
- package/tests/review-policy-judgment-day.test.ts +1 -1
- package/tests/review-policy-ordinary.test.ts +2 -2
- package/tests/review-relay-transport-agent.test.ts +66 -1
- package/tests/review-risk-assessment.test.ts +109 -33
- package/tests/review-session-standing-permission-controller.test.ts +16 -17
- package/tests/review-session-standing-permission-ipc.test.ts +6 -5
- package/tests/review-test-fixtures.ts +7 -1
- package/tests/review-transaction.test.ts +18 -1
- package/tests/run-test-suite.test.ts +83 -5
- package/tests/runtime-harness.mjs +42 -15
- package/tests/runtime-metrics-extension.test.ts +1 -1
- package/tests/runtime-metrics-native.test.ts +31 -1
- package/tests/selection-engine.test.ts +65 -5
- package/tests/session-change-capture.test.ts +10 -10
- package/tests/session-changes-shell.test.ts +242 -48
- package/tests/session-changes.test.ts +17 -24
- package/tests/session-profile-binding.test.ts +163 -0
- package/tests/session-profile-freeze.test.ts +394 -0
- package/tests/session-profile-persistence.test.ts +334 -0
- package/tests/session-subject-routing-contract.test.ts +34 -0
- package/tests/session-worktree-registry.test.ts +57 -2
- package/tests/shell-bar.test.ts +40 -0
- package/tests/shell-changes-view.test.ts +85 -34
- package/tests/shell-changes.test.ts +79 -62
- package/tests/shell-render-cache-runtime.test.ts +69 -0
- package/tests/shell-render-cache.test.ts +129 -0
- package/tests/shell-sidebar-layout.test.ts +27 -2
- package/tests/shell-todo.test.ts +132 -11
- package/tests/shell-usage-view.test.ts +1 -1
- package/tests/shell-usage.test.ts +35 -0
- package/tests/short-check-routing-contract.test.ts +49 -0
- package/tests/skill-registry-watcher.test.ts +119 -0
- package/tests/skill-registry.test.ts +311 -5
- package/tests/startup-banner.test.ts +117 -3
- package/tests/startup-home-repo.test.ts +170 -0
- package/tests/status-timing-diagnostics.test.ts +283 -0
- package/tests/support/orchestrator-modules.ts +19 -0
- package/tests/task-size-routing-contract.test.ts +241 -0
- package/tests/telemetry-trigger.test.ts +27 -1
- package/tests/test-strength-contract.test.ts +124 -0
- package/tests/verify-agent-contract.test.ts +90 -0
- package/tests/verify-first-launch-contract.test.ts +139 -0
- package/tests/verify-package-files.test.ts +56 -0
- package/tests/vim-editor-adapter.test.ts +47 -21
- package/tests/visual-customization-policy.test.ts +32 -3
- package/tests/visual-customize-view.test.ts +288 -10
- package/tests/writer-edit-surface-scope.test.ts +59 -2
- package/tests/writer-surfaces.test.ts +199 -0
- package/tests/yolo-customize.test.ts +18 -3
- package/tests/yolo-mode-runtime.test.ts +37 -10
- package/tests/yolo-mode.test.ts +38 -5
- package/tests/yolo-session-policy.test.ts +1 -1
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/README.md +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/lens.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/refuter.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/schemas/targeted-validator.schema.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/lens.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/refuter.json +0 -0
- /package/contracts/review-provider-contract-mirror/{v1.2.0 → v1.3.0}/bundle/vectors/targeted-validator.json +0 -0
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { CustomEditor, keyHint, type ExtensionAPI, type ExtensionContext, type KeybindingsManager } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { isHomeOrBroadUserDirectory } from "../lib/bounded-writer-admission.ts";
|
|
2
3
|
import { Editor, decodeKittyPrintable, isKeyRelease, matchesKey, parseKey, truncateToWidth, visibleWidth, type EditorTheme, type TUI } from "@earendil-works/pi-tui";
|
|
3
4
|
import { execFile, spawnSync } from "node:child_process";
|
|
4
|
-
import { realpathSync, statSync } from "node:fs";
|
|
5
|
+
import { existsSync, realpathSync, statSync } from "node:fs";
|
|
6
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
5
7
|
import { fileURLToPath } from "node:url";
|
|
6
8
|
import { profilesFilePath, profileRoleEntries, readProfilesFileResult } from "../lib/agent-profiles.ts";
|
|
7
|
-
import {
|
|
9
|
+
import { resolveSessionProfile, sessionProfileLabel, sessionProfileModelProfiles } from "../lib/session-profile-freeze.ts";
|
|
8
10
|
import * as os from "node:os";
|
|
9
11
|
import { dirname, join, resolve } from "node:path";
|
|
10
|
-
import { buildShellHeaderModel, renderShellBar, renderShellBelowInputFloat, renderShellBottomOnlyBar, renderShellHeaderChrome, renderShellSidebarBar, shellEnabled, shellHeaderUsageHit, type ShellBarModel, type ShellBarTheme } from "../lib/shell-bar.ts";
|
|
11
|
-
import { CHANGE_STATUS,
|
|
12
|
+
import { buildShellHeaderModel, renderShellBar, renderShellBelowInputFloat, renderShellBottomOnlyBar, renderShellHeaderChrome, renderShellSidebarBar, keepNativeWorkingRow, shellEnabled, shellHeaderUsageHit, shellJobsCount, type ShellBarModel, type ShellBarTheme } from "../lib/shell-bar.ts";
|
|
13
|
+
import { CHANGE_STATUS, DIFF_MAX_BYTES, DIFF_MAX_LINES, DIFF_TOO_LARGE, WorktreeChangesTracker, renderChangesWidget, type ChangedFile, type ChangesModel, type FileRevision, type GitRunner, type LineCounter, type WorktreeChanges } from "../lib/shell-changes.ts";
|
|
12
14
|
import { WorktreeChangesView } from "../lib/shell-changes-view.ts";
|
|
13
15
|
import { SessionWorktreeRegistry, resolveSessionWorktree, worktreeGitEnvironment, type WorktreeResolver, type WorktreeIdentity } from "../lib/session-worktree-registry.ts";
|
|
14
16
|
import { CARD_STYLE, CARD_TONE, cardStyle, renderCard, setCardStyle, type Card, type CardTheme } from "../lib/shell-card.ts";
|
|
15
17
|
import { resolveCardStyle, writeCardStyle } from "../lib/card-style-policy.ts";
|
|
18
|
+
import { CARD_CONTENT, resolveCardContent, setCardContent, writeCardContent } from "../lib/card-content-policy.ts";
|
|
16
19
|
import { CommandPalette, commandsKey, type CommandPaletteResult } from "../lib/command-palette.ts";
|
|
17
20
|
import { buildCommandPaletteGroups } from "../lib/command-palette-catalog.ts";
|
|
18
21
|
import { discoverYoloUiAdapter, YOLO_DISPLAY, type YoloDisplay, type YoloUiAdapter } from "../lib/yolo-session-policy.ts";
|
|
@@ -28,6 +31,8 @@ import { oddPhaseRegistry } from "../lib/odd-phase.ts";
|
|
|
28
31
|
import { inferOddPhase } from "../lib/odd-phase-inference.ts";
|
|
29
32
|
import { isInteractiveMode } from "../lib/rpc-host.ts";
|
|
30
33
|
import { gentlePiConfigHome } from "../lib/agent-home.ts";
|
|
34
|
+
import { bundledGentlePiConfigHome } from "../lib/gentle-shell-bundled.ts";
|
|
35
|
+
import { isNanAdaptiveReasoningModel } from "../lib/nan-provider.ts";
|
|
31
36
|
import { resolveAnimationPolicy, writeAnimationPolicy, type AnimationPolicy } from "../lib/animation-policy.ts";
|
|
32
37
|
import { resolveVimPolicy, writeVimPolicy, type VimPolicy } from "../lib/vim-policy.ts";
|
|
33
38
|
import { resolveHistoryCapture, writeHistoryCapturePolicy } from "../lib/history-capture-policy.ts";
|
|
@@ -112,12 +117,14 @@ import {
|
|
|
112
117
|
import { accountIdFromToken, CODEX_PROVIDER, CODEX_USAGE_URL, NAN_PROVIDER, NAN_QUOTA_URL, parseCodexUsage, parseNanQuota, parseProviderUsage, parseUsageHeaders, parseUsageSource, usageScopeProviders, UsageSourceRegistry, UsageStore, USAGE_SOURCE_EVENT, type ProviderUsage, type UsageSource } from "../lib/shell-usage.ts";
|
|
113
118
|
import { UsageView } from "../lib/shell-usage-view.ts";
|
|
114
119
|
import { sidebarHeader, sidebarPart, sidebarState, VISUAL_SETTINGS_CHANGED, type SidebarRail } from "../lib/shell-sidebar.ts";
|
|
115
|
-
import { installSidebar, invalidateSidebar, narrowStatusOwner, STATUS_OWNER } from "../lib/shell-sidebar-layout.ts";
|
|
116
|
-
import {
|
|
120
|
+
import { installSidebar, invalidateSidebar, narrowStatusOwner, SIDEBAR_BREAKPOINT, STATUS_OWNER } from "../lib/shell-sidebar-layout.ts";
|
|
121
|
+
import { sessionChangeRoots } from "../lib/session-changes.ts";
|
|
117
122
|
import { REVIEW_SIDEBAR_EVENT, isReviewSidebarSnapshot, type ReviewSidebarSnapshot } from "../lib/review-sidebar-state.ts";
|
|
123
|
+
import { JOBS_SIDEBAR_EVENT, JOBS_STATUS_KEY, isJobsSidebarSnapshot, type RunningJobDisplay } from "../lib/jobs-sidebar-state.ts";
|
|
118
124
|
import { installSessionChangeCapture } from "../lib/session-change-capture.ts";
|
|
119
125
|
import { SelectionEngine } from "../lib/selection-engine.ts";
|
|
120
126
|
import { withOverlayRepaint } from "../lib/overlay-repaint.ts";
|
|
127
|
+
import { buildNotificationRows } from "../lib/notification-customize.ts";
|
|
121
128
|
|
|
122
129
|
// Gentle Shell: the visual layer gentle-pi puts on top of pi. It installs the
|
|
123
130
|
// status bar, the petal prompt, the working-tree changes widget and overlay,
|
|
@@ -142,6 +149,7 @@ interface ShellBarComponent {
|
|
|
142
149
|
}
|
|
143
150
|
|
|
144
151
|
interface BuildOptions {
|
|
152
|
+
jobs?: RunningJobDisplay[];
|
|
145
153
|
profile?: string;
|
|
146
154
|
home?: string;
|
|
147
155
|
dirty?: number;
|
|
@@ -156,12 +164,15 @@ export interface ShellDeps {
|
|
|
156
164
|
now(): number;
|
|
157
165
|
devBinary(): DevBinaryNotice | undefined;
|
|
158
166
|
resolveWorktree: WorktreeResolver;
|
|
159
|
-
|
|
167
|
+
/** maxBuffer bounds one diff; status and discovery stay unbounded. */
|
|
168
|
+
gitRunner(cwd: string, maxBuffer?: number): GitRunner;
|
|
169
|
+
/** A root Changes may scan: removed worktrees and leftover directories are skipped. */
|
|
170
|
+
rootExists(root: string): boolean;
|
|
160
171
|
vimRuntimeVersion?(): string | undefined;
|
|
161
172
|
}
|
|
162
173
|
|
|
163
174
|
export type ActiveProfileReader = (() => string | undefined) & {
|
|
164
|
-
bind(cwd: string, resolveWorktree: WorktreeResolver): boolean;
|
|
175
|
+
bind(cwd: string, resolveWorktree: WorktreeResolver, sessionId?: string): boolean;
|
|
165
176
|
refresh(): boolean;
|
|
166
177
|
reset(): void;
|
|
167
178
|
};
|
|
@@ -169,12 +180,13 @@ export type ActiveProfileReader = (() => string | undefined) & {
|
|
|
169
180
|
// Unbound reads retain the global file-identity cache, including atomic replacements.
|
|
170
181
|
// Bound reads are snapshots: no filesystem or Git work is done during a frame.
|
|
171
182
|
export function createActiveProfileReader(env: NodeJS.ProcessEnv = process.env): ActiveProfileReader {
|
|
172
|
-
const path = profilesFilePath(env.GENTLE_PI_CONFIG_HOME ?? join(os.homedir(), ".pi", "gentle-ai"));
|
|
183
|
+
const path = profilesFilePath(bundledGentlePiConfigHome(env) ?? env.GENTLE_PI_CONFIG_HOME ?? join(os.homedir(), ".pi", "gentle-ai"));
|
|
173
184
|
let fingerprint: string | undefined;
|
|
174
185
|
let name: string | undefined;
|
|
175
186
|
let bound = false;
|
|
176
187
|
let cwd: string | undefined;
|
|
177
188
|
let identity: WorktreeIdentity | undefined;
|
|
189
|
+
let sessionId: string | undefined;
|
|
178
190
|
let effective: string | undefined;
|
|
179
191
|
const global = () => {
|
|
180
192
|
try {
|
|
@@ -195,24 +207,31 @@ export function createActiveProfileReader(env: NodeJS.ProcessEnv = process.env):
|
|
|
195
207
|
const reader = (() => bound ? effective : global()) as ActiveProfileReader;
|
|
196
208
|
reader.refresh = () => {
|
|
197
209
|
if (!bound) return false;
|
|
198
|
-
|
|
210
|
+
// gentle-shell#1064: the status shows the profile launches use, through
|
|
211
|
+
// the same single rule (explicit Enter binding → profile frozen at
|
|
212
|
+
// startup → live p → P → G only in follow mode), with the existing
|
|
213
|
+
// spelling: "name (session)", "name (local)", "name (repo)", or the bare
|
|
214
|
+
// global name.
|
|
215
|
+
const next = cwd === undefined ? global() : sessionProfileLabel(resolveSessionProfile({
|
|
216
|
+
sessionId,
|
|
199
217
|
cwd,
|
|
200
|
-
configHome: env.GENTLE_PI_CONFIG_HOME ?? join(os.homedir(), ".pi", "gentle-ai"),
|
|
201
|
-
resolveWorktree: () => identity
|
|
202
|
-
|
|
203
|
-
|
|
218
|
+
configHome: bundledGentlePiConfigHome(env) ?? env.GENTLE_PI_CONFIG_HOME ?? join(os.homedir(), ".pi", "gentle-ai"),
|
|
219
|
+
resolveWorktree: () => identity,
|
|
220
|
+
env,
|
|
221
|
+
}));
|
|
204
222
|
const changed = next !== effective;
|
|
205
223
|
effective = next;
|
|
206
224
|
return changed;
|
|
207
225
|
};
|
|
208
|
-
reader.bind = (nextCwd, resolveWorktree) => {
|
|
226
|
+
reader.bind = (nextCwd, resolveWorktree, nextSessionId) => {
|
|
209
227
|
reader.reset();
|
|
210
228
|
cwd = nextCwd;
|
|
229
|
+
sessionId = nextSessionId;
|
|
211
230
|
try { identity = resolveWorktree(nextCwd, nextCwd); } catch { identity = undefined; }
|
|
212
231
|
bound = true;
|
|
213
232
|
return reader.refresh();
|
|
214
233
|
};
|
|
215
|
-
reader.reset = () => { bound = false; cwd = undefined; identity = undefined; effective = undefined; };
|
|
234
|
+
reader.reset = () => { bound = false; cwd = undefined; identity = undefined; sessionId = undefined; effective = undefined; };
|
|
216
235
|
return reader;
|
|
217
236
|
}
|
|
218
237
|
|
|
@@ -226,7 +245,7 @@ function ambientDevBinary(): DevBinaryNotice | undefined {
|
|
|
226
245
|
}
|
|
227
246
|
}
|
|
228
247
|
|
|
229
|
-
const defaultShellDeps: Omit<ShellDeps, "activeProfile"> = { fetch: (input, init) => globalThis.fetch(input, init), now: () => Date.now(), devBinary: ambientDevBinary, resolveWorktree: resolveSessionWorktree, gitRunner: shellGitRunner };
|
|
248
|
+
const defaultShellDeps: Omit<ShellDeps, "activeProfile"> = { fetch: (input, init) => globalThis.fetch(input, init), now: () => Date.now(), devBinary: ambientDevBinary, resolveWorktree: resolveSessionWorktree, gitRunner: (cwd, maxBuffer) => shellGitRunner(cwd, process.env, execFile, maxBuffer), rootExists: (root) => existsSync(join(root, ".git")) };
|
|
230
249
|
|
|
231
250
|
interface AssistantUsageEntry {
|
|
232
251
|
type: string;
|
|
@@ -252,6 +271,37 @@ function sessionCost(ctx: ExtensionContext): number {
|
|
|
252
271
|
return total;
|
|
253
272
|
}
|
|
254
273
|
|
|
274
|
+
type ShellSessionStats = { costTotal: number; usage: ReturnType<ExtensionContext["getContextUsage"]> };
|
|
275
|
+
const shellSessionStats = new WeakMap<object, {
|
|
276
|
+
sessionId: string | undefined;
|
|
277
|
+
leafId: string | null;
|
|
278
|
+
entryCount: number | undefined;
|
|
279
|
+
model: ExtensionContext["model"];
|
|
280
|
+
contextWindow: number | undefined;
|
|
281
|
+
stats: ShellSessionStats;
|
|
282
|
+
}>();
|
|
283
|
+
|
|
284
|
+
function sessionStats(ctx: ExtensionContext): ShellSessionStats {
|
|
285
|
+
const manager = ctx.sessionManager as ExtensionContext["sessionManager"] & { getEntryCount?(): number };
|
|
286
|
+
const compute = (): ShellSessionStats => ({ costTotal: sessionCost(ctx), usage: ctx.getContextUsage() });
|
|
287
|
+
// Append-only session entries move the leaf, including compaction and branch
|
|
288
|
+
// switches. A virtual model's routed physical model comes from the latest
|
|
289
|
+
// response, which is appended as an entry, so the leaf covers it as well.
|
|
290
|
+
// Older/test hosts without that identity must stay uncached.
|
|
291
|
+
if (typeof manager.getLeafId !== "function") return compute();
|
|
292
|
+
const sessionId = manager.getSessionId();
|
|
293
|
+
const leafId = manager.getLeafId();
|
|
294
|
+
const entryCount = manager.getEntryCount?.();
|
|
295
|
+
const model = ctx.model;
|
|
296
|
+
const contextWindow = model?.contextWindow;
|
|
297
|
+
const cached = shellSessionStats.get(manager);
|
|
298
|
+
if (cached && cached.sessionId === sessionId && cached.leafId === leafId &&
|
|
299
|
+
cached.entryCount === entryCount && cached.model === model && cached.contextWindow === contextWindow) return cached.stats;
|
|
300
|
+
const stats = compute();
|
|
301
|
+
shellSessionStats.set(manager, { sessionId, leafId, entryCount, model, contextWindow, stats });
|
|
302
|
+
return stats;
|
|
303
|
+
}
|
|
304
|
+
|
|
255
305
|
export function buildShellBarModel(
|
|
256
306
|
pi: ExtensionAPI,
|
|
257
307
|
ctx: ExtensionContext,
|
|
@@ -259,9 +309,11 @@ export function buildShellBarModel(
|
|
|
259
309
|
options: BuildOptions = {},
|
|
260
310
|
): ShellBarModel {
|
|
261
311
|
const home = options.home ?? os.homedir();
|
|
262
|
-
const usage = ctx
|
|
312
|
+
const { usage, costTotal } = sessionStats(ctx);
|
|
263
313
|
const model = ctx.model;
|
|
314
|
+
const adaptiveReasoning = isNanAdaptiveReasoningModel(model);
|
|
264
315
|
const statuses = Array.from(footerData.getExtensionStatuses().entries())
|
|
316
|
+
.filter(([key]) => options.jobs === undefined || key !== JOBS_STATUS_KEY)
|
|
265
317
|
.sort(([a], [b]) => a.localeCompare(b))
|
|
266
318
|
.map(([, text]) => text);
|
|
267
319
|
return {
|
|
@@ -271,12 +323,13 @@ export function buildShellBarModel(
|
|
|
271
323
|
dirty: options.dirty,
|
|
272
324
|
sessionName: ctx.sessionManager.getSessionName(),
|
|
273
325
|
modelId: model?.id ?? "no-model",
|
|
274
|
-
effort: model?.reasoning ? pi.getThinkingLevel() : undefined,
|
|
326
|
+
effort: model?.reasoning ? (adaptiveReasoning ? "auto" : pi.getThinkingLevel()) : undefined,
|
|
275
327
|
contextPercent: usage?.percent ?? null,
|
|
276
328
|
contextWindow: usage?.contextWindow ?? model?.contextWindow ?? 0,
|
|
277
|
-
costTotal
|
|
329
|
+
costTotal,
|
|
278
330
|
subscription: model ? ctx.modelRegistry.isUsingOAuth(model) : false,
|
|
279
331
|
usage: options.usage,
|
|
332
|
+
...(options.jobs !== undefined ? { jobs: options.jobs } : {}),
|
|
280
333
|
statuses,
|
|
281
334
|
};
|
|
282
335
|
}
|
|
@@ -539,9 +592,10 @@ export class GentlePromptEditor extends CustomEditor {
|
|
|
539
592
|
this.pendingIdleClearText = undefined;
|
|
540
593
|
}
|
|
541
594
|
// Vim owns its modal keys and paste frames. Ordinary editing retains
|
|
542
|
-
// native selection before the prompt's existing input chain
|
|
595
|
+
// native selection before the prompt's existing input chain, but a
|
|
596
|
+
// registered extension shortcut still owns its chord (gentle-shell#1565).
|
|
543
597
|
if (this.vimPolicy === "on") this.handleInputNative(data);
|
|
544
|
-
else this.selectionEngine.handleInput(data, (d) => this.handleInputNative(d));
|
|
598
|
+
else this.selectionEngine.handleInput(data, (d) => this.handleInputNative(d), (d) => this.onExtensionShortcut?.(d) === true);
|
|
545
599
|
}
|
|
546
600
|
|
|
547
601
|
private handleInputNative(data: string): void {
|
|
@@ -1018,6 +1072,8 @@ export class GentlePromptEditor extends CustomEditor {
|
|
|
1018
1072
|
} catch { /* Unknown layout: keep the original rendered prompt. */ }
|
|
1019
1073
|
}
|
|
1020
1074
|
if (this.vimPolicy !== "on") editorLines = this.selectionEngine.decorateRows(editorLines, inner, 0);
|
|
1075
|
+
// SAFETY: this optional host hint is accepted only as an integer within the
|
|
1076
|
+
// rendered row bounds below; absent/invalid hints fall back to all rows.
|
|
1021
1077
|
const visibleCount = (this as unknown as { renderedVisibleLineCount?: number }).renderedVisibleLineCount;
|
|
1022
1078
|
const borderEnd = Number.isInteger(visibleCount) && visibleCount! >= 1 && visibleCount! + 2 <= editorLines.length
|
|
1023
1079
|
? visibleCount! + 2 : editorLines.length;
|
|
@@ -1170,7 +1226,7 @@ const COMMANDS_COMMAND_NAME = "gentle:commands";
|
|
|
1170
1226
|
const OVERLAY_HEIGHT_RATIO = 0.8;
|
|
1171
1227
|
const OVERLAY_MIN_ROWS = 8;
|
|
1172
1228
|
|
|
1173
|
-
export function shellGitRunner(cwd: string, env: NodeJS.ProcessEnv = process.env, run: typeof execFile = execFile): GitRunner {
|
|
1229
|
+
export function shellGitRunner(cwd: string, env: NodeJS.ProcessEnv = process.env, run: typeof execFile = execFile, maxBuffer = Infinity): GitRunner {
|
|
1174
1230
|
// Pi exec cannot replace the inherited environment. Use argv directly and
|
|
1175
1231
|
// a complete sanitized environment for discovery, status, and lazy diffs.
|
|
1176
1232
|
const childEnv = worktreeGitEnvironment(env);
|
|
@@ -1183,8 +1239,10 @@ export function shellGitRunner(cwd: string, env: NodeJS.ProcessEnv = process.env
|
|
|
1183
1239
|
timeout: GIT_TIMEOUT_MS,
|
|
1184
1240
|
// Pi exec accumulates output without a maxBuffer cap. In particular,
|
|
1185
1241
|
// large porcelain inventories must not become partial successful scans.
|
|
1186
|
-
|
|
1242
|
+
// Only a single diff passes a bound, and is told when it hit it.
|
|
1243
|
+
maxBuffer,
|
|
1187
1244
|
}, (error, stdout) => {
|
|
1245
|
+
if ((error as { code?: unknown } | null)?.code === "ERR_CHILD_PROCESS_STDIO_MAXBUFFER") return resolve({ stdout, code: 1, truncated: true });
|
|
1188
1246
|
resolve({ stdout, code: error ? typeof error.code === "number" ? error.code : 1 : 0 });
|
|
1189
1247
|
});
|
|
1190
1248
|
});
|
|
@@ -1193,9 +1251,24 @@ export function shellGitRunner(cwd: string, env: NodeJS.ProcessEnv = process.env
|
|
|
1193
1251
|
export async function loadFileDiff(git: GitRunner, file: ChangedFile): Promise<string> {
|
|
1194
1252
|
const args = file.status === CHANGE_STATUS.UNTRACKED ? ["diff", "--no-index", "--", "/dev/null", file.path] : ["diff", "HEAD", "--", file.path];
|
|
1195
1253
|
const result = await git(args);
|
|
1254
|
+
if (result.truncated || result.stdout.length > DIFF_MAX_BYTES || result.stdout.split("\n", DIFF_MAX_LINES + 2).length > DIFF_MAX_LINES + 1) return DIFF_TOO_LARGE;
|
|
1196
1255
|
return result.code === 0 || result.code === 1 ? result.stdout : "";
|
|
1197
1256
|
}
|
|
1198
1257
|
|
|
1258
|
+
// Untracked files have no numstat; count their lines without reading past the diff bound.
|
|
1259
|
+
const untrackedLines = (root: string): LineCounter => async (path) => {
|
|
1260
|
+
const target = join(root, path);
|
|
1261
|
+
const info = await lstat(target);
|
|
1262
|
+
if (!info.isFile() || info.size > DIFF_MAX_BYTES) return 0;
|
|
1263
|
+
const text = await readFile(target, "utf8");
|
|
1264
|
+
return text === "" ? 0 : text.split("\n").length - (text.endsWith("\n") ? 1 : 0);
|
|
1265
|
+
};
|
|
1266
|
+
// Same-count edits still change size or mtime, so an open diff reloads.
|
|
1267
|
+
const fileRevision = (root: string): FileRevision => async (path) => {
|
|
1268
|
+
const info = await lstat(join(root, path));
|
|
1269
|
+
return `${info.size}:${info.mtimeMs}`;
|
|
1270
|
+
};
|
|
1271
|
+
|
|
1199
1272
|
export interface ExternalEditorHost {
|
|
1200
1273
|
stop(): void;
|
|
1201
1274
|
start(): void;
|
|
@@ -1244,48 +1317,39 @@ const USAGE_FETCH_TIMEOUT_DEFAULT_MS = 10_000;
|
|
|
1244
1317
|
const usageFetchTimeoutMs = (env: NodeJS.ProcessEnv): number => positiveMs(env.GENTLE_PI_SHELL_USAGE_TIMEOUT_MS, USAGE_FETCH_TIMEOUT_DEFAULT_MS);
|
|
1245
1318
|
|
|
1246
1319
|
function changesFingerprint(model: ChangesModel): string {
|
|
1247
|
-
return
|
|
1320
|
+
return model.files.map((file) => `${file.path}:${file.status}:${file.added}:${file.deleted}:${file.diffRevision ?? ""}:${file.countsUnavailable ?? ""}`).join("|");
|
|
1248
1321
|
}
|
|
1249
1322
|
|
|
1250
1323
|
interface OverlayDeps {
|
|
1251
|
-
loadDiff(root: string, file: ChangedFile): string
|
|
1324
|
+
loadDiff(root: string, file: ChangedFile): Promise<string>;
|
|
1252
1325
|
worktrees(): WorktreeChanges[];
|
|
1253
1326
|
refresh(): Promise<ChangesModel>;
|
|
1254
1327
|
apply(ctx: ExtensionContext, model: ChangesModel): void;
|
|
1255
1328
|
pollMs: number;
|
|
1256
|
-
gitForRoot(root: string): GitRunner;
|
|
1257
1329
|
}
|
|
1258
1330
|
|
|
1259
|
-
//
|
|
1260
|
-
// only Git the overlay touches is each root's HEAD, to label its tree.
|
|
1331
|
+
// The overlay polls each root's Git state while it is open; diffs load lazily.
|
|
1261
1332
|
async function showChangesOverlay(ctx: ExtensionContext, deps: OverlayDeps): Promise<void> {
|
|
1262
1333
|
let host: ExternalEditorHost | undefined;
|
|
1263
1334
|
let view: WorktreeChangesView | undefined;
|
|
1264
|
-
// Session evidence knows roots, not branches; label them while the overlay
|
|
1265
|
-
// is open and repaint when Git answers.
|
|
1266
|
-
const labels = new RootBranchLabels(deps.gitForRoot, () => {
|
|
1267
|
-
view?.update(labels.decorate(deps.worktrees()));
|
|
1268
|
-
host?.requestRender();
|
|
1269
|
-
});
|
|
1270
|
-
const worktrees = () => labels.decorate(deps.worktrees());
|
|
1271
1335
|
const refresh = async () => {
|
|
1272
1336
|
const latest = await deps.refresh();
|
|
1273
|
-
view?.update(worktrees());
|
|
1337
|
+
view?.update(deps.worktrees());
|
|
1274
1338
|
deps.apply(ctx, latest);
|
|
1275
1339
|
};
|
|
1276
|
-
const poll = setInterval(() => void refresh(), deps.pollMs);
|
|
1340
|
+
const poll = setInterval(() => void refresh().catch(() => {}), deps.pollMs);
|
|
1277
1341
|
poll.unref();
|
|
1278
1342
|
try {
|
|
1279
1343
|
const chosen = await ctx.ui.custom<{ root: string; file: ChangedFile } | null>(
|
|
1280
1344
|
(tui, theme, _keybindings, done) => {
|
|
1281
1345
|
const close = withOverlayRepaint(tui, done);
|
|
1282
1346
|
host = tui;
|
|
1283
|
-
view = new WorktreeChangesView(worktrees(), {
|
|
1347
|
+
view = new WorktreeChangesView(deps.worktrees(), {
|
|
1284
1348
|
theme,
|
|
1285
1349
|
rows: () => Math.max(OVERLAY_MIN_ROWS, Math.floor(tui.terminal.rows * OVERLAY_HEIGHT_RATIO)),
|
|
1286
|
-
loadDiff:
|
|
1350
|
+
loadDiff: deps.loadDiff,
|
|
1287
1351
|
onOpen: (root, file) => close({ root, file }),
|
|
1288
|
-
onRefresh: () => void refresh(),
|
|
1352
|
+
onRefresh: () => void refresh().catch(() => {}),
|
|
1289
1353
|
onClose: () => close(null),
|
|
1290
1354
|
requestRender: () => tui.requestRender(),
|
|
1291
1355
|
});
|
|
@@ -1446,6 +1510,23 @@ async function fetchFromSource(source: UsageSource, apiKey: string | undefined,
|
|
|
1446
1510
|
}
|
|
1447
1511
|
}
|
|
1448
1512
|
|
|
1513
|
+
// Changes refreshes started without awaiting (turn end, worktree registration)
|
|
1514
|
+
// are tracked so tests can wait for them instead of guessing a delay.
|
|
1515
|
+
const backgroundChangeRefreshes = new Set<Promise<void>>();
|
|
1516
|
+
function trackChangeRefresh(refresh: Promise<void>): void {
|
|
1517
|
+
const tracked = refresh.catch(() => {}).finally(() => { backgroundChangeRefreshes.delete(tracked); });
|
|
1518
|
+
backgroundChangeRefreshes.add(tracked);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
/** Resolves once every background Changes refresh has finished, or after `timeoutMs` for one that never settles. */
|
|
1522
|
+
export async function backgroundChangeRefreshesSettled(timeoutMs = 2000): Promise<void> {
|
|
1523
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
1524
|
+
const deadline = new Promise<void>((resolve) => { timer = setTimeout(resolve, timeoutMs); });
|
|
1525
|
+
const settled = (async () => { while (backgroundChangeRefreshes.size > 0) await Promise.all([...backgroundChangeRefreshes]); })();
|
|
1526
|
+
await Promise.race([settled, deadline]);
|
|
1527
|
+
clearTimeout(timer);
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1449
1530
|
export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = process.env, overrides: Partial<ShellDeps> = {}): void {
|
|
1450
1531
|
installSessionChangeCapture(pi, env, overrides.resolveWorktree ?? resolveSessionWorktree);
|
|
1451
1532
|
if (!shellEnabled(env)) return;
|
|
@@ -1476,20 +1557,35 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1476
1557
|
// overlapping refreshes of the SAME source (or of builtins) can otherwise
|
|
1477
1558
|
// let an older failure settle after a newer success and poison it.
|
|
1478
1559
|
const usageGenerations = new Map<string, number>();
|
|
1560
|
+
// NaN accepts a thinking level for DeepSeek V4 Flash, Qwen 3.8 Flash, and MiMo
|
|
1561
|
+
// but manages their depth itself, so the header reports `auto` and the shell says
|
|
1562
|
+
// why once per model instead of repeating it on every level change.
|
|
1563
|
+
const adaptiveReasoningNotices = new Set<string>();
|
|
1564
|
+
const announceAdaptiveReasoning = (ctx: ExtensionContext, model: { provider: string; id: string; name?: string } | undefined) => {
|
|
1565
|
+
if (!ctx.hasUI || !isNanAdaptiveReasoningModel(model)) return;
|
|
1566
|
+
const key = `${model.provider}/${model.id}`;
|
|
1567
|
+
if (adaptiveReasoningNotices.has(key)) return;
|
|
1568
|
+
adaptiveReasoningNotices.add(key);
|
|
1569
|
+
ctx.ui.notify(`${model.name || model.id} chooses its reasoning depth automatically; the selected thinking level is accepted but does not change it.`, "info");
|
|
1570
|
+
};
|
|
1479
1571
|
// One spelling of the config home, so the pin resolver, the global profiles
|
|
1480
1572
|
// store and the profile reader cannot drift onto two different stores.
|
|
1481
1573
|
const usageConfigHome = gentlePiConfigHome(env);
|
|
1482
1574
|
const usageFetchTimeout = usageFetchTimeoutMs(env);
|
|
1483
|
-
// The subagent routing in force for this session
|
|
1484
|
-
//
|
|
1485
|
-
// the
|
|
1575
|
+
// The subagent routing in force for this session, through the same single
|
|
1576
|
+
// rule launches use (gentle-shell#1064): explicit Enter binding, then the
|
|
1577
|
+
// profile frozen at startup, then the live p → P → G only in follow mode.
|
|
1578
|
+
// Only the profile's own role entries count; the reserved orchestrator key
|
|
1579
|
+
// is not a route.
|
|
1486
1580
|
const activeRoutingModels = (ctx: ExtensionContext): Array<string | undefined> => {
|
|
1487
|
-
const
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1581
|
+
const config = sessionProfileModelProfiles(resolveSessionProfile({
|
|
1582
|
+
sessionId: ctx.sessionManager?.getSessionId?.(),
|
|
1583
|
+
cwd: ctx.cwd,
|
|
1584
|
+
configHome: usageConfigHome,
|
|
1585
|
+
resolveWorktree: deps.resolveWorktree,
|
|
1586
|
+
env,
|
|
1587
|
+
}));
|
|
1588
|
+
return config ? profileRoleEntries(config).map(([, entry]) => entry.model) : [];
|
|
1493
1589
|
};
|
|
1494
1590
|
// A bare model id names a provider only when exactly one provider in the
|
|
1495
1591
|
// registry carries that id; anything else stays untargeted rather than guessed.
|
|
@@ -1684,6 +1780,10 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1684
1780
|
// preference fills it at startup and again on every session start.
|
|
1685
1781
|
const applyCardStyle = () => setCardStyle(resolveCardStyle(animationOptions).style);
|
|
1686
1782
|
applyCardStyle();
|
|
1783
|
+
// The Card content preference rides the same kind of slot: the quiet tool
|
|
1784
|
+
// renderers read it live, so the saved level applies from the first render.
|
|
1785
|
+
const applyCardContent = () => setCardContent(resolveCardContent(animationOptions).content);
|
|
1786
|
+
applyCardContent();
|
|
1687
1787
|
const reportVim = (ctx: ExtensionContext, result: ReturnType<typeof resolveVimPolicy>) => {
|
|
1688
1788
|
const source = result.source === "default" ? "built-in default" : `global file ${result.globalFile}`;
|
|
1689
1789
|
const effective = prompt?.effectiveVimPolicy;
|
|
@@ -1706,10 +1806,11 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1706
1806
|
env,
|
|
1707
1807
|
gentlePiConfigHome: doubleEscCancelConfigHome,
|
|
1708
1808
|
}).policy;
|
|
1709
|
-
let changes:
|
|
1809
|
+
let changes: WorktreeChangesTracker | undefined;
|
|
1710
1810
|
let registry: SessionWorktreeRegistry | undefined;
|
|
1711
1811
|
let currentContext: ExtensionContext | undefined;
|
|
1712
1812
|
let review: ReviewSidebarSnapshot | undefined;
|
|
1813
|
+
let runningJobs: RunningJobDisplay[] | undefined;
|
|
1713
1814
|
const redrawReview = () => {
|
|
1714
1815
|
renderHost?.invalidateSidebar?.();
|
|
1715
1816
|
renderHost?.requestRender();
|
|
@@ -1721,6 +1822,13 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1721
1822
|
review = { state: event.snapshot.state, scope: event.snapshot.scope };
|
|
1722
1823
|
redrawReview();
|
|
1723
1824
|
});
|
|
1825
|
+
const unsubscribeJobs = pi.events.on(JOBS_SIDEBAR_EVENT, (value) => {
|
|
1826
|
+
const event = value as { sessionId?: unknown; snapshot?: unknown } | undefined;
|
|
1827
|
+
if (!currentContext || event?.sessionId !== currentContext.sessionManager.getSessionId()) return;
|
|
1828
|
+
if (!isJobsSidebarSnapshot(event.snapshot)) return;
|
|
1829
|
+
runningJobs = event.snapshot.jobs.map(({ id, label, startedAt }) => ({ id, label, startedAt }));
|
|
1830
|
+
redrawReview();
|
|
1831
|
+
});
|
|
1724
1832
|
pi.on("session_tree", () => {
|
|
1725
1833
|
review = undefined;
|
|
1726
1834
|
redrawReview();
|
|
@@ -1732,34 +1840,43 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1732
1840
|
shown = fingerprint;
|
|
1733
1841
|
showChanges(ctx, model, visualSettings.visibility.changes, (width) => groupedChangesOwner(width));
|
|
1734
1842
|
};
|
|
1843
|
+
const gitForRoot = (root: string) => deps.gitRunner(root);
|
|
1844
|
+
// Changes is the Git state of the session's roots that still exist: read
|
|
1845
|
+
// at turn end, on panel open and by the panel's poll, never at startup.
|
|
1846
|
+
const changesTracker = (ctx: ExtensionContext) => {
|
|
1847
|
+
const sessionId = ctx.sessionManager.getSessionId();
|
|
1848
|
+
const roots = () => {
|
|
1849
|
+
try { return sessionChangeRoots(sessionId, ctx.sessionManager.getEntries()).filter((root) => deps.rootExists(root) && !isHomeOrBroadUserDirectory(root)); }
|
|
1850
|
+
catch { return []; }
|
|
1851
|
+
};
|
|
1852
|
+
return new WorktreeChangesTracker(gitForRoot(ctx.cwd), gitForRoot, untrackedLines, roots, fileRevision);
|
|
1853
|
+
};
|
|
1735
1854
|
const refreshChanges = async (ctx: ExtensionContext) => {
|
|
1736
1855
|
const tracker = changes;
|
|
1737
1856
|
if (!tracker || !ctx.hasUI || registry?.sessionId !== ctx.sessionManager.getSessionId()) return;
|
|
1738
|
-
tracker.restore(ctx.sessionManager.getEntries());
|
|
1739
1857
|
const model = await tracker.refresh();
|
|
1740
1858
|
if (changes === tracker) applyChanges(ctx, model);
|
|
1741
1859
|
};
|
|
1742
|
-
const unsubscribeWorktrees = pi.events.on(SESSION_CHANGE_EVENT, (data) => {
|
|
1743
|
-
if (!currentContext || !registry || (data as { sessionId?: string } | undefined)?.sessionId !== registry.sessionId) return;
|
|
1744
|
-
const notice = (data as { notice?: string } | undefined)?.notice;
|
|
1745
|
-
if (notice) { if (changes) changes.notice = notice; currentContext.ui.notify(notice, "warning"); }
|
|
1746
|
-
void refreshChanges(currentContext);
|
|
1747
|
-
});
|
|
1748
1860
|
pi.registerTool({
|
|
1749
1861
|
name: "session_worktree_register",
|
|
1750
1862
|
renderShell: "self",
|
|
1751
1863
|
label: "Register session worktree",
|
|
1752
|
-
description: "Register a worktree in the same Git clone for session coordination.
|
|
1864
|
+
description: "Register a worktree in the same Git clone for session coordination. /gentle:changes then also lists that worktree's uncommitted Git changes.",
|
|
1753
1865
|
parameters: { type: "object", required: ["path"], additionalProperties: false, properties: { path: { type: "string", description: "Worktree path to include in this session." } } } as never,
|
|
1754
1866
|
async execute(_id, params, _signal, _onUpdate, ctx) {
|
|
1755
1867
|
if (!registry || registry.sessionId !== ctx.sessionManager.getSessionId()) throw new Error("No active session worktree registry.");
|
|
1756
1868
|
const root = registry.register((params as { path: string }).path, "explicit");
|
|
1757
|
-
|
|
1869
|
+
trackChangeRefresh(refreshChanges(ctx));
|
|
1758
1870
|
return { content: [{ type: "text", text: `Registered session worktree: ${root}` }], details: { root, sessionId: registry.sessionId } };
|
|
1759
1871
|
},
|
|
1760
1872
|
});
|
|
1761
1873
|
pi.on("session_start", async (_event, ctx) => {
|
|
1762
1874
|
closeCustomize?.();
|
|
1875
|
+
runningJobs = undefined;
|
|
1876
|
+
adaptiveReasoningNotices.clear();
|
|
1877
|
+
// A restored or default session never emits model_select, so the startup model
|
|
1878
|
+
// announces itself here when it manages its own depth.
|
|
1879
|
+
announceAdaptiveReasoning(ctx, ctx.model);
|
|
1763
1880
|
if (review) {
|
|
1764
1881
|
review = undefined;
|
|
1765
1882
|
redrawReview();
|
|
@@ -1771,10 +1888,11 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1771
1888
|
registry = new SessionWorktreeRegistry(pi, ctx.sessionManager, ctx.cwd, deps.resolveWorktree);
|
|
1772
1889
|
registry.start();
|
|
1773
1890
|
applyCardStyle();
|
|
1891
|
+
applyCardContent();
|
|
1774
1892
|
if (!ctx.hasUI) return;
|
|
1775
1893
|
visualSettings = resolveVisualSettings(animationOptions).settings;
|
|
1776
1894
|
if (!overrides.activeProfile) {
|
|
1777
|
-
profileReader.bind(ctx.cwd, deps.resolveWorktree);
|
|
1895
|
+
profileReader.bind(ctx.cwd, deps.resolveWorktree, ctx.sessionManager.getSessionId());
|
|
1778
1896
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
1779
1897
|
profilePoll = setInterval(() => {
|
|
1780
1898
|
if (currentContext !== ctx || ctx.sessionManager.getSessionId() !== sessionId) return;
|
|
@@ -1784,7 +1902,7 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1784
1902
|
}, 2_000);
|
|
1785
1903
|
profilePoll.unref?.();
|
|
1786
1904
|
}
|
|
1787
|
-
changes =
|
|
1905
|
+
changes = changesTracker(ctx);
|
|
1788
1906
|
const tracker = changes;
|
|
1789
1907
|
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
1790
1908
|
sidebarTui = tui;
|
|
@@ -1796,16 +1914,17 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1796
1914
|
// statuses. The digest is what keeps the fullscreen memo honest, and it
|
|
1797
1915
|
// rebuilds the model exactly as the narrow bottom bar does every frame.
|
|
1798
1916
|
const footerModel = (): ShellBarModel => ({
|
|
1799
|
-
...buildShellBarModel(pi, ctx, footerData, { dirty: tracker.model.files.length, usage: usage.get(ctx.model?.provider ?? ""), profile: deps.activeProfile() }),
|
|
1800
|
-
changes: { files: tracker.model.files.length, added: tracker.model.added, deleted: tracker.model.deleted
|
|
1917
|
+
...buildShellBarModel(pi, ctx, footerData, { dirty: tracker.model.files.length, usage: usage.get(ctx.model?.provider ?? ""), profile: deps.activeProfile(), jobs: runningJobs }),
|
|
1918
|
+
changes: { files: tracker.model.files.length, added: tracker.model.added, deleted: tracker.model.deleted },
|
|
1801
1919
|
review,
|
|
1802
1920
|
});
|
|
1803
1921
|
// At narrow fullscreen widths only one status row paints: a top header
|
|
1804
1922
|
// suppresses the bottom bar in the layout, and otherwise the bottom bar
|
|
1805
1923
|
// takes over the header's data while the below-input header steps aside.
|
|
1806
1924
|
const statusOwner = () => narrowStatusOwner({ mode: (tui as TUI & { mode?: string }).mode, columns: tui.terminal?.columns ?? 0, statusPlacement: visualSettings.statusPlacement, headerPlacement: visualSettings.headerPlacement });
|
|
1925
|
+
const statusCardVisible = () => Boolean(tui.terminal && tui.terminal.columns >= SIDEBAR_BREAKPOINT && sidebarState(tui).ownsHost?.() && visualSettings.statusPlacement !== "hidden");
|
|
1807
1926
|
const belowFloat = (width: number, statuses: boolean) => (tui as TUI & { mode?: string }).mode === "fullscreen"
|
|
1808
|
-
? renderShellBelowInputFloat({ ...footerModel(), ...(statuses ? {} : { statuses: [] }) }, theme, width, usageShortcutKey, visualSettings, tracker.model)
|
|
1927
|
+
? renderShellBelowInputFloat({ ...footerModel(), ...(statuses ? {} : { statuses: [] }), ...(statusCardVisible() ? { jobs: undefined } : {}) }, theme, width, usageShortcutKey, visualSettings, tracker.model)
|
|
1809
1928
|
: undefined;
|
|
1810
1929
|
// Match sidebarPart's live paint ownership, including an unavailable rail.
|
|
1811
1930
|
const footerSuppressed = () => {
|
|
@@ -1831,9 +1950,9 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1831
1950
|
invalidate() {},
|
|
1832
1951
|
});
|
|
1833
1952
|
// The header row carries everything that ticks every frame (model,
|
|
1834
|
-
// effort, context, cost, usage) plus session identity
|
|
1835
|
-
//
|
|
1836
|
-
const headerBar = (width: number) => renderShellHeaderChrome(buildShellHeaderModel(footerModel()), theme, width, usageShortcutKey, visualSettings);
|
|
1953
|
+
// effort, context, cost, usage) plus session identity and the jobs count
|
|
1954
|
+
// when Status is absent. Opaque statuses and working state stay excluded.
|
|
1955
|
+
const headerBar = (width: number) => renderShellHeaderChrome(buildShellHeaderModel(footerModel(), !statusCardVisible() && (statusOwner() === STATUS_OWNER.HEADER || visualSettings.statusPlacement === "hidden")), theme, width, usageShortcutKey, visualSettings);
|
|
1837
1956
|
const disposeHeader = sidebarHeader(tui, {
|
|
1838
1957
|
digest: () => JSON.stringify([footerModel(), tracker.model, visualSettings, cardStyle()]),
|
|
1839
1958
|
render: (width) => headerBar(width).rows,
|
|
@@ -1865,6 +1984,9 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1865
1984
|
}), { placement: "belowEditor" });
|
|
1866
1985
|
return { ...part,
|
|
1867
1986
|
render(width: number) {
|
|
1987
|
+
if (visualSettings.statusPlacement === "hidden" && (tui as TUI & { mode?: string }).mode !== "fullscreen" && runningJobs?.length) {
|
|
1988
|
+
return [truncateToWidth(theme.fg("muted", shellJobsCount(runningJobs.length)), width, "…")];
|
|
1989
|
+
}
|
|
1868
1990
|
const rows = part.render(width);
|
|
1869
1991
|
// Preserve a shrinkable exterior dock row, outside prompt/completion geometry.
|
|
1870
1992
|
return rows.length === 0 && (tui as TUI & { mode?: string }).mode === "fullscreen"
|
|
@@ -1885,7 +2007,8 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1885
2007
|
);
|
|
1886
2008
|
// Hide native feedback only when our petal replaces it. Native transcript
|
|
1887
2009
|
// thinking blocks remain Pi-owned; this changes only the supported loader UI.
|
|
1888
|
-
|
|
2010
|
+
// Inside Herdr the row stays: its native Pi detection reads it.
|
|
2011
|
+
if (ownsPrompt && !keepNativeWorkingRow(env)) ctx.ui.setWorkingVisible(false);
|
|
1889
2012
|
const notice = deps.devBinary();
|
|
1890
2013
|
ctx.ui.setWidget(
|
|
1891
2014
|
DEV_BINARY_WIDGET_KEY,
|
|
@@ -1906,6 +2029,8 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1906
2029
|
// Pi rebuilds the extension runtime after every shutdown (reload, replacement,
|
|
1907
2030
|
// fork, quit), so the factory-level subscription never needs to be restored.
|
|
1908
2031
|
unsubscribeReview();
|
|
2032
|
+
unsubscribeJobs();
|
|
2033
|
+
runningJobs = undefined;
|
|
1909
2034
|
stopProfilePoll();
|
|
1910
2035
|
oddPhaseRegistry.clear(ctx.sessionManager.getSessionId());
|
|
1911
2036
|
oddPhaseRegistry.clearRenderRequest(ctx.sessionManager.getSessionId());
|
|
@@ -1920,26 +2045,33 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1920
2045
|
registry = undefined;
|
|
1921
2046
|
changes = undefined;
|
|
1922
2047
|
currentContext = undefined;
|
|
1923
|
-
unsubscribeWorktrees();
|
|
1924
2048
|
});
|
|
1925
2049
|
const openChanges = async (ctx: ExtensionContext) => {
|
|
1926
2050
|
if (!changes) return;
|
|
1927
2051
|
const tracker = changes;
|
|
1928
2052
|
const model = await tracker.refresh();
|
|
2053
|
+
if (changes !== tracker) return;
|
|
2054
|
+
applyChanges(ctx, model);
|
|
2055
|
+
// A failed root scan is unknown, never clean: say so whenever one failed.
|
|
2056
|
+
const failed = tracker.failedRoots.length;
|
|
2057
|
+
const roots = tracker.scannedRootCount;
|
|
1929
2058
|
if (model.files.length === 0) {
|
|
1930
|
-
ctx.ui.notify(
|
|
2059
|
+
if (failed > 0 && failed === roots) ctx.ui.notify(`Could not read Git status for ${failed} ${failed === 1 ? "root" : "roots"}; uncommitted changes are unknown.`, "error");
|
|
2060
|
+
else if (failed > 0) ctx.ui.notify(`No uncommitted changes in the roots Git could read; Git status failed for ${failed} of ${roots} roots.`, "warning");
|
|
2061
|
+
else ctx.ui.notify("No uncommitted changes in this session's repository or its subagents' worktrees.", "info");
|
|
1931
2062
|
return;
|
|
1932
2063
|
}
|
|
1933
|
-
|
|
2064
|
+
if (failed > 0) ctx.ui.notify(`Git status failed for ${failed} ${failed === 1 ? "root" : "roots"}; ${failed === 1 ? "its" : "their"} changes are not listed.`, "warning");
|
|
2065
|
+
await showChangesOverlay(ctx, { loadDiff: (root, file) => loadFileDiff(deps.gitRunner(root, DIFF_MAX_BYTES), file), worktrees: () => tracker.worktrees, refresh: () => tracker.refresh(), apply: applyChanges, pollMs: changesPollMs(env) });
|
|
1934
2066
|
};
|
|
1935
2067
|
pi.registerCommand(CHANGES_COMMAND_NAME, {
|
|
1936
|
-
description: "Browse
|
|
2068
|
+
description: "Browse uncommitted Git changes (working tree vs HEAD, untracked included) in this session's repository and its subagents' worktrees. Press o to open $EDITOR.",
|
|
1937
2069
|
handler: async (_args, ctx) => openChanges(ctx),
|
|
1938
2070
|
});
|
|
1939
2071
|
const shortcut = changesShortcut(env);
|
|
1940
2072
|
if (shortcut) {
|
|
1941
2073
|
pi.registerShortcut(shortcut as Parameters<ExtensionAPI["registerShortcut"]>[0], {
|
|
1942
|
-
description: "Open
|
|
2074
|
+
description: "Open the session's uncommitted Git changes",
|
|
1943
2075
|
handler: async (ctx) => openChanges(ctx),
|
|
1944
2076
|
});
|
|
1945
2077
|
}
|
|
@@ -1955,10 +2087,10 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1955
2087
|
});
|
|
1956
2088
|
}
|
|
1957
2089
|
pi.registerCommand("gentle:customize", {
|
|
1958
|
-
description: "Configure appearance,
|
|
2090
|
+
description: "Configure appearance, audio notifications, Vim editing, session permissions and global Auto/YOLO defaults, and prompt history capture.",
|
|
1959
2091
|
handler: async (_args, ctx) => {
|
|
1960
2092
|
if (ctx.mode !== "tui" || !ctx.hasUI) {
|
|
1961
|
-
if (ctx.hasUI) ctx.ui.notify("
|
|
2093
|
+
if (ctx.hasUI) ctx.ui.notify("Customization requires an interactive terminal.", "warning");
|
|
1962
2094
|
return;
|
|
1963
2095
|
}
|
|
1964
2096
|
closeCustomize?.();
|
|
@@ -1966,9 +2098,11 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1966
2098
|
let adapter: YoloUiAdapter | undefined;
|
|
1967
2099
|
let unobserve: (() => void) | undefined;
|
|
1968
2100
|
let finish: (() => void) | undefined;
|
|
2101
|
+
let customizeView: VisualCustomizeView | undefined;
|
|
1969
2102
|
const close = () => {
|
|
1970
2103
|
if (closed) return;
|
|
1971
2104
|
closed = true;
|
|
2105
|
+
customizeView?.dispose();
|
|
1972
2106
|
unobserve?.(); adapter?.dispose(); finish?.();
|
|
1973
2107
|
if (closeCustomize === close) closeCustomize = undefined;
|
|
1974
2108
|
};
|
|
@@ -1979,7 +2113,6 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
1979
2113
|
let banner = await readBannerConfig(bannerHome).catch(error => { close(); throw error; });
|
|
1980
2114
|
if (closed) return;
|
|
1981
2115
|
let activeTheme = ctx.ui.theme.name;
|
|
1982
|
-
let customizeView: VisualCustomizeView | undefined;
|
|
1983
2116
|
let requestCustomizeRender: (() => void) | undefined;
|
|
1984
2117
|
let category: CustomizeCategory = "Animations";
|
|
1985
2118
|
const add = (label: CustomizeRow["label"], notice: string, action: () => void | false | Promise<void | false>, preview?: CustomizeRow["preview"]) => rows.push({ category, label, preview, action: async () => {
|
|
@@ -2080,6 +2213,7 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
2080
2213
|
},
|
|
2081
2214
|
});
|
|
2082
2215
|
let yoloDisplay: YoloDisplay = YOLO_DISPLAY.unavailable;
|
|
2216
|
+
let permissionDisplay = "normal", permissionDefault = "normal";
|
|
2083
2217
|
let requestYoloRender = () => {};
|
|
2084
2218
|
let refreshing: Promise<void> | undefined;
|
|
2085
2219
|
let refreshAgain = false;
|
|
@@ -2091,9 +2225,16 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
2091
2225
|
refreshing = (async () => {
|
|
2092
2226
|
while (refreshAgain && !closed) {
|
|
2093
2227
|
refreshAgain = false;
|
|
2094
|
-
const
|
|
2228
|
+
const snapshot = await adapter?.readSnapshot?.().catch(() => undefined);
|
|
2229
|
+
const next = snapshot?.yolo ?? await adapter?.read().catch(() => YOLO_DISPLAY.unavailable) ?? YOLO_DISPLAY.unavailable;
|
|
2095
2230
|
if (closed) return;
|
|
2231
|
+
const nextMode = snapshot?.mode ?? "normal";
|
|
2232
|
+
const nextDefault = snapshot?.defaultMode ?? "normal";
|
|
2233
|
+
if (closed) return;
|
|
2234
|
+
if (refreshAgain) continue;
|
|
2096
2235
|
yoloDisplay = next;
|
|
2236
|
+
permissionDisplay = nextMode;
|
|
2237
|
+
permissionDefault = nextDefault;
|
|
2097
2238
|
requestYoloRender();
|
|
2098
2239
|
}
|
|
2099
2240
|
})().finally(() => { refreshing = undefined; });
|
|
@@ -2113,6 +2254,28 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
2113
2254
|
await refreshYolo();
|
|
2114
2255
|
},
|
|
2115
2256
|
});
|
|
2257
|
+
for (const mode of ["normal", "auto", "yolo"] as const) rows.push({
|
|
2258
|
+
category,
|
|
2259
|
+
label: () => `Session permissions: ${mode.toUpperCase()}${mode === "auto" ? " (beta)" : ""}${permissionDisplay === mode ? " (current)" : ""}`,
|
|
2260
|
+
preview: () => ({ title: "Permissions · current session", sample: mode === "auto"
|
|
2261
|
+
? "independent session-model action checks · extra inference cost · hard safety and native consent remain"
|
|
2262
|
+
: mode === "yolo" ? "ordinary scoped delivery permission · destructive confirmations remain" : "normal checks · overrides an elevated global default" }),
|
|
2263
|
+
action: async () => {
|
|
2264
|
+
if (closed) return;
|
|
2265
|
+
if (!adapter?.setMode || !await adapter.setMode(mode)) ctx.ui.notify("Session mode unchanged — live primary UI unavailable or changed.", "warning");
|
|
2266
|
+
await refreshYolo();
|
|
2267
|
+
},
|
|
2268
|
+
});
|
|
2269
|
+
for (const mode of ["normal", "auto", "yolo"] as const) rows.push({
|
|
2270
|
+
category,
|
|
2271
|
+
label: () => `Default permissions: ${mode.toUpperCase()}${mode === "auto" ? " (beta)" : ""}${permissionDefault === mode ? " (saved)" : ""}`,
|
|
2272
|
+
preview: () => ({ title: "Permissions · user-global default", sample: `New primary sessions start in ${mode.toUpperCase()}; current session stays unchanged. ${mode === "auto" ? "Additional classifier inference uses the session model; usage is separate." : "Independent safety and consent remain."}` }),
|
|
2273
|
+
action: async () => {
|
|
2274
|
+
if (closed) return;
|
|
2275
|
+
if (!adapter?.saveDefault || !await adapter.saveDefault(mode)) ctx.ui.notify("Default unchanged — trusted UI unavailable or preference could not be saved.", "warning");
|
|
2276
|
+
await refreshYolo();
|
|
2277
|
+
},
|
|
2278
|
+
});
|
|
2116
2279
|
category = "History";
|
|
2117
2280
|
// The prompt-history extension re-reads this preference per prompt, so a
|
|
2118
2281
|
// change applies without restart. An explicit GENTLE_PI_HISTORY_CAPTURE
|
|
@@ -2138,6 +2301,7 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
2138
2301
|
else ctx.ui.notify(result.enabled ? "Prompt history capture: on. Applies from the next prompt; stored history is kept." : "Prompt history capture: off. New prompts are not recorded; stored history is kept.", "info");
|
|
2139
2302
|
},
|
|
2140
2303
|
});
|
|
2304
|
+
rows.push(...buildNotificationRows(ctx));
|
|
2141
2305
|
category = "Layout";
|
|
2142
2306
|
for (const value of Object.values(STATUS_PLACEMENT)) add(() => `Status placement: ${value}${visual().statusPlacement === value ? " (current)" : ""}`, pending, () => updateVisual((settings) => ({ ...settings, statusPlacement: value })), () => layoutPreview({ ...visual(), statusPlacement: value }));
|
|
2143
2307
|
for (const value of Object.values(HEADER_PLACEMENT)) add(() => `Header placement: ${value}${visual().headerPlacement === value ? " (current)" : ""}`, pending, () => updateVisual((settings) => ({ ...settings, headerPlacement: value })), () => layoutPreview({ ...visual(), headerPlacement: value }));
|
|
@@ -2160,6 +2324,26 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
2160
2324
|
},
|
|
2161
2325
|
() => ({ title: `Cards · ${style}`, sample: `${cardStylePreview[style]}${resolveCardStyle(home).malformed ? " · malformed or unreadable file" : ""}` }),
|
|
2162
2326
|
);
|
|
2327
|
+
// The content level picks between the quiet tools' result previews
|
|
2328
|
+
// (default) and command-only cards (minimal).
|
|
2329
|
+
const cardContentPreview = {
|
|
2330
|
+
[CARD_CONTENT.DEFAULT]: "≡ read a.ts · result previews + counts · bash rows keep pi's native card",
|
|
2331
|
+
[CARD_CONTENT.MINIMAL]: "≡ read a.ts · command only · ctrl+o expands · bash draws as a Gentle card",
|
|
2332
|
+
};
|
|
2333
|
+
for (const content of Object.values(CARD_CONTENT)) add(
|
|
2334
|
+
() => {
|
|
2335
|
+
const current = resolveCardContent(home);
|
|
2336
|
+
return `Card content: ${content}${current.content === content && !current.malformed ? " (current)" : ""}`;
|
|
2337
|
+
},
|
|
2338
|
+
`Card content: ${content}. Quiet tool cards redraw now; bash rows follow on new calls.`,
|
|
2339
|
+
() => {
|
|
2340
|
+
writeCardContent(content, home);
|
|
2341
|
+
setCardContent(content);
|
|
2342
|
+
renderHost?.requestRender();
|
|
2343
|
+
requestCustomizeRender?.();
|
|
2344
|
+
},
|
|
2345
|
+
() => ({ title: `Cards · content ${content}`, sample: `${cardContentPreview[content]}${resolveCardContent(home).malformed ? " · malformed or unreadable file" : ""}` }),
|
|
2346
|
+
);
|
|
2163
2347
|
category = "Sections";
|
|
2164
2348
|
for (const key of VISUAL_SECTION_KEYS) add(
|
|
2165
2349
|
() => `Section ${key}: ${visual().visibility[key] ? "shown" : "hidden"}`,
|
|
@@ -2256,7 +2440,7 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
2256
2440
|
if (closed) done(null);
|
|
2257
2441
|
requestYoloRender = () => { if (!closed) tui.requestRender(); };
|
|
2258
2442
|
requestCustomizeRender = requestYoloRender;
|
|
2259
|
-
customizeView = new VisualCustomizeView({ rows, profiles, theme, requestRender: requestYoloRender, rowsAvailable: () => Math.max(0, Math.floor(tui.terminal.rows * 0.85) - 2), onError: (error) => { if (!closed) ctx.ui.notify(
|
|
2443
|
+
customizeView = new VisualCustomizeView({ rows, profiles, theme, requestRender: requestYoloRender, rowsAvailable: () => Math.max(0, Math.floor(tui.terminal.rows * 0.85) - 2), onError: (error) => { if (!closed) ctx.ui.notify(`${customizeView?.title() ?? "Customization"}: ${error.message}`, "error"); }, onClose: close });
|
|
2260
2444
|
const view = customizeView;
|
|
2261
2445
|
// Own interaction lifetime here, leaving the shared view unchanged.
|
|
2262
2446
|
return {
|
|
@@ -2416,7 +2600,14 @@ export default function gentleShell(pi: ExtensionAPI, env: NodeJS.ProcessEnv = p
|
|
|
2416
2600
|
}
|
|
2417
2601
|
});
|
|
2418
2602
|
pi.on("agent_end", async (_event, ctx) => {
|
|
2419
|
-
|
|
2603
|
+
// A slow or failing Git never holds the turn end.
|
|
2604
|
+
trackChangeRefresh(refreshChanges(ctx));
|
|
2420
2605
|
void refreshUsage(ctx, false);
|
|
2421
2606
|
});
|
|
2607
|
+
pi.on("model_select", (event, ctx) => {
|
|
2608
|
+
announceAdaptiveReasoning(ctx, event.model);
|
|
2609
|
+
});
|
|
2610
|
+
pi.on("thinking_level_select", (_event, ctx) => {
|
|
2611
|
+
announceAdaptiveReasoning(ctx, ctx.model);
|
|
2612
|
+
});
|
|
2422
2613
|
}
|