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,9 +1,9 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
|
-
import { Text, type Component, type TUI } from "@earendil-works/pi-tui";
|
|
2
|
+
import { ScrollView, Text, truncateToWidth, type Component, type TUI } from "@earendil-works/pi-tui";
|
|
3
3
|
import { appendSystemPromptOnce } from "../lib/append-system-prompt.ts";
|
|
4
4
|
import { NativePointerRegion } from "../lib/native-pointer-region.ts";
|
|
5
5
|
import { panelHeaderRow } from "../lib/shell-card.ts";
|
|
6
|
-
import { sidebarPart } from "../lib/shell-sidebar.ts";
|
|
6
|
+
import { sidebarPart, sidebarState } from "../lib/shell-sidebar.ts";
|
|
7
7
|
import { invalidateSidebar } from "../lib/shell-sidebar-layout.ts";
|
|
8
8
|
import {
|
|
9
9
|
applyTodo,
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
TODO_TOOL_NAME,
|
|
17
17
|
todoCardTone,
|
|
18
18
|
todoPromptBlock,
|
|
19
|
+
todoStateMessage,
|
|
19
20
|
todoSummary,
|
|
20
21
|
type TodoParams,
|
|
21
22
|
type TodoState,
|
|
@@ -24,12 +25,18 @@ import {
|
|
|
24
25
|
// Gentle Todo: the task list the model keeps while it works, drawn as a
|
|
25
26
|
// Gentle Shell card above the editor. Three things keep it current that a
|
|
26
27
|
// static tool description cannot: `write` replaces the whole list in one
|
|
27
|
-
// call,
|
|
28
|
-
//
|
|
29
|
-
//
|
|
28
|
+
// call, each run's system prompt carries a byte-stable rules block while the
|
|
29
|
+
// open tasks reach the model as a request-local trailing message (so list
|
|
30
|
+
// changes never invalidate the cached prompt prefix, gentle-shell#1662), and
|
|
31
|
+
// a list that goes untouched while tasks stay open is marked stale for the
|
|
32
|
+
// human. Long runs also receive one request-local reminder after several
|
|
33
|
+
// tool-use turns, without waking the agent or changing tasks.
|
|
30
34
|
|
|
31
35
|
const WIDGET_KEY = "gentle-todo";
|
|
36
|
+
const STATUS_ENUM = ["pending", "in_progress", "blocked", "done", "dropped"];
|
|
32
37
|
const COLLAPSE_KEY_DEFAULT = "ctrl+shift+t";
|
|
38
|
+
const REMIND_AFTER_TOOL_TURNS = 4;
|
|
39
|
+
const TODO_REMINDER = "The todo plan has not been reviewed during several tool-use turns. Check whether it still matches the work performed and update tasks where needed. Do not mark work done without verification.";
|
|
33
40
|
const TOOL_PARAMETERS = {
|
|
34
41
|
type: "object",
|
|
35
42
|
additionalProperties: false,
|
|
@@ -46,15 +53,15 @@ const TOOL_PARAMETERS = {
|
|
|
46
53
|
properties: {
|
|
47
54
|
id: { type: "integer", description: "Existing task id to keep." },
|
|
48
55
|
title: { type: "string", description: "Short imperative title, e.g. 'Write the parser'." },
|
|
49
|
-
status: { type: "string", enum:
|
|
50
|
-
note: { type: "string", description: "What is happening right now, shown while in_progress, e.g. 'writing tests'." },
|
|
56
|
+
status: { type: "string", enum: STATUS_ENUM, description: "Defaults to pending. blocked: open but waiting on something outside the list; dropped: will not be done, on purpose." },
|
|
57
|
+
note: { type: "string", description: "What is happening right now, shown while in_progress, e.g. 'writing tests'; required for blocked, naming what it waits for, e.g. 'waiting for an admin'." },
|
|
51
58
|
},
|
|
52
59
|
},
|
|
53
60
|
},
|
|
54
61
|
id: { type: "integer", description: "Task id for update." },
|
|
55
62
|
title: { type: "string", description: "Title for add, or a new title for update." },
|
|
56
|
-
status: { type: "string", enum:
|
|
57
|
-
note: { type: "string", description: "Note for add or update." },
|
|
63
|
+
status: { type: "string", enum: STATUS_ENUM, description: "Status for add or update." },
|
|
64
|
+
note: { type: "string", description: "Note for add or update; required for blocked." },
|
|
58
65
|
},
|
|
59
66
|
} as const;
|
|
60
67
|
|
|
@@ -73,12 +80,18 @@ export function todoCollapseKey(env: NodeJS.ProcessEnv = process.env): string |
|
|
|
73
80
|
interface TodoSession {
|
|
74
81
|
state: TodoState;
|
|
75
82
|
turn: number;
|
|
83
|
+
/** Separate from the existing run-based card freshness. */
|
|
84
|
+
toolTurnsSinceWrite: number;
|
|
85
|
+
reminded: boolean;
|
|
86
|
+
wroteThisTurn: boolean;
|
|
76
87
|
collapsed: boolean;
|
|
77
88
|
/** A finished list stays on screen for the turn it finished in, then clears. */
|
|
78
89
|
clearOnNextTurn: boolean;
|
|
79
90
|
ui: ExtensionContext["ui"] | undefined;
|
|
80
91
|
host: { requestRender(): void } | undefined;
|
|
81
92
|
tui: TUI | undefined;
|
|
93
|
+
/** Resolve the visible host when the key is pressed, not when it renders. */
|
|
94
|
+
scrollTodo?: (lines: number) => void;
|
|
82
95
|
}
|
|
83
96
|
|
|
84
97
|
function sessionKey(ctx: ExtensionContext): string {
|
|
@@ -94,7 +107,7 @@ export default function gentleTodo(pi: ExtensionAPI, env: NodeJS.ProcessEnv = pr
|
|
|
94
107
|
const key = sessionKey(ctx);
|
|
95
108
|
let current = sessions.get(key);
|
|
96
109
|
if (!current) {
|
|
97
|
-
current = { state: emptyTodo(), turn: 0, collapsed: false, clearOnNextTurn: false, ui: undefined, host: undefined, tui: undefined };
|
|
110
|
+
current = { state: emptyTodo(), turn: 0, toolTurnsSinceWrite: 0, reminded: false, wroteThisTurn: false, collapsed: false, clearOnNextTurn: false, ui: undefined, host: undefined, tui: undefined };
|
|
98
111
|
sessions.set(key, current);
|
|
99
112
|
}
|
|
100
113
|
return current;
|
|
@@ -106,8 +119,20 @@ export default function gentleTodo(pi: ExtensionAPI, env: NodeJS.ProcessEnv = pr
|
|
|
106
119
|
current.host?.requestRender();
|
|
107
120
|
};
|
|
108
121
|
|
|
109
|
-
const todoCard = (current: TodoSession, theme: Parameters<typeof renderTodoCard>[1],
|
|
122
|
+
const todoCard = (current: TodoSession, theme: Parameters<typeof renderTodoCard>[1], spacer: boolean): Component & { dispose(): void; digest(): string; scrollBy(lines: number): void } => {
|
|
110
123
|
let hovered = false;
|
|
124
|
+
let body: string[] = [];
|
|
125
|
+
let collapsed = current.collapsed;
|
|
126
|
+
// Widgets are measured as leaves, so native layout cannot assign this
|
|
127
|
+
// body a viewport. Use native scroll state and explicitly slice its lines
|
|
128
|
+
// while keeping the collapse header and card footer outside the viewport.
|
|
129
|
+
const scroll = new ScrollView({ render: () => body, invalidate() {} }, { follow: "none", overscroll: "contain" });
|
|
130
|
+
const scrollBy = (lines: number) => {
|
|
131
|
+
if (current.collapsed) return;
|
|
132
|
+
scroll.scrollBy(lines);
|
|
133
|
+
if (current.tui) invalidateSidebar(current.tui);
|
|
134
|
+
current.host?.requestRender();
|
|
135
|
+
};
|
|
111
136
|
// The row the rendered card draws its header on: 0 for the outlined
|
|
112
137
|
// frame, 1 below the float panel's top padding row.
|
|
113
138
|
let headerRow = 0;
|
|
@@ -120,9 +145,25 @@ export default function gentleTodo(pi: ExtensionAPI, env: NodeJS.ProcessEnv = pr
|
|
|
120
145
|
staleTurns: stale,
|
|
121
146
|
collapseKey,
|
|
122
147
|
hovered,
|
|
123
|
-
|
|
148
|
+
scrollable: true,
|
|
124
149
|
});
|
|
125
|
-
|
|
150
|
+
if (collapsed !== current.collapsed) {
|
|
151
|
+
collapsed = current.collapsed;
|
|
152
|
+
scroll.scrollToStart();
|
|
153
|
+
}
|
|
154
|
+
// At most one third of the screen, capped at 16 rows. Read height
|
|
155
|
+
// on every render so a mobile resize immediately releases chat space.
|
|
156
|
+
const terminalRows = current.tui?.terminal?.rows ?? 48;
|
|
157
|
+
const height = Math.min(16, Math.floor(terminalRows / 3));
|
|
158
|
+
const bodyStart = headerRow === 1 ? 3 : 1;
|
|
159
|
+
body = lines.slice(bodyStart, -1);
|
|
160
|
+
const overflow = !current.collapsed && lines.length + Number(spacer) > height;
|
|
161
|
+
const room = Math.max(1, height - bodyStart - 1 - Number(spacer) - Number(overflow));
|
|
162
|
+
scroll.updateLayout(body.length, current.collapsed ? body.length : room, () => current.host?.requestRender());
|
|
163
|
+
const visible = scroll.render(width).slice(scroll.scrollTop, scroll.scrollTop + scroll.viewportHeight);
|
|
164
|
+
const output = [...lines.slice(0, bodyStart), ...visible, ...lines.slice(-1)];
|
|
165
|
+
if (overflow) output.push(theme.fg("muted", truncateToWidth(`↑↓ ${scroll.scrollTop + 1}–${scroll.scrollTop + visible.length}/${body.length} · ctrl+shift+↑/↓`, width)));
|
|
166
|
+
return spacer && output.length > 0 ? [...output, ""] : output;
|
|
126
167
|
},
|
|
127
168
|
invalidate() {
|
|
128
169
|
hovered = false;
|
|
@@ -148,8 +189,17 @@ export default function gentleTodo(pi: ExtensionAPI, env: NodeJS.ProcessEnv = pr
|
|
|
148
189
|
toggle(current);
|
|
149
190
|
return { handled: true, render: true };
|
|
150
191
|
},
|
|
192
|
+
onWheel(event) {
|
|
193
|
+
if (current.collapsed || body.length <= scroll.viewportHeight) return undefined;
|
|
194
|
+
scrollBy(event.wheelDelta ?? 0);
|
|
195
|
+
return { handled: true, render: true };
|
|
196
|
+
},
|
|
151
197
|
});
|
|
152
198
|
return {
|
|
199
|
+
scrollBy,
|
|
200
|
+
// Sidebar caches must observe height-only resizes and local viewport
|
|
201
|
+
// changes, even when terminal width and task content stay the same.
|
|
202
|
+
digest: () => JSON.stringify([current.tui?.terminal?.rows, current.collapsed, current.turn, hovered, scroll.scrollTop]),
|
|
153
203
|
render: (width) => region.render(width),
|
|
154
204
|
handleMouse: (event) => region.handleMouse(event),
|
|
155
205
|
invalidate: () => region.invalidate(),
|
|
@@ -161,6 +211,7 @@ export default function gentleTodo(pi: ExtensionAPI, env: NodeJS.ProcessEnv = pr
|
|
|
161
211
|
if (current.tui) invalidateSidebar(current.tui);
|
|
162
212
|
if (!current.ui) return;
|
|
163
213
|
if (current.state.tasks.length === 0) {
|
|
214
|
+
current.scrollTodo = undefined;
|
|
164
215
|
current.ui.setWidget(WIDGET_KEY, undefined);
|
|
165
216
|
return;
|
|
166
217
|
}
|
|
@@ -168,7 +219,15 @@ export default function gentleTodo(pi: ExtensionAPI, env: NodeJS.ProcessEnv = pr
|
|
|
168
219
|
current.ui.setWidget(WIDGET_KEY, (tui, theme) => {
|
|
169
220
|
snapshot.host = tui;
|
|
170
221
|
snapshot.tui = tui;
|
|
171
|
-
|
|
222
|
+
const bottom = todoCard(snapshot, theme, true);
|
|
223
|
+
const rail = todoCard(snapshot, theme, false);
|
|
224
|
+
snapshot.scrollTodo = (lines) => {
|
|
225
|
+
// Cached rail lines do not re-render when a narrow→wide resize
|
|
226
|
+
// restores the sidebar. Select ownership from the live layout.
|
|
227
|
+
const state = tui.terminal ? sidebarState(tui) : undefined;
|
|
228
|
+
(state?.active && state.ownsHost?.() ? rail : bottom).scrollBy(lines);
|
|
229
|
+
};
|
|
230
|
+
return sidebarPart(tui, "todo", bottom, rail);
|
|
172
231
|
});
|
|
173
232
|
};
|
|
174
233
|
|
|
@@ -183,6 +242,8 @@ export default function gentleTodo(pi: ExtensionAPI, env: NodeJS.ProcessEnv = pr
|
|
|
183
242
|
"Mark a task in_progress before starting it and done right after finishing it; keep exactly one task in_progress.",
|
|
184
243
|
"Prefer write with the complete list whenever the plan changes; keep ids of tasks that already exist.",
|
|
185
244
|
"Never mark a task done while tests fail or the work is partial; add a task for the blocker instead.",
|
|
245
|
+
"Mark a task blocked with a note naming what it waits for only when that is outside the list (a person, another team, an authorization); a prerequisite in the list is ordering, so keep the task pending. Return it to pending once the condition holds.",
|
|
246
|
+
"When the plan changes, mark tasks it made obsolete dropped, never done; dropped tasks no longer count as open.",
|
|
186
247
|
],
|
|
187
248
|
parameters: TOOL_PARAMETERS,
|
|
188
249
|
executionMode: "sequential",
|
|
@@ -200,6 +261,11 @@ export default function gentleTodo(pi: ExtensionAPI, env: NodeJS.ProcessEnv = pr
|
|
|
200
261
|
if (!result.error) {
|
|
201
262
|
current.state = result.state;
|
|
202
263
|
current.clearOnNextTurn = false;
|
|
264
|
+
if (params.action !== "list") {
|
|
265
|
+
current.toolTurnsSinceWrite = 0;
|
|
266
|
+
current.reminded = false;
|
|
267
|
+
current.wroteThisTurn = true;
|
|
268
|
+
}
|
|
203
269
|
if (current.tui) invalidateSidebar(current.tui);
|
|
204
270
|
}
|
|
205
271
|
return {
|
|
@@ -218,6 +284,15 @@ export default function gentleTodo(pi: ExtensionAPI, env: NodeJS.ProcessEnv = pr
|
|
|
218
284
|
});
|
|
219
285
|
}
|
|
220
286
|
|
|
287
|
+
// Pi binds ctrl+shift+up/down to prompt navigation (tui.altScreen.*Prompt) and
|
|
288
|
+
// alt+up/down to dequeue and model reordering; alt+shift+up/down are free.
|
|
289
|
+
for (const [key, lines] of [["alt+shift+up", -3], ["alt+shift+down", 3]] as const) {
|
|
290
|
+
pi.registerShortcut(key, {
|
|
291
|
+
description: `Scroll the Todo list ${lines < 0 ? "up" : "down"}`,
|
|
292
|
+
handler: async (ctx) => session(ctx).scrollTodo?.(lines),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
|
|
221
296
|
pi.on("session_start", (_event, ctx) => {
|
|
222
297
|
const current = session(ctx);
|
|
223
298
|
// A list that was already finished when the session was left is history,
|
|
@@ -225,6 +300,9 @@ export default function gentleTodo(pi: ExtensionAPI, env: NodeJS.ProcessEnv = pr
|
|
|
225
300
|
const replayed = replayTodo(ctx.sessionManager.getBranch());
|
|
226
301
|
current.state = replayed.tasks.length > 0 && todoSummary(replayed).open === 0 ? { ...replayed, tasks: [] } : replayed;
|
|
227
302
|
current.turn = ctx.sessionManager.getBranch().filter((entry) => (entry as { type?: string }).type === "message" && (entry as { message?: { role?: string } }).message?.role === "user").length;
|
|
303
|
+
current.toolTurnsSinceWrite = 0;
|
|
304
|
+
current.reminded = false;
|
|
305
|
+
current.wroteThisTurn = false;
|
|
228
306
|
current.ui = ctx.hasUI ? ctx.ui : undefined;
|
|
229
307
|
show(current);
|
|
230
308
|
});
|
|
@@ -242,14 +320,41 @@ export default function gentleTodo(pi: ExtensionAPI, env: NodeJS.ProcessEnv = pr
|
|
|
242
320
|
current.clearOnNextTurn = false;
|
|
243
321
|
show(current);
|
|
244
322
|
}
|
|
245
|
-
const block = todoPromptBlock(current.state, staleTurns(current.state, current.turn));
|
|
246
|
-
if (!block) return undefined;
|
|
247
323
|
// gentle-shell#1485: pi-claude-bridge drops a handler-returned
|
|
248
|
-
// systemPrompt, so the
|
|
249
|
-
|
|
324
|
+
// systemPrompt, so the rules block goes through appendSystemPrompt. It is
|
|
325
|
+
// the same bytes on every turn, list or no list (gentle-shell#1662).
|
|
326
|
+
appendSystemPromptOnce(event.systemPromptOptions, todoPromptBlock());
|
|
250
327
|
return undefined;
|
|
251
328
|
});
|
|
252
329
|
|
|
330
|
+
pi.on("turn_end", (event, ctx) => {
|
|
331
|
+
const current = session(ctx);
|
|
332
|
+
// A turn containing a valid todo write is already reconciled. Failed
|
|
333
|
+
// writes and list reads deliberately do not refresh this counter.
|
|
334
|
+
if (!current.wroteThisTurn && event.message.role === "assistant" && event.message.content.some((part) => part.type === "toolCall")) {
|
|
335
|
+
current.toolTurnsSinceWrite += 1;
|
|
336
|
+
}
|
|
337
|
+
current.wroteThisTurn = false;
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
pi.on("context", (event, ctx) => {
|
|
341
|
+
const current = session(ctx);
|
|
342
|
+
// Context transforms are request-local: no transcript entry, UI
|
|
343
|
+
// notification, continuation or provider-specific wake is needed. The
|
|
344
|
+
// current list trails the transcript (gentle-shell#1662), then the
|
|
345
|
+
// one-shot reminder when it is due.
|
|
346
|
+
const trailing: Array<{ role: "custom"; customType: string; content: string; display: false; timestamp: number }> = [];
|
|
347
|
+
const state = todoStateMessage(current.state);
|
|
348
|
+
if (state !== undefined) trailing.push({ role: "custom", customType: "gentle-todo-state", content: state, display: false, timestamp: Date.now() });
|
|
349
|
+
const actionable = current.state.tasks.some((task) => task.status === "pending" || task.status === "in_progress");
|
|
350
|
+
if (!current.reminded && current.toolTurnsSinceWrite >= REMIND_AFTER_TOOL_TURNS && actionable) {
|
|
351
|
+
current.reminded = true;
|
|
352
|
+
trailing.push({ role: "custom", customType: "gentle-todo-reminder", content: TODO_REMINDER, display: false, timestamp: Date.now() });
|
|
353
|
+
}
|
|
354
|
+
if (trailing.length === 0) return undefined;
|
|
355
|
+
return { messages: [...event.messages, ...trailing] };
|
|
356
|
+
});
|
|
357
|
+
|
|
253
358
|
pi.on("tool_execution_end", (event, ctx) => {
|
|
254
359
|
if (event.toolName !== TODO_TOOL_NAME) return;
|
|
255
360
|
show(session(ctx));
|
|
@@ -37,7 +37,7 @@ import {
|
|
|
37
37
|
type TuiMouseEvent,
|
|
38
38
|
truncateToWidth,
|
|
39
39
|
} from "@earendil-works/pi-tui";
|
|
40
|
-
import { gentlePiConfigHome } from "../../lib/agent-home.ts";
|
|
40
|
+
import { bundledGentlePiAgentHome, gentlePiConfigHome } from "../../lib/agent-home.ts";
|
|
41
41
|
import {
|
|
42
42
|
historyCaptureEnabled,
|
|
43
43
|
historyCaptureEnvOverride,
|
|
@@ -1316,14 +1316,19 @@ export default function promptHistoryExtension(
|
|
|
1316
1316
|
): void {
|
|
1317
1317
|
const env = deps.env ?? process.env;
|
|
1318
1318
|
const configHome = deps.gentlePiConfigHome ?? gentlePiConfigHome(env);
|
|
1319
|
-
// Per-prompt gate: re-read so a Customize toggle applies live.
|
|
1320
|
-
|
|
1321
|
-
|
|
1319
|
+
// Per-prompt gate: re-read so a Customize toggle applies live. A delegated
|
|
1320
|
+
// child never captures: its prompt is a delegation brief, not user history,
|
|
1321
|
+
// and the parent owns the store's init and GC (gentle-shell#1690).
|
|
1322
|
+
const capturing = () => env.GENTLE_PI_AGENTS_CHILD !== "1" && captureEnabled(env, configHome);
|
|
1323
|
+
// S9: a bundled Gentle Shell keeps the whole store in the agent home Pi runs
|
|
1324
|
+
// with; everywhere else it stays under ~/.pi/agent, where existing ones live.
|
|
1325
|
+
const bundledAgentDir = bundledGentlePiAgentHome(env);
|
|
1326
|
+
const root = deps.root ?? (bundledAgentDir === undefined ? PI_HISTORY_ROOT : join(bundledAgentDir, "history"));
|
|
1322
1327
|
const cwd = deps.cwd ?? process.cwd();
|
|
1323
1328
|
const instanceId = deps.instanceId ?? randomUUID();
|
|
1324
1329
|
const now = deps.now ?? Date.now;
|
|
1325
|
-
const agentDir = deps.agentDir ?? AGENT_DIR;
|
|
1326
|
-
const sessionsRoot = deps.sessionsRoot ?? SESSIONS_ROOT;
|
|
1330
|
+
const agentDir = deps.agentDir ?? bundledAgentDir ?? AGENT_DIR;
|
|
1331
|
+
const sessionsRoot = deps.sessionsRoot ?? (bundledAgentDir === undefined ? SESSIONS_ROOT : join(bundledAgentDir, "sessions"));
|
|
1327
1332
|
let writerState: SessionWriterState | null = null;
|
|
1328
1333
|
|
|
1329
1334
|
/**
|
package/extensions/pi-pretty.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
createBashToolDefinition,
|
|
3
|
+
createFindToolDefinition,
|
|
4
|
+
createGrepToolDefinition,
|
|
5
|
+
createLsToolDefinition,
|
|
6
|
+
createReadToolDefinition,
|
|
7
|
+
type ExtensionAPI,
|
|
8
|
+
type ExtensionContext,
|
|
9
|
+
} from "@earendil-works/pi-coding-agent";
|
|
10
|
+
import { bundledGentlePiAgentHome } from "../lib/agent-home.ts";
|
|
11
|
+
import { keepNativeWorkingRow, shellEnabled } from "../lib/shell-bar.ts";
|
|
3
12
|
import {
|
|
4
13
|
mergeDisabledTools,
|
|
5
14
|
PI_PRETTY_SUPPRESSED_TOOL_NAMES,
|
|
@@ -26,12 +35,37 @@ async function loadPiPrettyExtension(): Promise<PiPrettyExtension> {
|
|
|
26
35
|
);
|
|
27
36
|
}
|
|
28
37
|
|
|
38
|
+
// S9: pi-pretty keeps its FFF database under deps.sdk.getAgentDir(), falling
|
|
39
|
+
// back to $HOME/.pi/agent whatever agent home Pi runs with. In a bundled
|
|
40
|
+
// Gentle Shell it gets the agent home; a deps.sdk also replaces the host tool
|
|
41
|
+
// factories it would otherwise import, so the same five come along. Elsewhere,
|
|
42
|
+
// and for a host that already passes its own sdk, Pi's deps stay unchanged.
|
|
43
|
+
function withAgentHome(deps: unknown, env: NodeJS.ProcessEnv): unknown {
|
|
44
|
+
const agentHome = bundledGentlePiAgentHome(env);
|
|
45
|
+
const given = (deps ?? {}) as { sdk?: unknown };
|
|
46
|
+
if (agentHome === undefined || given.sdk !== undefined) return deps;
|
|
47
|
+
return {
|
|
48
|
+
...given,
|
|
49
|
+
sdk: {
|
|
50
|
+
createReadToolDefinition,
|
|
51
|
+
createBashToolDefinition,
|
|
52
|
+
createLsToolDefinition,
|
|
53
|
+
createFindToolDefinition,
|
|
54
|
+
createGrepToolDefinition,
|
|
55
|
+
getAgentDir: () => agentHome,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
29
60
|
export default async function gentlePiPrettyExtension(
|
|
30
61
|
pi: unknown,
|
|
31
62
|
deps?: unknown,
|
|
32
63
|
bundled?: PiPrettyExtension,
|
|
33
64
|
env: NodeJS.ProcessEnv = process.env,
|
|
34
65
|
): Promise<unknown> {
|
|
66
|
+
// gentle-shell#1690: a delegated child has no transcript to prettify, and
|
|
67
|
+
// the upstream fallback would start its own file indexing in every child.
|
|
68
|
+
if (env.GENTLE_PI_AGENTS_CHILD === "1") return undefined;
|
|
35
69
|
if (quietToolsEnabled()) {
|
|
36
70
|
process.env.PRETTY_DISABLE_TOOLS = mergeDisabledTools(
|
|
37
71
|
process.env.PRETTY_DISABLE_TOOLS,
|
|
@@ -39,6 +73,7 @@ export default async function gentlePiPrettyExtension(
|
|
|
39
73
|
);
|
|
40
74
|
}
|
|
41
75
|
const extension = bundled ?? await loadPiPrettyExtension();
|
|
76
|
+
deps = withAgentHome(deps, env);
|
|
42
77
|
if (!shellEnabled(env)) return extension(pi, deps);
|
|
43
78
|
const api = pi as ExtensionAPI;
|
|
44
79
|
// Scope interception to this bundled dependency, never to the shared host UI.
|
|
@@ -77,8 +112,9 @@ export default async function gentlePiPrettyExtension(
|
|
|
77
112
|
// GentlePromptEditor already carries the live working state in its frame.
|
|
78
113
|
// Hide Pi's separate loader row to avoid repeating Thinking above the input;
|
|
79
114
|
// transcript thinking blocks remain untouched as historical reasoning markers.
|
|
115
|
+
// Inside Herdr the row stays: its native Pi detection reads it.
|
|
80
116
|
api.on("session_start", (_event, ctx) => {
|
|
81
|
-
if (ctx.mode === "tui") ctx.ui.setWorkingVisible(false);
|
|
117
|
+
if (ctx.mode === "tui" && !keepNativeWorkingRow(env)) ctx.ui.setWorkingVisible(false);
|
|
82
118
|
});
|
|
83
119
|
api.on("session_shutdown", (_event, ctx) => {
|
|
84
120
|
if (ctx.mode !== "tui") return;
|
|
@@ -12,12 +12,14 @@ import { Text, type Component } from "@earendil-works/pi-tui";
|
|
|
12
12
|
import { homedir } from "node:os";
|
|
13
13
|
import { isAbsolute } from "node:path";
|
|
14
14
|
import { resolveGentleAiDevBinaryOverride, type GentleAiDevBinaryOverride } from "../lib/gentle-ai-binary.ts";
|
|
15
|
+
import { CARD_CONTENT, cardContent } from "../lib/card-content-policy.ts";
|
|
15
16
|
import { GentleAiElapsedTimingLedger } from "../lib/gentle-ai-elapsed-store.ts";
|
|
16
17
|
import { quietToolsEnabled } from "../lib/quiet-tools-config.ts";
|
|
17
18
|
import { registerCompactCodemode } from "../lib/codemode-renderer.ts";
|
|
19
|
+
import { offerBuiltinCodemodeOptOut, type BuiltinCodemodeOptOutOptions } from "../lib/builtin-codemode-optout.ts";
|
|
18
20
|
import { getGentleAiRenderState, renderGentleAiLifecycleCall, renderGentleAiResult, type GentleAiRenderContext } from "../lib/gentle-ai-renderer.ts";
|
|
19
21
|
import {
|
|
20
|
-
CARD_TONE, cardAwaitingResult, cardBottom, cardInnerWidth, cardLine, cardRunningLine, cardTopRows, floatRows, markCardResult,
|
|
22
|
+
CARD_TONE, cardAwaitingResult, cardBottom, cardInnerWidth, cardLine, cardRunningLine, cardStyle, cardTopRows, floatRows, markCardResult,
|
|
21
23
|
type CardRowContext, type CardTheme,
|
|
22
24
|
} from "../lib/shell-card.ts";
|
|
23
25
|
import { sanitizeTerminalText, stripAnsi } from "../lib/terminal-theme.ts";
|
|
@@ -55,6 +57,11 @@ const COLLAPSED_COUNT_LABELS: Partial<Record<QuietToolName, string>> = {
|
|
|
55
57
|
ls: "entries",
|
|
56
58
|
};
|
|
57
59
|
|
|
60
|
+
// Cards that show only the command while the Card content preference is
|
|
61
|
+
// minimal: their results stay one expand key away and never add rows to the
|
|
62
|
+
// transcript view.
|
|
63
|
+
const COMMAND_ONLY_TOOLS: ReadonlySet<QuietToolName> = new Set(["read", "bash", "grep", "find", "ls", "edit", "write"]);
|
|
64
|
+
|
|
58
65
|
const COLLAPSED_TAIL_LINE_LIMIT = 10;
|
|
59
66
|
const PREVIEW_LINE_LIMIT = 3;
|
|
60
67
|
|
|
@@ -423,7 +430,10 @@ export function formatToolResultOutput(
|
|
|
423
430
|
): string {
|
|
424
431
|
const text = safeText(extractTextContent(result));
|
|
425
432
|
if (expanded) {
|
|
426
|
-
|
|
433
|
+
// Write results only acknowledge the operation; the body lives in the call args.
|
|
434
|
+
const detail = toolName === "write" && !isError && typeof args?.content === "string" && args.content.length > 0
|
|
435
|
+
? safeText(args.content)
|
|
436
|
+
: expandedResultText(toolName, result, text);
|
|
427
437
|
return detail ? `\n${detail}` : "";
|
|
428
438
|
}
|
|
429
439
|
if (isError) {
|
|
@@ -470,7 +480,9 @@ interface ToolRenderContextLike {
|
|
|
470
480
|
lastComponent?: unknown;
|
|
471
481
|
state?: unknown;
|
|
472
482
|
cwd?: string;
|
|
473
|
-
|
|
483
|
+
expanded?: boolean;
|
|
484
|
+
toolCallId?: string;
|
|
485
|
+
durationMs?: number;
|
|
474
486
|
}
|
|
475
487
|
|
|
476
488
|
function formatToolCall(toolName: QuietToolName, args: Record<string, unknown>, theme: ThemeLike): string {
|
|
@@ -482,7 +494,9 @@ function formatToolCall(toolName: QuietToolName, args: Record<string, unknown>,
|
|
|
482
494
|
case "bash": {
|
|
483
495
|
const command = safeText(asString(args.command, "..."));
|
|
484
496
|
const timeout = typeof args.timeout === "number" ? theme.fg("muted", ` (timeout ${args.timeout}s)`) : "";
|
|
485
|
-
|
|
497
|
+
// Minimal cards title bash with the bare command; the card glyph supplies the `$` prompt.
|
|
498
|
+
const title = cardContent() === CARD_CONTENT.MINIMAL ? command : `bash $ ${command}`;
|
|
499
|
+
return `${theme.fg("toolTitle", theme.bold(title))}${timeout}`;
|
|
486
500
|
}
|
|
487
501
|
case "grep": {
|
|
488
502
|
let text = `${theme.fg("toolTitle", theme.bold("grep"))} ${theme.fg("accent", `/${safeText(asString(args.pattern))}/`)} in ${safeText(shortenPath(args.path) || ".")}`;
|
|
@@ -568,7 +582,23 @@ function toolTone(pending: boolean, failed: boolean): ToolTone {
|
|
|
568
582
|
return pending ? CARD_TONE.WARNING : CARD_TONE.SUCCESS;
|
|
569
583
|
}
|
|
570
584
|
|
|
585
|
+
/** One settled frame per component: bounded memory, including all card chrome. */
|
|
586
|
+
class ToolCardRows {
|
|
587
|
+
private frame?: { width: number; style: ReturnType<typeof cardStyle>; running: boolean; lines: string[] };
|
|
588
|
+
|
|
589
|
+
render(width: number, running: boolean, build: () => string[]): string[] {
|
|
590
|
+
const style = cardStyle();
|
|
591
|
+
if (this.frame?.width === width && this.frame.style === style && this.frame.running === running) return this.frame.lines;
|
|
592
|
+
const lines = build();
|
|
593
|
+
this.frame = { width, style, running, lines };
|
|
594
|
+
return lines;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
invalidate(): void { this.frame = undefined; }
|
|
598
|
+
}
|
|
599
|
+
|
|
571
600
|
class ToolCardTop implements Component {
|
|
601
|
+
private readonly rows = new ToolCardRows();
|
|
572
602
|
private readonly header: () => string;
|
|
573
603
|
private readonly glyph: string;
|
|
574
604
|
private readonly tone: ToolTone;
|
|
@@ -590,41 +620,44 @@ class ToolCardTop implements Component {
|
|
|
590
620
|
const target = Math.max(0, Math.floor(width));
|
|
591
621
|
if (target === 0) return [];
|
|
592
622
|
const running = this.row !== undefined && cardAwaitingResult(this.row);
|
|
593
|
-
return floatRows(this.tone, this.theme, target, (inner) => ({
|
|
623
|
+
return this.rows.render(target, running, () => floatRows(this.tone, this.theme, target, (inner) => ({
|
|
594
624
|
head: cardTopRows({ title: this.header(), glyph: this.glyph, body: [], tone: this.tone }, this.theme, inner, this.hint),
|
|
595
625
|
body: running ? [cardRunningLine(this.tone, this.theme, inner)] : undefined,
|
|
596
626
|
bottom: running ? cardBottom(this.tone, this.theme, inner) : undefined,
|
|
597
|
-
}));
|
|
627
|
+
})));
|
|
598
628
|
}
|
|
599
629
|
|
|
600
|
-
invalidate(): void {}
|
|
630
|
+
invalidate(): void { this.rows.invalidate(); }
|
|
601
631
|
}
|
|
602
632
|
|
|
603
633
|
class ToolCardBody implements Component {
|
|
634
|
+
private readonly rows = new ToolCardRows();
|
|
604
635
|
private readonly inner: () => Component;
|
|
605
636
|
private readonly tone: ToolTone;
|
|
606
637
|
private readonly theme: CardTheme;
|
|
607
638
|
|
|
608
|
-
|
|
639
|
+
private readonly cacheable: boolean;
|
|
640
|
+
|
|
641
|
+
constructor(inner: () => Component, tone: ToolTone, theme: CardTheme, cacheable = true) {
|
|
609
642
|
this.inner = inner;
|
|
610
643
|
this.tone = tone;
|
|
611
644
|
this.theme = theme;
|
|
645
|
+
this.cacheable = cacheable;
|
|
612
646
|
}
|
|
613
647
|
|
|
614
648
|
/** Renders the inner component between the card sides and closes the frame, even when the result has no rows. */
|
|
615
649
|
render(width: number): string[] {
|
|
616
650
|
const target = Math.max(0, Math.floor(width));
|
|
617
651
|
if (target === 0) return [];
|
|
618
|
-
|
|
652
|
+
const build = () => floatRows(this.tone, this.theme, target, (inner) => ({
|
|
619
653
|
body: this.inner().render(cardInnerWidth(inner)).map((line) => cardLine(line.trimEnd(), this.tone, this.theme, inner)),
|
|
620
654
|
bottom: cardBottom(this.tone, this.theme, inner),
|
|
621
655
|
afterHeading: true,
|
|
622
656
|
}));
|
|
657
|
+
return this.cacheable ? this.rows.render(target, false, build) : build();
|
|
623
658
|
}
|
|
624
659
|
|
|
625
|
-
invalidate(): void {
|
|
626
|
-
// Content is rebuilt with the current theme at render time.
|
|
627
|
-
}
|
|
660
|
+
invalidate(): void { this.rows.invalidate(); }
|
|
628
661
|
}
|
|
629
662
|
|
|
630
663
|
function shouldRenderPreviewTail(
|
|
@@ -649,11 +682,15 @@ function hasImageContent(result: AgentToolResult<unknown>): boolean {
|
|
|
649
682
|
return result.content.some((content) => content.type === "image");
|
|
650
683
|
}
|
|
651
684
|
|
|
652
|
-
function
|
|
685
|
+
function isRenderArgs(value: unknown): value is Record<string, unknown> {
|
|
686
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function sanitizedRenderContext(context: (Omit<ToolRenderContextLike, "args"> & { args?: unknown }) | undefined): ToolRenderContextLike {
|
|
653
690
|
if (!context) return { args: {} };
|
|
654
691
|
return {
|
|
655
692
|
...context,
|
|
656
|
-
args: sanitizedArgs(context.args),
|
|
693
|
+
args: sanitizedArgs(isRenderArgs(context.args) ? context.args : undefined),
|
|
657
694
|
cwd: typeof context.cwd === "string" ? safeText(context.cwd) : context.cwd,
|
|
658
695
|
};
|
|
659
696
|
}
|
|
@@ -685,7 +722,7 @@ function gentleAiRenderTransition(
|
|
|
685
722
|
return { directResult: false };
|
|
686
723
|
}
|
|
687
724
|
|
|
688
|
-
/** Rendering-only factory;
|
|
725
|
+
/** Rendering-only factory; the default export attaches the Bash renderers to native Bash through pi.registerToolRenderer, so execution and schema stay pi's. */
|
|
689
726
|
export function createQuietToolRenderer(
|
|
690
727
|
toolName: QuietToolName,
|
|
691
728
|
resolveOverride: GentleAiDevBinaryOverrideResolver = () => undefined,
|
|
@@ -702,7 +739,7 @@ export function createQuietToolRenderer(
|
|
|
702
739
|
renderShell: "self",
|
|
703
740
|
renderCall(args, theme, context) {
|
|
704
741
|
const callArgs = args as Record<string, unknown>;
|
|
705
|
-
const renderContext = sanitizedRenderContext(context
|
|
742
|
+
const renderContext = sanitizedRenderContext(context);
|
|
706
743
|
const operationPath = toolName === "bash"
|
|
707
744
|
? gentleAiRenderTransition(callArgs, renderContext, commandArguments()).operationPath
|
|
708
745
|
: undefined;
|
|
@@ -718,12 +755,15 @@ export function createQuietToolRenderer(
|
|
|
718
755
|
},
|
|
719
756
|
/** Builds the card component for this render pass; collapsed cards delegate to the wrapped-line cache keyed by the tool result object. */
|
|
720
757
|
renderResult(result, options, theme, context) {
|
|
721
|
-
const renderContext = context
|
|
758
|
+
const renderContext = context ? sanitizedRenderContext(context) : undefined;
|
|
722
759
|
markCardResult(renderContext?.state);
|
|
723
760
|
const cacheKey = typeof result === "object" && result !== null ? result : undefined;
|
|
724
761
|
const safeResult = sanitizedResult(result);
|
|
725
762
|
const text = safeText(extractTextContent(safeResult));
|
|
726
|
-
|
|
763
|
+
// Current Pi carries errors on the context. Older renderer callers
|
|
764
|
+
// supplied them on options; keep that structural fallback for those hosts.
|
|
765
|
+
const legacyError = "isError" in options && options.isError === true;
|
|
766
|
+
const isError = renderContext?.isError ?? legacyError;
|
|
727
767
|
const directResult = toolName === "bash" && gentleAiRenderTransition(
|
|
728
768
|
renderContext?.args,
|
|
729
769
|
renderContext,
|
|
@@ -734,7 +774,14 @@ export function createQuietToolRenderer(
|
|
|
734
774
|
return renderGentleAiResult(safeResult, { expanded: options.expanded, isPartial: options.isPartial, isError }, theme, renderContext ? withElapsedTiming(renderContext as GentleAiRenderContext) : undefined);
|
|
735
775
|
}
|
|
736
776
|
const resultTone = toolTone(options.isPartial === true, isError);
|
|
737
|
-
const carded = (component: () => Component): Component => new ToolCardBody(component, resultTone, theme);
|
|
777
|
+
const carded = (component: () => Component, cacheable = options.isPartial !== true): Component => new ToolCardBody(component, resultTone, theme, cacheable);
|
|
778
|
+
// Under the minimal Card content preference, every quiet tool draws
|
|
779
|
+
// the command alone while collapsed; the expand key still reveals
|
|
780
|
+
// the full result. Failures keep their bounded error tail so a red
|
|
781
|
+
// card always says why. The default preference keeps the previews.
|
|
782
|
+
if (COMMAND_ONLY_TOOLS.has(toolName) && !options.expanded && !isError && cardContent() === CARD_CONTENT.MINIMAL) {
|
|
783
|
+
return carded(() => new Text("", 0, 0));
|
|
784
|
+
}
|
|
738
785
|
if (options.isPartial) {
|
|
739
786
|
if (options.expanded) return carded(() => new Text(`${theme.fg("warning", partialLabel(toolName, text))}\n${theme.fg("muted", text)}`, 0, 0));
|
|
740
787
|
const visible = lastOutputLines(text, PREVIEW_LINE_LIMIT);
|
|
@@ -749,7 +796,7 @@ export function createQuietToolRenderer(
|
|
|
749
796
|
options,
|
|
750
797
|
theme,
|
|
751
798
|
sanitizedRenderContext(renderContext) as any,
|
|
752
|
-
));
|
|
799
|
+
), false);
|
|
753
800
|
}
|
|
754
801
|
let output = formatToolResultOutput(toolName, safeResult, {
|
|
755
802
|
expanded: options.expanded,
|
|
@@ -789,11 +836,24 @@ function registerQuietTool(pi: ExtensionAPI, toolName: RegisteredToolName, resol
|
|
|
789
836
|
});
|
|
790
837
|
}
|
|
791
838
|
|
|
792
|
-
export default function quietTools(
|
|
839
|
+
export default function quietTools(
|
|
840
|
+
pi: ExtensionAPI,
|
|
841
|
+
resolveOverride: GentleAiDevBinaryOverrideResolver = () => resolveGentleAiDevBinaryOverride(),
|
|
842
|
+
codemodeOptOut: Omit<BuiltinCodemodeOptOutOptions, "effectiveExtensions"> = {},
|
|
843
|
+
): ReturnType<ExtensionFactory> {
|
|
793
844
|
if (!quietToolsEnabled()) return;
|
|
794
845
|
let elapsedTiming: GentleAiElapsedTimingLedger | undefined;
|
|
846
|
+
let codemodeOptOutOffered = false;
|
|
795
847
|
pi.on("session_start", (_event, ctx) => {
|
|
796
848
|
elapsedTiming = new GentleAiElapsedTimingLedger(ctx.sessionManager, pi);
|
|
849
|
+
// The compact codemode below displaces Pi's builtin, which makes Pi warn
|
|
850
|
+
// at startup. Offer the settings opt-out once per process, detached so
|
|
851
|
+
// the dialog never holds up startup; the offer itself never throws.
|
|
852
|
+
if (codemodeOptOutOffered) return;
|
|
853
|
+
codemodeOptOutOffered = true;
|
|
854
|
+
let effectiveExtensions: unknown;
|
|
855
|
+
try { effectiveExtensions = pi.getSettings().extensions; } catch { /* Fall back to the settings file alone. */ }
|
|
856
|
+
void offerBuiltinCodemodeOptOut(ctx, { ...codemodeOptOut, effectiveExtensions });
|
|
797
857
|
});
|
|
798
858
|
// Only bash calls that render as gentle-ai cards carry a durable duration;
|
|
799
859
|
// every other quiet tool keeps its plain renderer and writes no entries.
|
|
@@ -812,5 +872,15 @@ export default function quietTools(pi: ExtensionAPI, resolveOverride: GentleAiDe
|
|
|
812
872
|
for (const toolName of Object.keys(TOOL_CREATORS) as RegisteredToolName[]) {
|
|
813
873
|
registerQuietTool(pi, toolName, resolveOverride, () => elapsedTiming);
|
|
814
874
|
}
|
|
875
|
+
// Native Bash keeps pi's execution and schema; only its drawing becomes a
|
|
876
|
+
// Gentle card while the Card content preference is minimal, so quiet bash
|
|
877
|
+
// rows match the other tools. The resolver API is newer than this
|
|
878
|
+
// package's pinned pi types, so it is probed structurally; hosts without it
|
|
879
|
+
// — and the default content preference — keep pi's native bash rendering.
|
|
880
|
+
const host = pi as typeof pi & { registerToolRenderer?: (resolver: (toolName: string, next: () => unknown) => unknown) => void };
|
|
881
|
+
if (typeof host.registerToolRenderer === "function") {
|
|
882
|
+
host.registerToolRenderer((toolName, next) =>
|
|
883
|
+
toolName === "bash" && cardContent() === CARD_CONTENT.MINIMAL ? createQuietToolRenderer("bash", resolveOverride, () => elapsedTiming) : next());
|
|
884
|
+
}
|
|
815
885
|
return registerCompactCodemode(pi);
|
|
816
886
|
}
|